Skip to content

Commit

Permalink
Add TV settings tab (#921)
Browse files Browse the repository at this point in the history
Includes:

- Credits
- Donations
- Diagnostics
- Version

Had to:

- Wrap tab view into a NavigationStack for full-screen navigation
- Take out navigation titles of about subviews
- Customize donations view layout with modifier
- Fix credits and debug log to support scrolling

Closes #914
  • Loading branch information
keeshux authored Nov 23, 2024
1 parent f13a292 commit bad9e8b
Show file tree
Hide file tree
Showing 27 changed files with 487 additions and 108 deletions.
3 changes: 0 additions & 3 deletions Passepartout/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//

#if !os(tvOS)
import AppUIMain
#endif
import CommonLibrary
import PassepartoutKit
import SwiftUI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,19 @@ extension AboutCoordinator {
switch item {
case .credits:
CreditsView()
.navigationTitle(Strings.Views.About.Credits.title)

case .diagnostics:
DiagnosticsView(profileManager: profileManager, tunnel: tunnel)
.navigationTitle(Strings.Views.Diagnostics.title)

case .donate:
DonateView()
DonateView(modifier: DonateViewModifier())
.navigationTitle(Strings.Views.Donate.title)

case .links:
LinksView()
.navigationTitle(Strings.Views.About.Links.title)

default:
Text(Strings.Global.Nouns.noSelection)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// DonateViewModifier+iOS.swift
// Passepartout
//
// Created by Davide De Rosa on 11/23/24.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//

#if os(iOS)

import SwiftUI

struct DonateViewModifier: ViewModifier {
func body(content: Content) -> some View {
List {
content
.themeSection(footer: Strings.Views.Donate.Sections.Main.footer)
}
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// DonateViewModifier+macOS.swift
// Passepartout
//
// Created by Davide De Rosa on 11/23/24.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//

#if os(macOS)

import SwiftUI

struct DonateViewModifier: ViewModifier {
func body(content: Content) -> some View {
Form {
Section {
Text(Strings.Views.Donate.Sections.Main.footer)
}
content
}
.themeForm()
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ struct DiagnosticsView: View {
tunnelLogs = await availableTunnelLogs()
}
.themeForm()
.navigationTitle(Strings.Views.Diagnostics.title)
.alert(Strings.Views.Diagnostics.ReportIssue.title, isPresented: $isPresentingUnableToEmail) {
Button(Strings.Global.Nouns.ok, role: .cancel) {
isPresentingUnableToEmail = false
Expand All @@ -105,11 +104,7 @@ private extension DiagnosticsView {
navLink(Strings.Views.Diagnostics.Rows.app, to: .app(title: Strings.Views.Diagnostics.Rows.app))
navLink(Strings.Views.Diagnostics.Rows.tunnel, to: .tunnel(title: Strings.Views.Diagnostics.Rows.tunnel, url: nil))

Toggle(Strings.Views.Diagnostics.Rows.includePrivateData, isOn: $logsPrivateData)
.onChange(of: logsPrivateData) {
PassepartoutConfiguration.shared.logsAddresses = $0
PassepartoutConfiguration.shared.logsModules = $0
}
LogsPrivateDataToggle()
}
.themeSection(header: Strings.Views.Diagnostics.Sections.live)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ struct DebugLogContentView: View {
var body: some View {
TextEditor(text: .constant(lines.joined(separator: "\n")))
.font(.caption)
.monospaced()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ struct DebugLogContentView: View {
List {
ForEach(Array(lines.enumerated()), id: \.offset) {
Text($0.element)
.monospaced()
}
}
}
Expand Down
66 changes: 50 additions & 16 deletions Passepartout/Library/Sources/AppUITV/Views/App/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,24 @@ public struct AppCoordinator: View, AppCoordinatorConforming {

public var body: some View {
debugChanges()
return TabView {
profileView
.tabItem {
Text(Strings.Global.Nouns.profile)
}

// searchView
// .tabItem {
// ThemeImage(.search)
// }

settingsView
.tabItem {
ThemeImage(.settings)
}
return NavigationStack {
TabView {
profileView
.tabItem {
Text(Strings.Global.Nouns.profile)
}

// searchView
// .tabItem {
// ThemeImage(.search)
// }

settingsView
.tabItem {
ThemeImage(.settings)
}
}
.navigationDestination(for: AppCoordinatorRoute.self, destination: pushDestination)
}
}
}
Expand All @@ -73,10 +76,41 @@ private extension AppCoordinator {
// }

var settingsView: some View {
SettingsView()
SettingsView(tunnel: tunnel)
}
}

private extension AppCoordinator {

@ViewBuilder
func pushDestination(_ item: AppCoordinatorRoute?) -> some View {
switch item {
case .appLog:
DebugLogView(withAppParameters: Constants.shared.log) {
DebugLogContentView(lines: $0)
}

case .credits:
CreditsView()
.resized(width: 0.5)
.themeList()

case .donate:
DonateView(modifier: DonateViewModifier())

case .tunnelLog:
DebugLogView(withTunnel: tunnel, parameters: Constants.shared.log) {
DebugLogContentView(lines: $0)
}

default:
EmptyView()
}
}
}

// MARK: -

#Preview {
AppCoordinator(
profileManager: .mock,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// AppCoordinatorRoute.swift
// Passepartout
//
// Created by Davide De Rosa on 11/23/24.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//

import Foundation

enum AppCoordinatorRoute: Hashable {
case appLog

case credits

case donate

case tunnelLog
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,24 +100,24 @@ private extension ActiveProfileView {
func detailView(for profile: Profile) -> some View {
VStack(spacing: 10) {
if let connectionType = profile.localizedDescription(optionalStyle: .connectionType) {
DetailRowView(title: Strings.Global.Nouns.protocol) {
ListRowView(title: Strings.Global.Nouns.protocol) {
Text(connectionType)
}
}
if let pair = profile.selectedProvider {
if let metadata = providerManager.provider(withId: pair.selection.id) {
DetailRowView(title: Strings.Global.Nouns.provider) {
ListRowView(title: Strings.Global.Nouns.provider) {
Text(metadata.description)
}
}
if let entity = pair.selection.entity {
DetailRowView(title: Strings.Global.Nouns.country) {
ListRowView(title: Strings.Global.Nouns.country) {
ThemeCountryText(entity.header.countryCode)
}
}
}
if let otherList = profile.localizedDescription(optionalStyle: .nonConnectionTypes) {
DetailRowView(title: otherList) {
ListRowView(title: otherList) {
EmptyView()
}
}
Expand Down Expand Up @@ -171,29 +171,11 @@ private extension ActiveProfileView {

private extension ActiveProfileView {
func onProviderEntityRequired(_ profile: Profile) {
// FIXME: #788, TV missing provider entity
// FIXME: #913, TV missing provider entity
}

func onPurchaseRequired(_ features: Set<AppFeature>) {
// FIXME: #788, TV purchase required
}
}

// MARK: - Subviews

private struct DetailRowView<Content>: View where Content: View {
let title: String

@ViewBuilder
let content: Content

var body: some View {
HStack {
Text(title)
.fontWeight(.light)
Spacer()
content
}
// FIXME: #913, TV purchase required
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ struct ProfileListView: View {
List {
ForEach(previews, id: \.id, content: toggleButton(for:))
}
.listStyle(.grouped)
.scrollClipDisabled()
.themeList()
.themeProgress(if: false, isEmpty: !profileManager.hasProfiles) {
Text(Strings.Views.App.Folders.noProfiles)
.themeEmptyMessage()
Expand Down Expand Up @@ -105,11 +104,11 @@ private extension ProfileListView {

private extension ProfileListView {
func onProviderEntityRequired(_ profile: Profile) {
// FIXME: #788, TV missing provider entity
// FIXME: #913, TV missing provider entity
}

func onPurchaseRequired(_ features: Set<AppFeature>) {
// FIXME: #788, TV purchase required
// FIXME: #913, TV purchase required
}
}

Expand Down
Loading

0 comments on commit bad9e8b

Please sign in to comment.