281 lines
8.5 KiB
Dart
281 lines
8.5 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import '../../data/datasources/remote/auth_remote_datasource.dart';
|
||
import '../../core/di/injection.dart';
|
||
|
||
class UserState {
|
||
final String? accountSequence;
|
||
final String? nickname;
|
||
final String? phone;
|
||
final String? kycStatus;
|
||
final String? source;
|
||
final String? status;
|
||
final String? realName;
|
||
final DateTime? createdAt;
|
||
final DateTime? lastLoginAt;
|
||
final String? accessToken;
|
||
final String? refreshToken;
|
||
final bool isLoggedIn;
|
||
final bool isLoading;
|
||
final String? error;
|
||
|
||
UserState({
|
||
this.accountSequence,
|
||
this.nickname,
|
||
this.phone,
|
||
this.kycStatus,
|
||
this.source,
|
||
this.status,
|
||
this.realName,
|
||
this.createdAt,
|
||
this.lastLoginAt,
|
||
this.accessToken,
|
||
this.refreshToken,
|
||
this.isLoggedIn = false,
|
||
this.isLoading = false,
|
||
this.error,
|
||
});
|
||
|
||
bool get isKycVerified => kycStatus == 'VERIFIED';
|
||
|
||
UserState copyWith({
|
||
String? accountSequence,
|
||
String? nickname,
|
||
String? phone,
|
||
String? kycStatus,
|
||
String? source,
|
||
String? status,
|
||
String? realName,
|
||
DateTime? createdAt,
|
||
DateTime? lastLoginAt,
|
||
String? accessToken,
|
||
String? refreshToken,
|
||
bool? isLoggedIn,
|
||
bool? isLoading,
|
||
String? error,
|
||
}) {
|
||
return UserState(
|
||
accountSequence: accountSequence ?? this.accountSequence,
|
||
nickname: nickname ?? this.nickname,
|
||
phone: phone ?? this.phone,
|
||
kycStatus: kycStatus ?? this.kycStatus,
|
||
source: source ?? this.source,
|
||
status: status ?? this.status,
|
||
realName: realName ?? this.realName,
|
||
createdAt: createdAt ?? this.createdAt,
|
||
lastLoginAt: lastLoginAt ?? this.lastLoginAt,
|
||
accessToken: accessToken ?? this.accessToken,
|
||
refreshToken: refreshToken ?? this.refreshToken,
|
||
isLoggedIn: isLoggedIn ?? this.isLoggedIn,
|
||
isLoading: isLoading ?? this.isLoading,
|
||
error: error,
|
||
);
|
||
}
|
||
}
|
||
|
||
class UserNotifier extends StateNotifier<UserState> {
|
||
final AuthRemoteDataSource _authDataSource;
|
||
|
||
UserNotifier(this._authDataSource) : super(UserState()) {
|
||
_loadFromStorage();
|
||
}
|
||
|
||
Future<void> _loadFromStorage() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
final accessToken = prefs.getString('access_token');
|
||
final refreshToken = prefs.getString('refresh_token');
|
||
final accountSequence = prefs.getString('account_sequence');
|
||
final phone = prefs.getString('phone');
|
||
|
||
if (accessToken != null && refreshToken != null && accountSequence != null) {
|
||
state = state.copyWith(
|
||
accessToken: accessToken,
|
||
refreshToken: refreshToken,
|
||
accountSequence: accountSequence,
|
||
phone: phone,
|
||
isLoggedIn: true,
|
||
);
|
||
// 登录后自动获取用户详情
|
||
await fetchProfile();
|
||
}
|
||
}
|
||
|
||
Future<void> _saveToStorage(AuthResult result) async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString('access_token', result.accessToken);
|
||
await prefs.setString('refresh_token', result.refreshToken);
|
||
await prefs.setString('account_sequence', result.user.accountSequence);
|
||
await prefs.setString('phone', result.user.phone);
|
||
}
|
||
|
||
Future<void> _clearStorage() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.remove('access_token');
|
||
await prefs.remove('refresh_token');
|
||
await prefs.remove('account_sequence');
|
||
await prefs.remove('phone');
|
||
}
|
||
|
||
Future<void> sendSmsCode(String phone, String type) async {
|
||
state = state.copyWith(isLoading: true, error: null);
|
||
try {
|
||
await _authDataSource.sendSmsCode(phone, type);
|
||
state = state.copyWith(isLoading: false);
|
||
} catch (e) {
|
||
state = state.copyWith(isLoading: false, error: e.toString());
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
Future<void> register(String phone, String password, String smsCode) async {
|
||
state = state.copyWith(isLoading: true, error: null);
|
||
try {
|
||
final result = await _authDataSource.register(phone, password, smsCode);
|
||
await _saveToStorage(result);
|
||
state = state.copyWith(
|
||
accessToken: result.accessToken,
|
||
refreshToken: result.refreshToken,
|
||
accountSequence: result.user.accountSequence,
|
||
phone: result.user.phone,
|
||
kycStatus: result.user.kycStatus,
|
||
source: result.user.source,
|
||
isLoggedIn: true,
|
||
isLoading: false,
|
||
);
|
||
} catch (e) {
|
||
state = state.copyWith(isLoading: false, error: e.toString());
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
Future<void> loginWithPassword(String phone, String password) async {
|
||
state = state.copyWith(isLoading: true, error: null);
|
||
try {
|
||
final result = await _authDataSource.loginWithPassword(phone, password);
|
||
await _saveToStorage(result);
|
||
state = state.copyWith(
|
||
accessToken: result.accessToken,
|
||
refreshToken: result.refreshToken,
|
||
accountSequence: result.user.accountSequence,
|
||
phone: result.user.phone,
|
||
kycStatus: result.user.kycStatus,
|
||
source: result.user.source,
|
||
isLoggedIn: true,
|
||
isLoading: false,
|
||
);
|
||
// 登录后获取详细信息
|
||
await fetchProfile();
|
||
} catch (e) {
|
||
state = state.copyWith(isLoading: false, error: e.toString());
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
Future<void> loginWithSms(String phone, String smsCode) async {
|
||
state = state.copyWith(isLoading: true, error: null);
|
||
try {
|
||
final result = await _authDataSource.loginWithSms(phone, smsCode);
|
||
await _saveToStorage(result);
|
||
state = state.copyWith(
|
||
accessToken: result.accessToken,
|
||
refreshToken: result.refreshToken,
|
||
accountSequence: result.user.accountSequence,
|
||
phone: result.user.phone,
|
||
kycStatus: result.user.kycStatus,
|
||
source: result.user.source,
|
||
isLoggedIn: true,
|
||
isLoading: false,
|
||
);
|
||
// 登录后获取详细信息
|
||
await fetchProfile();
|
||
} catch (e) {
|
||
state = state.copyWith(isLoading: false, error: e.toString());
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
Future<void> logout() async {
|
||
if (state.refreshToken != null) {
|
||
try {
|
||
await _authDataSource.logout(state.refreshToken!);
|
||
} catch (_) {}
|
||
}
|
||
await _clearStorage();
|
||
state = UserState();
|
||
}
|
||
|
||
Future<void> refreshTokenIfNeeded() async {
|
||
if (state.refreshToken == null) {
|
||
throw Exception('No refresh token available');
|
||
}
|
||
try {
|
||
final newAccessToken = await _authDataSource.refreshToken(state.refreshToken!);
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString('access_token', newAccessToken);
|
||
state = state.copyWith(accessToken: newAccessToken);
|
||
} catch (e) {
|
||
// 清除本地存储,不调用远程logout API(可能网络不可用)
|
||
await _clearStorage();
|
||
state = UserState();
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
Future<void> resetPassword(String phone, String smsCode, String newPassword) async {
|
||
state = state.copyWith(isLoading: true, error: null);
|
||
try {
|
||
await _authDataSource.resetPassword(phone, smsCode, newPassword);
|
||
state = state.copyWith(isLoading: false);
|
||
} catch (e) {
|
||
state = state.copyWith(isLoading: false, error: e.toString());
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
Future<void> changePassword(String oldPassword, String newPassword) async {
|
||
state = state.copyWith(isLoading: true, error: null);
|
||
try {
|
||
await _authDataSource.changePassword(oldPassword, newPassword);
|
||
state = state.copyWith(isLoading: false);
|
||
} catch (e) {
|
||
state = state.copyWith(isLoading: false, error: e.toString());
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
/// 获取用户详细信息
|
||
Future<void> fetchProfile() async {
|
||
if (!state.isLoggedIn) return;
|
||
try {
|
||
final profile = await _authDataSource.getProfile();
|
||
state = state.copyWith(
|
||
phone: profile.phone,
|
||
kycStatus: profile.kycStatus,
|
||
source: profile.source,
|
||
status: profile.status,
|
||
realName: profile.realName,
|
||
createdAt: profile.createdAt,
|
||
lastLoginAt: profile.lastLoginAt,
|
||
);
|
||
} catch (e) {
|
||
// 静默失败,不影响用户体验
|
||
}
|
||
}
|
||
}
|
||
|
||
final userNotifierProvider = StateNotifierProvider<UserNotifier, UserState>(
|
||
(ref) => UserNotifier(getIt<AuthRemoteDataSource>()),
|
||
);
|
||
|
||
final currentAccountSequenceProvider = Provider<String?>((ref) {
|
||
return ref.watch(userNotifierProvider).accountSequence;
|
||
});
|
||
|
||
final isLoggedInProvider = Provider<bool>((ref) {
|
||
return ref.watch(userNotifierProvider).isLoggedIn;
|
||
});
|
||
|
||
final accessTokenProvider = Provider<String?>((ref) {
|
||
return ref.watch(userNotifierProvider).accessToken;
|
||
});
|