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:
zarzet
2026-07-10 10:12:16 +07:00
parent 53824b7ac2
commit 927fc8abac
22 changed files with 389 additions and 24 deletions
+18
View File
@@ -5335,6 +5335,24 @@ abstract class AppLocalizations {
/// **'Pick any folder, including SD card'**
String get storageModeSafSubtitle;
/// Title of the warning banner shown in Files settings when the saved SAF folder grant is no longer valid
///
/// In en, this message translates to:
/// **'Download folder access lost'**
String get downloadFolderAccessLostTitle;
/// Subtitle of the warning banner shown when the saved SAF folder grant is no longer valid
///
/// In en, this message translates to:
/// **'Downloads will fail until you re-select the folder'**
String get downloadFolderAccessLostSubtitle;
/// Button that reopens the folder picker to restore download folder access
///
/// In en, this message translates to:
/// **'Re-select folder'**
String get downloadFolderReselect;
/// Description shown in filename format editor
///
/// In en, this message translates to:
+10
View File
@@ -3129,6 +3129,16 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3169,6 +3169,16 @@ class AppLocalizationsDe extends AppLocalizations {
String get storageModeSafSubtitle =>
'Wähle einen beliebigen Ordner, inklusive SD-Karte';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3129,6 +3129,16 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3129,6 +3129,16 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3213,6 +3213,16 @@ class AppLocalizationsFr extends AppLocalizations {
String get storageModeSafSubtitle =>
'Choisissez n\'importe quel dossier, y compris la carte SD';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3129,6 +3129,16 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3112,6 +3112,16 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3116,6 +3116,16 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3114,6 +3114,16 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3129,6 +3129,16 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3129,6 +3129,16 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3184,6 +3184,16 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3160,6 +3160,16 @@ class AppLocalizationsTr extends AppLocalizations {
String get storageModeSafSubtitle =>
'SD kart dahil herhangi bir klasörü seçin';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3176,6 +3176,16 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+10
View File
@@ -3129,6 +3129,16 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get storageModeSafSubtitle => 'Pick any folder, including SD card';
@override
String get downloadFolderAccessLostTitle => 'Download folder access lost';
@override
String get downloadFolderAccessLostSubtitle =>
'Downloads will fail until you re-select the folder';
@override
String get downloadFolderReselect => 'Re-select folder';
@override
String downloadFilenameDescription(
Object album,
+12
View File
@@ -4110,6 +4110,18 @@
"@storageModeSafSubtitle": {
"description": "Subtitle for SAF storage mode"
},
"downloadFolderAccessLostTitle": "Download folder access lost",
"@downloadFolderAccessLostTitle": {
"description": "Title of the warning banner shown in Files settings when the saved SAF folder grant is no longer valid"
},
"downloadFolderAccessLostSubtitle": "Downloads will fail until you re-select the folder",
"@downloadFolderAccessLostSubtitle": {
"description": "Subtitle of the warning banner shown when the saved SAF folder grant is no longer valid"
},
"downloadFolderReselect": "Re-select folder",
"@downloadFolderReselect": {
"description": "Button that reopens the folder picker to restore download folder access"
},
"downloadFilenameDescription": "Use {artist}, {title}, {album}, {track}, {year}, {date}, {disc} as placeholders.",
"@downloadFilenameDescription": {
"description": "Description shown in filename format editor"