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)
.themeRow(footer: policyFooterDescription)
}
var policyFooterDescription: String {

View File

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

View File

@ -44,7 +44,7 @@ struct StorageSection: View {
}
.themeSection(
header: Strings.Global.storage,
footer: Strings.Modules.General.Sections.Storage.footer
footer: footer
)
}
}
@ -52,6 +52,11 @@ struct StorageSection: View {
private extension StorageSection {
var sharingToggle: some View {
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)
Group {
eraseCloudKitButton
.themeRow(footer: iCloudFooter)
}
.themeSection(
header: Strings.Unlocalized.iCloud,
footer: Strings.Views.Settings.Sections.Icloud.footer
footer: iCloudFooter
)
}
}
@ -99,4 +100,8 @@ private extension SettingsSectionGroup {
}
.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
extension ThemeTappableText {

View File

@ -78,14 +78,25 @@ extension ThemeSectionWithHeaderFooterModifier {
func body(content: Content) -> some View {
Section {
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 {
Text($0)
.foregroundStyle(.secondary)
.font(.callout)
.font(.caption)
.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
extension ThemeTextField {

View File

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

View File

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