refactor(apps): 主题系统迁移至 ColorScheme + 扩展架构并支持 Dark Mode
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user