| ... | @@ -0,0 +1,80 @@ |
| 1 | const { withMainActivity } = require("expo/config-plugins"); |
| 2 | |
| 3 | // The top-level `orientation: "portrait"` writes android:screenOrientation="portrait" |
| 4 | // into the manifest, which locks every Android device — including tablets — to |
| 5 | // portrait. iOS doesn't have this problem: iPads must support all orientations |
| 6 | // because the app is multitasking-capable, so only iPhones end up portrait-only. |
| 7 | // Mirror that split on Android: keep the manifest lock for phones and lift it at |
| 8 | // runtime on tablets (smallest width >= 600dp, the standard tablet breakpoint), |
| 9 | // since requestedOrientation set at runtime overrides the manifest value. |
| 10 | // FULL_USER allows all four orientations while still respecting the user's |
| 11 | // auto-rotate lock, matching iPad behavior. Foldables change |
| 12 | // smallestScreenWidthDp on fold/unfold without recreating the activity |
| 13 | // (smallestScreenSize is in the manifest's configChanges), so the policy is |
| 14 | // re-evaluated in onConfigurationChanged: unfolding past the tablet breakpoint |
| 15 | // unlocks rotation, and folding back restores the portrait lock. |
| 16 | |
| 17 | const ORIENTATION_METHODS = ` |
| 18 | // Applied in onCreate and re-applied on fold/unfold; added by |
| 19 | // withAndroidTabletOrientation. |
| 20 | override fun onConfigurationChanged(newConfig: Configuration) { |
| 21 | super.onConfigurationChanged(newConfig) |
| 22 | applyTabletOrientation() |
| 23 | } |
| 24 | |
| 25 | private fun applyTabletOrientation() { |
| 26 | requestedOrientation = if (resources.configuration.smallestScreenWidthDp >= 600) { |
| 27 | ActivityInfo.SCREEN_ORIENTATION_FULL_USER |
| 28 | } else { |
| 29 | ActivityInfo.SCREEN_ORIENTATION_PORTRAIT |
| 30 | } |
| 31 | } |
| 32 | `; |
| 33 | |
| 34 | const ORIENTATION_ON_CREATE_CALL = ` |
| 35 | applyTabletOrientation()`; |
| 36 | |
| 37 | function insertAfter(contents, anchor, insertion, description) { |
| 38 | const index = contents.indexOf(anchor); |
| 39 | if (index === -1) { |
| 40 | throw new Error( |
| 41 | `withAndroidTabletOrientation: could not find ${description} in MainActivity — the Expo template changed; update the plugin anchors.`, |
| 42 | ); |
| 43 | } |
| 44 | const end = index + anchor.length; |
| 45 | return contents.slice(0, end) + insertion + contents.slice(end); |
| 46 | } |
| 47 | |
| 48 | module.exports = function withAndroidTabletOrientation(config) { |
| 49 | return withMainActivity(config, (nextConfig) => { |
| 50 | let contents = nextConfig.modResults.contents; |
| 51 | if (nextConfig.modResults.language !== "kt") { |
| 52 | throw new Error("withAndroidTabletOrientation: MainActivity must be Kotlin."); |
| 53 | } |
| 54 | if (contents.includes("SCREEN_ORIENTATION_FULL_USER")) { |
| 55 | return nextConfig; |
| 56 | } |
| 57 | |
| 58 | contents = insertAfter( |
| 59 | contents, |
| 60 | "import android.os.Bundle", |
| 61 | "\nimport android.content.pm.ActivityInfo\nimport android.content.res.Configuration", |
| 62 | "the android.os.Bundle import", |
| 63 | ); |
| 64 | contents = insertAfter( |
| 65 | contents, |
| 66 | "class MainActivity : ReactActivity() {", |
| 67 | ORIENTATION_METHODS, |
| 68 | "the MainActivity class declaration", |
| 69 | ); |
| 70 | contents = insertAfter( |
| 71 | contents, |
| 72 | "super.onCreate(null)", |
| 73 | ORIENTATION_ON_CREATE_CALL, |
| 74 | "the super.onCreate call", |
| 75 | ); |
| 76 | |
| 77 | nextConfig.modResults.contents = contents; |
| 78 | return nextConfig; |
| 79 | }); |
| 80 | }; |