diff options
Diffstat (limited to 'player-android/lib/providers')
| -rw-r--r-- | player-android/lib/providers/settings_provider.dart | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/player-android/lib/providers/settings_provider.dart b/player-android/lib/providers/settings_provider.dart new file mode 100644 index 0000000..8ffec0f --- /dev/null +++ b/player-android/lib/providers/settings_provider.dart @@ -0,0 +1,87 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +// SharedPreferences key for the server base URL setting. +const _kBaseUrlKey = 'server_base_url'; + +// Default base URL used when the user has not yet configured one. +// Points to the local Android emulator loopback address so the app is +// runnable out-of-the-box without any manual configuration. +// Private: only referenced within this file; callers read the resolved URL +// through [AppSettings.serverBaseUrl] obtained from [settingsProvider]. +const _kDefaultBaseUrl = 'http://10.0.2.2:8080'; + +/// Immutable snapshot of persisted app settings. +/// +/// Keeping settings as a value object means every state change produces a new +/// instance, which plays well with Riverpod's equality-based rebuild suppression +/// and keeps the notifier's contract straightforward. +class AppSettings { + const AppSettings({required this.serverBaseUrl}); + + /// The base URL of the player-server API (e.g. "https://player.example.com"). + final String serverBaseUrl; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AppSettings && + runtimeType == other.runtimeType && + serverBaseUrl == other.serverBaseUrl; + + @override + int get hashCode => serverBaseUrl.hashCode; + + @override + String toString() => 'AppSettings(serverBaseUrl: $serverBaseUrl)'; +} + +/// Manages persisted app settings via [SharedPreferences]. +/// +/// Uses [AsyncNotifier] because the initial state load is async (disk read). +/// After initialisation, [setServerBaseUrl] writes to disk and updates state +/// synchronously so the UI reflects changes immediately. +/// +/// Design notes (SRP / ISP): +/// - This notifier owns only settings persistence; auth is handled separately +/// by [AuthStateNotifier] to maintain single responsibility. +/// - [SharedPreferences] is created internally rather than injected because +/// it is a platform singleton; tests override the entire provider via +/// [ProviderScope] overrides instead. +class SettingsNotifier extends AsyncNotifier<AppSettings> { + @override + Future<AppSettings> build() async { + // Load persisted settings from disk on first access. The platform + // SharedPreferences instance is a singleton; obtaining it here is cheap + // because subsequent calls return the cached instance. + final prefs = await SharedPreferences.getInstance(); + final url = prefs.getString(_kBaseUrlKey) ?? _kDefaultBaseUrl; + return AppSettings(serverBaseUrl: url); + } + + /// Persists [url] as the new server base URL and updates the in-memory state. + /// + /// The UI calls this when the user edits the URL field and submits. The + /// async write to [SharedPreferences] is awaited so that a subsequent cold + /// start will see the new value; the in-memory state is updated first so the + /// UI is not blocked on the disk write. + Future<void> setServerBaseUrl(String url) async { + // Update in-memory state first for immediate UI feedback. + state = AsyncData(AppSettings(serverBaseUrl: url)); + + // Persist to disk so the value survives app restarts. + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_kBaseUrlKey, url); + } +} + +/// The single source of truth for persisted app settings. +/// +/// Currently consumed by [SettingsScreen] for displaying and editing settings. +/// Will also be consumed by [apiClientProvider] (for the server base URL) once +/// that provider is wired to read from settings rather than +/// [String.fromEnvironment] — tracked as a future task. +final settingsProvider = + AsyncNotifierProvider<SettingsNotifier, AppSettings>( + SettingsNotifier.new, +); |
