diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index ce581bab..a9d694ce 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3957,6 +3957,48 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Failed to fetch some albums'** String get discographyFailedToFetch; + + /// Section header for storage access settings + /// + /// In en, this message translates to: + /// **'Storage Access'** + String get sectionStorageAccess; + + /// Toggle for MANAGE_EXTERNAL_STORAGE permission + /// + /// In en, this message translates to: + /// **'All Files Access'** + String get allFilesAccess; + + /// Subtitle when all files access is enabled + /// + /// In en, this message translates to: + /// **'Can write to any folder'** + String get allFilesAccessEnabledSubtitle; + + /// Subtitle when all files access is disabled + /// + /// In en, this message translates to: + /// **'Limited to media folders only'** + String get allFilesAccessDisabledSubtitle; + + /// Description explaining when to enable all files access + /// + /// In en, this message translates to: + /// **'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'** + String get allFilesAccessDescription; + + /// Message when permission is permanently denied + /// + /// In en, this message translates to: + /// **'Permission was denied. Please enable \'All files access\' manually in system settings.'** + String get allFilesAccessDeniedMessage; + + /// Snackbar message when user disables all files access + /// + /// In en, this message translates to: + /// **'All Files Access disabled. The app will use limited storage access.'** + String get allFilesAccessDisabledMessage; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 452e2feb..0ef84182 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2199,4 +2199,28 @@ class AppLocalizationsDe extends AppLocalizations { @override String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 3a0882f4..4d4b9fd6 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2184,4 +2184,28 @@ class AppLocalizationsEn extends AppLocalizations { @override String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 7995e85f..0779ebfa 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -2184,6 +2184,30 @@ class AppLocalizationsEs extends AppLocalizations { @override String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } /// The translations for Spanish Castilian, as used in Spain (`es_ES`). diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a68da436..dee4eb55 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2184,4 +2184,28 @@ class AppLocalizationsFr extends AppLocalizations { @override String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index a9288f89..93b0dd82 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -2184,4 +2184,28 @@ class AppLocalizationsHi extends AppLocalizations { @override String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 543ab346..c797b701 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -2197,4 +2197,28 @@ class AppLocalizationsId extends AppLocalizations { @override String get discographyFailedToFetch => 'Gagal mengambil beberapa album'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 753e128a..3b2c833e 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -2170,4 +2170,28 @@ class AppLocalizationsJa extends AppLocalizations { @override String get discographyFailedToFetch => '一部のアルバムの取得に失敗しました'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 0ed381c7..d257a8de 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -2184,4 +2184,28 @@ class AppLocalizationsKo extends AppLocalizations { @override String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index c9f498ed..600ab6c2 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -2184,4 +2184,28 @@ class AppLocalizationsNl extends AppLocalizations { @override String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 875d6862..941fa8b7 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -2184,6 +2184,30 @@ class AppLocalizationsPt extends AppLocalizations { @override String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } /// The translations for Portuguese, as used in Portugal (`pt_PT`). diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 9b501c89..f9cfd3bd 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -2230,4 +2230,28 @@ class AppLocalizationsRu extends AppLocalizations { @override String get discographyFailedToFetch => 'Не удалось получить некоторые альбомы'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index d2acbe1a..5880f0ee 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -2199,4 +2199,28 @@ class AppLocalizationsTr extends AppLocalizations { @override String get discographyFailedToFetch => 'Bazı albümler alınamadı'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 82862b02..3083e987 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -2184,6 +2184,30 @@ class AppLocalizationsZh extends AppLocalizations { @override String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; } /// The translations for Chinese, as used in China (`zh_CN`). diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index b9f99a5f..d93e464f 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1646,5 +1646,20 @@ "discographyNoAlbums": "No albums available", "@discographyNoAlbums": {"description": "Error - no albums found for artist"}, "discographyFailedToFetch": "Failed to fetch some albums", - "@discographyFailedToFetch": {"description": "Error - some albums failed to load"} + "@discographyFailedToFetch": {"description": "Error - some albums failed to load"}, + + "sectionStorageAccess": "Storage Access", + "@sectionStorageAccess": {"description": "Section header for storage access settings"}, + "allFilesAccess": "All Files Access", + "@allFilesAccess": {"description": "Toggle for MANAGE_EXTERNAL_STORAGE permission"}, + "allFilesAccessEnabledSubtitle": "Can write to any folder", + "@allFilesAccessEnabledSubtitle": {"description": "Subtitle when all files access is enabled"}, + "allFilesAccessDisabledSubtitle": "Limited to media folders only", + "@allFilesAccessDisabledSubtitle": {"description": "Subtitle when all files access is disabled"}, + "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", + "@allFilesAccessDescription": {"description": "Description explaining when to enable all files access"}, + "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", + "@allFilesAccessDeniedMessage": {"description": "Message when permission is permanently denied"}, + "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", + "@allFilesAccessDisabledMessage": {"description": "Snackbar message when user disables all files access"} } diff --git a/lib/models/settings.dart b/lib/models/settings.dart index f90e276d..93b44cf3 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -35,6 +35,7 @@ class AppSettings { final String lossyFormat; final String lossyBitrate; // e.g., 'mp3_320', 'mp3_256', 'mp3_192', 'mp3_128', 'opus_128', 'opus_96', 'opus_64' final String lyricsMode; + final bool useAllFilesAccess; // Android 13+ only: enable MANAGE_EXTERNAL_STORAGE const AppSettings({ this.defaultService = 'tidal', @@ -68,6 +69,7 @@ class AppSettings { this.lossyFormat = 'mp3', this.lossyBitrate = 'mp3_320', this.lyricsMode = 'embed', + this.useAllFilesAccess = false, }); AppSettings copyWith({ @@ -103,6 +105,7 @@ class AppSettings { String? lossyFormat, String? lossyBitrate, String? lyricsMode, + bool? useAllFilesAccess, }) { return AppSettings( defaultService: defaultService ?? this.defaultService, @@ -136,6 +139,7 @@ class AppSettings { lossyFormat: lossyFormat ?? this.lossyFormat, lossyBitrate: lossyBitrate ?? this.lossyBitrate, lyricsMode: lyricsMode ?? this.lyricsMode, + useAllFilesAccess: useAllFilesAccess ?? this.useAllFilesAccess, ); } diff --git a/lib/models/settings.g.dart b/lib/models/settings.g.dart index ba3b7f05..327eec5d 100644 --- a/lib/models/settings.g.dart +++ b/lib/models/settings.g.dart @@ -40,6 +40,7 @@ AppSettings _$AppSettingsFromJson(Map json) => AppSettings( lossyFormat: json['lossyFormat'] as String? ?? 'mp3', lossyBitrate: json['lossyBitrate'] as String? ?? 'mp3_320', lyricsMode: json['lyricsMode'] as String? ?? 'embed', + useAllFilesAccess: json['useAllFilesAccess'] as bool? ?? false, ); Map _$AppSettingsToJson(AppSettings instance) => @@ -75,4 +76,5 @@ Map _$AppSettingsToJson(AppSettings instance) => 'lossyFormat': instance.lossyFormat, 'lossyBitrate': instance.lossyBitrate, 'lyricsMode': instance.lyricsMode, + 'useAllFilesAccess': instance.useAllFilesAccess, }; diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index eb5d118a..93b60f67 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -251,6 +251,11 @@ class SettingsNotifier extends Notifier { state = state.copyWith(lossyBitrate: bitrate, lossyFormat: format); _saveSettings(); } + + void setUseAllFilesAccess(bool enabled) { + state = state.copyWith(useAllFilesAccess: enabled); + _saveSettings(); + } } final settingsProvider = NotifierProvider( diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index 9f45194e..4cc2e501 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -3,18 +3,92 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:file_picker/file_picker.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/providers/extension_provider.dart'; import 'package:spotiflac_android/widgets/settings_group.dart'; -class DownloadSettingsPage extends ConsumerWidget { +class DownloadSettingsPage extends ConsumerStatefulWidget { const DownloadSettingsPage({super.key}); - - static const _builtInServices = ['tidal', 'qobuz', 'amazon']; @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => _DownloadSettingsPageState(); +} + +class _DownloadSettingsPageState extends ConsumerState { + static const _builtInServices = ['tidal', 'qobuz', 'amazon']; + int _androidSdkVersion = 0; + bool _hasAllFilesAccess = false; + + @override + void initState() { + super.initState(); + _initDeviceInfo(); + } + + Future _initDeviceInfo() async { + if (Platform.isAndroid) { + final deviceInfo = DeviceInfoPlugin(); + final androidInfo = await deviceInfo.androidInfo; + final sdkVersion = androidInfo.version.sdkInt; + final hasAccess = await Permission.manageExternalStorage.isGranted; + if (mounted) { + setState(() { + _androidSdkVersion = sdkVersion; + _hasAllFilesAccess = hasAccess; + }); + } + } + } + + Future _requestAllFilesAccess() async { + final status = await Permission.manageExternalStorage.request(); + if (status.isGranted) { + ref.read(settingsProvider.notifier).setUseAllFilesAccess(true); + if (mounted) { + setState(() => _hasAllFilesAccess = true); + } + } else if (status.isPermanentlyDenied) { + if (mounted) { + final shouldOpen = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.l10n.setupStorageAccessRequired), + content: Text(context.l10n.allFilesAccessDeniedMessage), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(context.l10n.dialogCancel), + ), + FilledButton( + onPressed: () => Navigator.pop(context, true), + child: Text(context.l10n.setupOpenSettings), + ), + ], + ), + ); + if (shouldOpen == true) { + await openAppSettings(); + } + } + } + } + + Future _disableAllFilesAccess() async { + ref.read(settingsProvider.notifier).setUseAllFilesAccess(false); + // Note: We can't revoke the permission programmatically, + // but we can stop using it in the app + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.allFilesAccessDisabledMessage)), + ); + } + } + + @override + Widget build(BuildContext context) { final settings = ref.watch(settingsProvider); final colorScheme = Theme.of(context).colorScheme; final topPadding = MediaQuery.of(context).padding.top; @@ -270,6 +344,59 @@ class DownloadSettingsPage extends ConsumerWidget { ), ), + // All Files Access section (Android 13+ only) + if (Platform.isAndroid && _androidSdkVersion >= 33) ...[ + SliverToBoxAdapter( + child: SettingsSectionHeader(title: context.l10n.sectionStorageAccess), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsSwitchItem( + icon: Icons.folder_special_outlined, + title: context.l10n.allFilesAccess, + subtitle: _hasAllFilesAccess + ? context.l10n.allFilesAccessEnabledSubtitle + : context.l10n.allFilesAccessDisabledSubtitle, + value: _hasAllFilesAccess && settings.useAllFilesAccess, + onChanged: (value) { + if (value) { + _requestAllFilesAccess(); + } else { + _disableAllFilesAccess(); + } + }, + showDivider: false, + ), + ], + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.info_outline, + size: 16, + color: colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + context.l10n.allFilesAccessDescription, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ), + ], + ), + ), + ), + ], + const SliverToBoxAdapter(child: SizedBox(height: 32)), ], ), diff --git a/lib/screens/setup_screen.dart b/lib/screens/setup_screen.dart index 7c9036db..0a80e0bd 100644 --- a/lib/screens/setup_screen.dart +++ b/lib/screens/setup_screen.dart @@ -67,10 +67,11 @@ class _SetupScreenState extends ConsumerState { bool storageGranted = false; if (_androidSdkVersion >= 33) { - final manageStatus = await Permission.manageExternalStorage.status; + // Android 13+: Only require READ_MEDIA_AUDIO by default + // MANAGE_EXTERNAL_STORAGE is optional and can be enabled in settings final audioStatus = await Permission.audio.status; - debugPrint('[Permission] Android 13+ check: MANAGE_EXTERNAL_STORAGE=$manageStatus, READ_MEDIA_AUDIO=$audioStatus'); - storageGranted = manageStatus.isGranted && audioStatus.isGranted; + debugPrint('[Permission] Android 13+ check: READ_MEDIA_AUDIO=$audioStatus'); + storageGranted = audioStatus.isGranted; } else if (_androidSdkVersion >= 30) { final manageStatus = await Permission.manageExternalStorage.status; debugPrint('[Permission] Android 11-12 check: MANAGE_EXTERNAL_STORAGE=$manageStatus'); @@ -108,44 +109,20 @@ class _SetupScreenState extends ConsumerState { bool allGranted = false; if (_androidSdkVersion >= 33) { - var manageStatus = await Permission.manageExternalStorage.status; - if (!manageStatus.isGranted) { - if (mounted) { - final shouldOpen = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(context.l10n.setupStorageAccessRequired), - content: Text( - '${context.l10n.setupStorageAccessMessage}\n\n' - '${context.l10n.setupAllowAccessToManageFiles}', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: Text(context.l10n.dialogCancel), - ), - FilledButton( - onPressed: () => Navigator.pop(context, true), - child: Text(context.l10n.setupOpenSettings), - ), - ], - ), - ); - - if (shouldOpen == true) { - await Permission.manageExternalStorage.request(); - await Future.delayed(const Duration(milliseconds: 500)); - manageStatus = await Permission.manageExternalStorage.status; - } - } - } - + // Android 13+: Only request READ_MEDIA_AUDIO by default + // MANAGE_EXTERNAL_STORAGE is optional (can be enabled in Settings) var audioStatus = await Permission.audio.status; - if (!audioStatus.isGranted && manageStatus.isGranted) { + if (!audioStatus.isGranted) { audioStatus = await Permission.audio.request(); } - allGranted = manageStatus.isGranted && audioStatus.isGranted; + allGranted = audioStatus.isGranted; + + if (audioStatus.isPermanentlyDenied) { + _showPermissionDeniedDialog('Audio'); + setState(() => _isLoading = false); + return; + } } else if (_androidSdkVersion >= 30) { var manageStatus = await Permission.manageExternalStorage.status;