From 39f5bf2b5f5737fa3e95320e673e345089798365 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 27 Sep 2026 11:07:38 +0200 Subject: [PATCH] fix(settings): keep the M3U groups view when every group is locked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With every group locked the filtered channel list is empty, so the container showed its generic empty state and the groups rail's "N locked · Enter PIN to show" row never appeared. Co-Authored-By: Claude Opus 5.5 --- docs/architecture/parental-lock.md | 5 ++++- .../channel-list-container.component.html | 2 +- .../channel-list-container.component.spec.ts | 20 +++++++++++++++++++ .../channel-list-container.component.ts | 11 ++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/docs/architecture/parental-lock.md b/docs/architecture/parental-lock.md index 008f9dfc0..d521db95c 100644 --- a/docs/architecture/parental-lock.md +++ b/docs/architecture/parental-lock.md @@ -229,7 +229,10 @@ on either side. - **M3U groups rail:** `ChannelListContainerComponent` derives `withheldGroupCount` (groups of the playlist the active lock withholds) and the groups rail renders the same "N locked · Enter PIN to show" row - as the portal category rail, so locked groups do not simply vanish. + as the portal category rail, so locked groups do not simply vanish — + also when EVERY group is locked: the container keeps the groups view + (`showChannelViews`) and the groups view keeps its rail instead of the + generic empty state. - **Xtream (PWA):** `PwaXtreamDataSource` drops withheld categories, streams and search hits at read time; the same reloads apply. - **Warm-cache detection (Electron):** the filtered category/content reads diff --git a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.html b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.html index 67eac8676..0134bbead 100644 --- a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.html +++ b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.html @@ -3,7 +3,7 @@ [view]="activeView()" [showEpg]="shouldShowEpg()" /> -} @else if (displayedChannels().length) { +} @else if (showChannelViews()) { @if ( !compact() && viewTitle() && diff --git a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.spec.ts b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.spec.ts index 09775f0dd..712abc7cc 100644 --- a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.spec.ts +++ b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.spec.ts @@ -201,6 +201,26 @@ describe('ChannelListContainerComponent', () => { fixture = TestBed.createComponent(ChannelListContainerComponent); }); + it('keeps the groups view (and its unlock row) when every group is withheld', () => { + const withheld = signal(0); + Object.defineProperty(fixture.componentInstance, 'withheldGroupCount', { + value: withheld, + }); + fixture.componentRef.setInput('activeView', 'groups'); + fixture.detectChanges(); + const component = fixture.componentInstance; + + // No channels and nothing withheld: the generic empty state. + expect(component.showChannelViews()).toBe(false); + + withheld.set(3); + expect(component.showChannelViews()).toBe(true); + + // Other views have no unlock row to show. + fixture.componentRef.setInput('activeView', 'all'); + expect(component.showChannelViews()).toBe(false); + }); + it('does not clear the shared channel list on destroy', () => { fixture.detectChanges(); diff --git a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.ts b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.ts index 29ed08880..046ba9d3c 100644 --- a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.ts +++ b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.ts @@ -354,6 +354,17 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy { withheld.has(group.key) ).length; }); + /** + * Whether the channel views render instead of the generic empty state. + * With every M3U group locked the filtered list is empty, yet the groups + * view must still render: it carries the "N locked · Enter PIN to show" + * row, the only in-context way back. + */ + readonly showChannelViews = computed( + () => + this.displayedChannels().length > 0 || + (this.activeView() === 'groups' && this.withheldGroupCount() > 0) + ); /** Group titles currently withheld: locked AND the lock is active. */ private readonly withheldGroupTitles = computed(() => { this.parentalLock.version();