diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 59a3a0b..0f9e887 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -602,6 +602,11 @@ "addUsers": "إضافة مستخدمين إلى المجموعة", "admin": "مشرف", "areYouSureWantTodelete": "هل أنت متأكد أنك تريد الحذف؟", + "confirmGrantMazayaAccess": "هل أنت متأكد أنك تريد منح صلاحية مزايا لـ \"{}\"؟", + "confirmRevokeMazayaAccess": "هل أنت متأكد أنك تريد إلغاء صلاحية مزايا لـ \"{}\"؟", + "yes": "نعم", + "no": "لا", + "mazayaInviteInstructions": "يرجى تحميل تطبيق مهمم من الرابط أعلاه واستخدام بيانات الاعتماد التالية لتسجيل الدخول", "Attendance": "الحضور", "audioCall": "مكالمة صوتية", "editGroups": "تعديل المجموعة", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 541ec48..5c3b589 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -549,6 +549,11 @@ "manage": "Manage", "members": "Members", "areYouSureWantTodelete": "Are you sure want to delete?", + "confirmGrantMazayaAccess": "Are you sure you want to grant Mazaya access to \"{}\"?", + "confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?", + "yes": "Yes", + "no": "No", + "mazayaInviteInstructions": "Please download the Mohemm app from the above link and use the following credentials to login", "groupMembers": "Group Members", "manageGroup": "Manage Group", "admin": "Admin", diff --git a/assets/pdf/mazaya_invite_steps_ar.pdf b/assets/pdf/mazaya_invite_steps_ar.pdf new file mode 100644 index 0000000..4603bd3 Binary files /dev/null and b/assets/pdf/mazaya_invite_steps_ar.pdf differ diff --git a/assets/pdf/mazaya_invite_steps_en.pdf b/assets/pdf/mazaya_invite_steps_en.pdf new file mode 100644 index 0000000..4603bd3 Binary files /dev/null and b/assets/pdf/mazaya_invite_steps_en.pdf differ diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index d8d6575..e346378 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -90,8 +90,6 @@ class ApiConsts { //DummyCards for the UI - - static int tabletMinLength = 500; // Notification Center GUIDs for OTP diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index f3c750f..f7c06bf 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -618,6 +618,11 @@ class CodegenLoader extends AssetLoader{ "addUsers": "إضافة مستخدمين إلى المجموعة", "admin": "مشرف", "areYouSureWantTodelete": "هل أنت متأكد أنك تريد الحذف؟", + "confirmGrantMazayaAccess": "هل أنت متأكد أنك تريد منح صلاحية مزايا لـ \"{}\"؟", + "confirmRevokeMazayaAccess": "هل أنت متأكد أنك تريد إلغاء صلاحية مزايا لـ \"{}\"؟", + "yes": "نعم", + "no": "لا", + "mazayaInviteInstructions": "يرجى تحميل تطبيق مهمم من الرابط أعلاه واستخدام بيانات الاعتماد التالية لتسجيل الدخول", "Attendance": "الحضور", "audioCall": "مكالمة صوتية", "editGroups": "تعديل المجموعة", @@ -1349,6 +1354,11 @@ static const Map _en_US = { "manage": "Manage", "members": "Members", "areYouSureWantTodelete": "Are you sure want to delete?", + "confirmGrantMazayaAccess": "Are you sure you want to grant Mazaya access to \"{}\"?", + "confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?", + "yes": "Yes", + "no": "No", + "mazayaInviteInstructions": "Please download the Mohemm app from the above link and use the following credentials to login", "groupMembers": "Group Members", "manageGroup": "Manage Group", "admin": "Admin", diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 064d201..28f0b19 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -590,6 +590,11 @@ abstract class LocaleKeys { static const addUsers = 'addUsers'; static const admin = 'admin'; static const areYouSureWantTodelete = 'areYouSureWantTodelete'; + static const confirmGrantMazayaAccess = 'confirmGrantMazayaAccess'; + static const confirmRevokeMazayaAccess = 'confirmRevokeMazayaAccess'; + static const yes = 'yes'; + static const no = 'no'; + static const mazayaInviteInstructions = 'mazayaInviteInstructions'; static const Attendance = 'Attendance'; static const audioCall = 'audioCall'; static const editGroups = 'editGroups'; diff --git a/lib/ui/landing/wala_plus_retailer_details_screen.dart b/lib/ui/landing/wala_plus_retailer_details_screen.dart index 6496048..a7c8372 100644 --- a/lib/ui/landing/wala_plus_retailer_details_screen.dart +++ b/lib/ui/landing/wala_plus_retailer_details_screen.dart @@ -149,31 +149,56 @@ class _WalaPlusRetailerDetailsScreenState extends State= maxAttempts) { - clearInterval(timer); - } - }, 200); + } + + tryClick(); + + if (window.__mohemmScanObserverActive) return; + window.__mohemmScanObserverActive = true; + + var observer = new MutationObserver(function() { + tryClick(); + }); + observer.observe(document.body, { childList: true, subtree: true }); + + // Safety net only - stop watching after a while so we don't observe forever + // (e.g. permission denied for good, or user left the page). + setTimeout(function() { + observer.disconnect(); + window.__mohemmScanObserverActive = false; + }, 120000); })(); """, ); diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 8cd4c71..b689bbd 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -126,7 +126,6 @@ class _LoginScreenState extends State { GetMobileLoginInfoListModel? loginInfo; GetMobileLoginInfoDependentModel? dependentLoginInfo; - Future checkFirebaseToken() async { try { await checkPrefs(); @@ -217,11 +216,7 @@ class _LoginScreenState extends State { String devType = Platform.isAndroid ? "android" : "ios"; if (ApiConsts.selectedMemberType.isDependent) { - dependentLoginInfo = await LoginApiClient().getMobileLoginInfoNEWDependent( - devToken, - devType, - memberType: MemberType.dependent.value, - ); + dependentLoginInfo = await LoginApiClient().getMobileLoginInfoNEWDependent(devToken, devType, memberType: MemberType.dependent.value); Utils.hideLoading(context); if (dependentLoginInfo != null) { _handleDependentLoginInfo(dependentLoginInfo!); @@ -229,11 +224,7 @@ class _LoginScreenState extends State { return; } - loginInfo = await LoginApiClient().getMobileLoginInfoNEW( - devToken, - devType, - memberType: ApiConsts.selectedMemberType.value, - ); + loginInfo = await LoginApiClient().getMobileLoginInfoNEW(devToken, devType, memberType: ApiConsts.selectedMemberType.value); if (loginInfo == null) { await checkPrefs(); _autoLogin = false; @@ -257,9 +248,7 @@ class _LoginScreenState extends State { nationalId.text = info.dependentID!; } - String? rawDob = (info.birthDate != null && info.birthDate!.isNotEmpty) - ? info.birthDate - : ((info.dob != null && info.dob!.isNotEmpty) ? info.dob : null); + String? rawDob = (info.birthDate != null && info.birthDate!.isNotEmpty) ? info.birthDate : ((info.dob != null && info.dob!.isNotEmpty) ? info.dob : null); if (rawDob != null && rawDob.isNotEmpty) { DateTime? parsed = _parseBirthDate(rawDob); @@ -341,7 +330,6 @@ class _LoginScreenState extends State { dobController.text = DateFormat('yyyy-MM-dd').format(parsed); } } - } Future checkPrefs() async { @@ -434,9 +422,7 @@ class _LoginScreenState extends State { Utils.handleException(ex, context, null); } }, - () => { - Navigator.pop(context), - }, + () => {Navigator.pop(context)}, onResendCode: () { performForgotPassword(); }, @@ -467,13 +453,7 @@ class _LoginScreenState extends State { } } - Widget _buildCtaCard({ - required BuildContext context, - required IconData icon, - required String title, - required String subtitle, - required VoidCallback onTap, - }) { + Widget _buildCtaCard({required BuildContext context, required IconData icon, required String title, required String subtitle, required VoidCallback onTap}) { bool isRtl = Directionality.of(context) == ui.TextDirection.rtl || AppState().isArabic(context); return InkWell( @@ -482,51 +462,30 @@ class _LoginScreenState extends State { child: Container( width: double.infinity, padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: const Color(0xFFEAF8F4), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: const Color(0xFFB8E8DB), width: 1.0), - ), + decoration: BoxDecoration(color: const Color(0xFFEAF8F4), borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFB8E8DB), width: 1.0)), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( width: 40, height: 40, - decoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), + decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle), alignment: Alignment.center, - child: Icon( - icon, - color: const Color(0xFF168A89), - size: 22, - ), + child: Icon(icon, color: const Color(0xFF168A89), size: 22), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - title.toText14( - isBold: true, - color: const Color(0xFF1E293B), - textAlign: isRtl ? TextAlign.right : TextAlign.left, - ), + title.toText14(isBold: true, color: const Color(0xFF1E293B), textAlign: isRtl ? TextAlign.right : TextAlign.left), const SizedBox(height: 4), - subtitle.toText12( - color: const Color(0xFF64748B), - ), + subtitle.toText12(color: const Color(0xFF64748B)), ], ), ), const SizedBox(width: 8), - Icon( - isRtl ? Icons.chevron_left_rounded : Icons.chevron_right_rounded, - color: const Color(0xFF64748B), - size: 20, - ), + Icon(isRtl ? Icons.chevron_left_rounded : Icons.chevron_right_rounded, color: const Color(0xFF64748B), size: 20), ], ), ), @@ -564,16 +523,7 @@ class _LoginScreenState extends State { firstDate: firstDate, lastDate: lastDate, builder: (context, child) { - return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light( - primary: MyColors.textMixColor, - onPrimary: Colors.white, - onSurface: MyColors.darkTextColor, - ), - ), - child: child!, - ); + return Theme(data: Theme.of(context).copyWith(colorScheme: const ColorScheme.light(primary: MyColors.textMixColor, onPrimary: Colors.white, onSurface: MyColors.darkTextColor)), child: child!); }, ); @@ -600,15 +550,7 @@ class _LoginScreenState extends State { name = await Utils.getStringFromPrefs("P_DEPENDENT_NAME"); } if (!mounted) return; - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => MazayaAccessResultScreen( - isGranted: false, - dependentName: name, - ), - ), - ); + Navigator.push(context, MaterialPageRoute(builder: (_) => MazayaAccessResultScreen(isGranted: false, dependentName: name))); } void performDependentLogin() async { @@ -664,10 +606,7 @@ class _LoginScreenState extends State { } } - String? authToken = (depLoginResponse?.authenticationTokenID != null && - depLoginResponse!.authenticationTokenID!.trim().isNotEmpty) - ? depLoginResponse.authenticationTokenID!.trim() - : null; + String? authToken = (depLoginResponse?.authenticationTokenID != null && depLoginResponse!.authenticationTokenID!.trim().isNotEmpty) ? depLoginResponse.authenticationTokenID!.trim() : null; String? logInTokenID = depLoginResponse?.logInTokenID; String? mobileNumber = depLoginResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber; @@ -676,13 +615,7 @@ class _LoginScreenState extends State { // No need to show OTP screen, direct move to dashboard and use AuthenticationTokenID as TokenID for all other requests if (authToken != null && authToken.isNotEmpty) { Utils.hideLoading(context); - _completeDependentLogin( - depLoginResponse, - logInTokenID ?? authToken, - mobileNumber ?? "", - loginType: 1, - navigateToDashboard: true, - ); + _completeDependentLogin(depLoginResponse, logInTokenID ?? authToken, mobileNumber ?? "", loginType: 1, navigateToDashboard: true); return; } @@ -750,13 +683,7 @@ class _LoginScreenState extends State { Navigator.pop(context); - _completeDependentLogin( - checkOtpResponse, - logInTokenID, - mobileNumber, - loginType: 1, - navigateToDashboard: true, - ); + _completeDependentLogin(checkOtpResponse, logInTokenID, mobileNumber, loginType: 1, navigateToDashboard: true); } catch (ex) { if (kDebugMode) print(ex); _pinPutController.clear(); @@ -776,12 +703,7 @@ class _LoginScreenState extends State { onResendCode: () async { Utils.showLoading(context); try { - await LoginApiClient().mohemmSendOTPDependent( - nationalId: nationalId.text.trim(), - mobileNumber: mobileNumber, - logInTokenID: logInTokenID, - context: context, - ); + await LoginApiClient().mohemmSendOTPDependent(nationalId: nationalId.text.trim(), mobileNumber: mobileNumber, logInTokenID: logInTokenID, context: context); Utils.hideLoading(context); Utils.showToast(LocaleKeys.verificationCodeResent.tr()); _showDependentOtpDialog(logInTokenID, mobileNumber); @@ -799,25 +721,20 @@ class _LoginScreenState extends State { } void _completeDependentLogin(GenericResponseModel? checkOtpResponse, String logInTokenID, String mobileNumber, {int loginType = 1, bool navigateToDashboard = true}) { - String effectiveToken = (checkOtpResponse?.authenticationTokenID != null && checkOtpResponse!.authenticationTokenID!.trim().isNotEmpty) - ? checkOtpResponse.authenticationTokenID!.trim() - : ((checkOtpResponse?.tokenID != null && checkOtpResponse!.tokenID!.isNotEmpty) - ? checkOtpResponse.tokenID! - : ((checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty) - ? checkOtpResponse.logInTokenID! - : logInTokenID)); + String effectiveToken = + (checkOtpResponse?.authenticationTokenID != null && checkOtpResponse!.authenticationTokenID!.trim().isNotEmpty) + ? checkOtpResponse.authenticationTokenID!.trim() + : ((checkOtpResponse?.tokenID != null && checkOtpResponse!.tokenID!.isNotEmpty) + ? checkOtpResponse.tokenID! + : ((checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty) ? checkOtpResponse.logInTokenID! : logInTokenID)); AppState().postParamsObject?.setTokenID = effectiveToken; AppState().setAuthenticationTokenID = effectiveToken; - AppState().postParamsObject?.setLogInTokenID = (checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty) - ? checkOtpResponse.logInTokenID! - : logInTokenID; + AppState().postParamsObject?.setLogInTokenID = (checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty) ? checkOtpResponse.logInTokenID! : logInTokenID; AppState().postParamsObject?.setUserName = nationalId.text.trim(); AppState().postParamsObject?.setPUserName = nationalId.text.trim(); - String resolvedMobile = mobileNumber.isNotEmpty - ? mobileNumber - : (checkOtpResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber ?? ""); + String resolvedMobile = mobileNumber.isNotEmpty ? mobileNumber : (checkOtpResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber ?? ""); if (resolvedMobile.isNotEmpty) { AppState().postParamsObject?.setMobileNumer = resolvedMobile; Utils.saveStringFromPrefs("P_DEP_MOBILE_NUMBER", resolvedMobile); @@ -856,7 +773,6 @@ class _LoginScreenState extends State { } } - @override Widget build(BuildContext context) { if (isAppOpenBySystem == null) { @@ -894,12 +810,7 @@ class _LoginScreenState extends State { LocaleKeys.english.tr().toText14(color: AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() { context.setLocale(const Locale("en", "US")); }), - Container( - width: 1, - color: MyColors.darkWhiteColor, - height: 16, - margin: const EdgeInsets.only(left: 10, right: 10), - ), + Container(width: 1, color: MyColors.darkWhiteColor, height: 16, margin: const EdgeInsets.only(left: 10, right: 10)), LocaleKeys.arabic.tr().toText14(color: !AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() { context.setLocale(const Locale("ar", "SA")); }), @@ -913,9 +824,7 @@ class _LoginScreenState extends State { return SingleChildScrollView( physics: const BouncingScrollPhysics(), child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), + constraints: BoxConstraints(minHeight: constraints.maxHeight), child: IntrinsicHeight( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -931,11 +840,9 @@ class _LoginScreenState extends State { 9.height, Align( alignment: AlignmentDirectional.centerEnd, - child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress( - () { - performForgotPassword(); - }, - ), + child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress(() { + performForgotPassword(); + }), ), 20.height, _buildDependentCtaCard(context), @@ -948,21 +855,11 @@ class _LoginScreenState extends State { child: Stack( alignment: AlignmentDirectional.centerEnd, children: [ - IgnorePointer( - child: InputWidget( - LocaleKeys.dateOfBirth.tr(), - "YYYY-MM-DD", - dobController, - ), - ), + IgnorePointer(child: InputWidget(LocaleKeys.dateOfBirth.tr(), "DD-MM-YYYY", dobController)), Positioned.directional( textDirection: Directionality.of(context), end: 16, - child: const Icon( - Icons.calendar_today_outlined, - size: 20, - color: MyColors.grey70Color, - ), + child: const Icon(Icons.calendar_today_outlined, size: 20, color: MyColors.grey70Color), ), ], ), @@ -977,22 +874,19 @@ class _LoginScreenState extends State { ); }, ), - ) + ), ], ), ), ), - DefaultButton( - LocaleKeys.login.tr(), - () async { - SystemChannels.textInput.invokeMethod('TextInput.hide'); - if (ApiConsts.ISDependetLogin) { - performDependentLogin(); - } else { - performLogin(); - } - }, - ).insideContainer + DefaultButton(LocaleKeys.login.tr(), () async { + SystemChannels.textInput.invokeMethod('TextInput.hide'); + if (ApiConsts.ISDependetLogin) { + performDependentLogin(); + } else { + performLogin(); + } + }).insideContainer, ], ), ); diff --git a/lib/ui/profile/family_members.dart b/lib/ui/profile/family_members.dart index 8deddac..0fbbddd 100644 --- a/lib/ui/profile/family_members.dart +++ b/lib/ui/profile/family_members.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mohem_flutter_app/api/profile_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; @@ -11,6 +12,7 @@ import 'package:mohem_flutter_app/models/get_employee_contacts.model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/ui/profile/add_update_family_member.dart'; import 'package:mohem_flutter_app/ui/profile/mazaya_access_result_screen.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:provider/provider.dart'; @@ -59,6 +61,39 @@ class _FamilyMembersState extends State { return flag?.trim().toUpperCase() == 'Y'; } + void _confirmToggleMazayaAccess(GetEmployeeContactsList contact) { + bool isAllowed = _isAccessAllowed(contact); + String dependentName = contact.cONTACTNAME ?? ""; + + ConfirmBottomSheet.show( + context, + title: isAllowed ? LocaleKeys.revokeMazayaAccess.tr() : LocaleKeys.allowMazayaAccess.tr(), + message: isAllowed ? LocaleKeys.confirmRevokeMazayaAccess.tr(args: [dependentName]) : LocaleKeys.confirmGrantMazayaAccess.tr(args: [dependentName]), + icon: isAllowed ? Icons.remove_circle_outline : Icons.check_circle_outline, + iconColor: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89), + // Same gradient as the "Add another user" button on this screen (DefaultButton's default). + confirmButtonColors: const [MyColors.gradiantEndColor, MyColors.gradiantStartColor], + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: () => _toggleMazayaAccess(contact), + ); + } + + void _openMazayaGrantedScreen(GetEmployeeContactsList item) { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext cxt) => MazayaAccessResultScreen( + isGranted: true, + dependentName: item.cONTACTNAME ?? "", + dependentInitials: _getInitials(item.cONTACTNAME), + nationalId: item.nATIONALIDENTIFIER, + dateOfBirth: item.dATEOFBIRTH, + ), + ), + ); + } + Future _toggleMazayaAccess(GetEmployeeContactsList contact) async { bool current = _isAccessAllowed(contact); String action = current ? "Revoke" : "Grant"; @@ -87,6 +122,8 @@ class _FamilyMembersState extends State { isGranted: true, dependentName: contact.cONTACTNAME ?? "", dependentInitials: _getInitials(contact.cONTACTNAME), + nationalId: contact.nATIONALIDENTIFIER, + dateOfBirth: contact.dATEOFBIRTH, ), ), ); @@ -325,8 +362,14 @@ class _FamilyMembersState extends State { bool canDelete, ) { bool isAllowed = _isAccessAllowed(item); + // Access already granted: tapping the card (outside the Revoke link) reopens the same + // "granted" result screen shown right after granting - no need to call the API again. + bool canOpenGrantedScreen = fromMazaya && isAllowed; - return Container( + return InkWell( + borderRadius: BorderRadius.circular(16), + onTap: canOpenGrantedScreen ? () => _openMazayaGrantedScreen(item) : null, + child: Container( padding: const EdgeInsets.fromLTRB(16, 16, 16, 14), decoration: BoxDecoration( color: Colors.white, @@ -430,7 +473,7 @@ class _FamilyMembersState extends State { Center( child: InkWell( borderRadius: BorderRadius.circular(8), - onTap: () => _toggleMazayaAccess(item), + onTap: () => _confirmToggleMazayaAccess(item), child: Padding( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), child: Row( @@ -544,6 +587,7 @@ class _FamilyMembersState extends State { ], ], ), + ), ); } diff --git a/lib/ui/profile/mazaya_access_result_screen.dart b/lib/ui/profile/mazaya_access_result_screen.dart index 2c20179..988b986 100644 --- a/lib/ui/profile/mazaya_access_result_screen.dart +++ b/lib/ui/profile/mazaya_access_result_screen.dart @@ -8,6 +8,7 @@ import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; class MazayaAccessResultParams { @@ -15,12 +16,16 @@ class MazayaAccessResultParams { final String dependentName; final String? dependentInitials; final String? downloadUrl; + final String? nationalId; + final String? dateOfBirth; const MazayaAccessResultParams({ required this.isGranted, required this.dependentName, this.dependentInitials, this.downloadUrl, + this.nationalId, + this.dateOfBirth, }); } @@ -85,6 +90,8 @@ class MazayaAccessResultScreen extends StatelessWidget { final String dependentName; final String? dependentInitials; final String? downloadUrl; + final String? nationalId; + final String? dateOfBirth; const MazayaAccessResultScreen({ Key? key, @@ -92,6 +99,8 @@ class MazayaAccessResultScreen extends StatelessWidget { this.dependentName = "", this.dependentInitials, this.downloadUrl, + this.nationalId, + this.dateOfBirth, }) : super(key: key); static String getDownloadLink() { @@ -129,17 +138,23 @@ class MazayaAccessResultScreen extends StatelessWidget { String effectiveName = dependentName; String? effectiveInitials = dependentInitials; String? effectiveUrl = downloadUrl; + String? effectiveNationalId = nationalId; + String? effectiveDateOfBirth = dateOfBirth; if (routeArgs is MazayaAccessResultParams) { effectiveGranted = routeArgs.isGranted; effectiveName = routeArgs.dependentName; effectiveInitials = routeArgs.dependentInitials; effectiveUrl = routeArgs.downloadUrl; + effectiveNationalId = routeArgs.nationalId; + effectiveDateOfBirth = routeArgs.dateOfBirth; } else if (routeArgs is Map) { effectiveGranted = routeArgs['isGranted'] ?? isGranted; effectiveName = routeArgs['dependentName'] ?? dependentName; effectiveInitials = routeArgs['dependentInitials'] ?? dependentInitials; effectiveUrl = routeArgs['downloadUrl'] ?? downloadUrl; + effectiveNationalId = routeArgs['nationalId'] ?? nationalId; + effectiveDateOfBirth = routeArgs['dateOfBirth'] ?? dateOfBirth; } if (effectiveName.trim().isEmpty) { @@ -210,7 +225,7 @@ class MazayaAccessResultScreen extends StatelessWidget { : _buildRevokedContent(context, isAr, fontFamily, effectiveName, initials), ), ), - _buildBottomButtons(context, isAr, fontFamily, effectiveGranted, linkToShare), + _buildBottomButtons(context, isAr, fontFamily, effectiveGranted, linkToShare, effectiveNationalId ?? "", effectiveDateOfBirth ?? ""), ], ), ), @@ -604,12 +619,41 @@ class MazayaAccessResultScreen extends StatelessWidget { ); } + // Builds the invite text (link + localized instructions + credentials) and shares it + // together with the language-appropriate steps PDF from assets. Falls back to sharing + // just the text if the PDF can't be loaded/copied for any reason. + Future _shareMazayaInvite( + bool isAr, + String linkToShare, + String nationalId, + String dateOfBirth, + ) async { + String message = + "$linkToShare\n\n${LocaleKeys.mazayaInviteInstructions.tr()}\n\n${LocaleKeys.nationalId.tr()}: $nationalId\n${LocaleKeys.dateOfBirth.tr()}: $dateOfBirth"; + + try { + String fileName = isAr ? "mazaya_invite_steps_ar.pdf" : "mazaya_invite_steps_en.pdf"; + ByteData bytes = await rootBundle.load("assets/pdf/$fileName"); + Directory tempDir = await getTemporaryDirectory(); + File pdfFile = File('${tempDir.path}/$fileName'); + await pdfFile.writeAsBytes(bytes.buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes)); + + await SharePlus.instance.share(ShareParams(text: message, files: [XFile(pdfFile.path)])); + } catch (_) { + try { + await SharePlus.instance.share(ShareParams(text: message)); + } catch (_) {} + } + } + Widget _buildBottomButtons( BuildContext context, bool isAr, String fontFamily, bool granted, String linkToShare, + String nationalId, + String dateOfBirth, ) { if (granted) { return Container( @@ -620,11 +664,7 @@ class MazayaAccessResultScreen extends StatelessWidget { children: [ DefaultButton( LocaleKeys.shareDownloadLink.tr(), - () async { - try { - await SharePlus.instance.share(ShareParams(text: linkToShare)); - } catch (_) {} - }, + () => _shareMazayaInvite(isAr, linkToShare, nationalId, dateOfBirth), trailingIconData: isAr ? Icons.arrow_back_rounded : Icons.arrow_forward_rounded, fontSize: 14, isTextExpanded: false, diff --git a/lib/widgets/app_in_app_webview_screen.dart b/lib/widgets/app_in_app_webview_screen.dart index 036cc6e..1c4cf60 100644 --- a/lib/widgets/app_in_app_webview_screen.dart +++ b/lib/widgets/app_in_app_webview_screen.dart @@ -107,17 +107,24 @@ class _AppInAppWebViewScreenState extends State { }, onPermissionRequest: (InAppWebViewController controller, PermissionRequest permissionRequest) async { debugPrint("InAppWebView onPermissionRequest: ${permissionRequest.resources}"); + bool isGranted = false; try { PermissionStatus status = await Permission.camera.status; if (!status.isGranted) { - await Permission.camera.request(); + status = await Permission.camera.request(); } + isGranted = status.isGranted; } catch (e) { debugPrint("Error requesting camera permission: $e"); } + // Retry the page-load click trigger now that the permission result is known - + // e.g. the page may only render the real "Start Scanning" button once camera + // access is resolved. Safe to call again: it's a no-op if already clicked or + // already watching (see _clickStartScanningButton's window-scoped guards). + widget.onPageLoaded?.call(controller); return PermissionResponse( resources: permissionRequest.resources, - action: PermissionResponseAction.GRANT, + action: isGranted ? PermissionResponseAction.GRANT : PermissionResponseAction.DENY, ); }, onProgressChanged: (InAppWebViewController controller, int progress) { diff --git a/pubspec.yaml b/pubspec.yaml index c736622..b476ab7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -188,6 +188,7 @@ flutter: - assets/lottie/ - assets/audio/ - assets/json/ + - assets/pdf/ - assets/images/ - assets/images/worklist/ - assets/images/services_icons/