diff --git a/assets/icons/arrow-right.svg b/assets/icons/arrow-right.svg
new file mode 100644
index 0000000..1aec4ea
--- /dev/null
+++ b/assets/icons/arrow-right.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/card-add.svg b/assets/icons/card-add.svg
new file mode 100644
index 0000000..c433794
--- /dev/null
+++ b/assets/icons/card-add.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/check-alternative.svg b/assets/icons/check-alternative.svg
new file mode 100644
index 0000000..9a692aa
--- /dev/null
+++ b/assets/icons/check-alternative.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/delivery off.svg b/assets/icons/delivery off.svg
new file mode 100644
index 0000000..af75715
--- /dev/null
+++ b/assets/icons/delivery off.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/delivery on.svg b/assets/icons/delivery on.svg
new file mode 100644
index 0000000..07a0186
--- /dev/null
+++ b/assets/icons/delivery on.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/pick up off.svg b/assets/icons/pick up off.svg
new file mode 100644
index 0000000..5312c2c
--- /dev/null
+++ b/assets/icons/pick up off.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/icons/pickup on.svg b/assets/icons/pickup on.svg
new file mode 100644
index 0000000..134379a
--- /dev/null
+++ b/assets/icons/pickup on.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/icons/ticket-discount.svg b/assets/icons/ticket-discount.svg
new file mode 100644
index 0000000..e9535d4
--- /dev/null
+++ b/assets/icons/ticket-discount.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/ticket-discount2.svg b/assets/icons/ticket-discount2.svg
new file mode 100644
index 0000000..3a66d8c
--- /dev/null
+++ b/assets/icons/ticket-discount2.svg
@@ -0,0 +1,6 @@
+
diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart
index 69d0cf9..35dc0a5 100644
--- a/lib/gen/assets.gen.dart
+++ b/lib/gen/assets.gen.dart
@@ -62,6 +62,10 @@ class $AssetsIconsGen {
/// File path: assets/icons/arrow-left.svg
SvgGenImage get arrowLeft => const SvgGenImage('assets/icons/arrow-left.svg');
+ /// File path: assets/icons/arrow-right.svg
+ SvgGenImage get arrowRight =>
+ const SvgGenImage('assets/icons/arrow-right.svg');
+
/// File path: assets/icons/back.svg
SvgGenImage get back => const SvgGenImage('assets/icons/back.svg');
@@ -73,12 +77,19 @@ class $AssetsIconsGen {
SvgGenImage get calendarTick =>
const SvgGenImage('assets/icons/calendar-tick.svg');
+ /// File path: assets/icons/card-add.svg
+ SvgGenImage get cardAdd => const SvgGenImage('assets/icons/card-add.svg');
+
/// File path: assets/icons/card-pos.svg
SvgGenImage get cardPos => const SvgGenImage('assets/icons/card-pos.svg');
/// File path: assets/icons/category-2.svg
SvgGenImage get category2 => const SvgGenImage('assets/icons/category-2.svg');
+ /// File path: assets/icons/check-alternative.svg
+ SvgGenImage get checkAlternative =>
+ const SvgGenImage('assets/icons/check-alternative.svg');
+
/// File path: assets/icons/clander.svg
SvgGenImage get clander => const SvgGenImage('assets/icons/clander.svg');
@@ -88,6 +99,14 @@ class $AssetsIconsGen {
/// File path: assets/icons/coin.svg
SvgGenImage get coin => const SvgGenImage('assets/icons/coin.svg');
+ /// File path: assets/icons/delivery off.svg
+ SvgGenImage get deliveryOff =>
+ const SvgGenImage('assets/icons/delivery off.svg');
+
+ /// File path: assets/icons/delivery on.svg
+ SvgGenImage get deliveryOn =>
+ const SvgGenImage('assets/icons/delivery on.svg');
+
/// File path: assets/icons/dislike.svg
SvgGenImage get dislike => const SvgGenImage('assets/icons/dislike.svg');
@@ -178,6 +197,13 @@ class $AssetsIconsGen {
/// File path: assets/icons/ph_cheese.svg
SvgGenImage get phCheese => const SvgGenImage('assets/icons/ph_cheese.svg');
+ /// File path: assets/icons/pick up off.svg
+ SvgGenImage get pickUpOff =>
+ const SvgGenImage('assets/icons/pick up off.svg');
+
+ /// File path: assets/icons/pickup on.svg
+ SvgGenImage get pickupOn => const SvgGenImage('assets/icons/pickup on.svg');
+
/// File path: assets/icons/profile 2.svg
SvgGenImage get profile2 => const SvgGenImage('assets/icons/profile 2.svg');
@@ -241,6 +267,14 @@ class $AssetsIconsGen {
/// File path: assets/icons/tick.svg
SvgGenImage get tick => const SvgGenImage('assets/icons/tick.svg');
+ /// File path: assets/icons/ticket-discount.svg
+ SvgGenImage get ticketDiscount =>
+ const SvgGenImage('assets/icons/ticket-discount.svg');
+
+ /// File path: assets/icons/ticket-discount2.svg
+ SvgGenImage get ticketDiscount2 =>
+ const SvgGenImage('assets/icons/ticket-discount2.svg');
+
/// File path: assets/icons/timer-pause.svg
SvgGenImage get timerPause =>
const SvgGenImage('assets/icons/timer-pause.svg');
@@ -268,14 +302,19 @@ class $AssetsIconsGen {
arrowDownBlack,
arrowDown,
arrowLeft,
+ arrowRight,
back,
calendarTick2,
calendarTick,
+ cardAdd,
cardPos,
category2,
+ checkAlternative,
clander,
clock,
coin,
+ deliveryOff,
+ deliveryOn,
dislike,
down,
elementEqual,
@@ -302,6 +341,8 @@ class $AssetsIconsGen {
next,
notificationBing,
phCheese,
+ pickUpOff,
+ pickupOn,
profile2,
profile,
receiptDiscount2,
@@ -321,6 +362,8 @@ class $AssetsIconsGen {
star,
stashStarsLight,
tick,
+ ticketDiscount,
+ ticketDiscount2,
timerPause,
timerStart,
timer,
diff --git a/lib/res/colors.dart b/lib/res/colors.dart
index ed57c64..7cce8bb 100644
--- a/lib/res/colors.dart
+++ b/lib/res/colors.dart
@@ -21,4 +21,6 @@ class LightAppColors{
static const cardBackground = Color.fromARGB(255, 242, 242, 241);
static const offerTimer = Color.fromARGB(255, 244, 67, 54);
static const offerCardDetail = Color.fromARGB(255, 73, 69, 79);
+ static const divider = Color.fromARGB(255, 189, 189, 188);
+ static const textPrice = Color.fromARGB(255, 85, 84, 81);
}
\ No newline at end of file
diff --git a/lib/screens/mains/discover/discover.dart b/lib/screens/mains/discover/discover.dart
index 92f8bff..89f23b1 100644
--- a/lib/screens/mains/discover/discover.dart
+++ b/lib/screens/mains/discover/discover.dart
@@ -1,3 +1,5 @@
+import 'dart:ui';
+
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:dots_indicator/dots_indicator.dart';
@@ -21,6 +23,14 @@ class _DiscoverState extends State with TickerProviderStateMixin {
late AnimationController _staggeredController;
late List> _staggeredAnimations;
+ final Map _filters = {
+ 'Top 10 Offers': true,
+ 'Flash Sale': true,
+ 'special discount': false,
+ 'Occasion Specials': true,
+ 'First Purchase': false,
+ };
+
final List categoryIcons = [
Assets.icons.stashStarsLight.path,
Assets.icons.shoppingCart.path,
@@ -83,8 +93,100 @@ class _DiscoverState extends State with TickerProviderStateMixin {
);
}
+ void _showFilterMenu() {
+ showGeneralDialog(
+ context: context,
+ barrierDismissible: true,
+ barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+ barrierColor: Colors.black.withOpacity(0.1),
+ transitionDuration: const Duration(milliseconds: 400),
+ pageBuilder: (context, animation1, animation2) {
+ return BackdropFilter(
+ filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0),
+ child: Align(
+ alignment: Alignment.topRight,
+ child: Container(
+ width: 250,
+ margin: const EdgeInsets.only(top: kToolbarHeight + 65, right: 16),
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(20),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withOpacity(0.1),
+ blurRadius: 10,
+ spreadRadius: 5,
+ )
+ ],
+ ),
+ child: StatefulBuilder(
+ builder: (context, setDialogState) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: _filters.keys.map((String filterName) {
+ return Material(
+ color: Colors.transparent,
+ child: InkWell(
+ onTap: () {
+ setDialogState(() {
+ setState(() {
+ _filters[filterName] = !_filters[filterName]!;
+ });
+ });
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 18.0, vertical: 12.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ filterName,
+ style: TextStyle(fontSize: 16),
+ ),
+ _buildCustomCheckbox(_filters[filterName]!),
+ ],
+ ),
+ ),
+ ),
+ );
+ }).toList(),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ },
+ transitionBuilder: (context, animation, secondaryAnimation, child) {
+ final curvedAnimation = CurvedAnimation(
+ parent: animation,
+ curve: Curves.easeOutCubic,
+ reverseCurve: Curves.easeInCubic,
+ );
+
+ return ScaleTransition(
+ scale: Tween(begin: 0.8, end: 1.0).animate(curvedAnimation),
+ alignment: Alignment.topRight,
+ child: FadeTransition(
+ opacity: curvedAnimation,
+ child: SlideTransition(
+ position: Tween(
+ begin: const Offset(0.2, -0.2),
+ end: Offset.zero,
+ ).animate(curvedAnimation),
+ child: child,
+ ),
+ ),
+ );
+ },
+ );
+ }
+
@override
Widget build(BuildContext context) {
+ // You can use the _filters map to conditionally show/hide sections
+ // Example: if (_filters['Flash Sale']!) ... [ ... Flash Sale Section ... ]
return Scaffold(
backgroundColor: Colors.white,
appBar: _buildAppBar(),
@@ -106,15 +208,20 @@ class _DiscoverState extends State with TickerProviderStateMixin {
0,
),
const SizedBox(height: 16),
- _buildAnimatedSection(_buildSectionTitle("what's on your mind?"), 1),
+ _buildAnimatedSection(
+ _buildSectionTitle("what's on your mind?"), 1),
const SizedBox(height: 12),
_buildAnimatedSection(_buildCategoryIcons(), 2),
const SizedBox(height: 24),
- _buildAnimatedSection(_buildSectionTitle("Top 10 Discount & Offers"), 3),
+ _buildAnimatedSection(
+ _buildSectionTitle("Top 10 Discount & Offers"), 3),
const SizedBox(height: 12),
_buildAnimatedSection(_buildTopOffersSection(), 4),
const SizedBox(height: 24),
- _buildAnimatedSection(_buildSectionTitle("Flash Sale"), 5),
+ _buildAnimatedSection(
+ _buildSectionTitle("Flash Sale",
+ showSeeAll: true, onSeeAllTap: () {}),
+ 5),
const SizedBox(height: 12),
_buildAnimatedSection(_buildFlashSaleSection(), 6),
const SizedBox(height: 24),
@@ -122,7 +229,10 @@ class _DiscoverState extends State with TickerProviderStateMixin {
const SizedBox(height: 12),
_buildAnimatedSection(_buildSpecialDiscountSection(), 1),
const SizedBox(height: 24),
- _buildAnimatedSection(_buildSectionTitle("Seasonal Discount"), 2),
+ _buildAnimatedSection(
+ _buildSectionTitle("Seasonal Discount",
+ showSeeAll: true, onSeeAllTap: () {}),
+ 2),
const SizedBox(height: 12),
_buildAnimatedSection(_buildSeasonalDiscountSection(), 3),
const SizedBox(height: 24),
@@ -130,7 +240,10 @@ class _DiscoverState extends State with TickerProviderStateMixin {
const SizedBox(height: 12),
_buildAnimatedSection(_buildCraftingSomethingSection(), 5),
const SizedBox(height: 24),
- _buildAnimatedSection(_buildSectionTitle("First Purchase Discount"), 6),
+ _buildAnimatedSection(
+ _buildSectionTitle("First Purchase Discount",
+ showSeeAll: true, onSeeAllTap: () {}),
+ 6),
const SizedBox(height: 12),
_buildAnimatedSection(_buildFirstPurchaseSection(), 7),
const SizedBox(height: 100),
@@ -178,15 +291,45 @@ class _DiscoverState extends State with TickerProviderStateMixin {
),
child: IconButton(
icon: SvgPicture.asset(Assets.icons.sort.path, color: Colors.white),
- onPressed: () {
- CustomBottomSheet.show(context, [
- "Food & Dining",
- "Entertainment & Leisure",
- "Health & Fitness",
- "Travel & Transportation",
- ]);
+ onPressed: _showFilterMenu,
+ ),
+ );
+ }
+
+ Widget _buildCustomCheckbox(bool isChecked) {
+ return AnimatedContainer(
+ duration: const Duration(milliseconds: 200),
+ curve: Curves.easeInOut,
+ width: 24,
+ height: 24,
+ decoration: BoxDecoration(
+ color: isChecked ? LightAppColors.primary : Colors.transparent,
+ border: isChecked
+ ? null
+ : Border.all(color: Color.fromARGB(255, 89, 93, 98), width: 2),
+ borderRadius: BorderRadius.circular(6),
+ ),
+ child: AnimatedSwitcher(
+ duration: const Duration(milliseconds: 200),
+ transitionBuilder: (child, animation) {
+ return ScaleTransition(
+ scale: animation,
+ child: FadeTransition(
+ opacity: animation,
+ child: child,
+ ),
+ );
},
- padding: const EdgeInsets.all(8),
+ child: isChecked
+ ? const Icon(
+ Icons.check,
+ color: Colors.white,
+ size: 18,
+ key: ValueKey('checked'),
+ )
+ : const SizedBox(
+ key: ValueKey('unchecked'),
+ ),
),
);
}
@@ -363,17 +506,43 @@ class _DiscoverState extends State with TickerProviderStateMixin {
);
}
- Widget _buildSectionTitle(String title) {
+ Widget _buildSectionTitle(String title,
+ {bool showSeeAll = false, VoidCallback? onSeeAllTap}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.normal),
),
const SizedBox(width: 8),
- const Expanded(child: Divider(color: Colors.grey, thickness: 1)),
+ const Expanded(
+ child: Divider(color: Colors.grey, thickness: 1),
+ ),
+ if (showSeeAll) ...[
+ const SizedBox(width: 8),
+ InkWell(
+ onTap: onSeeAllTap,
+ child: Row(
+ children: [
+ Text(
+ 'See all',
+ style: TextStyle(
+ color: LightAppColors.primary,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ const SizedBox(width: 7),
+ SvgPicture.asset(
+ Assets.icons.arrowRight.path,
+ ),
+ ],
+ ),
+ ),
+ ],
],
),
);
@@ -626,9 +795,8 @@ class FlashSaleCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final timer =
- RemainingTime()
- ..initializeFromExpiry(expiryTimeString: expiryTimeString);
+ final timer = RemainingTime()
+ ..initializeFromExpiry(expiryTimeString: expiryTimeString);
return Container(
decoration: BoxDecoration(
color: LightAppColors.cardBackground,
@@ -674,17 +842,18 @@ class FlashSaleCard extends StatelessWidget {
),
ValueListenableBuilder(
valueListenable: timer.remainingSeconds,
- builder:
- (context, _, __) => Text(
- timer.formatTime(),
- style: const TextStyle(
- color: LightAppColors.offerTimer,
- fontSize: 12,
- fontWeight: FontWeight.bold,
- ),
- ),
+ builder: (context, _, __) => Text(
+ timer.formatTime(),
+ style: const TextStyle(
+ color: LightAppColors.offerTimer,
+ fontSize: 12,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
),
- const SizedBox(height: 5,)
+ const SizedBox(
+ height: 5,
+ )
],
),
],
@@ -731,7 +900,8 @@ class FlashSaleCard extends StatelessWidget {
const SizedBox(width: 4),
Text(
location,
- style: const TextStyle(color: LightAppColors.offerCardDetail, fontSize: 12),
+ style: const TextStyle(
+ color: LightAppColors.offerCardDetail, fontSize: 12),
),
],
),
@@ -748,7 +918,7 @@ class FlashSaleCard extends StatelessWidget {
TextSpan(
style: const TextStyle(
fontSize: 12,
- color: LightAppColors.offerCardDetail ,
+ color: LightAppColors.offerCardDetail,
),
children: [
TextSpan(
@@ -769,7 +939,9 @@ class FlashSaleCard extends StatelessWidget {
],
),
),
- const SizedBox(width: 10,),
+ const SizedBox(
+ width: 10,
+ ),
Text(
'($discountPercent% off)',
style: const TextStyle(
@@ -800,11 +972,18 @@ class FlashSaleCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
- SvgPicture.asset(Assets.icons.shoppingCart.path,color: Colors.white,width: 20,),
- const SizedBox(width: 5,),
+ SvgPicture.asset(
+ Assets.icons.shoppingCart.path,
+ color: Colors.white,
+ width: 20,
+ ),
+ const SizedBox(
+ width: 5,
+ ),
const Text(
"Reservation",
- style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
+ style: TextStyle(
+ fontWeight: FontWeight.bold, fontSize: 15),
),
],
),
@@ -987,15 +1166,15 @@ class FirstPurchaseCard extends StatelessWidget {
const SizedBox(height: 5),
Text(
category,
- style: const TextStyle(color: LightAppColors.nearbyPopuphint, fontSize: 14),
+ style: const TextStyle(
+ color: LightAppColors.nearbyPopuphint, fontSize: 14),
),
const SizedBox(height: 5),
Text(
discount,
style: const TextStyle(
- fontWeight: FontWeight.w500,
- color: LightAppColors.nearbyPopuphint
- ),
+ fontWeight: FontWeight.w500,
+ color: LightAppColors.nearbyPopuphint),
),
const SizedBox(height: 5),
Row(
diff --git a/lib/screens/product/checkout.dart b/lib/screens/product/checkout.dart
new file mode 100644
index 0000000..9aedb4e
--- /dev/null
+++ b/lib/screens/product/checkout.dart
@@ -0,0 +1,709 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:lba/gen/assets.gen.dart';
+import 'package:lba/res/colors.dart';
+
+class CheckoutPage extends StatefulWidget {
+ const CheckoutPage({super.key});
+
+ @override
+ State createState() => _CheckoutPageState();
+}
+
+class _CheckoutPageState extends State
+ with TickerProviderStateMixin {
+ int _selectedTabIndex = 0;
+
+ late AnimationController _deliveryController;
+ late AnimationController _pickupController;
+ late Animation _deliveryColorAnimation;
+ late Animation _pickupColorAnimation;
+ late Animation _deliveryScaleAnimation;
+ late Animation _pickupScaleAnimation;
+ late Animation _deliverySlideAnimation;
+ late Animation _pickupSlideAnimation;
+
+ static const Color greyTextMid = Color(0xFF616161);
+ static const Color greyTextLight = Color(0xFF9E9E9E);
+ static const Color activeColor = Color(0xFF189CFF);
+
+ @override
+ void initState() {
+ super.initState();
+ _deliveryController = AnimationController(
+ vsync: this, duration: const Duration(milliseconds: 300));
+ _pickupController = AnimationController(
+ vsync: this, duration: const Duration(milliseconds: 300));
+
+ _deliveryColorAnimation =
+ ColorTween(begin: greyTextLight, end: activeColor)
+ .animate(_deliveryController);
+ _pickupColorAnimation = ColorTween(begin: greyTextLight, end: activeColor)
+ .animate(_pickupController);
+
+ _deliveryScaleAnimation =
+ Tween(begin: 1.0, end: 1.1).animate(_deliveryController);
+ _pickupScaleAnimation =
+ Tween(begin: 1.0, end: 1.1).animate(_pickupController);
+
+ _deliverySlideAnimation =
+ Tween(begin: const Offset(0, 0), end: const Offset(0, -0.1))
+ .animate(_deliveryController);
+ _pickupSlideAnimation =
+ Tween(begin: const Offset(0, 0), end: const Offset(0, -0.1))
+ .animate(_pickupController);
+
+ if (_selectedTabIndex == 0) {
+ _deliveryController.forward();
+ } else {
+ _pickupController.forward();
+ }
+ }
+
+ @override
+ void dispose() {
+ _deliveryController.dispose();
+ _pickupController.dispose();
+ super.dispose();
+ }
+
+ void _onTabSelected(int index) {
+ setState(() {
+ _selectedTabIndex = index;
+ });
+ if (index == 0) {
+ _deliveryController.forward();
+ _pickupController.reverse();
+ } else {
+ _pickupController.forward();
+ _deliveryController.reverse();
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Colors.white,
+ appBar: _buildAppBar(),
+ body: Column(
+ children: [
+ _buildTabs(),
+ Container(height: 1, color: LightAppColors.divider, width: 370),
+ Expanded(
+ child: AnimatedSwitcher(
+ duration: const Duration(milliseconds: 500),
+ transitionBuilder: (child, animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: SlideTransition(
+ position: Tween(
+ begin:
+ Offset(_selectedTabIndex == 0 ? 0.5 : -0.5, 0),
+ end: Offset.zero,
+ ).animate(animation),
+ child: child,
+ ),
+ );
+ },
+ child: _selectedTabIndex == 0
+ ? _buildDeliveryContent()
+ : _buildPickupContent(),
+ ),
+ ),
+ ],
+ ),
+ bottomNavigationBar: _payBar(),
+ );
+ }
+
+ Widget _buildDeliveryContent() {
+ return SingleChildScrollView(
+ key: const ValueKey('delivery'),
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 20),
+ _sectionHeader("Deliver to:",
+ trailingAction: "Select address", onAction: () {}),
+ const SizedBox(height: 10),
+ _selectAddressRow(),
+ const SizedBox(height: 20),
+ const DottedDivider(),
+ const SizedBox(height: 20),
+ _sectionHeader("Delivery time:"),
+ const SizedBox(height: 12),
+ _deliveryTimeCards(),
+ const SizedBox(height: 20),
+ const DottedDivider(),
+ const SizedBox(height: 15),
+ _sectionHeader("Payment Method:"),
+ const SizedBox(height: 12),
+ _clickableCard(
+ leading: Assets.icons.cardAdd.path,
+ title: "Add a credit card",
+ onTap: () {},
+ ),
+ const SizedBox(height: 24),
+ const DottedDivider(),
+ const SizedBox(height: 24),
+ _sectionHeader("promo code:"),
+ const SizedBox(height: 12),
+ _promoCodeField(),
+ const SizedBox(height: 28),
+ _priceDetails(),
+ const SizedBox(height: 40),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildPickupContent() {
+ return SingleChildScrollView(
+ key: const ValueKey('pickup'),
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 20),
+ _sectionHeader("Pickup from:",
+ trailingAction: "Select address", onAction: () {}),
+ const SizedBox(height: 10),
+ _pickupAddressRow("Mall of the Emirates"),
+ const SizedBox(height: 20),
+ const DottedDivider(),
+ const SizedBox(height: 20),
+ _sectionHeader("Pickup time:",
+ trailingAction: "Select time", onAction: () {}),
+ const SizedBox(height: 12),
+ _pickupTimeRow("Set a time for your Pickup"),
+ const SizedBox(height: 20),
+ const DottedDivider(),
+ const SizedBox(height: 15),
+ _sectionHeader("Payment Method:"),
+ const SizedBox(height: 12),
+ _clickableCard(
+ leading: Assets.icons.cardAdd.path,
+ title: "Add a credit card",
+ onTap: () {},
+ ),
+ const SizedBox(height: 24),
+ const DottedDivider(),
+ const SizedBox(height: 24),
+ _sectionHeader("promo code:"),
+ const SizedBox(height: 12),
+ _promoCodeField(),
+ const SizedBox(height: 28),
+ _priceDetails(),
+ const SizedBox(height: 40),
+ ],
+ ),
+ );
+ }
+
+ PreferredSizeWidget _buildAppBar() {
+ return AppBar(
+ elevation: 0,
+ backgroundColor: Colors.white,
+ centerTitle: false,
+ leading: IconButton(
+ icon: SvgPicture.asset(
+ Assets.icons.arrowLeft.path,
+ height: 20,
+ ),
+ onPressed: () => Navigator.of(context).pop(),
+ ),
+ title: const Text(
+ 'Checkout',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 18,
+ fontWeight: FontWeight.normal,
+ ),
+ ),
+ bottom: PreferredSize(
+ preferredSize: const Size.fromHeight(1.0),
+ child: Container(
+ color: LightAppColors.divider,
+ height: 1.0,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildTabs() {
+ return Padding(
+ padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
+ child: Row(
+ children: [
+ _buildAnimatedTabItem(
+ index: 0,
+ selectedIcon: Assets.icons.deliveryOn.path,
+ unselectedIcon: Assets.icons.deliveryOff.path,
+ label: 'Delivery',
+ controller: _deliveryController,
+ colorAnimation: _deliveryColorAnimation,
+ scaleAnimation: _deliveryScaleAnimation,
+ slideAnimation: _deliverySlideAnimation,
+ ),
+ const SizedBox(width: 16),
+ _buildAnimatedTabItem(
+ index: 1,
+ selectedIcon: Assets.icons.pickupOn.path,
+ unselectedIcon: Assets.icons.pickUpOff.path,
+ label: 'Pickup',
+ controller: _pickupController,
+ colorAnimation: _pickupColorAnimation,
+ scaleAnimation: _pickupScaleAnimation,
+ slideAnimation: _pickupSlideAnimation,
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildAnimatedTabItem({
+ required int index,
+ required String selectedIcon,
+ required String unselectedIcon,
+ required String label,
+ required AnimationController controller,
+ required Animation colorAnimation,
+ required Animation scaleAnimation,
+ required Animation slideAnimation,
+ }) {
+ final bool selected = _selectedTabIndex == index;
+
+ return Expanded(
+ child: InkWell(
+ borderRadius: BorderRadius.circular(6),
+ onTap: () => _onTabSelected(index),
+ child: Container(
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(6),
+ ),
+ padding: const EdgeInsets.fromLTRB(6, 8, 6, 0),
+ child: Column(
+ children: [
+ AnimatedBuilder(
+ animation: controller,
+ builder: (context, child) {
+ return SlideTransition(
+ position: slideAnimation,
+ child: ScaleTransition(
+ scale: scaleAnimation,
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SvgPicture.asset(
+ selected ? selectedIcon : unselectedIcon,
+ color: colorAnimation.value,
+ ),
+ const SizedBox(width: 4),
+ Text(
+ label,
+ style: TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.w600,
+ color: colorAnimation.value,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ ),
+ const SizedBox(height: 6),
+ AnimatedContainer(
+ duration: const Duration(milliseconds: 400),
+ height: 5,
+ width: selected ? 100 : 0,
+ child: selected
+ ? SvgPicture.asset(
+ Assets.icons.shape.path,
+ color: LightAppColors.primary,
+ )
+ : const SizedBox(),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _sectionHeader(String title,
+ {String? trailingAction, VoidCallback? onAction}) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ title,
+ style: const TextStyle(
+ color: Colors.black,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ if (trailingAction != null)
+ GestureDetector(
+ onTap: onAction,
+ child: Text(
+ trailingAction,
+ style: const TextStyle(
+ color: LightAppColors.offerTimer,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _selectAddressRow() {
+ return Row(
+ children: [
+ SvgPicture.asset(
+ Assets.icons.location.path,
+ color: const Color.fromARGB(255, 85, 84, 81),
+ ),
+ const SizedBox(width: 10),
+ const Text(
+ "Select address",
+ style: TextStyle(
+ fontSize: 15,
+ fontWeight: FontWeight.w500,
+ color: greyTextMid,
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _pickupAddressRow(String address) {
+ return Row(
+ children: [
+ SvgPicture.asset(
+ Assets.icons.location.path,
+ color: const Color.fromARGB(255, 85, 84, 81),
+ ),
+ const SizedBox(width: 10),
+ Text(
+ address,
+ style: const TextStyle(
+ fontSize: 15,
+ fontWeight: FontWeight.w500,
+ color: greyTextMid,
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _pickupTimeRow(String time) {
+ return Text(
+ time,
+ style: const TextStyle(
+ fontSize: 15,
+ fontWeight: FontWeight.w500,
+ color: greyTextMid,
+ ),
+ );
+ }
+
+ Widget _deliveryTimeCards() {
+ return Column(
+ children: [
+ _clickableCard(
+ customChild: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: const [
+ Text(
+ "ASAP",
+ style: TextStyle(
+ fontSize: 15,
+ fontWeight: FontWeight.w600,
+ color: Colors.black),
+ ),
+ SizedBox(height: 4),
+ Text(
+ "Delivered directly to you",
+ style: TextStyle(
+ fontSize: 13, color: greyTextLight, height: 1.1),
+ ),
+ ],
+ ),
+ onTap: () {},
+ ),
+ const SizedBox(height: 12),
+ _clickableCard(
+ customChild: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: const [
+ Text("Schedule",
+ style: TextStyle(
+ fontSize: 15,
+ fontWeight: FontWeight.w600,
+ color: Colors.black)),
+ SizedBox(height: 4),
+ Text("Select a time",
+ style: TextStyle(
+ fontSize: 13,
+ color: greyTextLight,
+ height: 1.1)),
+ ],
+ ),
+ ),
+ SvgPicture.asset(
+ Assets.icons.arrowRight.path,
+ height: 20,
+ color: Colors.black,
+ ),
+ ],
+ ),
+ onTap: () {},
+ ),
+ ],
+ );
+ }
+
+ Widget _clickableCard({
+ String? leading,
+ String? title,
+ Widget? customChild,
+ VoidCallback? onTap,
+ }) {
+ return InkWell(
+ borderRadius: BorderRadius.circular(6),
+ onTap: onTap,
+ child: Container(
+ width: double.infinity,
+ padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(15),
+ border: Border.all(color: LightAppColors.divider),
+ ),
+ child: customChild ??
+ Row(
+ children: [
+ if (leading != null) SvgPicture.asset(leading),
+ if (leading != null) const SizedBox(width: 10),
+ if (title != null)
+ Expanded(
+ child: Text(
+ title,
+ style: const TextStyle(
+ fontSize: 15,
+ fontWeight: FontWeight.w500,
+ color: greyTextMid,
+ ),
+ ),
+ ),
+ const Icon(Icons.arrow_forward_ios,
+ size: 16, color: greyTextLight),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _promoCodeField() {
+ return TextField(
+ style: const TextStyle(fontSize: 14),
+ decoration: InputDecoration(
+ prefixIcon: Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: SvgPicture.asset(
+ Assets.icons.ticketDiscount2.path,
+ ),
+ ),
+ hintText: 'Enter your promo code here',
+ hintStyle: const TextStyle(color: greyTextLight, fontSize: 14),
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(15),
+ borderSide: const BorderSide(color: LightAppColors.divider),
+ ),
+ enabledBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(15),
+ borderSide: const BorderSide(color: LightAppColors.divider),
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(15),
+ borderSide: const BorderSide(color: LightAppColors.divider),
+ ),
+ fillColor: Colors.white,
+ filled: true,
+ isDense: true,
+ contentPadding:
+ const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
+ ),
+ );
+ }
+
+ Widget _priceDetails() {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const DottedDivider(),
+ const SizedBox(height: 15),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: const [
+ Text(
+ "Price Details",
+ style: TextStyle(
+ color: LightAppColors.textPrice,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ Text(
+ "1 Item",
+ style: TextStyle(
+ color: LightAppColors.textPrice,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 12),
+ const DottedDivider(),
+ const SizedBox(height: 14),
+ _priceRow("Subtotal", "27.900"),
+ const SizedBox(height: 12),
+ _priceRow("Sales tax", "0.000"),
+ const SizedBox(height: 14),
+ const DottedDivider(),
+ const SizedBox(height: 14),
+ _priceRow("Total", "27.900", isTotal: true),
+ ],
+ );
+ }
+
+ Widget _priceRow(String label, String amount, {bool isTotal = false}) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ label,
+ style: TextStyle(
+ fontSize: 15,
+ color:
+ isTotal ? LightAppColors.textPrice : LightAppColors.popupText,
+ fontWeight: isTotal ? FontWeight.w600 : FontWeight.normal,
+ ),
+ ),
+ Text(
+ "AED $amount",
+ style: TextStyle(
+ fontSize: 15,
+ color:
+ isTotal ? LightAppColors.textPrice : LightAppColors.popupText,
+ fontWeight: isTotal ? FontWeight.w700 : FontWeight.w600,
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _payBar() {
+ return SafeArea(
+ top: false,
+ child: Container(
+ padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
+ decoration: const BoxDecoration(color: Colors.white),
+ child: SizedBox(
+ width: double.infinity,
+ height: 48,
+ child: ElevatedButton(
+ style: ElevatedButton.styleFrom(
+ backgroundColor: LightAppColors.offerTimer,
+ elevation: 0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(15),
+ ),
+ ),
+ onPressed: () {},
+ child: const Text(
+ "Pay AED 27.900",
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w600,
+ color: Colors.white,
+ letterSpacing: 0.2,
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class DottedDivider extends StatelessWidget {
+ final double height;
+ final Color color;
+ final double dashWidth;
+ final double dashSpace;
+
+ const DottedDivider({
+ super.key,
+ this.height = 1,
+ this.color = const Color(0xFFE0E0E0),
+ this.dashWidth = 4,
+ this.dashSpace = 3,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ height: height,
+ width: double.infinity,
+ child: CustomPaint(
+ painter: _DottedLinePainter(
+ color: color,
+ dashWidth: dashWidth,
+ dashSpace: dashSpace,
+ ),
+ ),
+ );
+ }
+}
+
+class _DottedLinePainter extends CustomPainter {
+ final Color color;
+ final double dashWidth;
+ final double dashSpace;
+
+ _DottedLinePainter({
+ required this.color,
+ required this.dashWidth,
+ required this.dashSpace,
+ });
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ final paint = Paint()
+ ..color = color
+ ..strokeWidth = size.height
+ ..style = PaintingStyle.stroke;
+
+ double x = 0;
+ while (x < size.width) {
+ canvas.drawLine(Offset(x, 0), Offset(x + dashWidth, 0), paint);
+ x += dashWidth + dashSpace;
+ }
+ }
+
+ @override
+ bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
+}
\ No newline at end of file
diff --git a/lib/screens/product/productdetail.dart b/lib/screens/product/productdetail.dart
index 31c77ee..1e06a7e 100644
--- a/lib/screens/product/productdetail.dart
+++ b/lib/screens/product/productdetail.dart
@@ -55,8 +55,6 @@ class _ProductdetailState extends State
});
}
-
-
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -238,7 +236,7 @@ class _ProductdetailState extends State
"Mall of the EmiratesŲ City Centre Mirdif",
"UEA",
],
-
+
workingHours: [
WorkingHours(
day: 'mo',
diff --git a/lib/widgets/price_reserve_widget.dart b/lib/widgets/price_reserve_widget.dart
index 22bb9fd..bdb3526 100644
--- a/lib/widgets/price_reserve_widget.dart
+++ b/lib/widgets/price_reserve_widget.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:lba/res/colors.dart';
+import 'package:lba/widgets/reserve_bottom_sheet.dart';
class PriceReserveWidget extends StatelessWidget {
const PriceReserveWidget({super.key});
@@ -9,8 +10,7 @@ class PriceReserveWidget extends StatelessWidget {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 12),
- decoration: BoxDecoration(
- ),
+ decoration: const BoxDecoration(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
@@ -24,8 +24,7 @@ class PriceReserveWidget extends StatelessWidget {
Row(
children: [
Container(
- padding:
- const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
decoration: BoxDecoration(
color: LightAppColors.offerTimer,
borderRadius: BorderRadius.circular(4),
@@ -41,8 +40,8 @@ class PriceReserveWidget extends StatelessWidget {
),
const SizedBox(width: 8),
Row(
- children: [
- const Text(
+ children: const [
+ Text(
'2126.88',
style: TextStyle(
fontSize: 14,
@@ -50,8 +49,8 @@ class PriceReserveWidget extends StatelessWidget {
decoration: TextDecoration.lineThrough,
),
),
- SizedBox(width: 5,),
- const Text(
+ SizedBox(width: 5),
+ Text(
'(12%)',
style: TextStyle(
fontSize: 14,
@@ -86,10 +85,15 @@ class PriceReserveWidget extends StatelessWidget {
height: 50,
child: ElevatedButton(
onPressed: () {
- // TODO: Add reservation logic here
+ showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ backgroundColor: Colors.transparent,
+ builder: (context) => const ReserveBottomSheet(),
+ );
},
style: ElevatedButton.styleFrom(
- backgroundColor: LightAppColors.offerTimer,
+ backgroundColor: LightAppColors.offerTimer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
diff --git a/lib/widgets/reserve_bottom_sheet.dart b/lib/widgets/reserve_bottom_sheet.dart
new file mode 100644
index 0000000..8e07983
--- /dev/null
+++ b/lib/widgets/reserve_bottom_sheet.dart
@@ -0,0 +1,322 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:lba/gen/assets.gen.dart';
+import 'package:lba/res/colors.dart';
+import 'package:lba/screens/product/checkout.dart';
+
+class ReserveBottomSheet extends StatefulWidget {
+ const ReserveBottomSheet({super.key});
+
+ @override
+ State createState() => _ReserveBottomSheetState();
+}
+
+class _ReserveBottomSheetState extends State {
+ int _quantity = 1;
+ final double _pricePerItem = 27.900;
+ final bool _isHotDeal = false;
+
+ void _incrementQuantity() {
+ if (_isHotDeal) return;
+ setState(() {
+ _quantity++;
+ });
+ }
+
+ void _decrementQuantity() {
+ if (_quantity <= 1) return;
+ setState(() {
+ _quantity--;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final subtotal = _pricePerItem * _quantity;
+ const salesTax = 0.000;
+ final total = subtotal + salesTax;
+
+ return Container(
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)),
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Container(
+ width: 40,
+ height: 4,
+ margin: const EdgeInsets.symmetric(vertical: 10),
+ decoration: BoxDecoration(
+ color: Colors.grey[300],
+ borderRadius: BorderRadius.circular(10),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
+ child: Container(
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
+ decoration: BoxDecoration(
+ color: const Color.fromARGB(255, 234, 247, 238),
+ borderRadius: BorderRadius.circular(16),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ _buildHeader(),
+ const SizedBox(height: 16),
+ Divider(color: Colors.grey.shade300, thickness: 1),
+ const SizedBox(height: 16),
+ _buildPriceDetails(subtotal, salesTax, total),
+ const SizedBox(height: 24),
+ _buildFooter(),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildHeader() {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ SvgPicture.asset(Assets.icons.shop.path,
+ height: 22, color: Colors.grey.shade700),
+ const SizedBox(width: 10),
+ const Text(
+ "Al Rawabi Dairy Company L.L.C",
+ style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ const Padding(
+ padding: EdgeInsets.only(left: 4.0),
+ child: Text(
+ "Philadelphia Honey Pecan Cream Cheese Spread,\n7.5 oz Tub",
+ style:
+ TextStyle(fontSize: 14, color: Colors.black87, height: 1.4),
+ ),
+ ),
+ const SizedBox(height: 6),
+ const Padding(
+ padding: EdgeInsets.only(left: 4.0),
+ child: Text(
+ "Pickup Now - 10:00 PM Today",
+ style: TextStyle(fontSize: 13, color: Colors.grey),
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildPriceDetails(double subtotal, double salesTax, double total) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ const Text(
+ "Price Details",
+ style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16),
+ ),
+ Text(
+ "$_quantity Item",
+ style: const TextStyle(fontSize: 14, color: Colors.grey),
+ ),
+ ],
+ ),
+ const SizedBox(height: 12),
+ const DashedLine(),
+ const SizedBox(height: 12),
+ _priceRow("Subtotal", subtotal.toStringAsFixed(3)),
+ const SizedBox(height: 10),
+ _priceRow("Sales tax", salesTax.toStringAsFixed(3)),
+ const SizedBox(height: 12),
+ const DashedLine(),
+ const SizedBox(height: 12),
+ _priceRow("Total", total.toStringAsFixed(3), isTotal: true),
+ ],
+ );
+ }
+
+ Widget _buildFooter() {
+ return Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ _buildQuantitySelector(),
+ ElevatedButton(
+ onPressed: () {
+ Navigator.pop(context);
+ Navigator.of(context).push(
+ PageRouteBuilder(
+ pageBuilder: (context, animation, secondaryAnimation) =>
+ const CheckoutPage(),
+ transitionsBuilder:
+ (context, animation, secondaryAnimation, child) {
+ const begin = Offset(0.0, 1.0);
+ const end = Offset.zero;
+ const curve = Curves.easeInOut;
+
+ final tween = Tween(begin: begin, end: end)
+ .chain(CurveTween(curve: curve));
+
+ return SlideTransition(
+ position: animation.drive(tween),
+ child: child,
+ );
+ },
+ ),
+ );
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor: LightAppColors.offerTimer,
+ foregroundColor: Colors.white,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10),
+ ),
+ padding:
+ const EdgeInsets.symmetric(horizontal: 65, vertical: 10),
+ ),
+ child: const Text(
+ 'Reserve',
+ style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
+ ),
+ ),
+ ],
+ ),
+ if (_isHotDeal) ...[
+ const SizedBox(height: 16),
+ _buildWarningMessage(),
+ ]
+ ],
+ );
+ }
+
+ Widget _priceRow(String label, String amount, {bool isTotal = false}) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ label,
+ style: TextStyle(
+ fontSize: 15,
+ fontWeight: isTotal ? FontWeight.bold : FontWeight.normal,
+ color: Colors.grey.shade700,
+ ),
+ ),
+ Text(
+ "AED $amount",
+ style: TextStyle(
+ fontSize: 15,
+ fontWeight: isTotal ? FontWeight.bold : FontWeight.w500,
+ color: Colors.black,
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildQuantitySelector() {
+ final bool canDecrement = _quantity > 1;
+ final bool canIncrement = !_isHotDeal;
+
+ return Container(
+ height: 45,
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Row(
+ children: [
+ IconButton(
+ icon: Icon(Icons.remove,
+ color: canDecrement ? Colors.red : Colors.grey),
+ onPressed: _decrementQuantity,
+ splashRadius: 20,
+ ),
+ Text(
+ _quantity.toString(),
+ style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
+ ),
+ IconButton(
+ icon: Icon(Icons.add,
+ color: canIncrement ? Colors.red : Colors.grey),
+ onPressed: _incrementQuantity,
+ splashRadius: 20,
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildWarningMessage() {
+ return Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const Icon(Icons.error, color: Colors.orangeAccent, size: 20),
+ const SizedBox(width: 10),
+ Expanded(
+ child: RichText(
+ text: TextSpan(
+ style: TextStyle(
+ fontSize: 12.5,
+ color: Colors.grey.shade800,
+ fontFamily: 'Roboto'),
+ children: const [
+ TextSpan(text: "Due to the "),
+ TextSpan(
+ text: "Hot",
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ TextSpan(
+ text:
+ " label discount, you can only purchase or reserve one item."),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+class DashedLine extends StatelessWidget {
+ const DashedLine({super.key, this.height = 1, this.color = Colors.grey});
+ final double height;
+ final Color color;
+
+ @override
+ Widget build(BuildContext context) {
+ return LayoutBuilder(
+ builder: (BuildContext context, BoxConstraints constraints) {
+ final boxWidth = constraints.constrainWidth();
+ const dashWidth = 5.0;
+ final dashHeight = height;
+ final dashCount = (boxWidth / (2 * dashWidth)).floor();
+ return Flex(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ direction: Axis.horizontal,
+ children: List.generate(dashCount, (_) {
+ return SizedBox(
+ width: dashWidth,
+ height: dashHeight,
+ child: DecoratedBox(
+ decoration: BoxDecoration(color: color),
+ ),
+ );
+ }),
+ );
+ },
+ );
+ }
+}
\ No newline at end of file