feat(app): новый визуальный язык, верхняя навигация, портфели, создание счетов и ручные события
Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
This commit is contained in:
@@ -1,15 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// Аналитика: a hub for the phase-4 screens.
|
||||
import '../portfolio/holdings_tab.dart';
|
||||
import '../portfolio/overview_tiles.dart';
|
||||
import '../portfolio/scope_selector.dart';
|
||||
|
||||
/// Аналитика → Общее. On a wide surface it is Snowball's overview — the four headline figures
|
||||
/// for the chosen scope, then the value chart, returns and benchmark comparison; the other
|
||||
/// analytics screens are one tab away (`AnalyticsTabs`). On a phone there is no tab strip, so
|
||||
/// the same entry is a list of the sections instead.
|
||||
///
|
||||
/// Why a hub instead of four more destinations: the shell already carried eleven, which on
|
||||
/// a phone leaves ~36 px per label in the bottom bar. Доходы, Ребалансировка, Цели and
|
||||
/// Налоги are all "what do I do with the portfolio next" questions, so they live behind one
|
||||
/// entry that highlights for all four (see `alsoMatches` in `app_shell.dart`), while each
|
||||
/// keeps its own top-level route from the contract (`/income`, `/rebalance`, `/goals`,
|
||||
/// `/tax`) and is deep-linkable.
|
||||
class AnalyticsHubPage extends StatelessWidget {
|
||||
/// The sections keep their own top-level routes from the contract (`/income`, `/rebalance`,
|
||||
/// `/goals`, `/tax`) and are deep-linkable; `alsoMatches` in `nav_destinations.dart` keeps
|
||||
/// Аналитика highlighted inside any of them.
|
||||
class AnalyticsHubPage extends ConsumerWidget {
|
||||
const AnalyticsHubPage({super.key});
|
||||
|
||||
static const _entries = [
|
||||
@@ -23,7 +28,8 @@ class AnalyticsHubPage extends StatelessWidget {
|
||||
path: '/rebalance',
|
||||
icon: Icons.balance,
|
||||
title: 'Ребалансировка',
|
||||
subtitle: 'Целевые веса портфеля и рекомендации, что докупить или продать',
|
||||
subtitle:
|
||||
'Целевые веса портфеля и рекомендации, что докупить или продать',
|
||||
),
|
||||
(
|
||||
path: '/goals',
|
||||
@@ -39,6 +45,43 @@ class AnalyticsHubPage extends StatelessWidget {
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
if (MediaQuery.sizeOf(context).width >= 600) return const _Overview();
|
||||
return const _SectionList();
|
||||
}
|
||||
}
|
||||
|
||||
class _Overview extends StatelessWidget {
|
||||
const _Overview();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(16, 8, 16, 0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: ScopeSelector(),
|
||||
),
|
||||
),
|
||||
const Expanded(
|
||||
child: HoldingsTab(
|
||||
sections: HoldingsSections.overview,
|
||||
header: OverviewTiles(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionList extends StatelessWidget {
|
||||
const _SectionList();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -46,7 +89,7 @@ class AnalyticsHubPage extends StatelessWidget {
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
for (final e in _entries)
|
||||
for (final e in AnalyticsHubPage._entries)
|
||||
Card(
|
||||
child: ListTile(
|
||||
leading: Icon(e.icon),
|
||||
|
||||
Reference in New Issue
Block a user