proxybuy-flutter/lib/screens/mains/discover/discover.dart

1021 lines
31 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:dots_indicator/dots_indicator.dart';
import 'package:lba/gen/assets.gen.dart';
import 'package:lba/res/colors.dart';
import 'package:lba/widgets/customBottomSheet.dart';
import 'package:lba/widgets/remainingTime.dart';
import 'package:lba/widgets/search_bar.dart';
class Discover extends StatefulWidget {
const Discover({super.key});
@override
State<Discover> createState() => _DiscoverState();
}
class _DiscoverState extends State<Discover> with TickerProviderStateMixin {
final PageController _pageController = PageController();
double _currentPage = 0;
late AnimationController _staggeredController;
late List<Animation<double>> _staggeredAnimations;
final List<String> categoryIcons = [
Assets.icons.stashStarsLight.path,
Assets.icons.shoppingCart.path,
Assets.icons.elementEqual.path,
Assets.icons.shop.path,
Assets.icons.game.path,
Assets.icons.receiptDiscount.path,
];
@override
void initState() {
super.initState();
_pageController.addListener(() {
if (_pageController.hasClients) {
setState(() {
_currentPage = _pageController.page!;
});
}
});
_staggeredController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
);
final int itemCount = 8;
_staggeredAnimations = List.generate(itemCount, (index) {
return Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _staggeredController,
curve: Interval(
(0.1 * index),
(0.5 + 0.1 * index).clamp(0.0, 1.0),
curve: Curves.easeOutCubic,
),
),
);
});
_staggeredController.forward();
}
@override
void dispose() {
_pageController.dispose();
_staggeredController.dispose();
super.dispose();
}
Widget _buildAnimatedSection(Widget child, int index) {
return FadeTransition(
opacity: _staggeredAnimations[index],
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 0.3),
end: Offset.zero,
).animate(_staggeredAnimations[index]),
child: child,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: _buildAppBar(),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildAnimatedSection(
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Row(
children: [
const Expanded(child: SearchBarWidget()),
const SizedBox(width: 10),
_buildFilterButton(),
],
),
),
0,
),
const SizedBox(height: 16),
_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),
const SizedBox(height: 12),
_buildAnimatedSection(_buildTopOffersSection(), 4),
const SizedBox(height: 24),
_buildAnimatedSection(_buildSectionTitle("Flash Sale"), 5),
const SizedBox(height: 12),
_buildAnimatedSection(_buildFlashSaleSection(), 6),
const SizedBox(height: 24),
_buildAnimatedSection(_buildSectionTitle("Special Discount"), 7),
const SizedBox(height: 12),
_buildAnimatedSection(_buildSpecialDiscountSection(), 1),
const SizedBox(height: 24),
_buildAnimatedSection(_buildSectionTitle("Seasonal Discount"), 2),
const SizedBox(height: 12),
_buildAnimatedSection(_buildSeasonalDiscountSection(), 3),
const SizedBox(height: 24),
_buildAnimatedSection(_buildSectionTitle("Occasion Specials"), 4),
const SizedBox(height: 12),
_buildAnimatedSection(_buildCraftingSomethingSection(), 5),
const SizedBox(height: 24),
_buildAnimatedSection(_buildSectionTitle("First Purchase Discount"), 6),
const SizedBox(height: 12),
_buildAnimatedSection(_buildFirstPurchaseSection(), 7),
const SizedBox(height: 100),
],
),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
elevation: 0,
backgroundColor: Colors.white,
title: Row(
children: [
SvgPicture.asset(Assets.icons.lBALogo.path, height: 32),
const SizedBox(width: 8),
const Text(
"Proxibuy",
style: TextStyle(
color: LightAppColors.hintTitle,
fontWeight: FontWeight.normal,
fontSize: 24,
),
),
],
),
actions: [
IconButton(
icon: SvgPicture.asset(Assets.icons.notificationBing.path),
onPressed: () {},
),
const SizedBox(width: 8),
],
);
}
Widget _buildFilterButton() {
return Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: const Color.fromARGB(255, 14, 63, 102),
borderRadius: BorderRadius.circular(12),
),
child: IconButton(
icon: SvgPicture.asset(Assets.icons.sort.path, color: Colors.white),
onPressed: () {
CustomBottomSheet.show(context, [
"Food & Dining",
"Entertainment & Leisure",
"Health & Fitness",
"Travel & Transportation",
]);
},
padding: const EdgeInsets.all(8),
),
);
}
Widget _buildCategoryIcons() {
return SizedBox(
height: 60,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: categoryIcons.length,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemBuilder: (context, index) {
final isFirstIcon = index == 0;
final backgroundColor = isFirstIcon
? const Color.fromRGBO(186, 222, 251, 1)
: const Color(0xFFF3F4F6);
final iconColor = isFirstIcon
? const Color.fromARGB(255, 14, 63, 102)
: Colors.grey.shade600;
return Container(
width: 60,
height: 60,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(12),
),
child: SvgPicture.asset(categoryIcons[index], color: iconColor),
);
},
separatorBuilder: (context, index) => const SizedBox(width: 12),
),
);
}
Widget _buildTopOffersSection() {
return Column(
children: [
SizedBox(
height: 180,
child: PageView(
controller: _pageController,
children: [
_buildOfferBanner(),
_buildOfferBanner(),
_buildOfferBanner(),
],
),
),
const SizedBox(height: 12),
DotsIndicator(
dotsCount: 3,
position: _currentPage,
decorator: DotsDecorator(
color: Colors.grey.shade300,
activeColor: LightAppColors.primary,
size: const Size.square(8.0),
activeSize: const Size(20.0, 8.0),
activeShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
),
),
),
],
);
}
Widget _buildOfferBanner() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(15),
child: Image.asset(
Assets.images.image.path,
height: 180,
width: double.infinity,
fit: BoxFit.cover,
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Colors.black.withOpacity(0.6),
Colors.black.withOpacity(0.1),
],
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.8),
borderRadius: BorderRadius.circular(8),
),
child: const Text(
"65% OFF",
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 8),
const Text(
"NEW COLLECTION",
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Row(
children: [
_buildTimeBox("12"),
_buildTimeSeparator(),
_buildTimeBox("25"),
_buildTimeSeparator(),
_buildTimeBox("14"),
],
),
],
),
),
],
),
);
}
Widget _buildTimeBox(String time) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
),
child: Text(
time,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
);
}
Widget _buildTimeSeparator() {
return const Padding(
padding: EdgeInsets.symmetric(horizontal: 4.0),
child: Text(
":",
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
);
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.normal),
),
const SizedBox(width: 8),
const Expanded(child: Divider(color: Colors.grey, thickness: 1)),
],
),
);
}
Widget _buildFlashSaleSection() {
return SizedBox(
height: 310,
child: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
SizedBox(
width: 250,
child: FlashSaleCard(
imagePath: Assets.images.media.path,
title: "Amul Cheese Slices",
location: "Fresno (750m away)",
originalPrice: "70",
discountedPrice: "53",
discountPercent: "13",
expiryTimeString: DateTime.now()
.add(const Duration(hours: 8, minutes: 35))
.millisecondsSinceEpoch
.toString(),
categoryIconPath: Assets.icons.phCheese.path,
),
),
const SizedBox(width: 16),
SizedBox(
width: 250,
child: FlashSaleCard(
imagePath: Assets.images.wp1929534FastFoodWallpapers1.path,
title: "Tulip Luncheon Meat",
location: "Fresno (2km away)",
originalPrice: "370",
discountedPrice: "194",
discountPercent: "50",
expiryTimeString: DateTime.now()
.add(const Duration(hours: 12, minutes: 45))
.millisecondsSinceEpoch
.toString(),
categoryIconPath: Assets.icons.shop.path,
),
),
],
),
);
}
Widget _buildSpecialDiscountSection() {
return Container(
height: 180,
margin: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
image: DecorationImage(
image: AssetImage(Assets.images.topDealsAndStores.path),
fit: BoxFit.cover,
),
),
);
}
Widget _buildSeasonalDiscountSection() {
final List<Map<String, String>> seasonalItems = [
{
"title": "Boots",
"brand": "Columbia Sportswear",
"discount": "22 - 35% off",
"imagePath": Assets.images.image.path,
},
{
"title": "Hoodie",
"brand": "The North Face",
"discount": "22 - 35% off",
"imagePath": Assets.images.topDealsAndStores.path,
},
{
"title": "Hats",
"brand": "Patagonia",
"discount": "10 - 15% off",
"imagePath": Assets.images.image.path,
},
{
"title": "Jacket",
"brand": "Arc'teryx",
"discount": "20% off",
"imagePath": Assets.images.topDealsAndStores.path,
},
{
"title": "Backpack",
"brand": "Osprey",
"discount": "15% off",
"imagePath": Assets.images.image.path,
},
{
"title": "Gloves",
"brand": "Nike",
"discount": "Up to 40% off",
"imagePath": Assets.images.topDealsAndStores.path,
},
];
final screenWidth = MediaQuery.of(context).size.width;
final cardWidth = screenWidth * 0.8;
final int itemCount = (seasonalItems.length / 2).ceil();
return SizedBox(
height: 110 * 2 + 16,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: itemCount,
itemBuilder: (context, index) {
final topIndex = index * 2;
final bottomIndex = topIndex + 1;
final topCard = SeasonalDiscountCard(
title: seasonalItems[topIndex]['title']!,
brand: seasonalItems[topIndex]['brand']!,
discount: seasonalItems[topIndex]['discount']!,
imagePath: seasonalItems[topIndex]['imagePath']!,
width: cardWidth,
);
Widget bottomCard;
if (bottomIndex < seasonalItems.length) {
bottomCard = SeasonalDiscountCard(
title: seasonalItems[bottomIndex]['title']!,
brand: seasonalItems[bottomIndex]['brand']!,
discount: seasonalItems[bottomIndex]['discount']!,
imagePath: seasonalItems[bottomIndex]['imagePath']!,
width: cardWidth,
);
} else {
bottomCard = SizedBox(width: cardWidth); // Placeholder
}
return Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
topCard,
const SizedBox(height: 16),
bottomCard,
],
),
);
},
),
);
}
Widget _buildCraftingSomethingSection() {
return Container(
height: 120,
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
image: DecorationImage(
image: AssetImage(Assets.images.image.path),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.4),
BlendMode.darken,
),
),
),
child: const Center(
child: Text(
"Crafting something for you\nTell us your birthday and unlock special Dineout treats!",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
);
}
Widget _buildFirstPurchaseSection() {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16.0),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFE3F2FD),
borderRadius: BorderRadius.circular(15),
),
child: Row(
children: [
FirstPurchaseCard(
title: "McDonald's",
category: "Fast Food",
discount: "Up to 25% Off",
rating: 4.2,
imagePath: Assets.images.wp1929534FastFoodWallpapers1.path,
),
const SizedBox(width: 16),
FirstPurchaseCard(
title: "Cafe Monarch",
category: "Cafe",
discount: "Up to 25% Off",
rating: 4.9,
imagePath: Assets.images.media.path,
),
// For testing with more cards:
// const SizedBox(width: 16),
// FirstPurchaseCard(
// title: "Another Cafe",
// category: "Cafe",
// discount: "15% Off",
// rating: 4.5,
// imagePath: Assets.images.media.path,
// ),
],
),
),
);
}
}
class FlashSaleCard extends StatelessWidget {
final String imagePath;
final String title;
final String location;
final String originalPrice;
final String discountedPrice;
final String discountPercent;
final String expiryTimeString;
final String categoryIconPath;
const FlashSaleCard({
super.key,
required this.imagePath,
required this.title,
required this.location,
required this.originalPrice,
required this.discountedPrice,
required this.discountPercent,
required this.expiryTimeString,
required this.categoryIconPath,
});
@override
Widget build(BuildContext context) {
final timer =
RemainingTime()
..initializeFromExpiry(expiryTimeString: expiryTimeString);
return Container(
decoration: BoxDecoration(
color: LightAppColors.cardBackground,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: SvgPicture.asset(
Assets.icons.timer.path,
color: LightAppColors.offerTimer,
height: 20,
),
),
const SizedBox(width: 10),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Limited time deal",
style: TextStyle(
color: Colors.black87,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
ValueListenableBuilder<int>(
valueListenable: timer.remainingSeconds,
builder:
(context, _, __) => Text(
timer.formatTime(),
style: const TextStyle(
color: LightAppColors.offerTimer,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 5,)
],
),
],
),
),
Image.asset(
imagePath,
height: 100,
width: double.infinity,
fit: BoxFit.cover,
),
Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
SvgPicture.asset(
categoryIconPath,
color: Colors.grey.shade700,
width: 18,
),
const SizedBox(width: 8),
Expanded(
child: Text(
title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
],
),
const SizedBox(height: 6),
Row(
children: [
SvgPicture.asset(
Assets.icons.location.path,
color: Colors.grey.shade700,
width: 18,
),
const SizedBox(width: 4),
Text(
location,
style: const TextStyle(color: LightAppColors.offerCardDetail, fontSize: 12),
),
],
),
const SizedBox(height: 6),
Row(
children: [
SvgPicture.asset(
Assets.icons.coin.path,
color: Colors.grey.shade700,
width: 18,
),
const SizedBox(width: 4),
Text.rich(
TextSpan(
style: const TextStyle(
fontSize: 12,
color: LightAppColors.offerCardDetail ,
),
children: [
TextSpan(
text: '$originalPrice\$',
style: const TextStyle(
decoration: TextDecoration.lineThrough,
),
),
TextSpan(
text: ' - $discountedPrice\$',
style: const TextStyle(
color: LightAppColors.offerCardDetail,
fontWeight: FontWeight.normal,
fontSize: 14,
decoration: TextDecoration.none,
),
),
],
),
),
const SizedBox(width: 10,),
Text(
'($discountPercent% off)',
style: const TextStyle(
color: Colors.green,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 20),
Center(
child: SizedBox(
width: 150,
height: 38,
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: Colors.green,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
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),
),
],
),
),
),
),
),
],
),
),
],
),
);
}
}
class SeasonalDiscountCard extends StatelessWidget {
final String title;
final String brand;
final String discount;
final String imagePath;
final double width;
const SeasonalDiscountCard({
super.key,
required this.title,
required this.brand,
required this.discount,
required this.imagePath,
required this.width,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: 110,
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200),
),
child: Row(
children: [
Expanded(
flex: 2,
child: Container(
color: const Color(0xFFF8F9FA),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildDetailRow(
icon: Assets.icons.winter.path,
text: title,
iconColor: const Color.fromARGB(255, 23, 107, 173),
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
_buildDetailRow(
icon: Assets.icons.shop.path,
text: brand,
iconColor: Colors.grey.shade600,
textStyle: const TextStyle(
color: Colors.black,
fontSize: 12,
),
),
_buildDetailRow(
icon: Assets.icons.icRoundLocalOffer.path,
text: discount,
iconColor: Colors.grey.shade600,
textStyle: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.normal,
fontSize: 14,
),
),
],
),
),
),
),
Expanded(
flex: 1,
child: Image.asset(
imagePath,
height: double.infinity,
fit: BoxFit.cover,
),
),
],
),
),
),
);
}
Widget _buildDetailRow({
required String icon,
required String text,
required Color iconColor,
required TextStyle textStyle,
}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: SvgPicture.asset(
icon,
color: iconColor,
width: 20,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
text,
style: textStyle,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
),
],
);
}
}
class FirstPurchaseCard extends StatelessWidget {
final String title;
final String category;
final String discount;
final double rating;
final String imagePath;
const FirstPurchaseCard({
super.key,
required this.title,
required this.category,
required this.discount,
required this.rating,
required this.imagePath,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 160,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: Image.asset(
imagePath,
height: 120,
width: double.infinity,
fit: BoxFit.cover,
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
const SizedBox(height: 5),
Text(
category,
style: const TextStyle(color: LightAppColors.nearbyPopuphint, fontSize: 14),
),
const SizedBox(height: 5),
Text(
discount,
style: const TextStyle(
fontWeight: FontWeight.w500,
color: LightAppColors.nearbyPopuphint
),
),
const SizedBox(height: 5),
Row(
children: [
SvgPicture.asset(Assets.icons.star.path, width: 16),
const SizedBox(width: 4),
Text(
rating.toString(),
style: const TextStyle(
color: LightAppColors.nearbyPopuphint,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
),
],
),
);
}
}