fix: scope library display settings per user profile

close #630
This commit is contained in:
edde746
2026-03-05 16:42:13 +01:00
parent c785ce1f88
commit 819d7b0f8f
+74 -26
View File
@@ -61,6 +61,35 @@ class StorageService extends BaseSharedPreferencesService {
LogRedactionManager.registerToken(getPlexToken());
}
// User-scoped storage for per-profile library settings
/// Returns `'user_{uuid}_'` for the current user, or `''` if no user is set.
String get _userPrefix {
final uuid = getCurrentUserUUID();
return uuid != null ? 'user_${uuid}_' : '';
}
/// Read a string with user-scoped key, migrating from legacy key if needed.
String? _getScopedString(String baseKey) {
final scopedKey = '$_userPrefix$baseKey';
final value = prefs.getString(scopedKey);
if (value != null || _userPrefix.isEmpty) return value;
// One-time migration from legacy global key
final legacy = prefs.getString(baseKey);
if (legacy != null) prefs.setString(scopedKey, legacy);
return legacy;
}
/// Read an int with user-scoped key, migrating from legacy key if needed.
int? _getScopedInt(String baseKey) {
final scopedKey = '$_userPrefix$baseKey';
final value = prefs.getInt(scopedKey);
if (value != null || _userPrefix.isEmpty) return value;
final legacy = prefs.getInt(baseKey);
if (legacy != null) prefs.setInt(scopedKey, legacy);
return legacy;
}
// Per-Server Endpoint URL (for multi-server connection caching)
Future<void> saveServerEndpoint(String serverId, String url) async {
await prefs.setString('$_prefixServerEndpoint$serverId', url);
@@ -101,34 +130,35 @@ class StorageService extends BaseSharedPreferencesService {
}
int? getSelectedLibraryIndex() {
return prefs.getInt(_keySelectedLibraryIndex);
return _getScopedInt(_keySelectedLibraryIndex);
}
// Selected Library Key (replaces index-based selection)
Future<void> saveSelectedLibraryKey(String key) async {
await prefs.setString(_keySelectedLibraryKey, key);
await prefs.setString('$_userPrefix$_keySelectedLibraryKey', key);
}
String? getSelectedLibraryKey() {
return prefs.getString(_keySelectedLibraryKey);
return _getScopedString(_keySelectedLibraryKey);
}
// Library Filters (stored as JSON string)
Future<void> saveLibraryFilters(Map<String, String> filters, {String? sectionId}) async {
final key = sectionId != null ? '$_prefixLibraryFilters$sectionId' : _keyLibraryFilters;
final baseKey = sectionId != null ? '$_prefixLibraryFilters$sectionId' : _keyLibraryFilters;
// Note: using Map<String, String> which json.encode handles correctly
final jsonString = json.encode(filters);
await prefs.setString(key, jsonString);
await prefs.setString('$_userPrefix$baseKey', jsonString);
}
Map<String, String> getLibraryFilters({String? sectionId}) {
final scopedKey = sectionId != null ? '$_prefixLibraryFilters$sectionId' : _keyLibraryFilters;
final baseKey = sectionId != null ? '$_prefixLibraryFilters$sectionId' : _keyLibraryFilters;
// Prefer per-library filters when available
final jsonString =
prefs.getString(scopedKey) ??
// Legacy support: fall back to global filters if present
prefs.getString(_keyLibraryFilters);
var jsonString = _getScopedString(baseKey);
if (jsonString == null && sectionId != null) {
// Legacy support: fall back to global filters if present
jsonString = _getScopedString(_keyLibraryFilters);
}
if (jsonString == null) return {};
final decoded = decodeJsonStringToMap(jsonString);
@@ -138,38 +168,45 @@ class StorageService extends BaseSharedPreferencesService {
// Library Sort (per-library, stored individually with descending flag)
Future<void> saveLibrarySort(String sectionId, String sortKey, {bool descending = false}) async {
final sortData = {'key': sortKey, 'descending': descending};
await _setJsonMap('$_prefixLibrarySort$sectionId', sortData);
await _setJsonMap('$_userPrefix$_prefixLibrarySort$sectionId', sortData);
}
Map<String, dynamic>? getLibrarySort(String sectionId) {
return _readJsonMap('$_prefixLibrarySort$sectionId', legacyStringOk: true);
final baseKey = '$_prefixLibrarySort$sectionId';
final scopedKey = '$_userPrefix$baseKey';
var result = _readJsonMap(scopedKey, legacyStringOk: true);
if (result != null || _userPrefix.isEmpty) return result;
// One-time migration from legacy key
result = _readJsonMap(baseKey, legacyStringOk: true);
if (result != null) _setJsonMap(scopedKey, result);
return result;
}
// Library Grouping (per-library, e.g., 'movies', 'shows', 'seasons', 'episodes')
Future<void> saveLibraryGrouping(String sectionId, String grouping) async {
await prefs.setString('$_prefixLibraryGrouping$sectionId', grouping);
await prefs.setString('$_userPrefix$_prefixLibraryGrouping$sectionId', grouping);
}
String? getLibraryGrouping(String sectionId) {
return prefs.getString('$_prefixLibraryGrouping$sectionId');
return _getScopedString('$_prefixLibraryGrouping$sectionId');
}
// Library Tab (per-library, saves last selected tab index)
Future<void> saveLibraryTab(String sectionId, int tabIndex) async {
await prefs.setInt('$_prefixLibraryTab$sectionId', tabIndex);
await prefs.setInt('$_userPrefix$_prefixLibraryTab$sectionId', tabIndex);
}
int? getLibraryTab(String sectionId) {
return prefs.getInt('$_prefixLibraryTab$sectionId');
return _getScopedInt('$_prefixLibraryTab$sectionId');
}
// Hidden Libraries (stored as JSON array of library section IDs)
Future<void> saveHiddenLibraries(Set<String> libraryKeys) async {
await _setStringList(_keyHiddenLibraries, libraryKeys.toList());
await _setStringList('$_userPrefix$_keyHiddenLibraries', libraryKeys.toList());
}
Set<String> getHiddenLibraries() {
final jsonString = prefs.getString(_keyHiddenLibraries);
final jsonString = _getScopedString(_keyHiddenLibraries);
if (jsonString == null) return {};
try {
@@ -180,23 +217,34 @@ class StorageService extends BaseSharedPreferencesService {
}
}
// Clear library preferences
// Clear library preferences (scoped to current user)
Future<void> clearLibraryPreferences() async {
final prefix = _userPrefix;
await Future.wait([
..._libraryPreferenceKeys.map((k) => prefs.remove(k)),
_clearKeysWithPrefix(_prefixLibrarySort),
_clearKeysWithPrefix(_prefixLibraryFilters),
_clearKeysWithPrefix(_prefixLibraryGrouping),
_clearKeysWithPrefix(_prefixLibraryTab),
..._libraryPreferenceKeys.map((k) => prefs.remove('$prefix$k')),
prefs.remove('$prefix$_keySelectedLibraryKey'),
_clearKeysWithPrefix('$prefix$_prefixLibrarySort'),
_clearKeysWithPrefix('$prefix$_prefixLibraryFilters'),
_clearKeysWithPrefix('$prefix$_prefixLibraryGrouping'),
_clearKeysWithPrefix('$prefix$_prefixLibraryTab'),
]);
}
// Library Order (stored as JSON list of library keys)
Future<void> saveLibraryOrder(List<String> libraryKeys) async {
await _setStringList(_keyLibraryOrder, libraryKeys);
await _setStringList('$_userPrefix$_keyLibraryOrder', libraryKeys);
}
List<String>? getLibraryOrder() => _getStringList(_keyLibraryOrder);
List<String>? getLibraryOrder() {
final baseKey = _keyLibraryOrder;
final scopedKey = '$_userPrefix$baseKey';
final value = _getStringList(scopedKey);
if (value != null || _userPrefix.isEmpty) return value;
// One-time migration from legacy key
final legacy = _getStringList(baseKey);
if (legacy != null) _setStringList(scopedKey, legacy);
return legacy;
}
// User Profile (stored as JSON string)
Future<void> saveUserProfile(Map<String, dynamic> profileJson) async {