mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-24 11:52:40 +02:00
feat(downloads): detect lost SAF grant and offer folder re-selection
A persisted SAF tree grant can die silently (folder deleted or renamed, SD card removed, app data reset, system revocation). Downloads then fail with no recovery path beyond digging through Settings. - Add isSafTreeAccessible bridge method: the grant must still be in the system's persisted permission list and the tree writable - Show a warning banner in Files & Folders when the saved SAF folder is no longer accessible, with an inline re-select action - In the download error dialog, replace Retry with a Re-select folder action when the failure is a lost folder grant (Android SAF or iOS bookmark); a successful re-pick retries the item automatically - Share the folder-access error messages as constants so the UI can match on them reliably
This commit is contained in:
@@ -3412,6 +3412,9 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
) async {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isRateLimit = item.errorType == DownloadErrorType.rateLimit;
|
||||
final isFolderAccessLost =
|
||||
item.errorMessage == safPermissionLostErrorMessage ||
|
||||
item.errorMessage == downloadFolderAccessLostErrorMessage;
|
||||
final title = isRateLimit
|
||||
? context.l10n.queueRateLimitTitle
|
||||
: context.l10n.updateDownloadFailed;
|
||||
@@ -3453,10 +3456,16 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
child: Text(context.l10n.dialogCancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(ctx).pop('retry'),
|
||||
child: Text(context.l10n.dialogRetry),
|
||||
),
|
||||
if (isFolderAccessLost)
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(ctx).pop('reselect'),
|
||||
child: Text(context.l10n.downloadFolderReselect),
|
||||
)
|
||||
else
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(ctx).pop('retry'),
|
||||
child: Text(context.l10n.dialogRetry),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -3465,9 +3474,69 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
ref.read(downloadQueueProvider.notifier).retryItem(item.id);
|
||||
} else if (action == 'remove') {
|
||||
ref.read(downloadQueueProvider.notifier).removeItem(item.id);
|
||||
} else if (action == 'reselect') {
|
||||
final reselected = await _reselectDownloadFolder();
|
||||
if (reselected && mounted) {
|
||||
ref.read(downloadQueueProvider.notifier).retryItem(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reopen the platform folder picker to restore download folder access,
|
||||
/// then persist the new location. Returns true when a folder was saved.
|
||||
Future<bool> _reselectDownloadFolder() async {
|
||||
if (Platform.isAndroid) {
|
||||
final result = await PlatformBridge.pickSafTree();
|
||||
if (result == null) return false;
|
||||
final treeUri = result['tree_uri'] as String? ?? '';
|
||||
final displayName = result['display_name'] as String? ?? '';
|
||||
if (treeUri.isEmpty) return false;
|
||||
final notifier = ref.read(settingsProvider.notifier);
|
||||
notifier.setStorageMode('saf');
|
||||
notifier.setDownloadTreeUri(
|
||||
treeUri,
|
||||
displayName: displayName.isNotEmpty ? displayName : treeUri,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if (Platform.isIOS) {
|
||||
IosPickedDirectory? picked;
|
||||
try {
|
||||
picked = await PlatformBridge.pickIosDirectory();
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarFolderPickerFailed(e.toString()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (picked == null) return false;
|
||||
final validation = validateIosPath(picked.path);
|
||||
if (!validation.isValid) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
validation.errorReason ?? context.l10n.setupIcloudNotSupported,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setDownloadDirectory(picked.path, iosBookmark: picked.bookmark);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Widget _buildDownloadGridItem(
|
||||
BuildContext context,
|
||||
DownloadItem item,
|
||||
|
||||
Reference in New Issue
Block a user