refactor(apps): 主题系统迁移至 ColorScheme + 扩展架构并支持 Dark Mode

This commit is contained in:
qzl
2026-03-27 19:07:39 +08:00
parent ecc1ec6ce4
commit ae29a8209b
146 changed files with 4301 additions and 3200 deletions
+6
View File
@@ -0,0 +1,6 @@
class CacheEntry<T> {
final T value;
final DateTime fetchedAt;
const CacheEntry({required this.value, required this.fetchedAt});
}
+27
View File
@@ -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
View File
@@ -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,
);
}
}
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}