From e05e97a5360f787c584fae3740be5abc9eb57beb Mon Sep 17 00:00:00 2001 From: FaizHashmi Date: Wed, 14 Oct 2026 12:39:38 +0300 Subject: [PATCH] Updated the dialogs to toast --- assets/langs/ar-SA.json | 6 +- assets/langs/en-US.json | 6 +- lib/api/marathon/marathon_api_client.dart | 7 +- lib/classes/app_permissions.dart | 28 ++- lib/classes/consts.dart | 4 +- lib/classes/utils.dart | 59 ++---- lib/generated/codegen_loader.g.dart | 12 +- lib/generated/locale_keys.g.dart | 4 + lib/provider/chat_provider_model.dart | 20 +- lib/provider/dashboard_provider_model.dart | 17 +- .../attendance/add_vacation_rule_screen.dart | 19 +- lib/ui/chat/group_chat.dart | 11 +- lib/ui/etqan_ovr/etqan_create_request.dart | 30 +-- .../dashboard_screen_for_dependet.dart | 12 +- .../wala_plus_retailer_details_screen.dart | 16 +- lib/ui/login/login_screen.dart | 28 +-- lib/ui/marathon/marathon_screen.dart | 58 +++--- lib/ui/marathon/marathon_waiting_screen.dart | 56 +++--- lib/ui/misc/request_submit_screen.dart | 19 +- lib/ui/my_team/employee_details.dart | 75 ++------ lib/ui/profile/family_members.dart | 27 ++- .../screens/mowadhafhi/request_details.dart | 12 +- lib/ui/work_list/worklist_detail_screen.dart | 37 ++-- .../bottom_sheets/confirm_bottom_sheet.dart | 7 + lib/widgets/mark_attendance_widget.dart | 178 +++++++----------- lib/widgets/toast/app_toast.dart | 141 ++++++++++++++ 26 files changed, 463 insertions(+), 426 deletions(-) create mode 100644 lib/widgets/toast/app_toast.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 3159d43e..66a2ba2c 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -802,6 +802,10 @@ "loginMethod": "طريقة تسجيل الدخول", "nationalIdAndDob": "الهوية الوطنية وتاريخ الميلاد", "sponsorOtp": "رمز التحقق للكفيل", - "revokingPreventsAccessDesc": "إلغاء الوصول يمنع التابع من استخدام المزايا حتى تعيد السماح به مرة أخرى." + "revokingPreventsAccessDesc": "إلغاء الوصول يمنع التابع من استخدام المزايا حتى تعيد السماح به مرة أخرى.", + "locationPermissionRequiredForAttendance": "يجب منح إذن الموقع لتسجيل الحضور", + "locationServicesRequiredForAttendance": "يجب تفعيل خدمة الموقع لتسجيل الحضور", + "pleaseProvideLocationPermission": "الرجاء منح إذن الموقع", + "microphoneAccessRequired": "يحتاج التطبيق إلى إذن الوصول إلى الميكروفون لتسجيل الصوت." } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 2cc1914f..bc4d2605 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -802,7 +802,11 @@ "loginMethod": "Login method", "nationalIdAndDob": "National ID & Date of Birth", "sponsorOtp": "Sponsor OTP", - "revokingPreventsAccessDesc": "Revoking prevents all future dependent access until you allow it again." + "revokingPreventsAccessDesc": "Revoking prevents all future dependent access until you allow it again.", + "locationPermissionRequiredForAttendance": "You need to give location permission to mark attendance", + "locationServicesRequiredForAttendance": "You need to enable location services to mark attendance", + "pleaseProvideLocationPermission": "Please provide location permission", + "microphoneAccessRequired": "The app needs microphone access to be able to record audio." } diff --git a/lib/api/marathon/marathon_api_client.dart b/lib/api/marathon/marathon_api_client.dart index d9021c69..e85f8821 100644 --- a/lib/api/marathon/marathon_api_client.dart +++ b/lib/api/marathon/marathon_api_client.dart @@ -7,6 +7,7 @@ import 'package:mohem_flutter_app/api/api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/widgets/toast/app_toast.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/models/marathon/marathon_generic_model.dart'; import 'package:mohem_flutter_app/models/marathon/marathon_model.dart'; @@ -114,10 +115,10 @@ class MarathonApiClient { MarathonGenericModel marathonModel = MarathonGenericModel.fromJson(json); if (marathonModel.statusCode == 404 || marathonModel.statusCode == 208 || marathonModel.statusCode == 204 || marathonModel.statusCode == 500) { - Utils.confirmDialog( - AppRoutes.navigatorKey.currentContext, + AppToast.show( marathonModel.message!, - onTap: () { + seconds: AppToast.actionSeconds, + onComplete: () { AppRoutes.navigatorKey.currentContext!.read().resetValues(); Navigator.of( AppRoutes.navigatorKey.currentContext!, diff --git a/lib/classes/app_permissions.dart b/lib/classes/app_permissions.dart index 1509b579..875a2be0 100644 --- a/lib/classes/app_permissions.dart +++ b/lib/classes/app_permissions.dart @@ -1,5 +1,5 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; -import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; @@ -18,20 +18,18 @@ class AppPermissions { } static void showErrorLocationDialog(bool isPermissionError, BuildContext context) { - showDialog( - context: context, - builder: (cxt) => ConfirmDialog( - message: "Please provide location permission", - onTap: () { - if (isPermissionError) { - Geolocator.openAppSettings(); - } else { - Geolocator.openLocationSettings(); - } - Navigator.pop(context); - // createVacationRule(list); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.pleaseProvideLocationPermission.tr(), + confirmText: LocaleKeys.openSettings.tr(), + showCancelButton: false, + onConfirm: () { + if (isPermissionError) { + Geolocator.openAppSettings(); + } else { + Geolocator.openLocationSettings(); + } + }, ); } diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index a66b657d..e3463788 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -4,12 +4,12 @@ class ApiConsts { // static String baseUrl = "http://10.201.214.101:2027"; // Local server // static String baseUrl = "https://erptstapp.srca.org.sa"; // SRCA server - // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver + static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver // static String baseUrl = "http://10.201.204.101:2024"; // static String baseUrl = "https://webservices.hmg.com"; // PreProd // static String baseUrl = "https://hmgwebservices.com"; // Live server - static String baseUrl = "https://mohemm.hmg.com"; // New Live server + // static String baseUrl = "https://mohemm.hmg.com"; // New Live server static String ssoBaseUrl = "https://sso.hmg.com"; // New Live server // // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index 7f906f4d..9d49f2fa 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -6,7 +6,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_api_availability/google_api_availability.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; @@ -19,6 +18,7 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; import 'package:mohem_flutter_app/widgets/loading_dialog.dart'; +import 'package:mohem_flutter_app/widgets/toast/app_toast.dart'; import 'package:nfc_manager/nfc_manager.dart'; import 'package:nfc_manager/platform_tags.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -31,16 +31,10 @@ class Utils { static bool get isLoading => _isLoadingVisible; + // longDuration is accepted but no longer varies the duration - every caller already used the + // default (true), so AppToast.plainSeconds covers both cases with one fixed value. static void showToast(String message, {bool longDuration = true}) { - Fluttertoast.showToast( - msg: message, - toastLength: longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT, - gravity: ToastGravity.BOTTOM, - timeInSecForIosWeb: 1, - backgroundColor: Colors.black54, - textColor: Colors.white, - fontSize: 13.0, - ); + AppToast.show(message, seconds: AppToast.plainSeconds, showIcon: false, showCountdownText: false); } static dynamic getNotNullValue(List list, int index) { @@ -118,11 +112,7 @@ class Utils { lower.contains("e4 :")) { return true; } - if (str.contains("طلب منح") || - str.contains("منح نشط") || - str.contains("لا يوجد طلب منح") || - str.contains("تم إلغاء وصول مزايا") || - str.contains("إلغاء مزايا")) { + if (str.contains("طلب منح") || str.contains("منح نشط") || str.contains("لا يوجد طلب منح") || str.contains("تم إلغاء وصول مزايا") || str.contains("إلغاء مزايا")) { return true; } return false; @@ -135,15 +125,7 @@ class Utils { depName = AppState().walaplusDepInfoModel?.pDependentName ?? ""; } if (cxt != null) { - Navigator.push( - cxt, - MaterialPageRoute( - builder: (context) => MazayaAccessResultScreen( - isGranted: false, - dependentName: depName, - ), - ), - ); + Navigator.push(cxt, MaterialPageRoute(builder: (context) => MazayaAccessResultScreen(isGranted: false, dependentName: depName))); return; } } @@ -165,17 +147,12 @@ class Utils { onErrorMessage(errorMessage); } else { if (!AppState().isAuthenticated) { - showDialog( - barrierDismissible: false, - context: cxt, - builder: - (cxt) => ConfirmDialog( - message: errorMessage, - onTap: () { - Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.login, (Route route) => false); - }, - onCloseTap: () {}, - ), + AppToast.show( + errorMessage, + seconds: AppToast.actionSeconds, + onComplete: () { + Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.login, (Route route) => false); + }, ); } else { if (cxt != null) { @@ -193,7 +170,13 @@ class Utils { } static void confirmDialog(cxt, String message, {VoidCallback? onTap}) { - showDialog(context: cxt, builder: (BuildContext cxt) => ConfirmDialog(message: message, onTap: onTap)); + // No onTap means there's nothing to confirm - it's a plain notice, so a self-dismissing + // toast replaces the old "OK just closes" dialog instead of requiring a tap. + if (onTap == null) { + AppToast.show(message); + } else { + showDialog(context: cxt, builder: (BuildContext cxt) => ConfirmDialog(message: message, onTap: onTap)); + } } static Widget getNoDataWidget(BuildContext context) { @@ -448,9 +431,7 @@ class Utils { if (chatData != null) { try { chatData.disposeData(); - } catch (e) { - - } + } catch (e) {} } // SharedPreferences prefs = await SharedPreferences.getInstance(); // await prefs.clear(); diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index c95fcce7..66fc637f 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -814,7 +814,11 @@ class CodegenLoader extends AssetLoader{ "loginMethod": "طريقة تسجيل الدخول", "nationalIdAndDob": "الهوية الوطنية وتاريخ الميلاد", "sponsorOtp": "رمز التحقق للكفيل", - "revokingPreventsAccessDesc": "إلغاء الوصول يمنع التابع من استخدام المزايا حتى تعيد السماح به مرة أخرى." + "revokingPreventsAccessDesc": "إلغاء الوصول يمنع التابع من استخدام المزايا حتى تعيد السماح به مرة أخرى.", + "locationPermissionRequiredForAttendance": "يجب منح إذن الموقع لتسجيل الحضور", + "locationServicesRequiredForAttendance": "يجب تفعيل خدمة الموقع لتسجيل الحضور", + "pleaseProvideLocationPermission": "الرجاء منح إذن الموقع", + "microphoneAccessRequired": "يحتاج التطبيق إلى إذن الوصول إلى الميكروفون لتسجيل الصوت." }; static const Map _en_US = { "mohemm": "Mohemm", @@ -1616,7 +1620,11 @@ static const Map _en_US = { "loginMethod": "Login method", "nationalIdAndDob": "National ID & Date of Birth", "sponsorOtp": "Sponsor OTP", - "revokingPreventsAccessDesc": "Revoking prevents all future dependent access until you allow it again." + "revokingPreventsAccessDesc": "Revoking prevents all future dependent access until you allow it again.", + "locationPermissionRequiredForAttendance": "You need to give location permission to mark attendance", + "locationServicesRequiredForAttendance": "You need to enable location services to mark attendance", + "pleaseProvideLocationPermission": "Please provide location permission", + "microphoneAccessRequired": "The app needs microphone access to be able to record audio." }; static const Map> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index a356c619..26070e4b 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -787,5 +787,9 @@ abstract class LocaleKeys { static const nationalIdAndDob = 'nationalIdAndDob'; static const sponsorOtp = 'sponsorOtp'; static const revokingPreventsAccessDesc = 'revokingPreventsAccessDesc'; + static const locationPermissionRequiredForAttendance = 'locationPermissionRequiredForAttendance'; + static const locationServicesRequiredForAttendance = 'locationServicesRequiredForAttendance'; + static const pleaseProvideLocationPermission = 'pleaseProvideLocationPermission'; + static const microphoneAccessRequired = 'microphoneAccessRequired'; } diff --git a/lib/provider/chat_provider_model.dart b/lib/provider/chat_provider_model.dart index e0f5900c..09736fa5 100644 --- a/lib/provider/chat_provider_model.dart +++ b/lib/provider/chat_provider_model.dart @@ -16,6 +16,8 @@ import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/encryption.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/chat/chat_user_image_model.dart'; @@ -1638,20 +1640,22 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { void startRecoding(BuildContext context) async { await Permission.microphone.request().then((PermissionStatus status) { if (status.isPermanentlyDenied) { - Utils.confirmDialog( + ConfirmBottomSheet.show( context, - "The app needs microphone access to be able to record audio.", - onTap: () { - Navigator.of(context).pop(); + message: LocaleKeys.microphoneAccessRequired.tr(), + confirmText: LocaleKeys.openSettings.tr(), + showCancelButton: false, + onConfirm: () { openAppSettings(); }, ); } else if (status.isDenied) { - Utils.confirmDialog( + ConfirmBottomSheet.show( context, - "The app needs microphone access to be able to record audio.", - onTap: () { - Navigator.of(context).pop(); + message: LocaleKeys.microphoneAccessRequired.tr(), + confirmText: LocaleKeys.openSettings.tr(), + showCancelButton: false, + onConfirm: () { openAppSettings(); }, ); diff --git a/lib/provider/dashboard_provider_model.dart b/lib/provider/dashboard_provider_model.dart index 904ef5d0..f4414b98 100644 --- a/lib/provider/dashboard_provider_model.dart +++ b/lib/provider/dashboard_provider_model.dart @@ -27,7 +27,7 @@ import 'package:mohem_flutter_app/models/greetings/greeting_card_model.dart'; import 'package:mohem_flutter_app/models/itg/itg_response_model.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart'; import 'package:mohem_flutter_app/models/sso_auth_model.dart'; -import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:mohem_flutter_app/widgets/toast/app_toast.dart'; /// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool // ignore: prefer_mixin @@ -190,15 +190,12 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); Utils.handleException(ex, context, (err) { Utils.hideLoading(context); - showDialog( - context: context, - builder: - (cxt) => ConfirmDialog( - message: err, - onTap: () { - Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.login, (Route route) => false); - }, - ), + AppToast.show( + err, + seconds: AppToast.actionSeconds, + onComplete: () { + Navigator.pushNamedAndRemoveUntil(context, AppRoutes.login, (Route route) => false); + }, ); }); } diff --git a/lib/ui/attendance/add_vacation_rule_screen.dart b/lib/ui/attendance/add_vacation_rule_screen.dart index eb32737f..52eab3dd 100644 --- a/lib/ui/attendance/add_vacation_rule_screen.dart +++ b/lib/ui/attendance/add_vacation_rule_screen.dart @@ -24,7 +24,7 @@ import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheets/search_employee_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; -import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/dynamic_forms/dynamic_textfield_widget.dart'; import 'package:mohem_flutter_app/widgets/item_detail_view_widget.dart'; @@ -490,15 +490,14 @@ class _AddVacationRuleScreenState extends State { } } - showDialog( - context: context, - builder: (cxt) => ConfirmDialog( - message: LocaleKeys.areYouSureYouWantToSubmit.tr(), - onTap: () { - Navigator.pop(context); - createVacationRule(list); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.areYouSureYouWantToSubmit.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: () { + createVacationRule(list); + }, ); }, ).insideContainer, diff --git a/lib/ui/chat/group_chat.dart b/lib/ui/chat/group_chat.dart index 35b4c5c0..575dfa0e 100644 --- a/lib/ui/chat/group_chat.dart +++ b/lib/ui/chat/group_chat.dart @@ -19,6 +19,7 @@ import 'package:mohem_flutter_app/ui/chat/create_group.dart'; import 'package:mohem_flutter_app/ui/chat/group_chat_detaied_screen.dart'; import 'package:mohem_flutter_app/ui/chat/manage_group.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheets/search_employee_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -269,11 +270,13 @@ void goToSelected(GroupResponse? groupDetails, ChatProviderModel m, String value void deleteGroup( GroupResponse? groupDetails, ChatProviderModel m, BuildContext context) { groupDetails!.groupUserList; - Utils.confirmDialog( + ConfirmBottomSheet.show( context, - LocaleKeys.areYouSureWantTodelete.tr(), - onTap: () { - Navigator.pop(context); + message: LocaleKeys.areYouSureWantTodelete.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + isDestructive: true, + onConfirm: () { m.deleteGroup(groupDetails); }, ); diff --git a/lib/ui/etqan_ovr/etqan_create_request.dart b/lib/ui/etqan_ovr/etqan_create_request.dart index ba3c5048..41b4473a 100644 --- a/lib/ui/etqan_ovr/etqan_create_request.dart +++ b/lib/ui/etqan_ovr/etqan_create_request.dart @@ -14,6 +14,7 @@ import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:mohem_flutter_app/widgets/image_picker.dart'; +import 'package:mohem_flutter_app/widgets/toast/app_toast.dart'; import 'package:provider/provider.dart'; class EtqanOvrCreateRequest extends StatefulWidget { @@ -87,23 +88,11 @@ class _EtqanOvrCreateRequestState extends State { DefaultButton(LocaleKeys.submit.tr(), () async { provider.setDescription(_descriptionController.text); if (provider.selectedProject == null) { - Utils.showErrorDialog( - context: context, - message: LocaleKeys.pleaseSelectProject.tr(), - onOkTapped: () { - Navigator.of(context).pop(); - }, - ); + AppToast.show(LocaleKeys.pleaseSelectProject.tr()); return; } if (!provider.isDescriptionValid()) { - Utils.showErrorDialog( - context: context, - message: LocaleKeys.pleaseEnterDescription.tr(), - onOkTapped: () { - Navigator.of(context).pop(); - }, - ); + AppToast.show(LocaleKeys.pleaseEnterDescription.tr()); return; } Map? ticketInfo = await provider.submitRequest(context); @@ -112,15 +101,10 @@ class _EtqanOvrCreateRequestState extends State { String ticketId = ticketInfo['id'] ?? ''; // String successMessage = '${LocaleKeys.requestCreatedSuccessfully.tr()}\n\n${LocaleKeys.ticketNumber.tr()}: $ticketNumber\n${LocaleKeys.ticketId.tr()}: $ticketId'; String successMessage = '${LocaleKeys.requestCreatedSuccessfully.tr()}\n\n${LocaleKeys.ticketNumber.tr()}: $ticketNumber'; - Utils.showErrorDialog( - context: context, - message: successMessage, - onOkTapped: () { - Navigator.pop(context); - Navigator.pop(context); - // Navigator.popAndPushNamed(context, AppRoutes.etqanOvr); - }, - onCloseTap: () { + AppToast.show( + successMessage, + seconds: AppToast.actionSeconds, + onComplete: () { Navigator.pop(context); Navigator.pop(context); // Navigator.popAndPushNamed(context, AppRoutes.etqanOvr); diff --git a/lib/ui/landing/dashboard_screen_for_dependet.dart b/lib/ui/landing/dashboard_screen_for_dependet.dart index a80a894e..df412fd8 100644 --- a/lib/ui/landing/dashboard_screen_for_dependet.dart +++ b/lib/ui/landing/dashboard_screen_for_dependet.dart @@ -15,6 +15,7 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category_model.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/info_chip_widget.dart'; import 'package:shimmer/shimmer.dart'; @@ -346,10 +347,13 @@ class _DashboardScreenForDependetState extends State } void _performLogout() { - Utils.confirmDialog( + ConfirmBottomSheet.show( context, - LocaleKeys.areYouSureWantToLogOut.tr(), - onTap: () { + message: LocaleKeys.areYouSureWantToLogOut.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + isDestructive: true, + onConfirm: () { ApiConsts.selectedMemberType = MemberType.employee; ApiConsts.ISDependetLogin = false; AppState().setPostParamsInitConfig(); @@ -391,7 +395,7 @@ class _DashboardScreenForDependetState extends State child: Icon(isAr ? Icons.arrow_forward_ios_rounded : Icons.arrow_back_ios_new_rounded, size: 20, color: const Color(0xFF1E293B)), ), ), - const SizedBox(width: 8), + const SizedBox(width: 8) , SvgPicture.asset("assets/icons/mazaya_brand.svg", height: 48, fit: BoxFit.contain), ], ), diff --git a/lib/ui/landing/wala_plus_retailer_details_screen.dart b/lib/ui/landing/wala_plus_retailer_details_screen.dart index a1db7dd1..fc171287 100644 --- a/lib/ui/landing/wala_plus_retailer_details_screen.dart +++ b/lib/ui/landing/wala_plus_retailer_details_screen.dart @@ -143,22 +143,23 @@ class _WalaPlusRetailerDetailsScreenState extends State _openInAppBrowser(String url, {String? offerType}) async { String title = _retailer?.name ?? LocaleKeys.offerDetails.tr(); - bool shouldStartScanning = offerType?.trim().toLowerCase() == "discount"; + // Offer types seen so far: "promo", "discount", "unique_promo", "promo_unique_qr" (and + // possibly others) - any type that mentions "discount" or "qr" needs the in-page scanner, + // regardless of exact spelling/casing, so check by substring rather than exact match. + String normalizedOfferType = offerType?.trim().toLowerCase() ?? ""; + bool shouldStartScanning = normalizedOfferType.contains("discount") || normalizedOfferType.contains("qr"); log("shouldStartScanning: ${shouldStartScanning.toString()} for offerType: ${offerType.toString()}"); // Ask for camera permission app-side, before the WebView (and the page's own // getUserMedia() call) even exists. By the time the page asks, the OS decision is // already made, so onPermissionRequest can answer instantly - no mid-flight dialog // racing the page's own timeout for its original getUserMedia() call. - // Discount (QR) offers need the camera to work at all, so if the user denies here, - // don't open the transaction URL - promo offers never reach this check. + // QR/discount-style offers need the camera to work at all, so if the user denies here, + // don't open the transaction URL - plain promo offers never reach this check. if (shouldStartScanning) { try { PermissionStatus status = await Permission.camera.status; log("permission status: ${status.toString()}"); - - // Permanently denied: Permission.camera.request() won't show a dialog at all in this - // state (Android just returns denied silently) - only the app settings screen can fix it. if (status.isPermanentlyDenied) { _showCameraPermissionSheet(LocaleKeys.cameraPermissionPermanentlyDenied.tr()); return; @@ -219,7 +220,8 @@ class _WalaPlusRetailerDetailsScreenState extends State { print(ex.toString()); Utils.hideLoading(context); Utils.handleException(ex, context, (msg) { - Utils.confirmDialog( - context, - msg, - onTap: () async { - if (msg.toLowerCase().contains("password has expired")) { - Navigator.pop(context); - // await Navigator.pushNamed(context, AppRoutes.newPassword, arguments: username.text); + if (msg.toLowerCase().contains("password has expired")) { + // Sends an OTP/activation code via performForgotPassword() - requires an explicit + // tap, not an auto-firing toast, since it's a real side effect, not just navigation. + ConfirmBottomSheet.show( + context, + message: msg, + confirmText: LocaleKeys.forgotPassword.tr(), + showCancelButton: false, + onConfirm: () { performForgotPassword(); - } else { - Navigator.pop(context); - } - }, - ); + }, + ); + } else { + AppToast.show(msg); + } }); } } diff --git a/lib/ui/marathon/marathon_screen.dart b/lib/ui/marathon/marathon_screen.dart index a39b998d..2d951b00 100644 --- a/lib/ui/marathon/marathon_screen.dart +++ b/lib/ui/marathon/marathon_screen.dart @@ -23,7 +23,7 @@ import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/question_card_builder.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; -import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:provider/provider.dart'; class MarathonScreen extends StatelessWidget { @@ -299,16 +299,16 @@ class MarathonScreen extends StatelessWidget { provider.getMarathonDetailsFromApi(); Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); } else { - showDialog( - context: context, - builder: (BuildContext context) => ConfirmDialog( - message: LocaleKeys.youWantToLeaveMarathon.tr(), - onTap: () { - provider.resetValues(); - provider.getMarathonDetailsFromApi(); - Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.youWantToLeaveMarathon.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: () { + provider.resetValues(); + provider.getMarathonDetailsFromApi(); + Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); + }, ); } }, @@ -318,15 +318,15 @@ class MarathonScreen extends StatelessWidget { provider.getMarathonDetailsFromApi(); Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); } else { - showDialog( - context: context, - builder: (BuildContext context) => ConfirmDialog( - message: LocaleKeys.youWantToLeaveMarathon.tr(), - onTap: () { - provider.resetValues(); - Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.youWantToLeaveMarathon.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: () { + provider.resetValues(); + Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); + }, ); } }, @@ -390,15 +390,15 @@ class MarathonScreen extends StatelessWidget { ), ), onWillPop: () { - showDialog( - context: context, - builder: (BuildContext context) => ConfirmDialog( - message: LocaleKeys.youWantToLeaveMarathon.tr(), - onTap: () { - provider.resetValues(); - Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.youWantToLeaveMarathon.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: () { + provider.resetValues(); + Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); + }, ); return Future.value(false); }, diff --git a/lib/ui/marathon/marathon_waiting_screen.dart b/lib/ui/marathon/marathon_waiting_screen.dart index 5dfb3eec..50d3852b 100644 --- a/lib/ui/marathon/marathon_waiting_screen.dart +++ b/lib/ui/marathon/marathon_waiting_screen.dart @@ -10,7 +10,7 @@ import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; -import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:provider/provider.dart'; class MarathonWaitingScreen extends StatelessWidget { @@ -21,15 +21,15 @@ class MarathonWaitingScreen extends StatelessWidget { MarathonProvider provider = context.watch(); return WillPopScope( onWillPop: () { - showDialog( - context: context, - builder: (BuildContext context) => ConfirmDialog( - message: LocaleKeys.youWantToLeaveMarathon.tr(), - onTap: () { - provider.resetValues(); - Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.youWantToLeaveMarathon.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: () { + provider.resetValues(); + Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); + }, ); return Future.value(false); }, @@ -38,27 +38,27 @@ class MarathonWaitingScreen extends StatelessWidget { context, title: LocaleKeys.brainMarathon.tr(), onHomeTapped: () { - showDialog( - context: context, - builder: (BuildContext context) => ConfirmDialog( - message: LocaleKeys.youWantToLeaveMarathon.tr(), - onTap: () { - provider.resetValues(); - Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.youWantToLeaveMarathon.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: () { + provider.resetValues(); + Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); + }, ); }, onBackTapped: () { - showDialog( - context: context, - builder: (BuildContext context) => ConfirmDialog( - message: LocaleKeys.youWantToLeaveMarathon.tr(), - onTap: () { - provider.resetValues(); - Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.youWantToLeaveMarathon.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: () { + provider.resetValues(); + Navigator.of(context).popUntil(ModalRoute.withName(AppRoutes.dashboard)); + }, ); }, ), diff --git a/lib/ui/misc/request_submit_screen.dart b/lib/ui/misc/request_submit_screen.dart index ceba8bec..230223bb 100644 --- a/lib/ui/misc/request_submit_screen.dart +++ b/lib/ui/misc/request_submit_screen.dart @@ -21,7 +21,7 @@ import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:mohem_flutter_app/widgets/button/simple_button.dart'; import 'package:mohem_flutter_app/widgets/circular_avatar.dart'; -import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/image_picker.dart'; import 'package:mohem_flutter_app/widgets/input_widget.dart'; @@ -216,15 +216,14 @@ class _RequestSubmitScreenState extends State { return; } - showDialog( - context: context, - builder: (cxt) => ConfirmDialog( - message: LocaleKeys.areYouSureYouWantToSubmit.tr(), - onTap: () { - Navigator.pop(context); - submitRequest(); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.areYouSureYouWantToSubmit.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: () { + submitRequest(); + }, ); }, ).insideContainer, diff --git a/lib/ui/my_team/employee_details.dart b/lib/ui/my_team/employee_details.dart index e78c304f..87172ac7 100644 --- a/lib/ui/my_team/employee_details.dart +++ b/lib/ui/my_team/employee_details.dart @@ -18,6 +18,7 @@ import 'package:mohem_flutter_app/models/my_team/get_employee_subordinates_list. import 'package:mohem_flutter_app/models/profile_menu.model.dart'; import 'package:mohem_flutter_app/models/worklist/get_favorite_replacements_model.dart'; import 'package:mohem_flutter_app/models/worklist/replacement_list_model.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/circular_avatar.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -217,19 +218,19 @@ class _EmployeeDetailsState extends State { } void showFavoriteAlertDialog(BuildContext context) { - Widget cancelButton = TextButton( - child: Text( - LocaleKeys.cancel.tr(), - ), - onPressed: () { - Navigator.pop(context); - }, - ); - Widget continueButton = TextButton( - child: Text( - LocaleKeys.ok.tr(), - ), - onPressed: () { + ConfirmBottomSheet.show( + context, + message: LocaleKeys.addFavoriteList.tr(namedArgs: {'name': '${getEmployeeSubordinates!.eMPLOYEENAME}'}), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + avatar: getEmployeeSubordinates!.eMPLOYEEIMAGE == null + ? SvgPicture.asset("assets/images/user.svg", height: 68, width: 68) + : CircleAvatar( + radius: 68, + backgroundImage: MemoryImage(Utils.dataFromBase64String(getEmployeeSubordinates!.eMPLOYEEIMAGE!)), + backgroundColor: Colors.black, + ), + onConfirm: () { fetchChangeFav( email: getEmployeeSubordinates?.eMPLOYEEEMAILADDRESS ?? "", employeName: getEmployeeSubordinates!.eMPLOYEENAME ?? "", @@ -238,54 +239,6 @@ class _EmployeeDetailsState extends State { isFav: true, ); setState(() {}); - Navigator.pop(context); - }, - ); - AlertDialog alert = AlertDialog( - title: Text( - LocaleKeys.confirm.tr(), - ), - content: Container( - height: 150, - child: Column( - children: [ - Text(LocaleKeys.addFavoriteList).tr(namedArgs: {'name': '${getEmployeeSubordinates!.eMPLOYEENAME}'}), - getEmployeeSubordinates!.eMPLOYEEIMAGE == null - ? SvgPicture.asset( - "assets/images/user.svg", - height: 68, - width: 68, - ) - : CircleAvatar( - radius: 68, - backgroundImage: MemoryImage(Utils.dataFromBase64String(getEmployeeSubordinates!.eMPLOYEEIMAGE!)), - backgroundColor: Colors.black, - ), - // Image.memory( - // Utils.getPostBytes( - // getEmployeeSubordinates!.eMPLOYEEIMAGE ?? "", - // ), - // errorBuilder: (BuildContext context, error, stackTrace) { - // return SvgPicture.asset( - // "assets/images/user.svg", height: 50, width: 50, - // ); - // }, - // width: 50, - // height: 50, - // fit: BoxFit.cover, - // ).circle(50).paddingOnly(top: 21), - ], - ), - ), - actions: [ - cancelButton, - continueButton, - ], - ); - showDialog( - context: context, - builder: (BuildContext context) { - return alert; }, ); } diff --git a/lib/ui/profile/family_members.dart b/lib/ui/profile/family_members.dart index 06344989..b41bd502 100644 --- a/lib/ui/profile/family_members.dart +++ b/lib/ui/profile/family_members.dart @@ -533,28 +533,27 @@ class _FamilyMembersState extends State { } void showUpdateAlertDialog(BuildContext context, int relationId, int flag, String actionType) { - Widget cancelButton = TextButton(child: LocaleKeys.cancel.tr().toText14(), onPressed: () => Navigator.pop(context)); - Widget continueButton = TextButton( - child: LocaleKeys.ok.tr().toText14(), - onPressed: () { - Navigator.pop(context); + ConfirmBottomSheet.show( + context, + message: LocaleKeys.updateMember.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: () { Navigator.pushNamed(context, AppRoutes.addUpdateFamilyMember, arguments: FamilyMemberScreenParams(relationId, flag: 2, actionType: "UPDATE")).then((_) => getEmployeeContacts()); }, ); - AlertDialog alert = AlertDialog(title: LocaleKeys.confirm.tr().toText16(isBold: true), content: LocaleKeys.updateMember.tr().toText14(), actions: [cancelButton, continueButton]); - showDialog(context: context, builder: (BuildContext context) => alert); } void showRemoveAlertDialog(BuildContext context, int relationId) { - Widget cancelButton = TextButton(child: LocaleKeys.cancel.tr().toText14(), onPressed: () => Navigator.pop(context)); - Widget continueButton = TextButton( - child: LocaleKeys.ok.tr().toText14(), - onPressed: () { - Navigator.pop(context); + ConfirmBottomSheet.show( + context, + message: LocaleKeys.removeThisMember.tr(), + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + isDestructive: true, + onConfirm: () { Navigator.pushNamed(context, AppRoutes.deleteFamilyMember, arguments: relationId).then((_) => getEmployeeContacts()); }, ); - AlertDialog alert = AlertDialog(title: LocaleKeys.confirm.tr().toText16(isBold: true), content: LocaleKeys.removeThisMember.tr().toText14(), actions: [cancelButton, continueButton]); - showDialog(context: context, builder: (BuildContext context) => alert); } } diff --git a/lib/ui/screens/mowadhafhi/request_details.dart b/lib/ui/screens/mowadhafhi/request_details.dart index a104f511..f4d8ba76 100644 --- a/lib/ui/screens/mowadhafhi/request_details.dart +++ b/lib/ui/screens/mowadhafhi/request_details.dart @@ -17,7 +17,7 @@ import 'package:mohem_flutter_app/models/mowadhafhi/get_ticket_transactions.dart import 'package:mohem_flutter_app/models/mowadhafhi/get_transaction_attachment_model.dart'; import 'package:mohem_flutter_app/ui/screens/mowadhafhi/view_transaction_attachment.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; -import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:mohem_flutter_app/widgets/toast/app_toast.dart'; import 'package:open_filex/open_filex.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -283,15 +283,7 @@ class _RequestDetailsState extends State { Utils.showToast("Cannot open file."); } } else { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: "You need to give storage permission to view files.", - onTap: () { - Navigator.pop(context); - }, - ), - ); + AppToast.show("You need to give storage permission to view files."); } }); } diff --git a/lib/ui/work_list/worklist_detail_screen.dart b/lib/ui/work_list/worklist_detail_screen.dart index 6bf8ea90..796acbe4 100644 --- a/lib/ui/work_list/worklist_detail_screen.dart +++ b/lib/ui/work_list/worklist_detail_screen.dart @@ -46,7 +46,7 @@ import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:mohem_flutter_app/widgets/dialogs/accept_reject_input_dialog.dart'; -import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; class WorkListDetailScreen extends StatefulWidget { WorkListDetailScreen({Key? key}) : super(key: key); @@ -606,24 +606,23 @@ class _WorkListDetailScreenState extends State { } Future performNetworkCall(BuildContext context, {String? email, String? userId}) async { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: LocaleKeys.wantToReject.tr(), - okTitle: LocaleKeys.reject.tr(), - onTap: () async { - Navigator.pop(cxt); - Utils.showLoading(context); - try { - await WorkListApiClient().submitComment(comment: "", email: email, userId: userId, notificationId: workListData!.nOTIFICATIONID, apiMode: "REJECT"); - Utils.hideLoading(context); - Navigator.pop(context, "delegate_reload"); - } catch (ex) { - Utils.hideLoading(context); - Utils.handleException(ex, context, null); - } - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.wantToReject.tr(), + confirmText: LocaleKeys.reject.tr(), + cancelText: LocaleKeys.cancel.tr(), + isDestructive: true, + onConfirm: () async { + Utils.showLoading(context); + try { + await WorkListApiClient().submitComment(comment: "", email: email, userId: userId, notificationId: workListData!.nOTIFICATIONID, apiMode: "REJECT"); + Utils.hideLoading(context); + Navigator.pop(context, "delegate_reload"); + } catch (ex) { + Utils.hideLoading(context); + Utils.handleException(ex, context, null); + } + }, ); } diff --git a/lib/widgets/bottom_sheets/confirm_bottom_sheet.dart b/lib/widgets/bottom_sheets/confirm_bottom_sheet.dart index ebc74888..850fcf44 100644 --- a/lib/widgets/bottom_sheets/confirm_bottom_sheet.dart +++ b/lib/widgets/bottom_sheets/confirm_bottom_sheet.dart @@ -26,6 +26,9 @@ class ConfirmBottomSheet extends StatelessWidget { final Color? confirmButtonTextColor; final Color? cancelButtonColor; final Color? cancelButtonTextColor; + // Optional custom content (e.g. an avatar/photo) rendered below the message, above the + // buttons - for cases that need more than the single IconData `icon` slot can show. + final Widget? avatar; const ConfirmBottomSheet({ Key? key, @@ -44,6 +47,7 @@ class ConfirmBottomSheet extends StatelessWidget { this.confirmButtonTextColor, this.cancelButtonColor, this.cancelButtonTextColor, + this.avatar, }) : super(key: key); /// Convenience launcher - mirrors Utils.confirmDialog's call shape but as a bottom sheet. @@ -64,6 +68,7 @@ class ConfirmBottomSheet extends StatelessWidget { Color? confirmButtonTextColor, Color? cancelButtonColor, Color? cancelButtonTextColor, + Widget? avatar, }) { return showModalBottomSheet( context: context, @@ -86,6 +91,7 @@ class ConfirmBottomSheet extends StatelessWidget { confirmButtonTextColor: confirmButtonTextColor, cancelButtonColor: cancelButtonColor, cancelButtonTextColor: cancelButtonTextColor, + avatar: avatar, ); }, ); @@ -127,6 +133,7 @@ class ConfirmBottomSheet extends StatelessWidget { (title ?? LocaleKeys.confirm.tr()).toText18(isBold: true, isCentered: true, color: MyColors.darkTextColor), 8.height, message.toText14(color: MyColors.grey70Color, textAlign: TextAlign.center), + if (avatar != null) ...[16.height, Center(child: avatar)], 24.height, Row( diff --git a/lib/widgets/mark_attendance_widget.dart b/lib/widgets/mark_attendance_widget.dart index ee258e1d..eb13e00c 100644 --- a/lib/widgets/mark_attendance_widget.dart +++ b/lib/widgets/mark_attendance_widget.dart @@ -17,8 +17,9 @@ import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:mohem_flutter_app/models/privilege_list_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/ui/dialogs/success_dialog.dart'; -import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/dialogs/dialogs.dart'; +import 'package:mohem_flutter_app/widgets/toast/app_toast.dart'; import 'package:mohem_flutter_app/widgets/location/Location.dart' as location; import 'package:mohem_flutter_app/widgets/nfc/nfc_reader_sheet.dart'; import 'package:mohem_flutter_app/widgets/qr_scanner_dialog.dart'; @@ -83,28 +84,19 @@ class _MarkAttendanceWidgetState extends State { if (has) { getHuaweiCurrentLocation(attendanceType); } else { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: "You need to give location permission to mark attendance", - onTap: () { - Navigator.pop(context); - }, - ), - ); + AppToast.show(LocaleKeys.locationPermissionRequiredForAttendance.tr()); } } }); } else { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: "You need to enable location services to mark attendance", - onTap: () async { - Navigator.pop(context); - await Geolocator.openLocationSettings(); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.locationServicesRequiredForAttendance.tr(), + confirmText: LocaleKeys.openSettings.tr(), + showCancelButton: false, + onConfirm: () async { + await Geolocator.openLocationSettings(); + }, ); } }); @@ -119,7 +111,7 @@ class _MarkAttendanceWidgetState extends State { // showDialog( // context: context, // builder: (BuildContext cxt) => ConfirmDialog( - // message: "You need to give location permission to mark attendance", + // message: LocaleKeys.locationPermissionRequiredForAttendance.tr(), // onTap: () { // Navigator.pop(context); // }, @@ -188,28 +180,26 @@ class _MarkAttendanceWidgetState extends State { context, ); } else { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: "You need to give location permission to mark attendance", - onTap: () async { - Navigator.pop(context); - await Geolocator.openAppSettings(); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.locationPermissionRequiredForAttendance.tr(), + confirmText: LocaleKeys.openSettings.tr(), + showCancelButton: false, + onConfirm: () async { + await Geolocator.openAppSettings(); + }, ); } }); } else { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: "You need to enable location services to mark attendance", - onTap: () async { - Navigator.pop(context); - await Geolocator.openLocationSettings(); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.locationServicesRequiredForAttendance.tr(), + confirmText: LocaleKeys.openSettings.tr(), + showCancelButton: false, + onConfirm: () async { + await Geolocator.openLocationSettings(); + }, ); } }); @@ -242,28 +232,26 @@ class _MarkAttendanceWidgetState extends State { context, ); } else { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: "You need to give location permission to mark attendance", - onTap: () async { - Navigator.pop(context); - await Geolocator.openAppSettings(); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.locationPermissionRequiredForAttendance.tr(), + confirmText: LocaleKeys.openSettings.tr(), + showCancelButton: false, + onConfirm: () async { + await Geolocator.openAppSettings(); + }, ); } }); } else { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: "You need to enable location services to mark attendance", - onTap: () async { - Navigator.pop(context); - await Geolocator.openLocationSettings(); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.locationServicesRequiredForAttendance.tr(), + confirmText: LocaleKeys.openSettings.tr(), + showCancelButton: false, + onConfirm: () async { + await Geolocator.openLocationSettings(); + }, ); } }); @@ -296,28 +284,26 @@ class _MarkAttendanceWidgetState extends State { context, ); } else { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: "You need to give location permission to mark attendance", - onTap: () async { - Navigator.pop(context); - await Geolocator.openAppSettings(); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.locationPermissionRequiredForAttendance.tr(), + confirmText: LocaleKeys.openSettings.tr(), + showCancelButton: false, + onConfirm: () async { + await Geolocator.openAppSettings(); + }, ); } }); } else { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: "You need to enable location services to mark attendance", - onTap: () async { - Navigator.pop(context); - await Geolocator.openLocationSettings(); - }, - ), + ConfirmBottomSheet.show( + context, + message: LocaleKeys.locationServicesRequiredForAttendance.tr(), + confirmText: LocaleKeys.openSettings.tr(), + showCancelButton: false, + onConfirm: () async { + await Geolocator.openLocationSettings(); + }, ); } }); @@ -410,15 +396,7 @@ class _MarkAttendanceWidgetState extends State { GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId, isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng); if (g?.messageStatus != 1) { Utils.hideLoading(context); - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: g?.errorEndUserMessage ?? "Unexpected error occurred", - onTap: () { - Navigator.pop(context); - }, - ), - ); + AppToast.show(g?.errorEndUserMessage ?? "Unexpected error occurred"); } else { bool status = await model.fetchAttendanceTracking(context); if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); @@ -442,15 +420,7 @@ class _MarkAttendanceWidgetState extends State { GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? "", isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng); if (g?.messageStatus != 1) { Utils.hideLoading(context); - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: g?.errorEndUserMessage ?? "Unexpected error occurred", - onTap: () { - Navigator.pop(context); - }, - ), - ); + AppToast.show(g?.errorEndUserMessage ?? "Unexpected error occurred"); } else { bool status = await model.fetchAttendanceTracking(context); Utils.hideLoading(context); @@ -512,17 +482,7 @@ class _MarkAttendanceWidgetState extends State { Utils.hideLoading(context); await closeWifiRequest(); if (g?.messageStatus == 2) { - showDialog( - barrierDismissible: true, - context: context, - builder: (cxt) => ConfirmDialog( - message: g?.errorEndUserMessage ?? "", - onTap: () { - Navigator.pop(context); - }, - onCloseTap: () {}, - ), - ); + AppToast.show(g?.errorEndUserMessage ?? ""); } else { showMDialog( context, @@ -566,17 +526,7 @@ class _MarkAttendanceWidgetState extends State { bool status = await model.fetchAttendanceTracking(context); Utils.hideLoading(context); if (g?.messageStatus == 2) { - showDialog( - barrierDismissible: true, - context: context, - builder: (cxt) => ConfirmDialog( - message: g?.errorEndUserMessage ?? "", - onTap: () { - Navigator.pop(context); - }, - onCloseTap: () {}, - ), - ); + AppToast.show(g?.errorEndUserMessage ?? ""); } else { showMDialog( context, diff --git a/lib/widgets/toast/app_toast.dart b/lib/widgets/toast/app_toast.dart new file mode 100644 index 00000000..beac1f23 --- /dev/null +++ b/lib/widgets/toast/app_toast.dart @@ -0,0 +1,141 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/config/routes.dart'; + +/// Self-dismissing toast with a visible countdown, replacing the generic "just closes" +/// ConfirmDialog for pure-info messages. Mounts via the root navigator's Overlay, so callers +/// don't need to pass a BuildContext (avoids the stale-context issues the dialog path has). +class AppToast { + static OverlayEntry? _entry; + + // Single place to retune how long an action-performing toast waits before firing its + // onComplete (either naturally, or immediately if the user taps it early - see _dismiss). + static const int actionSeconds = 4; + + // Duration for the plain toast that replaces Utils.showToast's old Fluttertoast call - + // change this single value to retune it everywhere that uses it. + static const int plainSeconds = 3; + + static void show(String message, {int seconds = 5, VoidCallback? onComplete, bool showIcon = true, bool showCountdownText = true}) { + OverlayState? overlay = AppRoutes.navigatorKey.currentState?.overlay; + if (overlay == null) return; + + // Only one toast on screen at a time - a new call replaces whatever is currently showing. + // Its onComplete (if any) never fires in that case - _dismiss only acts on the entry + // that's still current. + _entry?.remove(); + _entry = null; + + late OverlayEntry entry; + entry = OverlayEntry( + builder: (BuildContext context) => _CountdownToast( + message: message, + seconds: seconds, + showIcon: showIcon, + showCountdownText: showCountdownText, + onDismiss: () => _dismiss(entry, onComplete), + ), + ); + _entry = entry; + overlay.insert(entry); + } + + // Fires on both the timer completing and an early tap - a tap just means "don't make me + // wait", not "cancel this". Guarded so a toast that's already been replaced never fires its + // (now stale) action. + static void _dismiss(OverlayEntry entry, VoidCallback? onComplete) { + if (_entry == entry) { + entry.remove(); + _entry = null; + onComplete?.call(); + } + } +} + +class _CountdownToast extends StatefulWidget { + final String message; + final int seconds; + final VoidCallback onDismiss; + final bool showIcon; + final bool showCountdownText; + + const _CountdownToast({ + required this.message, + required this.seconds, + required this.onDismiss, + this.showIcon = true, + this.showCountdownText = true, + }); + + @override + State<_CountdownToast> createState() => _CountdownToastState(); +} + +class _CountdownToastState extends State<_CountdownToast> { + late int _remaining = widget.seconds; + Timer? _timer; + + @override + void initState() { + super.initState(); + _timer = Timer.periodic(const Duration(seconds: 1), (Timer t) { + setState(() => _remaining--); + if (_remaining <= 0) { + t.cancel(); + widget.onDismiss(); + } + }); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Positioned( + left: 16, + right: 16, + bottom: 32, + child: SafeArea( + child: Material( + color: Colors.transparent, + child: GestureDetector( + onTap: widget.onDismiss, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: MyColors.darkTextColor, + borderRadius: BorderRadius.circular(12), + boxShadow: const [BoxShadow(color: Color(0x33000000), blurRadius: 12, offset: Offset(0, 4))], + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.showIcon) ...[const Icon(Icons.error_outline, color: Colors.white, size: 20), const SizedBox(width: 10)], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(widget.message, style: const TextStyle(color: Colors.white, fontSize: 14, height: 1.3)), + if (widget.showCountdownText) ...[ + const SizedBox(height: 4), + Text("Dismissing in ${_remaining}s", style: const TextStyle(color: Colors.white70, fontSize: 11)), + ], + ], + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +}