mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-04 09:10:48 +02:00
feat: rebuild cross-extension sharing and queue controls
Co-authored-by: Amonoman <musaauron87@gmail.com>
This commit is contained in:
@@ -6994,6 +6994,84 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Releases'**
|
||||
String get artistReleases;
|
||||
|
||||
/// Button to clear selected fields for auto-fill
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'None'**
|
||||
String get editMetadataSelectNone;
|
||||
|
||||
/// Button to retry every failed download in the queue
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Retry {count} failed'**
|
||||
String queueRetryAllFailed(int count);
|
||||
|
||||
/// Settings switch title for storing completed downloads in history
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save download history'**
|
||||
String get settingsSaveDownloadHistory;
|
||||
|
||||
/// Settings switch subtitle for storing completed downloads in history
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Keep completed downloads in history and library views'**
|
||||
String get settingsSaveDownloadHistorySubtitle;
|
||||
|
||||
/// Confirmation dialog title shown before disabling download history
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Turn off download history?'**
|
||||
String get dialogDisableHistoryTitle;
|
||||
|
||||
/// Confirmation dialog message shown before disabling download history
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Existing history will be cleared. Downloaded files will not be deleted.'**
|
||||
String get dialogDisableHistoryMessage;
|
||||
|
||||
/// Confirmation action to disable download history and clear existing entries
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Turn off and clear'**
|
||||
String get dialogDisableAndClear;
|
||||
|
||||
/// Title and tooltip for finding the current collection in other services
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open in other services'**
|
||||
String get openInOtherServices;
|
||||
|
||||
/// Empty state when no extensions can be searched for cross-service links
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No other compatible services'**
|
||||
String get shareSheetNoExtensions;
|
||||
|
||||
/// Cross-service share sheet row subtitle when a service has no match
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not found'**
|
||||
String get shareSheetNotFound;
|
||||
|
||||
/// Tooltip for copying a cross-service link
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Copy link'**
|
||||
String get shareSheetCopyLink;
|
||||
|
||||
/// Snackbar after copying a cross-service link
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{service} link copied'**
|
||||
String shareSheetLinkCopied(Object service);
|
||||
|
||||
/// Tooltip for opening a cross-service link inside the app
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open'**
|
||||
String get shareSheetOpen;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -4229,4 +4229,49 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
@@ -4200,4 +4200,49 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
@@ -4194,6 +4194,51 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
/// The translations for Spanish Castilian, as used in Spain (`es_ES`).
|
||||
|
||||
@@ -4198,4 +4198,49 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
@@ -4195,4 +4195,49 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
@@ -4186,4 +4186,49 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'Tidak ada';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Coba ulang $count gagal';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Simpan riwayat unduhan';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Simpan unduhan selesai di riwayat dan tampilan pustaka';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Matikan riwayat unduhan?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Riwayat yang ada akan dihapus. File unduhan tidak akan dihapus.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Matikan dan hapus';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Buka di layanan lain';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'Tidak ada layanan lain yang kompatibel';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Tidak ditemukan';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Salin tautan';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return 'Tautan $service disalin';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Buka';
|
||||
}
|
||||
|
||||
@@ -4182,4 +4182,49 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
@@ -4175,4 +4175,49 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
@@ -4195,4 +4195,49 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
@@ -4194,6 +4194,51 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
/// The translations for Portuguese, as used in Portugal (`pt_PT`).
|
||||
|
||||
@@ -4254,4 +4254,49 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
@@ -4221,4 +4221,49 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
@@ -4254,4 +4254,49 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
@@ -4194,6 +4194,51 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get artistReleases => 'Releases';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectNone => 'None';
|
||||
|
||||
@override
|
||||
String queueRetryAllFailed(int count) {
|
||||
return 'Retry $count failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistory => 'Save download history';
|
||||
|
||||
@override
|
||||
String get settingsSaveDownloadHistorySubtitle =>
|
||||
'Keep completed downloads in history and library views';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryTitle => 'Turn off download history?';
|
||||
|
||||
@override
|
||||
String get dialogDisableHistoryMessage =>
|
||||
'Existing history will be cleared. Downloaded files will not be deleted.';
|
||||
|
||||
@override
|
||||
String get dialogDisableAndClear => 'Turn off and clear';
|
||||
|
||||
@override
|
||||
String get openInOtherServices => 'Open in other services';
|
||||
|
||||
@override
|
||||
String get shareSheetNoExtensions => 'No other compatible services';
|
||||
|
||||
@override
|
||||
String get shareSheetNotFound => 'Not found';
|
||||
|
||||
@override
|
||||
String get shareSheetCopyLink => 'Copy link';
|
||||
|
||||
@override
|
||||
String shareSheetLinkCopied(Object service) {
|
||||
return '$service link copied';
|
||||
}
|
||||
|
||||
@override
|
||||
String get shareSheetOpen => 'Open';
|
||||
}
|
||||
|
||||
/// The translations for Chinese, as used in China (`zh_CN`).
|
||||
|
||||
@@ -5494,5 +5494,65 @@
|
||||
"artistReleases": "Releases",
|
||||
"@artistReleases": {
|
||||
"description": "Section header for all artist releases"
|
||||
},
|
||||
"editMetadataSelectNone": "None",
|
||||
"@editMetadataSelectNone": {
|
||||
"description": "Button to clear selected fields for auto-fill"
|
||||
},
|
||||
"queueRetryAllFailed": "Retry {count} failed",
|
||||
"@queueRetryAllFailed": {
|
||||
"description": "Button to retry every failed download in the queue",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingsSaveDownloadHistory": "Save download history",
|
||||
"@settingsSaveDownloadHistory": {
|
||||
"description": "Settings switch title for storing completed downloads in history"
|
||||
},
|
||||
"settingsSaveDownloadHistorySubtitle": "Keep completed downloads in history and library views",
|
||||
"@settingsSaveDownloadHistorySubtitle": {
|
||||
"description": "Settings switch subtitle for storing completed downloads in history"
|
||||
},
|
||||
"dialogDisableHistoryTitle": "Turn off download history?",
|
||||
"@dialogDisableHistoryTitle": {
|
||||
"description": "Confirmation dialog title shown before disabling download history"
|
||||
},
|
||||
"dialogDisableHistoryMessage": "Existing history will be cleared. Downloaded files will not be deleted.",
|
||||
"@dialogDisableHistoryMessage": {
|
||||
"description": "Confirmation dialog message shown before disabling download history"
|
||||
},
|
||||
"dialogDisableAndClear": "Turn off and clear",
|
||||
"@dialogDisableAndClear": {
|
||||
"description": "Confirmation action to disable download history and clear existing entries"
|
||||
},
|
||||
"openInOtherServices": "Open in other services",
|
||||
"@openInOtherServices": {
|
||||
"description": "Title and tooltip for finding the current collection in other services"
|
||||
},
|
||||
"shareSheetNoExtensions": "No other compatible services",
|
||||
"@shareSheetNoExtensions": {
|
||||
"description": "Empty state when no extensions can be searched for cross-service links"
|
||||
},
|
||||
"shareSheetNotFound": "Not found",
|
||||
"@shareSheetNotFound": {
|
||||
"description": "Cross-service share sheet row subtitle when a service has no match"
|
||||
},
|
||||
"shareSheetCopyLink": "Copy link",
|
||||
"@shareSheetCopyLink": {
|
||||
"description": "Tooltip for copying a cross-service link"
|
||||
},
|
||||
"shareSheetLinkCopied": "{service} link copied",
|
||||
"@shareSheetLinkCopied": {
|
||||
"description": "Snackbar after copying a cross-service link",
|
||||
"placeholders": {
|
||||
"service": {}
|
||||
}
|
||||
},
|
||||
"shareSheetOpen": "Open",
|
||||
"@shareSheetOpen": {
|
||||
"description": "Tooltip for opening a cross-service link inside the app"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4622,5 +4622,65 @@
|
||||
"queueRateLimitMessage": "Lagu ini mungkin masih tersedia. Tunggu beberapa menit, kurangi unduhan paralel, lalu coba lagi.",
|
||||
"@queueRateLimitMessage": {
|
||||
"description": "Explanation shown on a failed queue item when the download service rate limits requests"
|
||||
},
|
||||
"editMetadataSelectNone": "Tidak ada",
|
||||
"@editMetadataSelectNone": {
|
||||
"description": "Button to clear selected fields for auto-fill"
|
||||
},
|
||||
"queueRetryAllFailed": "Coba ulang {count} gagal",
|
||||
"@queueRetryAllFailed": {
|
||||
"description": "Button to retry every failed download in the queue",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingsSaveDownloadHistory": "Simpan riwayat unduhan",
|
||||
"@settingsSaveDownloadHistory": {
|
||||
"description": "Settings switch title for storing completed downloads in history"
|
||||
},
|
||||
"settingsSaveDownloadHistorySubtitle": "Simpan unduhan selesai di riwayat dan tampilan pustaka",
|
||||
"@settingsSaveDownloadHistorySubtitle": {
|
||||
"description": "Settings switch subtitle for storing completed downloads in history"
|
||||
},
|
||||
"dialogDisableHistoryTitle": "Matikan riwayat unduhan?",
|
||||
"@dialogDisableHistoryTitle": {
|
||||
"description": "Confirmation dialog title shown before disabling download history"
|
||||
},
|
||||
"dialogDisableHistoryMessage": "Riwayat yang ada akan dihapus. File unduhan tidak akan dihapus.",
|
||||
"@dialogDisableHistoryMessage": {
|
||||
"description": "Confirmation dialog message shown before disabling download history"
|
||||
},
|
||||
"dialogDisableAndClear": "Matikan dan hapus",
|
||||
"@dialogDisableAndClear": {
|
||||
"description": "Confirmation action to disable download history and clear existing entries"
|
||||
},
|
||||
"openInOtherServices": "Buka di layanan lain",
|
||||
"@openInOtherServices": {
|
||||
"description": "Title and tooltip for finding the current collection in other services"
|
||||
},
|
||||
"shareSheetNoExtensions": "Tidak ada layanan lain yang kompatibel",
|
||||
"@shareSheetNoExtensions": {
|
||||
"description": "Empty state when no extensions can be searched for cross-service links"
|
||||
},
|
||||
"shareSheetNotFound": "Tidak ditemukan",
|
||||
"@shareSheetNotFound": {
|
||||
"description": "Cross-service share sheet row subtitle when a service has no match"
|
||||
},
|
||||
"shareSheetCopyLink": "Salin tautan",
|
||||
"@shareSheetCopyLink": {
|
||||
"description": "Tooltip for copying a cross-service link"
|
||||
},
|
||||
"shareSheetLinkCopied": "Tautan {service} disalin",
|
||||
"@shareSheetLinkCopied": {
|
||||
"description": "Snackbar after copying a cross-service link",
|
||||
"placeholders": {
|
||||
"service": {}
|
||||
}
|
||||
},
|
||||
"shareSheetOpen": "Buka",
|
||||
"@shareSheetOpen": {
|
||||
"description": "Tooltip for opening a cross-service link inside the app"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ class AppSettings {
|
||||
|
||||
final bool
|
||||
deduplicateDownloads; // Skip downloading tracks already present in history
|
||||
final bool saveDownloadHistory; // Record completed downloads in local history
|
||||
|
||||
const AppSettings({
|
||||
this.defaultService = '',
|
||||
@@ -152,6 +153,7 @@ class AppSettings {
|
||||
this.musixmatchLanguage = '',
|
||||
this.lastSeenVersion = '',
|
||||
this.deduplicateDownloads = true,
|
||||
this.saveDownloadHistory = true,
|
||||
});
|
||||
|
||||
AppSettings copyWith({
|
||||
@@ -217,6 +219,7 @@ class AppSettings {
|
||||
String? musixmatchLanguage,
|
||||
String? lastSeenVersion,
|
||||
bool? deduplicateDownloads,
|
||||
bool? saveDownloadHistory,
|
||||
}) {
|
||||
return AppSettings(
|
||||
defaultService: defaultService ?? this.defaultService,
|
||||
@@ -300,6 +303,7 @@ class AppSettings {
|
||||
musixmatchLanguage: musixmatchLanguage ?? this.musixmatchLanguage,
|
||||
lastSeenVersion: lastSeenVersion ?? this.lastSeenVersion,
|
||||
deduplicateDownloads: deduplicateDownloads ?? this.deduplicateDownloads,
|
||||
saveDownloadHistory: saveDownloadHistory ?? this.saveDownloadHistory,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
|
||||
musixmatchLanguage: json['musixmatchLanguage'] as String? ?? '',
|
||||
lastSeenVersion: json['lastSeenVersion'] as String? ?? '',
|
||||
deduplicateDownloads: json['deduplicateDownloads'] as bool? ?? true,
|
||||
saveDownloadHistory: json['saveDownloadHistory'] as bool? ?? true,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AppSettingsToJson(
|
||||
@@ -147,4 +148,5 @@ Map<String, dynamic> _$AppSettingsToJson(
|
||||
'musixmatchLanguage': instance.musixmatchLanguage,
|
||||
'lastSeenVersion': instance.lastSeenVersion,
|
||||
'deduplicateDownloads': instance.deduplicateDownloads,
|
||||
'saveDownloadHistory': instance.saveDownloadHistory,
|
||||
};
|
||||
|
||||
@@ -4023,6 +4023,57 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
}
|
||||
}
|
||||
|
||||
void retryAllFailed() {
|
||||
final failedIds = state.items
|
||||
.where(
|
||||
(item) =>
|
||||
item.status == DownloadStatus.failed ||
|
||||
item.status == DownloadStatus.skipped,
|
||||
)
|
||||
.map((item) => item.id)
|
||||
.toSet();
|
||||
if (failedIds.isEmpty) {
|
||||
_log.d('retryAllFailed: no failed downloads to retry');
|
||||
return;
|
||||
}
|
||||
|
||||
_log.i('Retrying ${failedIds.length} failed download(s)');
|
||||
_locallyCancelledItemIds.removeAll(failedIds);
|
||||
_pausePendingItemIds.removeAll(failedIds);
|
||||
|
||||
for (final item in state.items) {
|
||||
if (!failedIds.contains(item.id)) continue;
|
||||
final rgKey = _albumRgKey(item.track);
|
||||
final rgAcc = _albumRgData[rgKey];
|
||||
if (rgAcc == null) continue;
|
||||
rgAcc.entries.removeWhere((entry) => entry.trackId == item.track.id);
|
||||
if (rgAcc.entries.isEmpty) {
|
||||
_albumRgData.remove(rgKey);
|
||||
}
|
||||
}
|
||||
|
||||
final items = state.items
|
||||
.map((item) {
|
||||
if (!failedIds.contains(item.id)) return item;
|
||||
return item.copyWith(
|
||||
status: DownloadStatus.queued,
|
||||
progress: 0,
|
||||
speedMBps: 0,
|
||||
bytesReceived: 0,
|
||||
bytesTotal: 0,
|
||||
error: null,
|
||||
);
|
||||
})
|
||||
.toList(growable: false);
|
||||
|
||||
state = state.copyWith(items: items, isPaused: false);
|
||||
_saveQueueToStorage();
|
||||
|
||||
if (!state.isProcessing) {
|
||||
Future.microtask(() => _processQueue());
|
||||
}
|
||||
}
|
||||
|
||||
void removeItem(String id) {
|
||||
final removedItem = state.items.where((item) => item.id == id).firstOrNull;
|
||||
_locallyCancelledItemIds.remove(id);
|
||||
@@ -5336,6 +5387,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
DownloadRequestPayload.nativeWorkerContractVersion,
|
||||
'run_id': runId,
|
||||
'created_at': DateTime.now().toIso8601String(),
|
||||
'save_download_history': settings.saveDownloadHistory,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -5769,22 +5821,26 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
progress: 1.0,
|
||||
filePath: filePath,
|
||||
);
|
||||
final historyItem = result['history_item'];
|
||||
if (historyItem is Map) {
|
||||
try {
|
||||
ref
|
||||
.read(downloadHistoryProvider.notifier)
|
||||
.adoptNativeHistoryItem(
|
||||
DownloadHistoryItem.fromJson(
|
||||
Map<String, dynamic>.from(historyItem),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
_log.w('Failed to adopt native history item: $e');
|
||||
if (settings.saveDownloadHistory) {
|
||||
final historyItem = result['history_item'];
|
||||
if (historyItem is Map) {
|
||||
try {
|
||||
ref
|
||||
.read(downloadHistoryProvider.notifier)
|
||||
.adoptNativeHistoryItem(
|
||||
DownloadHistoryItem.fromJson(
|
||||
Map<String, dynamic>.from(historyItem),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
_log.w('Failed to adopt native history item: $e');
|
||||
await ref
|
||||
.read(downloadHistoryProvider.notifier)
|
||||
.reloadFromStorage();
|
||||
}
|
||||
} else if (result['history_written'] == true) {
|
||||
await ref.read(downloadHistoryProvider.notifier).reloadFromStorage();
|
||||
}
|
||||
} else if (result['history_written'] == true) {
|
||||
await ref.read(downloadHistoryProvider.notifier).reloadFromStorage();
|
||||
}
|
||||
_completedInSession++;
|
||||
await _notificationService.showDownloadComplete(
|
||||
@@ -5989,51 +6045,53 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
backendComposer,
|
||||
);
|
||||
|
||||
ref
|
||||
.read(downloadHistoryProvider.notifier)
|
||||
.addToHistory(
|
||||
DownloadHistoryItem(
|
||||
id: item.id,
|
||||
trackName: historyTitle,
|
||||
artistName: historyArtist,
|
||||
albumName: historyAlbum,
|
||||
albumArtist: normalizeOptionalString(trackToDownload.albumArtist),
|
||||
coverUrl: normalizeCoverReference(trackToDownload.coverUrl),
|
||||
filePath: filePath,
|
||||
storageMode: context.storageMode,
|
||||
downloadTreeUri: context.storageMode == 'saf'
|
||||
? context.downloadTreeUri
|
||||
: null,
|
||||
safRelativeDir: context.storageMode == 'saf'
|
||||
? context.safRelativeDir
|
||||
: null,
|
||||
safFileName: context.storageMode == 'saf'
|
||||
? ((resultSafFileName != null && resultSafFileName.isNotEmpty)
|
||||
? resultSafFileName
|
||||
: context.safFileName)
|
||||
: null,
|
||||
safRepaired: false,
|
||||
service: result['service'] as String? ?? item.service,
|
||||
downloadedAt: DateTime.now(),
|
||||
isrc: historyIsrc,
|
||||
spotifyId: trackToDownload.id,
|
||||
trackNumber: historyTrackNumber,
|
||||
totalTracks: historyTotalTracks,
|
||||
discNumber: historyDiscNumber,
|
||||
totalDiscs: historyTotalDiscs,
|
||||
duration: trackToDownload.duration,
|
||||
releaseDate: historyReleaseDate,
|
||||
quality: actualQuality,
|
||||
bitDepth: isLossyOutput ? null : actualBitDepth,
|
||||
sampleRate: isLossyOutput ? null : actualSampleRate,
|
||||
bitrate: isLossyOutput ? actualBitrate : null,
|
||||
format: historyFormat,
|
||||
genre: normalizeOptionalString(backendGenre),
|
||||
composer: historyComposer,
|
||||
label: normalizeOptionalString(backendLabel),
|
||||
copyright: normalizeOptionalString(backendCopyright),
|
||||
),
|
||||
);
|
||||
if (settings.saveDownloadHistory) {
|
||||
ref
|
||||
.read(downloadHistoryProvider.notifier)
|
||||
.addToHistory(
|
||||
DownloadHistoryItem(
|
||||
id: item.id,
|
||||
trackName: historyTitle,
|
||||
artistName: historyArtist,
|
||||
albumName: historyAlbum,
|
||||
albumArtist: normalizeOptionalString(trackToDownload.albumArtist),
|
||||
coverUrl: normalizeCoverReference(trackToDownload.coverUrl),
|
||||
filePath: filePath,
|
||||
storageMode: context.storageMode,
|
||||
downloadTreeUri: context.storageMode == 'saf'
|
||||
? context.downloadTreeUri
|
||||
: null,
|
||||
safRelativeDir: context.storageMode == 'saf'
|
||||
? context.safRelativeDir
|
||||
: null,
|
||||
safFileName: context.storageMode == 'saf'
|
||||
? ((resultSafFileName != null && resultSafFileName.isNotEmpty)
|
||||
? resultSafFileName
|
||||
: context.safFileName)
|
||||
: null,
|
||||
safRepaired: false,
|
||||
service: result['service'] as String? ?? item.service,
|
||||
downloadedAt: DateTime.now(),
|
||||
isrc: historyIsrc,
|
||||
spotifyId: trackToDownload.id,
|
||||
trackNumber: historyTrackNumber,
|
||||
totalTracks: historyTotalTracks,
|
||||
discNumber: historyDiscNumber,
|
||||
totalDiscs: historyTotalDiscs,
|
||||
duration: trackToDownload.duration,
|
||||
releaseDate: historyReleaseDate,
|
||||
quality: actualQuality,
|
||||
bitDepth: isLossyOutput ? null : actualBitDepth,
|
||||
sampleRate: isLossyOutput ? null : actualSampleRate,
|
||||
bitrate: isLossyOutput ? actualBitrate : null,
|
||||
format: historyFormat,
|
||||
genre: normalizeOptionalString(backendGenre),
|
||||
composer: historyComposer,
|
||||
label: normalizeOptionalString(backendLabel),
|
||||
copyright: normalizeOptionalString(backendCopyright),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
removeItem(item.id);
|
||||
}
|
||||
@@ -8659,47 +8717,51 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
backendComposer,
|
||||
);
|
||||
|
||||
ref
|
||||
.read(downloadHistoryProvider.notifier)
|
||||
.addToHistory(
|
||||
DownloadHistoryItem(
|
||||
id: item.id,
|
||||
trackName: historyTitle,
|
||||
artistName: historyArtist,
|
||||
albumName: historyAlbum,
|
||||
albumArtist: historyAlbumArtist,
|
||||
coverUrl: normalizeCoverReference(trackToDownload.coverUrl),
|
||||
filePath: filePath,
|
||||
storageMode: effectiveSafMode ? 'saf' : 'app',
|
||||
downloadTreeUri: effectiveSafMode
|
||||
? settings.downloadTreeUri
|
||||
: null,
|
||||
safRelativeDir: effectiveSafMode ? effectiveOutputDir : null,
|
||||
safFileName: effectiveSafMode
|
||||
? (finalSafFileName ?? safFileName)
|
||||
: null,
|
||||
safRepaired: false,
|
||||
service: result['service'] as String? ?? item.service,
|
||||
downloadedAt: DateTime.now(),
|
||||
isrc: historyIsrc,
|
||||
spotifyId: trackToDownload.id,
|
||||
trackNumber: historyTrackNumber,
|
||||
totalTracks: historyTotalTracks,
|
||||
discNumber: historyDiscNumber,
|
||||
totalDiscs: historyTotalDiscs,
|
||||
duration: trackToDownload.duration,
|
||||
releaseDate: historyReleaseDate,
|
||||
quality: actualQuality,
|
||||
bitDepth: historyBitDepth,
|
||||
sampleRate: historySampleRate,
|
||||
bitrate: historyBitrate,
|
||||
format: finalFormat,
|
||||
genre: effectiveGenre,
|
||||
composer: historyComposer,
|
||||
label: effectiveLabel,
|
||||
copyright: effectiveCopyright,
|
||||
),
|
||||
);
|
||||
if (settings.saveDownloadHistory) {
|
||||
ref
|
||||
.read(downloadHistoryProvider.notifier)
|
||||
.addToHistory(
|
||||
DownloadHistoryItem(
|
||||
id: item.id,
|
||||
trackName: historyTitle,
|
||||
artistName: historyArtist,
|
||||
albumName: historyAlbum,
|
||||
albumArtist: historyAlbumArtist,
|
||||
coverUrl: normalizeCoverReference(trackToDownload.coverUrl),
|
||||
filePath: filePath,
|
||||
storageMode: effectiveSafMode ? 'saf' : 'app',
|
||||
downloadTreeUri: effectiveSafMode
|
||||
? settings.downloadTreeUri
|
||||
: null,
|
||||
safRelativeDir: effectiveSafMode
|
||||
? effectiveOutputDir
|
||||
: null,
|
||||
safFileName: effectiveSafMode
|
||||
? (finalSafFileName ?? safFileName)
|
||||
: null,
|
||||
safRepaired: false,
|
||||
service: result['service'] as String? ?? item.service,
|
||||
downloadedAt: DateTime.now(),
|
||||
isrc: historyIsrc,
|
||||
spotifyId: trackToDownload.id,
|
||||
trackNumber: historyTrackNumber,
|
||||
totalTracks: historyTotalTracks,
|
||||
discNumber: historyDiscNumber,
|
||||
totalDiscs: historyTotalDiscs,
|
||||
duration: trackToDownload.duration,
|
||||
releaseDate: historyReleaseDate,
|
||||
quality: actualQuality,
|
||||
bitDepth: historyBitDepth,
|
||||
sampleRate: historySampleRate,
|
||||
bitrate: historyBitrate,
|
||||
format: finalFormat,
|
||||
genre: effectiveGenre,
|
||||
composer: historyComposer,
|
||||
label: effectiveLabel,
|
||||
copyright: effectiveCopyright,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
removeItem(item.id);
|
||||
}
|
||||
|
||||
@@ -600,6 +600,11 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
state = state.copyWith(deduplicateDownloads: enabled);
|
||||
_saveSettings();
|
||||
}
|
||||
|
||||
void setSaveDownloadHistory(bool enabled) {
|
||||
state = state.copyWith(saveDownloadHistory: enabled);
|
||||
_saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
final settingsProvider = NotifierProvider<SettingsNotifier, AppSettings>(
|
||||
|
||||
@@ -21,6 +21,7 @@ import 'package:spotiflac_android/providers/library_collections_provider.dart';
|
||||
import 'package:spotiflac_android/widgets/playlist_picker_sheet.dart';
|
||||
import 'package:spotiflac_android/utils/clickable_metadata.dart';
|
||||
import 'package:spotiflac_android/widgets/audio_quality_badges.dart';
|
||||
import 'package:spotiflac_android/widgets/cross_extension_share_sheet.dart';
|
||||
|
||||
class _AlbumCache {
|
||||
static final Map<String, _CacheEntry> _cache = {};
|
||||
@@ -566,6 +567,8 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
|
||||
children: [
|
||||
_buildLoveAllButton(),
|
||||
const SizedBox(width: 12),
|
||||
_buildShareButton(context, tracks, artistName),
|
||||
const SizedBox(width: 12),
|
||||
FilledButton.icon(
|
||||
onPressed: () => _downloadAll(context),
|
||||
icon: Icon(Icons.download, size: 18),
|
||||
@@ -846,6 +849,45 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShareButton(
|
||||
BuildContext context,
|
||||
List<Track> tracks,
|
||||
String? artistName,
|
||||
) {
|
||||
final sourceExtensionId = _directMetadataProviderId() ?? '';
|
||||
final resolvedArtists =
|
||||
artistName ??
|
||||
tracks.firstOrNull?.albumArtist ??
|
||||
tracks.firstOrNull?.artistName ??
|
||||
'';
|
||||
|
||||
return Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white.withValues(alpha: 0.15),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () => CrossExtensionShareSheet.show(
|
||||
context,
|
||||
name: widget.albumName,
|
||||
artists: resolvedArtists,
|
||||
type: 'album',
|
||||
sourceExtensionId: sourceExtensionId,
|
||||
),
|
||||
icon: const Icon(Icons.open_in_new_rounded, size: 22),
|
||||
color: Colors.white,
|
||||
tooltip: context.l10n.openInOtherServices,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loveAll(List<Track> tracks) async {
|
||||
final notifier = ref.read(libraryCollectionsProvider.notifier);
|
||||
final state = ref.read(libraryCollectionsProvider);
|
||||
|
||||
@@ -23,6 +23,7 @@ import 'package:spotiflac_android/widgets/track_collection_quick_actions.dart';
|
||||
import 'package:spotiflac_android/widgets/animation_utils.dart';
|
||||
import 'package:spotiflac_android/utils/clickable_metadata.dart';
|
||||
import 'package:spotiflac_android/widgets/cached_cover_image.dart';
|
||||
import 'package:spotiflac_android/widgets/cross_extension_share_sheet.dart';
|
||||
|
||||
class _ArtistCache {
|
||||
static final Map<String, _CacheEntry> _cache = {};
|
||||
@@ -1421,10 +1422,31 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
width: 52,
|
||||
height: 52,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () => CrossExtensionShareSheet.show(
|
||||
context,
|
||||
name: widget.artistName,
|
||||
artists: '',
|
||||
type: 'artist',
|
||||
sourceExtensionId: _directMetadataProviderId() ?? '',
|
||||
),
|
||||
icon: const Icon(Icons.open_in_new_rounded, size: 24),
|
||||
color: Colors.black87,
|
||||
tooltip: context.l10n.openInOtherServices,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+51
-10
@@ -3018,24 +3018,47 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
final queueCount = ref.watch(
|
||||
downloadQueueLookupProvider.select((lookup) => lookup.itemIds.length),
|
||||
);
|
||||
final failedCount = ref.watch(
|
||||
downloadQueueProvider.select((state) => state.failedCount),
|
||||
);
|
||||
final isProcessing = ref.watch(
|
||||
downloadQueueProvider.select((state) => state.isProcessing),
|
||||
);
|
||||
if (queueCount == 0) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||
child: Row(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.queueDownloadingCount(queueCount),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.queueDownloadingCount(queueCount),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
_buildPauseResumeButton(context, ref, colorScheme),
|
||||
const SizedBox(width: 4),
|
||||
_buildClearAllButton(context, ref, colorScheme),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
_buildPauseResumeButton(context, ref, colorScheme),
|
||||
const SizedBox(width: 4),
|
||||
_buildClearAllButton(context, ref, colorScheme),
|
||||
if (failedCount > 0 && !isProcessing) ...[
|
||||
const SizedBox(height: 6),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: _buildRetryAllFailedButton(
|
||||
context,
|
||||
ref,
|
||||
colorScheme,
|
||||
failedCount,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -4007,6 +4030,24 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRetryAllFailedButton(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ColorScheme colorScheme,
|
||||
int failedCount,
|
||||
) {
|
||||
return TextButton.icon(
|
||||
onPressed: () =>
|
||||
ref.read(downloadQueueProvider.notifier).retryAllFailed(),
|
||||
icon: const Icon(Icons.replay_rounded, size: 18),
|
||||
label: Text(context.l10n.queueRetryAllFailed(failedCount)),
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
foregroundColor: colorScheme.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showClearAllDialog(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
|
||||
@@ -114,6 +114,33 @@ class AppSettingsPage extends ConsumerWidget {
|
||||
subtitle: context.l10n.optionsClearHistorySubtitle,
|
||||
onTap: () =>
|
||||
_showClearHistoryDialog(context, ref, colorScheme),
|
||||
),
|
||||
SettingsSwitchItem(
|
||||
icon: Icons.history_toggle_off_outlined,
|
||||
title: context.l10n.settingsSaveDownloadHistory,
|
||||
subtitle: context.l10n.settingsSaveDownloadHistorySubtitle,
|
||||
value: settings.saveDownloadHistory,
|
||||
onChanged: (enabled) {
|
||||
if (enabled) {
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setSaveDownloadHistory(true);
|
||||
return;
|
||||
}
|
||||
|
||||
final hasHistory = ref
|
||||
.read(downloadHistoryProvider)
|
||||
.items
|
||||
.isNotEmpty;
|
||||
if (hasHistory) {
|
||||
_showDisableHistoryDialog(context, ref, colorScheme);
|
||||
return;
|
||||
}
|
||||
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setSaveDownloadHistory(false);
|
||||
},
|
||||
showDivider: false,
|
||||
),
|
||||
],
|
||||
@@ -148,6 +175,40 @@ class AppSettingsPage extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showDisableHistoryDialog(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.l10n.dialogDisableHistoryTitle),
|
||||
content: Text(context.l10n.dialogDisableHistoryMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.l10n.dialogCancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.read(downloadHistoryProvider.notifier).clearHistory();
|
||||
ref.read(settingsProvider.notifier).setSaveDownloadHistory(false);
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.snackbarHistoryCleared)),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
context.l10n.dialogDisableAndClear,
|
||||
style: TextStyle(color: colorScheme.error),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showClearHistoryDialog(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
|
||||
@@ -350,6 +350,10 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
});
|
||||
}
|
||||
|
||||
void _selectNoFields() {
|
||||
setState(_autoFillFields.clear);
|
||||
}
|
||||
|
||||
String _normalizeMetadataText(String value) {
|
||||
final collapsed = value
|
||||
.toLowerCase()
|
||||
@@ -1474,6 +1478,12 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
onTap: _selectEmptyFields,
|
||||
cs: cs,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_quickSelectButton(
|
||||
label: context.l10n.editMetadataSelectNone,
|
||||
onTap: _selectNoFields,
|
||||
cs: cs,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
|
||||
class CrossExtensionShareResult {
|
||||
final String extensionId;
|
||||
final String displayName;
|
||||
final bool found;
|
||||
final String? url;
|
||||
final String? itemName;
|
||||
final String? itemArtists;
|
||||
final String? error;
|
||||
|
||||
const CrossExtensionShareResult({
|
||||
required this.extensionId,
|
||||
required this.displayName,
|
||||
required this.found,
|
||||
this.url,
|
||||
this.itemName,
|
||||
this.itemArtists,
|
||||
this.error,
|
||||
});
|
||||
|
||||
factory CrossExtensionShareResult.fromJson(Map<String, dynamic> json) {
|
||||
return CrossExtensionShareResult(
|
||||
extensionId: json['extension_id'] as String? ?? '',
|
||||
displayName: json['display_name'] as String? ?? '',
|
||||
found: json['found'] as bool? ?? false,
|
||||
url: json['url'] as String?,
|
||||
itemName: json['item_name'] as String?,
|
||||
itemArtists: json['item_artists'] as String?,
|
||||
error: json['error'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CrossExtensionShareService {
|
||||
const CrossExtensionShareService();
|
||||
|
||||
Future<List<CrossExtensionShareResult>> findAcrossExtensions({
|
||||
required String name,
|
||||
required String artists,
|
||||
required String type,
|
||||
required String sourceExtensionId,
|
||||
}) async {
|
||||
final results = await PlatformBridge.findCollectionAcrossExtensions(
|
||||
name: name,
|
||||
artists: artists,
|
||||
type: type,
|
||||
sourceExtensionId: sourceExtensionId,
|
||||
);
|
||||
return results.map(CrossExtensionShareResult.fromJson).toList();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -236,6 +237,7 @@ class NotificationService {
|
||||
bool alreadyInLibrary = false,
|
||||
}) async {
|
||||
if (!_isInitialized) await initialize();
|
||||
unawaited(HapticFeedback.mediumImpact());
|
||||
|
||||
String title;
|
||||
if (alreadyInLibrary) {
|
||||
@@ -286,6 +288,11 @@ class NotificationService {
|
||||
}) async {
|
||||
if (!_isInitialized) await initialize();
|
||||
if (completedCount <= 0 && failedCount <= 0) return;
|
||||
unawaited(
|
||||
failedCount > 0
|
||||
? HapticFeedback.heavyImpact()
|
||||
: HapticFeedback.mediumImpact(),
|
||||
);
|
||||
|
||||
final title = failedCount > 0
|
||||
? (_l10n?.notifDownloadsFinished(completedCount, failedCount) ??
|
||||
@@ -330,6 +337,7 @@ class NotificationService {
|
||||
Future<void> showQueueCanceled({required int canceledCount}) async {
|
||||
if (!_isInitialized) await initialize();
|
||||
if (canceledCount <= 0) return;
|
||||
unawaited(HapticFeedback.lightImpact());
|
||||
|
||||
final title = _l10n?.notifDownloadsCanceledTitle ?? 'Downloads canceled';
|
||||
final body =
|
||||
|
||||
@@ -1309,6 +1309,25 @@ class PlatformBridge {
|
||||
return _decodeMapListResult(result, 'searchTracksWithMetadataProviders');
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> findCollectionAcrossExtensions({
|
||||
required String name,
|
||||
required String artists,
|
||||
required String type,
|
||||
required String sourceExtensionId,
|
||||
}) async {
|
||||
final requestJson = jsonEncode({
|
||||
'name': name,
|
||||
'artists': artists,
|
||||
'type': type,
|
||||
'source_extension_id': sourceExtensionId,
|
||||
});
|
||||
final result = await _channel.invokeMethod(
|
||||
'findCollectionAcrossExtensions',
|
||||
requestJson,
|
||||
);
|
||||
return _decodeMapListResult(result, 'findCollectionAcrossExtensions');
|
||||
}
|
||||
|
||||
static Future<void> cleanupExtensions() async {
|
||||
_log.d('cleanupExtensions');
|
||||
await _channel.invokeMethod('cleanupExtensions');
|
||||
|
||||
@@ -31,6 +31,12 @@ class ShareIntentService {
|
||||
return url;
|
||||
}
|
||||
|
||||
void injectUrl(String url) {
|
||||
if (url.isEmpty) return;
|
||||
_pendingUrl = url;
|
||||
_sharedUrlController.add(url);
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
if (_initialized) return;
|
||||
_initialized = true;
|
||||
|
||||
@@ -36,15 +36,14 @@ class AppAnnouncementDialog extends StatelessWidget {
|
||||
} catch (_) {
|
||||
launched = false;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
if (!launched) {
|
||||
_showCtaOpenFailed(context);
|
||||
return;
|
||||
}
|
||||
|
||||
onDismiss();
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
void _showCtaOpenFailed(BuildContext context) {
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/services/cross_extension_share_service.dart';
|
||||
import 'package:spotiflac_android/services/share_intent_service.dart';
|
||||
|
||||
class CrossExtensionShareSheet extends StatefulWidget {
|
||||
final String name;
|
||||
final String artists;
|
||||
final String type;
|
||||
final String sourceExtensionId;
|
||||
|
||||
const CrossExtensionShareSheet({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.artists,
|
||||
required this.type,
|
||||
required this.sourceExtensionId,
|
||||
});
|
||||
|
||||
static Future<void> show(
|
||||
BuildContext context, {
|
||||
required String name,
|
||||
required String artists,
|
||||
required String type,
|
||||
required String sourceExtensionId,
|
||||
}) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
|
||||
),
|
||||
builder: (_) => CrossExtensionShareSheet(
|
||||
name: name,
|
||||
artists: artists,
|
||||
type: type,
|
||||
sourceExtensionId: sourceExtensionId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
State<CrossExtensionShareSheet> createState() =>
|
||||
_CrossExtensionShareSheetState();
|
||||
}
|
||||
|
||||
class _CrossExtensionShareSheetState extends State<CrossExtensionShareSheet> {
|
||||
late final Future<List<CrossExtensionShareResult>> _future;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_future = const CrossExtensionShareService()
|
||||
.findAcrossExtensions(
|
||||
name: widget.name,
|
||||
artists: widget.artists,
|
||||
type: widget.type,
|
||||
sourceExtensionId: widget.sourceExtensionId,
|
||||
)
|
||||
.then((results) {
|
||||
final sorted = [...results];
|
||||
sorted.sort((a, b) {
|
||||
if (a.found != b.found) return a.found ? -1 : 1;
|
||||
return a.displayName.compareTo(b.displayName);
|
||||
});
|
||||
return sorted;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.sizeOf(context).height * 0.82,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 12, bottom: 8),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.4),
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 8, 24, 4),
|
||||
child: Text(
|
||||
context.l10n.openInOtherServices,
|
||||
style: textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 12),
|
||||
child: Text(
|
||||
widget.artists.isNotEmpty
|
||||
? '${widget.name} - ${widget.artists}'
|
||||
: widget.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: FutureBuilder<List<CrossExtensionShareResult>>(
|
||||
future: _future,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return const SizedBox(
|
||||
height: 180,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
final results = snapshot.data ?? const [];
|
||||
if (results.isEmpty) {
|
||||
return SizedBox(
|
||||
height: 180,
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.l10n.shareSheetNoExtensions,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.fromLTRB(12, 4, 12, 16),
|
||||
itemBuilder: (context, index) {
|
||||
return _CrossExtensionShareTile(result: results[index]);
|
||||
},
|
||||
separatorBuilder: (_, _) =>
|
||||
const Divider(height: 1, indent: 72),
|
||||
itemCount: results.length,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CrossExtensionShareTile extends StatelessWidget {
|
||||
final CrossExtensionShareResult result;
|
||||
|
||||
const _CrossExtensionShareTile({required this.result});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final url = result.found ? result.url : null;
|
||||
final hasUrl = url != null && url.isNotEmpty;
|
||||
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
leading: Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: hasUrl
|
||||
? colorScheme.primaryContainer
|
||||
: colorScheme.surfaceContainerHighest,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
hasUrl ? Icons.link_rounded : Icons.link_off_rounded,
|
||||
color: hasUrl
|
||||
? colorScheme.onPrimaryContainer
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
result.displayName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
hasUrl
|
||||
? (result.itemName?.isNotEmpty == true ? result.itemName! : url)
|
||||
: context.l10n.shareSheetNotFound,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: hasUrl ? colorScheme.primary : colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
trailing: hasUrl
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: context.l10n.shareSheetCopyLink,
|
||||
icon: const Icon(Icons.copy_rounded, size: 20),
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: url));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.shareSheetLinkCopied(result.displayName),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
tooltip: context.l10n.shareSheetOpen,
|
||||
icon: const Icon(Icons.open_in_new_rounded, size: 20),
|
||||
color: colorScheme.primary,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
ShareIntentService().injectUrl(url);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user