last 16-10-2025

This commit is contained in:
mageshwaran committed 2025-10-16 11:27:27 +05:30
commit 5fdfdd9302
146 files changed
+10525

No files matched your search

+577
View File
@@ -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,
),
),
],
),
],
),
),
);
}
}
+724
View File
@@ -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;
}
}
+802
View File
@@ -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;
}
}
}
+687
View File
@@ -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);
},
);
}
}
+95
View File
@@ -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);
},
),
),
);
}
}