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
master
Sultan khan 3 weeks ago
commit 58908df424

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

@ -553,7 +553,7 @@
"confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?",
"yes": "Yes",
"no": "No",
"mazayaInviteInstructions": "Please download the Mohemm app from the above link and use the following credentials to login",
"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",
"cameraPermissionPermanentlyDenied": "Please allow camera permission from the app settings > Permissions",
"openSettings": "Open Settings",
@ -808,7 +808,8 @@
"pleaseProvideLocationPermission": "Please provide location permission",
"microphoneAccessRequired": "The app needs microphone access to be able to record audio.",
"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": "الرجاء منح إذن الموقع",
"microphoneAccessRequired": "يحتاج التطبيق إلى إذن الوصول إلى الميكروفون لتسجيل الصوت.",
"shareAccessGuide": "مشاركة دليل الوصول",
"nationalIdOrIqamaNumber": "رقم الهوية الوطنية أو الإقامة"
"nationalIdOrIqamaNumber": "رقم الهوية الوطنية أو الإقامة",
"multipleCities": "مدن متعددة"
};
static const Map<String,dynamic> _en_US = {
"mohemm": "Mohemm",
@ -1377,7 +1378,7 @@ static const Map<String,dynamic> _en_US = {
"confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?",
"yes": "Yes",
"no": "No",
"mazayaInviteInstructions": "Please download the Mohemm app from the above link and use the following credentials to login",
"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",
"cameraPermissionPermanentlyDenied": "Please allow camera permission from the app settings > Permissions",
"openSettings": "Open Settings",
@ -1628,7 +1629,8 @@ static const Map<String,dynamic> _en_US = {
"pleaseProvideLocationPermission": "Please provide location permission",
"microphoneAccessRequired": "The app needs microphone access to be able to record audio.",
"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};
}

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

@ -471,32 +471,40 @@ class WalaPlusRetailerModel {
return false;
}
String getDisplayCity({bool isAr = false}) {
if (locations != null && locations!.isNotEmpty) {
// 1. First pass: look for specific city name
for (var loc in 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 if not generic country
for (var loc in 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 locations!) {
if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
return loc.city!.trim();
}
// Resolves a single location's city using the same 3-pass priority this used to apply
// globally: specific city name first, then a non-generic-country city, then any city at all.
String? _resolveLocationCity(WalaPlusRetailerLocation loc, bool isAr) {
String? cName = isAr ? loc.cityNameAr : loc.cityNameEn;
if (cName != null && cName.trim().isNotEmpty && cName != "null") {
return cName.trim();
}
if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
String c = loc.city!.trim();
if (c.toLowerCase() != 'saudi arabia' && c != 'المملكة العربية السعودية') {
return c;
}
}
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);
@ -582,37 +590,62 @@ class WalaPlusRetailerModel {
return null;
}
bool get isOnlineStore => (type?.trim().toLowerCase() == 'online');
bool get isInstore =>
(type?.trim().toLowerCase() == 'instore' ||
type?.trim().toLowerCase() == 'in_store' ||
type?.trim().toLowerCase() == 'in store' ||
type?.trim().toLowerCase() == 'store' ||
(type == null && locations != null && locations!.isNotEmpty));
String? get normalizedType {
// 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,
// so a combined retailer is correctly recognized as both rather than neither/just one.
List<String> get normalizedTypes {
if (type != null && type!.trim().isNotEmpty) {
String t = type!.trim().toLowerCase();
if (t == 'online') return 'online';
if (t == 'instore' || t == 'in_store' || t == 'in store' || t == 'store') return 'instore';
return t;
Set<String> result = <String>{};
for (String part in type!.split(',')) {
String t = part.trim().toLowerCase();
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) {
return 'instore';
return <String>['instore'];
}
return null;
return <String>[];
}
String? getDisplayType() {
String? norm = normalizedType;
bool get isOnlineStore => normalizedTypes.contains('online');
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 == 'instore') return LocaleKeys.inStore.tr();
if (type != null && type!.trim().isNotEmpty) {
return type!.trim();
}
return norm;
}
String? getDisplayType() {
List<String> types = normalizedTypes;
if (types.isNotEmpty) return _displayTypeLabel(types.first);
if (type != null && type!.trim().isNotEmpty) return type!.trim();
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 {

@ -513,6 +513,14 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
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
_buildSearchBar(isAr),
@ -852,8 +860,9 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
offerSubtitle = item.offerTitle!.trim();
}
String? typeText = item.getDisplayType();
bool isOnline = item.isOnlineStore;
// type can be a comma-separated combo (e.g. "online,instore") - normalizedTypes already
// 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 usageText = "";
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))],
// 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),
Wrap(
spacing: 5,
runSpacing: 4,
children: <Widget>[
// Type Chip (Online or In Store)
if (typeText != null && typeText.trim().isNotEmpty)
// One Type Chip per distinct type - Online (green) and/or In Store (blue).
for (String offerType in offerTypes)
InfoChipWidget(
text: typeText,
icon: isOnline ? Icons.language_rounded : Icons.storefront_rounded,
backgroundColor: isOnline ? const Color(0xFFEFF6FF) : const Color(0xFFF5F3FF),
textColor: isOnline ? const Color(0xFF2563EB) : const Color(0xFF7C3AED),
text: _getTypeFilterTitle(offerType),
icon: _getTypeFilterIcon(offerType),
backgroundColor: offerType == 'online' ? const Color(0xFFE6F7F0) : const Color(0xFFEFF6FF),
textColor: offerType == 'online' ? const Color(0xFF00A651) : const Color(0xFF2563EB),
),
// 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
if (cityText.trim().isNotEmpty)

@ -605,69 +605,27 @@ class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsS
}
Widget _buildInfoBadgesRow(bool isAr) {
bool isOnline = (_retailer?.type?.toLowerCase() == 'online');
String storeTypeText = isOnline ? LocaleKeys.online.tr() : LocaleKeys.inStore.tr();
IconData storeIcon = isOnline ? Icons.language_rounded : Icons.storefront_rounded;
String cityText = _getDisplayCity(isAr);
String validityText = _retailer?.getDisplayValidity() ?? LocaleKeys.offerValid.tr();
// type can be a comma-separated combo (e.g. "online,instore") - normalizedTypes splits
// that into its distinct parts, so a retailer offering both ways gets a chip each, same
// as the dashboard card.
List<String> offerTypes = _retailer?.normalizedTypes ?? <String>[];
return Wrap(
spacing: 12,
runSpacing: 10,
children: [
InfoChipWidget(
text: storeTypeText,
icon: storeIcon,
backgroundColor: isOnline ? const Color(0xFFEFF6FF) : const Color(0xFFF5F3FF),
textColor: isOnline ? const Color(0xFF2563EB) : const Color(0xFF7C3AED),
),
InfoChipWidget(text: validityText, backgroundColor: const Color(0xFFE6F7F0), textColor: const Color(0xFF00A651)),
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,
),
// One Type Chip per distinct type - Online (green) and/or In Store (blue).
for (String offerType in offerTypes)
InfoChipWidget(
text: offerType == 'online' ? LocaleKeys.online.tr() : (offerType == 'instore' ? LocaleKeys.inStore.tr() : (offerType.isNotEmpty ? (offerType[0].toUpperCase() + offerType.substring(1)) : offerType)),
icon: offerType == 'online' ? Icons.language_rounded : Icons.storefront_rounded,
backgroundColor: offerType == 'online' ? const Color(0xFFE6F7F0) : const Color(0xFFEFF6FF),
textColor: offerType == 'online' ? const Color(0xFF00A651) : const Color(0xFF2563EB),
),
],
);
}
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? content;

Loading…
Cancel
Save