import "dart:convert"; import "package:flutter/foundation.dart"; import "package:shared_preferences/shared_preferences.dart"; import "api.dart"; import "models.dart"; const _tokenKey = "cc_token"; const _userKey = "cc_user"; /// Holds session state and persists the token across app restarts. class AuthService extends ChangeNotifier { final ApiClient api; AuthUser? user; bool ready = false; /// In-memory (never persisted) app-lock flag. When biometric login is enabled /// the app starts/returns locked: the token is still valid but the UI hides /// behind a biometric unlock instead of jumping straight to the dashboard. bool locked = false; AuthService(this.api) { api.onUnauthorized = () => logout(); } bool get isAuthenticated => api.token != null; void lock() { if (!locked && isAuthenticated) { locked = true; notifyListeners(); } } void unlock() { if (locked) { locked = false; notifyListeners(); } } Future loadFromStorage() async { final prefs = await SharedPreferences.getInstance(); final t = prefs.getString(_tokenKey); final u = prefs.getString(_userKey); if (t != null) { api.token = t; if (u != null) user = AuthUser.fromJson(jsonDecode(u)); } ready = true; notifyListeners(); } Future login(String email, String password) async { final (token, u) = await api.login(email, password); api.token = token; user = u; locked = false; final prefs = await SharedPreferences.getInstance(); await prefs.setString(_tokenKey, token); await prefs.setString(_userKey, jsonEncode(u.toJson())); notifyListeners(); } /// Adopts the role from a freshly fetched profile. A session's role can change /// under it — creating an organization promotes the creator to that org's admin /// — and the nav gates the Users tab on the cached copy, so it has to catch up /// without requiring a re-login. A no-op when the role is unchanged. Future adoptRole(UserProfile profile) async { final u = user; if (u == null || profile.role == u.role) return; user = AuthUser(id: u.id, email: u.email, name: u.name, role: profile.role); final prefs = await SharedPreferences.getInstance(); await prefs.setString(_userKey, jsonEncode(user!.toJson())); notifyListeners(); } Future logout() async { api.token = null; user = null; locked = false; final prefs = await SharedPreferences.getInstance(); await prefs.remove(_tokenKey); await prefs.remove(_userKey); notifyListeners(); } }