Improves transaction input and display
Adds a tag selection to transaction dialog and refines the display of transactions. The changes introduce the ability to tag transactions, improving categorization and filtering. It also modifies the transaction input dialog to include time, as well as refines the appearance of transaction items on the home page by using a custom widget wrapped in a card. The UI of the summary widget is also improved by using theme colors and increasing the overall visibility.
This commit is contained in:
+3
-2
@@ -34,5 +34,6 @@
|
||||
"requiredField": "Required field",
|
||||
"invalidNumber": "Invalid number",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save"
|
||||
}
|
||||
"save": "Save",
|
||||
"tag": "Tag"
|
||||
}
|
||||
|
||||
@@ -307,6 +307,12 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Save'**
|
||||
String get save;
|
||||
|
||||
/// No description provided for @tag.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tag'**
|
||||
String get tag;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -114,4 +114,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get save => 'Save';
|
||||
|
||||
@override
|
||||
String get tag => 'Tag';
|
||||
}
|
||||
|
||||
@@ -115,4 +115,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get save => 'Сохранить';
|
||||
|
||||
@override
|
||||
String get tag => 'Тег';
|
||||
}
|
||||
|
||||
+3
-2
@@ -34,5 +34,6 @@
|
||||
"requiredField": "Обязательное поле",
|
||||
"invalidNumber": "Неверный формат числа",
|
||||
"cancel": "Отмена",
|
||||
"save": "Сохранить"
|
||||
}
|
||||
"save": "Сохранить",
|
||||
"tag": "Тег"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ import '../../services/user_service.dart';
|
||||
import '../home/widgets/summary_widget.dart'; // Импортируем новый виджет сводки
|
||||
import '../reports_page.dart'; // Импортируем новую страницу отчетов
|
||||
import '../settings_page.dart';
|
||||
import 'widgets/add_transaction_dialog.dart'; // Добавлен импорт для UserService
|
||||
import 'widgets/add_transaction_dialog.dart';
|
||||
import 'widgets/transaction_item.dart'; // Импортируем новый виджет для элемента транзакции
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
@@ -160,29 +161,23 @@ class TransactionsPage extends StatelessWidget {
|
||||
child: Text(localizations.noTransactionsText),
|
||||
) // Локализованный текст
|
||||
else
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: state.transactions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final transaction = state.transactions[index];
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
transaction.category.icon,
|
||||
color: transaction.category.color,
|
||||
),
|
||||
title: Text(transaction.vendor),
|
||||
subtitle: Text(transaction.category.name),
|
||||
trailing: Text(
|
||||
'${transaction.amount.toStringAsFixed(2)} ${transaction.currency}',
|
||||
style: TextStyle(
|
||||
color: transaction.isIncome
|
||||
? Colors.green
|
||||
: Colors.red,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
// Оборачиваем список транзакций в Card для создания единого блока
|
||||
Card(
|
||||
margin: const EdgeInsets.all(16.0),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: state.transactions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final transaction = state.transactions[index];
|
||||
// Возвращаем новый кастомный виджет для транзакции
|
||||
return TransactionItem(transaction: transaction);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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';
|
||||
@@ -6,7 +7,9 @@ import 'package:intl/intl.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import '../../../logic/auth/auth_bloc.dart';
|
||||
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 '../../../services/user_service.dart';
|
||||
import '../../../utils/category_utils.dart';
|
||||
@@ -26,12 +29,16 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
||||
|
||||
bool _isIncome = false;
|
||||
Category? _selectedCategory;
|
||||
DateTime _selectedDate = DateTime.now();
|
||||
// Комментарий: Добавляем состояние для выбранного тега.
|
||||
Tag? _selectedTag;
|
||||
// Комментарий: Заменяем _selectedDate на _selectedDateTime для хранения даты и времени.
|
||||
DateTime _selectedDateTime = DateTime.now();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_dateController.text = DateFormat.yMd().format(_selectedDate);
|
||||
// Комментарий: Устанавливаем начальное значение с датой и временем.
|
||||
_dateController.text = DateFormat.yMd().add_Hm().format(_selectedDateTime);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -42,19 +49,37 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _selectDate(BuildContext context) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
// Комментарий: Этот метод теперь обрабатывает выбор и даты, и времени.
|
||||
Future<void> _selectDateTime(BuildContext context) async {
|
||||
final DateTime? pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedDate,
|
||||
initialDate: _selectedDateTime,
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
if (picked != null && picked != _selectedDate) {
|
||||
setState(() {
|
||||
_selectedDate = picked;
|
||||
_dateController.text = DateFormat.yMd().format(_selectedDate);
|
||||
});
|
||||
}
|
||||
// Комментарий: Если пользователь не выбрал дату, выходим из функции.
|
||||
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.yMd().add_Hm().format(_selectedDateTime);
|
||||
});
|
||||
}
|
||||
|
||||
void _submitForm() {
|
||||
@@ -93,7 +118,10 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
||||
amount: amount,
|
||||
vendor: _vendorController.text,
|
||||
category: _selectedCategory!,
|
||||
dateTime: _selectedDate,
|
||||
// Комментарий: Используем _selectedDateTime для сохранения точного времени транзакции.
|
||||
dateTime: _selectedDateTime,
|
||||
// Комментарий: Добавляем выбранный тег в транзакцию.
|
||||
tag: _selectedTag,
|
||||
currency:
|
||||
GetIt.instance<UserService>().currentUser?.defaultCurrency ?? 'USD',
|
||||
userId: authState.user.id,
|
||||
@@ -135,7 +163,13 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
||||
TextFormField(
|
||||
controller: _amountController,
|
||||
decoration: InputDecoration(labelText: localizations.amount),
|
||||
keyboardType: TextInputType.number,
|
||||
// Комментарий: Устанавливаем числовую клавиатуру с поддержкой десятичных чисел.
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(decimal: true),
|
||||
// Комментарий: Добавляем фильтр для ввода только чисел и одной точки.
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*')),
|
||||
],
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return localizations.requiredField;
|
||||
@@ -173,13 +207,46 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
||||
validator: (value) =>
|
||||
value == null ? localizations.requiredField : null,
|
||||
),
|
||||
// Комментарий: Добавляем выпадающий список для выбора тега.
|
||||
// Он будет загружать теги асинхронно для текущего пользователя.
|
||||
FutureBuilder<List<Tag>>(
|
||||
future: GetIt.instance<ITagRepository>().getAllByUser(
|
||||
(context.read<AuthBloc>().state as AuthAuthenticated).user.id,
|
||||
),
|
||||
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: () => _selectDate(context),
|
||||
// Комментарий: Вызываем новый метод для выбора даты и времени.
|
||||
onPressed: () => _selectDateTime(context),
|
||||
),
|
||||
),
|
||||
readOnly: true,
|
||||
|
||||
@@ -27,82 +27,81 @@ class SummaryWidget extends StatelessWidget {
|
||||
final customColors = Theme.of(context).extension<CustomColors>()!;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
// Определяем цвет для градиента в зависимости от темы.
|
||||
final isDarkMode = theme.brightness == Brightness.dark;
|
||||
final gradientColor = isDarkMode ? Colors.grey[800]! : Colors.grey[200]!;
|
||||
|
||||
// Используем Card для придания виджету тени и скругленных углов.
|
||||
// Используем Card для создания тени и скругленных углов.
|
||||
// Увеличиваем радиус скругления и elevation для более выраженного эффекта.
|
||||
return Card(
|
||||
elevation: 4.0,
|
||||
elevation: 8.0,
|
||||
margin: const EdgeInsets.all(16.0),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
decoration: BoxDecoration(
|
||||
// Добавляем градиентный фон для более современного вида.
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
theme.colorScheme.surface,
|
||||
gradientColor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Центральный блок с балансом.
|
||||
Center(
|
||||
child: Column(
|
||||
// Используем акцентный цвет для заголовка.
|
||||
Text(
|
||||
localizations.balance,
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
color: customColors.accent,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
// Анимированное отображение общей суммы.
|
||||
// Увеличиваем шрифт для большей наглядности.
|
||||
AnimatedDigitWidget(
|
||||
value: balance,
|
||||
fractionDigits: 2,
|
||||
textStyle: theme.textTheme.displaySmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
suffix: ' $currencySymbol',
|
||||
),
|
||||
const SizedBox(height: 24.0),
|
||||
|
||||
// Добавляем разделитель с цветом из темы.
|
||||
Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
color: customColors.divider,
|
||||
),
|
||||
const SizedBox(height: 24.0),
|
||||
|
||||
// Разделение на доходы и расходы.
|
||||
// Используем IntrinsicHeight для выравнивания высоты дочерних элементов.
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
// Заголовок секции.
|
||||
Text(
|
||||
localizations.balance,
|
||||
style: theme.textTheme.titleLarge,
|
||||
// Виджет для отображения доходов.
|
||||
_buildIncomeExpense(
|
||||
context,
|
||||
localizations.income,
|
||||
income,
|
||||
customColors.income!, // Используем цвет дохода из темы
|
||||
Icons.arrow_circle_up_outlined,
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
// Анимированное отображение общей суммы.
|
||||
AnimatedDigitWidget(
|
||||
value: balance,
|
||||
fractionDigits: 2,
|
||||
textStyle: theme.textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
suffix: ' $currencySymbol',
|
||||
// Вертикальный разделитель.
|
||||
VerticalDivider(
|
||||
width: 1,
|
||||
thickness: 1,
|
||||
color: customColors.divider,
|
||||
),
|
||||
// Виджет для отображения расходов.
|
||||
_buildIncomeExpense(
|
||||
context,
|
||||
localizations.expense,
|
||||
expense,
|
||||
customColors.expense!, // Используем цвет расхода из темы
|
||||
Icons.arrow_circle_down_outlined,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
|
||||
// Добавляем разделитель для лучшей структуры.
|
||||
const Divider(height: 24.0),
|
||||
|
||||
// Разделение на доходы и расходы.
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
// Виджет для отображения доходов.
|
||||
_buildIncomeExpense(
|
||||
context,
|
||||
localizations.income,
|
||||
income,
|
||||
customColors.income!, // Используем цвет дохода из темы
|
||||
Icons.arrow_upward,
|
||||
),
|
||||
// Виджет для отображения расходов.
|
||||
_buildIncomeExpense(
|
||||
context,
|
||||
localizations.expense,
|
||||
expense,
|
||||
customColors.expense!, // Используем цвет расхода из темы
|
||||
Icons.arrow_downward,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -110,37 +109,43 @@ class SummaryWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
// Вспомогательный метод для создания виджета дохода/расхода.
|
||||
// Изменен для более компактного и чистого вида.
|
||||
Widget _buildIncomeExpense(
|
||||
BuildContext context, String title, double amount, Color color, IconData icon) {
|
||||
return Row(
|
||||
children: [
|
||||
// Иконка для наглядности.
|
||||
Icon(
|
||||
icon,
|
||||
color: color,
|
||||
size: 28.0,
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
BuildContext context,
|
||||
String title,
|
||||
double amount,
|
||||
Color color,
|
||||
IconData icon,
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
return Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
// Иконка для наглядности.
|
||||
Icon(
|
||||
icon,
|
||||
color: color,
|
||||
size: 32.0,
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
// Заголовок.
|
||||
Text(
|
||||
title,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
// Анимированное отображение суммы.
|
||||
// Используем основной цвет текста для суммы.
|
||||
AnimatedDigitWidget(
|
||||
value: amount,
|
||||
fractionDigits: 2,
|
||||
textStyle: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
// Анимированное отображение суммы.
|
||||
AnimatedDigitWidget(
|
||||
value: amount,
|
||||
fractionDigits: 2,
|
||||
textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import '../../../models/transaction_record.dart';
|
||||
|
||||
/// Виджет для отображения одной транзакции в списке.
|
||||
///
|
||||
/// Этот виджет представляет собой карточку с подробной информацией о транзакции,
|
||||
/// включая поставщика, сумму, категорию, тег и дату.
|
||||
class TransactionItem extends StatelessWidget {
|
||||
final TransactionRecord transaction;
|
||||
|
||||
const TransactionItem({super.key, required this.transaction});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
// Форматируем дату в соответствии с локалью
|
||||
final formattedDate = DateFormat.yMMMd(localizations.localeName).format(transaction.dateTime);
|
||||
|
||||
// Убираем Card, так как обертка будет в родительском виджете.
|
||||
// Добавляем разделитель и уменьшаем отступы для компактности.
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Верхняя строка: Название поставщика и сумма
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// Название поставщика
|
||||
Expanded(
|
||||
child: Text(
|
||||
transaction.vendor,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
// Сумма транзакции
|
||||
// Используем .abs() чтобы избежать двойного минуса для расходов
|
||||
Text(
|
||||
'${transaction.isIncome ? '+' : '-'}${transaction.amount.abs().toStringAsFixed(2)} ${transaction.currency}',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
color: transaction.isIncome
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.error,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
// Средняя строка: Категория и тег
|
||||
Row(
|
||||
children: [
|
||||
// Иконка категории
|
||||
Icon(
|
||||
transaction.category.icon,
|
||||
color: transaction.category.color,
|
||||
size: 20.0, // Уменьшаем размер иконки
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
// Название категории и тега
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
transaction.category.name,
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
if (transaction.tag != null)
|
||||
Text(
|
||||
'#${transaction.tag!.name}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Дата в правом углу
|
||||
Text(
|
||||
formattedDate,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Разделитель между транзакциями
|
||||
const Divider(height: 1, thickness: 1, indent: 16, endIndent: 16),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -37,10 +37,19 @@ class AppTheme {
|
||||
),
|
||||
),
|
||||
),
|
||||
extensions: const <ThemeExtension<dynamic>>[
|
||||
// Комментарий: Добавляем тему для выпадающих меню, чтобы цвет соответствовал фону.
|
||||
dropdownMenuTheme: DropdownMenuThemeData(
|
||||
menuStyle: MenuStyle(
|
||||
backgroundColor: MaterialStateProperty.all(Colors.white),
|
||||
),
|
||||
),
|
||||
// Изменяем цвета в соответствии с черно-белой палитрой
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
CustomColors(
|
||||
income: Colors.green,
|
||||
expense: Colors.red,
|
||||
income: Colors.grey[800]!, // Темно-серый для доходов
|
||||
expense: Colors.grey[600]!, // Серый для расходов
|
||||
divider: Colors.grey[300]!, // Светло-серый для разделителей
|
||||
accent: Colors.black, // Черный для акцентов
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -81,13 +90,21 @@ class AppTheme {
|
||||
),
|
||||
),
|
||||
),
|
||||
extensions: const <ThemeExtension<dynamic>>[
|
||||
// Комментарий: Добавляем тему для выпадающих меню, чтобы цвет соответствовал фону.
|
||||
dropdownMenuTheme: DropdownMenuThemeData(
|
||||
menuStyle: MenuStyle(
|
||||
backgroundColor: MaterialStateProperty.all(Colors.grey[800]),
|
||||
),
|
||||
),
|
||||
// Изменяем цвета в соответствии с черно-белой палитрой
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
CustomColors(
|
||||
income: Colors.greenAccent,
|
||||
expense: Colors.redAccent,
|
||||
income: Colors.grey[300]!, // Светло-серый для доходов
|
||||
expense: Colors.grey[500]!, // Серый для расходов
|
||||
divider: Colors.grey[700]!, // Темно-серый для разделителей
|
||||
accent: Colors.white, // Белый для акцентов
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,16 +7,27 @@ class CustomColors extends ThemeExtension<CustomColors> {
|
||||
const CustomColors({
|
||||
required this.income,
|
||||
required this.expense,
|
||||
this.divider, // Добавлен цвет для разделителей
|
||||
this.accent, // Добавлен цвет для акцентов
|
||||
});
|
||||
|
||||
final Color? income;
|
||||
final Color? expense;
|
||||
final Color? divider;
|
||||
final Color? accent;
|
||||
|
||||
@override
|
||||
CustomColors copyWith({Color? income, Color? expense}) {
|
||||
CustomColors copyWith({
|
||||
Color? income,
|
||||
Color? expense,
|
||||
Color? divider,
|
||||
Color? accent,
|
||||
}) {
|
||||
return CustomColors(
|
||||
income: income ?? this.income,
|
||||
expense: expense ?? this.expense,
|
||||
divider: divider ?? this.divider,
|
||||
accent: accent ?? this.accent,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +39,8 @@ class CustomColors extends ThemeExtension<CustomColors> {
|
||||
return CustomColors(
|
||||
income: Color.lerp(income, other.income, t),
|
||||
expense: Color.lerp(expense, other.expense, t),
|
||||
divider: Color.lerp(divider, other.divider, t),
|
||||
accent: Color.lerp(accent, other.accent, t),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user