feat(app): offline-кэш на остальных экранах — фаза 5
accounts, cashflow, categories, goals, income, portfolio (+instrument), rebalance, tax, rules переведены на Cached<T> по контракту docs/ai/offline-cache.md. Общие/второстепенные селекторы (scopesProvider, categoriesListProvider и т.п.) оставлены как есть — не основной контент экрана. sync_page.dart и settings_page.dart не тронуты (первый — заменён health-page отдельно, второй — чистые действия без списка для баннера).
This commit is contained in:
@@ -2,8 +2,11 @@ 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';
|
||||
|
||||
@@ -12,11 +15,23 @@ import 'providers.dart';
|
||||
/// 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(
|
||||
@@ -27,7 +42,18 @@ class PortfolioPage extends ConsumerWidget {
|
||||
tabs: [Tab(text: 'Позиции'), Tab(text: 'Аллокация')],
|
||||
),
|
||||
),
|
||||
body: const TabBarView(children: [HoldingsTab(), AllocationTab()]),
|
||||
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()]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user