Адрес бэкенда хранится в shared_preferences и читается до runApp; switchApiBaseUrl сбрасывает токены и кэш старого сервера. Выход стирает refresh-токен и sqlite-кэш ответов. Экран настроек переделан под разделы.
76 lines
2.7 KiB
Dart
76 lines
2.7 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:drift/drift.dart';
|
|
import 'package:drift_flutter/drift_flutter.dart';
|
|
|
|
part 'response_cache.g.dart';
|
|
|
|
/// The last successful body of one GET, keyed by endpoint+params exactly as
|
|
/// [CacheInterceptor] builds the key. See `docs/ai/offline-cache.md` for the
|
|
/// whole contract this table is part of.
|
|
class CachedResponses extends Table {
|
|
TextColumn get requestKey => text()();
|
|
TextColumn get body => text()();
|
|
DateTimeColumn get fetchedAt => dateTime()();
|
|
|
|
@override
|
|
Set<Column> get primaryKey => {requestKey};
|
|
}
|
|
|
|
/// A cached body plus when it was fetched — what [CacheInterceptor] needs to
|
|
/// rebuild a synthetic [Response] when the network itself is unreachable.
|
|
///
|
|
/// Not named `CachedResponse`: drift already generates a row class with that
|
|
/// name for the `CachedResponses` table above.
|
|
class CacheEntry {
|
|
const CacheEntry(this.body, this.fetchedAt);
|
|
final Object? body;
|
|
final DateTime fetchedAt;
|
|
}
|
|
|
|
@DriftDatabase(tables: [CachedResponses])
|
|
class ResponseCacheDatabase extends _$ResponseCacheDatabase {
|
|
ResponseCacheDatabase() : super(_openConnection());
|
|
ResponseCacheDatabase.forTesting(super.executor);
|
|
|
|
@override
|
|
int get schemaVersion => 1;
|
|
|
|
/// Replaces whatever was cached for [requestKey] — one row per endpoint+params,
|
|
/// never a history.
|
|
Future<void> put(String requestKey, Object? body) =>
|
|
into(cachedResponses).insertOnConflictUpdate(
|
|
CachedResponsesCompanion.insert(
|
|
requestKey: requestKey,
|
|
body: jsonEncode(body),
|
|
fetchedAt: DateTime.now(),
|
|
),
|
|
);
|
|
|
|
/// Drops every cached body — on sign-out, so the next account (or nobody) never sees them.
|
|
Future<void> clear() => delete(cachedResponses).go();
|
|
|
|
/// The body last stored for [requestKey], or null if this endpoint+params was
|
|
/// never fetched successfully on this device.
|
|
Future<CacheEntry?> get(String requestKey) async {
|
|
final row = await (select(
|
|
cachedResponses,
|
|
)..where((t) => t.requestKey.equals(requestKey))).getSingleOrNull();
|
|
if (row == null) return null;
|
|
return CacheEntry(jsonDecode(row.body), row.fetchedAt);
|
|
}
|
|
}
|
|
|
|
/// One sqlite file via native FFI on Android/iOS/desktop; wasm+OPFS (falling
|
|
/// back to IndexedDB) on web — `driftDatabase` from `package:drift_flutter`
|
|
/// picks the right backend, `web/sqlite3.wasm` and `web/drift_worker.js` are
|
|
/// the compiled assets it loads there (regenerate with `just app-web-assets`
|
|
/// whenever the `drift`/`sqlite3` package versions change).
|
|
QueryExecutor _openConnection() => driftDatabase(
|
|
name: 'response_cache',
|
|
web: DriftWebOptions(
|
|
sqlite3Wasm: Uri.parse('sqlite3.wasm'),
|
|
driftWorker: Uri.parse('drift_worker.js'),
|
|
),
|
|
);
|