feat: make core pages local-first offline

This commit is contained in:
2026-08-21 12:40:01 +08:00
parent 8828851631
commit fefa2d74e6
20 changed files with 921 additions and 188 deletions
+46 -18
View File
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:miaoji_zhang/shared/api/api_client.dart';
@@ -10,6 +12,7 @@ import 'package:miaoji_zhang/shared/theme/app_theme.dart';
import 'package:miaoji_zhang/shared/widgets/category_icon.dart';
import 'package:miaoji_zhang/shared/widgets/app_icons.dart';
import 'package:miaoji_zhang/shared/widgets/async_error_view.dart';
import 'package:miaoji_zhang/shared/widgets/backend_status_icon.dart';
import 'package:miaoji_zhang/features/home/pages/tx_detail_page.dart';
import 'package:miaoji_zhang/features/home/pages/ledger_sheet.dart';
import 'package:miaoji_zhang/shared/services/shanghai_time.dart';
@@ -28,6 +31,7 @@ class HomePageState extends State<HomePage> {
bool _loading = true;
String? _error;
late DateTime _month;
int _loadRevision = 0;
@override
void initState() {
@@ -36,7 +40,7 @@ class HomePageState extends State<HomePage> {
PublicConfigApi.companionNotifier.addListener(_refreshCompanion);
TransactionEvents.revision.addListener(_refreshTransactions);
CurrentLedgerStore.instance.addListener(_refreshLedger);
refresh();
unawaited(refresh());
}
void _refreshCompanion() {
@@ -60,33 +64,56 @@ class HomePageState extends State<HomePage> {
}
Future<void> refresh() async {
final revision = ++_loadRevision;
setState(() {
_loading = true;
_error = null;
});
try {
await CurrentLedgerStore.instance.ensureLoaded();
final results = await Future.wait<dynamic>([
TxApi.month(_month.year, _month.month),
BudgetApi.get(_month.year, _month.month),
]);
if (mounted) {
await CurrentLedgerStore.instance.loadCached();
final localSummary = TxApi.monthLocal(_month.year, _month.month);
final localBudgets = BudgetApi.getLocal(_month.year, _month.month);
if (mounted && revision == _loadRevision) {
setState(() {
_summary = results[0] as MonthSummary;
_budgets = results[1] as BudgetsData;
_summary = localSummary;
_budgets = localBudgets;
});
}
} catch (error) {
try {
final summary = await TxApi.month(_month.year, _month.month);
if (mounted) setState(() => _summary = summary);
} catch (fallbackError) {
if (mounted) {
setState(() => _error = apiErrorMessage(fallbackError));
}
if (mounted && revision == _loadRevision) {
setState(() {
_error = apiErrorMessage(error);
_loading = false;
});
}
} finally {
if (mounted) setState(() => _loading = false);
return;
}
await Future.wait([
() async {
try {
await CurrentLedgerStore.instance.refreshRemote();
} catch (_) {}
}(),
() async {
try {
final summary = await TxApi.monthRemote(_month.year, _month.month);
if (mounted && revision == _loadRevision) {
setState(() => _summary = summary);
}
} catch (_) {}
}(),
() async {
try {
final budgets = await BudgetApi.getRemote(_month.year, _month.month);
if (mounted && revision == _loadRevision) {
setState(() => _budgets = budgets);
}
} catch (_) {}
}(),
]);
if (mounted && revision == _loadRevision) {
setState(() => _loading = false);
}
}
@@ -175,6 +202,7 @@ class HomePageState extends State<HomePage> {
),
),
Spacer(),
BackendStatusIcon(onRetry: refresh),
IconButton(
icon: AppIcons.icon(
AppIcons.search,