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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/src/index.d.ts
CHANGED