|
|
|
|
@ -315,7 +315,9 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
|
|
|
|
|
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<DashboardScreenForDependet>
|
|
|
|
|
_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<DashboardScreenForDependet>
|
|
|
|
|
_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<DashboardScreenForDependet>
|
|
|
|
|
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<DashboardScreenForDependet>
|
|
|
|
|
},
|
|
|
|
|
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<DashboardScreenForDependet>
|
|
|
|
|
},
|
|
|
|
|
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<DashboardScreenForDependet>
|
|
|
|
|
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<DashboardScreenForDependet>
|
|
|
|
|
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<DashboardScreenForDependet>
|
|
|
|
|
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: <Widget>[
|
|
|
|
|
@ -1037,10 +1015,7 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
|
|
|
|
|
Widget sectionHeader(String title, {String? hint}) {
|
|
|
|
|
return Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
title.toText14(isBold: true, color: darkBg),
|
|
|
|
|
if (hint != null) hint.toText12(color: const Color(0xFF94A3B8)),
|
|
|
|
|
],
|
|
|
|
|
children: <Widget>[title.toText14(isBold: true, color: darkBg), if (hint != null) hint.toText12(color: const Color(0xFF94A3B8))],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1054,7 +1029,12 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
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<DashboardScreenForDependet>
|
|
|
|
|
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: <Widget>[
|
|
|
|
|
// One Type Chip per distinct type - Online (green) and/or In Store (blue).
|
|
|
|
|
for (String offerType in offerTypes) ...<Widget>[
|
|
|
|
|
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: <Widget>[
|
|
|
|
|
// 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) ...<Widget>[
|
|
|
|
|
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) ...<Widget>[
|
|
|
|
|
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) ...<Widget>[
|
|
|
|
|
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)),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|