diff --git a/README.md b/README.md index c9cbccd..5eb0acd 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Casha is the **"Simplest"**, **"Most Elegant"**, and **"Most Focused"** way to t ## 📱 Screenshots -> _Add your screenshots here_ +![screenshot](https://i.ibb.co/Fq06nxKM/screenshot.png) ## 🚀 Getting Started diff --git a/assets/screenshot_dashboard.png b/assets/screenshot_dashboard.png new file mode 100644 index 0000000..bc19bba Binary files /dev/null and b/assets/screenshot_dashboard.png differ diff --git a/lib/app/theme.dart b/lib/app/theme.dart index c786ca6..294489b 100644 --- a/lib/app/theme.dart +++ b/lib/app/theme.dart @@ -8,9 +8,11 @@ class AppTheme { final textTheme = GoogleFonts.poppinsTextTheme(base.textTheme).apply( bodyColor: AppColors.textPrimary, displayColor: AppColors.textPrimary, + fontFamilyFallback: ['Roboto'], // Ensures Cyrillic renders with same visual style ); return base.copyWith( + fontFamily: GoogleFonts.poppins().fontFamily, // Explicit font family for Cyrillic support textTheme: textTheme, scaffoldBackgroundColor: AppColors.background, colorScheme: const ColorScheme.dark( @@ -106,9 +108,11 @@ class AppTheme { final textTheme = GoogleFonts.poppinsTextTheme(base.textTheme).apply( bodyColor: const Color(0xFF1A1A2E), displayColor: const Color(0xFF1A1A2E), + fontFamilyFallback: ['Roboto'], // Ensures Cyrillic renders with same visual style ); return base.copyWith( + fontFamily: GoogleFonts.poppins().fontFamily, // Explicit font family for Cyrillic support textTheme: textTheme, scaffoldBackgroundColor: const Color(0xFFF0F0F7), colorScheme: const ColorScheme.light( diff --git a/lib/core/l10n/app_strings.dart b/lib/core/l10n/app_strings.dart new file mode 100644 index 0000000..1f2b176 --- /dev/null +++ b/lib/core/l10n/app_strings.dart @@ -0,0 +1,140 @@ +enum AppLocale { en, ru } + +class AppStrings { + final AppLocale locale; + + const AppStrings(this.locale); + + bool get _ru => locale == AppLocale.ru; + + // ── Dashboard ── + String get appTitle => _ru ? 'Мои финансы' : 'My Finances'; + String get totalBalance => _ru ? 'ОБЩИЙ БАЛАНС' : 'TOTAL BALANCE'; + String get add => _ru ? 'Добавить' : 'Add'; + String get transactions => _ru ? 'Транзакции' : 'Transactions'; + String get searchHint => _ru ? 'Поиск транзакций...' : 'Search transactions...'; + String get filterAll => _ru ? 'Все' : 'All'; + String get filterIncome => _ru ? 'Доход' : 'Income'; + String get filterExpense => _ru ? 'Расход' : 'Expense'; + String get filterAllTime => _ru ? 'Всё время' : 'All Time'; + String get filterMonth => _ru ? 'Месяц' : 'Month'; + String get income => _ru ? 'Доход' : 'Income'; + String get expenses => _ru ? 'Расходы' : 'Expenses'; + 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'; + + // ── Add/Edit transaction screen ── + String get addTransaction => _ru ? 'Новая транзакция' : 'Add Transaction'; + String get editTransaction => _ru ? 'Редактировать' : 'Edit Transaction'; + String get amount => _ru ? 'Сумма' : 'Amount'; + String get category => _ru ? 'Категория' : 'Category'; + String get note => _ru ? 'Заметка' : 'Note'; + String get date => _ru ? 'Дата' : 'Date'; + String get time => _ru ? 'Время' : 'Time'; + String get save => _ru ? 'Сохранить' : 'Save'; + String get delete => _ru ? 'Удалить' : 'Delete'; + 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 saveChanges => _ru ? 'Сохранить изменения' : 'Save Changes'; + String get noteOptional => _ru ? 'Заметка (необязательно)' : 'Note (optional)'; + String get addNote => _ru ? 'Добавить заметку...' : 'Add a note...'; + + // ── Settings ── + String get settings => _ru ? 'Настройки' : 'Settings'; + 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 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 biometricLock => _ru ? 'Биометрическая блокировка' : 'Biometric Lock'; + String get requireFingerprint => _ru ? 'Требовать отпечаток при запуске' : 'Require fingerprint on app launch'; + String get currency => _ru ? 'Валюта' : 'Currency'; + String get amountFormat => _ru ? 'Формат суммы' : 'Amount Format'; + String get language => _ru ? 'Язык' : 'Language'; + String get langRu => _ru ? 'Русский' : 'Russian'; + String get langEn => _ru ? 'Английский' : 'English'; + String get budget => _ru ? 'Бюджет' : 'Budget'; + 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 ? 'Установите месячный лимит расходов для отслеживания бюджета' : 'Set a monthly spending limit to track your budget'; + 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 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 dangerZone => _ru ? 'Опасная зона' : 'Danger Zone'; + + // ── Categories ── + String get categories => _ru ? 'Категории' : 'Categories'; + String get rankedByAmount => _ru ? 'По сумме' : 'Ranked by Amount'; + String get addCategory => _ru ? 'Добавить категорию' : 'Add Category'; + String get editCategory => _ru ? 'Редактировать' : 'Edit Category'; + String get categoryName => _ru ? 'Название' : 'Name'; + String get categoryIcon => _ru ? 'Иконка' : 'Icon'; + String get categoryColor => _ru ? 'Цвет' : 'Color'; + String get deleteCategory => _ru ? 'Удалить категорию' : 'Delete Category'; + String get noCategoriesYet => _ru ? 'Нет категорий' : 'No categories yet'; + + // ── Built-in category names (translated for display only, stored in English) ── + String categoryLabel(String key) { + if (!_ru) return key; // English: return key as-is + const map = { + 'Food': 'Еда', + 'Transport': 'Транспорт', + 'Shopping': 'Покупки', + 'Entertainment': 'Развлечения', + 'Health': 'Здоровье', + 'Housing': 'Жильё', + 'Education': 'Образование', + 'Travel': 'Путешествия', + 'Salary': 'Зарплата', + 'Freelance': 'Фриланс', + 'Investment': 'Инвестиции', + 'Gift': 'Подарок', + 'Other': 'Другое', + 'Utilities': 'Коммунальные', + 'Clothing': 'Одежда', + 'Sports': 'Спорт', + 'Beauty': 'Красота', + 'Pets': 'Питомцы', + 'Business': 'Бизнес', + 'Savings': 'Накопления', + }; + return map[key] ?? key; // fallback to English key if not in map + } + + // ── Color editor overlay ── + String get colorPrimary => _ru ? 'Основной' : 'Primary'; + String get colorSecondary => _ru ? 'Вторичный' : 'Secondary'; + String get colorSolid => _ru ? 'Однотон' : 'Solid'; + String get gradientLinear => _ru ? 'Линейный' : 'Linear'; + String get gradientReverse => _ru ? 'Обратный' : 'Reverse'; + String get gradientRadial => _ru ? 'Радиус' : 'Radial'; + String get gradientSweep => _ru ? 'Круговой' : 'Sweep'; + String get reset => _ru ? 'Сброс' : 'Reset'; + String get apply => _ru ? 'Применить' : 'Apply'; + + // ── Date locale code for intl DateFormat ── + String get dateLocale => _ru ? 'ru_RU' : 'en_US'; +} diff --git a/lib/core/l10n/locale_provider.dart b/lib/core/l10n/locale_provider.dart new file mode 100644 index 0000000..7137616 --- /dev/null +++ b/lib/core/l10n/locale_provider.dart @@ -0,0 +1,30 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../../features/dashboard/provider.dart'; +import 'app_strings.dart'; + +class LocaleNotifier extends Notifier { + static const _key = 'app_locale'; + + @override + AppLocale build() { + // Load persisted locale synchronously via ref + final prefs = ref.read(sharedPreferencesProvider); + final saved = prefs.getString(_key); + return saved == 'ru' ? AppLocale.ru : AppLocale.en; + } + + void setLocale(AppLocale locale) { + state = locale; + ref.read(sharedPreferencesProvider).setString(_key, locale.name); + } +} + +final localeProvider = NotifierProvider( + LocaleNotifier.new, +); + +final stringsProvider = Provider((ref) { + final locale = ref.watch(localeProvider); + return AppStrings(locale); +}); diff --git a/lib/features/add_transaction/screen.dart b/lib/features/add_transaction/screen.dart index b5f874d..2e2def4 100644 --- a/lib/features/add_transaction/screen.dart +++ b/lib/features/add_transaction/screen.dart @@ -5,6 +5,8 @@ import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:uuid/uuid.dart'; import '../../core/constants.dart'; +import '../../core/l10n/app_strings.dart'; +import '../../core/l10n/locale_provider.dart'; import '../../core/services/haptic_service.dart'; import '../../shared/models/transaction.dart'; import '../dashboard/provider.dart'; @@ -216,6 +218,7 @@ class _AddTransactionScreenState extends ConsumerState @override Widget build(BuildContext context) { + final s = ref.watch(stringsProvider); final state = ref.watch(addTransactionProvider(widget.initial)); final categories = ref.watch(availableCategoriesProvider(widget.initial)); final overrideCurrency = state.overrideCurrency; @@ -224,7 +227,7 @@ class _AddTransactionScreenState extends ConsumerState return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: AppBar( - title: Text(state.isEditing ? 'Edit Transaction' : 'Add Transaction'), + title: Text(state.isEditing ? s.editTransaction : s.addTransaction), leading: IconButton( icon: const Icon(Icons.close_rounded), onPressed: () => context.pop(), @@ -238,12 +241,12 @@ class _AddTransactionScreenState extends ConsumerState showDialog( context: context, builder: (ctx) => AlertDialog( - title: const Text('Delete transaction?'), - content: const Text('This action cannot be undone.'), + title: Text(s.confirmDelete), + content: Text(s.confirmDeleteBody), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text('Cancel'), + child: Text(s.cancel), ), TextButton( onPressed: () { @@ -254,7 +257,7 @@ class _AddTransactionScreenState extends ConsumerState style: TextButton.styleFrom( foregroundColor: const Color(0xFFE05C6B), ), - child: const Text('Delete'), + child: Text(s.delete), ), ], ), @@ -271,12 +274,13 @@ class _AddTransactionScreenState extends ConsumerState children: [ _TypeToggle( selected: state.type, + strings: s, onChanged: (t) => ref.read(addTransactionProvider(widget.initial).notifier).setType(t), ), const SizedBox(height: 24), - _SectionLabel('Amount'), + _SectionLabel(s.amount), const SizedBox(height: 8), AnimatedBuilder( animation: _borderColorAnimation, @@ -339,7 +343,7 @@ class _AddTransactionScreenState extends ConsumerState const SizedBox(height: 20), Text( - 'Currency', + s.currency, style: TextStyle( fontSize: 13, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), @@ -353,7 +357,7 @@ class _AddTransactionScreenState extends ConsumerState ), const SizedBox(height: 20), - _SectionLabel('Category'), + _SectionLabel(s.category), const SizedBox(height: 8), _CategoryPicker( categories: categories, @@ -372,7 +376,7 @@ class _AddTransactionScreenState extends ConsumerState crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Date', + s.date, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), fontWeight: FontWeight.w500, @@ -400,7 +404,7 @@ class _AddTransactionScreenState extends ConsumerState const SizedBox(width: 8), Expanded( child: Text( - DateFormat('MMM d, yyyy').format(_selectedDate), + DateFormat('MMM d, yyyy', s.dateLocale).format(_selectedDate), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.w500, @@ -422,7 +426,7 @@ class _AddTransactionScreenState extends ConsumerState crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Time', + s.time, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), fontWeight: FontWeight.w500, @@ -466,7 +470,7 @@ class _AddTransactionScreenState extends ConsumerState ), const SizedBox(height: 20), - _SectionLabel('Note (optional)'), + _SectionLabel(s.noteOptional), const SizedBox(height: 8), TextFormField( controller: _noteController, @@ -482,7 +486,7 @@ class _AddTransactionScreenState extends ConsumerState ), ), decoration: InputDecoration( - hintText: 'Add a note...', + hintText: s.addNote, enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: isDark @@ -530,7 +534,7 @@ class _AddTransactionScreenState extends ConsumerState ), ) : Text( - state.isEditing ? 'Save Changes' : 'Add Transaction', + state.isEditing ? s.saveChanges : s.addTransaction, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, @@ -570,7 +574,12 @@ class _SectionLabel extends StatelessWidget { class _TypeToggle extends StatelessWidget { final TransactionType selected; final ValueChanged onChanged; - const _TypeToggle({required this.selected, required this.onChanged}); + final AppStrings strings; + const _TypeToggle({ + required this.selected, + required this.onChanged, + required this.strings, + }); @override Widget build(BuildContext context) { @@ -584,14 +593,14 @@ class _TypeToggle extends StatelessWidget { child: Row( children: [ _TypeOption( - label: 'Income', + label: strings.typeIncome, icon: Icons.arrow_downward_rounded, color: AppColors.income, isSelected: selected == TransactionType.income, onTap: () => onChanged(TransactionType.income), ), _TypeOption( - label: 'Expense', + label: strings.typeExpense, icon: Icons.arrow_upward_rounded, color: AppColors.expense, isSelected: selected == TransactionType.expense, diff --git a/lib/features/categories/screen.dart b/lib/features/categories/screen.dart index 8f54bd9..3fd9f48 100644 --- a/lib/features/categories/screen.dart +++ b/lib/features/categories/screen.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../core/constants.dart'; +import '../../core/l10n/locale_provider.dart'; import '../../shared/utils/currency_utils.dart'; import '../../shared/providers/amount_format_provider.dart'; import '../settings/provider.dart'; @@ -24,6 +25,7 @@ class _CategoriesScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final s = ref.watch(stringsProvider); final data = ref.watch(categoryExpenseProvider); final monthlyData = ref.watch(monthlyBreakdownProvider); final total = data.values.fold(0.0, (a, b) => a + b); @@ -39,14 +41,14 @@ class _CategoriesScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Categories', + s.categories, style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w700, color: Theme.of(context).colorScheme.onSurface, ), ), Text( - 'Expense breakdown', + s.expenses, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), ), @@ -85,7 +87,7 @@ class _CategoriesScreenState extends ConsumerState { _BarChartCard(monthlyData: monthlyData, currency: currencyInfo.symbol), const SizedBox(height: 20), Text( - 'Ranked by Amount', + s.rankedByAmount, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, @@ -408,6 +410,7 @@ class _CategoryRow extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final s = ref.watch(stringsProvider); final fmt = ref.watch(amountFormatProvider); final color = AppCategories.colors[category] ?? AppColors.accent; final icon = AppCategories.icons[category] ?? Icons.category_rounded; @@ -451,7 +454,7 @@ class _CategoryRow extends ConsumerWidget { const SizedBox(width: 12), Expanded( child: Text( - category, + s.categoryLabel(category), style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, diff --git a/lib/features/dashboard/screen.dart b/lib/features/dashboard/screen.dart index 1cf016f..03a2d7b 100644 --- a/lib/features/dashboard/screen.dart +++ b/lib/features/dashboard/screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; +import '../../core/l10n/locale_provider.dart'; import '../../core/services/card_color_service.dart'; import '../../core/services/haptic_service.dart'; import '../settings/provider.dart'; @@ -115,6 +116,7 @@ class _DashboardScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final s = ref.watch(stringsProvider); final balance = ref.watch(totalBalanceProvider); final income = ref.watch(totalIncomeProvider); final expense = ref.watch(totalExpenseProvider); @@ -131,7 +133,7 @@ class _DashboardScreenState extends ConsumerState { scrolledUnderElevation: 0, titleSpacing: 20, title: Text( - 'Casha', + s.appTitle, style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.w800, color: Theme.of(context).colorScheme.onSurface, @@ -143,7 +145,7 @@ class _DashboardScreenState extends ConsumerState { padding: const EdgeInsets.only(right: 20), child: Center( child: Text( - DateFormat('MMMM yyyy').format(DateTime.now()), + DateFormat('MMMM yyyy', s.dateLocale).format(DateTime.now()), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5), fontWeight: FontWeight.w500, @@ -161,7 +163,7 @@ class _DashboardScreenState extends ConsumerState { backgroundColor: const Color(0xFF7C6DED), foregroundColor: Colors.white, icon: const Icon(Icons.add), - label: const Text('Add', style: TextStyle(fontWeight: FontWeight.w600)), + label: Text(s.add, style: const TextStyle(fontWeight: FontWeight.w600)), ), floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, body: SafeArea( @@ -188,6 +190,7 @@ class _DashboardScreenState extends ConsumerState { income: income, expense: expense, currencyInfo: currencyInfo, + strings: s, ), if (budget != null) ...[ const SizedBox(height: 16), @@ -195,6 +198,7 @@ class _DashboardScreenState extends ConsumerState { spent: monthExpense, budget: budget, currencyInfo: currencyInfo, + strings: s, ), ], const SizedBox(height: 24), @@ -203,12 +207,13 @@ class _DashboardScreenState extends ConsumerState { focusNode: _searchFocusNode, onTap: _scrollToSearch, ref: ref, + strings: s, ), const SizedBox(height: 12), - const FilterChips(), + FilterChips(strings: s), const SizedBox(height: 20), Text( - 'Transactions', + s.transactions, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, @@ -220,9 +225,9 @@ class _DashboardScreenState extends ConsumerState { ), ), if (recent.isEmpty) - const SliverFillRemaining( + SliverFillRemaining( hasScrollBody: false, - child: EmptyState(), + child: EmptyState(strings: s), ) else SliverPadding( diff --git a/lib/features/dashboard/widgets/budget_progress.dart b/lib/features/dashboard/widgets/budget_progress.dart index 886ffaa..e73abed 100644 --- a/lib/features/dashboard/widgets/budget_progress.dart +++ b/lib/features/dashboard/widgets/budget_progress.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/l10n/app_strings.dart'; import '../../../shared/providers/amount_format_provider.dart'; import '../../../shared/utils/currency_utils.dart'; import '../../settings/provider.dart'; @@ -9,11 +10,13 @@ class BudgetProgress extends ConsumerWidget { final double spent; final double budget; final CurrencyInfo currencyInfo; + final AppStrings strings; const BudgetProgress({ super.key, required this.spent, required this.budget, required this.currencyInfo, + required this.strings, }); Border? _themeBorder(BuildContext context) { @@ -51,7 +54,7 @@ class BudgetProgress extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Monthly Budget', + strings.monthlyBudget, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of( context, @@ -95,7 +98,7 @@ class BudgetProgress extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Spent: ${formatAmount(currencyInfo.symbol, spent, fmt)}', + '${strings.spent}: ${formatAmount(currencyInfo.symbol, spent, fmt)}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of( context, @@ -103,7 +106,7 @@ class BudgetProgress extends ConsumerWidget { ), ), Text( - 'Limit: ${formatAmount(currencyInfo.symbol, budget, fmt)}', + '${strings.limit}: ${formatAmount(currencyInfo.symbol, budget, fmt)}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of( context, diff --git a/lib/features/dashboard/widgets/color_editor_overlay.dart b/lib/features/dashboard/widgets/color_editor_overlay.dart index 8193d6b..8bf5943 100644 --- a/lib/features/dashboard/widgets/color_editor_overlay.dart +++ b/lib/features/dashboard/widgets/color_editor_overlay.dart @@ -2,6 +2,8 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/l10n/app_strings.dart'; +import '../../../core/l10n/locale_provider.dart'; import '../../../core/services/card_color_service.dart'; import '../../../core/services/haptic_service.dart'; import '../../settings/provider.dart'; @@ -96,6 +98,10 @@ class _FullScreenBlurOverlayState extends State { ), child: StatefulBuilder( builder: (ctx, setPanelState) { + final s = AppStrings( + ProviderScope.containerOf(widget.context).read(localeProvider), + ); + void onHSVChanged(HSVColor hsv) { setPanelState(() {}); dash.setState(() { @@ -125,7 +131,7 @@ class _FullScreenBlurOverlayState extends State { children: [ Expanded( child: PanelTab( - label: 'Primary', + label: s.colorPrimary, isSelected: dash.editingPrimary, color: dash.tempPrimary, isDimmed: isSolid, @@ -142,7 +148,7 @@ class _FullScreenBlurOverlayState extends State { const SizedBox(width: 6), Expanded( child: PanelTab( - label: 'Secondary', + label: s.colorSecondary, isSelected: !dash.editingPrimary, color: dash.tempSecondary, isDimmed: isSolid, @@ -196,7 +202,7 @@ class _FullScreenBlurOverlayState extends State { ), ), child: Text( - 'Solid', + s.colorSolid, style: TextStyle( fontSize: 11, fontWeight: isSolid @@ -391,10 +397,10 @@ class _FullScreenBlurOverlayState extends State { .map((type) { final isSelected = dash.tempGradientType == type; final label = switch (type) { - GradientType.linear => 'Linear', - GradientType.linearReverse => 'Reverse', - GradientType.radial => 'Radial', - GradientType.sweep => 'Sweep', + GradientType.linear => s.gradientLinear, + GradientType.linearReverse => s.gradientReverse, + GradientType.radial => s.gradientRadial, + GradientType.sweep => s.gradientSweep, GradientType.solid => '', }; final icon = switch (type) { @@ -498,8 +504,8 @@ class _FullScreenBlurOverlayState extends State { dash.overlayEntry?.markNeedsBuild(); }, icon: const Icon(Icons.restart_alt_rounded, size: 15), - label: const Text('Reset', - style: TextStyle(fontSize: 13)), + label: Text(s.reset, + style: const TextStyle(fontSize: 13)), style: OutlinedButton.styleFrom( foregroundColor: Theme.of(widget.context) .colorScheme @@ -529,8 +535,8 @@ class _FullScreenBlurOverlayState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), - child: const Text('Apply', - style: TextStyle( + child: Text(s.apply, + style: const TextStyle( fontWeight: FontWeight.w700, fontSize: 14)), ), ), diff --git a/lib/features/dashboard/widgets/filter_chips.dart b/lib/features/dashboard/widgets/filter_chips.dart index 155956d..6e5fc9b 100644 --- a/lib/features/dashboard/widgets/filter_chips.dart +++ b/lib/features/dashboard/widgets/filter_chips.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/constants.dart'; +import '../../../core/l10n/app_strings.dart'; import '../../../core/services/haptic_service.dart'; import '../provider.dart'; class FilterChips extends ConsumerWidget { - const FilterChips({super.key}); + final AppStrings strings; + const FilterChips({super.key, required this.strings}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -16,13 +18,13 @@ class FilterChips extends ConsumerWidget { return Row( children: [ _FilterChip( - label: 'All Time', + label: strings.filterAllTime, isSelected: timeFilter == TimeFilter.allTime, onTap: () => ref.read(timeFilterProvider.notifier).state = TimeFilter.allTime, ), const SizedBox(width: 6), _FilterChip( - label: 'Month', + label: strings.filterMonth, isSelected: timeFilter == TimeFilter.lastMonth, onTap: () => ref.read(timeFilterProvider.notifier).state = TimeFilter.lastMonth, ), @@ -37,20 +39,20 @@ class FilterChips extends ConsumerWidget { ), ), _FilterChip( - label: 'All', + label: strings.filterAll, isSelected: typeFilter == TransactionFilter.all, onTap: () => ref.read(transactionFilterProvider.notifier).state = TransactionFilter.all, ), const SizedBox(width: 6), _FilterChip( - label: 'Income', + label: strings.filterIncome, isSelected: typeFilter == TransactionFilter.income, color: AppColors.income, onTap: () => ref.read(transactionFilterProvider.notifier).state = TransactionFilter.income, ), const SizedBox(width: 6), _FilterChip( - label: 'Expense', + label: strings.filterExpense, isSelected: typeFilter == TransactionFilter.expense, color: AppColors.expense, onTap: () => ref.read(transactionFilterProvider.notifier).state = TransactionFilter.expense, diff --git a/lib/features/dashboard/widgets/search_bar.dart b/lib/features/dashboard/widgets/search_bar.dart index 13fe416..163eee4 100644 --- a/lib/features/dashboard/widgets/search_bar.dart +++ b/lib/features/dashboard/widgets/search_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/l10n/app_strings.dart'; import '../provider.dart'; class SearchBar extends StatelessWidget { @@ -7,12 +8,14 @@ class SearchBar extends StatelessWidget { final FocusNode focusNode; final VoidCallback onTap; final WidgetRef ref; + final AppStrings strings; const SearchBar({ super.key, required this.controller, required this.focusNode, required this.onTap, required this.ref, + required this.strings, }); @override @@ -23,7 +26,7 @@ class SearchBar extends StatelessWidget { focusNode: focusNode, onTap: onTap, decoration: InputDecoration( - hintText: 'Search transactions...', + hintText: strings.searchHint, prefixIcon: Icon( Icons.search_rounded, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), diff --git a/lib/features/dashboard/widgets/summary_row.dart b/lib/features/dashboard/widgets/summary_row.dart index 710912c..c4d66b7 100644 --- a/lib/features/dashboard/widgets/summary_row.dart +++ b/lib/features/dashboard/widgets/summary_row.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/constants.dart'; +import '../../../core/l10n/app_strings.dart'; import '../../../shared/providers/amount_format_provider.dart'; import '../../../shared/utils/currency_utils.dart'; import '../../settings/provider.dart'; @@ -10,11 +11,13 @@ class SummaryRow extends StatelessWidget { final double income; final double expense; final CurrencyInfo currencyInfo; + final AppStrings strings; const SummaryRow({ super.key, required this.income, required this.expense, required this.currencyInfo, + required this.strings, }); @override @@ -23,7 +26,7 @@ class SummaryRow extends StatelessWidget { children: [ Expanded( child: SummaryCard( - label: 'Income', + label: strings.income, amount: income, color: AppColors.income, icon: Icons.arrow_downward_rounded, @@ -33,7 +36,7 @@ class SummaryRow extends StatelessWidget { const SizedBox(width: 12), Expanded( child: SummaryCard( - label: 'Expenses', + label: strings.expenses, amount: expense, color: AppColors.expense, icon: Icons.arrow_upward_rounded, diff --git a/lib/features/dashboard/widgets/transaction_tile.dart b/lib/features/dashboard/widgets/transaction_tile.dart index 0ca74da..09b2c1a 100644 --- a/lib/features/dashboard/widgets/transaction_tile.dart +++ b/lib/features/dashboard/widgets/transaction_tile.dart @@ -3,6 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../../core/constants.dart'; +import '../../../core/l10n/app_strings.dart'; +import '../../../core/l10n/locale_provider.dart'; import '../../../shared/models/transaction.dart'; import '../../../shared/providers/amount_format_provider.dart'; import '../../../shared/utils/currency_utils.dart'; @@ -18,6 +20,7 @@ class TransactionTile extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final s = ref.watch(stringsProvider); final fmt = ref.watch(amountFormatProvider); final isIncome = transaction.type == TransactionType.income; final color = isIncome ? AppColors.income : AppColors.expense; @@ -51,7 +54,7 @@ class TransactionTile extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - transaction.category, + s.categoryLabel(transaction.category), style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, @@ -69,7 +72,7 @@ class TransactionTile extends ConsumerWidget { ) else Text( - DateFormat('MMM d, yyyy · HH:mm').format(transaction.date), + DateFormat('d MMM yyyy · HH:mm', s.dateLocale).format(transaction.date), style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of( context, @@ -94,7 +97,8 @@ class TransactionTile extends ConsumerWidget { } class EmptyState extends StatelessWidget { - const EmptyState({super.key}); + final AppStrings strings; + const EmptyState({super.key, required this.strings}); @override Widget build(BuildContext context) { @@ -116,7 +120,7 @@ class EmptyState extends StatelessWidget { ), const SizedBox(height: 16), Text( - 'No transactions found', + strings.noTransactions, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.w600, @@ -124,7 +128,7 @@ class EmptyState extends StatelessWidget { ), const SizedBox(height: 6), Text( - 'Tap + to add your first transaction', + strings.addFirstTx, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), ), diff --git a/lib/features/settings/screen.dart b/lib/features/settings/screen.dart index 577cf6a..c2ead26 100644 --- a/lib/features/settings/screen.dart +++ b/lib/features/settings/screen.dart @@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../core/constants.dart'; +import '../../core/l10n/app_strings.dart'; +import '../../core/l10n/locale_provider.dart'; import '../../core/services/biometric_service.dart'; import '../../core/services/haptic_service.dart'; import '../../shared/utils/currency_utils.dart'; @@ -54,15 +56,16 @@ class _SettingsScreenState extends ConsumerState { } void _confirmClearData(BuildContext context, WidgetRef ref) { + final s = ref.read(stringsProvider); showDialog( context: context, builder: (ctx) => AlertDialog( - title: const Text('Clear all transactions?'), - content: const Text('This will permanently delete all your transaction history. This cannot be undone.'), + title: Text(s.clearDataConfirm), + content: Text(s.clearDataWarning), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text('Cancel'), + child: Text(s.cancel), ), TextButton( onPressed: () { @@ -70,30 +73,30 @@ class _SettingsScreenState extends ConsumerState { showDialog( context: context, builder: (ctx2) => AlertDialog( - title: const Text('Are you absolutely sure?'), - content: const Text('All transactions will be deleted forever. There is no way to recover them.'), + title: Text(s.areYouSure), + content: Text(s.allTransactionsWillBeDeleted), actions: [ TextButton( onPressed: () => Navigator.pop(ctx2), - child: const Text('No, keep them'), + child: Text(s.noKeepThem), ), TextButton( onPressed: () { ref.read(transactionsProvider.notifier).clearAll(); Navigator.pop(ctx2); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('All transactions deleted')), + SnackBar(content: Text(s.allTransactionsDeleted)), ); }, style: TextButton.styleFrom(foregroundColor: const Color(0xFFE05C6B)), - child: const Text('Yes, delete everything'), + child: Text(s.yesDeleteEverything), ), ], ), ); }, style: TextButton.styleFrom(foregroundColor: const Color(0xFFE05C6B)), - child: const Text('Delete'), + child: Text(s.delete), ), ], ), @@ -102,6 +105,7 @@ class _SettingsScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final s = ref.watch(stringsProvider); final budget = ref.watch(budgetProvider); final themeMode = ref.watch(themeProvider); final isDarkMode = themeMode == ThemeMode.dark; @@ -118,14 +122,14 @@ class _SettingsScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Settings', + s.settings, style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w700, color: Theme.of(context).colorScheme.onSurface, ), ), Text( - 'Manage your preferences', + s.managePreferences, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), ), @@ -165,14 +169,14 @@ class _SettingsScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Dark Mode', + s.darkMode, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, ), ), Text( - isDarkMode ? 'Enabled' : 'Disabled', + isDarkMode ? s.enabled : s.disabled, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), ), @@ -223,14 +227,14 @@ class _SettingsScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Haptic Feedback', + s.hapticFeedback, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, ), ), Text( - 'Vibration on interactions', + s.vibrationOnInteractions, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), ), @@ -252,6 +256,112 @@ class _SettingsScreenState extends ConsumerState { const _BiometricSection(), + 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.language_rounded, + color: AppColors.accent, + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + s.language, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + Consumer( + builder: (context, ref, _) { + final currentLocale = ref.watch(localeProvider); + return Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () => ref.read(localeProvider.notifier).setLocale(AppLocale.en), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: currentLocale == AppLocale.en + ? AppColors.accent.withOpacity(0.2) + : Theme.of(context).scaffoldBackgroundColor, + borderRadius: BorderRadius.circular(12), + border: currentLocale == AppLocale.en + ? Border.all(color: AppColors.accent, width: 1.5) + : (isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1)), + ), + child: Text( + s.langEn, + textAlign: TextAlign.center, + style: TextStyle( + color: currentLocale == AppLocale.en + ? AppColors.accent + : Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + fontWeight: currentLocale == AppLocale.en ? FontWeight.w600 : FontWeight.normal, + ), + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: GestureDetector( + onTap: () => ref.read(localeProvider.notifier).setLocale(AppLocale.ru), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: currentLocale == AppLocale.ru + ? AppColors.accent.withOpacity(0.2) + : Theme.of(context).scaffoldBackgroundColor, + borderRadius: BorderRadius.circular(12), + border: currentLocale == AppLocale.ru + ? Border.all(color: AppColors.accent, width: 1.5) + : (isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1)), + ), + child: Text( + s.langRu, + textAlign: TextAlign.center, + style: TextStyle( + color: currentLocale == AppLocale.ru + ? AppColors.accent + : Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + fontWeight: currentLocale == AppLocale.ru ? FontWeight.w600 : FontWeight.normal, + ), + ), + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), + const SizedBox(height: 16), + Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( @@ -279,7 +389,7 @@ class _SettingsScreenState extends ConsumerState { const SizedBox(width: 12), Expanded( child: Text( - 'Currency', + s.currency, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, @@ -372,7 +482,7 @@ class _SettingsScreenState extends ConsumerState { const SizedBox(width: 12), Expanded( child: Text( - 'Amount Format', + s.amountFormat, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, @@ -455,7 +565,7 @@ class _SettingsScreenState extends ConsumerState { const SizedBox(width: 12), Expanded( child: Text( - 'Monthly Budget', + s.monthlyBudgetSetting, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, @@ -486,7 +596,7 @@ class _SettingsScreenState extends ConsumerState { ? '${currencyInfo.symbol} ' : currencyInfo.symbol, hintText: '0.00', - helperText: 'Leave empty to remove budget limit', + helperText: s.leaveEmptyToRemove, ), autofocus: true, ), @@ -500,7 +610,7 @@ class _SettingsScreenState extends ConsumerState { _budgetController.text = budget?.toStringAsFixed(2) ?? ''; setState(() => _isEditing = false); }, - child: const Text('Cancel'), + child: Text(s.cancel), ), const SizedBox(width: 8), ElevatedButton( @@ -508,7 +618,7 @@ class _SettingsScreenState extends ConsumerState { style: ElevatedButton.styleFrom( minimumSize: const Size(80, 40), ), - child: const Text('Save'), + child: Text(s.save), ), ], ), @@ -521,7 +631,7 @@ class _SettingsScreenState extends ConsumerState { Text( budget != null ? formatAmount(currencyInfo.symbol, budget, fmt) - : 'Not set', + : s.budgetNone, style: Theme.of(context).textTheme.headlineSmall?.copyWith( color: budget != null ? AppColors.accent : Theme.of(context).colorScheme.onSurface.withOpacity(0.6), fontWeight: FontWeight.w700, @@ -530,8 +640,8 @@ class _SettingsScreenState extends ConsumerState { const SizedBox(height: 8), Text( budget != null - ? 'Your monthly spending limit' - : 'Set a monthly spending limit to track your budget', + ? s.yourMonthlySpendingLimit + : s.setMonthlySpendingLimit, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), ), @@ -545,7 +655,7 @@ class _SettingsScreenState extends ConsumerState { const SizedBox(height: 24), Text( - 'Danger Zone', + s.dangerZone, style: TextStyle( fontSize: 12, letterSpacing: 1.2, @@ -559,9 +669,9 @@ class _SettingsScreenState extends ConsumerState { child: OutlinedButton.icon( onPressed: () => _confirmClearData(context, ref), icon: const Icon(Icons.delete_forever, color: Color(0xFFE05C6B)), - label: const Text( - 'Clear All Transactions', - style: TextStyle(color: Color(0xFFE05C6B)), + label: Text( + s.clearAllTransactions, + style: const TextStyle(color: Color(0xFFE05C6B)), ), style: OutlinedButton.styleFrom( side: BorderSide(color: const Color(0xFFE05C6B).withOpacity(0.5)), @@ -621,14 +731,14 @@ class _SettingsScreenState extends ConsumerState { } } -class _BiometricSection extends StatefulWidget { +class _BiometricSection extends ConsumerStatefulWidget { const _BiometricSection(); @override - State<_BiometricSection> createState() => _BiometricSectionState(); + ConsumerState<_BiometricSection> createState() => _BiometricSectionState(); } -class _BiometricSectionState extends State<_BiometricSection> { +class _BiometricSectionState extends ConsumerState<_BiometricSection> { bool _available = false; bool _enabled = false; bool _loading = true; @@ -667,60 +777,66 @@ class _BiometricSectionState extends State<_BiometricSection> { final isDark = Theme.of(context).brightness == Brightness.dark; - return Column( - children: [ - 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: Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: AppColors.accent.withOpacity(0.15), - borderRadius: BorderRadius.circular(12), - ), - child: const Icon( - Icons.fingerprint, - color: AppColors.accent, - size: 20, - ), + return Consumer( + builder: (context, ref, _) { + final s = ref.watch(stringsProvider); + + return Column( + children: [ + 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), ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Biometric Lock', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - color: Theme.of(context).colorScheme.onSurface, - ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.accent.withOpacity(0.15), + borderRadius: BorderRadius.circular(12), ), - Text( - 'Require fingerprint on app launch', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), - ), + child: const Icon( + Icons.fingerprint, + color: AppColors.accent, + size: 20, ), - ], - ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + s.biometricLock, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Text( + s.requireFingerprint, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + ), + ), + ], + ), + ), + Switch( + value: _enabled, + onChanged: _onToggle, + activeColor: const Color(0xFF7C6DED), + ), + ], ), - Switch( - value: _enabled, - onChanged: _onToggle, - activeColor: const Color(0xFF7C6DED), - ), - ], - ), - ), - const SizedBox(height: 16), - ], + ), + const SizedBox(height: 16), + ], + ); + }, ); } } diff --git a/lib/main.dart b/lib/main.dart index 506bf48..53596ed 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/date_symbol_data_local.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'app/app.dart'; import 'core/services/haptic_service.dart'; @@ -7,6 +8,13 @@ import 'features/dashboard/provider.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + + // Initialize date formatting for both locales + await initializeDateFormatting('en_US', null); + await initializeDateFormatting('ru_RU', null); + await initializeDateFormatting('en', null); + await initializeDateFormatting('ru', null); + final prefs = await SharedPreferences.getInstance(); await HapticService.init();