diff --git a/lib/config/provider_routes.dart b/lib/config/provider_routes.dart index d660b4e..8b1b249 100644 --- a/lib/config/provider_routes.dart +++ b/lib/config/provider_routes.dart @@ -23,6 +23,7 @@ import 'package:flutter/material.dart'; import 'package:mc_common_app/models/appointments_models/appointment_list_model.dart'; import 'package:mc_common_app/models/provider_branches_models/branch_detail_model.dart'; import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/views/advertisement/favourite_ads_view.dart'; import 'package:mc_common_app/views/appointments/appointments_filter_view.dart'; import 'package:mc_common_app/views/setting_options/provider_accepted_requests_view.dart'; @@ -69,5 +70,8 @@ class ProviderAppRoutes { //Requests AppRoutes.providerAcceptedRequestsView: (context) => const ProviderAcceptedRequestsView(), + + //Favourite Ads + AppRoutes.favouriteAdsView: (context) => const FavouriteAdsView(), }; } diff --git a/lib/views/branch_management/branch/branch_list_page.dart b/lib/views/branch_management/branch/branch_list_page.dart index 61a4cac..fa439f3 100644 --- a/lib/views/branch_management/branch/branch_list_page.dart +++ b/lib/views/branch_management/branch/branch_list_page.dart @@ -165,7 +165,9 @@ class BranchListPage extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - (branchModel.branchName ?? "").toText(fontSize: 16, height: 18 / 16), + Expanded( + child: (branchModel.branchName ?? "").toText(fontSize: 16, height: 18 / 16), + ) ], ).withArrow(isArrowEnabled: branchModel.branchStatus != BranchStatusEnum.blocked).margin(right: 10), (branchModel.branchDescription ?? "").toText( diff --git a/lib/views/branch_management/branch/dealer/widget/assign_dealer_user_sheet.dart b/lib/views/branch_management/branch/dealer/widget/assign_dealer_user_sheet.dart index 07fa4be..1397cf9 100644 --- a/lib/views/branch_management/branch/dealer/widget/assign_dealer_user_sheet.dart +++ b/lib/views/branch_management/branch/dealer/widget/assign_dealer_user_sheet.dart @@ -8,6 +8,7 @@ import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart'; import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/utils/utils.dart'; @@ -29,7 +30,10 @@ class AssignDealerUserSheet extends StatefulWidget { } class _AssignDealerUserSheetState extends State { - late ServiceVM provider; + ServiceVM? provider; + + // Track initially assigned users to detect removals + Set initiallyAssignedUserIds = {}; @override void initState() { @@ -40,11 +44,163 @@ class _AssignDealerUserSheetState extends State { }); } - getBranchUsers() { - provider.getAllProviderDealers({ + getBranchUsers() async { + if (provider == null) return; + + await provider!.getAllProviderDealers({ "ServiceProviderBranchID": widget.branchId, "ServiceProviderID": AppState().getUser.data?.userInfo?.providerId.toString() ?? "", }); + + // Store initially assigned user IDs + initiallyAssignedUserIds.clear(); + for (var user in provider!.allProviderDealersList) { + if (user.isBranchUser ?? false) { + initiallyAssignedUserIds.add(user.id); + } + } + setState(() {}); + } + + // Handle checkbox changes + Future onUserSelectionChanged(int userId, bool? newValue) async { + if (provider == null) return; + + final user = provider!.allProviderDealersList.firstWhere((u) => u.id == userId); + final wasInitiallyAssigned = initiallyAssignedUserIds.contains(userId); + + if (newValue == true && !wasInitiallyAssigned) { + // User is checking a new dealer - show assign confirmation + showAssignConfirmation(userId); + } else if (newValue == false && wasInitiallyAssigned) { + // User is unchecking an assigned dealer - show remove confirmation + showRemoveConfirmation(userId); + } else { + // Just toggle for non-initially-assigned users + user.isBranchUser = newValue ?? false; + provider!.notifyListeners(); + } + } + + // Show confirmation bottom sheet for assigning a dealer + void showAssignConfirmation(int userId) { + actionConfirmationBottomSheet( + context: context, + title: LocaleKeys.assignDealerUser.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), + subtitle: LocaleKeys.confirmAssignDealerUser.tr(), + actionButtonYes: Expanded( + child: ShowFillButton( + maxHeight: 55, + title: LocaleKeys.yes.tr(), + fontSize: 15, + onPressed: () async { + Navigator.pop(context); + await assignUser(userId); + }, + ), + ), + actionButtonNo: Expanded( + child: ShowFillButton( + maxHeight: 55, + isFilled: false, + borderColor: MyColors.darkPrimaryColor, + title: LocaleKeys.no.tr(), + txtColor: MyColors.darkPrimaryColor, + fontSize: 15, + onPressed: () { + Navigator.pop(context); + }, + ), + ), + ); + } + + // Show confirmation bottom sheet for removing a dealer + void showRemoveConfirmation(int userId) { + actionConfirmationBottomSheet( + context: context, + title: LocaleKeys.removeDealerUser.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), + subtitle: LocaleKeys.confirmRemoveDealerUser.tr(), + actionButtonYes: Expanded( + child: ShowFillButton( + maxHeight: 55, + title: LocaleKeys.yes.tr(), + fontSize: 15, + backgroundColor: Colors.red, + onPressed: () async { + Navigator.pop(context); + await removeUser(userId); + }, + ), + ), + actionButtonNo: Expanded( + child: ShowFillButton( + maxHeight: 55, + isFilled: false, + borderColor: MyColors.darkPrimaryColor, + title: LocaleKeys.no.tr(), + txtColor: MyColors.darkPrimaryColor, + fontSize: 15, + onPressed: () { + Navigator.pop(context); + }, + ), + ), + ); + } + + // Assign a single user + Future assignUser(int userId) async { + if (provider == null) return; + + Utils.showLoading(context); + + var map = { + "dealershipUserIDs": [userId], + "serviceProviderBranchID": widget.branchId, + }; + + GenericRespModel response = await provider!.assignDealerToBranch(map); + Utils.hideLoading(context); + + if (response.messageStatus == 1) { + Utils.showToast(response.message.toString()); + await getBranchUsers(); // Refresh the list + widget.callBackFunc(); + } else { + Utils.showToast(response.message.toString()); + // Revert checkbox on error + final user = provider!.allProviderDealersList.firstWhere((u) => u.id == userId); + user.isBranchUser = false; + provider!.notifyListeners(); + } + } + + // Remove a single user + Future removeUser(int userId) async { + if (provider == null) return; + + Utils.showLoading(context); + + var map = { + "dealershipUserIDs": [userId], + "serviceProviderBranchID": widget.branchId, + }; + + GenericRespModel response = await provider!.removeDealerFromBranch(map); + Utils.hideLoading(context); + + if (response.messageStatus == 1) { + Utils.showToast(response.message.toString()); + await getBranchUsers(); // Refresh the list + widget.callBackFunc(); + } else { + Utils.showToast(response.message.toString()); + // Revert checkbox on error + final user = provider!.allProviderDealersList.firstWhere((u) => u.id == userId); + user.isBranchUser = true; + provider!.notifyListeners(); + } } @override @@ -82,55 +238,20 @@ class _AssignDealerUserSheetState extends State { height: 1, color: Colors.grey[100], ), - Column( - children: [ - ShowFillButton( - title: LocaleKeys.assignDealerUser.tr(), - maxWidth: double.infinity, - maxHeight: 55, - fontSize: 15, - onPressed: () async { - if (validation()) { - Utils.showLoading(context); - List userIds = []; - for (var element in provider.allProviderDealersList) { - if (element.isBranchUser ?? false) { - userIds.add(element.id); - } - } - var map = { - "dealershipUserIDs": userIds, - "serviceProviderBranchID": widget.branchId, - }; - GenericRespModel response = await provider.assignDealerToBranch(map); - Utils.hideLoading(context); - if (response.messageStatus == 1) { - Utils.showToast(response.message.toString()); - pop(context); - widget.callBackFunc(); - } else { - Utils.showToast(response.message.toString()); - } - } - }, - ), - 12.height, - ShowFillButton( - title: ("+ ${LocaleKeys.createNewUser.tr()}"), - maxWidth: double.infinity, - maxHeight: 55, - fontSize: 15, - backgroundColor: Colors.transparent, - borderColor: MyColors.darkPrimaryColor, - txtColor: MyColors.darkPrimaryColor, - isFilled: false, - onPressed: () { - context.read().checkUserSubscription(SubscriptionActionTypeEnum.users, context, callback: () { - getBranchUsers(); - }); - }, - ) - ], + ShowFillButton( + title: ("+ ${LocaleKeys.createNewUser.tr()}"), + maxWidth: double.infinity, + maxHeight: 55, + fontSize: 15, + backgroundColor: Colors.transparent, + borderColor: MyColors.darkPrimaryColor, + txtColor: MyColors.darkPrimaryColor, + isFilled: false, + onPressed: () { + context.read().checkUserSubscription(SubscriptionActionTypeEnum.users, context, callback: () { + getBranchUsers(); + }); + }, ) ], ), @@ -145,11 +266,7 @@ class _AssignDealerUserSheetState extends State { Checkbox( value: model.allProviderDealersList[index].isBranchUser, onChanged: (v) { - for (var element in model.allProviderDealersList) { - element.isBranchUser = false; - } - model.allProviderDealersList[index].isBranchUser = v ?? false; - model.notifyListeners(); + onUserSelectionChanged(model.allProviderDealersList[index].id, v); }, visualDensity: VisualDensity.comfortable, ), @@ -185,14 +302,5 @@ class _AssignDealerUserSheetState extends State { ], ); } - - bool validation() { - bool isValid = false; - for (var element in provider.allProviderDealersList) { - if (element.isBranchUser ?? false) { - isValid = true; - } - } - return isValid; - } } + diff --git a/lib/views/dashboard/fragments/home_fragment.dart b/lib/views/dashboard/fragments/home_fragment.dart index 7a9b35d..e36fb69 100644 --- a/lib/views/dashboard/fragments/home_fragment.dart +++ b/lib/views/dashboard/fragments/home_fragment.dart @@ -8,12 +8,14 @@ import 'package:mc_common_app/models/appointments_models/appointment_list_model. import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart'; +import 'package:mc_common_app/view_models/appointments_view_model.dart'; import 'package:mc_common_app/view_models/dashboard_view_model_provider.dart'; import 'package:mc_common_app/views/advertisement/components/ads_list_widget.dart'; import 'package:mc_common_app/views/appointments/widgets/common_appointment_slider_widget.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/common_widgets/view_all_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:mc_common_app/widgets/shimmer/home_shimmer_widgets.dart'; import 'package:provider/provider.dart'; class HomeFragment extends StatelessWidget { @@ -59,15 +61,25 @@ class HomeFragment extends StatelessWidget { context.read().onNavbarTapped(1); }, ).horPaddingMain(), - CommonAppointmentSliderWidget( - onAppointmentClick: (AppointmentListModel value) => navigateWithName( - context, - AppRoutes.appointmentDetailList, - arguments: value, - )).toWhiteContainer(width: double.infinity, backgroundColor: Colors.transparent).margin( + Consumer( + builder: (BuildContext context, AppointmentsVM appointmentVM, Widget? child) { + // Show shimmer when loading + if (appointmentVM.state == ViewState.busy) { + return const AppointmentSliderShimmer(); + } + // Show actual data when idle + return CommonAppointmentSliderWidget( + onAppointmentClick: (AppointmentListModel value) => navigateWithName( + context, + AppRoutes.appointmentDetailList, + arguments: value, + ), + ).toWhiteContainer(width: double.infinity, backgroundColor: Colors.transparent).margin( left: 21, right: 21, - ), + ); + }, + ), 24.height, ViewAllWidget( title: LocaleKeys.myBranches.tr(), @@ -83,65 +95,76 @@ class HomeFragment extends StatelessWidget { ), Consumer( builder: (BuildContext context, AdVM adVM, Widget? child) { + // Show shimmer when loading if (adVM.state == ViewState.busy) { - return const Padding( - padding: EdgeInsets.all(30), - child: Center(child: CircularProgressIndicator()), - ); - } else { - return Column( - children: [ - if (adVM.myActiveAdsForHome.isNotEmpty) ...[ - Column( - children: [ - 15.height, - ViewAllWidget( - title: LocaleKeys.my_active_Ads.tr().toUpperCase(), - subTitle: LocaleKeys.view_all.tr(), - onSubtitleTapped: () { - context.read().onNavbarTapped(3); - context.read().updateIsExploreAds(true); - context.read().applyFilterOnExploreAds(vehicleBrandId: 0); // allAds - }, - ).horPaddingMain(), - AdsListWidget( - isDraftAds: false, - shouldShowAdStatus: true, - isAdsFragment: false, - adsList: adVM.myActiveAdsForHome, - scrollPhysics: const NeverScrollableScrollPhysics(), - hasMoreData: adVM.isExploreAdsTapped ? adVM.hasMoreDataForExploreAds : adVM.hasMoreDataForMyAds, - ), - ], - ) + return Padding( + padding: const EdgeInsets.only(top: 15), + child: Column( + children: [ + ViewAllWidget( + title: LocaleKeys.my_active_Ads.tr().toUpperCase(), + subTitle: LocaleKeys.view_all.tr(), + onSubtitleTapped: () {}, + ).horPaddingMain(), + 14.height, + const AdsListShimmer(itemCount: 2), ], - if (adVM.exploreAds.isNotEmpty) ...[ - Column( - children: [ - 15.height, - ViewAllWidget( - title: LocaleKeys.myRecommendedAds.tr().toUpperCase(), - subTitle: LocaleKeys.view_all.tr(), - onSubtitleTapped: () { - context.read().onNavbarTapped(3); - context.read().updateIsExploreAds(true); - context.read().applyFilterOnExploreAds(vehicleBrandId: 0); // allAds - }, - ).horPaddingMain(), - AdsListWidget( - isDraftAds: false, - shouldShowAdStatus: false, - adsList: adVM.exploreAds.length >= 3 ? adVM.exploreAds.take(3).toList() : adVM.exploreAds, - isAdsFragment: false, - scrollPhysics: const NeverScrollableScrollPhysics(), - hasMoreData: adVM.isExploreAdsTapped ? adVM.hasMoreDataForExploreAds : adVM.hasMoreDataForMyAds, - ), - ], - ) - ] - ], + ), ); } + // Show actual data when idle + return Column( + children: [ + if (adVM.myActiveAdsForHome.isNotEmpty) ...[ + Column( + children: [ + 15.height, + ViewAllWidget( + title: LocaleKeys.my_active_Ads.tr().toUpperCase(), + subTitle: LocaleKeys.view_all.tr(), + onSubtitleTapped: () { + context.read().onNavbarTapped(3); + context.read().updateIsExploreAds(true); + context.read().applyFilterOnExploreAds(vehicleBrandId: 0); // allAds + }, + ).horPaddingMain(), + AdsListWidget( + isDraftAds: false, + shouldShowAdStatus: true, + isAdsFragment: false, + adsList: adVM.myActiveAdsForHome, + scrollPhysics: const NeverScrollableScrollPhysics(), + hasMoreData: adVM.isExploreAdsTapped ? adVM.hasMoreDataForExploreAds : adVM.hasMoreDataForMyAds, + ), + ], + ) + ], + if (adVM.exploreAds.isNotEmpty) ...[ + Column( + children: [ + 15.height, + ViewAllWidget( + title: LocaleKeys.myRecommendedAds.tr().toUpperCase(), + subTitle: LocaleKeys.view_all.tr(), + onSubtitleTapped: () { + context.read().onNavbarTapped(3); + context.read().updateIsExploreAds(true); + context.read().applyFilterOnExploreAds(vehicleBrandId: 0); // allAds + }, + ).horPaddingMain(), + AdsListWidget( + isDraftAds: false, + shouldShowAdStatus: false, + adsList: adVM.exploreAds.length >= 3 ? adVM.exploreAds.take(3).toList() : adVM.exploreAds, + isAdsFragment: false, + scrollPhysics: const NeverScrollableScrollPhysics(), + hasMoreData: adVM.isExploreAdsTapped ? adVM.hasMoreDataForExploreAds : adVM.hasMoreDataForMyAds, + ), + ], + ) + ] + ], + ); }, ).toContainer(backgroundColor: const Color(0xFFF8F8F8), padding: EdgeInsets.zero, margin: EdgeInsets.zero), 21.height, diff --git a/lib/views/subscriptions/all_subscriptions_page.dart b/lib/views/subscriptions/all_subscriptions_page.dart index 38cef93..b857f96 100644 --- a/lib/views/subscriptions/all_subscriptions_page.dart +++ b/lib/views/subscriptions/all_subscriptions_page.dart @@ -68,7 +68,7 @@ class _SubscriptionsPageState extends State { }, ), 21.height, - ListView.separated( + ListView.separated( itemBuilder: (BuildContext context, int index) { return AllSubscriptionsCard( subcriptionsVM.tempSubscriptions[index], diff --git a/lib/views/subscriptions/widget/my_subscriptions_card.dart b/lib/views/subscriptions/widget/my_subscriptions_card.dart index e88a4f1..fb2a74f 100644 --- a/lib/views/subscriptions/widget/my_subscriptions_card.dart +++ b/lib/views/subscriptions/widget/my_subscriptions_card.dart @@ -18,7 +18,8 @@ class MySubscriptionsCard extends StatelessWidget { Function onUpgradeSubscriptionClick; bool isMainPage; - MySubscriptionsCard(this.subscriptionVM, {super.key, this.isSubscribed = false, this.backgroundColor, required this.onRenewSubscriptionClick, required this.onUpgradeSubscriptionClick, this.isMainPage = true}); + MySubscriptionsCard(this.subscriptionVM, + {super.key, this.isSubscribed = false, this.backgroundColor, required this.onRenewSubscriptionClick, required this.onUpgradeSubscriptionClick, this.isMainPage = true}); @override Widget build(BuildContext context) { @@ -101,13 +102,15 @@ class MySubscriptionsCard extends StatelessWidget { ), Row( children: [ - "${subscriptionVM.isExpired! ? LocaleKeys.expired.tr() : LocaleKeys.expiresOn.tr()} ${DateFormat('d MMMM yyyy', context.locale.toString()).format(DateHelper.parseStringToDate(DateHelper.formatDateT(subscriptionVM.dateEnd ?? "")))}".toText(fontSize: 14, letterSpacing: -0.56, color: textColor, fontWeight: MyFonts.Medium), + "${subscriptionVM.isExpired! ? LocaleKeys.expired.tr() : LocaleKeys.expiresOn.tr()} ${DateFormat('d MMMM yyyy', context.locale.toString()).format(DateHelper.parseStringToDate(DateHelper.formatDateT(subscriptionVM.dateEnd ?? "")))}" + .toText(fontSize: 14, letterSpacing: -0.56, color: textColor, fontWeight: MyFonts.Medium), // "${subscriptionVM.isExpired! ? LocaleKeys.expired.tr() : LocaleKeys.expiresOn.tr()} ${DateHelper.formatAsDayMonthYear(DateHelper.parseStringToDate(DateHelper.formatDateT(subscriptionVM.dateEnd ?? "")))}".toText(fontSize: 14, letterSpacing: -0.56, color: textColor, fontWeight: MyFonts.Medium), if (subscriptionVM.isRenewable!) Row( children: [ 6.width, - LocaleKeys.renew.tr().toText(color: MyColors.primaryColor, fontSize: 14, letterSpacing: -0.56, textDecoration: TextDecoration.underline, decorationColor: MyColors.primaryColor, fontWeight: MyFonts.Medium), + LocaleKeys.renew.tr().toText( + color: MyColors.primaryColor, fontSize: 14, letterSpacing: -0.56, textDecoration: TextDecoration.underline, decorationColor: MyColors.primaryColor, fontWeight: MyFonts.Medium), ], ).onPress(() { onRenewSubscriptionClick(); diff --git a/pubspec.yaml b/pubspec.yaml index 360c3a9..c9b96cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,8 @@ flutter: # - packages/mc_common_app/assets/images/ + + # fonts: # - family: Poppins # fonts: