generated from element-hq/.github
-
Notifications
You must be signed in to change notification settings - Fork 117
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial setup ready for PIN/Biometric app lock. (#1876)
* Add AppLockCoordinator and WindowManager.
- Loading branch information
Showing
32 changed files
with
838 additions
and
15 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
ElementX/Sources/Application/Windowing/SceneDelegate.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// | ||
// Copyright 2023 New Vector Ltd | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
|
||
import SwiftUI | ||
|
||
/// A basic window scene delegate used to configure the `WindowManager`. | ||
/// | ||
/// We don't support multiple scenes right now, so the implementation is pretty basic. | ||
class SceneDelegate: NSObject, UIWindowSceneDelegate { | ||
weak static var windowManager: WindowManager! | ||
|
||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { | ||
guard let windowScene = scene as? UIWindowScene, !ProcessInfo.isRunningTests else { return } | ||
Self.windowManager.configure(with: windowScene) | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
ElementX/Sources/Application/Windowing/WindowManager.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// | ||
// Copyright 2023 New Vector Ltd | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
|
||
import Combine | ||
import SwiftUI | ||
|
||
protocol WindowManagerDelegate: AnyObject { | ||
/// The window manager has configured its windows. | ||
func windowManagerDidConfigureWindows(_ windowManager: WindowManager) | ||
} | ||
|
||
@MainActor | ||
/// A window manager that supports switching between a main app window with an overlay and | ||
/// an alternate window to switch contexts whilst also preserving the main view hierarchy. | ||
class WindowManager { | ||
weak var delegate: WindowManagerDelegate? | ||
|
||
/// The app's main window (we only support a single scene). | ||
private(set) var mainWindow: UIWindow! | ||
/// Presented on top of the main window, to display e.g. user indicators. | ||
private(set) var overlayWindow: UIWindow! | ||
/// A secondary window that can be presented instead of the main/overlay window combo. | ||
private(set) var alternateWindow: UIWindow! | ||
|
||
/// Configures the window manager to operate on the supplied scene. | ||
func configure(with windowScene: UIWindowScene) { | ||
mainWindow = windowScene.keyWindow | ||
|
||
overlayWindow = UIWindow(windowScene: windowScene) | ||
overlayWindow.backgroundColor = .clear | ||
// We don't support user interaction on our indicators so disable interaction, to pass | ||
// touches through to the main window. If this changes, there's another solution here: | ||
// https://www.fivestars.blog/articles/swiftui-windows/ | ||
overlayWindow.isUserInteractionEnabled = false | ||
|
||
alternateWindow = UIWindow(windowScene: windowScene) | ||
|
||
delegate?.windowManagerDidConfigureWindows(self) | ||
} | ||
|
||
/// Shows the main and overlay window combo, hiding the alternate window. | ||
func switchToMain() { | ||
mainWindow.isHidden = false | ||
overlayWindow.isHidden = false | ||
alternateWindow.isHidden = true | ||
} | ||
|
||
/// Shows the alternate window, hiding the main and overlay combo. | ||
func switchToAlternate() { | ||
alternateWindow.isHidden = false | ||
overlayWindow.isHidden = true | ||
mainWindow.isHidden = true | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
ElementX/Sources/FlowCoordinators/AppLockFlowCoordinator.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// | ||
// Copyright 2023 New Vector Ltd | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
|
||
import Combine | ||
import SwiftUI | ||
|
||
enum AppLockFlowCoordinatorAction: Equatable { | ||
/// Display the unlock flow. | ||
case lockApp | ||
/// Hide the unlock flow. | ||
case unlockApp | ||
} | ||
|
||
/// Coordinates the display of any screens shown when the app is locked. | ||
class AppLockFlowCoordinator: CoordinatorProtocol { | ||
let appLockService: AppLockServiceProtocol | ||
let navigationCoordinator: NavigationRootCoordinator | ||
|
||
private var cancellables: Set<AnyCancellable> = [] | ||
|
||
private let actionsSubject: PassthroughSubject<AppLockFlowCoordinatorAction, Never> = .init() | ||
var actions: AnyPublisher<AppLockFlowCoordinatorAction, Never> { | ||
actionsSubject.eraseToAnyPublisher() | ||
} | ||
|
||
init(appLockService: AppLockServiceProtocol, navigationCoordinator: NavigationRootCoordinator) { | ||
self.appLockService = appLockService | ||
self.navigationCoordinator = navigationCoordinator | ||
|
||
NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification) | ||
.sink { [weak self] _ in | ||
self?.showPlaceholderIfNeeded() | ||
} | ||
.store(in: &cancellables) | ||
|
||
NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification) | ||
.sink { [weak self] _ in | ||
self?.showUnlockScreenIfNeeded() | ||
} | ||
.store(in: &cancellables) | ||
} | ||
|
||
func toPresentable() -> AnyView { | ||
AnyView(navigationCoordinator.toPresentable()) | ||
} | ||
|
||
// MARK: - App unlock | ||
|
||
/// Displays the unlock flow with the app's placeholder view to hide obscure the view hierarchy in the app switcher. | ||
private func showPlaceholderIfNeeded() { | ||
guard appLockService.isEnabled else { return } | ||
|
||
navigationCoordinator.setRootCoordinator(PlaceholderScreenCoordinator(), animated: false) | ||
actionsSubject.send(.lockApp) | ||
} | ||
|
||
/// Displays the unlock flow with the main unlock screen. | ||
private func showUnlockScreenIfNeeded() { | ||
guard appLockService.isEnabled, appLockService.needsUnlock else { return } | ||
|
||
let coordinator = AppLockScreenCoordinator(parameters: .init(appLockService: appLockService)) | ||
coordinator.actions.sink { [weak self] action in | ||
guard let self else { return } | ||
switch action { | ||
case .appUnlocked: | ||
actionsSubject.send(.unlockApp) | ||
} | ||
} | ||
.store(in: &cancellables) | ||
|
||
navigationCoordinator.setRootCoordinator(coordinator, animated: false) | ||
actionsSubject.send(.lockApp) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.