Files
iOS/Sources/App/WebView/WebViewWindowController.swift
Zac West 77c72785ff Update a few more housekeeping things in the project (#1142)
- Combines all .entitlements into either: App-iOS, App-catalyst, WatchApp, Extension-iOS or Extension-catalyst.
- Cleans up and renames all the schemes to match target names
- Moves around several folders and deletes some old files.
- Converts Podfile to be hierarchical, rather than calling shared methods.
- Always runs MaterialDesignIcons script; aborts early if it's up-to-date.
- Updates all dependencies.
2020-10-03 16:05:19 -07:00

204 lines
7.0 KiB
Swift

import Foundation
import UIKit
import Shared
import PromiseKit
@available(iOS, deprecated: 13.0)
enum StateRestorationKey: String {
case mainWindow
case webViewNavigationController
}
class WebViewWindowController {
let window: UIWindow
var restorationActivity: NSUserActivity?
var webViewControllerPromise: Guarantee<WebViewController>
private var webViewControllerSeal: (WebViewController) -> Void
init(window: UIWindow, restorationActivity: NSUserActivity?) {
self.window = window
self.restorationActivity = restorationActivity
(self.webViewControllerPromise, self.webViewControllerSeal) = Guarantee<WebViewController>.pending()
Current.onboardingObservation.register(observer: self)
}
func stateRestorationActivity() -> NSUserActivity? {
webViewControllerPromise.value?.userActivity
}
private func updateRootViewController(to newValue: UIViewController) {
let newWebViewController = newValue.children.compactMap { $0 as? WebViewController }.first
// must be before the seal fires, or it may request during deinit of an old one
window.rootViewController = newValue
if let newWebViewController = newWebViewController {
// any kind of ->webviewcontroller is the same, even if we are for some reason replacing an existing one
if webViewControllerPromise.isFulfilled {
webViewControllerPromise = .value(newWebViewController)
} else {
webViewControllerSeal(newWebViewController)
}
} else if webViewControllerPromise.isFulfilled {
// replacing one, so set up a new promise if necessary
(self.webViewControllerPromise, self.webViewControllerSeal) = Guarantee<WebViewController>.pending()
}
}
var requiresOnboarding: Bool {
if HomeAssistantAPI.authenticatedAPI() == nil {
Current.Log.info("requiring onboarding due to no auth token")
return true
}
return false
}
private func onboardingNavigationController() -> UINavigationController {
return StoryboardScene.Onboarding.navController.instantiate()
}
private func webViewNavigationController(rootViewController: UIViewController? = nil) -> UINavigationController {
let navigationController = UINavigationController()
if #available(iOS 13, *) {
} else {
navigationController.restorationIdentifier = StateRestorationKey.webViewNavigationController.rawValue
}
if let rootViewController = rootViewController {
navigationController.viewControllers = [rootViewController]
}
return navigationController
}
func setup() {
if requiresOnboarding {
Current.Log.info("showing onboarding")
updateRootViewController(to: onboardingNavigationController())
} else {
if let rootController = window.rootViewController, !rootController.children.isEmpty {
Current.Log.info("state restoration loaded controller, not creating a new one")
// not changing anything, but handle the promises
updateRootViewController(to: rootController)
} else {
Current.Log.info("state restoration didn't load anything, constructing controllers manually")
let webViewController = WebViewController(restorationActivity: restorationActivity)
let navController = webViewNavigationController(rootViewController: webViewController)
updateRootViewController(to: navController)
restorationActivity = nil
}
}
}
func present(_ viewController: UIViewController, animated: Bool = true, completion: (() -> Void)? = nil) {
window.rootViewController?.present(viewController, animated: animated, completion: completion)
}
var presentingViewController: UIViewController? {
var currentController = window.rootViewController
while let controller = currentController?.presentedViewController {
currentController = controller
}
return currentController
}
@available(iOS, deprecated: 13.0)
func viewController(
withRestorationIdentifierPath identifierComponents: [String]
) -> UIViewController? {
// iOS 12 and below state restoration code path only
if identifierComponents == [StateRestorationKey.webViewNavigationController.rawValue] {
let navigationController = webViewNavigationController()
window.rootViewController = navigationController
return navigationController
} else {
return nil
}
}
func navigate(to url: URL) {
webViewControllerPromise.done { webViewController in
webViewController.open(inline: url)
}
}
func open(urlString openUrlRaw: String) {
if let webviewURL = Current.settingsStore.connectionInfo?.webviewURL(from: openUrlRaw) {
navigate(to: webviewURL)
return
}
guard let url = URL(string: openUrlRaw) else {
return
}
let triggerOpen = {
openURLInBrowser(url, self.presentingViewController)
}
if prefs.bool(forKey: "confirmBeforeOpeningUrl") {
let alert = UIAlertController(
title: L10n.Alerts.OpenUrlFromNotification.title,
message: L10n.Alerts.OpenUrlFromNotification.message(openUrlRaw),
preferredStyle: UIAlertController.Style.alert
)
alert.addAction(UIAlertAction(
title: L10n.cancelLabel,
style: .cancel,
handler: nil
))
alert.addAction(UIAlertAction(
title: L10n.openLabel,
style: .default
) { _ in
triggerOpen()
})
present(alert)
} else {
triggerOpen()
}
}
}
extension WebViewWindowController: OnboardingStateObserver {
func onboardingStateDidChange(to state: OnboardingState) {
switch state {
case .needed(let type):
let controller = onboardingNavigationController()
updateRootViewController(to: controller)
if type.shouldShowError {
let alert = UIAlertController(
title: L10n.Alerts.AuthRequired.title,
message: L10n.Alerts.AuthRequired.message,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(
title: L10n.okLabel,
style: .default,
handler: nil
))
controller.present(alert, animated: true, completion: nil)
}
case .complete:
updateRootViewController(to: webViewNavigationController(rootViewController: WebViewController(
restorationActivity: restorationActivity
)))
restorationActivity = nil
}
}
}