diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8650b06..d59bdea 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -74,6 +74,9 @@ "profileSubtitle": "Settings", "darkTheme": "Dark theme", + "profileLanguageTile": "Language", + "langEnglish": "English", + "langRussian": "Russian", "profileHint": "User profile, currency, locale and other settings will appear here.", "onboardingTitle": "Welcome", @@ -154,6 +157,8 @@ "iconPickerTitle": "Pick an icon", "colorPickerTitle": "Pick a color", + "currencyPickerTitle": "Pick a currency", + "currencyPickerSearchHint": "Search…", "colorPickerAllUsed": "All palette colors are already in use by other categories.", "iconGroupFood": "Food", "iconGroupTransport": "Transport", @@ -166,5 +171,32 @@ "iconGroupFinance": "Finance", "iconGroupOther": "Other", - "profileCategoriesTile": "Categories" + "profileCategoriesTile": "Categories", + "profileAccountsTile": "Accounts", + + "accountsEmpty": "No accounts yet. Tap + to add one.", + "accountNewTitle": "New account", + "accountEditTitle": "Edit account", + "accountNameLabel": "Name", + "accountNameHint": "e.g. Cash wallet", + "accountTypeLabel": "Type", + "accountTypeCash": "Cash", + "accountTypeCard": "Card", + "accountTypeBank": "Bank", + "accountTypeSavings": "Savings", + "accountCurrencyLabel": "Currency", + "accountInitialBalanceLabel": "Initial balance", + "accountIconLabel": "Icon", + "accountColorLabel": "Color", + "accountIsDefaultLabel": "Default account", + "accountIsDefaultHint": "Used when creating new transactions", + "accountSaveButton": "Create account", + "accountSaveEditButton": "Save changes", + "accountArchiveButton": "Archive", + "accountArchiveConfirmTitle": "Archive account?", + "accountArchiveConfirmMessage": "Archived accounts disappear from pickers and lists. Existing transactions keep their reference.", + "accountValidationNameRequired": "Enter a name", + "accountValidationNameTooLong": "Name is too long (max 50 chars)", + "accountSaveError": "Failed to save the account. Please try again.", + "accountArchiveError": "Failed to archive the account. Please try again." } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 01eed48..e4fb291 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -74,6 +74,9 @@ "profileSubtitle": "Настройки", "darkTheme": "Тёмная тема", + "profileLanguageTile": "Язык", + "langEnglish": "English", + "langRussian": "Русский", "profileHint": "Здесь появится профиль пользователя, валюта, локаль и другие настройки.", "onboardingTitle": "Добро пожаловать", @@ -154,6 +157,8 @@ "iconPickerTitle": "Выберите иконку", "colorPickerTitle": "Выберите цвет", + "currencyPickerTitle": "Выберите валюту", + "currencyPickerSearchHint": "Поиск…", "colorPickerAllUsed": "Все цвета палитры уже заняты другими категориями.", "iconGroupFood": "Еда", "iconGroupTransport": "Транспорт", @@ -166,5 +171,32 @@ "iconGroupFinance": "Финансы", "iconGroupOther": "Прочее", - "profileCategoriesTile": "Категории" + "profileCategoriesTile": "Категории", + "profileAccountsTile": "Счета", + + "accountsEmpty": "Счетов нет. Нажмите +, чтобы добавить.", + "accountNewTitle": "Новый счёт", + "accountEditTitle": "Редактировать счёт", + "accountNameLabel": "Название", + "accountNameHint": "напр. Кошелёк", + "accountTypeLabel": "Тип", + "accountTypeCash": "Наличные", + "accountTypeCard": "Карта", + "accountTypeBank": "Банк", + "accountTypeSavings": "Накопления", + "accountCurrencyLabel": "Валюта", + "accountInitialBalanceLabel": "Начальный баланс", + "accountIconLabel": "Иконка", + "accountColorLabel": "Цвет", + "accountIsDefaultLabel": "Счёт по умолчанию", + "accountIsDefaultHint": "Подставляется при создании транзакций", + "accountSaveButton": "Создать счёт", + "accountSaveEditButton": "Сохранить", + "accountArchiveButton": "Архивировать", + "accountArchiveConfirmTitle": "Архивировать счёт?", + "accountArchiveConfirmMessage": "Архивные счета исчезают из списков и пикеров. Существующие транзакции сохранят ссылку.", + "accountValidationNameRequired": "Введите название", + "accountValidationNameTooLong": "Слишком длинное название (макс. 50 символов)", + "accountSaveError": "Не удалось сохранить счёт. Попробуйте ещё раз.", + "accountArchiveError": "Не удалось архивировать счёт. Попробуйте ещё раз." } diff --git a/lib/src/app/app.dart b/lib/src/app/app.dart index cf2147b..a36c315 100644 --- a/lib/src/app/app.dart +++ b/lib/src/app/app.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:new_budget/l10n/app_localizations.dart'; +import 'locale/app_locale_controller.dart'; import 'router/app_router.dart'; import 'theme/app_theme.dart'; import 'theme/theme_mode_controller.dart'; @@ -13,6 +14,7 @@ class NewBudgetApp extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final router = ref.watch(appRouterProvider); final themeMode = ref.watch(themeModeControllerProvider); + final locale = ref.watch(appLocaleControllerProvider); return MaterialApp.router( title: 'NewBudget', @@ -20,6 +22,7 @@ class NewBudgetApp extends ConsumerWidget { theme: AppTheme.light(), darkTheme: AppTheme.dark(), themeMode: themeMode, + locale: locale, routerConfig: router, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, diff --git a/lib/src/app/router/app_router.dart b/lib/src/app/router/app_router.dart index a6b821e..a5af90d 100644 --- a/lib/src/app/router/app_router.dart +++ b/lib/src/app/router/app_router.dart @@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../core/database/converters/enum_converters.dart'; +import '../../features/accounts/presentation/screens/account_form_screen.dart'; import '../../features/accounts/presentation/screens/accounts_screen.dart'; import '../../features/analytics/presentation/screens/analytics_screen.dart'; import '../../features/categories/presentation/screens/categories_list_screen.dart'; @@ -81,6 +82,20 @@ GoRouter appRouter(Ref ref) { CategoryFormScreen(categoryId: state.pathParameters['id']), ), ), + GoRoute( + path: AppRoutes.accountNew, + pageBuilder: (context, state) => _slideUpPage( + state, + const AccountFormScreen(), + ), + ), + GoRoute( + path: AppRoutes.accountEditPattern, + pageBuilder: (context, state) => _slideUpPage( + state, + AccountFormScreen(accountId: state.pathParameters['id']), + ), + ), StatefulShellRoute.indexedStack( builder: (context, state, navigationShell) => AppScaffold( navigationShell: navigationShell, diff --git a/lib/src/app/router/app_routes.dart b/lib/src/app/router/app_routes.dart index 0c43dfc..623cd39 100644 --- a/lib/src/app/router/app_routes.dart +++ b/lib/src/app/router/app_routes.dart @@ -15,4 +15,8 @@ class AppRoutes { static const categoryNew = '/categories/new'; static const categoryEditPattern = '/categories/edit/:id'; static String categoryEdit(String id) => '/categories/edit/$id'; + + static const accountNew = '/accounts/new'; + static const accountEditPattern = '/accounts/edit/:id'; + static String accountEdit(String id) => '/accounts/edit/$id'; } diff --git a/lib/src/core/database/app_database.dart b/lib/src/core/database/app_database.dart index 2848272..296e06a 100644 --- a/lib/src/core/database/app_database.dart +++ b/lib/src/core/database/app_database.dart @@ -39,7 +39,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase.forTesting(super.executor); @override - int get schemaVersion => 3; + int get schemaVersion => 4; @override MigrationStrategy get migration => MigrationStrategy( @@ -60,6 +60,10 @@ class AppDatabase extends _$AppDatabase { // v2 → v3: добавлено поле extra_info в transactions. await m.addColumn(transactionsTable, transactionsTable.extraInfo); } + if (from < 4) { + // v3 → v4: добавлено поле is_default в accounts. + await m.addColumn(accountsTable, accountsTable.isDefault); + } }, ); diff --git a/lib/src/core/database/daos/accounts_dao.dart b/lib/src/core/database/daos/accounts_dao.dart index 241000c..1407788 100644 --- a/lib/src/core/database/daos/accounts_dao.dart +++ b/lib/src/core/database/daos/accounts_dao.dart @@ -29,13 +29,33 @@ class AccountsDao extends DatabaseAccessor Future insertAccount(AccountsTableCompanion companion) => into(accountsTable).insert(companion); - Future updateAccount(AccountsTableCompanion companion) => - update(accountsTable).replace(companion); + Future updateAccount(AccountsTableCompanion companion) => + (update(accountsTable)..where((t) => t.id.equals(companion.id.value))) + .write(companion); Future archiveAccount(String id) => (update(accountsTable) ..where((t) => t.id.equals(id))) .write(const AccountsTableCompanion(archived: Value(true))); + /// Реактивный умолчательный счёт пользователя (или null). + Stream watchDefaultAccount(String userId) => + (select(accountsTable) + ..where((t) => t.userId.equals(userId) & t.isDefault.equals(true)) + ..limit(1)) + .watchSingleOrNull(); + + /// Атомарная смена умолчательного счёта. + /// id == null — снять дефолт со всех счетов пользователя. + Future setDefaultAccount(String? id, String userId) => + transaction(() async { + await (update(accountsTable)..where((t) => t.userId.equals(userId))) + .write(const AccountsTableCompanion(isDefault: Value(false))); + if (id != null) { + await (update(accountsTable)..where((t) => t.id.equals(id))) + .write(const AccountsTableCompanion(isDefault: Value(true))); + } + }); + /// Реактивный текущий баланс счёта. /// /// Формула: `initialBalance + Σincome − Σexpense + Σtransfer_in − Σtransfer_out`, diff --git a/lib/src/core/database/tables/accounts_table.dart b/lib/src/core/database/tables/accounts_table.dart index 61c4486..0532112 100644 --- a/lib/src/core/database/tables/accounts_table.dart +++ b/lib/src/core/database/tables/accounts_table.dart @@ -26,6 +26,7 @@ class AccountsTable extends Table { IntColumn get colorValue => integer().nullable()(); BoolColumn get archived => boolean().withDefault(const Constant(false))(); + BoolColumn get isDefault => boolean().withDefault(const Constant(false))(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); @override diff --git a/lib/src/features/accounts/application/accounts_controller.dart b/lib/src/features/accounts/application/accounts_controller.dart index 62cefbd..c88d2a5 100644 --- a/lib/src/features/accounts/application/accounts_controller.dart +++ b/lib/src/features/accounts/application/accounts_controller.dart @@ -15,6 +15,11 @@ Stream> accountsStream(Ref ref, String userId) => Stream accountBalance(Ref ref, String accountId) => ref.watch(accountRepositoryProvider).watchBalance(accountId); +/// Умолчательный счёт пользователя (или null если не задан). +@riverpod +Stream defaultAccount(Ref ref, String userId) => + ref.watch(accountRepositoryProvider).watchDefault(userId); + /// Контроллер CRUD-операций над счетами. @Riverpod(keepAlive: true) class AccountsController extends _$AccountsController { @@ -31,8 +36,8 @@ class AccountsController extends _$AccountsController { int? colorValue, }) async { state = const AsyncLoading(); - final result = await AsyncValue.guard( - () => ref.read(accountRepositoryProvider).create( + try { + final account = await ref.read(accountRepositoryProvider).create( userId: userId, name: name, type: type, @@ -40,25 +45,30 @@ class AccountsController extends _$AccountsController { initialBalance: initialBalance, iconCode: iconCode, colorValue: colorValue, - ), - ); - state = result.hasError - ? AsyncError(result.error!, StackTrace.current) - : const AsyncData(null); - return result.value!; + ); + state = const AsyncData(null); + return account; + } catch (e, st) { + state = AsyncError(e, st); + rethrow; + } } Future updateAccount(Account account) async { state = const AsyncLoading(); - final result = await AsyncValue.guard( - () => ref.read(accountRepositoryProvider).update(account), - ); - state = result.hasError - ? AsyncError(result.error!, StackTrace.current) - : const AsyncData(null); - return result.value!; + try { + final updated = await ref.read(accountRepositoryProvider).update(account); + state = const AsyncData(null); + return updated; + } catch (e, st) { + state = AsyncError(e, st); + rethrow; + } } + Future setDefaultAccount(String? id, String userId) => + ref.read(accountRepositoryProvider).setDefault(id, userId); + Future archiveAccount(String id) async { state = const AsyncLoading(); state = await AsyncValue.guard( diff --git a/lib/src/features/accounts/data/mappers/account_mapper.dart b/lib/src/features/accounts/data/mappers/account_mapper.dart index 1b9a339..32a0bcd 100644 --- a/lib/src/features/accounts/data/mappers/account_mapper.dart +++ b/lib/src/features/accounts/data/mappers/account_mapper.dart @@ -12,6 +12,7 @@ extension AccountMapper on AccountsTableData { iconCode: iconCode, colorValue: colorValue, archived: archived, + isDefault: isDefault, createdAt: createdAt, ); } diff --git a/lib/src/features/accounts/data/repositories/account_repository_impl.dart b/lib/src/features/accounts/data/repositories/account_repository_impl.dart index 7e53d27..6773eaa 100644 --- a/lib/src/features/accounts/data/repositories/account_repository_impl.dart +++ b/lib/src/features/accounts/data/repositories/account_repository_impl.dart @@ -65,6 +65,14 @@ class AccountRepositoryImpl implements AccountRepository { @override Future archive(String id) => _dao.archiveAccount(id); + @override + Stream watchDefault(String userId) => + _dao.watchDefaultAccount(userId).map((row) => row?.toDomain()); + + @override + Future setDefault(String? id, String userId) => + _dao.setDefaultAccount(id, userId); + @override Stream watchBalance(String accountId) => _dao.watchAccountBalance(accountId); } diff --git a/lib/src/features/accounts/domain/entities/account.dart b/lib/src/features/accounts/domain/entities/account.dart index 6bf4271..0e83fc9 100644 --- a/lib/src/features/accounts/domain/entities/account.dart +++ b/lib/src/features/accounts/domain/entities/account.dart @@ -15,6 +15,7 @@ abstract class Account with _$Account { int? iconCode, int? colorValue, required bool archived, + @Default(false) bool isDefault, required DateTime createdAt, }) = _Account; } diff --git a/lib/src/features/accounts/domain/repositories/account_repository.dart b/lib/src/features/accounts/domain/repositories/account_repository.dart index 12442da..6100a21 100644 --- a/lib/src/features/accounts/domain/repositories/account_repository.dart +++ b/lib/src/features/accounts/domain/repositories/account_repository.dart @@ -16,6 +16,12 @@ abstract interface class AccountRepository { Future update(Account account); Future archive(String id); + /// Реактивный умолчательный счёт пользователя. + Stream watchDefault(String userId); + + /// Установить (или снять, если id == null) умолчательный счёт. + Future setDefault(String? id, String userId); + /// Текущий баланс счёта (начальный + агрегат транзакций). Stream watchBalance(String accountId); } diff --git a/lib/src/features/accounts/presentation/screens/accounts_screen.dart b/lib/src/features/accounts/presentation/screens/accounts_screen.dart index edb896c..52596be 100644 --- a/lib/src/features/accounts/presentation/screens/accounts_screen.dart +++ b/lib/src/features/accounts/presentation/screens/accounts_screen.dart @@ -1,17 +1,205 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import '../../../../app/l10n/l10n.dart'; -import '../../../../shared/widgets/placeholder_screen.dart'; +import '../../../../app/router/app_routes.dart'; +import '../../../../app/theme/app_colors.dart'; +import '../../../home/presentation/widgets/money_text.dart'; +import '../../../user/application/active_user_controller.dart'; +import '../../application/accounts_controller.dart'; +import '../../domain/entities/account.dart'; +import '../widgets/account_icon.dart'; -class AccountsScreen extends StatelessWidget { +class AccountsScreen extends ConsumerWidget { const AccountsScreen({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final p = context.palette; + final activeUser = ref.watch(activeUserControllerProvider); + + return Scaffold( + backgroundColor: p.paper, + body: SafeArea( + child: activeUser.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, _) => Center(child: Text('$e')), + data: (user) { + if (user == null) return const SizedBox.shrink(); + return _AccountsList(userId: user.id); + }, + ), + ), + floatingActionButton: FloatingActionButton( + backgroundColor: p.accent, + foregroundColor: p.paper, + onPressed: () => context.push(AppRoutes.accountNew), + child: const Icon(Icons.add), + ), + ); + } +} + +class _AccountsList extends ConsumerWidget { + const _AccountsList({required this.userId}); + final String userId; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final p = context.palette; final l10n = context.l10n; - return PlaceholderScreen( - subtitle: l10n.accountsSubtitle, - title: l10n.navAccounts, + final accountsAsync = ref.watch(accountsStreamProvider(userId)); + final defaultAsync = ref.watch(defaultAccountProvider(userId)); + final defaultId = defaultAsync.value?.id; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 14, 16, 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.accountsSubtitle.toUpperCase(), + style: TextStyle( + fontSize: 11, + color: p.ink2, + letterSpacing: 0.6, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 2), + Text( + l10n.navAccounts, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: p.ink, + ), + ), + ], + ), + ), + Expanded( + child: accountsAsync.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, _) => Center(child: Text('$e')), + data: (accounts) { + if (accounts.isEmpty) { + return Center( + child: Text( + l10n.accountsEmpty, + style: TextStyle(fontSize: 14, color: p.ink2), + textAlign: TextAlign.center, + ), + ); + } + return ListView.builder( + padding: const EdgeInsets.fromLTRB(12, 4, 12, 80), + itemCount: accounts.length, + itemBuilder: (context, i) => _AccountTile( + account: accounts[i], + isDefault: accounts[i].id == defaultId, + onTap: () => context.push( + AppRoutes.accountEdit(accounts[i].id), + ), + ), + ); + }, + ), + ), + ], + ); + } +} + +class _AccountTile extends ConsumerWidget { + const _AccountTile({ + required this.account, + required this.isDefault, + required this.onTap, + }); + + final Account account; + final bool isDefault; + final VoidCallback onTap; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final p = context.palette; + final balance = ref.watch(accountBalanceProvider(account.id)).value ?? 0; + final color = Color(account.colorValue ?? 0xFFB8B5AC); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4), + child: Material( + color: p.paper2, + borderRadius: BorderRadius.circular(14), + child: InkWell( + borderRadius: BorderRadius.circular(14), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), + child: Row( + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: color.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(iconForAccount(account), size: 22, color: color), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Flexible( + child: Text( + account.name, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: p.ink, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (isDefault) ...[ + const SizedBox(width: 6), + Icon(Icons.star_rounded, + size: 14, color: p.accent), + ], + ], + ), + const SizedBox(height: 2), + Text( + shortAccountLabel(account), + style: TextStyle(fontSize: 12, color: p.ink2), + ), + ], + ), + ), + MoneyText( + balance, + color: p.ink, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + const SizedBox(width: 4), + Icon(Icons.chevron_right, size: 16, color: p.ink2), + ], + ), + ), + ), + ), ); } } diff --git a/lib/src/features/accounts/presentation/widgets/account_icon.dart b/lib/src/features/accounts/presentation/widgets/account_icon.dart index 00b6e87..e12bfdc 100644 --- a/lib/src/features/accounts/presentation/widgets/account_icon.dart +++ b/lib/src/features/accounts/presentation/widgets/account_icon.dart @@ -16,15 +16,4 @@ IconData iconForAccount(Account a) { } } -String shortAccountLabel(Account a) { - switch (a.type) { - case AccountType.cash: - return 'Кэш'; - case AccountType.card: - return 'Карта'; - case AccountType.bank: - return 'Банк'; - case AccountType.savings: - return 'Копилка'; - } -} +String shortAccountLabel(Account a) => a.name; diff --git a/lib/src/features/home/presentation/widgets/account_tabs.dart b/lib/src/features/home/presentation/widgets/account_tabs.dart index e282c34..a23102c 100644 --- a/lib/src/features/home/presentation/widgets/account_tabs.dart +++ b/lib/src/features/home/presentation/widgets/account_tabs.dart @@ -15,9 +15,9 @@ class AccountTabs extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final accounts = - ref.watch(accountsStreamProvider(userId)).value ?? - const []; + final accounts = [ + ...ref.watch(accountsStreamProvider(userId)).value ?? const [], + ]..sort((a, b) => (b.isDefault ? 1 : 0) - (a.isDefault ? 1 : 0)); final selected = ref.watch(selectedAccountProvider); return SizedBox( diff --git a/lib/src/features/profile/presentation/screens/profile_screen.dart b/lib/src/features/profile/presentation/screens/profile_screen.dart index 71c6a57..15d6aa5 100644 --- a/lib/src/features/profile/presentation/screens/profile_screen.dart +++ b/lib/src/features/profile/presentation/screens/profile_screen.dart @@ -42,6 +42,12 @@ class ProfileScreen extends ConsumerWidget { ), ), Container(height: 1, color: p.line), + _NavRow( + icon: Icons.account_balance_wallet_outlined, + title: l10n.profileAccountsTile, + onTap: () => context.go(AppRoutes.accounts), + ), + Container(height: 1, color: p.line), _NavRow( icon: Icons.label_outline, title: l10n.profileCategoriesTile, diff --git a/lib/src/features/transactions/presentation/screens/transaction_form_screen.dart b/lib/src/features/transactions/presentation/screens/transaction_form_screen.dart index ee24203..245fced 100644 --- a/lib/src/features/transactions/presentation/screens/transaction_form_screen.dart +++ b/lib/src/features/transactions/presentation/screens/transaction_form_screen.dart @@ -297,6 +297,21 @@ class _FormBodyState extends ConsumerState<_FormBody> { ref.read(transactionDraftControllerProvider(widget.txId).notifier); _syncControllersFromDraft(draft); + // Подставить умолчательный счёт при открытии формы создания. + if (widget.txId == null) { + final defaultAccount = ref.watch(defaultAccountProvider(widget.userId)); + if (draft.accountId == null && defaultAccount.value != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + final current = ref.read( + transactionDraftControllerProvider(widget.txId)); + if (current.accountId == null) { + draftCtrl.setAccount(defaultAccount.value!.id); + } + }); + } + } + final accounts = ref.watch(accountsStreamProvider(widget.userId)).value ?? const []; final accountById = {for (final a in accounts) a.id: a}; diff --git a/lib/src/features/transactions/presentation/widgets/account_picker_sheet.dart b/lib/src/features/transactions/presentation/widgets/account_picker_sheet.dart index 5991cc3..79e0874 100644 --- a/lib/src/features/transactions/presentation/widgets/account_picker_sheet.dart +++ b/lib/src/features/transactions/presentation/widgets/account_picker_sheet.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import '../../../../app/l10n/l10n.dart'; +import '../../../../app/router/app_routes.dart'; import '../../../../app/theme/app_colors.dart'; import '../../../accounts/application/accounts_controller.dart'; import '../../../accounts/domain/entities/account.dart'; @@ -99,10 +101,11 @@ class _AccountPickerSheet extends ConsumerWidget { if (i == accounts.length) { return _AddAccountTile( label: l10n.pickerAddAccount, - onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.comingSoonShort)), - ); + onTap: () async { + final id = await context.push(AppRoutes.accountNew); + if (id != null && context.mounted) { + Navigator.of(context).pop(id); + } }, ); }