react-native-orientation-director 2.3.0 → 2.3.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 +40 -6
- package/android/src/main/java/com/orientationdirector/implementation/ConfigurationChangedBroadcastReceiver.kt +52 -0
- package/android/src/main/java/com/orientationdirector/implementation/OrientationDirectorModuleImpl.kt +23 -12
- package/lib/typescript/plugin/src/index.d.ts.map +1 -1
- package/lib/typescript/plugin/src/withRNOrientationMainActivity.d.ts +4 -0
- package/lib/typescript/plugin/src/withRNOrientationMainActivity.d.ts.map +1 -0
- package/package.json +1 -1
- package/plugin/build/index.js +2 -0
- package/plugin/build/withRNOrientationMainActivity.d.ts +3 -0
- package/plugin/build/withRNOrientationMainActivity.js +100 -0
package/README.md
CHANGED
|
@@ -50,7 +50,11 @@ You can install the package like any other Expo package, using the following com
|
|
|
50
50
|
npx expo install react-native-orientation-director
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
## Setup
|
|
54
|
+
|
|
55
|
+
### Expo
|
|
56
|
+
|
|
57
|
+
Simply add the library plugin to your `app.json` file:
|
|
54
58
|
|
|
55
59
|
```json
|
|
56
60
|
{
|
|
@@ -64,12 +68,44 @@ Then, you need to add the plugin to your app.json file:
|
|
|
64
68
|
|
|
65
69
|
This way, Expo will handle the native setup for you during `prebuild`.
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
> Note: only SDK 50 and above are supported, the plugin is configured to handle only the kotlin template.
|
|
72
|
+
|
|
73
|
+
### Bare
|
|
74
|
+
|
|
75
|
+
#### Android
|
|
76
|
+
|
|
77
|
+
This library uses a custom broadcast receiver to handle the manual orientation changes: when the user disables the
|
|
78
|
+
autorotation feature and the system prompts the user to rotate the device, the library will listen to the broadcast
|
|
79
|
+
sent by the MainActivity and update the interface orientation accordingly.
|
|
80
|
+
|
|
81
|
+
To allow the library to listen to the broadcast, you need to override the `onConfigurationChanged` method in your
|
|
82
|
+
MainActivity file, as shown below:
|
|
83
|
+
|
|
84
|
+
```kotlin
|
|
85
|
+
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
86
|
+
super.onConfigurationChanged(newConfig)
|
|
87
|
+
|
|
88
|
+
val orientationDirectorCustomAction =
|
|
89
|
+
"${packageName}.${ConfigurationChangedBroadcastReceiver.CUSTOM_INTENT_ACTION}"
|
|
90
|
+
|
|
91
|
+
val intent =
|
|
92
|
+
Intent(orientationDirectorCustomAction).apply {
|
|
93
|
+
putExtra("newConfig", newConfig)
|
|
94
|
+
setPackage(packageName)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.sendBroadcast(intent)
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Nothing else is required for Android.
|
|
102
|
+
|
|
103
|
+
#### iOS
|
|
68
104
|
|
|
69
105
|
To properly handle interface orientation changes in iOS, you need to update your AppDelegate file. Since React Native
|
|
70
106
|
0.77, the AppDelegate has been migrated to Swift, so see the instructions below for both Swift and Objective-C.
|
|
71
107
|
|
|
72
|
-
|
|
108
|
+
##### Objective-C
|
|
73
109
|
|
|
74
110
|
In your AppDelegate.h file, import "OrientationDirector.h" and implement supportedInterfaceOrientationsForWindow method as follows:
|
|
75
111
|
|
|
@@ -82,7 +118,7 @@ In your AppDelegate.h file, import "OrientationDirector.h" and implement support
|
|
|
82
118
|
}
|
|
83
119
|
```
|
|
84
120
|
|
|
85
|
-
|
|
121
|
+
##### Swift
|
|
86
122
|
|
|
87
123
|
You need to create a [bridging header](https://developer.apple.com/documentation/swift/importing-objective-c-into-swift#Import-Code-Within-an-App-Target)
|
|
88
124
|
to import the library, as shown below:
|
|
@@ -101,8 +137,6 @@ override func application(_ application: UIApplication, supportedInterfaceOrient
|
|
|
101
137
|
|
|
102
138
|
If you need help, you can check the example project.
|
|
103
139
|
|
|
104
|
-
There is no need to do anything in Android, it works out of the box.
|
|
105
|
-
|
|
106
140
|
## Usage
|
|
107
141
|
|
|
108
142
|
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:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package com.orientationdirector.implementation
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.IntentFilter
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This custom broadcast receiver is needed to properly update the interface orientation when
|
|
12
|
+
* the user has disabled the automatic rotation.
|
|
13
|
+
*
|
|
14
|
+
* It listens for an explicit intent that the MainActivity can send in the onConfigurationChanged
|
|
15
|
+
* method and calls a custom callback that is set in the main implementation init
|
|
16
|
+
*/
|
|
17
|
+
class ConfigurationChangedBroadcastReceiver internal constructor(private val context: ReactApplicationContext) :
|
|
18
|
+
BroadcastReceiver() {
|
|
19
|
+
|
|
20
|
+
private var onReceiveCallback: ((intent: Intent?) -> Unit)? = null
|
|
21
|
+
|
|
22
|
+
override fun onReceive(context: Context?, intent: Intent?) {
|
|
23
|
+
this.onReceiveCallback?.invoke(intent)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fun setOnReceiveCallback(callback: (intent: Intent?) -> Unit) {
|
|
27
|
+
onReceiveCallback = callback
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* This method registers the receiver by checking the api we are currently running with.
|
|
32
|
+
* With the latest changes in Android 14, we need to explicitly set the `Context.RECEIVER_NOT_EXPORTED`
|
|
33
|
+
* flag.
|
|
34
|
+
*/
|
|
35
|
+
fun register() {
|
|
36
|
+
val filter = IntentFilter("${context.packageName}.$CUSTOM_INTENT_ACTION")
|
|
37
|
+
|
|
38
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
39
|
+
context.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED)
|
|
40
|
+
} else {
|
|
41
|
+
context.registerReceiver(this, filter)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fun unregister() {
|
|
46
|
+
context.unregisterReceiver(this)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
companion object {
|
|
50
|
+
const val CUSTOM_INTENT_ACTION = "CONFIGURATION_CHANGED"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -15,6 +15,7 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re
|
|
|
15
15
|
)
|
|
16
16
|
)
|
|
17
17
|
private var mLifecycleListener = LifecycleListener()
|
|
18
|
+
private var mBroadcastReceiver = ConfigurationChangedBroadcastReceiver(context)
|
|
18
19
|
|
|
19
20
|
private var initialSupportedInterfaceOrientations = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
|
20
21
|
private var lastInterfaceOrientation = Orientation.UNKNOWN
|
|
@@ -31,24 +32,31 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re
|
|
|
31
32
|
|
|
32
33
|
mAutoRotationObserver.enable()
|
|
33
34
|
|
|
35
|
+
mBroadcastReceiver.setOnReceiveCallback {
|
|
36
|
+
adaptInterfaceTo(lastDeviceOrientation, false)
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
context.addLifecycleEventListener(mLifecycleListener)
|
|
35
40
|
mLifecycleListener.setOnHostResumeCallback {
|
|
36
41
|
if (!didComputeInitialDeviceOrientation || areOrientationSensorsEnabled) {
|
|
37
42
|
mOrientationSensorsEventListener.enable()
|
|
38
43
|
}
|
|
39
44
|
mAutoRotationObserver.enable()
|
|
45
|
+
mBroadcastReceiver.register()
|
|
40
46
|
}
|
|
41
47
|
mLifecycleListener.setOnHostPauseCallback {
|
|
42
48
|
if (initialized && areOrientationSensorsEnabled) {
|
|
43
49
|
mOrientationSensorsEventListener.disable()
|
|
44
50
|
mAutoRotationObserver.disable()
|
|
45
51
|
}
|
|
52
|
+
mBroadcastReceiver.unregister()
|
|
46
53
|
}
|
|
47
54
|
mLifecycleListener.setOnHostDestroyCallback {
|
|
48
55
|
if (areOrientationSensorsEnabled) {
|
|
49
56
|
mOrientationSensorsEventListener.disable()
|
|
50
57
|
mAutoRotationObserver.disable()
|
|
51
58
|
}
|
|
59
|
+
mBroadcastReceiver.unregister()
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
initialSupportedInterfaceOrientations =
|
|
@@ -89,15 +97,16 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re
|
|
|
89
97
|
return
|
|
90
98
|
}
|
|
91
99
|
|
|
92
|
-
val lastInterfaceOrientationIsAlreadyInLandscape =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
val lastInterfaceOrientationIsAlreadyInLandscape =
|
|
101
|
+
lastInterfaceOrientation == Orientation.LANDSCAPE_RIGHT
|
|
102
|
+
|| lastInterfaceOrientation == Orientation.LANDSCAPE_LEFT
|
|
103
|
+
if (lastInterfaceOrientationIsAlreadyInLandscape) {
|
|
104
|
+
updateLastInterfaceOrientationTo(lastInterfaceOrientation)
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
98
107
|
|
|
99
108
|
val systemDefaultLandscapeOrientation = Orientation.LANDSCAPE_RIGHT
|
|
100
|
-
|
|
109
|
+
updateLastInterfaceOrientationTo(systemDefaultLandscapeOrientation)
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
fun unlock() {
|
|
@@ -160,12 +169,13 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re
|
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
|
|
163
|
-
private fun adaptInterfaceTo(deviceOrientation: Orientation) {
|
|
164
|
-
if (!mAutoRotationObserver.getLastAutoRotationStatus()) {
|
|
172
|
+
private fun adaptInterfaceTo(deviceOrientation: Orientation, checkLastAutoRotationStatus: Boolean = true) {
|
|
173
|
+
if (checkLastAutoRotationStatus && !mAutoRotationObserver.getLastAutoRotationStatus()) {
|
|
165
174
|
return
|
|
166
175
|
}
|
|
167
176
|
|
|
168
|
-
val supportsLandscape =
|
|
177
|
+
val supportsLandscape =
|
|
178
|
+
mUtils.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
|
|
169
179
|
if (isLocked && !supportsLandscape) {
|
|
170
180
|
return
|
|
171
181
|
}
|
|
@@ -191,8 +201,9 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re
|
|
|
191
201
|
* Instead, we check that its value is either LANDSCAPE_RIGHT or LANDSCAPE_LEFT, otherwise we
|
|
192
202
|
* exit
|
|
193
203
|
*/
|
|
194
|
-
val newInterfaceOrientationIsNotLandscape =
|
|
195
|
-
|
|
204
|
+
val newInterfaceOrientationIsNotLandscape =
|
|
205
|
+
newInterfaceOrientation != Orientation.LANDSCAPE_RIGHT
|
|
206
|
+
&& newInterfaceOrientation != Orientation.LANDSCAPE_LEFT;
|
|
196
207
|
if (supportsLandscape && newInterfaceOrientationIsNotLandscape) {
|
|
197
208
|
return
|
|
198
209
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../plugin/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,sBAAsB,CAAC;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../plugin/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,sBAAsB,CAAC;;AA2B9B,wBAIE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withRNOrientationMainActivity.d.ts","sourceRoot":"","sources":["../../../../plugin/src/withRNOrientationMainActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,sBAAsB,CAAC;AAI9B,eAAO,MAAM,6BAA6B,EAAE,YAE3C,CAAC;AA0BF,wBAAgB,aAAa,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CA8C9D"}
|
package/package.json
CHANGED
package/plugin/build/index.js
CHANGED
|
@@ -4,6 +4,7 @@ const config_plugins_1 = require("@expo/config-plugins");
|
|
|
4
4
|
const withBridgingHeader_1 = require("./custom-mod/withBridgingHeader");
|
|
5
5
|
const withRNOrientationAppDelegate_1 = require("./withRNOrientationAppDelegate");
|
|
6
6
|
const withRNOrientationBridgingHeader_1 = require("./withRNOrientationBridgingHeader");
|
|
7
|
+
const withRNOrientationMainActivity_1 = require("./withRNOrientationMainActivity");
|
|
7
8
|
/**
|
|
8
9
|
* So, expo config plugin are awesome and the documentation is well written, but I still needed to look around to see
|
|
9
10
|
* how other projects actually modify the AppDelegate. I've found react-native-firebase to implement a plugin config
|
|
@@ -18,6 +19,7 @@ const withRNOrientationDirector = (config) => {
|
|
|
18
19
|
return (0, config_plugins_1.withPlugins)(config, [
|
|
19
20
|
withRNOrientationAppDelegate_1.withRNOrientationAppDelegate,
|
|
20
21
|
withRNOrientationBridgingHeader_1.withRNOrientationBridgingHeader,
|
|
22
|
+
withRNOrientationMainActivity_1.withRNOrientationMainActivity,
|
|
21
23
|
withBridgingHeader_1.withAppBridgingHeaderMod,
|
|
22
24
|
]);
|
|
23
25
|
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withRNOrientationMainActivity = void 0;
|
|
4
|
+
exports.ktFileUpdater = ktFileUpdater;
|
|
5
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
6
|
+
const generateCode_1 = require("@expo/config-plugins/build/utils/generateCode");
|
|
7
|
+
const withRNOrientationMainActivity = (config) => {
|
|
8
|
+
return (0, config_plugins_1.withMainActivity)(config, readMainActivityFileAndUpdateContents);
|
|
9
|
+
};
|
|
10
|
+
exports.withRNOrientationMainActivity = withRNOrientationMainActivity;
|
|
11
|
+
async function readMainActivityFileAndUpdateContents(config) {
|
|
12
|
+
const { modResults: mainActivityFile } = config;
|
|
13
|
+
const worker = getCompatibleFileUpdater(mainActivityFile.language);
|
|
14
|
+
mainActivityFile.contents = worker(mainActivityFile.contents);
|
|
15
|
+
return config;
|
|
16
|
+
}
|
|
17
|
+
function getCompatibleFileUpdater(language) {
|
|
18
|
+
switch (language) {
|
|
19
|
+
case 'kt':
|
|
20
|
+
return ktFileUpdater;
|
|
21
|
+
default:
|
|
22
|
+
throw new Error(`Cannot add React Native Orientation Director code to MainActivity of language "${language}"`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function ktFileUpdater(originalContents) {
|
|
26
|
+
const systemImportsContents = updateContentsWithSystemImports(originalContents);
|
|
27
|
+
const libraryImportCodeBlock = 'import com.orientationdirector.implementation.ConfigurationChangedBroadcastReceiver';
|
|
28
|
+
const rightBeforeClassDeclaration = /class MainActivity/g;
|
|
29
|
+
const importMergeResults = (0, generateCode_1.mergeContents)({
|
|
30
|
+
tag: '@react-native-orientation-director/library-import',
|
|
31
|
+
src: systemImportsContents,
|
|
32
|
+
newSrc: libraryImportCodeBlock,
|
|
33
|
+
anchor: rightBeforeClassDeclaration,
|
|
34
|
+
offset: 0,
|
|
35
|
+
comment: '// React Native Orientation Director',
|
|
36
|
+
});
|
|
37
|
+
const onConfigurationChangedCodeBlock = `
|
|
38
|
+
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
39
|
+
super.onConfigurationChanged(newConfig)
|
|
40
|
+
|
|
41
|
+
val orientationDirectorCustomAction =
|
|
42
|
+
packageName + "." + ConfigurationChangedBroadcastReceiver.CUSTOM_INTENT_ACTION
|
|
43
|
+
|
|
44
|
+
val intent =
|
|
45
|
+
Intent(orientationDirectorCustomAction).apply {
|
|
46
|
+
putExtra("newConfig", newConfig)
|
|
47
|
+
setPackage(packageName)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.sendBroadcast(intent)
|
|
51
|
+
}\n`;
|
|
52
|
+
const rightBeforeLastClosingBrace = /super\.onCreate/g;
|
|
53
|
+
const pasteInTheListJustAfterTheClosingBracket = 2;
|
|
54
|
+
const implementationMergeResults = (0, generateCode_1.mergeContents)({
|
|
55
|
+
tag: '@react-native-orientation-director/supportedInterfaceOrientationsFor-implementation',
|
|
56
|
+
src: importMergeResults.contents,
|
|
57
|
+
newSrc: onConfigurationChangedCodeBlock,
|
|
58
|
+
anchor: rightBeforeLastClosingBrace,
|
|
59
|
+
offset: pasteInTheListJustAfterTheClosingBracket,
|
|
60
|
+
comment: '// React Native Orientation Director',
|
|
61
|
+
});
|
|
62
|
+
return implementationMergeResults.contents;
|
|
63
|
+
}
|
|
64
|
+
function updateContentsWithSystemImports(originalContents) {
|
|
65
|
+
const rightBeforeClassDeclaration = /class MainActivity/g;
|
|
66
|
+
let possibleUpdatedContents = originalContents;
|
|
67
|
+
possibleUpdatedContents = addIntentImportIfNecessary(possibleUpdatedContents);
|
|
68
|
+
possibleUpdatedContents = addConfigurationImportIfNecessary(possibleUpdatedContents);
|
|
69
|
+
return possibleUpdatedContents;
|
|
70
|
+
function addIntentImportIfNecessary(_contents) {
|
|
71
|
+
const systemIntentImportCodeBlock = 'import android.content.Intent';
|
|
72
|
+
if (_contents.includes(systemIntentImportCodeBlock)) {
|
|
73
|
+
return _contents;
|
|
74
|
+
}
|
|
75
|
+
const mergeResults = (0, generateCode_1.mergeContents)({
|
|
76
|
+
tag: '@react-native-orientation-director/system-intent-import',
|
|
77
|
+
src: _contents,
|
|
78
|
+
newSrc: systemIntentImportCodeBlock,
|
|
79
|
+
anchor: rightBeforeClassDeclaration,
|
|
80
|
+
offset: 0,
|
|
81
|
+
comment: '// React Native Orientation Director',
|
|
82
|
+
});
|
|
83
|
+
return mergeResults.contents;
|
|
84
|
+
}
|
|
85
|
+
function addConfigurationImportIfNecessary(_contents) {
|
|
86
|
+
const systemConfigurationImportCodeBlock = 'import android.content.res.Configuration';
|
|
87
|
+
if (possibleUpdatedContents.includes(systemConfigurationImportCodeBlock)) {
|
|
88
|
+
return _contents;
|
|
89
|
+
}
|
|
90
|
+
const mergeResults = (0, generateCode_1.mergeContents)({
|
|
91
|
+
tag: '@react-native-orientation-director/system-configuration-import',
|
|
92
|
+
src: possibleUpdatedContents,
|
|
93
|
+
newSrc: systemConfigurationImportCodeBlock,
|
|
94
|
+
anchor: rightBeforeClassDeclaration,
|
|
95
|
+
offset: 0,
|
|
96
|
+
comment: '// React Native Orientation Director',
|
|
97
|
+
});
|
|
98
|
+
return mergeResults.contents;
|
|
99
|
+
}
|
|
100
|
+
}
|