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 */; };
|
||||
0E0F4C5A29C761850022E884 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C5929C761850022E884 /* SceneDelegate.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 */; };
|
||||
0E1B5F5C29C506AD00FE7D18 /* ProfileView+Diagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -577,9 +581,9 @@
|
|||
0E29385A285A749E002A6E0E /* Context */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0E293850285A70AC002A6E0E /* AppPreference.swift */,
|
||||
0E021D9B284E68580077EF5D /* AppContext.swift */,
|
||||
0E293856285A73BC002A6E0E /* AppContext+Shared.swift */,
|
||||
0E293850285A70AC002A6E0E /* AppPreference.swift */,
|
||||
);
|
||||
path = Context;
|
||||
sourceTree = "<group>";
|
||||
|
@ -598,6 +602,7 @@
|
|||
0EF0FAF827DD212C007EB181 /* IntentActivity.swift */,
|
||||
0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */,
|
||||
0ED89C1627DE0E05008B36D6 /* IntentEditView.swift */,
|
||||
0E0F4C6329C84B5A0022E884 /* LockableView.swift */,
|
||||
0E5324A827D2AC55002565C3 /* LongContentView.swift */,
|
||||
0EBC075427EBC83800208AD9 /* MailComposerView.swift */,
|
||||
0ED30DCB27EA197C0057D8A3 /* RevealingSecureField.swift */,
|
||||
|
@ -648,6 +653,7 @@
|
|||
0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */,
|
||||
0E5349C727C176D100C71BB3 /* EndpointView+WireGuard.swift */,
|
||||
0EB90CC029C25BBD00E64628 /* InteractiveConnectionView.swift */,
|
||||
0E0F4C6529C84CF60022E884 /* LogoView.swift */,
|
||||
0E0BD27227B2EA2C00583AC5 /* MainView.swift */,
|
||||
0E71ACE827C1055200F85C4B /* NetworkSettingsView.swift */,
|
||||
0EB34BC927C6A70200B126DA /* OnDemandView.swift */,
|
||||
|
@ -1447,6 +1453,7 @@
|
|||
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */,
|
||||
0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */,
|
||||
0E5468002867AC9A00F74D1C /* MacUtils.swift in Sources */,
|
||||
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */,
|
||||
0E96D3052872010A005EFBCF /* DefaultLightVPNManager.swift in Sources */,
|
||||
0EBE880F281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift in Sources */,
|
||||
0ED30DCF27EA1EF80057D8A3 /* PaywallView+Beta.swift in Sources */,
|
||||
|
@ -1491,6 +1498,7 @@
|
|||
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
|
||||
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */,
|
||||
0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */,
|
||||
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */,
|
||||
0E039279281890B100827C10 /* AddHostView.swift in Sources */,
|
||||
0E5467F72867A57000F74D1C /* MacBridge.swift in Sources */,
|
||||
0E9ED48127FD9BAE003B2316 /* CopySavingButton.swift in Sources */,
|
||||
|
|
|
@ -438,6 +438,17 @@ extension View {
|
|||
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 {
|
||||
StyledPicker(title: title, selection: selection, values: values) {
|
||||
Text(description($0))
|
||||
|
|
|
@ -33,6 +33,8 @@ enum AppPreference: String, KeyStoreDomainLocation {
|
|||
|
||||
case didHandleSubreddit
|
||||
|
||||
case locksInBackground
|
||||
|
||||
var domain: String {
|
||||
"Passepartout.App"
|
||||
}
|
||||
|
|
|
@ -24,16 +24,23 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import LocalAuthentication
|
||||
import PassepartoutLibrary
|
||||
|
||||
@main
|
||||
struct PassepartoutApp: App {
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
||||
|
||||
@AppStorage(AppPreference.locksInBackground.rawValue) private var locksInBackground = false
|
||||
|
||||
@SceneBuilder var body: some Scene {
|
||||
WindowGroup {
|
||||
MainView()
|
||||
.withoutTitleBar()
|
||||
LockableView(
|
||||
reason: L10n.Global.Messages.unlockApp,
|
||||
locksInBackground: $locksInBackground,
|
||||
content: MainView.init,
|
||||
lockedContent: LogoView.init
|
||||
).withoutTitleBar()
|
||||
.onIntentActivity(IntentDispatcher.connectVPN)
|
||||
.onIntentActivity(IntentDispatcher.disableVPN)
|
||||
.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 {
|
||||
List {
|
||||
Section {
|
||||
// FIXME: l10n
|
||||
themeTextPicker(L10n.Endpoint.Advanced.Openvpn.Items.Digest.caption, selection: $liveAccount.authenticationMethod ?? .persistent, values: [
|
||||
.persistent,
|
||||
.interactive
|
||||
|
@ -77,27 +78,13 @@ struct AccountView: View {
|
|||
if liveAccount.authenticationMethod == .interactive {
|
||||
EmptyView()
|
||||
} else {
|
||||
RevealingSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password) {
|
||||
themeConceilImage.asSystemImage
|
||||
.themeAccentForegroundStyle()
|
||||
} revealImage: {
|
||||
themeRevealImage.asSystemImage
|
||||
.themeAccentForegroundStyle()
|
||||
}.textContentType(.password)
|
||||
.themeRawTextStyle()
|
||||
themeSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password)
|
||||
.withLeadingText(L10n.Account.Items.Password.caption)
|
||||
}
|
||||
|
||||
// TODO: interactive, scan QR code
|
||||
case .totp:
|
||||
RevealingSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password) {
|
||||
themeConceilImage.asSystemImage
|
||||
.themeAccentForegroundStyle()
|
||||
} revealImage: {
|
||||
themeRevealImage.asSystemImage
|
||||
.themeAccentForegroundStyle()
|
||||
}.textContentType(.oneTimeCode)
|
||||
.themeRawTextStyle()
|
||||
themeSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password, contentType: .oneTimeCode)
|
||||
.withLeadingText(L10n.Account.Items.Seed.caption)
|
||||
}
|
||||
} footer: {
|
||||
|
|
|
@ -50,14 +50,7 @@ struct InteractiveConnectionView: View {
|
|||
.withLeadingText(L10n.Account.Items.Username.caption)
|
||||
.disabled(true)
|
||||
|
||||
RevealingSecureField(L10n.Account.Items.Password.placeholder, text: $password) {
|
||||
themeConceilImage.asSystemImage
|
||||
.themeAccentForegroundStyle()
|
||||
} revealImage: {
|
||||
themeRevealImage.asSystemImage
|
||||
.themeAccentForegroundStyle()
|
||||
}.textContentType(.password)
|
||||
.themeRawTextStyle()
|
||||
themeSecureField(L10n.Account.Items.Password.placeholder, text: $password)
|
||||
.withLeadingText(L10n.Account.Items.Password.caption)
|
||||
} header: {
|
||||
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
|
||||
|
||||
// private var isTestBuild: Bool {
|
||||
// Constants.App.isBeta || Constants.InApp.appType == .beta
|
||||
// }
|
||||
//
|
||||
// private let appName = Unlocalized.appName
|
||||
@AppStorage(AppPreference.locksInBackground.rawValue) private var locksInBackground = false
|
||||
|
||||
private let versionString = Constants.Global.appVersionString
|
||||
|
||||
|
@ -48,6 +44,7 @@ struct SettingsView: View {
|
|||
|
||||
var body: some View {
|
||||
List {
|
||||
preferencesSection
|
||||
aboutSection
|
||||
}.toolbar {
|
||||
themeCloseItem(presentationMode: presentationMode)
|
||||
|
@ -55,6 +52,12 @@ struct SettingsView: View {
|
|||
.navigationTitle(L10n.Settings.title)
|
||||
}
|
||||
|
||||
private var preferencesSection: some View {
|
||||
Section {
|
||||
Toggle(L10n.Settings.Items.LocksInBackground.caption, isOn: $locksInBackground)
|
||||
}
|
||||
}
|
||||
|
||||
private var aboutSection: some View {
|
||||
Section {
|
||||
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.")
|
||||
/// 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 {
|
||||
/// My profile
|
||||
|
@ -895,6 +897,10 @@ internal enum L10n {
|
|||
/// 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 {
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
"global.strings.disconnect" = "Disconnect";
|
||||
"global.strings.download" = "Download";
|
||||
|
||||
"global.messages.unlock_app" = "Passepartout is locked";
|
||||
"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";
|
||||
|
||||
|
@ -320,6 +321,7 @@
|
|||
/* MARK: SettingsView */
|
||||
|
||||
"settings.title" = "Settings";
|
||||
"settings.items.locks_in_background.caption" = "Lock app in background";
|
||||
"settings.items.donate.caption" = "Make a donation";
|
||||
|
||||
/* MARK: AboutView */
|
||||
|
|
Loading…
Reference in New Issue