mirror of
https://github.com/yattee/yattee.git
synced 2026-06-04 13:54:19 +00:00
Inset/grouped list style doesn't work well with tvOS focus effects. Always return .plain on tvOS and hide the list style picker from appearance settings.
268 lines
8.1 KiB
Swift
268 lines
8.1 KiB
Swift
//
|
|
// AppearanceSettingsView.swift
|
|
// Yattee
|
|
//
|
|
// Appearance settings with theme and accent color selection.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct AppearanceSettingsView: View {
|
|
@Environment(\.appEnvironment) private var appEnvironment
|
|
|
|
var body: some View {
|
|
Form {
|
|
if let settings = appEnvironment?.settingsManager {
|
|
// Theme section
|
|
#if !os(tvOS)
|
|
ThemeSection(settings: settings)
|
|
#endif
|
|
|
|
// App icon section (iOS only)
|
|
#if os(iOS)
|
|
AppIconSection(settings: settings)
|
|
#endif
|
|
|
|
// Accent color section
|
|
#if !os(tvOS)
|
|
AccentColorSection(settings: settings)
|
|
#endif
|
|
|
|
// List style section
|
|
#if !os(tvOS)
|
|
ListStyleSection(settings: settings)
|
|
#endif
|
|
|
|
// Thumbnail section
|
|
ThumbnailSection(settings: settings)
|
|
}
|
|
}
|
|
#if !os(tvOS)
|
|
.navigationTitle(String(localized: "settings.appearance.title"))
|
|
#endif
|
|
#if os(iOS)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// MARK: - Theme Section
|
|
|
|
private struct ThemeSection: View {
|
|
@Bindable var settings: SettingsManager
|
|
|
|
var body: some View {
|
|
Section(String(localized: "settings.appearance.theme.header")) {
|
|
Picker(
|
|
String(localized: "settings.appearance.theme"),
|
|
selection: $settings.theme
|
|
) {
|
|
ForEach(AppTheme.allCases, id: \.self) { theme in
|
|
Text(theme.displayName).tag(theme)
|
|
}
|
|
}
|
|
.pickerStyle(.segmented)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - App Icon Section (iOS only)
|
|
|
|
#if os(iOS)
|
|
private struct AppIconSection: View {
|
|
@Bindable var settings: SettingsManager
|
|
|
|
var body: some View {
|
|
Section(String(localized: "settings.appearance.appIcon.header")) {
|
|
NavigationLink {
|
|
AppIconPickerView(settings: settings)
|
|
} label: {
|
|
HStack {
|
|
Image(settings.appIcon.previewImageName)
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fit)
|
|
.frame(width: 44, height: 44)
|
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
|
|
|
Text(settings.appIcon.displayName)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct AppIconPickerView: View {
|
|
@Bindable var settings: SettingsManager
|
|
|
|
var body: some View {
|
|
List {
|
|
ForEach(AppIcon.allCases, id: \.self) { appIcon in
|
|
Button {
|
|
settings.appIcon = appIcon
|
|
} label: {
|
|
HStack {
|
|
Image(appIcon.previewImageName)
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fit)
|
|
.frame(width: 60, height: 60)
|
|
.clipShape(RoundedRectangle(cornerRadius: 13.5))
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(appIcon.displayName)
|
|
.foregroundStyle(.primary)
|
|
|
|
if let author = appIcon.author {
|
|
Text(author)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
|
|
if settings.appIcon == appIcon {
|
|
Image(systemName: "checkmark")
|
|
.foregroundStyle(.blue)
|
|
}
|
|
}
|
|
.contentShape(Rectangle())
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
.navigationTitle(String(localized: "settings.appearance.appIcon.header"))
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// MARK: - Accent Color Section
|
|
|
|
private struct AccentColorSection: View {
|
|
@Bindable var settings: SettingsManager
|
|
|
|
var body: some View {
|
|
Section(String(localized: "settings.appearance.accentColor.header")) {
|
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 50))], spacing: 16) {
|
|
ForEach(AccentColor.allCases, id: \.self) { accentColor in
|
|
AccentColorButton(
|
|
accentColor: accentColor,
|
|
isSelected: settings.accentColor == accentColor,
|
|
onSelect: { settings.accentColor = accentColor }
|
|
)
|
|
}
|
|
}
|
|
.padding(.vertical, 8)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - List Style Section
|
|
|
|
private struct ListStyleSection: View {
|
|
@Bindable var settings: SettingsManager
|
|
|
|
var body: some View {
|
|
Section {
|
|
Picker(selection: $settings.listStyle) {
|
|
ForEach(VideoListStyle.allCases, id: \.self) { style in
|
|
Text(style.displayName).tag(style)
|
|
}
|
|
} label: {
|
|
Label(String(localized: "settings.appearance.listStyle"), systemImage: "list.bullet")
|
|
}
|
|
} header: {
|
|
Text(String(localized: "settings.appearance.listStyle.header"))
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Thumbnail Section
|
|
|
|
private struct ThumbnailSection: View {
|
|
@Bindable var settings: SettingsManager
|
|
|
|
var body: some View {
|
|
Section {
|
|
Toggle(isOn: $settings.showWatchedCheckmark) {
|
|
Label(String(localized: "settings.appearance.showWatchedCheckmark"),
|
|
systemImage: "checkmark.circle.fill")
|
|
}
|
|
} header: {
|
|
Text(String(localized: "settings.appearance.thumbnails.header"))
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Accent Color Button
|
|
|
|
private struct AccentColorButton: View {
|
|
let accentColor: AccentColor
|
|
let isSelected: Bool
|
|
let onSelect: () -> Void
|
|
|
|
var body: some View {
|
|
Button(action: onSelect) {
|
|
ZStack {
|
|
Circle()
|
|
.fill(accentColor.color)
|
|
.frame(width: 40, height: 40)
|
|
|
|
if isSelected {
|
|
Circle()
|
|
.strokeBorder(.white, lineWidth: 3)
|
|
.frame(width: 40, height: 40)
|
|
|
|
Image(systemName: "checkmark")
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.white)
|
|
}
|
|
}
|
|
}
|
|
.buttonStyle(.plain)
|
|
.accessibilityLabel(accentColor.displayName)
|
|
}
|
|
}
|
|
|
|
// MARK: - Theme Display Names
|
|
|
|
extension AppTheme {
|
|
var displayName: String {
|
|
switch self {
|
|
case .system: return String(localized: "settings.appearance.theme.system")
|
|
case .light: return String(localized: "settings.appearance.theme.light")
|
|
case .dark: return String(localized: "settings.appearance.theme.dark")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Ambient Glow Section
|
|
|
|
// MARK: - Accent Color Display Names
|
|
|
|
extension AccentColor {
|
|
var displayName: String {
|
|
switch self {
|
|
case .default: return String(localized: "settings.appearance.accentColor.default")
|
|
case .red: return String(localized: "settings.appearance.accentColor.red")
|
|
case .pink: return String(localized: "settings.appearance.accentColor.pink")
|
|
case .orange: return String(localized: "settings.appearance.accentColor.orange")
|
|
case .yellow: return String(localized: "settings.appearance.accentColor.yellow")
|
|
case .green: return String(localized: "settings.appearance.accentColor.green")
|
|
case .teal: return String(localized: "settings.appearance.accentColor.teal")
|
|
case .blue: return String(localized: "settings.appearance.accentColor.blue")
|
|
case .purple: return String(localized: "settings.appearance.accentColor.purple")
|
|
case .indigo: return String(localized: "settings.appearance.accentColor.indigo")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
NavigationStack {
|
|
AppearanceSettingsView()
|
|
}
|
|
.appEnvironment(.preview)
|
|
}
|