From ff461d9f71e1489b833406cae4b0ddebe26c6208 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 9 Aug 2026 10:07:09 +0200 Subject: [PATCH] fix(livetv): explain a guide emptied by the favorites filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lib/i18n/az.i18n.json | 3 ++ lib/i18n/bg.i18n.json | 3 ++ lib/i18n/da.i18n.json | 3 ++ lib/i18n/de.i18n.json | 3 ++ lib/i18n/en.i18n.json | 3 ++ lib/i18n/es.i18n.json | 3 ++ lib/i18n/fr.i18n.json | 3 ++ lib/i18n/hu.i18n.json | 3 ++ lib/i18n/it.i18n.json | 3 ++ lib/i18n/ja.i18n.json | 3 ++ lib/i18n/kk.i18n.json | 3 ++ lib/i18n/ko.i18n.json | 3 ++ lib/i18n/nb.i18n.json | 3 ++ lib/i18n/nl.i18n.json | 3 ++ lib/i18n/pl.i18n.json | 3 ++ lib/i18n/pt.i18n.json | 3 ++ lib/i18n/ru.i18n.json | 3 ++ lib/i18n/strings.g.dart | 2 +- lib/i18n/strings_en.g.dart | 16 +++++- lib/i18n/sv.i18n.json | 3 ++ lib/i18n/tr.i18n.json | 3 ++ lib/i18n/uz.i18n.json | 3 ++ lib/i18n/zh-Hant.i18n.json | 3 ++ lib/i18n/zh.i18n.json | 3 ++ lib/screens/livetv/live_tv_screen.dart | 57 ++++++++++++++++---- test/screens/livetv/live_tv_screen_test.dart | 29 ++++++++-- 26 files changed, 155 insertions(+), 15 deletions(-) diff --git a/lib/i18n/az.i18n.json b/lib/i18n/az.i18n.json index 7830c175..bf8971a4 100644 --- a/lib/i18n/az.i18n.json +++ b/lib/i18n/az.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Kanala bax", "favorites": "Sevimlilər", "reorderFavorites": "Sevimliləri yenidən sırala", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "Sevimlilər yüklənə bilmədi.", "favoritesUpdateFailed": "Sevimlilər yenilənə bilmədi.", "joinSession": "Davam edən seansa qoşul", diff --git a/lib/i18n/bg.i18n.json b/lib/i18n/bg.i18n.json index fe68c2c9..bba19dfa 100644 --- a/lib/i18n/bg.i18n.json +++ b/lib/i18n/bg.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Гледай канал", "favorites": "Любими", "reorderFavorites": "Пренареди любимите", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "Любимите не можаха да се заредят. Проверете връзката си и опитайте отново.", "favoritesUpdateFailed": "Любимите не можаха да бъдат обновени. Проверете връзката си и опитайте отново.", "joinSession": "Присъедини се към текуща сесия", diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index 07eea859..88728fb7 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Se kanal", "favorites": "Favoritter", "reorderFavorites": "Omarranger favoritter", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "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.", "joinSession": "Deltag i igangværende session", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 959f8d6e..9c0f3ef4 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Kanal ansehen", "favorites": "Favoriten", "reorderFavorites": "Favoriten sortieren", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "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.", "joinSession": "Aktueller Sitzung beitreten", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 0add721f..3a2669f6 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Watch Channel", "favorites": "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.", "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Join Session in Progress", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index d9083576..47961665 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Ver canal", "favorites": "Favoritos", "reorderFavorites": "Reordenar favoritos", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "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.", "joinSession": "Unirse a sesión en curso", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index 25110058..afadacae 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Regarder la chaîne", "favorites": "Favoris", "reorderFavorites": "Réorganiser les favoris", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "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.", "joinSession": "Rejoindre la session en cours", diff --git a/lib/i18n/hu.i18n.json b/lib/i18n/hu.i18n.json index 60116ac6..f9429d36 100644 --- a/lib/i18n/hu.i18n.json +++ b/lib/i18n/hu.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Csatorna nézése", "favorites": "Kedvencek", "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.", "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", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 38f7b53f..d76d524d 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Guarda il canale", "favorites": "Preferiti", "reorderFavorites": "Riordina i preferiti", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "Impossibile caricare i preferiti. Controlla la connessione e riprova.", "favoritesUpdateFailed": "Impossibile aggiornare i preferiti. Controlla la connessione e riprova.", "joinSession": "Partecipa alla sessione in corso", diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index b00a6b84..21bcef84 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -1262,6 +1262,9 @@ "watchChannel": "チャンネルを視聴", "favorites": "お気に入り", "reorderFavorites": "お気に入りを並べ替え", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "お気に入りを読み込めませんでした。接続を確認してもう一度お試しください。", "favoritesUpdateFailed": "お気に入りを更新できませんでした。接続を確認してもう一度お試しください。", "joinSession": "進行中のセッションに参加", diff --git a/lib/i18n/kk.i18n.json b/lib/i18n/kk.i18n.json index 1facd63d..2712a099 100644 --- a/lib/i18n/kk.i18n.json +++ b/lib/i18n/kk.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Арнаны көру", "favorites": "Таңдаулылар", "reorderFavorites": "Таңдаулыларды қайта реттеу", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "Таңдаулыларды жүктеу мүмкін болмады.", "favoritesUpdateFailed": "Таңдаулыларды жаңарту мүмкін болмады.", "joinSession": "Сеансқа қосылу", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index b1199c67..268b47d7 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -1262,6 +1262,9 @@ "watchChannel": "채널 시청", "favorites": "즐겨찾기", "reorderFavorites": "즐겨찾기 순서 변경", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "즐겨찾기를 불러올 수 없습니다. 연결을 확인하고 다시 시도하세요.", "favoritesUpdateFailed": "즐겨찾기를 업데이트할 수 없습니다. 연결을 확인하고 다시 시도하세요.", "joinSession": "진행 중인 세션 참여", diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index 6602b649..a20387f6 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Se kanal", "favorites": "Favoritter", "reorderFavorites": "Endre rekkefølge på favoritter", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "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.", "joinSession": "Bli med i pågående økt", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 00f9be35..3e06f2a3 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Kanaal bekijken", "favorites": "Favorieten", "reorderFavorites": "Favorieten herordenen", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "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.", "joinSession": "Deelnemen aan lopende sessie", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index 248a0ee7..cc874e19 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -1286,6 +1286,9 @@ "watchChannel": "Oglądaj kanał", "favorites": "Ulubione", "reorderFavorites": "Zmień kolejność ulubionych", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "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.", "joinSession": "Dołącz do trwającej sesji", diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index 1b7e5689..3588ecf2 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Assistir ao canal", "favorites": "Favoritos", "reorderFavorites": "Reordenar favoritos", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "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.", "joinSession": "Entrar na sessão em andamento", diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index 581e1d78..6bafaacf 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -1286,6 +1286,9 @@ "watchChannel": "Смотреть канал", "favorites": "Избранное", "reorderFavorites": "Изменить порядок избранного", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "Не удалось загрузить избранное. Проверьте подключение и повторите попытку.", "favoritesUpdateFailed": "Не удалось обновить избранное. Проверьте подключение и повторите попытку.", "joinSession": "Присоединиться к текущему сеансу", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 40239216..ce6a2720 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,7 +4,7 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 22 -/// Strings: 38877 (1767 per locale) +/// Strings: 38880 (1767 per locale) // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index 0d5f6ca0..93c4ffd8 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -3259,6 +3259,15 @@ class Translations$liveTv$en { /// en: '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.' 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.favorites' => '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.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Join Session in Progress', @@ -7667,11 +7679,11 @@ extension on Translations { 'performanceOverlay.matrix' => 'Matrix', 'performanceOverlay.primaries' => 'Primaries', 'performanceOverlay.transfer' => 'Transfer', + _ => null, + } ?? switch (path) { 'performanceOverlay.renderFps' => 'Render FPS', 'performanceOverlay.displayFps' => 'Display FPS', 'performanceOverlay.avSync' => 'A/V Sync', - _ => null, - } ?? switch (path) { 'performanceOverlay.dropped' => 'Dropped', 'performanceOverlay.dvRpus' => 'DV RPUs', 'performanceOverlay.dvRpuAverage' => 'DV RPU Avg', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index 5e9e75db..32c25bab 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Titta på kanal", "favorites": "Favoriter", "reorderFavorites": "Ordna om favoriter", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "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.", "joinSession": "Gå med i pågående session", diff --git a/lib/i18n/tr.i18n.json b/lib/i18n/tr.i18n.json index 069f281a..b980cf61 100644 --- a/lib/i18n/tr.i18n.json +++ b/lib/i18n/tr.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Kanalı İzle", "favorites": "Favoriler", "reorderFavorites": "Favorileri Yeniden Sırala", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "Favoriler yüklenemedi. 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", diff --git a/lib/i18n/uz.i18n.json b/lib/i18n/uz.i18n.json index eef28eae..2ef5e778 100644 --- a/lib/i18n/uz.i18n.json +++ b/lib/i18n/uz.i18n.json @@ -1270,6 +1270,9 @@ "watchChannel": "Kanalni tomosha qilish", "favorites": "Tanlanganlar", "reorderFavorites": "Tanlanganlarni tartiblash", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "Tanlanganlarni yuklab boʻlmadi.", "favoritesUpdateFailed": "Tanlanganlarni yangilab boʻlmadi.", "joinSession": "Seansga qoʻshilish", diff --git a/lib/i18n/zh-Hant.i18n.json b/lib/i18n/zh-Hant.i18n.json index 4a52145b..9895b172 100644 --- a/lib/i18n/zh-Hant.i18n.json +++ b/lib/i18n/zh-Hant.i18n.json @@ -1262,6 +1262,9 @@ "watchChannel": "觀看頻道", "favorites": "最愛", "reorderFavorites": "重新排序最愛頻道", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "無法載入最愛頻道。請檢查網路連線後重試。", "favoritesUpdateFailed": "無法更新最愛頻道。請檢查網路連線後重試。", "joinSession": "加入進行中的「一起看」", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index f537edad..7afebd1b 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -1262,6 +1262,9 @@ "watchChannel": "观看频道", "favorites": "收藏", "reorderFavorites": "重新排序收藏", + "noFavoriteChannels": "", + "noFavoriteChannelsHint": "", + "showAllChannels": "", "favoritesLoadFailed": "无法加载收藏。请检查网络连接后重试。", "favoritesUpdateFailed": "无法更新收藏。请检查网络连接后重试。", "joinSession": "加入正在进行的会话", diff --git a/lib/screens/livetv/live_tv_screen.dart b/lib/screens/livetv/live_tv_screen.dart index f26e9c15..ee48f0d4 100644 --- a/lib/screens/livetv/live_tv_screen.dart +++ b/lib/screens/livetv/live_tv_screen.dart @@ -53,6 +53,10 @@ class _LiveTvScreenState extends State final _whatsOnTabKey = GlobalKey(); final _recordingsTabKey = GlobalKey(); + /// 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 /// when at least one Live TV server has `liveTvDvr` capability. List _visibleTabs = [LiveTvTab.guide, LiveTvTab.whatsOn]; @@ -96,6 +100,10 @@ class _LiveTvScreenState extends State 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}'; _FavoriteScope? _favoriteScopeForChannel(LiveTvChannel channel) { @@ -138,6 +146,7 @@ class _LiveTvScreenState extends State _guideTabFocusNode.dispose(); _whatsOnTabFocusNode.dispose(); _recordingsTabFocusNode.dispose(); + _guideEmptyStateActionFocusNode.dispose(); disposeTabNavigation(); super.dispose(); } @@ -496,6 +505,18 @@ class _LiveTvScreenState extends State }); } + /// 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) { _enqueueFavoriteMutation(() { final source = _sourceForChannel(channel); @@ -597,7 +618,12 @@ class _LiveTvScreenState extends State if (tabController.index < _visibleTabs.length) { switch (_visibleTabs[tabController.index]) { 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: _whatsOnTabKey.currentState?.focusFirstHub(); case LiveTvTab.recordings: @@ -683,14 +709,27 @@ class _LiveTvScreenState extends State Widget _buildTabContent(LiveTvTab tab, List guideChannels) { return switch (tab) { - LiveTvTab.guide => GuideTab( - key: _guideTabKey, - channels: guideChannels, - isFavoriteChannel: _isFavoriteChannel, - onToggleFavorite: _toggleFavorite, - onNavigateUp: focusTabBar, - onBack: onTabBarBack, - ), + 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, + channels: guideChannels, + isFavoriteChannel: _isFavoriteChannel, + onToggleFavorite: _toggleFavorite, + onNavigateUp: focusTabBar, + onBack: onTabBarBack, + ), LiveTvTab.whatsOn => WhatsOnTab( key: _whatsOnTabKey, channels: _channels, diff --git a/test/screens/livetv/live_tv_screen_test.dart b/test/screens/livetv/live_tv_screen_test.dart index 0ab672ca..aa56c054 100644 --- a/test/screens/livetv/live_tv_screen_test.dart +++ b/test/screens/livetv/live_tv_screen_test.dart @@ -35,7 +35,7 @@ void main() { 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); addTearDown(() async { await tester.pumpWidget(const SizedBox.shrink()); @@ -48,8 +48,31 @@ void main() { harness.liveTv.favorites.complete(const []); await tester.pumpAndSettle(); - expect(find.byIcon(Symbols.star_rounded), findsOneWidget); - expect(_guideChannels(tester), isEmpty); + expect(find.byType(GuideTab), findsNothing); + 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 {