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
+21
View File
@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
/// Fixed categorical order from the design system's palette (dataviz skill):
/// slot 1 blue, slot 2 orange, slot 3 aqua, slot 4 yellow, slot 5 magenta.
/// Assigned to entities in this fixed order — never cycled, never by rank.
class ChartColors {
const ChartColors._();
static const slot1Blue = Color(0xFF2A78D6);
static const slot2Orange = Color(0xFFEB6834);
static const slot3Aqua = Color(0xFF1BAF7A);
static const slot4Yellow = Color(0xFFEDA100);
static const slot5Magenta = Color(0xFFE87BA4);
/// This app's fixed assignment for cashflow charts.
static const income = slot1Blue;
static const expense = slot2Orange;
static const baseline = slot3Aqua;
static const oneOff = slot4Yellow;
static const savingsTransfer = slot5Magenta;
}
+28
View File
@@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
const _prefsKey = 'theme_mode';
final themeModeProvider = NotifierProvider<ThemeModeController, ThemeMode>(ThemeModeController.new);
/// Persists the chosen [ThemeMode] to this device via `shared_preferences`.
class ThemeModeController extends Notifier<ThemeMode> {
@override
ThemeMode build() {
Future.microtask(_restore);
return ThemeMode.system;
}
Future<void> _restore() async {
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getString(_prefsKey);
state = ThemeMode.values.asNameMap()[saved] ?? ThemeMode.system;
}
Future<void> setMode(ThemeMode mode) async {
state = mode;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_prefsKey, mode.name);
}
}