From 7628800604ba8ea622abcd0cf115ffe2f95889b3 Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Mon, 19 Oct 2026 00:53:11 +0700 Subject: [PATCH] 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. --- lib/screens/main_shell.dart | 1 + lib/screens/main_shell_startup_prompts.dart | 7 +++--- lib/screens/setup_screen.dart | 15 ++++------- lib/services/saf_migration_prompt_store.dart | 18 ++++++++++++++ lib/services/setup_progress_store.dart | 26 ++++++++++++++++++++ 5 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 lib/services/saf_migration_prompt_store.dart create mode 100644 lib/services/setup_progress_store.dart diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index 3db3c2f2..fbb9f2e2 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -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'; diff --git a/lib/screens/main_shell_startup_prompts.dart b/lib/screens/main_shell_startup_prompts.dart index 287eb1d1..a84fd3bd 100644 --- a/lib/screens/main_shell_startup_prompts.dart +++ b/lib/screens/main_shell_startup_prompts.dart @@ -168,7 +168,7 @@ extension _MainShellStartupPrompts on _MainShellState { ); } - static const _safMigrationShownKey = 'saf_migration_prompt_shown'; + static const _safMigrationPromptStore = SafMigrationPromptStore(); Future _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; diff --git a/lib/screens/setup_screen.dart b/lib/screens/setup_screen.dart index 10dd88f1..6677de84 100644 --- a/lib/screens/setup_screen.dart +++ b/lib/screens/setup_screen.dart @@ -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 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 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; _currentStep = ((draft['step'] as int?) ?? 0).clamp(0, _totalSteps - 1); @@ -141,16 +139,13 @@ class _SetupScreenState extends ConsumerState } Future _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 _restoreSelectedFolder() => _folderRecovery ??= @@ -607,7 +602,7 @@ class _SetupScreenState extends ConsumerState } await ref.read(settingsProvider.notifier).setFirstLaunchComplete(); - await (await _prefs).remove(_progressKey); + await _progressStore.clear(); if (mounted) context.go('/tutorial'); } catch (e) { diff --git a/lib/services/saf_migration_prompt_store.dart b/lib/services/saf_migration_prompt_store.dart new file mode 100644 index 00000000..81c9071e --- /dev/null +++ b/lib/services/saf_migration_prompt_store.dart @@ -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 wasShown() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getBool(_shownKey) == true; + } + + Future markShown() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_shownKey, true); + } +} diff --git a/lib/services/setup_progress_store.dart b/lib/services/setup_progress_store.dart new file mode 100644 index 00000000..4b7d5e36 --- /dev/null +++ b/lib/services/setup_progress_store.dart @@ -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 _prefs; + + /// The stored draft as raw JSON text, or null when none was saved. + Future readDraft() async => (await _prefs).getString(_progressKey); + + Future writeDraft(Map draft) async { + final encoded = jsonEncode(draft); + if (!await (await _prefs).setString(_progressKey, encoded)) { + throw StateError('Could not save setup progress'); + } + } + + Future clear() async { + await (await _prefs).remove(_progressKey); + } +}