Skip to content

Commit a63f410

Browse files
committed
Sync Android viewport on external rotation
1 parent 0f591cd commit a63f410

1 file changed

Lines changed: 92 additions & 6 deletions

File tree

client/src/app/AppShell.tsx

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ import {
105105
const ACCESSIBILITY_REFRESH_MS = 1500;
106106
const REACT_NATIVE_ACCESSIBILITY_REFRESH_MS = 500;
107107
const FLUTTER_ACCESSIBILITY_REFRESH_MS = 1000;
108+
const ANDROID_METADATA_REFRESH_MS = 1000;
108109
const DEFAULT_ACCESSIBILITY_MAX_DEPTH = 10;
109110
const LOGICAL_INSPECTOR_MAX_DEPTH = 80;
110111
const FLUTTER_INSPECTOR_MAX_DEPTH = 48;
@@ -457,8 +458,12 @@ export function AppShell({
457458
udid: string;
458459
} | null>(null);
459460
const touchMoveFrameRef = useRef(0);
461+
const refreshRef = useRef(refresh);
462+
const previousAndroidDisplayKeyRef = useRef("");
463+
const previousAndroidViewportSizeKeyRef = useRef("");
460464
const canvasSize = useElementSize(outerCanvasElement);
461465
const zoomDockSize = useElementSize(zoomDockElement);
466+
refreshRef.current = refresh;
462467

463468
const handleOuterCanvasRef = useCallback((node: HTMLDivElement | null) => {
464469
outerCanvasRef.current = node;
@@ -689,10 +694,14 @@ export function AppShell({
689694
selectedSimulator != null && shouldRenderNativeChrome(selectedSimulator);
690695
const viewportChromeProfile = shouldRenderChrome ? chromeProfile : null;
691696
const isAndroidViewport = isAndroidSimulator(selectedSimulator);
697+
const androidDisplayKey =
698+
isAndroidViewport && selectedSimulator
699+
? androidDisplayKeyForSimulator(selectedSimulator)
700+
: "";
692701
const effectiveDeviceNaturalSize = useMemo(() => {
693702
const displaySize = simulatorDisplaySize(selectedSimulator);
694-
if (isAndroidViewport && displaySize) {
695-
return displaySize;
703+
if (isAndroidViewport) {
704+
return deviceNaturalSize ?? displaySize;
696705
}
697706
return (
698707
deviceNaturalSize ??
@@ -710,6 +719,10 @@ export function AppShell({
710719
selectedSimulator,
711720
shouldRenderChrome,
712721
]);
722+
const androidViewportSizeKey =
723+
isAndroidViewport && effectiveDeviceNaturalSize
724+
? `${Math.round(effectiveDeviceNaturalSize.width)}x${Math.round(effectiveDeviceNaturalSize.height)}`
725+
: "";
713726

714727
const zoomDockReservedHeight =
715728
zoomDockElement && typeof window !== "undefined"
@@ -1065,6 +1078,55 @@ export function AppShell({
10651078
});
10661079
}, [isAndroidViewport, simulatorRotationQuarterTurns]);
10671080

1081+
useEffect(() => {
1082+
if (!isAndroidViewport || !selectedSimulator?.isBooted) {
1083+
return;
1084+
}
1085+
1086+
let cancelled = false;
1087+
const refreshAndroidMetadata = () => {
1088+
if (cancelled || document.visibilityState !== "visible") {
1089+
return;
1090+
}
1091+
void refreshRef.current();
1092+
};
1093+
1094+
const intervalId = window.setInterval(
1095+
refreshAndroidMetadata,
1096+
ANDROID_METADATA_REFRESH_MS,
1097+
);
1098+
return () => {
1099+
cancelled = true;
1100+
window.clearInterval(intervalId);
1101+
};
1102+
}, [isAndroidViewport, selectedSimulator?.isBooted, selectedSimulator?.udid]);
1103+
1104+
useEffect(() => {
1105+
if (!isAndroidViewport || !androidDisplayKey) {
1106+
previousAndroidDisplayKeyRef.current = "";
1107+
return;
1108+
}
1109+
1110+
const previousKey = previousAndroidDisplayKeyRef.current;
1111+
previousAndroidDisplayKeyRef.current = androidDisplayKey;
1112+
if (previousKey && previousKey !== androidDisplayKey) {
1113+
beginZoomAnimation();
1114+
}
1115+
}, [androidDisplayKey, isAndroidViewport]);
1116+
1117+
useEffect(() => {
1118+
if (!isAndroidViewport || !androidViewportSizeKey) {
1119+
previousAndroidViewportSizeKeyRef.current = "";
1120+
return;
1121+
}
1122+
1123+
const previousKey = previousAndroidViewportSizeKeyRef.current;
1124+
previousAndroidViewportSizeKeyRef.current = androidViewportSizeKey;
1125+
if (previousKey && previousKey !== androidViewportSizeKey) {
1126+
beginZoomAnimation();
1127+
}
1128+
}, [androidViewportSizeKey, isAndroidViewport]);
1129+
10681130
useEffect(() => {
10691131
setChromeLoaded(!chromeRequired);
10701132
}, [chromeRequired, chromeUrl]);
@@ -1096,7 +1158,12 @@ export function AppShell({
10961158
return () => {
10971159
cancelled = true;
10981160
};
1099-
}, [selectedSimulator?.udid]);
1161+
}, [
1162+
selectedSimulator?.privateDisplay?.displayHeight,
1163+
selectedSimulator?.privateDisplay?.displayWidth,
1164+
selectedSimulator?.privateDisplay?.rotationQuarterTurns,
1165+
selectedSimulator?.udid,
1166+
]);
11001167

11011168
useEffect(() => {
11021169
if (!menuOpen) {
@@ -1329,7 +1396,7 @@ export function AppShell({
13291396
const screenOnlyStyle =
13301397
!viewportChromeProfile && chromeProfile && chromeProfile.screenWidth > 0
13311398
? isAndroidViewport
1332-
? androidScreenRadiusStyle(chromeProfile)
1399+
? androidScreenRadiusStyle(chromeProfile, effectiveDeviceNaturalSize)
13331400
: ({
13341401
borderRadius: `${Math.min(
13351402
chromeProfile.cornerRadius *
@@ -1906,6 +1973,7 @@ export function AppShell({
19061973
void runAction(async () => {
19071974
await rotateRight(selectedSimulator.udid);
19081975
setRotationQuarterTurns(0);
1976+
beginZoomAnimation();
19091977
await refresh();
19101978
}, false);
19111979
return;
@@ -2101,12 +2169,17 @@ export function AppShell({
21012169

21022170
function androidScreenRadiusStyle(
21032171
chromeProfile: ChromeProfile,
2172+
displaySize: Size | null,
21042173
): CSSProperties | null {
2105-
if (chromeProfile.screenWidth <= 0) {
2174+
const screenWidth =
2175+
displaySize && displaySize.width > 0
2176+
? displaySize.width
2177+
: chromeProfile.screenWidth;
2178+
if (screenWidth <= 0) {
21062179
return null;
21072180
}
21082181

2109-
const scale = DEVICE_SCREEN_WIDTH / chromeProfile.screenWidth;
2182+
const scale = DEVICE_SCREEN_WIDTH / screenWidth;
21102183
const maxRadius = DEVICE_SCREEN_WIDTH / 2;
21112184
const radii = chromeProfile.cornerRadii;
21122185
const topLeft = scaledScreenRadius(
@@ -2152,6 +2225,19 @@ function scaledScreenRadius(radius: number, scale: number, maxRadius: number) {
21522225
return Math.min(radius * scale, maxRadius);
21532226
}
21542227

2228+
function androidDisplayKeyForSimulator(simulator: SimulatorMetadata): string {
2229+
const display = simulator.privateDisplay;
2230+
if (!display) {
2231+
return simulator.udid;
2232+
}
2233+
return [
2234+
simulator.udid,
2235+
Math.round(display.displayWidth),
2236+
Math.round(display.displayHeight),
2237+
display.rotationQuarterTurns ?? 0,
2238+
].join("|");
2239+
}
2240+
21552241
function readDeviceQueryParam(): string | undefined {
21562242
if (typeof window === "undefined") {
21572243
return undefined;

0 commit comments

Comments
 (0)