mirror of
https://github.com/yattee/yattee.git
synced 2026-05-12 18:35:05 +00:00
Convert Advanced and Developer settings to macOS-native helpers
Extend SettingsFormSection to accept a @ViewBuilder footer for sections with dynamic multi-line content (last background refresh, orphaned files status). Move trailing button accessories (size, progress) out of button labels so buttons size to their content on macOS.
This commit is contained in:
@@ -26,7 +26,7 @@ struct AdvancedSettingsView: View {
|
|||||||
@State private var isScanningStorage = false
|
@State private var isScanningStorage = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
SettingsFormContainer {
|
||||||
streamDetailsSection
|
streamDetailsSection
|
||||||
mpvSection
|
mpvSection
|
||||||
settingsSection
|
settingsSection
|
||||||
@@ -98,7 +98,7 @@ struct AdvancedSettingsView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func feedSection(settingsManager: SettingsManager) -> some View {
|
private func feedSection(settingsManager: SettingsManager) -> some View {
|
||||||
Section {
|
SettingsFormSection("settings.advanced.feed.sectionTitle") {
|
||||||
PlatformMenuPicker(selection: Binding(
|
PlatformMenuPicker(selection: Binding(
|
||||||
get: { settingsManager.feedCacheValidityMinutes },
|
get: { settingsManager.feedCacheValidityMinutes },
|
||||||
set: { settingsManager.feedCacheValidityMinutes = $0 }
|
set: { settingsManager.feedCacheValidityMinutes = $0 }
|
||||||
@@ -109,17 +109,13 @@ struct AdvancedSettingsView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label(String(localized: "settings.advanced.feed.cacheValidity"), systemImage: "clock")
|
Label(String(localized: "settings.advanced.feed.cacheValidity"), systemImage: "clock")
|
||||||
}
|
}
|
||||||
} header: {
|
|
||||||
Text(String(localized: "settings.advanced.feed.sectionTitle"))
|
|
||||||
} footer: {
|
} footer: {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(String(localized: "settings.advanced.feed.footer"))
|
Text(String(localized: "settings.advanced.feed.footer"))
|
||||||
if let lastCheck = settingsManager.lastBackgroundCheck {
|
if let lastCheck = settingsManager.lastBackgroundCheck {
|
||||||
Text(String(localized: "settings.advanced.feed.lastBackgroundRefresh \(lastCheck.formatted(date: .abbreviated, time: .shortened))"))
|
Text(String(localized: "settings.advanced.feed.lastBackgroundRefresh \(lastCheck.formatted(date: .abbreviated, time: .shortened))"))
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
} else {
|
} else {
|
||||||
Text(String(localized: "settings.advanced.feed.lastBackgroundRefresh.never"))
|
Text(String(localized: "settings.advanced.feed.lastBackgroundRefresh.never"))
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +123,7 @@ struct AdvancedSettingsView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func userAgentSection(settingsManager: SettingsManager) -> some View {
|
private func userAgentSection(settingsManager: SettingsManager) -> some View {
|
||||||
Section {
|
SettingsFormSection("settings.advanced.userAgent.sectionTitle", footer: "settings.advanced.userAgent.footer") {
|
||||||
Toggle(isOn: Binding(
|
Toggle(isOn: Binding(
|
||||||
get: { settingsManager.randomizeUserAgentPerRequest },
|
get: { settingsManager.randomizeUserAgentPerRequest },
|
||||||
set: {
|
set: {
|
||||||
@@ -165,25 +161,19 @@ struct AdvancedSettingsView: View {
|
|||||||
Label(String(localized: "settings.advanced.userAgent.randomize"), systemImage: "arrow.trianglehead.2.clockwise")
|
Label(String(localized: "settings.advanced.userAgent.randomize"), systemImage: "arrow.trianglehead.2.clockwise")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} header: {
|
|
||||||
Text(String(localized: "settings.advanced.userAgent.sectionTitle"))
|
|
||||||
} footer: {
|
|
||||||
Text(String(localized: "settings.advanced.userAgent.footer"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var streamDetailsSection: some View {
|
private var streamDetailsSection: some View {
|
||||||
if let settingsManager = appEnvironment?.settingsManager {
|
if let settingsManager = appEnvironment?.settingsManager {
|
||||||
Section {
|
SettingsFormSection(footer: "settings.advanced.stream.showDetails.footer") {
|
||||||
Toggle(isOn: Binding(
|
Toggle(isOn: Binding(
|
||||||
get: { settingsManager.showAdvancedStreamDetails },
|
get: { settingsManager.showAdvancedStreamDetails },
|
||||||
set: { settingsManager.showAdvancedStreamDetails = $0 }
|
set: { settingsManager.showAdvancedStreamDetails = $0 }
|
||||||
)) {
|
)) {
|
||||||
Label(String(localized: "settings.advanced.stream.showDetails"), systemImage: "list.bullet.rectangle")
|
Label(String(localized: "settings.advanced.stream.showDetails"), systemImage: "list.bullet.rectangle")
|
||||||
}
|
}
|
||||||
} footer: {
|
|
||||||
Text(String(localized: "settings.advanced.stream.showDetails.footer"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,7 +181,7 @@ struct AdvancedSettingsView: View {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var mpvSection: some View {
|
private var mpvSection: some View {
|
||||||
if let settingsManager = appEnvironment?.settingsManager {
|
if let settingsManager = appEnvironment?.settingsManager {
|
||||||
Section {
|
SettingsFormSection("settings.advanced.mpv.title") {
|
||||||
PlatformMenuPicker(selection: Binding(
|
PlatformMenuPicker(selection: Binding(
|
||||||
get: { settingsManager.mpvBufferSeconds },
|
get: { settingsManager.mpvBufferSeconds },
|
||||||
set: { settingsManager.mpvBufferSeconds = $0 }
|
set: { settingsManager.mpvBufferSeconds = $0 }
|
||||||
@@ -217,13 +207,17 @@ struct AdvancedSettingsView: View {
|
|||||||
Label(String(localized: "settings.playback.dash"), systemImage: "bolt.horizontal")
|
Label(String(localized: "settings.playback.dash"), systemImage: "bolt.horizontal")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
MPVOptionsSettingsView()
|
MPVOptionsSettingsView()
|
||||||
} label: {
|
} label: {
|
||||||
Label(String(localized: "settings.advanced.mpv.options"), systemImage: "slider.horizontal.3")
|
Label(String(localized: "settings.advanced.mpv.options"), systemImage: "slider.horizontal.3")
|
||||||
}
|
}
|
||||||
} header: {
|
#else
|
||||||
Text(String(localized: "settings.advanced.mpv.title"))
|
SettingsNavigationRow("settings.advanced.mpv.options", systemImage: "slider.horizontal.3") {
|
||||||
|
MPVOptionsSettingsView()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,24 +235,26 @@ struct AdvancedSettingsView: View {
|
|||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var downloadsStorageSection: some View {
|
private var downloadsStorageSection: some View {
|
||||||
Section {
|
SettingsFormSection("settings.advanced.storage.title") {
|
||||||
// Storage diagnostic button
|
// Storage diagnostic button
|
||||||
Button {
|
HStack {
|
||||||
runStorageDiagnostics()
|
Button {
|
||||||
} label: {
|
runStorageDiagnostics()
|
||||||
HStack {
|
} label: {
|
||||||
Label(String(localized: "settings.advanced.storage.scan"), systemImage: "internaldrive")
|
Label(String(localized: "settings.advanced.storage.scan"), systemImage: "internaldrive")
|
||||||
Spacer()
|
|
||||||
if isScanningStorage {
|
|
||||||
ProgressView()
|
|
||||||
.controlSize(.small)
|
|
||||||
} else if let diagnostics = storageDiagnostics {
|
|
||||||
Text(diagnostics.formattedTotal)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.disabled(isScanningStorage)
|
||||||
|
|
||||||
|
if isScanningStorage {
|
||||||
|
ProgressView()
|
||||||
|
.controlSize(.small)
|
||||||
|
} else if let diagnostics = storageDiagnostics {
|
||||||
|
Text(diagnostics.formattedTotal)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
.disabled(isScanningStorage)
|
|
||||||
|
|
||||||
// Show storage breakdown if scanned
|
// Show storage breakdown if scanned
|
||||||
if let diagnostics = storageDiagnostics {
|
if let diagnostics = storageDiagnostics {
|
||||||
@@ -275,28 +271,31 @@ struct AdvancedSettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear cache button
|
// Clear cache button
|
||||||
Button(role: .destructive) {
|
HStack {
|
||||||
showingClearDataConfirmation = true
|
Button(role: .destructive) {
|
||||||
} label: {
|
showingClearDataConfirmation = true
|
||||||
Label(String(localized: "settings.advanced.data.clearCache"), systemImage: "trash")
|
} label: {
|
||||||
|
Label(String(localized: "settings.advanced.data.clearCache"), systemImage: "trash")
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete orphaned files button
|
// Delete orphaned files button
|
||||||
Button(role: .destructive) {
|
HStack {
|
||||||
showingOrphanCleanupConfirmation = true
|
Button(role: .destructive) {
|
||||||
} label: {
|
showingOrphanCleanupConfirmation = true
|
||||||
HStack {
|
} label: {
|
||||||
Label(String(localized: "settings.advanced.storage.deleteOrphaned"), systemImage: "trash")
|
Label(String(localized: "settings.advanced.storage.deleteOrphaned"), systemImage: "trash")
|
||||||
Spacer()
|
|
||||||
if isScanning {
|
|
||||||
ProgressView()
|
|
||||||
.controlSize(.small)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.disabled(orphanedFilesCount == 0 || isScanning)
|
||||||
|
|
||||||
|
if isScanning {
|
||||||
|
ProgressView()
|
||||||
|
.controlSize(.small)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
.disabled(orphanedFilesCount == 0 || isScanning)
|
|
||||||
} header: {
|
|
||||||
Text(String(localized: "settings.advanced.storage.title"))
|
|
||||||
} footer: {
|
} footer: {
|
||||||
if isScanning {
|
if isScanning {
|
||||||
Text(String(localized: "settings.advanced.storage.scanning"))
|
Text(String(localized: "settings.advanced.storage.scanning"))
|
||||||
@@ -314,7 +313,7 @@ struct AdvancedSettingsView: View {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var deviceNameSection: some View {
|
private var deviceNameSection: some View {
|
||||||
if let settingsManager = appEnvironment?.settingsManager {
|
if let settingsManager = appEnvironment?.settingsManager {
|
||||||
Section {
|
SettingsFormSection("settings.advanced.remoteControl.sectionTitle") {
|
||||||
TextField(
|
TextField(
|
||||||
LocalNetworkService.systemDeviceName,
|
LocalNetworkService.systemDeviceName,
|
||||||
text: Binding(
|
text: Binding(
|
||||||
@@ -338,8 +337,6 @@ struct AdvancedSettingsView: View {
|
|||||||
Label(String(localized: "remoteControl.hideWhenBackgrounded"), systemImage: "moon.fill")
|
Label(String(localized: "remoteControl.hideWhenBackgrounded"), systemImage: "moon.fill")
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} header: {
|
|
||||||
Text(String(localized: "settings.advanced.remoteControl.sectionTitle"))
|
|
||||||
} footer: {
|
} footer: {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(String(localized: "settings.advanced.remoteControl.footer"))
|
Text(String(localized: "settings.advanced.remoteControl.footer"))
|
||||||
@@ -355,22 +352,32 @@ struct AdvancedSettingsView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var developerSection: some View {
|
private var developerSection: some View {
|
||||||
Section {
|
SettingsFormSection(footer: "settings.developer.footer") {
|
||||||
|
#if os(tvOS)
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
DeveloperSettingsView()
|
DeveloperSettingsView()
|
||||||
} label: {
|
} label: {
|
||||||
Label(String(localized: "settings.developer.title"), systemImage: "hammer")
|
Label(String(localized: "settings.developer.title"), systemImage: "hammer")
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
SettingsNavigationRow("settings.developer.title", systemImage: "hammer") {
|
||||||
|
DeveloperSettingsView()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if appEnvironment?.legacyMigrationService.hasLegacyData() == true {
|
if appEnvironment?.legacyMigrationService.hasLegacyData() == true {
|
||||||
|
#if os(tvOS)
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
LegacyDataImportView()
|
LegacyDataImportView()
|
||||||
} label: {
|
} label: {
|
||||||
Label(String(localized: "settings.advanced.data.importLegacy"), systemImage: "arrow.up.doc")
|
Label(String(localized: "settings.advanced.data.importLegacy"), systemImage: "arrow.up.doc")
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
SettingsNavigationRow("settings.advanced.data.importLegacy", systemImage: "arrow.up.doc") {
|
||||||
|
LegacyDataImportView()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
} footer: {
|
|
||||||
Text(String(localized: "settings.developer.footer"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ struct DeveloperSettingsView: View {
|
|||||||
@State private var showingResetiCloudComplete = false
|
@State private var showingResetiCloudComplete = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
SettingsFormContainer {
|
||||||
loggingSection
|
loggingSection
|
||||||
if loggingEnabled {
|
if loggingEnabled {
|
||||||
verboseLoggingSection
|
verboseLoggingSection
|
||||||
@@ -87,7 +87,7 @@ struct DeveloperSettingsView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var loggingSection: some View {
|
private var loggingSection: some View {
|
||||||
Section {
|
SettingsFormSection("settings.advanced.logging.sectionTitle", footer: "settings.advanced.logging.footer") {
|
||||||
Toggle(isOn: $loggingEnabled) {
|
Toggle(isOn: $loggingEnabled) {
|
||||||
Label(String(localized: "settings.advanced.logging.enable"), systemImage: "doc.text")
|
Label(String(localized: "settings.advanced.logging.enable"), systemImage: "doc.text")
|
||||||
}
|
}
|
||||||
@@ -96,6 +96,7 @@ struct DeveloperSettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if loggingEnabled {
|
if loggingEnabled {
|
||||||
|
#if os(tvOS)
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
LogViewerView()
|
LogViewerView()
|
||||||
} label: {
|
} label: {
|
||||||
@@ -106,18 +107,23 @@ struct DeveloperSettingsView: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
SettingsNavigationRow(
|
||||||
|
"settings.advanced.logging.viewLogs",
|
||||||
|
systemImage: "list.bullet.rectangle",
|
||||||
|
trailing: { Text("\(loggingService.entries.count)") }
|
||||||
|
) {
|
||||||
|
LogViewerView()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
} header: {
|
|
||||||
Text(String(localized: "settings.advanced.logging.sectionTitle"))
|
|
||||||
} footer: {
|
|
||||||
Text(String(localized: "settings.advanced.logging.footer"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var verboseLoggingSection: some View {
|
private var verboseLoggingSection: some View {
|
||||||
if let settingsManager = appEnvironment?.settingsManager {
|
if let settingsManager = appEnvironment?.settingsManager {
|
||||||
Section {
|
SettingsFormSection("settings.advanced.verboseLogging.sectionTitle") {
|
||||||
Toggle(isOn: Binding(
|
Toggle(isOn: Binding(
|
||||||
get: { settingsManager.verboseMPVLogging },
|
get: { settingsManager.verboseMPVLogging },
|
||||||
set: { settingsManager.verboseMPVLogging = $0 }
|
set: { settingsManager.verboseMPVLogging = $0 }
|
||||||
@@ -131,8 +137,6 @@ struct DeveloperSettingsView: View {
|
|||||||
)) {
|
)) {
|
||||||
Label(String(localized: "settings.advanced.debug.verboseRemote"), systemImage: "antenna.radiowaves.left.and.right")
|
Label(String(localized: "settings.advanced.debug.verboseRemote"), systemImage: "antenna.radiowaves.left.and.right")
|
||||||
}
|
}
|
||||||
} header: {
|
|
||||||
Text(String(localized: "settings.advanced.verboseLogging.sectionTitle"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,7 +144,7 @@ struct DeveloperSettingsView: View {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var debugSection: some View {
|
private var debugSection: some View {
|
||||||
if let settingsManager = appEnvironment?.settingsManager {
|
if let settingsManager = appEnvironment?.settingsManager {
|
||||||
Section {
|
SettingsFormSection("settings.advanced.debug.sectionTitle") {
|
||||||
Toggle(isOn: Binding(
|
Toggle(isOn: Binding(
|
||||||
get: { settingsManager.showPlayerAreaDebug },
|
get: { settingsManager.showPlayerAreaDebug },
|
||||||
set: { settingsManager.showPlayerAreaDebug = $0 }
|
set: { settingsManager.showPlayerAreaDebug = $0 }
|
||||||
@@ -165,8 +169,6 @@ struct DeveloperSettingsView: View {
|
|||||||
Label(String(localized: "settings.advanced.debug.showTVDebugButton"), systemImage: "ant.circle")
|
Label(String(localized: "settings.advanced.debug.showTVDebugButton"), systemImage: "ant.circle")
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} header: {
|
|
||||||
Text(String(localized: "settings.advanced.debug.sectionTitle"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,22 +182,24 @@ struct DeveloperSettingsView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var dataSection: some View {
|
private var dataSection: some View {
|
||||||
Section {
|
SettingsFormSection("settings.advanced.data.sectionTitle", footer: "settings.advanced.data.footer") {
|
||||||
Button {
|
HStack {
|
||||||
showingDeduplicateConfirmation = true
|
Button {
|
||||||
} label: {
|
showingDeduplicateConfirmation = true
|
||||||
Label(String(localized: "settings.advanced.data.removeDuplicates"), systemImage: "doc.on.doc")
|
} label: {
|
||||||
|
Label(String(localized: "settings.advanced.data.removeDuplicates"), systemImage: "doc.on.doc")
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(role: .destructive) {
|
HStack {
|
||||||
showingResetiCloudConfirmation = true
|
Button(role: .destructive) {
|
||||||
} label: {
|
showingResetiCloudConfirmation = true
|
||||||
Label(String(localized: "settings.advanced.data.resetICloud"), systemImage: "icloud.slash")
|
} label: {
|
||||||
|
Label(String(localized: "settings.advanced.data.resetICloud"), systemImage: "icloud.slash")
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
} header: {
|
|
||||||
Text(String(localized: "settings.advanced.data.sectionTitle"))
|
|
||||||
} footer: {
|
|
||||||
Text(String(localized: "settings.advanced.data.footer"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,22 +226,24 @@ struct DeveloperSettingsView: View {
|
|||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var notificationTestingSection: some View {
|
private var notificationTestingSection: some View {
|
||||||
Section {
|
SettingsFormSection("settings.advanced.testing.notifications.sectionTitle", footer: "settings.advanced.testing.notifications.footer") {
|
||||||
Button {
|
HStack {
|
||||||
sendTestNotification()
|
Button {
|
||||||
} label: {
|
sendTestNotification()
|
||||||
Label(String(localized: "settings.advanced.testing.sendTestNotification"), systemImage: "bell.badge")
|
} label: {
|
||||||
|
Label(String(localized: "settings.advanced.testing.sendTestNotification"), systemImage: "bell.badge")
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
HStack {
|
||||||
triggerBackgroundRefresh()
|
Button {
|
||||||
} label: {
|
triggerBackgroundRefresh()
|
||||||
Label(String(localized: "settings.advanced.testing.triggerBackgroundRefresh"), systemImage: "arrow.clockwise")
|
} label: {
|
||||||
|
Label(String(localized: "settings.advanced.testing.triggerBackgroundRefresh"), systemImage: "arrow.clockwise")
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
} header: {
|
|
||||||
Text(String(localized: "settings.advanced.testing.notifications.sectionTitle"))
|
|
||||||
} footer: {
|
|
||||||
Text(String(localized: "settings.advanced.testing.notifications.footer"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,20 +43,10 @@ struct SettingsFormContainer<Content: View>: View {
|
|||||||
/// content with consistent padding, a bottom divider, and an optional
|
/// content with consistent padding, a bottom divider, and an optional
|
||||||
/// caption-sized footer.
|
/// caption-sized footer.
|
||||||
/// - On iOS/tvOS: renders a standard `Section { } header: { } footer: { }`.
|
/// - On iOS/tvOS: renders a standard `Section { } header: { } footer: { }`.
|
||||||
struct SettingsFormSection<Content: View>: View {
|
struct SettingsFormSection<Content: View, Footer: View>: View {
|
||||||
let header: LocalizedStringKey?
|
let header: LocalizedStringKey?
|
||||||
let footer: LocalizedStringKey?
|
|
||||||
@ViewBuilder let content: () -> Content
|
@ViewBuilder let content: () -> Content
|
||||||
|
@ViewBuilder let footer: () -> Footer
|
||||||
init(
|
|
||||||
_ header: LocalizedStringKey? = nil,
|
|
||||||
footer: LocalizedStringKey? = nil,
|
|
||||||
@ViewBuilder content: @escaping () -> Content
|
|
||||||
) {
|
|
||||||
self.header = header
|
|
||||||
self.footer = footer
|
|
||||||
self.content = content
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
@@ -91,49 +81,83 @@ struct SettingsFormSection<Content: View>: View {
|
|||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
if let footer {
|
footer()
|
||||||
Text(footer)
|
.font(.caption)
|
||||||
.font(.caption)
|
.foregroundStyle(.secondary)
|
||||||
.foregroundStyle(.secondary)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.padding(.horizontal, 16)
|
||||||
.padding(.horizontal, 16)
|
.padding(.top, 6)
|
||||||
.padding(.top, 6)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.padding(.bottom, 12)
|
.padding(.bottom, 12)
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var platformSection: some View {
|
private var platformSection: some View {
|
||||||
if let header, let footer {
|
if let header {
|
||||||
Section {
|
Section {
|
||||||
content()
|
content()
|
||||||
} header: {
|
} header: {
|
||||||
Text(header)
|
Text(header)
|
||||||
} footer: {
|
} footer: {
|
||||||
Text(footer)
|
footer()
|
||||||
}
|
|
||||||
} else if let header {
|
|
||||||
Section {
|
|
||||||
content()
|
|
||||||
} header: {
|
|
||||||
Text(header)
|
|
||||||
}
|
|
||||||
} else if let footer {
|
|
||||||
Section {
|
|
||||||
content()
|
|
||||||
} footer: {
|
|
||||||
Text(footer)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Section {
|
Section {
|
||||||
content()
|
content()
|
||||||
|
} footer: {
|
||||||
|
footer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SettingsFormSection where Footer == EmptyView {
|
||||||
|
init(
|
||||||
|
_ header: LocalizedStringKey? = nil,
|
||||||
|
@ViewBuilder content: @escaping () -> Content
|
||||||
|
) {
|
||||||
|
self.header = header
|
||||||
|
self.content = content
|
||||||
|
self.footer = { EmptyView() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SettingsFormSection where Footer == Text {
|
||||||
|
init(
|
||||||
|
_ header: LocalizedStringKey? = nil,
|
||||||
|
footer: LocalizedStringKey,
|
||||||
|
@ViewBuilder content: @escaping () -> Content
|
||||||
|
) {
|
||||||
|
self.header = header
|
||||||
|
self.content = content
|
||||||
|
self.footer = { Text(footer) }
|
||||||
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
_ header: LocalizedStringKey? = nil,
|
||||||
|
footer: LocalizedStringKey?,
|
||||||
|
@ViewBuilder content: @escaping () -> Content
|
||||||
|
) {
|
||||||
|
self.header = header
|
||||||
|
self.content = content
|
||||||
|
let footerKey = footer
|
||||||
|
self.footer = { footerKey.map { Text($0) } ?? Text(verbatim: "") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SettingsFormSection {
|
||||||
|
init(
|
||||||
|
_ header: LocalizedStringKey? = nil,
|
||||||
|
@ViewBuilder content: @escaping () -> Content,
|
||||||
|
@ViewBuilder footer: @escaping () -> Footer
|
||||||
|
) {
|
||||||
|
self.header = header
|
||||||
|
self.content = content
|
||||||
|
self.footer = footer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A label style that forces the icon to a fixed width so adjacent
|
/// A label style that forces the icon to a fixed width so adjacent
|
||||||
/// labels align regardless of icon glyph width. Use when a section has
|
/// labels align regardless of icon glyph width. Use when a section has
|
||||||
/// a vertical stack of `Label`s with mixed-width SF Symbols.
|
/// a vertical stack of `Label`s with mixed-width SF Symbols.
|
||||||
|
|||||||
Reference in New Issue
Block a user