Dactyloidae/mobile/ios/Client/Frontend/Settings/SettingsContentViewController.swift
2026-06-26 21:04:09 -07:00

182 lines
6.4 KiB
Swift

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
import Shared
import SnapKit
import UIKit
import WebKit
let DefaultTimeoutTimeInterval = 10.0 // Seconds. We'll want some telemetry on load times in the wild.
private var TODOPageLoadErrorString = NSLocalizedString("Could not load page.", comment: "Error message that is shown in settings when there was a problem loading")
/**
* A controller that manages a single web view and provides a way for
* the user to navigate back to Settings.
*/
class SettingsContentViewController: UIViewController, WKNavigationDelegate {
let interstitialBackgroundColor: UIColor
var settingsTitle: NSAttributedString?
var url: URL!
var timer: Timer?
var isLoaded: Bool = false {
didSet {
if isLoaded {
UIView.transition(from: interstitialView, to: webView,
duration: 0.5,
options: UIViewAnimationOptions.transitionCrossDissolve,
completion: { finished in
self.interstitialView.removeFromSuperview()
self.interstitialSpinnerView.stopAnimating()
})
}
}
}
fileprivate var isError: Bool = false {
didSet {
if isError {
interstitialErrorView.isHidden = false
UIView.transition(from: interstitialSpinnerView, to: interstitialErrorView,
duration: 0.5,
options: UIViewAnimationOptions.transitionCrossDissolve,
completion: { finished in
self.interstitialSpinnerView.removeFromSuperview()
self.interstitialSpinnerView.stopAnimating()
})
}
}
}
// The view shown while the content is loading in the background web view.
fileprivate var interstitialView: UIView!
fileprivate var interstitialSpinnerView: UIActivityIndicatorView!
fileprivate var interstitialErrorView: UILabel!
// The web view that displays content.
var webView: WKWebView!
fileprivate func startLoading(_ timeout: Double = DefaultTimeoutTimeInterval) {
if self.isLoaded {
return
}
if timeout > 0 {
self.timer = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(SettingsContentViewController.SELdidTimeOut), userInfo: nil, repeats: false)
} else {
self.timer = nil
}
self.webView.load(URLRequest(url: url))
self.interstitialSpinnerView.startAnimating()
}
init(backgroundColor: UIColor = UIColor.white, title: NSAttributedString? = nil) {
interstitialBackgroundColor = backgroundColor
settingsTitle = title
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// This background agrees with the web page background.
// Keeping the background constant prevents a pop of mismatched color.
view.backgroundColor = interstitialBackgroundColor
self.webView = makeWebView()
view.addSubview(webView)
self.webView.snp.remakeConstraints { make in
make.edges.equalTo(self.view)
}
// Destructuring let causes problems.
let ret = makeInterstitialViews()
self.interstitialView = ret.0
self.interstitialSpinnerView = ret.1
self.interstitialErrorView = ret.2
view.addSubview(interstitialView)
self.interstitialView.snp.remakeConstraints { make in
make.edges.equalTo(self.view)
}
startLoading()
}
func makeWebView() -> WKWebView {
let config = WKWebViewConfiguration()
let webView = WKWebView(
frame: CGRect(x: 0, y: 0, width: 1, height: 1),
configuration: config
)
webView.allowsLinkPreview = false
webView.navigationDelegate = self
return webView
}
fileprivate func makeInterstitialViews() -> (UIView, UIActivityIndicatorView, UILabel) {
let view = UIView()
// Keeping the background constant prevents a pop of mismatched color.
view.backgroundColor = interstitialBackgroundColor
let spinner = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
view.addSubview(spinner)
let error = UILabel()
if let _ = settingsTitle {
error.text = TODOPageLoadErrorString
error.textColor = UIColor.red // Firefox Orange!
error.textAlignment = NSTextAlignment.center
}
error.isHidden = true
view.addSubview(error)
spinner.snp.makeConstraints { make in
make.center.equalTo(view)
return
}
error.snp.makeConstraints { make in
make.center.equalTo(view)
make.left.equalTo(view.snp.left).offset(20)
make.right.equalTo(view.snp.right).offset(-20)
make.height.equalTo(44)
return
}
return (view, spinner, error)
}
func SELdidTimeOut() {
self.timer = nil
self.isError = true
}
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
// If this is a request to our local web server, use our private credentials.
if challenge.protectionSpace.host == "localhost" && challenge.protectionSpace.port == Int(WebServer.sharedInstance.server.port) {
completionHandler(.useCredential, WebServer.sharedInstance.credentials)
return
}
completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
SELdidTimeOut()
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
SELdidTimeOut()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.timer?.invalidate()
self.timer = nil
self.isLoaded = true
}
}