refactor(apps): 主题系统迁移至 ColorScheme + 扩展架构并支持 Dark Mode
This commit is contained in:
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
class CacheEntry<T> {
|
||||
final T value;
|
||||
final DateTime fetchedAt;
|
||||
|
||||
const CacheEntry({required this.value, required this.fetchedAt});
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'hybrid_cache_store.dart';
|
||||
|
||||
class CacheInvalidator {
|
||||
final HybridCacheStore? _store;
|
||||
final Set<String> _invalidated = <String>{};
|
||||
|
||||
CacheInvalidator({HybridCacheStore? store}) : _store = store;
|
||||
|
||||
void invalidate(String key) {
|
||||
_invalidated.add(key);
|
||||
final store = _store;
|
||||
if (store != null) {
|
||||
unawaited(store.remove(key));
|
||||
}
|
||||
}
|
||||
|
||||
void invalidateCalendarDay(DateTime date) {
|
||||
final month = '${date.year}-${date.month.toString().padLeft(2, '0')}';
|
||||
final day = '$month-${date.day.toString().padLeft(2, '0')}';
|
||||
invalidate('calendar:day:$day');
|
||||
invalidate('calendar:month:$month');
|
||||
}
|
||||
|
||||
bool wasInvalidated(String key) => _invalidated.contains(key);
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
class CacheDecision {
|
||||
final bool canUseCached;
|
||||
final bool shouldRefreshInBackground;
|
||||
final bool mustBlockForNetwork;
|
||||
|
||||
const CacheDecision({
|
||||
required this.canUseCached,
|
||||
required this.shouldRefreshInBackground,
|
||||
required this.mustBlockForNetwork,
|
||||
});
|
||||
}
|
||||
|
||||
class CachePolicy {
|
||||
final Duration softTtl;
|
||||
final Duration hardTtl;
|
||||
final Duration minRefreshInterval;
|
||||
|
||||
const CachePolicy({
|
||||
required this.softTtl,
|
||||
required this.hardTtl,
|
||||
this.minRefreshInterval = Duration.zero,
|
||||
});
|
||||
|
||||
CacheDecision evaluate({required DateTime now, required DateTime fetchedAt}) {
|
||||
final age = now.difference(fetchedAt);
|
||||
if (age >= hardTtl) {
|
||||
return const CacheDecision(
|
||||
canUseCached: false,
|
||||
shouldRefreshInBackground: false,
|
||||
mustBlockForNetwork: true,
|
||||
);
|
||||
}
|
||||
|
||||
if (age >= softTtl) {
|
||||
final shouldRefresh = age >= minRefreshInterval;
|
||||
return CacheDecision(
|
||||
canUseCached: true,
|
||||
shouldRefreshInBackground: shouldRefresh,
|
||||
mustBlockForNetwork: false,
|
||||
);
|
||||
}
|
||||
|
||||
return const CacheDecision(
|
||||
canUseCached: true,
|
||||
shouldRefreshInBackground: false,
|
||||
mustBlockForNetwork: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
abstract class CacheStore {
|
||||
Future<T?> read<T>(String key);
|
||||
Future<void> write<T>(String key, T value);
|
||||
Future<void> remove(String key);
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'cache_entry.dart';
|
||||
import 'cache_policy.dart';
|
||||
import 'hybrid_cache_store.dart';
|
||||
|
||||
abstract class CachedRepository<T> {
|
||||
final HybridCacheStore store;
|
||||
final CachePolicy policy;
|
||||
final DateTime Function() now;
|
||||
final Map<String, Future<void>> _refreshInFlight = <String, Future<void>>{};
|
||||
|
||||
CachedRepository({
|
||||
required this.store,
|
||||
required this.policy,
|
||||
DateTime Function()? now,
|
||||
}) : now = now ?? DateTime.now;
|
||||
|
||||
Future<T> getOrLoad({
|
||||
required String key,
|
||||
required Future<T> Function() loadFromRemote,
|
||||
bool Function(T loaded)? shouldWriteLoaded,
|
||||
bool forceRefresh = false,
|
||||
}) async {
|
||||
if (forceRefresh) {
|
||||
return _refreshAndWrite(
|
||||
key,
|
||||
loadFromRemote,
|
||||
shouldWriteLoaded: shouldWriteLoaded,
|
||||
);
|
||||
}
|
||||
|
||||
final cached = await readCacheEntry(key);
|
||||
if (cached == null) {
|
||||
return _refreshAndWrite(
|
||||
key,
|
||||
loadFromRemote,
|
||||
shouldWriteLoaded: shouldWriteLoaded,
|
||||
);
|
||||
}
|
||||
|
||||
final decision = policy.evaluate(now: now(), fetchedAt: cached.fetchedAt);
|
||||
if (decision.shouldRefreshInBackground) {
|
||||
refreshInBackground(
|
||||
key: key,
|
||||
loadFromRemote: loadFromRemote,
|
||||
shouldWriteLoaded: shouldWriteLoaded,
|
||||
);
|
||||
}
|
||||
if (decision.mustBlockForNetwork || !decision.canUseCached) {
|
||||
return _refreshAndWrite(
|
||||
key,
|
||||
loadFromRemote,
|
||||
shouldWriteLoaded: shouldWriteLoaded,
|
||||
);
|
||||
}
|
||||
return cached.value;
|
||||
}
|
||||
|
||||
Future<CacheEntry<T>?> readCacheEntry(String key) {
|
||||
return store.read<CacheEntry<T>>(key);
|
||||
}
|
||||
|
||||
Future<void> writeCacheEntry(String key, T value) {
|
||||
return store.write<CacheEntry<T>>(
|
||||
key,
|
||||
CacheEntry<T>(value: value, fetchedAt: now()),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeCacheKey(String key) {
|
||||
return store.remove(key);
|
||||
}
|
||||
|
||||
void refreshInBackground({
|
||||
required String key,
|
||||
required Future<T> Function() loadFromRemote,
|
||||
bool Function(T loaded)? shouldWriteLoaded,
|
||||
}) {
|
||||
if (_refreshInFlight.containsKey(key)) {
|
||||
return;
|
||||
}
|
||||
final task = _refreshAndWrite(
|
||||
key,
|
||||
loadFromRemote,
|
||||
shouldWriteLoaded: shouldWriteLoaded,
|
||||
).then((_) {});
|
||||
final tracked = task.whenComplete(() {
|
||||
_refreshInFlight.remove(key);
|
||||
});
|
||||
_refreshInFlight[key] = tracked;
|
||||
unawaited(tracked);
|
||||
}
|
||||
|
||||
Future<T> _refreshAndWrite(
|
||||
String key,
|
||||
Future<T> Function() loadFromRemote, {
|
||||
bool Function(T loaded)? shouldWriteLoaded,
|
||||
}) async {
|
||||
final remote = await loadFromRemote();
|
||||
if (shouldWriteLoaded != null && !shouldWriteLoaded(remote)) {
|
||||
return remote;
|
||||
}
|
||||
await writeCacheEntry(key, remote);
|
||||
return remote;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import 'memory_cache_store.dart';
|
||||
import 'persistent_cache_store.dart';
|
||||
|
||||
class HybridCacheStore {
|
||||
final MemoryCacheStore memory;
|
||||
final PersistentCacheStore persistent;
|
||||
final Map<String, Future<dynamic>> _inflight = <String, Future<dynamic>>{};
|
||||
|
||||
HybridCacheStore({required this.memory, required this.persistent});
|
||||
|
||||
Future<T?> read<T>(String key) async {
|
||||
final memoryValue = await memory.read<T>(key);
|
||||
if (memoryValue != null) {
|
||||
return memoryValue;
|
||||
}
|
||||
final persistentValue = await persistent.read<T>(key);
|
||||
if (persistentValue != null) {
|
||||
await memory.write(key, persistentValue);
|
||||
}
|
||||
return persistentValue;
|
||||
}
|
||||
|
||||
Future<void> write<T>(String key, T value) async {
|
||||
await memory.write<T>(key, value);
|
||||
await persistent.write<T>(key, value);
|
||||
}
|
||||
|
||||
Future<void> remove(String key) async {
|
||||
await memory.remove(key);
|
||||
await persistent.remove(key);
|
||||
}
|
||||
|
||||
Future<T> getOrLoad<T>(String key, {required Future<T> Function() loader}) {
|
||||
final running = _inflight[key];
|
||||
if (running != null) {
|
||||
return running.then((value) => value as T);
|
||||
}
|
||||
|
||||
final future = () async {
|
||||
final cached = await read<T>(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final loaded = await loader();
|
||||
await write<T>(key, loaded);
|
||||
return loaded;
|
||||
}();
|
||||
|
||||
_inflight[key] = future;
|
||||
return future.whenComplete(() {
|
||||
_inflight.remove(key);
|
||||
});
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import 'cache_store.dart';
|
||||
|
||||
class MemoryCacheStore implements CacheStore {
|
||||
final Map<String, Object?> _values = <String, Object?>{};
|
||||
|
||||
@override
|
||||
Future<T?> read<T>(String key) async {
|
||||
final value = _values[key];
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> write<T>(String key, T value) async {
|
||||
_values[key] = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String key) async {
|
||||
_values.remove(key);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import 'cache_store.dart';
|
||||
|
||||
class PersistentCacheStore implements CacheStore {
|
||||
final Map<String, Object?> _values = <String, Object?>{};
|
||||
|
||||
@override
|
||||
Future<T?> read<T>(String key) async {
|
||||
final value = _values[key];
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> write<T>(String key, T value) async {
|
||||
_values[key] = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String key) async {
|
||||
_values.remove(key);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user