Improve TV settings screen (#943)
- Show detail side by side rather than navigate - Fix scrolling in purchased view
This commit is contained in:
parent
e49e8881b3
commit
80d40c3161
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,15 @@ extension View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func scrollableOnTV() -> some View {
|
||||||
|
// focusable()
|
||||||
|
Button {
|
||||||
|
//
|
||||||
|
} label: {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ViewModifier {
|
extension ViewModifier {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue