mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 22:32:01 +00:00
Add card style option for Home shortcut cards
Adds a Style picker (Plain / Accent / Colorful) to the Customize Home sheet, shown when the shortcuts layout is Cards. Defaults to Plain. - Plain: current look (light accent tint + accent border) - Accent: solid accent fill, white icon/text - Colorful: solid fill with a fixed color per shortcut type The filled styles use a Reminders-style layout (icon top-leading, count top-trailing, title anchored to the bottom) with a subtle gradient sheen over the fill color. Cards without a meaningful count (Open Link, Remote, Subscriptions, instance content, media sources) hide the number; Remote shows its status dot in the top-right slot instead. Persisted via SettingsKey.homeShortcutCardStyle, mirroring the existing homeShortcutLayout accessor.
This commit is contained in:
@@ -22,6 +22,11 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
let title: String
|
||||
let count: Int
|
||||
let subtitle: String
|
||||
/// 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.
|
||||
var colorfulColor: Color
|
||||
var statusIndicator: StatusIndicator?
|
||||
|
||||
init(
|
||||
@@ -29,15 +34,49 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
title: String,
|
||||
count: Int,
|
||||
subtitle: String,
|
||||
showsCount: Bool = true,
|
||||
colorfulColor: Color = .accentColor,
|
||||
statusIndicator: StatusIndicator?
|
||||
) {
|
||||
self.icon = icon
|
||||
self.title = title
|
||||
self.count = count
|
||||
self.subtitle = subtitle
|
||||
self.showsCount = showsCount
|
||||
self.colorfulColor = colorfulColor
|
||||
self.statusIndicator = statusIndicator
|
||||
}
|
||||
|
||||
// MARK: - Card Style
|
||||
|
||||
private var cardStyle: HomeShortcutCardStyle {
|
||||
#if os(tvOS)
|
||||
return .plain
|
||||
#else
|
||||
return appEnvironment?.settingsManager.homeShortcutCardStyle ?? .plain
|
||||
#endif
|
||||
}
|
||||
|
||||
private var isFilled: Bool {
|
||||
cardStyle != .plain
|
||||
}
|
||||
|
||||
private var fillColor: Color {
|
||||
cardStyle == .colorful ? colorfulColor : accentColor
|
||||
}
|
||||
|
||||
private var iconColor: Color {
|
||||
isFilled ? .white : accentColor
|
||||
}
|
||||
|
||||
private var titleColor: Color {
|
||||
isFilled ? .white : .primary
|
||||
}
|
||||
|
||||
private var subtitleColor: Color {
|
||||
isFilled ? Color.white.opacity(0.85) : .secondary
|
||||
}
|
||||
|
||||
// Platform-specific styling
|
||||
#if os(tvOS)
|
||||
private let iconSize: CGFloat = 36
|
||||
@@ -70,18 +109,30 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Filled styles (accent/colorful) use a Reminders-style layout:
|
||||
/// icon top-leading, count top-trailing, title at the bottom.
|
||||
private var useRemindersLayout: Bool {
|
||||
#if os(tvOS)
|
||||
return false
|
||||
#else
|
||||
return isFilled
|
||||
#endif
|
||||
}
|
||||
|
||||
private var hasSubtitle: Bool {
|
||||
!subtitle.isEmpty
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if needsVerticalLayout {
|
||||
if useRemindersLayout {
|
||||
remindersLayout
|
||||
} else if needsVerticalLayout {
|
||||
// Vertical layout for tvOS and accessibility sizes
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Image(systemName: icon)
|
||||
.font(.title3)
|
||||
.foregroundStyle(accentColor)
|
||||
.foregroundStyle(iconColor)
|
||||
.frame(width: iconSize)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
@@ -89,7 +140,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
Text(title)
|
||||
.font(titleFont)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundStyle(.primary)
|
||||
.foregroundStyle(titleColor)
|
||||
if let statusIndicator {
|
||||
statusIndicator.padding(.leading, 4)
|
||||
}
|
||||
@@ -97,7 +148,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
|
||||
Text(hasSubtitle ? subtitle : " ")
|
||||
.font(subtitleFont)
|
||||
.foregroundStyle(.secondary)
|
||||
.foregroundStyle(subtitleColor)
|
||||
.opacity(hasSubtitle ? 1 : 0)
|
||||
}
|
||||
}
|
||||
@@ -107,7 +158,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
HStack(alignment: .center, spacing: 8) {
|
||||
Image(systemName: icon)
|
||||
.font(.title3)
|
||||
.foregroundStyle(accentColor)
|
||||
.foregroundStyle(iconColor)
|
||||
.frame(width: iconSize)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
@@ -115,7 +166,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
Text(title)
|
||||
.font(titleFont)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundStyle(.primary)
|
||||
.foregroundStyle(titleColor)
|
||||
.allowsTightening(true)
|
||||
.lineLimit(1)
|
||||
if let statusIndicator {
|
||||
@@ -126,7 +177,7 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
if hasSubtitle {
|
||||
Text(subtitle)
|
||||
.font(subtitleFont)
|
||||
.foregroundStyle(.secondary)
|
||||
.foregroundStyle(subtitleColor)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
@@ -141,11 +192,48 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
.background(cardBackground)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.strokeBorder(accentColor.opacity(0.3), lineWidth: 1)
|
||||
.strokeBorder(borderColor, lineWidth: 1)
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
|
||||
}
|
||||
|
||||
/// Reminders-style layout used by the accent/colorful filled styles:
|
||||
/// icon top-leading, count top-trailing, title anchored to the bottom.
|
||||
private var remindersLayout: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(alignment: .top, spacing: 4) {
|
||||
Image(systemName: icon)
|
||||
.font(.title2)
|
||||
.foregroundStyle(iconColor)
|
||||
|
||||
Spacer(minLength: 4)
|
||||
|
||||
if let statusIndicator {
|
||||
statusIndicator
|
||||
}
|
||||
|
||||
if showsCount {
|
||||
Text(count, format: .number)
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.monospacedDigit()
|
||||
.foregroundStyle(titleColor)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(minLength: 8)
|
||||
|
||||
Text(title)
|
||||
.font(titleFont)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundStyle(titleColor)
|
||||
.lineLimit(1)
|
||||
.allowsTightening(true)
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: remindersMinHeight, alignment: .leading)
|
||||
}
|
||||
|
||||
/// Minimum height for the text content area to ensure consistent card heights
|
||||
private var subtitleMinHeight: CGFloat {
|
||||
#if os(tvOS)
|
||||
@@ -160,11 +248,41 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
private var cardBackground: some ShapeStyle {
|
||||
#if os(tvOS)
|
||||
isFocused ? Color.white.opacity(0.2) : Color.gray.opacity(0.3)
|
||||
/// Minimum content height for the Reminders-style layout so the title sits
|
||||
/// well below the icon/count row (matches Apple's roomier card proportions).
|
||||
private var remindersMinHeight: CGFloat {
|
||||
#if os(macOS)
|
||||
64
|
||||
#else
|
||||
accentColor.opacity(0.1)
|
||||
60
|
||||
#endif
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var cardBackground: some View {
|
||||
#if os(tvOS)
|
||||
(isFocused ? Color.white.opacity(0.2) : Color.gray.opacity(0.3))
|
||||
#else
|
||||
if isFilled {
|
||||
// Solid fill plus a subtle top-to-bottom sheen for a gentle gradient.
|
||||
fillColor.overlay(
|
||||
LinearGradient(
|
||||
colors: [Color.white.opacity(0.28), Color.clear, Color.black.opacity(0.10)],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
} else {
|
||||
accentColor.opacity(0.1)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private var borderColor: Color {
|
||||
#if os(tvOS)
|
||||
return accentColor.opacity(0.3)
|
||||
#else
|
||||
return isFilled ? .clear : accentColor.opacity(0.3)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -176,12 +294,16 @@ extension HomeShortcutCardView where StatusIndicator == EmptyView {
|
||||
icon: String,
|
||||
title: String,
|
||||
count: Int,
|
||||
subtitle: String
|
||||
subtitle: String,
|
||||
showsCount: Bool = true,
|
||||
colorfulColor: Color = .accentColor
|
||||
) {
|
||||
self.icon = icon
|
||||
self.title = title
|
||||
self.count = count
|
||||
self.subtitle = subtitle
|
||||
self.showsCount = showsCount
|
||||
self.colorfulColor = colorfulColor
|
||||
self.statusIndicator = nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user