├── .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 |