From eac1e028b2ff2412747e751631a33d2bbb9baa13 Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 27 Nov 2024 13:24:44 +0100 Subject: [PATCH] Add version view in About/Settings (#952) Restored from v2. Refactor LogoView for reuse. --- .../Views/About/AboutCoordinator.swift | 34 +++++-- .../Views/About/AboutCoordinatorRoute.swift | 2 + .../About/iOS/AboutContentView+iOS.swift | 5 +- .../About/macOS/AboutContentView+macOS.swift | 7 +- .../AppUITV/Views/Profile/ProfileView.swift | 2 +- .../AppUITV/Views/Settings/SettingsView.swift | 28 +++--- .../Views/GenericCreditsView.swift | 2 - .../UILibrary/L10n/Strings+Unlocalized.swift | 2 + .../UILibrary/L10n/SwiftGen+Strings.swift | 12 ++- .../Sources/UILibrary/Resources/Credits.json | 1 - .../Resources/en.lproj/Localizable.strings | 3 +- .../UILibrary/Theme/Platforms/Theme+iOS.swift | 6 +- .../Theme/Platforms/Theme+tvOS.swift | 6 ++ .../UILibrary/Views/About/VersionView.swift | 90 +++++++++++++++++++ .../{LogoView.swift => FullScreenView.swift} | 22 +++-- .../UILibrary/Views/UI/LogoImage.swift | 39 ++++++++ 16 files changed, 225 insertions(+), 36 deletions(-) create mode 100644 Passepartout/Library/Sources/UILibrary/Views/About/VersionView.swift rename Passepartout/Library/Sources/UILibrary/Views/UI/{LogoView.swift => FullScreenView.swift} (73%) create mode 100644 Passepartout/Library/Sources/UILibrary/Views/UI/LogoImage.swift diff --git a/Passepartout/Library/Sources/AppUIMain/Views/About/AboutCoordinator.swift b/Passepartout/Library/Sources/AppUIMain/Views/About/AboutCoordinator.swift index dec62a96..9c4d7f73 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/About/AboutCoordinator.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/About/AboutCoordinator.swift @@ -61,25 +61,44 @@ struct AboutCoordinator: View { extension AboutCoordinator { func linkView(to route: AboutCoordinatorRoute) -> some View { - NavigationLink(title(for: route), value: route) + NavigationLink(value: route) { + linkLabel(for: route) + } } func title(for route: AboutCoordinatorRoute) -> String { switch route { case .credits: - return Strings.Views.About.Credits.title + Strings.Views.About.Credits.title case .diagnostics: - return Strings.Views.Diagnostics.title + Strings.Views.Diagnostics.title case .donate: - return Strings.Views.Donate.title + Strings.Views.Donate.title case .links: - return Strings.Views.About.Links.title + Strings.Views.About.Links.title case .purchased: - return Strings.Views.Purchased.title + Strings.Views.Purchased.title + + case .version: + Strings.Views.About.title + } + } + + @ViewBuilder + func linkLabel(for route: AboutCoordinatorRoute) -> some View { + switch route { + case .version: + Text(Strings.Global.Nouns.version) +#if os(iOS) + .themeTrailingValue(BundleConfiguration.mainVersionString) +#endif + + default: + Text(title(for: route)) } } @@ -106,6 +125,9 @@ extension AboutCoordinator { PurchasedView() .navigationTitle(title(for: .purchased)) + case .version: + VersionView() + default: Text(Strings.Global.Nouns.noSelection) .themeEmptyMessage() diff --git a/Passepartout/Library/Sources/AppUIMain/Views/About/AboutCoordinatorRoute.swift b/Passepartout/Library/Sources/AppUIMain/Views/About/AboutCoordinatorRoute.swift index 785a6e03..e98023f7 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/About/AboutCoordinatorRoute.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/About/AboutCoordinatorRoute.swift @@ -35,4 +35,6 @@ enum AboutCoordinatorRoute: Hashable { case links case purchased + + case version } diff --git a/Passepartout/Library/Sources/AppUIMain/Views/About/iOS/AboutContentView+iOS.swift b/Passepartout/Library/Sources/AppUIMain/Views/About/iOS/AboutContentView+iOS.swift index 80a4a256..9a023846 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/About/iOS/AboutContentView+iOS.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/About/iOS/AboutContentView+iOS.swift @@ -65,18 +65,17 @@ private extension AboutContentView { List { PreferencesGroup(profileManager: profileManager) Group { + linkContent(.version) linkContent(.links) linkContent(.credits) if !isRestricted { linkContent(.donate) } } - .themeSection(header: Strings.Views.About.Sections.resources) + .themeSection(header: Strings.Views.About.title) Section { linkContent(.purchased) linkContent(.diagnostics) - Text(Strings.Global.Nouns.version) - .themeTrailingValue(BundleConfiguration.mainVersionString) } } .navigationTitle(Strings.Global.Nouns.settings) diff --git a/Passepartout/Library/Sources/AppUIMain/Views/About/macOS/AboutContentView+macOS.swift b/Passepartout/Library/Sources/AppUIMain/Views/About/macOS/AboutContentView+macOS.swift index a21983b6..56570ff4 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/About/macOS/AboutContentView+macOS.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/About/macOS/AboutContentView+macOS.swift @@ -31,6 +31,9 @@ import SwiftUI struct AboutContentView: View where LinkContent: View, AboutDestination: View, LogDestination: View { + @EnvironmentObject + private var theme: Theme + @Environment(\.dismiss) private var dismiss @@ -66,8 +69,9 @@ struct AboutContentView: View whe } } } + .background(navigationRoute == .version ? theme.primaryColor : nil) .onLoad { - navigationRoute = .links + navigationRoute = .version } } } @@ -76,6 +80,7 @@ private extension AboutContentView { var listView: some View { List(selection: $navigationRoute) { Section { + linkContent(.version) linkContent(.links) linkContent(.credits) if !isRestricted { diff --git a/Passepartout/Library/Sources/AppUITV/Views/Profile/ProfileView.swift b/Passepartout/Library/Sources/AppUITV/Views/Profile/ProfileView.swift index 5e53b452..afd4b34b 100644 --- a/Passepartout/Library/Sources/AppUITV/Views/Profile/ProfileView.swift +++ b/Passepartout/Library/Sources/AppUITV/Views/Profile/ProfileView.swift @@ -80,7 +80,7 @@ struct ProfileView: View, Routable, TunnelInstallationProviding { } } .ignoresSafeArea(edges: .horizontal) - .background(theme.primaryColor.opacity(0.6).gradient) + .background(theme.primaryColor) .themeAnimation(on: showsSidePanel, category: .profiles) .defaultFocus($focusedField, .switchProfile) .onChange(of: tunnel.status, onTunnelStatus) diff --git a/Passepartout/Library/Sources/AppUITV/Views/Settings/SettingsView.swift b/Passepartout/Library/Sources/AppUITV/Views/Settings/SettingsView.swift index 3f0bff54..1d14d80f 100644 --- a/Passepartout/Library/Sources/AppUITV/Views/Settings/SettingsView.swift +++ b/Passepartout/Library/Sources/AppUITV/Views/Settings/SettingsView.swift @@ -37,17 +37,17 @@ enum Detail { case other case purchased + + case version } struct SettingsView: View { + + @EnvironmentObject + private var theme: Theme + let tunnel: ExtendedTunnel - @Namespace - private var masterScope - - @Namespace - private var detailScope - @FocusState private var focus: Detail? @@ -63,6 +63,7 @@ struct SettingsView: View { DetailView(detail: detail) .frame(maxWidth: .infinity) } + .background(theme.primaryColor) .onChange(of: focus) { guard focus != nil else { return @@ -84,12 +85,18 @@ private extension SettingsView { var creditsSection: some View { Group { + Button { + } label: { + Text(Strings.Global.Nouns.version) + .themeTrailingValue(BundleConfiguration.mainVersionString) + } + .focused($focus, equals: .version) Button(Strings.Views.About.Credits.title) {} .focused($focus, equals: .credits) Button(Strings.Views.Donate.title) {} .focused($focus, equals: .donate) } - .themeSection(header: Strings.Unlocalized.appName) + .themeSection(header: Strings.Views.About.title) } var diagnosticsSection: some View { @@ -105,10 +112,8 @@ private extension SettingsView { Group { Button(Strings.Views.Purchased.title) {} .focused($focus, equals: .purchased) - Text(Strings.Global.Nouns.version) - .themeTrailingValue(BundleConfiguration.mainVersionString) } - .themeSection(header: Strings.Views.About.title) + .themeSection() } } @@ -128,6 +133,9 @@ private struct DetailView: View { PurchasedView() .themeList() + case .version: + VersionView() + default: VStack {} } diff --git a/Passepartout/Library/Sources/CommonUtils/Views/GenericCreditsView.swift b/Passepartout/Library/Sources/CommonUtils/Views/GenericCreditsView.swift index e5dc4678..9265be11 100644 --- a/Passepartout/Library/Sources/CommonUtils/Views/GenericCreditsView.swift +++ b/Passepartout/Library/Sources/CommonUtils/Views/GenericCreditsView.swift @@ -40,8 +40,6 @@ public struct Credits: Decodable { public let message: String } - public let author: String - public let licenses: [License] public let notices: [Notice] diff --git a/Passepartout/Library/Sources/UILibrary/L10n/Strings+Unlocalized.swift b/Passepartout/Library/Sources/UILibrary/L10n/Strings+Unlocalized.swift index d65d5060..bd704b23 100644 --- a/Passepartout/Library/Sources/UILibrary/L10n/Strings+Unlocalized.swift +++ b/Passepartout/Library/Sources/UILibrary/L10n/Strings+Unlocalized.swift @@ -98,6 +98,8 @@ extension Strings { public static let appleTV = "Apple TV" + public static let authorName = "Davide De Rosa (keeshux)" + public static let ca = "CA" public static let dns = "DNS" diff --git a/Passepartout/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift b/Passepartout/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift index 34643a38..7d12e8b2 100644 --- a/Passepartout/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift +++ b/Passepartout/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift @@ -568,10 +568,6 @@ public enum Strings { public static let web = Strings.tr("Localizable", "views.about.links.sections.web", fallback: "Web") } } - public enum Sections { - /// Resources - public static let resources = Strings.tr("Localizable", "views.about.sections.resources", fallback: "Resources") - } } public enum App { public enum Errors { @@ -877,6 +873,14 @@ public enum Strings { } } } + public enum Version { + /// %@ is a project maintained by %@. + /// + /// Source code is publicly available on GitHub under the GPLv3, you can find links in the home page. + public static func extra(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "views.version.extra", String(describing: p1), String(describing: p2), fallback: "%@ is a project maintained by %@.\n\nSource code is publicly available on GitHub under the GPLv3, you can find links in the home page.") + } + } public enum Vpn { /// No servers public static let noServers = Strings.tr("Localizable", "views.vpn.no_servers", fallback: "No servers") diff --git a/Passepartout/Library/Sources/UILibrary/Resources/Credits.json b/Passepartout/Library/Sources/UILibrary/Resources/Credits.json index 9ac40045..c6c68410 100644 --- a/Passepartout/Library/Sources/UILibrary/Resources/Credits.json +++ b/Passepartout/Library/Sources/UILibrary/Resources/Credits.json @@ -1,5 +1,4 @@ { - "author": "Davide De Rosa", "licenses": [ { "name": "GenericJSON", diff --git a/Passepartout/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings b/Passepartout/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings index 3f037a1e..6be92a23 100644 --- a/Passepartout/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings +++ b/Passepartout/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings @@ -26,7 +26,6 @@ // MARK: Views "views.about.title" = "About"; -"views.about.sections.resources" = "Resources"; "views.about.links.title" = "Links"; "views.about.links.sections.support" = "Support"; "views.about.links.sections.web" = "Web"; @@ -129,6 +128,8 @@ "views.ui.purchase_required.purchase.help" = "Purchase required"; "views.ui.purchase_required.restricted.help" = "Feature is restricted"; +"views.version.extra" = "%@ is a project maintained by %@.\n\nSource code is publicly available on GitHub under the GPLv3, you can find links in the home page."; + "views.vpn.category.any" = "All categories"; "views.vpn.preset" = "Preset"; "views.vpn.no_servers" = "No servers"; diff --git a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift index ab5c9154..a5fca21d 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift @@ -51,7 +51,11 @@ extension View { } public func themeLockScreen() -> some View { - modifier(ThemeLockScreenModifier(lockedContent: LogoView.init)) + modifier(ThemeLockScreenModifier { + FullScreenView { + LogoImage() + } + }) } } diff --git a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+tvOS.swift b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+tvOS.swift index 5743dffe..057e35fd 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+tvOS.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+tvOS.swift @@ -31,6 +31,12 @@ extension Theme { public convenience init() { self.init(dummy: Void()) } + +// public var primaryGradient: AnyGradient { +// primaryColor +// .opacity(0.6) +// .gradient +// } } // MARK: - Shortcuts diff --git a/Passepartout/Library/Sources/UILibrary/Views/About/VersionView.swift b/Passepartout/Library/Sources/UILibrary/Views/About/VersionView.swift new file mode 100644 index 00000000..728b543e --- /dev/null +++ b/Passepartout/Library/Sources/UILibrary/Views/About/VersionView.swift @@ -0,0 +1,90 @@ +// +// VersionView.swift +// Passepartout +// +// Created by Davide De Rosa on 11/27/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 . +// + +import CommonLibrary +import PassepartoutKit +import SwiftUI + +public struct VersionView: View where Icon: View { + + @EnvironmentObject + private var theme: Theme + + private let icon: () -> Icon + + public init(icon: @escaping () -> Icon) { + self.icon = icon + } + + public var body: some View { + ScrollView { + icon() + .padding(.top) + Spacer() + Text(title) + .font(.largeTitle) + Spacer() + Text(subtitle) + VStack { + Text(message) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .multilineTextAlignment(.center) + .padding() + } + } + .background(theme.primaryColor) + .foregroundStyle(.white) + } +} + +extension VersionView where Icon == LogoImage { + public init() { + icon = { + LogoImage() + } + } +} + +private extension VersionView { + var title: String { + Strings.Unlocalized.appName + } + + var subtitle: String { + BundleConfiguration.mainVersionString + } + + var message: String { + Strings.Views.Version.extra(Strings.Unlocalized.appName, Strings.Unlocalized.authorName) + } +} + +#Preview { + VersionView { + ThemeImage(.cloudOn) + .font(.largeTitle) + } + .withMockEnvironment() +} diff --git a/Passepartout/Library/Sources/UILibrary/Views/UI/LogoView.swift b/Passepartout/Library/Sources/UILibrary/Views/UI/FullScreenView.swift similarity index 73% rename from Passepartout/Library/Sources/UILibrary/Views/UI/LogoView.swift rename to Passepartout/Library/Sources/UILibrary/Views/UI/FullScreenView.swift index c027e5db..75b93619 100644 --- a/Passepartout/Library/Sources/UILibrary/Views/UI/LogoView.swift +++ b/Passepartout/Library/Sources/UILibrary/Views/UI/FullScreenView.swift @@ -1,5 +1,5 @@ // -// LogoView.swift +// FullScreenView.swift // Passepartout // // Created by Davide De Rosa on 3/20/23. @@ -25,22 +25,32 @@ import SwiftUI -struct LogoView: View { +public struct FullScreenView: View where Icon: View { @EnvironmentObject private var theme: Theme - var body: some View { + @ViewBuilder + private let icon: () -> Icon + + public init(icon: @escaping () -> Icon) { + self.icon = icon + } + + public var body: some View { ZStack { theme.primaryColor .ignoresSafeArea() - Image(theme.logoImage) + icon() } .ignoresSafeArea() } } #Preview { - LogoView() - .withMockEnvironment() + FullScreenView { + ThemeImage(.cloudOn) + .foregroundStyle(.white) + } + .withMockEnvironment() } diff --git a/Passepartout/Library/Sources/UILibrary/Views/UI/LogoImage.swift b/Passepartout/Library/Sources/UILibrary/Views/UI/LogoImage.swift new file mode 100644 index 00000000..908cad26 --- /dev/null +++ b/Passepartout/Library/Sources/UILibrary/Views/UI/LogoImage.swift @@ -0,0 +1,39 @@ +// +// LogoImage.swift +// Passepartout +// +// Created by Davide De Rosa on 11/27/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 . +// + +import SwiftUI + +public struct LogoImage: View { + + @EnvironmentObject + private var theme: Theme + + public init() { + } + + public var body: some View { + Image(theme.logoImage) + } +}