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
|
### 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)
|
- 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)
|
- 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)
|
- Restore DNS "Domain" setting. [#260](https://github.com/passepartoutvpn/passepartout-apple/pull/260)
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import PassepartoutLibrary
|
import PassepartoutLibrary
|
||||||
|
import LocalAuthentication
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
var themeIdiom: UIUserInterfaceIdiom {
|
var themeIdiom: UIUserInterfaceIdiom {
|
||||||
|
@ -57,6 +58,9 @@ extension View {
|
||||||
extension View {
|
extension View {
|
||||||
func themeGlobal() -> some View {
|
func themeGlobal() -> some View {
|
||||||
themeNavigationViewStyle()
|
themeNavigationViewStyle()
|
||||||
|
#if !targetEnvironment(macCatalyst)
|
||||||
|
.themeLockScreen()
|
||||||
|
#endif
|
||||||
.themeTint()
|
.themeTint()
|
||||||
.listStyle(themeListStyleValue())
|
.listStyle(themeListStyleValue())
|
||||||
.toggleStyle(themeToggleStyleValue())
|
.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
|
// MARK: Validation
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
|
|
|
@ -30,11 +30,9 @@ import PassepartoutLibrary
|
||||||
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
|
MainView()
|
||||||
.withoutTitleBar()
|
.withoutTitleBar()
|
||||||
.onIntentActivity(IntentDispatcher.connectVPN)
|
.onIntentActivity(IntentDispatcher.connectVPN)
|
||||||
.onIntentActivity(IntentDispatcher.disableVPN)
|
.onIntentActivity(IntentDispatcher.disableVPN)
|
||||||
|
@ -46,19 +44,6 @@ struct PassepartoutApp: App {
|
||||||
.onIntentActivity(IntentDispatcher.untrustCurrentNetwork)
|
.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 {
|
extension View {
|
||||||
|
|
|
@ -24,37 +24,35 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import LocalAuthentication
|
|
||||||
|
|
||||||
struct LockableView<Content: View, LockedContent: View>: View {
|
struct LockableView<Content: View, LockedContent: View>: View {
|
||||||
let reason: String
|
|
||||||
|
|
||||||
@Binding var locksInBackground: Bool
|
@Binding var locksInBackground: Bool
|
||||||
|
|
||||||
let content: () -> Content
|
let content: () -> Content
|
||||||
|
|
||||||
let lockedContent: () -> LockedContent
|
let lockedContent: () -> LockedContent
|
||||||
|
|
||||||
|
let unlockBlock: (Binding<Bool>) -> Void
|
||||||
|
|
||||||
@Environment(\.scenePhase) private var scenePhase
|
@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 {
|
var body: some View {
|
||||||
Group {
|
ZStack {
|
||||||
if !isLocked {
|
content()
|
||||||
content()
|
if isLocked.wrappedValue {
|
||||||
} else {
|
|
||||||
lockedContent()
|
lockedContent()
|
||||||
}
|
}
|
||||||
}.onChange(of: scenePhase, perform: onScenePhase)
|
}.onChange(of: scenePhase, perform: onScenePhase)
|
||||||
.onAppear {
|
|
||||||
if !didAppear && locksInBackground {
|
|
||||||
didAppear = true
|
|
||||||
isLocked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func onScenePhase(_ scenePhase: ScenePhase) {
|
private func onScenePhase(_ scenePhase: ScenePhase) {
|
||||||
|
@ -74,30 +72,26 @@ struct LockableView<Content: View, LockedContent: View>: View {
|
||||||
guard locksInBackground else {
|
guard locksInBackground else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isLocked = true
|
isLocked.wrappedValue = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func unlockIfNeeded() {
|
func unlockIfNeeded() {
|
||||||
guard locksInBackground else {
|
guard locksInBackground else {
|
||||||
isLocked = false
|
isLocked.wrappedValue = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard isLocked else {
|
guard isLocked.wrappedValue else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let context = LAContext()
|
unlockBlock(isLocked)
|
||||||
let policy: LAPolicy = .deviceOwnerAuthentication
|
}
|
||||||
var error: NSError?
|
}
|
||||||
guard context.canEvaluatePolicy(policy, error: &error) else {
|
|
||||||
isLocked = false
|
private class Lock: ObservableObject {
|
||||||
return
|
static let shared = Lock()
|
||||||
}
|
|
||||||
Task { @MainActor in
|
@Published var isActive = true
|
||||||
do {
|
|
||||||
let isAuthorized = try await context.evaluatePolicy(policy, localizedReason: reason)
|
private init() {
|
||||||
isLocked = !isAuthorized
|
|
||||||
} catch {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue