import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../core/network/api_exception.dart'; import '../data/auth_providers.dart'; import '../domain/auth_repository.dart'; import '../domain/auth_state.dart'; part 'auth_controller.g.dart'; /// 登录状态控制器。 /// /// 会话级持有(`keepAlive`),负责令牌恢复、登录 / 注册 / 退出, /// 以及启动期的登录态校验(verify + refresh 重试)。 @Riverpod(keepAlive: true) class AuthController extends _$AuthController { AuthRepository get _repository => ref.read(authRepositoryProvider); @override FutureOr build() async { final repository = ref.watch(authRepositoryProvider); await repository.restore(); final loggedIn = repository.accessToken != null && await repository.hasRefreshToken(); return AuthState( loggedIn: loggedIn, username: await repository.getUsername(), ); } AuthState get _current => state.valueOrNull ?? const AuthState(); /// 校验本地令牌是否仍然有效(对应启动页流程)。 /// /// 无令牌直接返回 false;`verify` 失败时尝试 `refresh` 后重试一次, /// 仍失败则清空令牌。 Future checkLoginState() async { final repository = _repository; await repository.restore(); if (repository.accessToken == null) { if (!await repository.hasRefreshToken()) { await _reset(); return false; } } if (await _verifyQuietly()) { state = AsyncData( _current.copyWith( loggedIn: true, username: await repository.getUsername(), ), ); return true; } // access token 失效:用 refreshToken 续期后再校验一次。 try { await repository.refresh(); } on Object { await _reset(); return false; } if (await _verifyQuietly()) { state = AsyncData( _current.copyWith( loggedIn: true, username: await repository.getUsername(), ), ); return true; } await _reset(); return false; } Future _verifyQuietly() async { try { await _repository.verify(); return true; } on Object { return false; } } Future _reset() async { final username = await _repository.getUsername(); await _repository.clear(); state = AsyncData(AuthState(username: username)); } /// 登录:成功返回 true,失败返回 false 并写入 [AuthState.error]。 Future login(String username, String password) async { state = AsyncData(_current.copyWith(submitting: true, error: null)); try { await _repository.login(username, password); state = AsyncData(AuthState(loggedIn: true, username: username)); return true; } catch (e) { state = AsyncData( _current.copyWith( submitting: false, loggedIn: false, error: _describe(e, '登录失败'), ), ); return false; } } /// 注册:成功后不自动登录,仅通过 [AuthState.alert] 提示前往登录。 Future register(String username, String password) async { state = AsyncData(_current.copyWith(submitting: true, error: null)); try { await _repository.register(username, password); state = AsyncData( _current.copyWith( submitting: false, error: null, alert: '注册成功,请登录', ), ); return true; } catch (e) { state = AsyncData( _current.copyWith( submitting: false, error: _describe(e, '注册失败'), ), ); return false; } } /// 退出登录:清空令牌并复位状态(保留用户名用于回显)。 Future logout() async { await _reset(); } /// 展示一次性提示(如从主页因登录失效跳转时携带的文案)。 void showAlert(String message) { state = AsyncData(_current.copyWith(alert: message)); } /// 消费一次性提示,避免重建时重复展示。 void consumeAlert() { if (_current.alert != null) { state = AsyncData(_current.copyWith(alert: null)); } } /// 清空错误信息。 void clearError() { if (_current.error != null) { state = AsyncData(_current.copyWith(error: null)); } } /// 优先展示服务端返回的错误码,其余异常回落到通用提示。 String _describe(Object e, String fallback) { if (e is ApiException) return e.code; return '$fallback:$e'; } }