Skip to content

Commit

Permalink
Always show TransitAlertDetailViewController when tapping on a transi…
Browse files Browse the repository at this point in the history
…t alert

Fixes #700

We now show the full content of the transit alert's body in a popup view controller, and optionally include a "Learn More" button at the bottom of it if the transit alert includes a URL.
  • Loading branch information
aaronbrethorst committed Dec 15, 2023
1 parent 465e419 commit 44abd67
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 81 deletions.
75 changes: 64 additions & 11 deletions OBAKit/Alerts/TransitAlertDetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@

import OBAKitCore
import UIKit
import WebKit
import SafariServices

class TransitAlertDetailViewController: UIViewController {
private let transitAlert: TransitAlertViewModel
private let webView = DocumentWebView()
/// Renders a full page version of a `TransitAlertViewModel`
///
/// This includes an optional "Learn More" button at the bottom of the page if the transit alert has a value for `url(forLocale:)`.
class TransitAlertDetailViewController: UIViewController, WKScriptMessageHandler {
private let locale: Locale

init(_ transitAlert: TransitAlertViewModel) {
init(_ transitAlert: TransitAlertViewModel, locale: Locale = .current) {
self.transitAlert = transitAlert
self.locale = locale
super.init(nibName: nil, bundle: nil)

self.title = Strings.serviceAlert
Expand All @@ -22,19 +27,20 @@ class TransitAlertDetailViewController: UIViewController {
}

override func viewDidLoad() {
webView.frame = view.bounds
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: closeButton)

view.addSubview(webView)

let title = transitAlert.title(forLocale: .current) ?? Strings.serviceAlert
let body = transitAlert.body(forLocale: .current) ?? OBALoc("transit_alert.no_additional_details.body", value: "No additional details available.", comment: "A notice when a transit alert doesn't have body text.")
let title = transitAlert.title(forLocale: locale) ?? Strings.serviceAlert
var body = transitAlert.body(forLocale: locale) ?? OBALoc("transit_alert.no_additional_details.body", value: "No additional details available.", comment: "A notice when a transit alert doesn't have body text.")
body = body.replacingOccurrences(of: "\n", with: "<br>")

let html = """
<h1>\(title)</h1>
<p>\(body)</p>
<h1 class='title'>\(title)</h1>
<p class='body'>\(body)</p>
"""

webView.setPageContent(html)
webView.setPageContent(html, actionButtonTitle: destinationURL != nil ? Strings.learnMore : nil)
}

override func viewWillAppear(_ animated: Bool) {
Expand All @@ -43,6 +49,53 @@ class TransitAlertDetailViewController: UIViewController {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: Strings.close, style: .done, target: self, action: #selector(close))
}

// MARK: - Transit Alert

private let transitAlert: TransitAlertViewModel

private var destinationURL: URL? {
transitAlert.url(forLocale: locale)
}

// MARK: - Web View

private lazy var webView: DocumentWebView = {
let configuration = WKWebViewConfiguration()
let userContentController = WKUserContentController()
userContentController.add(self, name: DocumentWebView.actionButtonHandlerName)
configuration.userContentController = userContentController

let view = DocumentWebView(frame: view.bounds, configuration: configuration)
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]

if #available(iOS 16.4, *) {
view.isInspectable = true
}

return view
}()

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard
message.name == DocumentWebView.actionButtonHandlerName,
let destinationURL
else {
return
}

let safari = SFSafariViewController(url: destinationURL)
present(safari, animated: true)
}

// MARK: - Close

private lazy var closeButton: UIButton = {
let btn = UIButton.buildCloseButton()
btn.addTarget(self, action: #selector(close), for: .touchUpInside)

return btn
}()

@objc func close() {
if let navController = self.navigationController, navController.topViewController != self {
navController.popViewController(animated: true)
Expand Down
10 changes: 3 additions & 7 deletions OBAKit/ViewRouting/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,9 @@ public class ViewRouter: NSObject, UINavigationControllerDelegate {
public func navigateTo(alert: TransitAlertViewModel, locale: Locale = .current, from fromController: UIViewController) {
guard shouldNavigate(from: fromController, to: .transitAlert(alert)) else { return }

if let url = alert.url(forLocale: locale) {
let safari = SFSafariViewController(url: url)
present(safari, from: fromController, isModal: true)
} else {
let view = TransitAlertDetailViewController(alert)
present(view, from: fromController)
}
let view = TransitAlertDetailViewController(alert, locale: locale)
let navigationController = UINavigationController(rootViewController: view)
present(navigationController, from: fromController)
}

// MARK: - Helpers
Expand Down
84 changes: 33 additions & 51 deletions OBAKit/WebView/DocumentWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,64 +16,46 @@ import UIKit
/// HTML fragment with an HTML document, allowing for comfortable reading on a phone.
class DocumentWebView: WKWebView {

static let actionButtonHandlerName = "actionButtonClicked"

/// Pass along either a plain string or an HTML fragment to render it in the web view.
///
///
/// Example: You can pass in either values like "hello world" or `"<h1>Hello</h1><p>World</p>"`
///
///
/// - Parameter htmlFragment: The content to render in the web view.
func setPageContent(_ htmlFragment: String) {
let content = pageBody.replacingOccurrences(of: "{{{oba_page_content}}}", with: htmlFragment)
/// - Parameter actionButtonTitle: The title of the optional button shown at the bottom of the web view.
func setPageContent(_ htmlFragment: String, actionButtonTitle: String? = nil) {
var content = pageBody.replacingOccurrences(of: "{{{oba_page_content}}}", with: htmlFragment)
content = content.replacingOccurrences(of: "{{{accent_color}}}", with: accentHexColor)
content = content.replacingOccurrences(of: "{{{accent_foreground_color}}}", with: accentForegroundColor)

if let actionButtonTitle {
let buttonText = """
<div class="actions__button-container">
<button type="button" class="actions__button-container__button" onclick="window.webkit.messageHandlers.actionButtonClicked.postMessage({})">
\(actionButtonTitle)
</button>
</div>
"""
content = content.replacingOccurrences(of: "{{{oba_page_actions}}}", with: buttonText)
}

loadHTMLString(content, baseURL: nil)
}

private var pageBody: String {
"""
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<meta content='initial-scale=1.0, user-scalable=no' name='viewport'>
<style type='text/css'>
html {
overflow-x: hidden;
}
body {
-webkit-text-size-adjust: none;
font-family: system, -apple-system, "Helvetica Neue", Helvetica, sans-serif;
padding: 8px;
overflow-x: hidden;
background-color:#000;
color:#fff;
}
@media screen and (prefers-color-scheme:light) {
body {
background-color:#fff;
color:#000;
}
}
code, pre {
max-width: 300px;
overflow-x: hidden;
}
code h1 {
font-size: 14px;
}
private var accentForegroundColor: String {
let hex = UIColor.accentColor.contrastingTextColor.toHex!
return "#\(hex)"
}

h1 {
font-size: 18px;
}
private var accentHexColor: String {
let hex = UIColor.accentColor.toHex!
return "#\(hex)"
}

h2 {
font-size: 14px;
}
</style>
</head>
<body>
{{{oba_page_content}}}
</body>
</html>
"""
private var pageBody: String {
let frameworkBundle = Bundle(for: type(of: self))
let htmlPath = frameworkBundle.path(forResource: "document_web_view_content", ofType: "html")!
return try! String(contentsOfFile: htmlPath) // swiftlint:disable:this force_try
}
}
91 changes: 91 additions & 0 deletions OBAKit/WebView/document_web_view_content.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<!DOCTYPE html>

<html>

<head>
<meta content='initial-scale=1.0, user-scalable=no' name='viewport'>
<style type='text/css'>
:root {
color-scheme: light dark;
}

html {
overflow-x: hidden;
}

body {
-webkit-text-size-adjust: none;
font-family: system, -apple-system, "Helvetica Neue", Helvetica, sans-serif;
padding: 8px;
overflow-x: hidden;
}

@media screen and (prefers-color-scheme:light) {
body {
background-color: #fff;
color: #000;
}
}

@media screen and (prefers-color-scheme:dark) {
body {
background-color: #000;
color: #fff;
}
}

code,
pre {
max-width: 300px;
overflow-x: hidden;
}

code h1 {
font-size: 0.85rem;
}

h1 {
font-size: 1.25em;
line-height: 1.25;
}

h2 {
font-size: 0.85rem;
}

.page-content {
line-height: 1.5;
}

.actions {
margin-top: 8px;
}

.actions__button-container {
margin: 0 auto;
}

.actions__button-container__button {
background: {{{accent_color}}};
color: {{{accent_foreground_color}}};
display: block;
margin-bottom: 0.5rem;
border-radius: 8px;
font-size: 1.25rem;
width: 100%;
font-weight: 500;
padding: 0.75rem 0;
}
</style>
</head>

<body>
<div class='page-content'>
{{{oba_page_content}}}
</div>
<div class='actions'>
{{{oba_page_actions}}}
</div>
</body>

</html>
Loading

0 comments on commit 44abd67

Please sign in to comment.