// ============================================================ // AuthProvider — 认证状态 Riverpod Provider // // 管理 App 全局登录状态,替代原有的 ValueNotifier 模式。 // 所有页面通过 ref.watch(authStateProvider) 感知认证状态变化。 // ============================================================ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../domain/entities/auth_user.dart'; import '../../domain/entities/auth_session.dart'; import '../../domain/repositories/auth_repository.dart'; import '../../data/repositories/auth_repository_impl.dart'; // ── Repository Provider ─────────────────────────────────────── final authRepositoryProvider = Provider((ref) { return AuthRepositoryImpl(); }); // ── Auth State ──────────────────────────────────────────────── class AuthState { final AuthUser? user; final String? accessToken; final String? refreshToken; final bool isLoading; final String? error; const AuthState({ this.user, this.accessToken, this.refreshToken, this.isLoading = false, this.error, }); bool get isAuthenticated => user != null && accessToken != null; const AuthState.initial() : this(isLoading: false); AuthState copyWith({ AuthUser? user, String? accessToken, String? refreshToken, bool? isLoading, String? error, bool clearUser = false, bool clearError = false, }) { return AuthState( user: clearUser ? null : (user ?? this.user), accessToken: clearUser ? null : (accessToken ?? this.accessToken), refreshToken: clearUser ? null : (refreshToken ?? this.refreshToken), isLoading: isLoading ?? this.isLoading, error: clearError ? null : (error ?? this.error), ); } } // ── Auth Notifier ───────────────────────────────────────────── class AuthNotifier extends Notifier { @override AuthState build() => const AuthState.initial(); IAuthRepository get _repo => ref.read(authRepositoryProvider); /// 从 AuthSession 更新状态 void _setSession(AuthSession session) { state = AuthState( user: session.user, accessToken: session.accessToken, refreshToken: session.refreshToken, ); } /// Session 过期时由 ApiClient 回调触发(配合 main.dart 中的监听) void onSessionExpired() { state = const AuthState.initial(); } // ── 会话恢复 ─────────────────────────────────────────────── Future restoreSession() async { state = state.copyWith(isLoading: true, clearError: true); try { final session = await _repo.restoreSession(); if (session != null) { _setSession(session); return true; } state = state.copyWith(isLoading: false, clearUser: true); return false; } catch (e) { state = state.copyWith(isLoading: false, clearUser: true); return false; } } // ── 密码登录 ────────────────────────────────────────────── Future loginByPassword({ required String identifier, required String password, String? deviceInfo, }) async { state = state.copyWith(isLoading: true, clearError: true); try { final session = await _repo.loginByPassword( identifier: identifier, password: password, deviceInfo: deviceInfo, ); _setSession(session); } catch (e) { state = state.copyWith(isLoading: false, error: _extractMessage(e)); rethrow; } } // ── SMS 验证码登录 ──────────────────────────────────────── Future loginByPhone({ required String phone, required String smsCode, String? deviceInfo, }) async { state = state.copyWith(isLoading: true, clearError: true); try { final session = await _repo.loginByPhone( phone: phone, smsCode: smsCode, deviceInfo: deviceInfo, ); _setSession(session); } catch (e) { state = state.copyWith(isLoading: false, error: _extractMessage(e)); rethrow; } } // ── 邮件验证码登录 ──────────────────────────────────────── Future loginByEmail({required String email, required String emailCode}) async { state = state.copyWith(isLoading: true, clearError: true); try { final session = await _repo.loginByEmail(email: email, emailCode: emailCode); _setSession(session); } catch (e) { state = state.copyWith(isLoading: false, error: _extractMessage(e)); rethrow; } } // ── 第三方登录 ──────────────────────────────────────────── Future loginByWechat({required String code, String? referralCode}) async { state = state.copyWith(isLoading: true, clearError: true); try { final session = await _repo.loginByWechat(code: code, referralCode: referralCode); _setSession(session); } catch (e) { state = state.copyWith(isLoading: false, error: _extractMessage(e)); rethrow; } } Future loginByAlipay({required String authCode, String? referralCode}) async { state = state.copyWith(isLoading: true, clearError: true); try { final session = await _repo.loginByAlipay(authCode: authCode, referralCode: referralCode); _setSession(session); } catch (e) { state = state.copyWith(isLoading: false, error: _extractMessage(e)); rethrow; } } Future loginByGoogle({required String idToken, String? referralCode}) async { state = state.copyWith(isLoading: true, clearError: true); try { final session = await _repo.loginByGoogle(idToken: idToken, referralCode: referralCode); _setSession(session); } catch (e) { state = state.copyWith(isLoading: false, error: _extractMessage(e)); rethrow; } } Future loginByApple({ required String identityToken, String? displayName, String? referralCode, }) async { state = state.copyWith(isLoading: true, clearError: true); try { final session = await _repo.loginByApple( identityToken: identityToken, displayName: displayName, referralCode: referralCode, ); _setSession(session); } catch (e) { state = state.copyWith(isLoading: false, error: _extractMessage(e)); rethrow; } } // ── 注册 ────────────────────────────────────────────────── Future register({ required String phone, required String smsCode, required String password, String? nickname, String? referralCode, }) async { state = state.copyWith(isLoading: true, clearError: true); try { final session = await _repo.register( phone: phone, smsCode: smsCode, password: password, nickname: nickname, referralCode: referralCode, ); _setSession(session); } catch (e) { state = state.copyWith(isLoading: false, error: _extractMessage(e)); rethrow; } } Future registerByEmail({ required String email, required String emailCode, required String password, String? nickname, String? referralCode, }) async { state = state.copyWith(isLoading: true, clearError: true); try { final session = await _repo.registerByEmail( email: email, emailCode: emailCode, password: password, nickname: nickname, referralCode: referralCode, ); _setSession(session); } catch (e) { state = state.copyWith(isLoading: false, error: _extractMessage(e)); rethrow; } } // ── 登出 ────────────────────────────────────────────────── Future logout() async { await _repo.logout(); state = const AuthState.initial(); } // ── 验证码 ──────────────────────────────────────────────── Future sendSmsCode(String phone, SmsCodeType type) => _repo.sendSmsCode(phone, type); Future sendEmailCode(String email, EmailCodeType type) => _repo.sendEmailCode(email, type); // ── 推荐码验证 ──────────────────────────────────────────── Future validateReferralCode(String code) => _repo.validateReferralCode(code); // ── 密码管理 ────────────────────────────────────────────── Future resetPassword({ required String phone, required String smsCode, required String newPassword, }) => _repo.resetPassword(phone: phone, smsCode: smsCode, newPassword: newPassword); Future resetPasswordByEmail({ required String email, required String emailCode, required String newPassword, }) => _repo.resetPasswordByEmail( email: email, emailCode: emailCode, newPassword: newPassword, ); Future changePassword({ required String oldPassword, required String newPassword, }) => _repo.changePassword(oldPassword: oldPassword, newPassword: newPassword); Future getAlipayAuthString() => _repo.getAlipayAuthString(); // ── 私有工具 ────────────────────────────────────────────── String _extractMessage(Object e) { final str = e.toString(); // DioException 格式: "DioException [...]: message" final match = RegExp(r'message["\s:]+([^"}\n]+)').firstMatch(str); return match?.group(1)?.trim() ?? str; } } // ── Provider 导出 ───────────────────────────────────────────── final authProvider = NotifierProvider(AuthNotifier.new); /// 便捷选择器:当前登录用户(null = 未登录) final currentUserProvider = Provider((ref) { return ref.watch(authProvider).user; }); /// 便捷选择器:是否已登录 final isAuthenticatedProvider = Provider((ref) { return ref.watch(authProvider).isAuthenticated; });