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:
  km away fixed.
wala_plus
Sultan khan 3 days ago
commit 42b2844dbd

@ -602,6 +602,11 @@
"addUsers": "إضافة مستخدمين إلى المجموعة",
"admin": "مشرف",
"areYouSureWantTodelete": "هل أنت متأكد أنك تريد الحذف؟",
"confirmGrantMazayaAccess": "هل أنت متأكد أنك تريد منح صلاحية مزايا لـ \"{}\"؟",
"confirmRevokeMazayaAccess": "هل أنت متأكد أنك تريد إلغاء صلاحية مزايا لـ \"{}\"؟",
"yes": "نعم",
"no": "لا",
"mazayaInviteInstructions": "يرجى تحميل تطبيق مهمم من الرابط أعلاه واستخدام بيانات الاعتماد التالية لتسجيل الدخول",
"Attendance": "الحضور",
"audioCall": "مكالمة صوتية",
"editGroups": "تعديل المجموعة",

@ -549,6 +549,11 @@
"manage": "Manage",
"members": "Members",
"areYouSureWantTodelete": "Are you sure want to delete?",
"confirmGrantMazayaAccess": "Are you sure you want to grant Mazaya access to \"{}\"?",
"confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?",
"yes": "Yes",
"no": "No",
"mazayaInviteInstructions": "Please download the Mohemm app from the above link and use the following credentials to login",
"groupMembers": "Group Members",
"manageGroup": "Manage Group",
"admin": "Admin",

@ -90,8 +90,6 @@ class ApiConsts {
//DummyCards for the UI
static int tabletMinLength = 500;
// Notification Center GUIDs for OTP

@ -618,6 +618,11 @@ class CodegenLoader extends AssetLoader{
"addUsers": "إضافة مستخدمين إلى المجموعة",
"admin": "مشرف",
"areYouSureWantTodelete": "هل أنت متأكد أنك تريد الحذف؟",
"confirmGrantMazayaAccess": "هل أنت متأكد أنك تريد منح صلاحية مزايا لـ \"{}\"؟",
"confirmRevokeMazayaAccess": "هل أنت متأكد أنك تريد إلغاء صلاحية مزايا لـ \"{}\"؟",
"yes": "نعم",
"no": "لا",
"mazayaInviteInstructions": "يرجى تحميل تطبيق مهمم من الرابط أعلاه واستخدام بيانات الاعتماد التالية لتسجيل الدخول",
"Attendance": "الحضور",
"audioCall": "مكالمة صوتية",
"editGroups": "تعديل المجموعة",
@ -1349,6 +1354,11 @@ static const Map<String,dynamic> _en_US = {
"manage": "Manage",
"members": "Members",
"areYouSureWantTodelete": "Are you sure want to delete?",
"confirmGrantMazayaAccess": "Are you sure you want to grant Mazaya access to \"{}\"?",
"confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?",
"yes": "Yes",
"no": "No",
"mazayaInviteInstructions": "Please download the Mohemm app from the above link and use the following credentials to login",
"groupMembers": "Group Members",
"manageGroup": "Manage Group",
"admin": "Admin",

@ -590,6 +590,11 @@ abstract class LocaleKeys {
static const addUsers = 'addUsers';
static const admin = 'admin';
static const areYouSureWantTodelete = 'areYouSureWantTodelete';
static const confirmGrantMazayaAccess = 'confirmGrantMazayaAccess';
static const confirmRevokeMazayaAccess = 'confirmRevokeMazayaAccess';
static const yes = 'yes';
static const no = 'no';
static const mazayaInviteInstructions = 'mazayaInviteInstructions';
static const Attendance = 'Attendance';
static const audioCall = 'audioCall';
static const editGroups = 'editGroups';

@ -149,31 +149,56 @@ class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsS
);
}
// Vue's @click="startScanning()" is a component method, not something on window,
// so we find the actual button by its text and click it (lets Vue's own handler fire).
// Vue may still be mounting when onLoadStop fires, so poll briefly before giving up.
// Vue's @click handlers are component methods, not something on window, so we find the
// actual button by its text and click it (lets Vue's own handler fire).
// Two buttons matter here: "Start Scanning" (the initial trigger) and "Try Again" (shown
// instead of "Start Scanning" if the camera permission flow takes a while - clicking it
// retries the same flow). Either may need clicking more than once if permission stays
// pending, so instead of a one-shot "clicked" flag we keep watching for as long as the
// safety-net timeout allows, with a short cooldown after each click so a single button
// occurrence can't get double-clicked from back-to-back mutation events before the page
// has a chance to react.
// This can be injected more than once (e.g. once on page load, again once the native camera
// permission result comes back) - state lives on `window`, not in the closure, so a repeat
// call never starts a second observer or spams clicks.
// Only called for offerType "discount" - "promo" offers just show a promo code, no scanner.
void _clickStartScanningButton(InAppWebViewController controller) {
controller.evaluateJavascript(
source: r"""
(function() {
var attempts = 0;
var maxAttempts = 50; // ~10s at 200ms - page loads Tailwind from a CDN and mounts Vue, which can take a while on a real network
var timer = setInterval(function() {
attempts++;
var cooldownMs = 800;
function tryClick() {
var now = Date.now();
if (now - (window.__mohemmScanLastClickAt || 0) < cooldownMs) return;
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
var text = (buttons[i].textContent || '').replace(/\s+/g, ' ').trim();
if (text.indexOf('Start Scanning') !== -1) {
if (text.indexOf('Start Scanning') !== -1 || text.indexOf('Try Again') !== -1) {
buttons[i].click();
clearInterval(timer);
window.__mohemmScanLastClickAt = now;
return;
}
}
}
if (attempts >= maxAttempts) {
clearInterval(timer);
}
}, 200);
}
tryClick();
if (window.__mohemmScanObserverActive) return;
window.__mohemmScanObserverActive = true;
var observer = new MutationObserver(function() {
tryClick();
});
observer.observe(document.body, { childList: true, subtree: true });
// Safety net only - stop watching after a while so we don't observe forever
// (e.g. permission denied for good, or user left the page).
setTimeout(function() {
observer.disconnect();
window.__mohemmScanObserverActive = false;
}, 120000);
})();
""",
);

@ -126,7 +126,6 @@ class _LoginScreenState extends State<LoginScreen> {
GetMobileLoginInfoListModel? loginInfo;
GetMobileLoginInfoDependentModel? dependentLoginInfo;
Future<void> checkFirebaseToken() async {
try {
await checkPrefs();
@ -217,11 +216,7 @@ class _LoginScreenState extends State<LoginScreen> {
String devType = Platform.isAndroid ? "android" : "ios";
if (ApiConsts.selectedMemberType.isDependent) {
dependentLoginInfo = await LoginApiClient().getMobileLoginInfoNEWDependent(
devToken,
devType,
memberType: MemberType.dependent.value,
);
dependentLoginInfo = await LoginApiClient().getMobileLoginInfoNEWDependent(devToken, devType, memberType: MemberType.dependent.value);
Utils.hideLoading(context);
if (dependentLoginInfo != null) {
_handleDependentLoginInfo(dependentLoginInfo!);
@ -229,11 +224,7 @@ class _LoginScreenState extends State<LoginScreen> {
return;
}
loginInfo = await LoginApiClient().getMobileLoginInfoNEW(
devToken,
devType,
memberType: ApiConsts.selectedMemberType.value,
);
loginInfo = await LoginApiClient().getMobileLoginInfoNEW(devToken, devType, memberType: ApiConsts.selectedMemberType.value);
if (loginInfo == null) {
await checkPrefs();
_autoLogin = false;
@ -257,9 +248,7 @@ class _LoginScreenState extends State<LoginScreen> {
nationalId.text = info.dependentID!;
}
String? rawDob = (info.birthDate != null && info.birthDate!.isNotEmpty)
? info.birthDate
: ((info.dob != null && info.dob!.isNotEmpty) ? info.dob : null);
String? rawDob = (info.birthDate != null && info.birthDate!.isNotEmpty) ? info.birthDate : ((info.dob != null && info.dob!.isNotEmpty) ? info.dob : null);
if (rawDob != null && rawDob.isNotEmpty) {
DateTime? parsed = _parseBirthDate(rawDob);
@ -341,7 +330,6 @@ class _LoginScreenState extends State<LoginScreen> {
dobController.text = DateFormat('yyyy-MM-dd').format(parsed);
}
}
}
Future<void> checkPrefs() async {
@ -434,9 +422,7 @@ class _LoginScreenState extends State<LoginScreen> {
Utils.handleException(ex, context, null);
}
},
() => {
Navigator.pop(context),
},
() => {Navigator.pop(context)},
onResendCode: () {
performForgotPassword();
},
@ -467,13 +453,7 @@ class _LoginScreenState extends State<LoginScreen> {
}
}
Widget _buildCtaCard({
required BuildContext context,
required IconData icon,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
Widget _buildCtaCard({required BuildContext context, required IconData icon, required String title, required String subtitle, required VoidCallback onTap}) {
bool isRtl = Directionality.of(context) == ui.TextDirection.rtl || AppState().isArabic(context);
return InkWell(
@ -482,51 +462,30 @@ class _LoginScreenState extends State<LoginScreen> {
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFEAF8F4),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFB8E8DB), width: 1.0),
),
decoration: BoxDecoration(color: const Color(0xFFEAF8F4), borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFB8E8DB), width: 1.0)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 40,
height: 40,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
alignment: Alignment.center,
child: Icon(
icon,
color: const Color(0xFF168A89),
size: 22,
),
child: Icon(icon, color: const Color(0xFF168A89), size: 22),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
title.toText14(
isBold: true,
color: const Color(0xFF1E293B),
textAlign: isRtl ? TextAlign.right : TextAlign.left,
),
title.toText14(isBold: true, color: const Color(0xFF1E293B), textAlign: isRtl ? TextAlign.right : TextAlign.left),
const SizedBox(height: 4),
subtitle.toText12(
color: const Color(0xFF64748B),
),
subtitle.toText12(color: const Color(0xFF64748B)),
],
),
),
const SizedBox(width: 8),
Icon(
isRtl ? Icons.chevron_left_rounded : Icons.chevron_right_rounded,
color: const Color(0xFF64748B),
size: 20,
),
Icon(isRtl ? Icons.chevron_left_rounded : Icons.chevron_right_rounded, color: const Color(0xFF64748B), size: 20),
],
),
),
@ -564,16 +523,7 @@ class _LoginScreenState extends State<LoginScreen> {
firstDate: firstDate,
lastDate: lastDate,
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
colorScheme: const ColorScheme.light(
primary: MyColors.textMixColor,
onPrimary: Colors.white,
onSurface: MyColors.darkTextColor,
),
),
child: child!,
);
return Theme(data: Theme.of(context).copyWith(colorScheme: const ColorScheme.light(primary: MyColors.textMixColor, onPrimary: Colors.white, onSurface: MyColors.darkTextColor)), child: child!);
},
);
@ -600,15 +550,7 @@ class _LoginScreenState extends State<LoginScreen> {
name = await Utils.getStringFromPrefs("P_DEPENDENT_NAME");
}
if (!mounted) return;
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => MazayaAccessResultScreen(
isGranted: false,
dependentName: name,
),
),
);
Navigator.push(context, MaterialPageRoute(builder: (_) => MazayaAccessResultScreen(isGranted: false, dependentName: name)));
}
void performDependentLogin() async {
@ -664,10 +606,7 @@ class _LoginScreenState extends State<LoginScreen> {
}
}
String? authToken = (depLoginResponse?.authenticationTokenID != null &&
depLoginResponse!.authenticationTokenID!.trim().isNotEmpty)
? depLoginResponse.authenticationTokenID!.trim()
: null;
String? authToken = (depLoginResponse?.authenticationTokenID != null && depLoginResponse!.authenticationTokenID!.trim().isNotEmpty) ? depLoginResponse.authenticationTokenID!.trim() : null;
String? logInTokenID = depLoginResponse?.logInTokenID;
String? mobileNumber = depLoginResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber;
@ -676,13 +615,7 @@ class _LoginScreenState extends State<LoginScreen> {
// No need to show OTP screen, direct move to dashboard and use AuthenticationTokenID as TokenID for all other requests
if (authToken != null && authToken.isNotEmpty) {
Utils.hideLoading(context);
_completeDependentLogin(
depLoginResponse,
logInTokenID ?? authToken,
mobileNumber ?? "",
loginType: 1,
navigateToDashboard: true,
);
_completeDependentLogin(depLoginResponse, logInTokenID ?? authToken, mobileNumber ?? "", loginType: 1, navigateToDashboard: true);
return;
}
@ -750,13 +683,7 @@ class _LoginScreenState extends State<LoginScreen> {
Navigator.pop(context);
_completeDependentLogin(
checkOtpResponse,
logInTokenID,
mobileNumber,
loginType: 1,
navigateToDashboard: true,
);
_completeDependentLogin(checkOtpResponse, logInTokenID, mobileNumber, loginType: 1, navigateToDashboard: true);
} catch (ex) {
if (kDebugMode) print(ex);
_pinPutController.clear();
@ -776,12 +703,7 @@ class _LoginScreenState extends State<LoginScreen> {
onResendCode: () async {
Utils.showLoading(context);
try {
await LoginApiClient().mohemmSendOTPDependent(
nationalId: nationalId.text.trim(),
mobileNumber: mobileNumber,
logInTokenID: logInTokenID,
context: context,
);
await LoginApiClient().mohemmSendOTPDependent(nationalId: nationalId.text.trim(), mobileNumber: mobileNumber, logInTokenID: logInTokenID, context: context);
Utils.hideLoading(context);
Utils.showToast(LocaleKeys.verificationCodeResent.tr());
_showDependentOtpDialog(logInTokenID, mobileNumber);
@ -799,25 +721,20 @@ class _LoginScreenState extends State<LoginScreen> {
}
void _completeDependentLogin(GenericResponseModel? checkOtpResponse, String logInTokenID, String mobileNumber, {int loginType = 1, bool navigateToDashboard = true}) {
String effectiveToken = (checkOtpResponse?.authenticationTokenID != null && checkOtpResponse!.authenticationTokenID!.trim().isNotEmpty)
? checkOtpResponse.authenticationTokenID!.trim()
: ((checkOtpResponse?.tokenID != null && checkOtpResponse!.tokenID!.isNotEmpty)
? checkOtpResponse.tokenID!
: ((checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty)
? checkOtpResponse.logInTokenID!
: logInTokenID));
String effectiveToken =
(checkOtpResponse?.authenticationTokenID != null && checkOtpResponse!.authenticationTokenID!.trim().isNotEmpty)
? checkOtpResponse.authenticationTokenID!.trim()
: ((checkOtpResponse?.tokenID != null && checkOtpResponse!.tokenID!.isNotEmpty)
? checkOtpResponse.tokenID!
: ((checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty) ? checkOtpResponse.logInTokenID! : logInTokenID));
AppState().postParamsObject?.setTokenID = effectiveToken;
AppState().setAuthenticationTokenID = effectiveToken;
AppState().postParamsObject?.setLogInTokenID = (checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty)
? checkOtpResponse.logInTokenID!
: logInTokenID;
AppState().postParamsObject?.setLogInTokenID = (checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty) ? checkOtpResponse.logInTokenID! : logInTokenID;
AppState().postParamsObject?.setUserName = nationalId.text.trim();
AppState().postParamsObject?.setPUserName = nationalId.text.trim();
String resolvedMobile = mobileNumber.isNotEmpty
? mobileNumber
: (checkOtpResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber ?? "");
String resolvedMobile = mobileNumber.isNotEmpty ? mobileNumber : (checkOtpResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber ?? "");
if (resolvedMobile.isNotEmpty) {
AppState().postParamsObject?.setMobileNumer = resolvedMobile;
Utils.saveStringFromPrefs("P_DEP_MOBILE_NUMBER", resolvedMobile);
@ -856,7 +773,6 @@ class _LoginScreenState extends State<LoginScreen> {
}
}
@override
Widget build(BuildContext context) {
if (isAppOpenBySystem == null) {
@ -894,12 +810,7 @@ class _LoginScreenState extends State<LoginScreen> {
LocaleKeys.english.tr().toText14(color: AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() {
context.setLocale(const Locale("en", "US"));
}),
Container(
width: 1,
color: MyColors.darkWhiteColor,
height: 16,
margin: const EdgeInsets.only(left: 10, right: 10),
),
Container(width: 1, color: MyColors.darkWhiteColor, height: 16, margin: const EdgeInsets.only(left: 10, right: 10)),
LocaleKeys.arabic.tr().toText14(color: !AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() {
context.setLocale(const Locale("ar", "SA"));
}),
@ -913,9 +824,7 @@ class _LoginScreenState extends State<LoginScreen> {
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -931,11 +840,9 @@ class _LoginScreenState extends State<LoginScreen> {
9.height,
Align(
alignment: AlignmentDirectional.centerEnd,
child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress(
() {
performForgotPassword();
},
),
child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress(() {
performForgotPassword();
}),
),
20.height,
_buildDependentCtaCard(context),
@ -948,21 +855,11 @@ class _LoginScreenState extends State<LoginScreen> {
child: Stack(
alignment: AlignmentDirectional.centerEnd,
children: [
IgnorePointer(
child: InputWidget(
LocaleKeys.dateOfBirth.tr(),
"YYYY-MM-DD",
dobController,
),
),
IgnorePointer(child: InputWidget(LocaleKeys.dateOfBirth.tr(), "DD-MM-YYYY", dobController)),
Positioned.directional(
textDirection: Directionality.of(context),
end: 16,
child: const Icon(
Icons.calendar_today_outlined,
size: 20,
color: MyColors.grey70Color,
),
child: const Icon(Icons.calendar_today_outlined, size: 20, color: MyColors.grey70Color),
),
],
),
@ -977,22 +874,19 @@ class _LoginScreenState extends State<LoginScreen> {
);
},
),
)
),
],
),
),
),
DefaultButton(
LocaleKeys.login.tr(),
() async {
SystemChannels.textInput.invokeMethod('TextInput.hide');
if (ApiConsts.ISDependetLogin) {
performDependentLogin();
} else {
performLogin();
}
},
).insideContainer
DefaultButton(LocaleKeys.login.tr(), () async {
SystemChannels.textInput.invokeMethod('TextInput.hide');
if (ApiConsts.ISDependetLogin) {
performDependentLogin();
} else {
performLogin();
}
}).insideContainer,
],
),
);

@ -2,6 +2,7 @@ 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/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.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';
@ -11,6 +12,7 @@ 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/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';
@ -59,6 +61,39 @@ class _FamilyMembersState extends State<FamilyMembers> {
return flag?.trim().toUpperCase() == 'Y';
}
void _confirmToggleMazayaAccess(GetEmployeeContactsList contact) {
bool isAllowed = _isAccessAllowed(contact);
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),
// Same gradient as the "Add another user" button on this screen (DefaultButton's default).
confirmButtonColors: const [MyColors.gradiantEndColor, MyColors.gradiantStartColor],
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: () => _toggleMazayaAccess(contact),
);
}
void _openMazayaGrantedScreen(GetEmployeeContactsList item) {
Navigator.push(
context,
MaterialPageRoute<dynamic>(
builder: (BuildContext cxt) => MazayaAccessResultScreen(
isGranted: true,
dependentName: item.cONTACTNAME ?? "",
dependentInitials: _getInitials(item.cONTACTNAME),
nationalId: item.nATIONALIDENTIFIER,
dateOfBirth: item.dATEOFBIRTH,
),
),
);
}
Future<void> _toggleMazayaAccess(GetEmployeeContactsList contact) async {
bool current = _isAccessAllowed(contact);
String action = current ? "Revoke" : "Grant";
@ -87,6 +122,8 @@ class _FamilyMembersState extends State<FamilyMembers> {
isGranted: true,
dependentName: contact.cONTACTNAME ?? "",
dependentInitials: _getInitials(contact.cONTACTNAME),
nationalId: contact.nATIONALIDENTIFIER,
dateOfBirth: contact.dATEOFBIRTH,
),
),
);
@ -325,8 +362,14 @@ class _FamilyMembersState extends State<FamilyMembers> {
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 Container(
return InkWell(
borderRadius: BorderRadius.circular(16),
onTap: canOpenGrantedScreen ? () => _openMazayaGrantedScreen(item) : null,
child: Container(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 14),
decoration: BoxDecoration(
color: Colors.white,
@ -430,7 +473,7 @@ class _FamilyMembersState extends State<FamilyMembers> {
Center(
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => _toggleMazayaAccess(item),
onTap: () => _confirmToggleMazayaAccess(item),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
child: Row(
@ -544,6 +587,7 @@ class _FamilyMembersState extends State<FamilyMembers> {
],
],
),
),
);
}

@ -8,6 +8,7 @@ import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
class MazayaAccessResultParams {
@ -15,12 +16,16 @@ class MazayaAccessResultParams {
final String dependentName;
final String? dependentInitials;
final String? downloadUrl;
final String? nationalId;
final String? dateOfBirth;
const MazayaAccessResultParams({
required this.isGranted,
required this.dependentName,
this.dependentInitials,
this.downloadUrl,
this.nationalId,
this.dateOfBirth,
});
}
@ -85,6 +90,8 @@ class MazayaAccessResultScreen extends StatelessWidget {
final String dependentName;
final String? dependentInitials;
final String? downloadUrl;
final String? nationalId;
final String? dateOfBirth;
const MazayaAccessResultScreen({
Key? key,
@ -92,6 +99,8 @@ class MazayaAccessResultScreen extends StatelessWidget {
this.dependentName = "",
this.dependentInitials,
this.downloadUrl,
this.nationalId,
this.dateOfBirth,
}) : super(key: key);
static String getDownloadLink() {
@ -129,17 +138,23 @@ class MazayaAccessResultScreen extends StatelessWidget {
String effectiveName = dependentName;
String? effectiveInitials = dependentInitials;
String? effectiveUrl = downloadUrl;
String? effectiveNationalId = nationalId;
String? effectiveDateOfBirth = dateOfBirth;
if (routeArgs is MazayaAccessResultParams) {
effectiveGranted = routeArgs.isGranted;
effectiveName = routeArgs.dependentName;
effectiveInitials = routeArgs.dependentInitials;
effectiveUrl = routeArgs.downloadUrl;
effectiveNationalId = routeArgs.nationalId;
effectiveDateOfBirth = routeArgs.dateOfBirth;
} else if (routeArgs is Map<String, dynamic>) {
effectiveGranted = routeArgs['isGranted'] ?? isGranted;
effectiveName = routeArgs['dependentName'] ?? dependentName;
effectiveInitials = routeArgs['dependentInitials'] ?? dependentInitials;
effectiveUrl = routeArgs['downloadUrl'] ?? downloadUrl;
effectiveNationalId = routeArgs['nationalId'] ?? nationalId;
effectiveDateOfBirth = routeArgs['dateOfBirth'] ?? dateOfBirth;
}
if (effectiveName.trim().isEmpty) {
@ -210,7 +225,7 @@ class MazayaAccessResultScreen extends StatelessWidget {
: _buildRevokedContent(context, isAr, fontFamily, effectiveName, initials),
),
),
_buildBottomButtons(context, isAr, fontFamily, effectiveGranted, linkToShare),
_buildBottomButtons(context, isAr, fontFamily, effectiveGranted, linkToShare, effectiveNationalId ?? "", effectiveDateOfBirth ?? ""),
],
),
),
@ -604,12 +619,41 @@ class MazayaAccessResultScreen extends StatelessWidget {
);
}
// 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 {
String message =
"$linkToShare\n\n${LocaleKeys.mazayaInviteInstructions.tr()}\n\n${LocaleKeys.nationalId.tr()}: $nationalId\n${LocaleKeys.dateOfBirth.tr()}: $dateOfBirth";
try {
String fileName = isAr ? "mazaya_invite_steps_ar.pdf" : "mazaya_invite_steps_en.pdf";
ByteData bytes = await rootBundle.load("assets/pdf/$fileName");
Directory tempDir = await getTemporaryDirectory();
File pdfFile = File('${tempDir.path}/$fileName');
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 (_) {}
}
}
Widget _buildBottomButtons(
BuildContext context,
bool isAr,
String fontFamily,
bool granted,
String linkToShare,
String nationalId,
String dateOfBirth,
) {
if (granted) {
return Container(
@ -620,11 +664,7 @@ class MazayaAccessResultScreen extends StatelessWidget {
children: <Widget>[
DefaultButton(
LocaleKeys.shareDownloadLink.tr(),
() async {
try {
await SharePlus.instance.share(ShareParams(text: linkToShare));
} catch (_) {}
},
() => _shareMazayaInvite(isAr, linkToShare, nationalId, dateOfBirth),
trailingIconData: isAr ? Icons.arrow_back_rounded : Icons.arrow_forward_rounded,
fontSize: 14,
isTextExpanded: false,

@ -107,17 +107,24 @@ class _AppInAppWebViewScreenState extends State<AppInAppWebViewScreen> {
},
onPermissionRequest: (InAppWebViewController controller, PermissionRequest permissionRequest) async {
debugPrint("InAppWebView onPermissionRequest: ${permissionRequest.resources}");
bool isGranted = false;
try {
PermissionStatus status = await Permission.camera.status;
if (!status.isGranted) {
await Permission.camera.request();
status = await Permission.camera.request();
}
isGranted = status.isGranted;
} catch (e) {
debugPrint("Error requesting camera permission: $e");
}
// Retry the page-load click trigger now that the permission result is known -
// e.g. the page may only render the real "Start Scanning" button once camera
// access is resolved. Safe to call again: it's a no-op if already clicked or
// already watching (see _clickStartScanningButton's window-scoped guards).
widget.onPageLoaded?.call(controller);
return PermissionResponse(
resources: permissionRequest.resources,
action: PermissionResponseAction.GRANT,
action: isGranted ? PermissionResponseAction.GRANT : PermissionResponseAction.DENY,
);
},
onProgressChanged: (InAppWebViewController controller, int progress) {

@ -188,6 +188,7 @@ flutter:
- assets/lottie/
- assets/audio/
- assets/json/
- assets/pdf/
- assets/images/
- assets/images/worklist/
- assets/images/services_icons/

Loading…
Cancel
Save