Add the ability to delete media items

This commit is contained in:
Micah Morrison
2026-02-01 23:03:04 -05:00
parent 58adb5edad
commit 41bf2f4ede
23 changed files with 227 additions and 37 deletions
+5 -1
View File
@@ -241,7 +241,11 @@
"goToSeries": "Zur Serie",
"goToSeason": "Zur Staffel",
"shufflePlay": "Zufallswiedergabe",
"fileInfo": "Dateiinfo"
"fileInfo": "Dateiinfo",
"confirmDelete": "Sind Sie sicher, dass Sie dieses Element aus Ihrem Dateisystem löschen möchten?",
"deleteMultipleWarning": "Mehrere Elemente können gelöscht werden.",
"mediaDeletedSuccessfully": "Medienelement erfolgreich gelöscht",
"mediaFailedToDelete": "Löschen des Medienelements fehlgeschlagen"
},
"accessibility": {
"mediaCardMovie": "${title}, Film",
+5 -1
View File
@@ -240,7 +240,11 @@
"goToSeries": "Go to series",
"goToSeason": "Go to season",
"shufflePlay": "Shuffle Play",
"fileInfo": "File Info"
"fileInfo": "File Info",
"confirmDelete": "Are you sure you want to delete this item from your filesystem?",
"deleteMultipleWarning": "Multiple items may be deleted.",
"mediaDeletedSuccessfully": "Media item deleted successfully",
"mediaFailedToDelete": "Failed to delete media item"
},
"accessibility": {
"mediaCardMovie": "${title}, movie",
+5 -1
View File
@@ -240,7 +240,11 @@
"goToSeries": "Aller à la série",
"goToSeason": "Aller à la saison",
"shufflePlay": "Lecture aléatoire",
"fileInfo": "Informations sur le fichier"
"fileInfo": "Informations sur le fichier",
"confirmDelete": "Êtes-vous sûr de vouloir supprimer cet élément de votre système de fichiers?",
"deleteMultipleWarning": "Plusieurs éléments peuvent être supprimés.",
"mediaDeletedSuccessfully": "Élément média supprimé avec succès",
"mediaFailedToDelete": "Échec de la suppression de l'élément média"
},
"accessibility": {
"mediaCardMovie": "${title}, film",
+5 -1
View File
@@ -241,7 +241,11 @@
"goToSeries": "Vai alle serie",
"goToSeason": "Vai alla stagione",
"shufflePlay": "Riproduzione casuale",
"fileInfo": "Info sul file"
"fileInfo": "Info sul file",
"confirmDelete": "Sei sicuro di voler eliminare questo elemento dal tuo filesystem?",
"deleteMultipleWarning": "Potrebbero essere eliminati più elementi.",
"mediaDeletedSuccessfully": "Elemento multimediale eliminato con successo",
"mediaFailedToDelete": "Impossibile eliminare l'elemento multimediale"
},
"accessibility": {
"mediaCardMovie": "${title}, film",
+5 -1
View File
@@ -241,7 +241,11 @@
"goToSeries": "시리즈로 이동",
"goToSeason": "시즌으로 이동",
"shufflePlay": "무작위 재생",
"fileInfo": "파일 정보"
"fileInfo": "파일 정보",
"confirmDelete": "파일 시스템에서 이 항목을 삭제하시겠습니까?",
"deleteMultipleWarning": "여러 항목이 삭제될 수 있습니다.",
"mediaDeletedSuccessfully": "미디어 항목이 성공적으로 삭제되었습니다",
"mediaFailedToDelete": "미디어 항목 삭제 실패"
},
"accessibility": {
"mediaCardMovie": "${title}, 영화",
+5 -1
View File
@@ -241,7 +241,11 @@
"goToSeries": "Ga naar serie",
"goToSeason": "Ga naar seizoen",
"shufflePlay": "Willekeurig afspelen",
"fileInfo": "Bestand info"
"fileInfo": "Bestand info",
"confirmDelete": "Weet je zeker dat je dit item van je bestandssysteem wilt verwijderen?",
"deleteMultipleWarning": "Meerdere items kunnen worden verwijderd.",
"mediaDeletedSuccessfully": "Media-item succesvol verwijderd",
"mediaFailedToDelete": "Verwijderen van media-item mislukt"
},
"accessibility": {
"mediaCardMovie": "${title}, film",
+2 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 8
/// Strings: 4548 (568 per locale)
/// Strings: 4580 (572 per locale)
///
/// Built on 2026-02-01 at 12:48 UTC
/// Built on 2026-02-02 at 01:38 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+10 -2
View File
@@ -372,6 +372,10 @@ class _TranslationsMediaMenuDe implements TranslationsMediaMenuEn {
@override String get goToSeason => 'Zur Staffel';
@override String get shufflePlay => 'Zufallswiedergabe';
@override String get fileInfo => 'Dateiinfo';
@override String get confirmDelete => 'Sind Sie sicher, dass Sie dieses Element aus Ihrem Dateisystem löschen möchten?';
@override String get deleteMultipleWarning => 'Mehrere Elemente können gelöscht werden.';
@override String get mediaDeletedSuccessfully => 'Medienelement erfolgreich gelöscht';
@override String get mediaFailedToDelete => 'Löschen des Medienelements fehlgeschlagen';
}
// Path: accessibility
@@ -1206,6 +1210,10 @@ extension on TranslationsDe {
'mediaMenu.goToSeason' => 'Zur Staffel',
'mediaMenu.shufflePlay' => 'Zufallswiedergabe',
'mediaMenu.fileInfo' => 'Dateiinfo',
'mediaMenu.confirmDelete' => 'Sind Sie sicher, dass Sie dieses Element aus Ihrem Dateisystem löschen möchten?',
'mediaMenu.deleteMultipleWarning' => 'Mehrere Elemente können gelöscht werden.',
'mediaMenu.mediaDeletedSuccessfully' => 'Medienelement erfolgreich gelöscht',
'mediaMenu.mediaFailedToDelete' => 'Löschen des Medienelements fehlgeschlagen',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, Film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, Serie',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -1494,12 +1502,12 @@ extension on TranslationsDe {
'collections.selectCollection' => 'Sammlung auswählen',
'collections.createNewCollection' => 'Neue Sammlung erstellen',
'collections.collectionName' => 'Sammlungsname',
_ => null,
} ?? switch (path) {
'collections.enterCollectionName' => 'Sammlungsnamen eingeben',
'collections.addedToCollection' => 'Zur Sammlung hinzugefügt',
'collections.errorAddingToCollection' => 'Fehler beim Hinzufügen zur Sammlung',
'collections.created' => 'Sammlung erstellt',
_ => null,
} ?? switch (path) {
'collections.removeFromCollection' => 'Aus Sammlung entfernen',
'collections.removeFromCollectionConfirm' => ({required Object title}) => '"${title}" aus dieser Sammlung entfernen?',
'collections.removedFromCollection' => 'Aus Sammlung entfernt',
+18 -2
View File
@@ -777,6 +777,18 @@ class TranslationsMediaMenuEn {
/// en: 'File Info'
String get fileInfo => 'File Info';
/// en: 'Are you sure you want to delete this item from your filesystem?'
String get confirmDelete => 'Are you sure you want to delete this item from your filesystem?';
/// en: 'Multiple items may be deleted.'
String get deleteMultipleWarning => 'Multiple items may be deleted.';
/// en: 'Media item deleted successfully'
String get mediaDeletedSuccessfully => 'Media item deleted successfully';
/// en: 'Failed to delete media item'
String get mediaFailedToDelete => 'Failed to delete media item';
}
// Path: accessibility
@@ -2343,6 +2355,10 @@ extension on Translations {
'mediaMenu.goToSeason' => 'Go to season',
'mediaMenu.shufflePlay' => 'Shuffle Play',
'mediaMenu.fileInfo' => 'File Info',
'mediaMenu.confirmDelete' => 'Are you sure you want to delete this item from your filesystem?',
'mediaMenu.deleteMultipleWarning' => 'Multiple items may be deleted.',
'mediaMenu.mediaDeletedSuccessfully' => 'Media item deleted successfully',
'mediaMenu.mediaFailedToDelete' => 'Failed to delete media item',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, movie',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV show',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2632,12 +2648,12 @@ extension on Translations {
'watchTogether.joinSession' => 'Join Session',
'watchTogether.joining' => 'Joining...',
'watchTogether.controlMode' => 'Control Mode',
_ => null,
} ?? switch (path) {
'watchTogether.controlModeQuestion' => 'Who can control playback?',
'watchTogether.hostOnly' => 'Host Only',
'watchTogether.anyone' => 'Anyone',
'watchTogether.hostingSession' => 'Hosting Session',
_ => null,
} ?? switch (path) {
'watchTogether.inSession' => 'In Session',
'watchTogether.sessionCode' => 'Session Code',
'watchTogether.hostControlsPlayback' => 'Host controls playback',
+10 -2
View File
@@ -371,6 +371,10 @@ class _TranslationsMediaMenuFr implements TranslationsMediaMenuEn {
@override String get goToSeason => 'Aller à la saison';
@override String get shufflePlay => 'Lecture aléatoire';
@override String get fileInfo => 'Informations sur le fichier';
@override String get confirmDelete => 'Êtes-vous sûr de vouloir supprimer cet élément de votre système de fichiers?';
@override String get deleteMultipleWarning => 'Plusieurs éléments peuvent être supprimés.';
@override String get mediaDeletedSuccessfully => 'Élément média supprimé avec succès';
@override String get mediaFailedToDelete => 'Échec de la suppression de l\'élément média';
}
// Path: accessibility
@@ -1203,6 +1207,10 @@ extension on TranslationsFr {
'mediaMenu.goToSeason' => 'Aller à la saison',
'mediaMenu.shufflePlay' => 'Lecture aléatoire',
'mediaMenu.fileInfo' => 'Informations sur le fichier',
'mediaMenu.confirmDelete' => 'Êtes-vous sûr de vouloir supprimer cet élément de votre système de fichiers?',
'mediaMenu.deleteMultipleWarning' => 'Plusieurs éléments peuvent être supprimés.',
'mediaMenu.mediaDeletedSuccessfully' => 'Élément média supprimé avec succès',
'mediaMenu.mediaFailedToDelete' => 'Échec de la suppression de l\'élément média',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, show TV',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -1492,12 +1500,12 @@ extension on TranslationsFr {
'watchTogether.joinSession' => 'Rejoindre la session',
'watchTogether.joining' => 'Rejoindre...',
'watchTogether.controlMode' => 'Mode de contrôle',
_ => null,
} ?? switch (path) {
'watchTogether.controlModeQuestion' => 'Qui peut contrôler la lecture ?',
'watchTogether.hostOnly' => 'Hôte uniquement',
'watchTogether.anyone' => 'N\'importe qui',
'watchTogether.hostingSession' => 'Session d\'hébergement',
_ => null,
} ?? switch (path) {
'watchTogether.inSession' => 'En session',
'watchTogether.sessionCode' => 'Code de session',
'watchTogether.hostControlsPlayback' => 'L\'hôte contrôle la lecture',
+10 -2
View File
@@ -372,6 +372,10 @@ class _TranslationsMediaMenuIt implements TranslationsMediaMenuEn {
@override String get goToSeason => 'Vai alla stagione';
@override String get shufflePlay => 'Riproduzione casuale';
@override String get fileInfo => 'Info sul file';
@override String get confirmDelete => 'Sei sicuro di voler eliminare questo elemento dal tuo filesystem?';
@override String get deleteMultipleWarning => 'Potrebbero essere eliminati più elementi.';
@override String get mediaDeletedSuccessfully => 'Elemento multimediale eliminato con successo';
@override String get mediaFailedToDelete => 'Impossibile eliminare l\'elemento multimediale';
}
// Path: accessibility
@@ -1206,6 +1210,10 @@ extension on TranslationsIt {
'mediaMenu.goToSeason' => 'Vai alla stagione',
'mediaMenu.shufflePlay' => 'Riproduzione casuale',
'mediaMenu.fileInfo' => 'Info sul file',
'mediaMenu.confirmDelete' => 'Sei sicuro di voler eliminare questo elemento dal tuo filesystem?',
'mediaMenu.deleteMultipleWarning' => 'Potrebbero essere eliminati più elementi.',
'mediaMenu.mediaDeletedSuccessfully' => 'Elemento multimediale eliminato con successo',
'mediaMenu.mediaFailedToDelete' => 'Impossibile eliminare l\'elemento multimediale',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, serie TV',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -1494,12 +1502,12 @@ extension on TranslationsIt {
'collections.selectCollection' => 'Seleziona raccolta',
'collections.createNewCollection' => 'Crea nuova raccolta',
'collections.collectionName' => 'Nome raccolta',
_ => null,
} ?? switch (path) {
'collections.enterCollectionName' => 'Inserisci nome raccolta',
'collections.addedToCollection' => 'Aggiunto alla raccolta',
'collections.errorAddingToCollection' => 'Errore nell\'aggiunta alla raccolta',
'collections.created' => 'Raccolta creata',
_ => null,
} ?? switch (path) {
'collections.removeFromCollection' => 'Rimuovi dalla raccolta',
'collections.removeFromCollectionConfirm' => ({required Object title}) => 'Rimuovere "${title}" da questa raccolta?',
'collections.removedFromCollection' => 'Rimosso dalla raccolta',
+10 -2
View File
@@ -372,6 +372,10 @@ class _TranslationsMediaMenuKo implements TranslationsMediaMenuEn {
@override String get goToSeason => '시즌으로 이동';
@override String get shufflePlay => '무작위 재생';
@override String get fileInfo => '파일 정보';
@override String get confirmDelete => '파일 시스템에서 이 항목을 삭제하시겠습니까?';
@override String get deleteMultipleWarning => '여러 항목이 삭제될 수 있습니다.';
@override String get mediaDeletedSuccessfully => '미디어 항목이 성공적으로 삭제되었습니다';
@override String get mediaFailedToDelete => '미디어 항목 삭제 실패';
}
// Path: accessibility
@@ -1206,6 +1210,10 @@ extension on TranslationsKo {
'mediaMenu.goToSeason' => '시즌으로 이동',
'mediaMenu.shufflePlay' => '무작위 재생',
'mediaMenu.fileInfo' => '파일 정보',
'mediaMenu.confirmDelete' => '파일 시스템에서 이 항목을 삭제하시겠습니까?',
'mediaMenu.deleteMultipleWarning' => '여러 항목이 삭제될 수 있습니다.',
'mediaMenu.mediaDeletedSuccessfully' => '미디어 항목이 성공적으로 삭제되었습니다',
'mediaMenu.mediaFailedToDelete' => '미디어 항목 삭제 실패',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, 영화',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV 프로그램',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -1494,12 +1502,12 @@ extension on TranslationsKo {
'watchTogether.createSession' => '세션 생성',
'watchTogether.creating' => '생성 중...',
'watchTogether.joinSession' => '세션 참여',
_ => null,
} ?? switch (path) {
'watchTogether.joining' => '참가 중...',
'watchTogether.controlMode' => '제어 모드',
'watchTogether.controlModeQuestion' => '누가 재생을 제어할 수 있나요?',
'watchTogether.hostOnly' => '호스트만',
_ => null,
} ?? switch (path) {
'watchTogether.anyone' => '누구나',
'watchTogether.hostingSession' => '세션 호스팅',
'watchTogether.inSession' => '세션 중',
+10 -2
View File
@@ -372,6 +372,10 @@ class _TranslationsMediaMenuNl implements TranslationsMediaMenuEn {
@override String get goToSeason => 'Ga naar seizoen';
@override String get shufflePlay => 'Willekeurig afspelen';
@override String get fileInfo => 'Bestand info';
@override String get confirmDelete => 'Weet je zeker dat je dit item van je bestandssysteem wilt verwijderen?';
@override String get deleteMultipleWarning => 'Meerdere items kunnen worden verwijderd.';
@override String get mediaDeletedSuccessfully => 'Media-item succesvol verwijderd';
@override String get mediaFailedToDelete => 'Verwijderen van media-item mislukt';
}
// Path: accessibility
@@ -1206,6 +1210,10 @@ extension on TranslationsNl {
'mediaMenu.goToSeason' => 'Ga naar seizoen',
'mediaMenu.shufflePlay' => 'Willekeurig afspelen',
'mediaMenu.fileInfo' => 'Bestand info',
'mediaMenu.confirmDelete' => 'Weet je zeker dat je dit item van je bestandssysteem wilt verwijderen?',
'mediaMenu.deleteMultipleWarning' => 'Meerdere items kunnen worden verwijderd.',
'mediaMenu.mediaDeletedSuccessfully' => 'Media-item succesvol verwijderd',
'mediaMenu.mediaFailedToDelete' => 'Verwijderen van media-item mislukt',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV-serie',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -1494,12 +1502,12 @@ extension on TranslationsNl {
'collections.selectCollection' => 'Selecteer collectie',
'collections.createNewCollection' => 'Nieuwe collectie maken',
'collections.collectionName' => 'Collectienaam',
_ => null,
} ?? switch (path) {
'collections.enterCollectionName' => 'Voer collectienaam in',
'collections.addedToCollection' => 'Toegevoegd aan collectie',
'collections.errorAddingToCollection' => 'Fout bij toevoegen aan collectie',
'collections.created' => 'Collectie gemaakt',
_ => null,
} ?? switch (path) {
'collections.removeFromCollection' => 'Verwijderen uit collectie',
'collections.removeFromCollectionConfirm' => ({required Object title}) => '"${title}" uit deze collectie verwijderen?',
'collections.removedFromCollection' => 'Uit collectie verwijderd',
+10 -2
View File
@@ -372,6 +372,10 @@ class _TranslationsMediaMenuSv implements TranslationsMediaMenuEn {
@override String get goToSeason => 'Gå till säsong';
@override String get shufflePlay => 'Blanda uppspelning';
@override String get fileInfo => 'Filinformation';
@override String get confirmDelete => 'Är du säker på att du vill ta bort detta objekt från ditt filsystem?';
@override String get deleteMultipleWarning => 'Flera objekt kan komma att tas bort.';
@override String get mediaDeletedSuccessfully => 'Mediaobjekt borttaget';
@override String get mediaFailedToDelete => 'Kunde inte ta bort mediaobjekt';
}
// Path: accessibility
@@ -1206,6 +1210,10 @@ extension on TranslationsSv {
'mediaMenu.goToSeason' => 'Gå till säsong',
'mediaMenu.shufflePlay' => 'Blanda uppspelning',
'mediaMenu.fileInfo' => 'Filinformation',
'mediaMenu.confirmDelete' => 'Är du säker på att du vill ta bort detta objekt från ditt filsystem?',
'mediaMenu.deleteMultipleWarning' => 'Flera objekt kan komma att tas bort.',
'mediaMenu.mediaDeletedSuccessfully' => 'Mediaobjekt borttaget',
'mediaMenu.mediaFailedToDelete' => 'Kunde inte ta bort mediaobjekt',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV-serie',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -1494,12 +1502,12 @@ extension on TranslationsSv {
'collections.selectCollection' => 'Välj samling',
'collections.createNewCollection' => 'Skapa ny samling',
'collections.collectionName' => 'Samlingsnamn',
_ => null,
} ?? switch (path) {
'collections.enterCollectionName' => 'Ange samlingsnamn',
'collections.addedToCollection' => 'Tillagd i samling',
'collections.errorAddingToCollection' => 'Fel vid tillägg i samling',
'collections.created' => 'Samling skapad',
_ => null,
} ?? switch (path) {
'collections.removeFromCollection' => 'Ta bort från samling',
'collections.removeFromCollectionConfirm' => ({required Object title}) => 'Ta bort "${title}" från denna samling?',
'collections.removedFromCollection' => 'Borttagen från samling',
+10 -2
View File
@@ -372,6 +372,10 @@ class _TranslationsMediaMenuZh implements TranslationsMediaMenuEn {
@override String get goToSeason => '转到季';
@override String get shufflePlay => '随机播放';
@override String get fileInfo => '文件信息';
@override String get confirmDelete => '确定要从文件系统中删除此项吗?';
@override String get deleteMultipleWarning => '可能会删除多个项目。';
@override String get mediaDeletedSuccessfully => '媒体项已成功删除';
@override String get mediaFailedToDelete => '删除媒体项失败';
}
// Path: accessibility
@@ -1206,6 +1210,10 @@ extension on TranslationsZh {
'mediaMenu.goToSeason' => '转到季',
'mediaMenu.shufflePlay' => '随机播放',
'mediaMenu.fileInfo' => '文件信息',
'mediaMenu.confirmDelete' => '确定要从文件系统中删除此项吗?',
'mediaMenu.deleteMultipleWarning' => '可能会删除多个项目。',
'mediaMenu.mediaDeletedSuccessfully' => '媒体项已成功删除',
'mediaMenu.mediaFailedToDelete' => '删除媒体项失败',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, 电影',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, 电视剧',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -1494,12 +1502,12 @@ extension on TranslationsZh {
'collections.selectCollection' => '选择合集',
'collections.createNewCollection' => '创建新合集',
'collections.collectionName' => '合集名称',
_ => null,
} ?? switch (path) {
'collections.enterCollectionName' => '输入合集名称',
'collections.addedToCollection' => '已添加到合集',
'collections.errorAddingToCollection' => '添加到合集失败',
'collections.created' => '已创建合集',
_ => null,
} ?? switch (path) {
'collections.removeFromCollection' => '从合集移除',
'collections.removeFromCollectionConfirm' => ({required Object title}) => '将“${title}”从此合集移除?',
'collections.removedFromCollection' => '已从合集移除',
+5 -1
View File
@@ -241,7 +241,11 @@
"goToSeries": "Gå till serie",
"goToSeason": "Gå till säsong",
"shufflePlay": "Blanda uppspelning",
"fileInfo": "Filinformation"
"fileInfo": "Filinformation",
"confirmDelete": "Är du säker på att du vill ta bort detta objekt från ditt filsystem?",
"deleteMultipleWarning": "Flera objekt kan komma att tas bort.",
"mediaDeletedSuccessfully": "Mediaobjekt borttaget",
"mediaFailedToDelete": "Kunde inte ta bort mediaobjekt"
},
"accessibility": {
"mediaCardMovie": "${title}, film",
+5 -1
View File
@@ -241,7 +241,11 @@
"goToSeries": "转到系列",
"goToSeason": "转到季",
"shufflePlay": "随机播放",
"fileInfo": "文件信息"
"fileInfo": "文件信息",
"confirmDelete": "确定要从文件系统中删除此项吗?",
"deleteMultipleWarning": "可能会删除多个项目。",
"mediaDeletedSuccessfully": "媒体项已成功删除",
"mediaFailedToDelete": "删除媒体项失败"
},
"accessibility": {
"mediaCardMovie": "${title}, 电影",
@@ -774,6 +774,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<PlexMetadata, LibraryBr
_lastFocusedContentVersion = _contentVersion;
}
},
onListRefresh: _loadItems,
);
}
}
+18 -5
View File
@@ -934,6 +934,13 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
_watchStateChanged = true;
_updateWatchState();
},
onListRefresh: () {
if (widget.isOffline) {
_loadSeasonsFromDownloads();
} else {
_loadSeasons();
}
},
),
),
);
@@ -969,6 +976,13 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
_watchStateChanged = true;
_updateWatchState();
},
onListRefresh: () {
if (widget.isOffline) {
_loadSeasonsFromDownloads();
} else {
_loadSeasons();
}
},
);
},
);
@@ -1589,11 +1603,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
),
),
),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom,
),
),
SliverPadding(padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom)),
],
),
// Sticky top bar with fading background
@@ -1717,6 +1727,7 @@ class _SeasonCard extends StatefulWidget {
final PlexClient? client;
final VoidCallback onTap;
final VoidCallback onRefresh;
final VoidCallback? onListRefresh;
final bool isOffline;
final String? localPosterPath;
@@ -1725,6 +1736,7 @@ class _SeasonCard extends StatefulWidget {
this.client,
required this.onTap,
required this.onRefresh,
this.onListRefresh,
this.isOffline = false,
this.localPosterPath,
});
@@ -1753,6 +1765,7 @@ class _SeasonCardState extends State<_SeasonCard> {
key: _contextMenuKey,
item: widget.season,
onRefresh: (ratingKey) => widget.onRefresh(),
onListRefresh: widget.onListRefresh,
onTap: widget.onTap,
child: Semantics(
label: "media-season-${widget.season.ratingKey}",
+5 -5
View File
@@ -234,14 +234,11 @@ class _SeasonDetailScreenState extends State<SeasonDetailScreen> with ItemUpdata
);
},
onRefresh: widget.isOffline ? null : updateItem,
onListRefresh: widget.isOffline ? null : _loadEpisodes,
);
}, childCount: _episodes.length),
),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom,
),
),
SliverPadding(padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom)),
],
),
),
@@ -266,6 +263,7 @@ class _EpisodeCard extends StatefulWidget {
final PlexClient? client;
final VoidCallback onTap;
final Future<void> Function(String)? onRefresh;
final Future<void> Function()? onListRefresh;
final bool autofocus;
final bool isOffline;
final String? localPosterPath;
@@ -275,6 +273,7 @@ class _EpisodeCard extends StatefulWidget {
this.client,
required this.onTap,
this.onRefresh,
this.onListRefresh,
this.autofocus = false,
this.isOffline = false,
this.localPosterPath,
@@ -338,6 +337,7 @@ class _EpisodeCardState extends State<_EpisodeCard> {
key: _contextMenuKey,
item: widget.episode,
onRefresh: widget.onRefresh,
onListRefresh: widget.onListRefresh,
onTap: widget.onTap,
child: InkWell(
key: Key(widget.episode.ratingKey),
+7
View File
@@ -1095,6 +1095,13 @@ class PlexClient {
await _dio.put('/actions/removeFromContinueWatching', queryParameters: {'ratingKey': ratingKey});
}
/// Delete a media item from the library
/// This permanently removes the item and its associated files from the server
/// Returns true if deletion was successful, false otherwise
Future<bool> deleteMediaItem(String ratingKey) async {
return _wrapBoolApiCall(() => _dio.delete('/library/metadata/$ratingKey'), 'Failed to delete media item');
}
/// Get server preferences
Future<Map<String, dynamic>> getServerPreferences() async {
final response = await _dio.get('/:/prefs');
+5
View File
@@ -45,6 +45,9 @@ class FocusableListTile extends StatefulWidget {
/// If true, consumes the first select key event to avoid accidental activation.
final bool suppressInitialSelect;
/// An optional color to display behind the menu item when being hovered.
final Color? hoverColor;
const FocusableListTile({
super.key,
this.title,
@@ -60,6 +63,7 @@ class FocusableListTile extends StatefulWidget {
this.autofocus = false,
this.contentPadding,
this.suppressInitialSelect = false,
this.hoverColor,
});
@override
@@ -84,6 +88,7 @@ class _FocusableListTileState extends State<FocusableListTile> {
contentPadding: widget.contentPadding,
focusNode: widget.suppressInitialSelect ? null : widget.focusNode,
autofocus: widget.suppressInitialSelect ? false : widget.autofocus,
hoverColor: widget.hoverColor,
);
if (!widget.suppressInitialSelect) {
+61 -1
View File
@@ -31,8 +31,10 @@ class _MenuAction {
final String value;
final IconData icon;
final String label;
final PlexMediaType? mediaType;
final Color? hoverColor;
_MenuAction({required this.value, required this.icon, required this.label});
_MenuAction({required this.value, required this.icon, required this.label, this.mediaType, this.hoverColor});
}
/// A reusable wrapper widget that adds a context menu (long press / right click)
@@ -244,6 +246,22 @@ class MediaContextMenuState extends State<MediaContextMenu> {
mediaType == PlexMediaType.season) {
menuActions.add(_MenuAction(value: 'add_to', icon: Symbols.add_rounded, label: t.common.addTo));
}
// Delete media item (for episodes, movies, shows, and seasons)
if (mediaType == PlexMediaType.episode ||
mediaType == PlexMediaType.movie ||
mediaType == PlexMediaType.show ||
mediaType == PlexMediaType.season) {
menuActions.add(
_MenuAction(
value: 'delete_media',
icon: Symbols.delete_rounded,
label: t.common.delete,
mediaType: mediaType,
hoverColor: Colors.red,
),
);
}
} // End of regular menu items else block
String? selected;
@@ -411,6 +429,10 @@ class MediaContextMenuState extends State<MediaContextMenu> {
case 'delete_download':
await _handleDeleteDownload(context);
break;
case 'delete_media':
await _handleDeleteMediaItem(context, mediaType);
break;
}
} finally {
_isContextMenuOpen = false;
@@ -1028,6 +1050,42 @@ class MediaContextMenuState extends State<MediaContextMenu> {
}
}
/// Handle delete media item action
/// This permanently removes the media item and its associated files from the server
Future<void> _handleDeleteMediaItem(BuildContext context, PlexMediaType? mediaType) async {
final client = _getClientForItem();
final metadata = widget.item as PlexMetadata;
final isMultipleMediaItems = mediaType == PlexMediaType.show || mediaType == PlexMediaType.season;
// Show confirmation dialog
final confirmed = await showDeleteConfirmation(
context,
title: t.common.delete,
message: "${t.mediaMenu.confirmDelete}${isMultipleMediaItems ? " ${t.mediaMenu.deleteMultipleWarning}" : ""}",
);
if (!confirmed || !context.mounted) return;
try {
final success = await client.deleteMediaItem(metadata.ratingKey);
if (context.mounted) {
if (success) {
showSuccessSnackBar(context, t.mediaMenu.mediaDeletedSuccessfully);
// Trigger list refresh to remove the item from the view
widget.onListRefresh?.call();
} else {
showErrorSnackBar(context, t.mediaMenu.mediaFailedToDelete);
}
}
} catch (e) {
appLogger.e(t.mediaMenu.mediaFailedToDelete, error: e);
if (context.mounted) {
showErrorSnackBar(context, t.messages.errorLoading(error: e.toString()));
}
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
@@ -1187,6 +1245,7 @@ class _FocusableContextMenuSheetState extends State<_FocusableContextMenuSheet>
leading: AppIcon(action.icon, fill: 1),
title: Text(action.label),
onTap: () => Navigator.pop(context, action.value),
hoverColor: action.hoverColor,
);
}),
],
@@ -1293,6 +1352,7 @@ class _FocusablePopupMenuState extends State<_FocusablePopupMenu> {
leading: AppIcon(action.icon, fill: 1, size: 20),
title: Text(action.label),
onTap: () => Navigator.pop(context, action.value),
hoverColor: action.hoverColor,
);
}).toList(),
),