Skip to content

Commit 6d4ce27

Browse files
authored
Merge pull request #107 from OpenZesame/lock_app_scene
Lock app scene
2 parents f538412 + cdc1f13 commit 6d4ce27

49 files changed

Lines changed: 343 additions & 79 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Source/Application/AppCoordinator.swift

Lines changed: 94 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,47 @@ final class AppCoordinator: BaseCoordinator<AppCoordinatorNavigationStep> {
3535

3636
private lazy var walletUseCase = useCaseProvider.makeWalletUseCase()
3737
private lazy var pincodeUseCase = useCaseProvider.makePincodeUseCase()
38-
39-
init(navigationController: UINavigationController, deepLinkHandler: DeepLinkHandler, useCaseProvider: UseCaseProvider) {
38+
private lazy var lockAppScene = LockAppScene()
39+
40+
private lazy var unlockAppScene: UnlockAppWithPincode = {
41+
let viewModel = UnlockAppWithPincodeViewModel(useCase: pincodeUseCase)
42+
let scene = UnlockAppWithPincode(viewModel: viewModel)
43+
44+
self.bag <~ scene.viewModel.navigator.navigation
45+
.asObservable()
46+
.observeOn(MainScheduler.asyncInstance)
47+
.do(onNext: { [weak self] userDid in
48+
switch userDid {
49+
case .unlockApp:
50+
self?.appIsUnlockedEmitBufferedDeeplinks()
51+
self?.restoreMainNavigationStack()
52+
}
53+
})
54+
.asDriverOnErrorReturnEmpty()
55+
.drive()
56+
return scene
57+
}()
58+
59+
private let __setRootViewControllerOfWindow: (UIViewController) -> Void
60+
private let isViewControllerRootOfWindow: (UIViewController) -> Bool
61+
62+
init(
63+
navigationController: UINavigationController,
64+
deepLinkHandler: DeepLinkHandler,
65+
useCaseProvider: UseCaseProvider,
66+
isViewControllerRootOfWindow: @escaping (UIViewController) -> Bool,
67+
setRootViewControllerOfWindow: @escaping (UIViewController) -> Void
68+
) {
4069
self.deepLinkHandler = deepLinkHandler
4170
self.useCaseProvider = useCaseProvider
42-
71+
self.__setRootViewControllerOfWindow = setRootViewControllerOfWindow
72+
self.isViewControllerRootOfWindow = isViewControllerRootOfWindow
4373
super.init(navigationController: navigationController)
4474
}
4575

4676
override func start(didStart: Completion? = nil) {
4777
if walletUseCase.hasConfiguredWallet {
48-
toMain(lockIfNeeded: true)
78+
toMain(displayUnlockSceneIfNeeded: true)
4979
} else {
5080
toOnboarding()
5181
}
@@ -69,7 +99,7 @@ private extension AppCoordinator {
6999
}
70100
}
71101

72-
func toMain(lockIfNeeded lock: Bool = false) {
102+
func toMain(displayUnlockSceneIfNeeded displayUnlockScene: Bool = false) {
73103

74104
let main = MainCoordinator(
75105
navigationController: navigationController,
@@ -79,8 +109,8 @@ private extension AppCoordinator {
79109
)
80110

81111
start(coordinator: main, transition: .replace, didStart: { [unowned self] in
82-
if lock {
83-
self.lockApp()
112+
if displayUnlockScene {
113+
self.toUnlockAppWithPincodeIfNeeded()
84114
}
85115
}, navigationHandler: { [unowned self] userDid in
86116
switch userDid {
@@ -89,42 +119,81 @@ private extension AppCoordinator {
89119
})
90120
}
91121

122+
var hasConfiguredPincode: Bool {
123+
return pincodeUseCase.hasConfiguredPincode
124+
}
125+
92126
func toUnlockAppWithPincodeIfNeeded() {
93-
guard pincodeUseCase.hasConfiguredPincode, !isCurrentlyPresentingLockScene else { return }
94-
95-
let viewModel = UnlockAppWithPincodeViewModel(useCase: pincodeUseCase)
96-
97-
deepLinkHandler.appIsLockedBufferDeeplinks()
98-
99-
topMostCoordinator.modallyPresent(
100-
scene: UnlockAppWithPincode.self,
101-
viewModel: viewModel
102-
) { [unowned self] userDid, dismissScene in
103-
switch userDid {
104-
case .unlockApp:
105-
dismissScene(true, { [unowned self] in
106-
self.deepLinkHandler.appIsUnlockedEmitBufferedDeeplinks()
107-
})
108-
}
127+
128+
guard hasConfiguredPincode, !isCurrentlyPresentingUnLockScene else {
129+
return
109130
}
131+
132+
setRootViewControllerOfWindow(to: unlockAppScene)
133+
}
134+
}
135+
136+
private extension AppCoordinator {
137+
138+
func restoreMainNavigationStack() {
139+
setRootViewControllerOfWindow(to: navigationController)
140+
}
141+
142+
func setRootViewControllerOfWindow(to viewController: UIViewController) {
143+
__setRootViewControllerOfWindow(viewController)
110144
}
111145
}
112146

113147
// MARK: - Lock app with pincode
114148
extension AppCoordinator {
149+
115150
func appWillResignActive() {
116151
lockApp()
117152
}
153+
154+
func appDidBecomeActive() {
155+
unlockApp()
156+
}
118157
}
119158

120159
// MARK: - Private Lock app with pincode
121160
private extension AppCoordinator {
161+
122162
func lockApp() {
123-
toUnlockAppWithPincodeIfNeeded()
163+
print("🔐 Trying to lock app")
164+
if isCurrentlyPresentingUnLockScene || isCurrentlyPresentingLockScene {
165+
print("🙅🏻‍♀️ Avoided locking app 🔒")
166+
return
167+
}
168+
deepLinkHandler.appIsLockedBufferDeeplinks()
169+
setRootViewControllerOfWindow(to: lockAppScene)
170+
}
171+
172+
func unlockApp() {
173+
if isCurrentlyPresentingUnLockScene { return }
174+
guard isCurrentlyPresentingLockScene else { return }
175+
print("🔓 Trying to unlock app")
176+
if hasConfiguredPincode {
177+
toUnlockAppWithPincodeIfNeeded()
178+
} else {
179+
restoreMainNavigationStack()
180+
appIsUnlockedEmitBufferedDeeplinks()
181+
}
124182
}
125183

184+
var isCurrentlyPresentingUnLockScene: Bool {
185+
guard hasConfiguredPincode else { return false }
186+
return isViewControllerRootOfWindow(unlockAppScene)
187+
}
188+
126189
var isCurrentlyPresentingLockScene: Bool {
127-
return topMostScene is UnlockAppWithPincode
190+
return isViewControllerRootOfWindow(lockAppScene)
191+
}
192+
193+
func appIsUnlockedEmitBufferedDeeplinks(delayInSeconds: TimeInterval = 0.2) {
194+
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) { [weak self] in
195+
self?.deepLinkHandler.appIsUnlockedEmitBufferedDeeplinks()
196+
}
128197
}
129198
}
130199

Source/Application/AppDelegate.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,19 @@ class AppDelegate: UIResponder {
3030

3131
fileprivate lazy var appCoordinator: AppCoordinator = {
3232
let navigationController = NavigationBarLayoutingNavigationController()
33+
3334
window?.rootViewController = navigationController
3435

3536
return AppCoordinator(
3637
navigationController: navigationController,
3738
deepLinkHandler: DeepLinkHandler(),
38-
useCaseProvider: DefaultUseCaseProvider.shared
39+
useCaseProvider: DefaultUseCaseProvider.shared,
40+
isViewControllerRootOfWindow: { [weak self] in
41+
self?.window?.rootViewController == $0
42+
},
43+
setRootViewControllerOfWindow: { [weak self] in
44+
self?.window?.rootViewController = $0
45+
}
3946
)
4047
}()
4148
}
@@ -51,8 +58,10 @@ extension AppDelegate: UIApplicationDelegate {
5158

5259
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
5360
guard
54-
userActivity.activityType == NSUserActivityTypeBrowsingWeb, let incomingURL = userActivity.webpageURL else {
55-
return false
61+
userActivity.activityType == NSUserActivityTypeBrowsingWeb,
62+
let incomingURL = userActivity.webpageURL
63+
else {
64+
return false
5665
}
5766

5867
return appCoordinator.handleDeepLink(incomingURL)
@@ -61,4 +70,8 @@ extension AppDelegate: UIApplicationDelegate {
6170
func applicationWillResignActive(_ application: UIApplication) {
6271
appCoordinator.appWillResignActive()
6372
}
73+
74+
func applicationDidBecomeActive(_ application: UIApplication) {
75+
appCoordinator.appDidBecomeActive()
76+
}
6477
}

Source/Application/Navigation/Coordinator/Coordinating/Coordinating+Scene/Coordinating+Scene+Present.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ extension Coordinating {
3939
scene _: S.Type,
4040
viewModel: V.ViewModel,
4141
animated: Bool = true,
42-
navigationHandler: @escaping (_ step: V.ViewModel.NavigationStep, _ dismiss: DismissScene) -> Void
42+
presentationCompletion: Completion? = nil,
43+
// navigationHandler: @escaping (_ step: V.ViewModel.NavigationStep, _ dismiss: @escaping DismissScene) -> Void
44+
navigationHandler: @escaping NavigationHandlerModalScene<V.ViewModel>
4345
) where S: Scene<V>, V: ContentView, V.ViewModel: Navigating {
4446

4547
// Create a new instance of the `Scene`, injecting its ViewModel
@@ -48,6 +50,7 @@ extension Coordinating {
4850
modallyPresent(
4951
scene: scene,
5052
animated: animated,
53+
presentationCompletion: presentationCompletion,
5154
navigationHandler: navigationHandler
5255
)
5356
}
@@ -58,21 +61,21 @@ extension Coordinating {
5861
/// - scene: A `Scene` (UIViewController) to present.
5962
/// - animated: Whether to animate the presentation of the scene or not.
6063
/// - navigationHandler: **Required** closure handling the navigation steps emitted by the scene's ViewModel.
61-
/// - step: The navigation steps emitted by the `viewmodel`
62-
/// - dismiss: Closure you **should** invoke when you want to dimiss the scene.
6364
func modallyPresent<S, V>(
6465
scene: S,
6566
animated: Bool = true,
66-
navigationHandler: @escaping (_ step: V.ViewModel.NavigationStep, _ dismiss: DismissScene) -> Void
67-
) where S: Scene<V>, V: ContentView, V.ViewModel: Navigating {
67+
presentationCompletion: Completion? = nil,
68+
// navigationHandler: @escaping (_ step: V.ViewModel.NavigationStep, _ dismiss: @escaping DismissScene) -> Void
69+
navigationHandler: @escaping NavigationHandlerModalScene<V.ViewModel>
70+
) where S: Scene<V>, V: ContentView, V.ViewModel: Navigating {
6871

6972
let viewModel = scene.viewModel
7073

7174
// Create a new `UINavigationController` having the scene as root ViewController.
7275
// Since this is starting a new modal flow we should use a new NavigationController.
7376
let viewControllerToPresent = NavigationBarLayoutingNavigationController(rootViewController: scene)
7477

75-
navigationController.present(viewControllerToPresent, animated: true, completion: nil)
78+
navigationController.present(viewControllerToPresent, animated: animated, completion: presentationCompletion)
7679

7780
// Subscribe to the navigation steps emitted by the viewModel's navigator
7881
// And invoke the navigationHandler closure passed in to this method

Source/Application/Navigation/Coordinator/Coordinating/Coordinating+Scene/Coordinating+Scene+Push.swift

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -86,26 +86,3 @@ extension Coordinating {
8686
}).drive()
8787
}
8888
}
89-
90-
private extension UINavigationController {
91-
func setRootViewControllerIfEmptyElsePush(
92-
viewController: UIViewController,
93-
animated: Bool,
94-
completion: Completion? = nil
95-
) {
96-
97-
if viewControllers.isEmpty {
98-
setViewControllers([viewController], animated: false)
99-
} else {
100-
pushViewController(viewController, animated: animated)
101-
}
102-
103-
// Add extra functionality to pass a "completion" closure even for `push`ed ViewControllers.
104-
guard let completion = completion else { return }
105-
guard animated, let coordinator = transitionCoordinator else {
106-
DispatchQueue.main.async { completion() }
107-
return
108-
}
109-
coordinator.animate(alongsideTransition: nil) { _ in completion() }
110-
}
111-
}

0 commit comments

Comments
 (0)