Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re
mAutoRotationObserver.enable()

mBroadcastReceiver.setOnReceiveCallback {
adaptInterfaceTo(lastDeviceOrientation, false)
checkInterfaceOrientation(false)
}

context.addLifecycleEventListener(mLifecycleListener)
Expand Down Expand Up @@ -113,7 +113,7 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re
context.currentActivity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED

updateIsLockedTo(false)
adaptInterfaceTo(lastDeviceOrientation)
checkInterfaceOrientation()
}

fun resetSupportedInterfaceOrientations() {
Expand Down Expand Up @@ -161,58 +161,43 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re
mEventManager.sendDeviceOrientationDidChange(deviceOrientation.ordinal)
lastDeviceOrientation = deviceOrientation

adaptInterfaceTo(deviceOrientation)
checkInterfaceOrientation()

if (!didComputeInitialDeviceOrientation) {
didComputeInitialDeviceOrientation = true
mOrientationSensorsEventListener.disable()
}
}

private fun adaptInterfaceTo(deviceOrientation: Orientation, checkLastAutoRotationStatus: Boolean = true) {
if (checkLastAutoRotationStatus && !mAutoRotationObserver.getLastAutoRotationStatus()) {
private fun checkInterfaceOrientation(skipIfAutoRotationIsDisabled: Boolean = true) {
if (skipIfAutoRotationIsDisabled && !mAutoRotationObserver.getLastAutoRotationStatus()) {
return
}

val supportsLandscape =
mUtils.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
if (isLocked && !supportsLandscape) {
if (isLocked) {
return
}

var newInterfaceOrientation = mUtils.convertToInterfaceOrientationFrom(deviceOrientation);

/**
* When the device orientation is either face up or face down,
* we can't match it to an interface orientation, because
* it could be either portrait or any landscape.
* So we read it from the system itself.
*/
if (newInterfaceOrientation == Orientation.UNKNOWN) {
if (lastDeviceOrientation != Orientation.LANDSCAPE_RIGHT && lastDeviceOrientation != Orientation.LANDSCAPE_LEFT) {
val rotation = mUtils.getInterfaceRotation()
newInterfaceOrientation = mUtils.convertToOrientationFromScreenRotation(rotation)
}
val newInterfaceOrientation = mUtils.convertToOrientationFromScreenRotation(rotation)

/**
* This differs from iOS because we can't read the actual orientation of the interface,
* we read its rotation.
* This means that even if the requestedOrientation of the currentActivity is locked to landscape
* it reads every possible orientation and this is not what we want.
* Instead, we check that its value is either LANDSCAPE_RIGHT or LANDSCAPE_LEFT, otherwise we
* exit
*/
val newInterfaceOrientationIsNotLandscape =
newInterfaceOrientation != Orientation.LANDSCAPE_RIGHT
&& newInterfaceOrientation != Orientation.LANDSCAPE_LEFT;
if (supportsLandscape && newInterfaceOrientationIsNotLandscape) {
updateLastInterfaceOrientationTo(newInterfaceOrientation)
return
}

if (newInterfaceOrientation == lastInterfaceOrientation) {
return
}
/**
* The reason we invert the interface orientation is to match iOS behavior with
* UIInterfaceOrientation when device is in landscape mode
*/
val interfaceOrientationBasedOnDeviceOne =
if (lastDeviceOrientation == Orientation.LANDSCAPE_RIGHT) {
Orientation.LANDSCAPE_LEFT
} else {
Orientation.LANDSCAPE_RIGHT
}

updateLastInterfaceOrientationTo(newInterfaceOrientation)
updateLastInterfaceOrientationTo(interfaceOrientationBasedOnDeviceOne)
}

private fun updateIsLockedTo(value: Boolean) {
Expand All @@ -221,6 +206,10 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re
}

private fun updateLastInterfaceOrientationTo(value: Orientation) {
if (value == lastInterfaceOrientation) {
return
}

lastInterfaceOrientation = value
mEventManager.sendInterfaceOrientationDidChange(value.ordinal)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,47 @@ class Utils(private val context: ReactContext) {
}

fun convertToDeviceOrientationFrom(orientationAngles: FloatArray): Orientation {
val (_, pitchRadians, rollRadians) = orientationAngles;

val pitchDegrees = Math.toDegrees(pitchRadians.toDouble()).toFloat()
val rollDegrees = Math.toDegrees(rollRadians.toDouble()).toFloat()

// This is needed to account for inaccuracy due to subtle movements such as tilting
val pitchToleranceDefault = 5f
val rollTolerance = 0f
val toleranceForFaceUpOrDown = 5f;

//////////////////////////////////////
// These limits are set based on SensorManager.getOrientation reference
// https://developer.android.com/develop/sensors-and-location/sensors/sensors_position#sensors-pos-orient
//
val portraitLimit = -90f
val landscapeRightLimit = 180f
val landscapeLeftLimit = -180f
//
//////////////////////////////////////

val isPitchInLandscapeModeRange =
checkIfValueIsBetweenTolerance(pitchDegrees, pitchToleranceDefault)
val isPitchCloseToFaceUpOrDown =
checkIfValueIsBetweenTolerance(pitchDegrees, toleranceForFaceUpOrDown)
if (orientationAngles.size < 3) {
return Orientation.PORTRAIT
}

val (_, pitchRadians, rollRadians) = orientationAngles

val pitch = Math.toDegrees(pitchRadians.toDouble()).toFloat()
val roll = Math.toDegrees(rollRadians.toDouble()).toFloat()

val faceUpDownPitchTolerance = 30f

fun isValueCloseTo(value: Float, target: Float, tolerance: Float): Boolean {
return value in (target - tolerance)..(target + tolerance)
}

return when {
checkIfRollIsCloseToFaceUp(rollDegrees) && isPitchCloseToFaceUpOrDown -> Orientation.FACE_UP
checkIfRollIsCloseToFaceDown(rollDegrees) && isPitchCloseToFaceUpOrDown -> Orientation.FACE_DOWN
rollDegrees in rollTolerance..landscapeRightLimit - rollTolerance && isPitchInLandscapeModeRange -> Orientation.LANDSCAPE_RIGHT
rollDegrees in landscapeLeftLimit + rollTolerance..-rollTolerance && isPitchInLandscapeModeRange -> Orientation.LANDSCAPE_LEFT
pitchDegrees in portraitLimit..pitchToleranceDefault -> Orientation.PORTRAIT
else -> Orientation.PORTRAIT_UPSIDE_DOWN
// Face up: device is lying flat with screen up
isValueCloseTo(pitch, 0f, faceUpDownPitchTolerance) &&
isValueCloseTo(roll, 0f, faceUpDownPitchTolerance) -> Orientation.FACE_UP

// Face down: device is lying flat with screen down
isValueCloseTo(pitch, 0f, faceUpDownPitchTolerance) &&
(isValueCloseTo(roll, 180f, faceUpDownPitchTolerance) || isValueCloseTo(
roll,
-180f,
faceUpDownPitchTolerance
)) -> Orientation.FACE_DOWN

// Portrait
isValueCloseTo(pitch, -90f, 45f) -> Orientation.PORTRAIT

// Portrait upside down
isValueCloseTo(pitch, 90f, 45f) -> Orientation.PORTRAIT_UPSIDE_DOWN

// Landscape left
isValueCloseTo(roll, -90f, 45f) -> Orientation.LANDSCAPE_LEFT

// Landscape right
isValueCloseTo(roll, 90f, 45f) -> Orientation.LANDSCAPE_RIGHT

else -> Orientation.PORTRAIT
}
}

Expand Down Expand Up @@ -93,32 +102,4 @@ class Utils(private val context: ReactContext) {
else -> Orientation.UNKNOWN
}
}

fun getRequestedOrientation(): Int {
if (context.currentActivity?.requestedOrientation == null) {
return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}

return context.currentActivity!!.requestedOrientation;
}

private fun checkIfValueIsBetweenTolerance(value: Float, tolerance: Float): Boolean {
return value > -tolerance && value < tolerance
}

private fun checkIfRollIsCloseToFaceDown(value: Float): Boolean {
val landscapeLimit = 180f
val faceDownLimit = 170f

return value in faceDownLimit..landscapeLimit ||
value in -landscapeLimit..-faceDownLimit;
}

private fun checkIfRollIsCloseToFaceUp(value: Float): Boolean {
val landscapeLimit = 0f
val faceUpLimit = 10f

return value in landscapeLimit..faceUpLimit ||
value in -faceUpLimit..-landscapeLimit
}
}
Loading