fix(state): scope active collection preference by user with legacy key migration
This commit is contained in:
parent
edacf7ad1a
commit
404ac270f7
4 changed files with 86 additions and 13 deletions
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
43
lib/utils/preferences_utils.dart
Normal file
43
lib/utils/preferences_utils.dart
Normal 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);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue