diff --git a/.nvmrc b/.nvmrc index e6db45a..3c5535c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.14.0 +18.19.1 diff --git a/README.md b/README.md index 0f1a592..8ea201d 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,63 @@ Add the following to your project's AppDelegate.mm: @end ``` + +### Android + +Implement onConfigurationChanged method (in MainActivity.kt) + +```kotlin + +import android.content.Intent +import android.content.res.Configuration + +// ... + + +class MainActivity : ReactActivity() { +//... + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + val intent = Intent("onConfigurationChanged") + intent.putExtra("newConfig", newConfig) + this.sendBroadcast(intent) + } +} +``` + +Add following to MainApplication.kt + +```kotlin + +import com.neoorientation.NeoOrientationActivityLifecycle +import com.neoorientation.NeoOrientationPackage +//... + +class MainApplication : Application(), ReactApplication { + + override val reactNativeHost: ReactNativeHost = + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + add(NeoOrientationPackage()) + } + //... + } + + override fun onCreate() { + //... + registerActivityLifecycleCallbacks(NeoOrientationActivityLifecycle.instance) + } +} +``` ## Usage ```typescript import { Button, StyleSheet, Text, View } from 'react-native'; import React from 'react'; -import NeoOrientation from 'react-native-neo-orientation'; +import NeoOrientation, { useDeviceOrientationChange, useOrientationChange } from 'react-native-neo-orientation'; const Home = () => { const handleLockTolandscape = () => { @@ -63,6 +114,15 @@ const Home = () => { const handleUnlockAllOrientations = () => { NeoOrientation.unlockAllOrientations(); }; + + useDeviceOrientationChange((o) => { + // Handle device orientation change + }); + + useOrientationChange((o) => { + // Handle orientation change + }); + return ( Home @@ -105,6 +165,16 @@ const styles = StyleSheet.create({ }); ``` +## Events + + - `addOrientationListener(function(orientation))` + - `removeOrientationListener(function(orientation))` + - `addDeviceOrientationListener(function(deviceOrientation))` + - `removeDeviceOrientationListener(function(deviceOrientation))` + - `addLockListener(function(orientation))` + - `removeLockListener(function(orientation))` + - `removeAllListeners()` + ## Functions - `lockToLandscape` @@ -114,6 +184,9 @@ const styles = StyleSheet.create({ - `lockToLandscapeLeft` - `lockToAllOrientationButUpsideDown` - `unlockAllOrientations` + - `getOrientation` + - `getDeviceOrientation` + - `isLocked` ## Contributing diff --git a/android/src/main/java/com/neoorientation/NeoOrientationModule.kt b/android/src/main/java/com/neoorientation/NeoOrientationModule.kt index afef248..04b0b81 100644 --- a/android/src/main/java/com/neoorientation/NeoOrientationModule.kt +++ b/android/src/main/java/com/neoorientation/NeoOrientationModule.kt @@ -1,12 +1,12 @@ package com.neoorientation +import android.annotation.SuppressLint import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.ActivityInfo import android.hardware.SensorManager -import android.os.Build import android.provider.Settings import android.view.OrientationEventListener import android.view.Surface @@ -20,9 +20,8 @@ import com.facebook.react.bridge.ReactMethod import com.facebook.react.common.ReactConstants import com.facebook.react.modules.core.DeviceEventManagerModule -class NeoOrientationModule(reactContext: ReactApplicationContext) : +class NeoOrientationModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), NeoOrientationListeners { - private val reactContext: ReactApplicationContext private val mReceiver: BroadcastReceiver private val mOrientationListener: OrientationEventListener private var isLocking = false @@ -31,7 +30,6 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : private var lastDeviceOrientation = "" init { - this.reactContext = reactContext mOrientationListener = object : OrientationEventListener(reactContext, SensorManager.SENSOR_DELAY_UI) { override fun onOrientationChanged(p0: Int) { @@ -54,8 +52,7 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : if (reactContext.hasActiveReactInstance()) { reactContext.getJSModule( DeviceEventManagerModule.RCTDeviceEventEmitter::class.java - ) - .emit("deviceOrientationDidChange", params) + ).emit("deviceOrientationDidChange", params) } } val orientation: String = currentOrientation @@ -64,14 +61,11 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : val params = Arguments.createMap() params.putString("orientation", orientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule( - DeviceEventManagerModule.RCTDeviceEventEmitter::class.java - ) - .emit("orientationDidChange", params) + reactContext.getJSModule( + DeviceEventManagerModule.RCTDeviceEventEmitter::class.java + ).emit("orientationDidChange", params) } } - return } } if (mOrientationListener.canDetectOrientation()) { @@ -86,11 +80,9 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : val params = Arguments.createMap() params.putString("orientation", orientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule( - DeviceEventManagerModule.RCTDeviceEventEmitter::class.java - ) - .emit("orientationDidChange", params) + reactContext.getJSModule( + DeviceEventManagerModule.RCTDeviceEventEmitter::class.java + ).emit("orientationDidChange", params) } } } @@ -99,15 +91,14 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : private val currentOrientation: String get() { - val display = - (reactApplicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay - when (display.rotation) { - Surface.ROTATION_0 -> return "portrait" - Surface.ROTATION_90 -> return "landscapeLeft" - Surface.ROTATION_180 -> return "portraitUpsideDown" - Surface.ROTATION_270 -> return "landscapeRight" + val display = (reactApplicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay + return when (display.rotation) { + Surface.ROTATION_0 -> "portrait" + Surface.ROTATION_90 -> "landscapeLeft" + Surface.ROTATION_180 -> "portraitUpsideDown" + Surface.ROTATION_270 -> "landscapeRight" + else -> "unknown" } - return "unknown" } override fun getName(): String { @@ -141,8 +132,7 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : val lockParams = Arguments.createMap() lockParams.putString("orientation", lastOrientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("lockDidChange", lockParams) } } @@ -157,16 +147,14 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : val params = Arguments.createMap() params.putString("orientation", lastOrientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("orientationDidChange", params) } val lockParams = Arguments.createMap() lockParams.putString("orientation", lastOrientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("lockDidChange", lockParams) } } @@ -182,8 +170,7 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : val params = Arguments.createMap() params.putString("orientation", lastOrientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("orientationDidChange", params) } @@ -191,8 +178,7 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : val lockParams = Arguments.createMap() lockParams.putString("orientation", lastOrientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("lockDidChange", lockParams) } } @@ -208,16 +194,14 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : val params = Arguments.createMap() params.putString("orientation", lastOrientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("orientationDidChange", params) } val lockParams = Arguments.createMap() lockParams.putString("orientation", lastOrientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("lockDidChange", lockParams) } } @@ -232,16 +216,14 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : val params = Arguments.createMap() params.putString("orientation", lastOrientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("orientationDidChange", params) } val lockParams = Arguments.createMap() lockParams.putString("orientation", lastOrientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("lockDidChange", lockParams) } } @@ -256,16 +238,14 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : val params = Arguments.createMap() params.putString("orientation", lastOrientation) if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("orientationDidChange", params) } val lockParams = Arguments.createMap() lockParams.putString("orientation", "unknown") if (reactContext.hasActiveReactInstance()) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("lockDidChange", lockParams) } } @@ -289,7 +269,11 @@ class NeoOrientationModule(reactContext: ReactApplicationContext) : override fun start() { mOrientationListener.enable() - reactContext.registerReceiver(mReceiver, IntentFilter("onConfigurationChanged")) + val intentFilter = IntentFilter("onConfigurationChanged").apply { +// addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) + } + + reactContext.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED) isConfigurationChangeReceiverRegistered = true } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 30e8ec9..c02dd99 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -374,7 +374,7 @@ PODS: - React-jsinspector (0.72.1) - React-logger (0.72.1): - glog - - react-native-neo-orientation (1.1.0): + - react-native-neo-orientation (2.1.0): - RCT-Folly (= 2021.07.22.00) - React-Core - React-NativeModulesApple (0.72.1): @@ -692,7 +692,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 184eae1ecdedc7a083194bd9ff809c93f08fd34c React-jsinspector: d0b5bfd1085599265f4212034321e829bdf83cc0 React-logger: b8103c9b04e707b50cdd2b1aeb382483900cbb37 - react-native-neo-orientation: 670ff290ae71ff09ee110d7fa557e5ed8da27908 + react-native-neo-orientation: 5a340ffc64aac25e628cd57c2493abeb312d42b4 React-NativeModulesApple: 4f31a812364443cee6ef768d256c594ad3b20f53 React-perflogger: 3d501f34c8d4b10cb75f348e43591765788525ad React-RCTActionSheet: f5335572c979198c0c3daff67b07bd1ad8370c1d @@ -716,4 +716,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: e9e8830d1e35a8818e2f4789d9844d2ad7037dbf -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..436a576 --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,3 @@ +export * from './useOrientationChange'; +export * from './useDeviceOrientationChange'; +export * from './useLockListener'; diff --git a/src/hooks/useDeviceOrientationChange.ts b/src/hooks/useDeviceOrientationChange.ts new file mode 100644 index 0000000..c05c352 --- /dev/null +++ b/src/hooks/useDeviceOrientationChange.ts @@ -0,0 +1,28 @@ +import { useRef, useEffect, type MutableRefObject } from 'react'; +import Orientation, { type OrientationCallback } from '../orientation'; + +export const useDeviceOrientationChange = ( + callback: OrientationCallback +): void => { + const savedCallback: MutableRefObject = + useRef(); + + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + useEffect(() => { + const listener = (ori: string): void => { + if (savedCallback.current) { + savedCallback.current(ori); + } + }; + const initial = Orientation.getInitialOrientation(); + listener(initial); + Orientation.addDeviceOrientationListener(listener); + + return () => { + Orientation.removeDeviceOrientationListener(listener); + }; + }, []); +}; diff --git a/src/hooks/useLockListener.ts b/src/hooks/useLockListener.ts new file mode 100644 index 0000000..0b33078 --- /dev/null +++ b/src/hooks/useLockListener.ts @@ -0,0 +1,24 @@ +import { useRef, useEffect, type MutableRefObject } from 'react'; +import Orientation, { type OrientationCallback } from '../orientation'; + +export const useLockListener = (callback: OrientationCallback): void => { + const savedCallback: MutableRefObject = + useRef(); + + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + useEffect(() => { + const listener = (ori: string): void => { + if (savedCallback.current) { + savedCallback.current(ori); + } + }; + Orientation.addLockListener(listener); + + return () => { + Orientation.removeLockListener(listener); + }; + }, []); +}; diff --git a/src/hooks/useOrientationChange.ts b/src/hooks/useOrientationChange.ts new file mode 100644 index 0000000..9076e62 --- /dev/null +++ b/src/hooks/useOrientationChange.ts @@ -0,0 +1,26 @@ +import { useRef, useEffect, type MutableRefObject } from 'react'; +import Orientation, { type OrientationCallback } from '../orientation'; + +export const useOrientationChange = (callback: OrientationCallback): void => { + const savedCallback: MutableRefObject = + useRef(); + + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + useEffect(() => { + const listener = (ori: string): void => { + if (savedCallback.current) { + savedCallback.current(ori); + } + }; + const initial = Orientation.getInitialOrientation(); + listener(initial); + Orientation.addDeviceOrientationListener(listener); + + return () => { + Orientation.removeDeviceOrientationListener(listener); + }; + }, []); +}; diff --git a/src/index.tsx b/src/index.tsx index faf4907..5cf7a03 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,30 +1,4 @@ -import { NativeModules, Platform } from 'react-native'; +import Orientation from './orientation'; +export * from './hooks'; -const LINKING_ERROR = - `The package 'react-native-neo-orientation' doesn't seem to be linked. Make sure: \n\n` + - Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + - '- You rebuilt the app after installing the package\n' + - '- You are not using Expo Go\n'; - -const NeoOrientation = NativeModules.NeoOrientation - ? NativeModules.NeoOrientation - : new Proxy( - {}, - { - get() { - throw new Error(LINKING_ERROR); - }, - } - ); - -interface NeoOrientationInterface { - lockToLandscape: () => void; - lockToPortrait: () => void; - lockToPortraitUpsideDown: () => void; - lockToLandscapeRight: () => void; - lockToLandscapeLeft: () => void; - lockToAllOrientationButUpsideDown: () => void; - unlockAllOrientations: () => void; -} - -export default NeoOrientation as NeoOrientationInterface; +export default Orientation; diff --git a/src/orientation.ts b/src/orientation.ts new file mode 100644 index 0000000..e191ff2 --- /dev/null +++ b/src/orientation.ts @@ -0,0 +1,196 @@ +import { NativeModules, Platform, NativeEventEmitter } from 'react-native'; + +const LINKING_ERROR = + `The package 'react-native-neo-orientation' doesn't seem to be linked. Make sure: \n\n` + + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + + '- You rebuilt the app after installing the package\n' + + '- You are not using Expo Go\n'; + +const NeoOrientation = NativeModules.NeoOrientation + ? NativeModules.NeoOrientation + : new Proxy( + {}, + { + get() { + throw new Error(LINKING_ERROR); + }, + } + ); + +const IS_ANDROID = Platform.OS === 'android'; + +let LocalEventEmitter: NativeEventEmitter | null = IS_ANDROID + ? null + : new NativeEventEmitter(NeoOrientation); + +export type OrientationCallback = (orientation: string) => void; +export type AutoRotateCallback = (state: boolean) => void; + +export interface Listeners { + [key: string]: any; +} + +let listeners: Listeners = {}; +let id = 0; +const META = '__listener_id'; +let locked = false; + +function getKey(listener: any): string { + if (!listener.hasOwnProperty(META)) { + if (!Object.isExtensible(listener)) { + return 'F'; + } + Object.defineProperty(listener, META, { + value: 'L' + ++id, + }); + } + return listener[META]; +} + +export default class Orientation { + static configure = (options: any): void => { + if (IS_ANDROID) { + return; + } + NeoOrientation.configure(options); + }; + + static getOrientation = (cb: OrientationCallback): void => { + NeoOrientation.getOrientation((orientation: string) => { + cb(orientation); + }); + }; + + static getDeviceOrientation = (cb: OrientationCallback): void => { + NeoOrientation.getDeviceOrientation((orientation: string) => { + cb(orientation); + }); + }; + + static isLocked = (): boolean => { + return locked; + }; + + static lockToPortrait = (): void => { + locked = true; + NeoOrientation.lockToPortrait(); + }; + + static lockToPortraitUpsideDown = (): void => { + locked = true; + NeoOrientation.lockToPortraitUpsideDown(); + }; + + static lockToLandscape = (): void => { + locked = true; + NeoOrientation.lockToLandscape(); + }; + + static lockToLandscapeRight = (): void => { + locked = true; + NeoOrientation.lockToLandscapeRight(); + }; + + static lockToLandscapeLeft = (): void => { + locked = true; + NeoOrientation.lockToLandscapeLeft(); + }; + + static unlockAllOrientations = (): void => { + locked = false; + NeoOrientation.unlockAllOrientations(); + }; + + static addOrientationListener = (cb: OrientationCallback): void => { + const key = getKey(cb); + if (IS_ANDROID) { + LocalEventEmitter = + LocalEventEmitter ?? new NativeEventEmitter(NeoOrientation); + } + listeners[key] = LocalEventEmitter?.addListener( + 'orientationDidChange', + (body: { orientation: string }) => { + cb(body.orientation); + } + ); + }; + + static removeOrientationListener = (cb: OrientationCallback): void => { + const key = getKey(cb); + if (!listeners[key]) { + return; + } + listeners[key].remove(); + listeners[key] = null; + }; + + static addDeviceOrientationListener = (cb: OrientationCallback): void => { + const key = getKey(cb); + if (IS_ANDROID) { + LocalEventEmitter = + LocalEventEmitter ?? new NativeEventEmitter(NeoOrientation); + } + listeners[key] = LocalEventEmitter?.addListener( + 'deviceOrientationDidChange', + (body: { deviceOrientation: string }) => { + cb(body.deviceOrientation); + } + ); + }; + + static removeDeviceOrientationListener = (cb: OrientationCallback): void => { + const key = getKey(cb); + if (!listeners[key]) { + return; + } + listeners[key].remove(); + listeners[key] = null; + }; + + static addLockListener = (cb: OrientationCallback): void => { + const key = getKey(cb); + if (IS_ANDROID) { + LocalEventEmitter = + LocalEventEmitter ?? new NativeEventEmitter(NeoOrientation); + } + listeners[key] = LocalEventEmitter?.addListener( + 'lockDidChange', + (body: { orientation: string }) => { + cb(body.orientation); + } + ); + }; + + static removeLockListener = (cb: OrientationCallback): void => { + const key = getKey(cb); + if (!listeners[key]) { + return; + } + listeners[key].remove(); + listeners[key] = null; + }; + + static removeAllListeners = (): void => { + for (const key in listeners) { + if (!listeners[key]) { + continue; + } + listeners[key].remove(); + listeners[key] = null; + } + }; + + static getInitialOrientation = (): string => { + return NeoOrientation.initialOrientation; + }; + + static getAutoRotateState = (cb: AutoRotateCallback): void => { + if (!IS_ANDROID) { + cb(true); + return; + } + NeoOrientation.getAutoRotateState((state: boolean) => { + cb(state); + }); + }; +}