From 404ac270f7cea2ad022380c0d11748ba7e9eb69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=BCllner?= Date: Thu, 5 Mar 2026 14:46:12 +0100 Subject: [PATCH] fix(state): scope active collection preference by user with legacy key migration --- lib/main.dart | 13 +++++---- lib/screens/collections_screen.dart | 28 +++++++++++++++---- lib/screens/scan_tab.dart | 15 ++++++++-- lib/utils/preferences_utils.dart | 43 +++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 lib/utils/preferences_utils.dart diff --git a/lib/main.dart b/lib/main.dart index 24e299e..0d723c0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'services/main_collection_sync.dart'; import 'screens/login_screen.dart'; import 'screens/home_shell.dart'; import 'utils/error_utils.dart'; +import 'utils/preferences_utils.dart'; // Re-export so other files can `import '../main.dart'` for these. export 'package:supabase_flutter/supabase_flutter.dart' @@ -118,8 +119,6 @@ class AuthGate extends StatefulWidget { } class _AuthGateState extends State { - static const _activeCollectionPrefKey = 'active_collection_id'; - bool _isLoading = true; bool _isInPasswordRecoveryFlow = false; Session? _session; @@ -183,7 +182,7 @@ class _AuthGateState extends State { if (userId == null) return; final prefs = await SharedPreferences.getInstance(); - final persisted = prefs.getString(_activeCollectionPrefKey); + final persisted = await readActiveCollectionId(prefs, userId: userId); Future hasMembership(String collectionId) async { final membership = await supabase @@ -214,11 +213,15 @@ class _AuthGateState extends State { } if (nextActiveId == null) { - await prefs.remove(_activeCollectionPrefKey); + await clearActiveCollectionId(prefs, userId: userId); return; } - await prefs.setString(_activeCollectionPrefKey, nextActiveId); + await writeActiveCollectionId( + prefs, + userId: userId, + collectionId: nextActiveId, + ); MainCollectionSync.notifyChanged(); } diff --git a/lib/screens/collections_screen.dart b/lib/screens/collections_screen.dart index 276c902..c00e3f0 100644 --- a/lib/screens/collections_screen.dart +++ b/lib/screens/collections_screen.dart @@ -5,6 +5,7 @@ import '../services/collection_service.dart'; import '../services/main_collection_sync.dart'; import '../theme/app_colors.dart'; import '../utils/error_utils.dart'; +import '../utils/preferences_utils.dart'; import 'garage_screen.dart'; import 'manage_collection_screen.dart'; @@ -18,8 +19,6 @@ class CollectionsScreen extends StatefulWidget { class CollectionsScreenState extends State with WidgetsBindingObserver { - static const _activeCollectionPrefKey = 'active_collection_id'; - List _collections = []; bool _isLoading = true; String? _error; @@ -74,8 +73,13 @@ class CollectionsScreenState extends State await CollectionService.ensureDefaultCollection(); list = await CollectionService.getMyCollections(); } + final userId = supabase.auth.currentUser?.id; + if (userId == null) { + throw Exception('You must be signed in to load collections.'); + } + final prefs = await SharedPreferences.getInstance(); - final persisted = prefs.getString(_activeCollectionPrefKey); + final persisted = await readActiveCollectionId(prefs, userId: userId); String? activeId = persisted; if (activeId == null && list.isNotEmpty) { @@ -96,7 +100,11 @@ class CollectionsScreenState extends State }); if (activeId != null) { - await prefs.setString(_activeCollectionPrefKey, activeId); + await writeActiveCollectionId( + prefs, + userId: userId, + collectionId: activeId, + ); if (shouldNotifySync) { MainCollectionSync.notifyChanged(); } @@ -218,8 +226,18 @@ class CollectionsScreenState extends State } Future _setActiveCollection(String collectionId) async { + final userId = supabase.auth.currentUser?.id; + if (userId == null) { + showGlobalSnackBar('Please sign in again.', isError: true); + return; + } + final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_activeCollectionPrefKey, collectionId); + await writeActiveCollectionId( + prefs, + userId: userId, + collectionId: collectionId, + ); if (!mounted) return; setState(() => _activeCollectionId = collectionId); MainCollectionSync.notifyChanged(); diff --git a/lib/screens/scan_tab.dart b/lib/screens/scan_tab.dart index 70efc99..8a6d06c 100644 --- a/lib/screens/scan_tab.dart +++ b/lib/screens/scan_tab.dart @@ -5,6 +5,7 @@ import '../scanner_screen.dart'; import '../services/collection_service.dart'; import '../services/main_collection_sync.dart'; import '../theme/app_colors.dart'; +import '../utils/preferences_utils.dart'; class ScanTab extends StatefulWidget { const ScanTab({super.key}); @@ -14,7 +15,6 @@ class ScanTab extends StatefulWidget { } class ScanTabState extends State { - static const _activeCollectionPrefKey = 'active_collection_id'; static const _duplicateCooldown = Duration(seconds: 2); bool _isBusy = false; @@ -50,8 +50,13 @@ class ScanTabState extends State { Future _loadCollections() async { try { final list = await CollectionService.getMyCollections(); + final userId = supabase.auth.currentUser?.id; + if (userId == null) { + throw Exception('You must be signed in to load collections.'); + } + final prefs = await SharedPreferences.getInstance(); - final persistedId = prefs.getString(_activeCollectionPrefKey); + final persistedId = await readActiveCollectionId(prefs, userId: userId); Collection? selected; if (persistedId != null) { @@ -71,7 +76,11 @@ class ScanTabState extends State { }); if (selected != null) { - await prefs.setString(_activeCollectionPrefKey, selected.id); + await writeActiveCollectionId( + prefs, + userId: userId, + collectionId: selected.id, + ); } } catch (e) { if (!mounted) return; diff --git a/lib/utils/preferences_utils.dart b/lib/utils/preferences_utils.dart new file mode 100644 index 0000000..39da613 --- /dev/null +++ b/lib/utils/preferences_utils.dart @@ -0,0 +1,43 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +const _legacyActiveCollectionPrefKey = 'active_collection_id'; + +String activeCollectionPrefKeyForUser(String userId) { + return 'active_collection_id_$userId'; +} + +Future readActiveCollectionId( + SharedPreferences prefs, { + required String userId, +}) async { + final scopedKey = activeCollectionPrefKeyForUser(userId); + final scopedValue = prefs.getString(scopedKey); + if (scopedValue != null && scopedValue.isNotEmpty) { + return scopedValue; + } + + final legacyValue = prefs.getString(_legacyActiveCollectionPrefKey); + if (legacyValue == null || legacyValue.isEmpty) { + return null; + } + + await prefs.setString(scopedKey, legacyValue); + await prefs.remove(_legacyActiveCollectionPrefKey); + return legacyValue; +} + +Future writeActiveCollectionId( + SharedPreferences prefs, { + required String userId, + required String collectionId, +}) { + return prefs.setString(activeCollectionPrefKeyForUser(userId), collectionId); +} + +Future clearActiveCollectionId( + SharedPreferences prefs, { + required String userId, +}) async { + await prefs.remove(activeCollectionPrefKeyForUser(userId)); + await prefs.remove(_legacyActiveCollectionPrefKey); +}