import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:qpay/screens/groups/models/group_batch_model.dart'; import 'package:qpay/screens/groups/models/recipient_group_model.dart'; import 'package:qpay/models/responsive_policy.dart'; import 'package:qpay/screens/groups/controllers/batch_controller.dart'; import 'package:qpay/screens/home/home_controller.dart'; import 'package:qpay/screens/pay/pay_controller.dart'; import 'package:qpay/widgets/app_snack_bar.dart'; import 'package:shared_preferences/shared_preferences.dart'; class BatchCreateScreen extends StatefulWidget { final RecipientGroup group; const BatchCreateScreen({super.key, required this.group}); @override State createState() => _BatchCreateScreenState(); } class _BatchCreateScreenState extends State { late BatchController controller; final _formKey = GlobalKey(); final _descriptionController = TextEditingController(); final _amountController = TextEditingController(); final _debitPhoneController = TextEditingController(); String _currency = 'USD'; bool _loadingDropdowns = true; @override void initState() { super.initState(); controller = BatchController(context); _init(); } Future _init() async { final prefs = await SharedPreferences.getInstance(); if (prefs.containsKey('phone')) { _debitPhoneController.text = prefs.getString('phone')!; } await Future.wait([controller.loadProviders()]); if (mounted) setState(() => _loadingDropdowns = false); } @override void dispose() { controller.dispose(); _descriptionController.dispose(); _amountController.dispose(); _debitPhoneController.dispose(); super.dispose(); } Future _submit() async { if (!_formKey.currentState!.validate()) return; final selectedProvider = controller.model.selectedProvider; if (selectedProvider == null) { AppSnackBar.show(context, 'Please select a bill provider.'); return; } final amount = double.tryParse(_amountController.text.trim()); if (amount == null || amount <= 0) { AppSnackBar.show(context, 'Please enter a valid amount.'); return; } final prefs = await SharedPreferences.getInstance(); final userId = prefs.getString('userId') ?? ''; final transactionTemplate = { 'type': 'CONFIRM', 'billClientId': selectedProvider.clientId, 'debitPhone': _debitPhoneController.text.trim(), 'debitRef': 'Velocity', 'debitCurrency': _currency, 'billName': selectedProvider.name, 'providerImage': selectedProvider.image, 'providerLabel': selectedProvider.label, 'creditName': '', 'creditEmail': '', 'productUid': '', 'billProductName': '', 'region': 'ZW', }; final req = CreateBatchRequest( recipientGroupId: widget.group.id ?? '', userId: userId, currency: _currency, description: _descriptionController.text.trim(), amount: amount, transactionTemplate: transactionTemplate, ); final result = await controller.createBatch(req); if (!mounted) return; if (result.isSuccess) { final batch = result.data!; context.pushReplacement('/groups/${widget.group.id}/batches/${batch.id}'); } else { AppSnackBar.showError(context, result.error ?? 'Failed to create batch.'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('New Batch'), centerTitle: true), body: ListenableBuilder( listenable: controller, builder: (context, _) { if (_loadingDropdowns) { return const Center(child: CircularProgressIndicator()); } return Center( child: LayoutBuilder( builder: (context, constraints) { final width = constraints.maxWidth > ResponsivePolicy.md ? ResponsivePolicy.md.toDouble() : constraints.maxWidth; return SizedBox( width: width, child: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildSectionLabel('Bill Provider'), const SizedBox(height: 8), _buildProviderGrid(constraints), const SizedBox(height: 16), TextFormField( controller: _descriptionController, textInputAction: TextInputAction.next, decoration: InputDecoration( labelText: 'Description (optional)', hintText: 'e.g. Monthly salaries for January', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), ), ), const SizedBox(height: 16), Row( children: [ Expanded( flex: 2, child: TextFormField( controller: _amountController, keyboardType: const TextInputType.numberWithOptions( decimal: true, ), textInputAction: TextInputAction.next, decoration: InputDecoration( labelText: 'Amount each recipient will receive', hintText: 'Amount', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), ), validator: (v) => (v == null || v.trim().isEmpty) ? 'Required' : null, ), ), const SizedBox(width: 12), Expanded( child: DropdownButtonFormField( value: _currency, decoration: InputDecoration( labelText: 'Currency', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), ), items: const [ DropdownMenuItem( value: 'USD', child: Text('USD'), ), DropdownMenuItem( value: 'ZWG', child: Text('ZWG'), ), ], onChanged: (v) { if (v != null) setState(() => _currency = v); }, ), ), ], ), const SizedBox(height: 16), TextFormField( controller: _debitPhoneController, keyboardType: TextInputType.phone, textInputAction: TextInputAction.done, decoration: InputDecoration( labelText: 'Your Phone Number', hintText: '263773000000', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), ), validator: (v) => (v == null || v.trim().isEmpty) ? 'Phone number is required' : null, ), const SizedBox(height: 32), ElevatedButton( onPressed: controller.model.isActing ? null : _submit, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: controller.model.isActing ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Text( 'Create Batch', style: TextStyle(fontSize: 16), ), ), ], ), ), ), ); }, ), ); }, ), ); } Widget _buildSectionLabel(String text) { return Text( text, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.grey.shade700, ), ); } static const Map _categoryColorMap = { 'ECONET': Color(0xFF1A73E8), 'NETONE': Color.fromARGB(255, 230, 127, 0), 'TELECEL': Color(0xFF7B1FA2), 'ZESA': Color.fromARGB(255, 230, 58, 0), }; Color _resolveProviderColor(BillProvider provider) { final exact = _categoryColorMap[provider.category.toUpperCase()]; if (exact != null) return exact; final labelMatch = _categoryColorMap[provider.label.toUpperCase()]; if (labelMatch != null) return labelMatch; final name = provider.name.toUpperCase(); for (final entry in _categoryColorMap.entries) { if (name.contains(entry.key)) return entry.value; } final hash = provider.clientId.hashCode; final hue = (hash % 360).toDouble(); return HSVColor.fromAHSV(1.0, hue, 0.75, 0.85).toColor(); } Widget _buildProviderGrid(BoxConstraints constraints) { if (controller.model.providers.isEmpty) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( border: Border.all(color: Colors.black26), borderRadius: BorderRadius.circular(12), ), child: Text( 'No providers available', style: TextStyle(color: Colors.grey.shade600), ), ); } final isDark = Theme.of(context).brightness == Brightness.dark; return LayoutBuilder( builder: (context, gridConstraints) { // gridConstraints.maxWidth reflects the actual rendered width of this widget // 3-column grid: subtract 2 gaps (10*2 = 20), then divide by 3 final cardWidth = (gridConstraints.maxWidth - 20) / 3; return Wrap( spacing: 10, runSpacing: 10, children: controller.model.providers .map( (provider) => _buildProviderCard(provider, isDark, cardWidth), ) .toList(), ); }, ); } Widget _buildProviderCard(BillProvider provider, bool isDark, double width) { final isSelected = controller.model.selectedProvider?.clientId == provider.clientId; final accent = _resolveProviderColor(provider); return SizedBox( width: width, child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(12), onTap: () => controller.selectProvider(provider), child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: isDark ? const Color(0xFF1E1E1E) : Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all( color: isSelected ? accent : isDark ? Colors.white.withValues(alpha: 0.1) : Colors.black.withValues(alpha: 0.06), width: isSelected ? 1.5 : 1, ), ), child: Stack( children: [ Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 28, height: 28, padding: const EdgeInsets.all(2), decoration: BoxDecoration( color: accent.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(8), ), child: Image( image: AssetImage("assets/${provider.image}"), errorBuilder: (_, __, ___) => Icon( Icons.payment_rounded, size: 14, color: accent, ), ), ), ], ), const SizedBox(height: 8), Text( provider.name, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: isDark ? Colors.white : Colors.black87, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( provider.description, style: TextStyle( fontSize: 10, fontWeight: FontWeight.w400, color: isDark ? Colors.white54 : Colors.grey.shade500, height: 1.3, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), if (isSelected) Positioned( top: -2, right: -2, child: Container( width: 18, height: 18, decoration: BoxDecoration( color: accent, shape: BoxShape.circle, border: Border.all( color: isDark ? const Color(0xFF1E1E1E) : Colors.white, width: 1.5, ), ), child: const Icon( Icons.check_rounded, size: 11, color: Colors.white, ), ), ), ], ), ), ), ), ); } }