wordpress/lib/view/home_screen.dart
2025-10-16 11:27:27 +05:30

688 lines
23 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 dont 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);
},
);
}
}