From 1f6d129fc29ca3f59de7e308fb14816ac6d92f84 Mon Sep 17 00:00:00 2001 From: kolo Date: Sun, 29 Mar 2026 15:42:11 +0300 Subject: [PATCH] stableee --- lib/core/l10n/app_strings.dart | 13 ++++ lib/features/add_transaction/screen.dart | 63 ++++--------------- .../add_transaction/widgets/account_row.dart | 19 ++++-- .../dashboard/widgets/transaction_tile.dart | 18 +++--- 4 files changed, 44 insertions(+), 69 deletions(-) diff --git a/lib/core/l10n/app_strings.dart b/lib/core/l10n/app_strings.dart index 3157152..da1e4a3 100644 --- a/lib/core/l10n/app_strings.dart +++ b/lib/core/l10n/app_strings.dart @@ -190,5 +190,18 @@ class AppStrings { String get showConversionsOnCard => _ru ? 'Показывать на карточке' : 'Show on balance card'; + String get selectSourceAccount => + _ru ? 'Выберите счёт отправителя' : 'Select source account'; + String get selectDestAccount => + _ru ? 'Выберите счёт получателя' : 'Select destination account'; + String get accountsMustDiffer => + _ru ? 'Счета должны отличаться' : 'Accounts must differ'; + String get transferLabel => _ru ? 'Перевод' : 'Transfer'; + String get transferTo => _ru ? 'на' : 'to'; + String get transferFrom => _ru ? 'с' : 'from'; + String get selectAccount => _ru ? 'Выберите счёт' : 'Select account'; + String get accountPlaceholder => _ru ? 'Счёт' : 'Account'; + String get saveError => _ru ? 'Ошибка сохранения' : 'Save error'; + String get dateLocale => _ru ? 'ru_RU' : 'en_US'; } diff --git a/lib/features/add_transaction/screen.dart b/lib/features/add_transaction/screen.dart index 5313aa1..0dc19a9 100644 --- a/lib/features/add_transaction/screen.dart +++ b/lib/features/add_transaction/screen.dart @@ -80,13 +80,11 @@ class _AddTransactionScreenState extends ConsumerState _amountController.text = widget.initial!.amount.toString(); _noteController.text = widget.initial!.note ?? ''; - // Pre-populate toAccountId when opening Transfer for edit if (widget.initial!.category == 'Transfer') { WidgetsBinding.instance.addPostFrameCallback((_) { final allTxs = ref.read(transactionsProvider).valueOrNull ?? []; if (widget.initial!.type == TransactionType.expense) { - // widget.initial IS the expense side — find income counterpart for To final counterpart = allTxs.firstWhereOrNull( (t) => t.id != widget.initial!.id && @@ -110,7 +108,6 @@ class _AddTransactionScreenState extends ConsumerState _transferIncomeRecordId = counterpart?.id; }); } else { - // widget.initial IS the income side — find expense counterpart final expenseRecord = allTxs.firstWhereOrNull( (t) => t.id != widget.initial!.id && @@ -125,7 +122,6 @@ class _AddTransactionScreenState extends ConsumerState t.note == widget.initial!.note, ); if (expenseRecord != null) { - // Swap: From = expense account, To = this income account ref .read(addTransactionProvider(widget.initial).notifier) .setAccountId(expenseRecord.accountId); @@ -145,7 +141,6 @@ class _AddTransactionScreenState extends ConsumerState 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; @@ -153,7 +148,6 @@ class _AddTransactionScreenState extends ConsumerState .read(addTransactionProvider(null).notifier) .setCurrency(currencySymbol, currencyCode); - // Set the selected account if there's an active account if (activeAccount != null) { ref .read(addTransactionProvider(null).notifier) @@ -191,7 +185,7 @@ class _AddTransactionScreenState extends ConsumerState final parts = normalized.split('.'); if (parts.length == 2 && parts[1].length > 2) return null; - return normalized; // valid, return normalized string + return normalized; } void _triggerError() { @@ -200,6 +194,7 @@ class _AddTransactionScreenState extends ConsumerState } Future _submit() async { + final s = ref.read(stringsProvider); final raw = _amountController.text; final parsed = _validateAndParseAmount(raw); @@ -216,17 +211,17 @@ class _AddTransactionScreenState extends ConsumerState bool hasError = false; if (state.selectedAccountId == null) { - setState(() => _fromAccountError = 'Please select a source account'); + setState(() => _fromAccountError = s.selectSourceAccount); hasError = true; } else { setState(() => _fromAccountError = null); } if (state.toAccountId == null) { - setState(() => _toAccountError = 'Please select a destination account'); + setState(() => _toAccountError = s.selectDestAccount); hasError = true; } else if (state.toAccountId == state.selectedAccountId) { - setState(() => _toAccountError = 'Source and destination must differ'); + setState(() => _toAccountError = s.accountsMustDiffer); hasError = true; } else { setState(() => _toAccountError = null); @@ -256,14 +251,7 @@ class _AddTransactionScreenState extends ConsumerState : _noteController.text.trim(); try { - print('--- SUBMIT CLICKED ---'); - print( - 'Amount: $amount, Category: ${state.category}, Type: ${state.type.name}', - ); - if (state.type == TransactionType.transfer) { - // Handle transfer: create two transactions - // Get currency with fallback to global currency final curr = ref.read(currencyProvider); final currency = state.overrideCurrency.isNotEmpty ? state.overrideCurrency @@ -273,8 +261,6 @@ class _AddTransactionScreenState extends ConsumerState : curr.code; if (state.isEditing) { - // Update both sides of the transfer pair - // Update the expense side final updatedExpense = Transaction( id: _transferExpenseRecordId ?? widget.initial!.id, amount: amount, @@ -284,22 +270,21 @@ class _AddTransactionScreenState extends ConsumerState note: note, currency: currency, currencyCode: currencyCode, - accountId: state.selectedAccountId!, // from initState + accountId: state.selectedAccountId!, ); await ref.read(transactionsProvider.notifier).update(updatedExpense); - // Update the income side if (_transferIncomeRecordId != null) { final updatedIncome = Transaction( id: _transferIncomeRecordId!, - amount: amount, // updated amount + amount: amount, category: 'Transfer', type: TransactionType.income, date: finalDateTime, note: note, - currency: currency, // updated currency + currency: currency, currencyCode: currencyCode, - accountId: state.toAccountId!, // from initState + accountId: state.toAccountId!, ); await ref.read(transactionsProvider.notifier).update(updatedIncome); } @@ -332,12 +317,9 @@ class _AddTransactionScreenState extends ConsumerState accountId: state.toAccountId!, ); - print('Creating transfer transactions...'); await ref.read(transactionsProvider.notifier).add(expense); await ref.read(transactionsProvider.notifier).add(income); - print('Transfer completed'); } else { - // Handle regular income/expense final activeAccount = ref.read(activeAccountProvider); final selectedId = ref .read(addTransactionProvider(widget.initial)) @@ -345,21 +327,13 @@ class _AddTransactionScreenState extends ConsumerState 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; } @@ -375,42 +349,27 @@ class _AddTransactionScreenState extends ConsumerState 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); - + } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Save error: $e'), + content: Text('${s.saveError}: $e'), backgroundColor: Colors.red, duration: const Duration(seconds: 5), ), diff --git a/lib/features/add_transaction/widgets/account_row.dart b/lib/features/add_transaction/widgets/account_row.dart index fe47021..ca6cea5 100644 --- a/lib/features/add_transaction/widgets/account_row.dart +++ b/lib/features/add_transaction/widgets/account_row.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/l10n/locale_provider.dart'; import '../../../shared/models/account.dart'; import '../../../shared/models/transaction.dart'; import '../../dashboard/provider.dart'; @@ -37,12 +38,12 @@ class AccountRow extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final s = ref.watch(stringsProvider); final state = ref.watch(addTransactionProvider(initial)); final accountsAsync = ref.watch(accountsProvider); final accounts = accountsAsync.valueOrNull ?? []; final isTransfer = state.type == TransactionType.transfer; - // Auto-select toAccount when only 2 accounts exist if (isTransfer && accounts.length == 2 && state.selectedAccountId != null) { final otherId = accounts .firstWhere( @@ -63,7 +64,7 @@ class AccountRow extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Account', + s.accountPlaceholder, style: TextStyle( fontSize: 13, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), @@ -95,6 +96,7 @@ class AccountRow extends ConsumerWidget { indicatorKey: fromIndicatorKey, error: fromAccountError, isDark: isDark, + selectAccountText: s.selectAccount, ), ], ); @@ -109,6 +111,7 @@ class _SingleAccountSelector extends ConsumerWidget { final GlobalKey indicatorKey; final String? error; final bool isDark; + final String selectAccountText; const _SingleAccountSelector({ required this.initial, @@ -118,6 +121,7 @@ class _SingleAccountSelector extends ConsumerWidget { required this.indicatorKey, this.error, required this.isDark, + required this.selectAccountText, }); @override @@ -184,7 +188,7 @@ class _SingleAccountSelector extends ConsumerWidget { const SizedBox(width: 10), Expanded( child: Text( - displayAccount?.name ?? 'Select account', + displayAccount?.name ?? selectAccountText, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: displayAccount != null ? Theme.of(context).colorScheme.onSurface @@ -253,6 +257,7 @@ class _TransferAccountRow extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final s = ref.watch(stringsProvider); final state = ref.watch(addTransactionProvider(initial)); final activeAccount = ref.watch(activeAccountProvider); @@ -275,8 +280,6 @@ class _TransferAccountRow extends ConsumerWidget { ), ); } else { - // If no account is explicitly selected and we're on Total Balance - // creating a new transfer — show empty, force user to choose if (activeAccount == null && initial == null) { fromAccount = null; } else { @@ -306,6 +309,7 @@ class _TransferAccountRow extends ConsumerWidget { error: fromAccountError, isDark: isDark, disabled: isFromAccountLocked, + selectText: s.selectAccount, ), ), Padding( @@ -328,6 +332,7 @@ class _TransferAccountRow extends ConsumerWidget { error: toAccountError, isDark: isDark, disabled: autoSelectEnabled || isToAccountLocked, + selectText: s.selectAccount, ), ), ], @@ -344,6 +349,7 @@ class _AccountHalf extends StatelessWidget { final String? error; final bool isDark; final bool disabled; + final String selectText; const _AccountHalf({ required this.account, @@ -354,6 +360,7 @@ class _AccountHalf extends StatelessWidget { this.error, required this.isDark, this.disabled = false, + required this.selectText, }); @override @@ -413,7 +420,7 @@ class _AccountHalf extends StatelessWidget { ), const SizedBox(height: 2), Text( - account?.name ?? 'Select', + account?.name ?? selectText, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, diff --git a/lib/features/dashboard/widgets/transaction_tile.dart b/lib/features/dashboard/widgets/transaction_tile.dart index 5fc0d1e..d8d8daa 100644 --- a/lib/features/dashboard/widgets/transaction_tile.dart +++ b/lib/features/dashboard/widgets/transaction_tile.dart @@ -274,10 +274,10 @@ class TransactionTile extends ConsumerWidget { List accounts, Account? activeAccount, ) { - // Fallback if no counterpart + final s = ref.watch(stringsProvider); if (counterpart == null) { return Text( - 'Transfer', + s.transferLabel, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, @@ -298,13 +298,12 @@ class TransactionTile extends ConsumerWidget { final destAccountName = _accountName(accounts, destAccountId); final onSurface = Theme.of(context).colorScheme.onSurface; - // Total Balance view (activeAccount == null), showing expense side if (activeAccount == null) { return Row( mainAxisSize: MainAxisSize.min, children: [ Text( - 'Transfer', + s.transferLabel, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: onSurface, @@ -321,14 +320,12 @@ class TransactionTile extends ConsumerWidget { ); } - // Account view if (isExpense) { - // Expense side: Transfer to return Row( mainAxisSize: MainAxisSize.min, children: [ Text( - 'Transfer', + s.transferLabel, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: onSurface, @@ -336,7 +333,7 @@ class TransactionTile extends ConsumerWidget { ), const SizedBox(width: 6), Text( - 'to', + s.transferTo, style: TextStyle( fontSize: 11, color: onSurface.withOpacity(0.45), @@ -348,12 +345,11 @@ class TransactionTile extends ConsumerWidget { ], ); } else { - // Income side: Transfer from return Row( mainAxisSize: MainAxisSize.min, children: [ Text( - 'Transfer', + s.transferLabel, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: onSurface, @@ -361,7 +357,7 @@ class TransactionTile extends ConsumerWidget { ), const SizedBox(width: 6), Text( - 'from', + s.transferFrom, style: TextStyle( fontSize: 11, color: onSurface.withOpacity(0.45),