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:
parent
346aaec441
commit
735d3b2fbe
|
@ -103,6 +103,7 @@ private extension OnDemandView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.themeSection(footer: policyFooterDescription)
|
.themeSection(footer: policyFooterDescription)
|
||||||
|
.themeRow(footer: policyFooterDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
var policyFooterDescription: String {
|
var policyFooterDescription: String {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue