react-native-orientation-director 1.2.3 → 2.0.0

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.
Files changed (76) hide show
  1. package/README.md +42 -18
  2. package/android/build.gradle +13 -1
  3. package/android/gradle.properties +12 -0
  4. package/android/src/main/java/com/orientationdirector/OrientationDirectorPackage.kt +5 -4
  5. package/android/src/main/java/com/orientationdirector/implementation/EventManager.kt +0 -1
  6. package/android/src/main/java/com/orientationdirector/implementation/LifecycleListener.kt +1 -1
  7. package/android/src/main/java/com/orientationdirector/implementation/Orientation.kt +2 -0
  8. package/android/src/main/java/com/orientationdirector/implementation/{OrientationDirectorImpl.kt → OrientationDirectorModuleImpl.kt} +47 -33
  9. package/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt +79 -0
  10. package/android/src/main/java/com/orientationdirector/implementation/Utils.kt +28 -15
  11. package/android/src/newarch/OrientationDirectorModule.kt +59 -0
  12. package/android/src/oldarch/OrientationDirectorModule.kt +66 -0
  13. package/android/src/test/java/com/orientationdirector/implementation/OrientationDirectorModuleImplTest.kt +188 -0
  14. package/android/src/test/java/com/orientationdirector/implementation/UtilsTest.kt +314 -0
  15. package/ios/OrientationDirector.mm +12 -37
  16. package/lib/commonjs/EventEmitter.js +50 -0
  17. package/lib/commonjs/EventEmitter.js.map +1 -0
  18. package/lib/commonjs/NativeOrientationDirector.js.map +1 -1
  19. package/lib/commonjs/RNOrientationDirector.js +21 -12
  20. package/lib/commonjs/RNOrientationDirector.js.map +1 -1
  21. package/lib/commonjs/hooks/useDeviceOrientation.hook.js +1 -1
  22. package/lib/commonjs/hooks/useDeviceOrientation.hook.js.map +1 -1
  23. package/lib/commonjs/hooks/useInterfaceOrientation.hook.js +1 -1
  24. package/lib/commonjs/hooks/useInterfaceOrientation.hook.js.map +1 -1
  25. package/lib/commonjs/hooks/useIsInterfaceOrientationLocked.hook.js +1 -1
  26. package/lib/commonjs/hooks/useIsInterfaceOrientationLocked.hook.js.map +1 -1
  27. package/lib/commonjs/index.js +1 -1
  28. package/lib/commonjs/index.js.map +1 -1
  29. package/lib/commonjs/module.js +2 -2
  30. package/lib/commonjs/module.js.map +1 -1
  31. package/lib/commonjs/types/{InterfaceOrientationToLocalizedStringProvider.type.js → HumanReadableAutoRotationsResource.type.js} +1 -1
  32. package/lib/commonjs/types/HumanReadableAutoRotationsResource.type.js.map +1 -0
  33. package/lib/commonjs/types/HumanReadableOrientationsResource.type.js +6 -0
  34. package/lib/commonjs/types/HumanReadableOrientationsResource.type.js.map +1 -0
  35. package/lib/module/EventEmitter.js +41 -0
  36. package/lib/module/EventEmitter.js.map +1 -0
  37. package/lib/module/NativeOrientationDirector.js.map +1 -1
  38. package/lib/module/RNOrientationDirector.js +20 -9
  39. package/lib/module/RNOrientationDirector.js.map +1 -1
  40. package/lib/module/module.js +1 -1
  41. package/lib/module/module.js.map +1 -1
  42. package/lib/module/types/HumanReadableAutoRotationsResource.type.js +2 -0
  43. package/lib/module/types/HumanReadableAutoRotationsResource.type.js.map +1 -0
  44. package/lib/module/types/HumanReadableOrientationsResource.type.js +2 -0
  45. package/lib/module/types/HumanReadableOrientationsResource.type.js.map +1 -0
  46. package/lib/typescript/src/EventEmitter.d.ts +11 -0
  47. package/lib/typescript/src/EventEmitter.d.ts.map +1 -0
  48. package/lib/typescript/src/NativeOrientationDirector.d.ts +3 -1
  49. package/lib/typescript/src/NativeOrientationDirector.d.ts.map +1 -1
  50. package/lib/typescript/src/RNOrientationDirector.d.ts +8 -4
  51. package/lib/typescript/src/RNOrientationDirector.d.ts.map +1 -1
  52. package/lib/typescript/src/module.d.ts +1 -1
  53. package/lib/typescript/src/module.d.ts.map +1 -1
  54. package/lib/typescript/src/types/HumanReadableAutoRotationsResource.type.d.ts +3 -0
  55. package/lib/typescript/src/types/HumanReadableAutoRotationsResource.type.d.ts.map +1 -0
  56. package/lib/typescript/src/types/HumanReadableOrientationsResource.type.d.ts +3 -0
  57. package/lib/typescript/src/types/HumanReadableOrientationsResource.type.d.ts.map +1 -0
  58. package/lib/typescript/src/types/Orientation.enum.d.ts.map +1 -1
  59. package/package.json +3 -3
  60. package/src/EventEmitter.ts +70 -0
  61. package/src/NativeOrientationDirector.ts +12 -1
  62. package/src/RNOrientationDirector.ts +34 -21
  63. package/src/module.ts +1 -1
  64. package/src/types/HumanReadableAutoRotationsResource.type.ts +3 -0
  65. package/src/types/HumanReadableOrientationsResource.type.ts +3 -0
  66. package/src/types/Orientation.enum.ts +0 -2
  67. package/android/src/main/java/com/orientationdirector/OrientationDirectorModule.kt +0 -65
  68. package/android/src/main/java/com/orientationdirector/implementation/SensorListener.kt +0 -26
  69. package/android/src/newarch/OrientationDirectorSpec.kt +0 -7
  70. package/android/src/oldarch/OrientationDirectorSpec.kt +0 -18
  71. package/lib/commonjs/types/InterfaceOrientationToLocalizedStringProvider.type.js.map +0 -1
  72. package/lib/module/types/InterfaceOrientationToLocalizedStringProvider.type.js +0 -2
  73. package/lib/module/types/InterfaceOrientationToLocalizedStringProvider.type.js.map +0 -1
  74. package/lib/typescript/src/types/InterfaceOrientationToLocalizedStringProvider.type.d.ts +0 -3
  75. package/lib/typescript/src/types/InterfaceOrientationToLocalizedStringProvider.type.d.ts.map +0 -1
  76. package/src/types/InterfaceOrientationToLocalizedStringProvider.type.ts +0 -6
package/README.md CHANGED
@@ -4,7 +4,8 @@
4
4
 
5
5
  # react-native-orientation-director
6
6
 
7
- A simple library that allows you to handle orientation changes in your React Native app.
7
+ A React Native library that allows you to listen to orientation changes, lock interface orientation
8
+ to a selected one and get current orientation.
8
9
  Written in Kotlin, Swift and Typescript. It supports both the Old and New React Native architecture.
9
10
 
10
11
  This library takes inspiration from and builds upon the following amazing alternatives:
@@ -27,17 +28,29 @@ This library takes inspiration from and builds upon the following amazing altern
27
28
 
28
29
  ## Installation
29
30
 
31
+ ### React Native Bare
32
+
30
33
  You can install the package via npm or yarn:
31
34
 
32
35
  ```sh
33
36
  npm install react-native-orientation-director
34
37
  ```
38
+
35
39
  ```sh
36
40
  yarn add react-native-orientation-director
37
41
  ```
38
42
 
39
43
  Don't forget to run pod-install.
40
44
 
45
+ ### Expo
46
+
47
+ This library can be installed only for [Development Builds](https://docs.expo.dev/develop/development-builds/introduction/)
48
+ using the following command:
49
+
50
+ ```sh
51
+ npx expo install react-native-orientation-director
52
+ ```
53
+
41
54
  ## Setup
42
55
 
43
56
  To properly handle interface orientation changes in iOS, you need to update your AppDelegate.mm file.
@@ -60,36 +73,47 @@ There is no need to do anything in Android, it works out of the box.
60
73
 
61
74
  This library exports a class called: [RNOrientationDirector](https://github.com/gladiuscode/react-native-orientation-director/blob/main/src/RNOrientationDirector.ts) that exposes the following methods:
62
75
 
63
- | Method | Description |
64
- |-----------------------------------------|---------------------------------------------------------------------------------|
65
- | getInterfaceOrientation | Returns the last interface orientation |
66
- | getDeviceOrientation | Returns the last device orientation |
67
- | lockTo | Locks the interface to a specific orientation |
68
- | unlock | Unlock the interface |
69
- | isLocked | Returns the current interface orientation status (locked / unlocked) |
70
- | isAutoRotationEnabled | (Android Only) Returns if auto rotation is enabled |
71
- | listenForDeviceOrientationChanges | Triggers a provided callback each time the device orientation changes |
72
- | listenForInterfaceOrientationChanges | Triggers a provided callback each time the interface orientation changes |
73
- | listenForLockChanges | Triggers a provided callback each time the interface orientation status changes |
74
- | convertOrientationToHumanReadableString | Returns a human readable string based on the given orientation |
75
- | setLocalizedStringProvider | Sets the mapping needed to convert orientation values to human readable strings |
76
- | resetSupportedInterfaceOrientations | Resets the supported interface orientations to settings |
76
+ | Method | Description |
77
+ | ---------------------------------------- | --------------------------------------------------------------------------------- |
78
+ | getInterfaceOrientation | Returns the last interface orientation |
79
+ | getDeviceOrientation | Returns the last device orientation |
80
+ | lockTo | Locks the interface to a specific orientation |
81
+ | unlock | Unlock the interface |
82
+ | isLocked | Returns the current interface orientation status (locked / unlocked) |
83
+ | isAutoRotationEnabled | (Android Only) Returns if auto rotation is enabled |
84
+ | listenForDeviceOrientationChanges | Triggers a provided callback each time the device orientation changes |
85
+ | listenForInterfaceOrientationChanges | Triggers a provided callback each time the interface orientation changes |
86
+ | listenForLockChanges | Triggers a provided callback each time the interface orientation status changes |
87
+ | convertOrientationToHumanReadableString | Returns a human readable string based on the given orientation |
88
+ | convertAutoRotationToHumanReadableString | Returns a human readable string based on the given auto rotation |
89
+ | setHumanReadableOrientations | Sets the mapping needed to convert orientation values to human readable strings |
90
+ | setHumanReadableAutoRotations | Sets the mapping needed to convert auto rotation values to human readable strings |
91
+ | resetSupportedInterfaceOrientations | Resets the supported interface orientations to settings |
77
92
 
78
93
  In addition, the library exposes the following hooks:
79
94
 
80
95
  | Hook | Description |
81
- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
96
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
82
97
  | [useInterfaceOrientation](https://github.com/gladiuscode/react-native-orientation-director/blob/main/src/hooks/useInterfaceOrientation.hook.ts) | Returns the current interface orientation and listens to changes |
83
98
  | [useDeviceOrientation](https://github.com/gladiuscode/react-native-orientation-director/blob/main/src/hooks/useDeviceOrientation.hook.ts) | Returns the current device orientation and listens to changes |
84
99
  | [useIsInterfaceOrientationLocked](https://github.com/gladiuscode/react-native-orientation-director/blob/main/src/hooks/useIsInterfaceOrientationLocked.hook.ts) | Returns the current interface orientation status and listens to changes |
85
100
 
86
101
  Head over to the [example project](example) to see how to use the library.
87
102
 
103
+ ### Android
104
+
105
+ Since on Android we need to deal with sensors and their usage, it is worth noting that the device orientation computation works
106
+ differently than on iOS, mainly in the following ways:
107
+
108
+ 1. Upon start up, all required sensors are enabled just for the initial device orientation computation, then they are disabled;
109
+ 2. Each time a new device orientation listener is added, all required sensors are enabled if disabled;
110
+ 3. After the last device orientation listener is removed, all required sensors are disabled;
111
+
112
+ This behavior allows us to follow Google's best practices related to the Sensors Framework. More [here](https://developer.android.com/develop/sensors-and-location/sensors/sensors_overview#sensors-practices).
113
+
88
114
  ## Roadmap
89
115
 
90
- - [ ] Add support for Expo
91
116
  - [ ] Add JS side tests
92
- - [ ] Add Android side tests
93
117
  - [ ] Add iOS side tests
94
118
 
95
119
  ## Contributing
@@ -33,6 +33,10 @@ def getExtOrIntegerDefault(name) {
33
33
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["OrientationDirector_" + name]).toInteger()
34
34
  }
35
35
 
36
+ def getDefault(name) {
37
+ return project.properties["OrientationDirector_" + name]
38
+ }
39
+
36
40
  def supportsNamespace() {
37
41
  def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
38
42
  def major = parsed[0].toInteger()
@@ -59,7 +63,6 @@ android {
59
63
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
60
64
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
61
65
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
62
-
63
66
  }
64
67
 
65
68
  buildFeatures {
@@ -102,6 +105,10 @@ repositories {
102
105
  }
103
106
 
104
107
  def kotlin_version = getExtOrDefault("kotlinVersion")
108
+ def junit_version = getDefault("junitVersion")
109
+ def android_x_core_version = getDefault("androidXCoreVersion")
110
+ def robolectric_version = getDefault("robolectricVersion")
111
+ def mockito_core_version = getDefault("mockitoCoreVersion")
105
112
 
106
113
  dependencies {
107
114
  // For < 0.71, this will be from the local maven repo
@@ -109,6 +116,11 @@ dependencies {
109
116
  //noinspection GradleDynamicVersion
110
117
  implementation "com.facebook.react:react-native:+"
111
118
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
119
+
120
+ testImplementation "junit:junit:$junit_version"
121
+ testImplementation "androidx.test:core:$android_x_core_version"
122
+ testImplementation "org.robolectric:robolectric:$robolectric_version"
123
+ testImplementation "org.mockito:mockito-core:$mockito_core_version"
112
124
  }
113
125
 
114
126
  if (isNewArchitectureEnabled()) {
@@ -3,3 +3,15 @@ OrientationDirector_minSdkVersion=21
3
3
  OrientationDirector_targetSdkVersion=31
4
4
  OrientationDirector_compileSdkVersion=31
5
5
  OrientationDirector_ndkversion=21.4.7075529
6
+
7
+ #########################
8
+ # TESTING
9
+ #
10
+
11
+ OrientationDirector_junitVersion=4.13.2
12
+ OrientationDirector_androidXCoreVersion=1.5.+
13
+ OrientationDirector_robolectricVersion=4.13
14
+ OrientationDirector_mockitoCoreVersion=5.14.2
15
+
16
+ #
17
+ #########################
@@ -5,11 +5,12 @@ import com.facebook.react.bridge.ReactApplicationContext
5
5
  import com.facebook.react.bridge.NativeModule
6
6
  import com.facebook.react.module.model.ReactModuleInfoProvider
7
7
  import com.facebook.react.module.model.ReactModuleInfo
8
+ import com.orientationdirector.implementation.OrientationDirectorModuleImpl
8
9
  import java.util.HashMap
9
10
 
10
11
  class OrientationDirectorPackage : TurboReactPackage() {
11
12
  override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
- return if (name == OrientationDirectorModule.NAME) {
13
+ return if (name == OrientationDirectorModuleImpl.NAME) {
13
14
  OrientationDirectorModule(reactContext)
14
15
  } else {
15
16
  null
@@ -20,9 +21,9 @@ class OrientationDirectorPackage : TurboReactPackage() {
20
21
  return ReactModuleInfoProvider {
21
22
  val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
22
23
  val isTurboModule: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
23
- moduleInfos[OrientationDirectorModule.NAME] = ReactModuleInfo(
24
- OrientationDirectorModule.NAME,
25
- OrientationDirectorModule.NAME,
24
+ moduleInfos[OrientationDirectorModuleImpl.NAME] = ReactModuleInfo(
25
+ OrientationDirectorModuleImpl.NAME,
26
+ OrientationDirectorModuleImpl.NAME,
26
27
  false, // canOverrideExistingModule
27
28
  false, // needsEagerInit
28
29
  true, // hasConstants
@@ -1,6 +1,5 @@
1
1
  package com.orientationdirector.implementation
2
2
 
3
- import android.util.Log
4
3
  import com.facebook.react.bridge.Arguments
5
4
  import com.facebook.react.bridge.ReactApplicationContext
6
5
  import com.facebook.react.bridge.WritableMap
@@ -2,7 +2,7 @@ package com.orientationdirector.implementation
2
2
 
3
3
  import com.facebook.react.bridge.LifecycleEventListener
4
4
 
5
- class LifecycleListener() : LifecycleEventListener {
5
+ class LifecycleListener : LifecycleEventListener {
6
6
 
7
7
  private var onHostResumeCallback: (() -> Unit)? = null
8
8
  private var onHostPauseCallback: (() -> Unit)? = null
@@ -9,4 +9,6 @@ enum class Orientation {
9
9
  LANDSCAPE_RIGHT,
10
10
  PORTRAIT_UPSIDE_DOWN,
11
11
  LANDSCAPE_LEFT,
12
+ FACE_UP,
13
+ FACE_DOWN,
12
14
  }
@@ -3,13 +3,12 @@ package com.orientationdirector.implementation
3
3
  import android.content.pm.ActivityInfo
4
4
  import android.os.Handler
5
5
  import android.os.Looper
6
- import android.view.OrientationEventListener.ORIENTATION_UNKNOWN
7
6
  import com.facebook.react.bridge.ReactApplicationContext
8
7
 
9
- class OrientationDirectorImpl internal constructor(private val context: ReactApplicationContext) {
8
+ class OrientationDirectorModuleImpl internal constructor(private val context: ReactApplicationContext) {
10
9
  private var mUtils = Utils(context)
11
10
  private var mEventManager = EventManager(context)
12
- private var mSensorListener = SensorListener(context)
11
+ private var mOrientationSensorsEventListener = OrientationSensorsEventListener(context)
13
12
  private var mAutoRotationObserver = AutoRotationObserver(
14
13
  context, Handler(
15
14
  Looper.getMainLooper()
@@ -22,43 +21,39 @@ class OrientationDirectorImpl internal constructor(private val context: ReactApp
22
21
  private var lastDeviceOrientation = Orientation.UNKNOWN
23
22
  private var initialized = false
24
23
  private var isLocked: Boolean = false
24
+ private var areOrientationSensorsEnabled = false;
25
+ private var didComputeInitialDeviceOrientation = false;
25
26
 
26
27
  init {
27
- mSensorListener.setOnOrientationChangedCallback { orientation ->
28
- onOrientationChanged(orientation)
29
- }
30
-
31
- if (mSensorListener.canDetectOrientation()) {
32
- mSensorListener.enable()
33
- } else {
34
- mSensorListener.disable()
28
+ mOrientationSensorsEventListener.setOnOrientationAnglesChangedCallback { orientation ->
29
+ onOrientationAnglesChanged(orientation)
35
30
  }
36
31
 
37
32
  mAutoRotationObserver.enable()
38
33
 
39
34
  context.addLifecycleEventListener(mLifecycleListener)
40
35
  mLifecycleListener.setOnHostResumeCallback {
41
- if (mSensorListener.canDetectOrientation()) {
42
- mSensorListener.enable()
36
+ if (!didComputeInitialDeviceOrientation || areOrientationSensorsEnabled) {
37
+ mOrientationSensorsEventListener.enable()
43
38
  }
44
-
45
39
  mAutoRotationObserver.enable()
46
40
  }
47
41
  mLifecycleListener.setOnHostPauseCallback {
48
- if (initialized) {
49
- mSensorListener.disable()
42
+ if (initialized && areOrientationSensorsEnabled) {
43
+ mOrientationSensorsEventListener.disable()
50
44
  mAutoRotationObserver.disable()
51
45
  }
52
46
  }
53
47
  mLifecycleListener.setOnHostDestroyCallback {
54
- mSensorListener.disable()
55
- mAutoRotationObserver.disable()
48
+ if (areOrientationSensorsEnabled) {
49
+ mOrientationSensorsEventListener.disable()
50
+ mAutoRotationObserver.disable()
51
+ }
56
52
  }
57
53
 
58
54
  initialSupportedInterfaceOrientations =
59
55
  context.currentActivity?.requestedOrientation ?: initialSupportedInterfaceOrientations
60
56
  lastInterfaceOrientation = initInterfaceOrientation()
61
- lastDeviceOrientation = initDeviceOrientation()
62
57
  isLocked = initIsLocked()
63
58
 
64
59
  initialized = true
@@ -103,16 +98,19 @@ class OrientationDirectorImpl internal constructor(private val context: ReactApp
103
98
  updateLastInterfaceOrientationTo(initInterfaceOrientation())
104
99
  }
105
100
 
106
- private fun initInterfaceOrientation(): Orientation {
107
- val rotation = mUtils.getInterfaceRotation()
108
- return mUtils.convertToOrientationFromScreenRotation(rotation)
101
+ fun enableOrientationSensors() {
102
+ areOrientationSensorsEnabled = true
103
+ mOrientationSensorsEventListener.enable()
109
104
  }
110
105
 
111
- private fun initDeviceOrientation(): Orientation {
112
- val lastRotationDetected = mSensorListener.getLastRotationDetected()
113
- ?: return Orientation.UNKNOWN
106
+ fun disableOrientationSensors() {
107
+ areOrientationSensorsEnabled = false
108
+ mOrientationSensorsEventListener.disable()
109
+ }
114
110
 
115
- return mUtils.convertToDeviceOrientationFrom(lastRotationDetected)
111
+ private fun initInterfaceOrientation(): Orientation {
112
+ val rotation = mUtils.getInterfaceRotation()
113
+ return mUtils.convertToOrientationFromScreenRotation(rotation)
116
114
  }
117
115
 
118
116
  private fun initIsLocked(): Boolean {
@@ -126,11 +124,10 @@ class OrientationDirectorImpl internal constructor(private val context: ReactApp
126
124
  ).contains(activity.requestedOrientation)
127
125
  }
128
126
 
129
- private fun onOrientationChanged(rawDeviceOrientation: Int) {
130
- val deviceOrientation = mUtils.convertToDeviceOrientationFrom(rawDeviceOrientation)
131
-
132
- if (rawDeviceOrientation != ORIENTATION_UNKNOWN && deviceOrientation == Orientation.UNKNOWN) {
133
- return;
127
+ private fun onOrientationAnglesChanged(orientationAngles: FloatArray) {
128
+ val deviceOrientation = mUtils.convertToDeviceOrientationFrom(orientationAngles)
129
+ if (deviceOrientation == Orientation.UNKNOWN) {
130
+ return
134
131
  }
135
132
 
136
133
  if (lastDeviceOrientation == deviceOrientation) {
@@ -141,6 +138,11 @@ class OrientationDirectorImpl internal constructor(private val context: ReactApp
141
138
  lastDeviceOrientation = deviceOrientation
142
139
 
143
140
  adaptInterfaceTo(deviceOrientation)
141
+
142
+ if (!didComputeInitialDeviceOrientation) {
143
+ didComputeInitialDeviceOrientation = true
144
+ mOrientationSensorsEventListener.disable()
145
+ }
144
146
  }
145
147
 
146
148
  private fun adaptInterfaceTo(deviceOrientation: Orientation) {
@@ -152,7 +154,19 @@ class OrientationDirectorImpl internal constructor(private val context: ReactApp
152
154
  return
153
155
  }
154
156
 
155
- val newInterfaceOrientation = mUtils.convertToInterfaceOrientationFrom(deviceOrientation);
157
+ var newInterfaceOrientation = mUtils.convertToInterfaceOrientationFrom(deviceOrientation);
158
+
159
+ /**
160
+ * When the device orientation is either face up or face down,
161
+ * we can't match it to an interface orientation, because
162
+ * it could be either portrait or any landscape.
163
+ * So we read it from the system itself.
164
+ */
165
+ if (newInterfaceOrientation == Orientation.UNKNOWN) {
166
+ val rotation = mUtils.getInterfaceRotation()
167
+ newInterfaceOrientation = mUtils.convertToOrientationFromScreenRotation(rotation)
168
+ }
169
+
156
170
  if (newInterfaceOrientation == lastInterfaceOrientation) {
157
171
  return
158
172
  }
@@ -171,6 +185,6 @@ class OrientationDirectorImpl internal constructor(private val context: ReactApp
171
185
  }
172
186
 
173
187
  companion object {
174
- const val NAME = "OrientationDirectorImpl"
188
+ const val NAME = "OrientationDirector"
175
189
  }
176
190
  }
@@ -0,0 +1,79 @@
1
+ package com.orientationdirector.implementation
2
+
3
+ import android.content.Context.SENSOR_SERVICE
4
+ import android.hardware.Sensor
5
+ import android.hardware.SensorEvent
6
+ import android.hardware.SensorEventListener
7
+ import android.hardware.SensorManager
8
+ import com.facebook.react.bridge.ReactApplicationContext
9
+
10
+ class OrientationSensorsEventListener(
11
+ context: ReactApplicationContext,
12
+ ) : SensorEventListener {
13
+ private var mSensorManager: SensorManager = context.getSystemService(SENSOR_SERVICE) as SensorManager
14
+
15
+ private var mAccelerometerSensor: Sensor? = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
16
+ private var mMagneticFieldSensor: Sensor? = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
17
+
18
+ private var hasRequiredSensors: Boolean = mAccelerometerSensor != null && mMagneticFieldSensor != null
19
+
20
+ private val accelerometerReading = FloatArray(3)
21
+ private val magnetometerReading = FloatArray(3)
22
+
23
+ private var lastComputedOrientationAngles = FloatArray(3)
24
+ private var onOrientationAnglesChangedCallback: ((orientationAngles: FloatArray) -> Unit)? = null
25
+
26
+ fun setOnOrientationAnglesChangedCallback(callback: (orientation: FloatArray) -> Unit) {
27
+ onOrientationAnglesChangedCallback = callback
28
+ }
29
+
30
+ override fun onSensorChanged(event: SensorEvent?) {
31
+ if (event == null) {
32
+ return
33
+ }
34
+
35
+ if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
36
+ System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)
37
+ }
38
+
39
+ if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {
40
+ System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)
41
+ }
42
+
43
+ val rotationMatrix = FloatArray(9)
44
+ val didComputeMatrix = SensorManager.getRotationMatrix(
45
+ rotationMatrix,
46
+ null,
47
+ accelerometerReading,
48
+ magnetometerReading
49
+ )
50
+ if (!didComputeMatrix) {
51
+ return
52
+ }
53
+
54
+ val orientationAngles = FloatArray(3)
55
+ SensorManager.getOrientation(rotationMatrix, orientationAngles)
56
+
57
+ if (lastComputedOrientationAngles.contentEquals(orientationAngles)) {
58
+ return
59
+ }
60
+
61
+ onOrientationAnglesChangedCallback?.invoke(orientationAngles)
62
+ lastComputedOrientationAngles = orientationAngles
63
+ }
64
+
65
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
66
+
67
+ fun enable() {
68
+ if (!hasRequiredSensors) {
69
+ return
70
+ }
71
+
72
+ mSensorManager.registerListener(this, mAccelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL)
73
+ mSensorManager.registerListener(this, mMagneticFieldSensor, SensorManager.SENSOR_DELAY_NORMAL)
74
+ }
75
+
76
+ fun disable() {
77
+ mSensorManager.unregisterListener(this)
78
+ }
79
+ }
@@ -19,19 +19,32 @@ class Utils(private val context: ReactContext) {
19
19
  }
20
20
  }
21
21
 
22
- fun convertToDeviceOrientationFrom(deviceRotation: Int): Orientation {
23
- return if (deviceRotation == -1) {
24
- Orientation.UNKNOWN
25
- } else if (deviceRotation > 355 || deviceRotation < 5) {
26
- Orientation.PORTRAIT
27
- } else if (deviceRotation in 86..94) {
28
- Orientation.LANDSCAPE_RIGHT
29
- } else if (deviceRotation in 176..184) {
30
- Orientation.PORTRAIT_UPSIDE_DOWN
31
- } else if (deviceRotation in 266..274) {
32
- Orientation.LANDSCAPE_LEFT
33
- } else {
34
- return Orientation.UNKNOWN
22
+ fun convertToDeviceOrientationFrom(orientationAngles: FloatArray): Orientation {
23
+ val (_, pitchRadians, rollRadians) = orientationAngles;
24
+
25
+ val pitchDegrees = Math.toDegrees(pitchRadians.toDouble()).toFloat()
26
+ val rollDegrees = Math.toDegrees(rollRadians.toDouble()).toFloat()
27
+
28
+ // This is needed to account for inaccuracy due to subtle movements such as tilting
29
+ val tolerance = 20f
30
+
31
+ //////////////////////////////////////
32
+ // These limits are set based on SensorManager.getOrientation reference
33
+ // https://developer.android.com/develop/sensors-and-location/sensors/sensors_position#sensors-pos-orient
34
+ //
35
+ val portraitLimit = -90f
36
+ val landscapeRightLimit = 180f
37
+ val landscapeLeftLimit = -180f
38
+ //
39
+ //////////////////////////////////////
40
+
41
+ return when {
42
+ rollDegrees.equals(-0f) && (pitchDegrees.equals(0f) || pitchDegrees.equals(-0f)) -> Orientation.FACE_UP
43
+ rollDegrees.equals(-180f) && (pitchDegrees.equals(0f) || pitchDegrees.equals(-0f)) -> Orientation.FACE_DOWN
44
+ rollDegrees in tolerance..landscapeRightLimit - tolerance -> Orientation.LANDSCAPE_RIGHT
45
+ rollDegrees in landscapeLeftLimit + tolerance..-tolerance -> Orientation.LANDSCAPE_LEFT
46
+ pitchDegrees in portraitLimit..-0f -> Orientation.PORTRAIT
47
+ else -> Orientation.PORTRAIT_UPSIDE_DOWN
35
48
  }
36
49
  }
37
50
 
@@ -54,7 +67,7 @@ class Utils(private val context: ReactContext) {
54
67
  }
55
68
 
56
69
  fun convertToOrientationFromScreenRotation(screenRotation: Int): Orientation {
57
- return when(screenRotation) {
70
+ return when (screenRotation) {
58
71
  Surface.ROTATION_270 -> Orientation.LANDSCAPE_RIGHT
59
72
  Surface.ROTATION_90 -> Orientation.LANDSCAPE_LEFT
60
73
  Surface.ROTATION_180 -> Orientation.PORTRAIT_UPSIDE_DOWN
@@ -63,7 +76,7 @@ class Utils(private val context: ReactContext) {
63
76
  }
64
77
 
65
78
  fun convertToInterfaceOrientationFrom(deviceOrientation: Orientation): Orientation {
66
- return when(deviceOrientation) {
79
+ return when (deviceOrientation) {
67
80
  Orientation.PORTRAIT -> Orientation.PORTRAIT
68
81
  Orientation.LANDSCAPE_RIGHT -> Orientation.LANDSCAPE_LEFT
69
82
  Orientation.PORTRAIT_UPSIDE_DOWN -> Orientation.PORTRAIT_UPSIDE_DOWN
@@ -0,0 +1,59 @@
1
+ package com.orientationdirector
2
+
3
+ import com.facebook.react.bridge.Promise
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.orientationdirector.implementation.OrientationDirectorModuleImpl
6
+
7
+ class OrientationDirectorModule internal constructor(context: ReactApplicationContext) :
8
+ NativeOrientationDirectorSpec(context) {
9
+
10
+ private var implementation = OrientationDirectorModuleImpl(context)
11
+
12
+ override fun getName() = OrientationDirectorModuleImpl.NAME
13
+
14
+
15
+ override fun getInterfaceOrientation(promise: Promise) {
16
+ promise.resolve(implementation.getInterfaceOrientation().ordinal)
17
+ }
18
+
19
+
20
+ override fun getDeviceOrientation(promise: Promise) {
21
+ promise.resolve(implementation.getDeviceOrientation().ordinal)
22
+ }
23
+
24
+
25
+ override fun lockTo(orientation: Double) {
26
+ implementation.lockTo(orientation.toInt())
27
+ }
28
+
29
+
30
+ override fun unlock() {
31
+ implementation.unlock()
32
+ }
33
+
34
+
35
+ override fun resetSupportedInterfaceOrientations() {
36
+ implementation.resetSupportedInterfaceOrientations()
37
+ }
38
+
39
+ override fun isLocked(): Boolean {
40
+ return implementation.getIsLocked()
41
+ }
42
+
43
+ override fun isAutoRotationEnabled(): Boolean {
44
+ return implementation.getIsAutoRotationEnabled()
45
+ }
46
+
47
+ override fun enableOrientationSensors() {
48
+ return implementation.enableOrientationSensors()
49
+ }
50
+
51
+ override fun disableOrientationSensors() {
52
+ return implementation.disableOrientationSensors()
53
+ }
54
+
55
+ override fun addListener(eventName: String) {}
56
+
57
+ override fun removeListeners(count: Double) {}
58
+
59
+ }
@@ -0,0 +1,66 @@
1
+ package com.orientationdirector
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
5
+ import com.facebook.react.bridge.Promise
6
+ import com.facebook.react.bridge.ReactMethod
7
+ import com.orientationdirector.implementation.OrientationDirectorModuleImpl
8
+
9
+ class OrientationDirectorModule internal constructor(context: ReactApplicationContext) :
10
+ ReactContextBaseJavaModule(context) {
11
+
12
+ private var implementation = OrientationDirectorModuleImpl(context)
13
+
14
+ override fun getName() = OrientationDirectorModuleImpl.NAME
15
+
16
+ @ReactMethod()
17
+ fun getInterfaceOrientation(promise: Promise) {
18
+ promise.resolve(implementation.getInterfaceOrientation().ordinal)
19
+ }
20
+
21
+ @ReactMethod()
22
+ fun getDeviceOrientation(promise: Promise) {
23
+ promise.resolve(implementation.getDeviceOrientation().ordinal)
24
+ }
25
+
26
+ @ReactMethod()
27
+ fun lockTo(orientation: Double) {
28
+ implementation.lockTo(orientation.toInt())
29
+ }
30
+
31
+ @ReactMethod()
32
+ fun unlock() {
33
+ implementation.unlock()
34
+ }
35
+
36
+ @ReactMethod()
37
+ fun resetSupportedInterfaceOrientations() {
38
+ implementation.resetSupportedInterfaceOrientations()
39
+ }
40
+
41
+ @ReactMethod(isBlockingSynchronousMethod = true)
42
+ fun isLocked(): Boolean {
43
+ return implementation.getIsLocked()
44
+ }
45
+
46
+ @ReactMethod(isBlockingSynchronousMethod = true)
47
+ fun isAutoRotationEnabled(): Boolean {
48
+ return implementation.getIsAutoRotationEnabled()
49
+ }
50
+
51
+ @ReactMethod()
52
+ fun enableOrientationSensors() {
53
+ return implementation.enableOrientationSensors()
54
+ }
55
+
56
+ @ReactMethod()
57
+ fun disableOrientationSensors() {
58
+ return implementation.disableOrientationSensors()
59
+ }
60
+
61
+ @ReactMethod()
62
+ fun addListener(eventName: String) {}
63
+
64
+ @ReactMethod()
65
+ fun removeListeners(count: Double) {}
66
+ }