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

* 'wala_plus_with_dialog_removal' of http://34.17.182.140/Haroon6138/mohemm-flutter-app:
  Toast fixed

# Conflicts:
#	lib/generated/codegen_loader.g.dart
#	lib/generated/locale_keys.g.dart
master
Sultan khan 2 weeks ago
commit bd6e9577e3

@ -66,6 +66,20 @@ allprojects {
jvmTarget = "21"
}
}
if (project.name == 'nfc_manager') {
project.tasks.matching { it.name.startsWith("compile") && it.name.endsWith("Kotlin") }.configureEach {
doFirst {
def translatorFile = project.file("src/main/kotlin/io/flutter/plugins/nfcmanager/Translator.kt")
if (translatorFile.exists()) {
def content = translatorFile.text
if (content.contains("toLowerCase(Locale.ROOT)")) {
translatorFile.text = content.replace("toLowerCase(Locale.ROOT)", "lowercase(Locale.ROOT)")
}
}
}
}
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 KiB

@ -782,6 +782,7 @@
"showAllLocations": "عرض جميع الفروع ({})",
"searchBranches": "البحث عن الفروع أو المدن...",
"allCities": "الكل",
"allCitiesOption": "جميع المدن",
"noBranchesFound": "لم يتم العثور على فروع مطابقة",
"browseAllBranches": "تصفح جميع الفروع ({}) في مختلف المدن",
"branchesCount": "{} فرع",
@ -829,6 +830,27 @@
"merchantWebsiteNotAvailable": "الموقع الإلكتروني للتاجر غير متاح",
"locationServicesRequiredForNearestOffers": "فعّل خدمات الموقع لعرض العروض القريبة منك",
"locationPermissionRequiredForNearestOffers": "اسمح للتطبيق بالوصول إلى موقعك لعرض العروض القريبة منك",
"mazayaDeals": "عروض مزايا",
"findABrandOrSomething": "ابحث عن علامة تجارية أو شيء بسيط",
"exploreYourBenefits": "استكشف مزاياك",
"theMazayaEdit": "اختيارات مزايا",
"exclusivelyYours": "حصرياً لك",
"aLittleExtraJustForMazayaMembers": "مزايا إضافية، خاصة بأعضاء مزايا.",
"seeAll": "عرض الكل",
"latestOnMazaya": "أحدث ما في مزايا",
"goodFoodGreatCompanyEvenBetterPerks": "طعام رائع. رفقة ممتعة. مزايا أفضل.",
"travelEssentials": "أساسيات السفر",
"fromEverydayCareToYourNextAdventure": "من العناية اليومية إلى مغامرتك القادمة.",
"homeAndKitchen": "المنزل والمطبخ",
"curatedPicksForYourHomeAndKitchen": "اختيارات مميزة لمنزلك ومطبخك.",
"discover": "اكتشف",
"saved": "المحفوظات",
"myFamily": "عائلتي",
"educationAndTraining": "التعليم والتدريب",
"foodsAndDrinks": "الأطعمة والمشروبات",
"travelAndEntertainment": "السفر والترفيه",
"beautyAndFitness": "الجمال واللياقة",
"healthcare": "الرعاية الصحية",
"favoriteMerchant": "العروض المفضلة",
"favoriteOffers": "العروض المفضلة",
"goodToKnow": "معلومات مهمة",

@ -786,6 +786,7 @@
"showAllLocations": "Show all {} locations",
"searchBranches": "Search branches or cities...",
"allCities": "All",
"allCitiesOption": "All Cities",
"noBranchesFound": "No branches found",
"browseAllBranches": "Browse all {} branches across cities",
"branchesCount": "{} Branches",
@ -830,6 +831,27 @@
"merchantWebsiteNotAvailable": "Merchant website not available",
"locationServicesRequiredForNearestOffers": "You need to enable location services to show nearest offers",
"locationPermissionRequiredForNearestOffers": "You need to give location permission to show nearest offers",
"mazayaDeals": "Mazaya Deals",
"findABrandOrSomething": "Find a brand or a little something",
"exploreYourBenefits": "Explore your benefits",
"theMazayaEdit": "The Mazaya Edit",
"exclusivelyYours": "Exclusively yours",
"aLittleExtraJustForMazayaMembers": "A little extra, just for Mazaya members.",
"seeAll": "See all",
"latestOnMazaya": "Latest on Mazaya",
"goodFoodGreatCompanyEvenBetterPerks": "Good food. Great company. Even better perks.",
"travelEssentials": "Travel Essentials",
"fromEverydayCareToYourNextAdventure": "From everyday care to your next adventure.",
"homeAndKitchen": "Home & Kitchen",
"curatedPicksForYourHomeAndKitchen": "Curated picks for your home and kitchen.",
"discover": "Discover",
"saved": "Saved",
"myFamily": "My Family",
"educationAndTraining": "Education & Training",
"foodsAndDrinks": "Foods & Drinks",
"travelAndEntertainment": "Travel & Entertainment",
"beautyAndFitness": "Beauty & Fitness",
"healthcare": "Healthcare",
"favoriteAddedSuccess": "Added to favorites successfully",
"favoriteRemovedSuccess": "Removed from favorites successfully",
"favoriteError": "An error occurred while updating favorites",

@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
<string>13.0</string>
</dict>
</plist>

@ -7,8 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
0AD79F8B1905C17FADC8B42C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8F0CC8EAC13AF3E07BEE567 /* Pods_Runner.framework */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
1CCF895CFB3C95DEBB84A05F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8F0CC8EAC13AF3E07BEE567 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
76AE113C28E078EA00513112 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 76AE113B28E078EA00513112 /* GoogleService-Info.plist */; };
@ -57,7 +57,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1CCF895CFB3C95DEBB84A05F /* Pods_Runner.framework in Frameworks */,
0AD79F8B1905C17FADC8B42C /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -144,7 +144,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
C4CFBC4C5CAC00182015ACD5 /* [CP] Embed Pods Frameworks */,
EF29CE25BB57F5AA48CE1136 /* [CP] Copy Pods Resources */,
8ED1493DD0011941609D6F99 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -242,7 +242,7 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
6C6E76E4FB19FAF11C24005D /* [CP] Copy Pods Resources */ = {
8ED1493DD0011941609D6F99 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -367,7 +367,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@ -454,7 +454,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -504,7 +504,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;

@ -3,9 +3,23 @@
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8B74B458B450D74B75744B87BD747314"
BuildableName = "Pods_Runner.framework"
BlueprintName = "Pods-Runner"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"

@ -31,6 +31,11 @@
<string>sms</string>
<string>tel</string>
<string>mailto</string>
<string>https</string>
<string>http</string>
<string>comgooglemaps</string>
<string>googlechromes</string>
<string>waze</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
@ -46,19 +51,23 @@
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>This app requires camera access to capture &amp; upload picture as profile image.</string>
<string>This app requires camera access to capture &amp; upload picture as profile image, and scan QR codes.</string>
<key>NSFaceIDUsageDescription</key>
<string>This app requires Face ID to allow biometric authentication for app login.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>This app requires access to local network to connect to company Wi-Fi networks for attendance.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This App requires access to your location to mark your attendance.</string>
<string>This app requires access to your location to mark your attendance and discover nearby offers and merchants.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This App requires access to your location to mark your attendance.</string>
<string>This app requires access to your location to mark your attendance and discover nearby offers and merchants.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This App requires access to your location to mark your attendance.</string>
<string>This app requires access to your location to mark your attendance and discover nearby offers and merchants.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app requires microphone access to for call.</string>
<string>This app requires microphone access for audio calls and recording voice messages.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires photo library access to select image as document &amp; upload it.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app requires access to save photos and documents to your photo library.</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSSpeechRecognitionUsageDescription</key>

@ -20,6 +20,7 @@
<true/>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>

@ -294,9 +294,9 @@ class LoginApiClient {
return await ApiClient().postJsonForObject((json) => GenericResponseModel.fromJson(json).basicMemberInformation, url, postParams);
}
Future<GenericResponseModel?> checkActivationCode(bool isDeviceNFC, String? mobileNumber, String activationCode, String? pUserName) async {
Future<GenericResponseModel?> checkActivationCode(bool isDeviceNFC, String? mobileNumber, String activationCode, String? pUserName, String loginType) async {
String url = "${ApiConsts.erpRest}CheckActivationCode";
Map<String, dynamic> postParams = {"IsDeviceNFC": isDeviceNFC, "MobileNumber": mobileNumber, "activationCode": activationCode, "P_USER_NAME": pUserName};
Map<String, dynamic> postParams = {"IsDeviceNFC": isDeviceNFC, "MobileNumber": mobileNumber, "activationCode": activationCode, "P_USER_NAME": pUserName, "LoginType":loginType};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);

@ -258,6 +258,11 @@ class OffersAndDiscountsApiClient {
Future<WalaPlusRetailersResponse?> getWalaPlusRetailers({
String? categoryId,
// Every existing caller passes categoryId (even "" for "all") and relies on WP_CategoryID
// always being present in the request - default true preserves that for everyone. Pass
// false only when the category filter must be fully omitted (e.g. a cross-category newest
// feed), not just set to empty.
bool includeCategoryId = true,
int page = 1,
int perPage = 10,
String? geoLocation,
@ -285,7 +290,9 @@ class OffersAndDiscountsApiClient {
}
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
postParams["WP_CategoryID"] = effectiveCatId;
if (includeCategoryId) {
postParams["WP_CategoryID"] = effectiveCatId;
}
if (offerType != null && offerType.trim().isNotEmpty) {
postParams["WP_Offer_Type"] = offerType.trim();
}
@ -323,8 +330,6 @@ class OffersAndDiscountsApiClient {
postParams["WP_User_Reference"] = null;
}
log("postParams: ${postParams.toString()}");
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {

@ -551,6 +551,7 @@ class ProfileApiClient {
if (personId != null) {
postParams["P_PERSON_ID"] = personId;
}
postParams["PatientIdentificationID"] = patientIdentificationId?.replaceAll('-', '').trim();
// Only the Grant call carries these - Revoke's payload stays exactly as it was before
// this feature. P_BIRTH_DATE/P_NHIC_VERIFIED_FLAG are always sent (even as null, if the
// source contact from GET_EMPLOYEE_CONTACTS didn't have a value) once we're granting.
@ -561,7 +562,7 @@ class ProfileApiClient {
if (patientIdentificationId != null && patientIdentificationId.trim().isNotEmpty) {
// National ID comes formatted with dashes (e.g. "2-5616-1545-7") - the backend wants
// the plain digit string (e.g. "2561615457").
postParams["PatientIdentificationID"] = patientIdentificationId.replaceAll('-', '').trim();
}
}
postParams.addAll(AppState().postParamsJson);

@ -14,8 +14,6 @@ import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_create_request.dart';
import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_ovr_home.dart';
import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_request_detailed.dart';
import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart';
import 'package:mohem_flutter_app/ui/landing/dashboard_screen_for_dependet.dart';
import 'package:mohem_flutter_app/ui/landing/wala_plus_retailer_details_screen.dart';
import 'package:mohem_flutter_app/ui/landing/itg/change_itg_ad_password_screen.dart';
import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.dart';
import 'package:mohem_flutter_app/ui/landing/itg/survey_screen.dart';
@ -33,10 +31,15 @@ import 'package:mohem_flutter_app/ui/marathon/marathon_screen.dart';
import 'package:mohem_flutter_app/ui/marathon/marathon_sponsor_video_screen.dart';
import 'package:mohem_flutter_app/ui/marathon/marathon_tutorial_viewer_screen.dart';
import 'package:mohem_flutter_app/ui/marathon/marathon_waiting_screen.dart';
import 'package:mohem_flutter_app/ui/mazaya/retailers_list_screen.dart';
import 'package:mohem_flutter_app/ui/mazaya/mazaya_discover_screen.dart';
import 'package:mohem_flutter_app/ui/mazaya/saved_retailers_screen.dart';
import 'package:mohem_flutter_app/ui/mazaya/retailer_details_screen.dart';
import 'package:mohem_flutter_app/ui/misc/request_submit_screen.dart';
import 'package:mohem_flutter_app/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart';
import 'package:mohem_flutter_app/ui/my_attendance/dynamic_screens/dynamic_listview_screen.dart';
import 'package:mohem_flutter_app/ui/my_attendance/services_menu_list_screen.dart';
// import 'package:mohem_flutter_app/ui/my_attendance/my_attendance_screen.dart';
import 'package:mohem_flutter_app/ui/my_team/create_request.dart';
import 'package:mohem_flutter_app/ui/my_team/employee_details.dart';
@ -64,6 +67,7 @@ import 'package:mohem_flutter_app/ui/screens/announcements/announcement_details.
import 'package:mohem_flutter_app/ui/screens/announcements/announcements.dart';
import 'package:mohem_flutter_app/ui/screens/child_education/child_education_assistance.dart';
import 'package:mohem_flutter_app/ui/screens/courses/courses_screen.dart';
// import 'package:mohem_flutter_app/ui/my_attendance/work_from_home_screen.dart';
import 'package:mohem_flutter_app/ui/screens/eit/add_eit.dart';
import 'package:mohem_flutter_app/ui/screens/event_activity/event_activity.dart';
@ -103,8 +107,10 @@ class AppRoutes {
static const String newPassword = "/newPassword";
static const String loginVerification = "/loginVerification";
static const String dashboard = "/dashboard";
static const String dashboardScreenForDependet = "/dashboardScreenForDependet";
static const String walaPlusRetailerDetails = "/walaPlusRetailerDetails";
static const String retailersListScreen = "/dashboardScreenForDependet";
static const String retailerDetailsScreen = "/walaPlusRetailerDetails";
static const String mazayaDiscover = "/mazayaDiscover";
static const String savedReyailersScreen = "/mazayaSaved";
static const String todayAttendance = "/todayAttendance";
static const String subMenuScreen = "/submenuScreen";
static const String addEitScreen = "/addeitScreen";
@ -243,15 +249,14 @@ class AppRoutes {
verifyLogin: (BuildContext context) => VerifyLoginScreen(),
verifyLastLogin: (BuildContext context) => VerifyLastLoginScreen(),
dashboard: (BuildContext context) => DashboardScreen(),
dashboardScreenForDependet: (BuildContext context) => const DashboardScreenForDependet(),
walaPlusRetailerDetails: (BuildContext context) => const WalaPlusRetailerDetailsScreen(),
retailersListScreen: (BuildContext context) => const RetailersListScreen(),
retailerDetailsScreen: (BuildContext context) => const RetailerDetailsScreen(),
mazayaDiscover: (BuildContext context) => const MazayaDiscoverScreen(),
savedReyailersScreen: (BuildContext context) => const SavedRetailersScreen(),
appInAppWebView: (BuildContext context) {
Object? args = ModalRoute.of(context)?.settings.arguments;
if (args is Map<String, dynamic>) {
return AppInAppWebViewScreen(
url: args['url'] ?? "",
title: args['title'],
);
return AppInAppWebViewScreen(url: args['url'] ?? "", title: args['title']);
}
return const Scaffold();
},
@ -262,8 +267,8 @@ class AppRoutes {
newPassword: (BuildContext context) => NewPasswordScreen(),
forgotPassword: (BuildContext context) => ForgotPasswordScreen(),
todayAttendance: (BuildContext context) => TodayAttendanceScreen2(),
//eit
//eit
addEitScreen: (BuildContext context) => AddEITScreen(),
//Work List
@ -274,7 +279,6 @@ class AppRoutes {
worklistSettings: (BuildContext context) => WorklistSettings(),
// Leave Balance
leaveBalance: (BuildContext context) => LeaveBalance(),
addLeaveBalance: (BuildContext context) => AddLeaveBalanceScreen(),
@ -373,7 +377,7 @@ class AppRoutes {
unsafeDeviceScreen: (BuildContext context) => const UnsafeDeviceScreen(),
appUpdateScreen: (BuildContext context) => const AppUpdateScreen(),
childEducation: (BuildContext context) => ChildEducationAssistance(),
activityScreen:(BuildContext context) => const EventActivityScreen(),
activityScreen: (BuildContext context) => const EventActivityScreen(),
//ETQAN OVR
etqanOvr: (BuildContext context) => EtqanOvrHome(),
@ -382,6 +386,5 @@ class AppRoutes {
//Parking QR
parkingQr: (BuildContext context) => const ParkingQrScreen(),
};
}

@ -798,6 +798,7 @@ class CodegenLoader extends AssetLoader{
"showAllLocations": "عرض جميع الفروع ({})",
"searchBranches": "البحث عن الفروع أو المدن...",
"allCities": "الكل",
"allCitiesOption": "جميع المدن",
"noBranchesFound": "لم يتم العثور على فروع مطابقة",
"browseAllBranches": "تصفح جميع الفروع ({}) في مختلف المدن",
"branchesCount": "{} فرع",
@ -845,6 +846,27 @@ class CodegenLoader extends AssetLoader{
"merchantWebsiteNotAvailable": "الموقع الإلكتروني للتاجر غير متاح",
"locationServicesRequiredForNearestOffers": "فعّل خدمات الموقع لعرض العروض القريبة منك",
"locationPermissionRequiredForNearestOffers": "اسمح للتطبيق بالوصول إلى موقعك لعرض العروض القريبة منك",
"mazayaDeals": "عروض مزايا",
"findABrandOrSomething": "ابحث عن علامة تجارية أو شيء بسيط",
"exploreYourBenefits": "استكشف مزاياك",
"theMazayaEdit": "اختيارات مزايا",
"exclusivelyYours": "حصرياً لك",
"aLittleExtraJustForMazayaMembers": "مزايا إضافية، خاصة بأعضاء مزايا.",
"seeAll": "عرض الكل",
"latestOnMazaya": "أحدث ما في مزايا",
"goodFoodGreatCompanyEvenBetterPerks": "طعام رائع. رفقة ممتعة. مزايا أفضل.",
"travelEssentials": "أساسيات السفر",
"fromEverydayCareToYourNextAdventure": "من العناية اليومية إلى مغامرتك القادمة.",
"homeAndKitchen": "المنزل والمطبخ",
"curatedPicksForYourHomeAndKitchen": "اختيارات مميزة لمنزلك ومطبخك.",
"discover": "اكتشف",
"saved": "المحفوظات",
"myFamily": "عائلتي",
"educationAndTraining": "التعليم والتدريب",
"foodsAndDrinks": "الأطعمة والمشروبات",
"travelAndEntertainment": "السفر والترفيه",
"beautyAndFitness": "الجمال واللياقة",
"healthcare": "الرعاية الصحية",
"favoriteMerchant": "العروض المفضلة",
"favoriteOffers": "العروض المفضلة",
"goodToKnow": "معلومات مهمة",
@ -858,15 +880,13 @@ class CodegenLoader extends AssetLoader{
"phone": "الهاتف",
"viewAll": "عرض الكل",
"moreCitiesCount": "+ {} مدن أخرى",
"mazayaDeals": "عروض مزايا",
"exploreYourBenefits": "استكشف مزاياك",
"findAnEscapeOrAnExperience": "ابحث عن تجربة أو عطلة",
"allOffers": "جميع العروض",
"sampleAvailability": "حسب التوفر",
"offersCount": "{} عروض",
"mazayaExclusive": "حصري لمزايا",
"youAreAllCaughtUp": "لقد شاهدت كل العروض. المزيد قريباً.",
"selectCity": "اختر المدينة"
"selectCity": "اختر المدينة",
};
static const Map<String,dynamic> _en_US = {
"mohemm": "Mohemm",
@ -1652,6 +1672,7 @@ static const Map<String,dynamic> _en_US = {
"showAllLocations": "Show all {} locations",
"searchBranches": "Search branches or cities...",
"allCities": "All",
"allCitiesOption": "All Cities",
"noBranchesFound": "No branches found",
"browseAllBranches": "Browse all {} branches across cities",
"branchesCount": "{} Branches",
@ -1696,6 +1717,27 @@ static const Map<String,dynamic> _en_US = {
"merchantWebsiteNotAvailable": "Merchant website not available",
"locationServicesRequiredForNearestOffers": "You need to enable location services to show nearest offers",
"locationPermissionRequiredForNearestOffers": "You need to give location permission to show nearest offers",
"mazayaDeals": "Mazaya Deals",
"findABrandOrSomething": "Find a brand or a little something",
"exploreYourBenefits": "Explore your benefits",
"theMazayaEdit": "The Mazaya Edit",
"exclusivelyYours": "Exclusively yours",
"aLittleExtraJustForMazayaMembers": "A little extra, just for Mazaya members.",
"seeAll": "See all",
"latestOnMazaya": "Latest on Mazaya",
"goodFoodGreatCompanyEvenBetterPerks": "Good food. Great company. Even better perks.",
"travelEssentials": "Travel Essentials",
"fromEverydayCareToYourNextAdventure": "From everyday care to your next adventure.",
"homeAndKitchen": "Home & Kitchen",
"curatedPicksForYourHomeAndKitchen": "Curated picks for your home and kitchen.",
"discover": "Discover",
"saved": "Saved",
"myFamily": "My Family",
"educationAndTraining": "Education & Training",
"foodsAndDrinks": "Foods & Drinks",
"travelAndEntertainment": "Travel & Entertainment",
"beautyAndFitness": "Beauty & Fitness",
"healthcare": "Healthcare",
"favoriteAddedSuccess": "Added to favorites successfully",
"favoriteRemovedSuccess": "Removed from favorites successfully",
"favoriteError": "An error occurred while updating favorites",
@ -1712,15 +1754,13 @@ static const Map<String,dynamic> _en_US = {
"phone": "Phone",
"viewAll": "View all",
"moreCitiesCount": "+ {} more cities",
"mazayaDeals": "Mazaya Deals",
"exploreYourBenefits": "Explore your benefits",
"findAnEscapeOrAnExperience": "Find an escape or an experience",
"allOffers": "All offers",
"sampleAvailability": "sample availability",
"offersCount": "{} offers",
"mazayaExclusive": "MAZAYA EXCLUSIVE",
"youAreAllCaughtUp": "You're all caught up. More little extras soon.",
"selectCity": "Select City"
"selectCity": "Select City",
};
static const Map<String, Map<String,dynamic>> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US};
}

@ -770,6 +770,7 @@ abstract class LocaleKeys {
static const showAllLocations = 'showAllLocations';
static const searchBranches = 'searchBranches';
static const allCities = 'allCities';
static const allCitiesOption = 'allCitiesOption';
static const noBranchesFound = 'noBranchesFound';
static const browseAllBranches = 'browseAllBranches';
static const branchesCount = 'branchesCount';
@ -817,6 +818,27 @@ abstract class LocaleKeys {
static const merchantWebsiteNotAvailable = 'merchantWebsiteNotAvailable';
static const locationServicesRequiredForNearestOffers = 'locationServicesRequiredForNearestOffers';
static const locationPermissionRequiredForNearestOffers = 'locationPermissionRequiredForNearestOffers';
static const mazayaDeals = 'mazayaDeals';
static const findABrandOrSomething = 'findABrandOrSomething';
static const exploreYourBenefits = 'exploreYourBenefits';
static const theMazayaEdit = 'theMazayaEdit';
static const exclusivelyYours = 'exclusivelyYours';
static const aLittleExtraJustForMazayaMembers = 'aLittleExtraJustForMazayaMembers';
static const seeAll = 'seeAll';
static const latestOnMazaya = 'latestOnMazaya';
static const goodFoodGreatCompanyEvenBetterPerks = 'goodFoodGreatCompanyEvenBetterPerks';
static const travelEssentials = 'travelEssentials';
static const fromEverydayCareToYourNextAdventure = 'fromEverydayCareToYourNextAdventure';
static const homeAndKitchen = 'homeAndKitchen';
static const curatedPicksForYourHomeAndKitchen = 'curatedPicksForYourHomeAndKitchen';
static const discover = 'discover';
static const saved = 'saved';
static const myFamily = 'myFamily';
static const educationAndTraining = 'educationAndTraining';
static const foodsAndDrinks = 'foodsAndDrinks';
static const travelAndEntertainment = 'travelAndEntertainment';
static const beautyAndFitness = 'beautyAndFitness';
static const healthcare = 'healthcare';
static const favoriteMerchant = 'favoriteMerchant';
static const favoriteOffers = 'favoriteOffers';
static const goodToKnow = 'goodToKnow';
@ -830,8 +852,6 @@ abstract class LocaleKeys {
static const phone = 'phone';
static const viewAll = 'viewAll';
static const moreCitiesCount = 'moreCitiesCount';
static const mazayaDeals = 'mazayaDeals';
static const exploreYourBenefits = 'exploreYourBenefits';
static const findAnEscapeOrAnExperience = 'findAnEscapeOrAnExperience';
static const allOffers = 'allOffers';
static const sampleAvailability = 'sampleAvailability';

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category_model.dart';
class WalaPlusRetailersResponse {
@ -208,6 +209,11 @@ class WalaPlusRetailerModel {
String? countryNameEn;
bool? isFavorite;
// Drives the "MAZAYA EXCLUSIVE" badge on the retailer card (Figma node 927:496). NOT parsed
// from the WalaPlus_Retailers/WalaPlus_RetailersByID API - this is a different, separate
// "Exclusive" API/section, so callers set this manually on the model after fetching from there.
bool? isExclusive;
WalaPlusRetailerModel({
this.id,
this.name,
@ -221,6 +227,7 @@ class WalaPlusRetailerModel {
this.logoThumb,
this.website,
this.isFavorite,
this.isExclusive,
this.type,
this.usage,
this.weight,
@ -338,38 +345,44 @@ class WalaPlusRetailerModel {
categoriesList.add(WalaPlusCategoryModel(name: rawCats.toString().trim()));
}
String? singleCatName = (targetJson['category_name'] ??
targetJson['categoryName'] ??
targetJson['cat_name'] ??
targetJson['catName'] ??
targetJson['CategoryName'] ??
targetJson['Category_Name'] ??
targetJson['category_title'] ??
targetJson['categoryTitle'])?.toString().trim();
String? singleCatAr = (targetJson['category_name_ar'] ??
targetJson['categoryNameAr'] ??
targetJson['cat_name_ar'] ??
targetJson['catNameAr'] ??
targetJson['category_ar'] ??
targetJson['country_name_ar'] ??
targetJson['countryNameAr'])?.toString().trim();
String? singleCatEn = (targetJson['category_name_en'] ??
targetJson['categoryNameEn'] ??
targetJson['cat_name_en'] ??
targetJson['catNameEn'] ??
targetJson['category_en'] ??
targetJson['country_name_en'] ??
targetJson['countryNameEn'])?.toString().trim();
String? foundCategoryId = (targetJson['category_id'] ??
targetJson['categoryId'] ??
targetJson['cat_id'] ??
targetJson['catId'] ??
targetJson['WP_CategoryID'] ??
targetJson['WP_CategoryId'])?.toString().trim();
String? singleCatName =
(targetJson['category_name'] ??
targetJson['categoryName'] ??
targetJson['cat_name'] ??
targetJson['catName'] ??
targetJson['CategoryName'] ??
targetJson['Category_Name'] ??
targetJson['category_title'] ??
targetJson['categoryTitle'])
?.toString()
.trim();
String? singleCatAr =
(targetJson['category_name_ar'] ??
targetJson['categoryNameAr'] ??
targetJson['cat_name_ar'] ??
targetJson['catNameAr'] ??
targetJson['category_ar'] ??
targetJson['country_name_ar'] ??
targetJson['countryNameAr'])
?.toString()
.trim();
String? singleCatEn =
(targetJson['category_name_en'] ??
targetJson['categoryNameEn'] ??
targetJson['cat_name_en'] ??
targetJson['catNameEn'] ??
targetJson['category_en'] ??
targetJson['country_name_en'] ??
targetJson['countryNameEn'])
?.toString()
.trim();
String? foundCategoryId =
(targetJson['category_id'] ?? targetJson['categoryId'] ?? targetJson['cat_id'] ?? targetJson['catId'] ?? targetJson['WP_CategoryID'] ?? targetJson['WP_CategoryId'])?.toString().trim();
String? rawType = targetJson['type']?.toString().trim();
String? lowerType = rawType?.toLowerCase();
bool isTypeStoreKeyword = lowerType == null ||
bool isTypeStoreKeyword =
lowerType == null ||
lowerType.isEmpty ||
lowerType == 'online' ||
lowerType == 'instore' ||
@ -387,12 +400,9 @@ class WalaPlusRetailerModel {
if ((singleCatName != null && singleCatName.isNotEmpty && singleCatName != "null") ||
(singleCatAr != null && singleCatAr.isNotEmpty && singleCatAr != "null") ||
(singleCatEn != null && singleCatEn.isNotEmpty && singleCatEn != "null")) {
categoriesList.add(WalaPlusCategoryModel(
id: foundCategoryId,
name: singleCatName ?? singleCatEn ?? singleCatAr,
countryNameAr: singleCatAr ?? singleCatName,
countryNameEn: singleCatEn ?? singleCatName,
));
categoriesList.add(
WalaPlusCategoryModel(id: foundCategoryId, name: singleCatName ?? singleCatEn ?? singleCatAr, countryNameAr: singleCatAr ?? singleCatName, countryNameEn: singleCatEn ?? singleCatName),
);
}
}
@ -429,7 +439,8 @@ class WalaPlusRetailerModel {
}
}
String? foundWebsite = targetJson['website']?.toString() ??
String? foundWebsite =
targetJson['website']?.toString() ??
targetJson['merchant_website']?.toString() ??
targetJson['merchantWebsite']?.toString() ??
targetJson['website_url']?.toString() ??
@ -464,25 +475,13 @@ class WalaPlusRetailerModel {
bool? parsedIsFavorite;
if (targetJson.containsKey('isfavorite')) {
parsedIsFavorite = targetJson['isfavorite'] == true ||
targetJson['isfavorite'] == 1 ||
targetJson['isfavorite'] == 'true' ||
targetJson['isfavorite'] == '1';
parsedIsFavorite = targetJson['isfavorite'] == true || targetJson['isfavorite'] == 1 || targetJson['isfavorite'] == 'true' || targetJson['isfavorite'] == '1';
} else if (targetJson.containsKey('is_favorite')) {
parsedIsFavorite = targetJson['is_favorite'] == true ||
targetJson['is_favorite'] == 1 ||
targetJson['is_favorite'] == 'true' ||
targetJson['is_favorite'] == '1';
parsedIsFavorite = targetJson['is_favorite'] == true || targetJson['is_favorite'] == 1 || targetJson['is_favorite'] == 'true' || targetJson['is_favorite'] == '1';
} else if (targetJson.containsKey('isFavorite')) {
parsedIsFavorite = targetJson['isFavorite'] == true ||
targetJson['isFavorite'] == 1 ||
targetJson['isFavorite'] == 'true' ||
targetJson['isFavorite'] == '1';
parsedIsFavorite = targetJson['isFavorite'] == true || targetJson['isFavorite'] == 1 || targetJson['isFavorite'] == 'true' || targetJson['isFavorite'] == '1';
} else if (targetJson.containsKey('favorite')) {
parsedIsFavorite = targetJson['favorite'] == true ||
targetJson['favorite'] == 1 ||
targetJson['favorite'] == 'true' ||
targetJson['favorite'] == '1';
parsedIsFavorite = targetJson['favorite'] == true || targetJson['favorite'] == 1 || targetJson['favorite'] == 'true' || targetJson['favorite'] == '1';
}
return WalaPlusRetailerModel(
@ -532,6 +531,54 @@ class WalaPlusRetailerModel {
);
}
// Adapter for the Mazaya "Exclusive Offers" section, which reuses the legacy Offers &
// Discounts (ITG) API/implementation (lib/ui/screens/offers_and_discounts) rather than a
// WalaPlus endpoint. Maps whatever overlaps onto this model so the existing grid/card/detail
// screen can render it unchanged - fields with no equivalent on OffersListModel (favorite,
// redeem, locations, contact, ...) are simply left null, and isExclusive hides the actions
// that depend on them (see WalaPlusRetailerCard, RetailerDetailsScreen).
factory WalaPlusRetailerModel.fromExclusiveOffer(OffersListModel offer, {bool isAr = false}) {
String? name = decodeUnicodeString(isAr ? (offer.titleAr ?? offer.titleEn) : (offer.titleEn ?? offer.titleAr));
String? description = decodeUnicodeString(isAr ? (offer.descriptionAr ?? offer.descriptionEn) : (offer.descriptionEn ?? offer.descriptionAr));
String? categoryName = decodeUnicodeString(isAr ? (offer.categoryNameAr ?? offer.categoryNameEn) : (offer.categoryNameEn ?? offer.categoryNameAr));
String? discountText = _formatExclusiveDiscountText(decodeUnicodeString(offer.discountDescription) ?? decodeUnicodeString(offer.discount));
return WalaPlusRetailerModel(
id: "exclusive-${offer.offersDiscountId ?? ''}",
name: name,
// WalaPlusRetailerCard reads offerTitle as the promo subtitle shown under the name (e.g.
// "20% off") - the discount text is the closest equivalent on a legacy offer.
offerTitle: discountText,
description: description,
info: description,
image: offer.bannerImage,
logo: offer.logo,
website: offer.websiteUrl,
endDate: offer.endDate,
validTo: offer.endDate,
categoryName: categoryName,
categories: categoryName != null ? <WalaPlusCategoryModel>[WalaPlusCategoryModel(name: categoryName)] : null,
isExclusive: true,
);
}
// The legacy Offers & Discounts API sometimes returns a bare number ("10", "20") for its
// discount fields rather than a formatted "10% off" string. WalaPlusRetailerCard's badge
// logic (getDiscountBadge) only recognizes a "<digits>%" pattern, so a bare number would
// otherwise render verbatim instead of as a discount. Already-formatted text (containing "%",
// "off", or "خصم") is left untouched.
static String? _formatExclusiveDiscountText(String? raw) {
if (raw == null) return null;
String trimmed = raw.trim();
if (trimmed.isEmpty) return null;
if (trimmed.contains('%') || trimmed.toLowerCase().contains('off') || trimmed.contains('خصم')) {
return trimmed;
}
if (RegExp(r'^\d+(\.\d+)?$').hasMatch(trimmed)) {
return LocaleKeys.offDiscount.tr(args: ['$trimmed%']);
}
return trimmed;
}
static String? decodeUnicodeString(Object? val) {
if (val == null) return null;
String s = val.toString().trim();
@ -614,6 +661,7 @@ class WalaPlusRetailerModel {
data['country_name_en'] = countryNameEn;
data['is_favorite'] = isFavorite;
data['isfavorite'] = isFavorite;
data['is_exclusive'] = isExclusive;
if (offers != null) {
data['offers'] = offers!.map((v) => v.toJson()).toList();
}
@ -633,9 +681,8 @@ class WalaPlusRetailerModel {
for (var cat in categories!) {
String? primary = isAr ? cat.countryNameAr : cat.countryNameEn;
String? secondary = isAr ? (cat.name ?? cat.countryNameEn) : (cat.name ?? cat.countryNameAr);
String? resolved = (primary != null && primary.trim().isNotEmpty && primary != "null")
? primary.trim()
: (secondary != null && secondary.trim().isNotEmpty && secondary != "null" ? secondary.trim() : null);
String? resolved =
(primary != null && primary.trim().isNotEmpty && primary != "null") ? primary.trim() : (secondary != null && secondary.trim().isNotEmpty && secondary != "null" ? secondary.trim() : null);
if (resolved != null && resolved.isNotEmpty) {
return resolved;
}
@ -649,9 +696,10 @@ class WalaPlusRetailerModel {
if (cat.id?.toString() == categoryId?.toString()) {
String? primary = isAr ? cat.countryNameAr : cat.countryNameEn;
String? secondary = isAr ? (cat.name ?? cat.countryNameEn) : (cat.name ?? cat.countryNameAr);
String? resolved = (primary != null && primary.trim().isNotEmpty && primary != "null")
? primary.trim()
: (secondary != null && secondary.trim().isNotEmpty && secondary != "null" ? secondary.trim() : null);
String? resolved =
(primary != null && primary.trim().isNotEmpty && primary != "null")
? primary.trim()
: (secondary != null && secondary.trim().isNotEmpty && secondary != "null" ? secondary.trim() : null);
if (resolved != null && resolved.isNotEmpty) {
return resolved;
}

@ -460,7 +460,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {
Navigator.pushNamed(context, AppRoutes.dashboardScreenForDependet);
Navigator.pushNamed(context, AppRoutes.mazayaDiscover);
},
child: ClipRRect(
borderRadius: BorderRadius.circular(20),

@ -111,7 +111,7 @@ class ServicesWidget extends StatelessWidget {
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(child: data.homeMenus![parentIndex].menuEntiesList[index].prompt!.toText11(isBold: true)),
Expanded(child: data.homeMenus![parentIndex].menuEntiesList[index].prompt!.toText11(isBold: true, maxLine: 3)),
RotatedBox(quarterTurns: AppState().isArabic(context) ? 2 : 4, child: SvgPicture.asset("assets/images/arrow_next.svg").paddingOnly(bottom: 4)),
],
),

@ -920,7 +920,7 @@ class _LoginScreenState extends State<LoginScreen> {
if (effectiveToken.isNotEmpty && mounted) {
Utils.hideLoading(context);
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.dashboardScreenForDependet, (Route<dynamic> route) => false);
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.mazayaDiscover, (Route<dynamic> route) => false);
return true;
}
} catch (e) {
@ -990,7 +990,7 @@ class _LoginScreenState extends State<LoginScreen> {
Utils.saveStringFromPrefs("P_DEP_LOGIN_TYPE", loginType.toString());
if (navigateToDashboard && ApiConsts.ISDependetLogin) {
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.dashboardScreenForDependet, (Route<dynamic> route) => false);
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.mazayaDiscover, (Route<dynamic> route) => false);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,386 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/api/offers_and_discounts_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';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
import 'package:mohem_flutter_app/ui/mazaya/widgets/mazaya_bottom_nav.dart';
import 'package:mohem_flutter_app/ui/mazaya/widgets/wala_plus_retailer_grid.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
// Mazaya Saved tab - full-screen version of what was FavoriteMerchantsBottomSheet (a modal
// bottom sheet). Same fetch/search/pagination/toggle-favorite logic, now its own pushed route
// with the shared MazayaBottomNav instead of a dismissible sheet - matches Discover's "own
// route, fetches fresh on mount" pattern.
class SavedRetailersScreen extends StatefulWidget {
const SavedRetailersScreen({Key? key}) : super(key: key);
@override
State<SavedRetailersScreen> createState() => _SavedRetailersScreenState();
}
class _SavedRetailersScreenState extends State<SavedRetailersScreen> {
List<WalaPlusRetailerModel> favoriteRetailers = <WalaPlusRetailerModel>[];
bool isLoading = true;
bool isLoadingMore = false;
bool hasMore = true;
bool _isFetchingPage = false;
int currentPage = 1;
int totalPages = 1;
final int pageSize = 10;
String searchQuery = "";
Timer? _debounceTimer;
final TextEditingController _searchController = TextEditingController();
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) fetchFavorites();
});
}
@override
void dispose() {
_debounceTimer?.cancel();
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
_searchController.dispose();
super.dispose();
}
void _scrollListener() {
if (!_scrollController.hasClients) return;
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
if (currentScroll >= maxScroll - 35) {
if (!isLoading && !isLoadingMore && !_isFetchingPage && hasMore) {
fetchNextPage();
}
}
}
String? _getUserReferenceForFavorites() {
String ref = OffersAndDiscountsApiClient().resolveUserReference();
if (ref.trim().isNotEmpty) return ref.trim();
String? userName = AppState().getUserName;
if (userName != null && userName.trim().isNotEmpty) return userName.trim();
return null;
}
Future<void> fetchFavorites({String? query}) async {
if (query != null) searchQuery = query;
setState(() {
isLoading = true;
currentPage = 1;
hasMore = true;
});
try {
String? queryParam =
searchQuery.trim().isNotEmpty ? searchQuery.trim() : null;
String? userRef = _getUserReferenceForFavorites();
WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient()
.getWalaPlusRetailers(
categoryId: "",
page: 1,
perPage: pageSize,
offerType: null,
query: queryParam,
sortBy: null,
radius: null,
minDiscount: null,
context: null,
userReference: userRef,
);
favoriteRetailers = response?.retailers ?? <WalaPlusRetailerModel>[];
for (WalaPlusRetailerModel r in favoriteRetailers) {
r.isFavorite = true;
}
currentPage = 1;
totalPages = response?.pagesCount ?? 1;
hasMore =
currentPage < totalPages &&
(response?.retailers?.isNotEmpty ?? false);
} catch (e) {
debugPrint("MazayaSavedScreen: error fetching favorites: $e");
favoriteRetailers = <WalaPlusRetailerModel>[];
} finally {
if (mounted) setState(() => isLoading = false);
}
}
Future<void> fetchNextPage() async {
if (isLoading || isLoadingMore || _isFetchingPage || !hasMore) return;
_isFetchingPage = true;
setState(() => isLoadingMore = true);
try {
int nextPage = currentPage + 1;
String? queryParam =
searchQuery.trim().isNotEmpty ? searchQuery.trim() : null;
String? userRef = _getUserReferenceForFavorites();
WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient()
.getWalaPlusRetailers(
categoryId: "",
page: nextPage,
perPage: pageSize,
offerType: null,
query: queryParam,
sortBy: null,
radius: null,
minDiscount: null,
context: null,
userReference: userRef,
);
List<WalaPlusRetailerModel> newRetailers =
response?.retailers ?? <WalaPlusRetailerModel>[];
if (newRetailers.isNotEmpty) {
for (WalaPlusRetailerModel r in newRetailers) {
r.isFavorite = true;
}
currentPage = nextPage;
totalPages = response?.pagesCount ?? totalPages;
hasMore = currentPage < totalPages;
favoriteRetailers.addAll(newRetailers);
} else {
hasMore = false;
}
} catch (e) {
debugPrint("MazayaSavedScreen: error fetching next page: $e");
} finally {
_isFetchingPage = false;
if (mounted) setState(() => isLoadingMore = false);
}
}
void _onSearchChanged(String query) {
_debounceTimer?.cancel();
searchQuery = query;
if (query.trim().isEmpty) {
fetchFavorites(query: "");
} else {
_debounceTimer = Timer(const Duration(milliseconds: 600), () {
fetchFavorites(query: query.trim());
});
}
}
Future<void> _toggleFavorite(WalaPlusRetailerModel item) async {
String retailerId = item.id ?? "";
if (retailerId.isEmpty) return;
bool currentFav = item.isFavorite ?? true;
bool targetFav = !currentFav;
setState(() {
item.isFavorite = targetFav;
if (!targetFav) {
favoriteRetailers.removeWhere(
(WalaPlusRetailerModel r) => r.id == item.id,
);
}
});
Utils.showLoading(context);
try {
GenericResponseModel? response;
String? userRef = _getUserReferenceForFavorites();
if (targetFav) {
response = await OffersAndDiscountsApiClient().addWalaPlusFavorite(
retailerId: retailerId,
context: context,
userReference: userRef,
);
} else {
response = await OffersAndDiscountsApiClient().removeWalaPlusFavorite(
retailerId: retailerId,
context: context,
userReference: userRef,
);
}
Utils.hideLoading(context);
if (response != null && response.errorMessage == null) {
bool isAr = AppState().isArabic(context);
String msg =
isAr
? (targetFav
? LocaleKeys.favoriteAddedSuccess.tr()
: LocaleKeys.favoriteRemovedSuccess.tr())
: (response.wpFavoritesAddRemoveMessage ??
(targetFav
? LocaleKeys.favoriteAddedSuccess.tr()
: LocaleKeys.favoriteRemovedSuccess.tr()));
Utils.showToast(msg, isSuccess: true);
} else {
Utils.showToast(
response?.errorEndUserMessage ??
response?.errorMessage ??
LocaleKeys.favoriteError.tr(),
);
await fetchFavorites(query: _searchController.text);
}
} catch (e) {
Utils.hideLoading(context);
debugPrint("MazayaSavedScreen: error toggling favorite: $e");
Utils.showToast(LocaleKeys.favoriteError.tr());
await fetchFavorites(query: _searchController.text);
}
}
void _openRetailer(WalaPlusRetailerModel item) async {
await Navigator.pushNamed(
context,
AppRoutes.retailerDetailsScreen,
arguments: item,
);
if (mounted) {
await fetchFavorites(query: _searchController.text);
}
}
@override
Widget build(BuildContext context) {
bool isAr = AppState().isArabic(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBarWidget(
context,
title: LocaleKeys.favoriteOffers.tr(),
showHomeButton: false,
),
bottomNavigationBar: const MazayaBottomNav(currentTab: MazayaTab.saved),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Container(
width: double.infinity,
height: 70,
padding: const EdgeInsets.only(
top: 14,
right: 16,
bottom: 14,
left: 16,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFE8E8E8), width: 1.0),
boxShadow: <BoxShadow>[
BoxShadow(
color: const Color(0xFF000000).withOpacity(0.0314),
offset: const Offset(0, 2),
blurRadius: 5,
spreadRadius: 0,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
LocaleKeys.search.tr().toText12(
isBold: true,
color: const Color(0xFF2A333C),
),
const SizedBox(height: 2),
TextField(
controller: _searchController,
onChanged: _onSearchChanged,
onSubmitted: (String val) {
_debounceTimer?.cancel();
fetchFavorites(query: val.trim());
},
textInputAction: TextInputAction.search,
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
hintText: LocaleKeys.searchItems.tr(),
hintStyle: const TextStyle(
color: Color(0xFFACAFB1),
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.2,
),
border: InputBorder.none,
suffixIcon: ValueListenableBuilder<TextEditingValue>(
valueListenable: _searchController,
builder: (
BuildContext context,
TextEditingValue value,
Widget? child,
) {
if (value.text.isEmpty)
return const SizedBox.shrink();
return InkWell(
child: const Icon(
Icons.clear,
size: 18,
color: Color(0xFF848486),
),
onTap: () {
_searchController.clear();
_onSearchChanged("");
},
);
},
),
suffixIconConstraints: const BoxConstraints(
maxWidth: 24,
maxHeight: 28,
),
),
style: const TextStyle(
color: Color(0xFF2A333C),
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
],
),
),
),
Expanded(
child: RefreshIndicator(
onRefresh: () => fetchFavorites(query: _searchController.text),
color: MyColors.textMixColor,
child: SingleChildScrollView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Column(
children: <Widget>[
WalaPlusRetailerGrid(
isLoading: isLoading,
retailers: favoriteRetailers,
isAr: isAr,
isLoadingMore: isLoadingMore,
onTapRetailer: _openRetailer,
onFavoriteTap: _toggleFavorite,
),
const SizedBox(height: 20),
],
),
),
),
),
],
),
);
}
}

@ -0,0 +1,85 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.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/ui/mazaya/mazaya_discover_screen.dart';
import 'package:mohem_flutter_app/ui/mazaya/saved_retailers_screen.dart';
import 'package:mohem_flutter_app/ui/profile/family_members.dart';
enum MazayaTab { discover, saved, myFamily }
// Shared bottom nav for the Mazaya module (Discover / Saved / My Family). Each tab is its own
// screen, swapped in with zero-duration transition (see _onTabTapped) so switching tabs feels
// instant rather than like a new screen opening - not an IndexedStack shell, since each tab still
// needs to reload its own data fresh on every switch, not preserve state across tabs.
class MazayaBottomNav extends StatelessWidget {
final MazayaTab currentTab;
const MazayaBottomNav({Key? key, required this.currentTab}) : super(key: key);
static const Color _activeColor = Color(0xFF147D6B);
static const Color _inactiveColor = Color(0xFF78817F);
void _onTabTapped(BuildContext context, MazayaTab tab) {
if (tab == currentTab) return;
Widget targetScreen;
RouteSettings? settings;
switch (tab) {
case MazayaTab.discover:
targetScreen = const MazayaDiscoverScreen();
break;
case MazayaTab.saved:
targetScreen = const SavedRetailersScreen();
break;
case MazayaTab.myFamily:
targetScreen = const FamilyMembers();
settings = const RouteSettings(arguments: <String, dynamic>{'fromMazaya': true});
break;
}
Navigator.pushReplacement(
context,
PageRouteBuilder<void>(
settings: settings,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => targetScreen,
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
),
);
}
Widget _buildItem(BuildContext context, {required MazayaTab tab, required String svgAsset, required String label}) {
bool isActive = tab == currentTab;
return Expanded(
child: InkWell(
onTap: () => _onTabTapped(context, tab),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SvgPicture.asset(svgAsset, width: 22, height: 22, colorFilter: ColorFilter.mode(isActive ? _activeColor : _inactiveColor, BlendMode.srcIn)),
const SizedBox(height: 5),
label.toText11(weight: isActive ? FontWeight.w600 : FontWeight.w400, color: isActive ? _activeColor : _inactiveColor),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: Container(
decoration: const BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Color(0xFFE8EAE4)))),
padding: const EdgeInsets.only(top: 13, bottom: 9),
child: Row(
children: <Widget>[
_buildItem(context, tab: MazayaTab.discover, svgAsset: "assets/icons/mazaya_compass.svg", label: LocaleKeys.discover.tr()),
_buildItem(context, tab: MazayaTab.saved, svgAsset: "assets/icons/mazaya_heart_outline.svg", label: LocaleKeys.saved.tr()),
_buildItem(context, tab: MazayaTab.myFamily, svgAsset: "assets/icons/mazaya_user_round.svg", label: LocaleKeys.myFamily.tr()),
],
),
),
);
}
}

@ -0,0 +1,97 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.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/main.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
import 'package:mohem_flutter_app/ui/mazaya/retailers_list_screen.dart';
import 'package:shimmer/shimmer.dart';
class WalaPlusRetailerGrid extends StatelessWidget {
final bool isLoading;
final List<WalaPlusRetailerModel> retailers;
final bool isAr;
final void Function(WalaPlusRetailerModel item) onTapRetailer;
final void Function(WalaPlusRetailerModel item) onFavoriteTap;
final bool isLoadingMore;
final int shimmerItemCount;
const WalaPlusRetailerGrid({
Key? key,
required this.isLoading,
required this.retailers,
required this.isAr,
required this.onTapRetailer,
required this.onFavoriteTap,
this.isLoadingMore = false,
this.shimmerItemCount = 4,
}) : super(key: key);
SliverGridDelegateWithFixedCrossAxisCount get _gridDelegate =>
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: isTablet ? 4 : 2, mainAxisExtent: 235, crossAxisSpacing: 12, mainAxisSpacing: 16);
@override
Widget build(BuildContext context) {
if (isLoading) {
return Shimmer.fromColors(
baseColor: const Color(0xFFE2E8F0),
highlightColor: const Color(0xFFF8FAFC),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: _gridDelegate,
itemCount: shimmerItemCount,
itemBuilder: (BuildContext _, int __) => _buildShimmerCard(),
),
);
}
if (retailers.isEmpty) {
return Padding(padding: const EdgeInsets.only(top: 40), child: Center(child: LocaleKeys.noOffersAvailable.tr().toText14(color: const Color(0xFF64748B))));
}
return Column(
children: <Widget>[
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: _gridDelegate,
itemCount: retailers.length,
itemBuilder: (BuildContext context, int index) {
return WalaPlusRetailerCard(item: retailers[index], isAr: isAr, onTap: () => onTapRetailer(retailers[index]), onFavoriteTap: () => onFavoriteTap(retailers[index]));
},
),
if (isLoadingMore) ...<Widget>[
16.height,
Center(child: Padding(padding: const EdgeInsets.symmetric(vertical: 12), child: Image.asset("assets/images/logos/loading_mohemm_logo.gif", height: 48.0, width: 48.0))),
],
],
);
}
Widget _buildShimmerCard() {
return Container(
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(height: 120, width: double.infinity, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(14))),
const SizedBox(height: 8),
Container(height: 14, width: 110, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))),
const SizedBox(height: 6),
Container(height: 14, width: 80, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))),
const SizedBox(height: 6),
Row(
children: <Widget>[
Container(height: 28, width: 50, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(6))),
const SizedBox(width: 5),
Container(height: 28, width: 45, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(6))),
],
),
],
),
);
}
}

@ -1858,7 +1858,7 @@ class _AddDependentVerifyScreenState extends State<AddDependentVerifyScreen> {
onHomeTapped: () {
Navigator.pushNamedAndRemoveUntil(
context,
ApiConsts.ISDependetLogin ? AppRoutes.dashboardScreenForDependet : AppRoutes.dashboard,
ApiConsts.ISDependetLogin ? AppRoutes.mazayaDiscover : AppRoutes.dashboard,
(Route<dynamic> route) => false,
);
},

@ -6,6 +6,7 @@ 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/classes/consts.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
@ -52,13 +53,22 @@ 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 {
@ -71,7 +81,8 @@ class DependentDetailsScreen extends StatefulWidget {
// equivalent local state directly instead of needing this.
final bool? recentAction;
const DependentDetailsScreen({Key? key, this.contact, this.recentAction}) : super(key: key);
const DependentDetailsScreen({Key? key, this.contact, this.recentAction})
: super(key: key);
@override
_DependentDetailsScreenState createState() => _DependentDetailsScreenState();
@ -107,7 +118,8 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
bool get _isAccessAllowed {
if (_contact == null) return false;
String? flag = _contact!.mAZAYAACCESSFLG ?? _contact!.mazayaAccess?.toString();
String? flag =
_contact!.mAZAYAACCESSFLG ?? _contact!.mazayaAccess?.toString();
return flag?.trim().toUpperCase() == 'Y';
}
@ -118,17 +130,29 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
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();
List<String> parts =
name
.trim()
.split(RegExp(r'\s+'))
.where((String p) => p.isNotEmpty)
.toList();
if (parts.isEmpty) return "D";
if (parts.length == 1) {
return parts.first.length >= 2 ? parts.first.substring(0, 2).toUpperCase() : parts.first.toUpperCase();
return parts.first.length >= 2
? parts.first.substring(0, 2).toUpperCase()
: parts.first.toUpperCase();
}
return "${parts.first[0]}${parts.last[0]}".toUpperCase();
}
String _getFirstName(String? name) {
if (name == null || name.trim().isEmpty) return "";
List<String> parts = name.trim().split(RegExp(r'\s+')).where((String p) => p.isNotEmpty).toList();
List<String> parts =
name
.trim()
.split(RegExp(r'\s+'))
.where((String p) => p.isNotEmpty)
.toList();
if (parts.isEmpty) return "";
return parts.first;
}
@ -142,7 +166,10 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
if (match != null) {
try {
int millis = int.parse(match.group(1)!);
DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true);
DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(
millis,
isUtc: true,
);
return DateTime(dtUtc.year, dtUtc.month, dtUtc.day);
} catch (_) {}
}
@ -150,7 +177,10 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
if (RegExp(r'^-?\d{10,14}$').hasMatch(s)) {
try {
int millis = int.parse(s);
DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true);
DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(
millis,
isUtc: true,
);
return DateTime(dtUtc.year, dtUtc.month, dtUtc.day);
} catch (_) {}
}
@ -161,11 +191,28 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
// DATE_OF_BIRTH comes back as "M/d/yyyy h:mm:ss a" (e.g. "10/25/1997 12:00:00 AM") - strip
// that clock-time suffix first, since none of the date-only patterns below can match it
// with it still attached (parseStrict requires the whole string to match the pattern).
String dateOnly = s.replaceFirst(RegExp(r'\s+\d{1,2}:\d{2}(:\d{2})?\s*(AM|PM)?$', caseSensitive: false), '').trim();
String dateOnly =
s
.replaceFirst(
RegExp(
r'\s+\d{1,2}:\d{2}(:\d{2})?\s*(AM|PM)?$',
caseSensitive: false,
),
'',
)
.trim();
// MM/dd/yyyy before dd/MM/yyyy - the real source is confirmed month-first, so for an
// ambiguous case like "5/6/1990" this must resolve to May 6, not be misread as 5 June.
List<String> formats = <String>['yyyy-MM-dd', 'MM/dd/yyyy', 'dd/MM/yyyy', 'dd-MM-yyyy', 'yyyy/MM/dd', 'dd-MMM-yyyy', 'dd MMM yyyy'];
List<String> formats = <String>[
'yyyy-MM-dd',
'MM/dd/yyyy',
'dd/MM/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(dateOnly);
@ -205,9 +252,20 @@ 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]),
icon: isAllowed ? Icons.remove_circle_outline : Icons.check_circle_outline,
title:
isAllowed
? LocaleKeys.revokeMazayaAccess.tr()
: LocaleKeys.allowMazayaAccess.tr(),
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),
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
@ -271,7 +329,10 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
// 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.replaceAll('-', '')}";
String nationalIdLine =
(nationalId == null || nationalId.isEmpty)
? LocaleKeys.nationalIdOrIqamaFallback.tr()
: "${LocaleKeys.nationalId.tr()}: ${nationalId.replaceAll('-', '')}";
return "$linkToShare\n\n${LocaleKeys.mazayaInviteInstructions.tr()}\n\n$nationalIdLine\n${LocaleKeys.dateOfBirth.tr()}: ${_formatDobForShare()}";
}
@ -291,11 +352,16 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
// (including WhatsApp) just attaches the file with nothing to drop.
Future<void> _shareMazayaAccessGuide(bool isAr) async {
try {
ByteData bytes = await rootBundle.load(MazayaAccessResultScreen.getShareGuidePdfAssetPath(isAr));
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";
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 pdfFile.writeAsBytes(
bytes.buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes),
);
await SharePlus.instance.share(ShareParams(files: [XFile(pdfFile.path)]));
} catch (_) {}
@ -307,7 +373,11 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
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;
@ -328,15 +398,39 @@ 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))),
onTap:
() => Navigator.popUntil(
context,
ModalRoute.withName(
ApiConsts.isDependetLogin
? AppRoutes.mazayaDiscover
: AppRoutes.dashboard,
),
),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(
Icons.home_outlined,
size: 24,
color: Color(0xFF1E293B),
),
),
),
],
),
@ -360,21 +454,39 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
child: Container(
width: 72,
height: 72,
decoration: BoxDecoration(color: _recentAction! ? const Color(0xFFE6F7F2) : const Color(0xFFFEECEB), shape: BoxShape.circle),
decoration: BoxDecoration(
color:
_recentAction!
? const Color(0xFFE6F7F2)
: const Color(0xFFFEECEB),
shape: BoxShape.circle,
),
child: Center(
child: CustomPaint(
size: const Size(36, 36),
painter: _ShieldIconPainter(color: _recentAction! ? const Color(0xFF168A89) : const Color(0xFFED1C24), isGranted: _recentAction!),
painter: _ShieldIconPainter(
color:
_recentAction!
? const Color(0xFF168A89)
: const Color(0xFFED1C24),
isGranted: _recentAction!,
),
),
),
),
),
const SizedBox(height: 10),
Center(
child: (_recentAction! ? LocaleKeys.mazayaAccessGranted.tr() : LocaleKeys.mazayaAccessRevoked.tr()).toText16(
weight: FontWeight.w500,
color: _recentAction! ? const Color(0xFF168A89) : const Color(0xFFED1C24),
),
child: (_recentAction!
? LocaleKeys.mazayaAccessGranted.tr()
: LocaleKeys.mazayaAccessRevoked.tr())
.toText16(
weight: FontWeight.w500,
color:
_recentAction!
? const Color(0xFF168A89)
: const Color(0xFFED1C24),
),
),
const SizedBox(height: 16),
],
@ -383,7 +495,14 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
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)),
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>[
@ -391,8 +510,15 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
Container(
width: 52,
height: 52,
decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
child: Center(child: initials.toText17(color: const Color(0xFF168A89))),
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: Center(
child: initials.toText17(
color: const Color(0xFF168A89),
),
),
),
const SizedBox(width: 14),
@ -402,14 +528,32 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
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))],
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 - only when NHIC has actually verified this contact
if (_isNhicVerified) ...<Widget>[const SizedBox(width: 8), const Icon(Icons.verified_rounded, color: Color(0xFF059669), size: 24)],
if (_isNhicVerified) ...<Widget>[
const SizedBox(width: 8),
const Icon(
Icons.verified_rounded,
color: Color(0xFF059669),
size: 24,
),
],
],
),
),
@ -424,33 +568,62 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
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))],
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)),
if (_isNhicVerified) LocaleKeys.verified.tr().toText13(weight: FontWeight.w500, color: const Color(0xFF059669)),
LocaleKeys.keyDetails.tr().toText16(
weight: FontWeight.w500,
color: const Color(0xFF1E293B),
),
if (_isNhicVerified)
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 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)),
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)),
LocaleKeys.dateOfBirth.tr().toText13(
weight: FontWeight.w400,
color: const Color(0xFF64748B),
),
formattedDob.toText13(
weight: FontWeight.w500,
color: const Color(0xFF1E293B),
),
],
),
],
@ -460,9 +633,15 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
const SizedBox(height: 24),
// Services & actions section header
LocaleKeys.servicesAndActions.tr().toText17(weight: FontWeight.w500, color: const Color(0xFF1E293B)),
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)),
LocaleKeys.startDependentFlowsDesc.tr().toText13(
weight: FontWeight.w400,
color: const Color(0xFF64748B),
),
const SizedBox(height: 12),
@ -474,7 +653,13 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
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))],
boxShadow: const <BoxShadow>[
BoxShadow(
color: Color(0x08000000),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -483,29 +668,66 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
LocaleKeys.mazayaAccess.tr().toText16(weight: FontWeight.w500, color: const Color(0xFF1E293B)),
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),
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 3,
),
decoration: BoxDecoration(
color:
isAllowed
? const Color(0xFFECFDF5)
: const Color(0xFFFEE2E2),
borderRadius: BorderRadius.circular(12),
),
child: (isAllowed
? LocaleKeys.active.tr()
: LocaleKeys.accessOff.tr())
.toText12(
weight: FontWeight.w500,
color:
isAllowed
? const Color(0xFF059669)
: const Color(0xFFEF4444),
),
),
],
),
const SizedBox(height: 6),
LocaleKeys.allowDependentMazayaOffersDesc.tr(args: <String>[firstName.isNotEmpty ? firstName : fullName]).toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)),
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 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)),
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),
@ -514,8 +736,14 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
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)),
LocaleKeys.verification.tr().toText13(
weight: FontWeight.w400,
color: const Color(0xFF64748B),
),
LocaleKeys.sponsorOtp.tr().toText13(
weight: FontWeight.w500,
color: const Color(0xFF1E293B),
),
],
),
@ -529,19 +757,46 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
height: 48,
width: double.infinity,
decoration: BoxDecoration(
color: isAllowed ? const Color(0xFFFEF2F2) : const Color(0xFF22B0A6),
color:
isAllowed
? const Color(0xFFFEF2F2)
: const Color(0xFF22B0A6),
borderRadius: BorderRadius.circular(12),
border: isAllowed ? Border.all(color: const Color(0xFFFECACA), width: 1.2) : null,
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,
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,
),
],
),
),
@ -549,7 +804,15 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
if (isAllowed) ...<Widget>[
const SizedBox(height: 12),
Center(child: LocaleKeys.revokingPreventsAccessDesc.tr().toText11(weight: FontWeight.w400, color: const Color(0xFF64748B), isCenter: true)),
Center(
child: LocaleKeys.revokingPreventsAccessDesc
.tr()
.toText11(
weight: FontWeight.w400,
color: const Color(0xFF64748B),
isCenter: true,
),
),
],
],
),
@ -570,13 +833,24 @@ class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
child: DefaultButton(
LocaleKeys.shareAccessGuide.tr(),
() => _shareMazayaAccessGuide(isAr),
colors: const <Color>[Color(0xFFE8F7F2), Color(0xFFE8F7F2)],
border: Border.all(color: const Color(0xFFBCE6D7), width: 1.2),
colors: const <Color>[
Color(0xFFE8F7F2),
Color(0xFFE8F7F2),
],
border: Border.all(
color: const Color(0xFFBCE6D7),
width: 1.2,
),
textColor: const Color(0xFF168A89),
),
),
const SizedBox(width: 12),
Expanded(child: DefaultButton(LocaleKeys.shareDownloadLink.tr(), () => _shareMazayaInviteLink(isAr, linkToShare))),
Expanded(
child: DefaultButton(
LocaleKeys.shareDownloadLink.tr(),
() => _shareMazayaInviteLink(isAr, linkToShare),
),
),
],
).insideContainer,
],

@ -1,5 +1,4 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
@ -13,6 +12,7 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/dashboard/menu_entries.dart';
import 'package:mohem_flutter_app/models/get_employee_contacts.model.dart';
import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart';
import 'package:mohem_flutter_app/ui/mazaya/widgets/mazaya_bottom_nav.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/widgets/bottom_sheets/confirm_bottom_sheet.dart';
@ -191,6 +191,9 @@ class _FamilyMembersState extends State<FamilyMembers> {
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
// Only shown when reached via the Mazaya "My Family" tab - this screen is also opened from
// the regular profile flow (fromMazaya == false), where the Mazaya bottom nav doesn't apply.
bottomNavigationBar: fromMazaya ? const MazayaBottomNav(currentTab: MazayaTab.myFamily) : null,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: SafeArea(
@ -206,13 +209,22 @@ class _FamilyMembersState extends State<FamilyMembers> {
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)),
// Same title style as the other Mazaya tabs' headers (Discover/Saved use
// AppBarWidget's title.toText24(isBold: true, color: MyColors.darkTextColor)) -
// matched here since this screen builds its own header instead of AppBarWidget.
fromMazaya
? LocaleKeys.myFamily.tr().toText24(isBold: true, color: MyColors.darkTextColor)
: 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))),
),
// Home button hidden in the Mazaya flow too, matching AppBarWidget's
// showHomeButton: false on the other tabs - MazayaBottomNav's own Discover tab
// already serves as "home" for this module.
if (!fromMazaya)
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))),
),
],
),
),
@ -302,7 +314,7 @@ class _FamilyMembersState extends State<FamilyMembers> {
}
// 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
// retailers_list_screen.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(

@ -61,62 +61,58 @@ class _OffersAndDiscountsHomeState extends State<OffersAndDiscountsHome> {
LocaleKeys.browseCategories.tr().toText17().paddingOnly(left: 21, right: 21, top: 21),
SizedBox(
height: 110.0,
child: getCategoriesList.isNotEmpty
? ListView.separated(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.only(left: 21, right: 21, top: 13, bottom: 13),
scrollDirection: Axis.horizontal,
itemBuilder: (cxt, index) {
return AspectRatio(
aspectRatio: 1 / 1,
child: InkWell(
onTap: () {
setState(() {
currentCategoryID = getCategoriesList[index].id!;
getCategoryOffersListAPI();
// getItemsForSaleList.clear();
// currentPageNo = 1;
// getItemsForSale(currentPageNo, currentCategoryID);
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SvgPicture.string(
getCategoriesList[index].content!,
fit: BoxFit.contain,
width: 25,
height: 25,
),
currentCategoryID == getCategoriesList[index].id ? const Icon(Icons.check_circle_rounded, color: MyColors.greenColor, size: 16.0) : Container(),
],
).expanded,
AppState().isArabic(context) ? getCategoriesList[index].categoryNameAr!.toText10() : getCategoriesList[index].categoryNameEn!.toText10()
],
).paddingOnly(left: 10, right: 10, bottom: 10, top: 12).expanded.objectContainerView(disablePadding: true),
),
);
},
separatorBuilder: (cxt, index) => 12.width,
itemCount: getCategoriesList.length,
)
: Container(),
child:
getCategoriesList.isNotEmpty
? ListView.separated(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.only(left: 21, right: 21, top: 13, bottom: 13),
scrollDirection: Axis.horizontal,
itemBuilder: (cxt, index) {
return AspectRatio(
aspectRatio: 1 / 1,
child: InkWell(
onTap: () {
setState(() {
currentCategoryID = getCategoriesList[index].id!;
getCategoryOffersListAPI();
// getItemsForSaleList.clear();
// currentPageNo = 1;
// getItemsForSale(currentPageNo, currentCategoryID);
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SvgPicture.string(getCategoriesList[index].content!, fit: BoxFit.contain, width: 25, height: 25),
currentCategoryID == getCategoriesList[index].id ? const Icon(Icons.check_circle_rounded, color: MyColors.greenColor, size: 16.0) : Container(),
],
).expanded,
AppState().isArabic(context) ? getCategoriesList[index].categoryNameAr!.toText10() : getCategoriesList[index].categoryNameEn!.toText10(),
],
).paddingOnly(left: 10, right: 10, bottom: 10, top: 12).expanded.objectContainerView(disablePadding: true),
),
);
},
separatorBuilder: (cxt, index) => 12.width,
itemCount: getCategoriesList.length,
)
: Container(),
),
getOffersList.isNotEmpty
? GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 162 / 266, crossAxisSpacing: 12, mainAxisSpacing: 12),
padding: const EdgeInsets.only(left: 21, right: 21, bottom: 21, top: 21),
shrinkWrap: true,
primary: false,
physics: const ScrollPhysics(),
children: getItemsForSaleWidgets(),
)
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 162 / 266, crossAxisSpacing: 12, mainAxisSpacing: 12),
padding: const EdgeInsets.only(left: 21, right: 21, bottom: 21, top: 21),
shrinkWrap: true,
primary: false,
physics: const ScrollPhysics(),
children: getItemsForSaleWidgets(),
)
: Utils.getNoDataWidget(context).paddingOnly(top: 50),
],
),
@ -144,13 +140,7 @@ class _OffersAndDiscountsHomeState extends State<OffersAndDiscountsHome> {
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: const Color(0xff000000).withOpacity(.05),
blurRadius: 26,
offset: const Offset(0, -3),
),
],
boxShadow: [BoxShadow(color: const Color(0xff000000).withOpacity(.05), blurRadius: 26, offset: const Offset(0, -3))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -159,16 +149,7 @@ class _OffersAndDiscountsHomeState extends State<OffersAndDiscountsHome> {
Hero(
tag: "ItemImage" + getOffersList.offersDiscountId.toString()!,
transitionOnUserGestures: true,
child: AspectRatio(
aspectRatio: 118 / 127,
child: ClipRRect(
borderRadius: BorderRadius.circular(6),
child: Image.network(
getOffersList.bannerImage ?? "",
fit: BoxFit.contain,
),
),
),
child: AspectRatio(aspectRatio: 118 / 127, child: ClipRRect(borderRadius: BorderRadius.circular(6), child: Image.network(getOffersList.bannerImage ?? "", fit: BoxFit.contain))),
),
AppState().isArabic(context)
? getOffersList.titleAr!.toText16(isBold: true, color: const Color(0xff2B353E), maxlines: 1)
@ -183,10 +164,7 @@ class _OffersAndDiscountsHomeState extends State<OffersAndDiscountsHome> {
// 8.height,
getOffersList.discountDescription!.toText14(isBold: true, maxlines: 1),
20.height,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [checkDate(getOffersList.endDate!), SvgPicture.asset("assets/images/arrow_next.svg").paddingOnly(bottom: 4)],
),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [checkDate(getOffersList.endDate!), SvgPicture.asset("assets/images/arrow_next.svg").paddingOnly(bottom: 4)]),
],
),
),

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
@ -27,14 +28,20 @@ AppBar AppBarWidget(
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: Feedback.wrapForTap(() {
(onBackTapped == null ? Navigator.maybePop(context) : onBackTapped());
(onBackTapped == null
? Navigator.maybePop(context)
: onBackTapped());
}, context),
child: const Icon(Icons.arrow_back_ios, color: MyColors.darkIconColor),
child: const Icon(
Icons.arrow_back_ios,
color: MyColors.darkIconColor,
),
),
if (showLogo) 4.width,
if (showLogo) SvgPicture.asset(logoPath!),
4.width,
if (!showLogo)title.toText24(color: MyColors.darkTextColor, isBold: true).expanded,
if (!showLogo)
title.toText24(color: MyColors.darkTextColor, isBold: true).expanded,
],
),
centerTitle: false,
@ -44,7 +51,18 @@ AppBar AppBarWidget(
if (showHomeButton)
IconButton(
onPressed: () {
onHomeTapped == null ? Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)) : onHomeTapped();
if (onHomeTapped != null) {
onHomeTapped();
} else {
Navigator.popUntil(
context,
ModalRoute.withName(
ApiConsts.isDependetLogin
? AppRoutes.mazayaDiscover
: AppRoutes.dashboard,
),
);
}
},
icon: const Icon(Icons.home, color: MyColors.darkIconColor),
),
@ -53,7 +71,10 @@ AppBar AppBarWidget(
onPressed: () {
Navigator.pushNamed(context, AppRoutes.worklistSettings);
},
icon: const Icon(Icons.settings_rounded, color: MyColors.darkIconColor),
icon: const Icon(
Icons.settings_rounded,
color: MyColors.darkIconColor,
),
),
if (showMemberButton)
IconButton(

@ -50,7 +50,7 @@ class _AppInAppWebViewScreenState extends State<AppInAppWebViewScreen> {
void _onHomeTapped() {
if (ApiConsts.isDependetLogin) {
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboardScreenForDependet));
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.mazayaDiscover));
} else {
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard));
}

Loading…
Cancel
Save