import 'package:fintracker_api/fintracker_api.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/cache/cached.dart'; import '../../core/widgets/async_value_view.dart'; import '../../core/widgets/stale_banner.dart'; import 'allocation_tab.dart'; import 'benchmarks_card.dart'; import 'holdings_tab.dart'; import 'providers.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(), AllocationTab()]), ), ], ), ), ); } } /// Switches every portfolio screen at once. Hidden while there is nothing to choose /// between — a dropdown with one option is furniture, not a control. class _ScopeSelector extends ConsumerWidget { const _ScopeSelector(); @override Widget build(BuildContext context, WidgetRef ref) { final scopes = ref.watch(scopesProvider); final current = ref.watch(scopeProvider); return AsyncValueView>( value: scopes, data: (rows) { if (rows.length < 2) return const SizedBox.shrink(); final known = rows.any((s) => s.scope == current) ? current : rows.first.scope; return DropdownButtonHideUnderline( child: DropdownButton( value: known, borderRadius: BorderRadius.circular(8), items: [ for (final s in rows) DropdownMenuItem( value: s.scope, child: Text(s.name, overflow: TextOverflow.ellipsis), ), ], onChanged: (value) { if (value != null) ref.read(scopeProvider.notifier).state = value; }, ), ); }, ); } }