Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service alert features #701

Merged
merged 2 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
1 change: 0 additions & 1 deletion OBAKit/Donations/DonationsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ public class DonationsManager {
private let obacoService: ObacoAPIService?
private let analytics: Analytics?


// MARK: - User Defaults

private let userDefaults: UserDefaults
Expand Down
2 changes: 1 addition & 1 deletion OBAKit/Orchestration/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ public class Application: CoreApplication, PushServiceDelegate {
}

private var presentDonationUIOnActive = false
private var donationPromptID: String? = nil
private var donationPromptID: String?

public func pushService(_ pushService: PushService, receivedDonationPrompt id: String?) {
guard let topViewController else {
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
Loading