feat(app): offline-кэш GET-запросов на дашборде — фаза 5

CacheInterceptor кэширует каждый успешный GET по путь+параметры в drift
(sqlite нативно, wasm+OPFS в браузере) и подменяет им сетевую ошибку;
провайдер отдаёт Cached<T>, экран показывает баннер «данные на …».
Контракт для остальных экранов — docs/ai/offline-cache.md.

flake.nix: libsecret/pkg-config для линуксовой сборки, jq/curl для
just app-web-assets (сборка sqlite3.wasm + drift_worker.js).
This commit is contained in:
Dmitry
2026-09-19 12:40:08 +03:00
parent 95cd6f176e
commit ffc5ed959a
21 changed files with 15852 additions and 60 deletions
+15 -1
View File
@@ -3,9 +3,22 @@ import 'package:fintracker_api/fintracker_api.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../auth/auth_controller.dart';
import '../cache/cache_interceptor.dart';
import '../cache/response_cache.dart';
import '../config.dart';
/// Authenticated client: bearer header on every call, one transparent refresh on 401.
/// One sqlite-backed cache of the last successful GET per endpoint+params,
/// shared by every screen. See `docs/ai/offline-cache.md`.
final responseCacheDbProvider = Provider<ResponseCacheDatabase>((ref) {
final db = ResponseCacheDatabase();
ref.onDispose(db.close);
return db;
});
/// Authenticated client: bearer header on every call, one transparent refresh
/// on 401, and — since [CacheInterceptor] is added last, so it sees a request
/// only after auth has already handled it — the last cached answer when the
/// network itself is unreachable.
final apiProvider = Provider<FintrackerApi>((ref) {
final dio = Dio(BaseOptions(
baseUrl: apiBaseUrl(),
@@ -14,6 +27,7 @@ final apiProvider = Provider<FintrackerApi>((ref) {
));
dio.interceptors.add(DateQueryInterceptor());
dio.interceptors.add(_AuthInterceptor(ref));
dio.interceptors.add(CacheInterceptor(ref.watch(responseCacheDbProvider)));
return FintrackerApi(dio: dio, interceptors: const []);
});