diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bc27399..9bbcd320 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,8 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
-- 3D Touch items. [#267](https://github.com/passepartoutvpn/passepartout-apple/pull/267)
-- Prompt for password interactively. [#3](https://github.com/passepartoutvpn/passepartout-apple/issues/3)
+- Option to lock app when entering background (iOS). [#270](https://github.com/passepartoutvpn/passepartout-apple/pull/270)
+- 3D Touch items (iOS). [#267](https://github.com/passepartoutvpn/passepartout-apple/pull/267)
+- Prompt for password interactively. [#259](https://github.com/passepartoutvpn/passepartout-apple/pull/259)
- Ukranian translations (Dmitry Chirkin). [#243](https://github.com/passepartoutvpn/passepartout-apple/pull/243)
- Restore DNS "Domain" setting. [#260](https://github.com/passepartoutvpn/passepartout-apple/pull/260)
- OpenVPN: Full implementation of Tunnelblick XOR patch (tmthecoder). [#245](https://github.com/passepartoutvpn/passepartout-apple/pull/245), [tunnelkit#255][https://github.com/passepartoutvpn/tunnelkit/pull/255]
diff --git a/Passepartout/App/Info.plist b/Passepartout/App/Info.plist
index a82bb931..342965ed 100644
--- a/Passepartout/App/Info.plist
+++ b/Passepartout/App/Info.plist
@@ -46,6 +46,8 @@
LSRequiresIPhoneOS
+ NSFaceIDUsageDescription
+ dummy
NSHumanReadableCopyright
$(CFG_COPYRIGHT)
NSLocationWhenInUseUsageDescription
diff --git a/Passepartout/App/PassepartoutApp.swift b/Passepartout/App/PassepartoutApp.swift
index 7c6ab2e4..ba65210a 100644
--- a/Passepartout/App/PassepartoutApp.swift
+++ b/Passepartout/App/PassepartoutApp.swift
@@ -24,7 +24,6 @@
//
import SwiftUI
-import LocalAuthentication
import PassepartoutLibrary
@main
@@ -35,22 +34,31 @@ struct PassepartoutApp: App {
@SceneBuilder var body: some Scene {
WindowGroup {
- LockableView(
- reason: L10n.Global.Messages.unlockApp,
- locksInBackground: $locksInBackground,
- content: MainView.init,
- lockedContent: LogoView.init
- ).withoutTitleBar()
- .onIntentActivity(IntentDispatcher.connectVPN)
- .onIntentActivity(IntentDispatcher.disableVPN)
- .onIntentActivity(IntentDispatcher.enableVPN)
- .onIntentActivity(IntentDispatcher.moveToLocation)
- .onIntentActivity(IntentDispatcher.trustCellularNetwork)
- .onIntentActivity(IntentDispatcher.trustCurrentNetwork)
- .onIntentActivity(IntentDispatcher.untrustCellularNetwork)
- .onIntentActivity(IntentDispatcher.untrustCurrentNetwork)
+ mainView
+ .withoutTitleBar()
+ .onIntentActivity(IntentDispatcher.connectVPN)
+ .onIntentActivity(IntentDispatcher.disableVPN)
+ .onIntentActivity(IntentDispatcher.enableVPN)
+ .onIntentActivity(IntentDispatcher.moveToLocation)
+ .onIntentActivity(IntentDispatcher.trustCellularNetwork)
+ .onIntentActivity(IntentDispatcher.trustCurrentNetwork)
+ .onIntentActivity(IntentDispatcher.untrustCellularNetwork)
+ .onIntentActivity(IntentDispatcher.untrustCurrentNetwork)
}
}
+
+ private var mainView: some View {
+ #if targetEnvironment(macCatalyst)
+ MainView()
+ #else
+ LockableView(
+ reason: L10n.Global.Messages.unlockApp,
+ locksInBackground: $locksInBackground,
+ content: MainView.init,
+ lockedContent: LogoView.init
+ )
+ #endif
+ }
}
extension View {
diff --git a/Passepartout/App/Reusable/LockableView.swift b/Passepartout/App/Reusable/LockableView.swift
index c0fe6874..0ff45292 100644
--- a/Passepartout/App/Reusable/LockableView.swift
+++ b/Passepartout/App/Reusable/LockableView.swift
@@ -49,29 +49,21 @@ struct LockableView: View {
lockedContent()
}
}.onChange(of: scenePhase, perform: onScenePhase)
+ .onAppear {
+ if !didAppear && locksInBackground {
+ didAppear = true
+ isLocked = true
+ }
+ }
}
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
@@ -86,17 +78,23 @@ struct LockableView: View {
}
func unlockIfNeeded() {
+ guard locksInBackground else {
+ isLocked = false
+ return
+ }
guard isLocked else {
return
}
let context = LAContext()
+ let policy: LAPolicy = .deviceOwnerAuthentication
var error: NSError?
- guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
+ guard context.canEvaluatePolicy(policy, error: &error) else {
+ isLocked = false
return
}
- Task {
+ Task { @MainActor in
do {
- let isAuthorized = try await context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason)
+ let isAuthorized = try await context.evaluatePolicy(policy, localizedReason: reason)
isLocked = !isAuthorized
} catch {
}
diff --git a/Passepartout/App/Views/SettingsView.swift b/Passepartout/App/Views/SettingsView.swift
index cffda505..ab11033f 100644
--- a/Passepartout/App/Views/SettingsView.swift
+++ b/Passepartout/App/Views/SettingsView.swift
@@ -44,7 +44,9 @@ struct SettingsView: View {
var body: some View {
List {
+ #if !targetEnvironment(macCatalyst)
preferencesSection
+ #endif
aboutSection
}.toolbar {
themeCloseItem(presentationMode: presentationMode)
diff --git a/Passepartout/App/en.lproj/InfoPlist.strings b/Passepartout/App/en.lproj/InfoPlist.strings
index 04d31ce1..756012ad 100644
--- a/Passepartout/App/en.lproj/InfoPlist.strings
+++ b/Passepartout/App/en.lproj/InfoPlist.strings
@@ -24,3 +24,5 @@
//
"NSLocationWhenInUseUsageDescription" = "Access name of current Wi-Fi";
+
+"NSFaceIDUsageDescription" = "Unlock app with Face ID";
diff --git a/Passepartout/AppShared/Constants/SwiftGen+Strings.swift b/Passepartout/AppShared/Constants/SwiftGen+Strings.swift
index 1fe225d9..0f8d3acc 100644
--- a/Passepartout/AppShared/Constants/SwiftGen+Strings.swift
+++ b/Passepartout/AppShared/Constants/SwiftGen+Strings.swift
@@ -898,8 +898,8 @@ internal enum L10n {
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")
+ /// Lock app access
+ internal static let caption = L10n.tr("Localizable", "settings.items.locks_in_background.caption", fallback: "Lock app access")
}
}
}
diff --git a/Passepartout/AppShared/en.lproj/Localizable.strings b/Passepartout/AppShared/en.lproj/Localizable.strings
index 4ef72b7f..c0a43f80 100644
--- a/Passepartout/AppShared/en.lproj/Localizable.strings
+++ b/Passepartout/AppShared/en.lproj/Localizable.strings
@@ -321,7 +321,7 @@
/* MARK: SettingsView */
"settings.title" = "Settings";
-"settings.items.locks_in_background.caption" = "Lock app in background";
+"settings.items.locks_in_background.caption" = "Lock app access";
"settings.items.donate.caption" = "Make a donation";
/* MARK: AboutView */