mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-23 19:32:29 +02:00
fix(ios): use security-scoped bookmarks for download directory persistence
- Switch iOS bookmark creation from .minimalBookmark to .withSecurityScope - Add .withSecurityScope option when resolving bookmarks - Add downloadDirectoryBookmark field to AppSettings for persisting iOS bookmarks - Resolve bookmark and startAccessingIosBookmark before queue processing - Guarantee stopAccessingIosBookmark cleanup via try/finally - Create bookmark on folder pick in both setup screen and download settings - Clear bookmark when switching to SAF mode or iOS path normalization - Fix stale bottom sheet context usage (ctx -> context) in download settings
This commit is contained in:
@@ -1420,13 +1420,13 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
try {
|
||||
result = await FilePicker.platform.getDirectoryPath();
|
||||
} catch (e) {
|
||||
if (ctx.mounted) {
|
||||
ScaffoldMessenger.of(ctx).showSnackBar(
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
ctx.l10n.snackbarFolderPickerFailed(e.toString()),
|
||||
context.l10n.snackbarFolderPickerFailed(e.toString()),
|
||||
),
|
||||
backgroundColor: Theme.of(ctx).colorScheme.error,
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
@@ -1439,24 +1439,55 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
if (Platform.isIOS) {
|
||||
final validation = validateIosPath(result);
|
||||
if (!validation.isValid) {
|
||||
if (ctx.mounted) {
|
||||
ScaffoldMessenger.of(ctx).showSnackBar(
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
validation.errorReason ??
|
||||
context.l10n.setupIcloudNotSupported,
|
||||
),
|
||||
backgroundColor: Theme.of(ctx).colorScheme.error,
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final bookmark =
|
||||
await PlatformBridge.createIosBookmarkFromPath(result);
|
||||
if (bookmark == null || bookmark.isEmpty) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarFolderPickerFailed(
|
||||
'Could not keep access to the selected folder',
|
||||
),
|
||||
),
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.error,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setDownloadDirectory(result, iosBookmark: bookmark);
|
||||
return;
|
||||
}
|
||||
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setDownloadDirectory(result);
|
||||
} else if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.setupNoFolderSelected)),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -30,6 +30,7 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
bool _storagePermissionGranted = false;
|
||||
bool _notificationPermissionGranted = false;
|
||||
String? _selectedDirectory;
|
||||
String? _selectedDirectoryBookmark;
|
||||
String? _selectedTreeUri;
|
||||
bool _isLoading = false;
|
||||
int _androidSdkVersion = 0;
|
||||
@@ -338,7 +339,10 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
title: Text(context.l10n.setupAppDocumentsFolder),
|
||||
onTap: () async {
|
||||
final dir = await _getDefaultDirectory();
|
||||
setState(() => _selectedDirectory = dir);
|
||||
setState(() {
|
||||
_selectedDirectory = dir;
|
||||
_selectedDirectoryBookmark = null;
|
||||
});
|
||||
if (ctx.mounted) Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
@@ -369,30 +373,62 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
// iOS: Validate the selected path is writable
|
||||
if (Platform.isIOS) {
|
||||
final validation = validateIosPath(result);
|
||||
if (!validation.isValid) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
validation.errorReason ??
|
||||
'Invalid folder selected',
|
||||
),
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.error,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (result == null) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.setupNoFolderSelected),
|
||||
),
|
||||
);
|
||||
}
|
||||
setState(() => _selectedDirectory = result);
|
||||
return;
|
||||
}
|
||||
|
||||
// iOS: Validate the selected path is writable
|
||||
if (Platform.isIOS) {
|
||||
final validation = validateIosPath(result);
|
||||
if (!validation.isValid) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
validation.errorReason ?? 'Invalid folder selected',
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final bookmark =
|
||||
await PlatformBridge.createIosBookmarkFromPath(result);
|
||||
if (bookmark == null || bookmark.isEmpty) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarFolderPickerFailed(
|
||||
'Could not keep access to the selected folder',
|
||||
),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_selectedDirectory = result;
|
||||
_selectedDirectoryBookmark = bookmark;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _selectedDirectory = result);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -426,14 +462,20 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
if (!Platform.isAndroid ||
|
||||
_selectedTreeUri == null ||
|
||||
_selectedTreeUri!.isEmpty) {
|
||||
final dir = Directory(_selectedDirectory!);
|
||||
if (!await dir.exists()) {
|
||||
await dir.create(recursive: true);
|
||||
final iosBookmark = Platform.isIOS ? _selectedDirectoryBookmark : null;
|
||||
if (iosBookmark == null || iosBookmark.isEmpty) {
|
||||
final dir = Directory(_selectedDirectory!);
|
||||
if (!await dir.exists()) {
|
||||
await dir.create(recursive: true);
|
||||
}
|
||||
}
|
||||
ref.read(settingsProvider.notifier).setStorageMode('app');
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setDownloadDirectory(_selectedDirectory!);
|
||||
.setDownloadDirectory(
|
||||
_selectedDirectory!,
|
||||
iosBookmark: iosBookmark,
|
||||
);
|
||||
ref.read(settingsProvider.notifier).setDownloadTreeUri('');
|
||||
} else {
|
||||
ref.read(settingsProvider.notifier).setStorageMode('saf');
|
||||
|
||||
Reference in New Issue
Block a user