// lib/screens/checkout_screen.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../features/booking/providers/checkout_provider.dart'; import '../features/booking/services/payment_service.dart'; import '../features/booking/models/booking_models.dart'; import '../core/utils/error_utils.dart'; import 'tickets_booked_screen.dart'; class CheckoutScreen extends StatefulWidget { final int eventId; final String eventName; final String? eventImage; const CheckoutScreen({ Key? key, required this.eventId, required this.eventName, this.eventImage, }) : super(key: key); @override State createState() => _CheckoutScreenState(); } class _CheckoutScreenState extends State { late final PaymentService _paymentService; final _formKey = GlobalKey(); final _nameCtrl = TextEditingController(); final _emailCtrl = TextEditingController(); final _phoneCtrl = TextEditingController(); @override void initState() { super.initState(); _paymentService = PaymentService(); _paymentService.initialize( onSuccess: _onPaymentSuccess, onError: _onPaymentError, ); _prefillUserData(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().initForEvent(widget.eventId, widget.eventName); }); } Future _prefillUserData() async { final prefs = await SharedPreferences.getInstance(); _emailCtrl.text = prefs.getString('email') ?? ''; _nameCtrl.text = prefs.getString('display_name') ?? ''; _phoneCtrl.text = prefs.getString('phone_number') ?? ''; } void _onPaymentSuccess(dynamic response) { final provider = context.read(); provider.markPaymentSuccess(response.paymentId ?? 'success'); Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const TicketsBookedScreen()), ); } void _onPaymentError(dynamic response) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Payment failed: ${response.message ?? "Please try again"}'), backgroundColor: Colors.red, ), ); } @override void dispose() { _paymentService.dispose(); _nameCtrl.dispose(); _emailCtrl.dispose(); _phoneCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Checkout', style: TextStyle(fontWeight: FontWeight.w600)), backgroundColor: Colors.white, foregroundColor: Colors.black, elevation: 0, ), body: Consumer( builder: (ctx, provider, _) { if (provider.loading && provider.availableTickets.isEmpty) { return const Center(child: CircularProgressIndicator()); } if (provider.error != null && provider.availableTickets.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(provider.error!, style: const TextStyle(color: Colors.red)), const SizedBox(height: 16), ElevatedButton( onPressed: () => provider.initForEvent(widget.eventId, widget.eventName), child: const Text('Retry'), ), ], ), ); } return Column( children: [ _buildStepIndicator(provider), Expanded(child: _buildCurrentStep(provider)), _buildBottomBar(provider), ], ); }, ), ); } Widget _buildStepIndicator(CheckoutProvider provider) { final steps = ['Tickets', 'Details', 'Payment']; final currentIdx = provider.currentStep.index; return Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), color: Colors.white, child: Row( children: List.generate(steps.length, (i) { final isActive = i <= currentIdx; return Expanded( child: Row( children: [ Container( width: 28, height: 28, decoration: BoxDecoration( shape: BoxShape.circle, color: isActive ? const Color(0xFF0B63D6) : Colors.grey.shade300, ), child: Center( child: Text('${i + 1}', style: TextStyle( color: isActive ? Colors.white : Colors.grey, fontSize: 13, fontWeight: FontWeight.w600, )), ), ), const SizedBox(width: 6), Text(steps[i], style: TextStyle( fontSize: 13, fontWeight: isActive ? FontWeight.w600 : FontWeight.w400, color: isActive ? Colors.black : Colors.grey, )), if (i < steps.length - 1) Expanded( child: Container( height: 1, margin: const EdgeInsets.symmetric(horizontal: 8), color: i < currentIdx ? const Color(0xFF0B63D6) : Colors.grey.shade300, ), ), ], ), ); }), ), ); } Widget _buildCurrentStep(CheckoutProvider provider) { switch (provider.currentStep) { case CheckoutStep.tickets: return _buildTicketSelection(provider); case CheckoutStep.details: return _buildDetailsForm(provider); case CheckoutStep.payment: return _buildPaymentReview(provider); case CheckoutStep.confirmation: return const Center(child: Text('Booking confirmed!')); } } Widget _buildTicketSelection(CheckoutProvider provider) { if (provider.availableTickets.isEmpty) { return const Center(child: Text('No tickets available for this event.')); } return ListView( padding: const EdgeInsets.all(16), children: [ Text(widget.eventName, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w700)), const SizedBox(height: 20), ...provider.availableTickets.map((ticket) { final cartMatches = provider.cart.where((c) => c.ticket.id == ticket.id); final cartItem = cartMatches.isNotEmpty ? cartMatches.first : null; final qty = cartItem?.quantity ?? 0; return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: qty > 0 ? const Color(0xFF0B63D6) : Colors.grey.shade200), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(ticket.ticketType, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16)), const SizedBox(height: 4), Text('Rs ${ticket.price.toStringAsFixed(0)}', style: const TextStyle(color: Color(0xFF0B63D6), fontWeight: FontWeight.w700, fontSize: 18)), if (ticket.description != null) ...[ const SizedBox(height: 4), Text(ticket.description!, style: TextStyle(color: Colors.grey.shade600, fontSize: 13)), ], ], ), ), Row( children: [ IconButton( icon: const Icon(Icons.remove_circle_outline), onPressed: qty > 0 ? () => provider.setTicketQuantity(ticket, qty - 1) : null, ), Text('$qty', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), IconButton( icon: const Icon(Icons.add_circle_outline, color: Color(0xFF0B63D6)), onPressed: qty < ticket.availableQuantity ? () => provider.setTicketQuantity(ticket, qty + 1) : null, ), ], ), ], ), ); }), ], ); } Widget _buildDetailsForm(CheckoutProvider provider) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Contact Details', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), const SizedBox(height: 16), _field('Full Name', _nameCtrl, validator: (v) => v!.isEmpty ? 'Required' : null), _field('Email', _emailCtrl, type: TextInputType.emailAddress, validator: (v) => v!.isEmpty ? 'Required' : null), _field('Phone', _phoneCtrl, type: TextInputType.phone, validator: (v) => v!.isEmpty ? 'Required' : null), ], ), ), ); } Widget _field(String label, TextEditingController ctrl, {TextInputType? type, String? Function(String?)? validator}) { return Padding( padding: const EdgeInsets.only(bottom: 16), child: TextFormField( controller: ctrl, keyboardType: type, validator: validator, decoration: InputDecoration( labelText: label, border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), filled: true, fillColor: Colors.grey.shade50, ), ), ); } Widget _buildPaymentReview(CheckoutProvider provider) { return ListView( padding: const EdgeInsets.all(16), children: [ const Text('Order Summary', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), const SizedBox(height: 16), ...provider.cart.map((item) => Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('${item.ticket.ticketType} x${item.quantity}'), Text('Rs ${item.subtotal.toStringAsFixed(0)}', style: const TextStyle(fontWeight: FontWeight.w600)), ], ), )), const Divider(height: 32), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Total', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), Text('Rs ${provider.total.toStringAsFixed(0)}', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700, color: Color(0xFF0B63D6))), ], ), const SizedBox(height: 24), if (_nameCtrl.text.isNotEmpty) ...[ Text('Name: ${_nameCtrl.text}', style: TextStyle(color: Colors.grey.shade700)), Text('Email: ${_emailCtrl.text}', style: TextStyle(color: Colors.grey.shade700)), Text('Phone: ${_phoneCtrl.text}', style: TextStyle(color: Colors.grey.shade700)), ], ], ); } Widget _buildBottomBar(CheckoutProvider provider) { return Container( padding: EdgeInsets.fromLTRB(16, 12, 16, MediaQuery.of(context).padding.bottom + 12), decoration: BoxDecoration( color: Colors.white, boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.08), blurRadius: 12, offset: const Offset(0, -4))], ), child: Row( children: [ if (provider.currentStep != CheckoutStep.tickets) TextButton( onPressed: provider.previousStep, child: const Text('Back'), ), const Spacer(), if (provider.currentStep == CheckoutStep.tickets) ElevatedButton( onPressed: provider.hasItems ? provider.nextStep : null, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0B63D6), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Text('Continue Rs ${provider.subtotal.toStringAsFixed(0)}'), ) else if (provider.currentStep == CheckoutStep.details) ElevatedButton( onPressed: () { if (_formKey.currentState?.validate() ?? false) { provider.setShipping(ShippingDetails( name: _nameCtrl.text, email: _emailCtrl.text, phone: _phoneCtrl.text, )); provider.nextStep(); } }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0B63D6), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: const Text('Review Order'), ) else if (provider.currentStep == CheckoutStep.payment) ElevatedButton( onPressed: provider.loading ? null : () => _processPayment(provider), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0B63D6), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: provider.loading ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : Text('Pay Rs ${provider.total.toStringAsFixed(0)}'), ), ], ), ); } Future _processPayment(CheckoutProvider provider) async { try { await provider.processCheckout(); _paymentService.openPayment( amount: provider.total, email: _emailCtrl.text, phone: _phoneCtrl.text, eventName: widget.eventName, ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(userFriendlyError(e)), backgroundColor: Colors.red), ); } } }