fix(livetv): explain a guide emptied by the favorites filter

With "Default to Favorite Channels" enabled and no favorites stored —
or only favorites left over from a since-rebuilt lineup — the favorites
filter reduced the guide to zero channels and GuideTab rendered just
the timeline bar: no rows, no message, no sign a filter was active.
Users read it as Live TV being broken; the Aug 8 report in #887 shows
44 channels and 447 grid programs loading in the log while the
screenshot shows a blank guide and 0 favorite channels.

When the filter removes every loaded channel, the guide tab now shows
an empty state naming the cause with a "Show All Channels" action that
clears the filter. The action stays D-pad reachable: the tab-bar focus
handoff falls through to the action's focus node while the empty state
replaces GuideTab, and activating it hands focus back to the restored
guide content. Favorites that match no loaded channel get the same
treatment as an empty favorites list.

Verified: flutter test test/screens/livetv/, analyzer parity,
clean_translations --check --strict, and slang codegen freshness.

Refs #887.
This commit is contained in:
edde746
2026-08-09 11:12:08 +02:00
parent de76c0a515
commit ff461d9f71
26 changed files with 155 additions and 15 deletions
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Kanala bax", "watchChannel": "Kanala bax",
"favorites": "Sevimlilər", "favorites": "Sevimlilər",
"reorderFavorites": "Sevimliləri yenidən sırala", "reorderFavorites": "Sevimliləri yenidən sırala",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Sevimlilər yüklənə bilmədi.", "favoritesLoadFailed": "Sevimlilər yüklənə bilmədi.",
"favoritesUpdateFailed": "Sevimlilər yenilənə bilmədi.", "favoritesUpdateFailed": "Sevimlilər yenilənə bilmədi.",
"joinSession": "Davam edən seansa qoşul", "joinSession": "Davam edən seansa qoşul",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Гледай канал", "watchChannel": "Гледай канал",
"favorites": "Любими", "favorites": "Любими",
"reorderFavorites": "Пренареди любимите", "reorderFavorites": "Пренареди любимите",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Любимите не можаха да се заредят. Проверете връзката си и опитайте отново.", "favoritesLoadFailed": "Любимите не можаха да се заредят. Проверете връзката си и опитайте отново.",
"favoritesUpdateFailed": "Любимите не можаха да бъдат обновени. Проверете връзката си и опитайте отново.", "favoritesUpdateFailed": "Любимите не можаха да бъдат обновени. Проверете връзката си и опитайте отново.",
"joinSession": "Присъедини се към текуща сесия", "joinSession": "Присъедини се към текуща сесия",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Se kanal", "watchChannel": "Se kanal",
"favorites": "Favoritter", "favorites": "Favoritter",
"reorderFavorites": "Omarranger favoritter", "reorderFavorites": "Omarranger favoritter",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Favoritter kunne ikke indlæses. Kontrollér forbindelsen, og prøv igen.", "favoritesLoadFailed": "Favoritter kunne ikke indlæses. Kontrollér forbindelsen, og prøv igen.",
"favoritesUpdateFailed": "Favoritterne kunne ikke opdateres. Kontrollér forbindelsen, og prøv igen.", "favoritesUpdateFailed": "Favoritterne kunne ikke opdateres. Kontrollér forbindelsen, og prøv igen.",
"joinSession": "Deltag i igangværende session", "joinSession": "Deltag i igangværende session",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Kanal ansehen", "watchChannel": "Kanal ansehen",
"favorites": "Favoriten", "favorites": "Favoriten",
"reorderFavorites": "Favoriten sortieren", "reorderFavorites": "Favoriten sortieren",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Favoriten konnten nicht geladen werden. Überprüfe deine Verbindung und versuche es erneut.", "favoritesLoadFailed": "Favoriten konnten nicht geladen werden. Überprüfe deine Verbindung und versuche es erneut.",
"favoritesUpdateFailed": "Favoriten konnten nicht aktualisiert werden. Prüfe deine Verbindung und versuche es erneut.", "favoritesUpdateFailed": "Favoriten konnten nicht aktualisiert werden. Prüfe deine Verbindung und versuche es erneut.",
"joinSession": "Aktueller Sitzung beitreten", "joinSession": "Aktueller Sitzung beitreten",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Watch Channel", "watchChannel": "Watch Channel",
"favorites": "Favorites", "favorites": "Favorites",
"reorderFavorites": "Reorder Favorites", "reorderFavorites": "Reorder Favorites",
"noFavoriteChannels": "No favorite channels",
"noFavoriteChannelsHint": "Show all channels, then long-press a channel to add it to your favorites.",
"showAllChannels": "Show All Channels",
"favoritesLoadFailed": "Could not load favorites. Check your connection and try again.", "favoritesLoadFailed": "Could not load favorites. Check your connection and try again.",
"favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.",
"joinSession": "Join Session in Progress", "joinSession": "Join Session in Progress",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Ver canal", "watchChannel": "Ver canal",
"favorites": "Favoritos", "favorites": "Favoritos",
"reorderFavorites": "Reordenar favoritos", "reorderFavorites": "Reordenar favoritos",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "No se pudieron cargar los favoritos. Comprueba tu conexión e inténtalo de nuevo.", "favoritesLoadFailed": "No se pudieron cargar los favoritos. Comprueba tu conexión e inténtalo de nuevo.",
"favoritesUpdateFailed": "No se pudieron actualizar los favoritos. Comprueba tu conexión e inténtalo de nuevo.", "favoritesUpdateFailed": "No se pudieron actualizar los favoritos. Comprueba tu conexión e inténtalo de nuevo.",
"joinSession": "Unirse a sesión en curso", "joinSession": "Unirse a sesión en curso",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Regarder la chaîne", "watchChannel": "Regarder la chaîne",
"favorites": "Favoris", "favorites": "Favoris",
"reorderFavorites": "Réorganiser les favoris", "reorderFavorites": "Réorganiser les favoris",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Impossible de charger les favoris. Vérifiez votre connexion et réessayez.", "favoritesLoadFailed": "Impossible de charger les favoris. Vérifiez votre connexion et réessayez.",
"favoritesUpdateFailed": "Impossible de mettre à jour les favoris. Vérifiez votre connexion et réessayez.", "favoritesUpdateFailed": "Impossible de mettre à jour les favoris. Vérifiez votre connexion et réessayez.",
"joinSession": "Rejoindre la session en cours", "joinSession": "Rejoindre la session en cours",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Csatorna nézése", "watchChannel": "Csatorna nézése",
"favorites": "Kedvencek", "favorites": "Kedvencek",
"reorderFavorites": "Kedvencek rendezése", "reorderFavorites": "Kedvencek rendezése",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Nem sikerült a kedvencek betöltése. Ellenőrizd a kapcsolatot és próbáld újra.", "favoritesLoadFailed": "Nem sikerült a kedvencek betöltése. Ellenőrizd a kapcsolatot és próbáld újra.",
"favoritesUpdateFailed": "Nem sikerült frissíteni a kedvenceket. Ellenőrizd a kapcsolatot, majd próbáld újra.", "favoritesUpdateFailed": "Nem sikerült frissíteni a kedvenceket. Ellenőrizd a kapcsolatot, majd próbáld újra.",
"joinSession": "Csatlakozás a folyamatban lévő adáshoz", "joinSession": "Csatlakozás a folyamatban lévő adáshoz",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Guarda il canale", "watchChannel": "Guarda il canale",
"favorites": "Preferiti", "favorites": "Preferiti",
"reorderFavorites": "Riordina i preferiti", "reorderFavorites": "Riordina i preferiti",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Impossibile caricare i preferiti. Controlla la connessione e riprova.", "favoritesLoadFailed": "Impossibile caricare i preferiti. Controlla la connessione e riprova.",
"favoritesUpdateFailed": "Impossibile aggiornare i preferiti. Controlla la connessione e riprova.", "favoritesUpdateFailed": "Impossibile aggiornare i preferiti. Controlla la connessione e riprova.",
"joinSession": "Partecipa alla sessione in corso", "joinSession": "Partecipa alla sessione in corso",
+3
View File
@@ -1262,6 +1262,9 @@
"watchChannel": "チャンネルを視聴", "watchChannel": "チャンネルを視聴",
"favorites": "お気に入り", "favorites": "お気に入り",
"reorderFavorites": "お気に入りを並べ替え", "reorderFavorites": "お気に入りを並べ替え",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "お気に入りを読み込めませんでした。接続を確認してもう一度お試しください。", "favoritesLoadFailed": "お気に入りを読み込めませんでした。接続を確認してもう一度お試しください。",
"favoritesUpdateFailed": "お気に入りを更新できませんでした。接続を確認してもう一度お試しください。", "favoritesUpdateFailed": "お気に入りを更新できませんでした。接続を確認してもう一度お試しください。",
"joinSession": "進行中のセッションに参加", "joinSession": "進行中のセッションに参加",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Арнаны көру", "watchChannel": "Арнаны көру",
"favorites": "Таңдаулылар", "favorites": "Таңдаулылар",
"reorderFavorites": "Таңдаулыларды қайта реттеу", "reorderFavorites": "Таңдаулыларды қайта реттеу",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Таңдаулыларды жүктеу мүмкін болмады.", "favoritesLoadFailed": "Таңдаулыларды жүктеу мүмкін болмады.",
"favoritesUpdateFailed": "Таңдаулыларды жаңарту мүмкін болмады.", "favoritesUpdateFailed": "Таңдаулыларды жаңарту мүмкін болмады.",
"joinSession": "Сеансқа қосылу", "joinSession": "Сеансқа қосылу",
+3
View File
@@ -1262,6 +1262,9 @@
"watchChannel": "채널 시청", "watchChannel": "채널 시청",
"favorites": "즐겨찾기", "favorites": "즐겨찾기",
"reorderFavorites": "즐겨찾기 순서 변경", "reorderFavorites": "즐겨찾기 순서 변경",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "즐겨찾기를 불러올 수 없습니다. 연결을 확인하고 다시 시도하세요.", "favoritesLoadFailed": "즐겨찾기를 불러올 수 없습니다. 연결을 확인하고 다시 시도하세요.",
"favoritesUpdateFailed": "즐겨찾기를 업데이트할 수 없습니다. 연결을 확인하고 다시 시도하세요.", "favoritesUpdateFailed": "즐겨찾기를 업데이트할 수 없습니다. 연결을 확인하고 다시 시도하세요.",
"joinSession": "진행 중인 세션 참여", "joinSession": "진행 중인 세션 참여",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Se kanal", "watchChannel": "Se kanal",
"favorites": "Favoritter", "favorites": "Favoritter",
"reorderFavorites": "Endre rekkefølge på favoritter", "reorderFavorites": "Endre rekkefølge på favoritter",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Kunne ikke laste inn favoritter. Kontroller tilkoblingen og prøv på nytt.", "favoritesLoadFailed": "Kunne ikke laste inn favoritter. Kontroller tilkoblingen og prøv på nytt.",
"favoritesUpdateFailed": "Kunne ikke oppdatere favorittene. Kontroller tilkoblingen og prøv på nytt.", "favoritesUpdateFailed": "Kunne ikke oppdatere favorittene. Kontroller tilkoblingen og prøv på nytt.",
"joinSession": "Bli med i pågående økt", "joinSession": "Bli med i pågående økt",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Kanaal bekijken", "watchChannel": "Kanaal bekijken",
"favorites": "Favorieten", "favorites": "Favorieten",
"reorderFavorites": "Favorieten herordenen", "reorderFavorites": "Favorieten herordenen",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Favorieten konden niet worden geladen. Controleer je verbinding en probeer het opnieuw.", "favoritesLoadFailed": "Favorieten konden niet worden geladen. Controleer je verbinding en probeer het opnieuw.",
"favoritesUpdateFailed": "Favorieten konden niet worden bijgewerkt. Controleer je verbinding en probeer het opnieuw.", "favoritesUpdateFailed": "Favorieten konden niet worden bijgewerkt. Controleer je verbinding en probeer het opnieuw.",
"joinSession": "Deelnemen aan lopende sessie", "joinSession": "Deelnemen aan lopende sessie",
+3
View File
@@ -1286,6 +1286,9 @@
"watchChannel": "Oglądaj kanał", "watchChannel": "Oglądaj kanał",
"favorites": "Ulubione", "favorites": "Ulubione",
"reorderFavorites": "Zmień kolejność ulubionych", "reorderFavorites": "Zmień kolejność ulubionych",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Nie udało się wczytać ulubionych. Sprawdź połączenie i spróbuj ponownie.", "favoritesLoadFailed": "Nie udało się wczytać ulubionych. Sprawdź połączenie i spróbuj ponownie.",
"favoritesUpdateFailed": "Nie udało się zaktualizować ulubionych. Sprawdź połączenie i spróbuj ponownie.", "favoritesUpdateFailed": "Nie udało się zaktualizować ulubionych. Sprawdź połączenie i spróbuj ponownie.",
"joinSession": "Dołącz do trwającej sesji", "joinSession": "Dołącz do trwającej sesji",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Assistir ao canal", "watchChannel": "Assistir ao canal",
"favorites": "Favoritos", "favorites": "Favoritos",
"reorderFavorites": "Reordenar favoritos", "reorderFavorites": "Reordenar favoritos",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Não foi possível carregar os favoritos. Verifique sua conexão e tente novamente.", "favoritesLoadFailed": "Não foi possível carregar os favoritos. Verifique sua conexão e tente novamente.",
"favoritesUpdateFailed": "Não foi possível atualizar os favoritos. Verifique sua conexão e tente novamente.", "favoritesUpdateFailed": "Não foi possível atualizar os favoritos. Verifique sua conexão e tente novamente.",
"joinSession": "Entrar na sessão em andamento", "joinSession": "Entrar na sessão em andamento",
+3
View File
@@ -1286,6 +1286,9 @@
"watchChannel": "Смотреть канал", "watchChannel": "Смотреть канал",
"favorites": "Избранное", "favorites": "Избранное",
"reorderFavorites": "Изменить порядок избранного", "reorderFavorites": "Изменить порядок избранного",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Не удалось загрузить избранное. Проверьте подключение и повторите попытку.", "favoritesLoadFailed": "Не удалось загрузить избранное. Проверьте подключение и повторите попытку.",
"favoritesUpdateFailed": "Не удалось обновить избранное. Проверьте подключение и повторите попытку.", "favoritesUpdateFailed": "Не удалось обновить избранное. Проверьте подключение и повторите попытку.",
"joinSession": "Присоединиться к текущему сеансу", "joinSession": "Присоединиться к текущему сеансу",
+1 -1
View File
@@ -4,7 +4,7 @@
/// To regenerate, run: `dart run slang` /// To regenerate, run: `dart run slang`
/// ///
/// Locales: 22 /// Locales: 22
/// Strings: 38877 (1767 per locale) /// Strings: 38880 (1767 per locale)
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint, unused_import // ignore_for_file: type=lint, unused_import
+14 -2
View File
@@ -3259,6 +3259,15 @@ class Translations$liveTv$en {
/// en: 'Reorder Favorites' /// en: 'Reorder Favorites'
String get reorderFavorites => 'Reorder Favorites'; String get reorderFavorites => 'Reorder Favorites';
/// en: 'No favorite channels'
String get noFavoriteChannels => 'No favorite channels';
/// en: 'Show all channels, then long-press a channel to add it to your favorites.'
String get noFavoriteChannelsHint => 'Show all channels, then long-press a channel to add it to your favorites.';
/// en: 'Show All Channels'
String get showAllChannels => 'Show All Channels';
/// en: 'Could not load favorites. Check your connection and try again.' /// en: 'Could not load favorites. Check your connection and try again.'
String get favoritesLoadFailed => 'Could not load favorites. Check your connection and try again.'; String get favoritesLoadFailed => 'Could not load favorites. Check your connection and try again.';
@@ -7281,6 +7290,9 @@ extension on Translations {
'liveTv.watchChannel' => 'Watch Channel', 'liveTv.watchChannel' => 'Watch Channel',
'liveTv.favorites' => 'Favorites', 'liveTv.favorites' => 'Favorites',
'liveTv.reorderFavorites' => 'Reorder Favorites', 'liveTv.reorderFavorites' => 'Reorder Favorites',
'liveTv.noFavoriteChannels' => 'No favorite channels',
'liveTv.noFavoriteChannelsHint' => 'Show all channels, then long-press a channel to add it to your favorites.',
'liveTv.showAllChannels' => 'Show All Channels',
'liveTv.favoritesLoadFailed' => 'Could not load favorites. Check your connection and try again.', 'liveTv.favoritesLoadFailed' => 'Could not load favorites. Check your connection and try again.',
'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.',
'liveTv.joinSession' => 'Join Session in Progress', 'liveTv.joinSession' => 'Join Session in Progress',
@@ -7667,11 +7679,11 @@ extension on Translations {
'performanceOverlay.matrix' => 'Matrix', 'performanceOverlay.matrix' => 'Matrix',
'performanceOverlay.primaries' => 'Primaries', 'performanceOverlay.primaries' => 'Primaries',
'performanceOverlay.transfer' => 'Transfer', 'performanceOverlay.transfer' => 'Transfer',
_ => null,
} ?? switch (path) {
'performanceOverlay.renderFps' => 'Render FPS', 'performanceOverlay.renderFps' => 'Render FPS',
'performanceOverlay.displayFps' => 'Display FPS', 'performanceOverlay.displayFps' => 'Display FPS',
'performanceOverlay.avSync' => 'A/V Sync', 'performanceOverlay.avSync' => 'A/V Sync',
_ => null,
} ?? switch (path) {
'performanceOverlay.dropped' => 'Dropped', 'performanceOverlay.dropped' => 'Dropped',
'performanceOverlay.dvRpus' => 'DV RPUs', 'performanceOverlay.dvRpus' => 'DV RPUs',
'performanceOverlay.dvRpuAverage' => 'DV RPU Avg', 'performanceOverlay.dvRpuAverage' => 'DV RPU Avg',
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Titta på kanal", "watchChannel": "Titta på kanal",
"favorites": "Favoriter", "favorites": "Favoriter",
"reorderFavorites": "Ordna om favoriter", "reorderFavorites": "Ordna om favoriter",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Det gick inte att läsa in favoriter. Kontrollera anslutningen och försök igen.", "favoritesLoadFailed": "Det gick inte att läsa in favoriter. Kontrollera anslutningen och försök igen.",
"favoritesUpdateFailed": "Det gick inte att uppdatera favoriterna. Kontrollera anslutningen och försök igen.", "favoritesUpdateFailed": "Det gick inte att uppdatera favoriterna. Kontrollera anslutningen och försök igen.",
"joinSession": "Gå med i pågående session", "joinSession": "Gå med i pågående session",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Kanalı İzle", "watchChannel": "Kanalı İzle",
"favorites": "Favoriler", "favorites": "Favoriler",
"reorderFavorites": "Favorileri Yeniden Sırala", "reorderFavorites": "Favorileri Yeniden Sırala",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Favoriler yüklenemedi. Bağlantınızı kontrol edip tekrar deneyin.", "favoritesLoadFailed": "Favoriler yüklenemedi. Bağlantınızı kontrol edip tekrar deneyin.",
"favoritesUpdateFailed": "Favoriler güncellenemedi. Bağlantınızı kontrol edip tekrar deneyin.", "favoritesUpdateFailed": "Favoriler güncellenemedi. Bağlantınızı kontrol edip tekrar deneyin.",
"joinSession": "Devam Eden Oturuma Katıl", "joinSession": "Devam Eden Oturuma Katıl",
+3
View File
@@ -1270,6 +1270,9 @@
"watchChannel": "Kanalni tomosha qilish", "watchChannel": "Kanalni tomosha qilish",
"favorites": "Tanlanganlar", "favorites": "Tanlanganlar",
"reorderFavorites": "Tanlanganlarni tartiblash", "reorderFavorites": "Tanlanganlarni tartiblash",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "Tanlanganlarni yuklab boʻlmadi.", "favoritesLoadFailed": "Tanlanganlarni yuklab boʻlmadi.",
"favoritesUpdateFailed": "Tanlanganlarni yangilab boʻlmadi.", "favoritesUpdateFailed": "Tanlanganlarni yangilab boʻlmadi.",
"joinSession": "Seansga qoʻshilish", "joinSession": "Seansga qoʻshilish",
+3
View File
@@ -1262,6 +1262,9 @@
"watchChannel": "觀看頻道", "watchChannel": "觀看頻道",
"favorites": "最愛", "favorites": "最愛",
"reorderFavorites": "重新排序最愛頻道", "reorderFavorites": "重新排序最愛頻道",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "無法載入最愛頻道。請檢查網路連線後重試。", "favoritesLoadFailed": "無法載入最愛頻道。請檢查網路連線後重試。",
"favoritesUpdateFailed": "無法更新最愛頻道。請檢查網路連線後重試。", "favoritesUpdateFailed": "無法更新最愛頻道。請檢查網路連線後重試。",
"joinSession": "加入進行中的「一起看」", "joinSession": "加入進行中的「一起看」",
+3
View File
@@ -1262,6 +1262,9 @@
"watchChannel": "观看频道", "watchChannel": "观看频道",
"favorites": "收藏", "favorites": "收藏",
"reorderFavorites": "重新排序收藏", "reorderFavorites": "重新排序收藏",
"noFavoriteChannels": "",
"noFavoriteChannelsHint": "",
"showAllChannels": "",
"favoritesLoadFailed": "无法加载收藏。请检查网络连接后重试。", "favoritesLoadFailed": "无法加载收藏。请检查网络连接后重试。",
"favoritesUpdateFailed": "无法更新收藏。请检查网络连接后重试。", "favoritesUpdateFailed": "无法更新收藏。请检查网络连接后重试。",
"joinSession": "加入正在进行的会话", "joinSession": "加入正在进行的会话",
+41 -2
View File
@@ -53,6 +53,10 @@ class _LiveTvScreenState extends State<LiveTvScreen>
final _whatsOnTabKey = GlobalKey<WhatsOnTabState>(); final _whatsOnTabKey = GlobalKey<WhatsOnTabState>();
final _recordingsTabKey = GlobalKey<RecordingsTabState>(); final _recordingsTabKey = GlobalKey<RecordingsTabState>();
/// Focus target for the "Show all channels" action shown when the favorites
/// filter empties the guide; lets D-pad users reach the action from the tab bar.
final _guideEmptyStateActionFocusNode = FocusNode(debugLabel: 'guide_empty_state_action');
/// Visible tabs in the current session. Recordings tab is included only /// Visible tabs in the current session. Recordings tab is included only
/// when at least one Live TV server has `liveTvDvr` capability. /// when at least one Live TV server has `liveTvDvr` capability.
List<LiveTvTab> _visibleTabs = [LiveTvTab.guide, LiveTvTab.whatsOn]; List<LiveTvTab> _visibleTabs = [LiveTvTab.guide, LiveTvTab.whatsOn];
@@ -96,6 +100,10 @@ class _LiveTvScreenState extends State<LiveTvScreen>
sourceForChannel: _sourceForChannel, sourceForChannel: _sourceForChannel,
); );
/// True when the favorites filter removed every loaded channel, so the guide
/// tab shows an explanatory empty state instead of a bare timeline.
bool get _guideShowsFavoritesEmptyState => _channels.isNotEmpty && _filteredChannels.isEmpty;
String _liveServerScopeKey(LiveTvServerInfo serverInfo) => '${serverInfo.serverId}\u0000${serverInfo.dvrKey}'; String _liveServerScopeKey(LiveTvServerInfo serverInfo) => '${serverInfo.serverId}\u0000${serverInfo.dvrKey}';
_FavoriteScope? _favoriteScopeForChannel(LiveTvChannel channel) { _FavoriteScope? _favoriteScopeForChannel(LiveTvChannel channel) {
@@ -138,6 +146,7 @@ class _LiveTvScreenState extends State<LiveTvScreen>
_guideTabFocusNode.dispose(); _guideTabFocusNode.dispose();
_whatsOnTabFocusNode.dispose(); _whatsOnTabFocusNode.dispose();
_recordingsTabFocusNode.dispose(); _recordingsTabFocusNode.dispose();
_guideEmptyStateActionFocusNode.dispose();
disposeTabNavigation(); disposeTabNavigation();
super.dispose(); super.dispose();
} }
@@ -496,6 +505,18 @@ class _LiveTvScreenState extends State<LiveTvScreen>
}); });
} }
/// Clears the favorites filter from the guide's empty state. When the action
/// button owned the focus (TV/D-pad), hand focus to the guide content that
/// replaces it so focus is not dropped.
void _showAllChannelsFromEmptyState() {
final hadFocus = _guideEmptyStateActionFocusNode.hasFocus;
_toggleFavoritesFilter();
if (!hadFocus) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _focusCurrentTab();
});
}
void _toggleFavorite(LiveTvChannel channel) { void _toggleFavorite(LiveTvChannel channel) {
_enqueueFavoriteMutation(() { _enqueueFavoriteMutation(() {
final source = _sourceForChannel(channel); final source = _sourceForChannel(channel);
@@ -597,7 +618,12 @@ class _LiveTvScreenState extends State<LiveTvScreen>
if (tabController.index < _visibleTabs.length) { if (tabController.index < _visibleTabs.length) {
switch (_visibleTabs[tabController.index]) { switch (_visibleTabs[tabController.index]) {
case LiveTvTab.guide: case LiveTvTab.guide:
_guideTabKey.currentState?.focusContent(); final guideState = _guideTabKey.currentState;
if (guideState != null) {
guideState.focusContent();
} else if (_guideShowsFavoritesEmptyState && _guideEmptyStateActionFocusNode.context != null) {
_guideEmptyStateActionFocusNode.requestFocus();
}
case LiveTvTab.whatsOn: case LiveTvTab.whatsOn:
_whatsOnTabKey.currentState?.focusFirstHub(); _whatsOnTabKey.currentState?.focusFirstHub();
case LiveTvTab.recordings: case LiveTvTab.recordings:
@@ -683,7 +709,20 @@ class _LiveTvScreenState extends State<LiveTvScreen>
Widget _buildTabContent(LiveTvTab tab, List<LiveTvChannel> guideChannels) { Widget _buildTabContent(LiveTvTab tab, List<LiveTvChannel> guideChannels) {
return switch (tab) { return switch (tab) {
LiveTvTab.guide => GuideTab( LiveTvTab.guide =>
guideChannels.isEmpty && _channels.isNotEmpty
? EmptyStateWidget(
icon: Symbols.star_outline_rounded,
message: t.liveTv.noFavoriteChannels,
subtitle: t.liveTv.noFavoriteChannelsHint,
actionLabel: t.liveTv.showAllChannels,
actionIcon: Symbols.list_rounded,
actionFocusNode: _guideEmptyStateActionFocusNode,
onAction: _showAllChannelsFromEmptyState,
onActionNavigateUp: focusTabBar,
onActionBack: onTabBarBack,
)
: GuideTab(
key: _guideTabKey, key: _guideTabKey,
channels: guideChannels, channels: guideChannels,
isFavoriteChannel: _isFavoriteChannel, isFavoriteChannel: _isFavoriteChannel,
+26 -3
View File
@@ -35,7 +35,7 @@ void main() {
await settings.write(SettingsService.liveTvDefaultFavorites, true); await settings.write(SettingsService.liveTvDefaultFavorites, true);
}); });
testWidgets('loaded empty favorites produces an empty Guide after preserving channels during load', (tester) async { testWidgets('loaded empty favorites shows the favorites empty state and can restore all channels', (tester) async {
final harness = await _pumpLiveTvScreen(tester); final harness = await _pumpLiveTvScreen(tester);
addTearDown(() async { addTearDown(() async {
await tester.pumpWidget(const SizedBox.shrink()); await tester.pumpWidget(const SizedBox.shrink());
@@ -48,8 +48,31 @@ void main() {
harness.liveTv.favorites.complete(const []); harness.liveTv.favorites.complete(const []);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byIcon(Symbols.star_rounded), findsOneWidget); expect(find.byType(GuideTab), findsNothing);
expect(_guideChannels(tester), isEmpty); expect(find.text(t.liveTv.noFavoriteChannels), findsOneWidget);
expect(find.text(t.liveTv.showAllChannels), findsOneWidget);
await tester.tap(find.text(t.liveTv.showAllChannels));
await tester.pumpAndSettle();
expect(find.text(t.liveTv.noFavoriteChannels), findsNothing);
expect(find.byIcon(Symbols.star_outline_rounded), findsOneWidget);
expect(_guideChannels(tester).map((channel) => channel.key), ['channel-a']);
});
testWidgets('favorites matching no loaded channel show the favorites empty state', (tester) async {
final harness = await _pumpLiveTvScreen(tester);
addTearDown(() async {
await tester.pumpWidget(const SizedBox.shrink());
harness.dispose();
});
harness.liveTv.favorites.complete([FavoriteChannel(id: 'channel-gone', source: 'server://server-a/provider-a')]);
await tester.pumpAndSettle();
expect(find.byType(GuideTab), findsNothing);
expect(find.text(t.liveTv.noFavoriteChannels), findsOneWidget);
expect(find.text(t.liveTv.showAllChannels), findsOneWidget);
}); });
testWidgets('refresh keeps the favorites filter narrow while favorites reload', (tester) async { testWidgets('refresh keeps the favorites filter narrow while favorites reload', (tester) async {