import CocoaLumberjackSwift
import JDStatusBarNotification
import MBProgressHUD
import SafariServices
import ThreemaFramework
import ThreemaMacros
import UIKit
@preconcurrency import WebKit
class SettingsWebViewViewController: ThemedViewController {
// MARK: - Public property
private(set) lazy var webView: WKWebView = {
let webPagePreferences = WKWebpagePreferences()
webPagePreferences.allowsContentJavaScript = allowsContentJavaScript
let webConfiguration = WKWebViewConfiguration()
webConfiguration.defaultWebpagePreferences = webPagePreferences
let webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.allowsLinkPreview = false
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView.isOpaque = false
return webView
}()
// MARK: - Private properties
private var url: URL?
private var isSetupWizard = false
private var allowsContentJavaScript = false
private var loadingPresenter: NotificationPresenter?
// MARK: - Lifecycle
/// Show content of a website in a webView
/// - Parameters:
/// - url: URL of the content
/// - title: Title of the controller
/// - allowsContentJavaScript: The default value of this property is false. If you change the value to true, the
/// web view execute JavaScript code referenced by the web content.
/// - isSetupWizard: If true, it will set all to dark mode
@objc init(
url: URL? = nil,
title: String,
allowsContentJavaScript: Bool = false,
isSetupWizard: Bool = false
) {
self.url = url
self.allowsContentJavaScript = allowsContentJavaScript
self.isSetupWizard = isSetupWizard
super.init(nibName: nil, bundle: nil)
self.title = title
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.largeTitleDisplayMode = .never
view.backgroundColor = Colors.backgroundViewController
webView.navigationDelegate = self
view = webView
setupTraitRegistration()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
webView.backgroundColor = isSetupWizard ? Colors.backgroundWizard : Colors.backgroundViewController
loadURL()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
loadingPresenter?.dismiss(animated: false)
}
// MARK: Configurations
override var shouldAutorotate: Bool {
true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
.allButUpsideDown
}
// MARK: - Private functions
/// Begin loading the URL if it has been set. If the loading process takes more than 0.8 seconds, a loading
/// indicator will be displayed.
private func loadURL() {
guard let url,
let combinedURL = appendQueryItems(to: url) else {
DDLogError("Can't load url, because url is empty")
return
}
Task.detached {
try? await Task.sleep(seconds: 0.8)
Task { @MainActor in
if self.webView.isLoading {
NotificationPresenterWrapper.shared.presentIndefinitely(type: .loadingWebSite) { presenter in
if self.webView.isLoading {
self.loadingPresenter = presenter
}
else {
presenter.dismiss()
}
}
}
}
}
let request = URLRequest(url: combinedURL, cachePolicy: .reloadIgnoringCacheData)
webView.load(request)
}
/// Append the query parameters ‘lang’, ‘version’, ‘platform’, and ‘theme’.
/// If the ‘isSetupWizard’ parameter is true, the dark theme will always be selected.
/// - Parameter url: URL to append the items
/// - Returns: URL?
private func appendQueryItems(to url: URL) -> URL? {
let theme: String = {
guard !self.isSetupWizard else {
return "dark"
}
switch Colors.theme {
case .dark:
return "dark"
case .light:
return "light"
}
}()
return url.appending(queryItems: [
URLQueryItem(name: "lang", value: Bundle.main.preferredLocalizations.first ?? "en"),
URLQueryItem(name: "version", value: AppVersionInfo.appVersion.version ?? "-"),
URLQueryItem(name: "platform", value: "ios"),
URLQueryItem(name: "theme", value: theme),
])
}
/// Check and add SUPPORT as contact and open the chat with it.
private func addSupportContactAndOpenChat() {
ContactStore.shared()
.addContact(
with: "*SUPPORT",
verificationLevel: Int32(ContactEntity.VerificationLevel.fullyVerified.rawValue)
) { contact, _ in
let message = "My app version: \(ThreemaUtility.clientVersionWithMDM)"
let info = [
kKeyContact: contact as Any,
kKeyForceCompose: NSNumber(value: true),
kKeyText: message,
] as [String: Any]
DispatchQueue.main.async {
NotificationCenter.default.post(
name: NSNotification.Name(rawValue: kNotificationShowConversation),
object: nil,
userInfo: info
)
}
}
onError: { error in
DDLogError("\(error)")
}
}
}
// MARK: - Notifications
extension SettingsWebViewViewController {
private func setupTraitRegistration() {
let traits: [UITrait] = [UITraitUserInterfaceStyle.self]
registerForTraitChanges(traits) { [weak self] (_: Self, previous) in
guard let self else {
return
}
if previous.userInterfaceStyle != traitCollection.userInterfaceStyle {
loadURL()
}
}
}
}
// MARK: - WKNavigationDelegate
extension SettingsWebViewViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation?) {
loadingPresenter?.dismiss()
}
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
if navigationAction.navigationType == .linkActivated, let url = navigationAction.request.url {
// In-page anchor: let WebKit scroll to it natively
if url.fragment != nil, isSameDocument(url, as: webView.url) {
decisionHandler(.allow)
return
}
if let scheme = url.scheme, scheme.hasPrefix("http") || scheme.hasPrefix("https") {
let config = SFSafariViewController.Configuration()
let safariView = SFSafariViewController(url: url, configuration: config)
present(safariView, animated: true)
}
else if let host = url.host,
host == "compose",
let query = url.query,
query.hasPrefix("id=*SUPPORT") {
addSupportContactAndOpenChat()
}
else {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
/// Whether `url` points into the currently loaded document, i.e. only the fragment differs
private func isSameDocument(_ url: URL, as currentURL: URL?) -> Bool {
guard let currentURL,
var target = URLComponents(url: url, resolvingAgainstBaseURL: true),
var current = URLComponents(url: currentURL, resolvingAgainstBaseURL: true) else {
return false
}
target.fragment = nil
current.fragment = nil
return target == current
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation?, withError error: Error) {
loadingPresenter?.dismiss()
NotificationPresenterWrapper.shared.present(type: .noConnection)
}
func webView(
_ webView: WKWebView,
didFailProvisionalNavigation navigation: WKNavigation?,
withError error: any Error
) {
loadingPresenter?.dismiss()
if (error as NSError).code == URLError.Code.notConnectedToInternet.rawValue {
NotificationPresenterWrapper.shared.present(type: .noConnection)
}
}
}
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.1Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-11-04)
¤
*Eine klare Vorstellung vom Zielzustand
|
|