mirror of
https://github.com/yattee/yattee.git
synced 2026-07-19 22:02:10 +00:00
Split home shortcut card style into Layout, Color, and Palette
Decouple the two concerns that were entangled in a single card-style setting: - Layout (Compact / Regular) now only controls positioning of the icon / title / subtitle / counter. - Color (Soft / Vibrant) is a new independent setting: Soft = tinted background with palette-colored icon and neutral text; Vibrant = solid palette fill with white icon/text. - Palette (Accent first, then Classic/Sunset/Meadow/Berry/Grape/Custom) now applies to both layouts and both colors, and is always shown. Adds section headers (Layout / Color / Palette) to the style page and fixes the Compact preview to show the count subtitle line. Default is Regular + Soft + Accent.
This commit is contained in:
@@ -66,6 +66,7 @@ enum SettingsKey: String, CaseIterable {
|
||||
case homeShortcutVisibility
|
||||
case homeShortcutLayout
|
||||
case homeShortcutCardStyle
|
||||
case homeShortcutCardColor
|
||||
case homeShortcutColorfulPalette
|
||||
case homeShortcutCustomPaletteColors
|
||||
case homeSectionOrder
|
||||
@@ -141,7 +142,7 @@ enum SettingsKey: String, CaseIterable {
|
||||
.macPlayerSeparateWindow, .macPlayerFloating, .listStyle,
|
||||
// Home layout — different UI paradigms per platform
|
||||
.homeShortcutOrder, .homeShortcutVisibility, .homeShortcutLayout, .homeShortcutCardStyle,
|
||||
.homeShortcutColorfulPalette, .homeShortcutCustomPaletteColors,
|
||||
.homeShortcutCardColor, .homeShortcutColorfulPalette, .homeShortcutCustomPaletteColors,
|
||||
.homeSectionOrder, .homeSectionVisibility, .homeSectionItemsLimit, .homeSectionLayout,
|
||||
// Top Shelf — tvOS only
|
||||
.topShelfSections,
|
||||
|
||||
@@ -95,14 +95,14 @@ extension SettingsManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Visual style for home shortcut cards (plain, accent-filled, or colorful). Default is plain.
|
||||
/// Layout for home shortcut cards (compact or regular). Default is regular.
|
||||
var homeShortcutCardStyle: HomeShortcutCardStyle {
|
||||
get {
|
||||
if let cached = _homeShortcutCardStyle { return cached }
|
||||
guard let rawValue = string(for: .homeShortcutCardStyle) else {
|
||||
return .plain
|
||||
return .regular
|
||||
}
|
||||
return HomeShortcutCardStyle(rawValue: rawValue) ?? .plain
|
||||
return HomeShortcutCardStyle(rawValue: rawValue) ?? .regular
|
||||
}
|
||||
set {
|
||||
_homeShortcutCardStyle = newValue
|
||||
@@ -110,15 +110,31 @@ extension SettingsManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Palette used by the "colorful" card style. Colors are applied by grid
|
||||
/// position. Default is Classic.
|
||||
/// Color emphasis for home shortcut cards (soft or vibrant), independent of
|
||||
/// the layout style. Default is soft.
|
||||
var homeShortcutCardColor: HomeShortcutCardColor {
|
||||
get {
|
||||
if let cached = _homeShortcutCardColor { return cached }
|
||||
guard let rawValue = string(for: .homeShortcutCardColor) else {
|
||||
return .soft
|
||||
}
|
||||
return HomeShortcutCardColor(rawValue: rawValue) ?? .soft
|
||||
}
|
||||
set {
|
||||
_homeShortcutCardColor = newValue
|
||||
set(newValue.rawValue, for: .homeShortcutCardColor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Palette used by the "regular" card style. Colors are applied by grid
|
||||
/// position. Default is Accent (the first palette option).
|
||||
var homeShortcutColorfulPalette: HomeShortcutColorfulPalette {
|
||||
get {
|
||||
if let cached = _homeShortcutColorfulPalette { return cached }
|
||||
guard let rawValue = string(for: .homeShortcutColorfulPalette) else {
|
||||
return .classic
|
||||
return .accent
|
||||
}
|
||||
return HomeShortcutColorfulPalette(rawValue: rawValue) ?? .classic
|
||||
return HomeShortcutColorfulPalette(rawValue: rawValue) ?? .accent
|
||||
}
|
||||
set {
|
||||
_homeShortcutColorfulPalette = newValue
|
||||
|
||||
@@ -110,6 +110,7 @@ final class SettingsManager {
|
||||
var _homeShortcutVisibility: [HomeShortcutItem: Bool]?
|
||||
var _homeShortcutLayout: HomeShortcutLayout?
|
||||
var _homeShortcutCardStyle: HomeShortcutCardStyle?
|
||||
var _homeShortcutCardColor: HomeShortcutCardColor?
|
||||
var _homeShortcutColorfulPalette: HomeShortcutColorfulPalette?
|
||||
var _homeShortcutCustomPaletteColors: [String]?
|
||||
var _homeSectionOrder: [HomeSectionItem]?
|
||||
@@ -469,6 +470,7 @@ final class SettingsManager {
|
||||
_homeShortcutVisibility = nil
|
||||
_homeShortcutLayout = nil
|
||||
_homeShortcutCardStyle = nil
|
||||
_homeShortcutCardColor = nil
|
||||
_homeShortcutColorfulPalette = nil
|
||||
_homeShortcutCustomPaletteColors = nil
|
||||
_homeSectionOrder = nil
|
||||
|
||||
@@ -4238,6 +4238,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"home.settings.shortcuts.color" : {
|
||||
"comment" : "Label/header for the card color emphasis picker",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Color"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"home.settings.shortcuts.layout" : {
|
||||
"comment" : "Header for the card layout picker (Compact/Regular)",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Layout"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"home.settings.shortcuts.palette" : {
|
||||
"comment" : "Label/header for the colorful palette picker",
|
||||
"localizations" : {
|
||||
@@ -4417,6 +4439,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"home.shortcuts.color.soft" : {
|
||||
"comment" : "Soft card color option (tinted background)",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Soft"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"home.shortcuts.color.vibrant" : {
|
||||
"comment" : "Vibrant card color option (solid fill)",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vibrant"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"home.shortcuts.palette.accent" : {
|
||||
"comment" : "Palette name for the uniform accent-color fill",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Accent"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"home.shortcuts.palette.classic" : {
|
||||
"comment" : "Colorful palette name",
|
||||
"localizations" : {
|
||||
@@ -4549,35 +4604,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"home.shortcuts.style.accent" : {
|
||||
"comment" : "Accent card style option",
|
||||
"home.shortcuts.style.compact" : {
|
||||
"comment" : "Compact card style option",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Accent"
|
||||
"value" : "Compact"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"home.shortcuts.style.colorful" : {
|
||||
"comment" : "Colorful card style option",
|
||||
"home.shortcuts.style.regular" : {
|
||||
"comment" : "Regular card style option",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Colorful"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"home.shortcuts.style.plain" : {
|
||||
"comment" : "Plain card style option",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Plain"
|
||||
"value" : "Regular"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,31 +32,50 @@ enum HomeShortcutLayout: String, CaseIterable, Sendable {
|
||||
|
||||
// MARK: - Home Shortcut Card Style
|
||||
|
||||
/// Visual style for shortcut cards in the Home view (cards layout only).
|
||||
/// Layout for shortcut cards in the Home view (cards layout only). Controls
|
||||
/// only how the icon / title / subtitle / counter are positioned — the color
|
||||
/// emphasis is a separate setting (`HomeShortcutCardColor`).
|
||||
enum HomeShortcutCardStyle: String, CaseIterable, Sendable {
|
||||
/// Current look: light accent tint background with an accent border.
|
||||
case plain
|
||||
/// Solid fill in the user's accent color, white icon/text.
|
||||
case accent
|
||||
/// Solid fill with a fixed color per shortcut type, white icon/text (Reminders-style).
|
||||
case colorful
|
||||
/// Horizontal card: icon leading, title with a count subtitle beside it.
|
||||
case compact
|
||||
/// "Reminders-style" card: icon top-leading, count top-trailing, title bottom.
|
||||
case regular
|
||||
|
||||
var displayName: LocalizedStringKey {
|
||||
switch self {
|
||||
case .plain: return "home.shortcuts.style.plain"
|
||||
case .accent: return "home.shortcuts.style.accent"
|
||||
case .colorful: return "home.shortcuts.style.colorful"
|
||||
case .compact: return "home.shortcuts.style.compact"
|
||||
case .regular: return "home.shortcuts.style.regular"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Home Shortcut Card Color
|
||||
|
||||
/// Color emphasis for shortcut cards, independent of layout. The actual color
|
||||
/// comes from the selected `HomeShortcutColorfulPalette`.
|
||||
enum HomeShortcutCardColor: String, CaseIterable, Sendable {
|
||||
/// Light tinted background, palette-colored icon, neutral (primary/secondary) text.
|
||||
case soft
|
||||
/// Solid palette fill with a gentle sheen, white icon/text.
|
||||
case vibrant
|
||||
|
||||
var displayName: LocalizedStringKey {
|
||||
switch self {
|
||||
case .soft: return "home.shortcuts.color.soft"
|
||||
case .vibrant: return "home.shortcuts.color.vibrant"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Home Shortcut Colorful Palette
|
||||
|
||||
/// Palette used by the "colorful" card style. Colors are applied by grid
|
||||
/// Palette used by the "regular" card style. Colors are applied by grid
|
||||
/// position (wrapping by palette length), so a card's color depends on where
|
||||
/// it sits, not on which shortcut it is.
|
||||
enum HomeShortcutColorfulPalette: String, CaseIterable, Sendable {
|
||||
/// Default: reproduces the original per-position look (SwiftUI named colors).
|
||||
/// Uniform fill using the user's accent color on every card.
|
||||
case accent
|
||||
/// Reproduces the original per-position look (SwiftUI named colors).
|
||||
case classic
|
||||
case sunset
|
||||
case meadow
|
||||
@@ -67,6 +86,7 @@ enum HomeShortcutColorfulPalette: String, CaseIterable, Sendable {
|
||||
|
||||
var displayName: LocalizedStringKey {
|
||||
switch self {
|
||||
case .accent: return "home.shortcuts.palette.accent"
|
||||
case .classic: return "home.shortcuts.palette.classic"
|
||||
case .sunset: return "home.shortcuts.palette.sunset"
|
||||
case .meadow: return "home.shortcuts.palette.meadow"
|
||||
@@ -76,10 +96,12 @@ enum HomeShortcutColorfulPalette: String, CaseIterable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Built-in colors for this palette; `nil` for `.custom` (resolved from
|
||||
/// the user's stored hex list instead).
|
||||
/// Built-in colors for this palette; `nil` for `.accent` (resolved from the
|
||||
/// user's accent color) and `.custom` (resolved from the stored hex list).
|
||||
var builtInColors: [Color]? {
|
||||
switch self {
|
||||
case .accent:
|
||||
return nil
|
||||
case .classic:
|
||||
// Ordered to match `HomeShortcutItem.defaultOrder` so the classic
|
||||
// palette reproduces the app's original colorful appearance.
|
||||
@@ -101,9 +123,11 @@ enum HomeShortcutColorfulPalette: String, CaseIterable, Sendable {
|
||||
static let customStarterColors = ["#007AFF", "#30B0C7", "#5856D6", "#FF2D55", "#AF52DE"]
|
||||
|
||||
/// Resolves the color for a shortcut at `position` in the grid, wrapping by
|
||||
/// palette length. Falls back to the Classic palette if the selection yields
|
||||
/// palette length. The `.accent` palette fills every position with
|
||||
/// `accentColor`. Falls back to the Classic palette if the selection yields
|
||||
/// no usable colors (e.g. an empty or all-invalid custom list).
|
||||
static func color(forPosition position: Int, palette: HomeShortcutColorfulPalette, customHex: [String]) -> Color {
|
||||
static func color(forPosition position: Int, palette: HomeShortcutColorfulPalette, customHex: [String], accentColor: Color) -> Color {
|
||||
if palette == .accent { return accentColor }
|
||||
let colors = palette == .custom ? customHex.compactMap { Color(hex: $0) } : (palette.builtInColors ?? [])
|
||||
let usable = colors.isEmpty ? (classic.builtInColors ?? [.accentColor]) : colors
|
||||
guard !usable.isEmpty else { return .accentColor }
|
||||
|
||||
@@ -12,8 +12,9 @@ struct HomeSettingsView: View {
|
||||
|
||||
// Local state for editing (copied from settings on appear, saved on dismiss)
|
||||
@State private var shortcutLayout: HomeShortcutLayout = .cards
|
||||
@State private var shortcutCardStyle: HomeShortcutCardStyle = .plain
|
||||
@State private var shortcutColorfulPalette: HomeShortcutColorfulPalette = .classic
|
||||
@State private var shortcutCardStyle: HomeShortcutCardStyle = .regular
|
||||
@State private var shortcutCardColor: HomeShortcutCardColor = .soft
|
||||
@State private var shortcutColorfulPalette: HomeShortcutColorfulPalette = .accent
|
||||
@State private var shortcutCustomPaletteColors: [String] = []
|
||||
@State private var shortcutOrder: [HomeShortcutItem] = []
|
||||
@State private var shortcutVisibility: [HomeShortcutItem: Bool] = [:]
|
||||
@@ -63,6 +64,7 @@ struct HomeSettingsView: View {
|
||||
.navigationDestination(isPresented: $showingStyleSettings) {
|
||||
HomeShortcutStyleView(
|
||||
style: $shortcutCardStyle,
|
||||
color: $shortcutCardColor,
|
||||
palette: $shortcutColorfulPalette,
|
||||
customColors: $shortcutCustomPaletteColors,
|
||||
onSave: saveSettingsIfLoaded
|
||||
@@ -99,8 +101,8 @@ struct HomeSettingsView: View {
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
// Card style (Plain / Accent / Colorful) — navigates to a page with
|
||||
// a selector and a live preview. Only for cards layout.
|
||||
// Card style (Compact / Regular) — navigates to a page with a
|
||||
// selector and a live preview. Only for cards layout.
|
||||
if shortcutLayout == .cards {
|
||||
#if os(macOS)
|
||||
Button {
|
||||
@@ -127,6 +129,7 @@ struct HomeSettingsView: View {
|
||||
) {
|
||||
HomeShortcutStyleView(
|
||||
style: $shortcutCardStyle,
|
||||
color: $shortcutCardColor,
|
||||
palette: $shortcutColorfulPalette,
|
||||
customColors: $shortcutCustomPaletteColors,
|
||||
onSave: saveSettingsIfLoaded
|
||||
@@ -330,6 +333,7 @@ struct HomeSettingsView: View {
|
||||
|
||||
shortcutLayout = settings.homeShortcutLayout
|
||||
shortcutCardStyle = settings.homeShortcutCardStyle
|
||||
shortcutCardColor = settings.homeShortcutCardColor
|
||||
shortcutColorfulPalette = settings.homeShortcutColorfulPalette
|
||||
shortcutCustomPaletteColors = settings.homeShortcutCustomPaletteColors
|
||||
shortcutOrder = settings.homeShortcutOrder
|
||||
@@ -358,6 +362,7 @@ struct HomeSettingsView: View {
|
||||
guard let settings = settingsManager else { return }
|
||||
settings.homeShortcutLayout = shortcutLayout
|
||||
settings.homeShortcutCardStyle = shortcutCardStyle
|
||||
settings.homeShortcutCardColor = shortcutCardColor
|
||||
settings.homeShortcutColorfulPalette = shortcutColorfulPalette
|
||||
settings.homeShortcutCustomPaletteColors = shortcutCustomPaletteColors
|
||||
settings.homeShortcutOrder = shortcutOrder
|
||||
|
||||
@@ -27,11 +27,15 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
/// Whether to show the count number in the filled (Reminders) layout.
|
||||
/// Cards without a meaningful count (Open Link, Remote, Subscriptions) hide it.
|
||||
var showsCount: Bool
|
||||
/// Fixed color used when the "colorful" card style is active.
|
||||
/// Palette-resolved color for this card (from position/palette). Used as the
|
||||
/// tint in Soft and the solid fill in Vibrant.
|
||||
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.
|
||||
/// Explicit layout override. When set, takes precedence over the global
|
||||
/// setting — used to render live previews of a not-yet-saved layout.
|
||||
var styleOverride: HomeShortcutCardStyle?
|
||||
/// Explicit color override. When set, takes precedence over the global
|
||||
/// setting — used to render live previews of a not-yet-saved color.
|
||||
var colorOverride: HomeShortcutCardColor?
|
||||
var statusIndicator: StatusIndicator?
|
||||
|
||||
init(
|
||||
@@ -42,6 +46,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
showsCount: Bool = true,
|
||||
colorfulColor: Color = .accentColor,
|
||||
styleOverride: HomeShortcutCardStyle? = nil,
|
||||
colorOverride: HomeShortcutCardColor? = nil,
|
||||
statusIndicator: StatusIndicator?
|
||||
) {
|
||||
self.icon = icon
|
||||
@@ -51,6 +56,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
self.showsCount = showsCount
|
||||
self.colorfulColor = colorfulColor
|
||||
self.styleOverride = styleOverride
|
||||
self.colorOverride = colorOverride
|
||||
self.statusIndicator = statusIndicator
|
||||
}
|
||||
|
||||
@@ -58,30 +64,40 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
|
||||
private var cardStyle: HomeShortcutCardStyle {
|
||||
#if os(tvOS)
|
||||
return .plain
|
||||
return .compact
|
||||
#else
|
||||
return styleOverride ?? (appEnvironment?.settingsManager.homeShortcutCardStyle ?? .plain)
|
||||
return styleOverride ?? (appEnvironment?.settingsManager.homeShortcutCardStyle ?? .regular)
|
||||
#endif
|
||||
}
|
||||
|
||||
private var isFilled: Bool {
|
||||
cardStyle != .plain
|
||||
private var cardColor: HomeShortcutCardColor {
|
||||
#if os(tvOS)
|
||||
return .soft
|
||||
#else
|
||||
return colorOverride ?? (appEnvironment?.settingsManager.homeShortcutCardColor ?? .soft)
|
||||
#endif
|
||||
}
|
||||
|
||||
private var fillColor: Color {
|
||||
cardStyle == .colorful ? (positionColorfulColor ?? colorfulColor) : accentColor
|
||||
private var isVibrant: Bool {
|
||||
cardColor == .vibrant
|
||||
}
|
||||
|
||||
/// Palette-resolved color injected via the environment (the Accent palette
|
||||
/// injects the accent color). Used as the tint in Soft and the fill in Vibrant.
|
||||
private var paletteColor: Color {
|
||||
positionColorfulColor ?? colorfulColor
|
||||
}
|
||||
|
||||
private var iconColor: Color {
|
||||
isFilled ? .white : accentColor
|
||||
isVibrant ? .white : paletteColor
|
||||
}
|
||||
|
||||
private var titleColor: Color {
|
||||
isFilled ? .white : .primary
|
||||
isVibrant ? .white : .primary
|
||||
}
|
||||
|
||||
private var subtitleColor: Color {
|
||||
isFilled ? Color.white.opacity(0.85) : .secondary
|
||||
isVibrant ? Color.white.opacity(0.85) : .secondary
|
||||
}
|
||||
|
||||
// Platform-specific styling
|
||||
@@ -116,13 +132,13 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Filled styles (accent/colorful) use a Reminders-style layout:
|
||||
/// The Regular layout is the Reminders-style card:
|
||||
/// icon top-leading, count top-trailing, title at the bottom.
|
||||
private var useRemindersLayout: Bool {
|
||||
#if os(tvOS)
|
||||
return false
|
||||
#else
|
||||
return isFilled
|
||||
return cardStyle == .regular
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -204,7 +220,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
|
||||
}
|
||||
|
||||
/// Reminders-style layout used by the accent/colorful filled styles:
|
||||
/// Reminders-style layout used by the Regular layout:
|
||||
/// icon top-leading, count top-trailing, title anchored to the bottom.
|
||||
private var remindersLayout: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
@@ -270,9 +286,9 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
#if os(tvOS)
|
||||
(isFocused ? Color.white.opacity(0.2) : Color.gray.opacity(0.3))
|
||||
#else
|
||||
if isFilled {
|
||||
if isVibrant {
|
||||
// Solid fill plus a subtle top-to-bottom sheen for a gentle gradient.
|
||||
fillColor.overlay(
|
||||
paletteColor.overlay(
|
||||
LinearGradient(
|
||||
colors: [Color.white.opacity(0.28), Color.clear, Color.black.opacity(0.10)],
|
||||
startPoint: .top,
|
||||
@@ -280,7 +296,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
)
|
||||
)
|
||||
} else {
|
||||
accentColor.opacity(0.1)
|
||||
paletteColor.opacity(0.1)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -289,7 +305,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
#if os(tvOS)
|
||||
return accentColor.opacity(0.3)
|
||||
#else
|
||||
return isFilled ? .clear : accentColor.opacity(0.3)
|
||||
return isVibrant ? .clear : paletteColor.opacity(0.3)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -304,7 +320,8 @@ extension HomeShortcutCardView where StatusIndicator == EmptyView {
|
||||
subtitle: String,
|
||||
showsCount: Bool = true,
|
||||
colorfulColor: Color = .accentColor,
|
||||
styleOverride: HomeShortcutCardStyle? = nil
|
||||
styleOverride: HomeShortcutCardStyle? = nil,
|
||||
colorOverride: HomeShortcutCardColor? = nil
|
||||
) {
|
||||
self.icon = icon
|
||||
self.title = title
|
||||
@@ -313,6 +330,7 @@ extension HomeShortcutCardView where StatusIndicator == EmptyView {
|
||||
self.showsCount = showsCount
|
||||
self.colorfulColor = colorfulColor
|
||||
self.styleOverride = styleOverride
|
||||
self.colorOverride = colorOverride
|
||||
self.statusIndicator = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,18 @@
|
||||
import SwiftUI
|
||||
|
||||
struct HomeShortcutStyleView: View {
|
||||
@Environment(\.appEnvironment) private var appEnvironment
|
||||
|
||||
@Binding var style: HomeShortcutCardStyle
|
||||
@Binding var color: HomeShortcutCardColor
|
||||
@Binding var palette: HomeShortcutColorfulPalette
|
||||
@Binding var customColors: [String]
|
||||
|
||||
/// The user's accent color, used by the Accent palette (uniform fill).
|
||||
private var accentColor: Color {
|
||||
appEnvironment?.settingsManager.accentColor.color ?? .accentColor
|
||||
}
|
||||
|
||||
/// Called after any value changes so the owner persists immediately. The
|
||||
/// owner sits covered in the navigation stack where its own onChange never
|
||||
/// fires, and swipe-dismissing the sheet from here skips its lifecycle.
|
||||
@@ -43,7 +51,7 @@ struct HomeShortcutStyleView: View {
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
Picker(String(localized: "home.settings.shortcuts.style"), selection: $style) {
|
||||
Picker(String(localized: "home.settings.shortcuts.layout"), selection: $style) {
|
||||
ForEach(HomeShortcutCardStyle.allCases, id: \.self) { style in
|
||||
Text(style.displayName).tag(style)
|
||||
}
|
||||
@@ -51,14 +59,27 @@ struct HomeShortcutStyleView: View {
|
||||
#if os(iOS)
|
||||
.pickerStyle(.segmented)
|
||||
#endif
|
||||
} header: {
|
||||
Text(String(localized: "home.settings.shortcuts.layout"))
|
||||
}
|
||||
|
||||
if style == .colorful {
|
||||
paletteSection
|
||||
|
||||
if palette == .custom {
|
||||
customColorsSection
|
||||
Section {
|
||||
Picker(String(localized: "home.settings.shortcuts.color"), selection: $color) {
|
||||
ForEach(HomeShortcutCardColor.allCases, id: \.self) { color in
|
||||
Text(color.displayName).tag(color)
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
.pickerStyle(.segmented)
|
||||
#endif
|
||||
} header: {
|
||||
Text(String(localized: "home.settings.shortcuts.color"))
|
||||
}
|
||||
|
||||
paletteSection
|
||||
|
||||
if palette == .custom {
|
||||
customColorsSection
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -68,14 +89,15 @@ struct HomeShortcutStyleView: View {
|
||||
icon: item.icon,
|
||||
title: item.localizedTitle,
|
||||
count: sampleCount(for: item),
|
||||
subtitle: "",
|
||||
subtitle: sampleSubtitle(for: item),
|
||||
showsCount: showsCount(for: item),
|
||||
colorfulColor: item.cardColor,
|
||||
styleOverride: style
|
||||
styleOverride: style,
|
||||
colorOverride: color
|
||||
)
|
||||
.environment(
|
||||
\.homeShortcutColorfulColor,
|
||||
HomeShortcutColorfulPalette.color(forPosition: index, palette: palette, customHex: customColors)
|
||||
HomeShortcutColorfulPalette.color(forPosition: index, palette: palette, customHex: customColors, accentColor: accentColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -93,6 +115,7 @@ struct HomeShortcutStyleView: View {
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
.onChange(of: style) { onSave?() }
|
||||
.onChange(of: color) { onSave?() }
|
||||
.onChange(of: palette) { onSave?() }
|
||||
.onChange(of: customColors) { onSave?() }
|
||||
}
|
||||
@@ -117,6 +140,9 @@ struct HomeShortcutStyleView: View {
|
||||
|
||||
/// Colors shown in the selected-palette swatch strip.
|
||||
private var paletteSwatchColors: [Color] {
|
||||
if palette == .accent {
|
||||
return [accentColor]
|
||||
}
|
||||
if palette == .custom {
|
||||
let parsed = customColors.compactMap { Color(hex: $0) }
|
||||
return parsed.isEmpty ? (HomeShortcutColorfulPalette.classic.builtInColors ?? []) : parsed
|
||||
@@ -263,8 +289,8 @@ struct HomeShortcutStyleView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Representative count so the accent/colorful (Reminders-style) layout
|
||||
/// looks realistic in the preview.
|
||||
/// Representative count so the Regular (Reminders-style) layout looks
|
||||
/// realistic in the preview.
|
||||
private func sampleCount(for item: HomeShortcutItem) -> Int {
|
||||
switch item {
|
||||
case .playlists: return 8
|
||||
@@ -277,12 +303,45 @@ struct HomeShortcutStyleView: View {
|
||||
default: return 0
|
||||
}
|
||||
}
|
||||
|
||||
/// The count subtitle shown on real Compact cards, mirroring `HomeView`'s
|
||||
/// per-item subtitles so the Compact preview shows its second line.
|
||||
private func sampleSubtitle(for item: HomeShortcutItem) -> String {
|
||||
switch item {
|
||||
case .openURL, .remoteControl:
|
||||
return ""
|
||||
case .playlists:
|
||||
return formatCount(sampleCount(for: item), singular: "home.count.playlist", plural: "home.count.playlists")
|
||||
case .bookmarks:
|
||||
return formatCount(sampleCount(for: item), singular: "home.count.bookmark", plural: "home.count.bookmarks")
|
||||
case .continueWatching, .history:
|
||||
return formatCount(sampleCount(for: item), singular: "home.count.video", plural: "home.count.videos")
|
||||
case .downloads:
|
||||
return formatCount(sampleCount(for: item), singular: "home.count.video", plural: "home.count.videos")
|
||||
case .channels:
|
||||
return formatCount(sampleCount(for: item), singular: "home.count.channel", plural: "home.count.channels")
|
||||
case .subscriptions:
|
||||
return String(localized: "home.subscriptions.subtitle")
|
||||
case .mediaSources:
|
||||
return sampleCount(for: item) == 1 ? "1 source" : "\(sampleCount(for: item)) sources"
|
||||
case .instanceContent, .mediaSource:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/// Localized "N items" string, mirroring `HomeView.formatCount`.
|
||||
private func formatCount(_ count: Int, singular: String.LocalizationValue, plural: String.LocalizationValue) -> String {
|
||||
let formattedCount = CountFormatter.compact(count)
|
||||
let key = count == 1 ? singular : plural
|
||||
return String(localized: "\(formattedCount) \(String(localized: key))")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NavigationStack {
|
||||
HomeShortcutStyleView(
|
||||
style: .constant(.colorful),
|
||||
style: .constant(.regular),
|
||||
color: .constant(.vibrant),
|
||||
palette: .constant(.classic),
|
||||
customColors: .constant(HomeShortcutColorfulPalette.customStarterColors)
|
||||
)
|
||||
|
||||
@@ -402,12 +402,14 @@ struct HomeView: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Resolves the colorful-style color for a shortcut at the given grid
|
||||
/// position from the selected palette (and custom colors when applicable).
|
||||
/// Resolves the Regular-style fill color for a shortcut at the given grid
|
||||
/// position from the selected palette (custom colors and accent color when
|
||||
/// applicable).
|
||||
private func colorfulColor(atPosition position: Int) -> Color {
|
||||
let palette = settingsManager?.homeShortcutColorfulPalette ?? .classic
|
||||
let palette = settingsManager?.homeShortcutColorfulPalette ?? .accent
|
||||
let customHex = settingsManager?.homeShortcutCustomPaletteColors ?? []
|
||||
return HomeShortcutColorfulPalette.color(forPosition: position, palette: palette, customHex: customHex)
|
||||
let accentColor = settingsManager?.accentColor.color ?? .accentColor
|
||||
return HomeShortcutColorfulPalette.color(forPosition: position, palette: palette, customHex: customHex, accentColor: accentColor)
|
||||
}
|
||||
|
||||
private var shortcutsList: some View {
|
||||
|
||||
Reference in New Issue
Block a user