import 'package:flutter/painting.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; import '../core/config/app_config.dart'; import '../core/utils/avatar_urls.dart'; import '../data/repositories/auth_repository.dart'; import '../data/repositories/copy_trading_repository.dart'; import 'auth_provider.dart'; // ── Domain Models ────────────────────────────────────────── class UserProfile { final String email; final String rawEmail; final String uid; final String avatarLetter; /// 与 Web 顶栏一致:优先 uc/member/my-info,可由跟单资料补全。 final String? avatarUrl; final bool isLoggedIn; /// 是否为经纪商(superPartner == "1") final bool isBroker; const UserProfile({ required this.email, String? rawEmail, required this.uid, required this.avatarLetter, this.avatarUrl, required this.isLoggedIn, this.isBroker = false, }) : rawEmail = rawEmail ?? email; UserProfile copyWith({ String? email, String? rawEmail, String? uid, String? avatarLetter, String? avatarUrl, bool? isLoggedIn, bool? isBroker, }) => UserProfile( email: email ?? this.email, rawEmail: rawEmail ?? this.rawEmail, uid: uid ?? this.uid, avatarLetter: avatarLetter ?? this.avatarLetter, avatarUrl: avatarUrl ?? this.avatarUrl, isLoggedIn: isLoggedIn ?? this.isLoggedIn, isBroker: isBroker ?? this.isBroker, ); } class ProfileState { final UserProfile user; final String appVersion; final bool isLoadingProfile; const ProfileState({ required this.user, this.appVersion = AppConfig.appVersion, this.isLoadingProfile = false, }); ProfileState copyWith({ UserProfile? user, String? appVersion, bool? isLoadingProfile, }) => ProfileState( user: user ?? this.user, appVersion: appVersion ?? this.appVersion, isLoadingProfile: isLoadingProfile ?? this.isLoadingProfile, ); } // ── Notifier ─────────────────────────────────────────────── class ProfileNotifier extends Notifier { static const _guest = UserProfile( email: '未登录', uid: '', avatarLetter: 'U', isLoggedIn: false, ); @override ProfileState build() { // 监听登录状态变化,自动刷新用户信息 ref.listen(isLoggedInProvider, (_, loggedIn) { if (loggedIn) { _fetchProfile(); } else { state = state.copyWith(user: _guest, isLoadingProfile: false); } }); final loggedIn = ref.read(isLoggedInProvider); if (loggedIn) Future.microtask(_fetchProfile); Future.microtask(_loadAppVersion); return ProfileState( // 已登录时标记 loading,等 _fetchProfile 完成后才变 false isLoadingProfile: loggedIn, user: loggedIn ? const UserProfile( email: '***@**.com', uid: '00000', avatarLetter: 'U', avatarUrl: null, isLoggedIn: true, ) : _guest, ); } Future _loadAppVersion() async { // 版本号与 pubspec.yaml / ProfileState 默认值保持一致 } Future _fetchProfile() async { if (!ref.read(isLoggedInProvider)) { return; } state = state.copyWith(isLoadingProfile: true); try { final repo = ref.read(authRepositoryProvider); final data = await repo.getMyInfo(); if (!ref.read(isLoggedInProvider)) { return; } final email = (data['email'] as String?) ?? (data['username'] as String?) ?? '***@**.com'; final uid = data['id']?.toString() ?? data['uid']?.toString() ?? ''; final masked = _maskIdentity(email); final brokerRaw = data['superPartner'] ?? data['isBroker'] ?? data['broker']; final isBroker = brokerRaw == true || brokerRaw == 1 || brokerRaw == '1' || brokerRaw == 'true'; var avatarUrl = resolvedAvatarUrlFromRecord(Map.from(data)); if (avatarUrl == null || avatarUrl.isEmpty) { try { final follow = await ref.read(copyTradingRepositoryProvider).getFollowerInfo(); if (!ref.read(isLoggedInProvider)) { return; } avatarUrl = resolvedAvatarUrlFromRecord(follow); } catch (_) { // 未开通跟单或接口不可用 } } if (!ref.read(isLoggedInProvider)) { return; } state = state.copyWith( isLoadingProfile: false, user: UserProfile( email: masked, rawEmail: email, uid: uid, avatarLetter: masked.isNotEmpty ? masked[0].toUpperCase() : 'U', avatarUrl: avatarUrl != null && avatarUrl.isNotEmpty ? avatarUrl : null, isLoggedIn: true, isBroker: isBroker, ), ); } catch (_) { if (!ref.read(isLoggedInProvider)) { return; } state = state.copyWith(isLoadingProfile: false); } } String _maskIdentity(String identity) { if (identity.isEmpty) return identity; final at = identity.indexOf('@'); if (at > -1) { // 邮箱:本地部分首2位 + **** + @域名 final local = identity.substring(0, at); if (local.length <= 2) return identity; return '${local.substring(0, 2)}****@${identity.substring(at + 1)}'; } else { // 非邮箱用户名:首2位 + **** + 末2位 if (identity.length <= 4) return identity; return '${identity.substring(0, 2)}****${identity.substring(identity.length - 2)}'; } } Future logout() async { await ref.read(authProvider.notifier).logout(); state = state.copyWith(user: _guest); } /// 清除本地缓存(图片缓存 + 临时文件),不清除用户偏好和登录态 Future clearCache() async { // 1. 清除 Flutter 内存图片缓存 PaintingBinding.instance.imageCache.clear(); PaintingBinding.instance.imageCache.clearLiveImages(); // 2. 清除 cached_network_image 磁盘缓存 await DefaultCacheManager().emptyCache(); // 3. 清除系统临时目录 try { final tempDir = await getTemporaryDirectory(); if (tempDir.existsSync()) { await for (final entity in tempDir.list()) { try { await entity.delete(recursive: true); } catch (_) { // 跳过正在使用的文件 } } } } catch (_) { // 临时目录不可用时忽略 } } } // ── Provider ─────────────────────────────────────────────── final profileProvider = NotifierProvider( ProfileNotifier.new, );