refactor(setup): move SharedPreferences access behind SetupProgressStore and SafMigrationPromptStore

The setup draft and the SAF migration prompt flag were read and written
directly from screens. Keys, JSON shape and defaults are unchanged.
This commit is contained in:
zarzet committed 2026-10-19 00:56:52 +07:00
1 parent 8af7efb6ce
commit 7628800604
5 files changed
+53 -14

No files matched your search

+1
View File
@@ -11,6 +11,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/constants/app_info.dart';
import 'package:spotiflac_android/screens/upgrade_intro_screen.dart';
import 'package:spotiflac_android/services/saf_migration_prompt_store.dart';
import 'package:spotiflac_android/services/upgrade_intro_service.dart';
import 'package:spotiflac_android/providers/download_queue_provider.dart';
import 'package:spotiflac_android/providers/music_player_provider.dart';
+3 -4
View File
@@ -168,7 +168,7 @@ extension _MainShellStartupPrompts on _MainShellState {
);
}
static const _safMigrationShownKey = 'saf_migration_prompt_shown';
static const _safMigrationPromptStore = SafMigrationPromptStore();
Future<void> _checkSafMigration() async {
if (!Platform.isAndroid) return;
@@ -181,9 +181,8 @@ extension _MainShellStartupPrompts on _MainShellState {
final androidInfo = await deviceInfo.androidInfo;
if (androidInfo.version.sdkInt < 29) return;
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool(_safMigrationShownKey) == true) return;
await prefs.setBool(_safMigrationShownKey, true);
if (await _safMigrationPromptStore.wasShown()) return;
await _safMigrationPromptStore.markShown();
if (!mounted) return;
+5 -10
View File
@@ -19,7 +19,7 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:path_provider/path_provider.dart';
import 'package:go_router/go_router.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotiflac_android/services/setup_progress_store.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/constants/language_choices.dart';
@@ -46,8 +46,7 @@ class SetupScreen extends ConsumerStatefulWidget {
class _SetupScreenState extends ConsumerState<SetupScreen>
with WidgetsBindingObserver {
static const _progressKey = 'setup_progress_v1';
final _prefs = SharedPreferences.getInstance();
final _progressStore = SetupProgressStore();
final _platform = defaultTargetPlatform;
PageController? _pageController;
final ScrollController _languageScrollController = ScrollController();
@@ -98,10 +97,9 @@ class _SetupScreenState extends ConsumerState<SetupScreen>
final androidInfo = await DeviceInfoPlugin().androidInfo;
_androidSdkVersion = androidInfo.version.sdkInt;
}
final prefs = await _prefs;
final saved = await _progressStore.readDraft();
if (!mounted) return;
_selectedLocale = ref.read(settingsProvider).locale;
final saved = prefs.getString(_progressKey);
if (saved != null) {
final draft = jsonDecode(saved) as Map<String, dynamic>;
_currentStep = ((draft['step'] as int?) ?? 0).clamp(0, _totalSteps - 1);
@@ -141,16 +139,13 @@ class _SetupScreenState extends ConsumerState<SetupScreen>
}
Future<void> _saveProgress({int? step}) async {
final draft = jsonEncode({
await _progressStore.writeDraft({
'step': step ?? _currentStep,
'directory': _selectedDirectory,
'bookmark': _selectedDirectoryBookmark,
'treeUri': _selectedTreeUri,
'skipNotifications': _notificationPermissionSkipped,
});
if (!await (await _prefs).setString(_progressKey, draft)) {
throw StateError('Could not save setup progress');
}
}
Future<void> _restoreSelectedFolder() => _folderRecovery ??=
@@ -607,7 +602,7 @@ class _SetupScreenState extends ConsumerState<SetupScreen>
}
await ref.read(settingsProvider.notifier).setFirstLaunchComplete();
await (await _prefs).remove(_progressKey);
await _progressStore.clear();
if (mounted) context.go('/tutorial');
} catch (e) {
@@ -0,0 +1,18 @@
import 'package:shared_preferences/shared_preferences.dart';
/// Remembers that the one-time SAF migration prompt was already shown.
class SafMigrationPromptStore {
const SafMigrationPromptStore();
static const _shownKey = 'saf_migration_prompt_shown';
Future<bool> wasShown() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(_shownKey) == true;
}
Future<void> markShown() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_shownKey, true);
}
}
+26
View File
@@ -0,0 +1,26 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
/// Persists the first-run setup draft so an interrupted setup can resume.
class SetupProgressStore {
SetupProgressStore() : _prefs = SharedPreferences.getInstance();
static const _progressKey = 'setup_progress_v1';
final Future<SharedPreferences> _prefs;
/// The stored draft as raw JSON text, or null when none was saved.
Future<String?> readDraft() async => (await _prefs).getString(_progressKey);
Future<void> writeDraft(Map<String, Object?> draft) async {
final encoded = jsonEncode(draft);
if (!await (await _prefs).setString(_progressKey, encoded)) {
throw StateError('Could not save setup progress');
}
}
Future<void> clear() async {
await (await _prefs).remove(_progressKey);
}
}