├── .nvmrc
├── .watchmanconfig
├── example
├── .watchmanconfig
├── ios
│ ├── .ruby-version
│ ├── File.swift
│ ├── OrientationDirectorExample
│ │ ├── Images.xcassets
│ │ │ ├── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── 120.png
│ │ │ │ ├── 180.png
│ │ │ │ ├── 40.png
│ │ │ │ ├── 58.png
│ │ │ │ ├── 60.png
│ │ │ │ ├── 80.png
│ │ │ │ ├── 87.png
│ │ │ │ ├── 1024.png
│ │ │ │ ├── 120 1.png
│ │ │ │ └── Contents.json
│ │ ├── PrivacyInfo.xcprivacy
│ │ ├── AppDelegate.swift
│ │ ├── Info.plist
│ │ └── LaunchScreen.storyboard
│ ├── OrientationDirectorExample-Bridging-Header.h
│ ├── OrientationDirectorExample.xcworkspace
│ │ ├── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── contents.xcworkspacedata
│ ├── .xcode.env
│ ├── Podfile
│ └── OrientationDirectorExample.xcodeproj
│ │ └── xcshareddata
│ │ └── xcschemes
│ │ └── OrientationDirectorExample.xcscheme
├── jest.config.js
├── .bundle
│ └── config
├── app.json
├── android
│ ├── app
│ │ ├── debug.keystore
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── values
│ │ │ │ │ │ ├── strings.xml
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ │ │ └── ic_launcher_foreground.webp
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ │ │ └── ic_launcher_foreground.webp
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ │ │ └── ic_launcher_foreground.webp
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ │ │ └── ic_launcher_foreground.webp
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ │ │ └── ic_launcher_foreground.webp
│ │ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ │ ├── ic_launcher.xml
│ │ │ │ │ │ └── ic_launcher_round.xml
│ │ │ │ │ └── drawable
│ │ │ │ │ │ ├── rn_edit_text_material.xml
│ │ │ │ │ │ └── ic_launcher_background.xml
│ │ │ │ ├── ic_launcher-playstore.png
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ └── java
│ │ │ │ │ └── com
│ │ │ │ │ └── orientationdirectorexample
│ │ │ │ │ ├── MainApplication.kt
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── debug
│ │ │ │ └── AndroidManifest.xml
│ │ ├── proguard-rules.pro
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── settings.gradle
│ ├── build.gradle
│ ├── gradle.properties
│ └── gradlew.bat
├── src
│ ├── App.tsx
│ ├── screens
│ │ ├── styles.ts
│ │ ├── InnerExplore.tsx
│ │ ├── Explore.tsx
│ │ └── Home.tsx
│ └── AppNavigationContainer.tsx
├── index.js
├── babel.config.js
├── metro.config.js
├── react-native.config.js
├── Gemfile
├── package.json
├── Gemfile.lock
└── README.md
├── src
├── __tests__
│ └── index.test.tsx
├── types
│ ├── LockedEvent.interface.ts
│ ├── AutoRotation.enum.ts
│ ├── OrientationType.enum.ts
│ ├── OrientationEvent.interface.ts
│ ├── HumanReadableOrientationsResource.type.ts
│ ├── HumanReadableAutoRotationsResource.type.ts
│ ├── Event.enum.ts
│ ├── Orientation.enum.ts
│ └── LockableOrientation.type.ts
├── index.tsx
├── NativeOrientationDirector.ts
├── hooks
│ ├── useIsInterfaceOrientationLocked.hook.ts
│ ├── useDeviceOrientation.hook.ts
│ └── useInterfaceOrientation.hook.ts
├── module.ts
├── EventEmitter.ts
└── RNOrientationDirector.ts
├── app.plugin.js
├── plugin
├── tsconfig.tsbuildinfo
├── jest.config.js
├── .eslintrc.js
├── __tests__
│ ├── fixtures
│ │ ├── Bridging-Header.h
│ │ ├── AppDelegate52.swift
│ │ ├── MainActivity.kt
│ │ ├── MainActivityWithIntentImport.kt
│ │ ├── MainActivityWithConfigurationImport.kt
│ │ ├── AppDelegate53.swift
│ │ └── AppDelegate.mm
│ ├── __snapshots__
│ │ └── withRNOrientationBridgingHeader.spec.ts.snap
│ ├── withRNOrientationBridgingHeader.spec.ts
│ ├── withRNOrientationMainActivity.spec.ts
│ └── withRNOrientationAppDelegate.spec.ts
├── tsconfig.json
├── README.md
├── package.json
└── src
│ ├── withRNOrientationBridgingHeader.ts
│ ├── index.ts
│ ├── custom-mod
│ └── withBridgingHeader.ts
│ ├── withRNOrientationAppDelegate.ts
│ └── withRNOrientationMainActivity.ts
├── .gitattributes
├── tsconfig.build.json
├── android
├── src
│ ├── main
│ │ ├── AndroidManifestNew.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── orientationdirector
│ │ │ ├── implementation
│ │ │ ├── Orientation.kt
│ │ │ ├── LifecycleListener.kt
│ │ │ ├── AutoRotationObserver.kt
│ │ │ ├── EventManager.kt
│ │ │ ├── ConfigurationChangedBroadcastReceiver.kt
│ │ │ ├── Utils.kt
│ │ │ └── OrientationSensorsEventListener.kt
│ │ │ └── OrientationDirectorPackage.kt
│ ├── newarch
│ │ └── OrientationDirectorModule.kt
│ ├── oldarch
│ │ └── OrientationDirectorModule.kt
│ └── test
│ │ └── java
│ │ └── com
│ │ └── orientationdirector
│ │ └── implementation
│ │ └── OrientationDirectorModuleImplTest.kt
├── gradle.properties
└── build.gradle
├── ios
├── react-native-orientation-director-Bridging-Header.h
├── implementation
│ ├── Orientation.swift
│ ├── SensorListener.swift
│ ├── BundleManager.swift
│ ├── EventManager.swift
│ └── Utils.swift
├── OrientationDirector.h
└── OrientationDirector.mm
├── babel.config.js
├── .editorconfig
├── .yarnrc.yml
├── lefthook.yml
├── .github
├── ISSUE_TEMPLATE
│ ├── 🔨-feature-request.md
│ └── 🐛-bug-report.md
├── actions
│ └── setup
│ │ └── action.yml
└── workflows
│ ├── pr.yml
│ └── release.yml
├── tsconfig.json
├── turbo.json
├── LICENSE
├── eslint.config.mjs
├── .gitignore
├── react-native-orientation-director.podspec
├── CONTRIBUTING.md
├── package.json
└── CODE_OF_CONDUCT.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20
2 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/example/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/example/ios/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.4.4
2 |
--------------------------------------------------------------------------------
/src/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | it.todo('write a test');
2 |
--------------------------------------------------------------------------------
/app.plugin.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./plugin/build');
2 |
--------------------------------------------------------------------------------
/plugin/tsconfig.tsbuildinfo:
--------------------------------------------------------------------------------
1 | {"root":["./src/index.ts"],"version":"5.6.3"}
2 |
--------------------------------------------------------------------------------
/example/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'react-native',
3 | };
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 | # specific for windows script files
3 | *.bat text eol=crlf
--------------------------------------------------------------------------------
/example/.bundle/config:
--------------------------------------------------------------------------------
1 | BUNDLE_PATH: "vendor/bundle"
2 | BUNDLE_FORCE_RUBY_PLATFORM: 1
3 |
--------------------------------------------------------------------------------
/plugin/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require('expo-module-scripts/jest-preset-plugin');
2 |
--------------------------------------------------------------------------------
/src/types/LockedEvent.interface.ts:
--------------------------------------------------------------------------------
1 | export interface LockedEvent {
2 | locked: boolean;
3 | }
4 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "exclude": ["example", "lib", "plugin"]
4 | }
5 |
--------------------------------------------------------------------------------
/example/ios/File.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // OrientationDirectorExample
4 | //
5 |
6 | import Foundation
7 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "OrientationDirectorExample",
3 | "displayName": "OrientationDirectorExample"
4 | }
5 |
--------------------------------------------------------------------------------
/src/types/AutoRotation.enum.ts:
--------------------------------------------------------------------------------
1 | export enum AutoRotation {
2 | unknown = 0,
3 | enabled = 1,
4 | disabled = 2,
5 | }
6 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifestNew.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/plugin/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // @generated by expo-module-scripts
2 | module.exports = require('expo-module-scripts/eslintrc.base.js');
3 |
--------------------------------------------------------------------------------
/src/types/OrientationType.enum.ts:
--------------------------------------------------------------------------------
1 | export enum OrientationType {
2 | device = 'device',
3 | interface = 'interface',
4 | }
5 |
--------------------------------------------------------------------------------
/example/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/debug.keystore
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | OrientationDirectorExample
3 |
4 |
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/plugin/__tests__/fixtures/Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import AppNavigationContainer from './AppNavigationContainer';
2 |
3 | export default function App() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/src/types/OrientationEvent.interface.ts:
--------------------------------------------------------------------------------
1 | import type { Orientation } from './Orientation.enum';
2 |
3 | export interface OrientationEvent {
4 | orientation: Orientation;
5 | }
6 |
--------------------------------------------------------------------------------
/example/android/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/src/types/HumanReadableOrientationsResource.type.ts:
--------------------------------------------------------------------------------
1 | import type { Orientation } from './Orientation.enum';
2 |
3 | export type HumanReadableOrientationsResource = Record;
4 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/src/types/HumanReadableAutoRotationsResource.type.ts:
--------------------------------------------------------------------------------
1 | import type { AutoRotation } from './AutoRotation.enum';
2 |
3 | export type HumanReadableAutoRotationsResource = Record;
4 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import { AppRegistry } from 'react-native';
2 | import App from './src/App';
3 | import { name as appName } from './app.json';
4 |
5 | AppRegistry.registerComponent(appName, () => App);
6 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "OrientationDirector.h"
6 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/120.png
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/120 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gladiuscode/react-native-orientation-director/HEAD/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/120 1.png
--------------------------------------------------------------------------------
/ios/react-native-orientation-director-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "React/RCTBridgeModule.h"
6 | #import "React/RCTEventEmitter.h"
7 |
--------------------------------------------------------------------------------
/src/types/Event.enum.ts:
--------------------------------------------------------------------------------
1 | enum Event {
2 | DeviceOrientationDidChange = 'DeviceOrientationDidChange',
3 | InterfaceOrientationDidChange = 'InterfaceOrientationDidChange',
4 | LockDidChange = 'LockDidChange',
5 | }
6 |
7 | export default Event;
8 |
--------------------------------------------------------------------------------
/src/types/Orientation.enum.ts:
--------------------------------------------------------------------------------
1 | export enum Orientation {
2 | unknown = 0,
3 | portrait = 1,
4 | landscapeRight = 2,
5 | portraitUpsideDown = 3,
6 | landscapeLeft = 4,
7 | landscape = 5,
8 | faceUp = 6,
9 | faceDown = 7,
10 | }
11 |
--------------------------------------------------------------------------------
/plugin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo-module-scripts/tsconfig.plugin",
3 | "compilerOptions": {
4 | "outDir": "build",
5 | "rootDir": "src"
6 | },
7 | "include": ["./src"],
8 | "exclude": ["**/__mocks__/*", "**/__tests__/*"]
9 | }
10 |
--------------------------------------------------------------------------------
/plugin/README.md:
--------------------------------------------------------------------------------
1 | # react-native-orientation-director
2 |
3 | This package comes with its own expo plugin, which is a wrapper around the native module. The plugin automatically
4 | setups the native code for you, so you can enjoy CNG without touching the native code.
5 |
--------------------------------------------------------------------------------
/src/types/LockableOrientation.type.ts:
--------------------------------------------------------------------------------
1 | import { Orientation } from './Orientation.enum';
2 |
3 | export type LockableOrientation =
4 | | Orientation.portrait
5 | | Orientation.portraitUpsideDown
6 | | Orientation.landscapeLeft
7 | | Orientation.landscapeRight
8 | | Orientation.landscape;
9 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | overrides: [
3 | {
4 | exclude: /\/node_modules\//,
5 | presets: ['module:react-native-builder-bob/babel-preset'],
6 | },
7 | {
8 | include: /\/node_modules\//,
9 | presets: ['module:@react-native/babel-preset'],
10 | },
11 | ],
12 | };
13 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | indent_style = space
10 | indent_size = 2
11 |
12 | end_of_line = lf
13 | charset = utf-8
14 | trim_trailing_whitespace = true
15 | insert_final_newline = true
16 |
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ios/implementation/Orientation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Orientation.swift
3 | // react-native-orientation-director
4 | //
5 | // Created by gladiuscode on 19/05/2024.
6 | //
7 |
8 | import Foundation
9 |
10 | @objc public enum Orientation: Int {
11 | case UNKNOWN, PORTRAIT, LANDSCAPE_RIGHT, PORTRAIT_UPSIDE_DOWN, LANDSCAPE_LEFT, LANDSCAPE, FACE_UP, FACE_DOWN
12 | }
13 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 | nmHoistingLimits: workspaces
3 |
4 | plugins:
5 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
6 | spec: "@yarnpkg/plugin-interactive-tools"
7 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
8 | spec: "@yarnpkg/plugin-workspace-tools"
9 |
10 | yarnPath: .yarn/releases/yarn-3.6.1.cjs
11 |
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { getConfig } = require('react-native-builder-bob/babel-config');
3 | const pkg = require('../package.json');
4 |
5 | const root = path.resolve(__dirname, '..');
6 |
7 | module.exports = getConfig(
8 | {
9 | presets: ['module:@react-native/babel-preset'],
10 | },
11 | { root, pkg }
12 | );
13 |
--------------------------------------------------------------------------------
/android/src/main/java/com/orientationdirector/implementation/Orientation.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirector.implementation
2 |
3 | /*
4 | Don't change values positions
5 | */
6 | enum class Orientation {
7 | UNKNOWN,
8 | PORTRAIT,
9 | LANDSCAPE_RIGHT,
10 | PORTRAIT_UPSIDE_DOWN,
11 | LANDSCAPE_LEFT,
12 | LANDSCAPE,
13 | FACE_UP,
14 | FACE_DOWN,
15 | }
16 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
2 | plugins { id("com.facebook.react.settings") }
3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
4 | rootProject.name = 'OrientationDirectorExample'
5 | include ':app'
6 | includeBuild('../node_modules/@react-native/gradle-plugin')
7 |
--------------------------------------------------------------------------------
/plugin/__tests__/__snapshots__/withRNOrientationBridgingHeader.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`withRNOrientationBridgingHeader updates the App Bridging-Header with the header import 1`] = `
4 | "//
5 | // Use this file to import your target's public headers that you would like to expose to Swift.
6 | //
7 |
8 | #import "OrientationDirector.h""
9 | `;
10 |
--------------------------------------------------------------------------------
/example/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/example/metro.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { getDefaultConfig } = require('@react-native/metro-config');
3 | const { getConfig } = require('react-native-builder-bob/metro-config');
4 |
5 | const root = path.resolve(__dirname, '..');
6 |
7 | /**
8 | * Metro configuration
9 | * https://facebook.github.io/metro/docs/configuration
10 | *
11 | * @type {import('metro-config').MetroConfig}
12 | */
13 | module.exports = getConfig(getDefaultConfig(__dirname), {
14 | root,
15 | project: __dirname,
16 | });
17 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | OrientationDirector_kotlinVersion=2.0.21
2 | OrientationDirector_minSdkVersion=24
3 | OrientationDirector_targetSdkVersion=34
4 | OrientationDirector_compileSdkVersion=35
5 | OrientationDirector_ndkVersion=27.1.12297006
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 | #########################
18 |
--------------------------------------------------------------------------------
/example/ios/.xcode.env:
--------------------------------------------------------------------------------
1 | # This `.xcode.env` file is versioned and is used to source the environment
2 | # used when running script phases inside Xcode.
3 | # To customize your local environment, you can create an `.xcode.env.local`
4 | # file that is not versioned.
5 |
6 | # NODE_BINARY variable contains the PATH to the node executable.
7 | #
8 | # Customize the NODE_BINARY variable here.
9 | # For example, to use nvm with brew, add the following line
10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use
11 | export NODE_BINARY=$(command -v node)
12 |
--------------------------------------------------------------------------------
/example/react-native.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const pkg = require('../package.json');
3 |
4 | module.exports = {
5 | project: {
6 | ios: {
7 | automaticPodsInstallation: true,
8 | },
9 | },
10 | dependencies: {
11 | [pkg.name]: {
12 | root: path.join(__dirname, '..'),
13 | platforms: {
14 | // Codegen script incorrectly fails without this
15 | // So we explicitly specify the platforms with empty object
16 | ios: {},
17 | android: {},
18 | },
19 | },
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/example/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
4 | ruby ">= 2.6.10"
5 |
6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures.
7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
9 | gem 'xcodeproj', '< 1.26.0'
10 | gem 'concurrent-ruby', '< 1.3.4'
11 |
12 | # Ruby 3.4.0 has removed some libraries from the standard library.
13 | gem 'bigdecimal'
14 | gem 'logger'
15 | gem 'benchmark'
16 | gem 'mutex_m'
17 |
--------------------------------------------------------------------------------
/ios/OrientationDirector.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #ifdef RCT_NEW_ARCH_ENABLED
4 | #import
5 |
6 | NS_ASSUME_NONNULL_BEGIN
7 |
8 | @interface OrientationDirector : RCTEventEmitter
9 |
10 | NS_ASSUME_NONNULL_END
11 |
12 | #else
13 | #import
14 |
15 | @interface OrientationDirector : RCTEventEmitter
16 | #endif
17 |
18 | @property (nonatomic, assign) BOOL isJsListening;
19 |
20 | +(UIInterfaceOrientationMask)getSupportedInterfaceOrientationsForWindow;
21 |
22 | @end
23 |
--------------------------------------------------------------------------------
/plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-orientation-director-expo-plugin",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "build": "expo-module build",
7 | "clean": "expo-module clean",
8 | "test": "expo-module test",
9 | "typecheck": "expo-module typecheck",
10 | "lint": "expo-module lint",
11 | "prepare": "expo-module prepare",
12 | "expo-module": "expo-module"
13 | },
14 | "devDependencies": {
15 | "expo": "53.0.13",
16 | "expo-module-scripts": "4.1.8",
17 | "glob": "11.0.3"
18 | },
19 | "engines": {
20 | "node": ">=18"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | buildToolsVersion = "35.0.0"
4 | minSdkVersion = 24
5 | compileSdkVersion = 35
6 | targetSdkVersion = 35
7 | ndkVersion = "27.1.12297006"
8 | kotlinVersion = "2.1.20"
9 | }
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | dependencies {
15 | classpath("com.android.tools.build:gradle")
16 | classpath("com.facebook.react:react-native-gradle-plugin")
17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
18 | }
19 | }
20 |
21 | apply plugin: "com.facebook.react.rootproject"
22 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | export { Orientation } from './types/Orientation.enum';
2 | export { AutoRotation } from './types/AutoRotation.enum';
3 | export { OrientationType } from './types/OrientationType.enum';
4 |
5 | import useDeviceOrientation from './hooks/useDeviceOrientation.hook';
6 | export { useDeviceOrientation };
7 |
8 | import useInterfaceOrientation from './hooks/useInterfaceOrientation.hook';
9 | export { useInterfaceOrientation };
10 |
11 | import useIsInterfaceOrientationLocked from './hooks/useIsInterfaceOrientationLocked.hook';
12 | export { useIsInterfaceOrientationLocked };
13 |
14 | import RNOrientationDirector from './RNOrientationDirector';
15 | export default RNOrientationDirector;
16 |
--------------------------------------------------------------------------------
/lefthook.yml:
--------------------------------------------------------------------------------
1 | pre-commit:
2 | parallel: true
3 | commands:
4 | lint-src:
5 | glob: "src/*.{js,ts,jsx,tsx}"
6 | run: npx eslint {staged_files}
7 | lint-plugin:
8 | glob: "plugin/src/*.{ts}"
9 | run: npx eslint {staged_files}
10 | lint-example:
11 | glob: "example/src/*.{ts}"
12 | run: npx eslint {staged_files}
13 | types-src:
14 | glob: "src/*.{js,ts,jsx,tsx}"
15 | run: npx tsc
16 | types-plugin:
17 | glob: "plugin/src/*.{js,ts,jsx,tsx}"
18 | run: npx tsc
19 | types-example:
20 | glob: "example/src/*.{js,ts,jsx,tsx}"
21 | run: npx tsc
22 | commit-msg:
23 | parallel: true
24 | commands:
25 | commitlint:
26 | run: npx commitlint --edit
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/🔨-feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F528 Feature request"
3 | about: Suggest an idea for this project
4 | title: "[FEATURE]"
5 | labels: enhancement
6 | assignees: gladiuscode
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/example/src/screens/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export const homepageStyle = StyleSheet.create({
4 | container: {
5 | flexGrow: 1,
6 | padding: 20,
7 | },
8 | marginBottom: {
9 | marginBottom: 12,
10 | },
11 | buttonsContainer: {
12 | flex: 1,
13 | justifyContent: 'center',
14 | },
15 | text: {
16 | color: 'black',
17 | },
18 | });
19 |
20 | export const exploreStyle = StyleSheet.create({
21 | container: homepageStyle.container,
22 | marginBottom: homepageStyle.marginBottom,
23 | text: homepageStyle.text,
24 | body: {
25 | flex: 1,
26 | justifyContent: 'center',
27 | alignItems: 'center',
28 | },
29 | });
30 |
31 | export const innerExploreStyle = StyleSheet.create({
32 | ...exploreStyle,
33 | });
34 |
--------------------------------------------------------------------------------
/plugin/__tests__/withRNOrientationBridgingHeader.spec.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'node:fs';
2 | import * as path from 'node:path';
3 |
4 | import { bridgingHeaderUpdater } from '../src/withRNOrientationBridgingHeader';
5 |
6 | describe('withRNOrientationBridgingHeader', function () {
7 | beforeEach(function () {
8 | jest.resetAllMocks();
9 | });
10 |
11 | it('updates the App Bridging-Header with the header import', async function () {
12 | const bridgingHeaderPath = path.join(
13 | __dirname,
14 | './fixtures/Bridging-Header.h'
15 | );
16 | const bridgingHeader = await fs.promises.readFile(
17 | bridgingHeaderPath,
18 | 'utf-8'
19 | );
20 |
21 | const result = bridgingHeaderUpdater(bridgingHeader);
22 | expect(result).toMatchSnapshot();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/.github/actions/setup/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup
2 | description: Setup Node.js and install dependencies
3 |
4 | runs:
5 | using: composite
6 | steps:
7 | - name: Setup Node.js
8 | uses: actions/setup-node@v3
9 | with:
10 | node-version-file: .nvmrc
11 |
12 | - name: Cache dependencies
13 | id: yarn-cache
14 | uses: actions/cache@v3
15 | with:
16 | path: |
17 | **/node_modules
18 | .yarn/install-state.gz
19 | key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }}
20 | restore-keys: |
21 | ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
22 | ${{ runner.os }}-yarn-
23 |
24 | - name: Install dependencies
25 | if: steps.yarn-cache.outputs.cache-hit != 'true'
26 | run: yarn install --immutable
27 | shell: bash
28 |
--------------------------------------------------------------------------------
/src/NativeOrientationDirector.ts:
--------------------------------------------------------------------------------
1 | import type { TurboModule } from 'react-native';
2 | import { TurboModuleRegistry } from 'react-native';
3 |
4 | export interface Spec extends TurboModule {
5 | getInterfaceOrientation(): Promise;
6 | getDeviceOrientation(): Promise;
7 | lockTo(orientation: number): void;
8 | unlock(): void;
9 | isLocked(): boolean;
10 | resetSupportedInterfaceOrientations(): void;
11 |
12 | ////////////////////////////////////
13 | //
14 | // ANDROID ONLY
15 | //
16 |
17 | isAutoRotationEnabled(): boolean;
18 | enableOrientationSensors(): void;
19 | disableOrientationSensors(): void;
20 |
21 | //
22 | ////////////////////////////////////
23 |
24 | addListener: (eventType: string) => void;
25 | removeListeners: (count: number) => void;
26 | }
27 |
28 | export default TurboModuleRegistry.getEnforcing('OrientationDirector');
29 |
--------------------------------------------------------------------------------
/example/src/screens/InnerExplore.tsx:
--------------------------------------------------------------------------------
1 | import { Text, View } from 'react-native';
2 | import { innerExploreStyle } from './styles';
3 | import RNOrientationDirector, {
4 | useDeviceOrientation,
5 | } from 'react-native-orientation-director';
6 |
7 | function InnerExplore() {
8 | const deviceOrientation = useDeviceOrientation();
9 |
10 | return (
11 |
12 | Inner Explore!
13 |
14 |
15 |
16 |
17 | Current Device Orientation:
18 | {RNOrientationDirector.convertOrientationToHumanReadableString(
19 | deviceOrientation
20 | )}
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | export default InnerExplore;
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "rootDir": ".",
4 | "paths": {
5 | "react-native-orientation-director": ["./src/index"]
6 | },
7 | "allowUnreachableCode": false,
8 | "allowUnusedLabels": false,
9 | "esModuleInterop": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "jsx": "react-jsx",
12 | "lib": ["ESNext"],
13 | "module": "ESNext",
14 | "moduleResolution": "bundler",
15 | "noEmit": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "noImplicitReturns": true,
18 | "noImplicitUseStrict": false,
19 | "noStrictGenericChecks": false,
20 | "noUncheckedIndexedAccess": true,
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true,
23 | "resolveJsonModule": true,
24 | "skipLibCheck": true,
25 | "strict": true,
26 | "target": "ESNext",
27 | "verbatimModuleSyntax": true
28 | },
29 | "exclude": ["plugin"]
30 | }
31 |
--------------------------------------------------------------------------------
/src/hooks/useIsInterfaceOrientationLocked.hook.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import RNOrientationDirector from '../RNOrientationDirector';
3 | import type { LockedEvent } from '../types/LockedEvent.interface';
4 |
5 | /**
6 | * Hook that returns whether the interface is locked.
7 | * It listens for changes and updates the state accordingly.
8 | */
9 | const useIsInterfaceOrientationLocked = () => {
10 | const [orientation, setOrientation] = React.useState(() =>
11 | RNOrientationDirector.isLocked()
12 | );
13 |
14 | React.useEffect(() => {
15 | const onChange = (event: LockedEvent) => {
16 | setOrientation(event.locked);
17 | };
18 |
19 | const subscription = RNOrientationDirector.listenForLockChanges(onChange);
20 | return () => {
21 | subscription.remove();
22 | };
23 | }, []);
24 |
25 | return orientation;
26 | };
27 |
28 | export default useIsInterfaceOrientationLocked;
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/🐛-bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug report"
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: gladiuscode
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Smartphone (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Device: [e.g. iPhone 12]
29 | - Version [e.g. 15]
30 |
31 | **Environment**
32 | Please run: ```npx react-native info``` and provide its output.
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "pipeline": {
4 | "build:android": {
5 | "env": ["ORG_GRADLE_PROJECT_newArchEnabled"],
6 | "inputs": [
7 | "package.json",
8 | "android",
9 | "!android/build",
10 | "src/*.ts",
11 | "src/*.tsx",
12 | "example/package.json",
13 | "example/android",
14 | "!example/android/.gradle",
15 | "!example/android/build",
16 | "!example/android/app/build"
17 | ],
18 | "outputs": []
19 | },
20 | "build:ios": {
21 | "env": ["RCT_NEW_ARCH_ENABLED"],
22 | "inputs": [
23 | "package.json",
24 | "*.podspec",
25 | "ios",
26 | "src/*.ts",
27 | "src/*.tsx",
28 | "example/package.json",
29 | "example/ios",
30 | "!example/ios/build",
31 | "!example/ios/Pods"
32 | ],
33 | "outputs": []
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/module.ts:
--------------------------------------------------------------------------------
1 | import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
2 | import type { Spec } from './NativeOrientationDirector';
3 |
4 | const LINKING_ERROR =
5 | `The package 'react-native-orientation-director' doesn't seem to be linked. Make sure: \n\n` +
6 | Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
7 | '- You rebuilt the app after installing the package\n' +
8 | '- You are not using Expo Go\n';
9 |
10 | // @ts-expect-error
11 | const isTurboModuleEnabled = global.__turboModuleProxy != null;
12 |
13 | const Module = isTurboModuleEnabled
14 | ? require('./NativeOrientationDirector').default
15 | : NativeModules.OrientationDirector;
16 |
17 | const OrientationDirectorModule = Module
18 | ? Module
19 | : new Proxy(
20 | {},
21 | {
22 | get() {
23 | throw new Error(LINKING_ERROR);
24 | },
25 | }
26 | );
27 |
28 | export const ModuleEventEmitter = new NativeEventEmitter(Module);
29 |
30 | export default OrientationDirectorModule as Spec;
31 |
--------------------------------------------------------------------------------
/android/src/main/java/com/orientationdirector/implementation/LifecycleListener.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirector.implementation
2 |
3 | import com.facebook.react.bridge.LifecycleEventListener
4 |
5 | class LifecycleListener : LifecycleEventListener {
6 |
7 | private var onHostResumeCallback: (() -> Unit)? = null
8 | private var onHostPauseCallback: (() -> Unit)? = null
9 | private var onHostDestroyCallback: (() -> Unit)? = null
10 |
11 | fun setOnHostResumeCallback(callback: () -> Unit) {
12 | this.onHostResumeCallback = callback
13 | }
14 | fun setOnHostPauseCallback(callback: () -> Unit) {
15 | this.onHostPauseCallback = callback
16 | }
17 | fun setOnHostDestroyCallback(callback: () -> Unit) {
18 | this.onHostDestroyCallback = callback
19 | }
20 |
21 | override fun onHostResume() {
22 | this.onHostResumeCallback?.invoke()
23 | }
24 |
25 | override fun onHostPause() {
26 | this.onHostPauseCallback?.invoke()
27 | }
28 |
29 | override fun onHostDestroy() {
30 | this.onHostDestroyCallback?.invoke()
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/plugin/__tests__/fixtures/AppDelegate52.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import React
3 | import React_RCTAppDelegate
4 | import ReactAppDependencyProvider
5 |
6 | @main
7 | class AppDelegate: RCTAppDelegate {
8 | override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
9 | self.moduleName = "main"
10 | self.dependencyProvider = RCTAppDependencyProvider()
11 |
12 | // You can add your custom initial props in the dictionary below.
13 | // They will be passed down to the ViewController used by React Native.
14 | self.initialProps = [:]
15 |
16 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
17 | }
18 |
19 | override func sourceURL(for bridge: RCTBridge) -> URL? {
20 | self.bundleURL()
21 | }
22 |
23 | override func bundleURL() -> URL? {
24 | #if DEBUG
25 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
26 | #else
27 | Bundle.main.url(forResource: "main", withExtension: "jsbundle")
28 | #endif
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 gladius
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 |
--------------------------------------------------------------------------------
/plugin/__tests__/fixtures/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirectorexample
2 |
3 | import android.os.Bundle
4 | import com.facebook.react.ReactActivity
5 | import com.facebook.react.ReactActivityDelegate
6 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
7 | import com.facebook.react.defaults.DefaultReactActivityDelegate
8 |
9 | class MainActivity : ReactActivity() {
10 |
11 | /**
12 | * Returns the name of the main component registered from JavaScript. This is used to schedule
13 | * rendering of the component.
14 | */
15 | override fun getMainComponentName(): String = "OrientationDirectorExample"
16 |
17 | /**
18 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
19 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
20 | */
21 | override fun createReactActivityDelegate(): ReactActivityDelegate =
22 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
23 |
24 | override fun onCreate(savedInstanceState: Bundle?) {
25 | super.onCreate(null)
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/plugin/__tests__/fixtures/MainActivityWithIntentImport.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirectorexample
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import com.facebook.react.ReactActivity
6 | import com.facebook.react.ReactActivityDelegate
7 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
8 | import com.facebook.react.defaults.DefaultReactActivityDelegate
9 |
10 | class MainActivity : ReactActivity() {
11 |
12 | /**
13 | * Returns the name of the main component registered from JavaScript. This is used to schedule
14 | * rendering of the component.
15 | */
16 | override fun getMainComponentName(): String = "OrientationDirectorExample"
17 |
18 | /**
19 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
20 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
21 | */
22 | override fun createReactActivityDelegate(): ReactActivityDelegate =
23 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
24 |
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(null)
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyAccessedAPITypes
6 |
7 |
8 | NSPrivacyAccessedAPIType
9 | NSPrivacyAccessedAPICategoryFileTimestamp
10 | NSPrivacyAccessedAPITypeReasons
11 |
12 | C617.1
13 |
14 |
15 |
16 | NSPrivacyAccessedAPIType
17 | NSPrivacyAccessedAPICategoryUserDefaults
18 | NSPrivacyAccessedAPITypeReasons
19 |
20 | CA92.1
21 |
22 |
23 |
24 | NSPrivacyAccessedAPIType
25 | NSPrivacyAccessedAPICategorySystemBootTime
26 | NSPrivacyAccessedAPITypeReasons
27 |
28 | 35F9.1
29 |
30 |
31 |
32 | NSPrivacyCollectedDataTypes
33 |
34 | NSPrivacyTracking
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/plugin/__tests__/fixtures/MainActivityWithConfigurationImport.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirectorexample
2 |
3 | import android.content.res.Configuration
4 | import android.os.Bundle
5 | import com.facebook.react.ReactActivity
6 | import com.facebook.react.ReactActivityDelegate
7 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
8 | import com.facebook.react.defaults.DefaultReactActivityDelegate
9 |
10 | class MainActivity : ReactActivity() {
11 |
12 | /**
13 | * Returns the name of the main component registered from JavaScript. This is used to schedule
14 | * rendering of the component.
15 | */
16 | override fun getMainComponentName(): String = "OrientationDirectorExample"
17 |
18 | /**
19 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
20 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
21 | */
22 | override fun createReactActivityDelegate(): ReactActivityDelegate =
23 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
24 |
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(null)
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { fixupConfigRules } from '@eslint/compat';
2 | import { FlatCompat } from '@eslint/eslintrc';
3 | import js from '@eslint/js';
4 | import prettier from 'eslint-plugin-prettier';
5 | import { defineConfig } from 'eslint/config';
6 | import path from 'node:path';
7 | import { fileURLToPath } from 'node:url';
8 |
9 | const __filename = fileURLToPath(import.meta.url);
10 | const __dirname = path.dirname(__filename);
11 | const compat = new FlatCompat({
12 | baseDirectory: __dirname,
13 | recommendedConfig: js.configs.recommended,
14 | allConfig: js.configs.all,
15 | });
16 |
17 | export default defineConfig([
18 | {
19 | extends: fixupConfigRules(compat.extends('@react-native', 'prettier')),
20 | plugins: { prettier },
21 | rules: {
22 | 'react/react-in-jsx-scope': 'off',
23 | 'prettier/prettier': [
24 | 'error',
25 | {
26 | quoteProps: 'consistent',
27 | singleQuote: true,
28 | tabWidth: 2,
29 | trailingComma: 'es5',
30 | useTabs: false,
31 | },
32 | ],
33 | },
34 | },
35 | {
36 | ignores: [
37 | 'node_modules/',
38 | 'lib/'
39 | ],
40 | },
41 | ]);
42 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Resolve react_native_pods.rb with node to allow for hoisting
2 | require Pod::Executable.execute_command('node', ['-p',
3 | 'require.resolve(
4 | "react-native/scripts/react_native_pods.rb",
5 | {paths: [process.argv[1]]},
6 | )', __dir__]).strip
7 |
8 | platform :ios, min_ios_version_supported
9 | prepare_react_native_project!
10 |
11 | linkage = ENV['USE_FRAMEWORKS']
12 | if linkage != nil
13 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
14 | use_frameworks! :linkage => linkage.to_sym
15 | end
16 |
17 | target 'OrientationDirectorExample' do
18 | config = use_native_modules!
19 |
20 | use_react_native!(
21 | :path => config[:reactNativePath],
22 | # An absolute path to your application root.
23 | :app_path => "#{Pod::Config.instance.installation_root}/.."
24 | )
25 |
26 | post_install do |installer|
27 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
28 | react_native_post_install(
29 | installer,
30 | config[:reactNativePath],
31 | :mac_catalyst_enabled => false,
32 | # :ccache_enabled => true
33 | )
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/ios/implementation/SensorListener.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrientationSensorListener.swift
3 | // react-native-orientation-director
4 | //
5 | // Created by gladiuscode on 18/05/2024.
6 | //
7 |
8 | import Foundation
9 |
10 | public class SensorListener {
11 | private var onOrientationDidChangeCallback: ((_ deviceOrientation: UIDeviceOrientation) -> Void)?
12 |
13 | init() {
14 | NotificationCenter.default.addObserver(
15 | self,
16 | selector: #selector(orientationDidChange),
17 | name: UIDevice.orientationDidChangeNotification,
18 | object: nil
19 | )
20 | }
21 |
22 | deinit {
23 | NotificationCenter.default.removeObserver(self)
24 | }
25 |
26 | func setOnOrientationDidChange(callback: @escaping (_ deviceOrientation: UIDeviceOrientation) -> Void) {
27 | self.onOrientationDidChangeCallback = callback
28 | }
29 |
30 | @objc func orientationDidChange(_ notification: Notification) {
31 | guard let onOrientationDidChangeCallback = self.onOrientationDidChangeCallback else {
32 | return
33 | }
34 |
35 | onOrientationDidChangeCallback(UIDevice.current.orientation)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # XDE
6 | .expo/
7 |
8 | # VSCode
9 | .vscode/
10 | jsconfig.json
11 |
12 | # Xcode
13 | #
14 | build/
15 | *.pbxuser
16 | !default.pbxuser
17 | *.mode1v3
18 | !default.mode1v3
19 | *.mode2v3
20 | !default.mode2v3
21 | *.perspectivev3
22 | !default.perspectivev3
23 | xcuserdata
24 | *.xccheckout
25 | *.moved-aside
26 | DerivedData
27 | *.hmap
28 | *.ipa
29 | *.xcuserstate
30 | project.xcworkspace
31 | **/.xcode.env.local
32 |
33 | # Android/IJ
34 | #
35 | .classpath
36 | .cxx
37 | .gradle
38 | .idea
39 | .project
40 | .settings
41 | local.properties
42 | android.iml
43 |
44 | # Cocoapods
45 | #
46 | example/ios/Pods
47 |
48 | # Ruby
49 | example/vendor/
50 |
51 | # node.js
52 | #
53 | node_modules/
54 | npm-debug.log
55 | yarn-debug.log
56 | yarn-error.log
57 |
58 | # BUCK
59 | buck-out/
60 | \.buckd/
61 | android/app/libs
62 | android/keystores/debug.keystore
63 | .kotlin/
64 |
65 | # Yarn
66 | .yarn/*
67 | !.yarn/patches
68 | !.yarn/plugins
69 | !.yarn/releases
70 | !.yarn/sdks
71 | !.yarn/versions
72 |
73 | # Expo
74 | .expo/
75 |
76 | # Turborepo
77 | .turbo/
78 |
79 | # generated by bob
80 | lib/
81 |
82 | # React Native Codegen
83 | ios/generated
84 | android/generated
85 |
86 | # React Native Nitro Modules
87 | nitrogen/
88 |
--------------------------------------------------------------------------------
/src/hooks/useDeviceOrientation.hook.ts:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import RNOrientationDirector from '../RNOrientationDirector';
3 | import type { OrientationEvent } from '../types/OrientationEvent.interface';
4 | import { Orientation } from '../types/Orientation.enum';
5 |
6 | /**
7 | * Hook that returns the current device orientation.
8 | * It listens for orientation changes and updates the state accordingly.
9 | */
10 | const useDeviceOrientation = () => {
11 | const initialRender = useRef(false);
12 | const [orientation, setOrientation] = React.useState(
13 | Orientation.unknown
14 | );
15 |
16 | React.useEffect(() => {
17 | if (initialRender.current) {
18 | return;
19 | }
20 |
21 | initialRender.current = true;
22 | RNOrientationDirector.getDeviceOrientation().then(setOrientation);
23 | }, []);
24 |
25 | React.useEffect(() => {
26 | const onChange = (event: OrientationEvent) => {
27 | setOrientation(event.orientation);
28 | };
29 |
30 | const subscription =
31 | RNOrientationDirector.listenForDeviceOrientationChanges(onChange);
32 | return () => {
33 | subscription.remove();
34 | };
35 | }, []);
36 |
37 | return orientation;
38 | };
39 |
40 | export default useDeviceOrientation;
41 |
--------------------------------------------------------------------------------
/src/hooks/useInterfaceOrientation.hook.ts:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import RNOrientationDirector from '../RNOrientationDirector';
3 | import type { OrientationEvent } from '../types/OrientationEvent.interface';
4 | import { Orientation } from '../types/Orientation.enum';
5 |
6 | /**
7 | * Hook that returns the current interface orientation.
8 | * It listens for orientation changes and updates the state accordingly.
9 | */
10 | const useInterfaceOrientation = () => {
11 | const initialRender = useRef(false);
12 | const [orientation, setOrientation] = React.useState(
13 | Orientation.unknown
14 | );
15 |
16 | React.useEffect(() => {
17 | if (initialRender.current) {
18 | return;
19 | }
20 |
21 | initialRender.current = true;
22 | RNOrientationDirector.getInterfaceOrientation().then(setOrientation);
23 | }, []);
24 |
25 | React.useEffect(() => {
26 | const onChange = (event: OrientationEvent) => {
27 | setOrientation(event.orientation);
28 | };
29 |
30 | const subscription =
31 | RNOrientationDirector.listenForInterfaceOrientationChanges(onChange);
32 | return () => {
33 | subscription.remove();
34 | };
35 | }, []);
36 |
37 | return orientation;
38 | };
39 |
40 | export default useInterfaceOrientation;
41 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-orientation-director-example",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "android": "react-native run-android",
7 | "ios": "react-native run-ios",
8 | "start": "react-native start",
9 | "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"",
10 | "build:ios": "react-native build-ios --mode Debug"
11 | },
12 | "dependencies": {
13 | "@react-navigation/native": "7.1.14",
14 | "@react-navigation/native-stack": "7.3.21",
15 | "react": "19.1.0",
16 | "react-native": "0.80.0",
17 | "react-native-safe-area-context": "5.5.0",
18 | "react-native-screens": "4.11.1"
19 | },
20 | "devDependencies": {
21 | "@babel/core": "^7.25.2",
22 | "@babel/preset-env": "^7.25.3",
23 | "@babel/runtime": "^7.25.0",
24 | "@react-native-community/cli": "19.0.0",
25 | "@react-native-community/cli-platform-android": "19.0.0",
26 | "@react-native-community/cli-platform-ios": "19.0.0",
27 | "@react-native/babel-preset": "0.80.0",
28 | "@react-native/metro-config": "0.80.0",
29 | "@react-native/typescript-config": "0.80.0",
30 | "@types/react": "^19.1.0",
31 | "react-native-builder-bob": "^0.40.10"
32 | },
33 | "engines": {
34 | "node": ">=18"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/plugin/src/withRNOrientationBridgingHeader.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type ConfigPlugin,
3 | type ExportedConfigWithProps,
4 | IOSConfig,
5 | } from '@expo/config-plugins';
6 | import { type AppDelegateProjectFile } from '@expo/config-plugins/build/ios/Paths';
7 |
8 | import { withAppBridgingHeader } from './custom-mod/withBridgingHeader';
9 |
10 | export const withRNOrientationBridgingHeader: ConfigPlugin = (config) => {
11 | return withAppBridgingHeader(config, readBridgingHeaderFileAndUpdateContents);
12 | };
13 |
14 | async function readBridgingHeaderFileAndUpdateContents(
15 | config: ExportedConfigWithProps
16 | ) {
17 | const { projectRoot } = config.modRequest;
18 | if (isObjCTemplate(projectRoot)) {
19 | return config;
20 | }
21 |
22 | const { modResults: bridgingHeaderFile } = config;
23 |
24 | bridgingHeaderFile.contents = bridgingHeaderUpdater(
25 | bridgingHeaderFile.contents
26 | );
27 |
28 | return config;
29 | }
30 |
31 | export function bridgingHeaderUpdater(originalContents: string) {
32 | const libraryHeaderImport = '#import "OrientationDirector.h"';
33 |
34 | return originalContents.concat(`\n${libraryHeaderImport}`);
35 | }
36 |
37 | function isObjCTemplate(projectRoot: string) {
38 | const appDelegateFile = IOSConfig.Paths.getAppDelegate(projectRoot);
39 | return appDelegateFile.language !== 'swift';
40 | }
41 |
--------------------------------------------------------------------------------
/android/src/main/java/com/orientationdirector/implementation/AutoRotationObserver.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirector.implementation
2 |
3 | import android.database.ContentObserver
4 | import android.os.Handler
5 | import android.provider.Settings
6 | import com.facebook.react.bridge.ReactContext
7 |
8 | class AutoRotationObserver(private val context: ReactContext, handler: Handler?) : ContentObserver(handler) {
9 | private var lastAutoRotationStatus: Boolean = isAutoRotationEnabled()
10 |
11 | fun getLastAutoRotationStatus(): Boolean {
12 | return lastAutoRotationStatus
13 | }
14 |
15 | override fun onChange(selfChange: Boolean) {
16 | super.onChange(selfChange)
17 | val status = isAutoRotationEnabled()
18 | lastAutoRotationStatus = status
19 | }
20 |
21 | fun enable() {
22 | context.contentResolver.registerContentObserver(
23 | Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
24 | true,
25 | this,
26 | )
27 | }
28 |
29 | fun disable() {
30 | context.contentResolver.unregisterContentObserver(this)
31 | }
32 |
33 | private fun isAutoRotationEnabled(): Boolean {
34 | return try {
35 | Settings.System.getInt(context.contentResolver, Settings.System.ACCELEROMETER_ROTATION) == 1;
36 | } catch (ex: Settings.SettingNotFoundException) {
37 | false
38 | }
39 | }
40 |
41 | companion object {
42 | const val NAME = "AutoRotationObserver"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "40.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "60.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "58.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "87.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "80.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "120.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "120 1.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "180.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "1024.png",
53 | "idiom" : "ios-marketing",
54 | "scale" : "1x",
55 | "size" : "1024x1024"
56 | }
57 | ],
58 | "info" : {
59 | "author" : "xcode",
60 | "version" : 1
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/plugin/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type ConfigPlugin,
3 | createRunOncePlugin,
4 | withPlugins,
5 | } from '@expo/config-plugins';
6 |
7 | import { withAppBridgingHeaderMod } from './custom-mod/withBridgingHeader';
8 | import { withRNOrientationAppDelegate } from './withRNOrientationAppDelegate';
9 | import { withRNOrientationBridgingHeader } from './withRNOrientationBridgingHeader';
10 | import { withRNOrientationMainActivity } from './withRNOrientationMainActivity';
11 |
12 | /**
13 | * So, expo config plugin are awesome and the documentation is well written, but I still needed to look around to see
14 | * how other projects actually modify the AppDelegate. I've found react-native-firebase to implement a plugin config
15 | * that changes the AppDelegate, so I'll leave their link as reference:
16 | * https://github.com/invertase/react-native-firebase/blob/main/packages/app/plugin/src/ios/appDelegate.ts
17 | *
18 | * Kudos to them, because this stuff is hard!
19 | *
20 | * @param config
21 | */
22 | const withRNOrientationDirector: ConfigPlugin = (config) => {
23 | return withPlugins(config, [
24 | withRNOrientationAppDelegate,
25 | withRNOrientationBridgingHeader,
26 | withRNOrientationMainActivity,
27 | withAppBridgingHeaderMod,
28 | ]);
29 | };
30 |
31 | const pak = require('react-native-orientation-director/package.json');
32 | export default createRunOncePlugin(
33 | withRNOrientationDirector,
34 | pak.name,
35 | pak.version
36 | );
37 |
--------------------------------------------------------------------------------
/android/src/main/java/com/orientationdirector/OrientationDirectorPackage.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirector
2 |
3 | import com.facebook.react.BaseReactPackage
4 | import com.facebook.react.bridge.ReactApplicationContext
5 | import com.facebook.react.bridge.NativeModule
6 | import com.facebook.react.module.model.ReactModuleInfoProvider
7 | import com.facebook.react.module.model.ReactModuleInfo
8 | import com.orientationdirector.implementation.OrientationDirectorModuleImpl
9 | import java.util.HashMap
10 |
11 | class OrientationDirectorPackage : BaseReactPackage() {
12 | override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
13 | return if (name == OrientationDirectorModuleImpl.NAME) {
14 | OrientationDirectorModule(reactContext)
15 | } else {
16 | null
17 | }
18 | }
19 |
20 | override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
21 | return ReactModuleInfoProvider {
22 | val moduleInfos: MutableMap = HashMap()
23 | val isTurboModule: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
24 | moduleInfos[OrientationDirectorModuleImpl.NAME] = ReactModuleInfo(
25 | OrientationDirectorModuleImpl.NAME,
26 | OrientationDirectorModuleImpl.NAME,
27 | false, // canOverrideExistingModule
28 | false, // needsEagerInit
29 | false, // isCxxModule
30 | isTurboModule // isTurboModule
31 | )
32 | moduleInfos
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/android/src/main/java/com/orientationdirector/implementation/EventManager.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirector.implementation
2 |
3 | import com.facebook.react.bridge.Arguments
4 | import com.facebook.react.bridge.ReactApplicationContext
5 | import com.facebook.react.bridge.WritableMap
6 | import com.facebook.react.modules.core.DeviceEventManagerModule
7 |
8 | enum class Event {
9 | DeviceOrientationDidChange,
10 | InterfaceOrientationDidChange,
11 | LockDidChange,
12 | }
13 |
14 | class EventManager(private val context: ReactApplicationContext) {
15 |
16 | fun sendDeviceOrientationDidChange(orientationValue: Int) {
17 | val params = Arguments.createMap().apply {
18 | putInt("orientation", orientationValue)
19 | }
20 | sendEvent(Event.DeviceOrientationDidChange, params)
21 | }
22 |
23 | fun sendInterfaceOrientationDidChange(orientationValue: Int) {
24 | val params = Arguments.createMap().apply {
25 | putInt("orientation", orientationValue)
26 | }
27 | sendEvent(Event.InterfaceOrientationDidChange, params)
28 | }
29 |
30 | fun sendLockDidChange(value: Boolean) {
31 | val params = Arguments.createMap().apply {
32 | putBoolean("locked", value)
33 | }
34 | sendEvent(Event.LockDidChange, params)
35 | }
36 |
37 | private fun sendEvent(eventName: Event, params: WritableMap?) {
38 | context
39 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
40 | .emit(eventName.name, params)
41 | }
42 |
43 | companion object {
44 | const val NAME = "OrientationEventManager"
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/plugin/__tests__/withRNOrientationMainActivity.spec.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'node:fs';
2 | import * as path from 'node:path';
3 |
4 | import { ktFileUpdater } from '../src/withRNOrientationMainActivity';
5 |
6 | describe('withRNOrientationMainActivity', function () {
7 | beforeEach(function () {
8 | jest.resetAllMocks();
9 | });
10 |
11 | it('updates the MainActivity.kt with both imports and method implementation', async function () {
12 | const mainActivityPath = path.join(__dirname, './fixtures/MainActivity.kt');
13 | const mainActivity = await fs.promises.readFile(mainActivityPath, 'utf-8');
14 |
15 | const result = ktFileUpdater(mainActivity);
16 | expect(result).toMatchSnapshot();
17 | });
18 |
19 | it("skips the MainActivity.kt intent import when it's already set", async function () {
20 | const mainActivityPath = path.join(
21 | __dirname,
22 | './fixtures/MainActivityWithIntentImport.kt'
23 | );
24 | const mainActivity = await fs.promises.readFile(mainActivityPath, 'utf-8');
25 |
26 | const result = ktFileUpdater(mainActivity);
27 | expect(result).toMatchSnapshot();
28 | });
29 |
30 | it("skips the MainActivity.kt configuration import when it's already set", async function () {
31 | const mainActivityPath = path.join(
32 | __dirname,
33 | './fixtures/MainActivityWithConfigurationImport.kt'
34 | );
35 | const mainActivity = await fs.promises.readFile(mainActivityPath, 'utf-8');
36 |
37 | const result = ktFileUpdater(mainActivity);
38 | expect(result).toMatchSnapshot();
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/orientationdirectorexample/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirectorexample
2 |
3 | import android.app.Application
4 | import com.facebook.react.PackageList
5 | import com.facebook.react.ReactApplication
6 | import com.facebook.react.ReactHost
7 | import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
8 | import com.facebook.react.ReactNativeHost
9 | import com.facebook.react.ReactPackage
10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
11 | import com.facebook.react.defaults.DefaultReactNativeHost
12 |
13 | class MainApplication : Application(), ReactApplication {
14 |
15 | override val reactNativeHost: ReactNativeHost =
16 | object : DefaultReactNativeHost(this) {
17 | override fun getPackages(): List =
18 | PackageList(this).packages.apply {
19 | // Packages that cannot be autolinked yet can be added manually here, for example:
20 | // add(MyReactNativePackage())
21 | }
22 |
23 | override fun getJSMainModuleName(): String = "index"
24 |
25 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
26 |
27 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
28 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
29 | }
30 |
31 | override val reactHost: ReactHost
32 | get() = getDefaultReactHost(applicationContext, reactNativeHost)
33 |
34 | override fun onCreate() {
35 | super.onCreate()
36 | loadReactNative(this)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ios/implementation/BundleManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BundleManager.swift
3 | // react-native-orientation-director
4 | //
5 | // Created by gladiuscode on 26/05/2024.
6 | //
7 |
8 | import Foundation
9 |
10 | class BundleManager {
11 |
12 | private var supportedInterfaceOrientations: [UIInterfaceOrientationMask] = [UIInterfaceOrientationMask.all]
13 |
14 | init() {
15 | supportedInterfaceOrientations = readSupportedInterfaceOrientations()
16 | }
17 |
18 | func getSupportedInterfaceOrientations() -> [UIInterfaceOrientationMask] {
19 | return supportedInterfaceOrientations
20 | }
21 |
22 | private func readSupportedInterfaceOrientations() -> [UIInterfaceOrientationMask] {
23 | let orientations = Bundle.main.object(forInfoDictionaryKey: "UISupportedInterfaceOrientations") as? [String]
24 |
25 | guard let orientations = orientations else {
26 | return [UIInterfaceOrientationMask.all]
27 | }
28 |
29 | return orientations.compactMap { orientation in
30 | switch orientation {
31 | case "UIInterfaceOrientationPortrait":
32 | return UIInterfaceOrientationMask.portrait
33 | case "UIInterfaceOrientationLandscapeLeft":
34 | return UIInterfaceOrientationMask.landscapeLeft
35 | case "UIInterfaceOrientationLandscapeRight":
36 | return UIInterfaceOrientationMask.landscapeRight
37 | case "UIInterfaceOrientationPortraitUpsideDown":
38 | return UIInterfaceOrientationMask.portraitUpsideDown
39 | default:
40 | return UIInterfaceOrientationMask.allButUpsideDown
41 | }
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import React
3 | import React_RCTAppDelegate
4 | import ReactAppDependencyProvider
5 |
6 | @main
7 | class AppDelegate: UIResponder, UIApplicationDelegate {
8 | var window: UIWindow?
9 |
10 | var reactNativeDelegate: ReactNativeDelegate?
11 | var reactNativeFactory: RCTReactNativeFactory?
12 |
13 | func application(
14 | _ application: UIApplication,
15 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
16 | ) -> Bool {
17 | let delegate = ReactNativeDelegate()
18 | let factory = RCTReactNativeFactory(delegate: delegate)
19 | delegate.dependencyProvider = RCTAppDependencyProvider()
20 |
21 | reactNativeDelegate = delegate
22 | reactNativeFactory = factory
23 |
24 | window = UIWindow(frame: UIScreen.main.bounds)
25 |
26 | factory.startReactNative(
27 | withModuleName: "OrientationDirectorExample",
28 | in: window,
29 | launchOptions: launchOptions
30 | )
31 |
32 | return true
33 | }
34 |
35 | func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
36 | return OrientationDirector.getSupportedInterfaceOrientationsForWindow()
37 | }
38 | }
39 |
40 | class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
41 | override func sourceURL(for bridge: RCTBridge) -> URL? {
42 | self.bundleURL()
43 | }
44 |
45 | override func bundleURL() -> URL? {
46 | #if DEBUG
47 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
48 | #else
49 | Bundle.main.url(forResource: "main", withExtension: "jsbundle")
50 | #endif
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/android/src/newarch/OrientationDirectorModule.kt:
--------------------------------------------------------------------------------
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 | }
60 |
--------------------------------------------------------------------------------
/example/src/AppNavigationContainer.tsx:
--------------------------------------------------------------------------------
1 | import { NavigationContainer } from '@react-navigation/native';
2 | import { createNativeStackNavigator } from '@react-navigation/native-stack';
3 | import Home from './screens/Home';
4 | import Explore from './screens/Explore';
5 | import RNOrientationDirector from 'react-native-orientation-director';
6 | import InnerExplore from './screens/InnerExplore';
7 |
8 | function AppNavigationContainer() {
9 | const handleOnReady = async () => {
10 | console.log('App Navigation is ready');
11 |
12 | // With hot reload this returns UNKNOWN because
13 | // the listener is disabled on the native side.
14 | const initialDeviceOrientation =
15 | await RNOrientationDirector.getDeviceOrientation();
16 | const initialInterfaceOrientation =
17 | await RNOrientationDirector.getInterfaceOrientation();
18 |
19 | console.log(
20 | 'Initial device orientation: ',
21 | RNOrientationDirector.convertOrientationToHumanReadableString(
22 | initialDeviceOrientation
23 | )
24 | );
25 | console.log(
26 | 'Initial interface orientation: ',
27 | RNOrientationDirector.convertOrientationToHumanReadableString(
28 | initialInterfaceOrientation
29 | )
30 | );
31 | };
32 |
33 | return (
34 |
35 |
36 |
37 | );
38 | }
39 |
40 | export default AppNavigationContainer;
41 |
42 | const Stack = createNativeStackNavigator();
43 | function MainStack() {
44 | return (
45 |
46 |
47 |
48 |
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/orientationdirectorexample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirectorexample
2 |
3 | import android.content.Intent
4 | import android.content.res.Configuration
5 | import android.os.Bundle
6 | import com.facebook.react.ReactActivity
7 | import com.facebook.react.ReactActivityDelegate
8 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
9 | import com.facebook.react.defaults.DefaultReactActivityDelegate
10 | import com.orientationdirector.implementation.ConfigurationChangedBroadcastReceiver
11 |
12 | class MainActivity : ReactActivity() {
13 |
14 | /**
15 | * Returns the name of the main component registered from JavaScript. This is used to schedule
16 | * rendering of the component.
17 | */
18 | override fun getMainComponentName(): String = "OrientationDirectorExample"
19 |
20 | /**
21 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
22 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
23 | */
24 | override fun createReactActivityDelegate(): ReactActivityDelegate =
25 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
26 |
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(null)
29 | }
30 |
31 | override fun onConfigurationChanged(newConfig: Configuration) {
32 | super.onConfigurationChanged(newConfig)
33 |
34 | val orientationDirectorCustomAction =
35 | "${packageName}.${ConfigurationChangedBroadcastReceiver.CUSTOM_INTENT_ACTION}"
36 |
37 | val intent =
38 | Intent(orientationDirectorCustomAction).apply {
39 | putExtra("newConfig", newConfig)
40 | setPackage(packageName)
41 | }
42 |
43 | this.sendBroadcast(intent)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | OrientationDirectorExample
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSAllowsArbitraryLoads
30 |
31 | NSAllowsLocalNetworking
32 |
33 |
34 | NSLocationWhenInUseUsageDescription
35 |
36 | RCTNewArchEnabled
37 |
38 | UILaunchStoryboardName
39 | LaunchScreen
40 | UIRequiredDeviceCapabilities
41 |
42 | arm64
43 |
44 | UISupportedInterfaceOrientations
45 |
46 | UIInterfaceOrientationPortrait
47 | UIInterfaceOrientationLandscapeLeft
48 | UIInterfaceOrientationLandscapeRight
49 |
50 | UIViewControllerBasedStatusBarAppearance
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/example/src/screens/Explore.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Text, View } from 'react-native';
2 | import { exploreStyle } from './styles';
3 | import RNOrientationDirector, {
4 | useDeviceOrientation,
5 | useInterfaceOrientation,
6 | useIsInterfaceOrientationLocked,
7 | } from 'react-native-orientation-director';
8 | import { useNavigation } from '@react-navigation/native';
9 |
10 | function Explore() {
11 | const navigation = useNavigation();
12 |
13 | const interfaceOrientation = useInterfaceOrientation();
14 | const deviceOrientation = useDeviceOrientation();
15 | const isInterfaceOrientationLocked = useIsInterfaceOrientationLocked();
16 |
17 | const handleGoToInnerExploreOnPress = () => {
18 | navigation.navigate('InnerExplore' as never);
19 | };
20 |
21 | return (
22 |
23 | Explore!
24 |
25 |
29 |
30 |
31 |
32 | Current Interface Orientation:
33 | {RNOrientationDirector.convertOrientationToHumanReadableString(
34 | interfaceOrientation
35 | )}
36 |
37 |
38 | Current Device Orientation:
39 | {RNOrientationDirector.convertOrientationToHumanReadableString(
40 | deviceOrientation
41 | )}
42 |
43 |
44 | Is Interface Orientation Locked:
45 | {isInterfaceOrientationLocked ? 'Yes' : 'No'}
46 |
47 |
48 |
49 | );
50 | }
51 |
52 | export default Explore;
53 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 |
25 | # Use this property to specify which architecture you want to build.
26 | # You can also override it from the CLI using
27 | # ./gradlew -PreactNativeArchitectures=x86_64
28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
29 |
30 | # Use this property to enable support to the new architecture.
31 | # This will allow you to use TurboModules and the Fabric render in
32 | # your application. You should enable this flag either if you want
33 | # to write custom TurboModules/Fabric components OR use libraries that
34 | # are providing them.
35 | newArchEnabled=true
36 |
37 | # Use this property to enable or disable the Hermes JS engine.
38 | # If set to false, you will be using JSC instead.
39 | hermesEnabled=true
40 |
--------------------------------------------------------------------------------
/react-native-orientation-director.podspec:
--------------------------------------------------------------------------------
1 | require "json"
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4 | folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
5 |
6 | Pod::Spec.new do |s|
7 | s.name = "react-native-orientation-director"
8 | s.version = package["version"]
9 | s.summary = package["description"]
10 | s.homepage = package["homepage"]
11 | s.license = package["license"]
12 | s.authors = package["author"]
13 |
14 | s.platforms = { :ios => min_ios_version_supported }
15 | s.source = { :git => "https://github.com/gladiuscode/react-native-orientation-director.git", :tag => "#{s.version}" }
16 |
17 | s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
18 | s.private_header_files = "ios/generated/**/*.h"
19 |
20 | # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
21 | # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
22 | if respond_to?(:install_modules_dependencies, true)
23 | install_modules_dependencies(s)
24 | else
25 | s.dependency "React-Core"
26 |
27 | # Don't install the dependencies when we run `pod install` in the old architecture.
28 | if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
29 | s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
30 | s.pod_target_xcconfig = {
31 | "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
32 | "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
33 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
34 | }
35 | s.dependency "React-Codegen"
36 | s.dependency "RCT-Folly"
37 | s.dependency "RCTRequired"
38 | s.dependency "RCTTypeSafety"
39 | s.dependency "ReactCommon/turbomodule/core"
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/android/src/oldarch/OrientationDirectorModule.kt:
--------------------------------------------------------------------------------
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 | }
67 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/rn_edit_text_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
22 |
23 |
24 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/implementation/EventManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrientationEventEmitter.swift
3 | // react-native-orientation-director
4 | //
5 | // Created by gladiuscode on 18/05/2024.
6 | //
7 |
8 | import Foundation
9 |
10 | @objc public class EventManager: NSObject {
11 | @objc public weak var delegate: OrientationEventEmitterDelegate?
12 |
13 | func sendDeviceOrientationDidChange(orientationValue: Int) {
14 | guard let delegate = delegate else {
15 | return
16 | }
17 |
18 | if !delegate.isJsListening {
19 | return
20 | }
21 |
22 | let params = Dictionary(dictionaryLiteral: ("orientation", orientationValue))
23 | delegate.sendEvent(name: Event.DeviceOrientationDidChange.rawValue, params: params as NSDictionary)
24 | }
25 |
26 | func sendInterfaceOrientationDidChange(orientationValue: Int) {
27 | guard let delegate = delegate else {
28 | return
29 | }
30 |
31 | if !delegate.isJsListening {
32 | return
33 | }
34 |
35 | let params = Dictionary(dictionaryLiteral: ("orientation", orientationValue))
36 | delegate.sendEvent(name: Event.InterfaceOrientationDidChange.rawValue, params: params as NSDictionary)
37 | }
38 |
39 | func sendLockDidChange(value: Bool) {
40 | guard let delegate = delegate else {
41 | return
42 | }
43 |
44 | if !delegate.isJsListening {
45 | return
46 | }
47 |
48 | let params = Dictionary(dictionaryLiteral: ("locked", value))
49 | delegate.sendEvent(name: Event.LockDidChange.rawValue, params: params as NSDictionary)
50 | }
51 | }
52 |
53 | @objc public protocol OrientationEventEmitterDelegate {
54 | var isJsListening: Bool { get set }
55 |
56 | func sendEvent(name: String, params: NSDictionary)
57 | }
58 |
59 | public extension EventManager {
60 |
61 | enum Event: String, CaseIterable {
62 | case DeviceOrientationDidChange
63 | case InterfaceOrientationDidChange
64 | case LockDidChange
65 | }
66 |
67 | @objc static var supportedEvents: [String] {
68 | return Event.allCases.map(\.rawValue)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/plugin/__tests__/withRNOrientationAppDelegate.spec.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'node:fs';
2 | import * as path from 'node:path';
3 |
4 | import {
5 | objCFileUpdater,
6 | swiftFileUpdater,
7 | } from '../src/withRNOrientationAppDelegate';
8 |
9 | describe('withRNOrientationAppDelegate', function () {
10 | beforeEach(function () {
11 | jest.resetAllMocks();
12 | });
13 |
14 | it('updates the AppDelegate.mm with both header import and method implementation', async function () {
15 | const appDelegatePath = path.join(__dirname, './fixtures/AppDelegate.mm');
16 | const appDelegate = await fs.promises.readFile(appDelegatePath, 'utf-8');
17 |
18 | const result = objCFileUpdater(appDelegate);
19 | expect(result).toMatchSnapshot();
20 | });
21 |
22 | it('updates the AppDelegate52.swift with the method implementation having public override when sdk is <= 52', async function () {
23 | const appDelegatePath = path.join(
24 | __dirname,
25 | './fixtures/AppDelegate52.swift'
26 | );
27 | const appDelegate = await fs.promises.readFile(appDelegatePath, 'utf-8');
28 |
29 | const result = swiftFileUpdater(appDelegate, '52.0.0');
30 | expect(result).toMatchSnapshot();
31 | });
32 |
33 | it('updates the AppDelegate53.swift with the method implementation without public override when sdk is equal to 53', async function () {
34 | const appDelegatePath = path.join(
35 | __dirname,
36 | './fixtures/AppDelegate53.swift'
37 | );
38 | const appDelegate = await fs.promises.readFile(appDelegatePath, 'utf-8');
39 |
40 | const result = swiftFileUpdater(appDelegate, '53.0.0');
41 | expect(result).toMatchSnapshot();
42 | });
43 |
44 | it('updates the AppDelegate53.swift with the method implementation having public override when sdk is greater than or equal to 54', async function () {
45 | const appDelegatePath = path.join(
46 | __dirname,
47 | './fixtures/AppDelegate53.swift'
48 | );
49 | const appDelegate = await fs.promises.readFile(appDelegatePath, 'utf-8');
50 |
51 | const result = swiftFileUpdater(appDelegate, '54.0.0');
52 | expect(result).toMatchSnapshot();
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/android/src/main/java/com/orientationdirector/implementation/ConfigurationChangedBroadcastReceiver.kt:
--------------------------------------------------------------------------------
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 isRegistered = false
21 |
22 | private var onReceiveCallback: ((intent: Intent?) -> Unit)? = null
23 |
24 | override fun onReceive(context: Context?, intent: Intent?) {
25 | this.onReceiveCallback?.invoke(intent)
26 | }
27 |
28 | fun setOnReceiveCallback(callback: (intent: Intent?) -> Unit) {
29 | onReceiveCallback = callback
30 | }
31 |
32 | /**
33 | * This method registers the receiver by checking the api we are currently running with.
34 | * With the latest changes in Android 14, we need to explicitly set the `Context.RECEIVER_NOT_EXPORTED`
35 | * flag.
36 | */
37 | fun register() {
38 | val filter = IntentFilter("${context.packageName}.$CUSTOM_INTENT_ACTION")
39 |
40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
41 | context.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED)
42 | } else {
43 | context.registerReceiver(this, filter)
44 | }
45 |
46 | isRegistered = true
47 | }
48 |
49 | fun unregister() {
50 | if (!isRegistered) {
51 | return
52 | }
53 |
54 | try {
55 | context.unregisterReceiver(this)
56 | } catch(_: IllegalArgumentException) {
57 | } finally {
58 | isRegistered = false
59 | }
60 | }
61 |
62 | companion object {
63 | const val CUSTOM_INTENT_ACTION = "CONFIGURATION_CHANGED"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/EventEmitter.ts:
--------------------------------------------------------------------------------
1 | import { Platform, type EmitterSubscription } from 'react-native';
2 | import Module, { ModuleEventEmitter } from './module';
3 | import Event from './types/Event.enum';
4 | import type { OrientationEvent } from './types/OrientationEvent.interface';
5 | import type { LockedEvent } from './types/LockedEvent.interface';
6 |
7 | class EventEmitter {
8 | static addDeviceOrientationDidChangeListener(
9 | callback: (orientation: OrientationEvent) => void
10 | ) {
11 | let listener = ModuleEventEmitter.addListener(
12 | Event.DeviceOrientationDidChange,
13 | callback
14 | );
15 |
16 | if (Platform.OS !== 'android') {
17 | return listener;
18 | }
19 |
20 | const listenerCount = ModuleEventEmitter.listenerCount(
21 | Event.DeviceOrientationDidChange
22 | );
23 |
24 | if (listenerCount === 1) {
25 | Module.enableOrientationSensors();
26 | }
27 |
28 | return EventEmitter.createDeviceOrientationListenerProxy(listener);
29 | }
30 |
31 | static addInterfaceOrientationDidChangeListener(
32 | callback: (orientation: OrientationEvent) => void
33 | ) {
34 | return ModuleEventEmitter.addListener(
35 | Event.InterfaceOrientationDidChange,
36 | callback
37 | );
38 | }
39 |
40 | static addLockDidChangeListener(callback: (event: LockedEvent) => void) {
41 | return ModuleEventEmitter.addListener(Event.LockDidChange, callback);
42 | }
43 |
44 | private static createDeviceOrientationListenerProxy(
45 | listener: EmitterSubscription
46 | ) {
47 | const handler: ProxyHandler = {
48 | get(target, propertyKey, receiver) {
49 | if (propertyKey === 'remove') {
50 | disableOrientationSensorsIfLastListener();
51 | }
52 | return Reflect.get(target, propertyKey, receiver);
53 | },
54 | };
55 |
56 | return new Proxy(listener, handler);
57 |
58 | function disableOrientationSensorsIfLastListener() {
59 | const listenerCount = ModuleEventEmitter.listenerCount(
60 | Event.DeviceOrientationDidChange
61 | );
62 |
63 | if (listenerCount === 1) {
64 | Module.disableOrientationSensors();
65 | }
66 | }
67 | }
68 | }
69 |
70 | export default EventEmitter;
71 |
--------------------------------------------------------------------------------
/plugin/__tests__/fixtures/AppDelegate53.swift:
--------------------------------------------------------------------------------
1 | import Expo
2 | import React
3 | import ReactAppDependencyProvider
4 |
5 | @UIApplicationMain
6 | public class AppDelegate: ExpoAppDelegate {
7 | var window: UIWindow?
8 |
9 | var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
10 | var reactNativeFactory: RCTReactNativeFactory?
11 |
12 | public override func application(
13 | _ application: UIApplication,
14 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
15 | ) -> Bool {
16 | let delegate = ReactNativeDelegate()
17 | let factory = ExpoReactNativeFactory(delegate: delegate)
18 | delegate.dependencyProvider = RCTAppDependencyProvider()
19 |
20 | reactNativeDelegate = delegate
21 | reactNativeFactory = factory
22 | bindReactNativeFactory(factory)
23 |
24 | #if os(iOS) || os(tvOS)
25 | window = UIWindow(frame: UIScreen.main.bounds)
26 | factory.startReactNative(
27 | withModuleName: "main",
28 | in: window,
29 | launchOptions: launchOptions)
30 | #endif
31 |
32 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
33 | }
34 |
35 | // Linking API
36 | public override func application(
37 | _ app: UIApplication,
38 | open url: URL,
39 | options: [UIApplication.OpenURLOptionsKey: Any] = [:]
40 | ) -> Bool {
41 | return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
42 | }
43 |
44 | // Universal Links
45 | public override func application(
46 | _ application: UIApplication,
47 | continue userActivity: NSUserActivity,
48 | restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
49 | ) -> Bool {
50 | let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
51 | return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
52 | }
53 | }
54 |
55 | class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
56 | // Extension point for config-plugins
57 |
58 | override func sourceURL(for bridge: RCTBridge) -> URL? {
59 | // needed to return the correct URL for expo-dev-client.
60 | bridge.bundleURL ?? bundleURL()
61 | }
62 |
63 | override func bundleURL() -> URL? {
64 | #if DEBUG
65 | return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
66 | #else
67 | return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
68 | #endif
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/plugin/__tests__/fixtures/AppDelegate.mm:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 |
3 | #import
4 | #import
5 |
6 | @implementation AppDelegate
7 |
8 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
9 | {
10 | self.moduleName = @"main";
11 |
12 | // You can add your custom initial props in the dictionary below.
13 | // They will be passed down to the ViewController used by React Native.
14 | self.initialProps = @{};
15 |
16 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
17 | }
18 |
19 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
20 | {
21 | return [self bundleURL];
22 | }
23 |
24 | - (NSURL *)bundleURL
25 | {
26 | #if DEBUG
27 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];
28 | #else
29 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
30 | #endif
31 | }
32 |
33 | // Linking API
34 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options {
35 | return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options];
36 | }
37 |
38 | // Universal Links
39 | - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler {
40 | BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
41 | return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result;
42 | }
43 |
44 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
45 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
46 | {
47 | return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
48 | }
49 |
50 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
51 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
52 | {
53 | return [super application:application didFailToRegisterForRemoteNotificationsWithError:error];
54 | }
55 |
56 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
57 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
58 | {
59 | return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
60 | }
61 |
62 | @end
63 |
--------------------------------------------------------------------------------
/plugin/src/custom-mod/withBridgingHeader.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type ConfigPlugin,
3 | IOSConfig,
4 | type Mod,
5 | withMod,
6 | BaseMods,
7 | type ExportedConfig,
8 | } from '@expo/config-plugins';
9 | import { globSync } from 'glob';
10 | import * as fs from 'node:fs';
11 |
12 | /**
13 | * Reference: https://github.com/expo/expo/blob/cb38337d37c35a26ac9a39eac2268c4735f488ad/packages/%40expo/config-plugins/src/ios/Paths.ts#L10C1-L10C69
14 | */
15 | const ignoredPaths = ['**/@(Carthage|Pods|vendor|node_modules)/**'];
16 |
17 | /**
18 | * This plugin adds a new mod to the prebuild that is the appBridgingHeader.
19 | * This allows us to update it when the user launches the prebuild script.
20 | */
21 | export function withAppBridgingHeaderMod(config: ExportedConfig) {
22 | return BaseMods.withGeneratedBaseMods<'appBridgingHeader'>(config, {
23 | platform: 'ios',
24 | providers: {
25 | // Append a custom rule to supply AppDelegate header data to mods on `mods.ios.appBridgingHeader`
26 | appBridgingHeader:
27 | BaseMods.provider({
28 | // Get the local filepath that should be passed to the `read` method.
29 | getFilePath({ modRequest: { projectRoot } }) {
30 | return getBridgingHeaderFilePath(projectRoot);
31 | },
32 | // Read the input file from the filesystem.
33 | async read(filePath) {
34 | return IOSConfig.Paths.getFileInfo(filePath);
35 | },
36 | // Write the resulting output to the filesystem.
37 | async write(filePath: string, { modResults: { contents } }) {
38 | await fs.promises.writeFile(filePath, contents);
39 | },
40 | }),
41 | },
42 | });
43 | }
44 |
45 | /**
46 | * This mod provides the app bridging header for modifications
47 | * @param config
48 | * @param action
49 | */
50 | export const withAppBridgingHeader: ConfigPlugin<
51 | Mod
52 | > = (config, action) => {
53 | return withMod(config, {
54 | platform: 'ios',
55 | mod: 'appBridgingHeader',
56 | action,
57 | });
58 | };
59 |
60 | /**
61 | * Reference: https://github.com/expo/expo/blob/cb38337d37c35a26ac9a39eac2268c4735f488ad/packages/%40expo/config-plugins/src/ios/Paths.ts#L23
62 | * @param projectRoot
63 | */
64 | function getBridgingHeaderFilePath(projectRoot: string) {
65 | const [using, ...extra] = withSortedGlobResult(
66 | globSync('ios/*/*-Bridging-Header.h', {
67 | absolute: true,
68 | cwd: projectRoot,
69 | ignore: ignoredPaths,
70 | })
71 | );
72 |
73 | if (!using) {
74 | throw new Error(
75 | `Could not locate a valid Bridging-Header at root: "${projectRoot}"`
76 | );
77 | }
78 |
79 | if (extra.length) {
80 | throw new Error(`Multiple Bridging-Header found at root: "${projectRoot}"`);
81 | }
82 |
83 | return using;
84 | }
85 |
86 | /**
87 | * Reference: https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/utils/glob.ts
88 | */
89 | function withSortedGlobResult(glob: string[]) {
90 | return glob.sort((a, b) => a.localeCompare(b));
91 | }
92 |
--------------------------------------------------------------------------------
/example/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.7)
5 | base64
6 | nkf
7 | rexml
8 | activesupport (6.1.7.7)
9 | concurrent-ruby (~> 1.0, >= 1.0.2)
10 | i18n (>= 1.6, < 2)
11 | minitest (>= 5.1)
12 | tzinfo (~> 2.0)
13 | zeitwerk (~> 2.3)
14 | addressable (2.8.6)
15 | public_suffix (>= 2.0.2, < 6.0)
16 | algoliasearch (1.27.5)
17 | httpclient (~> 2.8, >= 2.8.3)
18 | json (>= 1.5.1)
19 | atomos (0.1.3)
20 | base64 (0.2.0)
21 | benchmark (0.2.1)
22 | bigdecimal (3.1.3)
23 | claide (1.1.0)
24 | cocoapods (1.14.3)
25 | addressable (~> 2.8)
26 | claide (>= 1.0.2, < 2.0)
27 | cocoapods-core (= 1.14.3)
28 | cocoapods-deintegrate (>= 1.0.3, < 2.0)
29 | cocoapods-downloader (>= 2.1, < 3.0)
30 | cocoapods-plugins (>= 1.0.0, < 2.0)
31 | cocoapods-search (>= 1.0.0, < 2.0)
32 | cocoapods-trunk (>= 1.6.0, < 2.0)
33 | cocoapods-try (>= 1.1.0, < 2.0)
34 | colored2 (~> 3.1)
35 | escape (~> 0.0.4)
36 | fourflusher (>= 2.3.0, < 3.0)
37 | gh_inspector (~> 1.0)
38 | molinillo (~> 0.8.0)
39 | nap (~> 1.0)
40 | ruby-macho (>= 2.3.0, < 3.0)
41 | xcodeproj (>= 1.23.0, < 2.0)
42 | cocoapods-core (1.14.3)
43 | activesupport (>= 5.0, < 8)
44 | addressable (~> 2.8)
45 | algoliasearch (~> 1.0)
46 | concurrent-ruby (~> 1.1)
47 | fuzzy_match (~> 2.0.4)
48 | nap (~> 1.0)
49 | netrc (~> 0.11)
50 | public_suffix (~> 4.0)
51 | typhoeus (~> 1.0)
52 | cocoapods-deintegrate (1.0.5)
53 | cocoapods-downloader (2.1)
54 | cocoapods-plugins (1.0.0)
55 | nap
56 | cocoapods-search (1.0.1)
57 | cocoapods-trunk (1.6.0)
58 | nap (>= 0.8, < 2.0)
59 | netrc (~> 0.11)
60 | cocoapods-try (1.2.0)
61 | colored2 (3.1.2)
62 | concurrent-ruby (1.2.3)
63 | escape (0.0.4)
64 | ethon (0.16.0)
65 | ffi (>= 1.15.0)
66 | ffi (1.16.3)
67 | fourflusher (2.3.1)
68 | fuzzy_match (2.0.4)
69 | gh_inspector (1.1.3)
70 | httpclient (2.8.3)
71 | i18n (1.14.5)
72 | concurrent-ruby (~> 1.0)
73 | json (2.7.2)
74 | logger (1.5.3)
75 | minitest (5.23.0)
76 | molinillo (0.8.0)
77 | mutex_m (0.1.2)
78 | nanaimo (0.3.0)
79 | nap (1.1.0)
80 | netrc (0.11.0)
81 | nkf (0.2.0)
82 | public_suffix (4.0.7)
83 | rexml (3.2.8)
84 | strscan (>= 3.0.9)
85 | ruby-macho (2.5.1)
86 | strscan (3.1.0)
87 | typhoeus (1.4.1)
88 | ethon (>= 0.9.0)
89 | tzinfo (2.0.6)
90 | concurrent-ruby (~> 1.0)
91 | xcodeproj (1.24.0)
92 | CFPropertyList (>= 2.3.3, < 4.0)
93 | atomos (~> 0.1.3)
94 | claide (>= 1.0.2, < 2.0)
95 | colored2 (~> 3.1)
96 | nanaimo (~> 0.3.0)
97 | rexml (~> 3.2.4)
98 | zeitwerk (2.6.14)
99 |
100 | PLATFORMS
101 | ruby
102 |
103 | DEPENDENCIES
104 | activesupport (>= 6.1.7.5, != 7.1.0)
105 | benchmark
106 | bigdecimal
107 | cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
108 | concurrent-ruby (< 1.3.4)
109 | logger
110 | mutex_m
111 | xcodeproj (< 1.26.0)
112 |
113 | RUBY VERSION
114 | ruby 2.6.10p210
115 |
116 | BUNDLED WITH
117 | 2.4.15
118 |
--------------------------------------------------------------------------------
/example/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @REM Copyright (c) Meta Platforms, Inc. and affiliates.
2 | @REM
3 | @REM This source code is licensed under the MIT license found in the
4 | @REM LICENSE file in the root directory of this source tree.
5 |
6 | @rem
7 | @rem Copyright 2015 the original author or authors.
8 | @rem
9 | @rem Licensed under the Apache License, Version 2.0 (the "License");
10 | @rem you may not use this file except in compliance with the License.
11 | @rem You may obtain a copy of the License at
12 | @rem
13 | @rem https://www.apache.org/licenses/LICENSE-2.0
14 | @rem
15 | @rem Unless required by applicable law or agreed to in writing, software
16 | @rem distributed under the License is distributed on an "AS IS" BASIS,
17 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | @rem See the License for the specific language governing permissions and
19 | @rem limitations under the License.
20 | @rem
21 | @rem SPDX-License-Identifier: Apache-2.0
22 | @rem
23 |
24 | @if "%DEBUG%"=="" @echo off
25 | @rem ##########################################################################
26 | @rem
27 | @rem Gradle startup script for Windows
28 | @rem
29 | @rem ##########################################################################
30 |
31 | @rem Set local scope for the variables with windows NT shell
32 | if "%OS%"=="Windows_NT" setlocal
33 |
34 | set DIRNAME=%~dp0
35 | if "%DIRNAME%"=="" set DIRNAME=.
36 | @rem This is normally unused
37 | set APP_BASE_NAME=%~n0
38 | set APP_HOME=%DIRNAME%
39 |
40 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
41 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
42 |
43 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
44 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
45 |
46 | @rem Find java.exe
47 | if defined JAVA_HOME goto findJavaFromJavaHome
48 |
49 | set JAVA_EXE=java.exe
50 | %JAVA_EXE% -version >NUL 2>&1
51 | if %ERRORLEVEL% equ 0 goto execute
52 |
53 | echo. 1>&2
54 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
55 | echo. 1>&2
56 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
57 | echo location of your Java installation. 1>&2
58 |
59 | goto fail
60 |
61 | :findJavaFromJavaHome
62 | set JAVA_HOME=%JAVA_HOME:"=%
63 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
64 |
65 | if exist "%JAVA_EXE%" goto execute
66 |
67 | echo. 1>&2
68 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
69 | echo. 1>&2
70 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
71 | echo location of your Java installation. 1>&2
72 |
73 | goto fail
74 |
75 | :execute
76 | @rem Setup the command line
77 |
78 | set CLASSPATH=
79 |
80 |
81 | @rem Execute Gradle
82 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
83 |
84 | :end
85 | @rem End local scope for the variables with windows NT shell
86 | if %ERRORLEVEL% equ 0 goto mainEnd
87 |
88 | :fail
89 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
90 | rem the _cmd.exe /c_ return code!
91 | set EXIT_CODE=%ERRORLEVEL%
92 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
93 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
94 | exit /b %EXIT_CODE%
95 |
96 | :mainEnd
97 | if "%OS%"=="Windows_NT" endlocal
98 |
99 | :omega
100 |
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample.xcodeproj/xcshareddata/xcschemes/OrientationDirectorExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.getExtOrDefault = {name ->
3 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['OrientationDirector_' + name]
4 | }
5 |
6 | repositories {
7 | google()
8 | mavenCentral()
9 | }
10 |
11 | dependencies {
12 | classpath "com.android.tools.build:gradle:8.7.2"
13 | // noinspection DifferentKotlinGradleVersion
14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
15 | }
16 | }
17 |
18 |
19 | def isNewArchitectureEnabled() {
20 | return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
21 | }
22 |
23 | apply plugin: "com.android.library"
24 | apply plugin: "kotlin-android"
25 |
26 | if (isNewArchitectureEnabled()) {
27 | apply plugin: "com.facebook.react"
28 | }
29 |
30 | def getExtOrIntegerDefault(name) {
31 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["OrientationDirector_" + name]).toInteger()
32 | }
33 |
34 | def getDefault(name) {
35 | return project.properties["OrientationDirector_" + name]
36 | }
37 |
38 | def supportsNamespace() {
39 | def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
40 | def major = parsed[0].toInteger()
41 | def minor = parsed[1].toInteger()
42 |
43 | // Namespace support was added in 7.3.0
44 | return (major == 7 && minor >= 3) || major >= 8
45 | }
46 |
47 | android {
48 | if (supportsNamespace()) {
49 | namespace "com.orientationdirector"
50 |
51 | sourceSets {
52 | main {
53 | manifest.srcFile "src/main/AndroidManifestNew.xml"
54 | }
55 | }
56 | }
57 |
58 | compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
59 |
60 | defaultConfig {
61 | minSdkVersion getExtOrIntegerDefault("minSdkVersion")
62 | targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
63 | buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
64 | }
65 |
66 | buildFeatures {
67 | buildConfig true
68 | }
69 |
70 | buildTypes {
71 | release {
72 | minifyEnabled false
73 | }
74 | }
75 |
76 | lintOptions {
77 | disable "GradleCompatible"
78 | }
79 |
80 | compileOptions {
81 | sourceCompatibility JavaVersion.VERSION_1_8
82 | targetCompatibility JavaVersion.VERSION_1_8
83 | }
84 |
85 | sourceSets {
86 | main {
87 | if (isNewArchitectureEnabled()) {
88 | java.srcDirs += [
89 | "src/newarch",
90 | // This is needed to build Kotlin project with NewArch enabled
91 | "${project.buildDir}/generated/source/codegen/java"
92 | ]
93 | } else {
94 | java.srcDirs += ["src/oldarch"]
95 | }
96 | }
97 | }
98 | }
99 |
100 | repositories {
101 | mavenCentral()
102 | google()
103 | }
104 |
105 | def kotlin_version = getExtOrDefault("kotlinVersion")
106 | def junit_version = getDefault("junitVersion")
107 | def android_x_core_version = getDefault("androidXCoreVersion")
108 | def robolectric_version = getDefault("robolectricVersion")
109 | def mockito_core_version = getDefault("mockitoCoreVersion")
110 |
111 | dependencies {
112 | implementation "com.facebook.react:react-android"
113 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
114 |
115 | testImplementation "junit:junit:$junit_version"
116 | testImplementation "androidx.test:core:$android_x_core_version"
117 | testImplementation "org.robolectric:robolectric:$robolectric_version"
118 | testImplementation "org.mockito:mockito-core:$mockito_core_version"
119 | }
120 |
121 | if (isNewArchitectureEnabled()) {
122 | react {
123 | jsRootDir = file("../src/")
124 | libraryName = "OrientationDirector"
125 | codegenJavaPackageName = "com.orientationdirector"
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli).
2 |
3 | # Getting Started
4 |
5 | > **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding.
6 |
7 | ## Step 1: Start Metro
8 |
9 | First, you will need to run **Metro**, the JavaScript build tool for React Native.
10 |
11 | To start the Metro dev server, run the following command from the root of your React Native project:
12 |
13 | ```sh
14 | # Using npm
15 | npm start
16 |
17 | # OR using Yarn
18 | yarn start
19 | ```
20 |
21 | ## Step 2: Build and run your app
22 |
23 | With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app:
24 |
25 | ### Android
26 |
27 | ```sh
28 | # Using npm
29 | npm run android
30 |
31 | # OR using Yarn
32 | yarn android
33 | ```
34 |
35 | ### iOS
36 |
37 | For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps).
38 |
39 | The first time you create a new project, run the Ruby bundler to install CocoaPods itself:
40 |
41 | ```sh
42 | bundle install
43 | ```
44 |
45 | Then, and every time you update your native dependencies, run:
46 |
47 | ```sh
48 | bundle exec pod install
49 | ```
50 |
51 | For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html).
52 |
53 | ```sh
54 | # Using npm
55 | npm run ios
56 |
57 | # OR using Yarn
58 | yarn ios
59 | ```
60 |
61 | If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device.
62 |
63 | This is one way to run your app — you can also build it directly from Android Studio or Xcode.
64 |
65 | ## Step 3: Modify your app
66 |
67 | Now that you have successfully run the app, let's make changes!
68 |
69 | Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes — this is powered by [Fast Refresh](https://reactnative.dev/docs/fast-refresh).
70 |
71 | When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload:
72 |
73 | - **Android**: Press the R key twice or select **"Reload"** from the **Dev Menu**, accessed via Ctrl + M (Windows/Linux) or Cmd ⌘ + M (macOS).
74 | - **iOS**: Press R in iOS Simulator.
75 |
76 | ## Congratulations! :tada:
77 |
78 | You've successfully run and modified your React Native App. :partying_face:
79 |
80 | ### Now what?
81 |
82 | - If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps).
83 | - If you're curious to learn more about React Native, check out the [docs](https://reactnative.dev/docs/getting-started).
84 |
85 | # Troubleshooting
86 |
87 | If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page.
88 |
89 | # Learn More
90 |
91 | To learn more about React Native, take a look at the following resources:
92 |
93 | - [React Native Website](https://reactnative.dev) - learn more about React Native.
94 | - [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment.
95 | - [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**.
96 | - [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts.
97 | - [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native.
98 |
--------------------------------------------------------------------------------
/ios/implementation/Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrientationDirectorUtils.swift
3 | // react-native-orientation-director
4 | //
5 | // Created by gladiuscode on 18/05/2024.
6 | //
7 |
8 | import Foundation
9 |
10 | class Utils {
11 |
12 | private static let TAG = "Utils"
13 |
14 | // TODO: Add .unknown
15 | public func convertToOrientationFrom(uiInterfaceOrientation: UIInterfaceOrientation) -> Orientation {
16 | switch uiInterfaceOrientation {
17 | case .landscapeRight:
18 | return .LANDSCAPE_RIGHT
19 | case .portraitUpsideDown:
20 | return .PORTRAIT_UPSIDE_DOWN
21 | case .landscapeLeft:
22 | return .LANDSCAPE_LEFT
23 | default:
24 | return .PORTRAIT
25 | }
26 | }
27 |
28 | public func convertToOrientationFrom(deviceOrientation: UIDeviceOrientation) -> Orientation {
29 | switch deviceOrientation {
30 | case .landscapeRight:
31 | return .LANDSCAPE_RIGHT
32 | case .portraitUpsideDown:
33 | return .PORTRAIT_UPSIDE_DOWN
34 | case .landscapeLeft:
35 | return .LANDSCAPE_LEFT
36 | case .faceUp:
37 | return .FACE_UP
38 | case .faceDown:
39 | return .FACE_DOWN
40 | default:
41 | return .PORTRAIT
42 | }
43 | }
44 |
45 | public func convertToOrientationFrom(jsValue: NSNumber) -> Orientation {
46 | switch jsValue {
47 | case 2:
48 | return .LANDSCAPE_RIGHT
49 | case 3:
50 | return .PORTRAIT_UPSIDE_DOWN
51 | case 4:
52 | return .LANDSCAPE_LEFT
53 | case 5:
54 | return .LANDSCAPE
55 | default:
56 | return .PORTRAIT
57 | }
58 | }
59 |
60 | /**
61 | Note: .portraitUpsideDown only works for devices with home button and iPads
62 | https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations
63 | */
64 | public func convertToMaskFrom(jsOrientation: Orientation) -> UIInterfaceOrientationMask {
65 | switch jsOrientation {
66 | case .PORTRAIT:
67 | return .portrait
68 | case .LANDSCAPE_RIGHT:
69 | return .landscapeRight
70 | case .PORTRAIT_UPSIDE_DOWN:
71 | return .portraitUpsideDown
72 | case .LANDSCAPE_LEFT:
73 | return .landscapeLeft
74 | case .LANDSCAPE:
75 | return .landscape
76 | default:
77 | return .all
78 | }
79 | }
80 |
81 | public func convertToInterfaceOrientationFrom(mask: UIInterfaceOrientationMask) -> UIInterfaceOrientation {
82 | switch mask {
83 | case .portrait:
84 | return .portrait
85 | case .landscapeRight:
86 | return .landscapeRight
87 | case .portraitUpsideDown:
88 | return .portraitUpsideDown
89 | case .landscapeLeft:
90 | return .landscapeLeft
91 | default:
92 | return .unknown
93 | }
94 | }
95 |
96 | public func getInterfaceOrientation() -> UIInterfaceOrientation {
97 | guard let windowScene = self.getCurrentWindow()?.windowScene else {
98 | return UIInterfaceOrientation.unknown
99 | }
100 |
101 | return windowScene.interfaceOrientation
102 | }
103 |
104 | /* This function is needed to get the current available window.
105 | Here in React Native we should have only one window tho.
106 | https://stackoverflow.com/questions/57134259/how-to-resolve-keywindow-was-deprecated-in-ios-13-0/58031897#58031897
107 | */
108 | public func getCurrentWindow() -> UIWindow? {
109 | return UIApplication
110 | .shared
111 | .connectedScenes
112 | .compactMap { $0 as? UIWindowScene }
113 | .flatMap { $0.windows }
114 | .last { $0.isKeyWindow }
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/android/src/main/java/com/orientationdirector/implementation/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirector.implementation
2 |
3 | import android.content.Context
4 | import android.content.pm.ActivityInfo
5 | import android.os.Build
6 | import android.view.Surface
7 | import android.view.WindowManager
8 | import com.facebook.react.bridge.ReactContext
9 |
10 | class Utils(private val context: ReactContext) {
11 |
12 | fun getInterfaceRotation(): Int {
13 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
14 | context.currentActivity?.display?.rotation ?: Surface.ROTATION_0
15 | } else {
16 | val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
17 | @Suppress("DEPRECATION")
18 | windowManager.defaultDisplay.rotation
19 | }
20 | }
21 |
22 | fun convertToDeviceOrientationFrom(orientationAngles: FloatArray): Orientation {
23 | if (orientationAngles.size < 3) {
24 | return Orientation.PORTRAIT
25 | }
26 |
27 | val (_, pitchRadians, rollRadians) = orientationAngles
28 |
29 | val pitch = Math.toDegrees(pitchRadians.toDouble()).toFloat()
30 | val roll = Math.toDegrees(rollRadians.toDouble()).toFloat()
31 |
32 | val faceUpDownPitchTolerance = 30f
33 |
34 | fun isValueCloseTo(value: Float, target: Float, tolerance: Float): Boolean {
35 | return value in (target - tolerance)..(target + tolerance)
36 | }
37 |
38 | return when {
39 | // Face up: device is lying flat with screen up
40 | isValueCloseTo(pitch, 0f, faceUpDownPitchTolerance) &&
41 | isValueCloseTo(roll, 0f, faceUpDownPitchTolerance) -> Orientation.FACE_UP
42 |
43 | // Face down: device is lying flat with screen down
44 | isValueCloseTo(pitch, 0f, faceUpDownPitchTolerance) &&
45 | (isValueCloseTo(roll, 180f, faceUpDownPitchTolerance) || isValueCloseTo(
46 | roll,
47 | -180f,
48 | faceUpDownPitchTolerance
49 | )) -> Orientation.FACE_DOWN
50 |
51 | // Portrait
52 | isValueCloseTo(pitch, -90f, 45f) -> Orientation.PORTRAIT
53 |
54 | // Portrait upside down
55 | isValueCloseTo(pitch, 90f, 45f) -> Orientation.PORTRAIT_UPSIDE_DOWN
56 |
57 | // Landscape left
58 | isValueCloseTo(roll, -90f, 45f) -> Orientation.LANDSCAPE_LEFT
59 |
60 | // Landscape right
61 | isValueCloseTo(roll, 90f, 45f) -> Orientation.LANDSCAPE_RIGHT
62 |
63 | else -> Orientation.UNKNOWN
64 | }
65 | }
66 |
67 | fun convertToActivityOrientationFrom(orientation: Orientation): Int {
68 | return when (orientation) {
69 | Orientation.LANDSCAPE_RIGHT -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
70 | Orientation.PORTRAIT_UPSIDE_DOWN -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
71 | Orientation.LANDSCAPE_LEFT -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
72 | Orientation.LANDSCAPE -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
73 | else -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
74 | }
75 | }
76 |
77 | fun convertToOrientationFromJsValue(jsValue: Int): Orientation {
78 | return when (jsValue) {
79 | 2 -> Orientation.LANDSCAPE_RIGHT
80 | 3 -> Orientation.PORTRAIT_UPSIDE_DOWN
81 | 4 -> Orientation.LANDSCAPE_LEFT
82 | 5 -> Orientation.LANDSCAPE
83 | else -> Orientation.PORTRAIT
84 | }
85 | }
86 |
87 | fun convertToOrientationFromScreenRotation(screenRotation: Int): Orientation {
88 | return when (screenRotation) {
89 | Surface.ROTATION_270 -> Orientation.LANDSCAPE_RIGHT
90 | Surface.ROTATION_90 -> Orientation.LANDSCAPE_LEFT
91 | Surface.ROTATION_180 -> Orientation.PORTRAIT_UPSIDE_DOWN
92 | else -> Orientation.PORTRAIT
93 | }
94 | }
95 |
96 | fun convertToInterfaceOrientationFrom(deviceOrientation: Orientation): Orientation {
97 | return when (deviceOrientation) {
98 | Orientation.PORTRAIT -> Orientation.PORTRAIT
99 | Orientation.LANDSCAPE_RIGHT -> Orientation.LANDSCAPE_LEFT
100 | Orientation.PORTRAIT_UPSIDE_DOWN -> Orientation.PORTRAIT_UPSIDE_DOWN
101 | Orientation.LANDSCAPE_LEFT -> Orientation.LANDSCAPE_RIGHT
102 | else -> Orientation.UNKNOWN
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/ios/OrientationDirector.mm:
--------------------------------------------------------------------------------
1 | #import "OrientationDirector.h"
2 |
3 | /*
4 | This condition is needed to support use_frameworks.
5 | https://github.com/callstack/react-native-builder-bob/discussions/412#discussioncomment-6352402
6 | */
7 | #if __has_include("react_native_orientation_director-Swift.h")
8 | #import "react_native_orientation_director-Swift.h"
9 | #else
10 | #import "react_native_orientation_director/react_native_orientation_director-Swift.h"
11 | #endif
12 |
13 | static OrientationDirectorImpl *_director = [OrientationDirectorImpl new];
14 |
15 | ///////////////////////////////////////////////////////////////////////////////////////
16 | /// EVENT EMITTER SETUP
17 | ///https://github.com/react-native-community/RNNewArchitectureLibraries/tree/feat/swift-event-emitter
18 | @interface OrientationDirector()
19 | @end
20 | ///
21 | //////////////////////////////////////////////////////////////////////////////////////////
22 |
23 | @implementation OrientationDirector
24 | RCT_EXPORT_MODULE()
25 |
26 | - (instancetype)init
27 | {
28 | self = [super init];
29 | if (self) {
30 | ///////////////////////////////////////////////////////////////////////////////////////
31 | /// EVENT EMITTER SETUP
32 | [_director setEventManagerWithDelegate:self];
33 | ///
34 | //////////////////////////////////////////////////////////////////////////////////////////
35 | }
36 | return self;
37 | }
38 |
39 | + (BOOL)requiresMainQueueSetup
40 | {
41 | return YES;
42 | }
43 |
44 | + (UIInterfaceOrientationMask)getSupportedInterfaceOrientationsForWindow
45 | {
46 | return [_director supportedInterfaceOrientations];
47 | }
48 |
49 | ///////////////////////////////////////////////////////////////////////////////////////
50 | /// EVENT EMITTER SETUP
51 | ///
52 | -(void)startObserving {
53 | self.isJsListening = YES;
54 | }
55 |
56 | -(void)stopObserving {
57 | self.isJsListening = NO;
58 | }
59 |
60 | - (NSArray *)supportedEvents {
61 | return [EventManager supportedEvents];
62 | }
63 |
64 | - (void)sendEventWithName:(NSString * _Nonnull)name params:(NSDictionary *)params {
65 | [self sendEventWithName:name body:params];
66 | }
67 | ///
68 | ///////////////////////////////////////////////////////////////////////////////////////
69 |
70 | ///////////////////////////////////////////////////////////////////////////////////////
71 | /// EXPORTED METHODS
72 | ///
73 | RCT_EXPORT_METHOD(getInterfaceOrientation:(RCTPromiseResolveBlock)resolve
74 | reject:(RCTPromiseRejectBlock)reject)
75 | {
76 | dispatch_async(dispatch_get_main_queue(), ^{
77 | resolve(@([_director getInterfaceOrientation]));
78 | });
79 | }
80 |
81 | RCT_EXPORT_METHOD(getDeviceOrientation:(RCTPromiseResolveBlock)resolve
82 | reject:(RCTPromiseRejectBlock)reject)
83 | {
84 | dispatch_async(dispatch_get_main_queue(), ^{
85 | resolve(@([_director getDeviceOrientation]));
86 | });
87 | }
88 |
89 | RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isLocked)
90 | {
91 | return @([_director getIsLocked]);
92 | }
93 |
94 | RCT_EXPORT_METHOD(lockTo:(double)jsOrientation)
95 | {
96 | NSNumber *jsOrientationNumber = @(jsOrientation);
97 | dispatch_async(dispatch_get_main_queue(), ^{
98 | [_director lockToJsValue:jsOrientationNumber];
99 | });
100 | }
101 |
102 | RCT_EXPORT_METHOD(unlock)
103 | {
104 | dispatch_async(dispatch_get_main_queue(), ^{
105 | [_director unlock];
106 | });
107 | }
108 |
109 | RCT_EXPORT_METHOD(resetSupportedInterfaceOrientations)
110 | {
111 | dispatch_async(dispatch_get_main_queue(), ^{
112 | [_director resetSupportedInterfaceOrientations];
113 | });
114 | }
115 |
116 | /**
117 | This method is a pure stub since we cannot access auto rotation setting in iOS
118 | */
119 | RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isAutoRotationEnabled)
120 | {
121 | return @(NO);
122 | }
123 | ///
124 | ///////////////////////////////////////////////////////////////////////////////////////
125 |
126 | // Don't compile this code when we build for the old architecture.
127 | #ifdef RCT_NEW_ARCH_ENABLED
128 | - (std::shared_ptr)getTurboModule:
129 | (const facebook::react::ObjCTurboModule::InitParams &)params
130 | {
131 | return std::make_shared(params);
132 | }
133 | #endif
134 |
135 | @end
136 |
--------------------------------------------------------------------------------
/example/ios/OrientationDirectorExample/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/example/src/screens/Home.tsx:
--------------------------------------------------------------------------------
1 | import { useNavigation } from '@react-navigation/native';
2 | import { Button, ScrollView, Text, View } from 'react-native';
3 | import { homepageStyle } from './styles';
4 | import RNOrientationDirector, {
5 | Orientation,
6 | } from 'react-native-orientation-director';
7 |
8 | function Home() {
9 | const navigation = useNavigation();
10 |
11 | const handleGoToExploreOnPress = () => {
12 | navigation.navigate('Explore' as never);
13 | };
14 |
15 | return (
16 |
17 | Welcome!
18 |
19 |
20 |
21 |
22 |
121 |
122 | );
123 | }
124 |
125 | export default Home;
126 |
--------------------------------------------------------------------------------
/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt:
--------------------------------------------------------------------------------
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 =
14 | context.getSystemService(SENSOR_SERVICE) as SensorManager
15 |
16 | private var mRotationSensor: Sensor? =
17 | mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
18 | private var mAccelerometerSensor: Sensor? =
19 | mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
20 | private var mMagneticFieldSensor: Sensor? =
21 | mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
22 |
23 | private var hasRotationSensor: Boolean =
24 | mRotationSensor != null
25 | private var hasAccelerometerAndMagneticFieldSensors: Boolean =
26 | mAccelerometerSensor != null && mMagneticFieldSensor != null
27 |
28 | private val accelerometerReading = FloatArray(3)
29 | private val magnetometerReading = FloatArray(3)
30 |
31 | private var lastComputedOrientationAngles = FloatArray(3)
32 | private var onOrientationAnglesChangedCallback: ((orientationAngles: FloatArray) -> Unit)? = null
33 |
34 | fun setOnOrientationAnglesChangedCallback(callback: (orientation: FloatArray) -> Unit) {
35 | onOrientationAnglesChangedCallback = callback
36 | }
37 |
38 | override fun onSensorChanged(event: SensorEvent?) {
39 | if (event == null) {
40 | return
41 | }
42 |
43 | if (event.sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
44 | computeOrientationFromRotationSensor(event.values);
45 | return
46 | }
47 |
48 | computeOrientationFromOtherSensors(event)
49 | }
50 |
51 | override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
52 |
53 | fun enable() {
54 | if (hasRotationSensor) {
55 | mSensorManager.registerListener(
56 | this,
57 | mRotationSensor,
58 | SensorManager.SENSOR_DELAY_NORMAL,
59 | SensorManager.SENSOR_DELAY_UI
60 | )
61 | return
62 | }
63 |
64 | if (hasAccelerometerAndMagneticFieldSensors) {
65 | mSensorManager.registerListener(
66 | this,
67 | mAccelerometerSensor,
68 | SensorManager.SENSOR_DELAY_NORMAL,
69 | SensorManager.SENSOR_DELAY_UI
70 | )
71 | mSensorManager.registerListener(
72 | this,
73 | mMagneticFieldSensor,
74 | SensorManager.SENSOR_DELAY_NORMAL,
75 | SensorManager.SENSOR_DELAY_UI
76 | )
77 | return
78 | }
79 | }
80 |
81 | fun disable() {
82 | mSensorManager.unregisterListener(this)
83 | }
84 |
85 | private fun computeOrientationFromRotationSensor(values: FloatArray) {
86 | val rotationMatrix = FloatArray(9)
87 | SensorManager.getRotationMatrixFromVector(rotationMatrix, values)
88 |
89 | val orientationAngles = FloatArray(3)
90 | SensorManager.getOrientation(rotationMatrix, orientationAngles)
91 |
92 | notifyOrientationAnglesChanged(orientationAngles)
93 | }
94 |
95 | private fun computeOrientationFromOtherSensors(event: SensorEvent) {
96 | if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
97 | System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)
98 | }
99 |
100 | if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {
101 | System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)
102 | }
103 |
104 | val rotationMatrix = FloatArray(9)
105 | val didComputeMatrix = SensorManager.getRotationMatrix(
106 | rotationMatrix,
107 | null,
108 | accelerometerReading,
109 | magnetometerReading
110 | )
111 | if (!didComputeMatrix) {
112 | return
113 | }
114 |
115 | val orientationAngles = FloatArray(3)
116 | SensorManager.getOrientation(rotationMatrix, orientationAngles)
117 |
118 | notifyOrientationAnglesChanged(orientationAngles)
119 | }
120 |
121 | private fun notifyOrientationAnglesChanged(orientationAngles: FloatArray) {
122 | if (lastComputedOrientationAngles.contentEquals(orientationAngles)) {
123 | return
124 | }
125 |
126 | onOrientationAnglesChangedCallback?.invoke(orientationAngles)
127 | lastComputedOrientationAngles = orientationAngles
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/plugin/src/withRNOrientationAppDelegate.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type ConfigPlugin,
3 | type ExportedConfigWithProps,
4 | withAppDelegate,
5 | } from '@expo/config-plugins';
6 | import { type AppDelegateProjectFile } from '@expo/config-plugins/build/ios/Paths';
7 | import { mergeContents } from '@expo/config-plugins/build/utils/generateCode';
8 |
9 | export const withRNOrientationAppDelegate: ConfigPlugin = (config) => {
10 | return withAppDelegate(config, readAppDelegateFileAndUpdateContents);
11 | };
12 |
13 | async function readAppDelegateFileAndUpdateContents(
14 | config: ExportedConfigWithProps
15 | ): Promise> {
16 | const { modResults: appDelegateFile } = config;
17 |
18 | const fileUpdater = getCompatibleFileUpdater(appDelegateFile.language);
19 | if (fileUpdater.language === 'swift') {
20 | const { worker } = fileUpdater;
21 | appDelegateFile.contents = worker(
22 | appDelegateFile.contents,
23 | config.sdkVersion
24 | );
25 | } else {
26 | const { worker } = fileUpdater;
27 | appDelegateFile.contents = worker(appDelegateFile.contents);
28 | }
29 |
30 | return config;
31 | }
32 |
33 | function getCompatibleFileUpdater(
34 | language: AppDelegateProjectFile['language']
35 | ):
36 | | { language: 'swift'; worker: typeof swiftFileUpdater }
37 | | { language: 'objc' | 'objcpp'; worker: typeof objCFileUpdater } {
38 | switch (language) {
39 | case 'objc':
40 | case 'objcpp': {
41 | return {
42 | language,
43 | worker: objCFileUpdater,
44 | };
45 | }
46 | case 'swift':
47 | return {
48 | language,
49 | worker: swiftFileUpdater,
50 | };
51 | default:
52 | throw new Error(
53 | `Cannot add React Native Orientation Director code to AppDelegate of language "${language}"`
54 | );
55 | }
56 | }
57 |
58 | export function swiftFileUpdater(
59 | originalContents: string,
60 | sdkVersion?: string
61 | ): string {
62 | const methodPrefix = computeMethodPrefix(sdkVersion);
63 | const supportedInterfaceOrientationsForCodeBlock = `\n ${methodPrefix} func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
64 | return OrientationDirector.getSupportedInterfaceOrientationsForWindow()
65 | }\n`;
66 | const rightBeforeLastClosingBrace =
67 | /didFinishLaunchingWithOptions:\s*launchOptions\)/g;
68 | const pasteInTheListJustAfterTheClosingBracket = 2;
69 |
70 | const results = mergeContents({
71 | tag: '@react-native-orientation-director/supportedInterfaceOrientationsFor-implementation',
72 | src: originalContents,
73 | newSrc: supportedInterfaceOrientationsForCodeBlock,
74 | anchor: rightBeforeLastClosingBrace,
75 | offset: pasteInTheListJustAfterTheClosingBracket,
76 | comment: '// React Native Orientation Director',
77 | });
78 |
79 | return results.contents;
80 |
81 | function computeMethodPrefix(_sdkVersion?: string) {
82 | if (!_sdkVersion) {
83 | return '';
84 | }
85 |
86 | const rawMajor = _sdkVersion.split('.').at(0);
87 | if (!rawMajor) {
88 | return '';
89 | }
90 |
91 | const major = Number(rawMajor);
92 | if (Number.isNaN(major)) {
93 | return '';
94 | }
95 |
96 | if (major === 53) {
97 | return '';
98 | }
99 |
100 | return 'public override';
101 | }
102 | }
103 |
104 | export function objCFileUpdater(originalContents: string): string {
105 | const libraryHeaderImportCodeBlock = '#import "OrientationDirector.h"\n';
106 | const rightBeforeAppDelegateImplementation = /@implementation\s+\w+/g;
107 |
108 | const headerImportMergeResults = mergeContents({
109 | tag: '@react-native-orientation-director/library-header-import',
110 | src: originalContents,
111 | newSrc: libraryHeaderImportCodeBlock,
112 | anchor: rightBeforeAppDelegateImplementation,
113 | offset: 0,
114 | comment: '// React Native Orientation Director',
115 | });
116 |
117 | const supportedInterfaceOrientationsForCodeBlock = `- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
118 | {
119 | return [OrientationDirector getSupportedInterfaceOrientationsForWindow];
120 | }\n`;
121 | const rightBeforeLastClosingEnd = /@end[^@]*$/g;
122 |
123 | const implementationMergeResults = mergeContents({
124 | tag: '@react-native-orientation-director/supportedInterfaceOrientationsFor-implementation',
125 | src: headerImportMergeResults.contents,
126 | newSrc: supportedInterfaceOrientationsForCodeBlock,
127 | anchor: rightBeforeLastClosingEnd,
128 | offset: 0,
129 | comment: '// React Native Orientation Director',
130 | });
131 |
132 | return implementationMergeResults.contents;
133 | }
134 |
--------------------------------------------------------------------------------
/plugin/src/withRNOrientationMainActivity.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type ConfigPlugin,
3 | type ExportedConfigWithProps,
4 | withMainActivity,
5 | } from '@expo/config-plugins';
6 | import { type ApplicationProjectFile } from '@expo/config-plugins/build/android/Paths';
7 | import { mergeContents } from '@expo/config-plugins/build/utils/generateCode';
8 |
9 | export const withRNOrientationMainActivity: ConfigPlugin = (config) => {
10 | return withMainActivity(config, readMainActivityFileAndUpdateContents);
11 | };
12 |
13 | async function readMainActivityFileAndUpdateContents(
14 | config: ExportedConfigWithProps
15 | ): Promise> {
16 | const { modResults: mainActivityFile } = config;
17 |
18 | const worker = getCompatibleFileUpdater(mainActivityFile.language);
19 | mainActivityFile.contents = worker(mainActivityFile.contents);
20 |
21 | return config;
22 | }
23 |
24 | function getCompatibleFileUpdater(
25 | language: ApplicationProjectFile['language']
26 | ): (originalContents: string) => string {
27 | switch (language) {
28 | case 'kt':
29 | return ktFileUpdater;
30 | default:
31 | throw new Error(
32 | `Cannot add React Native Orientation Director code to MainActivity of language "${language}"`
33 | );
34 | }
35 | }
36 |
37 | export function ktFileUpdater(originalContents: string): string {
38 | const systemImportsContents =
39 | updateContentsWithSystemImports(originalContents);
40 |
41 | const libraryImportCodeBlock =
42 | 'import com.orientationdirector.implementation.ConfigurationChangedBroadcastReceiver';
43 | const rightBeforeClassDeclaration = /class MainActivity/g;
44 |
45 | const importMergeResults = mergeContents({
46 | tag: '@react-native-orientation-director/library-import',
47 | src: systemImportsContents,
48 | newSrc: libraryImportCodeBlock,
49 | anchor: rightBeforeClassDeclaration,
50 | offset: 0,
51 | comment: '// React Native Orientation Director',
52 | });
53 |
54 | const onConfigurationChangedCodeBlock = `
55 | override fun onConfigurationChanged(newConfig: Configuration) {
56 | super.onConfigurationChanged(newConfig)
57 |
58 | val orientationDirectorCustomAction =
59 | packageName + "." + ConfigurationChangedBroadcastReceiver.CUSTOM_INTENT_ACTION
60 |
61 | val intent =
62 | Intent(orientationDirectorCustomAction).apply {
63 | putExtra("newConfig", newConfig)
64 | setPackage(packageName)
65 | }
66 |
67 | this.sendBroadcast(intent)
68 | }\n`;
69 |
70 | const rightBeforeLastClosingBrace = /super\.onCreate/g;
71 | const pasteInTheListJustAfterTheClosingBracket = 2;
72 |
73 | const implementationMergeResults = mergeContents({
74 | tag: '@react-native-orientation-director/supportedInterfaceOrientationsFor-implementation',
75 | src: importMergeResults.contents,
76 | newSrc: onConfigurationChangedCodeBlock,
77 | anchor: rightBeforeLastClosingBrace,
78 | offset: pasteInTheListJustAfterTheClosingBracket,
79 | comment: '// React Native Orientation Director',
80 | });
81 |
82 | return implementationMergeResults.contents;
83 | }
84 |
85 | function updateContentsWithSystemImports(originalContents: string) {
86 | const rightBeforeClassDeclaration = /class MainActivity/g;
87 |
88 | let possibleUpdatedContents = originalContents;
89 | possibleUpdatedContents = addIntentImportIfNecessary(possibleUpdatedContents);
90 | possibleUpdatedContents = addConfigurationImportIfNecessary(
91 | possibleUpdatedContents
92 | );
93 |
94 | return possibleUpdatedContents;
95 |
96 | function addIntentImportIfNecessary(_contents: string) {
97 | const systemIntentImportCodeBlock = 'import android.content.Intent';
98 | if (_contents.includes(systemIntentImportCodeBlock)) {
99 | return _contents;
100 | }
101 |
102 | const mergeResults = mergeContents({
103 | tag: '@react-native-orientation-director/system-intent-import',
104 | src: _contents,
105 | newSrc: systemIntentImportCodeBlock,
106 | anchor: rightBeforeClassDeclaration,
107 | offset: 0,
108 | comment: '// React Native Orientation Director',
109 | });
110 |
111 | return mergeResults.contents;
112 | }
113 | function addConfigurationImportIfNecessary(_contents: string) {
114 | const systemConfigurationImportCodeBlock =
115 | 'import android.content.res.Configuration';
116 | if (possibleUpdatedContents.includes(systemConfigurationImportCodeBlock)) {
117 | return _contents;
118 | }
119 |
120 | const mergeResults = mergeContents({
121 | tag: '@react-native-orientation-director/system-configuration-import',
122 | src: possibleUpdatedContents,
123 | newSrc: systemConfigurationImportCodeBlock,
124 | anchor: rightBeforeClassDeclaration,
125 | offset: 0,
126 | comment: '// React Native Orientation Director',
127 | });
128 |
129 | return mergeResults.contents;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 | apply plugin: "org.jetbrains.kotlin.android"
3 | apply plugin: "com.facebook.react"
4 |
5 | /**
6 | * This is the configuration block to customize your React Native Android app.
7 | * By default you don't need to apply any configuration, just uncomment the lines you need.
8 | */
9 | react {
10 | /* Folders */
11 | // The root of your project, i.e. where "package.json" lives. Default is '../..'
12 | // root = file("../../")
13 | // The folder where the react-native NPM package is. Default is ../../node_modules/react-native
14 | // reactNativeDir = file("../../node_modules/react-native")
15 | // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
16 | // codegenDir = file("../../node_modules/@react-native/codegen")
17 | // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js
18 | // cliFile = file("../../node_modules/react-native/cli.js")
19 |
20 | /* Variants */
21 | // The list of variants to that are debuggable. For those we're going to
22 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'.
23 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
24 | // debuggableVariants = ["liteDebug", "prodDebug"]
25 |
26 | /* Bundling */
27 | // A list containing the node command and its flags. Default is just 'node'.
28 | // nodeExecutableAndArgs = ["node"]
29 | //
30 | // The command to run when bundling. By default is 'bundle'
31 | // bundleCommand = "ram-bundle"
32 | //
33 | // The path to the CLI configuration file. Default is empty.
34 | // bundleConfig = file(../rn-cli.config.js)
35 | //
36 | // The name of the generated asset file containing your JS bundle
37 | // bundleAssetName = "MyApplication.android.bundle"
38 | //
39 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
40 | // entryFile = file("../js/MyApplication.android.js")
41 | //
42 | // A list of extra flags to pass to the 'bundle' commands.
43 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
44 | // extraPackagerArgs = []
45 |
46 | /* Hermes Commands */
47 | // The hermes compiler command to run. By default it is 'hermesc'
48 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
49 | //
50 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
51 | // hermesFlags = ["-O", "-output-source-map"]
52 |
53 | /* Autolinking */
54 | autolinkLibrariesWithApp()
55 | }
56 |
57 | /**
58 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
59 | */
60 | def enableProguardInReleaseBuilds = false
61 |
62 | /**
63 | * The preferred build flavor of JavaScriptCore (JSC)
64 | *
65 | * For example, to use the international variant, you can use:
66 | * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`
67 | *
68 | * The international variant includes ICU i18n library and necessary data
69 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
70 | * give correct results when using with locales other than en-US. Note that
71 | * this variant is about 6MiB larger per architecture than default.
72 | */
73 | def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
74 |
75 | android {
76 | ndkVersion rootProject.ext.ndkVersion
77 | buildToolsVersion rootProject.ext.buildToolsVersion
78 | compileSdk rootProject.ext.compileSdkVersion
79 |
80 | namespace "com.orientationdirectorexample"
81 | defaultConfig {
82 | applicationId "com.orientationdirectorexample"
83 | minSdkVersion rootProject.ext.minSdkVersion
84 | targetSdkVersion rootProject.ext.targetSdkVersion
85 | versionCode 1
86 | versionName "1.0"
87 | }
88 | signingConfigs {
89 | debug {
90 | storeFile file('debug.keystore')
91 | storePassword 'android'
92 | keyAlias 'androiddebugkey'
93 | keyPassword 'android'
94 | }
95 | }
96 | buildTypes {
97 | debug {
98 | signingConfig signingConfigs.debug
99 | }
100 | release {
101 | // Caution! In production, you need to generate your own keystore file.
102 | // see https://reactnative.dev/docs/signed-apk-android.
103 | signingConfig signingConfigs.debug
104 | minifyEnabled enableProguardInReleaseBuilds
105 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
106 | }
107 | }
108 | }
109 |
110 | dependencies {
111 | // The version of react-native is set by the React Native Gradle Plugin
112 | implementation("com.facebook.react:react-android")
113 |
114 | if (hermesEnabled.toBoolean()) {
115 | implementation("com.facebook.react:hermes-android")
116 | } else {
117 | implementation jscFlavor
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/.github/workflows/pr.yml:
--------------------------------------------------------------------------------
1 | name: PR Validation
2 |
3 | on: workflow_dispatch
4 |
5 | jobs:
6 | lint:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Checkout
10 | uses: actions/checkout@v3
11 |
12 | - name: Setup
13 | uses: ./.github/actions/setup
14 |
15 | - name: Lint library & example files
16 | run: yarn lint
17 |
18 | - name: Lint plugin files
19 | run: yarn expo-plugin lint
20 |
21 | - name: Typecheck library & example files
22 | run: yarn typecheck
23 |
24 | - name: Typecheck plugin files
25 | run: yarn expo-plugin typecheck
26 |
27 | test:
28 | runs-on: ubuntu-latest
29 | steps:
30 | - name: Checkout
31 | uses: actions/checkout@v3
32 |
33 | - name: Setup
34 | uses: ./.github/actions/setup
35 |
36 | - name: Run unit tests
37 | run: yarn test --maxWorkers=2 --coverage
38 |
39 | build-library:
40 | runs-on: ubuntu-latest
41 | steps:
42 | - name: Checkout
43 | uses: actions/checkout@v3
44 |
45 | - name: Setup
46 | uses: ./.github/actions/setup
47 |
48 | - name: Build package
49 | run: yarn prepare
50 |
51 | build-android:
52 | runs-on: ubuntu-latest
53 | env:
54 | TURBO_CACHE_DIR: .turbo/android
55 | steps:
56 | - name: Checkout
57 | uses: actions/checkout@v3
58 |
59 | - name: Setup
60 | uses: ./.github/actions/setup
61 |
62 | - name: Cache turborepo for Android
63 | uses: actions/cache@v3
64 | with:
65 | path: ${{ env.TURBO_CACHE_DIR }}
66 | key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }}
67 | restore-keys: |
68 | ${{ runner.os }}-turborepo-android-
69 |
70 | - name: Check turborepo cache for Android
71 | run: |
72 | TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status")
73 |
74 | if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
75 | echo "turbo_cache_hit=1" >> $GITHUB_ENV
76 | fi
77 |
78 | - name: Install JDK
79 | if: env.turbo_cache_hit != 1
80 | uses: actions/setup-java@v3
81 | with:
82 | distribution: 'zulu'
83 | java-version: '17'
84 |
85 | - name: Finalize Android SDK
86 | if: env.turbo_cache_hit != 1
87 | run: |
88 | /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null"
89 |
90 | - name: Cache Gradle
91 | if: env.turbo_cache_hit != 1
92 | uses: actions/cache@v3
93 | with:
94 | path: |
95 | ~/.gradle/wrapper
96 | ~/.gradle/caches
97 | key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }}
98 | restore-keys: |
99 | ${{ runner.os }}-gradle-
100 |
101 | - name: Run test
102 | if: env.turbo_cache_hit != 1
103 | run: |
104 | cd example/android && ./gradlew react-native-orientation-director:testDebugUnitTest
105 |
106 | - name: Build example for Android
107 | env:
108 | JAVA_OPTS: '-XX:MaxHeapSize=6g'
109 | run: |
110 | yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"
111 |
112 | build-ios:
113 | runs-on: macos-14
114 | env:
115 | TURBO_CACHE_DIR: .turbo/ios
116 | steps:
117 | - name: Checkout
118 | uses: actions/checkout@v3
119 |
120 | - name: Setup
121 | uses: ./.github/actions/setup
122 |
123 | - name: Cache turborepo for iOS
124 | uses: actions/cache@v3
125 | with:
126 | path: ${{ env.TURBO_CACHE_DIR }}
127 | key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('yarn.lock') }}
128 | restore-keys: |
129 | ${{ runner.os }}-turborepo-ios-
130 |
131 | - name: Check turborepo cache for iOS
132 | run: |
133 | TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status")
134 |
135 | if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
136 | echo "turbo_cache_hit=1" >> $GITHUB_ENV
137 | fi
138 |
139 | - name: Cache cocoapods
140 | if: env.turbo_cache_hit != 1
141 | id: cocoapods-cache
142 | uses: actions/cache@v3
143 | with:
144 | path: |
145 | **/ios/Pods
146 | key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }}
147 | restore-keys: |
148 | ${{ runner.os }}-cocoapods-
149 |
150 | - name: Install cocoapods
151 | if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'
152 | run: |
153 | cd example/ios
154 | pod install
155 | env:
156 | NO_FLIPPER: 1
157 | RCT_NEW_ARCH_ENABLED: 1
158 |
159 | - name: Build example for iOS
160 | run: |
161 | yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"
162 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are always welcome, no matter how large or small!
4 |
5 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md).
6 |
7 | ## Development workflow
8 |
9 | This project is a monorepo managed using [Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains the following packages:
10 |
11 | - The library package in the root directory.
12 | - An example app in the `example/` directory.
13 |
14 | To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
15 |
16 | ```sh
17 | yarn
18 | ```
19 |
20 | > Since the project relies on Yarn workspaces, you cannot use [`npm`](https://github.com/npm/cli) for development.
21 |
22 | The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make.
23 |
24 | It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app.
25 |
26 | If you want to use Android Studio or XCode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/OrientationDirectorExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-orientation-director`.
27 |
28 | To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-orientation-director` under `Android`.
29 |
30 | You can use various commands from the root directory to work with the project.
31 |
32 | To start the packager:
33 |
34 | ```sh
35 | yarn example start
36 | ```
37 |
38 | To run the example app on Android:
39 |
40 | ```sh
41 | yarn example android
42 | ```
43 |
44 | To run the example app on iOS:
45 |
46 | ```sh
47 | yarn example ios
48 | ```
49 |
50 | To confirm that the app is running with the new architecture, you can check the Metro logs for a message like this:
51 |
52 | ```sh
53 | Running "OrientationDirectorExample" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1}
54 | ```
55 |
56 | Note the `"fabric":true` and `"concurrentRoot":true` properties.
57 |
58 | Make sure your code passes TypeScript and ESLint. Run the following to verify:
59 |
60 | ```sh
61 | yarn typecheck
62 | yarn lint
63 | ```
64 |
65 | To fix formatting errors, run the following:
66 |
67 | ```sh
68 | yarn lint --fix
69 | ```
70 |
71 | Remember to add tests for your change if possible. Run the unit tests by:
72 |
73 | ```sh
74 | yarn test
75 | ```
76 |
77 | ### Commit message convention
78 |
79 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
80 |
81 | - `fix`: bug fixes, e.g. fix crash due to deprecated method.
82 | - `feat`: new features, e.g. add new method to the module.
83 | - `refactor`: code refactor, e.g. migrate from class components to hooks.
84 | - `docs`: changes into documentation, e.g. add usage example for the module..
85 | - `test`: adding or updating tests, e.g. add integration tests using detox.
86 | - `chore`: tooling changes, e.g. change CI config.
87 |
88 | Our pre-commit hooks verify that your commit message matches this format when committing.
89 |
90 | ### Linting and tests
91 |
92 | [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
93 |
94 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
95 |
96 | Our pre-commit hooks verify that the linter and tests pass when committing.
97 |
98 | ### Publishing to npm
99 |
100 | We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc.
101 |
102 | To publish new versions, run the following:
103 |
104 | ```sh
105 | yarn release
106 | ```
107 |
108 | ### Scripts
109 |
110 | The `package.json` file contains various scripts for common tasks:
111 |
112 | - `yarn`: setup project by installing dependencies.
113 | - `yarn typecheck`: type-check files with TypeScript.
114 | - `yarn lint`: lint files with ESLint.
115 | - `yarn test`: run unit tests with Jest.
116 | - `yarn example start`: start the Metro server for the example app.
117 | - `yarn example android`: run the example app on Android.
118 | - `yarn example ios`: run the example app on iOS.
119 |
120 | ### Sending a pull request
121 |
122 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
123 |
124 | When you're sending a pull request:
125 |
126 | - Prefer small pull requests focused on one change.
127 | - Verify that linters and tests are passing.
128 | - Review the documentation to make sure it looks good.
129 | - Follow the pull request template when opening a pull request.
130 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
131 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-orientation-director",
3 | "version": "2.6.5",
4 | "description": "A Modern React Native library that allows you to access orientation",
5 | "main": "./lib/module/index.js",
6 | "types": "./lib/typescript/module/src/index.d.ts",
7 | "exports": {
8 | ".": {
9 | "import": {
10 | "types": "./lib/typescript/module/src/index.d.ts",
11 | "default": "./lib/module/index.js"
12 | },
13 | "require": {
14 | "types": "./lib/typescript/commonjs/src/index.d.ts",
15 | "default": "./lib/commonjs/index.js"
16 | }
17 | },
18 | "./package.json": "./package.json",
19 | "./app.plugin.js": "./app.plugin.js"
20 | },
21 | "files": [
22 | "src",
23 | "lib",
24 | "android",
25 | "ios",
26 | "cpp",
27 | "plugin/build",
28 | "plugin/tsconfig.json",
29 | "app.plugin.js",
30 | "*.podspec",
31 | "react-native.config.js",
32 | "!ios/build",
33 | "!android/build",
34 | "!android/gradle",
35 | "!android/gradlew",
36 | "!android/gradlew.bat",
37 | "!android/local.properties",
38 | "!**/__tests__",
39 | "!**/__fixtures__",
40 | "!**/__mocks__",
41 | "!**/.*"
42 | ],
43 | "scripts": {
44 | "example": "yarn workspace react-native-orientation-director-example",
45 | "expo-plugin": "yarn workspace react-native-orientation-director-expo-plugin",
46 | "test": "jest",
47 | "typecheck": "tsc",
48 | "lint": "eslint \"src/**/*.{js,ts,tsx}\" \"example/**/*.{js,ts,tsx}\"",
49 | "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
50 | "prepare": "bob build",
51 | "release": "release-it"
52 | },
53 | "keywords": [
54 | "react-native",
55 | "ios",
56 | "android"
57 | ],
58 | "repository": {
59 | "type": "git",
60 | "url": "git+https://github.com/gladiuscode/react-native-orientation-director.git"
61 | },
62 | "author": "gladius (https://github.com/gladiuscode)",
63 | "license": "MIT",
64 | "bugs": {
65 | "url": "https://github.com/gladiuscode/react-native-orientation-director/issues"
66 | },
67 | "homepage": "https://github.com/gladiuscode/react-native-orientation-director#readme",
68 | "publishConfig": {
69 | "registry": "https://registry.npmjs.org/"
70 | },
71 | "devDependencies": {
72 | "@commitlint/config-conventional": "^19.6.0",
73 | "@eslint/compat": "^1.2.7",
74 | "@eslint/eslintrc": "^3.3.0",
75 | "@eslint/js": "^9.22.0",
76 | "@evilmartians/lefthook": "^1.5.0",
77 | "@react-native-community/cli": "15.0.0-alpha.2",
78 | "@react-native/babel-preset": "0.80.0",
79 | "@react-native/eslint-config": "0.80.0",
80 | "@release-it/conventional-changelog": "^9.0.2",
81 | "@types/jest": "^29.5.5",
82 | "@types/react": "^19.1.0",
83 | "@typescript-eslint/eslint-plugin": "^7.18.0",
84 | "commitlint": "^19.6.1",
85 | "del-cli": "^5.1.0",
86 | "eslint": "^9.22.0",
87 | "eslint-config-prettier": "^10.1.1",
88 | "eslint-plugin-jest": "^27.9.0",
89 | "eslint-plugin-prettier": "^5.2.3",
90 | "jest": "^29.7.0",
91 | "prettier": "^3.0.3",
92 | "react": "19.1.0",
93 | "react-native": "0.80.0",
94 | "react-native-builder-bob": "^0.40.10",
95 | "release-it": "^17.10.0",
96 | "turbo": "^1.10.7",
97 | "typescript": "^5.2.2"
98 | },
99 | "peerDependencies": {
100 | "expo": ">=47.0.0",
101 | "react": "*",
102 | "react-native": "*"
103 | },
104 | "peerDependenciesMeta": {
105 | "expo": {
106 | "optional": true
107 | }
108 | },
109 | "resolutions": {
110 | "eslint-plugin-jest": "^27.9.0",
111 | "@typescript-eslint/eslint-plugin": "^7.18.0"
112 | },
113 | "workspaces": [
114 | "example",
115 | "plugin"
116 | ],
117 | "packageManager": "yarn@3.6.1",
118 | "jest": {
119 | "preset": "react-native",
120 | "modulePathIgnorePatterns": [
121 | "/example/node_modules",
122 | "/lib/"
123 | ]
124 | },
125 | "commitlint": {
126 | "extends": [
127 | "@commitlint/config-conventional"
128 | ]
129 | },
130 | "release-it": {
131 | "git": {
132 | "commitMessage": "chore: release ${version}",
133 | "tagName": "v${version}"
134 | },
135 | "npm": {
136 | "publish": true
137 | },
138 | "github": {
139 | "release": true
140 | },
141 | "plugins": {
142 | "@release-it/conventional-changelog": {
143 | "infile": "CHANGELOG.md",
144 | "preset": {
145 | "name": "angular",
146 | "types": [
147 | {
148 | "type": "feat",
149 | "section": "🚀 Features"
150 | },
151 | {
152 | "type": "fix",
153 | "section": "🔨 Bug Fixes"
154 | }
155 | ]
156 | }
157 | }
158 | }
159 | },
160 | "prettier": {
161 | "quoteProps": "consistent",
162 | "singleQuote": true,
163 | "tabWidth": 2,
164 | "trailingComma": "es5",
165 | "useTabs": false
166 | },
167 | "react-native-builder-bob": {
168 | "source": "src",
169 | "output": "lib",
170 | "targets": [
171 | [
172 | "commonjs",
173 | {
174 | "esm": true,
175 | "sourceMaps": false
176 | }
177 | ],
178 | [
179 | "module",
180 | {
181 | "esm": true
182 | }
183 | ],
184 | "typescript"
185 | ]
186 | },
187 | "codegenConfig": {
188 | "name": "RNOrientationDirectorSpec",
189 | "type": "modules",
190 | "jsSrcsDir": "src",
191 | "android": {
192 | "javaPackageName": "com.orientationdirector"
193 | }
194 | },
195 | "create-react-native-library": {
196 | "languages": "kotlin-objc",
197 | "type": "turbo-module",
198 | "version": "0.50.2"
199 | },
200 | "volta": {
201 | "node": "22.17.0",
202 | "yarn": "3.6.1"
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, caste, color, religion, or sexual
11 | identity and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the overall
27 | community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or advances of
32 | any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email address,
36 | without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official e-mail address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the community leaders responsible for enforcement at
64 | [INSERT CONTACT METHOD].
65 | All complaints will be reviewed and investigated promptly and fairly.
66 |
67 | All community leaders are obligated to respect the privacy and security of the
68 | reporter of any incident.
69 |
70 | ## Enforcement Guidelines
71 |
72 | Community leaders will follow these Community Impact Guidelines in determining
73 | the consequences for any action they deem in violation of this Code of Conduct:
74 |
75 | ### 1. Correction
76 |
77 | **Community Impact**: Use of inappropriate language or other behavior deemed
78 | unprofessional or unwelcome in the community.
79 |
80 | **Consequence**: A private, written warning from community leaders, providing
81 | clarity around the nature of the violation and an explanation of why the
82 | behavior was inappropriate. A public apology may be requested.
83 |
84 | ### 2. Warning
85 |
86 | **Community Impact**: A violation through a single incident or series of
87 | actions.
88 |
89 | **Consequence**: A warning with consequences for continued behavior. No
90 | interaction with the people involved, including unsolicited interaction with
91 | those enforcing the Code of Conduct, for a specified period of time. This
92 | includes avoiding interactions in community spaces as well as external channels
93 | like social media. Violating these terms may lead to a temporary or permanent
94 | ban.
95 |
96 | ### 3. Temporary Ban
97 |
98 | **Community Impact**: A serious violation of community standards, including
99 | sustained inappropriate behavior.
100 |
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 |
107 | ### 4. Permanent Ban
108 |
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 |
113 | **Consequence**: A permanent ban from any sort of public interaction within the
114 | community.
115 |
116 | ## Attribution
117 |
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.1, available at
120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
121 |
122 | Community Impact Guidelines were inspired by
123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
124 |
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
127 | [https://www.contributor-covenant.org/translations][translations].
128 |
129 | [homepage]: https://www.contributor-covenant.org
130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
131 | [Mozilla CoC]: https://github.com/mozilla/diversity
132 | [FAQ]: https://www.contributor-covenant.org/faq
133 | [translations]: https://www.contributor-covenant.org/translations
134 |
--------------------------------------------------------------------------------
/src/RNOrientationDirector.ts:
--------------------------------------------------------------------------------
1 | import { Platform } from 'react-native';
2 | import Module from './module';
3 | import type { HumanReadableOrientationsResource } from './types/HumanReadableOrientationsResource.type';
4 | import { Orientation } from './types/Orientation.enum';
5 | import { AutoRotation } from './types/AutoRotation.enum';
6 | import { OrientationType } from './types/OrientationType.enum';
7 | import type { OrientationEvent } from './types/OrientationEvent.interface';
8 | import type { LockableOrientation } from './types/LockableOrientation.type';
9 | import type { LockedEvent } from './types/LockedEvent.interface';
10 | import type { HumanReadableAutoRotationsResource } from './types/HumanReadableAutoRotationsResource.type';
11 | import EventEmitter from './EventEmitter';
12 |
13 | class RNOrientationDirector {
14 | private static _humanReadableOrientationsResource: HumanReadableOrientationsResource =
15 | {
16 | [Orientation.unknown]: 'Unknown',
17 | [Orientation.portrait]: 'Portrait',
18 | [Orientation.portraitUpsideDown]: 'Portrait Upside Down',
19 | [Orientation.landscapeLeft]: 'Landscape Left',
20 | [Orientation.landscapeRight]: 'Landscape Right',
21 | [Orientation.landscape]: 'Landscape',
22 | [Orientation.faceUp]: 'Face Up',
23 | [Orientation.faceDown]: 'Face Down',
24 | };
25 |
26 | private static _humanReadableAutoRotationsResource: HumanReadableAutoRotationsResource =
27 | {
28 | [AutoRotation.unknown]: 'Unknown',
29 | [AutoRotation.enabled]: 'Enabled',
30 | [AutoRotation.disabled]: 'Disabled',
31 | };
32 |
33 | setHumanReadableOrientations(resource: HumanReadableOrientationsResource) {
34 | RNOrientationDirector._humanReadableOrientationsResource = resource;
35 | }
36 |
37 | setHumanReadableAutoRotations(resource: HumanReadableAutoRotationsResource) {
38 | RNOrientationDirector._humanReadableAutoRotationsResource = resource;
39 | }
40 |
41 | static getInterfaceOrientation(): Promise {
42 | return Module.getInterfaceOrientation();
43 | }
44 |
45 | static getDeviceOrientation(): Promise {
46 | return Module.getDeviceOrientation();
47 | }
48 |
49 | /**
50 | * Please be aware that device orientation is not the
51 | * same as interface orientation.
52 | *
53 | * Specifically, landscape left and right are inverted:
54 | *
55 | * - landscapeLeft in device orientation is landscapeRight in interface orientation
56 | * - landscapeRight in device orientation is landscapeLeft in interface orientation
57 | *
58 | * This is a behavior of the native API.
59 | *
60 | * When you pass an orientation value, do provide orientationType
61 | * as well if the orientation value is not an interface orientation.
62 | * Example: when using listenForDeviceOrientationChanges.
63 | *
64 | * @param orientation any lockable orientation enum value
65 | * @param orientationType any orientation type enum value
66 | */
67 | static lockTo(
68 | orientation: LockableOrientation,
69 | orientationType: OrientationType = OrientationType.interface
70 | ) {
71 | if (orientationType === OrientationType.interface) {
72 | Module.lockTo(orientation);
73 | return;
74 | }
75 |
76 | if (orientation === Orientation.landscapeLeft) {
77 | Module.lockTo(Orientation.landscapeRight);
78 | return;
79 | }
80 |
81 | if (orientation === Orientation.landscapeRight) {
82 | Module.lockTo(Orientation.landscapeLeft);
83 | return;
84 | }
85 |
86 | Module.lockTo(orientation);
87 | }
88 |
89 | static unlock() {
90 | Module.unlock();
91 | }
92 |
93 | static isLocked() {
94 | return Module.isLocked();
95 | }
96 |
97 | static isAutoRotationEnabled() {
98 | if (Platform.OS !== 'android') {
99 | return AutoRotation.unknown;
100 | }
101 | return Module.isAutoRotationEnabled()
102 | ? AutoRotation.enabled
103 | : AutoRotation.disabled;
104 | }
105 |
106 | static resetSupportedInterfaceOrientations() {
107 | Module.resetSupportedInterfaceOrientations();
108 | }
109 |
110 | static listenForDeviceOrientationChanges(
111 | callback: (orientation: OrientationEvent) => void
112 | ) {
113 | return EventEmitter.addDeviceOrientationDidChangeListener(callback);
114 | }
115 |
116 | static listenForInterfaceOrientationChanges(
117 | callback: (orientation: OrientationEvent) => void
118 | ) {
119 | return EventEmitter.addInterfaceOrientationDidChangeListener(callback);
120 | }
121 |
122 | static listenForLockChanges(callback: (event: LockedEvent) => void) {
123 | return EventEmitter.addLockDidChangeListener(callback);
124 | }
125 |
126 | static convertOrientationToHumanReadableString(orientation: Orientation) {
127 | return RNOrientationDirector._humanReadableOrientationsResource[
128 | orientation
129 | ];
130 | }
131 |
132 | static convertAutoRotationToHumanReadableString(autoRotation: AutoRotation) {
133 | return RNOrientationDirector._humanReadableAutoRotationsResource[
134 | autoRotation
135 | ];
136 | }
137 |
138 | /**
139 | * This method checks if the given orientation is lockable
140 | * by interface perspective.
141 | *
142 | * All orientations are lockable except for unknown, faceUp
143 | * and faceDown.
144 | *
145 | * This method is useful when you want to lock the interface
146 | * orientation from a given device orientation.
147 | *
148 | * Example: with listenForDeviceOrientationChanges
149 | *
150 | * @param orientation any orientation enum value
151 | * @returns true if the orientation is lockable
152 | */
153 | static isLockableOrientation(
154 | orientation: Orientation
155 | ): orientation is LockableOrientation {
156 | return !(
157 | orientation === Orientation.unknown ||
158 | orientation === Orientation.faceUp ||
159 | orientation === Orientation.faceDown
160 | );
161 | }
162 | }
163 |
164 | export default RNOrientationDirector;
165 |
--------------------------------------------------------------------------------
/android/src/test/java/com/orientationdirector/implementation/OrientationDirectorModuleImplTest.kt:
--------------------------------------------------------------------------------
1 | package com.orientationdirector.implementation
2 |
3 | import android.os.Looper
4 | import androidx.test.core.app.ApplicationProvider
5 | import com.facebook.react.bridge.BridgeReactContext
6 | import org.junit.Assert.*
7 | import org.junit.Before
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 | import org.mockito.InjectMocks
11 | import org.mockito.Mock
12 | import org.mockito.Mockito.`when`
13 | import org.mockito.MockitoAnnotations
14 | import org.robolectric.RobolectricTestRunner
15 | import org.robolectric.annotation.Config
16 |
17 | @RunWith(RobolectricTestRunner::class)
18 | class OrientationDirectorModuleImplTest {
19 | private var context = BridgeReactContext(ApplicationProvider.getApplicationContext())
20 |
21 | @Mock
22 | private val mockEventManager = EventManager(context)
23 |
24 | @Mock
25 | private val mockAutoRotationObserver = AutoRotationObserver(
26 | context,
27 | android.os.Handler(Looper.getMainLooper())
28 | )
29 |
30 | @InjectMocks
31 | private val mModule = OrientationDirectorModuleImpl(context)
32 |
33 | @Before
34 | fun setup() {
35 | MockitoAnnotations.openMocks(this)
36 |
37 | // We stub this method because adaptInterfaceTo checks it
38 | `when`(mockAutoRotationObserver.getLastAutoRotationStatus()).thenReturn(true)
39 | }
40 |
41 | @Test
42 | @Config(
43 | qualifiers = "port"
44 | )
45 | fun assert_initial_orientation_matches_portrait() {
46 | val orientation = mModule.getInterfaceOrientation()
47 |
48 | assertEquals(
49 | "When user starts the app with the device in portrait, the initial interface should be portrait",
50 | Orientation.PORTRAIT,
51 | orientation
52 | )
53 | }
54 |
55 | @Test
56 | @Config(
57 | qualifiers = "land"
58 | )
59 | fun assert_initial_orientation_matches_landscape_left() {
60 | val orientation = mModule.getInterfaceOrientation()
61 |
62 | assertEquals(
63 | "When user starts the app with the device in landscape, the initial interface should be landscape left",
64 | Orientation.LANDSCAPE_LEFT,
65 | orientation
66 | )
67 | }
68 |
69 | @Test
70 | fun assert_initial_device_orientation_matches_unknown_at_startup() {
71 | val orientation = mModule.getDeviceOrientation()
72 |
73 | assertEquals(
74 | "When user starts the app, the initial device orientation should be unknown",
75 | Orientation.UNKNOWN,
76 | orientation
77 | )
78 | }
79 |
80 | @Test
81 | fun assert_initial_is_locked_matches_false_at_startup() {
82 | val isLocked = mModule.getIsLocked()
83 |
84 | assertEquals(
85 | "When user starts the app, interface orientation shouldn't be locked",
86 | false,
87 | isLocked
88 | )
89 | }
90 |
91 | @Test
92 | fun assert_is_locked_matches_true_after_lock_to_gets_executed() {
93 | mModule.lockTo(1)
94 | val isLocked = mModule.getIsLocked()
95 |
96 | assertEquals(
97 | "When lockTo is executed, getIsLocked should match true",
98 | true,
99 | isLocked
100 | )
101 | }
102 |
103 | @Test
104 | fun assert_interface_orientation_matches_locked_to_portrait() {
105 | mModule.lockTo(1)
106 |
107 | assertEquals(
108 | "When the interface is locked to portrait, getInterfaceOrientation should return portrait",
109 | Orientation.PORTRAIT,
110 | mModule.getInterfaceOrientation()
111 | )
112 | }
113 |
114 | @Test
115 | fun assert_interface_orientation_matches_locked_to_landscape_right() {
116 | mModule.lockTo(2)
117 |
118 | assertEquals(
119 | "When the interface is locked to landscape right, getInterfaceOrientation should return landscape right",
120 | Orientation.LANDSCAPE_RIGHT,
121 | mModule.getInterfaceOrientation()
122 | )
123 | }
124 |
125 | @Test
126 | fun assert_interface_orientation_matches_locked_to_portrait_upside_down() {
127 | mModule.lockTo(3)
128 |
129 | assertEquals(
130 | "When the interface is locked to portrait upside down, getInterfaceOrientation should return portrait upside down",
131 | Orientation.PORTRAIT_UPSIDE_DOWN,
132 | mModule.getInterfaceOrientation()
133 | )
134 | }
135 |
136 | @Test
137 | fun assert_interface_orientation_matches_locked_to_landscape_left() {
138 | mModule.lockTo(4)
139 |
140 | assertEquals(
141 | "When the interface is locked to landscape left, getInterfaceOrientation should return landscape left",
142 | Orientation.LANDSCAPE_LEFT,
143 | mModule.getInterfaceOrientation()
144 | )
145 | }
146 |
147 | @Test
148 | fun assert_interface_orientation_matches_locked_to_landscape() {
149 | mModule.lockTo(5)
150 |
151 | assertEquals(
152 | "When the interface is locked to landscape, getInterfaceOrientation should return landscape right",
153 | Orientation.LANDSCAPE_RIGHT,
154 | mModule.getInterfaceOrientation()
155 | )
156 | }
157 |
158 | @Test
159 | fun assert_is_locked_reset_when_unlock_is_executed() {
160 | mModule.lockTo(1)
161 | mModule.unlock()
162 |
163 | assertEquals(
164 | "When unlock is executed, getIsLocked should return false",
165 | false,
166 | mModule.getIsLocked()
167 | )
168 | }
169 |
170 | @Test
171 | @Config(
172 | qualifiers = "port"
173 | )
174 | fun assert_interface_reset_to_portrait_on_unlock() {
175 | mModule.lockTo(2)
176 | mModule.unlock()
177 |
178 | assertEquals(
179 | "When unlock is executed, getInterfaceOrientation should match portrait when device is in portrait",
180 | Orientation.PORTRAIT,
181 | mModule.getInterfaceOrientation()
182 | )
183 | }
184 |
185 | @Test
186 | @Config(
187 | qualifiers = "land"
188 | )
189 | fun assert_interface_reset_to_landscape_left_on_unlock() {
190 | mModule.lockTo(2)
191 | mModule.unlock()
192 |
193 | assertEquals(
194 | "When unlock is executed, getInterfaceOrientation should match landscape left when device is in landscape right",
195 | Orientation.LANDSCAPE_LEFT,
196 | mModule.getInterfaceOrientation()
197 | )
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This workflow is based on the https://github.com/gladiuscode/github-actions-playground repository.
2 |
3 | name: Release 🚀
4 |
5 | on:
6 | workflow_dispatch:
7 | inputs:
8 | dry-run:
9 | description: 'Run release-it in dry-run mode'
10 | required: false
11 | default: false
12 | type: boolean
13 |
14 | jobs:
15 | lint:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v4
20 |
21 | - name: Setup
22 | uses: ./.github/actions/setup
23 |
24 | - name: Lint library & example files
25 | run: yarn lint
26 |
27 | - name: Lint plugin files
28 | run: yarn expo-plugin lint
29 |
30 | - name: Typecheck library & example files
31 | run: yarn typecheck
32 |
33 | - name: Typecheck plugin files
34 | run: yarn expo-plugin typecheck
35 |
36 | test:
37 | runs-on: ubuntu-latest
38 | steps:
39 | - name: Checkout
40 | uses: actions/checkout@v4
41 |
42 | - name: Setup
43 | uses: ./.github/actions/setup
44 |
45 | - name: Run unit tests
46 | run: yarn test --maxWorkers=2 --coverage
47 |
48 |
49 | build-library:
50 | runs-on: ubuntu-latest
51 | steps:
52 | - name: Checkout
53 | uses: actions/checkout@v4
54 |
55 | - name: Setup
56 | uses: ./.github/actions/setup
57 |
58 | - name: Build package
59 | run: yarn prepare
60 |
61 | - name: Upload Build Artifact
62 | uses: actions/upload-artifact@v4
63 | with:
64 | name: build-library-artifact
65 | path: lib
66 |
67 | build-plugin:
68 | runs-on: ubuntu-latest
69 | steps:
70 | - name: Checkout
71 | uses: actions/checkout@v4
72 |
73 | - name: Setup
74 | uses: ./.github/actions/setup
75 |
76 | - name: Build package
77 | run: yarn expo-plugin prepare
78 |
79 | - name: Upload Build Artifact
80 | uses: actions/upload-artifact@v4
81 | with:
82 | name: build-plugin-artifact
83 | path: plugin
84 |
85 | build-android:
86 | runs-on: ubuntu-latest
87 | env:
88 | TURBO_CACHE_DIR: .turbo/android
89 | steps:
90 | - name: Checkout
91 | uses: actions/checkout@v4
92 |
93 | - name: Setup
94 | uses: ./.github/actions/setup
95 |
96 | - name: Cache turborepo for Android
97 | uses: actions/cache@v3
98 | with:
99 | path: ${{ env.TURBO_CACHE_DIR }}
100 | key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }}
101 | restore-keys: |
102 | ${{ runner.os }}-turborepo-android-
103 |
104 | - name: Check turborepo cache for Android
105 | run: |
106 | TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status")
107 |
108 | if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
109 | echo "turbo_cache_hit=1" >> $GITHUB_ENV
110 | fi
111 |
112 | - name: Install JDK
113 | if: env.turbo_cache_hit != 1
114 | uses: actions/setup-java@v3
115 | with:
116 | distribution: 'zulu'
117 | java-version: '17'
118 |
119 | - name: Finalize Android SDK
120 | if: env.turbo_cache_hit != 1
121 | run: |
122 | /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null"
123 |
124 | - name: Cache Gradle
125 | if: env.turbo_cache_hit != 1
126 | uses: actions/cache@v3
127 | with:
128 | path: |
129 | ~/.gradle/wrapper
130 | ~/.gradle/caches
131 | key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }}
132 | restore-keys: |
133 | ${{ runner.os }}-gradle-
134 |
135 | - name: Run test
136 | if: env.turbo_cache_hit != 1
137 | run: |
138 | cd example/android && ./gradlew react-native-orientation-director:testDebugUnitTest
139 |
140 | - name: Build example for Android
141 | env:
142 | JAVA_OPTS: '-XX:MaxHeapSize=6g'
143 | run: |
144 | yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"
145 |
146 |
147 |
148 | build-ios:
149 | runs-on: macos-14
150 | env:
151 | TURBO_CACHE_DIR: .turbo/ios
152 | steps:
153 | - name: Checkout
154 | uses: actions/checkout@v4
155 |
156 | - name: Setup
157 | uses: ./.github/actions/setup
158 |
159 | - name: Cache turborepo for iOS
160 | uses: actions/cache@v3
161 | with:
162 | path: ${{ env.TURBO_CACHE_DIR }}
163 | key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('yarn.lock') }}
164 | restore-keys: |
165 | ${{ runner.os }}-turborepo-ios-
166 |
167 | - name: Check turborepo cache for iOS
168 | run: |
169 | TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status")
170 |
171 | if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
172 | echo "turbo_cache_hit=1" >> $GITHUB_ENV
173 | fi
174 |
175 | - name: Cache cocoapods
176 | if: env.turbo_cache_hit != 1
177 | id: cocoapods-cache
178 | uses: actions/cache@v3
179 | with:
180 | path: |
181 | **/ios/Pods
182 | key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }}
183 | restore-keys: |
184 | ${{ runner.os }}-cocoapods-
185 |
186 | - name: Install cocoapods
187 | if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'
188 | run: |
189 | cd example/ios
190 | pod install
191 | env:
192 | NO_FLIPPER: 1
193 | RCT_NEW_ARCH_ENABLED: 1
194 |
195 | - name: Build example for iOS
196 | run: |
197 | yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"
198 |
199 |
200 | release:
201 | name: Release
202 | runs-on: ubuntu-latest
203 | needs: [lint, test, build-plugin, build-library, build-android, build-ios]
204 |
205 | steps:
206 | # (1) Create a GitHub App token
207 | # Note: the Github App must be installed on the repository and included in the bypass list of the ruleset.
208 | - uses: actions/create-github-app-token@v1
209 | id: app-token
210 | with:
211 | app-id: ${{ vars.APP_ID }}
212 | private-key: ${{ secrets.PRIVATE_KEY }}
213 |
214 | - name: Checkout
215 | uses: actions/checkout@v4
216 | with:
217 | # (2) Use the GitHub App token to init the repository
218 | token: ${{ steps.app-token.outputs.token }}
219 | # (3) Fetch all history so that release-it can determine the version
220 | fetch-depth: 0
221 |
222 | - name: Setup
223 | uses: ./.github/actions/setup
224 |
225 | # (4) Configure Git user
226 | - name: Configure Git User
227 | run: |
228 | git config --global user.name "${GITHUB_ACTOR}"
229 | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com"
230 |
231 | - name: Download Build Library Artifact
232 | uses: actions/download-artifact@v4
233 | with:
234 | name: build-library-artifact
235 |
236 | - name: Download Build Plugin Artifact
237 | uses: actions/download-artifact@v4
238 | with:
239 | name: build-plugin-artifact
240 |
241 | - name: Display current working directory
242 | run: ls
243 |
244 | - name: Release
245 | run: |
246 | if [ ${{ inputs.dry-run }} = true ]; then
247 | yarn release-it --dry-run
248 | else
249 | yarn release-it
250 | fi
251 | env:
252 | # (5) Make GITHUB_TOKEN available to release-it but use the GitHub App token
253 | GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
254 | # (6) Make NPM_ACCESS_TOKEN available to release-it and npm publish command
255 | NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
256 |
257 |
--------------------------------------------------------------------------------