From 4f5781bc20e368c4799a993bc74e2fdd6b9004df Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 20 Feb 2026 20:14:01 +0100 Subject: [PATCH] Show toolbar buttons and tab picker during channel loading Display the view options button, channel menu, and content type tabs immediately when the cached header is shown, instead of waiting for the full channel data to load. The spinner now appears only in the content area below the tabs. --- Yattee/Localizable.xcstrings | 47 +++++++++++---------- Yattee/Views/Channel/ChannelView.swift | 56 +++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index 63585317..7994517b 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -10953,6 +10953,7 @@ } }, "settings.icloud.dev.badge" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -10963,6 +10964,7 @@ } }, "settings.icloud.dev.footer" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -10973,6 +10975,7 @@ } }, "settings.icloud.dev.title" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -14649,17 +14652,6 @@ } } }, - "sources.field.proxiesVideos" : { - "comment" : "Toggle label for video proxy option", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Proxy videos" - } - } - } - }, "sources.field.password" : { "comment" : "Field label for password", "localizations" : { @@ -14704,6 +14696,17 @@ } } }, + "sources.field.proxiesVideos" : { + "comment" : "Toggle label for video proxy option", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proxy videos" + } + } + } + }, "sources.field.serverVersion" : { "comment" : "Field label for server version in Yattee Server info", "localizations" : { @@ -14792,17 +14795,6 @@ } } }, - "sources.footer.proxiesVideos" : { - "comment" : "Footer text explaining video proxy option", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Route video streams through this instance instead of connecting directly. Enable if direct video playback is blocked." - } - } - } - }, "sources.footer.auth" : { "comment" : "Footer text for authentication section", "localizations" : { @@ -14847,6 +14839,17 @@ } } }, + "sources.footer.proxiesVideos" : { + "comment" : "Footer text explaining video proxy option", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Route video streams through this instance instead of connecting directly. Enable if direct video playback is blocked." + } + } + } + }, "sources.footer.remoteServer" : { "comment" : "Footer text for remote server URL entry", "localizations" : { diff --git a/Yattee/Views/Channel/ChannelView.swift b/Yattee/Views/Channel/ChannelView.swift index 38063129..3b0e684e 100644 --- a/Yattee/Views/Channel/ChannelView.swift +++ b/Yattee/Views/Channel/ChannelView.swift @@ -381,10 +381,17 @@ struct ChannelView: View { header(name: cached.name, thumbnailURL: cached.thumbnailURL, bannerURL: cached.bannerURL) .id("channelTop") - // Centered spinner for content area + // Show tab picker during loading (doesn't depend on channel) + if supportsChannelTabs { + contentTypePicker + .padding(.horizontal) + .padding(.vertical, 8) + } + + // Centered spinner for content area below tabs ProgressView() .frame(maxWidth: .infinity) - .padding(.top, 60) + .padding(.top, 40) } } .onChange(of: geometry.size.width, initial: true) { _, newWidth in @@ -418,11 +425,56 @@ struct ChannelView: View { } .opacity(collapsedTitleOpacity) } + + ToolbarItem(placement: .primaryAction) { + Button { + showViewOptions = true + } label: { + Label(String(localized: "viewOptions.title"), systemImage: "slider.horizontal.3") + } + .liquidGlassTransitionSource(id: "channelViewOptions", in: sheetTransition) + } + + #if !os(tvOS) + if #available(iOS 26, macOS 26, *) { + ToolbarSpacer(.fixed, placement: .primaryAction) + } + #endif + + ToolbarItem(placement: .primaryAction) { + channelMenu + } } + .sheet(isPresented: $showViewOptions) { + ViewOptionsSheet( + layout: $layout, + rowStyle: $rowStyle, + gridColumns: $gridColumns, + hideWatched: $hideWatched, + maxGridColumns: gridConfig.maxColumns + ) + .liquidGlassSheetContent(sourceID: "channelViewOptions", in: sheetTransition) + } + #if os(iOS) + .toolbarBackground(collapseProgress > 0.8 ? .visible : .hidden, for: .navigationBar) + .navigationBarTitleDisplayMode(.inline) + #endif .modifier(ChannelScrollOffsetModifier( scrollOffset: $scrollOffset, isPlayerExpanded: appEnvironment?.navigationCoordinator.isPlayerExpanded ?? false )) + .confirmationDialog( + String(localized: "channel.unsubscribe.confirmation.title"), + isPresented: $showingUnsubscribeConfirmation, + titleVisibility: .visible + ) { + Button(String(localized: "channel.unsubscribe.confirmation.action"), role: .destructive) { + unsubscribe() + } + Button(String(localized: "common.cancel"), role: .cancel) {} + } message: { + Text(String(localized: "channel.unsubscribe.confirmation.message")) + } } // MARK: - Header