import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:uuid/uuid.dart'; import '../../core/constants.dart'; import '../../core/l10n/app_strings.dart'; import '../../core/l10n/locale_provider.dart'; import '../../core/services/haptic_service.dart'; import '../../shared/models/account.dart'; import '../../shared/models/transaction.dart'; import '../dashboard/provider.dart'; import '../settings/provider.dart'; import 'provider.dart'; const _uuid = Uuid(); // Provider to get the account for new transactions final transactionAccountProvider = Provider<({int id, String name})>((ref) { final activeAccount = ref.watch(activeAccountProvider); if (activeAccount != null) { // User is on a specific account page return (id: activeAccount.id, name: activeAccount.name); } // User is on Total Balance page, use Main account // This will be resolved in the widget return (id: 0, name: ''); // Placeholder, will be replaced }); class AddTransactionScreen extends ConsumerStatefulWidget { final Transaction? initial; const AddTransactionScreen({super.key, this.initial}); @override ConsumerState createState() => _AddTransactionScreenState(); } class _AddTransactionScreenState extends ConsumerState with SingleTickerProviderStateMixin { final _formKey = GlobalKey(); final _amountController = TextEditingController(); final _noteController = TextEditingController(); final _accountIndicatorKey = GlobalKey(); late AnimationController _shakeController; late Animation _borderColorAnimation; bool _showError = false; late DateTime _selectedDate; late TimeOfDay _selectedTime; bool _showAccountDropdown = false; @override void initState() { super.initState(); final now = DateTime.now(); _selectedDate = widget.initial?.date ?? now; _selectedTime = widget.initial != null ? TimeOfDay.fromDateTime(widget.initial!.date) : TimeOfDay(hour: now.hour, minute: now.minute); _shakeController = AnimationController( vsync: this, duration: const Duration(milliseconds: 5000), ); _borderColorAnimation = ColorTween( begin: const Color(0xFFE05C6B), end: Colors.transparent, ).animate(CurvedAnimation(parent: _shakeController, curve: Curves.easeOut)); _shakeController.addStatusListener((status) { if (status == AnimationStatus.completed) { if (mounted) setState(() => _showError = false); } }); if (widget.initial != null) { _amountController.text = widget.initial!.amount.toString(); _noteController.text = widget.initial!.note ?? ''; } else { WidgetsBinding.instance.addPostFrameCallback((_) { final activeAccount = ref.read(activeAccountProvider); final curr = ref.read(currencyProvider); // Use active account's currency if available, otherwise use global currency final currencyCode = activeAccount?.currency ?? curr.code; final currencySymbol = currencyMap[currencyCode]?.symbol ?? curr.symbol; ref .read(addTransactionProvider(null).notifier) .setCurrency(currencySymbol, currencyCode); }); } } @override void dispose() { _amountController.dispose(); _noteController.dispose(); _shakeController.dispose(); super.dispose(); } String? _validateAndParseAmount(String raw) { final trimmed = raw.trim(); if (trimmed.isEmpty) return null; final normalized = trimmed.replaceAll(',', '.'); final validPattern = RegExp(r'^\d+\.?\d*$'); if (!validPattern.hasMatch(normalized)) return null; final value = double.tryParse(normalized); if (value == null) return null; if (value <= 0) return null; if (value > 999_999_999) return null; final parts = normalized.split('.'); if (parts.length == 2 && parts[1].length > 2) return null; return normalized; // valid, return normalized string } void _triggerError() { setState(() => _showError = true); _shakeController.forward(from: 0); } Future _submit() async { final raw = _amountController.text; final parsed = _validateAndParseAmount(raw); if (parsed == null) { _triggerError(); return; } final amount = double.parse(parsed); final state = ref.read(addTransactionProvider(widget.initial)); final finalDateTime = DateTime( _selectedDate.year, _selectedDate.month, _selectedDate.day, _selectedTime.hour, _selectedTime.minute, ); ref.read(addTransactionProvider(widget.initial).notifier).setAmount(amount); ref .read(addTransactionProvider(widget.initial).notifier) .setDate(finalDateTime); ref .read(addTransactionProvider(widget.initial).notifier) .setSubmitting(true); final note = _noteController.text.trim().isEmpty ? null : _noteController.text.trim(); try { print('--- SUBMIT CLICKED ---'); print( 'Amount: $amount, Category: ${state.category}, Type: ${state.type.name}', ); final activeAccount = ref.read(activeAccountProvider); final selectedId = ref .read(addTransactionProvider(widget.initial)) .selectedAccountId; int accountId; if (selectedId != null && selectedId != 0) { print('Using selected account ID: $selectedId'); accountId = selectedId; } else if (activeAccount != null) { print( 'Using active account ID: ${activeAccount.id}, Name: ${activeAccount.name}', ); accountId = activeAccount.id; } else { print('No active account. Fetching main account...'); final mainAccount = await ref.read(accountRepositoryProvider).getMain(); print( 'Main account fetched: ID=${mainAccount.id}, Name: ${mainAccount.name}', ); accountId = mainAccount.id; } final tx = Transaction( id: state.editingId ?? _uuid.v4(), amount: amount, category: state.category, type: state.type, date: finalDateTime, note: note, currency: state.overrideCurrency, currencyCode: state.overrideCurrencyCode, accountId: accountId, ); print('Transaction object created: ID=${tx.id}, AccId=${tx.accountId}'); print('Calling provider to save...'); if (state.isEditing) { await ref.read(transactionsProvider.notifier).update(tx); print('Update completed'); } else { final res = await ref.read(transactionsProvider.notifier).add(tx); print( 'Add completed. Result: ${res.isSuccess ? "SUCCESS" : "FAILURE"}', ); if (res.isFailure) { print('!!! Provider returned failure: ${res.errorOrNull}'); throw Exception(res.errorOrNull); } } print('Provider save completed successfully'); HapticService.medium(); if (mounted) { print('Popping screen...'); context.pop(); } } catch (e, stack) { print('!!! SAVE CRASHED !!!'); print('Error: $e'); print('Stack trace:'); print(stack); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Save error: $e'), backgroundColor: Colors.red, duration: const Duration(seconds: 5), ), ); } } finally { ref .read(addTransactionProvider(widget.initial).notifier) .setSubmitting(false); } } Future _pickDate() async { final picked = await showDatePicker( context: context, initialDate: _selectedDate, firstDate: DateTime(2000), lastDate: DateTime.now(), builder: (context, child) => Theme( data: Theme.of(context).copyWith( colorScheme: Theme.of( context, ).colorScheme.copyWith(primary: AppColors.accent), ), child: child!, ), ); if (picked != null) { setState(() => _selectedDate = picked); } } Future _pickTime() async { final picked = await showTimePicker( context: context, initialTime: _selectedTime, builder: (context, child) => Theme( data: Theme.of(context).copyWith( timePickerTheme: TimePickerThemeData( backgroundColor: Theme.of(context).colorScheme.surface, hourMinuteShape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), dayPeriodShape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), colorScheme: Theme.of( context, ).colorScheme.copyWith(primary: const Color(0xFF7C6DED)), ), child: child!, ), ); if (picked != null) { setState(() => _selectedTime = picked); } } @override Widget build(BuildContext context) { final s = ref.watch(stringsProvider); final state = ref.watch(addTransactionProvider(widget.initial)); final categories = ref.watch(availableCategoriesProvider(widget.initial)); final overrideCurrency = state.overrideCurrency; final isDark = Theme.of(context).brightness == Brightness.dark; // Get active account or fallback to main final activeAccount = ref.watch(activeAccountProvider); final accountsAsync = ref.watch(accountsProvider); return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: AppBar( title: Text(state.isEditing ? s.editTransaction : s.addTransaction), leading: IconButton( icon: const Icon(Icons.close_rounded), onPressed: () => context.pop(), ), actions: [ if (state.isEditing) IconButton( icon: const Icon(Icons.delete_outline_rounded), color: const Color(0xFFE05C6B), onPressed: () { showDialog( context: context, builder: (ctx) => AlertDialog( title: Text(s.confirmDelete), content: Text(s.confirmDeleteBody), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: Text(s.cancel), ), TextButton( onPressed: () { ref .read(transactionsProvider.notifier) .delete(widget.initial!.id); Navigator.pop(ctx); context.pop(); }, style: TextButton.styleFrom( foregroundColor: const Color(0xFFE05C6B), ), child: Text(s.delete), ), ], ), ); }, ), ], ), body: SafeArea( child: Form( key: _formKey, child: Stack( children: [ ListView( padding: const EdgeInsets.all(20), children: [ Row( children: [ // Left: Account Indicator (50% width) Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ accountsAsync.when( data: (accounts) { final txAccountId = ref .read( addTransactionProvider(widget.initial), ) .selectedAccountId; final Account displayAccount; if (txAccountId != null) { displayAccount = accounts.firstWhere( (a) => a.id == txAccountId, orElse: () => accounts.firstWhere( (a) => a.isMain, orElse: () => accounts.first, ), ); } else { displayAccount = activeAccount ?? accounts.firstWhere( (a) => a.isMain, orElse: () => accounts.first, ); } final canChangeAccount = activeAccount == null; return GestureDetector( onTap: canChangeAccount ? () => setState( () => _showAccountDropdown = !_showAccountDropdown, ) : null, child: Container( key: _accountIndicatorKey, height: 56, padding: const EdgeInsets.symmetric( horizontal: 14, ), decoration: BoxDecoration( color: const Color( 0xFF7C6DED, ).withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color( 0xFF7C6DED, ).withOpacity(0.3), width: 1.5, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.account_balance_wallet_rounded, size: 18, color: Color(0xFF7C6DED), ), const SizedBox(width: 10), Flexible( child: Text( displayAccount.name, style: Theme.of(context) .textTheme .bodyMedium ?.copyWith( color: const Color( 0xFF7C6DED, ), fontWeight: FontWeight.w600, fontSize: 14, ), overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, ), ), if (canChangeAccount) ...[ const SizedBox(width: 6), Icon( _showAccountDropdown ? Icons.arrow_drop_up : Icons.arrow_drop_down, size: 18, color: const Color(0xFF7C6DED), ), ], ], ), ), ); }, loading: () => Container( height: 56, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), ), ), error: (_, __) => Container( height: 56, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), ), ), ), ], ), ), const SizedBox(width: 12), // Right: Type Toggle (50% width) Expanded( child: Container( height: 56, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), border: isDark ? null : Border.all( color: const Color(0xFFDDDDEE), width: 1, ), ), child: Row( children: [ Expanded( child: GestureDetector( onTap: () => ref .read( addTransactionProvider( widget.initial, ).notifier, ) .setType(TransactionType.income), child: AnimatedContainer( duration: const Duration(milliseconds: 200), decoration: BoxDecoration( color: state.type == TransactionType.income ? AppColors.income.withOpacity(0.15) : Colors.transparent, borderRadius: BorderRadius.circular(11), ), child: Center( child: Icon( Icons.arrow_downward_rounded, color: state.type == TransactionType.income ? AppColors.income : Theme.of(context) .colorScheme .onSurface .withOpacity(0.4), size: 20, ), ), ), ), ), Expanded( child: GestureDetector( onTap: () => ref .read( addTransactionProvider( widget.initial, ).notifier, ) .setType(TransactionType.expense), child: AnimatedContainer( duration: const Duration(milliseconds: 200), decoration: BoxDecoration( color: state.type == TransactionType.expense ? AppColors.expense.withOpacity(0.15) : Colors.transparent, borderRadius: BorderRadius.circular(11), ), child: Center( child: Icon( Icons.arrow_upward_rounded, color: state.type == TransactionType.expense ? AppColors.expense : Theme.of(context) .colorScheme .onSurface .withOpacity(0.4), size: 20, ), ), ), ), ), ], ), ), ), ], ), const SizedBox(height: 24), _SectionLabel(s.amount), const SizedBox(height: 8), AnimatedBuilder( animation: _borderColorAnimation, builder: (context, child) { final isError = _showError; final normalBorder = isDark ? Colors.transparent : const Color(0xFFCCCCDD); final borderColor = isError ? (_borderColorAnimation.value ?? const Color(0xFFE05C6B)) : normalBorder; return Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), border: Border.all( color: borderColor, width: isError ? 1.5 : 1, ), ), child: Row( children: [ Padding( padding: const EdgeInsets.symmetric( horizontal: 14, ), child: Text( overrideCurrency, style: Theme.of(context).textTheme.bodyLarge ?.copyWith( color: Theme.of( context, ).colorScheme.onSurface.withOpacity(0.7), fontWeight: FontWeight.w600, ), ), ), Expanded( child: TextField( controller: _amountController, keyboardType: const TextInputType.numberWithOptions( decimal: true, ), style: Theme.of(context).textTheme.headlineSmall ?.copyWith( color: Theme.of( context, ).colorScheme.onSurface, fontWeight: FontWeight.w600, ), decoration: const InputDecoration( hintText: '0.00', border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, errorBorder: InputBorder.none, focusedErrorBorder: InputBorder.none, filled: false, contentPadding: EdgeInsets.symmetric( vertical: 14, ), ), onChanged: (v) { final parsed = double.tryParse(v); ref .read( addTransactionProvider( widget.initial, ).notifier, ) .setAmount(parsed); }, ), ), ], ), ); }, ), const SizedBox(height: 20), Text( s.currency, style: TextStyle( fontSize: 13, color: Theme.of( context, ).colorScheme.onSurface.withOpacity(0.6), ), ), const SizedBox(height: 8), _CurrencyPicker( selected: state.overrideCurrencyCode, onChanged: (symbol, code) => ref .read(addTransactionProvider(widget.initial).notifier) .setCurrency(symbol, code), ), const SizedBox(height: 20), _SectionLabel(s.category), const SizedBox(height: 8), _CategoryPicker( categories: categories, selected: state.category, onChanged: (c) => ref .read(addTransactionProvider(widget.initial).notifier) .setCategory(c), ), const SizedBox(height: 20), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( s.date, style: Theme.of(context).textTheme.bodySmall ?.copyWith( color: Theme.of( context, ).colorScheme.onSurface.withOpacity(0.6), fontWeight: FontWeight.w500, ), ), const SizedBox(height: 6), GestureDetector( onTap: _pickDate, child: Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), border: isDark ? null : Border.all( color: const Color(0xFFCCCCDD), width: 1, ), ), child: Row( children: [ Icon( Icons.calendar_today_rounded, size: 16, color: Theme.of( context, ).colorScheme.onSurface.withOpacity(0.6), ), const SizedBox(width: 8), Expanded( child: Text( DateFormat( 'MMM d, yyyy', s.dateLocale, ).format(_selectedDate), style: Theme.of(context) .textTheme .bodyMedium ?.copyWith( color: Theme.of( context, ).colorScheme.onSurface, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), ], ), ), ), ], ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( s.time, style: Theme.of(context).textTheme.bodySmall ?.copyWith( color: Theme.of( context, ).colorScheme.onSurface.withOpacity(0.6), fontWeight: FontWeight.w500, ), ), const SizedBox(height: 6), GestureDetector( onTap: _pickTime, child: Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), border: isDark ? null : Border.all( color: const Color(0xFFCCCCDD), width: 1, ), ), child: Row( children: [ Icon( Icons.access_time_rounded, size: 16, color: Theme.of( context, ).colorScheme.onSurface.withOpacity(0.6), ), const SizedBox(width: 8), Text( _selectedTime.format(context), style: Theme.of(context) .textTheme .bodyMedium ?.copyWith( color: Theme.of( context, ).colorScheme.onSurface, fontWeight: FontWeight.w500, ), ), ], ), ), ), ], ), ), ], ), const SizedBox(height: 20), _SectionLabel(s.noteOptional), const SizedBox(height: 8), TextFormField( controller: _noteController, maxLines: 2, maxLength: 20, maxLengthEnforcement: MaxLengthEnforcement.enforced, buildCounter: ( context, { required currentLength, required isFocused, maxLength, }) => Text( '$currentLength/$maxLength', style: Theme.of(context).textTheme.bodySmall ?.copyWith( color: Theme.of( context, ).colorScheme.onSurface.withOpacity(0.4), fontSize: 11, ), ), decoration: InputDecoration( hintText: s.addNote, enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: isDark ? BorderSide.none : const BorderSide( color: Color(0xFFCCCCDD), width: 1, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide( color: Color(0xFF7C6DED), width: 1.5, ), ), ), onChanged: (v) => ref .read(addTransactionProvider(widget.initial).notifier) .setNote(v.trim()), ), const SizedBox(height: 32), AnimatedContainer( duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, child: Builder( builder: (context) { final selectedType = state.type; final typeColor = selectedType == TransactionType.income ? const Color(0xFF4CAF8C) : const Color(0xFFE05C6B); return SizedBox( width: double.infinity, child: OutlinedButton( onPressed: state.isSubmitting ? null : _submit, style: OutlinedButton.styleFrom( backgroundColor: typeColor.withOpacity(0.1), side: BorderSide(color: typeColor, width: 2), padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), foregroundColor: typeColor, ), child: state.isSubmitting ? SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, color: typeColor, ), ) : Text( state.isEditing ? s.saveChanges : s.addTransaction, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: typeColor, ), ), ), ); }, ), ), ], ), if (_showAccountDropdown) Positioned.fill( child: GestureDetector( onTap: () => setState(() => _showAccountDropdown = false), behavior: HitTestBehavior.translucent, child: const SizedBox.expand(), ), ), if (_showAccountDropdown) Positioned( top: 76, left: 20, right: MediaQuery.of(context).size.width / 2 + 6, child: Material( elevation: 8, borderRadius: BorderRadius.circular(12), color: Colors.transparent, child: Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0xFF7C6DED).withOpacity(0.3), width: 1.5, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.2), blurRadius: 16, offset: const Offset(0, 4), ), ], ), child: accountsAsync.when( data: (accounts) { final txAccountId = ref .read(addTransactionProvider(widget.initial)) .selectedAccountId; final Account displayAccount; if (txAccountId != null) { displayAccount = accounts.firstWhere( (a) => a.id == txAccountId, orElse: () => accounts.firstWhere( (a) => a.isMain, orElse: () => accounts.first, ), ); } else { displayAccount = activeAccount ?? accounts.firstWhere( (a) => a.isMain, orElse: () => accounts.first, ); } return Column( mainAxisSize: MainAxisSize.min, children: accounts.map((account) { final isSelected = account.id == displayAccount.id; return InkWell( borderRadius: BorderRadius.circular(12), onTap: () { ref .read( addTransactionProvider( widget.initial, ).notifier, ) .setAccountId(account.id); setState(() => _showAccountDropdown = false); HapticService.light(); }, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 12, ), child: Row( children: [ Icon( Icons.account_balance_wallet_rounded, size: 16, color: isSelected ? const Color(0xFF7C6DED) : Theme.of(context) .colorScheme .onSurface .withOpacity(0.5), ), const SizedBox(width: 10), Expanded( child: Text( account.name, style: TextStyle( fontSize: 14, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, color: isSelected ? const Color(0xFF7C6DED) : Theme.of( context, ).colorScheme.onSurface, ), ), ), if (isSelected) const Icon( Icons.check_rounded, size: 16, color: Color(0xFF7C6DED), ), ], ), ), ); }).toList(), ); }, loading: () => const SizedBox.shrink(), error: (_, __) => const SizedBox.shrink(), ), ), ), ), ], ), ), ), ); } } class _SectionLabel extends StatelessWidget { final String text; const _SectionLabel(this.text); @override Widget build(BuildContext context) { return Text( text, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), fontWeight: FontWeight.w600, letterSpacing: 0.5, ), ); } } class _TypeToggle extends StatelessWidget { final TransactionType selected; final ValueChanged onChanged; final AppStrings strings; const _TypeToggle({ required this.selected, required this.onChanged, required this.strings, }); @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(14), border: isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1), ), child: Row( children: [ _TypeOption( label: strings.typeIncome, icon: Icons.arrow_downward_rounded, color: AppColors.income, isSelected: selected == TransactionType.income, onTap: () => onChanged(TransactionType.income), ), _TypeOption( label: strings.typeExpense, icon: Icons.arrow_upward_rounded, color: AppColors.expense, isSelected: selected == TransactionType.expense, onTap: () => onChanged(TransactionType.expense), ), ], ), ); } } class _TypeOption extends StatelessWidget { final String label; final IconData icon; final Color color; final bool isSelected; final VoidCallback onTap; const _TypeOption({ required this.label, required this.icon, required this.color, required this.isSelected, required this.onTap, }); @override Widget build(BuildContext context) { return Expanded( child: GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( color: isSelected ? color.withOpacity(0.15) : Colors.transparent, borderRadius: BorderRadius.circular(13), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( icon, color: isSelected ? color : Theme.of(context).colorScheme.onSurface.withOpacity(0.6), size: 18, ), const SizedBox(width: 6), Text( label, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: isSelected ? color : Theme.of( context, ).colorScheme.onSurface.withOpacity(0.6), fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), ), ], ), ), ), ); } } class _CategoryPicker extends ConsumerWidget { final List categories; final String selected; final ValueChanged onChanged; const _CategoryPicker({ required this.categories, required this.selected, required this.onChanged, }); @override Widget build(BuildContext context, WidgetRef ref) { final s = ref.watch(stringsProvider); final isDark = Theme.of(context).brightness == Brightness.dark; return Wrap( spacing: 8, runSpacing: 8, children: categories.map((cat) { final isSelected = cat == selected; final color = AppCategories.colors[cat] ?? AppColors.accent; final icon = AppCategories.icons[cat] ?? Icons.category_rounded; return GestureDetector( onTap: () => onChanged(cat), child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: isSelected ? color.withOpacity(0.2) : Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), border: isSelected ? Border.all(color: color, width: 1.5) : (isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, color: isSelected ? color : Theme.of( context, ).colorScheme.onSurface.withOpacity(0.6), size: 16, ), const SizedBox(width: 6), Text( s.categoryLabel(cat), style: Theme.of(context).textTheme.bodySmall?.copyWith( color: isSelected ? color : Theme.of( context, ).colorScheme.onSurface.withOpacity(0.6), fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), ), ], ), ), ); }).toList(), ); } } class _CurrencyPicker extends StatelessWidget { final String selected; final void Function(String symbol, String code) onChanged; const _CurrencyPicker({required this.selected, required this.onChanged}); @override Widget build(BuildContext context) { final currencies = [ ('USD', '\$'), ('EUR', '€'), ('BYN', 'Br'), ('RUB', '₽'), ]; final colorScheme = Theme.of(context).colorScheme; return Row( children: currencies.map((c) { final isSelected = c.$1 == selected; return Expanded( child: GestureDetector( onTap: () => onChanged(c.$2, c.$1), child: Container( margin: EdgeInsets.only( right: c.$1 == currencies.last.$1 ? 0 : 8, ), padding: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( color: isSelected ? const Color(0xFF7C6DED).withOpacity(0.15) : Theme.of(context).cardColor, borderRadius: BorderRadius.circular(10), border: Border.all( color: isSelected ? const Color(0xFF7C6DED) : Colors.transparent, width: 1.5, ), ), child: Column( children: [ Text( c.$2, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: isSelected ? const Color(0xFF7C6DED) : colorScheme.onSurface, ), ), Text( c.$1, style: TextStyle( fontSize: 10, color: colorScheme.onSurface.withOpacity(0.5), ), ), ], ), ), ), ); }).toList(), ); } }