import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; /// 本地凭据持久化(基于 SharedPreferences,跨平台可靠持久化)。 /// /// 存储内容: /// - 账号 / 密码(仅当用户勾选「记住密码」时保存) /// - token / refreshToken / 用户信息(登录成功后保存,用于免登录恢复) /// /// 历史坑:早期版本使用 dart:io 文件存储 + `Directory.current`,但 Android 上 /// `Directory.current` 指向不可写路径,会静默退化为「仅内存」存储,导致 App 重启后 /// 凭据与登录态全部丢失(表现即「记住账号密码不生效 / 每次都要重新登录」)。 /// 现统一改用 SharedPreferences(应用私有、沙箱隔离、必定可写、跨重启持久)。 class CredentialStorage { CredentialStorage._(); /// 单例。 static final CredentialStorage instance = CredentialStorage._(); /// SharedPreferences 实例;[init] 完成后非 null(除非极端初始化失败)。 SharedPreferences? _prefs; /// 是否已尝试初始化(无论成功失败都置 true,避免重复 await 同一 Future)。 bool _initialized = false; /// 异步预初始化:必须在 runApp 前 await,使下方同步 getter 可读到持久化数据。 static Future init() async { await instance._init(); } Future _init() async { if (_initialized) return; try { _prefs = await SharedPreferences.getInstance(); } catch (_) { // 极端情况下 SharedPreferences 不可用:退化为内存态,不影响启动与登录。 _prefs = null; } finally { _initialized = true; } } // ── 账号密码 ────────────────────────────────────────── /// 是否记住密码。 bool get rememberPassword => _prefs?.getBool(_kRemember) ?? false; /// 保存的账号(rememberPassword=true 时有值)。 String? get savedAccount => _prefs?.getString(_kAccount); /// 保存的密码(rememberPassword=true 时有值)。 String? get savedPassword => _prefs?.getString(_kPassword); /// 保存账号密码(用户勾选「记住密码」并登录成功后调用)。 Future saveCredentials({ required String account, required String password, }) async { final prefs = _prefs; if (prefs == null) return; await prefs.setString(_kAccount, account); await prefs.setString(_kPassword, password); await prefs.setBool(_kRemember, true); } /// 清除保存的账号密码(用户未勾选「记住密码」时调用)。 Future clearCredentials() async { final prefs = _prefs; if (prefs == null) return; await prefs.remove(_kAccount); await prefs.remove(_kPassword); await prefs.setBool(_kRemember, false); } // ── Token & 用户信息(用于免登录恢复) ────────────────── /// 保存的 token。 String? get token => _prefs?.getString(_kToken); /// 保存的 refreshToken。 String? get refreshToken => _prefs?.getString(_kRefreshToken); /// 保存的用户信息 JSON 字符串。 String? get userJson => _prefs?.getString(_kUserJson); /// 保存登录态(token + refreshToken + 用户信息)。 Future saveSession({ required String token, String? refreshToken, String? userJson, }) async { final prefs = _prefs; if (prefs == null) return; await prefs.setString(_kToken, token); if (refreshToken != null && refreshToken.isNotEmpty) { await prefs.setString(_kRefreshToken, refreshToken); } if (userJson != null && userJson.isNotEmpty) { await prefs.setString(_kUserJson, userJson); } } /// 清除登录态(登出或会话失效时调用)。 Future clearSession() async { final prefs = _prefs; if (prefs == null) return; await prefs.remove(_kToken); await prefs.remove(_kRefreshToken); await prefs.remove(_kUserJson); } /// 是否有已保存的登录态。 bool get hasSession => (token ?? '').isNotEmpty; /// 从 JSON 恢复用户信息 Map。 Map? get savedUserMap { final json = userJson; if (json == null || json.isEmpty) return null; try { return jsonDecode(json) as Map; } catch (_) { return null; } } // key 常量(加前缀避免与 StoreLocalCache 等业务 key 冲突)。 static const String _kRemember = 'cred_remember'; static const String _kAccount = 'cred_account'; static const String _kPassword = 'cred_password'; static const String _kToken = 'cred_token'; static const String _kRefreshToken = 'cred_refreshToken'; static const String _kUserJson = 'cred_userJson'; }