From 858fa7edb76a034eeb9e4e3345356a736981434e Mon Sep 17 00:00:00 2001 From: FaizHashmi Date: Sun, 11 Oct 2026 14:57:40 +0300 Subject: [PATCH] changes --- android/gradle.properties | 6 +- assets/icons/duration.svg | 4 + assets/icons/location.svg | 5 + assets/icons/store.svg | 6 + assets/langs/ar-SA.json | 1 + assets/langs/en-US.json | 1 + lib/api/api_client.dart | 10 +- lib/classes/consts.dart | 27 +- lib/extensions/string_extensions.dart | 261 ++--- lib/generated/codegen_loader.g.dart | 2 + lib/generated/locale_keys.g.dart | 1 + lib/theme/app_theme.dart | 1 + lib/ui/landing/dashboard_screen.dart | 236 ++-- .../dashboard_screen_for_dependet.dart | 587 ++-------- .../wala_plus_retailer_details_screen.dart | 1040 ++++++----------- lib/widgets/app_in_app_webview_screen.dart | 5 + lib/widgets/info_chip_widget.dart | 66 ++ 17 files changed, 833 insertions(+), 1426 deletions(-) create mode 100644 assets/icons/duration.svg create mode 100644 assets/icons/location.svg create mode 100644 assets/icons/store.svg create mode 100644 lib/widgets/info_chip_widget.dart diff --git a/android/gradle.properties b/android/gradle.properties index 8df3fe9..acecd2f 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -4,4 +4,8 @@ android.useAndroidX=true android.enableJetifier=true apmsInstrumentationEnabled=false -#org.gradle.java.home=/Users/amirs/Library/Java/JavaVirtualMachines/jbr-17.0.12/Contents/Home/ \ No newline at end of file +#org.gradle.java.home=/Users/amirs/Library/Java/JavaVirtualMachines/jbr-17.0.12/Contents/Home/ +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/assets/icons/duration.svg b/assets/icons/duration.svg new file mode 100644 index 0000000..ba3daa0 --- /dev/null +++ b/assets/icons/duration.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/location.svg b/assets/icons/location.svg new file mode 100644 index 0000000..355aca8 --- /dev/null +++ b/assets/icons/location.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/store.svg b/assets/icons/store.svg new file mode 100644 index 0000000..5cd8fdb --- /dev/null +++ b/assets/icons/store.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 7878074..ee32173 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -711,6 +711,7 @@ "riyadh": "الرياض", "noInstructionsAvailable": "لا توجد طريقة استخدام محددة.", "contactInformation": "معلومات التواصل:", + "contactInfo": "معلومات التواصل", "humanResources": "الموارد البشرية", "drSulaimanAlHabib": "سليمان الحبيب", "allCategories": "الكل", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 6e194e9..03277bb 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -711,6 +711,7 @@ "riyadh": "Riyadh", "noInstructionsAvailable": "No instructions available.", "contactInformation": "Contact Information:", + "contactInfo": "Contact Information", "humanResources": "Human Resources", "drSulaimanAlHabib": "DR SULAIMAN AL HABIB", "allCategories": "All", diff --git a/lib/api/api_client.dart b/lib/api/api_client.dart index edce588..0ef23ed 100644 --- a/lib/api/api_client.dart +++ b/lib/api/api_client.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; import 'package:flutter/cupertino.dart'; @@ -86,15 +87,14 @@ class ApiClient { _headers.addAll(headers); } if (!kReleaseMode) { - print("Url:$url"); + log("Url: $url"); var bodyJson = json.encode(jsonObject); - print("body:$bodyJson" ); + log("body:$bodyJson"); } var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes, isFormData: isFormData); try { if (!kReleaseMode) { - logger.i("Url: " + url); - logger.i("res: " + response.body); + log("res: " + response.body); } var jsonData = jsonDecode(response.body); @@ -112,7 +112,7 @@ class ApiClient { } else if (jsonData["MessageStatus"] == 2 && jsonData["IsOTPMaxLimitExceed"] == true) { await Utils.performLogout(AppRoutes.navigatorKey.currentContext, null); throw const APIException(APIException.UNAUTHORIZED, error: null); - } else { + } else { APIError? apiError; apiError = APIError(jsonData['ErrorCode'], jsonData['ErrorEndUserMessage'], jsonData['ErrorType'] ?? 0, jsonData['ErrorStatusCode']); throw APIException(APIException.BAD_REQUEST, error: apiError); diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index dfe42c4..e346378 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -1,15 +1,15 @@ import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; class ApiConsts { - // static String baseUrl = "http://10.201.214.101:2027"; // Local server + // static String baseUrl = "http://10.201.214.101:2027"; // Local server // static String baseUrl = "https://erptstapp.srca.org.sa"; // SRCA server - // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver - // static String baseUrl = "http://10.201.204.101:2024"; - // static String baseUrl = "https://webservices.hmg.com"; // PreProd - // static String baseUrl = "https://hmgwebservices.com"; // Live server + static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver + // static String baseUrl = "http://10.201.204.101:2024"; + // static String baseUrl = "https://webservices.hmg.com"; // PreProd + // static String baseUrl = "https://hmgwebservices.com"; // Live server - static String baseUrl = "https://mohemm.hmg.com"; // New Live server + // static String baseUrl = "https://mohemm.hmg.com"; // New Live server static String ssoBaseUrl = "https://sso.hmg.com"; // New Live server // // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver @@ -21,8 +21,8 @@ class ApiConsts { // static String baseUrl = "http://10.20.200.111:1010/"; - // static String baseUrl = "https://webservices.hmg.com"; // PreProd - // static String baseUrl = "https://mohemm.hmg.com"; + // static String baseUrl = "https://webservices.hmg.com"; // PreProd + // static String baseUrl = "https://mohemm.hmg.com"; // static String baseUrl = "https://hmgwebservices.com"; // Live server static String baseUrlServices = baseUrl + "/Services/"; // server @@ -48,7 +48,6 @@ class ApiConsts { static String ssoAuthRedirection = ssoBaseUrl + "/api/auth/connect"; - //Chat static String chatServerBaseUrl = "https://apiderichat.hmg.com/"; static String chatServerBaseApiUrl = chatServerBaseUrl + "api/"; @@ -99,11 +98,15 @@ class ApiConsts { static String notificationCenterForgotPasswordServiceGUID = "5CCCC2C9-EEA0-4207-9BE9-EE1FBBFFE8D0"; static MemberType selectedMemberType = MemberType.employee; + static bool get ISDependetLogin => selectedMemberType == MemberType.dependent; + static set ISDependetLogin(bool value) { selectedMemberType = value ? MemberType.dependent : MemberType.employee; } + static bool get isDependetLogin => ISDependetLogin; + static set isDependetLogin(bool value) => ISDependetLogin = value; } @@ -112,9 +115,11 @@ enum MemberType { dependent(2); final int value; + const MemberType(this.value); bool get isEmployee => this == MemberType.employee; + bool get isDependent => this == MemberType.dependent; static MemberType fromValue(int? value) { @@ -136,7 +141,3 @@ class SharedPrefsConsts { static String mohemmWifiPassword = "mohemmWifiPassword"; static String editItemForSale = "editItemForSale"; } - - - - diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index a3dddff..a6f2476 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -63,63 +63,58 @@ extension EmailValidator on String { Widget get toWidget => Text(this); Widget toText10({Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle}) => Text( - this, - maxLines: maxlines, - style: TextStyle(fontSize: 10, fontStyle: fontStyle ?? FontStyle.normal, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), - ); + this, + maxLines: maxlines, + style: TextStyle(fontSize: 10, fontStyle: fontStyle ?? FontStyle.normal, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), + ); + + Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0, TextOverflow? overflow}) => Text( + this, + textAlign: isCenter ? TextAlign.center : null, + maxLines: (maxLine > 0) ? maxLine : null, + overflow: overflow, + style: TextStyle( + fontSize: 11, + fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), + color: color ?? MyColors.darkTextColor, + letterSpacing: -0.33, + decoration: isUnderLine ? TextDecoration.underline : null, + ), + ); + + Widget toText12({Color? color, bool isUnderLine = false, Color? underlineColor, double underlineGap = 2, bool isBold = false, bool isCenter = false, int? maxLine, TextOverflow? overflow}) { + Widget text = Text( + this, + textAlign: isCenter ? TextAlign.center : null, + maxLines: maxLine == 0 ? null : maxLine, + style: TextStyle(fontSize: 12, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.72, overflow: TextOverflow.visible), + ); - Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => Text( - this, - textAlign: isCenter ? TextAlign.center : null, - maxLines: (maxLine > 0) ? maxLine : null, - style: TextStyle( - fontSize: 11, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), - color: color ?? MyColors.darkTextColor, - letterSpacing: -0.33, - decoration: isUnderLine ? TextDecoration.underline : null, - ), - ); + if (!isUnderLine) return text; - Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int? maxLine, TextOverflow? overflow}) => Text( - this, - textAlign: isCenter ? TextAlign.center : null, - maxLines: maxLine == 0 ? null : maxLine, - style: TextStyle( - fontSize: 12, - fontWeight: isBold ? FontWeight.bold : FontWeight.w600, - color: color ?? MyColors.darkTextColor, - letterSpacing: -0.72, - decoration: isUnderLine ? TextDecoration.underline : null, - overflow: TextOverflow.visible - ), - ); - - Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int? maxLine,TextOverflow? overflow}) => AutoSizeText( - this, - textAlign: isCenter ? TextAlign.center : null, - maxLines: maxLine == 0 ? null : maxLine, - minFontSize: 8, - style: TextStyle( - fontSize: 12, - fontWeight: isBold ? FontWeight.bold : FontWeight.w600, - color: color ?? MyColors.darkTextColor, - overflow: overflow, - letterSpacing: -0.72, - decoration: isUnderLine ? TextDecoration.underline : null, - ), - ); + return Container( + padding: EdgeInsets.only(bottom: underlineGap), + decoration: BoxDecoration(border: Border(bottom: BorderSide(color: underlineColor ?? color ?? MyColors.darkTextColor, width: 1))), + child: text, + ); + } - Widget toTextAuto({ - Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - int maxLine = 0, - double fontSize = 12, - double letterSpacing = -0.72, - double height = 1, - }) => + Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int? maxLine, TextOverflow? overflow}) => AutoSizeText( + this, + textAlign: isCenter ? TextAlign.center : null, + maxLines: maxLine == 0 ? null : maxLine, + minFontSize: 8, + style: TextStyle( + fontSize: 12, + fontWeight: isBold ? FontWeight.bold : FontWeight.w600, + color: color ?? MyColors.darkTextColor, + overflow: overflow, + letterSpacing: -0.72, + decoration: isUnderLine ? TextDecoration.underline : null, + ), + ); + + Widget toTextAuto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0, double fontSize = 12, double letterSpacing = -0.72, double height = 1}) => AutoSizeText( this, textAlign: isCenter ? TextAlign.center : null, @@ -135,101 +130,102 @@ extension EmailValidator on String { ); Widget toText13({Color? color, bool isUnderLine = false, bool isBold = false, int? maxlines, TextOverflow? overflow}) => Text( - this, - maxLines: maxlines, - overflow: overflow, - style: TextStyle(fontSize: 13, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.52, decoration: isUnderLine ? TextDecoration.underline : null), - ); - - Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? weight, int? maxlines, TextAlign? textAlign, bool isCenter = false}) => Text( - this, - textAlign: isCenter ? TextAlign.center : textAlign, - maxLines: maxlines, - style: TextStyle( - color: color ?? MyColors.darkTextColor, - fontSize: 14, - letterSpacing: -0.48, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), - decoration: isUnderLine ? TextDecoration.underline : null), - ); + this, + maxLines: maxlines, + overflow: overflow, + style: TextStyle( + fontSize: 13, + fontWeight: isBold ? FontWeight.bold : FontWeight.w600, + color: color ?? MyColors.darkTextColor, + letterSpacing: -0.52, + decoration: isUnderLine ? TextDecoration.underline : null, + ), + ); + + Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? weight, int? maxlines, TextAlign? textAlign, bool isCenter = false, TextOverflow? overflow}) => Text( + this, + textAlign: isCenter ? TextAlign.center : textAlign, + maxLines: maxlines, + style: TextStyle( + overflow: overflow, + color: color ?? MyColors.darkTextColor, + fontSize: 14, + letterSpacing: -0.48, + fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), + decoration: isUnderLine ? TextDecoration.underline : null, + ), + ); Widget toText16({Color? color, bool isUnderLine = false, bool isBold = false, int? maxlines, double? height, bool isCentered = false}) => Text( - this, - maxLines: maxlines, - textAlign: isCentered ? TextAlign.center : null, - style: TextStyle( - color: color ?? MyColors.darkTextColor, - fontSize: 16, - letterSpacing: -0.64, - height: height, - fontWeight: isBold ? FontWeight.bold : FontWeight.w600, - decoration: isUnderLine ? TextDecoration.underline : null, - ), - ); + this, + maxLines: maxlines, + textAlign: isCentered ? TextAlign.center : null, + style: TextStyle( + color: color ?? MyColors.darkTextColor, + fontSize: 16, + letterSpacing: -0.64, + height: height, + fontWeight: isBold ? FontWeight.bold : FontWeight.w600, + decoration: isUnderLine ? TextDecoration.underline : null, + ), + ); Widget toText17({Color? color, bool isBold = false, bool isCentered = false}) => Text( - this, - textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), - ); + this, + textAlign: isCentered ? TextAlign.center : null, + style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); Widget toText18({Color? color, bool isBold = false, bool isCentered = false}) => Text( - this, - textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(fontSize: 18, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.08), - ); + this, + textAlign: isCentered ? TextAlign.center : null, + style: TextStyle(fontSize: 18, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.08), + ); - Widget toText19({Color? color, bool isBold = false}) => Text( - this, - style: TextStyle(fontSize: 19, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.14), - ); + Widget toText19({Color? color, bool isBold = false}) => + Text(this, style: TextStyle(fontSize: 19, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.14)); Widget toText20({Color? color, bool isBold = false, bool isCentered = false}) => Text( - this, - textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(fontSize: 20, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), - ); + this, + textAlign: isCentered ? TextAlign.center : null, + style: TextStyle(fontSize: 20, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), + ); - Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( - this, - maxLines: maxlines, - style: TextStyle(color: color ?? MyColors.grey3AColor, fontSize: 21, letterSpacing: -0.84, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600)), - ); + Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => + Text(this, maxLines: maxlines, style: TextStyle(color: color ?? MyColors.grey3AColor, fontSize: 21, letterSpacing: -0.84, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600))); Widget toText22({Color? color, bool isBold = false, bool isCentered = false}) => Text( - this, - textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(height: 1, color: color ?? MyColors.darkTextColor, fontSize: 22, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), - ); + this, + textAlign: isCentered ? TextAlign.center : null, + style: TextStyle(height: 1, color: color ?? MyColors.darkTextColor, fontSize: 22, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); Widget toText23({Color? color, bool isBold = false, bool isCentered = false}) => Text( - this, - textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 23, letterSpacing: -0.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), - ); + this, + textAlign: isCentered ? TextAlign.center : null, + style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 23, letterSpacing: -0.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); Widget toText24({Color? color, bool isBold = false, bool isCentered = false}) => Text( - this, - textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 24, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), - ); + this, + textAlign: isCentered ? TextAlign.center : null, + style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 24, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); Widget toText30({Color? color, bool isBold = false, bool isCentered = false}) => Text( - this, - textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(height: 20 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.2, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), - ); + this, + textAlign: isCentered ? TextAlign.center : null, + style: TextStyle(height: 20 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.2, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); Widget toText32({Color? color, bool isBold = false, bool isCentered = false}) => Text( - this, - textAlign: isCentered ? TextAlign.center : null, - style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), - ); + this, + textAlign: isCentered ? TextAlign.center : null, + style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); - Widget toText44({Color? color, bool isBold = false}) => Text( - this, - style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 44, letterSpacing: -2.64, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), - ); + Widget toText44({Color? color, bool isBold = false}) => + Text(this, style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 44, letterSpacing: -2.64, fontWeight: isBold ? FontWeight.bold : FontWeight.w600)); Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { String upper = ""; @@ -253,14 +249,7 @@ extension EmailValidator on String { lower = lowerHeading; } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (upper.isNotEmpty) upper.toText12(), - lower.toText24(isBold: true), - ], - ); + return Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [if (upper.isNotEmpty) upper.toText12(), lower.toText24(isBold: true)]); } bool isValidEmail() { diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 242fde5..6fdfdfe 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -727,6 +727,7 @@ class CodegenLoader extends AssetLoader{ "riyadh": "الرياض", "noInstructionsAvailable": "لا توجد طريقة استخدام محددة.", "contactInformation": "معلومات التواصل:", + "contactInfo": "معلومات التواصل", "humanResources": "الموارد البشرية", "drSulaimanAlHabib": "سليمان الحبيب", "allCategories": "الكل", @@ -1509,6 +1510,7 @@ static const Map _en_US = { "riyadh": "Riyadh", "noInstructionsAvailable": "No instructions available.", "contactInformation": "Contact Information:", + "contactInfo": "Contact Information", "humanResources": "Human Resources", "drSulaimanAlHabib": "DR SULAIMAN AL HABIB", "allCategories": "All", diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 73e9b86..3fb1e5b 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -699,6 +699,7 @@ abstract class LocaleKeys { static const riyadh = 'riyadh'; static const noInstructionsAvailable = 'noInstructionsAvailable'; static const contactInformation = 'contactInformation'; + static const contactInfo = 'contactInfo'; static const humanResources = 'humanResources'; static const drSulaimanAlHabib = 'drSulaimanAlHabib'; static const allCategories = 'allCategories'; diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index f228600..d2d9fab 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 65165aa..94a49da 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -290,9 +290,7 @@ class _DashboardScreenState extends State with WidgetsBindingOb }); }, ), - Image - .asset("assets/images/logos/main_mohemm_logo.png", width: 134, height: 28) - .expanded, + Image.asset("assets/images/logos/main_mohemm_logo.png", width: 134, height: 28).expanded, SvgPicture.asset("assets/images/announcements.svg", matchTextDirection: true).onPress(() async { await Navigator.pushNamed(context, AppRoutes.announcements); }), @@ -324,103 +322,103 @@ class _DashboardScreenState extends State with WidgetsBindingOb child: Consumer( builder: (BuildContext context, DashboardProviderModel model, Widget? child) { return (model.isAttendanceTrackingLoading - ? GetAttendanceTrackingShimmer() - : Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - gradient: const LinearGradient( - transform: GradientRotation(.46), - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor], - ), - ), - child: Stack( - alignment: Alignment.center, - children: [ - if (model.isTimeRemainingInSeconds == 0) SvgPicture.asset("assets/images/thumb.svg"), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.markAttendance.tr().toText14(color: Colors.white, isBold: true), - if (model.isTimeRemainingInSeconds == 0) DateTime.now().toString().split(" ")[0].toText12(color: Colors.white), - if (model.isTimeRemainingInSeconds != 0) - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + ? GetAttendanceTrackingShimmer() + : Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: const LinearGradient( + transform: GradientRotation(.46), + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor], + ), + ), + child: Stack( + alignment: Alignment.center, + children: [ + if (model.isTimeRemainingInSeconds == 0) SvgPicture.asset("assets/images/thumb.svg"), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.markAttendance.tr().toText14(color: Colors.white, isBold: true), + if (model.isTimeRemainingInSeconds == 0) DateTime.now().toString().split(" ")[0].toText12(color: Colors.white), + if (model.isTimeRemainingInSeconds != 0) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 9.height, + Directionality( + textDirection: ui.TextDirection.ltr, + child: CountdownTimer( + endTime: model.endTime, + onEnd: null, + endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), + textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), + ), + ), + LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white), + 9.height, + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(20)), + child: LinearProgressIndicator( + value: model.progress, + minHeight: 8, + valueColor: const AlwaysStoppedAnimation(Colors.white), + backgroundColor: const Color(0xff196D73), + ), + ), + ], + ), + ], + ).paddingOnly(top: 12, right: 15, left: 12), + ), + Row( children: [ - 9.height, - Directionality( - textDirection: ui.TextDirection.ltr, - child: CountdownTimer( - endTime: model.endTime, - onEnd: null, - endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), - textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), - ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.checkIn.tr().toText12(color: Colors.white), + (model.attendanceTracking!.pSwipeIn == null ? "--:--" : model.attendanceTracking!.pSwipeIn).toString().toText14( + color: Colors.white, + isBold: true, + ), + 4.height, + ], + ).paddingOnly(left: 12, right: 12), ), - LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white), - 9.height, - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(20)), - child: LinearProgressIndicator( - value: model.progress, - minHeight: 8, - valueColor: const AlwaysStoppedAnimation(Colors.white), - backgroundColor: const Color(0xff196D73), + Container( + margin: EdgeInsets.only(top: AppState().isArabic(context) ? 6 : 0), + width: 45, + height: 45, + padding: const EdgeInsets.only(left: 10, right: 10), + decoration: BoxDecoration( + color: const Color(0xff259EA4), + borderRadius: BorderRadius.only( + bottomRight: AppState().isArabic(context) ? const Radius.circular(0) : const Radius.circular(15), + bottomLeft: AppState().isArabic(context) ? const Radius.circular(15) : const Radius.circular(0), + ), ), - ), + child: SvgPicture.asset(model.isTimeRemainingInSeconds == 0 ? "assets/images/biometrics.svg" : "assets/images/biometrics.svg"), + ).onPress(() { + showMyBottomSheet(context, callBackFunc: () {}, child: MarkAttendanceWidget(model, isFromDashboard: true)); + }), ], ), - ], - ).paddingOnly(top: 12, right: 15, left: 12), - ), - Row( - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.checkIn.tr().toText12(color: Colors.white), - (model.attendanceTracking!.pSwipeIn == null ? "--:--" : model.attendanceTracking!.pSwipeIn).toString().toText14( - color: Colors.white, - isBold: true, - ), - 4.height, - ], - ).paddingOnly(left: 12, right: 12), + ], ), - Container( - margin: EdgeInsets.only(top: AppState().isArabic(context) ? 6 : 0), - width: 45, - height: 45, - padding: const EdgeInsets.only(left: 10, right: 10), - decoration: BoxDecoration( - color: const Color(0xff259EA4), - borderRadius: BorderRadius.only( - bottomRight: AppState().isArabic(context) ? const Radius.circular(0) : const Radius.circular(15), - bottomLeft: AppState().isArabic(context) ? const Radius.circular(15) : const Radius.circular(0), - ), - ), - child: SvgPicture.asset(model.isTimeRemainingInSeconds == 0 ? "assets/images/biometrics.svg" : "assets/images/biometrics.svg"), - ).onPress(() { - showMyBottomSheet(context, callBackFunc: () {}, child: MarkAttendanceWidget(model, isFromDashboard: true)); - }), ], ), - ], - ), - ], - ), - ).onPress(() { - Navigator.pushNamed(context, AppRoutes.todayAttendance); - })) + ).onPress(() { + Navigator.pushNamed(context, AppRoutes.todayAttendance); + })) .animatedSwither(); }, ), @@ -455,7 +453,7 @@ class _DashboardScreenState extends State with WidgetsBindingOb }, ).paddingOnly(bottom: 18, left: 24, right: 24), // eventActivityWidget(context), - if (isDisplayMazaya) ...[ + if (isDisplayMazaya) ...[ InkWell( borderRadius: BorderRadius.circular(20), onTap: () { @@ -466,9 +464,7 @@ class _DashboardScreenState extends State with WidgetsBindingOb child: AspectRatio( aspectRatio: 386 / 121.37, child: SvgPicture.asset( - AppState().isArabic(context) - ? "assets/images/mazaya_banner_ar.svg" - : "assets/images/mazaya_banner_en.svg", + AppState().isArabic(context) ? "assets/images/mazaya_banner_ar.svg" : "assets/images/mazaya_banner_en.svg", width: double.infinity, fit: BoxFit.fill, ), @@ -570,7 +566,6 @@ class _DashboardScreenState extends State with WidgetsBindingOb ], ), */ - Container( width: double.infinity, padding: const EdgeInsets.only(top: 31), @@ -583,9 +578,7 @@ class _DashboardScreenState extends State with WidgetsBindingOb mainAxisSize: MainAxisSize.min, children: [ ServicesWidget(), - context - .watch() - .isLoading ? const MarathonBannerShimmer().paddingAll(20) : const MarathonBanner().paddingOnly(left: 21, right: 21, bottom: 8, top: 8), + context.watch().isLoading ? const MarathonBannerShimmer().paddingAll(20) : const MarathonBanner().paddingOnly(left: 21, right: 21, bottom: 8, top: 8), // context.watch().isTutorialLoading // ? const MarathonBannerShimmer().paddingAll(20) // : Container( @@ -689,28 +682,28 @@ class _DashboardScreenState extends State with WidgetsBindingOb SvgPicture.asset( "assets/icons/chat/chat.svg", color: - !checkIfPrivilegedForChat() - ? MyColors.lightGreyE3Color - : currentIndex == 4 - ? MyColors.grey3AColor - : cProvider.disbaleChatForThisUser - ? MyColors.lightGreyE3Color - : MyColors.grey98Color, + !checkIfPrivilegedForChat() + ? MyColors.lightGreyE3Color + : currentIndex == 4 + ? MyColors.grey3AColor + : cProvider.disbaleChatForThisUser + ? MyColors.lightGreyE3Color + : MyColors.grey98Color, ).paddingAll(4), Consumer( builder: (BuildContext cxt, ChatProviderModel data, Widget? child) { return !checkIfPrivilegedForChat() ? const SizedBox() : Positioned( - right: 0, - top: 0, - child: Container( - padding: const EdgeInsets.only(left: 4, right: 4), - alignment: Alignment.center, - decoration: BoxDecoration(color: cProvider.disbaleChatForThisUser ? MyColors.pinkDarkColor : MyColors.redColor, borderRadius: BorderRadius.circular(17)), - child: data.chatUConvCounter.toString().toText10(color: Colors.white), - ), - ); + right: 0, + top: 0, + child: Container( + padding: const EdgeInsets.only(left: 4, right: 4), + alignment: Alignment.center, + decoration: BoxDecoration(color: cProvider.disbaleChatForThisUser ? MyColors.pinkDarkColor : MyColors.redColor, borderRadius: BorderRadius.circular(17)), + child: data.chatUConvCounter.toString().toText10(color: Colors.white), + ), + ); }, ), ], @@ -746,16 +739,9 @@ class _DashboardScreenState extends State with WidgetsBindingOb } Widget eventActivityWidget(BuildContext context) { - return (context - .watch() - .isEventLoadingLoading) + return (context.watch().isEventLoadingLoading) ? const MarathonBannerShimmer().paddingOnly(left: 21, right: 21, bottom: 21, top: 0) - : (context - .watch() - .eventActivity != null && context - .watch() - .eventActivity! - .isActive == true) + : (context.watch().eventActivity != null && context.watch().eventActivity!.isActive == true) ? const EventActivityBanner().paddingOnly(left: 21, right: 21, bottom: 21, top: 0) : const SizedBox(); } diff --git a/lib/ui/landing/dashboard_screen_for_dependet.dart b/lib/ui/landing/dashboard_screen_for_dependet.dart index 49fb552..a4ec744 100644 --- a/lib/ui/landing/dashboard_screen_for_dependet.dart +++ b/lib/ui/landing/dashboard_screen_for_dependet.dart @@ -14,6 +14,7 @@ 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/offers_and_discounts/wala_plus_category_model.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart'; +import 'package:mohem_flutter_app/widgets/info_chip_widget.dart'; import 'package:shimmer/shimmer.dart'; class DashboardScreenForDependet extends StatefulWidget { @@ -105,17 +106,14 @@ class _DashboardScreenForDependetState extends State String dob = AppState().dependentDob ?? ""; String name = AppState().dependentName ?? ""; String mobile = AppState().postParamsObject?.mobileNumber ?? ""; - String tokenID = (AppState().postParamsObject?.tokenID != null && AppState().postParamsObject!.tokenID!.isNotEmpty) - ? AppState().postParamsObject!.tokenID! - : (AppState().authenticationTokenID ?? ""); + String tokenID = + (AppState().postParamsObject?.tokenID != null && AppState().postParamsObject!.tokenID!.isNotEmpty) ? AppState().postParamsObject!.tokenID! : (AppState().authenticationTokenID ?? ""); if (tokenID.isEmpty) { tokenID = await Utils.getStringFromPrefs("P_DEP_TOKEN_ID"); } int sessionId = AppState().postParamsObject?.pSessionId ?? 0; String email = AppState().postParamsObject?.pEmailAddress ?? ""; - String deviceToken = AppState().getIsHuawei - ? AppState().getHuaweiPushToken - : (AppState().getDeviceToken ?? ""); + String deviceToken = AppState().getIsHuawei ? AppState().getHuaweiPushToken : (AppState().getDeviceToken ?? ""); await LoginApiClient().insertMobileLoginInfoDependent( loginType: loginType, @@ -178,10 +176,7 @@ class _DashboardScreenForDependetState extends State List fullList = []; bool hasAll = list.any((c) => (c.id == "all" || c.name?.toLowerCase() == "all" || c.name == "الجميع" || c.name == "الكل")); if (!hasAll) { - fullList.add(WalaPlusCategoryModel( - id: "all", - name: LocaleKeys.allCategories.tr(), - )); + fullList.add(WalaPlusCategoryModel(id: "all", name: LocaleKeys.allCategories.tr())); } fullList.addAll(list); @@ -201,10 +196,7 @@ class _DashboardScreenForDependetState extends State List fullList = []; bool hasAll = retryList.any((c) => (c.id == "all" || c.name?.toLowerCase() == "all" || c.name == "الجميع" || c.name == "الكل")); if (!hasAll) { - fullList.add(WalaPlusCategoryModel( - id: "all", - name: LocaleKeys.allCategories.tr(), - )); + fullList.add(WalaPlusCategoryModel(id: "all", name: LocaleKeys.allCategories.tr())); } fullList.addAll(retryList); setState(() { @@ -221,9 +213,7 @@ class _DashboardScreenForDependetState extends State if (mounted && categoriesList.isEmpty) { setState(() { - categoriesList = [ - WalaPlusCategoryModel(id: "all", name: LocaleKeys.allCategories.tr()), - ]; + categoriesList = [WalaPlusCategoryModel(id: "all", name: LocaleKeys.allCategories.tr())]; if (currentCategoryId.isEmpty) { currentCategoryId = "all"; } @@ -322,10 +312,7 @@ class _DashboardScreenForDependetState extends State } Future _onRefresh() async { - await Future.wait([ - fetchCategories(), - fetchOffers(categoryId: currentCategoryId.isEmpty ? "all" : currentCategoryId), - ]); + await Future.wait([fetchCategories(), fetchOffers(categoryId: currentCategoryId.isEmpty ? "all" : currentCategoryId)]); } void _runFilter([String? query]) { @@ -335,40 +322,36 @@ class _DashboardScreenForDependetState extends State List temp = retailersList; if (selectedTypeFilter != "all" && selectedTypeFilter.isNotEmpty) { - temp = temp.where((WalaPlusRetailerModel item) { - String? norm = item.normalizedType; - if (selectedTypeFilter == "online") { - return norm == "online"; - } else if (selectedTypeFilter == "instore") { - return norm == "instore"; - } else { - return norm == selectedTypeFilter || (item.type?.trim().toLowerCase() == selectedTypeFilter); - } - }).toList(); + temp = + temp.where((WalaPlusRetailerModel item) { + String? norm = item.normalizedType; + if (selectedTypeFilter == "online") { + return norm == "online"; + } else if (selectedTypeFilter == "instore") { + return norm == "instore"; + } else { + return norm == selectedTypeFilter || (item.type?.trim().toLowerCase() == selectedTypeFilter); + } + }).toList(); } if (searchQuery.trim().isNotEmpty) { String q = searchQuery.trim().toLowerCase(); - temp = temp.where((WalaPlusRetailerModel item) { - String name = (item.name ?? "").toLowerCase(); - String title = (item.offerTitle ?? "").toLowerCase(); - String desc = (item.description ?? "").toLowerCase(); - String city = item.displayCity.toLowerCase(); - return name.contains(q) || title.contains(q) || desc.contains(q) || city.contains(q); - }).toList(); + temp = + temp.where((WalaPlusRetailerModel item) { + String name = (item.name ?? "").toLowerCase(); + String title = (item.offerTitle ?? "").toLowerCase(); + String desc = (item.description ?? "").toLowerCase(); + String city = item.displayCity.toLowerCase(); + return name.contains(q) || title.contains(q) || desc.contains(q) || city.contains(q); + }).toList(); } _filteredRetailersList = temp; setState(() {}); } - - void _openRetailer(WalaPlusRetailerModel item) { - Navigator.pushNamed( - context, - AppRoutes.walaPlusRetailerDetails, - arguments: item, - ); + Navigator.pushNamed(context, AppRoutes.walaPlusRetailerDetails, arguments: item); } void _performLogout() { @@ -414,19 +397,11 @@ class _DashboardScreenForDependetState extends State }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), - child: Icon( - isAr ? Icons.arrow_forward_ios_rounded : Icons.arrow_back_ios_new_rounded, - size: 20, - color: const Color(0xFF1E293B), - ), + child: Icon(isAr ? Icons.arrow_forward_ios_rounded : Icons.arrow_back_ios_new_rounded, size: 20, color: const Color(0xFF1E293B)), ), ), const SizedBox(width: 8), - SvgPicture.asset( - "assets/icons/mazaya_brand.svg", - height: 48, - fit: BoxFit.contain, - ), + SvgPicture.asset("assets/icons/mazaya_brand.svg", height: 48, fit: BoxFit.contain), ], ), Row( @@ -439,20 +414,13 @@ class _DashboardScreenForDependetState extends State width: 24, height: 24, color: const Color(0xFF1E293B), - errorBuilder: (context, error, stackTrace) => SvgPicture.asset( - 'assets/icons/ic_dependents_top.svg', - width: 24, - height: 24, - colorFilter: const ColorFilter.mode(Color(0xFF1E293B), BlendMode.srcIn), - ), + errorBuilder: + (context, error, stackTrace) => + SvgPicture.asset('assets/icons/ic_dependents_top.svg', width: 24, height: 24, colorFilter: const ColorFilter.mode(Color(0xFF1E293B), BlendMode.srcIn)), ), tooltip: LocaleKeys.addDependents.tr(), onPressed: () { - Navigator.pushNamed( - context, - AppRoutes.familyMembers, - arguments: {'fromMazaya': true}, - ); + Navigator.pushNamed(context, AppRoutes.familyMembers, arguments: {'fromMazaya': true}); }, ), ], @@ -490,10 +458,7 @@ class _DashboardScreenForDependetState extends State _buildBrowseCategoriesHeader(isAr), // Categories Section - if (categoriesList.isNotEmpty) - _buildCategoriesList(isAr) - else - _buildCategoriesShimmer(), + if (categoriesList.isNotEmpty) _buildCategoriesList(isAr) else _buildCategoriesShimmer(), // Type Filter Chips (All, In Store, Online) _buildTypeFilterChips(isAr), @@ -507,12 +472,7 @@ class _DashboardScreenForDependetState extends State GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - childAspectRatio: 0.72, - crossAxisSpacing: 12, - mainAxisSpacing: 16, - ), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 0.85, crossAxisSpacing: 12, mainAxisSpacing: 16), itemCount: _filteredRetailersList.length, itemBuilder: (context, index) { return _buildOfferCard(_filteredRetailersList[index], isAr); @@ -520,26 +480,10 @@ class _DashboardScreenForDependetState extends State ), if (isLoadingMore) ...[ 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, - ), - ), - ), + 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))), ], ] else - Padding( - padding: const EdgeInsets.only(top: 40), - child: Center( - child: LocaleKeys.noOffersAvailable.tr().toText14( - color: const Color(0xFF64748B), - ), - ), - ), + Padding(padding: const EdgeInsets.only(top: 40), child: Center(child: LocaleKeys.noOffersAvailable.tr().toText14(color: const Color(0xFF64748B)))), ], ), ), @@ -557,23 +501,13 @@ class _DashboardScreenForDependetState extends State color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFE8E8E8), width: 1.0), - boxShadow: [ - BoxShadow( - color: const Color(0xFF000000).withOpacity(0.0314), - offset: const Offset(0, 2), - blurRadius: 5, - spreadRadius: 0, - ), - ], + 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: [ - LocaleKeys.search.tr().toText12( - isBold: true, - color: const Color(0xFF2A333C), - ), + LocaleKeys.search.tr().toText12(isBold: true, color: const Color(0xFF2A333C)), const SizedBox(height: 2), TextField( controller: _searchController, @@ -582,19 +516,10 @@ class _DashboardScreenForDependetState extends State isDense: true, contentPadding: EdgeInsets.zero, hintText: LocaleKeys.searchItems.tr(), - hintStyle: const TextStyle( - color: Color(0xFFACAFB1), - fontSize: 14, - fontWeight: FontWeight.w400, - height: 1.2, - ), + hintStyle: const TextStyle(color: Color(0xFFACAFB1), fontSize: 14, fontWeight: FontWeight.w400, height: 1.2), border: InputBorder.none, ), - style: const TextStyle( - color: Color(0xFF2A333C), - fontSize: 14, - fontWeight: FontWeight.w400, - ), + style: const TextStyle(color: Color(0xFF2A333C), fontSize: 14, fontWeight: FontWeight.w400), ), ], ), @@ -602,13 +527,7 @@ class _DashboardScreenForDependetState extends State } Widget _buildBrowseCategoriesHeader(bool isAr) { - return Padding( - padding: const EdgeInsets.only(top: 22, bottom: 14), - child: LocaleKeys.browseCategories.tr().toText18( - isBold: true, - color: const Color(0xFF1E293B), - ), - ); + return Padding(padding: const EdgeInsets.only(top: 22, bottom: 14), child: LocaleKeys.browseCategories.tr().toText18(isBold: true, color: const Color(0xFF1E293B))); } Widget _buildCategoriesList(bool isAr) { @@ -623,8 +542,7 @@ class _DashboardScreenForDependetState extends State separatorBuilder: (_, __) => const SizedBox(width: 14), itemBuilder: (BuildContext context, int index) { WalaPlusCategoryModel item = categoriesList[index]; - bool isSelected = (item.id == "all" && (currentCategoryId == "all" || currentCategoryId.isEmpty)) || - (currentCategoryId.isNotEmpty && currentCategoryId == item.id); + bool isSelected = (item.id == "all" && (currentCategoryId == "all" || currentCategoryId.isEmpty)) || (currentCategoryId.isNotEmpty && currentCategoryId == item.id); String title = (item.id == "all") ? LocaleKeys.allCategories.tr() : (item.name ?? ""); return InkWell( @@ -691,22 +609,13 @@ class _DashboardScreenForDependetState extends State child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6), - color: isSelected ? MyColors.darkIconColor : MyColors.lightGreyEAColor, - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(6), color: isSelected ? MyColors.darkIconColor : MyColors.lightGreyEAColor), child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( - icon, - size: 14, - color: isSelected ? MyColors.white : MyColors.black, - ), + Icon(icon, size: 14, color: isSelected ? MyColors.white : MyColors.black), const SizedBox(width: 5), - title.toText12( - color: isSelected ? MyColors.white : MyColors.black, - ), + title.toText12(color: isSelected ? MyColors.white : MyColors.black), ], ), ), @@ -723,26 +632,14 @@ class _DashboardScreenForDependetState extends State const double borderWidthVal = 2.8; const Color activeBorderColor = Color(0xFF00A86B); - bool isAll = (item.id == "all" || - item.id == "0" || - (item.name ?? "").toLowerCase() == "all" || - item.name == "الجميع" || - item.name == "الكل"); + bool isAll = (item.id == "all" || item.id == "0" || (item.name ?? "").toLowerCase() == "all" || item.name == "الجميع" || item.name == "الكل"); Widget imageContent; if (isAll) { - imageContent = Image.asset( - 'assets/images/cat_all.png', - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => _getFallbackCategoryAsset(item), - ); + imageContent = Image.asset('assets/images/cat_all.png', width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _getFallbackCategoryAsset(item)); } else { - String categoryImageUrl = item.categoryImageUrl.isNotEmpty - ? item.categoryImageUrl - : (item.image?.trim() ?? ""); + String categoryImageUrl = item.categoryImageUrl.isNotEmpty ? item.categoryImageUrl : (item.image?.trim() ?? ""); if (categoryImageUrl.isNotEmpty) { if (categoryImageUrl.toLowerCase().endsWith('.svg') || item.isSvg) { @@ -751,22 +648,10 @@ class _DashboardScreenForDependetState extends State width: double.infinity, height: double.infinity, fit: BoxFit.cover, - placeholderBuilder: (_) => const Center( - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF00A86B)), - ), - ), + placeholderBuilder: (_) => const Center(child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF00A86B)))), ); } else { - imageContent = Image.network( - categoryImageUrl, - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => _getFallbackCategoryAsset(item), - ); + imageContent = Image.network(categoryImageUrl, width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _getFallbackCategoryAsset(item)); } } else { imageContent = _getFallbackCategoryAsset(item); @@ -779,19 +664,9 @@ class _DashboardScreenForDependetState extends State decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(borderRadiusVal), - border: isSelected - ? Border.all( - color: activeBorderColor, - width: borderWidthVal, - ) - : null, - ), - child: ClipRRect( - borderRadius: BorderRadius.circular( - isSelected ? (borderRadiusVal - borderWidthVal) : borderRadiusVal, - ), - child: imageContent, + border: isSelected ? Border.all(color: activeBorderColor, width: borderWidthVal) : null, ), + child: ClipRRect(borderRadius: BorderRadius.circular(isSelected ? (borderRadiusVal - borderWidthVal) : borderRadiusVal), child: imageContent), ); } @@ -799,59 +674,20 @@ class _DashboardScreenForDependetState extends State String name = (item.name ?? "").toLowerCase(); if (item.id == "all" || item.id == "0" || name == "all" || name == "الكل" || name == "الجميع") { - return Image.asset( - 'assets/images/cat_all.png', - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item), - ); + return Image.asset('assets/images/cat_all.png', width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item)); } else if (name.contains("shop") || name.contains("retail") || name.contains("تسوق") || name.contains("تجزئة")) { - return Image.asset( - 'assets/images/cat_retail.png', - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item), - ); - } else if (name.contains("food") || - name.contains("restaurant") || - name.contains("مطاعم") || - name.contains("أغذية") || - name.contains("cafe") || - name.contains("مقهى")) { - return Image.asset( - 'assets/images/cat_food.png', - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item), - ); - } else if (name.contains("fitness") || - name.contains("wellness") || - name.contains("sport") || - name.contains("gym") || - name.contains("رياضة") || - name.contains("لياقة") || - name.contains("صحة")) { - return Image.asset( - 'assets/images/cat_fitness.png', - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item), - ); + return Image.asset('assets/images/cat_retail.png', width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item)); + } else if (name.contains("food") || name.contains("restaurant") || name.contains("مطاعم") || name.contains("أغذية") || name.contains("cafe") || name.contains("مقهى")) { + return Image.asset('assets/images/cat_food.png', width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item)); + } else if (name.contains("fitness") || name.contains("wellness") || name.contains("sport") || name.contains("gym") || name.contains("رياضة") || name.contains("لياقة") || name.contains("صحة")) { + return Image.asset('assets/images/cat_fitness.png', width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item)); } return _buildFallbackCategoryIconWidget(item); } Widget _buildFallbackCategoryIconWidget(WalaPlusCategoryModel item) { - return Container( - color: Colors.transparent, - alignment: Alignment.center, - child: _getFallbackCategoryIcon(item, const Color(0xFF168A89)), - ); + return Container(color: Colors.transparent, alignment: Alignment.center, child: _getFallbackCategoryIcon(item, const Color(0xFF168A89))); } Widget _getFallbackCategoryIcon(WalaPlusCategoryModel item, Color iconColor) { @@ -885,11 +721,7 @@ class _DashboardScreenForDependetState extends State iconData = Icons.home_rounded; } - return Icon( - iconData, - color: color, - size: 26, - ); + return Icon(iconData, color: color, size: 26); } String _getDiscountBadge(WalaPlusRetailerModel item) { @@ -911,23 +743,12 @@ class _DashboardScreenForDependetState extends State } Widget _buildMerchantMIcon() { - return Container( - color: const Color(0xFFED1C24), - alignment: Alignment.center, - child: "M".toText10( - isBold: true, - color: Colors.white, - ), - ); + return Container(color: const Color(0xFFED1C24), alignment: Alignment.center, child: "M".toText10(isBold: true, color: Colors.white)); } Widget _buildOfferCard(WalaPlusRetailerModel item, bool isAr) { - String? bannerUrl = (item.imageThumb != null && item.imageThumb!.isNotEmpty) - ? item.imageThumb - : item.image; - String? logoUrl = (item.logoThumb != null && item.logoThumb!.isNotEmpty) - ? item.logoThumb - : item.logo; + String? bannerUrl = (item.imageThumb != null && item.imageThumb!.isNotEmpty) ? item.imageThumb : item.image; + String? logoUrl = (item.logoThumb != null && item.logoThumb!.isNotEmpty) ? item.logoThumb : item.logo; String discountText = _getDiscountBadge(item); @@ -947,9 +768,7 @@ class _DashboardScreenForDependetState extends State String validityText = item.getDisplayValidity(); String usageText = ""; if (item.displayUsage.isNotEmpty) { - usageText = item.displayUsage - .replaceAll('usage:', LocaleKeys.usageLabel.tr()) - .replaceAll('Usage :', LocaleKeys.usageLabel.tr()); + usageText = item.displayUsage.replaceAll('usage:', LocaleKeys.usageLabel.tr()).replaceAll('Usage :', LocaleKeys.usageLabel.tr()); } String cityText = item.getDisplayCity(isAr: isAr); if (cityText.trim().isEmpty) { @@ -963,14 +782,7 @@ class _DashboardScreenForDependetState extends State decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: const Color(0xFF0D1720).withOpacity(0.0392), - offset: const Offset(0, 5), - blurRadius: 18, - spreadRadius: 0, - ), - ], + boxShadow: [BoxShadow(color: const Color(0xFF0D1720).withOpacity(0.0392), offset: const Offset(0, 5), blurRadius: 18, spreadRadius: 0)], ), padding: const EdgeInsets.all(10), child: Column( @@ -980,7 +792,7 @@ class _DashboardScreenForDependetState extends State ClipRRect( borderRadius: BorderRadius.circular(14), child: SizedBox( - height: 105, + height: 120, width: double.infinity, child: Stack( fit: StackFit.expand, @@ -988,17 +800,11 @@ class _DashboardScreenForDependetState extends State // Banner Image (bannerUrl != null && bannerUrl.isNotEmpty) ? Image.network( - bannerUrl, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => Container( - color: const Color(0xFFF1F5F9), - child: const Icon(Icons.image_outlined, color: Color(0xFF94A3B8), size: 32), - ), - ) - : Container( - color: const Color(0xFFF1F5F9), - child: const Icon(Icons.image_outlined, color: Color(0xFF94A3B8), size: 32), - ), + bannerUrl, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container(color: const Color(0xFFF1F5F9), child: const Icon(Icons.image_outlined, color: Color(0xFF94A3B8), size: 32)), + ) + : Container(color: const Color(0xFFF1F5F9), child: const Icon(Icons.image_outlined, color: Color(0xFF94A3B8), size: 32)), // Gradient overlay at bottom of image Positioned( @@ -1007,42 +813,19 @@ class _DashboardScreenForDependetState extends State right: 0, height: 50, child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - colors: [ - Colors.black.withOpacity(0.75), - Colors.transparent, - ], - ), - ), + decoration: BoxDecoration(gradient: LinearGradient(begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [Colors.black.withOpacity(0.75), Colors.transparent])), ), ), // Discount badge at top right (flush with edge, pill on start side) if (discountText.isNotEmpty) PositionedDirectional( - top: 8, + top: 20, end: 0, child: Container( - padding: const EdgeInsetsDirectional.only( - start: 14, - end: 10, - top: 4, - bottom: 4, - ), - decoration: const BoxDecoration( - color: Color(0xFFED1C24), - borderRadius: BorderRadiusDirectional.only( - topStart: Radius.circular(20), - bottomStart: Radius.circular(20), - ), - ), - child: discountText.toText14( - isBold: true, - color: Colors.white, - ), + padding: const EdgeInsetsDirectional.only(start: 14, end: 10, top: 4, bottom: 4), + decoration: const BoxDecoration(color: Color(0xFFED1C24), borderRadius: BorderRadiusDirectional.only(topStart: Radius.circular(20), bottomStart: Radius.circular(20))), + child: discountText.toText14(isBold: true, color: Colors.white), ), ), @@ -1056,27 +839,12 @@ class _DashboardScreenForDependetState extends State Container( width: 18, height: 18, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4)), clipBehavior: Clip.antiAlias, - child: (logoUrl != null && logoUrl.isNotEmpty) - ? Image.network( - logoUrl, - fit: BoxFit.contain, - errorBuilder: (_, __, ___) => _buildMerchantMIcon(), - ) - : _buildMerchantMIcon(), + child: (logoUrl != null && logoUrl.isNotEmpty) ? Image.network(logoUrl, fit: BoxFit.contain, errorBuilder: (_, __, ___) => _buildMerchantMIcon()) : _buildMerchantMIcon(), ), const SizedBox(width: 5), - Expanded( - child: (item.name ?? "").toText11( - isBold: true, - maxLine: 1, - color: Colors.white, - ), - ), + Expanded(child: (item.name ?? "").toText11(isBold: true, maxLine: 1, color: Colors.white)), ], ), ), @@ -1088,25 +856,11 @@ class _DashboardScreenForDependetState extends State const SizedBox(height: 8), // Retailer Name / Bundle Title - (item.name ?? "").toText13( - isBold: true, - maxlines: 1, - overflow: TextOverflow.ellipsis, - color: const Color(0xFF1E293B), - ), - if (offerSubtitle.isNotEmpty) ...[ - const SizedBox(height: 2), - offerSubtitle.toText11( - maxLine: 1, - color: const Color(0xFF64748B), - ), - ], + (item.name ?? "").toText14(isBold: true, maxlines: 1, overflow: TextOverflow.ellipsis, 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 - if ((typeText != null && typeText.trim().isNotEmpty) || - validityText.trim().isNotEmpty || - cityText.trim().isNotEmpty || - usageText.trim().isNotEmpty) ...[ + if ((typeText != null && typeText.trim().isNotEmpty) || validityText.trim().isNotEmpty || cityText.trim().isNotEmpty || usageText.trim().isNotEmpty) ...[ const SizedBox(height: 6), Wrap( spacing: 5, @@ -1114,80 +868,30 @@ class _DashboardScreenForDependetState extends State children: [ // Type Chip (Online or In Store) if (typeText != null && typeText.trim().isNotEmpty) - Container( - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2.5), - decoration: BoxDecoration( - color: isOnline ? const Color(0xFFEFF6FF) : const Color(0xFFF5F3FF), - borderRadius: BorderRadius.circular(10), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - isOnline ? Icons.language_rounded : Icons.storefront_rounded, - size: 10, - color: isOnline ? const Color(0xFF2563EB) : const Color(0xFF7C3AED), - ), - const SizedBox(width: 3), - typeText.toText10( - isBold: true, - color: isOnline ? const Color(0xFF2563EB) : const Color(0xFF7C3AED), - ), - ], - ), + 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), ), // Validity badge - if (validityText.trim().isNotEmpty) - Container( - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2.5), - decoration: BoxDecoration( - color: const Color(0xFFE6F7F0), - borderRadius: BorderRadius.circular(10), - ), - child: validityText.toText10( - isBold: true, - color: const Color(0xFF00A651), - ), - ), + if (validityText.trim().isNotEmpty) InfoChipWidget(text: validityText, backgroundColor: const Color(0xFFE6F7F0), textColor: const Color(0xFF00A651)), // Location Badge if (cityText.trim().isNotEmpty) - Container( - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2.5), - decoration: BoxDecoration( - color: const Color(0xFFF3F4F6), - borderRadius: BorderRadius.circular(10), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.location_on_rounded, - size: 10, - color: Color(0xFF64748B), - ), - const SizedBox(width: 2.5), - cityText.toText10( - color: const Color(0xFF4B5563), - ), - ], - ), + 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, ), // Usage badge - if (usageText.trim().isNotEmpty) - Container( - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2.5), - decoration: BoxDecoration( - color: const Color(0xFFFEF3C7), - borderRadius: BorderRadius.circular(10), - ), - child: usageText.toText10( - isBold: true, - color: const Color(0xFFB45309), - ), - ), + if (usageText.trim().isNotEmpty) InfoChipWidget(text: usageText, backgroundColor: const Color(0xFFFEF3C7), textColor: const Color(0xFFB45309)), ], ), ], @@ -1204,78 +908,28 @@ class _DashboardScreenForDependetState extends State child: GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - childAspectRatio: 0.72, - crossAxisSpacing: 12, - mainAxisSpacing: 16, - ), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 0.72, crossAxisSpacing: 12, mainAxisSpacing: 16), itemCount: 4, itemBuilder: (_, __) { return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)), padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - height: 105, - width: double.infinity, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(14), - ), - ), + Container(height: 105, 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), - ), - ), + Container(height: 14, width: 110, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))), const SizedBox(height: 4), - Container( - height: 12, - width: 80, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), + Container(height: 12, width: 80, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))), const SizedBox(height: 6), Wrap( spacing: 5, runSpacing: 4, children: [ - Container( - height: 16, - width: 50, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10), - ), - ), - Container( - height: 16, - width: 45, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10), - ), - ), - Container( - height: 16, - width: 45, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10), - ), - ), + Container(height: 16, width: 50, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10))), + Container(height: 16, width: 45, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10))), + Container(height: 16, width: 45, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10))), ], ), ], @@ -1306,32 +960,11 @@ class _DashboardScreenForDependetState extends State crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Container( - width: 84, - height: 84, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(18), - ), - ), + Container(width: 84, height: 84, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(18))), const SizedBox(height: 8), - Container( - width: 65, - height: 11, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), + Container(width: 65, height: 11, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))), const SizedBox(height: 4), - Container( - width: 42, - height: 11, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), + Container(width: 42, height: 11, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))), ], ), ); diff --git a/lib/ui/landing/wala_plus_retailer_details_screen.dart b/lib/ui/landing/wala_plus_retailer_details_screen.dart index c46863a..cc15b04 100644 --- a/lib/ui/landing/wala_plus_retailer_details_screen.dart +++ b/lib/ui/landing/wala_plus_retailer_details_screen.dart @@ -1,7 +1,9 @@ +import 'dart:developer'; import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mohem_flutter_app/api/offers_and_discounts_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; @@ -11,22 +13,20 @@ import 'package:mohem_flutter_app/classes/utils.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'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/app_in_app_webview_screen.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; +import 'package:mohem_flutter_app/widgets/info_chip_widget.dart'; import 'package:url_launcher/url_launcher.dart'; class WalaPlusRetailerDetailsScreen extends StatefulWidget { final WalaPlusRetailerModel? retailer; final String? retailerId; - const WalaPlusRetailerDetailsScreen({ - Key? key, - this.retailer, - this.retailerId, - }) : super(key: key); + const WalaPlusRetailerDetailsScreen({Key? key, this.retailer, this.retailerId}) : super(key: key); @override State createState() => _WalaPlusRetailerDetailsScreenState(); @@ -81,10 +81,7 @@ class _WalaPlusRetailerDetailsScreenState extends State _openInAppBrowser(String url) async { + Future _openInAppBrowser(String url, {String? offerType}) async { String title = _retailer?.name ?? LocaleKeys.offerDetails.tr(); + bool shouldStartScanning = offerType?.trim().toLowerCase() == "discount"; await Navigator.push( context, - MaterialPageRoute( - builder: (BuildContext context) => AppInAppWebViewScreen( - url: url, - title: title, - ), - ), + MaterialPageRoute(builder: (BuildContext context) => AppInAppWebViewScreen(url: url, title: title, onPageLoaded: shouldStartScanning ? _clickStartScanningButton : null)), + ); + } + + // Vue's @click="startScanning()" is a component method, not something on window, + // so we find the actual button by its text and click it (lets Vue's own handler fire). + // Vue may still be mounting when onLoadStop fires, so poll briefly before giving up. + // Only called for offerType "discount" - "promo" offers just show a promo code, no scanner. + void _clickStartScanningButton(InAppWebViewController controller) { + controller.evaluateJavascript( + source: r""" + (function() { + var attempts = 0; + var maxAttempts = 10; // ~0.5s at 50ms + var timer = setInterval(function() { + attempts++; + var buttons = document.querySelectorAll('button'); + for (var i = 0; i < buttons.length; i++) { + var text = (buttons[i].textContent || '').replace(/\s+/g, ' ').trim(); + if (text.indexOf('Start Scanning') !== -1) { + buttons[i].click(); + clearInterval(timer); + return; + } + } + if (attempts >= maxAttempts) { + clearInterval(timer); + } + }, 50); + })(); + """, ); } @@ -160,16 +183,14 @@ class _WalaPlusRetailerDetailsScreenState extends State offers = _retailer?.offers ?? []; + log("offers.length: ${offers.length}"); + if (offers.length > 1) { _showOffersBottomSheet(offers); } else if (offers.length == 1) { WalaPlusOfferModel singleOffer = offers.first; - String offerId = (singleOffer.id != null && singleOffer.id!.isNotEmpty) - ? singleOffer.id! - : (_retailer?.id ?? _effectiveRetailerId ?? ""); - String offerType = (singleOffer.type != null && singleOffer.type!.isNotEmpty) - ? singleOffer.type! - : (_retailer?.offerType ?? "promo"); + String offerId = (singleOffer.id != null && singleOffer.id!.isNotEmpty) ? singleOffer.id! : (_retailer?.id ?? _effectiveRetailerId ?? ""); + String offerType = (singleOffer.type != null && singleOffer.type!.isNotEmpty) ? singleOffer.type! : (_retailer?.offerType ?? "promo"); _redeemOffer(offerId: offerId, offerType: offerType); } else { String offerId = _retailer?.id ?? _effectiveRetailerId ?? ""; @@ -194,31 +215,21 @@ class _WalaPlusRetailerDetailsScreenState extends State? res = await OffersAndDiscountsApiClient().redeemWalaPlusOffer( - offerId: offerId, - offerType: offerType, - context: context, - ); + Map? res = await OffersAndDiscountsApiClient().redeemWalaPlusOffer(offerId: offerId, offerType: offerType, context: context); Utils.hideLoading(context); String? transactionUrl; if (res != null) { - transactionUrl = res['Transaction_Url'] ?? - res['TransactionUrl'] ?? - res['transaction_url'] ?? - res['transactionUrl'] ?? - res['Url'] ?? - res['url']; + transactionUrl = res['Transaction_Url'] ?? res['TransactionUrl'] ?? res['transaction_url'] ?? res['transactionUrl'] ?? res['Url'] ?? res['url']; } + log("transactionUrl: ${transactionUrl.toString()}"); + if (transactionUrl != null && transactionUrl.trim().isNotEmpty) { - await _openInAppBrowser(transactionUrl.trim()); + await _openInAppBrowser(transactionUrl.trim(), offerType: offerType); } else { - String? error = res?['Error']?.toString() ?? - res?['error']?.toString() ?? - res?['ErrorMessage']?.toString() ?? - res?['message']?.toString(); + String? error = res?['Error']?.toString() ?? res?['error']?.toString() ?? res?['ErrorMessage']?.toString() ?? res?['message']?.toString(); if (error != null && error.isNotEmpty && error != "null") { Utils.showToast(error); } else { @@ -249,28 +260,14 @@ class _WalaPlusRetailerDetailsScreenState extends State(Color(0xFF168A89)), - ), - Expanded( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: const Color(0xff000000).withOpacity(.05), - blurRadius: 14, - offset: const Offset(0, 4), - ), - ], - ), - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 1. Top Banner Image with rounded corners - ClipRRect( - borderRadius: BorderRadius.circular(16), - child: SizedBox( - height: 190, - width: double.infinity, - child: (_retailer?.displayImage != null && _retailer!.displayImage!.isNotEmpty) - ? Image.network( - _retailer!.displayImage!, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => Container( - color: const Color(0xFFF3F4F6), - child: const Icon(Icons.broken_image, size: 48, color: Color(0xFF9CA3AF)), - ), - ) - : Container( - color: const Color(0xFFF3F4F6), - child: const Icon(Icons.image_outlined, size: 48, color: Color(0xFF9CA3AF)), - ), - ), - ), - 14.height, - - // 2. Brand Strip (Human Resources - Retailer Logo - Dr Sulaiman Al Habib) - _buildBrandStrip(isAr), - 18.height, - - // 3. Retailer Name (Centered, Bold) & Offer Title Subtitle - Center( - child: (_retailer?.name ?? "").toText22( - isBold: true, - isCentered: true, - color: const Color(0xFF1E293B), - ), - ), - if (_retailer?.offerTitle != null && - _retailer!.offerTitle!.trim().isNotEmpty && - _retailer!.offerTitle!.trim() != _retailer!.name?.trim()) ...[ - 6.height, - Center( - child: _cleanHtml(_retailer!.offerTitle!).toText16( - isCentered: true, - color: const Color(0xFF168A89), + body: + (_isLoading && !_hasDetailsData) + ? Center(child: Image.asset("assets/images/logos/loading_mohemm_logo.gif", height: 96.0, width: 96.0)) + : Column( + children: [ + if (_isLoading && _hasDetailsData) const LinearProgressIndicator(minHeight: 2.5, backgroundColor: Colors.transparent, valueColor: AlwaysStoppedAnimation(Color(0xFF168A89))), + Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [BoxShadow(color: const Color(0xff000000).withOpacity(.05), blurRadius: 14, offset: const Offset(0, 4))], + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 1. Top Banner Image with rounded corners + ClipRRect( + borderRadius: BorderRadius.circular(16), + child: SizedBox( + height: 190, + width: double.infinity, + child: + (_retailer?.displayImage != null && _retailer!.displayImage!.isNotEmpty) + ? Image.network( + _retailer!.displayImage!, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container(color: const Color(0xFFF3F4F6), child: const Icon(Icons.broken_image, size: 48, color: Color(0xFF9CA3AF))), + ) + : Container(color: const Color(0xFFF3F4F6), child: const Icon(Icons.image_outlined, size: 48, color: Color(0xFF9CA3AF))), ), ), + 14.height, + + // 2. Brand Strip (Human Resources - Retailer Logo - Dr Sulaiman Al Habib) + _buildBrandStrip(isAr), + // 18.height, + + // 3. Retailer Name (Centered, Bold) & Offer Title Subtitle + (_retailer?.name ?? "").toText22(isBold: true, isCentered: false, color: const Color(0xFF1E293B)), + // if (_retailer?.offerTitle != null && _retailer!.offerTitle!.trim().isNotEmpty && _retailer!.offerTitle!.trim() != _retailer!.name?.trim()) ...[ + // 6.height, + // Center(child: _cleanHtml(_retailer!.offerTitle!).toText16(isCentered: true, color: const Color(0xFF168A89))), + // ], + 18.height, + + // 4. 3 Info Badges Row (Store Type, Validity, City) + _buildInfoBadgesRow(isAr), + 10.height, + + // 5. Description Section (parsed as HTML) + // "${LocaleKeys.description.tr()}:".toText16(isBold: true, color: const Color(0xFF1E293B)), + // 8.height, + // Html( + // data: _getHowToUseHtml(isAr), + // style: {"body": Style(fontSize: FontSize(14.0), color: const Color(0xFF4B5563))}, + // onLinkTap: (String? url, Map attributes, _) async { + // if (url != null && url.isNotEmpty) { + // try { + // Uri uri = Uri.parse(url); + // if (!uri.hasScheme) { + // uri = Uri.parse("https://$url"); + // } + // await launchUrl(uri, mode: LaunchMode.externalApplication); + // } catch (e) { + // debugPrint("Error launching url: $e"); + // } + // } + // }, + // ), ], - 18.height, - - // 4. 3 Info Badges Row (Store Type, Validity, City) - _buildInfoBadgesRow(isAr), - 22.height, - - // 5. Description Section (parsed as HTML) - "${LocaleKeys.description.tr()}:".toText16( - isBold: true, - color: const Color(0xFF1E293B), - ), - 8.height, - Html( - data: _getHowToUseHtml(isAr), - style: { - "body": Style( - fontSize: FontSize(14.0), - color: const Color(0xFF4B5563), - ), - }, - onLinkTap: (String? url, Map attributes, _) async { - if (url != null && url.isNotEmpty) { - try { - Uri uri = Uri.parse(url); - if (!uri.hasScheme) { - uri = Uri.parse("https://$url"); - } - await launchUrl(uri, mode: LaunchMode.externalApplication); - } catch (e) { - debugPrint("Error launching url: $e"); - } - } - }, - ), - ], + ), ), - ), - // 6. Locations Section (below description) - _buildLocationsSection(isAr), + // 6. Locations Section (below description) + _buildLocationsSection(isAr), - // 7. Contact Information (Email & Phone Number) - _buildContactSection(isAr), + // 7. Contact Information (Email & Phone Number) + _buildContactSection(isAr), - 16.height, - ], + 16.height, + ], + ), ), ), - ), - // 7. Bottom Action Button - Container( - padding: const EdgeInsets.fromLTRB(16, 10, 16, 20), - color: Colors.white, - child: SizedBox( - width: double.infinity, - height: 50, - child: DefaultButton( - LocaleKeys.useOffer.tr(), - _isRedeeming ? null : _onUseOffer, - height: 50, - borderRadius: 12, - fontSize: 16, - ), + // 7. Bottom Action Button + Container( + padding: const EdgeInsets.fromLTRB(16, 10, 16, 20), + color: Colors.white, + child: SizedBox(width: double.infinity, height: 50, child: DefaultButton(LocaleKeys.useOffer.tr(), _isRedeeming ? null : _onUseOffer, height: 50, borderRadius: 12, fontSize: 16)), ), - ), - ], - ), + ], + ), ); } @@ -597,69 +505,48 @@ class _WalaPlusRetailerDetailsScreenState extends State const SizedBox(width: 36, height: 36), - ), + child: Image.network(logoUrl, height: 36, width: 36, fit: BoxFit.contain, errorBuilder: (_, __, ___) => const SizedBox(width: 36, height: 36)), ) else const SizedBox(width: 36, height: 36), // Right: Mazaya Brand Logo - SvgPicture.asset( - "assets/icons/mazaya_brand.svg", - height: 48, - fit: BoxFit.contain, - ), + SvgPicture.asset("assets/icons/mazaya_brand.svg", height: 48, fit: BoxFit.contain), ], ); } Widget _buildInfoBadgesRow(bool isAr) { - String storeTypeText = (_retailer?.type?.toLowerCase() == 'online') - ? LocaleKeys.online.tr() - : LocaleKeys.inStore.tr(); + bool isOnline = (_retailer?.type?.toLowerCase() == 'online'); + String storeTypeText = isOnline ? LocaleKeys.online.tr() : LocaleKeys.inStore.tr(); - IconData storeIcon = (_retailer?.type?.toLowerCase() == 'online') - ? Icons.language_rounded - : Icons.storefront_rounded; + IconData storeIcon = isOnline ? Icons.language_rounded : Icons.storefront_rounded; String cityText = _getDisplayCity(isAr); String validityText = _retailer?.getDisplayValidity() ?? LocaleKeys.offerValid.tr(); - return Row( + return Wrap( + spacing: 12, + runSpacing: 10, children: [ - Expanded( - child: _buildInfoCard( - icon: storeIcon, - label: storeTypeText, - ), + InfoChipWidget( + text: storeTypeText, + icon: storeIcon, + backgroundColor: isOnline ? const Color(0xFFEFF6FF) : const Color(0xFFF5F3FF), + textColor: isOnline ? const Color(0xFF2563EB) : const Color(0xFF7C3AED), ), - 12.width, - Expanded( - child: _buildInfoCard( - icon: Icons.check_circle_outline_rounded, - label: validityText, - ), - ), - 12.width, - Expanded( - child: _buildInfoCard( - icon: Icons.location_on_rounded, - label: cityText, - ), + 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, ), ], ); @@ -696,29 +583,6 @@ class _WalaPlusRetailerDetailsScreenState extends State]*>').hasMatch(s); @@ -884,9 +737,8 @@ class _WalaPlusRetailerDetailsScreenState extends State previewLocations = - totalCount <= 2 ? locations : locations.take(2).toList(); + int totalCount = locations.length; + List previewLocations = totalCount <= 2 ? locations : locations.take(2).toList(); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -900,31 +752,19 @@ class _WalaPlusRetailerDetailsScreenState extends State 2) @@ -936,16 +776,9 @@ class _WalaPlusRetailerDetailsScreenState extends State 2) ...[ - 10.height, - InkWell( - onTap: () => _showAllLocationsBottomSheet(locations, isAr), - borderRadius: BorderRadius.circular(16), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: const Color(0xff000000).withOpacity(.05), - blurRadius: 10, - offset: const Offset(0, 3), - ), - ], - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(9), - decoration: BoxDecoration( - color: const Color(0xFF168A89).withOpacity(0.08), - shape: BoxShape.circle, - ), - child: const Icon(Icons.map_outlined, color: Color(0xFF168A89), size: 18), - ), - 12.width, - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.showAllLocations.tr(args: ['$totalCount']).toText14( - isBold: true, - color: const Color(0xFF168A89), - ), - 2.height, - LocaleKeys.browseAllBranches.tr(args: ['$totalCount']).toText11( - color: const Color(0xFF64748B), - ), - ], - ), - ), - Icon( - isAr ? Icons.arrow_back_ios_rounded : Icons.arrow_forward_ios_rounded, - size: 14, - color: const Color(0xFF168A89), - ), - ], - ), - ), - ), - ], + // if (totalCount > 2) ...[ + // 10.height, + // InkWell( + // onTap: () => _showAllLocationsBottomSheet(locations, isAr), + // borderRadius: BorderRadius.circular(16), + // child: Container( + // padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), + // decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(16), + // boxShadow: [BoxShadow(color: const Color(0xff000000).withOpacity(.05), blurRadius: 10, offset: const Offset(0, 3))], + // ), + // child: Row( + // children: [ + // Container( + // padding: const EdgeInsets.all(9), + // decoration: BoxDecoration(color: const Color(0xFF168A89).withOpacity(0.08), shape: BoxShape.circle), + // child: const Icon(Icons.map_outlined, color: Color(0xFF168A89), size: 18), + // ), + // 12.width, + // Expanded( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // LocaleKeys.showAllLocations.tr(args: ['$totalCount']).toText14(isBold: true, color: const Color(0xFF168A89)), + // 2.height, + // LocaleKeys.browseAllBranches.tr(args: ['$totalCount']).toText11(color: const Color(0xFF64748B)), + // ], + // ), + // ), + // Icon(isAr ? Icons.arrow_back_ios_rounded : Icons.arrow_forward_ios_rounded, size: 14, color: const Color(0xFF168A89)), + // ], + // ), + // ), + // ), + // ], ], ); } @@ -1031,120 +846,91 @@ class _WalaPlusRetailerDetailsScreenState extends State[BoxShadow(color: const Color(0xff000000).withOpacity(.05), blurRadius: 10, offset: const Offset(0, 3))], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // City Pill Badge - if (cityText.isNotEmpty) - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3.5), - decoration: BoxDecoration( - color: const Color(0xFF168A89).withOpacity(0.08), - borderRadius: BorderRadius.circular(6), - ), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // // City Pill Badge + // if (cityText.isNotEmpty) + // Container( + // padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3.5), + // decoration: BoxDecoration(color: const Color(0xFF168A89).withOpacity(0.08), borderRadius: BorderRadius.circular(6)), + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [const Icon(Icons.location_on, size: 12, color: Color(0xFF168A89)), const SizedBox(width: 4), cityText.toText11(isBold: true, color: const Color(0xFF168A89))], + // ), + // ) + // else + // const SizedBox.shrink(), + // ], + // ), + // 10.height, + // Branch title & pin icon + InkWell( + onTap: () => _openMapForLocation(loc), + borderRadius: BorderRadius.circular(8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( child: Row( - mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Icon(Icons.location_on, size: 12, color: Color(0xFF168A89)), - const SizedBox(width: 4), - cityText.toText11( - isBold: true, - color: const Color(0xFF168A89), + Container( + margin: const EdgeInsets.only(top: 2), + width: 36, + height: 36, + alignment: Alignment.center, + decoration: const BoxDecoration(color: Color(0xFFF3F4F6), shape: BoxShape.circle), + child: SvgPicture.asset("assets/icons/location.svg", width: 16, height: 16), ), - ], - ), - ) - else - const SizedBox.shrink(), - - // Directions / Map action button - InkWell( - onTap: () => _openMapForLocation(loc), - borderRadius: BorderRadius.circular(8), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - decoration: BoxDecoration( - gradient: const LinearGradient( - transform: GradientRotation(.83), - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor], - ), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.directions_outlined, color: Colors.white, size: 14), - const SizedBox(width: 4), - LocaleKeys.viewOnMap.tr().toText11( - color: Colors.white, - isBold: true, + 8.width, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + primaryTitle.toText14(isBold: true, color: const Color(0xFF1E293B), maxlines: 2), + if (secondaryText != null) ...[2.height, secondaryText.toText12(color: const Color(0xFF057958), maxLine: 1)], + ], + ), ), ], ), ), - ), - ], - ), - 10.height, - // Branch title & pin icon - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: const EdgeInsets.only(top: 2), - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - color: const Color(0xFF168A89).withOpacity(0.08), - shape: BoxShape.circle, - ), - child: const Icon(Icons.store_mall_directory_rounded, size: 14, color: Color(0xFF168A89)), - ), - 8.width, - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - primaryTitle.toText14( - isBold: true, - color: const Color(0xFF1E293B), - maxlines: 2, - ), - if (secondaryText != null) ...[ - 2.height, - secondaryText.toText12( - color: const Color(0xFF64748B), - maxLine: 1, - ), - ], - ], - ), - ), - ], + + 12.width, + SvgPicture.asset("assets/icons/location.svg", width: 18, height: 18), + + // Container( + // padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + // decoration: BoxDecoration( + // gradient: const LinearGradient( + // transform: GradientRotation(.83), + // begin: Alignment.topRight, + // end: Alignment.bottomLeft, + // colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor], + // ), + // borderRadius: BorderRadius.circular(8), + // ), + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [const Icon(Icons.directions_outlined, color: Colors.white, size: 14), const SizedBox(width: 4), LocaleKeys.viewOnMap.tr().toText11(color: Colors.white, isBold: true)], + // ), + // ), + ], + ), ), ], ), @@ -1160,8 +946,7 @@ class _WalaPlusRetailerDetailsScreenState extends State sortedCities = cityCounts.keys.toList() - ..sort((a, b) => (cityCounts[b] ?? 0).compareTo(cityCounts[a] ?? 0)); + List sortedCities = cityCounts.keys.toList()..sort((a, b) => (cityCounts[b] ?? 0).compareTo(cityCounts[a] ?? 0)); String selectedCity = "ALL"; String searchQuery = ""; @@ -1174,48 +959,35 @@ class _WalaPlusRetailerDetailsScreenState extends State filtered = locations.where((loc) { - String c = loc.getLocalizedCity(isAr); - if (c.isEmpty) c = loc.city ?? ""; - if (selectedCity != "ALL" && c.trim() != selectedCity) { - return false; - } - if (searchQuery.isNotEmpty) { - String q = searchQuery.toLowerCase(); - String addr = (loc.address ?? "").toLowerCase(); - String cityEn = (loc.cityNameEn ?? "").toLowerCase(); - String cityAr = (loc.cityNameAr ?? "").toLowerCase(); - String genericCity = (loc.city ?? "").toLowerCase(); - if (!addr.contains(q) && !cityEn.contains(q) && !cityAr.contains(q) && !genericCity.contains(q)) { - return false; - } - } - return true; - }).toList(); + List filtered = + locations.where((loc) { + String c = loc.getLocalizedCity(isAr); + if (c.isEmpty) c = loc.city ?? ""; + if (selectedCity != "ALL" && c.trim() != selectedCity) { + return false; + } + if (searchQuery.isNotEmpty) { + String q = searchQuery.toLowerCase(); + String addr = (loc.address ?? "").toLowerCase(); + String cityEn = (loc.cityNameEn ?? "").toLowerCase(); + String cityAr = (loc.cityNameAr ?? "").toLowerCase(); + String genericCity = (loc.city ?? "").toLowerCase(); + if (!addr.contains(q) && !cityEn.contains(q) && !cityAr.contains(q) && !genericCity.contains(q)) { + return false; + } + } + return true; + }).toList(); return Container( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(sheetContext).size.height * 0.85, - ), - decoration: const BoxDecoration( - color: Color(0xFFF8F9FA), - borderRadius: BorderRadius.vertical(top: Radius.circular(24)), - ), + constraints: BoxConstraints(maxHeight: MediaQuery.of(sheetContext).size.height * 0.85), + decoration: const BoxDecoration(color: Color(0xFFF8F9FA), borderRadius: BorderRadius.vertical(top: Radius.circular(24))), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ 12.height, - Center( - child: Container( - width: 44, - height: 4.5, - decoration: BoxDecoration( - color: const Color(0xFFCBD5E1), - borderRadius: BorderRadius.circular(2.5), - ), - ), - ), + Center(child: Container(width: 44, height: 4.5, decoration: BoxDecoration(color: const Color(0xFFCBD5E1), borderRadius: BorderRadius.circular(2.5)))), 12.height, Padding( padding: const EdgeInsets.symmetric(horizontal: 20), @@ -1227,14 +999,9 @@ class _WalaPlusRetailerDetailsScreenState extends State 10.height, + itemBuilder: (BuildContext _, int index) { + return _buildLocationCard(filtered[index], isAr); + }, ), - ) - : ListView.separated( - shrinkWrap: true, - physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.fromLTRB(20, 16, 20, 24), - itemCount: filtered.length, - separatorBuilder: (_, __) => 10.height, - itemBuilder: (BuildContext _, int index) { - return _buildLocationCard(filtered[index], isAr); - }, - ), ), ], ), @@ -1401,10 +1147,7 @@ class _WalaPlusRetailerDetailsScreenState extends State _openMapForLocation(WalaPlusRetailerLocation loc) async { - bool hasCoordinates = loc.latitude != null && - loc.longitude != null && - loc.latitude != 0 && - loc.longitude != 0; + bool hasCoordinates = loc.latitude != null && loc.longitude != null && loc.latitude != 0 && loc.longitude != 0; Uri googleMapsUrl; if (hasCoordinates) { @@ -1440,10 +1183,7 @@ class _WalaPlusRetailerDetailsScreenState extends State { } } }, + onLoadStop: (InAppWebViewController controller, WebUri? url) async { + widget.onPageLoaded?.call(controller); + }, ), if (_progress < 1.0) LinearProgressIndicator( diff --git a/lib/widgets/info_chip_widget.dart b/lib/widgets/info_chip_widget.dart new file mode 100644 index 0000000..67bd374 --- /dev/null +++ b/lib/widgets/info_chip_widget.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; + +/// Small pill-shaped badge used for retailer/offer meta info (type, validity, location, usage, ...). +/// Flexible via optional params - every call only needs to override what differs from the given defaults. +class InfoChipWidget extends StatelessWidget { + final String text; + final Color backgroundColor; + final Color textColor; + final Color? iconColor; + final IconData? icon; + final String? svgIcon; + final bool isBold; + final double iconSize; + final double iconTextGap; + final double horizontalPadding; + final double verticalPadding; + final double borderRadius; + final Color? borderColor; + final double borderWidth; + + const InfoChipWidget({ + Key? key, + required this.text, + required this.backgroundColor, + required this.textColor, + this.iconColor, + this.icon, + this.svgIcon, + this.isBold = true, + this.iconSize = 10, + this.iconTextGap = 3, + this.horizontalPadding = 7, + this.verticalPadding = 2.5, + this.borderRadius = 10, + this.borderColor, + this.borderWidth = 1, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: horizontalPadding, vertical: verticalPadding), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(borderRadius), + border: Border.all(color: (borderColor ?? textColor).withValues(alpha: 0.7), width: borderWidth), + ), + child: + (icon == null && svgIcon == null) + ? text.toText10(isBold: isBold, color: textColor) + : Row( + mainAxisSize: MainAxisSize.min, + children: [_buildIcon(), SizedBox(width: iconTextGap), text.toText10(isBold: isBold, color: textColor)], + ), + ); + } + + Widget _buildIcon() { + if (svgIcon != null) { + return SvgPicture.asset(svgIcon!, width: iconSize, height: iconSize, colorFilter: ColorFilter.mode(iconColor ?? textColor, BlendMode.srcIn)); + } + return Icon(icon, size: iconSize, color: iconColor ?? textColor); + } +}