Merge branch 'wala_plus' of http://34.17.182.140/Haroon6138/mohemm-flutter-app into wala_plus

* 'wala_plus' of http://34.17.182.140/Haroon6138/mohemm-flutter-app:
  UI Fixes
wala_plus
Sultan khan 12 hours ago
commit 87bcdf72cb

@ -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) {

@ -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<DependentDetailsScreen> {
GetEmployeeContactsList? _contact;
bool _argsLoaded = false;
bool _justGranted = false;
@override
void didChangeDependencies() {
@ -89,6 +95,7 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
_contact = args['contact'] as GetEmployeeContactsList;
}
}
_justGranted = widget.justGranted;
_argsLoaded = true;
}
}
@ -141,15 +148,7 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
DateTime? dt = DateTime.tryParse(s);
if (dt != null) return DateTime(dt.year, dt.month, dt.day);
List<String> formats = <String>[
'yyyy-MM-dd',
'dd/MM/yyyy',
'MM/dd/yyyy',
'dd-MM-yyyy',
'yyyy/MM/dd',
'dd-MMM-yyyy',
'dd MMM yyyy',
];
List<String> formats = <String>['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<DependentDetailsScreen> {
ConfirmBottomSheet.show(
context,
title: isAllowed ? LocaleKeys.revokeMazayaAccess.tr() : LocaleKeys.allowMazayaAccess.tr(),
message: isAllowed
? LocaleKeys.confirmRevokeMazayaAccess.tr(args: <String>[dependentName])
: LocaleKeys.confirmGrantMazayaAccess.tr(args: <String>[dependentName]),
message: isAllowed ? LocaleKeys.confirmRevokeMazayaAccess.tr(args: <String>[dependentName]) : LocaleKeys.confirmGrantMazayaAccess.tr(args: <String>[dependentName]),
icon: isAllowed ? Icons.remove_circle_outline : Icons.check_circle_outline,
iconColor: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89),
confirmButtonColors: isAllowed
? const <Color>[Color(0xFFEF4444), Color(0xFFDC2626)]
: const <Color>[Color(0xFF22B0A6), Color(0xFF168A89)],
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: _performToggleAccess,
@ -216,27 +210,19 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
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<dynamic>(
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<DependentDetailsScreen> {
}
}
// 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<void> _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<DependentDetailsScreen> {
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<DependentDetailsScreen> {
),
),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 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: <Widget>[
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 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) ...<Widget>[
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: <Widget>[
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) ...<Widget>[
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: <Widget>[
fullName.toText16(maxlines: 2, weight: FontWeight.w500, color: const Color(0xFF1E293B)),
if (relation.isNotEmpty) ...<Widget>[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>[
BoxShadow(
color: Color(0x08000000),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
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: <Widget>[
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: <Widget>[
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>[BoxShadow(color: Color(0x08000000), blurRadius: 8, offset: Offset(0, 2))],
),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
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: <Widget>[
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: <Widget>[
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>[
BoxShadow(
color: Color(0x08000000),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Header Row: Mazaya access + Badge
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
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>[BoxShadow(color: Color(0x08000000), blurRadius: 8, offset: Offset(0, 2))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Header Row: Mazaya access + Badge
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
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: <String>[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: <Widget>[
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: <Widget>[
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: <String>[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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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) ...<Widget>[
const SizedBox(height: 12),
Center(child: LocaleKeys.revokingPreventsAccessDesc.tr().toText11(weight: FontWeight.w400, color: const Color(0xFF64748B), isCenter: true)),
],
),
],
),
),
if (isAllowed) ...<Widget>[
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,
],
),
),
);

@ -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<FamilyMembers> {
if (mounted) {
setState(() {});
if (action == "Grant") {
Navigator.push(
context,
MaterialPageRoute<dynamic>(
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<dynamic>(builder: (BuildContext cxt) => DependentDetailsScreen(contact: contact, justGranted: true)));
getEmployeeContacts();
} else {
Utils.showToast(LocaleKeys.revokeMazayaAccess.tr());
}
@ -142,11 +135,7 @@ class _FamilyMembersState extends State<FamilyMembers> {
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<FamilyMembers> {
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<FamilyMembers> {
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: <Widget>[
@ -263,9 +228,7 @@ class _FamilyMembersState extends State<FamilyMembers> {
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<FamilyMembers> {
_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<FamilyMembers> {
// 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: <String, dynamic>{'fromMazaya': fromMazaya},
);
getEmployeeContacts();
}
await Navigator.pushNamed(context, AppRoutes.addDependentVerify, arguments: <String, dynamic>{'fromMazaya': fromMazaya});
getEmployeeContacts();
}
: null,
iconData: Icons.add,
isTextExpanded: false,
@ -341,279 +285,232 @@ class _FamilyMembersState extends State<FamilyMembers> {
);
}
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: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
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: <Widget>[
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<dynamic>(
builder: (BuildContext cxt) => DependentDetailsScreen(contact: item),
),
);
await Navigator.push(context, MaterialPageRoute<dynamic>(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>[
BoxShadow(
color: Color(0x0A000000),
blurRadius: 8,
offset: Offset(0, 3),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Top row: Avatar, Info, and Status Badge
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// 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>[BoxShadow(color: Color(0x0A000000), blurRadius: 8, offset: Offset(0, 3))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Top row: Avatar, Info, and Status Badge
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// 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: <Widget>[
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: <Widget>[Flexible(child: (item.rELATIONSHIP ?? "").toText12(maxLine: 1, color: const Color(0xFF168A89)))]),
],
),
),
),
const SizedBox(width: 12),
// Name & Details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
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) ...<Widget>[
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) ...<Widget>[
// 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: <Widget>[
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) ...<Widget>[
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) ...<Widget>[
// 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: <Widget>[
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 ...<Widget>[
// Profile Flow: Update and Remove action buttons
Row(
children: <Widget>[
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 ...<Widget>[
// Profile Flow: Update and Remove action buttons
Row(
children: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[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: <Widget>[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<FamilyMembers> {
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: <Widget>[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: <Widget>[cancelButton, continueButton]);
showDialog(context: context, builder: (BuildContext context) => alert);
}
}

@ -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<String> 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<void> _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<void> _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 <Color>[Colors.white, Colors.white],

Loading…
Cancel
Save