Bug Fixes

fatima_dev
FaizHashmi 2 weeks ago
parent 4e8ae4d0d1
commit 2861246434

@ -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">
<!-- Specifies an Android theme to apply to this Activity as soon as
@ -99,5 +99,16 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- Disable Flutter's built-in auto-deep-link route push: without this, FlutterActivity
forwards the raw VIEW intent URI (e.g. "mowaterprovider://ad/1541") straight to the
Navigator as a route name on cold start, before AdDeepLinkService ever runs. Since
that string isn't a registered route, it collides with the app's own bootstrap
navigation (splash to login flow), confirmed on a real device: this caused an
"_elements.contains(element)" crash in debug and a silent black screen hang in
release. app_links + AdDeepLinkService already handle every deep link deliberately,
so the native auto-forwarding must stay off. -->
<meta-data
android:name="flutter_deeplinking_enabled"
android:value="false" />
</application>
</manifest>

@ -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<void> main() async {
paymentService: injector.get<PaymentService>(),
paymentRepo: injector.get<PaymentsRepo>(),
),
),
),
ChangeNotifierProvider<ShippingManagementVM>(
create: (_) => ShippingManagementVM(
shippingRepo: injector.get<ShippingRepo>(),
@ -194,6 +195,42 @@ class _MyAppState extends State<MyApp> {
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<void> route;
route = PageRouteBuilder<void>(
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;
},
),
);
},

@ -84,7 +84,7 @@ class _SubscriptionsPageState extends State<SubscriptionsPage> {
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], ));
},
);
},

@ -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<DowngradeDataSheet> {
context.read<SubscriptionsVM>().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<BranchSelectionModel> 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<BranchSelectionModel> 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 = <String>[
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<DowngradeDataSheet> {
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<SubscriptionsVM>(
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<DowngradeDataSheet> {
),
),
ShowFillButton(
title: "Next",
title: LocaleKeys.next.tr(),
maxWidth: double.infinity,
margin: const EdgeInsets.all(12),
isDisabled: !canProceed,
onPressed: () async {
List<int> listOfBranches = [];
List<int> 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<DowngradeDataSheet> {
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<DowngradeDataSheet> {
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<DowngradeDataSheet> {
);
}
}
class _SelectedCounts {
final int branches;
final int users;
_SelectedCounts(this.branches, this.users);
}

Loading…
Cancel
Save