4dc4127252
三端布局(mobile/tablet/desktop): - core/responsive/form_factor.dart 形态判定 + shell/ 分发器(home_shell→desktop/mobile) - desktop_shell 对照 ui_kits/desktop/dapp.jsx: 侧栏204·6项 + 套餐卡 + 顶栏(标题/状态/主题切换) + 连接页居中单列 - 新增组件 nav_sidebar / plan_badge_card / content_top_bar / bottom_tab_bar - 新增一级页 contact_page / settings_page; navigation_provider(NavView) - 删除旧 widgets/home_shell.dart(逻辑迁入 shell/) macOS 桌面端: - 窗口默认 920×600 + 最小 720×560(MainFlutterWindow.swift) - app 图标替换为穿山甲(AppIcon.appiconset 全套, 由 app-icon.svg 渲染) 其余(本会话): - Phase2 接线: auth_api/token_store/auth_provider/vpn_bridge_provider + 真实 connection/nodes - lucide_icons 兼容补丁(packages/lucide_icons_patched) 修复 IconData final 报错 - 测试修复: connect_passthrough(UTF-8) / harness / golden @Skip - l10n 新增 settingsTitle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74 lines
2.6 KiB
Dart
74 lines
2.6 KiB
Dart
// auth_provider.dart — 认证状态(JWT 令牌生命周期)
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../services/auth_api.dart';
|
|
import '../services/token_store.dart';
|
|
|
|
// ── 状态 ────────────────────────────────────────────────────────────
|
|
|
|
class AuthState {
|
|
const AuthState({this.accessToken, this.isLoading = false});
|
|
|
|
final String? accessToken;
|
|
final bool isLoading;
|
|
|
|
bool get isLoggedIn => accessToken != null && accessToken!.isNotEmpty;
|
|
|
|
AuthState copyWith({String? accessToken, bool? isLoading}) => AuthState(
|
|
accessToken: accessToken ?? this.accessToken,
|
|
isLoading: isLoading ?? this.isLoading,
|
|
);
|
|
}
|
|
|
|
// ── 状态机 ───────────────────────────────────────────────────────────
|
|
|
|
class AuthNotifier extends StateNotifier<AuthState> {
|
|
AuthNotifier(this._store) : super(const AuthState(isLoading: true)) {
|
|
_loadFromStore();
|
|
}
|
|
|
|
final TokenStore _store;
|
|
|
|
Future<void> _loadFromStore() async {
|
|
try {
|
|
final token = await _store.loadAccessToken();
|
|
state = AuthState(accessToken: token);
|
|
} catch (_) {
|
|
// FlutterSecureStorage 在测试环境 / 未初始化时抛异常,视为未登录。
|
|
state = const AuthState();
|
|
}
|
|
}
|
|
|
|
/// 登录 / 注册成功后保存令牌。
|
|
Future<void> saveTokens(AuthTokens tokens) async {
|
|
await _store.saveTokens(
|
|
access: tokens.accessToken,
|
|
refresh: tokens.refreshToken,
|
|
);
|
|
state = AuthState(accessToken: tokens.accessToken);
|
|
}
|
|
|
|
/// 退出登录:清除本地令牌。
|
|
Future<void> logout() async {
|
|
await _store.clear();
|
|
state = const AuthState();
|
|
}
|
|
|
|
/// Dev-only:仅设置内存登录态,不写 keychain。
|
|
/// 用于 debug 测试账户旁路,规避 flutter_secure_storage 在未签名
|
|
/// macOS app 上的 keychain entitlement 问题(-34018)。
|
|
void devLogin(String token) {
|
|
state = AuthState(accessToken: token);
|
|
}
|
|
}
|
|
|
|
// ── Providers ────────────────────────────────────────────────────────
|
|
|
|
/// 可在测试中 override,注入 stub TokenStore(避免 FlutterSecureStorage 平台依赖)。
|
|
final tokenStoreProvider = Provider<TokenStore>((_) => const TokenStore());
|
|
|
|
final authProvider =
|
|
StateNotifierProvider<AuthNotifier, AuthState>(
|
|
(ref) => AuthNotifier(ref.watch(tokenStoreProvider)),
|
|
);
|