Repository navigation
Add DisplayFeatures plugin: size class, pose, and reserved regions (iOS, iPhone Duo) - #8597
saqibkayani2077 wants to merge 2 commits into
Conversation
WKWebView already gets safe-area CSS and resizes with the window, so
basic iPhone Duo layout needs no plugin. This exposes what CSS can't:
active size class (the HIG's primary outer/inner signal), device pose,
and reserved-region frames (camera/fold), via getDisplayFeatures() and
a displayFeaturesChanged listener.
Pose/region data comes from a DisplayFeaturesProvider. The default
UnsupportedDisplayFeaturesProvider reports supported: false and never
guesses, because the iOS 27.1 SDK that ships the reserved-region APIs
(per Apple Tech Talk 111463) is not public yet - Apple lists Xcode 27.1
beta as "coming later this month" as of 2026-09-10. Size classes,
safe-area insets and bounds are real values on every iOS device today.
Android is unimplemented (rejects as Unimplemented).
Verified: `npm run test` (ts-jest, from core/) - all 5 tests pass:
DisplayFeatures
✓ getDisplayFeatures proxies to the native implementation and returns its result untouched
✓ addListener on native returns a removable handle for displayFeaturesChanged
✓ delivers displayFeaturesChanged state to listeners
✓ rejects as unavailable on web
✓ rejects as unimplemented on a native platform without the plugin
ts-jest type-checks as part of running, so the TS API surface (core-plugins.ts,
index.ts) is now type-verified, not just written. UNVERIFIED: the Swift
plugin itself - no Xcode in this environment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAkF81dVyenmpEChH2Nh1k
|
This is intriguing! I haven't looked this over in detail yet, but have you thought about adding Android support for similar display features? |
|
Great suggestion, Joey! Android is actually in a much better position than iOS here: I'm going to add real Android support mapping |
Following up on review feedback on this PR (theproducer/Joey Pender):
adds the Android side of the DisplayFeatures plugin, backed by
androidx.window's FoldingFeature API - stable since 2021 and in
production on real foldables (Samsung Galaxy Z Fold, and, while it
shipped, the Microsoft Surface Duo). Unlike the iOS half of this PR,
this is fully compiled and tested against the real SDK, not just
written.
Cross-platform shape reconciliation: DisplayFeaturesState was designed
around iPhone Duo's two-named-displays/UIKit-trait model, so several
fields have no honest Android equivalent:
- activeDisplay ('inner'/'outer'): no Android analogue for a single
Activity's window - always 'unknown'.
- horizontalSizeClass/verticalSizeClass: a UIKit trait-collection
concept. Android has an analogous but differently-shaped system
(androidx.window.core.layout.WindowSizeClass: three tiers, width and
height reported separately) that's deliberately out of scope here to
keep this change focused on fold detection - always 'unspecified'.
- reservedRegions/pose reuse the existing shape as-is: a separating
FoldingFeature maps to a 'fold'-type region and 'partiallyOpen'
(HALF_OPENED) or 'open' (FLAT) pose. A non-separating fold (e.g. some
tabletop postures) isn't reported as a region, since it isn't meant to
divide content. No FoldingFeature at all reports 'unknown' pose rather
than guessing 'open', since WindowLayoutInfo can't distinguish "flat,
nothing to report" from "not a foldable device" from "on hardware this
API doesn't cover".
- safeAreaInsets/bounds are computed for real via WindowInsetsCompat and
the WebView's measured size, mirroring how SystemBars already computes
safe-area insets on this codebase.
getDisplayFeatures() registers a one-shot WindowInfoTracker listener and
resolves on its first callback rather than reading a cached value, so it
can't race with plugin/WebView startup ordering. The persistent listener
from handleOnStart() drives displayFeaturesChanged separately.
Verified for real, not just written:
`./gradlew clean lint build test -b capacitor/build.gradle` (the exact
command this repo's own CI runs) - BUILD SUCCESSFUL. Lint: no new issues
against the existing baseline. Both testDebugUnitTest and
testReleaseUnitTest pass, including 5 new DisplayFeaturesTest cases.
Two real bugs found only by actually compiling/running this against the
SDK, not by review:
1. WindowInfoTrackerCallbackAdapter's listener methods take
androidx.core.util.Consumer, not java.util.function.Consumer - a
real javac error, not a style choice.
2. Rect.width()/height() throw "not mocked" under Android's unit-test
stub jar; rewrote to use (right - left)/(bottom - top) field
arithmetic instead, which is unaffected and equally correct. Also
found the stub jar's Rect 4-arg constructor doesn't reliably persist
field values either - the test fixture builds Rects via direct field
assignment instead of the constructor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAkF81dVyenmpEChH2Nh1k
|
Android support is up. A few notes on how it turned out: This one is fully compiled and tested against the real SDK (unlike the iOS half, which I flagged as unverified) — Backed by Two real bugs only showed up once I actually compiled it — |
|
For anyone who needs it today, @erkamyaman/capacitor-foldable (npm) covers all of this for Capacitor 7 and 8.
It reports fold state, posture, hinge position and angle, size classes, and where iPhone Duo moves native bars, with change events for each. |
What
Adds a
DisplayFeaturesplugin exposing native display metadata that web content can't read from CSS alone: active size class, device pose, and reserved-region frames (hinge/camera) — starting with iPhone Duo, Apple's first foldable iPhone, announced September 9, 2026.WKWebView already resizes with the window and exposes
env(safe-area-inset-*)to CSS, so basic layout adaptation on iPhone Duo works without this plugin. This is for the cases JS needs to decide something CSS can't express: which size class is active (the HIG's primary outer/inner-display signal), whether the device is partially folded, and where the folding region or an active camera region sits.Why
The reserved-region/pose model follows Apple's Tech Talk 111463 ("Strike a pose with adaptive layouts on iPhone Duo") and the Designing for iPhone Duo HIG.
Current limitation — please read before reviewing
The iOS 27.1 SDK that ships the underlying reserved-region APIs (
UIView.reservedRegions(kind:)) is not publicly available yet — Apple lists Xcode 27.1 beta as "coming later this month" as of 2026-09-10. So today,getDisplayFeatures()honestly reportssupported: falseand never guesses at pose/region data. Size classes, safe-area insets, and bounds are real values on every iOS device today. The nativeDisplayFeaturesProviderprotocol is structured so a real implementation can be dropped in once the SDK ships, without changing the public API.The Swift side is unverified — I don't have Xcode in the environment I used to write this, so
DisplayFeatures.swifthas never been compiled. Everything else has:Test plan
npm run test(ts-jest) fromcore/— all 5 tests pass:ts-jest type-checks as part of running, so the TS API surface is type-verified, not just written.
Disclosure
This PR was written with AI assistance (Claude Code), directed and reviewed by me. Happy to answer questions, adjust the approach, or close this if a different direction (e.g. extending an existing plugin, or waiting for the SDK to be public) is preferred.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LAkF81dVyenmpEChH2Nh1k