fix(state): scope active collection preference by user with legacy key migration

This commit is contained in:
Lukas Müllner 2026-03-05 14:46:12 +01:00
parent edacf7ad1a
commit 404ac270f7
4 changed files with 86 additions and 13 deletions

View file

@ -7,6 +7,7 @@ import 'services/main_collection_sync.dart';
import 'screens/login_screen.dart'; import 'screens/login_screen.dart';
import 'screens/home_shell.dart'; import 'screens/home_shell.dart';
import 'utils/error_utils.dart'; import 'utils/error_utils.dart';
import 'utils/preferences_utils.dart';
// Re-export so other files can `import '../main.dart'` for these. // Re-export so other files can `import '../main.dart'` for these.
export 'package:supabase_flutter/supabase_flutter.dart' export 'package:supabase_flutter/supabase_flutter.dart'
@ -118,8 +119,6 @@ class AuthGate extends StatefulWidget {
} }
class _AuthGateState extends State<AuthGate> { class _AuthGateState extends State<AuthGate> {
static const _activeCollectionPrefKey = 'active_collection_id';
bool _isLoading = true; bool _isLoading = true;
bool _isInPasswordRecoveryFlow = false; bool _isInPasswordRecoveryFlow = false;
Session? _session; Session? _session;
@ -183,7 +182,7 @@ class _AuthGateState extends State<AuthGate> {
if (userId == null) return; if (userId == null) return;
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final persisted = prefs.getString(_activeCollectionPrefKey); final persisted = await readActiveCollectionId(prefs, userId: userId);
Future<bool> hasMembership(String collectionId) async { Future<bool> hasMembership(String collectionId) async {
final membership = await supabase final membership = await supabase
@ -214,11 +213,15 @@ class _AuthGateState extends State<AuthGate> {
} }
if (nextActiveId == null) { if (nextActiveId == null) {
await prefs.remove(_activeCollectionPrefKey); await clearActiveCollectionId(prefs, userId: userId);
return; return;
} }
await prefs.setString(_activeCollectionPrefKey, nextActiveId); await writeActiveCollectionId(
prefs,
userId: userId,
collectionId: nextActiveId,
);
MainCollectionSync.notifyChanged(); MainCollectionSync.notifyChanged();
} }

View file

@ -5,6 +5,7 @@ import '../services/collection_service.dart';
import '../services/main_collection_sync.dart'; import '../services/main_collection_sync.dart';
import '../theme/app_colors.dart'; import '../theme/app_colors.dart';
import '../utils/error_utils.dart'; import '../utils/error_utils.dart';
import '../utils/preferences_utils.dart';
import 'garage_screen.dart'; import 'garage_screen.dart';
import 'manage_collection_screen.dart'; import 'manage_collection_screen.dart';
@ -18,8 +19,6 @@ class CollectionsScreen extends StatefulWidget {
class CollectionsScreenState extends State<CollectionsScreen> class CollectionsScreenState extends State<CollectionsScreen>
with WidgetsBindingObserver { with WidgetsBindingObserver {
static const _activeCollectionPrefKey = 'active_collection_id';
List<Collection> _collections = []; List<Collection> _collections = [];
bool _isLoading = true; bool _isLoading = true;
String? _error; String? _error;
@ -74,8 +73,13 @@ class CollectionsScreenState extends State<CollectionsScreen>
await CollectionService.ensureDefaultCollection(); await CollectionService.ensureDefaultCollection();
list = await CollectionService.getMyCollections(); 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 prefs = await SharedPreferences.getInstance();
final persisted = prefs.getString(_activeCollectionPrefKey); final persisted = await readActiveCollectionId(prefs, userId: userId);
String? activeId = persisted; String? activeId = persisted;
if (activeId == null && list.isNotEmpty) { if (activeId == null && list.isNotEmpty) {
@ -96,7 +100,11 @@ class CollectionsScreenState extends State<CollectionsScreen>
}); });
if (activeId != null) { if (activeId != null) {
await prefs.setString(_activeCollectionPrefKey, activeId); await writeActiveCollectionId(
prefs,
userId: userId,
collectionId: activeId,
);
if (shouldNotifySync) { if (shouldNotifySync) {
MainCollectionSync.notifyChanged(); MainCollectionSync.notifyChanged();
} }
@ -218,8 +226,18 @@ class CollectionsScreenState extends State<CollectionsScreen>
} }
Future<void> _setActiveCollection(String collectionId) async { Future<void> _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(); final prefs = await SharedPreferences.getInstance();
await prefs.setString(_activeCollectionPrefKey, collectionId); await writeActiveCollectionId(
prefs,
userId: userId,
collectionId: collectionId,
);
if (!mounted) return; if (!mounted) return;
setState(() => _activeCollectionId = collectionId); setState(() => _activeCollectionId = collectionId);
MainCollectionSync.notifyChanged(); MainCollectionSync.notifyChanged();

View file

@ -5,6 +5,7 @@ import '../scanner_screen.dart';
import '../services/collection_service.dart'; import '../services/collection_service.dart';
import '../services/main_collection_sync.dart'; import '../services/main_collection_sync.dart';
import '../theme/app_colors.dart'; import '../theme/app_colors.dart';
import '../utils/preferences_utils.dart';
class ScanTab extends StatefulWidget { class ScanTab extends StatefulWidget {
const ScanTab({super.key}); const ScanTab({super.key});
@ -14,7 +15,6 @@ class ScanTab extends StatefulWidget {
} }
class ScanTabState extends State<ScanTab> { class ScanTabState extends State<ScanTab> {
static const _activeCollectionPrefKey = 'active_collection_id';
static const _duplicateCooldown = Duration(seconds: 2); static const _duplicateCooldown = Duration(seconds: 2);
bool _isBusy = false; bool _isBusy = false;
@ -50,8 +50,13 @@ class ScanTabState extends State<ScanTab> {
Future<void> _loadCollections() async { Future<void> _loadCollections() async {
try { try {
final list = await CollectionService.getMyCollections(); 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 prefs = await SharedPreferences.getInstance();
final persistedId = prefs.getString(_activeCollectionPrefKey); final persistedId = await readActiveCollectionId(prefs, userId: userId);
Collection? selected; Collection? selected;
if (persistedId != null) { if (persistedId != null) {
@ -71,7 +76,11 @@ class ScanTabState extends State<ScanTab> {
}); });
if (selected != null) { if (selected != null) {
await prefs.setString(_activeCollectionPrefKey, selected.id); await writeActiveCollectionId(
prefs,
userId: userId,
collectionId: selected.id,
);
} }
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;

View file

@ -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<String?> 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<void> writeActiveCollectionId(
SharedPreferences prefs, {
required String userId,
required String collectionId,
}) {
return prefs.setString(activeCollectionPrefKeyForUser(userId), collectionId);
}
Future<void> clearActiveCollectionId(
SharedPreferences prefs, {
required String userId,
}) async {
await prefs.remove(activeCollectionPrefKeyForUser(userId));
await prefs.remove(_legacyActiveCollectionPrefKey);
}