From 735d3b2fbe5b9b04d49a7591c6d7309817ad2e4b Mon Sep 17 00:00:00 2001 From: Davide Date: Tue, 5 Nov 2024 13:32:09 +0100 Subject: [PATCH] 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 --- .../AppUIMain/Views/Modules/OnDemandView.swift | 1 + .../Views/Profile/AppleTVSection.swift | 1 + .../Views/Profile/StorageSection.swift | 7 ++++++- .../Views/Settings/SettingsSectionGroup.swift | 7 ++++++- .../UILibrary/Theme/Platforms/Theme+iOS.swift | 7 +++++++ .../UILibrary/Theme/Platforms/Theme+macOS.swift | 17 ++++++++++++++--- .../UILibrary/Theme/Platforms/Theme+tvOS.swift | 7 +++++++ .../UILibrary/Theme/UI/Theme+Modifiers.swift | 8 ++++++++ .../Views/Modules/OpenVPNView+Credentials.swift | 1 + 9 files changed, 51 insertions(+), 5 deletions(-) diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Modules/OnDemandView.swift b/Passepartout/Library/Sources/AppUIMain/Views/Modules/OnDemandView.swift index a629f0fa..af6c6ec9 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Modules/OnDemandView.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Modules/OnDemandView.swift @@ -103,6 +103,7 @@ private extension OnDemandView { } } .themeSection(footer: policyFooterDescription) + .themeRow(footer: policyFooterDescription) } var policyFooterDescription: String { diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Profile/AppleTVSection.swift b/Passepartout/Library/Sources/AppUIMain/Views/Profile/AppleTVSection.swift index 1faa565d..23d6acd2 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Profile/AppleTVSection.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Profile/AppleTVSection.swift @@ -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 { diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Profile/StorageSection.swift b/Passepartout/Library/Sources/AppUIMain/Views/Profile/StorageSection.swift index 773db740..fb783a5f 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Profile/StorageSection.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Profile/StorageSection.swift @@ -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 } } diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Settings/SettingsSectionGroup.swift b/Passepartout/Library/Sources/AppUIMain/Views/Settings/SettingsSectionGroup.swift index 84effaa9..7f0c357b 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Settings/SettingsSectionGroup.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Settings/SettingsSectionGroup.swift @@ -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 + } } diff --git a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift index a7568624..a9a2dd6b 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift @@ -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 { diff --git a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+macOS.swift b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+macOS.swift index 7f58bbf7..b3d50fa6 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+macOS.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+macOS.swift @@ -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) } } } diff --git a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+tvOS.swift b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+tvOS.swift index eab31011..5743dffe 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+tvOS.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+tvOS.swift @@ -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 { diff --git a/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift b/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift index 7231f82b..51ccff25 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift @@ -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 { diff --git a/Passepartout/Library/Sources/UILibrary/Views/Modules/OpenVPNView+Credentials.swift b/Passepartout/Library/Sources/UILibrary/Views/Modules/OpenVPNView+Credentials.swift index 07e776c6..47e55c11 100644 --- a/Passepartout/Library/Sources/UILibrary/Views/Modules/OpenVPNView+Credentials.swift +++ b/Passepartout/Library/Sources/UILibrary/Views/Modules/OpenVPNView+Credentials.swift @@ -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) {