mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-02 09:08:35 +02:00
feat(queue): offer one-tap retry when connectivity returns
The connectivity listener now stays alive while network-failed items remain and, on reconnect, shows a debounced snackbar counting them with a Retry action wired to retryAllFailed scoped to network failures. Also regenerates localizations for the recent feature strings.
This commit is contained in:
@@ -1270,6 +1270,48 @@ abstract class AppLocalizations {
|
||||
/// **'No platform links found for this track.'**
|
||||
String get trackOpenOnNoLinks;
|
||||
|
||||
/// Library settings row opening the duplicate review sheet
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Review duplicates'**
|
||||
String get libraryReviewDuplicates;
|
||||
|
||||
/// Subtitle for the duplicate review settings row
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Find tracks stored more than once'**
|
||||
String get libraryReviewDuplicatesSubtitle;
|
||||
|
||||
/// Title of the duplicate review sheet
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Duplicates'**
|
||||
String get duplicatesTitle;
|
||||
|
||||
/// Shown when the duplicate review sheet finds nothing
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No duplicate tracks found.'**
|
||||
String get duplicatesEmpty;
|
||||
|
||||
/// Button that deletes all but the highest-quality copy in a duplicate group
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Keep best'**
|
||||
String get duplicatesKeepBest;
|
||||
|
||||
/// Confirmation message for the keep-best action
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete {count} lower-quality copies of \"{trackName}\"?'**
|
||||
String duplicatesKeepBestMessage(int count, String trackName);
|
||||
|
||||
/// Confirmation message for deleting one duplicate copy
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete this copy of \"{trackName}\"?'**
|
||||
String duplicatesDeleteCopyMessage(String trackName);
|
||||
|
||||
/// Snackbar - track added to download queue
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -6252,6 +6294,30 @@ abstract class AppLocalizations {
|
||||
/// **'Download next'**
|
||||
String get queueDownloadNext;
|
||||
|
||||
/// Repeat toggle tooltip when repeat is disabled
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Repeat off'**
|
||||
String get nowPlayingRepeatOff;
|
||||
|
||||
/// Repeat toggle tooltip when the whole queue repeats
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Repeat all'**
|
||||
String get nowPlayingRepeatAll;
|
||||
|
||||
/// Repeat toggle tooltip when the current track repeats
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Repeat one'**
|
||||
String get nowPlayingRepeatOne;
|
||||
|
||||
/// Snackbar shown when connectivity returns and network-failed downloads can be retried
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{count} downloads failed while offline'**
|
||||
String queueNetworkFailedOffline(int count);
|
||||
|
||||
/// Accessibility label when a downloaded file is missing from disk
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -6840,12 +6906,6 @@ abstract class AppLocalizations {
|
||||
/// **'Android background service for extension downloads'**
|
||||
String get downloadNativeWorkerSubtitle;
|
||||
|
||||
/// Badge label for beta features
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'BETA'**
|
||||
String get badgeBeta;
|
||||
|
||||
/// Extension detail section header for service status
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -659,6 +659,32 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return '\"$trackName\" hinzugefügt';
|
||||
@@ -3808,6 +3834,20 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => 'Heruntergeladene Datei fehlt';
|
||||
|
||||
@@ -4220,9 +4260,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Android-Hintergrunddienst für Downloads von Erweiterungen';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Dienststatus';
|
||||
|
||||
|
||||
@@ -651,6 +651,32 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return 'Added \"$trackName\" to queue';
|
||||
@@ -3762,6 +3788,20 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => 'Downloaded file missing';
|
||||
|
||||
@@ -4175,9 +4215,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Android background service for extension downloads';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Service Status';
|
||||
|
||||
|
||||
@@ -651,6 +651,32 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return 'Added \"$trackName\" to queue';
|
||||
@@ -3756,6 +3782,20 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => 'Downloaded file missing';
|
||||
|
||||
@@ -4169,9 +4209,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Android background service for extension downloads';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Service Status';
|
||||
|
||||
@@ -8875,9 +8912,6 @@ class AppLocalizationsEsEs extends AppLocalizationsEs {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Servicio de Android en segundo plano para descargas de extensiones';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Estado del servicio';
|
||||
|
||||
|
||||
@@ -667,6 +667,32 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return 'Ajout de « $trackName » à la file d\'attente';
|
||||
@@ -3863,6 +3889,20 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => 'Fichier téléchargé manquant';
|
||||
|
||||
@@ -4286,9 +4326,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Service Android en arrière-plan pour les téléchargements d\'extensions';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BÊTA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'État du service';
|
||||
|
||||
|
||||
@@ -653,6 +653,32 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return 'Menambahkan \"$trackName\" ke antrian';
|
||||
@@ -3765,6 +3791,20 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => 'Downloaded file missing';
|
||||
|
||||
@@ -4179,9 +4219,6 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Layanan latar belakang Android untuk unduhan ekstensi';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Service Status';
|
||||
|
||||
|
||||
@@ -648,6 +648,32 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return '「$trackName」をキューに追加しました';
|
||||
@@ -3751,6 +3777,20 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => 'Downloaded file missing';
|
||||
|
||||
@@ -4164,9 +4204,6 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'拡張機能のダウンロード用 Android バックグラウンドサービス';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Service Status';
|
||||
|
||||
|
||||
@@ -635,6 +635,32 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return '\'\'$trackName\'\'가 다운로드 목록에 추가됨';
|
||||
@@ -3652,6 +3678,20 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => '다운로드된 파일이 없음';
|
||||
|
||||
@@ -4051,9 +4091,6 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'확장 프로그램 다운로드를 위한 Android 백그라운드 서비스';
|
||||
|
||||
@override
|
||||
String get badgeBeta => '베타';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => '서비스 상태';
|
||||
|
||||
|
||||
@@ -651,6 +651,32 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return 'Added \"$trackName\" to queue';
|
||||
@@ -3756,6 +3782,20 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => 'Downloaded file missing';
|
||||
|
||||
@@ -4169,9 +4209,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Android background service for extension downloads';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Service Status';
|
||||
|
||||
@@ -8840,9 +8877,6 @@ class AppLocalizationsPtPt extends AppLocalizationsPt {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Serviço Android em segundo plano para transferências de extensões';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Service Status';
|
||||
|
||||
|
||||
@@ -655,6 +655,32 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return '\"$trackName\" добавлен в очередь';
|
||||
@@ -3793,6 +3819,20 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => 'Downloaded file missing';
|
||||
|
||||
@@ -4206,9 +4246,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Фоновая служба Android для загрузок через расширения';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Service Status';
|
||||
|
||||
|
||||
@@ -657,6 +657,32 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return '\"$trackName\" kuyruğa eklendi';
|
||||
@@ -3792,6 +3818,20 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => 'Downloaded file missing';
|
||||
|
||||
@@ -4205,9 +4245,6 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Uzantı indirmeleri için Android arka plan hizmeti';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Service Status';
|
||||
|
||||
|
||||
@@ -658,6 +658,32 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get trackOpenOnNoLinks => 'No platform links found for this track.';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicates => 'Review duplicates';
|
||||
|
||||
@override
|
||||
String get libraryReviewDuplicatesSubtitle =>
|
||||
'Find tracks stored more than once';
|
||||
|
||||
@override
|
||||
String get duplicatesTitle => 'Duplicates';
|
||||
|
||||
@override
|
||||
String get duplicatesEmpty => 'No duplicate tracks found.';
|
||||
|
||||
@override
|
||||
String get duplicatesKeepBest => 'Keep best';
|
||||
|
||||
@override
|
||||
String duplicatesKeepBestMessage(int count, String trackName) {
|
||||
return 'Delete $count lower-quality copies of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String duplicatesDeleteCopyMessage(String trackName) {
|
||||
return 'Delete this copy of \"$trackName\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String snackbarAddedToQueue(String trackName) {
|
||||
return 'Додано \"$trackName\" до черги';
|
||||
@@ -3810,6 +3836,20 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get queueDownloadNext => 'Download next';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOff => 'Repeat off';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatAll => 'Repeat all';
|
||||
|
||||
@override
|
||||
String get nowPlayingRepeatOne => 'Repeat one';
|
||||
|
||||
@override
|
||||
String queueNetworkFailedOffline(int count) {
|
||||
return '$count downloads failed while offline';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedFileMissing => 'Downloaded file missing';
|
||||
|
||||
@@ -4223,9 +4263,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
String get downloadNativeWorkerSubtitle =>
|
||||
'Фонова служба Android для завантажень через розширення';
|
||||
|
||||
@override
|
||||
String get badgeBeta => 'BETA';
|
||||
|
||||
@override
|
||||
String get extensionServiceStatus => 'Service Status';
|
||||
|
||||
|
||||
@@ -4922,6 +4922,27 @@
|
||||
"@queueDownloadNext": {
|
||||
"description": "Tooltip on a queued download row; moves the item to the front of the queue so the next free slot downloads it"
|
||||
},
|
||||
"nowPlayingRepeatOff": "Repeat off",
|
||||
"@nowPlayingRepeatOff": {
|
||||
"description": "Repeat toggle tooltip when repeat is disabled"
|
||||
},
|
||||
"nowPlayingRepeatAll": "Repeat all",
|
||||
"@nowPlayingRepeatAll": {
|
||||
"description": "Repeat toggle tooltip when the whole queue repeats"
|
||||
},
|
||||
"nowPlayingRepeatOne": "Repeat one",
|
||||
"@nowPlayingRepeatOne": {
|
||||
"description": "Repeat toggle tooltip when the current track repeats"
|
||||
},
|
||||
"queueNetworkFailedOffline": "{count} downloads failed while offline",
|
||||
"@queueNetworkFailedOffline": {
|
||||
"description": "Snackbar shown when connectivity returns and network-failed downloads can be retried",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"queueDownloadedFileMissing": "Downloaded file missing",
|
||||
"@queueDownloadedFileMissing": {
|
||||
"description": "Accessibility label when a downloaded file is missing from disk"
|
||||
|
||||
@@ -2,14 +2,18 @@ import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart'
|
||||
show ScaffoldMessenger, SnackBar, SnackBarAction, Text;
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/models/download_item.dart';
|
||||
import 'package:spotiflac_android/models/settings.dart';
|
||||
import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/services/app_navigation_service.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/providers/download_verification_retry_guard.dart';
|
||||
@@ -159,6 +163,9 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
bool _networkPausedByWifiOnly = false;
|
||||
List<ConnectivityResult>? _lastConnectivityResults;
|
||||
DateTime _lastConnectionCleanupAt = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
DateTime _lastReconnectRetryPromptAt = DateTime.fromMillisecondsSinceEpoch(
|
||||
0,
|
||||
);
|
||||
static const _connectionCleanupDebounce = Duration(seconds: 2);
|
||||
String? _lastServiceTrackName;
|
||||
String? _lastServiceArtistName;
|
||||
@@ -1581,12 +1588,14 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
}
|
||||
}
|
||||
|
||||
void retryAllFailed() {
|
||||
void retryAllFailed({bool networkOnly = false}) {
|
||||
final failedIds = state.items
|
||||
.where(
|
||||
(item) =>
|
||||
item.status == DownloadStatus.failed ||
|
||||
item.status == DownloadStatus.skipped,
|
||||
(item.status == DownloadStatus.failed ||
|
||||
item.status == DownloadStatus.skipped) &&
|
||||
(!networkOnly ||
|
||||
item.errorType == DownloadErrorType.network),
|
||||
)
|
||||
.map((item) => item.id)
|
||||
.toSet();
|
||||
@@ -1817,11 +1826,53 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
}
|
||||
|
||||
void _stopConnectivityMonitoring({bool clearNetworkPause = true}) {
|
||||
_connectivitySub?.cancel();
|
||||
_connectivitySub = null;
|
||||
if (clearNetworkPause) {
|
||||
_networkPausedByWifiOnly = false;
|
||||
}
|
||||
// Keep listening while network-failed items remain so the reconnect
|
||||
// retry prompt can still fire when the queue is otherwise idle.
|
||||
if (_hasNetworkFailedItems) return;
|
||||
_connectivitySub?.cancel();
|
||||
_connectivitySub = null;
|
||||
}
|
||||
|
||||
bool get _hasNetworkFailedItems => state.items.any(
|
||||
(item) =>
|
||||
item.status == DownloadStatus.failed &&
|
||||
item.errorType == DownloadErrorType.network,
|
||||
);
|
||||
|
||||
/// Offers a one-tap retry for network-failed items once connectivity
|
||||
/// returns, debounced so network flapping doesn't spam snackbars.
|
||||
void _maybeOfferRetryAfterReconnect(List<ConnectivityResult> results) {
|
||||
if (results.every((result) => result == ConnectivityResult.none)) return;
|
||||
final failedCount = state.items
|
||||
.where(
|
||||
(item) =>
|
||||
item.status == DownloadStatus.failed &&
|
||||
item.errorType == DownloadErrorType.network,
|
||||
)
|
||||
.length;
|
||||
if (failedCount == 0) return;
|
||||
final now = DateTime.now();
|
||||
if (now.difference(_lastReconnectRetryPromptAt) <
|
||||
const Duration(minutes: 1)) {
|
||||
return;
|
||||
}
|
||||
_lastReconnectRetryPromptAt = now;
|
||||
|
||||
final context = AppNavigationService.rootNavigatorKey.currentContext;
|
||||
if (context == null || !context.mounted) return;
|
||||
final l10n = context.l10n;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.queueNetworkFailedOffline(failedCount)),
|
||||
action: SnackBarAction(
|
||||
label: l10n.dialogRetry,
|
||||
onPressed: () => retryAllFailed(networkOnly: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleDownloadNetworkModeChanged(String mode) {
|
||||
@@ -1883,6 +1934,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
|
||||
void _handleConnectivityResults(List<ConnectivityResult> results) {
|
||||
_maybeCleanupOnNetworkChange(results);
|
||||
_maybeOfferRetryAfterReconnect(results);
|
||||
|
||||
final settings = ref.read(settingsProvider);
|
||||
if (settings.downloadNetworkMode != 'wifi_only') return;
|
||||
|
||||
Reference in New Issue
Block a user