import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:travel_mate/models/expense_split.dart'; import '../../blocs/expense/expense_bloc.dart'; import '../../blocs/expense/expense_event.dart'; import '../../blocs/expense/expense_state.dart'; import '../../blocs/user/user_state.dart' as user_state; import '../../models/group.dart'; import '../../models/expense.dart'; class AddExpenseDialog extends StatefulWidget { final Group group; final user_state.UserModel currentUser; final Expense? expenseToEdit; const AddExpenseDialog({ super.key, required this.group, required this.currentUser, this.expenseToEdit, }); @override State createState() => _AddExpenseDialogState(); } class _AddExpenseDialogState extends State { final _formKey = GlobalKey(); final _descriptionController = TextEditingController(); final _amountController = TextEditingController(); late DateTime _selectedDate; late ExpenseCategory _selectedCategory; late ExpenseCurrency _selectedCurrency; late String _paidById; final Map _splits = {}; File? _receiptImage; bool _isLoading = false; bool _splitEqually = true; @override void initState() { super.initState(); _selectedDate = widget.expenseToEdit?.date ?? DateTime.now(); _selectedCategory = widget.expenseToEdit?.category ?? ExpenseCategory.other; _selectedCurrency = widget.expenseToEdit?.currency ?? ExpenseCurrency.eur; _paidById = widget.expenseToEdit?.paidById ?? widget.currentUser.id; if (widget.expenseToEdit != null) { _descriptionController.text = widget.expenseToEdit!.description; _amountController.text = widget.expenseToEdit!.amount.toString(); for (final split in widget.expenseToEdit!.splits) { _splits[split.userId] = split.amount; } _splitEqually = false; } else { // Initialiser avec tous les membres sélectionnés for (final member in widget.group.members) { _splits[member.userId] = 0; } } } @override void dispose() { _descriptionController.dispose(); _amountController.dispose(); super.dispose(); } Future _pickImage() async { final picker = ImagePicker(); final pickedFile = await picker.pickImage( source: ImageSource.gallery, maxWidth: 1920, maxHeight: 1920, ); if (pickedFile != null) { final file = File(pickedFile.path); final fileSize = await file.length(); if (fileSize > 5 * 1024 * 1024) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('L\'image ne doit pas dépasser 5 Mo'), backgroundColor: Colors.red, ), ); } return; } setState(() { _receiptImage = file; }); } } void _calculateSplits() { if (!_splitEqually) return; final amount = double.tryParse(_amountController.text) ?? 0; final selectedMembers = _splits.entries.where((e) => e.value >= 0).toList(); if (selectedMembers.isEmpty) return; final splitAmount = amount / selectedMembers.length; setState(() { for (final entry in selectedMembers) { _splits[entry.key] = splitAmount; } }); } Future _submit() async { if (!_formKey.currentState!.validate()) return; final amount = double.parse(_amountController.text); final selectedSplits = _splits.entries .where((e) => e.value > 0) .map((e) { final member = widget.group.members.firstWhere((m) => m.userId == e.key); return ExpenseSplit( userId: e.key, userName: member.firstName, amount: e.value, ); }) .toList(); if (selectedSplits.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Veuillez sélectionner au moins un participant'), backgroundColor: Colors.red, ), ); return; } setState(() => _isLoading = true); try { // Convertir en EUR final amountInEur = context.read().state is ExpensesLoaded ? (context.read().state as ExpensesLoaded) .exchangeRates[_selectedCurrency]! * amount : amount; final payer = widget.group.members.firstWhere((m) => m.userId == _paidById); final expense = Expense( id: widget.expenseToEdit?.id ?? '', groupId: widget.group.id, description: _descriptionController.text.trim(), amount: amount, currency: _selectedCurrency, amountInEur: amountInEur, category: _selectedCategory, paidById: _paidById, paidByName: payer.firstName, splits: selectedSplits, date: _selectedDate, receiptUrl: widget.expenseToEdit?.receiptUrl, createdAt: widget.expenseToEdit?.createdAt ?? DateTime.now(), ); if (widget.expenseToEdit == null) { context.read().add(CreateExpense( expense: expense, receiptImage: _receiptImage, )); } else { context.read().add(UpdateExpense( expense: expense, newReceiptImage: _receiptImage, )); } if (mounted) { Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(widget.expenseToEdit == null ? 'Dépense ajoutée' : 'Dépense modifiée'), backgroundColor: Colors.green, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur: $e'), backgroundColor: Colors.red, ), ); } } finally { if (mounted) { setState(() => _isLoading = false); } } } @override Widget build(BuildContext context) { return Dialog( child: Container( constraints: const BoxConstraints(maxWidth: 600, maxHeight: 700), child: Scaffold( appBar: AppBar( title: Text(widget.expenseToEdit == null ? 'Nouvelle dépense' : 'Modifier la dépense'), automaticallyImplyLeading: false, actions: [ IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), ], ), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ // Description TextFormField( controller: _descriptionController, decoration: const InputDecoration( labelText: 'Description', hintText: 'Ex: Restaurant, Essence...', border: OutlineInputBorder(), prefixIcon: Icon(Icons.description), ), validator: (value) { if (value == null || value.trim().isEmpty) { return 'Veuillez entrer une description'; } return null; }, ), const SizedBox(height: 16), // Montant et devise Row( children: [ Expanded( flex: 2, child: TextFormField( controller: _amountController, decoration: const InputDecoration( labelText: 'Montant', border: OutlineInputBorder(), prefixIcon: Icon(Icons.euro), ), keyboardType: const TextInputType.numberWithOptions(decimal: true), onChanged: (_) => _calculateSplits(), validator: (value) { if (value == null || value.isEmpty) { return 'Requis'; } if (double.tryParse(value) == null || double.parse(value) <= 0) { return 'Montant invalide'; } return null; }, ), ), const SizedBox(width: 8), Expanded( child: DropdownButtonFormField( initialValue: _selectedCurrency, decoration: const InputDecoration( labelText: 'Devise', border: OutlineInputBorder(), ), items: ExpenseCurrency.values.map((currency) { return DropdownMenuItem( value: currency, child: Text(currency.code), ); }).toList(), onChanged: (value) { if (value != null) { setState(() => _selectedCurrency = value); } }, ), ), ], ), const SizedBox(height: 16), // Catégorie DropdownButtonFormField( initialValue: _selectedCategory, decoration: const InputDecoration( labelText: 'Catégorie', border: OutlineInputBorder(), prefixIcon: Icon(Icons.category), ), items: ExpenseCategory.values.map((category) { return DropdownMenuItem( value: category, child: Row( children: [ Icon(category.icon, size: 20), const SizedBox(width: 8), Text(category.displayName), ], ), ); }).toList(), onChanged: (value) { if (value != null) { setState(() => _selectedCategory = value); } }, ), const SizedBox(height: 16), // Date ListTile( leading: const Icon(Icons.calendar_today), title: const Text('Date'), subtitle: Text(DateFormat('dd/MM/yyyy').format(_selectedDate)), shape: RoundedRectangleBorder( side: BorderSide(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(4), ), onTap: () async { final date = await showDatePicker( context: context, initialDate: _selectedDate, firstDate: DateTime(2020), lastDate: DateTime.now().add(const Duration(days: 365)), ); if (date != null) { setState(() => _selectedDate = date); } }, ), const SizedBox(height: 16), // Payé par DropdownButtonFormField( initialValue: _paidById, decoration: const InputDecoration( labelText: 'Payé par', border: OutlineInputBorder(), prefixIcon: Icon(Icons.person), ), items: widget.group.members.map((member) { return DropdownMenuItem( value: member.userId, child: Text(member.firstName), ); }).toList(), onChanged: (value) { if (value != null) { setState(() => _paidById = value); } }, ), const SizedBox(height: 16), // Division Card( child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Text( 'Division', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const Spacer(), Text(_splitEqually ? 'Égale' : 'Personnalisée'), Switch( value: _splitEqually, onChanged: (value) { setState(() { _splitEqually = value; if (value) _calculateSplits(); }); }, ), ], ), const Divider(), ...widget.group.members.map((member) { final isSelected = _splits.containsKey(member.userId) && _splits[member.userId]! >= 0; return CheckboxListTile( title: Text(member.firstName), subtitle: _splitEqually || !isSelected ? null : TextFormField( initialValue: _splits[member.userId]?.toStringAsFixed(2), decoration: const InputDecoration( labelText: 'Montant', isDense: true, ), keyboardType: const TextInputType.numberWithOptions(decimal: true), onChanged: (value) { final amount = double.tryParse(value) ?? 0; setState(() => _splits[member.userId] = amount); }, ), value: isSelected, onChanged: (value) { setState(() { if (value == true) { _splits[member.userId] = 0; if (_splitEqually) _calculateSplits(); } else { _splits[member.userId] = -1; } }); }, ); }), ], ), ), ), const SizedBox(height: 16), // Reçu ListTile( leading: const Icon(Icons.receipt), title: Text(_receiptImage != null || widget.expenseToEdit?.receiptUrl != null ? 'Reçu ajouté' : 'Ajouter un reçu'), subtitle: _receiptImage != null ? const Text('Nouveau reçu sélectionné') : null, trailing: IconButton( icon: const Icon(Icons.add_photo_alternate), onPressed: _pickImage, ), shape: RoundedRectangleBorder( side: BorderSide(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 24), // Boutons Row( children: [ Expanded( child: OutlinedButton( onPressed: _isLoading ? null : () => Navigator.of(context).pop(), child: const Text('Annuler'), ), ), const SizedBox(width: 16), Expanded( child: ElevatedButton( onPressed: _isLoading ? null : _submit, child: _isLoading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : Text(widget.expenseToEdit == null ? 'Ajouter' : 'Modifier'), ), ), ], ), ], ), ), ), ), ); } }