gcx/frontend/genex-mobile/lib/features/auth/presentation/providers/auth_provider.dart

328 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// AuthProvider — 认证状态 Riverpod Provider
//
// 管理 App 全局登录状态,替代原有的 ValueNotifier<AuthResult?> 模式。
// 所有页面通过 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<IAuthRepository>((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<AuthState> {
@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<bool> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> logout() async {
await _repo.logout();
state = const AuthState.initial();
}
// ── 验证码 ────────────────────────────────────────────────
Future<int> sendSmsCode(String phone, SmsCodeType type) =>
_repo.sendSmsCode(phone, type);
Future<int> sendEmailCode(String email, EmailCodeType type) =>
_repo.sendEmailCode(email, type);
// ── 推荐码验证 ────────────────────────────────────────────
Future<bool> validateReferralCode(String code) =>
_repo.validateReferralCode(code);
// ── 密码管理 ──────────────────────────────────────────────
Future<void> resetPassword({
required String phone,
required String smsCode,
required String newPassword,
}) =>
_repo.resetPassword(phone: phone, smsCode: smsCode, newPassword: newPassword);
Future<void> resetPasswordByEmail({
required String email,
required String emailCode,
required String newPassword,
}) =>
_repo.resetPasswordByEmail(
email: email,
emailCode: emailCode,
newPassword: newPassword,
);
Future<void> changePassword({
required String oldPassword,
required String newPassword,
}) =>
_repo.changePassword(oldPassword: oldPassword, newPassword: newPassword);
Future<String> 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, AuthState>(AuthNotifier.new);
/// 便捷选择器当前登录用户null = 未登录)
final currentUserProvider = Provider<AuthUser?>((ref) {
return ref.watch(authProvider).user;
});
/// 便捷选择器:是否已登录
final isAuthenticatedProvider = Provider<bool>((ref) {
return ref.watch(authProvider).isAuthenticated;
});