expo-rotation-module 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,7 +49,7 @@ import Rotation, {
49
49
 
50
50
  - `canWrite(): Promise<boolean>` — returns true if the app has `WRITE_SETTINGS` granted (Android M+). Returns `true` on older OS versions.
51
51
  - `requestWritePermission(): void` — opens the Android Settings screen where the user can grant the permission for your app.
52
- - `getRotationState(): Promise<'AUTOROTATE'|'PORTRAIT'|'LANDSCAPE'>` — reads the current global rotation state.
52
+ - `getRotationState(): Promise<'AUTOROTATE'|'PORTRAIT'|'LANDSCAPE'>` — reads the current global rotation state. The module reports the coarse axis (PORTRAIT or LANDSCAPE); within those axes the system sensor still allows regular or inverted orientations when auto-rotate is off.
53
53
  - `setRotationState(state): Promise<void>` — sets the rotation state. Rejects with an Error that may contain a `code` property (e.g. `E_PERMISSION`).
54
54
  - `getPackageName(): Promise<string>` — returns the package name the native module is using (useful for diagnostics).
55
55
 
@@ -2,11 +2,13 @@ package ktsierra.expo.rotationmodule
2
2
 
3
3
  import android.app.Activity
4
4
  import android.content.ContentResolver
5
+ import android.content.Context
5
6
  import android.content.Intent
6
7
  import android.net.Uri
7
8
  import android.os.Build
8
9
  import android.provider.Settings
9
10
  import android.util.Log
11
+ import android.content.pm.ActivityInfo
10
12
  import expo.modules.kotlin.modules.Module
11
13
  import expo.modules.kotlin.modules.ModuleDefinition
12
14
 
@@ -16,6 +18,10 @@ class ExpoRotationModule : Module() {
16
18
  const val TAG = "ExpoRotationModule"
17
19
  }
18
20
 
21
+ private var orientationListener: android.view.OrientationEventListener? = null
22
+ private var desiredAxis: String? = null
23
+ private var lastWrittenRotation: Int = -1
24
+
19
25
  override fun definition() = ModuleDefinition {
20
26
  Name(NAME)
21
27
 
@@ -34,6 +40,20 @@ class ExpoRotationModule : Module() {
34
40
  }
35
41
  }
36
42
 
43
+ // Expose helpers (optional)
44
+ Function("startOrientationListener") {
45
+ val ctx = appContext.activityProvider?.currentActivity ?: appContext.reactContext!!
46
+ startOrientationListener(ctx)
47
+ return@Function null
48
+ }
49
+
50
+ Function("stopOrientationListener") {
51
+ orientationListener?.disable()
52
+ orientationListener = null
53
+ desiredAxis = null
54
+ return@Function null
55
+ }
56
+
37
57
  Function("requestWritePermission") {
38
58
  try {
39
59
  val activity: Activity? = appContext.activityProvider?.currentActivity
@@ -86,9 +106,8 @@ class ExpoRotationModule : Module() {
86
106
  0
87
107
  }
88
108
  return@AsyncFunction when (rotation) {
89
- 0 -> "PORTRAIT"
90
- 1 -> "LANDSCAPE"
91
- 3 -> "LANDSCAPE"
109
+ 0, 2 -> "PORTRAIT"
110
+ 1, 3 -> "LANDSCAPE"
92
111
  else -> "PORTRAIT"
93
112
  }
94
113
  }
@@ -104,23 +123,36 @@ class ExpoRotationModule : Module() {
104
123
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.System.canWrite(ctx)) {
105
124
  throw Exception("E_PERMISSION: WRITE_SETTINGS not granted")
106
125
  }
107
- val resolver: ContentResolver = ctx.contentResolver
126
+
108
127
  when (state) {
109
128
  "AUTOROTATE" -> {
129
+ // Restore system auto-rotate
130
+ val resolver: ContentResolver = ctx.contentResolver
110
131
  Settings.System.putInt(resolver, Settings.System.ACCELEROMETER_ROTATION, 1)
132
+ // unregister listener if present
133
+ orientationListener?.disable()
134
+ orientationListener = null
135
+ desiredAxis = null
136
+ lastWrittenRotation = -1
111
137
  }
112
138
  "PORTRAIT" -> {
139
+ // disable system auto-rotate and enable axis-lock with sensor flips on portrait axis
140
+ val resolver: ContentResolver = ctx.contentResolver
113
141
  Settings.System.putInt(resolver, Settings.System.ACCELEROMETER_ROTATION, 0)
114
- Settings.System.putInt(resolver, Settings.System.USER_ROTATION, 0)
142
+ desiredAxis = "PORTRAIT"
143
+ startOrientationListener(ctx)
115
144
  }
116
145
  "LANDSCAPE" -> {
146
+ val resolver: ContentResolver = ctx.contentResolver
117
147
  Settings.System.putInt(resolver, Settings.System.ACCELEROMETER_ROTATION, 0)
118
- Settings.System.putInt(resolver, Settings.System.USER_ROTATION, 1)
148
+ desiredAxis = "LANDSCAPE"
149
+ startOrientationListener(ctx)
119
150
  }
120
151
  else -> {
121
152
  throw Exception("E_INVALID_STATE: Invalid rotation state: $state")
122
153
  }
123
154
  }
155
+
124
156
  return@AsyncFunction null
125
157
  } catch (e: Exception) {
126
158
  Log.e(TAG, "setRotationState error", e)
@@ -128,4 +160,51 @@ class ExpoRotationModule : Module() {
128
160
  }
129
161
  }
130
162
  }
163
+
164
+ private fun startOrientationListener(ctx: Context) {
165
+ // If listener already exists, do nothing
166
+ if (orientationListener != null) return
167
+
168
+ orientationListener = object : android.view.OrientationEventListener(ctx) {
169
+ override fun onOrientationChanged(orientation: Int) {
170
+ // orientation: 0..359 degrees, or ORIENTATION_UNKNOWN (-1)
171
+ if (orientation == android.view.OrientationEventListener.ORIENTATION_UNKNOWN) return
172
+ // Normalize to one of the 4 Android rotations: 0, 90, 180, 270
173
+ val rot = when {
174
+ orientation in 315..359 || orientation in 0..44 -> 0
175
+ orientation in 45..134 -> 1
176
+ orientation in 135..224 -> 2
177
+ else -> 3
178
+ }
179
+
180
+ try {
181
+ val resolver: ContentResolver = ctx.contentResolver
182
+ // desiredAxis controls whether we want portrait or landscape axis.
183
+ when (desiredAxis) {
184
+ "PORTRAIT" -> {
185
+ // We want 0 or 2 depending on angle
186
+ val target = if (rot == 2) 2 else 0
187
+ if (lastWrittenRotation != target) {
188
+ Settings.System.putInt(resolver, Settings.System.USER_ROTATION, target)
189
+ lastWrittenRotation = target
190
+ }
191
+ }
192
+ "LANDSCAPE" -> {
193
+ // We want 1 or 3 depending on angle
194
+ val target = if (rot == 1) 3 else if (rot == 3) 1 else if (rot == 2) 1 else 3
195
+ // swapped mapping: 90 -> 3, 270 -> 1, fallback 180->1, 0->3
196
+ if (lastWrittenRotation != target) {
197
+ Settings.System.putInt(resolver, Settings.System.USER_ROTATION, target)
198
+ lastWrittenRotation = target
199
+ }
200
+ }
201
+ }
202
+ } catch (e: Exception) {
203
+ Log.e(TAG, "orientation listener write error", e)
204
+ }
205
+ }
206
+ }
207
+
208
+ orientationListener?.enable()
209
+ }
131
210
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-rotation-module",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Screen Orientation Native Module",
5
5
  "main": "index.js",
6
6
  "types": "src/index.d.ts",
package/src/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type RotationState = "AUTOROTATE" | "PORTRAIT" | "LANDSCAPE";
1
+ export type RotationState = 'AUTOROTATE' | 'PORTRAIT' | 'LANDSCAPE';
2
2
 
3
3
  export function canWrite(): Promise<boolean>;
4
4
  export function requestWritePermission(): void;