From 62058d6dd9bbb38b88401c4af97bd143a854117c Mon Sep 17 00:00:00 2001 From: FaizHashmi Date: Sun, 18 Oct 2026 16:46:44 +0300 Subject: [PATCH] chip set --- lib/classes/consts.dart | 4 +- .../dashboard_screen_for_dependet.dart | 347 +++++++++--------- 2 files changed, 170 insertions(+), 181 deletions(-) diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 3debbc7..4fc7544 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -4,12 +4,12 @@ class ApiConsts { // 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 = "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"; // diff --git a/lib/ui/landing/dashboard_screen_for_dependet.dart b/lib/ui/landing/dashboard_screen_for_dependet.dart index 8648ea1..ecaeb76 100644 --- a/lib/ui/landing/dashboard_screen_for_dependet.dart +++ b/lib/ui/landing/dashboard_screen_for_dependet.dart @@ -315,7 +315,9 @@ class _DashboardScreenForDependetState extends State String? queryParam = searchQuery.trim().isNotEmpty ? searchQuery.trim() : null; int? radiusParam = _radiusFromFilterDistance(_filterDistance); int? minDiscountParam = _filterMinDiscount == 'any' ? null : int.tryParse(_filterMinDiscount); - print("Fetching WalaPlus retailers: category='$catParam', offerType='$offerTypeParam', query='$queryParam', sortBy='$_filterSortBy', radius=$radiusParam, minDiscount=$minDiscountParam, page=1, per_page=$pageSize"); + print( + "Fetching WalaPlus retailers: category='$catParam', offerType='$offerTypeParam', query='$queryParam', sortBy='$_filterSortBy', radius=$radiusParam, minDiscount=$minDiscountParam, page=1, per_page=$pageSize", + ); String? userRef = isFavoriteOnly ? _getUserReferenceForFavorites() : null; print("Fetching WalaPlus retailers: category='$catParam', offerType='$offerTypeParam', query='$queryParam', userReference='$userRef', page=1, per_page=$pageSize"); WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient().getWalaPlusRetailers( @@ -606,10 +608,7 @@ class _DashboardScreenForDependetState extends State _changeLanguage(const Locale("ar", "SA")); } }, - child: const Padding( - padding: EdgeInsets.all(3), - child: Icon(Icons.language_rounded, size: 24, color: Color(0xFF1E293B)), - ), + child: const Padding(padding: EdgeInsets.all(3), child: Icon(Icons.language_rounded, size: 24, color: Color(0xFF1E293B))), ), ); } @@ -644,10 +643,7 @@ class _DashboardScreenForDependetState extends State _performLogout(); } }, - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 4, vertical: 8), - child: Icon(Icons.arrow_back_ios_new_rounded, size: 20, color: Color(0xFF1E293B)), - ), + child: const Padding(padding: EdgeInsets.symmetric(horizontal: 4, vertical: 8), child: Icon(Icons.arrow_back_ios_new_rounded, size: 20, color: Color(0xFF1E293B))), ), const SizedBox(width: 8), ], @@ -657,8 +653,7 @@ class _DashboardScreenForDependetState extends State Row( mainAxisSize: MainAxisSize.min, children: [ - if (ApiConsts.isDependetLogin) - _buildLanguageIconButton(isAr), + if (ApiConsts.isDependetLogin) _buildLanguageIconButton(isAr), if (!ApiConsts.isDependetLogin) ...[ const SizedBox(width: 4), Tooltip( @@ -670,12 +665,7 @@ class _DashboardScreenForDependetState extends State }, child: Padding( padding: const EdgeInsets.all(3), - child: SvgPicture.asset( - 'assets/icons/ic_dependents_top.svg', - width: 24, - height: 24, - colorFilter: const ColorFilter.mode(Color(0xFF1E293B), BlendMode.srcIn), - ), + child: SvgPicture.asset('assets/icons/ic_dependents_top.svg', width: 24, height: 24, colorFilter: const ColorFilter.mode(Color(0xFF1E293B), BlendMode.srcIn)), ), ), ), @@ -693,11 +683,7 @@ class _DashboardScreenForDependetState extends State }, child: Padding( padding: const EdgeInsets.all(3), - child: Icon( - isFavoriteOnly ? Icons.favorite_rounded : Icons.favorite_border_rounded, - size: 24, - color: isFavoriteOnly ? const Color(0xFFE11D48) : const Color(0xFF1E293B), - ), + child: Icon(isFavoriteOnly ? Icons.favorite_rounded : Icons.favorite_border_rounded, size: 24, color: isFavoriteOnly ? const Color(0xFFE11D48) : const Color(0xFF1E293B)), ), ), ), @@ -706,10 +692,7 @@ class _DashboardScreenForDependetState extends State InkWell( borderRadius: BorderRadius.circular(8), onTap: _performLogout, - child: Padding( - padding: const EdgeInsets.all(3), - child: SvgPicture.asset('assets/images/drawer/logout.svg', height: 20, width: 20), - ), + child: Padding(padding: const EdgeInsets.all(3), child: SvgPicture.asset('assets/images/drawer/logout.svg', height: 20, width: 20)), ), ], ], @@ -937,50 +920,49 @@ class _DashboardScreenForDependetState extends State Expanded( child: isSubcategoriesLoading - ? Shimmer.fromColors( - baseColor: const Color(0xFFE2E8F0), - highlightColor: const Color(0xFFF8FAFC), - child: ListView.separated( + ? Shimmer.fromColors( + baseColor: const Color(0xFFE2E8F0), + highlightColor: const Color(0xFFF8FAFC), + child: ListView.separated( + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + itemCount: 4, + separatorBuilder: (BuildContext _, int __) => const SizedBox(width: 8), + itemBuilder: (BuildContext _, int __) => Container(width: 70, height: 32, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(6))), + ), + ) + : ListView.separated( scrollDirection: Axis.horizontal, - physics: const NeverScrollableScrollPhysics(), - itemCount: 4, + physics: const BouncingScrollPhysics(), + // +1 for the leading "All" chip (clears the subcategory selection, + // falling back to just the parent main category). + itemCount: subcategoriesList.length + 1, separatorBuilder: (BuildContext _, int __) => const SizedBox(width: 8), - itemBuilder: - (BuildContext _, int __) => Container(width: 70, height: 32, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(6))), + itemBuilder: (BuildContext context, int index) { + bool isAllChip = index == 0; + WalaPlusCategoryModel? subcategory = isAllChip ? null : subcategoriesList[index - 1]; + bool isSelected = isAllChip ? _selectedSubcategoryId == null : _selectedSubcategoryId == subcategory!.id; + String label = isAllChip ? LocaleKeys.allCategories.tr() : (subcategory!.name ?? ""); + + return InkWell( + borderRadius: BorderRadius.circular(6), + onTap: () { + setState(() { + _selectedSubcategoryId = isAllChip ? null : (isSelected ? null : subcategory!.id); + }); + // Selecting a subcategory filters offers to just that subcategory; + // "All" (or deselecting) falls back to the parent main category. + fetchOffers(categoryId: _selectedSubcategoryId ?? currentCategoryId); + }, + 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), + child: label.toText12(color: isSelected ? MyColors.white : MyColors.black), + ), + ); + }, ), - ) - : ListView.separated( - scrollDirection: Axis.horizontal, - physics: const BouncingScrollPhysics(), - // +1 for the leading "All" chip (clears the subcategory selection, - // falling back to just the parent main category). - itemCount: subcategoriesList.length + 1, - separatorBuilder: (BuildContext _, int __) => const SizedBox(width: 8), - itemBuilder: (BuildContext context, int index) { - bool isAllChip = index == 0; - WalaPlusCategoryModel? subcategory = isAllChip ? null : subcategoriesList[index - 1]; - bool isSelected = isAllChip ? _selectedSubcategoryId == null : _selectedSubcategoryId == subcategory!.id; - String label = isAllChip ? LocaleKeys.allCategories.tr() : (subcategory!.name ?? ""); - - return InkWell( - borderRadius: BorderRadius.circular(6), - onTap: () { - setState(() { - _selectedSubcategoryId = isAllChip ? null : (isSelected ? null : subcategory!.id); - }); - // Selecting a subcategory filters offers to just that subcategory; - // "All" (or deselecting) falls back to the parent main category. - fetchOffers(categoryId: _selectedSubcategoryId ?? currentCategoryId); - }, - 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), - child: label.toText12(color: isSelected ? MyColors.white : MyColors.black), - ), - ); - }, - ), ), ], ], @@ -1016,11 +998,7 @@ class _DashboardScreenForDependetState extends State child: Container( height: 44, alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: isSelected ? darkBg : Colors.white, - border: Border.all(color: isSelected ? darkBg : borderColor), - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8), color: isSelected ? darkBg : Colors.white, border: Border.all(color: isSelected ? darkBg : borderColor)), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -1037,10 +1015,7 @@ class _DashboardScreenForDependetState extends State Widget sectionHeader(String title, {String? hint}) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - title.toText14(isBold: true, color: darkBg), - if (hint != null) hint.toText12(color: const Color(0xFF94A3B8)), - ], + children: [title.toText14(isBold: true, color: darkBg), if (hint != null) hint.toText12(color: const Color(0xFF94A3B8))], ); } @@ -1054,7 +1029,12 @@ class _DashboardScreenForDependetState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( - child: Container(width: 44, height: 4.5, margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration(color: const Color(0xFFCBD5E1), borderRadius: BorderRadius.circular(2.5))), + child: Container( + width: 44, + height: 4.5, + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration(color: const Color(0xFFCBD5E1), borderRadius: BorderRadius.circular(2.5)), + ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1357,122 +1337,131 @@ class _DashboardScreenForDependetState extends State children: [ // Inset image with rounded corners ClipRRect( - borderRadius: BorderRadius.circular(14), - child: SizedBox( - height: 120, - width: double.infinity, - child: Stack( - fit: StackFit.expand, - children: [ - // 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)), - - // Gradient overlay at bottom of image - Positioned( - bottom: 0, - left: 0, - right: 0, - height: 50, - child: Container( - 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: 20, - end: 0, + borderRadius: BorderRadius.circular(14), + child: SizedBox( + height: 120, + width: double.infinity, + child: Stack( + fit: StackFit.expand, + children: [ + // 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)), + + // Gradient overlay at bottom of image + Positioned( + bottom: 0, + left: 0, + right: 0, + height: 50, 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), + decoration: BoxDecoration(gradient: LinearGradient(begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [Colors.black.withOpacity(0.75), Colors.transparent])), ), ), - // Brand logo & name at bottom left - PositionedDirectional( - bottom: 8, - start: 8, - end: 8, - child: Row( - children: [ - Container( - width: 18, - height: 18, - 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(), + // Discount badge at top right (flush with edge, pill on start side) + if (discountText.isNotEmpty) + PositionedDirectional( + 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), ), - const SizedBox(width: 5), - Expanded(child: (item.name ?? "").toText11(isBold: true, maxLine: 1, color: Colors.white)), - ], + ), + + // Brand logo & name at bottom left + PositionedDirectional( + bottom: 8, + start: 8, + end: 8, + child: Row( + children: [ + Container( + width: 18, + height: 18, + 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(), + ), + const SizedBox(width: 5), + Expanded(child: (item.name ?? "").toText11(isBold: true, maxLine: 1, color: Colors.white)), + ], + ), ), - ), - ], + ], + ), ), ), - ), - const SizedBox(height: 8), - - // Retailer Name / Bundle Title - (item.name ?? "").toText14(isBold: true, maxlines: 1, overflow: TextOverflow.ellipsis, color: const Color(0xFF1E293B)), - // Subtitle line is always rendered (even empty) so every card reserves the same - // vertical space here, instead of shorter-content cards ending up shorter overall. - const SizedBox(height: 2), - offerSubtitle.toText14(maxlines: 1, overflow: TextOverflow.ellipsis, isBold: true, color: const Color(0xFF1E293B)), - - // Badges row: Type, Location & Usage - always exactly one row, fixed height. - // Horizontally scrollable instead of wrapping to a 2nd row, so every card's badge - // section is the same height regardless of how many badges it has. - const SizedBox(height: 6), - SizedBox( - height: 28, - child: ListView( - scrollDirection: Axis.horizontal, - physics: const BouncingScrollPhysics(), - children: [ - // One Type Chip per distinct type - Online (green) and/or In Store (blue). - for (String offerType in offerTypes) ...[ - InfoChipWidget( - text: _getTypeFilterTitle(offerType), - icon: _getTypeFilterIcon(offerType), - backgroundColor: offerType == 'online' ? const Color(0xFFE6F7F0) : const Color(0xFFEFF6FF), - textColor: offerType == 'online' ? const Color(0xFF00A651) : const Color(0xFF2563EB), - ), - const SizedBox(width: 5), - ], + const SizedBox(height: 8), + + // Retailer Name / Bundle Title + (item.name ?? "").toText14(isBold: true, maxlines: 1, overflow: TextOverflow.ellipsis, color: const Color(0xFF1E293B)), + // Subtitle line is always rendered (even empty) so every card reserves the same + // vertical space here, instead of shorter-content cards ending up shorter overall. + const SizedBox(height: 2), + offerSubtitle.toText14(maxlines: 1, overflow: TextOverflow.ellipsis, isBold: true, color: const Color(0xFF1E293B)), + + // Badges row: Type, Location & Usage - always exactly one row, fixed height. + // Horizontally scrollable instead of wrapping to a 2nd row, so every card's badge + // section is the same height regardless of how many badges it has. + const SizedBox(height: 6), + SizedBox( + height: 28, + child: ListView( + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + children: [ + // One Type Chip per distinct type - Online (green) and/or In Store (blue). + // Same pill styling (padding/border/icon size) as the retailer details screen. + for (String offerType in offerTypes) ...[ + InfoChipWidget( + text: _getTypeFilterTitle(offerType), + icon: _getTypeFilterIcon(offerType), + backgroundColor: offerType == 'online' ? const Color(0xFFE6F7F0) : const Color(0xFFEFF6FF), + textColor: offerType == 'online' ? const Color(0xFF00A651) : const Color(0xFF2563EB), + borderColor: offerType == 'online' ? const Color(0xFF00A651) : const Color(0xFF3B82F6), + horizontalPadding: 10, + verticalPadding: 5, + borderRadius: 12, + iconSize: 14, + ), + const SizedBox(width: 5), + ], + + // Location Badge + if (cityText.trim().isNotEmpty) ...[ + InfoChipWidget( + text: cityText, + icon: Icons.location_on_outlined, + backgroundColor: const Color(0xFFF8FAFC), + textColor: const Color(0xFF475569), + borderColor: const Color(0xFFCBD5E1), + horizontalPadding: 10, + verticalPadding: 5, + borderRadius: 12, + iconSize: 14, + ), + const SizedBox(width: 5), + ], - // Location Badge - if (cityText.trim().isNotEmpty) ...[ - 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, - ), - const SizedBox(width: 5), + // Usage badge + if (usageText.trim().isNotEmpty) + InfoChipWidget(text: usageText, backgroundColor: const Color(0xFFFEF3C7), textColor: const Color(0xFFB45309), horizontalPadding: 10, verticalPadding: 5, borderRadius: 12), ], - - // Usage badge - if (usageText.trim().isNotEmpty) InfoChipWidget(text: usageText, backgroundColor: const Color(0xFFFEF3C7), textColor: const Color(0xFFB45309)), - ], + ), ), - ), - ], + ], + ), ), ), - ), ); }