Improve footers in macOS form sections (#814)

Revisit the use of informational footers in forms because:

- iOS uses Section footers
- macOS uses a secondary label below the main row label

Therefore:

- Add .themeRow() modifier to accomplish macOS behavior
- iOS: leave .themeSection() as is, and add a dummy .themeRow() that
does nothing
- macOS: make footer ineffective in .themeSection(), but add .themeRow()
modifiers to move footers to rows
This commit is contained in:
Davide 2024-11-05 13:32:09 +01:00 committed by GitHub
parent 346aaec441
commit 735d3b2fbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 51 additions and 5 deletions

View File

@ -103,6 +103,7 @@ private extension OnDemandView {
} }
} }
.themeSection(footer: policyFooterDescription) .themeSection(footer: policyFooterDescription)
.themeRow(footer: policyFooterDescription)
} }
var policyFooterDescription: String { var policyFooterDescription: String {

View File

@ -51,6 +51,7 @@ struct AppleTVSection: View {
private extension AppleTVSection { private extension AppleTVSection {
var availableToggle: some View { var availableToggle: some View {
Toggle(Strings.Modules.General.Rows.appleTv(Strings.Unlocalized.appleTV), isOn: $profileEditor.isAvailableForTV) Toggle(Strings.Modules.General.Rows.appleTv(Strings.Unlocalized.appleTV), isOn: $profileEditor.isAvailableForTV)
.themeRow(footer: footer)
} }
var purchaseButton: some View { var purchaseButton: some View {

View File

@ -44,7 +44,7 @@ struct StorageSection: View {
} }
.themeSection( .themeSection(
header: Strings.Global.storage, header: Strings.Global.storage,
footer: Strings.Modules.General.Sections.Storage.footer footer: footer
) )
} }
} }
@ -52,6 +52,11 @@ struct StorageSection: View {
private extension StorageSection { private extension StorageSection {
var sharingToggle: some View { var sharingToggle: some View {
Toggle(Strings.Modules.General.Rows.icloudSharing, isOn: $profileEditor.isShared) Toggle(Strings.Modules.General.Rows.icloudSharing, isOn: $profileEditor.isShared)
.themeRow(footer: footer)
}
var footer: String {
Strings.Modules.General.Sections.Storage.footer
} }
} }

View File

@ -56,10 +56,11 @@ struct SettingsSectionGroup: View {
.themeSection(header: Strings.Global.general) .themeSection(header: Strings.Global.general)
Group { Group {
eraseCloudKitButton eraseCloudKitButton
.themeRow(footer: iCloudFooter)
} }
.themeSection( .themeSection(
header: Strings.Unlocalized.iCloud, header: Strings.Unlocalized.iCloud,
footer: Strings.Views.Settings.Sections.Icloud.footer footer: iCloudFooter
) )
} }
} }
@ -99,4 +100,8 @@ private extension SettingsSectionGroup {
} }
.disabled(isErasingiCloud) .disabled(isErasingiCloud)
} }
var iCloudFooter: String {
Strings.Views.Settings.Sections.Icloud.footer
}
} }

View File

@ -130,6 +130,13 @@ extension ThemeSectionWithHeaderFooterModifier {
} }
} }
extension ThemeRowWithFooterModifier {
func body(content: Content) -> some View {
content
// omit footer on iOS/tvOS, use ThemeSectionWithHeaderFooterModifier
}
}
// MARK: - Views // MARK: - Views
extension ThemeTappableText { extension ThemeTappableText {

View File

@ -78,14 +78,25 @@ extension ThemeSectionWithHeaderFooterModifier {
func body(content: Content) -> some View { func body(content: Content) -> some View {
Section { Section {
content content
} header: {
header.map(Text.init)
}
// omit footer on macOS, use ThemeRowWithFooterModifier
}
}
extension ThemeRowWithFooterModifier {
func body(content: Content) -> some View {
VStack {
content
.frame(maxWidth: .infinity, alignment: .leading)
footer.map { footer.map {
Text($0) Text($0)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.font(.callout) .font(.caption)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
} header: {
header.map(Text.init)
} }
} }
} }

View File

@ -63,6 +63,13 @@ extension ThemeSectionWithHeaderFooterModifier {
} }
} }
extension ThemeRowWithFooterModifier {
func body(content: Content) -> some View {
content
// omit footer on iOS/tvOS, use ThemeSectionWithHeaderFooterModifier
}
}
// MARK: - Views // MARK: - Views
extension ThemeTextField { extension ThemeTextField {

View File

@ -91,6 +91,10 @@ extension View {
modifier(ThemeSectionWithHeaderFooterModifier(header: header, footer: footer)) modifier(ThemeSectionWithHeaderFooterModifier(header: header, footer: footer))
} }
public func themeRow(footer: String? = nil) -> some View {
modifier(ThemeRowWithFooterModifier(footer: footer))
}
public func themeNavigationDetail() -> some View { public func themeNavigationDetail() -> some View {
#if os(iOS) #if os(iOS)
navigationBarTitleDisplayMode(.inline) navigationBarTitleDisplayMode(.inline)
@ -290,6 +294,10 @@ struct ThemeSectionWithHeaderFooterModifier: ViewModifier {
let footer: String? let footer: String?
} }
struct ThemeRowWithFooterModifier: ViewModifier {
let footer: String?
}
#if !os(tvOS) #if !os(tvOS)
struct ThemeWindowModifier: ViewModifier { struct ThemeWindowModifier: ViewModifier {

View File

@ -130,6 +130,7 @@ private extension OpenVPNCredentialsView {
var interactiveSection: some View { var interactiveSection: some View {
Group { Group {
Toggle(Strings.Modules.Openvpn.Credentials.interactive, isOn: $isInteractive) Toggle(Strings.Modules.Openvpn.Credentials.interactive, isOn: $isInteractive)
.themeRow(footer: interactiveFooter)
if isInteractive { if isInteractive {
Picker(Strings.Unlocalized.otp, selection: $builder.otpMethod) { Picker(Strings.Unlocalized.otp, selection: $builder.otpMethod) {