feat(app): экраны портфеля — позиции, аллокация и карточка инструмента

Позиции и аллокация сделаны вкладками одного экрана, а не двумя пунктами навигации:
они отвечают на две половины одного вопроса, и десятый пункт в bottom bar оставил бы
по сорок пикселей на подпись. Scope переключается один раз и сразу для всех трёх
экранов — три экрана с разными scope были бы ловушкой.

Позиция без цены показывается прочерком, никогда нулём: её стоимость не входит в
итоги выше, и 0 ₽ читался бы как «ничего не стоит» вместо «неизвестно». То же для
общей прибыли, когда в портфеле есть хоть одна неоценённая бумага.

Подписи ключей берутся по .value, а не по .name: генератор придумывает Dart-имя
(assetClass для asset_class), и словарь, ключёванный по .name, молча не совпадает
никогда.

Тесты пампят экран на высокой поверхности: вкладки это длинные списки, а ListView
строит только видимое, и на дефолтных 800x600 таблица позиций просто не существует.
This commit is contained in:
Dmitry
2026-09-18 14:21:06 +03:00
parent 28ff63bdfa
commit 1d7769ffbf
11 changed files with 1530 additions and 2 deletions
+8
View File
@@ -8,6 +8,8 @@ import 'features/cashflow/cashflow_page.dart';
import 'features/categories/categories_page.dart';
import 'features/home/home_page.dart';
import 'features/login/login_page.dart';
import 'features/portfolio/instrument_page.dart';
import 'features/portfolio/portfolio_page.dart';
import 'features/rules/rules_page.dart';
import 'features/settings/settings_page.dart';
import 'features/shell/app_shell.dart';
@@ -39,6 +41,12 @@ final routerProvider = Provider<GoRouter>((ref) {
routes: [
GoRoute(path: '/', builder: (_, _) => const HomePage()),
GoRoute(path: '/accounts', builder: (_, _) => const AccountsPage()),
GoRoute(path: '/portfolio', builder: (_, _) => const PortfolioPage()),
GoRoute(
path: '/portfolio/instrument/:id',
builder: (_, state) =>
InstrumentPage(instrumentId: int.parse(state.pathParameters['id']!)),
),
GoRoute(path: '/cashflow', builder: (_, _) => const CashflowPage()),
GoRoute(
path: '/categories',