diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 503539d..f84ed69 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -789,5 +789,21 @@ "noBranchesFound": "لم يتم العثور على فروع مطابقة", "browseAllBranches": "تصفح جميع الفروع ({}) في مختلف المدن", "branchesCount": "{} فرع", - "kmAway": "على بعد {} كم" + "kmAway": "على بعد {} كم", + "dependentDetails": "تفاصيل التابع", + "verifiedWithNhic": "موثق مع NHIC", + "yearsOld": "{} سنة", + "keyDetails": "البيانات الأساسية", + "verified": "موثق", + "idEnding": "الهوية تنتهي بـ {}", + "servicesAndActions": "الخدمات والإجراءات", + "startDependentFlowsDesc": "بدء إجراءات التابع وإدارة الوصول من مكان واحد.", + "mazayaAccess": "الوصول إلى مزايا", + "accessOff": "معطل", + "allowDependentMazayaOffersDesc": "السماح لـ {} باستخدام عروض مزايا بهويتها الموثقة.", + "loginMethod": "طريقة تسجيل الدخول", + "nationalIdAndDob": "الهوية الوطنية وتاريخ الميلاد", + "sponsorOtp": "رمز التحقق للكفيل", + "revokingPreventsAccessDesc": "إلغاء الوصول يمنع التابع من استخدام المزايا حتى تعيد السماح به مرة أخرى." } + diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index e909dd5..14c598d 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -789,5 +789,21 @@ "noBranchesFound": "No branches found", "browseAllBranches": "Browse all {} branches across cities", "branchesCount": "{} Branches", - "kmAway": "{} KM Away" + "kmAway": "{} KM Away", + "dependentDetails": "Dependent Details", + "verifiedWithNhic": "Verified with NHIC", + "yearsOld": "{} years", + "keyDetails": "Key details", + "verified": "Verified", + "idEnding": "ID ending {}", + "servicesAndActions": "Services & actions", + "startDependentFlowsDesc": "Start dependent flows and manage access from one place.", + "mazayaAccess": "Mazaya access", + "accessOff": "Off", + "allowDependentMazayaOffersDesc": "Allow {} to use Mazaya offers with her verified identity.", + "loginMethod": "Login method", + "nationalIdAndDob": "National ID & Date of Birth", + "sponsorOtp": "Sponsor OTP", + "revokingPreventsAccessDesc": "Revoking prevents all future dependent access until you allow it again." } + diff --git a/lib/config/routes.dart b/lib/config/routes.dart index e555d02..db46a5c 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -49,6 +49,7 @@ import 'package:mohem_flutter_app/ui/parking_qr/parking_qr.dart'; import 'package:mohem_flutter_app/ui/payslip/monthly_pay_slip_screen.dart'; import 'package:mohem_flutter_app/ui/performance/performance_appraisal_screen.dart'; import 'package:mohem_flutter_app/ui/profile/add_dependent_verify_screen.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/ui/profile/add_update_family_member.dart'; import 'package:mohem_flutter_app/ui/profile/basic_details.dart'; @@ -150,6 +151,7 @@ class AppRoutes { static const String deleteFamilyMember = "/deleteFamilyMember"; static const String addUpdateFamilyMember = "/addUpdateFamilyMember"; static const String addDependentVerify = "/addDependentVerify"; + static const String dependentDetails = "/dependentDetails"; static const String mazayaAccessResult = "/mazayaAccessResult"; // Mowadhafhi @@ -304,6 +306,7 @@ class AppRoutes { requestSubmitScreen: (BuildContext context) => RequestSubmitScreen(), addUpdateFamilyMember: (BuildContext context) => AddUpdateFamilyMember(), addDependentVerify: (BuildContext context) => const AddDependentVerifyScreen(), + dependentDetails: (BuildContext context) => const DependentDetailsScreen(), mazayaAccessResult: (BuildContext context) => const MazayaAccessResultScreen(), //mowadhafhi diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index a6f2476..424fb78 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -82,12 +82,12 @@ extension EmailValidator on String { ), ); - Widget toText12({Color? color, bool isUnderLine = false, Color? underlineColor, double underlineGap = 2, bool isBold = false, bool isCenter = false, int? maxLine, TextOverflow? overflow}) { + Widget toText12({Color? color, FontWeight? weight, bool isUnderLine = false, Color? underlineColor, double underlineGap = 2, bool isBold = false, bool isCenter = false, int? maxLine, TextOverflow? overflow}) { Widget text = Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxLine == 0 ? null : maxLine, - style: TextStyle(fontSize: 12, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.72, overflow: TextOverflow.visible), + style: TextStyle(fontSize: 12, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), color: color ?? MyColors.darkTextColor, letterSpacing: -0.72, overflow: TextOverflow.visible), ); if (!isUnderLine) return text; @@ -129,13 +129,13 @@ extension EmailValidator on String { ), ); - Widget toText13({Color? color, bool isUnderLine = false, bool isBold = false, int? maxlines, TextOverflow? overflow}) => Text( + Widget toText13({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, int? maxlines, TextOverflow? overflow}) => Text( this, maxLines: maxlines, overflow: overflow, style: TextStyle( fontSize: 13, - fontWeight: isBold ? FontWeight.bold : FontWeight.w600, + fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), color: color ?? MyColors.darkTextColor, letterSpacing: -0.52, decoration: isUnderLine ? TextDecoration.underline : null, @@ -156,7 +156,7 @@ extension EmailValidator on String { ), ); - Widget toText16({Color? color, bool isUnderLine = false, bool isBold = false, int? maxlines, double? height, bool isCentered = false}) => Text( + Widget toText16({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, int? maxlines, double? height, bool isCentered = false}) => Text( this, maxLines: maxlines, textAlign: isCentered ? TextAlign.center : null, @@ -165,30 +165,30 @@ extension EmailValidator on String { fontSize: 16, letterSpacing: -0.64, height: height, - fontWeight: isBold ? FontWeight.bold : FontWeight.w600, + fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), decoration: isUnderLine ? TextDecoration.underline : null, ), ); - Widget toText17({Color? color, bool isBold = false, bool isCentered = false}) => Text( + Widget toText17({Color? color, FontWeight? weight, bool isBold = false, bool isCentered = false}) => Text( this, textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600)), ); - Widget toText18({Color? color, bool isBold = false, bool isCentered = false}) => Text( + Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCentered = false}) => Text( this, textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(fontSize: 18, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.08), + style: TextStyle(fontSize: 18, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), color: color ?? MyColors.darkTextColor, letterSpacing: -1.08), ); Widget toText19({Color? color, bool isBold = false}) => Text(this, style: TextStyle(fontSize: 19, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.14)); - Widget toText20({Color? color, bool isBold = false, bool isCentered = false}) => Text( + Widget toText20({Color? color, FontWeight? weight, bool isBold = false, bool isCentered = false}) => Text( this, textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(fontSize: 20, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), + style: TextStyle(fontSize: 20, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), ); Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 2917cda..c144c50 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -805,7 +805,22 @@ class CodegenLoader extends AssetLoader{ "noBranchesFound": "لم يتم العثور على فروع مطابقة", "browseAllBranches": "تصفح جميع الفروع ({}) في مختلف المدن", "branchesCount": "{} فرع", - "kmAway": "على بعد {} كم" + "kmAway": "على بعد {} كم", + "dependentDetails": "تفاصيل التابع", + "verifiedWithNhic": "موثق مع NHIC", + "yearsOld": "{} سنة", + "keyDetails": "البيانات الأساسية", + "verified": "موثق", + "idEnding": "الهوية تنتهي بـ {}", + "servicesAndActions": "الخدمات والإجراءات", + "startDependentFlowsDesc": "بدء إجراءات التابع وإدارة الوصول من مكان واحد.", + "mazayaAccess": "الوصول إلى مزايا", + "accessOff": "معطل", + "allowDependentMazayaOffersDesc": "السماح لـ {} باستخدام عروض مزايا بهويتها الموثقة.", + "loginMethod": "طريقة تسجيل الدخول", + "nationalIdAndDob": "الهوية الوطنية وتاريخ الميلاد", + "sponsorOtp": "رمز التحقق للكفيل", + "revokingPreventsAccessDesc": "إلغاء الوصول يمنع التابع من استخدام المزايا حتى تعيد السماح به مرة أخرى." }; static const Map _en_US = { "mohemm": "Mohemm", @@ -1598,7 +1613,22 @@ static const Map _en_US = { "noBranchesFound": "No branches found", "browseAllBranches": "Browse all {} branches across cities", "branchesCount": "{} Branches", - "kmAway": "{} KM Away" + "kmAway": "{} KM Away", + "dependentDetails": "Dependent Details", + "verifiedWithNhic": "Verified with NHIC", + "yearsOld": "{} years", + "keyDetails": "Key details", + "verified": "Verified", + "idEnding": "ID ending {}", + "servicesAndActions": "Services & actions", + "startDependentFlowsDesc": "Start dependent flows and manage access from one place.", + "mazayaAccess": "Mazaya access", + "accessOff": "Off", + "allowDependentMazayaOffersDesc": "Allow {} to use Mazaya offers with her verified identity.", + "loginMethod": "Login method", + "nationalIdAndDob": "National ID & Date of Birth", + "sponsorOtp": "Sponsor OTP", + "revokingPreventsAccessDesc": "Revoking prevents all future dependent access until you allow it again." }; static const Map> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 547d809..4f88cff 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -778,6 +778,20 @@ abstract class LocaleKeys { static const browseAllBranches = 'browseAllBranches'; static const branchesCount = 'branchesCount'; static const kmAway = 'kmAway'; - + static const dependentDetails = 'dependentDetails'; + static const verifiedWithNhic = 'verifiedWithNhic'; + static const yearsOld = 'yearsOld'; + static const keyDetails = 'keyDetails'; + static const verified = 'verified'; + static const idEnding = 'idEnding'; + static const servicesAndActions = 'servicesAndActions'; + static const startDependentFlowsDesc = 'startDependentFlowsDesc'; + static const mazayaAccess = 'mazayaAccess'; + static const accessOff = 'accessOff'; + static const allowDependentMazayaOffersDesc = 'allowDependentMazayaOffersDesc'; + static const loginMethod = 'loginMethod'; + static const nationalIdAndDob = 'nationalIdAndDob'; + static const sponsorOtp = 'sponsorOtp'; + static const revokingPreventsAccessDesc = 'revokingPreventsAccessDesc'; } diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index b689bbd..f0af056 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -254,7 +254,7 @@ class _LoginScreenState extends State { DateTime? parsed = _parseBirthDate(rawDob); if (parsed != null) { selectedDob = parsed; - dobController.text = DateFormat('yyyy-MM-dd').format(parsed); + dobController.text = DateFormat('dd-MM-yyyy').format(parsed); } else { dobController.text = rawDob; } @@ -295,7 +295,7 @@ class _LoginScreenState extends State { if (dt != null) return DateTime(dt.year, dt.month, dt.day); // 4. Try standard custom date patterns - List formats = ['yyyy-MM-dd', 'dd/MM/yyyy', 'MM/dd/yyyy', 'dd-MM-yyyy', 'yyyy/MM/dd']; + List formats = ['dd-MM-yyyy', 'yyyy-MM-dd', 'dd/MM/yyyy', 'MM/dd/yyyy', 'yyyy/MM/dd']; for (String fmt in formats) { try { DateTime parsed = DateFormat(fmt).parseStrict(s); @@ -317,7 +317,7 @@ class _LoginScreenState extends State { DateTime? parsed = _parseBirthDate(savedDob); if (parsed != null) { selectedDob = parsed; - dobController.text = DateFormat('yyyy-MM-dd').format(parsed); + dobController.text = DateFormat('dd-MM-yyyy').format(parsed); } else { dobController.text = savedDob; } @@ -327,7 +327,7 @@ class _LoginScreenState extends State { DateTime? parsed = _parseBirthDate(dobController.text); if (parsed != null) { selectedDob = parsed; - dobController.text = DateFormat('yyyy-MM-dd').format(parsed); + dobController.text = DateFormat('dd-MM-yyyy').format(parsed); } } } @@ -530,7 +530,7 @@ class _LoginScreenState extends State { if (picked != null) { setState(() { selectedDob = picked; - dobController.text = DateFormat('yyyy-MM-dd', 'en_US').format(picked); + dobController.text = DateFormat('dd-MM-yyyy', 'en_US').format(picked); }); } } diff --git a/lib/ui/profile/dependent_details_screen.dart b/lib/ui/profile/dependent_details_screen.dart new file mode 100644 index 0000000..a0c8a81 --- /dev/null +++ b/lib/ui/profile/dependent_details_screen.dart @@ -0,0 +1,596 @@ +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/classes/utils.dart'; +import 'package:mohem_flutter_app/config/routes.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/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'; + +class _ShieldIconPainter extends CustomPainter { + final Color color; + final bool isGranted; + + _ShieldIconPainter({required this.color, required this.isGranted}); + + @override + void paint(Canvas canvas, Size size) { + 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; + + 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) { + 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 { + 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; +} + +class DependentDetailsScreen extends StatefulWidget { + final GetEmployeeContactsList? contact; + + const DependentDetailsScreen({Key? key, this.contact}) : super(key: key); + + @override + _DependentDetailsScreenState createState() => _DependentDetailsScreenState(); +} + +class _DependentDetailsScreenState extends State { + GetEmployeeContactsList? _contact; + bool _argsLoaded = false; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (!_argsLoaded) { + if (widget.contact != null) { + _contact = widget.contact; + } else { + Object? args = ModalRoute.of(context)?.settings.arguments; + if (args is GetEmployeeContactsList) { + _contact = args; + } else if (args is Map && args['contact'] is GetEmployeeContactsList) { + _contact = args['contact'] as GetEmployeeContactsList; + } + } + _argsLoaded = true; + } + } + + bool get _isAccessAllowed { + if (_contact == null) return false; + String? flag = _contact!.mAZAYAACCESSFLG ?? _contact!.mazayaAccess?.toString(); + return flag?.trim().toUpperCase() == 'Y'; + } + + 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(); + } + + String _getFirstName(String? name) { + if (name == null || name.trim().isEmpty) return ""; + List parts = name.trim().split(RegExp(r'\s+')).where((String p) => p.isNotEmpty).toList(); + if (parts.isEmpty) return ""; + return parts.first; + } + + DateTime? _parseBirthDate(String? dateStr) { + if (dateStr == null || dateStr.trim().isEmpty) return null; + String s = dateStr.trim(); + + RegExp dateRegex = RegExp(r'/?Date\((-?\d+)(?:[+-]\d+)?\)/?'); + Match? match = dateRegex.firstMatch(s); + if (match != null) { + try { + int millis = int.parse(match.group(1)!); + DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true); + return DateTime(dtUtc.year, dtUtc.month, dtUtc.day); + } catch (_) {} + } + + if (RegExp(r'^-?\d{10,14}$').hasMatch(s)) { + try { + int millis = int.parse(s); + DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true); + return DateTime(dtUtc.year, dtUtc.month, dtUtc.day); + } catch (_) {} + } + + 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', + ]; + for (String fmt in formats) { + try { + DateTime parsed = DateFormat(fmt, 'en_US').parseStrict(s); + return DateTime(parsed.year, parsed.month, parsed.day); + } catch (_) {} + } + + return null; + } + + String _formatDob(DateTime? dob, String? raw) { + if (dob != null) { + return DateFormat('dd MMM yyyy', 'en_US').format(dob); + } + if (raw != null && raw.trim().isNotEmpty) { + return raw.split(' ').first; + } + return "-"; + } + + String _formatNationalId(String? nationalId) { + if (nationalId == null || nationalId.trim().isEmpty) return "-"; + String clean = nationalId.trim(); + String last4 = clean.length >= 4 ? clean.substring(clean.length - 4) : clean; + return LocaleKeys.idEnding.tr(args: [last4]); + } + + void _onToggleAccess() { + if (_contact == null) return; + bool isAllowed = _isAccessAllowed; + 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), + confirmButtonColors: isAllowed + ? const [Color(0xFFEF4444), Color(0xFFDC2626)] + : const [Color(0xFF22B0A6), Color(0xFF168A89)], + confirmText: LocaleKeys.yes.tr(), + cancelText: LocaleKeys.no.tr(), + onConfirm: _performToggleAccess, + ); + } + + Future _performToggleAccess() async { + if (_contact == null) return; + bool isAllowed = _isAccessAllowed; + String action = isAllowed ? "Revoke" : "Grant"; + + try { + Utils.showLoading(context); + await ProfileApiClient().processContactMazayaAccess( + accessAction: action, + contactRelationshipId: _contact!.cONTACTRELATIONSHIPID, + contactPersonId: _contact!.cONTACTPERSONID, + personId: _contact!.pERSONID, + ); + + setState(() { + _contact!.mAZAYAACCESSFLG = action == "Grant" ? "Y" : "N"; + _contact!.mazayaAccess = _contact!.mAZAYAACCESSFLG; + }); + + Utils.hideLoading(context); + + 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()); + } + } + } catch (ex) { + Utils.hideLoading(context); + Utils.handleException(ex, context, null); + } + } + + @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 formattedId = _formatNationalId(_contact?.nATIONALIDENTIFIER); + String formattedDob = _formatDob(dob, _contact?.dATEOFBIRTH); + bool isAllowed = _isAccessAllowed; + + return Scaffold( + backgroundColor: const Color(0xFFF8FAFC), + appBar: PreferredSize( + preferredSize: const Size.fromHeight(56), + child: SafeArea( + child: Container( + height: 56, + color: const Color(0xFFF8FAFC), + 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: 8), + 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), + ), + ), + ), + ], + ), + ), + ), + ), + 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, + 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), + ), + ), + ), + 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, + ), + ], + ), + ), + + 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), + ), + ], + ), + ], + ), + ), + + 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), + ), + + 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), + ), + ), + ], + ), + 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), + + // 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, + ), + ], + ), + ), + ), + + if (isAllowed) ...[ + const SizedBox(height: 12), + Center( + child: LocaleKeys.revokingPreventsAccessDesc.tr().toText11( + weight: FontWeight.w400, + color: const Color(0xFF64748B), + isCenter: true, + ), + ), + ], + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/ui/profile/family_members.dart b/lib/ui/profile/family_members.dart index 0fbbddd..8c45dde 100644 --- a/lib/ui/profile/family_members.dart +++ b/lib/ui/profile/family_members.dart @@ -11,6 +11,7 @@ import 'package:mohem_flutter_app/models/dashboard/menu_entries.dart'; 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'; @@ -79,21 +80,6 @@ class _FamilyMembersState extends State { ); } - 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"; @@ -362,13 +348,18 @@ 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 InkWell( borderRadius: BorderRadius.circular(16), - onTap: canOpenGrantedScreen ? () => _openMazayaGrantedScreen(item) : null, + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext cxt) => DependentDetailsScreen(contact: item), + ), + ); + getEmployeeContacts(); + }, child: Container( padding: const EdgeInsets.fromLTRB(16, 16, 16, 14), decoration: BoxDecoration(