Add accs
This commit is contained in:
+34
-1
@@ -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,33 @@
|
||||
"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.",
|
||||
"accountSetDefaultError": "Account saved, but couldn't mark it as default."
|
||||
}
|
||||
|
||||
@@ -254,6 +254,24 @@ abstract class AppLocalizations {
|
||||
/// **'Тёмная тема'**
|
||||
String get darkTheme;
|
||||
|
||||
/// No description provided for @profileLanguageTile.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Язык'**
|
||||
String get profileLanguageTile;
|
||||
|
||||
/// No description provided for @langEnglish.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'English'**
|
||||
String get langEnglish;
|
||||
|
||||
/// No description provided for @langRussian.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Русский'**
|
||||
String get langRussian;
|
||||
|
||||
/// No description provided for @profileHint.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
@@ -620,6 +638,18 @@ abstract class AppLocalizations {
|
||||
/// **'Выберите цвет'**
|
||||
String get colorPickerTitle;
|
||||
|
||||
/// No description provided for @currencyPickerTitle.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Выберите валюту'**
|
||||
String get currencyPickerTitle;
|
||||
|
||||
/// No description provided for @currencyPickerSearchHint.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Поиск…'**
|
||||
String get currencyPickerSearchHint;
|
||||
|
||||
/// No description provided for @colorPickerAllUsed.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
@@ -691,6 +721,168 @@ abstract class AppLocalizations {
|
||||
/// In ru, this message translates to:
|
||||
/// **'Категории'**
|
||||
String get profileCategoriesTile;
|
||||
|
||||
/// No description provided for @profileAccountsTile.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Счета'**
|
||||
String get profileAccountsTile;
|
||||
|
||||
/// No description provided for @accountsEmpty.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Счетов нет. Нажмите +, чтобы добавить.'**
|
||||
String get accountsEmpty;
|
||||
|
||||
/// No description provided for @accountNewTitle.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Новый счёт'**
|
||||
String get accountNewTitle;
|
||||
|
||||
/// No description provided for @accountEditTitle.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Редактировать счёт'**
|
||||
String get accountEditTitle;
|
||||
|
||||
/// No description provided for @accountNameLabel.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Название'**
|
||||
String get accountNameLabel;
|
||||
|
||||
/// No description provided for @accountNameHint.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'напр. Кошелёк'**
|
||||
String get accountNameHint;
|
||||
|
||||
/// No description provided for @accountTypeLabel.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Тип'**
|
||||
String get accountTypeLabel;
|
||||
|
||||
/// No description provided for @accountTypeCash.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Наличные'**
|
||||
String get accountTypeCash;
|
||||
|
||||
/// No description provided for @accountTypeCard.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Карта'**
|
||||
String get accountTypeCard;
|
||||
|
||||
/// No description provided for @accountTypeBank.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Банк'**
|
||||
String get accountTypeBank;
|
||||
|
||||
/// No description provided for @accountTypeSavings.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Накопления'**
|
||||
String get accountTypeSavings;
|
||||
|
||||
/// No description provided for @accountCurrencyLabel.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Валюта'**
|
||||
String get accountCurrencyLabel;
|
||||
|
||||
/// No description provided for @accountInitialBalanceLabel.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Начальный баланс'**
|
||||
String get accountInitialBalanceLabel;
|
||||
|
||||
/// No description provided for @accountIconLabel.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Иконка'**
|
||||
String get accountIconLabel;
|
||||
|
||||
/// No description provided for @accountColorLabel.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Цвет'**
|
||||
String get accountColorLabel;
|
||||
|
||||
/// No description provided for @accountIsDefaultLabel.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Счёт по умолчанию'**
|
||||
String get accountIsDefaultLabel;
|
||||
|
||||
/// No description provided for @accountIsDefaultHint.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Подставляется при создании транзакций'**
|
||||
String get accountIsDefaultHint;
|
||||
|
||||
/// No description provided for @accountSaveButton.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Создать счёт'**
|
||||
String get accountSaveButton;
|
||||
|
||||
/// No description provided for @accountSaveEditButton.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Сохранить'**
|
||||
String get accountSaveEditButton;
|
||||
|
||||
/// No description provided for @accountArchiveButton.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Архивировать'**
|
||||
String get accountArchiveButton;
|
||||
|
||||
/// No description provided for @accountArchiveConfirmTitle.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Архивировать счёт?'**
|
||||
String get accountArchiveConfirmTitle;
|
||||
|
||||
/// No description provided for @accountArchiveConfirmMessage.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Архивные счета исчезают из списков и пикеров. Существующие транзакции сохранят ссылку.'**
|
||||
String get accountArchiveConfirmMessage;
|
||||
|
||||
/// No description provided for @accountValidationNameRequired.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Введите название'**
|
||||
String get accountValidationNameRequired;
|
||||
|
||||
/// No description provided for @accountValidationNameTooLong.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Слишком длинное название (макс. 50 символов)'**
|
||||
String get accountValidationNameTooLong;
|
||||
|
||||
/// No description provided for @accountSaveError.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Не удалось сохранить счёт. Попробуйте ещё раз.'**
|
||||
String get accountSaveError;
|
||||
|
||||
/// No description provided for @accountArchiveError.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Не удалось архивировать счёт. Попробуйте ещё раз.'**
|
||||
String get accountArchiveError;
|
||||
|
||||
/// No description provided for @accountSetDefaultError.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Счёт сохранён, но не удалось сделать его умолчательным.'**
|
||||
String get accountSetDefaultError;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -114,6 +114,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get darkTheme => 'Dark theme';
|
||||
|
||||
@override
|
||||
String get profileLanguageTile => 'Language';
|
||||
|
||||
@override
|
||||
String get langEnglish => 'English';
|
||||
|
||||
@override
|
||||
String get langRussian => 'Russian';
|
||||
|
||||
@override
|
||||
String get profileHint =>
|
||||
'User profile, currency, locale and other settings will appear here.';
|
||||
@@ -319,6 +328,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get colorPickerTitle => 'Pick a color';
|
||||
|
||||
@override
|
||||
String get currencyPickerTitle => 'Pick a currency';
|
||||
|
||||
@override
|
||||
String get currencyPickerSearchHint => 'Search…';
|
||||
|
||||
@override
|
||||
String get colorPickerAllUsed =>
|
||||
'All palette colors are already in use by other categories.';
|
||||
@@ -355,4 +370,89 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get profileCategoriesTile => 'Categories';
|
||||
|
||||
@override
|
||||
String get profileAccountsTile => 'Accounts';
|
||||
|
||||
@override
|
||||
String get accountsEmpty => 'No accounts yet. Tap + to add one.';
|
||||
|
||||
@override
|
||||
String get accountNewTitle => 'New account';
|
||||
|
||||
@override
|
||||
String get accountEditTitle => 'Edit account';
|
||||
|
||||
@override
|
||||
String get accountNameLabel => 'Name';
|
||||
|
||||
@override
|
||||
String get accountNameHint => 'e.g. Cash wallet';
|
||||
|
||||
@override
|
||||
String get accountTypeLabel => 'Type';
|
||||
|
||||
@override
|
||||
String get accountTypeCash => 'Cash';
|
||||
|
||||
@override
|
||||
String get accountTypeCard => 'Card';
|
||||
|
||||
@override
|
||||
String get accountTypeBank => 'Bank';
|
||||
|
||||
@override
|
||||
String get accountTypeSavings => 'Savings';
|
||||
|
||||
@override
|
||||
String get accountCurrencyLabel => 'Currency';
|
||||
|
||||
@override
|
||||
String get accountInitialBalanceLabel => 'Initial balance';
|
||||
|
||||
@override
|
||||
String get accountIconLabel => 'Icon';
|
||||
|
||||
@override
|
||||
String get accountColorLabel => 'Color';
|
||||
|
||||
@override
|
||||
String get accountIsDefaultLabel => 'Default account';
|
||||
|
||||
@override
|
||||
String get accountIsDefaultHint => 'Used when creating new transactions';
|
||||
|
||||
@override
|
||||
String get accountSaveButton => 'Create account';
|
||||
|
||||
@override
|
||||
String get accountSaveEditButton => 'Save changes';
|
||||
|
||||
@override
|
||||
String get accountArchiveButton => 'Archive';
|
||||
|
||||
@override
|
||||
String get accountArchiveConfirmTitle => 'Archive account?';
|
||||
|
||||
@override
|
||||
String get accountArchiveConfirmMessage =>
|
||||
'Archived accounts disappear from pickers and lists. Existing transactions keep their reference.';
|
||||
|
||||
@override
|
||||
String get accountValidationNameRequired => 'Enter a name';
|
||||
|
||||
@override
|
||||
String get accountValidationNameTooLong => 'Name is too long (max 50 chars)';
|
||||
|
||||
@override
|
||||
String get accountSaveError =>
|
||||
'Failed to save the account. Please try again.';
|
||||
|
||||
@override
|
||||
String get accountArchiveError =>
|
||||
'Failed to archive the account. Please try again.';
|
||||
|
||||
@override
|
||||
String get accountSetDefaultError =>
|
||||
'Account saved, but couldn\'t mark it as default.';
|
||||
}
|
||||
|
||||
@@ -120,6 +120,15 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get darkTheme => 'Тёмная тема';
|
||||
|
||||
@override
|
||||
String get profileLanguageTile => 'Язык';
|
||||
|
||||
@override
|
||||
String get langEnglish => 'English';
|
||||
|
||||
@override
|
||||
String get langRussian => 'Русский';
|
||||
|
||||
@override
|
||||
String get profileHint =>
|
||||
'Здесь появится профиль пользователя, валюта, локаль и другие настройки.';
|
||||
@@ -330,6 +339,12 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get colorPickerTitle => 'Выберите цвет';
|
||||
|
||||
@override
|
||||
String get currencyPickerTitle => 'Выберите валюту';
|
||||
|
||||
@override
|
||||
String get currencyPickerSearchHint => 'Поиск…';
|
||||
|
||||
@override
|
||||
String get colorPickerAllUsed =>
|
||||
'Все цвета палитры уже заняты другими категориями.';
|
||||
@@ -366,4 +381,90 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get profileCategoriesTile => 'Категории';
|
||||
|
||||
@override
|
||||
String get profileAccountsTile => 'Счета';
|
||||
|
||||
@override
|
||||
String get accountsEmpty => 'Счетов нет. Нажмите +, чтобы добавить.';
|
||||
|
||||
@override
|
||||
String get accountNewTitle => 'Новый счёт';
|
||||
|
||||
@override
|
||||
String get accountEditTitle => 'Редактировать счёт';
|
||||
|
||||
@override
|
||||
String get accountNameLabel => 'Название';
|
||||
|
||||
@override
|
||||
String get accountNameHint => 'напр. Кошелёк';
|
||||
|
||||
@override
|
||||
String get accountTypeLabel => 'Тип';
|
||||
|
||||
@override
|
||||
String get accountTypeCash => 'Наличные';
|
||||
|
||||
@override
|
||||
String get accountTypeCard => 'Карта';
|
||||
|
||||
@override
|
||||
String get accountTypeBank => 'Банк';
|
||||
|
||||
@override
|
||||
String get accountTypeSavings => 'Накопления';
|
||||
|
||||
@override
|
||||
String get accountCurrencyLabel => 'Валюта';
|
||||
|
||||
@override
|
||||
String get accountInitialBalanceLabel => 'Начальный баланс';
|
||||
|
||||
@override
|
||||
String get accountIconLabel => 'Иконка';
|
||||
|
||||
@override
|
||||
String get accountColorLabel => 'Цвет';
|
||||
|
||||
@override
|
||||
String get accountIsDefaultLabel => 'Счёт по умолчанию';
|
||||
|
||||
@override
|
||||
String get accountIsDefaultHint => 'Подставляется при создании транзакций';
|
||||
|
||||
@override
|
||||
String get accountSaveButton => 'Создать счёт';
|
||||
|
||||
@override
|
||||
String get accountSaveEditButton => 'Сохранить';
|
||||
|
||||
@override
|
||||
String get accountArchiveButton => 'Архивировать';
|
||||
|
||||
@override
|
||||
String get accountArchiveConfirmTitle => 'Архивировать счёт?';
|
||||
|
||||
@override
|
||||
String get accountArchiveConfirmMessage =>
|
||||
'Архивные счета исчезают из списков и пикеров. Существующие транзакции сохранят ссылку.';
|
||||
|
||||
@override
|
||||
String get accountValidationNameRequired => 'Введите название';
|
||||
|
||||
@override
|
||||
String get accountValidationNameTooLong =>
|
||||
'Слишком длинное название (макс. 50 символов)';
|
||||
|
||||
@override
|
||||
String get accountSaveError =>
|
||||
'Не удалось сохранить счёт. Попробуйте ещё раз.';
|
||||
|
||||
@override
|
||||
String get accountArchiveError =>
|
||||
'Не удалось архивировать счёт. Попробуйте ещё раз.';
|
||||
|
||||
@override
|
||||
String get accountSetDefaultError =>
|
||||
'Счёт сохранён, но не удалось сделать его умолчательным.';
|
||||
}
|
||||
|
||||
+34
-1
@@ -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,33 @@
|
||||
"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": "Не удалось архивировать счёт. Попробуйте ещё раз.",
|
||||
"accountSetDefaultError": "Счёт сохранён, но не удалось сделать его умолчательным."
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<String?>(
|
||||
state,
|
||||
const AccountFormScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.accountEditPattern,
|
||||
pageBuilder: (context, state) => _slideUpPage<String?>(
|
||||
state,
|
||||
AccountFormScreen(accountId: state.pathParameters['id']),
|
||||
),
|
||||
),
|
||||
StatefulShellRoute.indexedStack(
|
||||
builder: (context, state, navigationShell) => AppScaffold(
|
||||
navigationShell: navigationShell,
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -29,12 +29,43 @@ class AccountsDao extends DatabaseAccessor<AppDatabase>
|
||||
Future<void> insertAccount(AccountsTableCompanion companion) =>
|
||||
into(accountsTable).insert(companion);
|
||||
|
||||
Future<bool> updateAccount(AccountsTableCompanion companion) =>
|
||||
update(accountsTable).replace(companion);
|
||||
Future<void> updateAccount(AccountsTableCompanion companion) =>
|
||||
(update(accountsTable)..where((t) => t.id.equals(companion.id.value)))
|
||||
.write(companion);
|
||||
|
||||
Future<void> archiveAccount(String id) => (update(accountsTable)
|
||||
..where((t) => t.id.equals(id)))
|
||||
.write(const AccountsTableCompanion(archived: Value(true)));
|
||||
/// Архивирует счёт и одновременно снимает с него флаг "по умолчанию",
|
||||
/// чтобы он не попадал в [watchDefaultAccount] и не подставлялся в новые
|
||||
/// транзакции.
|
||||
Future<void> archiveAccount(String id) =>
|
||||
(update(accountsTable)..where((t) => t.id.equals(id))).write(
|
||||
const AccountsTableCompanion(
|
||||
archived: Value(true),
|
||||
isDefault: Value(false),
|
||||
),
|
||||
);
|
||||
|
||||
/// Реактивный умолчательный счёт пользователя (или null).
|
||||
/// Архивные счета исключены — даже если флаг isDefault остался от прошлого.
|
||||
Stream<AccountsTableData?> watchDefaultAccount(String userId) =>
|
||||
(select(accountsTable)
|
||||
..where((t) =>
|
||||
t.userId.equals(userId) &
|
||||
t.isDefault.equals(true) &
|
||||
t.archived.equals(false))
|
||||
..limit(1))
|
||||
.watchSingleOrNull();
|
||||
|
||||
/// Атомарная смена умолчательного счёта.
|
||||
/// id == null — снять дефолт со всех счетов пользователя.
|
||||
Future<void> 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)));
|
||||
}
|
||||
});
|
||||
|
||||
/// Реактивный текущий баланс счёта.
|
||||
///
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,6 +15,11 @@ Stream<List<Account>> accountsStream(Ref ref, String userId) =>
|
||||
Stream<int> accountBalance(Ref ref, String accountId) =>
|
||||
ref.watch(accountRepositoryProvider).watchBalance(accountId);
|
||||
|
||||
/// Умолчательный счёт пользователя (или null если не задан).
|
||||
@riverpod
|
||||
Stream<Account?> 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,29 +45,38 @@ 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<Account> 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<void> setDefaultAccount(String? id, String userId) =>
|
||||
ref.read(accountRepositoryProvider).setDefault(id, userId);
|
||||
|
||||
Future<void> archiveAccount(String id) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(
|
||||
() => ref.read(accountRepositoryProvider).archive(id),
|
||||
).then((_) => const AsyncData(null));
|
||||
try {
|
||||
await ref.read(accountRepositoryProvider).archive(id);
|
||||
state = const AsyncData(null);
|
||||
} catch (e, st) {
|
||||
state = AsyncError(e, st);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ extension AccountMapper on AccountsTableData {
|
||||
iconCode: iconCode,
|
||||
colorValue: colorValue,
|
||||
archived: archived,
|
||||
isDefault: isDefault,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,14 @@ class AccountRepositoryImpl implements AccountRepository {
|
||||
@override
|
||||
Future<void> archive(String id) => _dao.archiveAccount(id);
|
||||
|
||||
@override
|
||||
Stream<Account?> watchDefault(String userId) =>
|
||||
_dao.watchDefaultAccount(userId).map((row) => row?.toDomain());
|
||||
|
||||
@override
|
||||
Future<void> setDefault(String? id, String userId) =>
|
||||
_dao.setDefaultAccount(id, userId);
|
||||
|
||||
@override
|
||||
Stream<int> watchBalance(String accountId) => _dao.watchAccountBalance(accountId);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ abstract class Account with _$Account {
|
||||
int? iconCode,
|
||||
int? colorValue,
|
||||
required bool archived,
|
||||
@Default(false) bool isDefault,
|
||||
required DateTime createdAt,
|
||||
}) = _Account;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,12 @@ abstract interface class AccountRepository {
|
||||
Future<Account> update(Account account);
|
||||
Future<void> archive(String id);
|
||||
|
||||
/// Реактивный умолчательный счёт пользователя.
|
||||
Stream<Account?> watchDefault(String userId);
|
||||
|
||||
/// Установить (или снять, если id == null) умолчательный счёт.
|
||||
Future<void> setDefault(String? id, String userId);
|
||||
|
||||
/// Текущий баланс счёта (начальный + агрегат транзакций).
|
||||
Stream<int> watchBalance(String accountId);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,9 +15,9 @@ class AccountTabs extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final accounts =
|
||||
ref.watch(accountsStreamProvider(userId)).value ??
|
||||
const <Account>[];
|
||||
final accounts = [
|
||||
...ref.watch(accountsStreamProvider(userId)).value ?? const <Account>[],
|
||||
]..sort((a, b) => (b.isDefault ? 1 : 0) - (a.isDefault ? 1 : 0));
|
||||
final selected = ref.watch(selectedAccountProvider);
|
||||
|
||||
return SizedBox(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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/locale/app_locale_controller.dart';
|
||||
import '../../../../app/router/app_routes.dart';
|
||||
import '../../../../app/theme/app_colors.dart';
|
||||
import '../../../../app/theme/theme_mode_controller.dart';
|
||||
@@ -17,6 +17,7 @@ class ProfileScreen extends ConsumerWidget {
|
||||
final l10n = context.l10n;
|
||||
final mode = ref.watch(themeModeControllerProvider);
|
||||
final controller = ref.read(themeModeControllerProvider.notifier);
|
||||
final locale = ref.watch(appLocaleControllerProvider);
|
||||
|
||||
return PlaceholderScreen(
|
||||
subtitle: l10n.profileSubtitle,
|
||||
@@ -42,6 +43,19 @@ class ProfileScreen extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
Container(height: 1, color: p.line),
|
||||
_NavRow(
|
||||
icon: Icons.language_outlined,
|
||||
title: l10n.profileLanguageTile,
|
||||
subtitle: _localeName(locale?.languageCode, l10n),
|
||||
onTap: () => _showLanguagePicker(context, ref, locale, l10n),
|
||||
),
|
||||
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,
|
||||
@@ -95,10 +109,12 @@ class _NavRow extends StatelessWidget {
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
this.subtitle,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
@@ -118,6 +134,10 @@ class _NavRow extends StatelessWidget {
|
||||
style: TextStyle(fontSize: 14, color: p.ink),
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
Text(subtitle!, style: TextStyle(fontSize: 13, color: p.ink2)),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
Icon(Icons.chevron_right, size: 18, color: p.ink2),
|
||||
],
|
||||
),
|
||||
@@ -125,3 +145,70 @@ class _NavRow extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _localeName(String? code, AppLocalizations l10n) => switch (code) {
|
||||
'ru' => l10n.langRussian,
|
||||
_ => l10n.langEnglish,
|
||||
};
|
||||
|
||||
void _showLanguagePicker(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
Locale? current,
|
||||
AppLocalizations l10n,
|
||||
) {
|
||||
final p = context.palette;
|
||||
final currentCode = current?.languageCode ?? 'en';
|
||||
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
backgroundColor: p.paper,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
builder: (_) {
|
||||
final langs = [
|
||||
('en', l10n.langEnglish),
|
||||
('ru', l10n.langRussian),
|
||||
];
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Container(width: 40, height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: p.line,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...langs.map((entry) {
|
||||
final (code, name) = entry;
|
||||
final selected = code == currentCode;
|
||||
return ListTile(
|
||||
title: Text(name,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: selected ? p.accent : p.ink,
|
||||
fontWeight: selected ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
trailing: selected
|
||||
? Icon(Icons.check, color: p.accent)
|
||||
: null,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(appLocaleControllerProvider.notifier)
|
||||
.setLocale(code);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 <Account>[];
|
||||
final accountById = {for (final a in accounts) a.id: a};
|
||||
|
||||
@@ -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<String?>(AppRoutes.accountNew);
|
||||
if (id != null && context.mounted) {
|
||||
Navigator.of(context).pop(id);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user