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:
@@ -3,11 +3,13 @@ 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/theme/chart_colors.dart';
|
||||
import '../../core/utils/ru_date.dart';
|
||||
import '../../core/widgets/async_value_view.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../core/widgets/money_text.dart';
|
||||
import '../../core/widgets/stale_banner.dart';
|
||||
import 'providers.dart';
|
||||
|
||||
class _Group {
|
||||
@@ -79,6 +81,7 @@ class _CategoriesPageState extends ConsumerState<CategoriesPage> {
|
||||
Widget build(BuildContext context) {
|
||||
final month = ref.watch(selectedSpendingMonthProvider);
|
||||
final spending = ref.watch(spendingProvider(month));
|
||||
final stale = oldestFetch([spending.valueOrNull?.fetchedAt]);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Категории')),
|
||||
@@ -108,10 +111,12 @@ class _CategoriesPageState extends ConsumerState<CategoriesPage> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (stale != null) StaleBanner(fetchedAt: stale),
|
||||
AsyncValueView(
|
||||
value: spending,
|
||||
onRetry: () => ref.invalidate(spendingProvider(month)),
|
||||
data: (rows) {
|
||||
data: (cached) {
|
||||
final rows = cached.data;
|
||||
if (rows.isEmpty) {
|
||||
return const EmptyState(
|
||||
icon: Icons.donut_small_outlined,
|
||||
|
||||
@@ -2,9 +2,11 @@ import 'package:fintracker_api/fintracker_api.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../core/api/api_client.dart';
|
||||
import '../../core/cache/cached.dart';
|
||||
import '../../core/utils/ru_date.dart';
|
||||
|
||||
/// Flat ZenMoney tag tree — the client nests it by `parent_id` where needed.
|
||||
/// Flat ZenMoney tag tree — the client nests it by `parent_id` where needed. Shared with
|
||||
/// Транзакции; not wrapped in `Cached` since it is a lookup, not a screen's own primary read.
|
||||
final categoriesListProvider = FutureProvider.autoDispose<List<CategoryOut>>((ref) async {
|
||||
final r = await ref.watch(apiProvider).getCategoriesApi().categoriesList();
|
||||
return r.data ?? const [];
|
||||
@@ -16,11 +18,12 @@ final categoryNamesProvider = Provider.autoDispose<Map<int, String>>((ref) {
|
||||
return {for (final c in categories) c.id: c.name};
|
||||
});
|
||||
|
||||
/// Spending by category for one month (`YYYY-MM`); null means "the latest month".
|
||||
/// Spending by category for one month (`YYYY-MM`); null means "the latest month". See
|
||||
/// `docs/ai/offline-cache.md`.
|
||||
final spendingProvider =
|
||||
FutureProvider.autoDispose.family<List<SpendingRow>, String?>((ref, month) async {
|
||||
FutureProvider.autoDispose.family<Cached<List<SpendingRow>>, String?>((ref, month) async {
|
||||
final r = await ref.watch(apiProvider).getCashflowApi().cashflowSpending(month: month);
|
||||
return r.data ?? const [];
|
||||
return Cached(r.data ?? const [], fetchedAt: r.extra['fetchedAt'] as DateTime?);
|
||||
});
|
||||
|
||||
/// The month currently selected on the Категории screen, `YYYY-MM`.
|
||||
|
||||
Reference in New Issue
Block a user