fix(ios): block iCloud Drive folder selection

- Detect iCloud path and show error when user tries to select it
- Fallback to app Documents folder if iCloud path detected at runtime
- Add localization string for iCloud not supported error
This commit is contained in:
zarzet
2026-02-01 21:14:45 +07:00
parent 8ace180fa8
commit 4633c7253a
18 changed files with 99 additions and 3 deletions
+1
View File
@@ -8,6 +8,7 @@ Same as 3.3.1 but fixes crash issues caused by FFmpeg.
- **FFmpeg Crash**: Fixed crash issues during M4A to MP3/Opus conversion
- **Service Selection Ignored**: Fixed bug where selecting Qobuz/Amazon from service picker was ignored and always used Tidal instead
- **iOS iCloud Drive Permission Error**: Block iCloud Drive folder selection on iOS (Go backend cannot access iCloud due to sandboxing)
### Changed
+6
View File
@@ -1312,6 +1312,12 @@ abstract class AppLocalizations {
/// **'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.'**
String get setupIosEmptyFolderWarning;
/// Error when user selects iCloud Drive on iOS
///
/// In en, this message translates to:
/// **'iCloud Drive is not supported. Please use the app Documents folder.'**
String get setupIcloudNotSupported;
/// App tagline in setup
///
/// In en, this message translates to:
+4
View File
@@ -698,6 +698,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS-Einschränkung: Leere Ordner können nicht ausgewählt werden. Wählen Sie einen Ordner mit mindestens einer Datei.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Spotify Titel in FLAC herunterladen';
+4
View File
@@ -684,6 +684,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
+4
View File
@@ -684,6 +684,10 @@ class AppLocalizationsEs extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
+4
View File
@@ -684,6 +684,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
+4
View File
@@ -684,6 +684,10 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
+4
View File
@@ -689,6 +689,10 @@ class AppLocalizationsId extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'Batasan iOS: Folder kosong tidak dapat dipilih. Pilih folder dengan minimal satu file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Unduh lagu Spotify dalam format FLAC';
+4
View File
@@ -679,6 +679,10 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Spotify のトラックを FLAC でダウンロード';
+4
View File
@@ -684,6 +684,10 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
+4
View File
@@ -684,6 +684,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
+4
View File
@@ -684,6 +684,10 @@ class AppLocalizationsPt extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
+4
View File
@@ -702,6 +702,10 @@ class AppLocalizationsRu extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'Ограничение iOS: пустые папки не могут быть выбраны. Выберите папку, содержащую хотя бы один файл.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Скачать Spotify треки во FLAC';
+4
View File
@@ -691,6 +691,10 @@ class AppLocalizationsTr extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS\'un sınırlaması: Boş klasörler seçilemiyor. İçinde en az bir dosya bulunan bir klasör seçin.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Spotify şarkılarını FLAC olarak indirin';
+4
View File
@@ -684,6 +684,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
+3 -1
View File
@@ -481,8 +481,10 @@
"@setupChooseFromFiles": {"description": "iOS file picker option"},
"setupChooseFromFilesSubtitle": "Select iCloud or other location",
"@setupChooseFromFilesSubtitle": {"description": "Subtitle for file picker"},
"setupIosEmptyFolderWarning": "iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.",
"setupIosEmptyFolderWarning": "iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.",
"@setupIosEmptyFolderWarning": {"description": "iOS folder selection warning"},
"setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.",
"@setupIcloudNotSupported": {"description": "Error when user selects iCloud Drive on iOS"},
"setupDownloadInFlac": "Download Spotify tracks in FLAC",
"@setupDownloadInFlac": {"description": "App tagline in setup"},
"setupStepStorage": "Storage",
+18 -1
View File
@@ -1564,11 +1564,28 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
if (state.outputDir.isEmpty) {
if (state.outputDir.isEmpty) {
_log.d('Output dir empty, initializing...');
await _initOutputDir();
}
// iOS: Validate that outputDir is writable (not iCloud Drive which Go can't access)
if (Platform.isIOS && state.outputDir.isNotEmpty) {
final isICloudPath = state.outputDir.contains('Mobile Documents') ||
state.outputDir.contains('CloudDocs') ||
state.outputDir.contains('com~apple~CloudDocs');
if (isICloudPath) {
_log.w('iOS: iCloud Drive path detected, falling back to app Documents folder');
_log.w('Go backend cannot write to iCloud Drive due to iOS sandboxing');
final dir = await getApplicationDocumentsDirectory();
final musicDir = Directory('${dir.path}/SpotiFLAC');
if (!await musicDir.exists()) {
await musicDir.create(recursive: true);
}
state = state.copyWith(outputDir: musicDir.path);
}
}
if (state.outputDir.isEmpty) {
_log.d('Using fallback directory...');
final dir = await getApplicationDocumentsDirectory();
@@ -702,7 +702,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
if (ctx.mounted) Navigator.pop(ctx);
},
),
ListTile(
ListTile(
leading: Icon(Icons.cloud, color: colorScheme.onSurfaceVariant),
title: Text(context.l10n.setupChooseFromFiles),
subtitle: Text(context.l10n.setupChooseFromFilesSubtitle),
@@ -711,6 +711,24 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
// Note: iOS requires folder to have at least one file to be selectable
final result = await FilePicker.platform.getDirectoryPath();
if (result != null) {
// iOS: Check if user selected iCloud Drive (not accessible by Go backend)
if (Platform.isIOS) {
final isICloudPath = result.contains('Mobile Documents') ||
result.contains('CloudDocs') ||
result.contains('com~apple~CloudDocs');
if (isICloudPath) {
if (ctx.mounted) {
ScaffoldMessenger.of(ctx).showSnackBar(
SnackBar(
content: Text(context.l10n.setupIcloudNotSupported),
backgroundColor: Theme.of(ctx).colorScheme.error,
duration: const Duration(seconds: 4),
),
);
}
return;
}
}
ref
.read(settingsProvider.notifier)
.setDownloadDirectory(result);