last 16-10-2025
This commit is contained in:
commit
5fdfdd9302
146 files changed
+10525
No files matched your search
@@ -0,0 +1,577 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:wordpress/providers/api_controller.dart';
|
||||
import 'package:wordpress/viewmodel/all_product_model.dart';
|
||||
|
||||
class AllProductScreen extends ConsumerStatefulWidget {
|
||||
const AllProductScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<AllProductScreen> createState() => _AllProductScreenState();
|
||||
}
|
||||
|
||||
class _AllProductScreenState extends ConsumerState<AllProductScreen>
|
||||
with TickerProviderStateMixin {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final int _itemsPerPage = 10;
|
||||
int _currentPage = 1;
|
||||
List<AllProductModel> _displayedProducts = [];
|
||||
bool _isLoadingMore = false;
|
||||
|
||||
late AnimationController _fadeController;
|
||||
late AnimationController _slideController;
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeAnimations();
|
||||
_setupScrollListener();
|
||||
}
|
||||
|
||||
void _initializeAnimations() {
|
||||
_fadeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 800),
|
||||
vsync: this,
|
||||
);
|
||||
_slideController = AnimationController(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_slideAnimation =
|
||||
Tween<Offset>(begin: const Offset(0, 0.3), end: Offset.zero).animate(
|
||||
CurvedAnimation(parent: _slideController, curve: Curves.easeOutBack),
|
||||
);
|
||||
}
|
||||
|
||||
void _setupScrollListener() {
|
||||
_scrollController.addListener(() {
|
||||
if (_scrollController.position.pixels >=
|
||||
_scrollController.position.maxScrollExtent - 200) {
|
||||
_loadMoreProducts();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _loadMoreProducts() {
|
||||
if (_isLoadingMore) return;
|
||||
|
||||
final asyncProducts = ref.read(allProductProvider);
|
||||
asyncProducts.whenData((allProducts) {
|
||||
if (_displayedProducts.length < allProducts.length) {
|
||||
setState(() {
|
||||
_isLoadingMore = true;
|
||||
});
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
setState(() {
|
||||
final startIndex = _currentPage * _itemsPerPage;
|
||||
final endIndex = ((startIndex + _itemsPerPage) > allProducts.length)
|
||||
? allProducts.length
|
||||
: startIndex + _itemsPerPage;
|
||||
|
||||
_displayedProducts.addAll(
|
||||
allProducts.sublist(startIndex, endIndex),
|
||||
);
|
||||
_currentPage++;
|
||||
_isLoadingMore = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_fadeController.dispose();
|
||||
_slideController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final asyncProducts = ref.watch(allProductProvider);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
'All Products',
|
||||
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
backgroundColor: Colors.blue[600],
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
),
|
||||
body: asyncProducts.when(
|
||||
loading: () => _buildLoadingWidget(),
|
||||
error: (err, stack) => _buildErrorWidget(err),
|
||||
data: (products) {
|
||||
if (products.isEmpty) {
|
||||
return _buildEmptyWidget();
|
||||
}
|
||||
|
||||
// Initialize displayed products on first load
|
||||
if (_displayedProducts.isEmpty) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
_displayedProducts = products.take(_itemsPerPage).toList();
|
||||
});
|
||||
_fadeController.forward();
|
||||
_slideController.forward();
|
||||
});
|
||||
}
|
||||
|
||||
return _buildProductGrid(products);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingWidget() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TweenAnimationBuilder<double>(
|
||||
duration: const Duration(seconds: 1),
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
builder: (context, value, child) {
|
||||
return Transform.rotate(
|
||||
angle: value * 2 * 3.14159,
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
gradient: const LinearGradient(
|
||||
colors: [Colors.blue, Colors.purple],
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.shopping_bag,
|
||||
color: Colors.white,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
"Loading amazing products...",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorWidget(Object error) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.error_outline, size: 64, color: Colors.red[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Oops! Something went wrong',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey[800],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Error: $error',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
ref.invalidate(allProductProvider);
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Retry'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue[600],
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyWidget() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.shopping_bag_outlined, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'No products found',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey[800],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Check back later for new products',
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProductGrid(List<AllProductModel> allProducts) {
|
||||
return FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
childAspectRatio: 0.75,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
transitionBuilder: (child, animation) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 0.3),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: FadeTransition(opacity: animation, child: child),
|
||||
);
|
||||
},
|
||||
child: ProductCard(
|
||||
product: _displayedProducts[index],
|
||||
key: ValueKey(_displayedProducts[index].id),
|
||||
animationDelay: Duration(milliseconds: index * 100),
|
||||
),
|
||||
);
|
||||
}, childCount: _displayedProducts.length),
|
||||
),
|
||||
),
|
||||
if (_isLoadingMore ||
|
||||
_displayedProducts.length < allProducts.length)
|
||||
SliverToBoxAdapter(child: _buildLoadMoreWidget()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadMoreWidget() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Center(
|
||||
child: _isLoadingMore
|
||||
? Column(
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.blue[600]!,
|
||||
),
|
||||
strokeWidth: 2,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Loading more products...',
|
||||
style: TextStyle(color: Colors.grey[600], fontSize: 14),
|
||||
),
|
||||
],
|
||||
)
|
||||
: ElevatedButton.icon(
|
||||
onPressed: _loadMoreProducts,
|
||||
icon: const Icon(Icons.expand_more),
|
||||
label: const Text('Load More'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue[600],
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProductCard extends StatefulWidget {
|
||||
final AllProductModel product;
|
||||
final Duration animationDelay;
|
||||
|
||||
const ProductCard({
|
||||
super.key,
|
||||
required this.product,
|
||||
this.animationDelay = Duration.zero,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ProductCard> createState() => _ProductCardState();
|
||||
}
|
||||
|
||||
class _ProductCardState extends State<ProductCard>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _hoverController;
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<double> _elevationAnimation;
|
||||
bool _isVisible = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_hoverController = AnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.05).animate(
|
||||
CurvedAnimation(parent: _hoverController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_elevationAnimation = Tween<double>(begin: 4.0, end: 12.0).animate(
|
||||
CurvedAnimation(parent: _hoverController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
// Staggered animation entrance
|
||||
Future.delayed(widget.animationDelay, () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isVisible = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hoverController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedOpacity(
|
||||
opacity: _isVisible ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 600),
|
||||
child: AnimatedBuilder(
|
||||
animation: _hoverController,
|
||||
builder: (context, child) {
|
||||
return Transform.scale(
|
||||
scale: _scaleAnimation.value,
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => _hoverController.forward(),
|
||||
onTapUp: (_) => _hoverController.reverse(),
|
||||
onTapCancel: () => _hoverController.reverse(),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: _elevationAnimation.value,
|
||||
offset: Offset(0, _elevationAnimation.value / 2),
|
||||
spreadRadius: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [_buildProductImage(), _buildProductDetails()],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProductImage() {
|
||||
return Expanded(
|
||||
flex: 3,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: 'product_${widget.product.id}',
|
||||
child: Image.network(
|
||||
widget.product.images.isNotEmpty
|
||||
? widget.product.images.first.src
|
||||
: '',
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Container(
|
||||
color: Colors.grey[100],
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
value: loadingProgress.expectedTotalBytes != null
|
||||
? loadingProgress.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: null,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.blue[300]!,
|
||||
),
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) => Container(
|
||||
color: Colors.grey[100],
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.image_not_supported_outlined,
|
||||
color: Colors.grey[400],
|
||||
size: 32,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Image not available',
|
||||
style: TextStyle(color: Colors.grey[500], fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Gradient overlay
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.black.withOpacity(0.15),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProductDetails() {
|
||||
return Expanded(
|
||||
flex: 3,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
widget.product.name,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
height: 1.3,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"₹${widget.product.price}",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green[600],
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue[600],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.withOpacity(0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.add_shopping_cart,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,724 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:wordpress/providers/api_controller.dart';
|
||||
import 'package:wordpress/viewmodel/cart_model.dart';
|
||||
|
||||
// ✅ Checkout Screen with similar design to Cart Screen
|
||||
class CheckOutScreen extends ConsumerWidget {
|
||||
const CheckOutScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final cartAsync = ref.watch(cartProvider);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade50,
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
"Checkout",
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
shadowColor: Colors.black12,
|
||||
iconTheme: const IconThemeData(color: Colors.black87),
|
||||
systemOverlayStyle: SystemUiOverlayStyle.dark,
|
||||
actions: [
|
||||
cartAsync.when(
|
||||
data: (cartResponse) => cartResponse.cart.isNotEmpty
|
||||
? Container(
|
||||
margin: const EdgeInsets.only(right: 16),
|
||||
child: Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade100,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
"${cartResponse.cart.length} items",
|
||||
style: TextStyle(
|
||||
color: Colors.orange.shade800,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
loading: () => const SizedBox(),
|
||||
error: (_, __) => const SizedBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: cartAsync.when(
|
||||
data: (cartResponse) {
|
||||
if (cartResponse.cart.isEmpty) {
|
||||
return _buildEmptyCart(context);
|
||||
}
|
||||
|
||||
final items = cartResponse.cart.values.toList();
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// Header with item count
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 16,
|
||||
),
|
||||
color: Colors.white,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Review your order",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade100,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
"${items.length} items",
|
||||
style: TextStyle(
|
||||
color: Colors.orange.shade800,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Cart Items
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: items.length,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
separatorBuilder: (context, index) =>
|
||||
const SizedBox(height: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return _buildCheckoutItem(context, item);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Delivery Address Section
|
||||
_buildAddressSection(context),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Payment Method Section
|
||||
_buildPaymentSection(context),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Cart Summary
|
||||
_buildCheckoutSummary(context, items),
|
||||
|
||||
// Add extra space for bottom navigation
|
||||
const SizedBox(height: 100),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => Container(
|
||||
color: Colors.white,
|
||||
child: const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.orange),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
"Loading your order...",
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
error: (err, stack) => Container(
|
||||
color: Colors.white,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.error_outline, size: 64, color: Colors.red.shade300),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
"Something went wrong",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"Please try again later",
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey.shade600),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () => ref.invalidate(cartProvider),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text("Retry"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Bottom Checkout Button
|
||||
bottomNavigationBar: cartAsync.when(
|
||||
data: (cartResponse) {
|
||||
if (cartResponse.cart.isEmpty) return const SizedBox();
|
||||
|
||||
final items = cartResponse.cart.values.toList();
|
||||
final total = _calculateTotal(items);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, -4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SafeArea(
|
||||
child: SizedBox(
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("Processing payment..."),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
elevation: 3,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.payment),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"Pay ₹${total.toStringAsFixed(2)}",
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const SizedBox(),
|
||||
error: (_, __) => const SizedBox(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyCart(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade50,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.shopping_cart_outlined,
|
||||
size: 80,
|
||||
color: Colors.orange.shade300,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
"Your cart is empty",
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"Add items to checkout",
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey.shade600),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.shopping_bag_outlined),
|
||||
label: const Text("Continue Shopping"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
elevation: 2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCheckoutItem(BuildContext context, CartItem item) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
// Product Image/Icon
|
||||
Container(
|
||||
width: 70,
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Colors.orange.shade100, Colors.orange.shade50],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.shopping_bag_outlined,
|
||||
size: 32,
|
||||
color: Colors.orange.shade600,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// Product Info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Product Name
|
||||
Text(
|
||||
item.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Price and Quantity
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// Price
|
||||
Text(
|
||||
"₹${((double.tryParse(item.price) ?? 0) * item.quantity).toStringAsFixed(2)}",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.orange.shade700,
|
||||
),
|
||||
),
|
||||
|
||||
// Quantity
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
child: Text(
|
||||
"Qty: ${item.quantity}",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAddressSection(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Delivery Address",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
child: Text(
|
||||
"Change",
|
||||
style: TextStyle(
|
||||
color: Colors.orange.shade700,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_on_outlined,
|
||||
color: Colors.orange.shade600,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Home",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"123 Main Street, Apartment 4B, City, State - 123456",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"Phone: +91 9876543210",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPaymentSection(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Payment Method",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
child: Text(
|
||||
"Change",
|
||||
style: TextStyle(
|
||||
color: Colors.orange.shade700,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.credit_card,
|
||||
color: Colors.blue.shade700,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
"Credit/Debit Card",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Icon(Icons.check_circle, color: Colors.green.shade600),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCheckoutSummary(BuildContext context, List<CartItem> items) {
|
||||
double subtotal = 0;
|
||||
int totalItems = 0;
|
||||
|
||||
for (var item in items) {
|
||||
subtotal += (double.tryParse(item.price) ?? 0) * item.quantity;
|
||||
totalItems += item.quantity;
|
||||
}
|
||||
|
||||
final shipping = subtotal > 500 ? 0.0 : 50.0;
|
||||
final tax = subtotal * 0.18; // 18% GST
|
||||
final total = subtotal + shipping + tax;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Summary Header
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
"Order Summary",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"$totalItems items",
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey.shade600),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Price Breakdown
|
||||
_buildPriceRow("Subtotal", subtotal),
|
||||
const SizedBox(height: 8),
|
||||
_buildPriceRow(
|
||||
"Shipping",
|
||||
shipping,
|
||||
subtitle: shipping == 0 ? "Free shipping!" : null,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildPriceRow("GST (18%)", tax),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
const Divider(thickness: 1),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Total
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
"Total",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"₹${total.toStringAsFixed(2)}",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.orange.shade700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPriceRow(String label, double amount, {String? subtitle}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey.shade700),
|
||||
),
|
||||
if (subtitle != null)
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.green.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
amount == 0 ? "FREE" : "₹${amount.toStringAsFixed(2)}",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: amount == 0 ? Colors.green.shade600 : Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
double _calculateTotal(List<CartItem> items) {
|
||||
double subtotal = 0;
|
||||
for (var item in items) {
|
||||
subtotal += (double.tryParse(item.price) ?? 0) * item.quantity;
|
||||
}
|
||||
final shipping = subtotal > 500 ? 0.0 : 50.0;
|
||||
final tax = subtotal * 0.18;
|
||||
return subtotal + shipping + tax;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,802 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:wordpress/providers/api_controller.dart';
|
||||
import 'package:wordpress/utils/ftaf.dart';
|
||||
import 'package:wordpress/viewmodel/cart_screen.dart';
|
||||
|
||||
// Providers
|
||||
final selectedColorProvider = StateProvider<String?>((ref) => null);
|
||||
final selectedWeightProvider = StateProvider<String?>((ref) => null);
|
||||
final quantityProvider = StateProvider<int>((ref) => 1);
|
||||
final currentImageIndexProvider = StateProvider<int>((ref) => 0);
|
||||
final isFavoriteProvider = StateProvider<bool>((ref) => false);
|
||||
|
||||
class BigBasketDetailsScreen extends ConsumerWidget {
|
||||
final int productId;
|
||||
|
||||
const BigBasketDetailsScreen({super.key, required this.productId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final detailAsync = ref.watch(productDetailProvider(productId));
|
||||
final selectedColor = ref.watch(selectedColorProvider);
|
||||
final selectedWeight = ref.watch(selectedWeightProvider);
|
||||
final quantity = ref.watch(quantityProvider);
|
||||
final currentImageIndex = ref.watch(currentImageIndexProvider);
|
||||
final isFavorite = ref.watch(isFavoriteProvider);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
body: detailAsync.when(
|
||||
data: (product) {
|
||||
final images = product.images ?? [];
|
||||
String? mainImage = images.isNotEmpty ? images.first.src : null;
|
||||
|
||||
// Update main image based on selected color
|
||||
if (selectedColor != null && images.isNotEmpty) {
|
||||
final match = images.firstWhere(
|
||||
(img) =>
|
||||
(img.name != null &&
|
||||
img.name!.toLowerCase() == selectedColor.toLowerCase()) ||
|
||||
(img.alt != null &&
|
||||
img.alt!.toLowerCase() == selectedColor.toLowerCase()),
|
||||
orElse: () => images.first,
|
||||
);
|
||||
mainImage = match.src ?? mainImage;
|
||||
}
|
||||
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
// Custom App Bar with image
|
||||
SliverAppBar(
|
||||
expandedHeight: 350,
|
||||
pinned: true,
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
leading: Container(
|
||||
margin: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Container(
|
||||
margin: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
isFavorite ? Icons.favorite : Icons.favorite_border,
|
||||
color: isFavorite ? Colors.red : Colors.black,
|
||||
),
|
||||
onPressed: () {
|
||||
ref.read(isFavoriteProvider.notifier).state =
|
||||
!isFavorite;
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.share, color: Colors.black),
|
||||
onPressed: () {
|
||||
// Share functionality
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Container(
|
||||
color: Colors.white,
|
||||
child: images.isNotEmpty
|
||||
? PageView.builder(
|
||||
itemCount: images.length,
|
||||
onPageChanged: (index) {
|
||||
ref
|
||||
.read(currentImageIndexProvider.notifier)
|
||||
.state =
|
||||
index;
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.grey[100],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.network(
|
||||
images[index].src ?? '',
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
color: Colors.grey[200],
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.image_not_supported,
|
||||
size: 80,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.grey[200],
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.image_not_supported,
|
||||
size: 100,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Product Content
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Image indicators
|
||||
if (images.length > 1)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(
|
||||
images.length,
|
||||
(index) => Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: currentImageIndex == index
|
||||
? Colors.green[600]
|
||||
: Colors.grey[300],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Product Title & Brand
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
product.name ?? "Product Name",
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Price : ₹ ${product.price.toString()}',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green[50],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: Colors.green[200]!,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.star,
|
||||
size: 14,
|
||||
color: Colors.green[600],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
"4.2",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.green[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
"1,234 reviews",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Attributes Section
|
||||
if (product.attributes != null &&
|
||||
product.attributes!.isNotEmpty)
|
||||
_buildAttributesSection(
|
||||
product,
|
||||
ref,
|
||||
selectedColor,
|
||||
selectedWeight,
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Quantity Selector
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Text(
|
||||
"Quantity:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
if (quantity > 1) {
|
||||
ref
|
||||
.read(quantityProvider.notifier)
|
||||
.state--;
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: const Icon(
|
||||
Icons.remove,
|
||||
size: 18,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: Colors.grey[300]!,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
quantity.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
ref
|
||||
.read(quantityProvider.notifier)
|
||||
.state++;
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
size: 18,
|
||||
color: Colors.green[600],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Product Description
|
||||
if (product.shortDescription != null &&
|
||||
product.shortDescription!.isNotEmpty)
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Product Details",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
HtmlWidget(
|
||||
product.shortDescription!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[700],
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Delivery Info
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.blue[100]!),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.local_shipping,
|
||||
color: Colors.blue[700],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Free Delivery",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"Get it by tomorrow, 6:00 AM - 10:00 AM",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.assignment_return,
|
||||
color: Colors.blue[700],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Easy Return",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"7 days return policy",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 100), // Space for bottom buttons
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () =>
|
||||
const Scaffold(body: Center(child: CircularProgressIndicator())),
|
||||
error: (err, stack) =>
|
||||
Scaffold(body: Center(child: Text("Error: $err"))),
|
||||
),
|
||||
bottomNavigationBar: detailAsync.when(
|
||||
data: (product) => _buildBottomBar(
|
||||
context,
|
||||
ref,
|
||||
product,
|
||||
selectedColor,
|
||||
selectedWeight,
|
||||
quantity,
|
||||
),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
error: (err, stack) => const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAttributesSection(
|
||||
product,
|
||||
WidgetRef ref,
|
||||
String? selectedColor,
|
||||
String? selectedWeight,
|
||||
) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: product.attributes!.map<Widget>((attr) {
|
||||
final attrName = attr.name?.toLowerCase();
|
||||
|
||||
if (attrName == "color") {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Available Colors",
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
children: attr.options!.map<Widget>((option) {
|
||||
final isSelected = option == selectedColor;
|
||||
Color colorValue = _getColorFromName(option);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
ref.read(selectedColorProvider.notifier).state = option;
|
||||
},
|
||||
child: Container(
|
||||
width: 45,
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
color: colorValue,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? Colors.green[600]!
|
||||
: Colors.grey[400]!,
|
||||
width: isSelected ? 3 : 1,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: isSelected
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: colorValue == Colors.white
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
size: 20,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
);
|
||||
} else if (attrName == "weight" || attrName == "size") {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Select ${attr.name}",
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
children: attr.options!.map<Widget>((option) {
|
||||
final isSelected = option == selectedWeight;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
ref.read(selectedWeightProvider.notifier).state =
|
||||
option;
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? Colors.green[600] : Colors.white,
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? Colors.green[600]!
|
||||
: Colors.grey[400]!,
|
||||
width: 1.5,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
child: Text(
|
||||
option,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSelected ? Colors.white : Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomBar(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
product,
|
||||
String? selectedColor,
|
||||
String? selectedWeight,
|
||||
int quantity,
|
||||
) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, -5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green[600],
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 2,
|
||||
),
|
||||
onPressed: () {
|
||||
if (_validateSelection(
|
||||
context,
|
||||
selectedColor,
|
||||
selectedWeight,
|
||||
product,
|
||||
)) {
|
||||
// Handle Add to Cart
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Added $quantity x ${product.name} to cart",
|
||||
),
|
||||
backgroundColor: Colors.green[600],
|
||||
action: SnackBarAction(
|
||||
label: "View Cart",
|
||||
textColor: Colors.white,
|
||||
onPressed: () async {
|
||||
await CartButtonHelper.addToCartAndNavigate(
|
||||
context: context,
|
||||
ref: ref,
|
||||
product: product,
|
||||
quantity: quantity,
|
||||
selectedColor: selectedColor,
|
||||
selectedWeight: selectedWeight,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
"Add to Cart",
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _validateSelection(
|
||||
BuildContext context,
|
||||
String? selectedColor,
|
||||
String? selectedWeight,
|
||||
product,
|
||||
) {
|
||||
bool hasColorAttribute =
|
||||
product.attributes?.any(
|
||||
(attr) => attr.name?.toLowerCase() == "color",
|
||||
) ??
|
||||
false;
|
||||
bool hasWeightAttribute =
|
||||
product.attributes?.any(
|
||||
(attr) =>
|
||||
attr.name?.toLowerCase() == "weight" ||
|
||||
attr.name?.toLowerCase() == "size",
|
||||
) ??
|
||||
false;
|
||||
|
||||
if (hasColorAttribute && selectedColor == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("Please select a color"),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasWeightAttribute && selectedWeight == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("Please select size/weight"),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Color _getColorFromName(String colorName) {
|
||||
if (colorName.startsWith("#") && colorName.length == 7) {
|
||||
return Color(
|
||||
int.parse(colorName.substring(1, 7), radix: 16) + 0xFF000000,
|
||||
);
|
||||
}
|
||||
|
||||
switch (colorName.toLowerCase()) {
|
||||
case "red":
|
||||
return Colors.red;
|
||||
case "blue":
|
||||
return Colors.blue;
|
||||
case "green":
|
||||
return Colors.green;
|
||||
case "black":
|
||||
return Colors.black;
|
||||
case "white":
|
||||
return Colors.white;
|
||||
case "orange":
|
||||
return Colors.orange;
|
||||
case "yellow":
|
||||
return Colors.yellow;
|
||||
case "purple":
|
||||
return Colors.purple;
|
||||
case "pink":
|
||||
return Colors.pink;
|
||||
case "brown":
|
||||
return Colors.brown;
|
||||
default:
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,687 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:wordpress/providers/api_const.dart';
|
||||
import 'package:wordpress/providers/api_controller.dart';
|
||||
import 'package:wordpress/utils/app_assests.dart';
|
||||
import 'package:wordpress/view/all_product_screen.dart';
|
||||
import 'package:wordpress/view/details_screen.dart';
|
||||
import 'package:wordpress/viewmodel/cart_screen.dart';
|
||||
import 'package:wordpress/viewmodel/product_home_page_model.dart';
|
||||
|
||||
class HomeScreen extends ConsumerStatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends ConsumerState<HomeScreen>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _fadeController;
|
||||
late AnimationController _slideController;
|
||||
late AnimationController _scaleController;
|
||||
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
late Animation<double> _scaleAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Initialize animation controllers
|
||||
_fadeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 800),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_slideController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1000),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_scaleController = AnimationController(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
// Initialize animations
|
||||
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_slideAnimation =
|
||||
Tween<Offset>(begin: const Offset(0, 0.3), end: Offset.zero).animate(
|
||||
CurvedAnimation(parent: _slideController, curve: Curves.easeOutBack),
|
||||
);
|
||||
|
||||
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut),
|
||||
);
|
||||
|
||||
// Start animations
|
||||
_startAnimations();
|
||||
}
|
||||
|
||||
void _startAnimations() async {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
_fadeController.forward();
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
_slideController.forward();
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
_scaleController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fadeController.dispose();
|
||||
_slideController.dispose();
|
||||
_scaleController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String getFullImageUrl(String url) {
|
||||
if (url.startsWith("http")) {
|
||||
return url;
|
||||
}
|
||||
return "${ConstsApi.baseUrl}$url";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final homeAsync = ref.watch(productHomePageProvider);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
body: SafeArea(
|
||||
child: homeAsync.when(
|
||||
data: (data) {
|
||||
return SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ScaleTransition(
|
||||
scale: _scaleAnimation,
|
||||
child: _buildHeaderSection(),
|
||||
),
|
||||
// 🎯 Section 1: Animated Banner Carousel
|
||||
FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: _buildBannerSection(data.banners),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 🎯 Section 2: Animated Categories
|
||||
SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: _buildCategoriesSection(data.categories),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 🎯 Section 3: Animated Latest Products
|
||||
ScaleTransition(
|
||||
scale: _scaleAnimation,
|
||||
child: _buildLatestProductsSection(data.latest),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => _buildLoadingState(),
|
||||
error: (err, stack) => _buildErrorState(err),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 🎨 Banner Section with Animation
|
||||
Widget _buildBannerSection(List<BannerModel> banners) {
|
||||
if (banners.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 25),
|
||||
CarouselSlider(
|
||||
options: CarouselOptions(
|
||||
height: 160,
|
||||
autoPlay: true,
|
||||
enlargeCenterPage: true,
|
||||
viewportFraction: 0.9,
|
||||
autoPlayInterval: const Duration(seconds: 4),
|
||||
autoPlayAnimationDuration: const Duration(milliseconds: 800),
|
||||
autoPlayCurve: Curves.fastOutSlowIn,
|
||||
),
|
||||
items: banners.map((banner) {
|
||||
final url = getFullImageUrl(banner.image);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
color: Colors.grey[200], // fallback bg
|
||||
child: Image.network(
|
||||
url,
|
||||
width: double.infinity,
|
||||
height: 160,
|
||||
fit: BoxFit
|
||||
.fill, // fills both width & height // change to BoxFit.contain if you don’t want cropping
|
||||
errorBuilder: (context, error, stackTrace) => Image.network(
|
||||
'https://project2022.amrithaa.com/ecomdemo/wp-content/uploads/2025/08/product1.jpg',
|
||||
width: double.infinity,
|
||||
height: 160,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderSection() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Image.asset(AppAssets.logo, height: 60, width: 100),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Get.to(CartScreen());
|
||||
},
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 6,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(Icons.shopping_cart, color: Colors.blue),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 🎨 Categories Section with Animation
|
||||
Widget _buildCategoriesSection(List<CategoryModel> categories) {
|
||||
if (categories.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Shop by Category",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
height: 130,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: categories.length,
|
||||
itemBuilder: (context, index) {
|
||||
final category = categories[index];
|
||||
return TweenAnimationBuilder<double>(
|
||||
duration: Duration(milliseconds: 400 + (index * 100)),
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
builder: (context, value, child) {
|
||||
return Transform.scale(
|
||||
scale: value,
|
||||
child: Container(
|
||||
width: 100,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 80,
|
||||
width: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors
|
||||
.primaries[index % Colors.primaries.length]
|
||||
.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: Colors
|
||||
.primaries[index %
|
||||
Colors.primaries.length]
|
||||
.withOpacity(0.3),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: category.image != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
child: Image.network(
|
||||
getFullImageUrl(category.image!),
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder:
|
||||
(
|
||||
context,
|
||||
error,
|
||||
stackTrace,
|
||||
) => Icon(
|
||||
Icons.category,
|
||||
color:
|
||||
Colors.primaries[index %
|
||||
Colors.primaries.length],
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
Icons.category,
|
||||
color:
|
||||
Colors.primaries[index %
|
||||
Colors.primaries.length],
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
category.name,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 🎨 Latest Products Section with Animation
|
||||
Widget _buildLatestProductsSection(List<ProductModel> products) {
|
||||
if (products.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
"Latest Products",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => AllProductScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
"View All",
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
childAspectRatio: 0.75,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
itemCount: products.length > 6 ? 6 : products.length,
|
||||
itemBuilder: (context, index) {
|
||||
final product = products[index];
|
||||
return TweenAnimationBuilder<double>(
|
||||
duration: Duration(milliseconds: 500 + (index * 150)),
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
builder: (context, value, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(0, 30 * (1 - value)),
|
||||
child: Opacity(
|
||||
opacity: value,
|
||||
child: _buildProductCard(product),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 🎨 Product Card with Hover Effect
|
||||
Widget _buildProductCard(ProductModel product) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BigBasketDetailsScreen(productId: product.id),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Product Image
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(16),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Image.network(
|
||||
getFullImageUrl(product.image),
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) => Container(
|
||||
color: Colors.grey[100],
|
||||
child: const Icon(
|
||||
Icons.image_not_supported,
|
||||
color: Colors.grey,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Gradient overlay
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.black.withOpacity(0.1),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Product Details
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
product.name,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"₹${product.price}",
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.add_shopping_cart,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 🎨 Loading State with Animation
|
||||
Widget _buildLoadingState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TweenAnimationBuilder<double>(
|
||||
duration: const Duration(seconds: 1),
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
builder: (context, value, child) {
|
||||
return Transform.rotate(
|
||||
angle: value * 2 * 3.14159,
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
gradient: const LinearGradient(
|
||||
colors: [Colors.blue, Colors.purple],
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.shopping_bag,
|
||||
color: Colors.white,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
"Loading amazing products...",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 🎨 Error State
|
||||
Widget _buildErrorState(dynamic error) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TweenAnimationBuilder<double>(
|
||||
duration: const Duration(milliseconds: 800),
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
builder: (context, value, child) {
|
||||
return Transform.scale(
|
||||
scale: value,
|
||||
child: Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.red,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
"Oops! Something went wrong",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"Error: $error",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Retry logic
|
||||
ref.invalidate(productHomePageProvider);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
"Try Again",
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 🎨 Additional Animation Extensions for Enhanced UI
|
||||
extension AnimatedWidgetExtensions on Widget {
|
||||
Widget fadeInUp({
|
||||
Duration duration = const Duration(milliseconds: 600),
|
||||
Duration delay = Duration.zero,
|
||||
}) {
|
||||
return TweenAnimationBuilder<double>(
|
||||
duration: duration,
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
builder: (context, value, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(0, 20 * (1 - value)),
|
||||
child: Opacity(opacity: value, child: this),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget bounceIn({Duration duration = const Duration(milliseconds: 800)}) {
|
||||
return TweenAnimationBuilder<double>(
|
||||
duration: duration,
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
curve: Curves.elasticOut,
|
||||
builder: (context, value, child) {
|
||||
return Transform.scale(scale: value, child: this);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import 'package:animated_notch_bottom_bar/animated_notch_bottom_bar/animated_notch_bottom_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wordpress/view/home_screen.dart';
|
||||
import 'package:wordpress/viewmodel/cart_screen.dart';
|
||||
|
||||
class MainController extends StatefulWidget {
|
||||
const MainController({super.key});
|
||||
|
||||
@override
|
||||
State<MainController> createState() => _MainControllerState();
|
||||
|
||||
// 👇 Add static method to access from children
|
||||
static _MainControllerState? of(BuildContext context) =>
|
||||
context.findAncestorStateOfType<_MainControllerState>();
|
||||
}
|
||||
|
||||
class _MainControllerState extends State<MainController> {
|
||||
final PageController _pageController = PageController(initialPage: 0);
|
||||
|
||||
final NotchBottomBarController _controller = NotchBottomBarController(
|
||||
index: 0,
|
||||
);
|
||||
|
||||
void navigateToTab(int index) {
|
||||
_controller.jumpTo(index);
|
||||
_pageController.jumpToPage(index);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController.dispose();
|
||||
_controller.dispose(); // ✅ prevents AnimationController errors
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
extendBody: true,
|
||||
body: PageView(
|
||||
controller: _pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: const [
|
||||
HomeScreen(),
|
||||
CartScreen(),
|
||||
Center(child: Text("👤 Profile Page")),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: AnimatedNotchBottomBar(
|
||||
notchBottomBarController: _controller,
|
||||
color: Colors.white,
|
||||
notchColor: Colors.blue,
|
||||
showLabel: true,
|
||||
itemLabelStyle: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
showShadow: true,
|
||||
elevation: 8,
|
||||
kIconSize: 24,
|
||||
kBottomRadius: 24,
|
||||
removeMargins: false,
|
||||
bottomBarWidth: MediaQuery.of(context).size.width * 0.9,
|
||||
durationInMilliSeconds: 300,
|
||||
bottomBarHeight: 70,
|
||||
showTopRadius: true,
|
||||
showBottomRadius: true,
|
||||
bottomBarItems: const [
|
||||
BottomBarItem(
|
||||
inActiveItem: Icon(Icons.home, color: Colors.grey),
|
||||
activeItem: Icon(Icons.home, color: Colors.white),
|
||||
itemLabel: 'Home',
|
||||
),
|
||||
BottomBarItem(
|
||||
inActiveItem: Icon(Icons.shopping_cart, color: Colors.grey),
|
||||
activeItem: Icon(Icons.shopping_cart, color: Colors.white),
|
||||
itemLabel: 'Cart',
|
||||
),
|
||||
BottomBarItem(
|
||||
inActiveItem: Icon(Icons.person, color: Colors.grey),
|
||||
activeItem: Icon(Icons.person, color: Colors.white),
|
||||
itemLabel: 'Profile',
|
||||
),
|
||||
],
|
||||
onTap: (index) {
|
||||
_pageController.jumpToPage(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user