dev_bloc #1
@@ -10,6 +10,7 @@
|
||||
- Все настройки цветов выноси в тему
|
||||
- Все элементы должны разбиваться на мелкие и иметь логичную структуру по попкам
|
||||
- Весь текст должен иметь локализацию чрезе flutter_localizations. Смотри папку l10n
|
||||
- Разработка ведется под windows, ты можешь исопльзовать его консольные команды
|
||||
|
||||
## Coding Style:
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
+9
-1
@@ -26,5 +26,13 @@
|
||||
"transactionsHistoryTitle": "Transactions History",
|
||||
"balance": "Balance",
|
||||
"income": "Income",
|
||||
"expense": "Expense"
|
||||
"expense": "Expense",
|
||||
"amount": "Amount",
|
||||
"vendor": "Vendor",
|
||||
"category": "Category",
|
||||
"date": "Date",
|
||||
"requiredField": "Required field",
|
||||
"invalidNumber": "Invalid number",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save"
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations_en.dart';
|
||||
import 'app_localizations_ru.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// Callers can lookup localized strings with an instance of AppLocalizations
|
||||
/// returned by `AppLocalizations.of(context)`.
|
||||
///
|
||||
/// Applications need to include `AppLocalizations.delegate()` in their app's
|
||||
/// `localizationDelegates` list, and the locales they support in the app's
|
||||
/// `supportedLocales` list. For example:
|
||||
///
|
||||
/// ```dart
|
||||
/// import 'l10n/app_localizations.dart';
|
||||
///
|
||||
/// return MaterialApp(
|
||||
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
/// supportedLocales: AppLocalizations.supportedLocales,
|
||||
/// home: MyApplicationHome(),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ## Update pubspec.yaml
|
||||
///
|
||||
/// Please make sure to update your pubspec.yaml to include the following
|
||||
/// packages:
|
||||
///
|
||||
/// ```yaml
|
||||
/// dependencies:
|
||||
/// # Internationalization support.
|
||||
/// flutter_localizations:
|
||||
/// sdk: flutter
|
||||
/// intl: any # Use the pinned version from flutter_localizations
|
||||
///
|
||||
/// # Rest of dependencies
|
||||
/// ```
|
||||
///
|
||||
/// ## iOS Applications
|
||||
///
|
||||
/// iOS applications define key application metadata, including supported
|
||||
/// locales, in an Info.plist file that is built into the application bundle.
|
||||
/// To configure the locales supported by your app, you’ll need to edit this
|
||||
/// file.
|
||||
///
|
||||
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
||||
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
||||
/// project’s Runner folder.
|
||||
///
|
||||
/// Next, select the Information Property List item, select Add Item from the
|
||||
/// Editor menu, then select Localizations from the pop-up menu.
|
||||
///
|
||||
/// Select and expand the newly-created Localizations item then, for each
|
||||
/// locale your application supports, add a new item and select the locale
|
||||
/// you wish to add from the pop-up menu in the Value field. This list should
|
||||
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
|
||||
/// property.
|
||||
abstract class AppLocalizations {
|
||||
AppLocalizations(String locale)
|
||||
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
||||
|
||||
final String localeName;
|
||||
|
||||
static AppLocalizations? of(BuildContext context) {
|
||||
return Localizations.of<AppLocalizations>(context, AppLocalizations);
|
||||
}
|
||||
|
||||
static const LocalizationsDelegate<AppLocalizations> delegate =
|
||||
_AppLocalizationsDelegate();
|
||||
|
||||
/// A list of this localizations delegate along with the default localizations
|
||||
/// delegates.
|
||||
///
|
||||
/// Returns a list of localizations delegates containing this delegate along with
|
||||
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
|
||||
/// and GlobalWidgetsLocalizations.delegate.
|
||||
///
|
||||
/// Additional delegates can be added by appending to this list in
|
||||
/// MaterialApp. This list does not have to be used at all if a custom list
|
||||
/// of delegates is preferred or required.
|
||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
||||
<LocalizationsDelegate<dynamic>>[
|
||||
delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
];
|
||||
|
||||
/// A list of this localizations delegate's supported locales.
|
||||
static const List<Locale> supportedLocales = <Locale>[
|
||||
Locale('en'),
|
||||
Locale('ru'),
|
||||
];
|
||||
|
||||
/// No description provided for @appTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Budget App'**
|
||||
String get appTitle;
|
||||
|
||||
/// No description provided for @homePageTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Home'**
|
||||
String get homePageTitle;
|
||||
|
||||
/// No description provided for @reportsPageTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Reports'**
|
||||
String get reportsPageTitle;
|
||||
|
||||
/// No description provided for @settingsPageTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Settings'**
|
||||
String get settingsPageTitle;
|
||||
|
||||
/// No description provided for @addTransactionButton.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add new transaction'**
|
||||
String get addTransactionButton;
|
||||
|
||||
/// No description provided for @noTransactionsText.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No transactions yet'**
|
||||
String get noTransactionsText;
|
||||
|
||||
/// No description provided for @loadingTransactionsText.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Loading transactions...'**
|
||||
String get loadingTransactionsText;
|
||||
|
||||
/// No description provided for @transactionErrorText.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Error: {message}'**
|
||||
String transactionErrorText(Object message);
|
||||
|
||||
/// No description provided for @loginPageTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Login'**
|
||||
String get loginPageTitle;
|
||||
|
||||
/// No description provided for @nameFieldLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Name'**
|
||||
String get nameFieldLabel;
|
||||
|
||||
/// No description provided for @emailFieldLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Email'**
|
||||
String get emailFieldLabel;
|
||||
|
||||
/// No description provided for @nameFieldEmptyError.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please enter your name'**
|
||||
String get nameFieldEmptyError;
|
||||
|
||||
/// No description provided for @emailFieldEmptyError.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please enter your email'**
|
||||
String get emailFieldEmptyError;
|
||||
|
||||
/// No description provided for @loginButtonText.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Login / Register'**
|
||||
String get loginButtonText;
|
||||
|
||||
/// No description provided for @darkModeSetting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dark Theme'**
|
||||
String get darkModeSetting;
|
||||
|
||||
/// No description provided for @darkModeDescription.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Toggle between light and dark theme'**
|
||||
String get darkModeDescription;
|
||||
|
||||
/// No description provided for @languageSetting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Language'**
|
||||
String get languageSetting;
|
||||
|
||||
/// No description provided for @languageDescription.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Change application language'**
|
||||
String get languageDescription;
|
||||
|
||||
/// No description provided for @russianLanguage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Russian'**
|
||||
String get russianLanguage;
|
||||
|
||||
/// No description provided for @englishLanguage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'English'**
|
||||
String get englishLanguage;
|
||||
|
||||
/// No description provided for @defaultUser.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Default User'**
|
||||
String get defaultUser;
|
||||
|
||||
/// No description provided for @currencySetting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Default Currency'**
|
||||
String get currencySetting;
|
||||
|
||||
/// No description provided for @currencyDescription.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Set the default currency for transactions'**
|
||||
String get currencyDescription;
|
||||
|
||||
/// No description provided for @transactionsHistoryTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Transactions History'**
|
||||
String get transactionsHistoryTitle;
|
||||
|
||||
/// No description provided for @balance.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Balance'**
|
||||
String get balance;
|
||||
|
||||
/// No description provided for @income.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Income'**
|
||||
String get income;
|
||||
|
||||
/// No description provided for @expense.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Expense'**
|
||||
String get expense;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
extends LocalizationsDelegate<AppLocalizations> {
|
||||
const _AppLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<AppLocalizations> load(Locale locale) {
|
||||
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) =>
|
||||
<String>['en', 'ru'].contains(locale.languageCode);
|
||||
|
||||
@override
|
||||
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||
// Lookup logic when only language code is specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'en':
|
||||
return AppLocalizationsEn();
|
||||
case 'ru':
|
||||
return AppLocalizationsRu();
|
||||
}
|
||||
|
||||
throw FlutterError(
|
||||
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
|
||||
'an issue with the localizations generation tool. Please file an issue '
|
||||
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||||
'that was used.',
|
||||
);
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for English (`en`).
|
||||
class AppLocalizationsEn extends AppLocalizations {
|
||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'Budget App';
|
||||
|
||||
@override
|
||||
String get homePageTitle => 'Home';
|
||||
|
||||
@override
|
||||
String get reportsPageTitle => 'Reports';
|
||||
|
||||
@override
|
||||
String get settingsPageTitle => 'Settings';
|
||||
|
||||
@override
|
||||
String get addTransactionButton => 'Add new transaction';
|
||||
|
||||
@override
|
||||
String get noTransactionsText => 'No transactions yet';
|
||||
|
||||
@override
|
||||
String get loadingTransactionsText => 'Loading transactions...';
|
||||
|
||||
@override
|
||||
String transactionErrorText(Object message) {
|
||||
return 'Error: $message';
|
||||
}
|
||||
|
||||
@override
|
||||
String get loginPageTitle => 'Login';
|
||||
|
||||
@override
|
||||
String get nameFieldLabel => 'Name';
|
||||
|
||||
@override
|
||||
String get emailFieldLabel => 'Email';
|
||||
|
||||
@override
|
||||
String get nameFieldEmptyError => 'Please enter your name';
|
||||
|
||||
@override
|
||||
String get emailFieldEmptyError => 'Please enter your email';
|
||||
|
||||
@override
|
||||
String get loginButtonText => 'Login / Register';
|
||||
|
||||
@override
|
||||
String get darkModeSetting => 'Dark Theme';
|
||||
|
||||
@override
|
||||
String get darkModeDescription => 'Toggle between light and dark theme';
|
||||
|
||||
@override
|
||||
String get languageSetting => 'Language';
|
||||
|
||||
@override
|
||||
String get languageDescription => 'Change application language';
|
||||
|
||||
@override
|
||||
String get russianLanguage => 'Russian';
|
||||
|
||||
@override
|
||||
String get englishLanguage => 'English';
|
||||
|
||||
@override
|
||||
String get defaultUser => 'Default User';
|
||||
|
||||
@override
|
||||
String get currencySetting => 'Default Currency';
|
||||
|
||||
@override
|
||||
String get currencyDescription => 'Set the default currency for transactions';
|
||||
|
||||
@override
|
||||
String get transactionsHistoryTitle => 'Transactions History';
|
||||
|
||||
@override
|
||||
String get balance => 'Balance';
|
||||
|
||||
@override
|
||||
String get income => 'Income';
|
||||
|
||||
@override
|
||||
String get expense => 'Expense';
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Russian (`ru`).
|
||||
class AppLocalizationsRu extends AppLocalizations {
|
||||
AppLocalizationsRu([String locale = 'ru']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'Бюджетное приложение';
|
||||
|
||||
@override
|
||||
String get homePageTitle => 'Главная';
|
||||
|
||||
@override
|
||||
String get reportsPageTitle => 'Отчеты';
|
||||
|
||||
@override
|
||||
String get settingsPageTitle => 'Настройки';
|
||||
|
||||
@override
|
||||
String get addTransactionButton => 'Добавить новую транзакцию';
|
||||
|
||||
@override
|
||||
String get noTransactionsText => 'Нет транзакций';
|
||||
|
||||
@override
|
||||
String get loadingTransactionsText => 'Загрузка транзакций...';
|
||||
|
||||
@override
|
||||
String transactionErrorText(Object message) {
|
||||
return 'Ошибка: $message';
|
||||
}
|
||||
|
||||
@override
|
||||
String get loginPageTitle => 'Вход';
|
||||
|
||||
@override
|
||||
String get nameFieldLabel => 'Имя';
|
||||
|
||||
@override
|
||||
String get emailFieldLabel => 'Email';
|
||||
|
||||
@override
|
||||
String get nameFieldEmptyError => 'Пожалуйста, введите имя';
|
||||
|
||||
@override
|
||||
String get emailFieldEmptyError => 'Пожалуйста, введите email';
|
||||
|
||||
@override
|
||||
String get loginButtonText => 'Войти / Зарегистрироваться';
|
||||
|
||||
@override
|
||||
String get darkModeSetting => 'Темная тема';
|
||||
|
||||
@override
|
||||
String get darkModeDescription => 'Переключить между светлой и темной темой';
|
||||
|
||||
@override
|
||||
String get languageSetting => 'Язык';
|
||||
|
||||
@override
|
||||
String get languageDescription => 'Изменить язык приложения';
|
||||
|
||||
@override
|
||||
String get russianLanguage => 'Русский';
|
||||
|
||||
@override
|
||||
String get englishLanguage => 'Английский';
|
||||
|
||||
@override
|
||||
String get defaultUser => 'Пользователь по умолчанию';
|
||||
|
||||
@override
|
||||
String get currencySetting => 'Валюта по умолчанию';
|
||||
|
||||
@override
|
||||
String get currencyDescription =>
|
||||
'Установить валюту по умолчанию для транзакций';
|
||||
|
||||
@override
|
||||
String get transactionsHistoryTitle => 'История транзакций';
|
||||
|
||||
@override
|
||||
String get balance => 'Баланс';
|
||||
|
||||
@override
|
||||
String get income => 'Доходы';
|
||||
|
||||
@override
|
||||
String get expense => 'Расходы';
|
||||
}
|
||||
+9
-1
@@ -26,5 +26,13 @@
|
||||
"transactionsHistoryTitle": "История транзакций",
|
||||
"balance": "Баланс",
|
||||
"income": "Доходы",
|
||||
"expense": "Расходы"
|
||||
"expense": "Расходы",
|
||||
"amount": "Сумма",
|
||||
"vendor": "Название",
|
||||
"category": "Категория",
|
||||
"date": "Дата",
|
||||
"requiredField": "Обязательное поле",
|
||||
"invalidNumber": "Неверный формат числа",
|
||||
"cancel": "Отмена",
|
||||
"save": "Сохранить"
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import '../utils/id_generator.dart';
|
||||
@@ -8,7 +9,7 @@ part 'category.g.dart';
|
||||
|
||||
/// Модель категории для группировки транзакций
|
||||
/// Содержит основные параметры для визуализации и классификации
|
||||
class Category {
|
||||
class Category extends Equatable {
|
||||
/// Уникальный идентификатор категории
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
@@ -97,4 +98,9 @@ class Category {
|
||||
updatedAt: DateTime.now(), // Обновляем updatedAt при каждом копировании
|
||||
);
|
||||
}
|
||||
|
||||
// Используем Equatable для сравнения объектов по их свойствам.
|
||||
// В данном случае, мы считаем категории уникальными по их 'id'.
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,10 +1,11 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import '../../utils/id_generator.dart';
|
||||
|
||||
part 'tag.g.dart';
|
||||
|
||||
@HiveType(typeId: 1001)
|
||||
class Tag {
|
||||
class Tag extends Equatable {
|
||||
@HiveField(0)
|
||||
/// Уникальный идентификатор тега
|
||||
final String id;
|
||||
@@ -71,4 +72,9 @@ class Tag {
|
||||
updatedAt: DateTime.now(), // Обновляем updatedAt при каждом копировании
|
||||
);
|
||||
}
|
||||
|
||||
// Используем Equatable для сравнения объектов по их свойствам.
|
||||
// В данном случае, мы считаем теги уникальными по их 'id'.
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
import '../../utils/id_generator.dart';
|
||||
@@ -9,7 +10,7 @@ part 'transaction_record.g.dart';
|
||||
@HiveType(typeId: 1002)
|
||||
/// Модель записи о транзакции - основной элемент учета бюджета
|
||||
/// Содержит все детали финансовой операции
|
||||
class TransactionRecord {
|
||||
class TransactionRecord extends Equatable {
|
||||
/// Уникальный идентификатор транзакции
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
@@ -121,4 +122,9 @@ class TransactionRecord {
|
||||
updatedAt: DateTime.now(), // Обновляем updatedAt при каждом копировании
|
||||
);
|
||||
}
|
||||
|
||||
// Используем Equatable для сравнения объектов по их свойствам.
|
||||
// В данном случае, мы считаем записи транзакций уникальными по их 'id'.
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import '../utils/id_generator.dart';
|
||||
|
||||
@@ -8,7 +9,7 @@ part 'user.g.dart';
|
||||
@HiveType(typeId: 1003)
|
||||
/// Модель пользователя приложения
|
||||
/// Содержит основную информацию для идентификации пользователя
|
||||
class User {
|
||||
class User extends Equatable {
|
||||
/// Уникальный идентификатор пользователя
|
||||
@HiveField(0) // Поле 0 в Hive - первое поле модели
|
||||
final String id;
|
||||
@@ -98,4 +99,9 @@ class User {
|
||||
defaultCurrency: defaultCurrency ?? this.defaultCurrency,
|
||||
);
|
||||
}
|
||||
|
||||
// Используем Equatable для сравнения объектов по их свойствам.
|
||||
// В данном случае, мы считаем пользователей уникальными по их 'id'.
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ import 'package:get_it/get_it.dart';
|
||||
import '/l10n/app_localizations.dart';
|
||||
import '../../logic/auth/auth_bloc.dart';
|
||||
import '../../logic/transaction/transaction_bloc.dart';
|
||||
import '../../services/user_service.dart';
|
||||
import '../home/widgets/summary_widget.dart'; // Импортируем новый виджет сводки
|
||||
import '../reports_page.dart'; // Импортируем новую страницу отчетов
|
||||
import '../settings_page.dart';
|
||||
import '../../services/user_service.dart'; // Добавлен импорт для UserService
|
||||
import 'widgets/add_transaction_dialog.dart'; // Добавлен импорт для UserService
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
@@ -70,11 +71,11 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
// TODO: Добавить логику для добавления новой транзакции
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(localizations.addTransactionButton),
|
||||
), // Локализованный текст
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return const AddTransactionDialog();
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Icon(Icons.add),
|
||||
@@ -137,7 +138,9 @@ class TransactionsPage extends StatelessWidget {
|
||||
final balance = income - expense;
|
||||
// Получаем текущего пользователя для определения валюты
|
||||
final userService = GetIt.instance<UserService>();
|
||||
final currencySymbol = userService.currentUser?.defaultCurrency ?? '₽'; // Валюта по умолчанию, если не найдена
|
||||
final currencySymbol =
|
||||
userService.currentUser?.defaultCurrency ??
|
||||
'₽'; // Валюта по умолчанию, если не найдена
|
||||
|
||||
return SummaryWidget(
|
||||
income: income,
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import '../../../logic/auth/auth_bloc.dart';
|
||||
import '../../../logic/transaction/transaction_bloc.dart';
|
||||
import '../../../models/category.dart';
|
||||
import '../../../models/transaction_record.dart';
|
||||
import '../../../services/user_service.dart';
|
||||
import '../../../utils/category_utils.dart';
|
||||
|
||||
class AddTransactionDialog extends StatefulWidget {
|
||||
const AddTransactionDialog({super.key});
|
||||
|
||||
@override
|
||||
State<AddTransactionDialog> createState() => _AddTransactionDialogState();
|
||||
}
|
||||
|
||||
class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _amountController = TextEditingController();
|
||||
final _vendorController = TextEditingController();
|
||||
final _dateController = TextEditingController();
|
||||
|
||||
bool _isIncome = false;
|
||||
Category? _selectedCategory;
|
||||
DateTime _selectedDate = DateTime.now();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_dateController.text = DateFormat.yMd().format(_selectedDate);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_amountController.dispose();
|
||||
_vendorController.dispose();
|
||||
_dateController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _selectDate(BuildContext context) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedDate,
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
if (picked != null && picked != _selectedDate) {
|
||||
setState(() {
|
||||
_selectedDate = picked;
|
||||
_dateController.text = DateFormat.yMd().format(_selectedDate);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _submitForm() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final amount = double.tryParse(_amountController.text);
|
||||
if (amount == null || _selectedCategory == null) {
|
||||
// Показать ошибку, если сумма некорректна или категория не выбрана
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
)!.transactionErrorText('Invalid data'),
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final authState = context.read<AuthBloc>().state;
|
||||
if (authState is! AuthAuthenticated) {
|
||||
// Показать ошибку, если пользователь не аутентифицирован
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
)!.transactionErrorText('User not authenticated'),
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final newTransaction = TransactionRecord(
|
||||
amount: amount,
|
||||
vendor: _vendorController.text,
|
||||
category: _selectedCategory!,
|
||||
dateTime: _selectedDate,
|
||||
currency:
|
||||
GetIt.instance<UserService>().currentUser?.defaultCurrency ?? 'USD',
|
||||
userId: authState.user.id,
|
||||
);
|
||||
|
||||
context.read<TransactionBloc>().add(
|
||||
AddTransaction(transaction: newTransaction),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
final categories = CategoryUtils.getDefaultCategories(
|
||||
(context.read<AuthBloc>().state as AuthAuthenticated).user.id,
|
||||
).where((c) => c.isIncome == _isIncome).toList();
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(localizations.addTransactionButton),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
title: Text(localizations.income),
|
||||
value: _isIncome,
|
||||
onChanged: (bool value) {
|
||||
setState(() {
|
||||
_isIncome = value;
|
||||
_selectedCategory =
|
||||
null; // Сбрасываем категорию при смене типа
|
||||
});
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
controller: _amountController,
|
||||
decoration: InputDecoration(labelText: localizations.amount),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return localizations.requiredField;
|
||||
}
|
||||
if (double.tryParse(value) == null) {
|
||||
return localizations.invalidNumber;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
controller: _vendorController,
|
||||
decoration: InputDecoration(labelText: localizations.vendor),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return localizations.requiredField;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
DropdownButtonFormField<Category>(
|
||||
value: _selectedCategory,
|
||||
decoration: InputDecoration(labelText: localizations.category),
|
||||
items: categories.map((Category category) {
|
||||
return DropdownMenuItem<Category>(
|
||||
value: category,
|
||||
child: Text(category.name),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (Category? newValue) {
|
||||
setState(() {
|
||||
_selectedCategory = newValue;
|
||||
});
|
||||
},
|
||||
validator: (value) =>
|
||||
value == null ? localizations.requiredField : null,
|
||||
),
|
||||
TextFormField(
|
||||
controller: _dateController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localizations.date,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.calendar_today),
|
||||
onPressed: () => _selectDate(context),
|
||||
),
|
||||
),
|
||||
readOnly: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(localizations.cancel),
|
||||
),
|
||||
ElevatedButton(onPressed: _submitForm, child: Text(localizations.save)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Расширение темы для добавления пользовательских цветов.
|
||||
@immutable
|
||||
class CustomColors extends ThemeExtension<CustomColors> {
|
||||
const CustomColors({
|
||||
required this.income,
|
||||
required this.expense,
|
||||
});
|
||||
|
||||
final Color? income;
|
||||
final Color? expense;
|
||||
|
||||
@override
|
||||
CustomColors copyWith({Color? income, Color? expense}) {
|
||||
return CustomColors(
|
||||
income: income ?? this.income,
|
||||
expense: expense ?? this.expense,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
CustomColors lerp(ThemeExtension<CustomColors>? other, double t) {
|
||||
if (other is! CustomColors) {
|
||||
return this;
|
||||
}
|
||||
return CustomColors(
|
||||
income: Color.lerp(income, other.income, t),
|
||||
expense: Color.lerp(expense, other.expense, t),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user