Improve TV settings screen (#943)

- Show detail side by side rather than navigate
- Fix scrolling in purchased view
This commit is contained in:
Davide 2024-11-26 15:55:04 +01:00 committed by GitHub
parent e49e8881b3
commit 80d40c3161
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 87 additions and 46 deletions

View File

@ -108,19 +108,6 @@ private extension AppCoordinator {
DebugLogContentView(lines: $0) DebugLogContentView(lines: $0)
} }
case .credits:
CreditsView()
.resized(width: 0.5)
.themeList()
case .donate:
DonateView(modifier: DonateViewModifier())
case .purchased:
PurchasedView()
.resized(width: 0.5)
.themeList()
case .tunnelLog: case .tunnelLog:
DebugLogView(withTunnel: tunnel, parameters: Constants.shared.log) { DebugLogView(withTunnel: tunnel, parameters: Constants.shared.log) {
DebugLogContentView(lines: $0) DebugLogContentView(lines: $0)

View File

@ -28,11 +28,5 @@ import Foundation
enum AppCoordinatorRoute: Hashable { enum AppCoordinatorRoute: Hashable {
case appLog case appLog
case credits
case donate
case purchased
case tunnelLog case tunnelLog
} }

View File

@ -38,12 +38,11 @@ struct DonateViewModifier: ViewModifier {
} }
} }
} }
.padding(.top, 150)
} }
} }
private extension DonateViewModifier { private extension DonateViewModifier {
var columns: [GridItem] { var columns: [GridItem] {
[GridItem(.adaptive(minimum: 500))] [GridItem(.adaptive(minimum: 300))]
} }
} }

View File

@ -29,17 +29,51 @@ import PassepartoutKit
import SwiftUI import SwiftUI
import UILibrary import UILibrary
enum Detail {
case credits
case donate
case other
case purchased
}
struct SettingsView: View { struct SettingsView: View {
let tunnel: ExtendedTunnel let tunnel: ExtendedTunnel
@Namespace
private var masterScope
@Namespace
private var detailScope
@FocusState
private var focus: Detail?
@State
private var detail: Detail?
var body: some View { var body: some View {
listView HStack {
.resized(width: 0.5) masterView
.frame(maxWidth: .infinity)
.focused($focus, equals: .other)
DetailView(detail: detail)
.frame(maxWidth: .infinity)
}
.onChange(of: focus) {
guard focus != nil else {
return
}
detail = focus
}
} }
} }
private extension SettingsView { private extension SettingsView {
var listView: some View { var masterView: some View {
List { List {
creditsSection creditsSection
diagnosticsSection diagnosticsSection
@ -50,8 +84,10 @@ private extension SettingsView {
var creditsSection: some View { var creditsSection: some View {
Group { Group {
NavigationLink(Strings.Views.About.Credits.title, value: AppCoordinatorRoute.credits) Button(Strings.Views.About.Credits.title) {}
NavigationLink(Strings.Views.Donate.title, value: AppCoordinatorRoute.donate) .focused($focus, equals: .credits)
Button(Strings.Views.Donate.title) {}
.focused($focus, equals: .donate)
} }
.themeSection(header: Strings.Unlocalized.appName) .themeSection(header: Strings.Unlocalized.appName)
} }
@ -67,7 +103,8 @@ private extension SettingsView {
var aboutSection: some View { var aboutSection: some View {
Group { Group {
NavigationLink(Strings.Views.Purchased.title, value: AppCoordinatorRoute.purchased) Button(Strings.Views.Purchased.title) {}
.focused($focus, equals: .purchased)
Text(Strings.Global.Nouns.version) Text(Strings.Global.Nouns.version)
.themeTrailingValue(BundleConfiguration.mainVersionString) .themeTrailingValue(BundleConfiguration.mainVersionString)
} }
@ -75,6 +112,28 @@ private extension SettingsView {
} }
} }
private struct DetailView: View {
let detail: Detail?
var body: some View {
switch detail {
case .credits:
CreditsView()
.themeList()
case .donate:
DonateView(modifier: DonateViewModifier())
case .purchased:
PurchasedView()
.themeList()
default:
VStack {}
}
}
}
// MARK: - // MARK: -
#Preview { #Preview {

View File

@ -178,15 +178,7 @@ private extension GenericCreditsView {
var translationsSection: some View { var translationsSection: some View {
Section { Section {
ForEach(sortedLanguages, id: \.self) { code in ForEach(sortedLanguages, id: \.self) { code in
#if os(tvOS)
Button {
//
} label: {
translationLabel(code)
}
#else
translationLabel(code) translationLabel(code)
#endif
} }
} header: { } header: {
translationsHeader.map(Text.init) translationsHeader.map(Text.init)
@ -215,6 +207,7 @@ private extension GenericCreditsView {
} }
} }
} }
.scrollableOnTV()
} }
} }

View File

@ -67,6 +67,15 @@ extension View {
) )
} }
} }
public func scrollableOnTV() -> some View {
// focusable()
Button {
//
} label: {
self
}
}
} }
extension ViewModifier { extension ViewModifier {

View File

@ -77,19 +77,23 @@ private extension PurchasedView {
Group { Group {
Text(Strings.Views.Purchased.Rows.buildNumber) Text(Strings.Views.Purchased.Rows.buildNumber)
.themeTrailingValue(build.description) .themeTrailingValue(build.description)
.scrollableOnTV()
} }
.themeSection(header: Strings.Views.Purchased.Sections.Download.header) .themeSection(header: Strings.Views.Purchased.Sections.Download.header)
} }
} }
var productsSection: some View { var productsSection: some View {
Group { products.nilIfEmpty.map { products in
ForEach(products, id: \.productIdentifier) { Group {
Text($0.localizedTitle) ForEach(products, id: \.productIdentifier) {
.themeTrailingValue($0.localizedPrice) Text($0.localizedTitle)
.themeTrailingValue($0.localizedPrice)
.scrollableOnTV()
}
} }
.themeSection(header: Strings.Views.Purchased.Sections.Products.header)
} }
.themeSection(header: Strings.Views.Purchased.Sections.Products.header)
} }
var featuresSection: some View { var featuresSection: some View {
@ -101,6 +105,7 @@ private extension PurchasedView {
ThemeImage(.marked) ThemeImage(.marked)
.opaque(iapManager.isEligible(for: feature)) .opaque(iapManager.isEligible(for: feature))
} }
.scrollableOnTV()
} }
} }
.themeSection(header: Strings.Views.Purchased.Sections.Features.header) .themeSection(header: Strings.Views.Purchased.Sections.Features.header)

View File

@ -155,13 +155,8 @@ private extension PaywallView {
} }
func featureView(for feature: AppFeature) -> some View { func featureView(for feature: AppFeature) -> some View {
#if os(tvOS)
Button(feature.localizedDescription) {
//
}
#else
Text(feature.localizedDescription) Text(feature.localizedDescription)
#endif .scrollableOnTV()
} }
var restoreView: some View { var restoreView: some View {