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.
This commit is contained in:
Arkadiusz Fal
2026-05-28 20:34:38 +02:00
parent ef213307dc
commit bcd4206c55
4 changed files with 152 additions and 6 deletions

View File

@@ -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" : {

View File

@@ -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

View File

@@ -27,6 +27,9 @@ struct HomeShortcutCardView<StatusIndicator: View>: 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<StatusIndicator: View>: View {
subtitle: String,
showsCount: Bool = true,
colorfulColor: Color = .accentColor,
styleOverride: HomeShortcutCardStyle? = nil,
statusIndicator: StatusIndicator?
) {
self.icon = icon
@@ -44,6 +48,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
self.subtitle = subtitle
self.showsCount = showsCount
self.colorfulColor = colorfulColor
self.styleOverride = styleOverride
self.statusIndicator = statusIndicator
}
@@ -53,7 +58,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: 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
}
}

View File

@@ -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