feat(watchlist): add watchlist toggle to library context menus

Watchlist membership was only reachable from Explore cards and the
detail screen's action row, which drops the bookmark first on narrow
screens with no fallback in the overflow menu. Add an entry to
MediaContextMenu for movies and shows whenever a connected catalog
source can hold the item, covering card long-press everywhere and the
detail screen's overflow.

External-id resolution is session-cached per item on
CatalogSourcesProvider and shared with the detail screen. A cold cache
labels the entry "Add to Watchlist" and always adds (idempotent), so a
press can never turn into a surprise removal; "Remove" is offered once
cached membership proves it. Several capable sources open the same
per-source chooser the detail screen uses.

close #1822
This commit is contained in:
edde746
2026-08-09 17:14:48 +02:00
parent 9d13584c00
commit 716fe9b51b
34 changed files with 936 additions and 72 deletions
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Personajlar",
"addToWatchlist": "İzləmə siyahısına əlavə et",
"removeFromWatchlist": "İzləmə siyahısından sil",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "İzləmə siyahısı yenilənə bilmədi",
"watchlistNoMatch": "",
"notInLibrary": "Kitabxananızda yoxdur",
"inTheseLibraries": "Bu kitabxanalarda var",
"checkingLibrary": "Kitabxananız yoxlanılır...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Герои",
"addToWatchlist": "Добави в списъка за гледане",
"removeFromWatchlist": "Премахни от списъка за гледане",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Неуспешно обновяване на списъка за гледане",
"watchlistNoMatch": "",
"notInLibrary": "Не е в твоята библиотека",
"inTheseLibraries": "В тези библиотеки",
"checkingLibrary": "Проверка на твоята библиотека...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Figurer",
"addToWatchlist": "Føj til ønskeliste",
"removeFromWatchlist": "Fjern fra ønskeliste",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Kunne ikke opdatere ønskelisten",
"watchlistNoMatch": "",
"notInLibrary": "Ikke i dit bibliotek",
"inTheseLibraries": "I disse biblioteker",
"checkingLibrary": "Tjekker dit bibliotek...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Charaktere",
"addToWatchlist": "Zur Merkliste hinzufügen",
"removeFromWatchlist": "Von Merkliste entfernen",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Merkliste konnte nicht aktualisiert werden",
"watchlistNoMatch": "",
"notInLibrary": "Nicht in deiner Mediathek",
"inTheseLibraries": "In diesen Mediatheken",
"checkingLibrary": "Deine Mediathek wird überprüft …",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Characters",
"addToWatchlist": "Add to Watchlist",
"removeFromWatchlist": "Remove from Watchlist",
"addedToWatchlist": "Added to watchlist",
"removedFromWatchlist": "Removed from watchlist",
"watchlistUpdateFailed": "Couldn't update watchlist",
"watchlistNoMatch": "Couldn't match this item to a watchlist",
"notInLibrary": "Not in your library",
"inTheseLibraries": "In these libraries",
"checkingLibrary": "Checking your library...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Personajes",
"addToWatchlist": "Añadir a la lista de seguimiento",
"removeFromWatchlist": "Quitar de la lista de seguimiento",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "No se pudo actualizar la lista de seguimiento",
"watchlistNoMatch": "",
"notInLibrary": "No está en tu biblioteca",
"inTheseLibraries": "En estas bibliotecas",
"checkingLibrary": "Comprobando tu biblioteca...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Personnages",
"addToWatchlist": "Ajouter à la liste de suivi",
"removeFromWatchlist": "Retirer de la liste de suivi",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Impossible de mettre à jour la liste de suivi",
"watchlistNoMatch": "",
"notInLibrary": "Absent de votre bibliothèque",
"inTheseLibraries": "Dans ces bibliothèques",
"checkingLibrary": "Vérification de votre bibliothèque...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Karakterek",
"addToWatchlist": "Hozzáadás a Néznivalókhoz",
"removeFromWatchlist": "Eltávolítás a Néznivalókból",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Nem sikerült a Néznivalók frissítése",
"watchlistNoMatch": "",
"notInLibrary": "Nincs a könyvtáradban",
"inTheseLibraries": "Ezekben a könyvtárakban",
"checkingLibrary": "Könyvtár ellenőrzése...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Personaggi",
"addToWatchlist": "Aggiungi alla lista da guardare",
"removeFromWatchlist": "Rimuovi dalla lista da guardare",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Impossibile aggiornare la lista da guardare",
"watchlistNoMatch": "",
"notInLibrary": "Non è nella tua libreria",
"inTheseLibraries": "In queste librerie",
"checkingLibrary": "Ricerca nella tua libreria...",
+3
View File
@@ -1075,7 +1075,10 @@
"characters": "キャラクター",
"addToWatchlist": "ウォッチリストに追加",
"removeFromWatchlist": "ウォッチリストから削除",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "ウォッチリストを更新できませんでした",
"watchlistNoMatch": "",
"notInLibrary": "ライブラリにありません",
"inTheseLibraries": "これらのライブラリにあります",
"checkingLibrary": "ライブラリを確認中…",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Кейіпкерлер",
"addToWatchlist": "Көру тізіміне қосу",
"removeFromWatchlist": "Көру тізімінен өшіру",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Көру тізімін жаңарту мүмкін болмады",
"watchlistNoMatch": "",
"notInLibrary": "Кітапханаңызда жоқ",
"inTheseLibraries": "Осы кітапханаларда бар",
"checkingLibrary": "Кітапхана тексерілуде...",
+3
View File
@@ -1075,7 +1075,10 @@
"characters": "캐릭터",
"addToWatchlist": "관심 목록에 추가",
"removeFromWatchlist": "관심 목록에서 제거",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "관심 목록을 업데이트하지 못했습니다",
"watchlistNoMatch": "",
"notInLibrary": "라이브러리에 없음",
"inTheseLibraries": "이 라이브러리에 있음",
"checkingLibrary": "라이브러리 확인 중...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Figurer",
"addToWatchlist": "Legg til i ønskeliste",
"removeFromWatchlist": "Fjern fra ønskeliste",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Kunne ikke oppdatere ønskelisten",
"watchlistNoMatch": "",
"notInLibrary": "Ikke i biblioteket ditt",
"inTheseLibraries": "I disse bibliotekene",
"checkingLibrary": "Sjekker biblioteket ditt...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Personages",
"addToWatchlist": "Toevoegen aan kijklijst",
"removeFromWatchlist": "Verwijderen uit kijklijst",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Kon kijklijst niet bijwerken",
"watchlistNoMatch": "",
"notInLibrary": "Niet in je bibliotheek",
"inTheseLibraries": "In deze bibliotheken",
"checkingLibrary": "Je bibliotheek controleren...",
+3
View File
@@ -1090,7 +1090,10 @@
"characters": "Postacie",
"addToWatchlist": "Dodaj do listy do obejrzenia",
"removeFromWatchlist": "Usuń z listy do obejrzenia",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Nie udało się zaktualizować listy do obejrzenia",
"watchlistNoMatch": "",
"notInLibrary": "Nie ma tego w Twojej bibliotece",
"inTheseLibraries": "W tych bibliotekach",
"checkingLibrary": "Sprawdzanie Twojej biblioteki...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Personagens",
"addToWatchlist": "Adicionar à lista para assistir",
"removeFromWatchlist": "Remover da lista para assistir",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Não foi possível atualizar a lista para assistir",
"watchlistNoMatch": "",
"notInLibrary": "Não está na sua biblioteca",
"inTheseLibraries": "Nestas bibliotecas",
"checkingLibrary": "Verificando sua biblioteca...",
+3
View File
@@ -1090,7 +1090,10 @@
"characters": "Персонажи",
"addToWatchlist": "Добавить в список для просмотра",
"removeFromWatchlist": "Удалить из списка для просмотра",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Не удалось обновить список для просмотра",
"watchlistNoMatch": "",
"notInLibrary": "Нет в вашей библиотеке",
"inTheseLibraries": "В этих библиотеках",
"checkingLibrary": "Проверка вашей библиотеки...",
+1 -1
View File
@@ -4,7 +4,7 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 22
/// Strings: 38356 (1743 per locale)
/// Strings: 38359 (1743 per locale)
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+16 -4
View File
@@ -3054,9 +3054,18 @@ class Translations$explore$en {
/// en: 'Remove from Watchlist'
String get removeFromWatchlist => 'Remove from Watchlist';
/// en: 'Added to watchlist'
String get addedToWatchlist => 'Added to watchlist';
/// en: 'Removed from watchlist'
String get removedFromWatchlist => 'Removed from watchlist';
/// en: 'Couldn't update watchlist'
String get watchlistUpdateFailed => 'Couldn\'t update watchlist';
/// en: 'Couldn't match this item to a watchlist'
String get watchlistNoMatch => 'Couldn\'t match this item to a watchlist';
/// en: 'Not in your library'
String get notInLibrary => 'Not in your library';
@@ -7065,7 +7074,10 @@ extension on Translations {
'explore.characters' => 'Characters',
'explore.addToWatchlist' => 'Add to Watchlist',
'explore.removeFromWatchlist' => 'Remove from Watchlist',
'explore.addedToWatchlist' => 'Added to watchlist',
'explore.removedFromWatchlist' => 'Removed from watchlist',
'explore.watchlistUpdateFailed' => 'Couldn\'t update watchlist',
'explore.watchlistNoMatch' => 'Couldn\'t match this item to a watchlist',
'explore.notInLibrary' => 'Not in your library',
'explore.inTheseLibraries' => 'In these libraries',
'explore.checkingLibrary' => 'Checking your library...',
@@ -7102,11 +7114,11 @@ extension on Translations {
'explore.stats.viewersWeek' => ({required Object n}) => '${n} watched this week',
'explore.stats.viewersMonth' => ({required Object n}) => '${n} watched this month',
'explore.stats.viewersYear' => ({required Object n}) => '${n} watched this year',
_ => null,
} ?? switch (path) {
'explore.stats.viewersAllTime' => ({required Object n}) => '${n} viewers',
'explore.stats.planning' => ({required Object n}) => '${n} planning to watch',
'explore.stats.favorited' => ({required Object n}) => '${n} favorites',
_ => null,
} ?? switch (path) {
'explore.stats.dropRate' => ({required Object percent}) => '${percent} dropped it',
'explore.stats.comments' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: '${n} comment', other: '${n} comments', ),
'explore.stats.votes' => ({required Object n}) => '${n} votes',
@@ -7616,11 +7628,11 @@ extension on Translations {
'performanceOverlay.player' => 'Player',
'performanceOverlay.memory' => 'Memory',
'performanceOverlay.uiFps' => 'UI FPS',
_ => null,
} ?? switch (path) {
'externalPlayer.title' => 'External Player',
'externalPlayer.useExternalPlayer' => 'Use External Player',
'externalPlayer.useExternalPlayerDescription' => 'Open videos in another app',
_ => null,
} ?? switch (path) {
'externalPlayer.selectPlayer' => 'Select Player',
'externalPlayer.customPlayers' => 'Custom Players',
'externalPlayer.systemDefault' => 'System Default',
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Karaktärer",
"addToWatchlist": "Lägg till i bevakningslista",
"removeFromWatchlist": "Ta bort från bevakningslista",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Det gick inte att uppdatera bevakningslistan",
"watchlistNoMatch": "",
"notInLibrary": "Finns inte i ditt bibliotek",
"inTheseLibraries": "I dessa bibliotek",
"checkingLibrary": "Kontrollerar ditt bibliotek...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Karakterler",
"addToWatchlist": "İzleme Listesine Ekle",
"removeFromWatchlist": "İzleme Listesinden Kaldır",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "İzleme listesi güncellenemedi",
"watchlistNoMatch": "",
"notInLibrary": "Kitaplığınızda yok",
"inTheseLibraries": "Bu kitaplıklarda var",
"checkingLibrary": "Kitaplığınız kontrol ediliyor...",
+3
View File
@@ -1080,7 +1080,10 @@
"characters": "Qahramonlar",
"addToWatchlist": "Tomosha roʻyxatiga qoʻshish",
"removeFromWatchlist": "Tomosha roʻyxatidan oʻchirish",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "Tomosha roʻyxatini yangilab boʻlmadi",
"watchlistNoMatch": "",
"notInLibrary": "Kutubxonangizda yoʻq",
"inTheseLibraries": "Ushbu kutubxonalarda bor",
"checkingLibrary": "Kutubxona tekshirilmoqda...",
+3
View File
@@ -1075,7 +1075,10 @@
"characters": "角色",
"addToWatchlist": "新增至待看清單",
"removeFromWatchlist": "從待看清單移除",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "無法更新待看清單",
"watchlistNoMatch": "",
"notInLibrary": "不在您的媒體庫中",
"inTheseLibraries": "在這些媒體庫中",
"checkingLibrary": "正在檢查您的媒體庫…",
+3
View File
@@ -1075,7 +1075,10 @@
"characters": "角色",
"addToWatchlist": "添加到想看列表",
"removeFromWatchlist": "从想看列表移除",
"addedToWatchlist": "",
"removedFromWatchlist": "",
"watchlistUpdateFailed": "无法更新想看列表",
"watchlistNoMatch": "",
"notInLibrary": "不在你的媒体库中",
"inTheseLibraries": "在这些媒体库中",
"checkingLibrary": "正在检查你的媒体库…",