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:
  minor changes
  refactoriåtion
wala_plus_with_dialog_removal
Sultan khan 3 weeks ago
commit 58908df424

@ -808,6 +808,7 @@
"pleaseProvideLocationPermission": "الرجاء منح إذن الموقع", "pleaseProvideLocationPermission": "الرجاء منح إذن الموقع",
"microphoneAccessRequired": "يحتاج التطبيق إلى إذن الوصول إلى الميكروفون لتسجيل الصوت.", "microphoneAccessRequired": "يحتاج التطبيق إلى إذن الوصول إلى الميكروفون لتسجيل الصوت.",
"shareAccessGuide": "مشاركة دليل الوصول", "shareAccessGuide": "مشاركة دليل الوصول",
"nationalIdOrIqamaNumber": "رقم الهوية الوطنية أو الإقامة" "nationalIdOrIqamaNumber": "رقم الهوية الوطنية أو الإقامة",
"multipleCities": "مدن متعددة"
} }

@ -553,7 +553,7 @@
"confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?", "confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?",
"yes": "Yes", "yes": "Yes",
"no": "No", "no": "No",
"mazayaInviteInstructions": "Please download the Mohemm app from the above link and use the following credentials to login", "mazayaInviteInstructions": "Please download the MoheM app from the above link and use the following credentials to login",
"cameraPermissionRequiredToScan": "Please grant camera permission to scan the QR code", "cameraPermissionRequiredToScan": "Please grant camera permission to scan the QR code",
"cameraPermissionPermanentlyDenied": "Please allow camera permission from the app settings > Permissions", "cameraPermissionPermanentlyDenied": "Please allow camera permission from the app settings > Permissions",
"openSettings": "Open Settings", "openSettings": "Open Settings",
@ -808,7 +808,8 @@
"pleaseProvideLocationPermission": "Please provide location permission", "pleaseProvideLocationPermission": "Please provide location permission",
"microphoneAccessRequired": "The app needs microphone access to be able to record audio.", "microphoneAccessRequired": "The app needs microphone access to be able to record audio.",
"shareAccessGuide": "Share Access Guide", "shareAccessGuide": "Share Access Guide",
"nationalIdOrIqamaNumber": "National ID or Iqama Number" "nationalIdOrIqamaNumber": "National ID or Iqama Number",
"multipleCities": "Multiple Cities"
} }

@ -820,7 +820,8 @@ class CodegenLoader extends AssetLoader{
"pleaseProvideLocationPermission": "الرجاء منح إذن الموقع", "pleaseProvideLocationPermission": "الرجاء منح إذن الموقع",
"microphoneAccessRequired": "يحتاج التطبيق إلى إذن الوصول إلى الميكروفون لتسجيل الصوت.", "microphoneAccessRequired": "يحتاج التطبيق إلى إذن الوصول إلى الميكروفون لتسجيل الصوت.",
"shareAccessGuide": "مشاركة دليل الوصول", "shareAccessGuide": "مشاركة دليل الوصول",
"nationalIdOrIqamaNumber": "رقم الهوية الوطنية أو الإقامة" "nationalIdOrIqamaNumber": "رقم الهوية الوطنية أو الإقامة",
"multipleCities": "مدن متعددة"
}; };
static const Map<String,dynamic> _en_US = { static const Map<String,dynamic> _en_US = {
"mohemm": "Mohemm", "mohemm": "Mohemm",
@ -1377,7 +1378,7 @@ static const Map<String,dynamic> _en_US = {
"confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?", "confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?",
"yes": "Yes", "yes": "Yes",
"no": "No", "no": "No",
"mazayaInviteInstructions": "Please download the Mohemm app from the above link and use the following credentials to login", "mazayaInviteInstructions": "Please download the MoheM app from the above link and use the following credentials to login",
"cameraPermissionRequiredToScan": "Please grant camera permission to scan the QR code", "cameraPermissionRequiredToScan": "Please grant camera permission to scan the QR code",
"cameraPermissionPermanentlyDenied": "Please allow camera permission from the app settings > Permissions", "cameraPermissionPermanentlyDenied": "Please allow camera permission from the app settings > Permissions",
"openSettings": "Open Settings", "openSettings": "Open Settings",
@ -1628,7 +1629,8 @@ static const Map<String,dynamic> _en_US = {
"pleaseProvideLocationPermission": "Please provide location permission", "pleaseProvideLocationPermission": "Please provide location permission",
"microphoneAccessRequired": "The app needs microphone access to be able to record audio.", "microphoneAccessRequired": "The app needs microphone access to be able to record audio.",
"shareAccessGuide": "Share Access Guide", "shareAccessGuide": "Share Access Guide",
"nationalIdOrIqamaNumber": "National ID or Iqama Number" "nationalIdOrIqamaNumber": "National ID or Iqama Number",
"multipleCities": "Multiple Cities"
}; };
static const Map<String, Map<String,dynamic>> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; static const Map<String, Map<String,dynamic>> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US};
} }

@ -793,5 +793,6 @@ abstract class LocaleKeys {
static const microphoneAccessRequired = 'microphoneAccessRequired'; static const microphoneAccessRequired = 'microphoneAccessRequired';
static const shareAccessGuide = 'shareAccessGuide'; static const shareAccessGuide = 'shareAccessGuide';
static const nationalIdOrIqamaNumber = 'nationalIdOrIqamaNumber'; static const nationalIdOrIqamaNumber = 'nationalIdOrIqamaNumber';
static const multipleCities = 'multipleCities';
} }

@ -471,32 +471,40 @@ class WalaPlusRetailerModel {
return false; return false;
} }
String getDisplayCity({bool isAr = false}) { // Resolves a single location's city using the same 3-pass priority this used to apply
if (locations != null && locations!.isNotEmpty) { // globally: specific city name first, then a non-generic-country city, then any city at all.
// 1. First pass: look for specific city name String? _resolveLocationCity(WalaPlusRetailerLocation loc, bool isAr) {
for (var loc in locations!) { String? cName = isAr ? loc.cityNameAr : loc.cityNameEn;
String? cName = isAr ? loc.cityNameAr : loc.cityNameEn; if (cName != null && cName.trim().isNotEmpty && cName != "null") {
if (cName != null && cName.trim().isNotEmpty && cName != "null") { return cName.trim();
return cName.trim(); }
} if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
} String c = loc.city!.trim();
// 2. Second pass: fallback to city if not generic country if (c.toLowerCase() != 'saudi arabia' && c != 'المملكة العربية السعودية') {
for (var loc in locations!) { return c;
if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
String c = loc.city!.trim();
if (c.toLowerCase() != 'saudi arabia' && c != 'المملكة العربية السعودية') {
return c;
}
}
}
// 3. Third pass: any non-empty city
for (var loc in locations!) {
if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
return loc.city!.trim();
}
} }
} }
return ''; if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
return loc.city!.trim();
}
return null;
}
// Multiple distinct cities across this retailer's locations -> "Multiple Cities" instead of
// just picking the first one found, so the badge doesn't silently hide the other branches.
String getDisplayCity({bool isAr = false}) {
log("locations: ${locations?.map((e) => e.toJson()).toList()}");
if (locations == null || locations!.isEmpty) return '';
Set<String> distinctCities = <String>{};
for (var loc in locations!) {
String? resolved = _resolveLocationCity(loc, isAr);
if (resolved != null) distinctCities.add(resolved);
}
if (distinctCities.isEmpty) return '';
if (distinctCities.length > 1) return LocaleKeys.multipleCities.tr();
return distinctCities.first;
} }
String get displayCity => getDisplayCity(isAr: false); String get displayCity => getDisplayCity(isAr: false);
@ -582,37 +590,62 @@ class WalaPlusRetailerModel {
return null; return null;
} }
bool get isOnlineStore => (type?.trim().toLowerCase() == 'online'); // type can be a comma-separated combo (e.g. "online,instore") when a retailer offers both -
// this splits and normalizes every part instead of only ever matching a single exact value,
bool get isInstore => // so a combined retailer is correctly recognized as both rather than neither/just one.
(type?.trim().toLowerCase() == 'instore' || List<String> get normalizedTypes {
type?.trim().toLowerCase() == 'in_store' ||
type?.trim().toLowerCase() == 'in store' ||
type?.trim().toLowerCase() == 'store' ||
(type == null && locations != null && locations!.isNotEmpty));
String? get normalizedType {
if (type != null && type!.trim().isNotEmpty) { if (type != null && type!.trim().isNotEmpty) {
String t = type!.trim().toLowerCase(); Set<String> result = <String>{};
if (t == 'online') return 'online'; for (String part in type!.split(',')) {
if (t == 'instore' || t == 'in_store' || t == 'in store' || t == 'store') return 'instore'; String t = part.trim().toLowerCase();
return t; if (t.isEmpty) continue;
if (t == 'online') {
result.add('online');
} else if (t == 'instore' || t == 'in_store' || t == 'in store' || t == 'store') {
result.add('instore');
} else {
result.add(t);
}
}
if (result.isNotEmpty) return result.toList();
} }
if (locations != null && locations!.isNotEmpty) { if (locations != null && locations!.isNotEmpty) {
return 'instore'; return <String>['instore'];
} }
return null; return <String>[];
} }
String? getDisplayType() { bool get isOnlineStore => normalizedTypes.contains('online');
String? norm = normalizedType;
bool get isInstore => normalizedTypes.contains('instore');
// Kept for any existing single-value callers - returns the first normalized type found.
String? get normalizedType {
List<String> types = normalizedTypes;
return types.isEmpty ? null : types.first;
}
String _displayTypeLabel(String norm) {
if (norm == 'online') return LocaleKeys.online.tr(); if (norm == 'online') return LocaleKeys.online.tr();
if (norm == 'instore') return LocaleKeys.inStore.tr(); if (norm == 'instore') return LocaleKeys.inStore.tr();
if (type != null && type!.trim().isNotEmpty) { return norm;
return type!.trim(); }
}
String? getDisplayType() {
List<String> types = normalizedTypes;
if (types.isNotEmpty) return _displayTypeLabel(types.first);
if (type != null && type!.trim().isNotEmpty) return type!.trim();
return null; return null;
} }
// One label per distinct type (e.g. ["Online", "In Store"]) - used to render a separate chip
// for each, instead of collapsing a dual-type retailer down to a single badge.
List<String> getDisplayTypes() {
List<String> types = normalizedTypes;
if (types.isNotEmpty) return types.map(_displayTypeLabel).toList();
if (type != null && type!.trim().isNotEmpty) return <String>[type!.trim()];
return <String>[];
}
} }
class WalaPlusRetailerContact { class WalaPlusRetailerContact {

@ -513,6 +513,14 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Welcome greeting - same "Welcome Back" label + large bold name design used
// on the last-login screen, shown for dependent logins only.
if (ApiConsts.isDependetLogin && (AppState().dependentName ?? "").isNotEmpty) ...[
LocaleKeys.welcomeBack.tr().toText12(),
AppState().dependentName!.toText24(isBold: true),
const SizedBox(height: 16),
],
// Search Input Card // Search Input Card
_buildSearchBar(isAr), _buildSearchBar(isAr),
@ -852,8 +860,9 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
offerSubtitle = item.offerTitle!.trim(); offerSubtitle = item.offerTitle!.trim();
} }
String? typeText = item.getDisplayType(); // type can be a comma-separated combo (e.g. "online,instore") - normalizedTypes already
bool isOnline = item.isOnlineStore; // splits that into its distinct parts, so a retailer offering both ways gets a chip each.
List<String> offerTypes = item.normalizedTypes;
String validityText = item.getDisplayValidity(); String validityText = item.getDisplayValidity();
String usageText = ""; String usageText = "";
if (item.displayUsage.isNotEmpty) { if (item.displayUsage.isNotEmpty) {
@ -949,23 +958,23 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
if (offerSubtitle.isNotEmpty) ...[const SizedBox(height: 2), offerSubtitle.toText14(maxlines: 1, overflow: TextOverflow.ellipsis, isBold: true, color: const Color(0xFF1E293B))], if (offerSubtitle.isNotEmpty) ...[const SizedBox(height: 2), offerSubtitle.toText14(maxlines: 1, overflow: TextOverflow.ellipsis, isBold: true, color: const Color(0xFF1E293B))],
// Badges Row: Type, Validity, Location & Usage // Badges Row: Type, Validity, Location & Usage
if ((typeText != null && typeText.trim().isNotEmpty) || validityText.trim().isNotEmpty || cityText.trim().isNotEmpty || usageText.trim().isNotEmpty) ...[ if (offerTypes.isNotEmpty || validityText.trim().isNotEmpty || cityText.trim().isNotEmpty || usageText.trim().isNotEmpty) ...[
const SizedBox(height: 6), const SizedBox(height: 6),
Wrap( Wrap(
spacing: 5, spacing: 5,
runSpacing: 4, runSpacing: 4,
children: <Widget>[ children: <Widget>[
// Type Chip (Online or In Store) // One Type Chip per distinct type - Online (green) and/or In Store (blue).
if (typeText != null && typeText.trim().isNotEmpty) for (String offerType in offerTypes)
InfoChipWidget( InfoChipWidget(
text: typeText, text: _getTypeFilterTitle(offerType),
icon: isOnline ? Icons.language_rounded : Icons.storefront_rounded, icon: _getTypeFilterIcon(offerType),
backgroundColor: isOnline ? const Color(0xFFEFF6FF) : const Color(0xFFF5F3FF), backgroundColor: offerType == 'online' ? const Color(0xFFE6F7F0) : const Color(0xFFEFF6FF),
textColor: isOnline ? const Color(0xFF2563EB) : const Color(0xFF7C3AED), textColor: offerType == 'online' ? const Color(0xFF00A651) : const Color(0xFF2563EB),
), ),
// Validity badge // Validity badge
if (validityText.trim().isNotEmpty) InfoChipWidget(text: validityText, backgroundColor: const Color(0xFFE6F7F0), textColor: const Color(0xFF00A651)), // if (validityText.trim().isNotEmpty) InfoChipWidget(text: validityText, backgroundColor: const Color(0xFFE6F7F0), textColor: const Color(0xFF00A651)),
// Location Badge // Location Badge
if (cityText.trim().isNotEmpty) if (cityText.trim().isNotEmpty)

@ -605,69 +605,27 @@ class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsS
} }
Widget _buildInfoBadgesRow(bool isAr) { Widget _buildInfoBadgesRow(bool isAr) {
bool isOnline = (_retailer?.type?.toLowerCase() == 'online'); // type can be a comma-separated combo (e.g. "online,instore") - normalizedTypes splits
String storeTypeText = isOnline ? LocaleKeys.online.tr() : LocaleKeys.inStore.tr(); // that into its distinct parts, so a retailer offering both ways gets a chip each, same
// as the dashboard card.
IconData storeIcon = isOnline ? Icons.language_rounded : Icons.storefront_rounded; List<String> offerTypes = _retailer?.normalizedTypes ?? <String>[];
String cityText = _getDisplayCity(isAr);
String validityText = _retailer?.getDisplayValidity() ?? LocaleKeys.offerValid.tr();
return Wrap( return Wrap(
spacing: 12, spacing: 12,
runSpacing: 10, runSpacing: 10,
children: [ children: [
InfoChipWidget( // One Type Chip per distinct type - Online (green) and/or In Store (blue).
text: storeTypeText, for (String offerType in offerTypes)
icon: storeIcon, InfoChipWidget(
backgroundColor: isOnline ? const Color(0xFFEFF6FF) : const Color(0xFFF5F3FF), text: offerType == 'online' ? LocaleKeys.online.tr() : (offerType == 'instore' ? LocaleKeys.inStore.tr() : (offerType.isNotEmpty ? (offerType[0].toUpperCase() + offerType.substring(1)) : offerType)),
textColor: isOnline ? const Color(0xFF2563EB) : const Color(0xFF7C3AED), icon: offerType == 'online' ? Icons.language_rounded : Icons.storefront_rounded,
), backgroundColor: offerType == 'online' ? const Color(0xFFE6F7F0) : const Color(0xFFEFF6FF),
InfoChipWidget(text: validityText, backgroundColor: const Color(0xFFE6F7F0), textColor: const Color(0xFF00A651)), textColor: offerType == 'online' ? const Color(0xFF00A651) : const Color(0xFF2563EB),
InfoChipWidget( ),
text: cityText,
svgIcon: "assets/icons/location.svg",
isBold: false,
backgroundColor: const Color(0xFFF3F4F6),
textColor: const Color(0xFF4B5563),
iconColor: const Color(0xFF64748B),
iconTextGap: 2.5,
),
], ],
); );
} }
String _getDisplayCity(bool isAr) {
if (_retailer?.locations != null && _retailer!.locations!.isNotEmpty) {
// 1. First pass: look for a specific city name (e.g. Riyadh, Jeddah, Hail)
for (var loc in _retailer!.locations!) {
String? cName = isAr ? loc.cityNameAr : loc.cityNameEn;
if (cName != null && cName.trim().isNotEmpty && cName != "null") {
return cName.trim();
}
}
// 2. Second pass: fallback to 'city' field if not generic country
for (var loc in _retailer!.locations!) {
if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
String c = loc.city!.trim();
if (c.toLowerCase() != 'saudi arabia' && c != 'المملكة العربية السعودية') {
return c;
}
}
}
// 3. Third pass: any non-empty city
for (var loc in _retailer!.locations!) {
if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
return loc.city!.trim();
}
}
}
if (_retailer?.type?.toLowerCase() == 'online') {
return LocaleKeys.ksa.tr();
}
return LocaleKeys.riyadh.tr();
}
String _getHowToUseHtml(bool isAr) { String _getHowToUseHtml(bool isAr) {
String? content; String? content;

Loading…
Cancel
Save