From 24ae24ee7420d3713cd7274b441dd9ec0edcd44a Mon Sep 17 00:00:00 2001 From: kolo Date: Wed, 25 Mar 2026 13:26:07 +0300 Subject: [PATCH] update --- lib/core/l10n/app_strings.dart | 77 ++++++--- lib/core/services/card_color_service.dart | 6 +- .../dashboard/widgets/balance_card.dart | 43 +++-- lib/features/settings/provider.dart | 28 +++ lib/features/settings/screen.dart | 11 +- .../widgets/card_text_color_section.dart | 160 ++++++++++++++++++ 6 files changed, 279 insertions(+), 46 deletions(-) create mode 100644 lib/features/settings/widgets/card_text_color_section.dart diff --git a/lib/core/l10n/app_strings.dart b/lib/core/l10n/app_strings.dart index c1ff21c..97dc35a 100644 --- a/lib/core/l10n/app_strings.dart +++ b/lib/core/l10n/app_strings.dart @@ -9,11 +9,13 @@ class AppStrings { String get appTitle => _ru ? 'Мои финансы' : 'My Finances'; String get totalBalance => _ru ? 'ОБЩИЙ БАЛАНС' : 'TOTAL BALANCE'; - String get tapAndHoldToEdit => _ru ? 'удерживайте для редактирования' : 'tap and hold to edit'; + String get tapAndHoldToEdit => + _ru ? 'удерживайте для редактирования' : 'tap and hold to edit'; String get addAccount => _ru ? 'Добавить счёт' : 'Add account'; String get addTransactionDashboard => _ru ? 'Добавить' : 'Add'; String get transactions => _ru ? 'Транзакции' : 'Transactions'; - String get searchHint => _ru ? 'Поиск транзакций...' : 'Search transactions...'; + String get searchHint => + _ru ? 'Поиск транзакций...' : 'Search transactions...'; String get filterAll => _ru ? 'Все' : 'All'; String get filterIncome => _ru ? 'Доход' : 'Income'; String get filterExpense => _ru ? 'Расход' : 'Expense'; @@ -24,8 +26,11 @@ class AppStrings { String get monthlyBudget => _ru ? 'Бюджет на месяц' : 'Monthly Budget'; String get spent => _ru ? 'Потрачено' : 'Spent'; String get limit => _ru ? 'Лимит' : 'Limit'; - String get noTransactions => _ru ? 'Транзакции не найдены' : 'No transactions found'; - String get addFirstTx => _ru ? 'Нажмите + чтобы добавить первую транзакцию' : 'Tap + to add your first transaction'; + String get noTransactions => + _ru ? 'Транзакции не найдены' : 'No transactions found'; + String get addFirstTx => _ru + ? 'Нажмите + чтобы добавить первую транзакцию' + : 'Tap + to add your first transaction'; String get addTransaction => _ru ? 'Новая транзакция' : 'Add Transaction'; String get editTransaction => _ru ? 'Редактировать' : 'Edit Transaction'; @@ -39,26 +44,36 @@ class AppStrings { String get cancel => _ru ? 'Отмена' : 'Cancel'; String get typeIncome => _ru ? 'Доход' : 'Income'; String get typeExpense => _ru ? 'Расход' : 'Expense'; - String get confirmDelete => _ru ? 'Удалить транзакцию?' : 'Delete transaction?'; - String get confirmDeleteBody => _ru ? 'Это действие нельзя отменить.' : 'This action cannot be undone.'; + String get confirmDelete => + _ru ? 'Удалить транзакцию?' : 'Delete transaction?'; + String get confirmDeleteBody => + _ru ? 'Это действие нельзя отменить.' : 'This action cannot be undone.'; String get saveChanges => _ru ? 'Сохранить изменения' : 'Save Changes'; - String get noteOptional => _ru ? 'Заметка (необязательно)' : 'Note (optional)'; + String get noteOptional => + _ru ? 'Заметка (необязательно)' : 'Note (optional)'; String get addNote => _ru ? 'Добавить заметку...' : 'Add a note...'; String get settings => _ru ? 'Настройки' : 'Settings'; - String get managePreferences => _ru ? 'Управление настройками' : 'Manage your preferences'; + String get managePreferences => + _ru ? 'Управление настройками' : 'Manage your preferences'; String get appearance => _ru ? 'Внешний вид' : 'Appearance'; String get theme => _ru ? 'Тема' : 'Theme'; String get themeDark => _ru ? 'Тёмная' : 'Dark'; String get themeLight => _ru ? 'Светлая' : 'Light'; String get themeSystem => _ru ? 'Системная' : 'System'; + String get cardTextColor => _ru ? 'Цвет текста карточки' : 'Card text color'; + String get cardTextColorWhite => _ru ? 'Белый' : 'White'; + String get cardTextColorAdaptive => _ru ? 'Авто' : 'Adaptive'; + String get cardTextColorBlack => _ru ? 'Чёрный' : 'Black'; String get darkMode => _ru ? 'Тёмная тема' : 'Dark Mode'; String get enabled => _ru ? 'Включено' : 'Enabled'; String get disabled => _ru ? 'Выключено' : 'Disabled'; String get hapticFeedback => _ru ? 'Тактильная отдача' : 'Haptic Feedback'; - String get vibrationOnInteractions => _ru ? 'Вибрация при взаимодействии' : 'Vibration on interactions'; + String get vibrationOnInteractions => + _ru ? 'Вибрация при взаимодействии' : 'Vibration on interactions'; String get biometricLock => _ru ? 'Биометрия' : 'Biometric Lock'; - String get requireFingerprint => _ru ? 'Отпечаток при запуске' : 'Require fingerprint to unlock'; + String get requireFingerprint => + _ru ? 'Отпечаток при запуске' : 'Require fingerprint to unlock'; String get currency => _ru ? 'Валюта' : 'Currency'; String get amountFormat => _ru ? 'Формат суммы' : 'Amount Format'; String get language => _ru ? 'Язык' : 'Language'; @@ -68,20 +83,34 @@ class AppStrings { String get budgetHint => _ru ? 'Месячный лимит' : 'Monthly limit'; String get budgetNone => _ru ? 'Не установлен' : 'Not set'; String get monthlyBudgetSetting => _ru ? 'Месячный бюджет' : 'Monthly Budget'; - String get yourMonthlySpendingLimit => _ru ? 'Ваш лимит расходов на месяц' : 'Your monthly spending limit'; - String get setMonthlySpendingLimit => _ru ? 'Контролируйте свои расходы за месяц' : 'Track your monthly spending'; - String get leaveEmptyToRemove => _ru ? 'Оставьте пустым для удаления лимита' : 'Leave empty to remove budget limit'; + String get yourMonthlySpendingLimit => + _ru ? 'Ваш лимит расходов на месяц' : 'Your monthly spending limit'; + String get setMonthlySpendingLimit => _ru + ? 'Контролируйте свои расходы за месяц' + : 'Track your monthly spending'; + String get leaveEmptyToRemove => _ru + ? 'Оставьте пустым для удаления лимита' + : 'Leave empty to remove budget limit'; String get data => _ru ? 'Данные' : 'Data'; String get exportData => _ru ? 'Экспорт данных' : 'Export data'; String get clearData => _ru ? 'Очистить данные' : 'Clear all data'; - String get clearAllTransactions => _ru ? 'Удалить транзакции' : 'Clear All Transactions'; - String get clearDataConfirm => _ru ? 'Удалить транзакции?' : 'Clear all transactions?'; - String get clearDataWarning => _ru ? 'Это навсегда удалит всю историю транзакций. Это действие нельзя отменить.' : 'This will permanently delete all your transaction history. This cannot be undone.'; - String get areYouSure => _ru ? 'Вы абсолютно уверены?' : 'Are you absolutely sure?'; - String get allTransactionsDeleted => _ru ? 'Все транзакции удалены' : 'All transactions deleted'; + String get clearAllTransactions => + _ru ? 'Удалить транзакции' : 'Clear All Transactions'; + String get clearDataConfirm => + _ru ? 'Удалить транзакции?' : 'Clear all transactions?'; + String get clearDataWarning => _ru + ? 'Это навсегда удалит всю историю транзакций. Это действие нельзя отменить.' + : 'This will permanently delete all your transaction history. This cannot be undone.'; + String get areYouSure => + _ru ? 'Вы абсолютно уверены?' : 'Are you absolutely sure?'; + String get allTransactionsDeleted => + _ru ? 'Все транзакции удалены' : 'All transactions deleted'; String get noKeepThem => _ru ? 'Нет, оставить' : 'No, keep them'; - String get yesDeleteEverything => _ru ? 'Да, удалить всё' : 'Yes, delete everything'; - String get allTransactionsWillBeDeleted => _ru ? 'Все транзакции будут удалены навсегда. Восстановить их будет невозможно.' : 'All transactions will be deleted forever. There is no way to recover them.'; + String get yesDeleteEverything => + _ru ? 'Да, удалить всё' : 'Yes, delete everything'; + String get allTransactionsWillBeDeleted => _ru + ? 'Все транзакции будут удалены навсегда. Восстановить их будет невозможно.' + : 'All transactions will be deleted forever. There is no way to recover them.'; String get dangerZone => _ru ? 'Опасная зона' : 'Danger Zone'; String get navDashboard => _ru ? 'Главная' : 'Dashboard'; @@ -98,9 +127,13 @@ class AppStrings { String get deleteCategory => _ru ? 'Удалить категорию' : 'Delete Category'; String get noCategoriesYet => _ru ? 'Нет категорий' : 'No categories yet'; String get noExpenseData => _ru ? 'Нет данных о расходах' : 'No expense data'; - String get addExpensesToSeeBreakdown => _ru ? 'Добавьте расходы, чтобы увидеть разбивку' : 'Add some expenses to see the breakdown'; + String get addExpensesToSeeBreakdown => _ru + ? 'Добавьте расходы, чтобы увидеть разбивку' + : 'Add some expenses to see the breakdown'; String get noIncomeData => _ru ? 'Нет данных о доходах' : 'No income data'; - String get addIncomeToSeeBreakdown => _ru ? 'Добавьте доходы, чтобы увидеть разбивку' : 'Add some income to see the breakdown'; + String get addIncomeToSeeBreakdown => _ru + ? 'Добавьте доходы, чтобы увидеть разбивку' + : 'Add some income to see the breakdown'; String get total => _ru ? 'Всего' : 'Total'; String get lastSixMonths => _ru ? 'Последние 6 месяцев' : 'Last 6 Months'; diff --git a/lib/core/services/card_color_service.dart b/lib/core/services/card_color_service.dart index 88e89d8..84c812d 100644 --- a/lib/core/services/card_color_service.dart +++ b/lib/core/services/card_color_service.dart @@ -8,13 +8,13 @@ class CardColorService { static const _key2 = 'card_color_secondary'; static const _keyGradient = 'card_gradient_type'; - static const defaultPrimary = Color(0xFF5E5D5D); - static const defaultSecondary = Color(0xFF9E9E9E); + static const defaultPrimary = Color(0xFFFCD34D); + static const defaultSecondary = Color(0xFFDC2626); static const defaultPrimaryLight = Color(0xFF6A6482); static const defaultSecondaryLight = Color(0xFF000000); - static const defaultGradient = GradientType.sweep; + static const defaultGradient = GradientType.linear; static Future<(Color, Color, GradientType)> load({int? accountId}) async { final prefs = await SharedPreferences.getInstance(); diff --git a/lib/features/dashboard/widgets/balance_card.dart b/lib/features/dashboard/widgets/balance_card.dart index 3e9aac5..ac1c2c0 100644 --- a/lib/features/dashboard/widgets/balance_card.dart +++ b/lib/features/dashboard/widgets/balance_card.dart @@ -36,7 +36,7 @@ class BalanceCard extends ConsumerStatefulWidget { final GradientType? previewGradientType; final String? accountName; final CardColors? accountColors; - + const BalanceCard({ super.key, required this.balance, @@ -114,13 +114,7 @@ class BalanceCardState extends ConsumerState center: Alignment.center, startAngle: 0.0, endAngle: 3.14159 * 2, - colors: [ - primary, - secondary, - colorDark, - secondary, - primary, - ], + colors: [primary, secondary, colorDark, secondary, primary], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], ); case GradientType.solid: @@ -136,14 +130,14 @@ class BalanceCardState extends ConsumerState final s = ref.watch(stringsProvider); final rates = ref.read(exchangeRateServiceProvider); final fmt = ref.watch(amountFormatProvider); - + // Use account-specific colors if provided, otherwise use global colors final globalColors = ref.watch(cardColorsProvider); final savedColors = widget.accountColors ?? globalColors; final primary = widget.previewPrimary ?? savedColors.primary; final secondary = widget.previewSecondary ?? savedColors.secondary; final gradientType = widget.previewGradientType ?? savedColors.gradientType; - + final allCurrencies = [ ('USD', r'$'), ('EUR', '€'), @@ -154,6 +148,17 @@ class BalanceCardState extends ConsumerState .where((c) => c.$1 != widget.currencyInfo.code) .toList(); + final textColorMode = ref.watch(cardTextColorProvider); + final Color onCard; + switch (textColorMode) { + case CardTextColorMode.white: + onCard = Colors.white; + case CardTextColorMode.black: + onCard = Colors.black; + case CardTextColorMode.adaptive: + onCard = primary.computeLuminance() > 0.3 ? Colors.black : Colors.white; + } + return GestureDetector( onLongPress: () { HapticService.heavy(); @@ -199,7 +204,7 @@ class BalanceCardState extends ConsumerState Text( widget.accountName!, style: TextStyle( - color: Colors.white.withOpacity(0.7), + color: onCard.withOpacity(0.7), fontSize: 12, fontWeight: FontWeight.w500, letterSpacing: 0.3, @@ -229,7 +234,7 @@ class BalanceCardState extends ConsumerState style: TextStyle( fontSize: 11, letterSpacing: 1.5, - color: Colors.white.withOpacity(0.6), + color: onCard.withOpacity(0.6), ), ), if (widget.accountName == null) @@ -243,10 +248,10 @@ class BalanceCardState extends ConsumerState fmt, widget.currencyInfo.symbol, ), - style: const TextStyle( + style: TextStyle( fontSize: 48, fontWeight: FontWeight.w700, - color: Colors.white, + color: onCard, ), maxLines: 1, ), @@ -259,7 +264,7 @@ class BalanceCardState extends ConsumerState Container( width: 1, height: 70, - color: Colors.white.withOpacity(0.15), + color: onCard.withOpacity(0.15), ), const SizedBox(width: 16), SizedBox( @@ -274,7 +279,9 @@ class BalanceCardState extends ConsumerState c.$1, ); return Padding( - padding: const EdgeInsets.symmetric(vertical: 3), + padding: const EdgeInsets.symmetric( + vertical: 3, + ), child: FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, @@ -283,7 +290,7 @@ class BalanceCardState extends ConsumerState style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: Colors.white.withOpacity(0.65), + color: onCard.withOpacity(0.65), ), maxLines: 1, ), @@ -305,7 +312,7 @@ class BalanceCardState extends ConsumerState textAlign: TextAlign.center, style: TextStyle( fontSize: 9, - color: Colors.white.withOpacity(0.18), + color: onCard.withOpacity(0.18), letterSpacing: 0.6, ), ), diff --git a/lib/features/settings/provider.dart b/lib/features/settings/provider.dart index 9adc0e8..f3ca27b 100644 --- a/lib/features/settings/provider.dart +++ b/lib/features/settings/provider.dart @@ -106,6 +106,34 @@ final themeProvider = StateNotifierProvider(( return ThemeModeNotifier(prefs); }); +enum CardTextColorMode { white, adaptive, black } + +class CardTextColorNotifier extends StateNotifier { + static const _key = 'card_text_color'; + final SharedPreferences _prefs; + + CardTextColorNotifier(this._prefs) + : super(_fromString(_prefs.getString(_key))); + + static CardTextColorMode _fromString(String? value) { + return CardTextColorMode.values.firstWhere( + (e) => e.name == value, + orElse: () => CardTextColorMode.adaptive, + ); + } + + void set(CardTextColorMode mode) { + state = mode; + _prefs.setString(_key, mode.name); + } +} + +final cardTextColorProvider = + StateNotifierProvider((ref) { + final prefs = ref.watch(sharedPreferencesProvider); + return CardTextColorNotifier(prefs); + }); + final exchangeRateServiceProvider = Provider((ref) { final prefs = ref.watch(sharedPreferencesProvider); return ExchangeRateService(prefs); diff --git a/lib/features/settings/screen.dart b/lib/features/settings/screen.dart index 7df6a55..7dcac72 100644 --- a/lib/features/settings/screen.dart +++ b/lib/features/settings/screen.dart @@ -7,6 +7,7 @@ import '../../core/services/haptic_service.dart'; import '../dashboard/provider.dart'; import 'provider.dart'; import 'widgets/theme_section.dart'; +import 'widgets/card_text_color_section.dart'; import 'widgets/haptic_section.dart'; import 'widgets/language_section.dart'; import 'widgets/currency_section.dart'; @@ -45,9 +46,11 @@ class SettingsScreen extends ConsumerWidget { ), TextButton( onPressed: () async { - final biometricEnabled = await BiometricService.isEnabled(); + final biometricEnabled = + await BiometricService.isEnabled(); if (biometricEnabled) { - final authenticated = await BiometricService.authenticate(); + final authenticated = + await BiometricService.authenticate(); if (!authenticated) { Navigator.pop(ctx2); return; @@ -55,7 +58,7 @@ class SettingsScreen extends ConsumerWidget { } ref.read(transactionsProvider.notifier).clearAll(); Navigator.pop(ctx2); - + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( @@ -122,6 +125,8 @@ class SettingsScreen extends ConsumerWidget { children: [ const ThemeSection(), const SizedBox(height: 16), + const CardTextColorSection(), + const SizedBox(height: 16), const HapticSection(), const SizedBox(height: 16), const _BiometricSection(), diff --git a/lib/features/settings/widgets/card_text_color_section.dart b/lib/features/settings/widgets/card_text_color_section.dart new file mode 100644 index 0000000..a243368 --- /dev/null +++ b/lib/features/settings/widgets/card_text_color_section.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/constants.dart'; +import '../../../core/l10n/locale_provider.dart'; +import '../provider.dart'; + +class CardTextColorSection extends ConsumerWidget { + const CardTextColorSection({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final s = ref.watch(stringsProvider); + final textColorMode = ref.watch(cardTextColorProvider); + final isDark = Theme.of(context).brightness == Brightness.dark; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(16), + border: isDark + ? null + : Border.all(color: const Color(0xFFDDDDEE), width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.accent.withOpacity(0.15), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.text_fields_rounded, + color: AppColors.accent, + size: 20, + ), + ), + const SizedBox(width: 12), + Text( + s.cardTextColor, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: _CardTextColorOption( + label: s.cardTextColorWhite, + icon: Icons.brightness_high_rounded, + isSelected: textColorMode == CardTextColorMode.white, + onTap: () => ref + .read(cardTextColorProvider.notifier) + .set(CardTextColorMode.white), + ), + ), + const SizedBox(width: 8), + Expanded( + child: _CardTextColorOption( + label: s.cardTextColorAdaptive, + icon: Icons.auto_awesome_rounded, + isSelected: textColorMode == CardTextColorMode.adaptive, + onTap: () => ref + .read(cardTextColorProvider.notifier) + .set(CardTextColorMode.adaptive), + ), + ), + const SizedBox(width: 8), + Expanded( + child: _CardTextColorOption( + label: s.cardTextColorBlack, + icon: Icons.brightness_low_rounded, + isSelected: textColorMode == CardTextColorMode.black, + onTap: () => ref + .read(cardTextColorProvider.notifier) + .set(CardTextColorMode.black), + ), + ), + ], + ), + ], + ), + ); + } +} + +class _CardTextColorOption extends StatelessWidget { + final String label; + final IconData icon; + final bool isSelected; + final VoidCallback onTap; + + const _CardTextColorOption({ + required this.label, + required this.icon, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: isSelected + ? AppColors.accent.withOpacity(0.15) + : (isDark + ? Colors.white.withOpacity(0.05) + : Colors.black.withOpacity(0.03)), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isSelected + ? AppColors.accent + : (isDark + ? Colors.white.withOpacity(0.1) + : Colors.black.withOpacity(0.08)), + width: isSelected ? 2 : 1, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + color: isSelected + ? AppColors.accent + : Theme.of(context).colorScheme.onSurface.withOpacity(0.5), + size: 22, + ), + const SizedBox(height: 6), + Text( + label, + style: TextStyle( + fontSize: 11, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + color: isSelected + ? AppColors.accent + : Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +}