// // ReadViewController.swift // Reader // // Created by Shadowfacts on 1/9/22. // import UIKit import WebKit import HTMLEntities import SafariServices class ReadViewController: UIViewController { private static let publishedFormatter: DateFormatter = { let f = DateFormatter() f.dateStyle = .medium f.timeStyle = .medium return f }() let fervorController: FervorController let item: Item private var webView: WKWebView! #if targetEnvironment(macCatalyst) private var itemReadObservation: NSKeyValueObservation? #endif override var prefersStatusBarHidden: Bool { navigationController?.isNavigationBarHidden ?? false } override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { .slide } init(item: Item, fervorController: FervorController) { self.fervorController = fervorController self.item = item super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() navigationItem.largeTitleDisplayMode = .never view.backgroundColor = .appBackground view.addInteraction(StretchyMenuInteraction(delegate: self)) webView = WKWebView() webView.translatesAutoresizingMaskIntoConstraints = false webView.navigationDelegate = self webView.uiDelegate = self // transparent background required to prevent white flash in dark mode, just using .appBackground doesn't work webView.isOpaque = false webView.backgroundColor = .clear if let content = itemContentHTML() { webView.loadHTMLString(content, baseURL: item.url) } view.addSubview(webView) NSLayoutConstraint.activate([ webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), webView.topAnchor.constraint(equalTo: view.topAnchor), webView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) if let url = item.url { activityItemsConfiguration = UIActivityItemsConfiguration(objects: [url as NSURL]) } #if targetEnvironment(macCatalyst) itemReadObservation = item.observe(\.read) { [unowned self] _, _ in self.updateToggleReadToolbarImage() } #endif } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateScrollIndicatorStyle() } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) updateScrollIndicatorStyle() } private func updateScrollIndicatorStyle() { guard #available(iOS 15.4, *) else { // different workaround pre-iOS 15.4 return } // can't use .default because that causes the WKScrollView to think the indicator style has not been set by the client (us): // https://github.com/WebKit/WebKit/blob/1dbd34cf01d8b5aedcb8820b13cb6553ed60e8ed/Source/WebKit/UIProcess/ios/WKScrollView.mm#L247 // if that happens, it goes back to trying to set it based on background color which doesn't work because we give it a clear background // https://github.com/WebKit/WebKit/blob/d085008d57f784d0913a8c37351f60b4a0eb8a3a/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm#L562 // https://github.com/WebKit/WebKit/blob/d085008d57f784d0913a8c37351f60b4a0eb8a3a/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm#L520 // so, we set it ourselves based on the user interface style if traitCollection.userInterfaceStyle == .dark { webView.scrollView.indicatorStyle = .white } else { webView.scrollView.indicatorStyle = .black } } private static let css = try! String(contentsOf: Bundle.main.url(forResource: "read", withExtension: "css")!) private static let js = try! String(contentsOf: Bundle.main.url(forResource: "read", withExtension: "js")!) private func itemContentHTML() -> String? { guard let content = item.content else { return nil } var info = "" if let title = item.title, !title.isEmpty { info += "