From b25b39869525c6e9ee006bb042745b6991228a0d Mon Sep 17 00:00:00 2001 From: FaizHashmi Date: Tue, 20 Oct 2026 12:21:32 +0300 Subject: [PATCH] Toast fixed --- lib/api/marathon/marathon_api_client.dart | 203 +++++++++--------- lib/classes/utils.dart | 23 +- lib/provider/dashboard_provider_model.dart | 4 +- lib/ui/etqan_ovr/etqan_create_request.dart | 9 +- .../dashboard_screen_for_dependet.dart | 3 +- lib/ui/login/login_screen.dart | 22 +- .../screens/mowadhafhi/request_details.dart | 3 +- lib/widgets/mark_attendance_widget.dart | 11 +- lib/widgets/toast/app_toast.dart | 9 + 9 files changed, 143 insertions(+), 144 deletions(-) diff --git a/lib/api/marathon/marathon_api_client.dart b/lib/api/marathon/marathon_api_client.dart index e85f8821..2d81c42e 100644 --- a/lib/api/marathon/marathon_api_client.dart +++ b/lib/api/marathon/marathon_api_client.dart @@ -7,7 +7,6 @@ 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'; @@ -105,24 +104,18 @@ class MarathonApiClient { } Future getNextQuestion({required String? questionId, required String marathonId}) async { - Map jsonObject = { - "previousQuestionId": questionId, - "marathonId": marathonId, - }; + Map jsonObject = {"previousQuestionId": questionId, "marathonId": marathonId}; return await ApiClient().postJsonForObject( (json) { MarathonGenericModel marathonModel = MarathonGenericModel.fromJson(json); if (marathonModel.statusCode == 404 || marathonModel.statusCode == 208 || marathonModel.statusCode == 204 || marathonModel.statusCode == 500) { - AppToast.show( + Utils.showToast( marathonModel.message!, - seconds: AppToast.actionSeconds, onComplete: () { AppRoutes.navigatorKey.currentContext!.read().resetValues(); - Navigator.of( - AppRoutes.navigatorKey.currentContext!, - ).popUntil(ModalRoute.withName(AppRoutes.dashboard)); + Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.dashboard)); }, ); } @@ -215,99 +208,99 @@ class MarathonApiClient { return null; } -// late HubConnection hubConnection; -// Future buildHubConnection(BuildContext context, String prizeId) async { -// HttpConnectionOptions httpOptions = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); -// hubConnection = HubConnectionBuilder() -// .withUrl( -// ApiConsts.marathonHubConnectionUrl + "?employeeNumber=${AppState().memberInformationList!.eMPLOYEENUMBER ?? ""}&employeeName=${AppState().memberInformationList!.eMPLOYEENAME ?? ""}", -// options: httpOptions, -// ) -// .withAutomaticReconnect( -// retryDelays: [2000, 5000, 10000, 20000], -// ) -// .configureLogging( -// Logger("Logging"), -// ) -// .build(); -// hubConnection.onclose( -// ({Exception? error}) { -// logger.i("onclose"); -// }, -// ); -// hubConnection.onreconnecting( -// ({Exception? error}) { -// logger.i("onreconnecting"); -// }, -// ); -// hubConnection.onreconnected( -// ({String? connectionId}) { -// logger.i("onreconnected"); -// }, -// ); -// if (hubConnection.state != HubConnectionState.Connected) { -// await hubConnection.start(); -// logger.i("Started HubConnection"); -// -// await hubConnection.invoke( -// "AddParticipant", -// args: [ -// { -// "employeeNumber": AppState().memberInformationList!.eMPLOYEENUMBER ?? "", -// "employeeName": AppState().memberInformationList!.eMPLOYEENAME ?? "", -// "marathonId": AppState().getMarathonProjectId, -// "prizeId": "8577B2E8-5DD7-43F0-10DD-08DACB0AC064", -// } -// ], -// ).catchError((e) { -// logger.i("Error in AddParticipant: $e"); -// }); -// -// context.read().addItemToList(ApiConsts.dummyQuestion); -// -// await hubConnection.invoke( -// "SendQuestionToParticipant", -// args: [ -// { -// "marathonId": "${AppState().getMarathonProjectId}", -// } -// ], -// ).catchError((e) { -// Utils.confirmDialog(context, e.toString()); -// logger.i("Error in SendQuestionToParticipant: $e"); -// }); -// -// try { -// hubConnection.on("OnSendQuestionToParticipant", (List? arguments) { -// onSendQuestionToParticipant(arguments, context); -// }); -// } catch (e, s) { -// logger.i("Error in OnSendQuestionToParticipant"); -// } -// -// try { -// hubConnection.on("OnParticipantJoin", (List? arguments) { -// onParticipantJoin(arguments, context); -// }); -// } catch (e, s) { -// logger.i("Error in OnParticipantJoin"); -// } -// } -// } -// -// Future onSendQuestionToParticipant(List? arguments, BuildContext context) async { -// logger.i("onSendQuestionToParticipant arguments: $arguments"); -// -// if (arguments != null) { -// Map data = arguments.first! as Map; -// var json = data["data"]; -// QuestionModel newQuestion = QuestionModel.fromJson(json); -// AppRoutes.navigatorKey.currentContext!.read().onNewQuestionReceived(newQuestion); -// } -// } -// -// Future onParticipantJoin(List? arguments, BuildContext context) async { -// logger.i("OnParticipantJoin arguments: $arguments"); -// context.watch().totalMarathoners++; -// } + // late HubConnection hubConnection; + // Future buildHubConnection(BuildContext context, String prizeId) async { + // HttpConnectionOptions httpOptions = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); + // hubConnection = HubConnectionBuilder() + // .withUrl( + // ApiConsts.marathonHubConnectionUrl + "?employeeNumber=${AppState().memberInformationList!.eMPLOYEENUMBER ?? ""}&employeeName=${AppState().memberInformationList!.eMPLOYEENAME ?? ""}", + // options: httpOptions, + // ) + // .withAutomaticReconnect( + // retryDelays: [2000, 5000, 10000, 20000], + // ) + // .configureLogging( + // Logger("Logging"), + // ) + // .build(); + // hubConnection.onclose( + // ({Exception? error}) { + // logger.i("onclose"); + // }, + // ); + // hubConnection.onreconnecting( + // ({Exception? error}) { + // logger.i("onreconnecting"); + // }, + // ); + // hubConnection.onreconnected( + // ({String? connectionId}) { + // logger.i("onreconnected"); + // }, + // ); + // if (hubConnection.state != HubConnectionState.Connected) { + // await hubConnection.start(); + // logger.i("Started HubConnection"); + // + // await hubConnection.invoke( + // "AddParticipant", + // args: [ + // { + // "employeeNumber": AppState().memberInformationList!.eMPLOYEENUMBER ?? "", + // "employeeName": AppState().memberInformationList!.eMPLOYEENAME ?? "", + // "marathonId": AppState().getMarathonProjectId, + // "prizeId": "8577B2E8-5DD7-43F0-10DD-08DACB0AC064", + // } + // ], + // ).catchError((e) { + // logger.i("Error in AddParticipant: $e"); + // }); + // + // context.read().addItemToList(ApiConsts.dummyQuestion); + // + // await hubConnection.invoke( + // "SendQuestionToParticipant", + // args: [ + // { + // "marathonId": "${AppState().getMarathonProjectId}", + // } + // ], + // ).catchError((e) { + // Utils.confirmDialog(context, e.toString()); + // logger.i("Error in SendQuestionToParticipant: $e"); + // }); + // + // try { + // hubConnection.on("OnSendQuestionToParticipant", (List? arguments) { + // onSendQuestionToParticipant(arguments, context); + // }); + // } catch (e, s) { + // logger.i("Error in OnSendQuestionToParticipant"); + // } + // + // try { + // hubConnection.on("OnParticipantJoin", (List? arguments) { + // onParticipantJoin(arguments, context); + // }); + // } catch (e, s) { + // logger.i("Error in OnParticipantJoin"); + // } + // } + // } + // + // Future onSendQuestionToParticipant(List? arguments, BuildContext context) async { + // logger.i("onSendQuestionToParticipant arguments: $arguments"); + // + // if (arguments != null) { + // Map data = arguments.first! as Map; + // var json = data["data"]; + // QuestionModel newQuestion = QuestionModel.fromJson(json); + // AppRoutes.navigatorKey.currentContext!.read().onNewQuestionReceived(newQuestion); + // } + // } + // + // Future onParticipantJoin(List? arguments, BuildContext context) async { + // logger.i("OnParticipantJoin arguments: $arguments"); + // context.watch().totalMarathoners++; + // } } diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index 548dec47..fe54c432 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -38,14 +38,18 @@ class Utils { // Callers showing a value straight from an API response should pass isSuccess: response's // MessageStatus == 1; everywhere else (validation messages, caught errors) just defaults to // red, and the handful of hardcoded success confirmations pass isSuccess: true explicitly. - static void showToast(String message, {bool longDuration = true, bool isSuccess = false}) { + // onComplete makes this an action-toast (longer actionSeconds duration + icon shown) instead + // of a plain info toast - this is the single calling point, every caller should go through + // here instead of calling AppToast.show directly, so the color/duration/icon rules in one place. + static void showToast(String message, {bool longDuration = true, bool isSuccess = false, VoidCallback? onComplete}) { AppToast.show( message, - seconds: AppToast.plainSeconds, - showIcon: false, + seconds: onComplete != null ? AppToast.actionSeconds : AppToast.plainSeconds, + showIcon: onComplete != null, showCountdownText: true, - bgColor: isSuccess ? const Color(0xFFECFDF5) : const Color(0xFFF1CECE), - textColor: isSuccess ? const Color(0xFF059669) : const Color(0xFFC53030), + bgColor: isSuccess ? AppToast.successBgColor : AppToast.errorBgColor, + textColor: isSuccess ? AppToast.successTextColor : AppToast.errorTextColor, + onComplete: onComplete, ); } @@ -167,9 +171,8 @@ class Utils { onErrorMessage(errorMessage); } else { if (!AppState().isAuthenticated) { - AppToast.show( + showToast( errorMessage, - seconds: AppToast.actionSeconds, onComplete: () { Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.login, (Route route) => false); }, @@ -191,9 +194,11 @@ class Utils { static void confirmDialog(cxt, String message, {VoidCallback? 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. + // toast replaces the old "OK just closes" dialog instead of requiring a tap. Every current + // caller of this path passes an error/warning message (catch blocks, location/fake-location + // warnings, session errors) - never success - so red is correct here. if (onTap == null) { - AppToast.show(message); + showToast(message); } else { showDialog(context: cxt, builder: (BuildContext cxt) => ConfirmDialog(message: message, onTap: onTap)); } diff --git a/lib/provider/dashboard_provider_model.dart b/lib/provider/dashboard_provider_model.dart index f4414b98..abc3d709 100644 --- a/lib/provider/dashboard_provider_model.dart +++ b/lib/provider/dashboard_provider_model.dart @@ -27,7 +27,6 @@ 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/toast/app_toast.dart'; /// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool // ignore: prefer_mixin @@ -190,9 +189,8 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); Utils.handleException(ex, context, (err) { Utils.hideLoading(context); - AppToast.show( + Utils.showToast( err, - seconds: AppToast.actionSeconds, onComplete: () { Navigator.pushNamedAndRemoveUntil(context, AppRoutes.login, (Route route) => false); }, diff --git a/lib/ui/etqan_ovr/etqan_create_request.dart b/lib/ui/etqan_ovr/etqan_create_request.dart index 41b4473a..8ab7380b 100644 --- a/lib/ui/etqan_ovr/etqan_create_request.dart +++ b/lib/ui/etqan_ovr/etqan_create_request.dart @@ -14,7 +14,6 @@ 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 { @@ -88,11 +87,11 @@ class _EtqanOvrCreateRequestState extends State { DefaultButton(LocaleKeys.submit.tr(), () async { provider.setDescription(_descriptionController.text); if (provider.selectedProject == null) { - AppToast.show(LocaleKeys.pleaseSelectProject.tr()); + Utils.showToast(LocaleKeys.pleaseSelectProject.tr()); return; } if (!provider.isDescriptionValid()) { - AppToast.show(LocaleKeys.pleaseEnterDescription.tr()); + Utils.showToast(LocaleKeys.pleaseEnterDescription.tr()); return; } Map? ticketInfo = await provider.submitRequest(context); @@ -101,9 +100,9 @@ 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'; - AppToast.show( + Utils.showToast( successMessage, - seconds: AppToast.actionSeconds, + isSuccess: true, onComplete: () { Navigator.pop(context); Navigator.pop(context); diff --git a/lib/ui/landing/dashboard_screen_for_dependet.dart b/lib/ui/landing/dashboard_screen_for_dependet.dart index fbe91024..e6bf6910 100644 --- a/lib/ui/landing/dashboard_screen_for_dependet.dart +++ b/lib/ui/landing/dashboard_screen_for_dependet.dart @@ -22,7 +22,6 @@ import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dar import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:mohem_flutter_app/widgets/info_chip_widget.dart'; import 'package:mohem_flutter_app/widgets/location/Location.dart' as location; -import 'package:mohem_flutter_app/widgets/toast/app_toast.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shimmer/shimmer.dart'; @@ -429,7 +428,7 @@ class _DashboardScreenForDependetState extends State if (!hasPermission) { bool granted = await _requestLocationPermission(); if (!granted) { - AppToast.show(LocaleKeys.locationPermissionRequiredForNearestOffers.tr()); + Utils.showToast(LocaleKeys.locationPermissionRequiredForNearestOffers.tr()); return false; } } diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 6e440f64..057420cd 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -20,7 +20,6 @@ import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/dialogs/otp_dialog.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; -import 'package:mohem_flutter_app/widgets/toast/app_toast.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; @@ -429,7 +428,7 @@ class _LoginScreenState extends State { }, ); } else { - AppToast.show(msg); + Utils.showToast(msg); } }); } @@ -596,16 +595,15 @@ class _LoginScreenState extends State { } void _navigateToMazayaAccessRevoked([String? depName, String? message]) async { - - await Utils.removeStringFromPrefs("P_DEP_IS_LOGGED_IN"); - await Utils.removeStringFromPrefs("P_DEP_NATIONAL_ID"); - await Utils.removeStringFromPrefs("P_DEP_BIRTH_DATE"); - await Utils.removeStringFromPrefs("P_DEP_TOKEN_ID"); - await Utils.removeStringFromPrefs("P_DEP_LOGIN_TOKEN_ID"); - await Utils.removeStringFromPrefs("P_DEP_AUTHENTICATION_TOKEN_ID"); - await Utils.removeStringFromPrefs("P_DEP_MOBILE_NUMBER"); - await Utils.removeStringFromPrefs("P_DEPENDENT_NAME"); - await Utils.removeStringFromPrefs("P_DEP_LOGIN_TYPE"); + await Utils.removeStringFromPrefs("P_DEP_IS_LOGGED_IN"); + await Utils.removeStringFromPrefs("P_DEP_NATIONAL_ID"); + await Utils.removeStringFromPrefs("P_DEP_BIRTH_DATE"); + await Utils.removeStringFromPrefs("P_DEP_TOKEN_ID"); + await Utils.removeStringFromPrefs("P_DEP_LOGIN_TOKEN_ID"); + await Utils.removeStringFromPrefs("P_DEP_AUTHENTICATION_TOKEN_ID"); + await Utils.removeStringFromPrefs("P_DEP_MOBILE_NUMBER"); + await Utils.removeStringFromPrefs("P_DEPENDENT_NAME"); + await Utils.removeStringFromPrefs("P_DEP_LOGIN_TYPE"); ApiConsts.selectedMemberType = MemberType.employee; ApiConsts.ISDependetLogin = false; AppState().setDependentName = null; diff --git a/lib/ui/screens/mowadhafhi/request_details.dart b/lib/ui/screens/mowadhafhi/request_details.dart index f4d8ba76..ec63f67f 100644 --- a/lib/ui/screens/mowadhafhi/request_details.dart +++ b/lib/ui/screens/mowadhafhi/request_details.dart @@ -17,7 +17,6 @@ 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/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,7 +282,7 @@ class _RequestDetailsState extends State { Utils.showToast("Cannot open file."); } } else { - AppToast.show("You need to give storage permission to view files."); + Utils.showToast("You need to give storage permission to view files."); } }); } diff --git a/lib/widgets/mark_attendance_widget.dart b/lib/widgets/mark_attendance_widget.dart index eb13e00c..7c44038c 100644 --- a/lib/widgets/mark_attendance_widget.dart +++ b/lib/widgets/mark_attendance_widget.dart @@ -19,7 +19,6 @@ 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/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'; @@ -84,7 +83,7 @@ class _MarkAttendanceWidgetState extends State { if (has) { getHuaweiCurrentLocation(attendanceType); } else { - AppToast.show(LocaleKeys.locationPermissionRequiredForAttendance.tr()); + Utils.showToast(LocaleKeys.locationPermissionRequiredForAttendance.tr()); } } }); @@ -396,7 +395,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); - AppToast.show(g?.errorEndUserMessage ?? "Unexpected error occurred"); + Utils.showToast(g?.errorEndUserMessage ?? "Unexpected error occurred"); } else { bool status = await model.fetchAttendanceTracking(context); if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); @@ -420,7 +419,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); - AppToast.show(g?.errorEndUserMessage ?? "Unexpected error occurred"); + Utils.showToast(g?.errorEndUserMessage ?? "Unexpected error occurred"); } else { bool status = await model.fetchAttendanceTracking(context); Utils.hideLoading(context); @@ -482,7 +481,7 @@ class _MarkAttendanceWidgetState extends State { Utils.hideLoading(context); await closeWifiRequest(); if (g?.messageStatus == 2) { - AppToast.show(g?.errorEndUserMessage ?? ""); + Utils.showToast(g?.errorEndUserMessage ?? ""); } else { showMDialog( context, @@ -526,7 +525,7 @@ class _MarkAttendanceWidgetState extends State { bool status = await model.fetchAttendanceTracking(context); Utils.hideLoading(context); if (g?.messageStatus == 2) { - AppToast.show(g?.errorEndUserMessage ?? ""); + Utils.showToast(g?.errorEndUserMessage ?? ""); } else { showMDialog( context, diff --git a/lib/widgets/toast/app_toast.dart b/lib/widgets/toast/app_toast.dart index af177e77..0217861a 100644 --- a/lib/widgets/toast/app_toast.dart +++ b/lib/widgets/toast/app_toast.dart @@ -18,6 +18,15 @@ class AppToast { // change this single value to retune it everywhere that uses it. static const int plainSeconds = 3; + // Shared success/error colors - same combo as dependent_details_screen.dart's + // "Active"/"Access Off" badges. Any direct AppToast.show(...) call (not going through + // Utils.showToast) must pass these explicitly, otherwise it silently falls back to the + // plain dark/white style below regardless of whether it's reporting success or failure. + static const Color errorBgColor = Color(0xFFF1CECE); + static const Color errorTextColor = Color(0xFFC53030); + static const Color successBgColor = Color(0xFFECFDF5); + static const Color successTextColor = Color(0xFF059669); + static void show(String message, {int seconds = 5, VoidCallback? onComplete, bool showIcon = true, bool showCountdownText = true, Color? bgColor, Color? textColor}) { OverlayState? overlay = AppRoutes.navigatorKey.currentState?.overlay; if (overlay == null) return;