diff --git a/assets/pdf/Mazaya Access Guide CS ar.pdf b/assets/pdf/Mazaya Access Guide CS ar.pdf new file mode 100644 index 0000000..6556f4c Binary files /dev/null and b/assets/pdf/Mazaya Access Guide CS ar.pdf differ diff --git a/assets/pdf/Mazaya Access Guide English.pdf b/assets/pdf/Mazaya Access Guide CS en.pdf similarity index 97% rename from assets/pdf/Mazaya Access Guide English.pdf rename to assets/pdf/Mazaya Access Guide CS en.pdf index 0df5aab..b7d767b 100644 Binary files a/assets/pdf/Mazaya Access Guide English.pdf and b/assets/pdf/Mazaya Access Guide CS en.pdf differ diff --git a/assets/pdf/Mazaya Access Guide HMG ar.pdf b/assets/pdf/Mazaya Access Guide HMG ar.pdf new file mode 100644 index 0000000..d773527 Binary files /dev/null and b/assets/pdf/Mazaya Access Guide HMG ar.pdf differ diff --git a/assets/pdf/Mazaya Access Guide Arabic.pdf b/assets/pdf/Mazaya Access Guide HMG en.pdf similarity index 97% rename from assets/pdf/Mazaya Access Guide Arabic.pdf rename to assets/pdf/Mazaya Access Guide HMG en.pdf index 0df5aab..c7b2911 100644 Binary files a/assets/pdf/Mazaya Access Guide Arabic.pdf and b/assets/pdf/Mazaya Access Guide HMG en.pdf differ diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index 99e7207..7f906f4 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -149,7 +149,7 @@ class Utils { } String errorMessage; - if (exception.error.errorType != null && exception.error.errorType == 4) { + if (exception is APIException && exception.error?.errorType == 4) { Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.appUpdateScreen, (_) => false, arguments: exception.error?.errorMessage); } else { if (exception is APIException) { diff --git a/lib/ui/profile/dependent_details_screen.dart b/lib/ui/profile/dependent_details_screen.dart index a0c8a81..b84b2d2 100644 --- a/lib/ui/profile/dependent_details_screen.dart +++ b/lib/ui/profile/dependent_details_screen.dart @@ -1,6 +1,10 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.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/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; @@ -8,6 +12,9 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/models/get_employee_contacts.model.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:path_provider/path_provider.dart'; +import 'package:share_plus/share_plus.dart'; class _ShieldIconPainter extends CustomPainter { final Color color; @@ -17,12 +24,13 @@ class _ShieldIconPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - Paint paint = Paint() - ..color = color - ..style = PaintingStyle.stroke - ..strokeWidth = 2.0 - ..strokeCap = StrokeCap.round - ..strokeJoin = StrokeJoin.round; + Paint paint = + Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round; double w = size.width; double h = size.height; @@ -44,28 +52,25 @@ class _ShieldIconPainter extends CustomPainter { checkPath.lineTo(w * 0.66, h * 0.39); canvas.drawPath(checkPath, paint); } else { - canvas.drawLine( - Offset(w * 0.38, h * 0.38), - Offset(w * 0.62, h * 0.62), - paint, - ); - canvas.drawLine( - Offset(w * 0.62, h * 0.38), - Offset(w * 0.38, h * 0.62), - paint, - ); + canvas.drawLine(Offset(w * 0.38, h * 0.38), Offset(w * 0.62, h * 0.62), paint); + canvas.drawLine(Offset(w * 0.62, h * 0.38), Offset(w * 0.38, h * 0.62), paint); } } @override - bool shouldRepaint(covariant _ShieldIconPainter oldDelegate) => - color != oldDelegate.color || isGranted != oldDelegate.isGranted; + bool shouldRepaint(covariant _ShieldIconPainter oldDelegate) => color != oldDelegate.color || isGranted != oldDelegate.isGranted; } class DependentDetailsScreen extends StatefulWidget { final GetEmployeeContactsList? contact; - const DependentDetailsScreen({Key? key, this.contact}) : super(key: key); + // True when this screen is being opened right after a successful grant (e.g. from the + // dependents list's inline "Allow Mazaya Access" link), so the success badge shows on + // first build. Granting from within this screen itself (_performToggleAccess) sets the + // equivalent local state directly instead of needing this. + final bool justGranted; + + const DependentDetailsScreen({Key? key, this.contact, this.justGranted = false}) : super(key: key); @override _DependentDetailsScreenState createState() => _DependentDetailsScreenState(); @@ -74,6 +79,7 @@ class DependentDetailsScreen extends StatefulWidget { class _DependentDetailsScreenState extends State { GetEmployeeContactsList? _contact; bool _argsLoaded = false; + bool _justGranted = false; @override void didChangeDependencies() { @@ -89,6 +95,7 @@ class _DependentDetailsScreenState extends State { _contact = args['contact'] as GetEmployeeContactsList; } } + _justGranted = widget.justGranted; _argsLoaded = true; } } @@ -141,15 +148,7 @@ class _DependentDetailsScreenState extends State { DateTime? dt = DateTime.tryParse(s); if (dt != null) return DateTime(dt.year, dt.month, dt.day); - List formats = [ - 'yyyy-MM-dd', - 'dd/MM/yyyy', - 'MM/dd/yyyy', - 'dd-MM-yyyy', - 'yyyy/MM/dd', - 'dd-MMM-yyyy', - 'dd MMM yyyy', - ]; + List formats = ['yyyy-MM-dd', 'dd/MM/yyyy', 'MM/dd/yyyy', 'dd-MM-yyyy', 'yyyy/MM/dd', 'dd-MMM-yyyy', 'dd MMM yyyy']; for (String fmt in formats) { try { DateTime parsed = DateFormat(fmt, 'en_US').parseStrict(s); @@ -185,14 +184,9 @@ class _DependentDetailsScreenState extends State { ConfirmBottomSheet.show( context, title: isAllowed ? LocaleKeys.revokeMazayaAccess.tr() : LocaleKeys.allowMazayaAccess.tr(), - message: isAllowed - ? LocaleKeys.confirmRevokeMazayaAccess.tr(args: [dependentName]) - : LocaleKeys.confirmGrantMazayaAccess.tr(args: [dependentName]), + 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), - confirmButtonColors: isAllowed - ? const [Color(0xFFEF4444), Color(0xFFDC2626)] - : const [Color(0xFF22B0A6), Color(0xFF168A89)], confirmText: LocaleKeys.yes.tr(), cancelText: LocaleKeys.no.tr(), onConfirm: _performToggleAccess, @@ -216,27 +210,19 @@ class _DependentDetailsScreenState extends State { setState(() { _contact!.mAZAYAACCESSFLG = action == "Grant" ? "Y" : "N"; _contact!.mazayaAccess = _contact!.mAZAYAACCESSFLG; + // Tick badge reflects the most recent action, not just "was granted at some point" - + // a later revoke clears it so a stale grant doesn't linger in state. + _justGranted = action == "Grant"; }); Utils.hideLoading(context); + // Stay on this same screen either way - it already reflects the new access state via + // setState above (status badge, action button, and the share-link section all update + // in place), so tapping a dependent card and successfully granting access both land on + // the exact same screen instead of a separate "success" screen. if (mounted) { - if (action == "Grant") { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext cxt) => MazayaAccessResultScreen( - isGranted: true, - dependentName: _contact!.cONTACTNAME ?? "", - dependentInitials: _getInitials(_contact!.cONTACTNAME), - nationalId: _contact!.nATIONALIDENTIFIER, - dateOfBirth: _contact!.dATEOFBIRTH, - ), - ), - ); - } else { - Utils.showToast(LocaleKeys.revokeMazayaAccess.tr()); - } + Utils.showToast(action == "Grant" ? LocaleKeys.mazayaAccessGranted.tr() : LocaleKeys.revokeMazayaAccess.tr()); } } catch (ex) { Utils.hideLoading(context); @@ -244,18 +230,58 @@ class _DependentDetailsScreenState extends State { } } + // Full (unmasked) DOB for the share message - distinct from _formatDob/_formatNationalId + // above, which are for the masked on-screen Key Details display. + String _formatDobForShare() { + DateTime? dob = _parseBirthDate(_contact?.dATEOFBIRTH); + if (dob != null) return DateFormat('dd-MM-yyyy').format(dob); + String raw = _contact?.dATEOFBIRTH?.trim() ?? ""; + if (raw.isEmpty) return ""; + if (raw.contains('T')) return raw.split('T').first; + if (raw.contains(' ')) return raw.split(' ').first; + return raw; + } + + // Same message built for both "Share Download Link" and "Copy Link" - they must always + // carry identical content, so this is the single source of truth for it. + String _buildMazayaInviteMessage(String linkToShare) { + String? nationalId = _contact?.nATIONALIDENTIFIER?.trim(); + String nationalIdLine = (nationalId == null || nationalId.isEmpty) ? LocaleKeys.nationalIdOrIqamaFallback.tr() : "${LocaleKeys.nationalId.tr()}: $nationalId"; + return "$linkToShare\n\n${LocaleKeys.mazayaInviteInstructions.tr()}\n\n$nationalIdLine\n${LocaleKeys.dateOfBirth.tr()}: ${_formatDobForShare()}"; + } + + // Shares the invite text 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) async { + String message = _buildMazayaInviteMessage(linkToShare); + + try { + ByteData bytes = await rootBundle.load(MazayaAccessResultScreen.getShareGuidePdfAssetPath(isAr)); + Directory tempDir = await getTemporaryDirectory(); + String localFileName = isAr ? "mazaya_access_guide_ar.pdf" : "mazaya_access_guide_en.pdf"; + File pdfFile = File('${tempDir.path}/$localFileName'); + 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 (_) {} + } + } + @override Widget build(BuildContext context) { DateTime? dob = _parseBirthDate(_contact?.dATEOFBIRTH); String initials = _getInitials(_contact?.cONTACTNAME); String fullName = _contact?.cONTACTNAME ?? ""; String firstName = _getFirstName(_contact?.cONTACTNAME); - String relation = (_contact?.rELATIONSHIP != null && _contact!.rELATIONSHIP!.trim().isNotEmpty) - ? _contact!.rELATIONSHIP!.trim() - : (_contact?.cONTACTTYPE?.trim() ?? ""); + String relation = (_contact?.rELATIONSHIP != null && _contact!.rELATIONSHIP!.trim().isNotEmpty) ? _contact!.rELATIONSHIP!.trim() : (_contact?.cONTACTTYPE?.trim() ?? ""); String formattedId = _formatNationalId(_contact?.nATIONALIDENTIFIER); String formattedDob = _formatDob(dob, _contact?.dATEOFBIRTH); bool isAllowed = _isAccessAllowed; + bool isAr = AppState().isArabic(context); + String linkToShare = MazayaAccessResultScreen.getDownloadLink(); return Scaffold( backgroundColor: const Color(0xFFF8FAFC), @@ -271,31 +297,15 @@ class _DependentDetailsScreenState extends State { InkWell( borderRadius: BorderRadius.circular(20), onTap: () => Navigator.pop(context), - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Icon( - Icons.arrow_back_ios_new_rounded, - size: 20, - color: Color(0xFF1E293B), - ), - ), + child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.arrow_back_ios_new_rounded, size: 20, color: Color(0xFF1E293B))), ), const SizedBox(width: 8), - LocaleKeys.dependentDetails.tr().toText20( - color: const Color(0xFF1E293B), - ), + LocaleKeys.dependentDetails.tr().toText20(color: const Color(0xFF1E293B)), const Spacer(), InkWell( borderRadius: BorderRadius.circular(20), onTap: () => Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)), - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Icon( - Icons.home_outlined, - size: 24, - color: Color(0xFF1E293B), - ), - ), + child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.home_outlined, size: 24, color: Color(0xFF1E293B))), ), ], ), @@ -303,292 +313,214 @@ class _DependentDetailsScreenState extends State { ), ), body: SafeArea( - child: SingleChildScrollView( - padding: const EdgeInsets.fromLTRB(20, 12, 20, 30), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Profile Card (Mint Green) - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: const Color(0xFFE8F7F2), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: const Color(0xFFBCE6D7), width: 1.2), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(20, 12, 20, 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Avatar circle with initials - Container( - width: 52, - height: 52, - decoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - child: Center( - child: initials.toText17( - color: const Color(0xFF168A89), + // Shown only right after a successful grant (not when just viewing an + // already-granted dependent by tapping into this screen). + if (_justGranted && isAllowed) ...[ + Center( + child: Container( + width: 72, + height: 72, + decoration: const BoxDecoration(color: Color(0xFFE6F7F2), shape: BoxShape.circle), + child: Center(child: CustomPaint(size: const Size(36, 36), painter: _ShieldIconPainter(color: const Color(0xFF168A89), isGranted: true))), ), ), - ), - const SizedBox(width: 14), + const SizedBox(height: 10), + Center(child: LocaleKeys.mazayaAccessGranted.tr().toText16(weight: FontWeight.w500, color: const Color(0xFF168A89))), + const SizedBox(height: 16), + ], - // Dependent Name & Relation - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, + // Profile Card (Mint Green) + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration(color: const Color(0xFFE8F7F2), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFBCE6D7), width: 1.2)), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - fullName.toText16( - maxlines: 2, - weight: FontWeight.w500, - color: const Color(0xFF1E293B), + // Avatar circle with initials + Container( + width: 52, + height: 52, + decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle), + child: Center(child: initials.toText17(color: const Color(0xFF168A89))), ), - if (relation.isNotEmpty) ...[ - const SizedBox(height: 3), - relation.toText12( - maxLine: 1, - weight: FontWeight.w500, - color: const Color(0xFF168A89), + const SizedBox(width: 14), + + // Dependent Name & Relation + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + fullName.toText16(maxlines: 2, weight: FontWeight.w500, color: const Color(0xFF1E293B)), + if (relation.isNotEmpty) ...[const SizedBox(height: 3), relation.toText12(maxLine: 1, weight: FontWeight.w500, color: const Color(0xFF168A89))], + ], ), - ], + ), + + // Verified badge on right + const SizedBox(width: 8), + const Icon(Icons.verified_outlined, color: Color(0xFF059669), size: 24), ], ), ), - // Verified badge on right - const SizedBox(width: 8), - const Icon( - Icons.verified_outlined, - color: Color(0xFF059669), - size: 24, - ), - ], - ), - ), + const SizedBox(height: 16), - const SizedBox(height: 16), - - // Key details Card - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: const Color(0xFFF1F5F9)), - boxShadow: const [ - BoxShadow( - color: Color(0x08000000), - blurRadius: 8, - offset: Offset(0, 2), - ), - ], - ), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.keyDetails.tr().toText16( - weight: FontWeight.w500, - color: const Color(0xFF1E293B), - ), - LocaleKeys.verified.tr().toText13( - weight: FontWeight.w500, - color: const Color(0xFF059669), - ), - ], - ), - const SizedBox(height: 14), - const Divider(height: 1, thickness: 1, color: Color(0xFFF1F5F9)), - const SizedBox(height: 14), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.nationalId.tr().toText13( - weight: FontWeight.w400, - color: const Color(0xFF64748B), - ), - formattedId.toText13( - weight: FontWeight.w500, - color: const Color(0xFF1E293B), - ), - ], - ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.dateOfBirth.tr().toText13( - weight: FontWeight.w400, - color: const Color(0xFF64748B), - ), - formattedDob.toText13( - weight: FontWeight.w500, - color: const Color(0xFF1E293B), - ), - ], + // Key details Card + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: const Color(0xFFF1F5F9)), + boxShadow: const [BoxShadow(color: Color(0x08000000), blurRadius: 8, offset: Offset(0, 2))], + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.keyDetails.tr().toText16(weight: FontWeight.w500, color: const Color(0xFF1E293B)), + LocaleKeys.verified.tr().toText13(weight: FontWeight.w500, color: const Color(0xFF059669)), + ], + ), + const SizedBox(height: 14), + const Divider(height: 1, thickness: 1, color: Color(0xFFF1F5F9)), + const SizedBox(height: 14), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.nationalId.tr().toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)), + formattedId.toText13(weight: FontWeight.w500, color: const Color(0xFF1E293B)), + ], + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.dateOfBirth.tr().toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)), + formattedDob.toText13(weight: FontWeight.w500, color: const Color(0xFF1E293B)), + ], + ), + ], + ), ), - ], - ), - ), - const SizedBox(height: 24), + const SizedBox(height: 24), - // Services & actions section header - LocaleKeys.servicesAndActions.tr().toText17( - weight: FontWeight.w500, - color: const Color(0xFF1E293B), - ), - const SizedBox(height: 4), - LocaleKeys.startDependentFlowsDesc.tr().toText13( - weight: FontWeight.w400, - color: const Color(0xFF64748B), - ), + // Services & actions section header + LocaleKeys.servicesAndActions.tr().toText17(weight: FontWeight.w500, color: const Color(0xFF1E293B)), + const SizedBox(height: 4), + LocaleKeys.startDependentFlowsDesc.tr().toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)), - const SizedBox(height: 12), - - // Mazaya access Card - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: const Color(0xFFF1F5F9)), - boxShadow: const [ - BoxShadow( - color: Color(0x08000000), - blurRadius: 8, - offset: Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header Row: Mazaya access + Badge - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.mazayaAccess.tr().toText16( - weight: FontWeight.w500, - color: const Color(0xFF1E293B), - ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3), - decoration: BoxDecoration( - color: isAllowed ? const Color(0xFFECFDF5) : const Color(0xFFFEE2E2), - borderRadius: BorderRadius.circular(12), + const SizedBox(height: 12), + + // Mazaya access Card + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: const Color(0xFFF1F5F9)), + boxShadow: const [BoxShadow(color: Color(0x08000000), blurRadius: 8, offset: Offset(0, 2))], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Row: Mazaya access + Badge + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.mazayaAccess.tr().toText16(weight: FontWeight.w500, color: const Color(0xFF1E293B)), + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3), + decoration: BoxDecoration(color: isAllowed ? const Color(0xFFECFDF5) : const Color(0xFFFEE2E2), borderRadius: BorderRadius.circular(12)), + child: (isAllowed ? LocaleKeys.active.tr() : LocaleKeys.accessOff.tr()).toText12( + weight: FontWeight.w500, + color: isAllowed ? const Color(0xFF059669) : const Color(0xFFEF4444), + ), + ), + ], ), - child: (isAllowed ? LocaleKeys.active.tr() : LocaleKeys.accessOff.tr()).toText12( - weight: FontWeight.w500, - color: isAllowed ? const Color(0xFF059669) : const Color(0xFFEF4444), + const SizedBox(height: 6), + LocaleKeys.allowDependentMazayaOffersDesc.tr(args: [firstName.isNotEmpty ? firstName : fullName]).toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)), + const SizedBox(height: 16), + const Divider(height: 1, thickness: 1, color: Color(0xFFF1F5F9)), + const SizedBox(height: 14), + + // Login method row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.loginMethod.tr().toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)), + LocaleKeys.nationalIdAndDob.tr().toText13(weight: FontWeight.w500, color: const Color(0xFF1E293B)), + ], + ), + const SizedBox(height: 12), + + // Verification row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.verification.tr().toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)), + LocaleKeys.sponsorOtp.tr().toText13(weight: FontWeight.w500, color: const Color(0xFF1E293B)), + ], ), - ), - ], - ), - const SizedBox(height: 6), - LocaleKeys.allowDependentMazayaOffersDesc.tr( - args: [firstName.isNotEmpty ? firstName : fullName], - ).toText13( - weight: FontWeight.w400, - color: const Color(0xFF64748B), - ), - const SizedBox(height: 16), - const Divider(height: 1, thickness: 1, color: Color(0xFFF1F5F9)), - const SizedBox(height: 14), - - // Login method row - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.loginMethod.tr().toText13( - weight: FontWeight.w400, - color: const Color(0xFF64748B), - ), - LocaleKeys.nationalIdAndDob.tr().toText13( - weight: FontWeight.w500, - color: const Color(0xFF1E293B), - ), - ], - ), - const SizedBox(height: 12), - // Verification row - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.verification.tr().toText13( - weight: FontWeight.w400, - color: const Color(0xFF64748B), - ), - LocaleKeys.sponsorOtp.tr().toText13( - weight: FontWeight.w500, - color: const Color(0xFF1E293B), - ), - ], - ), + const SizedBox(height: 20), - const SizedBox(height: 20), - - // Action Button: Allow Mazaya Access (Teal) or Revoke Mazaya Access (Red Outline) - InkWell( - borderRadius: BorderRadius.circular(12), - onTap: _onToggleAccess, - child: Container( - height: 48, - width: double.infinity, - decoration: BoxDecoration( - color: isAllowed ? const Color(0xFFFEF2F2) : const Color(0xFF22B0A6), - borderRadius: BorderRadius.circular(12), - border: isAllowed ? Border.all(color: const Color(0xFFFECACA), width: 1.2) : null, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 18, - height: 18, - child: CustomPaint( - painter: _ShieldIconPainter( - color: isAllowed ? const Color(0xFFDC2626) : Colors.white, - isGranted: !isAllowed, - ), + // Action Button: Allow Mazaya Access (Teal) or Revoke Mazaya Access (Red Outline) + InkWell( + borderRadius: BorderRadius.circular(12), + onTap: _onToggleAccess, + child: Container( + height: 48, + width: double.infinity, + decoration: BoxDecoration( + color: isAllowed ? const Color(0xFFFEF2F2) : const Color(0xFF22B0A6), + borderRadius: BorderRadius.circular(12), + border: isAllowed ? Border.all(color: const Color(0xFFFECACA), width: 1.2) : null, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 18, height: 18, child: CustomPaint(painter: _ShieldIconPainter(color: isAllowed ? const Color(0xFFDC2626) : Colors.white, isGranted: !isAllowed))), + const SizedBox(width: 8), + (isAllowed ? LocaleKeys.revokeMazayaAccess.tr() : LocaleKeys.allowMazayaAccess.tr()).toText16( + weight: FontWeight.w500, + color: isAllowed ? const Color(0xFFDC2626) : Colors.white, + ), + ], ), ), - const SizedBox(width: 8), - (isAllowed - ? LocaleKeys.revokeMazayaAccess.tr() - : LocaleKeys.allowMazayaAccess.tr()) - .toText16( - weight: FontWeight.w500, - color: isAllowed ? const Color(0xFFDC2626) : Colors.white, - ), + ), + + if (isAllowed) ...[ + const SizedBox(height: 12), + Center(child: LocaleKeys.revokingPreventsAccessDesc.tr().toText11(weight: FontWeight.w400, color: const Color(0xFF64748B), isCenter: true)), ], - ), + ], ), ), - - if (isAllowed) ...[ - const SizedBox(height: 12), - Center( - child: LocaleKeys.revokingPreventsAccessDesc.tr().toText11( - weight: FontWeight.w400, - color: const Color(0xFF64748B), - isCenter: true, - ), - ), - ], ], ), ), - ], - ), + ), + // Share download link - same bottom-sticky button convention used elsewhere in + // the app (e.g. "Join Marathon"), shown once access is granted. + if (isAllowed) DefaultButton(LocaleKeys.shareDownloadLink.tr(), () => _shareMazayaInvite(isAr, linkToShare)).insideContainer, + ], ), ), ); diff --git a/lib/ui/profile/family_members.dart b/lib/ui/profile/family_members.dart index 8c45dde..9bc5131 100644 --- a/lib/ui/profile/family_members.dart +++ b/lib/ui/profile/family_members.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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'; @@ -12,10 +13,10 @@ 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/dependent_details_screen.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'; +import 'package:shimmer/shimmer.dart'; class FamilyMembers extends StatefulWidget { const FamilyMembers({Key? key}) : super(key: key); @@ -101,18 +102,10 @@ class _FamilyMembersState extends State { if (mounted) { setState(() {}); if (action == "Grant") { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext cxt) => MazayaAccessResultScreen( - isGranted: true, - dependentName: contact.cONTACTNAME ?? "", - dependentInitials: _getInitials(contact.cONTACTNAME), - nationalId: contact.nATIONALIDENTIFIER, - dateOfBirth: contact.dATEOFBIRTH, - ), - ), - ); + // Same destination as tapping the card itself, so granting from either entry + // point lands on the exact same screen (which now includes the share-link section). + await Navigator.push(context, MaterialPageRoute(builder: (BuildContext cxt) => DependentDetailsScreen(contact: contact, justGranted: true))); + getEmployeeContacts(); } else { Utils.showToast(LocaleKeys.revokeMazayaAccess.tr()); } @@ -142,11 +135,7 @@ class _FamilyMembersState extends State { String personId = (item.pERSONID?.toString() ?? "").toLowerCase(); String contactPersonId = (item.cONTACTPERSONID?.toString() ?? "").toLowerCase(); String contactType = (item.cONTACTTYPE ?? "").toLowerCase(); - return name.contains(q) || - rel.contains(q) || - personId.contains(q) || - contactPersonId.contains(q) || - contactType.contains(q); + return name.contains(q) || rel.contains(q) || personId.contains(q) || contactPersonId.contains(q) || contactType.contains(q); }).toList(); } @@ -198,35 +187,15 @@ class _FamilyMembersState extends State { InkWell( borderRadius: BorderRadius.circular(20), onTap: () => Navigator.pop(context), - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Icon( - Icons.arrow_back_ios_new_rounded, - size: 20, - color: Color(0xFF1E293B), - ), - ), + child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.arrow_back_ios_new_rounded, size: 20, color: Color(0xFF1E293B))), ), const SizedBox(width: 8), - (fromMazaya - ? LocaleKeys.myDependents.tr() - : LocaleKeys.profile_familyDetails.tr()) - .toText20( - isBold: true, - color: const Color(0xFF1E293B), - ), + (fromMazaya ? LocaleKeys.myDependents.tr() : LocaleKeys.profile_familyDetails.tr()).toText20(isBold: true, color: const Color(0xFF1E293B)), const Spacer(), InkWell( borderRadius: BorderRadius.circular(20), onTap: () => Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)), - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Icon( - Icons.home_outlined, - size: 24, - color: Color(0xFF1E293B), - ), - ), + child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.home_outlined, size: 24, color: Color(0xFF1E293B))), ), ], ), @@ -241,11 +210,7 @@ class _FamilyMembersState extends State { padding: const EdgeInsets.fromLTRB(20, 8, 20, 12), child: Container( height: 48, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFE2E8F0)), - ), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFE2E8F0))), padding: const EdgeInsets.symmetric(horizontal: 14), child: Row( children: [ @@ -263,9 +228,7 @@ class _FamilyMembersState extends State { border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, - hintText: fromMazaya - ? LocaleKeys.searchDependents.tr() - : LocaleKeys.searchFamilyMembers.tr(), + hintText: fromMazaya ? LocaleKeys.searchDependents.tr() : LocaleKeys.searchFamilyMembers.tr(), hintStyle: const TextStyle(color: Color(0xFF94A3B8), fontSize: 14), ), ), @@ -278,10 +241,7 @@ class _FamilyMembersState extends State { _searchQuery = ""; }); }, - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 4), - child: Icon(Icons.close, color: Color(0xFF94A3B8), size: 18), - ), + child: const Padding(padding: EdgeInsets.symmetric(horizontal: 4), child: Icon(Icons.close, color: Color(0xFF94A3B8), size: 18)), ), ], ), @@ -290,45 +250,29 @@ class _FamilyMembersState extends State { // Dependents list Expanded( - child: _isLoading - ? Center( - child: Image.asset( - "assets/images/logos/loading_mohemm_logo.gif", - height: 96.0, - width: 96.0, - ), - ) - : displayList.isEmpty + child: + _isLoading + ? _buildDependentsShimmer() + : displayList.isEmpty ? Utils.getNoDataWidget(context) : ListView.separated( - padding: const EdgeInsets.fromLTRB(20, 4, 20, 20), - separatorBuilder: (BuildContext cxt, int index) => const SizedBox(height: 12), - itemCount: displayList.length, - itemBuilder: (BuildContext context, int index) { - return _buildDependentCard( - displayList[index], - isAr, - canUpdateFamilyMember, - canDeleteFamilyMember, - ); - }, - ), + padding: const EdgeInsets.fromLTRB(20, 4, 20, 20), + separatorBuilder: (BuildContext cxt, int index) => const SizedBox(height: 12), + itemCount: displayList.length, + itemBuilder: (BuildContext context, int index) { + return _buildDependentCard(displayList[index], isAr, canUpdateFamilyMember, canDeleteFamilyMember); + }, + ), ), // Bottom action button: + Add another user DefaultButton( - fromMazaya - ? LocaleKeys.addAnotherUser.tr() - : LocaleKeys.addNewFamilyMember.tr(), + fromMazaya ? LocaleKeys.addAnotherUser.tr() : LocaleKeys.addNewFamilyMember.tr(), (canAddFamilyMember || fromMazaya) ? () async { - await Navigator.pushNamed( - context, - AppRoutes.addDependentVerify, - arguments: {'fromMazaya': fromMazaya}, - ); - getEmployeeContacts(); - } + await Navigator.pushNamed(context, AppRoutes.addDependentVerify, arguments: {'fromMazaya': fromMazaya}); + getEmployeeContacts(); + } : null, iconData: Icons.add, isTextExpanded: false, @@ -341,279 +285,232 @@ class _FamilyMembersState extends State { ); } - Widget _buildDependentCard( - GetEmployeeContactsList item, - bool isAr, - bool canUpdate, - bool canDelete, - ) { + // Same Shimmer.fromColors skeleton-card convention used elsewhere in the app (e.g. + // dashboard_screen_for_dependet.dart's retailer/category shimmers), shaped like the + // real _buildDependentCard layout so the loading state doesn't jump around once it loads. + Widget _buildDependentsShimmer() { + return Shimmer.fromColors( + baseColor: const Color(0xFFE2E8F0), + highlightColor: const Color(0xFFF8FAFC), + child: ListView.separated( + padding: const EdgeInsets.fromLTRB(20, 4, 20, 20), + separatorBuilder: (BuildContext _, int __) => const SizedBox(height: 12), + itemCount: 6, + itemBuilder: (BuildContext _, int __) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 14), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container(width: 48, height: 48, decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle)), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container(height: 18, width: 140, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))), + const SizedBox(height: 6), + Container(height: 14, width: 90, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))), + ], + ), + ), + ], + ), + const SizedBox(height: 14), + Container(height: 1, color: Colors.white), + const SizedBox(height: 12), + Center(child: Container(height: 14, width: 150, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4)))), + ], + ), + ); + }, + ), + ); + } + + Widget _buildDependentCard(GetEmployeeContactsList item, bool isAr, bool canUpdate, bool canDelete) { bool isAllowed = _isAccessAllowed(item); return InkWell( borderRadius: BorderRadius.circular(16), onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext cxt) => DependentDetailsScreen(contact: item), - ), - ); + await Navigator.push(context, MaterialPageRoute(builder: (BuildContext cxt) => DependentDetailsScreen(contact: item))); getEmployeeContacts(); }, child: Container( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 14), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: const Color(0xFFF1F5F9)), - boxShadow: const [ - BoxShadow( - color: Color(0x0A000000), - blurRadius: 8, - offset: Offset(0, 3), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Top row: Avatar, Info, and Status Badge - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // Initials Circle - Container( - width: 48, - height: 48, - decoration: const BoxDecoration( - color: Color(0xFFE6F5F3), - shape: BoxShape.circle, + padding: const EdgeInsets.fromLTRB(16, 16, 16, 14), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: const Color(0xFFF1F5F9)), + boxShadow: const [BoxShadow(color: Color(0x0A000000), blurRadius: 8, offset: Offset(0, 3))], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Top row: Avatar, Info, and Status Badge + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Initials Circle + Container( + width: 48, + height: 48, + decoration: const BoxDecoration(color: Color(0xFFE6F5F3), shape: BoxShape.circle), + child: Center(child: Text(_getInitials(item.cONTACTNAME), style: const TextStyle(color: Color(0xFF168A89), fontWeight: FontWeight.w700, fontSize: 15))), ), - child: Center( - child: Text( - _getInitials(item.cONTACTNAME), - style: const TextStyle( - color: Color(0xFF168A89), - fontWeight: FontWeight.w700, - fontSize: 15, - ), + const SizedBox(width: 12), + + // Name & Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(item.cONTACTNAME ?? "", maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1E293B))), + const SizedBox(height: 3), + Row(children: [Flexible(child: (item.rELATIONSHIP ?? "").toText12(maxLine: 1, color: const Color(0xFF168A89)))]), + ], ), ), - ), - const SizedBox(width: 12), - // Name & Details - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item.cONTACTNAME ?? "", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Color(0xFF1E293B), - ), - ), - const SizedBox(height: 3), - Row( + // Trailing arrow (shown in Mazaya flow): the whole card opens + // DependentDetailsScreen on tap, so this just signals that. + // Same "arrow_next.svg" + RotatedBox-for-RTL convention used in menus_widget.dart + // and services_widget.dart on the dashboard. + if (fromMazaya) ...[ + const SizedBox(width: 8), + RotatedBox(quarterTurns: isAr ? 2 : 4, child: SvgPicture.asset("assets/images/arrow_next.svg", color: const Color(0xFF94A3B8), width: 14)), + ], + ], + ), + + const SizedBox(height: 14), + const Divider(height: 1, thickness: 1, color: Color(0xFFF1F5F9)), + const SizedBox(height: 12), + + // Bottom Action Row + if (fromMazaya) ...[ + // Mazaya Access Buttons: Revoke or Allow + Center( + child: InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => _confirmToggleMazayaAccess(item), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Flexible( - child: (item.rELATIONSHIP ?? "").toText12( - maxLine: 1, - color: const Color(0xFF168A89), + Icon(isAllowed ? Icons.close : Icons.check, size: isAllowed ? 16 : 18, color: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89)), + const SizedBox(width: 6), + Text( + isAllowed ? LocaleKeys.revokeMazayaAccess.tr() : LocaleKeys.allowMazayaAccess.tr(), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89), + decoration: TextDecoration.underline, + decorationColor: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89), ), ), ], ), - ], - ), - ), - - // Top right pill (shown in Mazaya flow) - if (fromMazaya) ...[ - const SizedBox(width: 8), - Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - color: isAllowed ? const Color(0xFFE8F8F0) : const Color(0xFFFDE8E8), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - isAllowed ? LocaleKeys.active.tr() : LocaleKeys.noAccess.tr(), - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w400, - color: isAllowed ? const Color(0xFF168A89) : const Color(0xFFDC2626), - ), - ), - ), - ], - ], - ), - - const SizedBox(height: 14), - const Divider(height: 1, thickness: 1, color: Color(0xFFF1F5F9)), - const SizedBox(height: 12), - - // Bottom Action Row - if (fromMazaya) ...[ - // Mazaya Access Buttons: Revoke or Allow - Center( - child: InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () => _confirmToggleMazayaAccess(item), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - isAllowed ? Icons.close : Icons.check, - size: isAllowed ? 16 : 18, - color: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89), - ), - const SizedBox(width: 6), - Text( - isAllowed - ? LocaleKeys.revokeMazayaAccess.tr() - : LocaleKeys.allowMazayaAccess.tr(), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89), - decoration: TextDecoration.underline, - decorationColor: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89), - ), - ), - ], ), ), ), - ), - ] else ...[ - // Profile Flow: Update and Remove action buttons - Row( - children: [ - Expanded( - child: InkWell( - borderRadius: BorderRadius.circular(10), - onTap: canUpdate - ? () { - int? relationId = item.cONTACTRELATIONSHIPID; - if (relationId != null) { - showUpdateAlertDialog(context, relationId, 2, "UPDATE"); - } - } - : null, - child: Container( - height: 38, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: canUpdate ? const Color(0xFFCBD5E1) : const Color(0xFFE2E8F0), + ] else ...[ + // Profile Flow: Update and Remove action buttons + Row( + children: [ + Expanded( + child: InkWell( + borderRadius: BorderRadius.circular(10), + onTap: + canUpdate + ? () { + int? relationId = item.cONTACTRELATIONSHIPID; + if (relationId != null) { + showUpdateAlertDialog(context, relationId, 2, "UPDATE"); + } + } + : null, + child: Container( + height: 38, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: canUpdate ? const Color(0xFFCBD5E1) : const Color(0xFFE2E8F0)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.edit_outlined, size: 15, color: canUpdate ? const Color(0xFF475569) : const Color(0xFF94A3B8)), + const SizedBox(width: 6), + LocaleKeys.update.tr().toText12(color: canUpdate ? const Color(0xFF475569) : const Color(0xFF94A3B8)), + ], ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.edit_outlined, - size: 15, - color: canUpdate ? const Color(0xFF475569) : const Color(0xFF94A3B8), - ), - const SizedBox(width: 6), - LocaleKeys.update.tr().toText12( - color: canUpdate ? const Color(0xFF475569) : const Color(0xFF94A3B8), - ), - ], ), ), ), - ), - const SizedBox(width: 10), - Expanded( - child: InkWell( - borderRadius: BorderRadius.circular(10), - onTap: canDelete - ? () { - int? relationId = item.cONTACTRELATIONSHIPID; - if (relationId != null) { - showRemoveAlertDialog(context, relationId); - } - } - : null, - child: Container( - height: 38, - decoration: BoxDecoration( - color: canDelete ? const Color(0xFFFFF5F5) : Colors.white, - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: canDelete ? const Color(0xFFFFCDD2) : const Color(0xFFE2E8F0), + const SizedBox(width: 10), + Expanded( + child: InkWell( + borderRadius: BorderRadius.circular(10), + onTap: + canDelete + ? () { + int? relationId = item.cONTACTRELATIONSHIPID; + if (relationId != null) { + showRemoveAlertDialog(context, relationId); + } + } + : null, + child: Container( + height: 38, + decoration: BoxDecoration( + color: canDelete ? const Color(0xFFFFF5F5) : Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: canDelete ? const Color(0xFFFFCDD2) : const Color(0xFFE2E8F0)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.delete_outline_rounded, size: 15, color: canDelete ? const Color(0xFFED1C24) : const Color(0xFF94A3B8)), + const SizedBox(width: 6), + LocaleKeys.remove.tr().toText12(color: canDelete ? const Color(0xFFED1C24) : const Color(0xFF94A3B8)), + ], ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.delete_outline_rounded, - size: 15, - color: canDelete ? const Color(0xFFED1C24) : const Color(0xFF94A3B8), - ), - const SizedBox(width: 6), - LocaleKeys.remove.tr().toText12( - color: canDelete ? const Color(0xFFED1C24) : const Color(0xFF94A3B8), - ), - ], ), ), ), - ), - ], - ), + ], + ), + ], ], - ], - ), + ), ), ); } void showUpdateAlertDialog(BuildContext context, int relationId, int flag, String actionType) { - Widget cancelButton = TextButton( - child: LocaleKeys.cancel.tr().toText14(), - onPressed: () => Navigator.pop(context), - ); + Widget cancelButton = TextButton(child: LocaleKeys.cancel.tr().toText14(), onPressed: () => Navigator.pop(context)); Widget continueButton = TextButton( child: LocaleKeys.ok.tr().toText14(), onPressed: () { Navigator.pop(context); - Navigator.pushNamed( - context, - AppRoutes.addUpdateFamilyMember, - arguments: FamilyMemberScreenParams(relationId, flag: 2, actionType: "UPDATE"), - ).then((_) => getEmployeeContacts()); + 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, - ); + 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 cancelButton = TextButton(child: LocaleKeys.cancel.tr().toText14(), onPressed: () => Navigator.pop(context)); Widget continueButton = TextButton( child: LocaleKeys.ok.tr().toText14(), onPressed: () { @@ -621,14 +518,7 @@ class _FamilyMembersState extends State { 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, - ); + 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/profile/mazaya_access_result_screen.dart b/lib/ui/profile/mazaya_access_result_screen.dart index e8e6d71..e33597a 100644 --- a/lib/ui/profile/mazaya_access_result_screen.dart +++ b/lib/ui/profile/mazaya_access_result_screen.dart @@ -87,6 +87,15 @@ class MazayaAccessResultScreen extends StatelessWidget { return channel == 33 ? "https://mohemm.hmg.com/mhmg.html" : "https://mohemm.hmg.com/mohem.html"; } + // Channel 33 (HMG) gets the HMG-branded guide, everything else gets the CS-branded one - + // same channel check as getDownloadLink() - crossed with the current app language. + static String getShareGuidePdfAssetPath(bool isAr) { + int channel = AppState().postParamsObject?.channel ?? 31; + String brand = channel == 33 ? "HMG" : "CS"; + String lang = isAr ? "ar" : "en"; + return "assets/pdf/Mazaya Access Guide $brand $lang.pdf"; + } + String _getInitials(String? name) { if (name == null || name.trim().isEmpty) return "D"; List parts = name.trim().split(RegExp(r'\s+')).where((String p) => p.isNotEmpty).toList(); @@ -104,7 +113,6 @@ class MazayaAccessResultScreen extends StatelessWidget { // Resolve arguments if passed via ModalRoute Object? routeArgs = ModalRoute.of(context)?.settings.arguments; - log("MazayaAccessResultScreen routeArgs: $routeArgs"); bool effectiveGranted = isGranted; String effectiveName = dependentName; String? effectiveInitials = dependentInitials; @@ -441,16 +449,20 @@ class MazayaAccessResultScreen extends StatelessWidget { return s; } - // 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 { + // Same message built for both "Share Download Link" and "Copy Link" - they must always + // carry identical content, so this is the single source of truth for it. + String _buildMazayaInviteMessage(String linkToShare, String nationalId, String dateOfBirth) { String nationalIdLine = nationalId.trim().isEmpty ? LocaleKeys.nationalIdOrIqamaFallback.tr() : "${LocaleKeys.nationalId.tr()}: $nationalId"; - String message = "$linkToShare\n\n${LocaleKeys.mazayaInviteInstructions.tr()}\n\n$nationalIdLine\n${LocaleKeys.dateOfBirth.tr()}: ${_formatDob(dateOfBirth)}"; + return "$linkToShare\n\n${LocaleKeys.mazayaInviteInstructions.tr()}\n\n$nationalIdLine\n${LocaleKeys.dateOfBirth.tr()}: ${_formatDob(dateOfBirth)}"; + } + + // Shares the invite text 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 = _buildMazayaInviteMessage(linkToShare, nationalId, dateOfBirth); try { - String assetFileName = isAr ? "Mazaya Access Guide Arabic.pdf" : "Mazaya Access Guide English.pdf"; - ByteData bytes = await rootBundle.load("assets/pdf/$assetFileName"); + ByteData bytes = await rootBundle.load(getShareGuidePdfAssetPath(isAr)); Directory tempDir = await getTemporaryDirectory(); // Write to a space-free filename locally - the asset path above is what actually matters. String localFileName = isAr ? "mazaya_access_guide_ar.pdf" : "mazaya_access_guide_en.pdf"; @@ -487,7 +499,7 @@ class MazayaAccessResultScreen extends StatelessWidget { DefaultButton( LocaleKeys.copyLink.tr(), () { - Clipboard.setData(ClipboardData(text: linkToShare)); + Clipboard.setData(ClipboardData(text: _buildMazayaInviteMessage(linkToShare, nationalId, dateOfBirth))); Utils.showToast(LocaleKeys.linkCopied.tr()); }, colors: const [Colors.white, Colors.white],