first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
class Banner {
Banner({
this.id,
this.imageUrl,
this.status,
this.createdAt,
this.updatedAt,
});
Banner.fromJson(dynamic json) {
id = json['id'];
imageUrl = json['imageUrl'];
status = json['status'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
}
num? id;
String? imageUrl;
num? status;
String? createdAt;
String? updatedAt;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['imageUrl'] = imageUrl;
map['status'] = status;
map['created_at'] = createdAt;
map['updated_at'] = updatedAt;
return map;
}
}

View File

@@ -0,0 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../Model/banner_model.dart';
import '../Repo/banner_repo.dart';
BannerRepo imageRepo = BannerRepo();
final bannerProvider = FutureProvider<List<Banner>>((ref) => imageRepo.fetchAllIBanners());

View File

@@ -0,0 +1,26 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../../Const/api_config.dart';
import '../../../Repository/constant_functions.dart';
import '../../../http_client/customer_http_client_get.dart';
import '../Model/banner_model.dart';
class BannerRepo {
Future<List<Banner>> fetchAllIBanners() async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final uri = Uri.parse('${APIConfig.url}/banners');
final response = await clientGet.get(url: uri);
if (response.statusCode == 200) {
final parsedData = jsonDecode(response.body) as Map<String, dynamic>;
final partyList = parsedData['data'] as List<dynamic>;
return partyList.map((user) => Banner.fromJson(user)).toList();
// Parse into Party objects
} else {
throw Exception('Failed to fetch Users');
}
}
}

View File

@@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:mobile_pos/Screens/Settings/settings_screen.dart';
import 'package:mobile_pos/constant.dart';
import 'package:nb_utils/nb_utils.dart';
class BottomNav extends StatefulWidget {
const BottomNav({
Key? key,
}) : super(key: key);
@override
State<BottomNav> createState() => _BottomNavState();
}
class _BottomNavState extends State<BottomNav> {
int _selectedIndex = 0;
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
switch (_selectedIndex) {
case 0:
Navigator.pushNamed(context, '/home');
break;
case 1:
Navigator.pushNamed(context, '/order');
break;
case 2:
Navigator.pushNamed(context, '/featuredProduct');
break;
case 3:
const SettingScreen().launch(context);
break;
}
});
}
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
elevation: 6.0,
selectedItemColor: kMainColor,
// ignore: prefer_const_literals_to_create_immutables
items: [
const BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
const BottomNavigationBarItem(
icon: Icon(Icons.flare_sharp),
label: 'Maan',
),
const BottomNavigationBarItem(
icon: Icon(Icons.backpack),
label: 'Package',
),
const BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
],
currentIndex: _selectedIndex,
onTap: _onItemTapped,
);
}
}

View File

@@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
class GridItems {
final String title, icon, route;
GridItems({required this.title, required this.icon, required this.route});
}
List<GridItems> getFreeIcons({required BuildContext context, bool? brunchPermission, bool? hrmPermission}) {
List<GridItems> freeIcons = [
GridItems(
title: lang.S.of(context).sale,
icon: 'assets/sales.svg',
route: 'Sales',
),
GridItems(
title: lang.S.of(context).posSale,
icon: 'images/dash_pos.svg',
route: 'Pos Sale',
),
GridItems(
title: lang.S.of(context).parties,
icon: 'assets/parties.svg',
route: 'Parties',
),
GridItems(
title: lang.S.of(context).purchase,
icon: 'assets/purchase.svg',
route: 'Purchase',
),
GridItems(
title: lang.S.of(context).product,
icon: 'assets/products.svg',
route: 'Products',
),
GridItems(
title: lang.S.of(context).dueList,
icon: 'assets/duelist.svg',
route: 'Due List',
),
GridItems(
title: lang.S.of(context).stockList,
icon: 'assets/h_stock.svg',
route: 'Stock',
),
GridItems(
title: lang.S.of(context).reports,
icon: 'assets/reports.svg',
route: 'Reports',
),
GridItems(
title: lang.S.of(context).saleList,
icon: 'assets/salelist.svg',
route: 'Sales List',
),
GridItems(
title: lang.S.of(context).purchaseList,
icon: 'assets/purchaseLisst.svg',
route: 'Purchase List',
),
GridItems(
// TODO: Shakil change this to `Profit & Loss`
title: lang.S.of(context).profitAndLoss,
icon: 'assets/h_lossProfit.svg',
route: 'Loss/Profit',
),
GridItems(
title: lang.S.of(context).ledger,
icon: 'assets/ledger.svg',
route: 'ledger',
),
GridItems(
title: lang.S.of(context).income,
icon: 'assets/h_income.svg',
route: 'Income',
),
GridItems(
title: lang.S.of(context).expense,
icon: 'assets/expense.svg',
route: 'Expense',
),
GridItems(
title: lang.S.of(context).vatAndTax,
icon: 'assets/tax.svg',
route: 'tax',
),
// GridItems(
// title: 'Warehouse',
// icon: 'assets/tax.svg',
// route: 'warehouse',
// ),
GridItems(
title: lang.S.of(context).customPrint,
icon: 'assets/printer.svg',
route: 'customPrint',
),
if (brunchPermission == true)
GridItems(
title: lang.S.of(context).branch,
icon: 'assets/branch.svg',
route: 'branch',
),
if (hrmPermission ?? false)
GridItems(
title: lang.S.of(context).hrm,
icon: 'assets/hrm/hrm.svg',
route: 'hrm',
),
];
return freeIcons;
}

132
lib/Screens/Home/home.dart Normal file
View File

@@ -0,0 +1,132 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mobile_pos/Screens/DashBoard/dashboard.dart';
import 'package:mobile_pos/Screens/Home/home_screen.dart';
import 'package:mobile_pos/Screens/Report/reports.dart';
import 'package:mobile_pos/Screens/Settings/settings_screen.dart';
import 'package:mobile_pos/Screens/pos_sale/pos_sale.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:mobile_pos/model/business_info_model.dart' as visible;
import '../../GlobalComponents/glonal_popup.dart';
import '../../Provider/profile_provider.dart';
import '../../service/check_actions_when_no_branch.dart';
class Home extends StatefulWidget {
const Home({super.key});
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _tabIndex = 0;
late final PageController pageController = PageController(initialPage: _tabIndex);
@override
void dispose() {
pageController.dispose();
super.dispose();
}
void _handleNavigation(
int index,
BuildContext context,
) {
setState(() => _tabIndex = index);
pageController.jumpToPage(index);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async =>
await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(lang.S.of(context).areYouSure),
content: Text(lang.S.of(context).doYouWantToExitTheApp),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: Text(lang.S.of(context).no)),
TextButton(onPressed: () => Navigator.pop(context, true), child: Text(lang.S.of(context).yes)),
],
),
) ??
false,
child: Consumer(builder: (context, ref, __) {
ref.watch(getExpireDateProvider(ref));
return GlobalPopup(
child: Scaffold(
body: PageView(
controller: pageController,
physics: const NeverScrollableScrollPhysics(),
onPageChanged: (v) => setState(() => _tabIndex = v),
children: [
HomeScreen(),
PosSaleScreen(),
DashboardScreen(),
Reports(),
SettingScreen(),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _tabIndex,
backgroundColor: Colors.white,
// onTap: (i) => _handleNavigation(i, context, visibility),
onTap: (i) => _handleNavigation(
i,
context,
),
items: [
_buildNavItem(index: 0, activeIcon: 'cHome', icon: 'home', label: lang.S.of(context).home),
_buildNavItem(
index: 1,
activeIcon: 'cPos',
icon: 'pos',
label: lang.S.of(context).pos,
),
_buildNavItem(
index: 2,
activeIcon: 'dashbord1',
icon: 'dashbord',
label: lang.S.of(context).dashboard,
),
_buildNavItem(
index: 3,
activeIcon: 'cFile',
icon: 'file',
label: lang.S.of(context).reports,
),
_buildNavItem(
index: 4,
activeIcon: 'cSetting',
icon: 'setting',
label: lang.S.of(context).setting,
),
],
type: BottomNavigationBarType.fixed,
selectedItemColor: kMainColor,
unselectedItemColor: kGreyTextColor,
selectedLabelStyle: const TextStyle(fontSize: 14),
unselectedLabelStyle: const TextStyle(fontSize: 14),
),
),
);
}),
);
}
BottomNavigationBarItem _buildNavItem(
{required int index, required String activeIcon, required String icon, required String label}) {
return BottomNavigationBarItem(
icon: _tabIndex == index
? SvgPicture.asset('assets/$activeIcon.svg', height: 28, width: 28, fit: BoxFit.scaleDown)
: SvgPicture.asset('assets/$icon.svg',
colorFilter: const ColorFilter.mode(kGreyTextColor, BlendMode.srcIn), height: 24, width: 24),
label: label,
);
}
}

View File

@@ -0,0 +1,500 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mobile_pos/Const/api_config.dart';
import 'package:mobile_pos/Repository/check_addon_providers.dart';
import 'package:mobile_pos/Screens/DashBoard/dashboard.dart';
import 'package:mobile_pos/Screens/Home/components/grid_items.dart';
import 'package:mobile_pos/Screens/Profile%20Screen/profile_details.dart';
import 'package:mobile_pos/core/theme/_app_colors.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:nb_utils/nb_utils.dart';
import 'package:restart_app/restart_app.dart';
import '../../Provider/profile_provider.dart';
import '../../constant.dart';
import '../../currency.dart';
import '../../service/check_actions_when_no_branch.dart';
import '../Customers/Provider/customer_provider.dart';
import '../DashBoard/global_container.dart';
import '../Home/Model/banner_model.dart' as b;
import '../../service/check_user_role_permission_provider.dart';
import '../branch/branch_list.dart';
import '../branch/repo/branch_repo.dart';
import '../subscription/package_screen.dart';
import 'Provider/banner_provider.dart';
class HomeScreen extends ConsumerStatefulWidget {
const HomeScreen({super.key});
@override
ConsumerState<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends ConsumerState<HomeScreen> {
PageController pageController = PageController(initialPage: 0, viewportFraction: 0.8);
bool _isRefreshing = false;
Future<void> refreshAllProviders({required WidgetRef ref}) async {
if (_isRefreshing) return; // Prevent multiple refresh calls
_isRefreshing = true;
try {
ref.refresh(summaryInfoProvider);
ref.refresh(bannerProvider);
ref.refresh(businessInfoProvider);
ref.refresh(partiesProvider);
ref.refresh(getExpireDateProvider(ref));
await Future.delayed(const Duration(seconds: 3));
} finally {
_isRefreshing = false;
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Consumer(builder: (_, ref, __) {
final businessInfo = ref.watch(businessInfoProvider);
final summaryInfo = ref.watch(summaryInfoProvider);
final banner = ref.watch(bannerProvider);
final permissionService = PermissionService(ref);
return businessInfo.when(data: (details) {
final icons = getFreeIcons(
context: context,
hrmPermission: (details.data?.addons?.hrmAddon == true),
brunchPermission: (((details.data?.addons?.multiBranchAddon == true) &&
(details.data?.enrolledPlan?.allowMultibranch == 1) &&
(details.data?.user?.branchId == null)))
? true
: false);
return Scaffold(
backgroundColor: kBackgroundColor,
appBar: AppBar(
backgroundColor: kWhite,
titleSpacing: 5,
surfaceTintColor: kWhite,
actions: [
if ((details.data?.addons?.multiBranchAddon ?? false) && (details.data?.user?.activeBranch != null))
TextButton.icon(
label: Text(
'${details.data?.user?.activeBranch?.name}',
style: theme.textTheme.bodyMedium?.copyWith(color: kTitleColor),
),
style: ButtonStyle(
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(
borderRadius: BorderRadiusGeometry.circular(2),
),
),
textStyle: WidgetStatePropertyAll(
theme.textTheme.bodyMedium?.copyWith(color: kTitleColor),
),
),
onPressed: () async {
if (details.data?.user?.branchId != null) {
return;
}
bool switchBranch = await BranchListScreen.switchDialog(context: context, isLogin: false);
if (switchBranch) {
EasyLoading.show();
final switched =
await BranchRepo().exitBranch(id: details.data?.user?.activeBranchId.toString() ?? '');
if (switched) {
Restart.restartApp();
}
EasyLoading.dismiss();
}
},
icon: SvgPicture.asset(
'assets/branch_icon.svg',
height: 16,
width: 16,
),
),
IconButton(onPressed: () async => refreshAllProviders(ref: ref), icon: const Icon(Icons.refresh))
],
leading: Padding(
padding: const EdgeInsets.all(10),
child: GestureDetector(
onTap: () {
const ProfileDetails().launch(context);
},
child: Container(
height: 50,
width: 50,
decoration: details.data?.pictureUrl == null
? BoxDecoration(
image:
const DecorationImage(image: AssetImage('images/no_shop_image.png'), fit: BoxFit.cover),
borderRadius: BorderRadius.circular(50),
)
: BoxDecoration(
image: DecorationImage(
image: NetworkImage('${APIConfig.domain}${details.data?.pictureUrl}'),
fit: BoxFit.cover),
borderRadius: BorderRadius.circular(50),
),
),
),
),
title: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
details.data?.user?.role == 'staff'
? '${details.data?.companyName ?? ''} [${details.data?.user?.name ?? ''}]'
: details.data?.companyName ?? '',
style: theme.textTheme.titleLarge?.copyWith(
fontSize: 18.0,
fontWeight: FontWeight.w500,
),
),
GestureDetector(
// onTap: () {
// showDialog(
// context: context,
// builder: (BuildContext context) {
// return goToPackagePagePopup(
// context: context,
// enrolledPlan: details.enrolledPlan);
// });
// },
child: Text.rich(
TextSpan(
text: '${details.data?.enrolledPlan?.plan?.subscriptionName ?? 'No Active'} Plan',
children: [
// if (details.enrolledPlan?.duration != null &&
// details.enrolledPlan!.duration! <= 7)
// TextSpan(
// text: ' (${getDayLeftInExpiring(
// expireDate: details.willExpire,
// shortMSG: false,
// )})',
// style: theme.textTheme.bodySmall?.copyWith(
// fontSize: 13,
// color: kPeraColor,
// ),
// ),
],
),
style: theme.textTheme.bodySmall?.copyWith(
fontSize: 13,
color: kPeraColor,
fontWeight: FontWeight.w500,
)),
)
],
),
),
resizeToAvoidBottomInset: true,
body: RefreshIndicator.adaptive(
onRefresh: () async => refreshAllProviders(ref: ref),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (permissionService.hasPermission(Permit.dashboardRead.value)) ...{
summaryInfo.when(data: (summary) {
return Container(
padding: EdgeInsets.fromLTRB(16, 16, 16, 12),
decoration: BoxDecoration(
color: kMainColor,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
lang.S.of(context).quickOver,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
color: kWhite,
),
),
),
GestureDetector(
onTap: () => Navigator.push(
context, MaterialPageRoute(builder: (context) => DashboardScreen())),
child: Text(
lang.S.of(context).viewAll,
style: theme.textTheme.bodySmall?.copyWith(color: kWhite, fontSize: 16),
),
)
],
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Flexible(
child: GlobalContainer(
minVerticalPadding: 0,
minTileHeight: 0,
titlePadding: EdgeInsets.zero,
// isShadow: true,
textColor: true,
title: lang.S.of(context).sales,
subtitle: '$currency${formatAmount(summary.data!.sales.toString())}',
),
),
Flexible(
child: GlobalContainer(
minVerticalPadding: 0,
minTileHeight: 0,
// isShadow: true,
textColor: true,
alainRight: true,
titlePadding: EdgeInsets.zero,
title: lang.S.of(context).purchased,
subtitle: '$currency${formatAmount(summary.data!.purchase.toString())}',
),
),
],
),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Flexible(
child: GlobalContainer(
minVerticalPadding: 0,
textColor: true,
minTileHeight: 0,
titlePadding: EdgeInsets.zero,
title: lang.S.of(context).income,
subtitle: '$currency${formatAmount(summary.data!.income.toString())}',
),
),
Flexible(
child: GlobalContainer(
minVerticalPadding: 0,
minTileHeight: 0,
textColor: true,
alainRight: true,
titlePadding: EdgeInsets.zero,
title: lang.S.of(context).expense,
subtitle: '$currency${formatAmount(summary.data!.expense.toString())}',
),
),
],
),
],
),
);
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return Center(
child: CircularProgressIndicator(),
);
}),
SizedBox(height: 16),
},
GridView.count(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
childAspectRatio: 3.0,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
crossAxisCount: 2,
children: List.generate(
icons.length,
(index) => HomeGridCards(
gridItems: icons[index],
),
),
),
const SizedBox(height: 20),
///________________Banner_______________________________________
banner.when(data: (imageData) {
List<b.Banner> images = [];
if (imageData.isNotEmpty) {
images.addAll(imageData.where(
(element) => element.status == 1,
));
}
if (images.isNotEmpty) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
lang.S.of(context).whatNew,
textAlign: TextAlign.start,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 12),
Container(
height: 150,
width: MediaQuery.of(context).size.width,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.zero,
itemCount: images.length,
itemBuilder: (_, index) {
return GestureDetector(
onTap: () {
const PackageScreen().launch(context);
},
child: Padding(
padding: EdgeInsetsDirectional.only(end: 10), // Spacing between items
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Image.network(
"${APIConfig.domain}${images[index].imageUrl}",
width: MediaQuery.of(context).size.width * 0.7, // 80% width
fit: BoxFit.cover,
),
),
),
);
},
),
),
const SizedBox(height: 12),
],
);
} else {
return Center(
child: Container(
height: 150,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage('images/banner1.png'),
),
),
),
);
}
}, error: (e, stack) {
return Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Center(
child: Text(
lang.S.of(context).noDataFound,
style: theme.textTheme.titleMedium,
//'No Data Found'
),
),
);
}, loading: () {
return const CircularProgressIndicator();
}),
],
),
),
),
));
}, error: (e, stack) {
return Center(child: Text(e.toString()));
}, loading: () {
return const Center(child: CircularProgressIndicator());
});
});
}
}
class HomeGridCards extends StatefulWidget {
const HomeGridCards({
super.key,
required this.gridItems,
// this.visibility,
});
final GridItems gridItems;
// final business.Visibility? visibility;
@override
State<HomeGridCards> createState() => _HomeGridCardsState();
}
class _HomeGridCardsState extends State<HomeGridCards> {
@override
Widget build(BuildContext context) {
return Consumer(builder: (context, ref, __) {
return GestureDetector(
onTap: () async {
bool result = await checkActionWhenNoBranch(context: context, actionName: widget.gridItems.title, ref: ref);
if (!result) {
return;
}
Navigator.of(context).pushNamed('/${widget.gridItems.route}');
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8), color: kWhite, boxShadow: [
BoxShadow(
color: const Color(0xff171717).withOpacity(0.07),
offset: const Offset(0, 3),
blurRadius: 50,
spreadRadius: -4)
]),
child: Row(
children: [
SvgPicture.asset(
widget.gridItems.icon.toString(),
height: 40,
width: 40,
),
const SizedBox(
width: 8,
),
Flexible(
child: Text(
widget.gridItems.title.toString(),
style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: DAppColors.kNeutral700),
overflow: TextOverflow.ellipsis,
maxLines: 1,
))
],
),
),
);
});
}
}
String getSubscriptionExpiring({required String? expireDate, required bool shortMSG}) {
if (expireDate == null) {
return shortMSG ? 'N/A' : lang.S.current.subscribeNow;
}
DateTime expiringDay = DateTime.parse(expireDate).add(const Duration(days: 1));
if (expiringDay.isBefore(DateTime.now())) {
return lang.S.current.expired;
}
if (expiringDay.difference(DateTime.now()).inDays < 1) {
return shortMSG
? '${expiringDay.difference(DateTime.now()).inHours}\n${lang.S.current.hoursLeft}'
: '${expiringDay.difference(DateTime.now()).inHours} ${lang.S.current.hoursLeft}';
} else {
return shortMSG
? '${expiringDay.difference(DateTime.now()).inDays}\n${lang.S.current.daysLeft}'
: '${expiringDay.difference(DateTime.now()).inDays} ${lang.S.current.daysLeft}';
}
}