From 28612464346aec14f5ff09d3596a2841fae302bc Mon Sep 17 00:00:00 2001 From: FaizHashmi Date: Wed, 30 Sep 2026 09:26:16 +0300 Subject: [PATCH] Bug Fixes --- android/app/src/main/AndroidManifest.xml | 13 +- lib/main.dart | 39 +++++- .../subscriptions/all_subscriptions_page.dart | 2 +- .../sheet/downgrade_data_sheet.dart | 131 ++++++++++++++++-- 4 files changed, 167 insertions(+), 18 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index da426b3..7f9cdbe 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -46,7 +46,7 @@ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:exported="true" android:hardwareAccelerated="true" - android:launchMode="singleTop" + android:launchMode="singleTask" android:theme="@style/LaunchTheme" android:windowSoftInputMode="adjustResize"> + diff --git a/lib/main.dart b/lib/main.dart index 0165014..9842225 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'package:mc_common_app/services/firebase_service.dart'; import 'package:mc_common_app/utils/location/Location.dart'; import 'package:mc_common_app/view_models/dashboard_view_model_provider.dart'; import 'package:mc_common_app/view_models/service_view_model.dart'; +import 'package:mc_common_app/views/splash/splash_page.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; @@ -135,7 +136,7 @@ Future main() async { paymentService: injector.get(), paymentRepo: injector.get(), ), - ), + ), ChangeNotifierProvider( create: (_) => ShippingManagementVM( shippingRepo: injector.get(), @@ -194,6 +195,42 @@ class _MyAppState extends State { locale: context.locale, initialRoute: AppRoutes.initialRoute, routes: ProviderAppRoutes.routes, + navigatorObservers: [AdDeepLinkService.routeObserver], + // Confirmed on a real device, two different unknown-route cases land here: + // 1) Cold-start restoration can try to regenerate a bare "/" instead of + // respecting initialRoute -- uncaught, that's a "Could not find a generator + // for route" crash before AdDeepLinkService ever runs. Splash is a real, + // correct landing page for this one. + // 2) On an ALREADY-RUNNING app, Android's Flutter embedding auto-forwards the + // incoming deep-link intent's path as its own route push (e.g. "/1541" from + // mowaterprovider://ad/1541) independently of app_links/AdDeepLinkService, + // which already handles that same uri correctly. Since "/1541" isn't a real + // route, it used to fall into case 1 and push Splash right over whatever + // screen (e.g. the dashboard) was already showing -- looking exactly like the + // app had restarted. Anything other than bare "/" must be a no-op here: push + // something inert that removes itself immediately instead of a real page. + onUnknownRoute: (settings) { + if (settings.name == '/') { + return MaterialPageRoute(builder: (_) => const SplashPage()); + } + // Remove THIS specific route once inserted, not just "whatever is on top" -- + // AdDeepLinkService's own (legitimate) push can land after this one, and a + // plain Navigator.pop() would close that instead of this inert placeholder. + late final PageRoute route; + route = PageRouteBuilder( + settings: const RouteSettings(name: AdDeepLinkService.ignoredPlaceholderRouteName), + opaque: false, + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, + pageBuilder: (context, __, ___) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (route.isActive) route.navigator?.removeRoute(route); + }); + return const SizedBox.shrink(); + }, + ); + return route; + }, ), ); }, diff --git a/lib/views/subscriptions/all_subscriptions_page.dart b/lib/views/subscriptions/all_subscriptions_page.dart index 359bdb9..f4185bf 100644 --- a/lib/views/subscriptions/all_subscriptions_page.dart +++ b/lib/views/subscriptions/all_subscriptions_page.dart @@ -84,7 +84,7 @@ class _SubscriptionsPageState extends State { showMyBottomSheet(context, child: UpgradePackageSheet(subscription: subcriptionsVM.tempSubscriptions[index], isRenew: false)); }, onDowngradeSubscriptionClick: () { - showMyBottomSheet(context, child: DowngradeDataSheet(subcriptionsVM.tempSubscriptions[index])); + showMyBottomSheet(context, child: DowngradeDataSheet(subcriptionsVM.tempSubscriptions[index], )); }, ); }, diff --git a/lib/views/subscriptions/sheet/downgrade_data_sheet.dart b/lib/views/subscriptions/sheet/downgrade_data_sheet.dart index c9bb1c3..353297a 100644 --- a/lib/views/subscriptions/sheet/downgrade_data_sheet.dart +++ b/lib/views/subscriptions/sheet/downgrade_data_sheet.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; 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/generated/locale_keys.g.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/subscriptions_models/branch_user_selection_model.dart'; import 'package:mc_common_app/models/subscriptions_models/subscription_model.dart'; @@ -15,6 +16,7 @@ import 'package:mc_common_app/views/payments/payment_methods_view.dart'; import 'package:mc_common_app/widgets/button/show_fill_button.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:provider/provider.dart'; +import 'package:easy_localization/easy_localization.dart'; class DowngradeDataSheet extends StatefulWidget { final Subscription subscription; @@ -32,8 +34,65 @@ class _DowngradeDataSheetState extends State { context.read().getSPBranchUserGet(); } + // The provider's current active plan (not the plan being downgraded to) holds how many + // branches/sub-users they currently have -- confirmed with the user this is where + // "current totals" come from, as opposed to widget.subscription which is the target plan. + Subscription? get _currentSubscription { + final list = AppState().getproviderSubscription; + return list.isEmpty ? null : list.first; + } + + // subscriptionBranches/subscriptionSubUsers are each plan's own allowed quota. + // totalBranches/totalSubUsers looked plausible but turned out to be unrelated (they + // read identically on both the current and target plan records in testing). + int get _currentBranches => _currentSubscription?.subscriptionBranches ?? 0; + + int get _currentUsers => _currentSubscription?.subscriptionSubUsers ?? 0; + + int get _targetBranches => widget.subscription.subscriptionBranches ?? 0; + + int get _targetUsers => widget.subscription.subscriptionSubUsers ?? 0; + + int get _branchesToRemove => (_currentBranches - _targetBranches).clamp(0, _currentBranches); + + int get _usersToRemove => (_currentUsers - _targetUsers).clamp(0, _currentUsers); + + // Selecting a whole branch removes every user in it too, so that counts towards the + // user quota as well, not just the branch quota. + _SelectedCounts _selectedCounts(List branchSelectionList) { + int selectedBranches = 0; + int selectedUsers = 0; + for (var branch in branchSelectionList) { + if (branch.isSelected) { + selectedBranches++; + selectedUsers += branch.usersList.length; + } else { + selectedUsers += branch.usersList.where((u) => u.isSelected).length; + } + } + return _SelectedCounts(selectedBranches, selectedUsers); + } + + Widget _buildRequirementCounter(List branchSelectionList) { + final counts = _selectedCounts(branchSelectionList); + final remainingBranches = (_branchesToRemove - counts.branches).clamp(0, _branchesToRemove); + final remainingUsers = (_usersToRemove - counts.users).clamp(0, _usersToRemove); + + if (remainingBranches == 0 && remainingUsers == 0) { + return "You have selected enough branches/users to downgrade.".toText(fontSize: 13, color: MyColors.primaryColor, isBold: true); + } + + final parts = [ + if (remainingBranches > 0) "$remainingBranches more branch${remainingBranches == 1 ? '' : 'es'}", + if (remainingUsers > 0) "$remainingUsers more user${remainingUsers == 1 ? '' : 's'}", + ]; + return "Select ${parts.join(' and ')} to downgrade.".toText(fontSize: 13, color: MyColors.textColor, isBold: true); + } + @override Widget build(BuildContext context) { + final targetBranchesText = "$_targetBranches branch${_targetBranches == 1 ? '' : 'es'}"; + final targetUsersText = "$_targetUsers sub-user${_targetUsers == 1 ? '' : 's'}"; return SizedBox( width: double.infinity, height: MediaQuery.of(context).size.height / 1.2, @@ -49,19 +108,44 @@ class _DowngradeDataSheetState extends State { isBold: true, ), 8.height, - "To select this subscription you need to deactivate your branches and users as this package only supports 1 branch and 2 users per branch. You can reactivate these branches later after upgrading the package" - .toText( + LocaleKeys.downgradeDeactivateInstruction.tr(namedArgs: {'branches': targetBranchesText, 'users': targetUsersText}).toText( fontSize: 14, color: MyColors.textColor, ), + 12.height, + // isStartNow is passed as false for a downgrade, so the new package isn't + // applied immediately -- it takes effect once the current one expires. Make + // that explicit so it isn't mistaken for an immediate switch. + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon(Icons.info_outline_rounded, size: 18, color: MyColors.textColor), + 8.width, + Expanded( + child: LocaleKeys.downgradeAppliedAfterExpiry.tr().toText(fontSize: 13, color: MyColors.textColor), + ), + ], + ).toContainer( + backgroundColor: MyColors.greyButtonColor, + paddingAll: 10, + borderRadius: 6, + ), ], ), ), Expanded( child: Consumer( builder: (BuildContext context, model, Widget? child) { + final counts = model.branchSelectionList == null ? _SelectedCounts(0, 0) : _selectedCounts(model.branchSelectionList!); + final canProceed = model.branchSelectionList != null && counts.branches >= _branchesToRemove && counts.users >= _usersToRemove; return Column( children: [ + if (model.branchSelectionList != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: _buildRequirementCounter(model.branchSelectionList!), + ), + if (model.branchSelectionList != null) 8.height, Expanded( child: model.branchSelectionList == null ? const Center(child: CircularProgressIndicator()) @@ -77,19 +161,23 @@ class _DowngradeDataSheetState extends State { ), ), ShowFillButton( - title: "Next", + title: LocaleKeys.next.tr(), maxWidth: double.infinity, margin: const EdgeInsets.all(12), + isDisabled: !canProceed, onPressed: () async { List listOfBranches = []; List listOfUsers = []; - for (var element in model.branchSelectionList!) { - if (element.isSelected) { - listOfBranches.add(element.branchId); + for (var branch in model.branchSelectionList!) { + if (branch.isSelected) { + listOfBranches.add(branch.branchId); + for (var user in branch.usersList) { + listOfUsers.add(user.userId); + } } else { - for (var element in element.usersList) { - if (element.isSelected) { - listOfUsers.add(element.userId); + for (var user in branch.usersList) { + if (user.isSelected) { + listOfUsers.add(user.userId); } } } @@ -103,7 +191,7 @@ class _DowngradeDataSheetState extends State { Utils.showLoading(context); GenericRespModel response = await model.payForSubscription( subscriptionId: widget.subscription.id!, - isStartNow: true, + isStartNow: false, isRenew: false, amount: widget.subscription.price.toString(), isDegrade: true, @@ -152,15 +240,21 @@ class _DowngradeDataSheetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - customCheckBox(model.isSelected).onPress(() { + // Padding around the checkbox widens its tap target (18x18 alone is too small to + // hit reliably) without growing the checkbox's own visual size or changing the + // row's separate tap-to-expand behavior. + Padding( + padding: const EdgeInsets.all(10), + child: customCheckBox(model.isSelected), + ).onPress(() { model.isSelected = !model.isSelected; if (model.isSelected) { - for (var element in model.usersList) { - element.isSelected = true; + for (var user in model.usersList) { + user.isSelected = true; } } else { - for (var element in model.usersList) { - element.isSelected = false; + for (var user in model.usersList) { + user.isSelected = false; } } model.isOpend = true; @@ -265,3 +359,10 @@ class _DowngradeDataSheetState extends State { ); } } + +class _SelectedCounts { + final int branches; + final int users; + + _SelectedCounts(this.branches, this.users); +}