From bcd4206c552b8eb29f0fe7665f4c58a530dfa5cb Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Thu, 28 May 2026 20:34:38 +0200 Subject: [PATCH] Move Home shortcut style picker to its own page with live preview Replace the inline Plain/Accent/Colorful picker in Home customization with a navigation row that opens a dedicated page. The new page keeps the style selector and renders a non-interactive preview of every shortcut type so the chosen style is visible before committing. - Add an optional styleOverride to HomeShortcutCardView so the preview can reflect the not-yet-saved selection. - On macOS, navigate via navigationDestination(isPresented:) instead of a List-embedded NavigationLink, which otherwise stays stuck in its selected state after popping back and can't be reopened. --- Yattee/Localizable.xcstrings | 11 +++ Yattee/Views/Home/HomeSettingsView.swift | 40 +++++++- Yattee/Views/Home/HomeShortcutCardView.swift | 11 ++- Yattee/Views/Home/HomeShortcutStyleView.swift | 96 +++++++++++++++++++ 4 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 Yattee/Views/Home/HomeShortcutStyleView.swift diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index 5b73506a..b47acff6 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -4214,6 +4214,17 @@ } } }, + "home.settings.shortcuts.style.preview" : { + "comment" : "Header for the shortcut style preview section", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preview" + } + } + } + }, "home.shortcuts.style.accent" : { "comment" : "Accent card style option", "localizations" : { diff --git a/Yattee/Views/Home/HomeSettingsView.swift b/Yattee/Views/Home/HomeSettingsView.swift index 9ae64b0b..15713f39 100644 --- a/Yattee/Views/Home/HomeSettingsView.swift +++ b/Yattee/Views/Home/HomeSettingsView.swift @@ -29,6 +29,13 @@ struct HomeSettingsView: View { // Edit mode for delete functionality @State private var isEditMode = false + #if os(macOS) + // macOS: drive the style page programmatically. A List-embedded NavigationLink + // gets stuck in the selected (blue) state after popping back and can't be + // re-activated, so navigate via navigationDestination(isPresented:) instead. + @State private var showingStyleSettings = false + #endif + private var settingsManager: SettingsManager? { appEnvironment?.settingsManager } var body: some View { @@ -46,6 +53,9 @@ struct HomeSettingsView: View { #endif #if os(macOS) .listStyle(.inset) + .navigationDestination(isPresented: $showingStyleSettings) { + HomeShortcutStyleView(style: $shortcutCardStyle) + } #endif #if !os(tvOS) .navigationTitle(String(localized: "home.settings.title")) @@ -75,13 +85,35 @@ struct HomeSettingsView: View { } .pickerStyle(.segmented) - // Card style picker (Plain / Accent / Colorful) — only for cards layout + // Card style (Plain / Accent / Colorful) — navigates to a page with + // a selector and a live preview. Only for cards layout. if shortcutLayout == .cards { - Picker(String(localized: "home.settings.shortcuts.style"), selection: $shortcutCardStyle) { - ForEach(HomeShortcutCardStyle.allCases, id: \.self) { style in - Text(style.displayName).tag(style) + #if os(macOS) + Button { + showingStyleSettings = true + } label: { + HStack(spacing: 8) { + Label(String(localized: "home.settings.shortcuts.style"), systemImage: "paintpalette") + Spacer() + Text(shortcutCardStyle.displayName) + .foregroundStyle(.secondary) + Image(systemName: "chevron.right") + .font(.caption) + .foregroundStyle(.tertiary) } + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) } + .buttonStyle(.plain) + #else + SettingsNavigationRow( + "home.settings.shortcuts.style", + systemImage: "paintpalette", + trailing: { Text(shortcutCardStyle.displayName) } + ) { + HomeShortcutStyleView(style: $shortcutCardStyle) + } + #endif } #endif diff --git a/Yattee/Views/Home/HomeShortcutCardView.swift b/Yattee/Views/Home/HomeShortcutCardView.swift index bbaf9512..09a4a978 100644 --- a/Yattee/Views/Home/HomeShortcutCardView.swift +++ b/Yattee/Views/Home/HomeShortcutCardView.swift @@ -27,6 +27,9 @@ struct HomeShortcutCardView: View { var showsCount: Bool /// Fixed color used when the "colorful" card style is active. var colorfulColor: Color + /// Explicit style override. When set, takes precedence over the global + /// setting — used to render live previews of a not-yet-saved style. + var styleOverride: HomeShortcutCardStyle? var statusIndicator: StatusIndicator? init( @@ -36,6 +39,7 @@ struct HomeShortcutCardView: View { subtitle: String, showsCount: Bool = true, colorfulColor: Color = .accentColor, + styleOverride: HomeShortcutCardStyle? = nil, statusIndicator: StatusIndicator? ) { self.icon = icon @@ -44,6 +48,7 @@ struct HomeShortcutCardView: View { self.subtitle = subtitle self.showsCount = showsCount self.colorfulColor = colorfulColor + self.styleOverride = styleOverride self.statusIndicator = statusIndicator } @@ -53,7 +58,7 @@ struct HomeShortcutCardView: View { #if os(tvOS) return .plain #else - return appEnvironment?.settingsManager.homeShortcutCardStyle ?? .plain + return styleOverride ?? (appEnvironment?.settingsManager.homeShortcutCardStyle ?? .plain) #endif } @@ -296,7 +301,8 @@ extension HomeShortcutCardView where StatusIndicator == EmptyView { count: Int, subtitle: String, showsCount: Bool = true, - colorfulColor: Color = .accentColor + colorfulColor: Color = .accentColor, + styleOverride: HomeShortcutCardStyle? = nil ) { self.icon = icon self.title = title @@ -304,6 +310,7 @@ extension HomeShortcutCardView where StatusIndicator == EmptyView { self.subtitle = subtitle self.showsCount = showsCount self.colorfulColor = colorfulColor + self.styleOverride = styleOverride self.statusIndicator = nil } } diff --git a/Yattee/Views/Home/HomeShortcutStyleView.swift b/Yattee/Views/Home/HomeShortcutStyleView.swift new file mode 100644 index 00000000..b6aa9c27 --- /dev/null +++ b/Yattee/Views/Home/HomeShortcutStyleView.swift @@ -0,0 +1,96 @@ +// +// HomeShortcutStyleView.swift +// Yattee +// +// Dedicated page for choosing the Home shortcut card style, with a live +// non-interactive preview of every shortcut type in the selected style. +// + +#if !os(tvOS) +import SwiftUI + +struct HomeShortcutStyleView: View { + @Binding var style: HomeShortcutCardStyle + + private let previewColumns = [GridItem(.adaptive(minimum: 150), spacing: 16)] + + /// All static shortcut types, previewed regardless of the user's current + /// Home configuration so every style variation is visible. + private let previewItems = HomeShortcutItem.defaultOrder + + var body: some View { + List { + Section { + Picker(String(localized: "home.settings.shortcuts.style"), selection: $style) { + ForEach(HomeShortcutCardStyle.allCases, id: \.self) { style in + Text(style.displayName).tag(style) + } + } + #if os(iOS) + .pickerStyle(.segmented) + #endif + } + + Section { + LazyVGrid(columns: previewColumns, spacing: 16) { + ForEach(previewItems) { item in + HomeShortcutCardView( + icon: item.icon, + title: item.localizedTitle, + count: sampleCount(for: item), + subtitle: "", + showsCount: showsCount(for: item), + colorfulColor: item.cardColor, + styleOverride: style + ) + } + } + .padding(.vertical, 4) + .allowsHitTesting(false) + } header: { + Text(String(localized: "home.settings.shortcuts.style.preview")) + } + } + #if os(macOS) + .listStyle(.inset) + #endif + .navigationTitle(String(localized: "home.settings.shortcuts.style")) + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + } + + /// Shortcuts without a meaningful count hide it in the filled layout, + /// mirroring `HomeView`'s real card configuration. + private func showsCount(for item: HomeShortcutItem) -> Bool { + switch item { + case .openURL, .remoteControl, .subscriptions: + return false + default: + return true + } + } + + /// Representative count so the accent/colorful (Reminders-style) layout + /// looks realistic in the preview. + private func sampleCount(for item: HomeShortcutItem) -> Int { + switch item { + case .playlists: return 8 + case .bookmarks: return 12 + case .continueWatching: return 3 + case .history: return 42 + case .downloads: return 5 + case .channels: return 24 + case .mediaSources: return 2 + default: return 0 + } + } +} + +#Preview { + NavigationStack { + HomeShortcutStyleView(style: .constant(.colorful)) + .appEnvironment(.preview) + } +} +#endif