import 'dart:developer'; import 'dart:io'; import 'package:flutter/widgets.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/config/dependency_injection.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/main.dart'; import 'package:mc_common_app/models/user_models/image_response.dart'; import 'package:mc_common_app/repositories/user_repo.dart'; import 'package:mc_common_app/services/common_services.dart'; import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/view_models/shipping_management_view_model.dart'; import 'package:mc_common_app/widgets/common_widgets/add_phone_num_wiget.dart'; import 'package:provider/provider.dart'; import 'package:mc_common_app/utils/utils.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/base_view_model.dart'; import 'package:mc_common_app/widgets/bottom_sheet.dart'; import 'package:mc_common_app/view_models/chat_view_model.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; import 'package:mc_common_app/view_models/service_view_model.dart'; import 'package:mc_common_app/view_models/subscriptions_view_model.dart'; class DashboardVMProvider extends BaseVM with WidgetsBindingObserver { final CommonAppServices commonServices; final UserRepo userRepo; BuildContext? _context; // Store context for lifecycle events DashboardVMProvider({required this.commonServices, required this.userRepo}) { // Register as lifecycle observer WidgetsBinding.instance.addObserver(this); } // Store context when dashboard is initialized void setContext(BuildContext context) { _context = context; } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _context = null; super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); logger.i("App lifecycle state changed to: $state"); if (_context == null) return; final chatVM = Provider.of(_context!, listen: false); switch (state) { case AppLifecycleState.resumed: // App came back to foreground - ensure SignalR is connected logger.i("App resumed - checking SignalR connection"); chatVM.buildHubConnection(context: _context); break; case AppLifecycleState.paused: // App went to background - keep connection alive (as per user requirement) logger.i("App paused - SignalR staying connected"); break; case AppLifecycleState.inactive: case AppLifecycleState.detached: case AppLifecycleState.hidden: // Other states - no action needed break; } } String pickedImage = ""; int selectedNavbarBarIndex = 2; void onNavbarTapped(int index) async { log("token: ${AppState().getDeviceToken.toString()}"); // Block access to Requests tab (index 4) for Individual Providers // Only Dealerships and Dealership Users can access Special Requests if (index == 4 && AppState().userType != UserType.providerDealer && AppState().userType != UserType.dealershipUser) { logger.w("Access denied: Individual providers cannot access Special Requests"); return; } selectedNavbarBarIndex = index; notifyListeners(); } void pickImageFromPhone(BuildContext context, int sourceFlag) async { final File? pickedImageFile = await commonServices.pickImageFromPhone(sourceFlag); if (pickedImageFile == null) { return; } int sizeInBytes = pickedImageFile.lengthSync(); if (sizeInBytes > 1000000) { Utils.showToast(LocaleKeys.fileLarger.tr()); return; } else { String image64 = Utils.convertFileToBase64(pickedImageFile); Utils.showLoading(context); ImageResponse response = await userRepo.updateUserImage(image64); Utils.hideLoading(context); Navigator.pop(context); if (response.messageStatus == 1) { Utils.showToast(LocaleKeys.imageUploaded.tr()); AppState().getUser.data!.userInfo!.userImageUrl = response.data; } else { Utils.showToast(response.message ?? ""); } } } Future updateUserImage(String image) async { return await userRepo.updateUserImage(image); } Future onRefresh(BuildContext context, {DashboardRouteEnum dashboardRouteEnum = DashboardRouteEnum.none}) async { final requestsVM = context.read(); final chatVM = context.read(); final appointmentVM = context.read(); final shippingManagementVM = context.read(); final serviceVM = context.read(); final adVM = context.read(); final subscriptionsVM = context.read(); // Set loading state immediately to prevent "No data" flash adVM.setState(ViewState.busy); appointmentVM.setState(ViewState.busy); requestsVM.setState(ViewState.busy); serviceVM.setState(ViewState.busy); // ✅ Inject ViewModels into ChatVM (only needs to be done once, but safe to repeat) chatVM.setViewModels(requestsVM: requestsVM, adVM: adVM); // Call independent filter population APIs in parallel await Future.wait([ requestsVM.populateDataForRequestsFilter(), requestsVM.populateDeliveryOptionEnums(), appointmentVM.populateAppointmentsFilterList(), adVM.populateAdsFilterList(), serviceVM.populateBranchServiceFilters(), shippingManagementVM.populateShippingRequestFilterList(), shippingManagementVM.populateSelfPickupRequestFilterList(), adVM.getVehicleTypes(), adVM.getVehicleAdsDuration(), ]); // Call data APIs in parallel await Future.wait([ serviceVM.getBranchAndServices(), requestsVM.getRequests(), subscriptionsVM.getSubscriptionBySP(AppState().getUser.data?.userInfo?.providerId.toString() ?? "", true), appointmentVM.applyFilterOnAppointmentsVMForProviders( appointmentStatusEnum: AppointmentStatusEnum.allAppointments, isNeedCustomerFilter: true, shouldPopulateUpcoming: true, ), ]); // Call ads APIs in parallel (conditionally) if (dashboardRouteEnum != DashboardRouteEnum.fromAdsPayment && dashboardRouteEnum != DashboardRouteEnum.fromAdsSubmit && dashboardRouteEnum != DashboardRouteEnum.fromReserveAdsPayment) { await Future.wait([ adVM.getMyAds(), adVM.getExploreAds(), ]); } // Handle reserve ads payment completion if (dashboardRouteEnum == DashboardRouteEnum.fromReserveAdsPayment) { adVM.updateIsExploreAds(false); onNavbarTapped(3); // Go to My Ads tab await adVM.applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.reserved); log("✅ Successfully navigated to Reserved Ads after payment"); } // Build chat hub connection last await chatVM.buildHubConnection(context: context); } void checkUserSubscription(SubscriptionActionTypeEnum actionType, BuildContext context, {required Function() callback}) async { switch (actionType) { case SubscriptionActionTypeEnum.ads: performCheckOnAds(context); break; case SubscriptionActionTypeEnum.users: performCheckOnUsers(context, callback); break; case SubscriptionActionTypeEnum.branches: performCheckOnBranches(context); break; case SubscriptionActionTypeEnum.subscription: performCheckSubscription(context, callback); break; } } void performCheckOnBranches(BuildContext context) async { if (AppState().getproviderSubscription.isNotEmpty && AppState().getproviderSubscription.first.branchesRemaining! > 0) { await navigateWithName(context, AppRoutes.defineBranch); } else { Utils.showToast(LocaleKeys.upgradeBranches.tr()); } } void performCheckSubscription(BuildContext context, Function() callBack) async { if (AppState().getproviderSubscription.isNotEmpty && !AppState().getproviderSubscription.first.isExpired!) { callBack(); } else { Utils.showToast(LocaleKeys.upgradeSubscription.tr()); } } void performCheckOnAds(BuildContext context) async { if (AppState().getproviderSubscription.isNotEmpty && AppState().getproviderSubscription.first.adsRemaining! > 0) { await navigateWithName(context, AppRoutes.defineBranch); } else { Utils.showToast(LocaleKeys.upgradeAds.tr()); } } void performCheckOnUsers(BuildContext context, Function() callBack) async { if (AppState().getproviderSubscription.isNotEmpty && AppState().getproviderSubscription.first.subUsersRemaining! > 0) { showMyBottomSheet(context, isDismissible: false, isScrollControlled: true, child: AddPhoneNumWidget(), callBackFunc: callBack); } else { Utils.showToast(LocaleKeys.upgradeSubUsers.tr()); } } }