mirror of
https://github.com/yattee/yattee.git
synced 2026-05-13 02:45:03 +00:00
Improve tvOS settings layout: use navigation instead of sheets, fix focus clipping
- Replace sheets with navigationDestination for Add/Edit Source on tvOS (tvOS sheets have fixed size that doesn't fit the content) - Fix focused cell clipping by replacing TVSettingsContainer's frame-based layout with safeAreaInset, matching the main settings view pattern - Use standard List with .listStyle(.grouped) for Sources on tvOS - Add sidebar icons and titles to TVSettingsContainer for all settings subviews, utilizing the left column space - Remove redundant large navigation titles on tvOS (shown in sidebar) - Move Edit Source Save button from toolbar into form above Delete button for better tvOS focus navigation
This commit is contained in:
@@ -51,7 +51,9 @@ struct AboutView: View {
|
|||||||
versionInfoSection
|
versionInfoSection
|
||||||
mpvInfoSection
|
mpvInfoSection
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.navigationTitle(String(localized: "settings.about.title"))
|
.navigationTitle(String(localized: "settings.about.title"))
|
||||||
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -25,43 +25,28 @@ struct AddSourceView: View {
|
|||||||
@State private var discoveredAllowInvalidCerts = false
|
@State private var discoveredAllowInvalidCerts = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
#if os(tvOS)
|
||||||
#if os(tvOS)
|
listContent
|
||||||
VStack(spacing: 0) {
|
|
||||||
HStack {
|
|
||||||
Button(String(localized: "common.cancel")) {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
.buttonStyle(TVToolbarButtonStyle())
|
|
||||||
Spacer()
|
|
||||||
Text(String(localized: "sources.newSource"))
|
|
||||||
.font(.title2)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
Spacer()
|
|
||||||
Text(String(localized: "common.cancel"))
|
|
||||||
.opacity(0)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 48)
|
|
||||||
.padding(.vertical, 24)
|
|
||||||
|
|
||||||
listContent
|
|
||||||
}
|
|
||||||
.navigationDestination(isPresented: $navigateToWebDAV) {
|
.navigationDestination(isPresented: $navigateToWebDAV) {
|
||||||
AddWebDAVView(
|
AddWebDAVView(
|
||||||
prefillURL: discoveredWebDAVURL,
|
prefillURL: discoveredWebDAVURL,
|
||||||
prefillName: discoveredName,
|
prefillName: discoveredName,
|
||||||
prefillAllowInvalidCertificates: discoveredAllowInvalidCerts,
|
prefillAllowInvalidCertificates: discoveredAllowInvalidCerts
|
||||||
dismissSheet: dismiss
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.navigationDestination(isPresented: $navigateToSMB) {
|
.navigationDestination(isPresented: $navigateToSMB) {
|
||||||
AddSMBView(
|
AddSMBView(
|
||||||
prefillServer: discoveredSMBServer,
|
prefillServer: discoveredSMBServer,
|
||||||
prefillName: discoveredName,
|
prefillName: discoveredName
|
||||||
dismissSheet: dismiss
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
#else
|
.sheet(isPresented: $showingNetworkDiscovery) {
|
||||||
|
NetworkShareDiscoverySheet(filterType: selectedShareType) { share in
|
||||||
|
handleSelectedShare(share)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
NavigationStack {
|
||||||
listContent
|
listContent
|
||||||
.navigationTitle(String(localized: "sources.newSource"))
|
.navigationTitle(String(localized: "sources.newSource"))
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@@ -92,13 +77,13 @@ struct AddSourceView: View {
|
|||||||
dismissSheet: dismiss
|
dismissSheet: dismiss
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showingNetworkDiscovery) {
|
.sheet(isPresented: $showingNetworkDiscovery) {
|
||||||
NetworkShareDiscoverySheet(filterType: selectedShareType) { share in
|
NetworkShareDiscoverySheet(filterType: selectedShareType) { share in
|
||||||
handleSelectedShare(share)
|
handleSelectedShare(share)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private var listContent: some View {
|
private var listContent: some View {
|
||||||
@@ -113,19 +98,31 @@ struct AddSourceView: View {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
|
#if os(tvOS)
|
||||||
|
AddWebDAVView()
|
||||||
|
#else
|
||||||
AddWebDAVView(dismissSheet: dismiss)
|
AddWebDAVView(dismissSheet: dismiss)
|
||||||
|
#endif
|
||||||
} label: {
|
} label: {
|
||||||
Label(String(localized: "sources.addWebDAV"), systemImage: "externaldrive.connected.to.line.below")
|
Label(String(localized: "sources.addWebDAV"), systemImage: "externaldrive.connected.to.line.below")
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
|
#if os(tvOS)
|
||||||
|
AddSMBView()
|
||||||
|
#else
|
||||||
AddSMBView(dismissSheet: dismiss)
|
AddSMBView(dismissSheet: dismiss)
|
||||||
|
#endif
|
||||||
} label: {
|
} label: {
|
||||||
Label(String(localized: "sources.addSMB"), systemImage: "server.rack")
|
Label(String(localized: "sources.addSMB"), systemImage: "server.rack")
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
|
#if os(tvOS)
|
||||||
|
AddRemoteServerView()
|
||||||
|
#else
|
||||||
AddRemoteServerView(dismissSheet: dismiss)
|
AddRemoteServerView(dismissSheet: dismiss)
|
||||||
|
#endif
|
||||||
} label: {
|
} label: {
|
||||||
Label(String(localized: "sources.addRemoteServer"), systemImage: "globe")
|
Label(String(localized: "sources.addRemoteServer"), systemImage: "globe")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ struct AdvancedSettingsView: View {
|
|||||||
#endif
|
#endif
|
||||||
developerSection
|
developerSection
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.navigationTitle(String(localized: "settings.advanced.title"))
|
.navigationTitle(String(localized: "settings.advanced.title"))
|
||||||
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ struct AppearanceSettingsView: View {
|
|||||||
ThumbnailSection(settings: settings)
|
ThumbnailSection(settings: settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.navigationTitle(String(localized: "settings.appearance.title"))
|
.navigationTitle(String(localized: "settings.appearance.title"))
|
||||||
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -77,31 +77,11 @@ private struct EditRemoteServerContent: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
#if os(tvOS)
|
||||||
|
formContent
|
||||||
|
.accessibilityIdentifier("editSource.view")
|
||||||
|
#else
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
#if os(tvOS)
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
HStack {
|
|
||||||
Button(String(localized: "common.cancel")) {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
.buttonStyle(TVToolbarButtonStyle())
|
|
||||||
Spacer()
|
|
||||||
Text(String(localized: "sources.editSource"))
|
|
||||||
.font(.title2)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
Spacer()
|
|
||||||
Button(String(localized: "common.save")) {
|
|
||||||
saveChanges()
|
|
||||||
}
|
|
||||||
.buttonStyle(TVToolbarButtonStyle())
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 48)
|
|
||||||
.padding(.vertical, 24)
|
|
||||||
|
|
||||||
formContent
|
|
||||||
.accessibilityIdentifier("editSource.view")
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
formContent
|
formContent
|
||||||
.navigationTitle(String(localized: "sources.editSource"))
|
.navigationTitle(String(localized: "sources.editSource"))
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@@ -120,8 +100,8 @@ private struct EditRemoteServerContent: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.accessibilityIdentifier("editSource.view")
|
.accessibilityIdentifier("editSource.view")
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private var formContent: some View {
|
private var formContent: some View {
|
||||||
@@ -313,6 +293,17 @@ private struct EditRemoteServerContent: View {
|
|||||||
testResultSection(result)
|
testResultSection(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
Section {
|
||||||
|
Button {
|
||||||
|
saveChanges()
|
||||||
|
} label: {
|
||||||
|
Label(String(localized: "common.save"), systemImage: "checkmark.circle")
|
||||||
|
}
|
||||||
|
.buttonStyle(TVSettingsButtonStyle())
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
showingDeleteConfirmation = true
|
showingDeleteConfirmation = true
|
||||||
@@ -529,35 +520,14 @@ private struct EditFileSourceContent: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
#if os(tvOS)
|
||||||
#if os(tvOS)
|
formContent
|
||||||
VStack(spacing: 0) {
|
|
||||||
HStack {
|
|
||||||
Button(String(localized: "common.cancel")) {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
.buttonStyle(TVToolbarButtonStyle())
|
|
||||||
Spacer()
|
|
||||||
Text(String(localized: "sources.editSource"))
|
|
||||||
.font(.title2)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
Spacer()
|
|
||||||
Button(String(localized: "common.save")) {
|
|
||||||
saveChanges()
|
|
||||||
}
|
|
||||||
.disabled(name.isEmpty)
|
|
||||||
.buttonStyle(TVToolbarButtonStyle())
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 48)
|
|
||||||
.padding(.vertical, 24)
|
|
||||||
|
|
||||||
formContent
|
|
||||||
}
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
hasExistingPassword = appEnvironment?.mediaSourcesManager.password(for: source) != nil
|
hasExistingPassword = appEnvironment?.mediaSourcesManager.password(for: source) != nil
|
||||||
smbProtocolVersion = source.smbProtocolVersion ?? .auto
|
smbProtocolVersion = source.smbProtocolVersion ?? .auto
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
NavigationStack {
|
||||||
formContent
|
formContent
|
||||||
.navigationTitle(String(localized: "sources.editSource"))
|
.navigationTitle(String(localized: "sources.editSource"))
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@@ -581,8 +551,8 @@ private struct EditFileSourceContent: View {
|
|||||||
hasExistingPassword = appEnvironment?.mediaSourcesManager.password(for: source) != nil
|
hasExistingPassword = appEnvironment?.mediaSourcesManager.password(for: source) != nil
|
||||||
smbProtocolVersion = source.smbProtocolVersion ?? .auto
|
smbProtocolVersion = source.smbProtocolVersion ?? .auto
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private var formContent: some View {
|
private var formContent: some View {
|
||||||
@@ -718,6 +688,18 @@ private struct EditFileSourceContent: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
Section {
|
||||||
|
Button {
|
||||||
|
saveChanges()
|
||||||
|
} label: {
|
||||||
|
Label(String(localized: "common.save"), systemImage: "checkmark.circle")
|
||||||
|
}
|
||||||
|
.disabled(name.isEmpty)
|
||||||
|
.buttonStyle(TVSettingsButtonStyle())
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
showingDeleteConfirmation = true
|
showingDeleteConfirmation = true
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ struct LayoutNavigationSettingsView: View {
|
|||||||
HandoffSection(settings: settings)
|
HandoffSection(settings: settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.navigationTitle(String(localized: "settings.layoutNavigation.title"))
|
.navigationTitle(String(localized: "settings.layoutNavigation.title"))
|
||||||
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ struct PlaybackSettingsView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.navigationTitle(String(localized: "settings.playback.title"))
|
.navigationTitle(String(localized: "settings.playback.title"))
|
||||||
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ struct PrivacySettingsView: View {
|
|||||||
historySection
|
historySection
|
||||||
searchSection
|
searchSection
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.navigationTitle(String(localized: "settings.privacy.title"))
|
.navigationTitle(String(localized: "settings.privacy.title"))
|
||||||
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ struct SettingsView: View {
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
macOSSettings
|
macOSSettings
|
||||||
.frame(minWidth: 600, minHeight: 400)
|
.frame(minWidth: 600, minHeight: 400)
|
||||||
|
#elseif os(tvOS)
|
||||||
|
tvOSSettings
|
||||||
#else
|
#else
|
||||||
iOSSettings
|
iOSSettings
|
||||||
#endif
|
#endif
|
||||||
@@ -83,9 +85,107 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// MARK: - iOS/tvOS Settings
|
// MARK: - tvOS Settings
|
||||||
|
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(tvOS)
|
||||||
|
private var tvOSSettings: some View {
|
||||||
|
NavigationStack {
|
||||||
|
List {
|
||||||
|
if let appEnvironment {
|
||||||
|
NavigationLink {
|
||||||
|
TVSettingsContainer(systemImage: "server.rack", title: String(localized: "sources.title")) { 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 {
|
||||||
|
TVSettingsContainer(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 { TVSettingsContainer(systemImage: "paintbrush", title: String(localized: "settings.appearance.sectionTitle")) { AppearanceSettingsView() } } label: {
|
||||||
|
Label(String(localized: "settings.appearance.sectionTitle"), systemImage: "paintbrush")
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationLink { TVSettingsContainer(systemImage: "hand.tap", title: String(localized: "settings.layoutNavigation.title")) { LayoutNavigationSettingsView() } } label: {
|
||||||
|
Label(String(localized: "settings.layoutNavigation.title"), systemImage: "hand.tap")
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationLink { TVSettingsContainer(systemImage: "play.circle", title: String(localized: "settings.playback.sectionTitle")) { PlaybackSettingsView() } } label: {
|
||||||
|
Label(String(localized: "settings.playback.sectionTitle"), systemImage: "play.circle")
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationLink { TVSettingsContainer(systemImage: "hand.raised", title: String(localized: "settings.privacy.title")) { PrivacySettingsView() } } label: {
|
||||||
|
Label(String(localized: "settings.privacy.title"), systemImage: "hand.raised")
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationLink { TVSettingsContainer(systemImage: "gearshape.2", title: String(localized: "settings.advanced.title")) { AdvancedSettingsView() } } label: {
|
||||||
|
Label(String(localized: "settings.advanced.title"), systemImage: "gearshape.2")
|
||||||
|
}
|
||||||
|
|
||||||
|
if appEnvironment.instancesManager.enabledInstances.contains(where: \.isYouTubeInstance) {
|
||||||
|
NavigationLink { TVSettingsContainer(systemImage: "play.rectangle", title: String(localized: "settings.youtubeEnhancements.title")) { YouTubeEnhancementsSettingsView() } } label: {
|
||||||
|
Label(String(localized: "settings.youtubeEnhancements.title"), systemImage: "play.rectangle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationLink { TVSettingsContainer(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 {
|
private var iOSSettings: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
List {
|
List {
|
||||||
@@ -206,12 +306,8 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !os(tvOS)
|
|
||||||
.navigationTitle(String(localized: "settings.title"))
|
.navigationTitle(String(localized: "settings.title"))
|
||||||
#endif
|
|
||||||
#if os(iOS)
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
if showCloseButton {
|
if showCloseButton {
|
||||||
ToolbarItem(placement: .confirmationAction) {
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
@@ -288,6 +384,50 @@ enum SettingsSection: String, CaseIterable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - tvOS Settings Container
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
struct TVSettingsContainer<Content: View>: View {
|
||||||
|
let content: Content
|
||||||
|
var systemImage: String?
|
||||||
|
var title: String?
|
||||||
|
|
||||||
|
init(systemImage: String? = nil, title: String? = nil, @ViewBuilder content: () -> Content) {
|
||||||
|
self.content = content()
|
||||||
|
self.systemImage = systemImage
|
||||||
|
self.title = title
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
content
|
||||||
|
.safeAreaInset(edge: .leading) {
|
||||||
|
if let systemImage {
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: systemImage)
|
||||||
|
.font(.system(size: 80))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
if let title {
|
||||||
|
Text(title)
|
||||||
|
.font(.title3)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(width: 400)
|
||||||
|
.allowsHitTesting(false)
|
||||||
|
} else {
|
||||||
|
Spacer()
|
||||||
|
.frame(width: 400)
|
||||||
|
.allowsHitTesting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
SettingsView()
|
SettingsView()
|
||||||
.appEnvironment(.preview)
|
.appEnvironment(.preview)
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ struct SourcesListView: View {
|
|||||||
sourcesList
|
sourcesList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.navigationTitle(String(localized: "sources.title"))
|
.navigationTitle(String(localized: "sources.title"))
|
||||||
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
@@ -56,12 +58,24 @@ struct SourcesListView: View {
|
|||||||
.accessibilityIdentifier("sources.addButton")
|
.accessibilityIdentifier("sources.addButton")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if os(tvOS)
|
||||||
|
.navigationDestination(isPresented: $showingAddSheet) {
|
||||||
|
TVSettingsContainer(systemImage: "plus.circle", title: String(localized: "sources.newSource")) { AddSourceView() }
|
||||||
|
}
|
||||||
|
#else
|
||||||
.sheet(isPresented: $showingAddSheet) {
|
.sheet(isPresented: $showingAddSheet) {
|
||||||
AddSourceView()
|
AddSourceView()
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#if os(tvOS)
|
||||||
|
.navigationDestination(item: $sourceToEdit) { source in
|
||||||
|
TVSettingsContainer(systemImage: "pencil.circle", title: String(localized: "sources.editSource")) { EditSourceView(source: source) }
|
||||||
|
}
|
||||||
|
#else
|
||||||
.sheet(item: $sourceToEdit) { source in
|
.sheet(item: $sourceToEdit) { source in
|
||||||
EditSourceView(source: source)
|
EditSourceView(source: source)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
deleteConfirmationMessage,
|
deleteConfirmationMessage,
|
||||||
isPresented: $showingDeleteConfirmation,
|
isPresented: $showingDeleteConfirmation,
|
||||||
@@ -98,6 +112,37 @@ struct SourcesListView: View {
|
|||||||
// MARK: - Sources List
|
// MARK: - Sources List
|
||||||
|
|
||||||
private var sourcesList: some View {
|
private var sourcesList: some View {
|
||||||
|
#if os(tvOS)
|
||||||
|
List {
|
||||||
|
if let manager = instancesManager, !manager.instances.isEmpty {
|
||||||
|
Section(String(localized: "sources.section.remoteServers")) {
|
||||||
|
let instances = manager.instances.sorted { $0.dateAdded < $1.dateAdded }
|
||||||
|
ForEach(instances) { instance in
|
||||||
|
Button {
|
||||||
|
sourceToEdit = .remoteServer(instance)
|
||||||
|
} label: {
|
||||||
|
instanceRow(instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let allFileSources = allMediaSources
|
||||||
|
if !allFileSources.isEmpty {
|
||||||
|
Section(String(localized: "sources.section.fileSources")) {
|
||||||
|
ForEach(allFileSources) { source in
|
||||||
|
let needsPassword = mediaSourcesManager?.needsPassword(for: source) ?? false
|
||||||
|
Button {
|
||||||
|
sourceToEdit = .fileSource(source)
|
||||||
|
} label: {
|
||||||
|
mediaSourceRow(source, needsPassword: needsPassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(.grouped)
|
||||||
|
#else
|
||||||
(listStyle == .inset ? ListBackgroundStyle.grouped.color : ListBackgroundStyle.plain.color)
|
(listStyle == .inset ? ListBackgroundStyle.grouped.color : ListBackgroundStyle.plain.color)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.overlay(
|
.overlay(
|
||||||
@@ -108,6 +153,7 @@ struct SourcesListView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Section Header
|
// MARK: - Section Header
|
||||||
@@ -131,9 +177,13 @@ struct SourcesListView: View {
|
|||||||
LazyVStack(spacing: 0) {
|
LazyVStack(spacing: 0) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
|
#if os(tvOS)
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
#else
|
||||||
.background(ListBackgroundStyle.card.color)
|
.background(ListBackgroundStyle.card.color)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
|
#endif
|
||||||
.padding(.bottom, 16)
|
.padding(.bottom, 16)
|
||||||
} else {
|
} else {
|
||||||
LazyVStack(spacing: 0) {
|
LazyVStack(spacing: 0) {
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ struct YouTubeEnhancementsSettingsView: View {
|
|||||||
DeArrowSection(settings: settings)
|
DeArrowSection(settings: settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.navigationTitle(String(localized: "settings.youtubeEnhancements.title"))
|
.navigationTitle(String(localized: "settings.youtubeEnhancements.title"))
|
||||||
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -82,7 +82,9 @@ struct iCloudSettingsView: View {
|
|||||||
syncStatusSection
|
syncStatusSection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.navigationTitle(String(localized: "settings.icloud.title"))
|
.navigationTitle(String(localized: "settings.icloud.title"))
|
||||||
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user