feature: OrientationChanged event + System::orientation() — react to device rotation from PHP - #371
feature: OrientationChanged event + System::orientation() — react to device rotation from PHP#371SRWieZ wants to merge 2 commits into
Conversation
…ation from PHP
Mirrors the AppearanceChanged pattern end to end: native pushes an
OrientationChanged event ('portrait' | 'landscape') when the device
rotates, a NativeServiceProvider listener keeps System's process cache
fresh, and System.GetOrientation backs the cold read before the first
push. Android emits from onConfigurationChanged (the manifest already
routes rotation there); iOS derives orientation from the window aspect
so it tracks what layout actually sees. Only fires when the app enables
a second orientation in config/nativephp.php.
Claude-Session: https://claude.ai/code/session_015kkVBULWwHPzWccgz4zLVe
gwleuverink
left a comment
There was a problem hiding this comment.
Great addition, this one was missing. Verified on an Android emulator and the iOS simulator: works as described on EDGE screens, one event per rotation, clean cold read, no false positives on theme flips.
Two cases where it goes wrong, both measured:
iPad keyboard. With ipad => true, focusing a text field in portrait emits landscape, hiding the keyboard emits portrait. The GeometryReader measures the safe-area frame, which the keyboard shrinks below square on an iPad (1024x1366 → 1024x963); GetOrientation reads window.bounds, so the two disagree while the keyboard is up, and a rotation with the keyboard open leaves System::orientation() stuck on landscape. Read the window (or interfaceOrientation) on both sides, or put .ignoresSafeArea(.keyboard) on the reader. iPhone is unaffected.
Android multi-window. A resize that changes smallestScreenSize or screenLayout recreates the activity instead of calling onConfigurationChanged (the manifest only declares uiMode|colorMode|orientation|screenSize), so nothing is pushed and onCreate re-seeds lastOrientation silently. PHP keeps the stale value for the rest of the process. Keeping lastOrientation in the companion object and emitting from onCreate when the fresh read differs covers it.
Smaller:
- Docblock: both platforms measure the window aspect, not the device. Worth saying, and the Swift
GetOrientationcomment "same signal the push uses" is not true. .onChange(of:) { size in }is the iOS 17-deprecated form (target is 18.2; the colorScheme twin has it too).- Nothing covers the provider listener: deleting the
Event::listenleaves the suite green for both events. - Docs need a follow-up, orientation has no page yet.
Merge after #379: on Android a rotation on a webview screen never reaches PHP (the #360 gap, inherited from AppearanceChanged) and the cache then stays wrong process-wide. #379 routes those events through /_native/api/events, which dispatches through the provider listener, so that closes on its own.
What this does
When the app window changes between portrait and landscape, PHP is notified, so a screen can render a different layout for each orientation.
Until now, an app that allows both orientations would reflow natively when its window changed, but the PHP side never knew it happened. There was no way to say "in landscape, put the score tiles side by side".
How you use it
React to an orientation change in any screen:
Or ask at any time:
How it works
It follows the existing
AppearanceChangedpattern end to end:onConfigurationChanged. The last orientation lives in the companion object, so an activity recreation caused by multi-window resizing compares the fresh configuration inonCreate()and emits when PHP's process cache would otherwise remain stale.onChangeform.NativeServiceProviderkeepsSystem's cached value fresh, so reads avoid another bridge round-trip after the first one.System.GetOrientationbacks the first read, before any orientation event has arrived.Good to know
config/nativephp.php.'portrait' | 'landscape'.BroadcastsGlobally, it also reaches app-wideEvent::listen()listeners.Tests
Full suite: 902 passed. Pint and PHPStan clean.