feat(app): Flutter-клиент — логин, дашборд, потоки, категории, транзакции, правила

Riverpod + go_router с auth-guard, NavigationRail на широком экране и bottom bar
на узком. Токены в flutter_secure_storage, на web access живёт в памяти.
Интерцептор подставляет токен и делает ровно один refresh на 401.

Деньги приходят строками и форматируются через Decimal: парсить их в double
значило бы терять копейки ровно там, где бэкенд их бережёт.
This commit is contained in:
Dmitry
2026-09-18 13:44:09 +03:00
parent b9c12fa1a1
commit ce0966fca2
99 changed files with 6284 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
/// Currency symbols for the instruments we expect to see. Falls back to the
/// ISO code itself (e.g. 'USDT') when we don't have a nicer glyph.
const _currencySymbols = {
'RUB': '',
'USD': '\$',
'EUR': '',
};
/// Formats a decimal-string amount (as the API sends it, to avoid double
/// rounding errors) with `intl`'s ru_RU rules and a currency symbol.
class MoneyText extends StatelessWidget {
const MoneyText(this.amount, {required this.currency, super.key, this.style});
/// The amount as a string, e.g. `'1234.5'` — never a [double].
final String amount;
final String currency;
final TextStyle? style;
static String format(String amount, String currency) {
final value = Decimal.parse(amount).toDouble();
final symbol = _currencySymbols[currency] ?? currency;
final formatter = NumberFormat.currency(locale: 'ru_RU', symbol: symbol, decimalDigits: 2);
return formatter.format(value);
}
@override
Widget build(BuildContext context) {
return Text(format(amount, currency), style: style);
}
}