Skip to content

Commit

Permalink
Merge pull request #107 from OpenZesame/lock_app_scene
Browse files Browse the repository at this point in the history
Lock app scene
  • Loading branch information
Sajjon authored Jun 21, 2019
2 parents f538412 + cdc1f13 commit 6d4ce27
Show file tree
Hide file tree
Showing 49 changed files with 343 additions and 79 deletions.
119 changes: 94 additions & 25 deletions Source/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,47 @@ final class AppCoordinator: BaseCoordinator<AppCoordinatorNavigationStep> {

private lazy var walletUseCase = useCaseProvider.makeWalletUseCase()
private lazy var pincodeUseCase = useCaseProvider.makePincodeUseCase()

init(navigationController: UINavigationController, deepLinkHandler: DeepLinkHandler, useCaseProvider: UseCaseProvider) {
private lazy var lockAppScene = LockAppScene()

private lazy var unlockAppScene: UnlockAppWithPincode = {
let viewModel = UnlockAppWithPincodeViewModel(useCase: pincodeUseCase)
let scene = UnlockAppWithPincode(viewModel: viewModel)

self.bag <~ scene.viewModel.navigator.navigation
.asObservable()
.observeOn(MainScheduler.asyncInstance)
.do(onNext: { [weak self] userDid in
switch userDid {
case .unlockApp:
self?.appIsUnlockedEmitBufferedDeeplinks()
self?.restoreMainNavigationStack()
}
})
.asDriverOnErrorReturnEmpty()
.drive()
return scene
}()

private let __setRootViewControllerOfWindow: (UIViewController) -> Void
private let isViewControllerRootOfWindow: (UIViewController) -> Bool

init(
navigationController: UINavigationController,
deepLinkHandler: DeepLinkHandler,
useCaseProvider: UseCaseProvider,
isViewControllerRootOfWindow: @escaping (UIViewController) -> Bool,
setRootViewControllerOfWindow: @escaping (UIViewController) -> Void
) {
self.deepLinkHandler = deepLinkHandler
self.useCaseProvider = useCaseProvider

self.__setRootViewControllerOfWindow = setRootViewControllerOfWindow
self.isViewControllerRootOfWindow = isViewControllerRootOfWindow
super.init(navigationController: navigationController)
}

override func start(didStart: Completion? = nil) {
if walletUseCase.hasConfiguredWallet {
toMain(lockIfNeeded: true)
toMain(displayUnlockSceneIfNeeded: true)
} else {
toOnboarding()
}
Expand All @@ -69,7 +99,7 @@ private extension AppCoordinator {
}
}

func toMain(lockIfNeeded lock: Bool = false) {
func toMain(displayUnlockSceneIfNeeded displayUnlockScene: Bool = false) {

let main = MainCoordinator(
navigationController: navigationController,
Expand All @@ -79,8 +109,8 @@ private extension AppCoordinator {
)

start(coordinator: main, transition: .replace, didStart: { [unowned self] in
if lock {
self.lockApp()
if displayUnlockScene {
self.toUnlockAppWithPincodeIfNeeded()
}
}, navigationHandler: { [unowned self] userDid in
switch userDid {
Expand All @@ -89,42 +119,81 @@ private extension AppCoordinator {
})
}

var hasConfiguredPincode: Bool {
return pincodeUseCase.hasConfiguredPincode
}

func toUnlockAppWithPincodeIfNeeded() {
guard pincodeUseCase.hasConfiguredPincode, !isCurrentlyPresentingLockScene else { return }

let viewModel = UnlockAppWithPincodeViewModel(useCase: pincodeUseCase)

deepLinkHandler.appIsLockedBufferDeeplinks()

topMostCoordinator.modallyPresent(
scene: UnlockAppWithPincode.self,
viewModel: viewModel
) { [unowned self] userDid, dismissScene in
switch userDid {
case .unlockApp:
dismissScene(true, { [unowned self] in
self.deepLinkHandler.appIsUnlockedEmitBufferedDeeplinks()
})
}

guard hasConfiguredPincode, !isCurrentlyPresentingUnLockScene else {
return
}

setRootViewControllerOfWindow(to: unlockAppScene)
}
}

private extension AppCoordinator {

func restoreMainNavigationStack() {
setRootViewControllerOfWindow(to: navigationController)
}

func setRootViewControllerOfWindow(to viewController: UIViewController) {
__setRootViewControllerOfWindow(viewController)
}
}

// MARK: - Lock app with pincode
extension AppCoordinator {

func appWillResignActive() {
lockApp()
}

func appDidBecomeActive() {
unlockApp()
}
}

// MARK: - Private Lock app with pincode
private extension AppCoordinator {

func lockApp() {
toUnlockAppWithPincodeIfNeeded()
print("🔐 Trying to lock app")
if isCurrentlyPresentingUnLockScene || isCurrentlyPresentingLockScene {
print("🙅🏻‍♀️ Avoided locking app 🔒")
return
}
deepLinkHandler.appIsLockedBufferDeeplinks()
setRootViewControllerOfWindow(to: lockAppScene)
}

func unlockApp() {
if isCurrentlyPresentingUnLockScene { return }
guard isCurrentlyPresentingLockScene else { return }
print("🔓 Trying to unlock app")
if hasConfiguredPincode {
toUnlockAppWithPincodeIfNeeded()
} else {
restoreMainNavigationStack()
appIsUnlockedEmitBufferedDeeplinks()
}
}

var isCurrentlyPresentingUnLockScene: Bool {
guard hasConfiguredPincode else { return false }
return isViewControllerRootOfWindow(unlockAppScene)
}

var isCurrentlyPresentingLockScene: Bool {
return topMostScene is UnlockAppWithPincode
return isViewControllerRootOfWindow(lockAppScene)
}

func appIsUnlockedEmitBufferedDeeplinks(delayInSeconds: TimeInterval = 0.2) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) { [weak self] in
self?.deepLinkHandler.appIsUnlockedEmitBufferedDeeplinks()
}
}
}

Expand Down
19 changes: 16 additions & 3 deletions Source/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,19 @@ class AppDelegate: UIResponder {

fileprivate lazy var appCoordinator: AppCoordinator = {
let navigationController = NavigationBarLayoutingNavigationController()

window?.rootViewController = navigationController

return AppCoordinator(
navigationController: navigationController,
deepLinkHandler: DeepLinkHandler(),
useCaseProvider: DefaultUseCaseProvider.shared
useCaseProvider: DefaultUseCaseProvider.shared,
isViewControllerRootOfWindow: { [weak self] in
self?.window?.rootViewController == $0
},
setRootViewControllerOfWindow: { [weak self] in
self?.window?.rootViewController = $0
}
)
}()
}
Expand All @@ -51,8 +58,10 @@ extension AppDelegate: UIApplicationDelegate {

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard
userActivity.activityType == NSUserActivityTypeBrowsingWeb, let incomingURL = userActivity.webpageURL else {
return false
userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL
else {
return false
}

return appCoordinator.handleDeepLink(incomingURL)
Expand All @@ -61,4 +70,8 @@ extension AppDelegate: UIApplicationDelegate {
func applicationWillResignActive(_ application: UIApplication) {
appCoordinator.appWillResignActive()
}

func applicationDidBecomeActive(_ application: UIApplication) {
appCoordinator.appDidBecomeActive()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ extension Coordinating {
scene _: S.Type,
viewModel: V.ViewModel,
animated: Bool = true,
navigationHandler: @escaping (_ step: V.ViewModel.NavigationStep, _ dismiss: DismissScene) -> Void
presentationCompletion: Completion? = nil,
// navigationHandler: @escaping (_ step: V.ViewModel.NavigationStep, _ dismiss: @escaping DismissScene) -> Void
navigationHandler: @escaping NavigationHandlerModalScene<V.ViewModel>
) where S: Scene<V>, V: ContentView, V.ViewModel: Navigating {

// Create a new instance of the `Scene`, injecting its ViewModel
Expand All @@ -48,6 +50,7 @@ extension Coordinating {
modallyPresent(
scene: scene,
animated: animated,
presentationCompletion: presentationCompletion,
navigationHandler: navigationHandler
)
}
Expand All @@ -58,21 +61,21 @@ extension Coordinating {
/// - scene: A `Scene` (UIViewController) to present.
/// - animated: Whether to animate the presentation of the scene or not.
/// - navigationHandler: **Required** closure handling the navigation steps emitted by the scene's ViewModel.
/// - step: The navigation steps emitted by the `viewmodel`
/// - dismiss: Closure you **should** invoke when you want to dimiss the scene.
func modallyPresent<S, V>(
scene: S,
animated: Bool = true,
navigationHandler: @escaping (_ step: V.ViewModel.NavigationStep, _ dismiss: DismissScene) -> Void
) where S: Scene<V>, V: ContentView, V.ViewModel: Navigating {
presentationCompletion: Completion? = nil,
// navigationHandler: @escaping (_ step: V.ViewModel.NavigationStep, _ dismiss: @escaping DismissScene) -> Void
navigationHandler: @escaping NavigationHandlerModalScene<V.ViewModel>
) where S: Scene<V>, V: ContentView, V.ViewModel: Navigating {

let viewModel = scene.viewModel

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

navigationController.present(viewControllerToPresent, animated: true, completion: nil)
navigationController.present(viewControllerToPresent, animated: animated, completion: presentationCompletion)

// Subscribe to the navigation steps emitted by the viewModel's navigator
// And invoke the navigationHandler closure passed in to this method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,26 +86,3 @@ extension Coordinating {
}).drive()
}
}

private extension UINavigationController {
func setRootViewControllerIfEmptyElsePush(
viewController: UIViewController,
animated: Bool,
completion: Completion? = nil
) {

if viewControllers.isEmpty {
setViewControllers([viewController], animated: false)
} else {
pushViewController(viewController, animated: animated)
}

// Add extra functionality to pass a "completion" closure even for `push`ed ViewControllers.
guard let completion = completion else { return }
guard animated, let coordinator = transitionCoordinator else {
DispatchQueue.main.async { completion() }
return
}
coordinator.animate(alongsideTransition: nil) { _ in completion() }
}
}
Loading

0 comments on commit 6d4ce27

Please sign in to comment.