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

803 lines
32 KiB
Dart

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;
}
}
}