mirror of
https://github.com/koloideal/Casha.git
synced 2026-06-10 10:25:28 +03:00
stableee
This commit is contained in:
@@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user