170 lines
7.4 KiB
Dart
170 lines
7.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'services/cart_service.dart';
|
|
|
|
class CheckoutPage extends StatefulWidget {
|
|
const CheckoutPage({super.key});
|
|
|
|
@override
|
|
State<CheckoutPage> createState() => _CheckoutPageState();
|
|
}
|
|
|
|
class _CheckoutPageState extends State<CheckoutPage> {
|
|
final CartService _cartService = CartService();
|
|
final _formKey = GlobalKey<FormState>();
|
|
bool _isLoading = false;
|
|
|
|
// To store mock final total from cart page
|
|
late double _finalTotal;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Calculate final total once when page loads
|
|
_finalTotal = _cartService.totalPrice +
|
|
(_cartService.totalPrice * 0.07) + // Mock tax
|
|
(_cartService.totalPrice > 50 ? 0 : 5.00); // Mock shipping
|
|
|
|
// If cart is empty when navigating here, redirect.
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (_cartService.items.isEmpty && mounted) {
|
|
Navigator.pushNamedAndRemoveUntil(context, '/home', (route) => false);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Your cart is empty. Cannot proceed to checkout.')),
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
void _placeOrder() async {
|
|
if (_formKey.currentState!.validate()) {
|
|
_formKey.currentState!.save();
|
|
setState(() => _isLoading = true);
|
|
|
|
await Future.delayed(const Duration(seconds: 2)); // Simulate network call
|
|
|
|
final orderTotal = _finalTotal; // Use the calculated final total
|
|
_cartService.clearCart();
|
|
|
|
setState(() => _isLoading = false);
|
|
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Mock order placed successfully for \$${orderTotal.toStringAsFixed(2)}!'),
|
|
duration: const Duration(seconds: 3),
|
|
backgroundColor: Theme.of(context).colorScheme.primary, // Or a specific success color
|
|
),
|
|
);
|
|
Navigator.pushNamedAndRemoveUntil(context, '/home', (route) => false);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final cartItems = _cartService.items;
|
|
|
|
// This check is a fallback, initState handles initial redirection
|
|
if (cartItems.isEmpty && !_isLoading) {
|
|
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
|
}
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Checkout'),
|
|
centerTitle: true,
|
|
),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Text('Order Summary', style: theme.textTheme.headlineSmall),
|
|
const SizedBox(height: 8),
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Column(
|
|
children: [
|
|
...cartItems.map((item) => ListTile(
|
|
leading: Image.network(item.product.imageUrl, width: 40, height: 40, fit: BoxFit.cover, errorBuilder: (c,o,s) => const Icon(Icons.image)),
|
|
title: Text('${item.product.name} (x${item.quantity})', style: theme.textTheme.bodyMedium),
|
|
trailing: Text('\$${item.subtotal.toStringAsFixed(2)}', style: theme.textTheme.bodyMedium),
|
|
)),
|
|
const Divider(height: 20, thickness: 0.5),
|
|
ListTile(
|
|
title: Text('Subtotal', style: theme.textTheme.titleSmall),
|
|
trailing: Text('\$${_cartService.totalPrice.toStringAsFixed(2)}', style: theme.textTheme.titleSmall),
|
|
),
|
|
ListTile(
|
|
title: Text('Est. Tax (7%)', style: theme.textTheme.titleSmall),
|
|
trailing: Text('\$${(_cartService.totalPrice * 0.07).toStringAsFixed(2)}', style: theme.textTheme.titleSmall),
|
|
),
|
|
ListTile(
|
|
title: Text('Shipping', style: theme.textTheme.titleSmall),
|
|
trailing: Text(_cartService.totalPrice > 50 ? 'FREE' : '\$5.00', style: theme.textTheme.titleSmall),
|
|
),
|
|
const Divider(height: 20, thickness: 1),
|
|
ListTile(
|
|
title: Text('Total Amount', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
|
|
trailing: Text('\$${_finalTotal.toStringAsFixed(2)}', style: theme.textTheme.titleLarge?.copyWith(color: theme.colorScheme.primary, fontWeight: FontWeight.bold)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
Text('Shipping Information (Mock)', style: theme.textTheme.headlineSmall),
|
|
const SizedBox(height: 12),
|
|
_buildTextField(label: 'Full Name', icon: Icons.person_outline, validator: (val) => val!.isEmpty ? 'Enter full name' : null),
|
|
const SizedBox(height: 12),
|
|
_buildTextField(label: 'Address Line 1', icon: Icons.home_outlined, validator: (val) => val!.isEmpty ? 'Enter address' : null),
|
|
const SizedBox(height: 12),
|
|
_buildTextField(label: 'City', icon: Icons.location_city_outlined, validator: (val) => val!.isEmpty ? 'Enter city' : null),
|
|
const SizedBox(height: 12),
|
|
_buildTextField(label: 'Postal Code', icon: Icons.markunread_mailbox_outlined, keyboardType: TextInputType.number, validator: (val) => val!.isEmpty ? 'Enter postal code' : null),
|
|
const SizedBox(height: 24),
|
|
|
|
Text('Payment Details (Mock)', style: theme.textTheme.headlineSmall),
|
|
const SizedBox(height: 12),
|
|
_buildTextField(label: 'Card Number', icon: Icons.credit_card_outlined, keyboardType: TextInputType.number, validator: (val) => val!.isEmpty ? 'Enter card number' : null),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
Expanded(child: _buildTextField(label: 'Expiry Date (MM/YY)', icon: Icons.calendar_today_outlined, keyboardType: TextInputType.datetime, validator: (val) => val!.isEmpty ? 'Enter expiry' : null)),
|
|
const SizedBox(width: 12),
|
|
Expanded(child: _buildTextField(label: 'CVV', icon: Icons.lock_outline, keyboardType: TextInputType.number, validator: (val) => val!.isEmpty ? 'Enter CVV' : null)),
|
|
],
|
|
),
|
|
const SizedBox(height: 32),
|
|
_isLoading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: ElevatedButton(
|
|
onPressed: _placeOrder,
|
|
child: const Text('Place Mock Order'),
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTextField({required String label, required IconData icon, TextInputType? keyboardType, required FormFieldValidator<String> validator}) {
|
|
return TextFormField(
|
|
decoration: InputDecoration(
|
|
labelText: label,
|
|
prefixIcon: Icon(icon),
|
|
),
|
|
keyboardType: keyboardType,
|
|
validator: validator,
|
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
|
);
|
|
}
|
|
} |