iOS: presses are felt (#430)

The page asks the app for a haptic ({t:"haptic", kind}) and the app
plays the phone's own. A light tap for play, pause, flag and pull to
refresh; the system's success for marking read, one item or all; its
warning with an error toast. A browser has no host to ask, and an
older app ignores the message, so there is nothing to gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ray Slakinski
2026-11-08 16:57:51 -05:00
parent 2e031763fa
commit cf0dad7380
12 changed files with 40 additions and 9 deletions

View File

@@ -14,6 +14,8 @@ final class Bridge: NSObject, WKScriptMessageHandler {
private weak var webView: WKWebView?
private let playback: Playback
private let impact = UIImpactFeedbackGenerator(style: .light)
private let notice = UINotificationFeedbackGenerator()
/// The system's AirPlay picker, which has no API to open it but its own button: kept on the
/// web view, invisible, and pressed when the page's button is.
private lazy var routePicker: AVRoutePickerView = {
@@ -91,6 +93,7 @@ final class Bridge: NSObject, WKScriptMessageHandler {
case "clearKept": Offline.shared.removeAll()
case "sleep": playback.sleep(after: m["secs"] as? Double ?? 0)
case "route": showRoutes(m)
case "haptic": haptic(m["kind"] as? String)
default: break
}
}
@@ -110,6 +113,16 @@ final class Bridge: NSObject, WKScriptMessageHandler {
button.sendActions(for: primary ? .primaryActionTriggered : .touchUpInside)
}
/// The page's presses felt (#430): light for a tap, the system's success and warning for
/// something done and something gone wrong, as Mail and Messages use them.
private func haptic(_ kind: String?) {
switch kind {
case "done": notice.notificationOccurred(.success)
case "warn": notice.notificationOccurred(.warning)
default: impact.impactOccurred()
}
}
private static func controls(in v: UIView) -> [UIControl] {
v.subviews.flatMap { ($0 as? UIControl).map { [$0] } ?? controls(in: $0) }
}