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
@@ -1,30 +0,0 @@
import 'dart:async';
import '../../../contacts/data/users/models/user_response.dart';
import 'user_profile_cache_repository.dart';
class SettingsUserCache {
final UserProfileCacheRepository _repository;
SettingsUserCache(this._repository);
UserResponse? _cachedUser;
UserResponse? get cachedUser => _cachedUser;
Future<UserResponse> getProfile({bool forceRefresh = false}) async {
final user = await _repository.getProfile(forceRefresh: forceRefresh);
_cachedUser = user;
return user;
}
void set(UserResponse user) {
_cachedUser = user;
unawaited(_repository.setCached(user));
}
void invalidate() {
_cachedUser = null;
unawaited(_repository.invalidate());
}
}
@@ -1,83 +1,67 @@
import 'dart:async';
import '../../../../data/cache/cache_policy.dart';
import '../../../../data/cache/cached_repository.dart';
import '../../../../data/models/user_profile.dart';
import '../../../../core/cache/cache_entry.dart';
import '../../../../core/cache/cache_policy.dart';
import '../../../../core/cache/hybrid_cache_store.dart';
import '../../../contacts/data/users/models/user_response.dart';
class UserProfileCacheRepository {
class UserProfileCacheRepository extends CachedRepository<UserProfile> {
static const String cacheKey = 'settings:user_profile';
final HybridCacheStore store;
final CachePolicy policy;
final DateTime Function() now;
final Future<UserResponse> Function() remoteLoader;
Future<void>? _refreshInFlight;
final Future<UserProfile> Function() remoteLoader;
UserProfile? _cachedUser;
int _generation = 0;
UserProfileCacheRepository({
required this.store,
required super.store,
required this.remoteLoader,
CachePolicy? policy,
DateTime Function()? now,
}) : policy =
policy ??
const CachePolicy(
softTtl: Duration(minutes: 2),
hardTtl: Duration(minutes: 30),
minRefreshInterval: Duration(minutes: 1),
),
now = now ?? DateTime.now;
super.now,
}) : super(
policy:
policy ??
const CachePolicy(
softTtl: Duration(minutes: 2),
hardTtl: Duration(minutes: 30),
minRefreshInterval: Duration(minutes: 1),
),
);
Future<UserResponse> getProfile({bool forceRefresh = false}) async {
if (forceRefresh) {
return _refreshAndRead();
}
UserProfile? get cachedUser => _cachedUser;
final cached = await store.read<CacheEntry<UserResponse>>(cacheKey);
if (cached == null) {
return _refreshAndRead();
}
final decision = policy.evaluate(now: now(), fetchedAt: cached.fetchedAt);
if (decision.shouldRefreshInBackground) {
_refreshInBackground();
}
if (decision.mustBlockForNetwork || !decision.canUseCached) {
return _refreshAndRead();
}
return cached.value;
}
Future<void> setCached(UserResponse user) {
return store.write<CacheEntry<UserResponse>>(
cacheKey,
CacheEntry<UserResponse>(value: user, fetchedAt: now()),
Future<UserProfile> getProfile({bool forceRefresh = false}) async {
final generation = _generation;
final user = await getOrLoad(
key: cacheKey,
forceRefresh: forceRefresh,
loadFromRemote: _loadAndRemember,
shouldWriteLoaded: (_) => generation == _generation,
);
}
Future<void> invalidate() => store.remove(cacheKey);
void _refreshInBackground() {
final running = _refreshInFlight;
if (running != null) {
return;
if (generation == _generation) {
_cachedUser = user;
}
final task = _refreshAndWrite().whenComplete(() {
_refreshInFlight = null;
});
_refreshInFlight = task;
unawaited(task);
return user;
}
Future<UserResponse> _refreshAndRead() async {
await _refreshAndWrite();
final cached = await store.read<CacheEntry<UserResponse>>(cacheKey);
return cached!.value;
Future<void> setCached(UserProfile user) async {
final generation = _generation;
_cachedUser = user;
await writeCacheEntry(cacheKey, user);
if (generation != _generation) {
_cachedUser = null;
await removeCacheKey(cacheKey);
}
}
Future<void> _refreshAndWrite() async {
Future<void> invalidate() async {
_generation += 1;
_cachedUser = null;
await removeCacheKey(cacheKey);
}
Future<UserProfile> _loadAndRemember() async {
final generation = _generation;
final remote = await remoteLoader();
await setCached(remote);
if (generation == _generation) {
_cachedUser = remote;
}
return remote;
}
}
@@ -0,0 +1,53 @@
import 'dart:io';
import 'package:dio/dio.dart';
import '../../../../core/network/i_api_client.dart';
import '../../../../data/models/user_profile.dart';
class UserProfileService {
static const _prefix = '/api/v1/users';
final IApiClient _client;
UserProfileService(this._client);
Future<UserProfile> getMe() async {
final response = await _client.get<Map<String, dynamic>>('$_prefix/me');
final data = response.data;
if (data == null) {
throw StateError('Invalid getMe response: empty payload');
}
return UserProfile.fromJson(data);
}
Future<UserProfile> updateMe(UserUpdateRequest request) async {
final response = await _client.patch<Map<String, dynamic>>(
'$_prefix/me',
data: request.toJson(),
);
final data = response.data;
if (data == null) {
throw StateError('Invalid updateMe response: empty payload');
}
return UserProfile.fromJson(data);
}
Future<String> uploadAvatar(File file) async {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(
file.path,
filename: file.path.split('/').last,
),
});
final response = await _client.post<Map<String, dynamic>>(
'$_prefix/me/avatar',
data: formData,
);
final data = response.data;
if (data == null || data['url'] is! String) {
throw StateError('Invalid uploadAvatar response: missing url');
}
return data['url'] as String;
}
}