Files
BudgetApp/lib/pages/home/widgets/add_transaction_dialog.dart
T
Sanders fd0c3dd9a8 Removes user-specific repository methods
Removes the now-unnecessary user-specific methods from the category and tag repositories. The repositories are now user-specific at the box level, so filtering by user ID within the repository methods is redundant.

Also, removes now-unused imports related to user logic in category and tag list pages, and the add transaction dialog.
2025-07-12 21:56:39 +03:00

279 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.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/settings/settings_cubit.dart'; // Добавлен импорт SettingsCubit
import '../../../logic/transaction/transaction_bloc.dart';
import '../../../data/repositories/interfaces/itag_repository.dart';
import '../../../models/category.dart';
import '../../../models/tag.dart';
import '../../../models/transaction_record.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;
// Комментарий: Добавляем состояние для выбранного тега.
Tag? _selectedTag;
// Комментарий: Заменяем _selectedDate на _selectedDateTime для хранения даты и времени.
DateTime _selectedDateTime = DateTime.now();
@override
void initState() {
super.initState();
// Комментарий: Устанавливаем начальное значение с датой и временем.
_dateController.text = DateFormat('dd-MM-yyyy').add_Hm().format(_selectedDateTime);
}
@override
void dispose() {
_amountController.dispose();
_vendorController.dispose();
_dateController.dispose();
super.dispose();
}
// Комментарий: Этот метод теперь обрабатывает выбор и даты, и времени.
Future<void> _selectDateTime(BuildContext context) async {
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedDateTime,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
// Комментарий: Если пользователь не выбрал дату, выходим из функции.
if (pickedDate == null) return;
// ignore: use_build_context_synchronously
final TimeOfDay? pickedTime = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(_selectedDateTime),
);
// Комментарий: Если пользователь не выбрал время, выходим из функции.
if (pickedTime == null) return;
// Комментарий: Обновляем состояние с новой датой и временем.
setState(() {
_selectedDateTime = DateTime(
pickedDate.year,
pickedDate.month,
pickedDate.day,
pickedTime.hour,
pickedTime.minute,
);
// Комментарий: Обновляем текстовое поле с отформатированной датой и временем.
_dateController.text = DateFormat('dd-MM-yyyy').add_Hm().format(_selectedDateTime);
});
}
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 settingsState = context.read<SettingsCubit>().state;
final currency = (settingsState is SettingsLoaded)
? settingsState.defaultCurrency
: 'RUB';
// Получаем ID текущего пользователя из UserCubit
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)!.transactionErrorText('User not found'),
),
),
);
final newTransaction = TransactionRecord(
amount: amount,
vendor: _vendorController.text,
category: _selectedCategory!,
dateTime: _selectedDateTime,
tag: _selectedTag,
currency: currency,
);
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()
.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:
const TextInputType.numberWithOptions(decimal: true),
// Комментарий: Добавляем фильтр для ввода только чисел и одной точки.
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*')),
],
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,
),
// Комментарий: Добавляем выпадающий список для выбора тега.
// Он будет загружать теги асинхронно для текущего пользователя.
FutureBuilder<List<Tag>>(
future: GetIt.instance<ITagRepository>().getAll(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
final tags = snapshot.data ?? [];
return DropdownButtonFormField<Tag>(
value: _selectedTag,
decoration: InputDecoration(labelText: localizations.tag),
items: tags.map((Tag tag) {
return DropdownMenuItem<Tag>(
value: tag,
child: Text(tag.name),
);
}).toList(),
onChanged: (Tag? newValue) {
setState(() {
_selectedTag = newValue;
});
},
// Комментарий: Тег не является обязательным полем.
);
},
),
TextFormField(
controller: _dateController,
decoration: InputDecoration(
labelText: localizations.date,
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
// Комментарий: Вызываем новый метод для выбора даты и времени.
onPressed: () => _selectDateTime(context),
),
),
readOnly: true,
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(localizations.cancel),
),
ElevatedButton(onPressed: _submitForm, child: Text(localizations.save)),
],
);
}
}