This commit is contained in:
2026-03-22 01:21:29 +03:00
parent 70c6568a1b
commit 1c3b506d4b
7 changed files with 3 additions and 35 deletions
+2 -10
View File
@@ -7,7 +7,6 @@ class AppStrings {
bool get _ru => locale == AppLocale.ru; bool get _ru => locale == AppLocale.ru;
// ── Dashboard ──
String get appTitle => _ru ? 'Мои финансы' : 'My Finances'; String get appTitle => _ru ? 'Мои финансы' : 'My Finances';
String get totalBalance => _ru ? 'ОБЩИЙ БАЛАНС' : 'TOTAL BALANCE'; String get totalBalance => _ru ? 'ОБЩИЙ БАЛАНС' : 'TOTAL BALANCE';
String get tapAndHoldToEdit => _ru ? 'удерживайте для редактирования' : 'tap and hold to edit'; String get tapAndHoldToEdit => _ru ? 'удерживайте для редактирования' : 'tap and hold to edit';
@@ -27,7 +26,6 @@ class AppStrings {
String get noTransactions => _ru ? 'Транзакции не найдены' : 'No transactions found'; String get noTransactions => _ru ? 'Транзакции не найдены' : 'No transactions found';
String get addFirstTx => _ru ? 'Нажмите + чтобы добавить первую транзакцию' : 'Tap + to add your first transaction'; String get addFirstTx => _ru ? 'Нажмите + чтобы добавить первую транзакцию' : 'Tap + to add your first transaction';
// ── Add/Edit transaction screen ──
String get addTransaction => _ru ? 'Новая транзакция' : 'Add Transaction'; String get addTransaction => _ru ? 'Новая транзакция' : 'Add Transaction';
String get editTransaction => _ru ? 'Редактировать' : 'Edit Transaction'; String get editTransaction => _ru ? 'Редактировать' : 'Edit Transaction';
String get amount => _ru ? 'Сумма' : 'Amount'; String get amount => _ru ? 'Сумма' : 'Amount';
@@ -46,7 +44,6 @@ class AppStrings {
String get noteOptional => _ru ? 'Заметка (необязательно)' : 'Note (optional)'; String get noteOptional => _ru ? 'Заметка (необязательно)' : 'Note (optional)';
String get addNote => _ru ? 'Добавить заметку...' : 'Add a note...'; String get addNote => _ru ? 'Добавить заметку...' : 'Add a note...';
// ── Settings ──
String get settings => _ru ? 'Настройки' : 'Settings'; 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 appearance => _ru ? 'Внешний вид' : 'Appearance';
@@ -86,12 +83,10 @@ class AppStrings {
String get allTransactionsWillBeDeleted => _ru ? 'Все транзакции будут удалены навсегда. Восстановить их будет невозможно.' : 'All transactions will be deleted forever. There is no way to recover them.'; 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 dangerZone => _ru ? 'Опасная зона' : 'Danger Zone';
// ── Navigation ──
String get navDashboard => _ru ? 'Главная' : 'Dashboard'; String get navDashboard => _ru ? 'Главная' : 'Dashboard';
String get navCategories => _ru ? 'Категории' : 'Categories'; String get navCategories => _ru ? 'Категории' : 'Categories';
String get navSettings => _ru ? 'Настройки' : 'Settings'; String get navSettings => _ru ? 'Настройки' : 'Settings';
// ── Categories ──
String get categories => _ru ? 'Категории' : 'Categories'; String get categories => _ru ? 'Категории' : 'Categories';
String get rankedByAmount => _ru ? 'По сумме' : 'Ranked by Amount'; String get rankedByAmount => _ru ? 'По сумме' : 'Ranked by Amount';
String get addCategory => _ru ? 'Добавить категорию' : 'Add Category'; String get addCategory => _ru ? 'Добавить категорию' : 'Add Category';
@@ -106,9 +101,8 @@ class AppStrings {
String get total => _ru ? 'Всего' : 'Total'; String get total => _ru ? 'Всего' : 'Total';
String get lastSixMonths => _ru ? 'Последние 6 месяцев' : 'Last 6 Months'; String get lastSixMonths => _ru ? 'Последние 6 месяцев' : 'Last 6 Months';
// ── Built-in category names (translated for display only, stored in English) ──
String categoryLabel(String key) { String categoryLabel(String key) {
if (!_ru) return key; // English: return key as-is if (!_ru) return key;
const map = { const map = {
'Food': 'Еда', 'Food': 'Еда',
'Transport': 'Транспорт', 'Transport': 'Транспорт',
@@ -131,10 +125,9 @@ class AppStrings {
'Business': 'Бизнес', 'Business': 'Бизнес',
'Savings': 'Накопления', 'Savings': 'Накопления',
}; };
return map[key] ?? key; // fallback to English key if not in map return map[key] ?? key;
} }
// ── Color editor overlay ──
String get colorPrimary => _ru ? 'Основной' : 'Primary'; String get colorPrimary => _ru ? 'Основной' : 'Primary';
String get colorSecondary => _ru ? 'Второй' : 'Secondary'; String get colorSecondary => _ru ? 'Второй' : 'Secondary';
String get colorSolid => _ru ? 'Однотон' : 'Solid'; String get colorSolid => _ru ? 'Однотон' : 'Solid';
@@ -145,6 +138,5 @@ class AppStrings {
String get reset => _ru ? 'Сброс' : 'Reset'; String get reset => _ru ? 'Сброс' : 'Reset';
String get apply => _ru ? 'Применить' : 'Apply'; String get apply => _ru ? 'Применить' : 'Apply';
// ── Date locale code for intl DateFormat ──
String get dateLocale => _ru ? 'ru_RU' : 'en_US'; String get dateLocale => _ru ? 'ru_RU' : 'en_US';
} }
-1
View File
@@ -7,7 +7,6 @@ class LocaleNotifier extends Notifier<AppLocale> {
@override @override
AppLocale build() { AppLocale build() {
// Load persisted locale synchronously via ref
final prefs = ref.read(sharedPreferencesProvider); final prefs = ref.read(sharedPreferencesProvider);
final saved = prefs.getString(_key); final saved = prefs.getString(_key);
return saved == 'ru' ? AppLocale.ru : AppLocale.en; return saved == 'ru' ? AppLocale.ru : AppLocale.en;
-4
View File
@@ -18,25 +18,21 @@ class HapticService {
await prefs.setBool(_key, value); await prefs.setBool(_key, value);
} }
// Light tap — filter chips, toggles, small interactions
static void light() { static void light() {
if (!_enabled) return; if (!_enabled) return;
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
} }
// Medium — confirm button, apply, save
static void medium() { static void medium() {
if (!_enabled) return; if (!_enabled) return;
HapticFeedback.mediumImpact(); HapticFeedback.mediumImpact();
} }
// Heavy — long press on balance card
static void heavy() { static void heavy() {
if (!_enabled) return; if (!_enabled) return;
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
} }
// Selection click — switching tabs, filter chips
static void selection() { static void selection() {
if (!_enabled) return; if (!_enabled) return;
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
+1 -14
View File
@@ -81,30 +81,22 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
} }
String? _validateAndParseAmount(String raw) { String? _validateAndParseAmount(String raw) {
// trim whitespace
final trimmed = raw.trim(); final trimmed = raw.trim();
// empty check if (trimmed.isEmpty) return null;
if (trimmed.isEmpty) return null; // returns null = invalid, show dialog
// replace comma with dot for European locale input
final normalized = trimmed.replaceAll(',', '.'); final normalized = trimmed.replaceAll(',', '.');
// only digits and one dot allowed
final validPattern = RegExp(r'^\d+\.?\d*$'); final validPattern = RegExp(r'^\d+\.?\d*$');
if (!validPattern.hasMatch(normalized)) return null; if (!validPattern.hasMatch(normalized)) return null;
// parse
final value = double.tryParse(normalized); final value = double.tryParse(normalized);
if (value == null) return null; if (value == null) return null;
// must be greater than zero
if (value <= 0) return null; if (value <= 0) return null;
// must not exceed reasonable max (prevent overflow)
if (value > 999_999_999) return null; if (value > 999_999_999) return null;
// must not have more than 2 decimal places
final parts = normalized.split('.'); final parts = normalized.split('.');
if (parts.length == 2 && parts[1].length > 2) return null; if (parts.length == 2 && parts[1].length > 2) return null;
@@ -128,7 +120,6 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
final amount = double.parse(parsed); final amount = double.parse(parsed);
final state = ref.read(addTransactionProvider(widget.initial)); final state = ref.read(addTransactionProvider(widget.initial));
// Combine date and time
final finalDateTime = DateTime( final finalDateTime = DateTime(
_selectedDate.year, _selectedDate.year,
_selectedDate.month, _selectedDate.month,
@@ -168,8 +159,6 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
} }
Future<void> _pickDate() async { Future<void> _pickDate() async {
// Note: Using showDatePicker (system bottom sheet) which cannot be resized from Flutter.
// The calendar height is controlled by the system and varies by platform.
final picked = await showDatePicker( final picked = await showDatePicker(
context: context, context: context,
initialDate: _selectedDate, initialDate: _selectedDate,
@@ -370,7 +359,6 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// DATE column
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -420,7 +408,6 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
// TIME column
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
-4
View File
@@ -115,7 +115,6 @@ final filteredTransactionsProvider = Provider<List<Transaction>>((ref) {
var filtered = txs; var filtered = txs;
// Apply time filter first
if (timeFilter == TimeFilter.lastMonth) { if (timeFilter == TimeFilter.lastMonth) {
final now = DateTime.now(); final now = DateTime.now();
final start = DateTime(now.year, now.month, 1); final start = DateTime(now.year, now.month, 1);
@@ -126,14 +125,12 @@ final filteredTransactionsProvider = Provider<List<Transaction>>((ref) {
).toList(); ).toList();
} }
// Apply type filter
if (typeFilter == TransactionFilter.income) { if (typeFilter == TransactionFilter.income) {
filtered = filtered.where((t) => t.type == TransactionType.income).toList(); filtered = filtered.where((t) => t.type == TransactionType.income).toList();
} else if (typeFilter == TransactionFilter.expense) { } else if (typeFilter == TransactionFilter.expense) {
filtered = filtered.where((t) => t.type == TransactionType.expense).toList(); filtered = filtered.where((t) => t.type == TransactionType.expense).toList();
} }
// Apply search query
if (query.isNotEmpty) { if (query.isNotEmpty) {
filtered = filtered.where((t) { filtered = filtered.where((t) {
final matchesCategory = t.category.toLowerCase().contains(query); final matchesCategory = t.category.toLowerCase().contains(query);
@@ -150,7 +147,6 @@ final recentTransactionsProvider = Provider<List<Transaction>>((ref) {
return ref.watch(filteredTransactionsProvider).take(20).toList(); return ref.watch(filteredTransactionsProvider).take(20).toList();
}); });
// Loaded card colors state
class CardColors { class CardColors {
final Color primary; final Color primary;
final Color secondary; final Color secondary;
-1
View File
@@ -127,7 +127,6 @@ class HapticNotifier extends StateNotifier<bool> {
Future<void> toggle(bool value) async { Future<void> toggle(bool value) async {
await HapticService.setEnabled(value); await HapticService.setEnabled(value);
state = value; state = value;
// Give tactile confirmation when turning ON
if (value) HapticFeedback.mediumImpact(); if (value) HapticFeedback.mediumImpact();
} }
} }
-1
View File
@@ -10,7 +10,6 @@ import 'features/dashboard/provider.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// Initialize date formatting for both locales
await initializeDateFormatting('en_US', null); await initializeDateFormatting('en_US', null);
await initializeDateFormatting('ru_RU', null); await initializeDateFormatting('ru_RU', null);
await initializeDateFormatting('en', null); await initializeDateFormatting('en', null);