Cover screen on .inactive (#282)

* Make unlock block actor-safe

* Cover views on .inactive, lock on .background
This commit is contained in:
Davide De Rosa 2023-04-05 16:31:17 +02:00 committed by GitHub
parent 0591363b15
commit e3cfdadf97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 25 deletions

View File

@ -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), [#271](https://github.com/passepartoutvpn/passepartout-apple/pull/271), [#273](https://github.com/passepartoutvpn/passepartout-apple/pull/273), [#275](https://github.com/passepartoutvpn/passepartout-apple/pull/275) - 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), [#275](https://github.com/passepartoutvpn/passepartout-apple/pull/275), [#282](https://github.com/passepartoutvpn/passepartout-apple/pull/282)
- 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)

View File

@ -498,23 +498,21 @@ extension View {
) )
} }
private static func themeUnlockScreenBlock(isLocked: Binding<Bool>) { private static func themeUnlockScreenBlock() async -> Bool {
let context = LAContext() let context = LAContext()
let policy: LAPolicy = .deviceOwnerAuthentication let policy: LAPolicy = .deviceOwnerAuthentication
var error: NSError? var error: NSError?
guard context.canEvaluatePolicy(policy, error: &error) else { guard context.canEvaluatePolicy(policy, error: &error) else {
isLocked.wrappedValue = false return true
return
} }
Task { @MainActor in
do { do {
let isAuthorized = try await context.evaluatePolicy( let isAuthorized = try await context.evaluatePolicy(
policy, policy,
localizedReason: L10n.Global.Messages.unlockApp localizedReason: L10n.Global.Messages.unlockApp
) )
isLocked.wrappedValue = !isAuthorized return isAuthorized
} catch { } catch {
} return false
} }
} }
} }

View File

@ -32,24 +32,36 @@ struct LockableView<Content: View, LockedContent: View>: View {
let lockedContent: () -> LockedContent let lockedContent: () -> LockedContent
let unlockBlock: (Binding<Bool>) -> Void let unlockBlock: () async -> Bool
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase
@ObservedObject private var lock: Lock = .shared @ObservedObject private var lock: Lock = .shared
private var isLocked: Binding<Bool> { @Binding private var state: Lock.State
.init {
Lock.shared.isActive init(
locksInBackground: Binding<Bool>,
content: @escaping () -> Content,
lockedContent: @escaping () -> LockedContent,
unlockBlock: @escaping () async -> Bool
) {
_locksInBackground = locksInBackground
self.content = content
self.lockedContent = lockedContent
self.unlockBlock = unlockBlock
_state = .init {
Lock.shared.state
} set: { } set: {
Lock.shared.isActive = $0 Lock.shared.state = $0
} }
} }
var body: some View { var body: some View {
ZStack { ZStack {
content() content()
if isLocked.wrappedValue { if locksInBackground && state != .none {
lockedContent() lockedContent()
} }
}.onChange(of: scenePhase, perform: onScenePhase) }.onChange(of: scenePhase, perform: onScenePhase)
@ -60,6 +72,11 @@ struct LockableView<Content: View, LockedContent: View>: View {
case .active: case .active:
unlockIfNeeded() unlockIfNeeded()
case .inactive:
if state == .none {
state = .covered
}
case .background: case .background:
lockIfNeeded() lockIfNeeded()
@ -72,25 +89,44 @@ struct LockableView<Content: View, LockedContent: View>: View {
guard locksInBackground else { guard locksInBackground else {
return return
} }
isLocked.wrappedValue = true state = .locked
} }
func unlockIfNeeded() { func unlockIfNeeded() {
guard locksInBackground else { guard locksInBackground else {
isLocked.wrappedValue = false state = .none
return return
} }
guard isLocked.wrappedValue else { switch state {
case .none:
break
case .covered:
state = .none
case .locked:
Task { @MainActor in
guard await unlockBlock() else {
return return
} }
unlockBlock(isLocked) state = .none
}
}
} }
} }
private class Lock: ObservableObject { private class Lock: ObservableObject {
enum State {
case none
case covered
case locked
}
static let shared = Lock() static let shared = Lock()
@Published var isActive = true @Published var state: State = .locked
private init() { private init() {
} }