Files
yattee/Yattee/Views/Settings/SettingsView.swift
Arkadiusz Fal e0ad43ca0b Move Integrations into main settings section above Advanced
Drop the standalone iOS section for Integrations and inline its row into
the main list right above Advanced Settings. Swap the tvOS sidebar order
so Integrations appears before Advanced as well. macOS was already
correctly ordered via SettingsSection enum declaration.
2026-04-23 07:39:03 +02:00

414 lines
17 KiB
Swift

//
// SettingsView.swift
// Yattee
//
// Main settings view.
//
import SwiftUI
struct SettingsView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.appEnvironment) private var appEnvironment
var showCloseButton: Bool = true
#if os(macOS)
@State private var selectedSection: SettingsSection? = .sources
#endif
var body: some View {
#if os(macOS)
macOSSettings
.frame(minWidth: 600, minHeight: 520)
#elseif os(tvOS)
tvOSSettings
#else
iOSSettings
#endif
}
// MARK: - macOS Settings
#if os(macOS)
private var macOSSettings: some View {
NavigationSplitView {
VStack(spacing: 0) {
List(SettingsSection.allCases, selection: $selectedSection) { section in
Label(section.title, systemImage: section.icon)
.tag(section)
}
.listStyle(.sidebar)
Divider()
macOSSidebarFooter
}
.navigationSplitViewColumnWidth(min: 180, ideal: 200)
} detail: {
if appEnvironment != nil {
NavigationStack {
Group {
switch selectedSection {
case .sources:
SourcesListView()
case .icloud:
iCloudSettingsView()
case .appearance:
AppearanceSettingsView()
case .layoutNavigation:
LayoutNavigationSettingsView()
case .playback:
PlaybackSettingsView()
case .notifications:
NotificationSettingsView()
case .downloads:
DownloadSettingsView()
case .privacy:
PrivacySettingsView()
case .youtubeEnhancements:
YouTubeEnhancementsSettingsView()
case .advanced:
AdvancedSettingsView()
case .about:
AboutView()
case .none:
Text(String(localized: "settings.placeholder.selectSection"))
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
}
}
}
private var macOSSidebarFooter: some View {
VStack(spacing: 6) {
Image("AppIconPreview")
.resizable()
.scaledToFit()
.frame(width: 48, height: 48)
.clipShape(RoundedRectangle(cornerRadius: 10))
Text(verbatim: "Yattee")
.font(.callout)
.fontWeight(.semibold)
Text("\(appVersion) (\(buildNumber))")
.font(.caption)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.allowsHitTesting(false)
}
#endif
// MARK: - tvOS Settings
#if os(tvOS)
private var tvOSSettings: some View {
NavigationStack {
List {
if let appEnvironment {
NavigationLink {
SourcesListView()
} label: {
HStack {
Label(String(localized: "sources.title"), systemImage: "server.rack")
Spacer()
if appEnvironment.mediaSourcesManager.hasSourcesNeedingPassword {
Image(systemName: "exclamationmark.circle.fill")
.foregroundStyle(.orange)
}
}
}
.accessibilityIdentifier("settings.row.sources")
NavigationLink {
TVSidebarDetailContainer(systemImage: "icloud", title: String(localized: "settings.icloud.title")) { iCloudSettingsView() }
} label: {
HStack {
Label(String(localized: "settings.icloud.title"), systemImage: "icloud")
#if DEBUG
Spacer()
Text(String(localized: "settings.icloud.dev.badge"))
.font(.caption2.bold())
.foregroundStyle(.white)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(.orange, in: Capsule())
#endif
}
}
NavigationLink { TVSidebarDetailContainer(systemImage: "paintbrush", title: String(localized: "settings.appearance.sectionTitle")) { AppearanceSettingsView() } } label: {
Label(String(localized: "settings.appearance.sectionTitle"), systemImage: "paintbrush")
}
NavigationLink { TVSidebarDetailContainer(systemImage: "hand.tap", title: String(localized: "settings.layoutNavigation.title")) { LayoutNavigationSettingsView() } } label: {
Label(String(localized: "settings.layoutNavigation.title"), systemImage: "hand.tap")
}
NavigationLink { TVSidebarDetailContainer(systemImage: "rectangle.on.rectangle.angled", title: String(localized: "settings.topShelf.title", defaultValue: "Top Shelf")) { TopShelfSettingsView() } } label: {
Label(String(localized: "settings.topShelf.title", defaultValue: "Top Shelf"), systemImage: "rectangle.on.rectangle.angled")
}
NavigationLink { TVSidebarDetailContainer(systemImage: "play.circle", title: String(localized: "settings.playback.sectionTitle")) { PlaybackSettingsView() } } label: {
Label(String(localized: "settings.playback.sectionTitle"), systemImage: "play.circle")
}
NavigationLink { TVSidebarDetailContainer(systemImage: "hand.raised", title: String(localized: "settings.privacy.title")) { PrivacySettingsView() } } label: {
Label(String(localized: "settings.privacy.title"), systemImage: "hand.raised")
}
if appEnvironment.instancesManager.enabledInstances.contains(where: \.isYouTubeInstance) {
NavigationLink { TVSidebarDetailContainer(systemImage: "puzzlepiece.extension", title: String(localized: "settings.youtubeEnhancements.title")) { YouTubeEnhancementsSettingsView() } } label: {
Label(String(localized: "settings.youtubeEnhancements.title"), systemImage: "puzzlepiece.extension")
}
}
NavigationLink { TVSidebarDetailContainer(systemImage: "gearshape.2", title: String(localized: "settings.advanced.title")) { AdvancedSettingsView() } } label: {
Label(String(localized: "settings.advanced.title"), systemImage: "gearshape.2")
}
NavigationLink { TVSidebarDetailContainer(systemImage: "info.circle", title: String(localized: "settings.about.title")) { AboutView() } } label: {
Label(String(localized: "settings.about.title"), systemImage: "info.circle")
}
}
}
.listStyle(.grouped)
.safeAreaInset(edge: .leading) {
VStack(spacing: 20) {
Spacer()
Image("AppIconPreview")
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
.clipShape(RoundedRectangle(cornerRadius: 46))
Text(verbatim: "Yattee")
.font(.title2)
.fontWeight(.semibold)
Text("\(appVersion) (\(buildNumber))")
.font(.callout)
.foregroundStyle(.secondary)
Spacer()
}
.frame(width: 400)
.allowsHitTesting(false)
}
.accessibilityIdentifier("settings.view")
}
}
#endif
// MARK: - iOS Settings
#if os(iOS)
private var iOSSettings: some View {
NavigationStack {
List {
if let appEnvironment {
Section {
NavigationLink {
SourcesListView()
} label: {
HStack {
Label(String(localized: "sources.title"), systemImage: "server.rack")
Spacer()
if appEnvironment.mediaSourcesManager.hasSourcesNeedingPassword {
Image(systemName: "exclamationmark.circle.fill")
.foregroundStyle(.orange)
}
}
}
.accessibilityIdentifier("settings.row.sources")
}
Section {
NavigationLink {
iCloudSettingsView()
} label: {
HStack {
Label(String(localized: "settings.icloud.title"), systemImage: "icloud")
#if DEBUG
Spacer()
Text(String(localized: "settings.icloud.dev.badge"))
.font(.caption2.bold())
.foregroundStyle(.white)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(.orange, in: Capsule())
#endif
}
}
NavigationLink {
AppearanceSettingsView()
} label: {
Label(String(localized: "settings.appearance.sectionTitle"), systemImage: "paintbrush")
}
NavigationLink {
LayoutNavigationSettingsView()
} label: {
Label(String(localized: "settings.layoutNavigation.title"), systemImage: "hand.tap")
}
NavigationLink {
PlaybackSettingsView()
} label: {
Label(String(localized: "settings.playback.sectionTitle"), systemImage: "play.circle")
}
#if os(iOS)
NavigationLink {
PlayerControlsSettingsView()
} label: {
Label(String(localized: "settings.playerControls.title"), systemImage: "slider.horizontal.below.rectangle")
}
NavigationLink {
NotificationSettingsView()
} label: {
Label(String(localized: "settings.notifications.title"), systemImage: "bell.badge")
}
NavigationLink {
DownloadSettingsView()
} label: {
Label(String(localized: "settings.downloads.title"), systemImage: "arrow.down.circle")
}
#endif
NavigationLink {
PrivacySettingsView()
} label: {
Label(String(localized: "settings.privacy.title"), systemImage: "hand.raised")
}
if appEnvironment.instancesManager.enabledInstances.contains(where: \.isYouTubeInstance) {
NavigationLink {
YouTubeEnhancementsSettingsView()
} label: {
Label(String(localized: "settings.youtubeEnhancements.title"), systemImage: "puzzlepiece.extension")
}
}
NavigationLink {
AdvancedSettingsView()
} label: {
Label(String(localized: "settings.advanced.title"), systemImage: "gearshape.2")
}
}
Section {
NavigationLink {
AboutView()
} label: {
Label(String(localized: "settings.about.title"), systemImage: "info.circle")
}
}
Section {
VStack(spacing: 4) {
Text(verbatim: "Yattee")
.font(.headline)
Text("\(appVersion) (\(buildNumber))")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity)
.listRowBackground(Color.clear)
}
}
}
.navigationTitle(String(localized: "settings.title"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
if showCloseButton {
ToolbarItem(placement: .confirmationAction) {
Button(role: .cancel) {
dismiss()
} label: {
Label(String(localized: "common.close"), systemImage: "xmark")
.labelStyle(.iconOnly)
}
.accessibilityIdentifier("settings.doneButton")
}
}
}
.accessibilityIdentifier("settings.view")
}
}
#endif
// MARK: - App Info
private var appVersion: String {
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "Unknown"
}
private var buildNumber: String {
Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "Unknown"
}
}
// MARK: - Settings Sections
enum SettingsSection: String, CaseIterable, Identifiable {
case sources
case icloud
case appearance
case layoutNavigation
case playback
case notifications
case downloads
case privacy
case youtubeEnhancements
case advanced
case about
var id: String { rawValue }
var title: String {
switch self {
case .sources: return String(localized: "sources.title")
case .icloud: return String(localized: "settings.icloud.title")
case .appearance: return String(localized: "settings.appearance.sectionTitle")
case .layoutNavigation: return String(localized: "settings.layoutNavigation.title")
case .playback: return String(localized: "settings.playback.sectionTitle")
case .notifications: return String(localized: "settings.notifications.title")
case .downloads: return String(localized: "settings.downloads.title")
case .privacy: return String(localized: "settings.privacy.title")
case .youtubeEnhancements: return String(localized: "settings.youtubeEnhancements.title")
case .advanced: return String(localized: "settings.advanced.title")
case .about: return String(localized: "settings.about.title")
}
}
var icon: String {
switch self {
case .sources: return "server.rack"
case .icloud: return "icloud"
case .appearance: return "paintbrush"
case .layoutNavigation: return "hand.tap"
case .playback: return "play.circle"
case .notifications: return "bell.badge"
case .downloads: return "arrow.down.circle"
case .privacy: return "hand.raised"
case .youtubeEnhancements: return "puzzlepiece.extension"
case .advanced: return "gearshape.2"
case .about: return "info.circle"
}
}
}
#Preview {
SettingsView()
.appEnvironment(.preview)
}