此提交包含多项变更: 1. 新增鸿蒙平台支持,完善设备检测与数据库适配 2. 替换旧版分享插件API为SharePlus 3. 批量迁移StateNotifier到Notifier以适配新版Riverpod 4. 修复zip编码判断、图表API参数等bug 5. 更新应用图标、启动页资源与多尺寸适配图标 6. 调整Android最小SDK版本与应用名称 7. 优化日志打印与正则表达式使用 8. 修正编辑器画布样式初始化与配置逻辑 9. 更新依赖与CI插件配置
281 lines
7.9 KiB
Dart
281 lines
7.9 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 认证状态管理
|
||
/// 创建时间: 2026-04-28
|
||
/// 更新时间: 2026-05-15
|
||
/// 作用: 管理用户登录状态、Token、用户信息
|
||
/// 上次更新: v10.1.0 register增加密保参数; changePassword支持多验证方式
|
||
/// ============================================================
|
||
|
||
import 'dart:convert';
|
||
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
||
import '../../../core/network/api_exception.dart';
|
||
import '../../../core/services/device/device_info_service.dart';
|
||
import '../../../core/storage/app_kv_store.dart';
|
||
import '../../../core/storage/secure_storage.dart';
|
||
import '../../../core/utils/logger.dart';
|
||
import '../models/user_model.dart';
|
||
import '../services/auth_service.dart';
|
||
|
||
class AuthState {
|
||
const AuthState({
|
||
this.user,
|
||
this.isLoading = false,
|
||
this.isLoggedIn = false,
|
||
this.isInitialized = false,
|
||
this.error,
|
||
});
|
||
|
||
final UserModel? user;
|
||
final bool isLoading;
|
||
final bool isLoggedIn;
|
||
final bool isInitialized;
|
||
final String? error;
|
||
|
||
AuthState copyWith({
|
||
UserModel? user,
|
||
bool clearUser = false,
|
||
bool? isLoading,
|
||
bool? isLoggedIn,
|
||
bool? isInitialized,
|
||
String? error,
|
||
bool clearError = false,
|
||
}) {
|
||
return AuthState(
|
||
user: clearUser ? null : (user ?? this.user),
|
||
isLoading: isLoading ?? this.isLoading,
|
||
isLoggedIn: isLoggedIn ?? this.isLoggedIn,
|
||
isInitialized: isInitialized ?? this.isInitialized,
|
||
error: clearError ? null : (error ?? this.error),
|
||
);
|
||
}
|
||
}
|
||
|
||
class AuthNotifier extends Notifier<AuthState> {
|
||
@override
|
||
AuthState build() => _loadInitialState();
|
||
static const String _userCacheKey = 'cached_user_info';
|
||
|
||
AuthNotifier() {
|
||
_init();
|
||
}
|
||
|
||
static AuthState _loadInitialState() {
|
||
if (!AppKVStore.isReady) return const AuthState();
|
||
final cached = AppKVStore.getString(_userCacheKey);
|
||
if (cached != null) {
|
||
try {
|
||
final user = UserModel.fromJson(
|
||
jsonDecode(cached) as Map<String, dynamic>,
|
||
);
|
||
return AuthState(user: user, isLoggedIn: true);
|
||
} catch (_) {}
|
||
}
|
||
return const AuthState();
|
||
}
|
||
|
||
Future<void> _init() async {
|
||
final hasToken = await SecureStorage.isLoggedIn;
|
||
|
||
if (state.user != null && hasToken) {
|
||
state = state.copyWith(isInitialized: true);
|
||
_validateTokenInBackground();
|
||
} else if (state.user != null && !hasToken) {
|
||
await AuthService.clearCachedUser();
|
||
state = const AuthState(isInitialized: true);
|
||
} else if (hasToken) {
|
||
state = state.copyWith(isLoading: true);
|
||
final success = await AuthService.tryAutoLogin();
|
||
if (success) {
|
||
try {
|
||
final data = await AuthService.getUserInfo();
|
||
final user = UserModel.fromJson(data);
|
||
await _saveUserCache(user);
|
||
state = state.copyWith(
|
||
user: user,
|
||
isLoggedIn: true,
|
||
isLoading: false,
|
||
isInitialized: true,
|
||
clearError: true,
|
||
);
|
||
} catch (e) {
|
||
Log.w('自动登录后获取用户信息失败: $e');
|
||
state = state.copyWith(isLoading: false, isInitialized: true);
|
||
}
|
||
} else {
|
||
state = state.copyWith(
|
||
isLoading: false,
|
||
isInitialized: true,
|
||
clearUser: true,
|
||
);
|
||
}
|
||
} else {
|
||
state = state.copyWith(isLoading: false, isInitialized: true);
|
||
}
|
||
}
|
||
|
||
Future<void> _validateTokenInBackground() async {
|
||
try {
|
||
final valid = await AuthService.validateLocalToken();
|
||
if (!valid) {
|
||
final refreshed = await AuthService.refreshToken();
|
||
if (!refreshed) {
|
||
await AuthService.clearCachedUser();
|
||
state = state.copyWith(isLoggedIn: false, clearUser: true);
|
||
Log.i('Token已失效且刷新失败,已清除登录状态');
|
||
} else {
|
||
try {
|
||
final data = await AuthService.getUserInfo();
|
||
final user = UserModel.fromJson(data);
|
||
await _saveUserCache(user);
|
||
state = state.copyWith(user: user);
|
||
} catch (_) {}
|
||
}
|
||
} else {
|
||
try {
|
||
final data = await AuthService.getUserInfo();
|
||
final user = UserModel.fromJson(data);
|
||
await _saveUserCache(user);
|
||
state = state.copyWith(user: user);
|
||
} catch (_) {}
|
||
}
|
||
} catch (e) {
|
||
Log.w('后台Token验证失败: $e');
|
||
}
|
||
}
|
||
|
||
Future<void> _saveUserCache(UserModel user) async {
|
||
try {
|
||
await AppKVStore.setString(_userCacheKey, jsonEncode(user.toJson()));
|
||
} catch (e) {
|
||
Log.w('保存用户缓存失败: $e');
|
||
}
|
||
}
|
||
|
||
Future<bool> login({
|
||
required String account,
|
||
required String password,
|
||
}) async {
|
||
state = state.copyWith(isLoading: true, clearError: true);
|
||
try {
|
||
final user = await AuthService.login(
|
||
account: account,
|
||
password: password,
|
||
);
|
||
await _saveUserCache(user);
|
||
state = state.copyWith(user: user, isLoggedIn: true, isLoading: false);
|
||
DeviceInfoService.registerDeviceIfNeeded();
|
||
return true;
|
||
} on ApiException catch (e) {
|
||
state = state.copyWith(isLoading: false, error: e.message);
|
||
return false;
|
||
} catch (e) {
|
||
state = state.copyWith(isLoading: false, error: '登录失败: $e');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
Future<bool> register({
|
||
required String username,
|
||
required String password,
|
||
required String email,
|
||
String? mobile,
|
||
String? mobileCode,
|
||
int? secQuestion,
|
||
String? secAnswer,
|
||
}) async {
|
||
state = state.copyWith(isLoading: true, clearError: true);
|
||
try {
|
||
final user = await AuthService.register(
|
||
username: username,
|
||
password: password,
|
||
email: email,
|
||
mobile: mobile,
|
||
mobileCode: mobileCode,
|
||
secQuestion: secQuestion,
|
||
secAnswer: secAnswer,
|
||
);
|
||
await _saveUserCache(user);
|
||
state = state.copyWith(user: user, isLoggedIn: true, isLoading: false);
|
||
DeviceInfoService.registerDeviceIfNeeded();
|
||
return true;
|
||
} on ApiException catch (e) {
|
||
state = state.copyWith(isLoading: false, error: e.message);
|
||
return false;
|
||
} catch (e) {
|
||
state = state.copyWith(isLoading: false, error: '注册失败: $e');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
Future<void> logout() async {
|
||
await DeviceInfoService.resetRegistration();
|
||
await AuthService.logout();
|
||
await AuthService.clearCachedUser();
|
||
state = const AuthState(isInitialized: true);
|
||
}
|
||
|
||
Future<void> refreshUser() async {
|
||
try {
|
||
final data = await AuthService.getUserInfo();
|
||
final user = UserModel.fromJson(data);
|
||
await _saveUserCache(user);
|
||
state = state.copyWith(user: user);
|
||
} catch (e) {
|
||
Log.e('刷新用户信息失败', e);
|
||
}
|
||
}
|
||
|
||
Future<bool> updateProfile({
|
||
String? nickname,
|
||
String? bio,
|
||
String? avatarUrl,
|
||
}) async {
|
||
try {
|
||
await AuthService.updateProfile(
|
||
nickname: nickname,
|
||
bio: bio,
|
||
avatarUrl: avatarUrl,
|
||
);
|
||
await refreshUser();
|
||
return true;
|
||
} on ApiException catch (e) {
|
||
state = state.copyWith(error: e.message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
Future<bool> changePassword({
|
||
required String newPassword,
|
||
String? oldPassword,
|
||
String? secAnswer,
|
||
String? userId,
|
||
String verifyMethod = 'password',
|
||
}) async {
|
||
try {
|
||
await AuthService.changePassword(
|
||
newPassword: newPassword,
|
||
oldPassword: oldPassword,
|
||
secAnswer: secAnswer,
|
||
userId: userId,
|
||
verifyMethod: verifyMethod,
|
||
);
|
||
return true;
|
||
} on ApiException catch (e) {
|
||
state = state.copyWith(error: e.message);
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
final authProvider = NotifierProvider<AuthNotifier, AuthState>(AuthNotifier.new);
|
||
|
||
final isLoggedInProvider = Provider<bool>((ref) {
|
||
return ref.watch(authProvider).isLoggedIn;
|
||
});
|
||
|
||
final currentUserProvider = Provider<UserModel?>((ref) {
|
||
return ref.watch(authProvider).user;
|
||
});
|