324 lines
10 KiB
Dart
324 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_svg/flutter_svg.dart';
|
|
import 'package:lba/gen/assets.gen.dart';
|
|
|
|
class Bestnearby extends StatefulWidget {
|
|
const Bestnearby({
|
|
super.key,
|
|
});
|
|
|
|
@override
|
|
State<Bestnearby> createState() => _BestnearbyState();
|
|
}
|
|
|
|
class _BestnearbyState extends State<Bestnearby>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _controller;
|
|
late List<Animation<double>> _staggeredAnimations;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_controller = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 1500),
|
|
);
|
|
|
|
// --- انیمیشنهای مرحلهای برای هر بخش ---
|
|
_staggeredAnimations = List.generate(3, (index) {
|
|
return Tween<double>(begin: 0.0, end: 1.0).animate(
|
|
CurvedAnimation(
|
|
parent: _controller,
|
|
curve: Interval(
|
|
(0.2 * index),
|
|
(0.6 + 0.2 * index).clamp(0.0, 1.0),
|
|
curve: Curves.easeOutCubic,
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
_controller.forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.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.5),
|
|
end: Offset.zero,
|
|
).animate(_staggeredAnimations[index]),
|
|
child: child,
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.white,
|
|
elevation: 2,
|
|
shadowColor: Colors.grey,
|
|
automaticallyImplyLeading: false,
|
|
leading: InkWell(
|
|
onTap: () => Navigator.pop(context),
|
|
child: SvgPicture.asset(
|
|
Assets.icons.back.path,
|
|
fit: BoxFit.scaleDown,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
title: const Text("Top Deals & Stores",
|
|
style: TextStyle(fontSize: 18, color: Colors.black)),
|
|
),
|
|
body: SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
_buildAnimatedSection(NearbyItems(detailsType: 0), 0),
|
|
_buildAnimatedSection(NearbyItems(detailsType: 1), 1),
|
|
_buildAnimatedSection(NearbyItems(detailsType: 2), 2),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class NearbyItems extends StatefulWidget {
|
|
const NearbyItems({super.key, required this.detailsType});
|
|
|
|
final int detailsType;
|
|
|
|
@override
|
|
State<NearbyItems> createState() => _NearbyItemsState();
|
|
}
|
|
|
|
class _NearbyItemsState extends State<NearbyItems>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _listController;
|
|
late List<Animation<double>> _itemAnimations;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_listController = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 1000),
|
|
);
|
|
|
|
_itemAnimations = List.generate(5, (index) {
|
|
return Tween<double>(begin: 0.0, end: 1.0).animate(
|
|
CurvedAnimation(
|
|
parent: _listController,
|
|
curve: Interval(
|
|
(0.15 * index),
|
|
(0.5 + 0.15 * index).clamp(0.0, 1.0),
|
|
curve: Curves.easeInOut,
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
_listController.forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_listController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Widget _buildAnimatedItem(Widget child, int index) {
|
|
return FadeTransition(
|
|
opacity: _itemAnimations[index],
|
|
child: SlideTransition(
|
|
position: Tween<Offset>(
|
|
begin: const Offset(0.3, 0.0),
|
|
end: Offset.zero,
|
|
).animate(_itemAnimations[index]),
|
|
child: child,
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Column(
|
|
children: [
|
|
DividerTitle(detailsType: widget.detailsType),
|
|
SizedBox(
|
|
height: widget.detailsType == 0
|
|
? 180
|
|
: widget.detailsType == 1
|
|
? 240
|
|
: widget.detailsType == 2
|
|
? 240
|
|
: 200,
|
|
child: ListView.builder(
|
|
itemCount: 5,
|
|
scrollDirection: Axis.horizontal,
|
|
itemBuilder: (context, index) {
|
|
return _buildAnimatedItem(
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(height: 7),
|
|
Container(
|
|
width: 120,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(12),
|
|
color: Colors.grey[200],
|
|
),
|
|
child: Image.asset(
|
|
Assets.images.topDealsAndStores.path,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
Text(
|
|
"Dubai Outlet Mall",
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
SizedBox(height: 5),
|
|
if (widget.detailsType == 0)
|
|
Row(
|
|
children: [
|
|
SvgPicture.asset(Assets.icons.routing.path),
|
|
SizedBox(width: 4),
|
|
Text(
|
|
"1.2 km away",
|
|
style: TextStyle(fontSize: 12),
|
|
),
|
|
],
|
|
)
|
|
else if (widget.detailsType == 1)
|
|
Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(height: 2),
|
|
Row(
|
|
children: [
|
|
SvgPicture.asset(
|
|
Assets.icons.category2.path),
|
|
SizedBox(width: 4),
|
|
Text("Stationery store"),
|
|
],
|
|
),
|
|
SizedBox(height: 5),
|
|
Row(
|
|
children: [
|
|
SvgPicture.asset(
|
|
Assets.icons.icRoundLocalOffer.path,
|
|
),
|
|
SizedBox(width: 4),
|
|
Text(
|
|
"22 - 35% off",
|
|
style: TextStyle(color: Colors.red),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 5),
|
|
Row(
|
|
children: [
|
|
SvgPicture.asset(Assets.icons.routing.path),
|
|
SizedBox(width: 4),
|
|
Text("3.6 km away"),
|
|
],
|
|
),
|
|
],
|
|
)
|
|
else if (widget.detailsType == 2)
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(height: 2),
|
|
Row(
|
|
children: [
|
|
SvgPicture.asset(Assets.icons.shop2.path),
|
|
SizedBox(width: 4),
|
|
Text("Jumbo Electronics "),
|
|
],
|
|
),
|
|
SizedBox(height: 5),
|
|
Row(
|
|
children: [
|
|
SvgPicture.asset(
|
|
Assets.icons.icRoundLocalOffer.path,
|
|
),
|
|
SizedBox(width: 4),
|
|
Text(
|
|
"22 - 35% off",
|
|
style: TextStyle(color: Colors.red),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 5),
|
|
Row(
|
|
children: [
|
|
SvgPicture.asset(Assets.icons.routing.path),
|
|
SizedBox(width: 4),
|
|
Text("3.6 km away"),
|
|
],
|
|
),
|
|
],
|
|
)
|
|
],
|
|
),
|
|
),
|
|
index,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class DividerTitle extends StatelessWidget {
|
|
const DividerTitle({
|
|
super.key,
|
|
required this.detailsType,
|
|
});
|
|
|
|
final int detailsType;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
detailsType == 0
|
|
? "Highest Discount Areas"
|
|
: detailsType == 1
|
|
? "Highest Discount Areas"
|
|
: "Best Deal, Don't Miss",
|
|
style: TextStyle(fontSize: 15),
|
|
),
|
|
const SizedBox(width: 8),
|
|
const Expanded(
|
|
child: Divider(color: Colors.grey, thickness: 2),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |