import 'dart:developer'; import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; 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 { final bool isGranted; 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}); } class _ShieldPainter extends CustomPainter { final Color color; final bool isGranted; _ShieldPainter({required this.color, required this.isGranted}); @override void paint(Canvas canvas, Size size) { Paint paint = Paint() ..color = color ..style = PaintingStyle.stroke ..strokeWidth = 2.4 ..strokeCap = StrokeCap.round ..strokeJoin = StrokeJoin.round; double w = size.width; double h = size.height; // Shield path Path path = Path(); path.moveTo(w * 0.5, h * 0.1); path.cubicTo(w * 0.72, h * 0.1, w * 0.88, h * 0.18, w * 0.88, h * 0.38); path.cubicTo(w * 0.88, h * 0.65, w * 0.68, h * 0.84, w * 0.5, h * 0.92); path.cubicTo(w * 0.32, h * 0.84, w * 0.12, h * 0.65, w * 0.12, h * 0.38); path.cubicTo(w * 0.12, h * 0.18, w * 0.28, h * 0.1, w * 0.5, h * 0.1); path.close(); canvas.drawPath(path, paint); if (isGranted) { // Checkmark path Path checkPath = Path(); checkPath.moveTo(w * 0.34, h * 0.49); checkPath.lineTo(w * 0.46, h * 0.61); checkPath.lineTo(w * 0.66, h * 0.39); canvas.drawPath(checkPath, paint); } else { // Cross path 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 _ShieldPainter oldDelegate) => color != oldDelegate.color || isGranted != oldDelegate.isGranted; } class MazayaAccessResultScreen extends StatelessWidget { final bool isGranted; final String dependentName; final String? dependentInitials; final String? downloadUrl; final String? nationalId; final String? dateOfBirth; const MazayaAccessResultScreen({Key? key, this.isGranted = true, this.dependentName = "", this.dependentInitials, this.downloadUrl, this.nationalId, this.dateOfBirth}) : super(key: key); static String getDownloadLink() { int channel = AppState().postParamsObject?.channel ?? 31; 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(); if (parts.isEmpty) return "D"; if (parts.length == 1) { return parts.first.length >= 2 ? parts.first.substring(0, 2).toUpperCase() : parts.first.toUpperCase(); } return "${parts.first[0]}${parts.last[0]}".toUpperCase(); } @override Widget build(BuildContext context) { bool isAr = AppState().isArabic(context); String fontFamily = isAr ? 'Cairo' : 'Poppins'; // Resolve arguments if passed via ModalRoute Object? routeArgs = ModalRoute.of(context)?.settings.arguments; bool effectiveGranted = isGranted; 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) { String fallback = AppState().dependentName ?? ""; if (fallback.isNotEmpty) { effectiveName = fallback; } else { effectiveName = isAr ? "التابع" : "Dependent"; } } String linkToShare = effectiveUrl ?? getDownloadLink(); String initials = effectiveInitials ?? _getInitials(effectiveName); return Scaffold( backgroundColor: Colors.white, appBar: PreferredSize( preferredSize: const Size.fromHeight(56), child: SafeArea( child: Container( height: 56, padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ 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))), ), const SizedBox(width: 4), LocaleKeys.mazayaTitle.tr().toText20(isBold: true, color: const Color(0xFF1E293B)), const Spacer(), InkWell( borderRadius: BorderRadius.circular(20), onTap: () => Navigator.pop(context), child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.close_rounded, size: 24, color: Color(0xFF1E293B))), ), ], ), ), ), ), body: SafeArea( child: Column( children: [ Expanded( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 16, 20, 20), child: effectiveGranted ? _buildGrantedContent(context, isAr, fontFamily, effectiveName, initials, linkToShare) : _buildRevokedContent(context, isAr, fontFamily, effectiveName, initials), ), ), _buildBottomButtons(context, isAr, fontFamily, effectiveGranted, linkToShare, effectiveNationalId ?? "", effectiveDateOfBirth ?? ""), ], ), ), ); } Widget _buildGrantedContent(BuildContext context, bool isAr, String fontFamily, String name, String initials, String linkToShare) { return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 8), // Shield checkmark badge Container( width: 80, height: 80, decoration: const BoxDecoration(color: Color(0xFFE6F7F2), shape: BoxShape.circle), child: Center(child: CustomPaint(size: const Size(40, 40), painter: _ShieldPainter(color: const Color(0xFF168A89), isGranted: true))), ), const SizedBox(height: 18), // Title LocaleKeys.mazayaAccessGranted.tr().toText22(isBold: true, isCentered: true, color: const Color(0xFF1E293B)), const SizedBox(height: 8), // Description LocaleKeys.mazayaAccessGrantedDesc.tr(args: [name]).toText14(isCenter: true, color: const Color(0xFF64748B)), const SizedBox(height: 22), // Access is now active banner Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), decoration: BoxDecoration(color: const Color(0xFFE8F8F0), borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFCEEFE7), width: 1)), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding(padding: EdgeInsets.only(top: 2), child: Icon(Icons.check_circle_outline_rounded, color: Color(0xFF168A89), size: 20)), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.accessIsNowActive.tr().toText14(isBold: true, color: const Color(0xFF168A89)), const SizedBox(height: 3), LocaleKeys.accessIsNowActiveDesc.tr(args: [name]).toText12(color: const Color(0xFF64748B)), ], ), ), ], ), ), const SizedBox(height: 14), // Dependent Card Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFF1F5F9)), boxShadow: const [BoxShadow(color: Color(0x08000000), blurRadius: 10, offset: Offset(0, 3))], ), child: Row( children: [ Container( width: 42, height: 42, decoration: const BoxDecoration(color: Color(0xFFE6F7F2), shape: BoxShape.circle), child: Center(child: initials.toText14(isBold: true, color: const Color(0xFF168A89))), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ name.toText14(isBold: true, maxlines: 1, color: const Color(0xFF1E293B)), const SizedBox(height: 2), LocaleKeys.dependentAccessActive.tr().toText12(isBold: true, color: const Color(0xFF168A89)), ], ), ), const Icon(Icons.lock_open_rounded, color: Color(0xFF168A89), size: 20), ], ), ), const SizedBox(height: 14), // Shareable App-Download Link Card Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFE2E8F0))), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 36, height: 36, decoration: BoxDecoration(color: const Color(0xFFE8F8F0), borderRadius: BorderRadius.circular(10)), child: const Center(child: Icon(Icons.smartphone_rounded, color: Color(0xFF168A89), size: 20)), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.shareableAppDownloadLink.tr().toText14(isBold: true, color: const Color(0xFF1E293B)), const SizedBox(height: 2), LocaleKeys.shareableAppDownloadLinkDesc.tr(args: [name]).toText12(color: const Color(0xFF64748B)), ], ), ), ], ), const SizedBox(height: 12), Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9), decoration: BoxDecoration(color: const Color(0xFFF8FAFC), borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFFE2E8F0))), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.downloadLink.tr().toText11(isBold: true, color: const Color(0xFF168A89)), const SizedBox(height: 3), linkToShare.toText12(maxLine: 1, color: const Color(0xFF334155)), ], ), ), ], ), ), ], ); } Widget _buildRevokedContent(BuildContext context, bool isAr, String fontFamily, String name, String initials) { return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 8), // Shield bad badge Container( width: 80, height: 80, decoration: const BoxDecoration(color: Color(0xFFFEECEB), shape: BoxShape.circle), child: Center(child: CustomPaint(size: const Size(40, 40), painter: _ShieldPainter(color: const Color(0xFFED1C24), isGranted: false))), ), const SizedBox(height: 18), // Title LocaleKeys.mazayaAccessRevoked.tr().toText22(isBold: true, isCentered: true, color: const Color(0xFF1E293B)), const SizedBox(height: 8), // Description LocaleKeys.mazayaAccessRevokedDesc.tr().toText14(isCenter: true, color: const Color(0xFF64748B)), const SizedBox(height: 22), // Contact your sponsor banner Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), decoration: BoxDecoration(color: const Color(0xFFFFF5F5), borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFFFCDD2), width: 1)), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding(padding: EdgeInsets.only(top: 2), child: Icon(Icons.phone_outlined, color: Color(0xFFED1C24), size: 20)), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.contactYourSponsor.tr().toText14(isBold: true, color: const Color(0xFFED1C24)), const SizedBox(height: 3), LocaleKeys.contactYourSponsorDesc.tr().toText12(color: const Color(0xFF64748B)), ], ), ), ], ), ), const SizedBox(height: 14), // Dependent Card Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFF1F5F9)), boxShadow: const [BoxShadow(color: Color(0x08000000), blurRadius: 10, offset: Offset(0, 3))], ), child: Row( children: [ Container( width: 42, height: 42, decoration: const BoxDecoration(color: Color(0xFFFFF1F0), shape: BoxShape.circle), child: Center(child: initials.toText14(isBold: true, color: const Color(0xFFED1C24))), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ name.toText14(isBold: true, maxlines: 1, color: const Color(0xFF1E293B)), const SizedBox(height: 2), LocaleKeys.dependentAccessBlocked.tr().toText12(isBold: true, color: const Color(0xFFED1C24)), ], ), ), const Icon(Icons.lock_outline_rounded, color: Color(0xFFED1C24), size: 20), ], ), ), ], ); } // Display-only DOB formatting for the share message: "dd-MM-yyyy", no time component. // The raw value can come in as the API's "/Date(ms+tz)/" wrapper or a plain ISO datetime string // with a time component - either way we only want the date part. String _formatDob(String raw) { String s = raw.trim(); if (s.isEmpty) return ""; try { RegExp aspNetDateRegex = RegExp(r'/Date\((-?\d+)([+-]\d{4})?\)/'); Match? match = aspNetDateRegex.firstMatch(s); if (match != null) { int ms = int.parse(match.group(1)!); return DateFormat('dd-MM-yyyy').format(DateTime.fromMillisecondsSinceEpoch(ms, isUtc: true)); } DateTime? parsed = DateTime.tryParse(s); if (parsed != null) { return DateFormat('dd-MM-yyyy').format(parsed); } } catch (_) {} // Fallback: strip a time component off a plain "yyyy-MM-dd HH:mm:ss" / "yyyy-MM-ddTHH:mm:ss" // string we couldn't otherwise parse, rather than showing it as-is with the time attached. if (s.contains('T')) return s.split('T').first; if (s.contains(' ')) return s.split(' ').first; return s; } // 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"; 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 { 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"; File pdfFile = File('${tempDir.path}/$localFileName'); await pdfFile.writeAsBytes(bytes.buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes)); log("final: $message\n\nfile: ${pdfFile.path}"); 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( color: Colors.white, padding: const EdgeInsets.fromLTRB(20, 10, 20, 20), child: Column( mainAxisSize: MainAxisSize.min, children: [ DefaultButton( LocaleKeys.shareDownloadLink.tr(), () => _shareMazayaInvite(isAr, linkToShare, nationalId, dateOfBirth), trailingIconData: isAr ? Icons.arrow_back_rounded : Icons.arrow_forward_rounded, fontSize: 14, isTextExpanded: false, height: 48, borderRadius: 12, ), const SizedBox(height: 10), DefaultButton( LocaleKeys.copyLink.tr(), () { Clipboard.setData(ClipboardData(text: _buildMazayaInviteMessage(linkToShare, nationalId, dateOfBirth))); Utils.showToast(LocaleKeys.linkCopied.tr()); }, colors: const [Colors.white, Colors.white], border: Border.all(color: const Color(0xFFCBD5E1)), textColor: const Color(0xFF168A89), fontSize: 14, isTextExpanded: false, height: 48, borderRadius: 12, ), const SizedBox(height: 10), DefaultButton( LocaleKeys.done.tr(), () => Navigator.pop(context), colors: const [Colors.white, Colors.white], border: Border.all(color: const Color(0xFFCBD5E1)), textColor: const Color(0xFF334155), fontSize: 14, isTextExpanded: false, height: 48, borderRadius: 12, ), ], ), ); } else { return Container( color: Colors.white, padding: const EdgeInsets.fromLTRB(20, 10, 20, 20), child: DefaultButton( LocaleKeys.returnToSignIn.tr(), () => Navigator.pop(context), colors: const [Colors.white, Colors.white], border: Border.all(color: const Color(0xFFCEEFE7), width: 1.2), textColor: const Color(0xFF168A89), iconData: isAr ? Icons.arrow_forward_rounded : Icons.arrow_back_rounded, fontSize: 14, isTextExpanded: false, height: 48, borderRadius: 12, ), ); } } }