t3-code-android-nightly/apps/mobile/plugins/withAndroidTabletOrientation.cjs
2026-08-10 21:18:13 +02:00

80 lines
3 KiB
JavaScript

const { withMainActivity } = require("expo/config-plugins");
// The top-level `orientation: "portrait"` writes android:screenOrientation="portrait"
// into the manifest, which locks every Android device — including tablets — to
// portrait. iOS doesn't have this problem: iPads must support all orientations
// because the app is multitasking-capable, so only iPhones end up portrait-only.
// Mirror that split on Android: keep the manifest lock for phones and lift it at
// runtime on tablets (smallest width >= 600dp, the standard tablet breakpoint),
// since requestedOrientation set at runtime overrides the manifest value.
// FULL_USER allows all four orientations while still respecting the user's
// auto-rotate lock, matching iPad behavior. Foldables change
// smallestScreenWidthDp on fold/unfold without recreating the activity
// (smallestScreenSize is in the manifest's configChanges), so the policy is
// re-evaluated in onConfigurationChanged: unfolding past the tablet breakpoint
// unlocks rotation, and folding back restores the portrait lock.
const ORIENTATION_METHODS = `
// Applied in onCreate and re-applied on fold/unfold; added by
// withAndroidTabletOrientation.
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
applyTabletOrientation()
}
private fun applyTabletOrientation() {
requestedOrientation = if (resources.configuration.smallestScreenWidthDp >= 600) {
ActivityInfo.SCREEN_ORIENTATION_FULL_USER
} else {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}
`;
const ORIENTATION_ON_CREATE_CALL = `
applyTabletOrientation()`;
function insertAfter(contents, anchor, insertion, description) {
const index = contents.indexOf(anchor);
if (index === -1) {
throw new Error(
`withAndroidTabletOrientation: could not find ${description} in MainActivity — the Expo template changed; update the plugin anchors.`,
);
}
const end = index + anchor.length;
return contents.slice(0, end) + insertion + contents.slice(end);
}
module.exports = function withAndroidTabletOrientation(config) {
return withMainActivity(config, (nextConfig) => {
let contents = nextConfig.modResults.contents;
if (nextConfig.modResults.language !== "kt") {
throw new Error("withAndroidTabletOrientation: MainActivity must be Kotlin.");
}
if (contents.includes("SCREEN_ORIENTATION_FULL_USER")) {
return nextConfig;
}
contents = insertAfter(
contents,
"import android.os.Bundle",
"\nimport android.content.pm.ActivityInfo\nimport android.content.res.Configuration",
"the android.os.Bundle import",
);
contents = insertAfter(
contents,
"class MainActivity : ReactActivity() {",
ORIENTATION_METHODS,
"the MainActivity class declaration",
);
contents = insertAfter(
contents,
"super.onCreate(null)",
ORIENTATION_ON_CREATE_CALL,
"the super.onCreate call",
);
nextConfig.modResults.contents = contents;
return nextConfig;
});
};