| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- 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<ProfileState> {
- static const _guest = UserProfile(
- email: '未登录',
- uid: '',
- avatarLetter: 'U',
- isLoggedIn: false,
- );
- @override
- ProfileState build() {
- // 监听登录状态变化,自动刷新用户信息
- ref.listen<bool>(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<void> _loadAppVersion() async {
- // 版本号与 pubspec.yaml / ProfileState 默认值保持一致
- }
- Future<void> _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<String, dynamic>.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<void> logout() async {
- await ref.read(authProvider.notifier).logout();
- state = state.copyWith(user: _guest);
- }
- /// 清除本地缓存(图片缓存 + 临时文件),不清除用户偏好和登录态
- Future<void> 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, ProfileState>(
- ProfileNotifier.new,
- );
|