Files
BudgetApp/lib/pages/home/widgets/add_transaction_dialog.dart
T
Sanders 42f8e20bf0 Improves: Internationalization and data models
- Adds new localization keys for transaction details.
- Deletes obsolete localization files, switching to a single ARB file per language.
- Adds `Equatable` to data models (`Category`, `Tag`, `TransactionRecord`, `User`) for improved state management and change detection.
- Introduces the transaction creation dialog.
- Adds development note to GEMINI.md

Improves internationalization and data models

- Adds new localization keys for transaction details, supporting enhanced user experience.
- Migrates to single ARB file for each language, streamlining the localization process.
- Implements Equatable in data models (Category, Tag, TransactionRecord, User) for improved state management and simplified change detection.
- Introduces transaction creation dialog, simplifying the transaction creation process.
- Adds development note to GEMINI.md
2025-06-29 23:03:17 +03:00

201 lines
6.6 KiB
Dart

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)),
],
);
}
}