import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'theme/app_theme.dart'; import 'screens/dashboard_screen.dart'; import 'screens/budget_screen.dart'; import 'screens/accounts_screen.dart'; import 'screens/transactions_screen.dart'; import 'providers/db_provider.dart'; import 'data/db.dart'; final _selectedIndexProvider = StateProvider((ref) => 0); class YnabApp extends ConsumerStatefulWidget { const YnabApp({super.key}); @override ConsumerState createState() => _YnabAppState(); } class _YnabAppState extends ConsumerState { late final List _screens; late final List<_NavItem> _navItems; final _searchController = TextEditingController(); bool _searchActive = false; @override void initState() { super.initState(); _screens = const [ DashboardScreen(), BudgetScreen(), AccountsScreen(), TransactionsScreen(), ]; _navItems = const [ _NavItem(icon: Icons.dashboard_outlined, selectedIcon: Icons.dashboard, label: 'Tableau de bord'), _NavItem(icon: Icons.pie_chart_outline, selectedIcon: Icons.pie_chart, label: 'Budget'), _NavItem(icon: Icons.account_balance_outlined, selectedIcon: Icons.account_balance, label: 'Comptes'), _NavItem(icon: Icons.receipt_long_outlined, selectedIcon: Icons.receipt_long, label: 'Transactions'), ]; } @override void dispose() { _searchController.dispose(); super.dispose(); } Future _addTransaction() async { final accounts = ref.read(openAccountsProvider); final db = ref.read(dbProvider); if (accounts.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Créez d\'abord un compte')), ); return; } final payeeCtrl = TextEditingController(); final amountCtrl = TextEditingController(); int? accountId = accounts.first.id; DateTime date = DateTime.now(); bool isExpense = true; await showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( title: const Text('Nouvelle transaction'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: payeeCtrl, decoration: const InputDecoration(labelText: 'Bénéficiaire', hintText: 'Ex: Supermarché'), autofocus: true, ), const SizedBox(height: 12), TextField( controller: amountCtrl, decoration: const InputDecoration(labelText: 'Montant', hintText: '0,00'), keyboardType: const TextInputType.numberWithOptions(decimal: true), ), const SizedBox(height: 12), SegmentedButton( segments: const [ ButtonSegment(value: true, label: Text('Dépense'), icon: Icon(Icons.arrow_upward)), ButtonSegment(value: false, label: Text('Revenu'), icon: Icon(Icons.arrow_downward)), ], selected: {isExpense}, onSelectionChanged: (v) => setDialogState(() => isExpense = v.first), style: ButtonStyle( visualDensity: VisualDensity.compact, ), ), const SizedBox(height: 12), DropdownButtonFormField( value: accountId, decoration: const InputDecoration(labelText: 'Compte'), items: accounts .map((a) => DropdownMenuItem(value: a.id, child: Text(a.name))) .toList(), onChanged: (v) => accountId = v, ), ], ), ), actions: [ FilledButton.tonal( onPressed: () => Navigator.pop(ctx), child: const Text('Annuler'), ), FilledButton( onPressed: () async { final payee = payeeCtrl.text.trim(); final amountStr = amountCtrl.text.trim().replaceAll(',', '.'); final raw = double.tryParse(amountStr); if (payee.isEmpty || raw == null || accountId == null) return; final amount = isExpense ? -raw.abs() : raw.abs(); await db.into(db.transactions).insert(TransactionsCompanion.insert( accountId: accountId!, payee: payee, amount: amount, date: date, )); if (ctx.mounted) Navigator.pop(ctx); }, child: const Text('Ajouter'), ), ], ), ), ); } void _toggleSearch() { setState(() { _searchActive = !_searchActive; if (!_searchActive) { _searchController.clear(); ref.read(searchQueryProvider.notifier).state = ''; } }); } @override Widget build(BuildContext context) { final selectedIndex = ref.watch(_selectedIndexProvider); final theme = Theme.of(context); final colorScheme = theme.colorScheme; return Scaffold( appBar: AppBar( title: _searchActive ? SearchBar( controller: _searchController, hintText: 'Rechercher…', leading: const Icon(Icons.search, size: 20), padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 4)), constraints: BoxConstraints.tightFor(height: 44), onChanged: (v) => ref.read(searchQueryProvider.notifier).state = v, ) : Text(_navItems[selectedIndex].label), actions: [ if (_searchActive) IconButton( icon: const Icon(Icons.close), onPressed: _toggleSearch, tooltip: 'Fermer la recherche', ) else ...[ if (selectedIndex == 0) IconButton( icon: Icon(Icons.add, color: colorScheme.primary), onPressed: _addTransaction, tooltip: 'Ajouter une transaction', ), if (selectedIndex >= 1) IconButton( icon: Icon(Icons.search, color: colorScheme.onSurfaceVariant), onPressed: _toggleSearch, tooltip: 'Rechercher', ), ], ], ), body: AnimatedSwitcher( duration: const Duration(milliseconds: 200), child: _screens[selectedIndex], ), floatingActionButton: selectedIndex == 3 ? FloatingActionButton.extended( icon: const Icon(Icons.add), label: const Text('Transaction'), onPressed: _addTransaction, ) : null, bottomNavigationBar: NavigationBar( selectedIndex: selectedIndex, onDestinationSelected: (int index) { setState(() { _searchActive = false; _searchController.clear(); ref.read(searchQueryProvider.notifier).state = ''; }); ref.read(_selectedIndexProvider.notifier).state = index; }, destinations: _navItems.map((item) { return NavigationDestination( icon: Icon(item.icon), selectedIcon: Icon(item.selectedIcon), label: item.label, ); }).toList(), ), ); } } class _NavItem { final IconData icon; final IconData selectedIcon; final String label; const _NavItem({required this.icon, required this.selectedIcon, required this.label}); }