feat(client): 三端布局架构 + macOS 桌面端 + app 图标
三端布局(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>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// 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)),
|
||||
);
|
||||
Reference in New Issue
Block a user