/// Settings Provider library; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:io'; import '../../../domain/entities/user.dart'; import '../../../infrastructure/datasources/remote/auth_api_service.dart'; /// Audio Quality enum enum AudioQuality { low, medium, high, lossless } /// Settings state class SettingsState { final User? user; final AudioQuality audioQuality; final bool downloadOnMobileData; final bool showExplicitContent; final bool crossfadeEnabled; final double crossfadeDuration; final bool gaplessPlayback; final bool normalizeVolume; final String cacheSize; final bool isLoading; final String? error; const SettingsState({ this.user, this.audioQuality = AudioQuality.high, this.downloadOnMobileData = false, this.showExplicitContent = true, this.crossfadeEnabled = false, this.crossfadeDuration = 5.0, this.gaplessPlayback = true, this.normalizeVolume = false, this.cacheSize = '0 MB', this.isLoading = false, this.error, }); SettingsState copyWith({ User? user, AudioQuality? audioQuality, bool? downloadOnMobileData, bool? showExplicitContent, bool? crossfadeEnabled, double? crossfadeDuration, bool? gaplessPlayback, bool? normalizeVolume, String? cacheSize, bool? isLoading, String? error, }) { return SettingsState( user: user ?? this.user, audioQuality: audioQuality ?? this.audioQuality, downloadOnMobileData: downloadOnMobileData ?? this.downloadOnMobileData, showExplicitContent: showExplicitContent ?? this.showExplicitContent, crossfadeEnabled: crossfadeEnabled ?? this.crossfadeEnabled, crossfadeDuration: crossfadeDuration ?? this.crossfadeDuration, gaplessPlayback: gaplessPlayback ?? this.gaplessPlayback, normalizeVolume: normalizeVolume ?? this.normalizeVolume, cacheSize: cacheSize ?? this.cacheSize, isLoading: isLoading ?? this.isLoading, error: error, ); } } /// Settings notifier class SettingsNotifier extends StateNotifier { SettingsNotifier(this._authApiService) : super(const SettingsState()) { _loadSettingsFromPrefs(); } final AuthApiService _authApiService; SharedPreferences? _prefs; // Keys for shared preferences static const String _audioQualityKey = 'audio_quality'; static const String _downloadOnMobileDataKey = 'download_on_mobile_data'; static const String _showExplicitContentKey = 'show_explicit_content'; static const String _crossfadeEnabledKey = 'crossfade_enabled'; static const String _crossfadeDurationKey = 'crossfade_duration'; static const String _gaplessPlaybackKey = 'gapless_playback'; static const String _normalizeVolumeKey = 'normalize_volume'; /// Initialize shared preferences and load settings Future _loadSettingsFromPrefs() async { _prefs = await SharedPreferences.getInstance(); final audioQualityIndex = _prefs?.getInt(_audioQualityKey) ?? 2; final downloadOnMobileData = _prefs?.getBool(_downloadOnMobileDataKey) ?? false; final showExplicitContent = _prefs?.getBool(_showExplicitContentKey) ?? true; final crossfadeEnabled = _prefs?.getBool(_crossfadeEnabledKey) ?? false; final crossfadeDuration = _prefs?.getDouble(_crossfadeDurationKey) ?? 5.0; final gaplessPlayback = _prefs?.getBool(_gaplessPlaybackKey) ?? true; final normalizeVolume = _prefs?.getBool(_normalizeVolumeKey) ?? false; state = state.copyWith( audioQuality: AudioQuality.values[audioQualityIndex], downloadOnMobileData: downloadOnMobileData, showExplicitContent: showExplicitContent, crossfadeEnabled: crossfadeEnabled, crossfadeDuration: crossfadeDuration, gaplessPlayback: gaplessPlayback, normalizeVolume: normalizeVolume, ); await _calculateCacheSize(); } /// Load user profile from API Future loadSettings() async { state = state.copyWith(isLoading: true, error: null); try { final user = await _authApiService.getCurrentUser(); state = state.copyWith(user: user, isLoading: false); } catch (e) { state = state.copyWith( error: e.toString(), isLoading: false, ); } } /// Update user profile Future updateProfile({ String? displayName, String? avatarUrl, }) async { state = state.copyWith(isLoading: true, error: null); try { final updatedUser = await _authApiService.updateProfile( displayName: displayName, avatarUrl: avatarUrl, ); state = state.copyWith( user: updatedUser, isLoading: false, ); } catch (e) { state = state.copyWith( error: e.toString(), isLoading: false, ); rethrow; } } /// Set audio quality Future setAudioQuality(AudioQuality quality) async { await _prefs?.setInt(_audioQualityKey, quality.index); state = state.copyWith(audioQuality: quality); } /// Toggle download on mobile data Future toggleDownloadOnMobileData(bool value) async { await _prefs?.setBool(_downloadOnMobileDataKey, value); state = state.copyWith(downloadOnMobileData: value); } /// Toggle explicit content Future toggleShowExplicitContent(bool value) async { await _prefs?.setBool(_showExplicitContentKey, value); state = state.copyWith(showExplicitContent: value); } /// Toggle crossfade Future toggleCrossfade(bool value) async { await _prefs?.setBool(_crossfadeEnabledKey, value); state = state.copyWith(crossfadeEnabled: value); } /// Set crossfade duration Future setCrossfadeDuration(double duration) async { await _prefs?.setDouble(_crossfadeDurationKey, duration); state = state.copyWith(crossfadeDuration: duration); } /// Toggle gapless playback Future toggleGaplessPlayback(bool value) async { await _prefs?.setBool(_gaplessPlaybackKey, value); state = state.copyWith(gaplessPlayback: value); } /// Toggle normalize volume Future toggleNormalizeVolume(bool value) async { await _prefs?.setBool(_normalizeVolumeKey, value); state = state.copyWith(normalizeVolume: value); } /// Calculate cache size Future _calculateCacheSize() async { try { final tempDir = await getTemporaryDirectory(); final appDocDir = await getApplicationDocumentsDirectory(); final tempSize = _getFolderSize(tempDir); final docSize = _getFolderSize(appDocDir); final totalSize = tempSize + docSize; final cacheSizeStr = _formatBytes(totalSize); state = state.copyWith(cacheSize: cacheSizeStr); } catch (e) { state = state.copyWith(cacheSize: 'Unknown'); } } /// Get folder size in bytes int _getFolderSize(Directory dir) { int size = 0; try { if (dir.existsSync()) { dir .listSync(recursive: true, followLinks: false) .forEach((FileSystemEntity entity) { if (entity is File) { size += entity.lengthSync(); } }); } } catch (e) { // Ignore errors } return size; } /// Format bytes to human readable string String _formatBytes(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; if (bytes < 1024 * 1024 * 1024) { return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; } return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB'; } /// Clear cache Future clearCache() async { state = state.copyWith(isLoading: true); try { final tempDir = await getTemporaryDirectory(); if (await tempDir.exists()) { await _deleteFolderContents(tempDir); } await _calculateCacheSize(); state = state.copyWith(isLoading: false); } catch (e) { state = state.copyWith( error: e.toString(), isLoading: false, ); rethrow; } } /// Delete folder contents Future _deleteFolderContents(Directory dir) async { try { if (await dir.exists()) { await for (final entity in dir.list()) { if (entity is File) { await entity.delete(); } else if (entity is Directory) { await entity.delete(recursive: true); } } } } catch (e) { // Ignore errors } } } /// Settings provider final settingsProvider = StateNotifierProvider( (ref) { final authApiService = ref.watch(authApiServiceProvider); return SettingsNotifier(authApiService); }, );