t3-code-android-nightly/apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift
Julius Marminge c85dc64268
Some checks are pending
CI / Lint (push) Waiting to run
CI / Typecheck (push) Waiting to run
CI / Build (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test Web (push) Waiting to run
CI / Test Server 1 (push) Waiting to run
CI / Test Server 2 (push) Waiting to run
CI / Test Server 3 (push) Waiting to run
CI / Test Server 4 (push) Waiting to run
CI / Test Server 5 (push) Waiting to run
CI / Test Server 6 (push) Waiting to run
CI / Transfer report artifact (push) Blocked by required conditions
CI / Rust (push) Waiting to run
CI / Mobile Native Changes (push) Waiting to run
CI / Mobile Native Static Analysis (push) Blocked by required conditions
CI / Release Smoke (push) Waiting to run
CI / Check (push) Blocked by required conditions
Forward to Cursor hygiene / POST to Cursor (push) Waiting to run
Deploy T3 Connect relay / Deploy production relay (push) Waiting to run
Mobile EAS Production / EAS production (push) Waiting to run
feat(mobile): support Duo in the shared iOS app (#12648)
2026-10-09 16:02:19 -07:00

95 lines
3.3 KiB
Swift

import ExpoModulesCore
import UIKit
/// Reports geometry in the observing view's coordinate space, including system
/// reservations that safe-area rectangles cannot represent, such as the fold.
final class T3LayoutMetricsView: ExpoView {
let onMetricsChange = EventDispatcher()
private var lastMetrics: NSDictionary?
private var hasHinge: Bool? = false
required init(appContext: AppContext? = nil) {
super.init(appContext: appContext)
isUserInteractionEnabled = false
registerForTraitChanges([UITraitHorizontalSizeClass.self, UITraitVerticalSizeClass.self]) { (view: T3LayoutMetricsView, _: UITraitCollection) in
view.publishMetrics()
}
#if compiler(>=6.4)
if #available(iOS 27.1, *) {
hasHinge = nil
addInteraction(UIHingeInteraction { [weak self] _, update in
guard let self, self.window != nil else { return }
// Capability survives temporary detachment and does not follow the hinge angle.
let hasHinge = self.hasHinge == true || update.hinge != nil
guard self.hasHinge != hasHinge else { return }
self.hasHinge = hasHinge
self.publishMetrics()
})
registerForTraitChanges(UITraitCollection.systemTraitsAffectingVerticalBarEdge) { (view: T3LayoutMetricsView, _: UITraitCollection) in
view.publishMetrics()
}
}
#endif
}
override func didMoveToWindow() {
super.didMoveToWindow()
lastMetrics = nil
publishMetrics()
}
override func layoutSubviews() {
super.layoutSubviews()
publishMetrics()
}
override func safeAreaInsetsDidChange() {
super.safeAreaInsetsDidChange()
publishMetrics()
}
private func publishMetrics() {
guard let window, bounds.width > 0, bounds.height > 0, let hasHinge else { return }
if hasHinge {
T3WorkspaceOrientationSubscriber.allowHingeOrientations(in: window)
}
var verticalBarEdge = "none"
var regions: [[String: Any]] = []
#if compiler(>=6.4)
if #available(iOS 27.1, *) {
let rtl = effectiveUserInterfaceLayoutDirection == .rightToLeft
switch traitCollection.verticalBarEdge {
case .leading: verticalBarEdge = rtl ? "right" : "left"
case .trailing: verticalBarEdge = rtl ? "left" : "right"
default: break
}
for kind in [UIView.ReservedRegion.Kind.division, .occlusion] {
for region in reservedRegions(kind: kind) where region.isActive {
let frame = region.frame.intersection(bounds)
guard !frame.isNull, !frame.isEmpty else { continue }
regions.append([
"kind": kind == .division ? "division" : "occlusion",
"x": frame.minX, "y": frame.minY,
"width": frame.width, "height": frame.height,
])
}
}
}
#endif
let metrics: [String: Any] = [
"width": bounds.width, "height": bounds.height,
"hasHinge": hasHinge,
"horizontalSizeClass": traitCollection.horizontalSizeClass == .regular ? "regular" : "compact",
"verticalBarEdge": verticalBarEdge,
"safeArea": [
"top": safeAreaInsets.top, "bottom": safeAreaInsets.bottom,
"left": safeAreaInsets.left, "right": safeAreaInsets.right,
],
"reservedRegions": regions,
]
let next = metrics as NSDictionary
guard lastMetrics?.isEqual(next) != true else { return }
lastMetrics = next
onMetricsChange(metrics)
}
}