|
|
|
|
@ -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,
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|