Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
68 lines
2.3 KiB
Dart
68 lines
2.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../core/cache/cached.dart';
|
|
import '../../core/widgets/stale_banner.dart';
|
|
import 'allocation_tab.dart';
|
|
import 'benchmarks_card.dart';
|
|
import 'holdings_tab.dart';
|
|
import 'providers.dart';
|
|
import 'scope_selector.dart';
|
|
|
|
/// Портфель: позиции и аллокация as two tabs of one screen, sharing one scope.
|
|
///
|
|
/// They are tabs rather than two navigation destinations because they answer two halves of
|
|
/// the same question, and because a tenth item in the bottom bar would leave 40 px per
|
|
/// label on a phone.
|
|
///
|
|
/// One offline banner covers both tabs (`docs/ai/offline-cache.md`) — `TabBarView` builds
|
|
/// both eagerly anyway, so watching every provider here to compute it costs nothing extra.
|
|
class PortfolioPage extends ConsumerWidget {
|
|
const PortfolioPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final stale = oldestFetch([
|
|
ref.watch(portfolioSummaryProvider).valueOrNull?.fetchedAt,
|
|
ref.watch(holdingsProvider).valueOrNull?.fetchedAt,
|
|
ref.watch(valueSeriesProvider).valueOrNull?.fetchedAt,
|
|
ref.watch(portfolioReturnsProvider).valueOrNull?.fetchedAt,
|
|
ref.watch(benchmarkRowsProvider).valueOrNull?.fetchedAt,
|
|
ref.watch(allocationProvider).valueOrNull?.fetchedAt,
|
|
]);
|
|
|
|
return DefaultTabController(
|
|
length: 2,
|
|
child: Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Портфель'),
|
|
actions: const [ScopeSelector(), SizedBox(width: 8)],
|
|
bottom: const TabBar(
|
|
tabs: [
|
|
Tab(text: 'Позиции'),
|
|
Tab(text: 'Аллокация'),
|
|
],
|
|
),
|
|
),
|
|
body: Column(
|
|
children: [
|
|
if (stale != null)
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
|
child: StaleBanner(fetchedAt: stale),
|
|
),
|
|
const Expanded(
|
|
child: TabBarView(
|
|
children: [
|
|
HoldingsTab(sections: HoldingsSections.table),
|
|
AllocationTab(),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|