Prepare for macOS settings (#629)
This commit is contained in:
parent
c0234b07a8
commit
5deb8ec763
|
@ -33,21 +33,8 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
||||||
#else
|
#else
|
||||||
|
|
||||||
import AppKit
|
import AppKit
|
||||||
import CommonLibrary
|
|
||||||
import PassepartoutKit
|
|
||||||
|
|
||||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
||||||
NSWindow.allowsAutomaticWindowTabbing = false
|
|
||||||
|
|
||||||
// XXX: hack to only retain "Edit" menu
|
|
||||||
NSApp.mainMenu?.items = NSApp.mainMenu?.items.filter {
|
|
||||||
[BundleConfiguration.main.displayName, "Edit"].contains($0.title)
|
|
||||||
} ?? []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -53,6 +53,11 @@ struct PassepartoutApp: App {
|
||||||
#else
|
#else
|
||||||
Window(appName, id: appName, content: content)
|
Window(appName, id: appName, content: content)
|
||||||
.defaultSize(width: 600.0, height: 400.0)
|
.defaultSize(width: 600.0, height: 400.0)
|
||||||
|
|
||||||
|
// Settings {
|
||||||
|
// SettingsView()
|
||||||
|
// .frame(minWidth: 300, minHeight: 100)
|
||||||
|
// }
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,6 +139,8 @@ internal enum Strings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Global {
|
internal enum Global {
|
||||||
|
/// About
|
||||||
|
internal static let about = Strings.tr("Localizable", "global.about", fallback: "About")
|
||||||
/// Account
|
/// Account
|
||||||
internal static let account = Strings.tr("Localizable", "global.account", fallback: "Account")
|
internal static let account = Strings.tr("Localizable", "global.account", fallback: "Account")
|
||||||
/// Address
|
/// Address
|
||||||
|
@ -397,46 +399,44 @@ internal enum Strings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Views {
|
internal enum Views {
|
||||||
internal enum Advanced {
|
internal enum About {
|
||||||
/// Lock app access
|
/// About
|
||||||
internal static let lockInBackground = Strings.tr("Localizable", "views.advanced.lock_in_background", fallback: "Lock app access")
|
internal static let title = Strings.tr("Localizable", "views.about.title", fallback: "About")
|
||||||
/// Advanced
|
|
||||||
internal static let title = Strings.tr("Localizable", "views.advanced.title", fallback: "Advanced")
|
|
||||||
internal enum Credits {
|
internal enum Credits {
|
||||||
/// Licenses
|
/// Licenses
|
||||||
internal static let licenses = Strings.tr("Localizable", "views.advanced.credits.licenses", fallback: "Licenses")
|
internal static let licenses = Strings.tr("Localizable", "views.about.credits.licenses", fallback: "Licenses")
|
||||||
/// Notices
|
/// Notices
|
||||||
internal static let notices = Strings.tr("Localizable", "views.advanced.credits.notices", fallback: "Notices")
|
internal static let notices = Strings.tr("Localizable", "views.about.credits.notices", fallback: "Notices")
|
||||||
/// Credits
|
/// Credits
|
||||||
internal static let title = Strings.tr("Localizable", "views.advanced.credits.title", fallback: "Credits")
|
internal static let title = Strings.tr("Localizable", "views.about.credits.title", fallback: "Credits")
|
||||||
/// Translations
|
/// Translations
|
||||||
internal static let translations = Strings.tr("Localizable", "views.advanced.credits.translations", fallback: "Translations")
|
internal static let translations = Strings.tr("Localizable", "views.about.credits.translations", fallback: "Translations")
|
||||||
}
|
}
|
||||||
internal enum Links {
|
internal enum Links {
|
||||||
/// Links
|
/// Links
|
||||||
internal static let title = Strings.tr("Localizable", "views.advanced.links.title", fallback: "Links")
|
internal static let title = Strings.tr("Localizable", "views.about.links.title", fallback: "Links")
|
||||||
internal enum Rows {
|
internal enum Rows {
|
||||||
/// Disclaimer
|
/// Disclaimer
|
||||||
internal static let disclaimer = Strings.tr("Localizable", "views.advanced.links.rows.disclaimer", fallback: "Disclaimer")
|
internal static let disclaimer = Strings.tr("Localizable", "views.about.links.rows.disclaimer", fallback: "Disclaimer")
|
||||||
/// Home page
|
/// Home page
|
||||||
internal static let homePage = Strings.tr("Localizable", "views.advanced.links.rows.home_page", fallback: "Home page")
|
internal static let homePage = Strings.tr("Localizable", "views.about.links.rows.home_page", fallback: "Home page")
|
||||||
/// Join community
|
/// Join community
|
||||||
internal static let joinCommunity = Strings.tr("Localizable", "views.advanced.links.rows.join_community", fallback: "Join community")
|
internal static let joinCommunity = Strings.tr("Localizable", "views.about.links.rows.join_community", fallback: "Join community")
|
||||||
/// Privacy policy
|
/// Privacy policy
|
||||||
internal static let privacyPolicy = Strings.tr("Localizable", "views.advanced.links.rows.privacy_policy", fallback: "Privacy policy")
|
internal static let privacyPolicy = Strings.tr("Localizable", "views.about.links.rows.privacy_policy", fallback: "Privacy policy")
|
||||||
/// Write a review
|
/// Write a review
|
||||||
internal static let writeReview = Strings.tr("Localizable", "views.advanced.links.rows.write_review", fallback: "Write a review")
|
internal static let writeReview = Strings.tr("Localizable", "views.about.links.rows.write_review", fallback: "Write a review")
|
||||||
}
|
}
|
||||||
internal enum Sections {
|
internal enum Sections {
|
||||||
/// Support
|
/// Support
|
||||||
internal static let support = Strings.tr("Localizable", "views.advanced.links.sections.support", fallback: "Support")
|
internal static let support = Strings.tr("Localizable", "views.about.links.sections.support", fallback: "Support")
|
||||||
/// Web
|
/// Web
|
||||||
internal static let web = Strings.tr("Localizable", "views.advanced.links.sections.web", fallback: "Web")
|
internal static let web = Strings.tr("Localizable", "views.about.links.sections.web", fallback: "Web")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Sections {
|
internal enum Sections {
|
||||||
/// Resources
|
/// Resources
|
||||||
internal static let resources = Strings.tr("Localizable", "views.advanced.sections.resources", fallback: "Resources")
|
internal static let resources = Strings.tr("Localizable", "views.about.sections.resources", fallback: "Resources")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Diagnostics {
|
internal enum Diagnostics {
|
||||||
|
@ -479,10 +479,6 @@ internal enum Strings {
|
||||||
/// Make a donation
|
/// Make a donation
|
||||||
internal static let title = Strings.tr("Localizable", "views.donate.title", fallback: "Make a donation")
|
internal static let title = Strings.tr("Localizable", "views.donate.title", fallback: "Make a donation")
|
||||||
}
|
}
|
||||||
internal enum Lockable {
|
|
||||||
/// Passepartout is locked
|
|
||||||
internal static let message = Strings.tr("Localizable", "views.lockable.message", fallback: "Passepartout is locked")
|
|
||||||
}
|
|
||||||
internal enum Profile {
|
internal enum Profile {
|
||||||
internal enum ModuleList {
|
internal enum ModuleList {
|
||||||
internal enum Section {
|
internal enum Section {
|
||||||
|
@ -543,6 +539,20 @@ internal enum Strings {
|
||||||
internal static let newProfile = Strings.tr("Localizable", "views.profiles.toolbar.new_profile", fallback: "New profile")
|
internal static let newProfile = Strings.tr("Localizable", "views.profiles.toolbar.new_profile", fallback: "New profile")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
internal enum Settings {
|
||||||
|
internal enum Rows {
|
||||||
|
/// Lock in background
|
||||||
|
internal static let lockInBackground = Strings.tr("Localizable", "views.settings.rows.lock_in_background", fallback: "Lock in background")
|
||||||
|
internal enum LockInBackground {
|
||||||
|
/// Passepartout is locked
|
||||||
|
internal static let message = Strings.tr("Localizable", "views.settings.rows.lock_in_background.message", fallback: "Passepartout is locked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal enum Sections {
|
||||||
|
/// Lock
|
||||||
|
internal static let lock = Strings.tr("Localizable", "views.settings.sections.lock", fallback: "Lock")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// MARK: Global
|
// MARK: Global
|
||||||
|
|
||||||
|
"global.about" = "About";
|
||||||
"global.account" = "Account";
|
"global.account" = "Account";
|
||||||
"global.address" = "Address";
|
"global.address" = "Address";
|
||||||
"global.addresses" = "Addresses";
|
"global.addresses" = "Addresses";
|
||||||
|
@ -119,23 +120,26 @@
|
||||||
"views.profile.rows.add_module" = "Add module";
|
"views.profile.rows.add_module" = "Add module";
|
||||||
"views.profile.module_list.section.footer" = "Drag modules to rearrange them, as their order determines priority.";
|
"views.profile.module_list.section.footer" = "Drag modules to rearrange them, as their order determines priority.";
|
||||||
|
|
||||||
"views.advanced.title" = "Advanced";
|
"views.settings.sections.lock" = "Lock";
|
||||||
"views.advanced.sections.resources" = "Resources";
|
"views.settings.rows.lock_in_background" = "Lock in background";
|
||||||
"views.advanced.lock_in_background" = "Lock app access";
|
"views.settings.rows.lock_in_background.message" = "Passepartout is locked";
|
||||||
|
|
||||||
"views.advanced.links.title" = "Links";
|
"views.about.title" = "About";
|
||||||
"views.advanced.links.sections.support" = "Support";
|
"views.about.sections.resources" = "Resources";
|
||||||
"views.advanced.links.sections.web" = "Web";
|
|
||||||
"views.advanced.links.rows.join_community" = "Join community";
|
|
||||||
"views.advanced.links.rows.write_review" = "Write a review";
|
|
||||||
"views.advanced.links.rows.home_page" = "Home page";
|
|
||||||
"views.advanced.links.rows.disclaimer" = "Disclaimer";
|
|
||||||
"views.advanced.links.rows.privacy_policy" = "Privacy policy";
|
|
||||||
|
|
||||||
"views.advanced.credits.title" = "Credits";
|
"views.about.links.title" = "Links";
|
||||||
"views.advanced.credits.licenses" = "Licenses";
|
"views.about.links.sections.support" = "Support";
|
||||||
"views.advanced.credits.notices" = "Notices";
|
"views.about.links.sections.web" = "Web";
|
||||||
"views.advanced.credits.translations" = "Translations";
|
"views.about.links.rows.join_community" = "Join community";
|
||||||
|
"views.about.links.rows.write_review" = "Write a review";
|
||||||
|
"views.about.links.rows.home_page" = "Home page";
|
||||||
|
"views.about.links.rows.disclaimer" = "Disclaimer";
|
||||||
|
"views.about.links.rows.privacy_policy" = "Privacy policy";
|
||||||
|
|
||||||
|
"views.about.credits.title" = "Credits";
|
||||||
|
"views.about.credits.licenses" = "Licenses";
|
||||||
|
"views.about.credits.notices" = "Notices";
|
||||||
|
"views.about.credits.translations" = "Translations";
|
||||||
|
|
||||||
"views.donate.title" = "Make a donation";
|
"views.donate.title" = "Make a donation";
|
||||||
|
|
||||||
|
@ -152,8 +156,6 @@
|
||||||
"views.diagnostics.report_issue.title" = "Report issue";
|
"views.diagnostics.report_issue.title" = "Report issue";
|
||||||
"views.diagnostics.alerts.report_issue.email" = "The device is not configured to send e-mails.";
|
"views.diagnostics.alerts.report_issue.email" = "The device is not configured to send e-mails.";
|
||||||
|
|
||||||
"views.lockable.message" = "Passepartout is locked";
|
|
||||||
|
|
||||||
// MARK: - Module views
|
// MARK: - Module views
|
||||||
|
|
||||||
"modules.dns.servers.add" = "Add address";
|
"modules.dns.servers.add" = "Add address";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// AdvancedRouterView.swift
|
// AboutRouterView.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 8/22/24.
|
// Created by Davide De Rosa on 8/22/24.
|
||||||
|
@ -28,7 +28,7 @@ import CommonLibrary
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct AdvancedRouterView: View {
|
struct AboutRouterView: View {
|
||||||
|
|
||||||
@Environment(\.dismiss)
|
@Environment(\.dismiss)
|
||||||
var dismiss
|
var dismiss
|
||||||
|
@ -43,7 +43,7 @@ struct AdvancedRouterView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AdvancedRouterView {
|
extension AboutRouterView {
|
||||||
enum NavigationRoute: Hashable {
|
enum NavigationRoute: Hashable {
|
||||||
case donate
|
case donate
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ extension AdvancedRouterView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
AdvancedRouterView(
|
AboutRouterView(
|
||||||
tunnel: .mock
|
tunnel: .mock
|
||||||
)
|
)
|
||||||
.environmentObject(Theme())
|
.environmentObject(Theme())
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// AdvancedView.swift
|
// AboutView.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 8/23/24.
|
// Created by Davide De Rosa on 8/23/24.
|
||||||
|
@ -28,27 +28,19 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
struct AdvancedView: View {
|
struct AboutView: View {
|
||||||
|
|
||||||
@AppStorage(AppPreference.locksInBackground.key)
|
|
||||||
private var locksInBackground = false
|
|
||||||
|
|
||||||
let identifiers: Constants.Identifiers
|
let identifiers: Constants.Identifiers
|
||||||
|
|
||||||
@Binding
|
@Binding
|
||||||
var navigationRoute: AdvancedRouterView.NavigationRoute?
|
var navigationRoute: AboutRouterView.NavigationRoute?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
listView
|
listView
|
||||||
.navigationTitle(Strings.Views.Advanced.title)
|
.navigationTitle(Strings.Views.About.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AdvancedView {
|
extension AboutView {
|
||||||
var lockInBackgroundToggle: some View {
|
|
||||||
Toggle(Strings.Views.Advanced.lockInBackground, isOn: $locksInBackground)
|
|
||||||
}
|
|
||||||
|
|
||||||
var donateLink: some View {
|
var donateLink: some View {
|
||||||
navLink(Strings.Views.Donate.title, to: .donate)
|
navLink(Strings.Views.Donate.title, to: .donate)
|
||||||
}
|
}
|
||||||
|
@ -58,22 +50,22 @@ extension AdvancedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
var linksLink: some View {
|
var linksLink: some View {
|
||||||
navLink(Strings.Views.Advanced.Links.title, to: .links)
|
navLink(Strings.Views.About.Links.title, to: .links)
|
||||||
}
|
}
|
||||||
|
|
||||||
var creditsLink: some View {
|
var creditsLink: some View {
|
||||||
navLink(Strings.Views.Advanced.Credits.title, to: .credits)
|
navLink(Strings.Views.About.Credits.title, to: .credits)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension AdvancedView {
|
private extension AboutView {
|
||||||
func navLink(_ title: String, to route: AdvancedRouterView.NavigationRoute) -> some View {
|
func navLink(_ title: String, to route: AboutRouterView.NavigationRoute) -> some View {
|
||||||
NavigationLink(title, value: route)
|
NavigationLink(title, value: route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
AdvancedView(
|
AboutView(
|
||||||
identifiers: Constants.shared.identifiers,
|
identifiers: Constants.shared.identifiers,
|
||||||
navigationRoute: .constant(nil)
|
navigationRoute: .constant(nil)
|
||||||
)
|
)
|
|
@ -30,15 +30,15 @@ struct CreditsView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GenericCreditsView(
|
GenericCreditsView(
|
||||||
credits: Self.credits,
|
credits: Self.credits,
|
||||||
licensesHeader: Strings.Views.Advanced.Credits.licenses,
|
licensesHeader: Strings.Views.About.Credits.licenses,
|
||||||
noticesHeader: Strings.Views.Advanced.Credits.notices,
|
noticesHeader: Strings.Views.About.Credits.notices,
|
||||||
translationsHeader: Strings.Views.Advanced.Credits.translations,
|
translationsHeader: Strings.Views.About.Credits.translations,
|
||||||
errorDescription: {
|
errorDescription: {
|
||||||
AppError($0)
|
AppError($0)
|
||||||
.localizedDescription
|
.localizedDescription
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.navigationTitle(Strings.Views.Advanced.Credits.title)
|
.navigationTitle(Strings.Views.About.Credits.title)
|
||||||
.themeForm()
|
.themeForm()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -32,7 +32,7 @@ struct LinksView: View {
|
||||||
supportSection
|
supportSection
|
||||||
webSection
|
webSection
|
||||||
}
|
}
|
||||||
.navigationTitle(Strings.Views.Advanced.Links.title)
|
.navigationTitle(Strings.Views.About.Links.title)
|
||||||
.themeForm()
|
.themeForm()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,21 +44,21 @@ private extension LinksView {
|
||||||
|
|
||||||
var supportSection: some View {
|
var supportSection: some View {
|
||||||
Section {
|
Section {
|
||||||
Link(Strings.Views.Advanced.Links.Rows.joinCommunity, destination: constants.websites.subreddit)
|
Link(Strings.Views.About.Links.Rows.joinCommunity, destination: constants.websites.subreddit)
|
||||||
Link(Strings.Views.Advanced.Links.Rows.writeReview, destination: constants.urlForReview)
|
Link(Strings.Views.About.Links.Rows.writeReview, destination: constants.urlForReview)
|
||||||
} header: {
|
} header: {
|
||||||
Text(Strings.Views.Advanced.Links.Sections.support)
|
Text(Strings.Views.About.Links.Sections.support)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var webSection: some View {
|
var webSection: some View {
|
||||||
Section {
|
Section {
|
||||||
Link(Strings.Views.Advanced.Links.Rows.homePage, destination: constants.websites.home)
|
Link(Strings.Views.About.Links.Rows.homePage, destination: constants.websites.home)
|
||||||
Link(Strings.Unlocalized.faq, destination: constants.websites.faq)
|
Link(Strings.Unlocalized.faq, destination: constants.websites.faq)
|
||||||
Link(Strings.Views.Advanced.Links.Rows.disclaimer, destination: constants.websites.disclaimer)
|
Link(Strings.Views.About.Links.Rows.disclaimer, destination: constants.websites.disclaimer)
|
||||||
Link(Strings.Views.Advanced.Links.Rows.privacyPolicy, destination: constants.websites.privacyPolicy)
|
Link(Strings.Views.About.Links.Rows.privacyPolicy, destination: constants.websites.privacyPolicy)
|
||||||
} header: {
|
} header: {
|
||||||
Text(Strings.Views.Advanced.Links.Sections.web)
|
Text(Strings.Views.About.Links.Sections.web)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// AdvancedRouterView+iOS.swift
|
// AboutRouterView+iOS.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 8/26/24.
|
// Created by Davide De Rosa on 8/26/24.
|
||||||
|
@ -28,10 +28,10 @@
|
||||||
import CommonLibrary
|
import CommonLibrary
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension AdvancedRouterView {
|
extension AboutRouterView {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
AdvancedView(
|
AboutView(
|
||||||
identifiers: Constants.shared.identifiers,
|
identifiers: Constants.shared.identifiers,
|
||||||
navigationRoute: $navigationRoute
|
navigationRoute: $navigationRoute
|
||||||
)
|
)
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// AdvancedView+iOS.swift
|
// AboutView+iOS.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 8/27/24.
|
// Created by Davide De Rosa on 8/27/24.
|
||||||
|
@ -27,21 +27,16 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension AdvancedView {
|
extension AboutView {
|
||||||
var listView: some View {
|
var listView: some View {
|
||||||
List {
|
List {
|
||||||
Section {
|
|
||||||
lockInBackgroundToggle
|
|
||||||
} header: {
|
|
||||||
Text(Strings.Global.settings)
|
|
||||||
}
|
|
||||||
Section {
|
Section {
|
||||||
// TODO: donations
|
// TODO: donations
|
||||||
// donateLink
|
// donateLink
|
||||||
linksLink
|
linksLink
|
||||||
creditsLink
|
creditsLink
|
||||||
} header: {
|
} header: {
|
||||||
Text(Strings.Views.Advanced.Sections.resources)
|
Text(Strings.Views.About.Sections.resources)
|
||||||
}
|
}
|
||||||
Section {
|
Section {
|
||||||
diagnosticsLink
|
diagnosticsLink
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// AdvancedRouterView+macOS.swift
|
// AboutRouterView+macOS.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 8/26/24.
|
// Created by Davide De Rosa on 8/26/24.
|
||||||
|
@ -28,10 +28,10 @@
|
||||||
import CommonLibrary
|
import CommonLibrary
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension AdvancedRouterView {
|
extension AboutRouterView {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationSplitView {
|
NavigationSplitView {
|
||||||
AdvancedView(
|
AboutView(
|
||||||
identifiers: Constants.shared.identifiers,
|
identifiers: Constants.shared.identifiers,
|
||||||
navigationRoute: $navigationRoute
|
navigationRoute: $navigationRoute
|
||||||
)
|
)
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// AdvancedView+macOS.swift
|
// AboutView+macOS.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 8/27/24.
|
// Created by Davide De Rosa on 8/27/24.
|
||||||
|
@ -27,14 +27,9 @@ import SwiftUI
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
|
||||||
extension AdvancedView {
|
extension AboutView {
|
||||||
var listView: some View {
|
var listView: some View {
|
||||||
List(selection: $navigationRoute) {
|
List(selection: $navigationRoute) {
|
||||||
Section {
|
|
||||||
lockInBackgroundToggle
|
|
||||||
} header: {
|
|
||||||
Text(Strings.Global.settings)
|
|
||||||
}
|
|
||||||
Section {
|
Section {
|
||||||
// TODO: donations
|
// TODO: donations
|
||||||
// donateLink
|
// donateLink
|
|
@ -71,11 +71,10 @@ private extension AppInlineCoordinator {
|
||||||
enum ModalRoute: String, Identifiable {
|
enum ModalRoute: String, Identifiable {
|
||||||
case settings
|
case settings
|
||||||
|
|
||||||
var id: [String] {
|
case about
|
||||||
switch self {
|
|
||||||
case .settings:
|
var id: String {
|
||||||
return ["settings"]
|
rawValue
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +102,9 @@ private extension AppInlineCoordinator {
|
||||||
onSettings: {
|
onSettings: {
|
||||||
modalRoute = .settings
|
modalRoute = .settings
|
||||||
},
|
},
|
||||||
|
onAbout: {
|
||||||
|
modalRoute = .about
|
||||||
|
},
|
||||||
onNewProfile: enterDetail
|
onNewProfile: enterDetail
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -127,7 +129,10 @@ private extension AppInlineCoordinator {
|
||||||
func modalDestination(for item: ModalRoute?) -> some View {
|
func modalDestination(for item: ModalRoute?) -> some View {
|
||||||
switch item {
|
switch item {
|
||||||
case .settings:
|
case .settings:
|
||||||
AdvancedRouterView(tunnel: tunnel)
|
SettingsView()
|
||||||
|
|
||||||
|
case .about:
|
||||||
|
AboutRouterView(tunnel: tunnel)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
|
|
|
@ -62,19 +62,15 @@ struct AppModalCoordinator: View {
|
||||||
// MARK: - Destinations
|
// MARK: - Destinations
|
||||||
|
|
||||||
extension AppModalCoordinator {
|
extension AppModalCoordinator {
|
||||||
enum ModalRoute: Identifiable {
|
enum ModalRoute: String, Identifiable {
|
||||||
case editProfile
|
case editProfile
|
||||||
|
|
||||||
case settings
|
case settings
|
||||||
|
|
||||||
var id: [String] {
|
case about
|
||||||
switch self {
|
|
||||||
case .editProfile:
|
|
||||||
return ["editProfile"]
|
|
||||||
|
|
||||||
case .settings:
|
var id: String {
|
||||||
return ["settings"]
|
rawValue
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +98,9 @@ extension AppModalCoordinator {
|
||||||
onSettings: {
|
onSettings: {
|
||||||
modalRoute = .settings
|
modalRoute = .settings
|
||||||
},
|
},
|
||||||
|
onAbout: {
|
||||||
|
modalRoute = .about
|
||||||
|
},
|
||||||
onNewProfile: enterDetail
|
onNewProfile: enterDetail
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -121,7 +120,10 @@ extension AppModalCoordinator {
|
||||||
}
|
}
|
||||||
|
|
||||||
case .settings:
|
case .settings:
|
||||||
AdvancedRouterView(tunnel: tunnel)
|
SettingsView()
|
||||||
|
|
||||||
|
case .about:
|
||||||
|
AboutRouterView(tunnel: tunnel)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
|
|
|
@ -45,18 +45,20 @@ struct AppToolbar: ToolbarContent {
|
||||||
|
|
||||||
let onSettings: () -> Void
|
let onSettings: () -> Void
|
||||||
|
|
||||||
|
let onAbout: () -> Void
|
||||||
|
|
||||||
let onNewProfile: (Profile) -> Void
|
let onNewProfile: (Profile) -> Void
|
||||||
|
|
||||||
var body: some ToolbarContent {
|
var body: some ToolbarContent {
|
||||||
if hsClass == .regular && vsClass == .regular {
|
if hsClass == .regular && vsClass == .regular {
|
||||||
ToolbarItemGroup {
|
ToolbarItemGroup {
|
||||||
addProfileMenu
|
addProfileMenu
|
||||||
settingsButton
|
aboutButton
|
||||||
layoutPicker
|
layoutPicker
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ToolbarItem(placement: .navigation) {
|
ToolbarItem(placement: .navigation) {
|
||||||
settingsButton
|
moreMenu
|
||||||
}
|
}
|
||||||
ToolbarItemGroup(placement: .primaryAction) {
|
ToolbarItemGroup(placement: .primaryAction) {
|
||||||
addProfileMenu
|
addProfileMenu
|
||||||
|
@ -75,9 +77,24 @@ private extension AppToolbar {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var moreMenu: some View {
|
||||||
|
Menu {
|
||||||
|
settingsButton
|
||||||
|
aboutButton
|
||||||
|
} label: {
|
||||||
|
ThemeImage(.moreDetails)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var settingsButton: some View {
|
var settingsButton: some View {
|
||||||
Button(action: onSettings) {
|
Button(action: onSettings) {
|
||||||
ThemeImage(.advanced)
|
ThemeImageLabel(Strings.Global.settings, .settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var aboutButton: some View {
|
||||||
|
Button(action: onAbout) {
|
||||||
|
ThemeImageLabel(Strings.Global.about, .info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +112,7 @@ private extension AppToolbar {
|
||||||
layout: .constant(.list),
|
layout: .constant(.list),
|
||||||
isImporting: .constant(false),
|
isImporting: .constant(false),
|
||||||
onSettings: {},
|
onSettings: {},
|
||||||
|
onAbout: {},
|
||||||
onNewProfile: { _ in}
|
onNewProfile: { _ in}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,7 +161,7 @@ private extension DiagnosticsView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func navLink(_ title: String, to value: AdvancedRouterView.NavigationRoute) -> some View {
|
func navLink(_ title: String, to value: AboutRouterView.NavigationRoute) -> some View {
|
||||||
NavigationLink(title, value: value)
|
NavigationLink(title, value: value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
//
|
||||||
|
// SettingsView.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 9/28/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 CommonLibrary
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct SettingsView: View {
|
||||||
|
|
||||||
|
@AppStorage(AppPreference.locksInBackground.key)
|
||||||
|
private var locksInBackground = false
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var path = NavigationPath()
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
Form {
|
||||||
|
Section {
|
||||||
|
lockInBackgroundToggle
|
||||||
|
} header: {
|
||||||
|
Text(Strings.Views.Settings.Sections.lock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.themeForm()
|
||||||
|
.navigationTitle(Strings.Global.settings)
|
||||||
|
#if os(iOS)
|
||||||
|
.themeNavigationDetail()
|
||||||
|
.themeNavigationStack(if: true, closable: true, path: $path)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension SettingsView {
|
||||||
|
var lockInBackgroundToggle: some View {
|
||||||
|
Toggle(Strings.Views.Settings.Rows.lockInBackground, isOn: $locksInBackground)
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,6 @@ import Foundation
|
||||||
extension Theme {
|
extension Theme {
|
||||||
public enum ImageName {
|
public enum ImageName {
|
||||||
case add
|
case add
|
||||||
case advanced
|
|
||||||
case close
|
case close
|
||||||
case contextDuplicate
|
case contextDuplicate
|
||||||
case contextRemove
|
case contextRemove
|
||||||
|
@ -47,6 +46,7 @@ extension Theme {
|
||||||
case profilesGrid
|
case profilesGrid
|
||||||
case profilesList
|
case profilesList
|
||||||
case remove
|
case remove
|
||||||
|
case settings
|
||||||
case share
|
case share
|
||||||
case show
|
case show
|
||||||
case sleeping
|
case sleeping
|
||||||
|
|
|
@ -102,6 +102,40 @@ struct ThemeItemModalModifier<Modal, T>: ViewModifier where Modal: View, T: Iden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ThemeNavigationStackModifier: ViewModifier {
|
||||||
|
|
||||||
|
@Environment(\.dismiss)
|
||||||
|
private var dismiss
|
||||||
|
|
||||||
|
let condition: Bool
|
||||||
|
|
||||||
|
let closable: Bool
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
var path: NavigationPath
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
if condition {
|
||||||
|
NavigationStack(path: $path) {
|
||||||
|
content
|
||||||
|
.toolbar {
|
||||||
|
if closable {
|
||||||
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
Button {
|
||||||
|
dismiss()
|
||||||
|
} label: {
|
||||||
|
ThemeImage(.close)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ThemePlainButtonModifier: ViewModifier {
|
struct ThemePlainButtonModifier: ViewModifier {
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
}
|
}
|
||||||
|
@ -228,7 +262,7 @@ struct ThemeLockScreenModifier: ViewModifier {
|
||||||
do {
|
do {
|
||||||
let isAuthorized = try await context.evaluatePolicy(
|
let isAuthorized = try await context.evaluatePolicy(
|
||||||
policy,
|
policy,
|
||||||
localizedReason: Strings.Views.Lockable.message
|
localizedReason: Strings.Views.Settings.Rows.LockInBackground.message
|
||||||
)
|
)
|
||||||
return isAuthorized
|
return isAuthorized
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -79,7 +79,6 @@ public final class Theme: ObservableObject {
|
||||||
var systemImage: (ImageName) -> String = {
|
var systemImage: (ImageName) -> String = {
|
||||||
switch $0 {
|
switch $0 {
|
||||||
case .add: return "plus"
|
case .add: return "plus"
|
||||||
case .advanced: return "gearshape"
|
|
||||||
case .close: return "xmark"
|
case .close: return "xmark"
|
||||||
case .contextDuplicate: return "plus.square.on.square"
|
case .contextDuplicate: return "plus.square.on.square"
|
||||||
case .contextRemove: return "trash"
|
case .contextRemove: return "trash"
|
||||||
|
@ -98,6 +97,7 @@ public final class Theme: ObservableObject {
|
||||||
case .profilesGrid: return "square.grid.2x2"
|
case .profilesGrid: return "square.grid.2x2"
|
||||||
case .profilesList: return "rectangle.grid.1x2"
|
case .profilesList: return "rectangle.grid.1x2"
|
||||||
case .remove: return "minus"
|
case .remove: return "minus"
|
||||||
|
case .settings: return "gearshape"
|
||||||
case .share: return "square.and.arrow.up"
|
case .share: return "square.and.arrow.up"
|
||||||
case .show: return "eye"
|
case .show: return "eye"
|
||||||
case .sleeping: return "powersleep"
|
case .sleeping: return "powersleep"
|
||||||
|
@ -160,15 +160,8 @@ extension View {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
public func themeNavigationStack(if condition: Bool, closable: Bool = false, path: Binding<NavigationPath>) -> some View {
|
||||||
public func themeNavigationStack(if condition: Bool, path: Binding<NavigationPath>) -> some View {
|
modifier(ThemeNavigationStackModifier(condition: condition, closable: closable, path: path))
|
||||||
if condition {
|
|
||||||
NavigationStack(path: path) {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func themePlainButton(action: @escaping () -> Void) -> some View {
|
public func themePlainButton(action: @escaping () -> Void) -> some View {
|
||||||
|
|
Loading…
Reference in New Issue