Resolve issues with lock screen (#273)
* Make lock screen a View extension - Reuse in global theme (apply to all modals) - Use a ZStack rather than replace (retain content/navigation) - Share lock state across all LockableView
This commit is contained in:
parent
76084dbd30
commit
6af4bb7e0f
|
@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
|
||||
- Option to lock app when entering background (iOS). [#270](https://github.com/passepartoutvpn/passepartout-apple/pull/270)
|
||||
- Option to lock app when entering background (iOS). [#270](https://github.com/passepartoutvpn/passepartout-apple/pull/270), [#271](https://github.com/passepartoutvpn/passepartout-apple/pull/271), [#273](https://github.com/passepartoutvpn/passepartout-apple/pull/273)
|
||||
- 3D Touch items (iOS). [#267](https://github.com/passepartoutvpn/passepartout-apple/pull/267)
|
||||
- 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)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import PassepartoutLibrary
|
||||
import LocalAuthentication
|
||||
|
||||
extension View {
|
||||
var themeIdiom: UIUserInterfaceIdiom {
|
||||
|
@ -57,6 +58,9 @@ extension View {
|
|||
extension View {
|
||||
func themeGlobal() -> some View {
|
||||
themeNavigationViewStyle()
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
.themeLockScreen()
|
||||
#endif
|
||||
.themeTint()
|
||||
.listStyle(themeListStyleValue())
|
||||
.toggleStyle(themeToggleStyleValue())
|
||||
|
@ -490,6 +494,42 @@ extension View {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Lock screen
|
||||
|
||||
extension View {
|
||||
func themeLockScreen() -> some View {
|
||||
@AppStorage(AppPreference.locksInBackground.rawValue) var locksInBackground = false
|
||||
return LockableView(
|
||||
locksInBackground: $locksInBackground,
|
||||
content: {
|
||||
self
|
||||
},
|
||||
lockedContent: LogoView.init,
|
||||
unlockBlock: Self.themeUnlockScreenBlock
|
||||
)
|
||||
}
|
||||
|
||||
private static func themeUnlockScreenBlock(isLocked: Binding<Bool>) {
|
||||
let context = LAContext()
|
||||
let policy: LAPolicy = .deviceOwnerAuthentication
|
||||
var error: NSError?
|
||||
guard context.canEvaluatePolicy(policy, error: &error) else {
|
||||
isLocked.wrappedValue = false
|
||||
return
|
||||
}
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let isAuthorized = try await context.evaluatePolicy(
|
||||
policy,
|
||||
localizedReason: L10n.Global.Messages.unlockApp
|
||||
)
|
||||
isLocked.wrappedValue = !isAuthorized
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Validation
|
||||
|
||||
extension View {
|
||||
|
|
|
@ -30,11 +30,9 @@ import PassepartoutLibrary
|
|||
struct PassepartoutApp: App {
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
||||
|
||||
@AppStorage(AppPreference.locksInBackground.rawValue) private var locksInBackground = false
|
||||
|
||||
@SceneBuilder var body: some Scene {
|
||||
WindowGroup {
|
||||
mainView
|
||||
MainView()
|
||||
.withoutTitleBar()
|
||||
.onIntentActivity(IntentDispatcher.connectVPN)
|
||||
.onIntentActivity(IntentDispatcher.disableVPN)
|
||||
|
@ -46,19 +44,6 @@ struct PassepartoutApp: App {
|
|||
.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 {
|
||||
|
|
|
@ -24,37 +24,35 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import LocalAuthentication
|
||||
|
||||
struct LockableView<Content: View, LockedContent: View>: View {
|
||||
let reason: String
|
||||
|
||||
@Binding var locksInBackground: Bool
|
||||
|
||||
let content: () -> Content
|
||||
|
||||
let lockedContent: () -> LockedContent
|
||||
|
||||
let unlockBlock: (Binding<Bool>) -> Void
|
||||
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
|
||||
@State private var didAppear = false
|
||||
@ObservedObject private var lock: Lock = .shared
|
||||
|
||||
@State private var isLocked = false
|
||||
private var isLocked: Binding<Bool> {
|
||||
.init {
|
||||
Lock.shared.isActive
|
||||
} set: {
|
||||
Lock.shared.isActive = $0
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if !isLocked {
|
||||
content()
|
||||
} else {
|
||||
ZStack {
|
||||
content()
|
||||
if isLocked.wrappedValue {
|
||||
lockedContent()
|
||||
}
|
||||
}.onChange(of: scenePhase, perform: onScenePhase)
|
||||
.onAppear {
|
||||
if !didAppear && locksInBackground {
|
||||
didAppear = true
|
||||
isLocked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func onScenePhase(_ scenePhase: ScenePhase) {
|
||||
|
@ -74,30 +72,26 @@ struct LockableView<Content: View, LockedContent: View>: View {
|
|||
guard locksInBackground else {
|
||||
return
|
||||
}
|
||||
isLocked = true
|
||||
isLocked.wrappedValue = true
|
||||
}
|
||||
|
||||
func unlockIfNeeded() {
|
||||
guard locksInBackground else {
|
||||
isLocked = false
|
||||
isLocked.wrappedValue = false
|
||||
return
|
||||
}
|
||||
guard isLocked else {
|
||||
guard isLocked.wrappedValue else {
|
||||
return
|
||||
}
|
||||
let context = LAContext()
|
||||
let policy: LAPolicy = .deviceOwnerAuthentication
|
||||
var error: NSError?
|
||||
guard context.canEvaluatePolicy(policy, error: &error) else {
|
||||
isLocked = false
|
||||
return
|
||||
}
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let isAuthorized = try await context.evaluatePolicy(policy, localizedReason: reason)
|
||||
isLocked = !isAuthorized
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
unlockBlock(isLocked)
|
||||
}
|
||||
}
|
||||
|
||||
private class Lock: ObservableObject {
|
||||
static let shared = Lock()
|
||||
|
||||
@Published var isActive = true
|
||||
|
||||
private init() {
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue