Option to lock app when entering background (#270)
This commit is contained in:
parent
7346bfc65c
commit
325e10845d
|
@ -27,6 +27,8 @@
|
||||||
0E0C0729236087A100155AAC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E0C072B236087A100155AAC /* InfoPlist.strings */; };
|
0E0C0729236087A100155AAC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E0C072B236087A100155AAC /* InfoPlist.strings */; };
|
||||||
0E0F4C5A29C761850022E884 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C5929C761850022E884 /* SceneDelegate.swift */; };
|
0E0F4C5A29C761850022E884 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C5929C761850022E884 /* SceneDelegate.swift */; };
|
||||||
0E0F4C5C29C76B790022E884 /* SceneDelegate+Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */; };
|
0E0F4C5C29C76B790022E884 /* SceneDelegate+Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */; };
|
||||||
|
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6329C84B5A0022E884 /* LockableView.swift */; };
|
||||||
|
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6529C84CF60022E884 /* LogoView.swift */; };
|
||||||
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12BC8E27F62C8500B2F912 /* Validators.swift */; };
|
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12BC8E27F62C8500B2F912 /* Validators.swift */; };
|
||||||
0E1B5F5C29C506AD00FE7D18 /* ProfileView+Diagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */; };
|
0E1B5F5C29C506AD00FE7D18 /* ProfileView+Diagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */; };
|
||||||
0E1F5628287F0ECB00F8ADD7 /* ProviderProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */; };
|
0E1F5628287F0ECB00F8ADD7 /* ProviderProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */; };
|
||||||
|
@ -308,6 +310,8 @@
|
||||||
0E0C072C236087C800155AAC /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
0E0C072C236087C800155AAC /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
0E0F4C5929C761850022E884 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
0E0F4C5929C761850022E884 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||||
0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SceneDelegate+Shortcuts.swift"; sourceTree = "<group>"; };
|
0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SceneDelegate+Shortcuts.swift"; sourceTree = "<group>"; };
|
||||||
|
0E0F4C6329C84B5A0022E884 /* LockableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockableView.swift; sourceTree = "<group>"; };
|
||||||
|
0E0F4C6529C84CF60022E884 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = "<group>"; };
|
||||||
0E12BC8E27F62C8500B2F912 /* Validators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = "<group>"; };
|
0E12BC8E27F62C8500B2F912 /* Validators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = "<group>"; };
|
||||||
0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Diagnostics.swift"; sourceTree = "<group>"; };
|
0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Diagnostics.swift"; sourceTree = "<group>"; };
|
||||||
0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
|
@ -577,9 +581,9 @@
|
||||||
0E29385A285A749E002A6E0E /* Context */ = {
|
0E29385A285A749E002A6E0E /* Context */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0E293850285A70AC002A6E0E /* AppPreference.swift */,
|
|
||||||
0E021D9B284E68580077EF5D /* AppContext.swift */,
|
0E021D9B284E68580077EF5D /* AppContext.swift */,
|
||||||
0E293856285A73BC002A6E0E /* AppContext+Shared.swift */,
|
0E293856285A73BC002A6E0E /* AppContext+Shared.swift */,
|
||||||
|
0E293850285A70AC002A6E0E /* AppPreference.swift */,
|
||||||
);
|
);
|
||||||
path = Context;
|
path = Context;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -598,6 +602,7 @@
|
||||||
0EF0FAF827DD212C007EB181 /* IntentActivity.swift */,
|
0EF0FAF827DD212C007EB181 /* IntentActivity.swift */,
|
||||||
0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */,
|
0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */,
|
||||||
0ED89C1627DE0E05008B36D6 /* IntentEditView.swift */,
|
0ED89C1627DE0E05008B36D6 /* IntentEditView.swift */,
|
||||||
|
0E0F4C6329C84B5A0022E884 /* LockableView.swift */,
|
||||||
0E5324A827D2AC55002565C3 /* LongContentView.swift */,
|
0E5324A827D2AC55002565C3 /* LongContentView.swift */,
|
||||||
0EBC075427EBC83800208AD9 /* MailComposerView.swift */,
|
0EBC075427EBC83800208AD9 /* MailComposerView.swift */,
|
||||||
0ED30DCB27EA197C0057D8A3 /* RevealingSecureField.swift */,
|
0ED30DCB27EA197C0057D8A3 /* RevealingSecureField.swift */,
|
||||||
|
@ -648,6 +653,7 @@
|
||||||
0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */,
|
0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */,
|
||||||
0E5349C727C176D100C71BB3 /* EndpointView+WireGuard.swift */,
|
0E5349C727C176D100C71BB3 /* EndpointView+WireGuard.swift */,
|
||||||
0EB90CC029C25BBD00E64628 /* InteractiveConnectionView.swift */,
|
0EB90CC029C25BBD00E64628 /* InteractiveConnectionView.swift */,
|
||||||
|
0E0F4C6529C84CF60022E884 /* LogoView.swift */,
|
||||||
0E0BD27227B2EA2C00583AC5 /* MainView.swift */,
|
0E0BD27227B2EA2C00583AC5 /* MainView.swift */,
|
||||||
0E71ACE827C1055200F85C4B /* NetworkSettingsView.swift */,
|
0E71ACE827C1055200F85C4B /* NetworkSettingsView.swift */,
|
||||||
0EB34BC927C6A70200B126DA /* OnDemandView.swift */,
|
0EB34BC927C6A70200B126DA /* OnDemandView.swift */,
|
||||||
|
@ -1447,6 +1453,7 @@
|
||||||
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */,
|
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */,
|
||||||
0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */,
|
0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */,
|
||||||
0E5468002867AC9A00F74D1C /* MacUtils.swift in Sources */,
|
0E5468002867AC9A00F74D1C /* MacUtils.swift in Sources */,
|
||||||
|
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */,
|
||||||
0E96D3052872010A005EFBCF /* DefaultLightVPNManager.swift in Sources */,
|
0E96D3052872010A005EFBCF /* DefaultLightVPNManager.swift in Sources */,
|
||||||
0EBE880F281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift in Sources */,
|
0EBE880F281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift in Sources */,
|
||||||
0ED30DCF27EA1EF80057D8A3 /* PaywallView+Beta.swift in Sources */,
|
0ED30DCF27EA1EF80057D8A3 /* PaywallView+Beta.swift in Sources */,
|
||||||
|
@ -1491,6 +1498,7 @@
|
||||||
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
|
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
|
||||||
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */,
|
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */,
|
||||||
0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */,
|
0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */,
|
||||||
|
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */,
|
||||||
0E039279281890B100827C10 /* AddHostView.swift in Sources */,
|
0E039279281890B100827C10 /* AddHostView.swift in Sources */,
|
||||||
0E5467F72867A57000F74D1C /* MacBridge.swift in Sources */,
|
0E5467F72867A57000F74D1C /* MacBridge.swift in Sources */,
|
||||||
0E9ED48127FD9BAE003B2316 /* CopySavingButton.swift in Sources */,
|
0E9ED48127FD9BAE003B2316 /* CopySavingButton.swift in Sources */,
|
||||||
|
|
|
@ -438,6 +438,17 @@ extension View {
|
||||||
Text(L10n.Global.Strings.save)
|
Text(L10n.Global.Strings.save)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func themeSecureField(_ placeholder: String, text: Binding<String>, contentType: UITextContentType = .password) -> some View {
|
||||||
|
RevealingSecureField(placeholder, text: text) {
|
||||||
|
themeConceilImage.asSystemImage
|
||||||
|
.themeAccentForegroundStyle()
|
||||||
|
} revealImage: {
|
||||||
|
themeRevealImage.asSystemImage
|
||||||
|
.themeAccentForegroundStyle()
|
||||||
|
}.textContentType(contentType)
|
||||||
|
.themeRawTextStyle()
|
||||||
|
}
|
||||||
|
|
||||||
func themeTextPicker<T: Hashable>(_ title: String, selection: Binding<T>, values: [T], description: @escaping (T) -> String) -> some View {
|
func themeTextPicker<T: Hashable>(_ title: String, selection: Binding<T>, values: [T], description: @escaping (T) -> String) -> some View {
|
||||||
StyledPicker(title: title, selection: selection, values: values) {
|
StyledPicker(title: title, selection: selection, values: values) {
|
||||||
Text(description($0))
|
Text(description($0))
|
||||||
|
|
|
@ -33,6 +33,8 @@ enum AppPreference: String, KeyStoreDomainLocation {
|
||||||
|
|
||||||
case didHandleSubreddit
|
case didHandleSubreddit
|
||||||
|
|
||||||
|
case locksInBackground
|
||||||
|
|
||||||
var domain: String {
|
var domain: String {
|
||||||
"Passepartout.App"
|
"Passepartout.App"
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,16 +24,23 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import LocalAuthentication
|
||||||
import PassepartoutLibrary
|
import PassepartoutLibrary
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct PassepartoutApp: App {
|
struct PassepartoutApp: App {
|
||||||
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
||||||
|
|
||||||
|
@AppStorage(AppPreference.locksInBackground.rawValue) private var locksInBackground = false
|
||||||
|
|
||||||
@SceneBuilder var body: some Scene {
|
@SceneBuilder var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
MainView()
|
LockableView(
|
||||||
.withoutTitleBar()
|
reason: L10n.Global.Messages.unlockApp,
|
||||||
|
locksInBackground: $locksInBackground,
|
||||||
|
content: MainView.init,
|
||||||
|
lockedContent: LogoView.init
|
||||||
|
).withoutTitleBar()
|
||||||
.onIntentActivity(IntentDispatcher.connectVPN)
|
.onIntentActivity(IntentDispatcher.connectVPN)
|
||||||
.onIntentActivity(IntentDispatcher.disableVPN)
|
.onIntentActivity(IntentDispatcher.disableVPN)
|
||||||
.onIntentActivity(IntentDispatcher.enableVPN)
|
.onIntentActivity(IntentDispatcher.enableVPN)
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
//
|
||||||
|
// LockableView.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 3/20/23.
|
||||||
|
// Copyright (c) 2023 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 SwiftUI
|
||||||
|
import LocalAuthentication
|
||||||
|
|
||||||
|
struct LockableView<Content: View, LockedContent: View>: View {
|
||||||
|
let reason: String
|
||||||
|
|
||||||
|
@Binding var locksInBackground: Bool
|
||||||
|
|
||||||
|
let content: () -> Content
|
||||||
|
|
||||||
|
let lockedContent: () -> LockedContent
|
||||||
|
|
||||||
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
|
|
||||||
|
@State private var didAppear = false
|
||||||
|
|
||||||
|
@State private var isLocked = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if !isLocked {
|
||||||
|
content()
|
||||||
|
} else {
|
||||||
|
lockedContent()
|
||||||
|
}
|
||||||
|
}.onChange(of: scenePhase, perform: onScenePhase)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onScenePhase(_ scenePhase: ScenePhase) {
|
||||||
|
switch scenePhase {
|
||||||
|
case .active:
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
break
|
||||||
|
#else
|
||||||
|
if !didAppear {
|
||||||
|
didAppear = true
|
||||||
|
if locksInBackground {
|
||||||
|
isLocked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unlockIfNeeded()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case .inactive:
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
break
|
||||||
|
#else
|
||||||
|
lockIfNeeded()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lockIfNeeded() {
|
||||||
|
guard locksInBackground else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isLocked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockIfNeeded() {
|
||||||
|
guard isLocked else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let context = LAContext()
|
||||||
|
var error: NSError?
|
||||||
|
guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
let isAuthorized = try await context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason)
|
||||||
|
isLocked = !isAuthorized
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,6 +59,7 @@ struct AccountView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
Section {
|
Section {
|
||||||
|
// FIXME: l10n
|
||||||
themeTextPicker(L10n.Endpoint.Advanced.Openvpn.Items.Digest.caption, selection: $liveAccount.authenticationMethod ?? .persistent, values: [
|
themeTextPicker(L10n.Endpoint.Advanced.Openvpn.Items.Digest.caption, selection: $liveAccount.authenticationMethod ?? .persistent, values: [
|
||||||
.persistent,
|
.persistent,
|
||||||
.interactive
|
.interactive
|
||||||
|
@ -77,27 +78,13 @@ struct AccountView: View {
|
||||||
if liveAccount.authenticationMethod == .interactive {
|
if liveAccount.authenticationMethod == .interactive {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
} else {
|
} else {
|
||||||
RevealingSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password) {
|
themeSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password)
|
||||||
themeConceilImage.asSystemImage
|
|
||||||
.themeAccentForegroundStyle()
|
|
||||||
} revealImage: {
|
|
||||||
themeRevealImage.asSystemImage
|
|
||||||
.themeAccentForegroundStyle()
|
|
||||||
}.textContentType(.password)
|
|
||||||
.themeRawTextStyle()
|
|
||||||
.withLeadingText(L10n.Account.Items.Password.caption)
|
.withLeadingText(L10n.Account.Items.Password.caption)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: interactive, scan QR code
|
// TODO: interactive, scan QR code
|
||||||
case .totp:
|
case .totp:
|
||||||
RevealingSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password) {
|
themeSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password, contentType: .oneTimeCode)
|
||||||
themeConceilImage.asSystemImage
|
|
||||||
.themeAccentForegroundStyle()
|
|
||||||
} revealImage: {
|
|
||||||
themeRevealImage.asSystemImage
|
|
||||||
.themeAccentForegroundStyle()
|
|
||||||
}.textContentType(.oneTimeCode)
|
|
||||||
.themeRawTextStyle()
|
|
||||||
.withLeadingText(L10n.Account.Items.Seed.caption)
|
.withLeadingText(L10n.Account.Items.Seed.caption)
|
||||||
}
|
}
|
||||||
} footer: {
|
} footer: {
|
||||||
|
|
|
@ -50,14 +50,7 @@ struct InteractiveConnectionView: View {
|
||||||
.withLeadingText(L10n.Account.Items.Username.caption)
|
.withLeadingText(L10n.Account.Items.Username.caption)
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
|
|
||||||
RevealingSecureField(L10n.Account.Items.Password.placeholder, text: $password) {
|
themeSecureField(L10n.Account.Items.Password.placeholder, text: $password)
|
||||||
themeConceilImage.asSystemImage
|
|
||||||
.themeAccentForegroundStyle()
|
|
||||||
} revealImage: {
|
|
||||||
themeRevealImage.asSystemImage
|
|
||||||
.themeAccentForegroundStyle()
|
|
||||||
}.textContentType(.password)
|
|
||||||
.themeRawTextStyle()
|
|
||||||
.withLeadingText(L10n.Account.Items.Password.caption)
|
.withLeadingText(L10n.Account.Items.Password.caption)
|
||||||
} header: {
|
} header: {
|
||||||
Text(L10n.Account.title)
|
Text(L10n.Account.title)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// LogoView.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 3/20/23.
|
||||||
|
// Copyright (c) 2023 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 SwiftUI
|
||||||
|
|
||||||
|
struct LogoView: View {
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
themePrimaryBackground
|
||||||
|
Image(themeAssetsLogoImage)
|
||||||
|
}.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,11 +33,7 @@ struct SettingsView: View {
|
||||||
|
|
||||||
@Environment(\.presentationMode) private var presentationMode
|
@Environment(\.presentationMode) private var presentationMode
|
||||||
|
|
||||||
// private var isTestBuild: Bool {
|
@AppStorage(AppPreference.locksInBackground.rawValue) private var locksInBackground = false
|
||||||
// Constants.App.isBeta || Constants.InApp.appType == .beta
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private let appName = Unlocalized.appName
|
|
||||||
|
|
||||||
private let versionString = Constants.Global.appVersionString
|
private let versionString = Constants.Global.appVersionString
|
||||||
|
|
||||||
|
@ -48,6 +44,7 @@ struct SettingsView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
|
preferencesSection
|
||||||
aboutSection
|
aboutSection
|
||||||
}.toolbar {
|
}.toolbar {
|
||||||
themeCloseItem(presentationMode: presentationMode)
|
themeCloseItem(presentationMode: presentationMode)
|
||||||
|
@ -55,6 +52,12 @@ struct SettingsView: View {
|
||||||
.navigationTitle(L10n.Settings.title)
|
.navigationTitle(L10n.Settings.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var preferencesSection: some View {
|
||||||
|
Section {
|
||||||
|
Toggle(L10n.Settings.Items.LocksInBackground.caption, isOn: $locksInBackground)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var aboutSection: some View {
|
private var aboutSection: some View {
|
||||||
Section {
|
Section {
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
|
|
|
@ -446,6 +446,8 @@ internal enum L10n {
|
||||||
internal static let emailNotConfigured = L10n.tr("Localizable", "global.messages.email_not_configured", fallback: "No e-mail account is configured.")
|
internal static let emailNotConfigured = L10n.tr("Localizable", "global.messages.email_not_configured", fallback: "No e-mail account is configured.")
|
||||||
/// Passepartout is a user-friendly, open source OpenVPN / WireGuard client for iOS and macOS
|
/// Passepartout is a user-friendly, open source OpenVPN / WireGuard client for iOS and macOS
|
||||||
internal static let share = L10n.tr("Localizable", "global.messages.share", fallback: "Passepartout is a user-friendly, open source OpenVPN / WireGuard client for iOS and macOS")
|
internal static let share = L10n.tr("Localizable", "global.messages.share", fallback: "Passepartout is a user-friendly, open source OpenVPN / WireGuard client for iOS and macOS")
|
||||||
|
/// Passepartout is locked
|
||||||
|
internal static let unlockApp = L10n.tr("Localizable", "global.messages.unlock_app", fallback: "Passepartout is locked")
|
||||||
}
|
}
|
||||||
internal enum Placeholders {
|
internal enum Placeholders {
|
||||||
/// My profile
|
/// My profile
|
||||||
|
@ -895,6 +897,10 @@ internal enum L10n {
|
||||||
/// Make a donation
|
/// Make a donation
|
||||||
internal static let caption = L10n.tr("Localizable", "settings.items.donate.caption", fallback: "Make a donation")
|
internal static let caption = L10n.tr("Localizable", "settings.items.donate.caption", fallback: "Make a donation")
|
||||||
}
|
}
|
||||||
|
internal enum LocksInBackground {
|
||||||
|
/// Lock app in background
|
||||||
|
internal static let caption = L10n.tr("Localizable", "settings.items.locks_in_background.caption", fallback: "Lock app in background")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Shortcuts {
|
internal enum Shortcuts {
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
"global.strings.disconnect" = "Disconnect";
|
"global.strings.disconnect" = "Disconnect";
|
||||||
"global.strings.download" = "Download";
|
"global.strings.download" = "Download";
|
||||||
|
|
||||||
|
"global.messages.unlock_app" = "Passepartout is locked";
|
||||||
"global.messages.email_not_configured" = "No e-mail account is configured.";
|
"global.messages.email_not_configured" = "No e-mail account is configured.";
|
||||||
"global.messages.share" = "Passepartout is a user-friendly, open source OpenVPN / WireGuard client for iOS and macOS";
|
"global.messages.share" = "Passepartout is a user-friendly, open source OpenVPN / WireGuard client for iOS and macOS";
|
||||||
|
|
||||||
|
@ -320,6 +321,7 @@
|
||||||
/* MARK: SettingsView */
|
/* MARK: SettingsView */
|
||||||
|
|
||||||
"settings.title" = "Settings";
|
"settings.title" = "Settings";
|
||||||
|
"settings.items.locks_in_background.caption" = "Lock app in background";
|
||||||
"settings.items.donate.caption" = "Make a donation";
|
"settings.items.donate.caption" = "Make a donation";
|
||||||
|
|
||||||
/* MARK: AboutView */
|
/* MARK: AboutView */
|
||||||
|
|
Loading…
Reference in New Issue