import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; class StorageService { static const String _keyServerUrl = 'server_url'; static const String _keyToken = 'token'; static const String _keyPlexToken = 'plex_token'; static const String _keyServerData = 'server_data'; static const String _keyClientId = 'client_identifier'; static const String _keySelectedLibraryIndex = 'selected_library_index'; static const String _keyLibraryFilters = 'library_filters'; static const String _keyUserProfile = 'user_profile'; static const String _keyCurrentUserUUID = 'current_user_uuid'; static const String _keyHomeUsersCache = 'home_users_cache'; static const String _keyHomeUsersCacheExpiry = 'home_users_cache_expiry'; static StorageService? _instance; late SharedPreferences _prefs; StorageService._(); static Future getInstance() async { if (_instance == null) { _instance = StorageService._(); await _instance!._init(); } return _instance!; } Future _init() async { _prefs = await SharedPreferences.getInstance(); } // Server URL Future saveServerUrl(String url) async { await _prefs.setString(_keyServerUrl, url); } String? getServerUrl() { return _prefs.getString(_keyServerUrl); } // Server Access Token Future saveToken(String token) async { await _prefs.setString(_keyToken, token); } String? getToken() { return _prefs.getString(_keyToken); } // Alias for server access token for clarity Future saveServerAccessToken(String token) async { await saveToken(token); } String? getServerAccessToken() { return getToken(); } // Plex.tv Token (for API access) Future savePlexToken(String token) async { await _prefs.setString(_keyPlexToken, token); } String? getPlexToken() { return _prefs.getString(_keyPlexToken); } // Server Data (full PlexServer object as JSON) Future saveServerData(Map serverJson) async { final jsonString = json.encode(serverJson); await _prefs.setString(_keyServerData, jsonString); } Map? getServerData() { final jsonString = _prefs.getString(_keyServerData); if (jsonString == null) return null; try { return json.decode(jsonString) as Map; } catch (e) { return null; } } // Client Identifier Future saveClientIdentifier(String clientId) async { await _prefs.setString(_keyClientId, clientId); } String? getClientIdentifier() { return _prefs.getString(_keyClientId); } // Save all credentials at once Future saveCredentials({ required String serverUrl, required String token, required String clientIdentifier, }) async { await Future.wait([ saveServerUrl(serverUrl), saveToken(token), saveClientIdentifier(clientIdentifier), ]); } // Check if credentials exist bool hasCredentials() { return getServerUrl() != null && getToken() != null; } // Clear all credentials Future clearCredentials() async { await Future.wait([ _prefs.remove(_keyServerUrl), _prefs.remove(_keyToken), _prefs.remove(_keyPlexToken), _prefs.remove(_keyServerData), _prefs.remove(_keyClientId), _prefs.remove(_keyUserProfile), _prefs.remove(_keyCurrentUserUUID), _prefs.remove(_keyHomeUsersCache), _prefs.remove(_keyHomeUsersCacheExpiry), ]); } // Get all credentials as a map Map getCredentials() { return { 'serverUrl': getServerUrl(), 'token': getToken(), 'clientIdentifier': getClientIdentifier(), }; } // Selected Library Index Future saveSelectedLibraryIndex(int index) async { await _prefs.setInt(_keySelectedLibraryIndex, index); } int? getSelectedLibraryIndex() { return _prefs.getInt(_keySelectedLibraryIndex); } // Library Filters (stored as JSON string) Future saveLibraryFilters(Map filters) async { final jsonString = json.encode(filters); await _prefs.setString(_keyLibraryFilters, jsonString); } Map getLibraryFilters() { final jsonString = _prefs.getString(_keyLibraryFilters); if (jsonString == null) return {}; try { final decoded = json.decode(jsonString) as Map; return decoded.map((key, value) => MapEntry(key, value.toString())); } catch (e) { return {}; } } // Library Sort (per-library, stored individually) Future saveLibrarySort(String sectionId, String sortKey) async { await _prefs.setString('library_sort_$sectionId', sortKey); } String getLibrarySort(String sectionId) { // Return saved sort or default to titleSort (alphabetical) return _prefs.getString('library_sort_$sectionId') ?? 'titleSort'; } // Clear library preferences Future clearLibraryPreferences() async { await Future.wait([ _prefs.remove(_keySelectedLibraryIndex), _prefs.remove(_keyLibraryFilters), ]); // Also clear all library sort preferences final keys = _prefs.getKeys(); final sortKeys = keys.where((key) => key.startsWith('library_sort_')); await Future.wait(sortKeys.map((key) => _prefs.remove(key))); } // User Profile (stored as JSON string) Future saveUserProfile(Map profileJson) async { final jsonString = json.encode(profileJson); await _prefs.setString(_keyUserProfile, jsonString); } Map? getUserProfile() { final jsonString = _prefs.getString(_keyUserProfile); if (jsonString == null) return null; try { return json.decode(jsonString) as Map; } catch (e) { return null; } } // Current User UUID Future saveCurrentUserUUID(String uuid) async { await _prefs.setString(_keyCurrentUserUUID, uuid); } String? getCurrentUserUUID() { return _prefs.getString(_keyCurrentUserUUID); } // Home Users Cache (stored as JSON string with expiry) Future saveHomeUsersCache(Map homeData) async { final jsonString = json.encode(homeData); await _prefs.setString(_keyHomeUsersCache, jsonString); // Set cache expiry to 1 hour from now final expiry = DateTime.now() .add(const Duration(hours: 1)) .millisecondsSinceEpoch; await _prefs.setInt(_keyHomeUsersCacheExpiry, expiry); } Map? getHomeUsersCache() { final expiry = _prefs.getInt(_keyHomeUsersCacheExpiry); if (expiry == null || DateTime.now().millisecondsSinceEpoch > expiry) { // Cache expired, clear it clearHomeUsersCache(); return null; } final jsonString = _prefs.getString(_keyHomeUsersCache); if (jsonString == null) return null; try { return json.decode(jsonString) as Map; } catch (e) { return null; } } Future clearHomeUsersCache() async { await Future.wait([ _prefs.remove(_keyHomeUsersCache), _prefs.remove(_keyHomeUsersCacheExpiry), ]); } // Clear current user UUID (for server switching) Future clearCurrentUserUUID() async { await _prefs.remove(_keyCurrentUserUUID); } // Clear all user-related data (for logout) Future clearUserData() async { await Future.wait([clearCredentials(), clearLibraryPreferences()]); } // Update current user after switching Future updateCurrentUser(String userUUID, String authToken) async { await Future.wait([ saveCurrentUserUUID(userUUID), saveToken(authToken), // Update the main token ]); } }