├── .gitattributes ├── .gitignore ├── Example.js ├── LICENSE ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── rnimmersivemode │ ├── BarMode.java │ ├── BarStyle.java │ ├── ImmersiveEvent.java │ ├── ImmersiveMode.java │ ├── RNImmersiveModeModule.java │ └── RNImmersiveModePackage.java ├── index.d.ts ├── index.js └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OSX 3 | # 4 | .DS_Store 5 | 6 | # node.js 7 | # 8 | node_modules/ 9 | npm-debug.log 10 | yarn-error.log 11 | 12 | 13 | # Xcode 14 | # 15 | build/ 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata 25 | *.xccheckout 26 | *.moved-aside 27 | DerivedData 28 | *.hmap 29 | *.ipa 30 | *.xcuserstate 31 | project.xcworkspace 32 | 33 | 34 | # Android/IntelliJ 35 | # 36 | build/ 37 | .idea 38 | .gradle 39 | local.properties 40 | *.iml 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | -------------------------------------------------------------------------------- /Example.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | SafeAreaView, 4 | StyleSheet, 5 | Text, 6 | TouchableOpacity 7 | } from 'react-native'; 8 | import ImmersiveMode from 'react-native-immersive-mode'; 9 | 10 | export default class App extends Component { 11 | 12 | state = { 13 | color: '#ff0000', 14 | } 15 | 16 | buttonImmersiveMode = [ 17 | 'Normal', 18 | 'Full', 19 | 'FullSticky', 20 | 'Bottom', 21 | 'BottomSticky', 22 | ] 23 | 24 | componentDidMount() { 25 | ImmersiveMode.fullLayout(true); 26 | this.listen = ImmersiveMode.addEventListener((e) => { 27 | /** 28 | * e = { 29 | * statusBar: boolean, 30 | * navigationBottomBar: boolean, 31 | * } 32 | */ 33 | console.log(e) 34 | }) 35 | } 36 | 37 | componentWillUnmount() { 38 | this.listen.remove(); 39 | ImmersiveMode.fullLayout(false); 40 | } 41 | 42 | render() { 43 | return ( 44 | 45 | 46 | Main 47 | 48 | { 49 | this.buttonImmersiveMode.map(v => 50 | { 51 | ImmersiveMode.setBarMode(v); 52 | }}> 53 | {v} 54 | 55 | ) 56 | } 57 | 58 | this.setState({ color: t })} 70 | maxLength={9} /> 71 | { 72 | ImmersiveMode.setBarColor(this.state.color); 73 | }}> 74 | Bar color {this.state.color} 75 | 76 | { 77 | ImmersiveMode.setBarColor(null); 78 | }}> 79 | Bar color default 80 | 81 | 82 | 85 | { 86 | ImmersiveMode.setBarStyle('Dark'); 87 | }}> 88 | Bar Style Dark 89 | 90 | { 91 | ImmersiveMode.setBarStyle('Light'); 92 | }}> 93 | Bar Style Light 94 | 95 | 96 | 97 | 100 | { 101 | ImmersiveMode.setBarTranslucent(true); 102 | }}> 103 | Translucent On 104 | 105 | { 106 | ImmersiveMode.setBarTranslucent(false); 107 | }}> 108 | Translucent Off 109 | 110 | 111 | 112 | 113 | 114 | ); 115 | } 116 | }; 117 | 118 | const styles = StyleSheet.create({ 119 | container: { 120 | flex: 1, 121 | justifyContent: 'center', 122 | alignItems: 'center', 123 | backgroundColor: '#ddd', 124 | }, 125 | button: { 126 | margin: 8, 127 | paddingVertical: 8, 128 | paddingHorizontal: 16, 129 | borderRadius: 4, 130 | backgroundColor: '#5fba7d', 131 | } 132 | }); 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 wrathyz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # react-native-immersive-mode 3 | [![npm version](https://badge.fury.io/js/react-native-immersive-mode.svg)](https://badge.fury.io/js/react-native-immersive-mode) 4 | 5 | ## Installation 6 | 7 | ### Mostly automatic installation 8 | 9 | ```python 10 | npm install react-native-immersive-mode --save 11 | ``` 12 | 13 | ### Auto linking library (react-native < 0.60) 14 | 15 | ```python 16 | react-native link react-native-immersive-mode 17 | ``` 18 | 19 | ### Manual linking library 20 | 21 | #### Android 22 | 23 | 1. Append the following lines to `android/settings.gradle` 24 | ``` 25 | include ':react-native-immersive-mode' 26 | project(':react-native-immersive-mode').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive-mode/android') 27 | ``` 28 | 2. Insert the following lines inside the dependencies block in `android/app/build.gradle` 29 | ``` 30 | implementation project(':react-native-immersive-mode') 31 | ``` 32 | 3. Add it to your `MainApplication.java` 33 | ``` 34 | import com.rnimmersivemode.RNImmersiveModePackage; // add this 35 | 36 | public class MainActivity extends ReactActivity { 37 | @Override 38 | protected List getPackages() { 39 | return Arrays.asList( 40 | new MainReactPackage(), 41 | new RNImmersiveModePackage() // add this 42 | ); 43 | } 44 | } 45 | ``` 46 | ## Usage 47 | 48 | ### Bar Mode 49 | - **Normal** - show status and navigation 50 | - **Full** - hide status and navigation 51 | - **FullSticky** - hide status and navigation with sticky 52 | - **Bottom** - hide navigation 53 | - **BottomSticky** - hide navigation with sticky 54 | 55 | ### Bar Style 56 | - **Dark** 57 | - **Light** 58 | 59 | ### Methods 60 | 61 | - [fullLayout](#fulllayout) 62 | - [setBarMode](#setbarmode) 63 | - [setBarStyle](#setbarstyle) 64 | - [setBarTranslucent](#setbartranslucent) 65 | - [setBarColor](#setbarcolor) 66 | - [setBarDefaultColor](#setBarDefaultColor) 67 | - [addEventListener](#addeventlistener) 68 | 69 | ### fullLayout 70 | `fullLayout(full: boolean): void` 71 | use all area of screen 72 | 73 | | name | type | description | 74 | | ---- | ---- | ------------| 75 | | full | boolean | `true` use all area of screen, `false` not include status and navigation bar | 76 | 77 | ```javascript 78 | import ImmersiveMode from 'react-native-immersive-mode'; 79 | 80 | // should set full layout in componentDidMount 81 | componentDidMount() { 82 | ImmersiveMode.fullLayout(true); 83 | } 84 | // and should restore layout 85 | componentWillUnmount() { 86 | ImmersiveMode.fullLayout(false); 87 | } 88 | ``` 89 | 90 | ### setBarMode 91 | `setBarMode(mode: string): void` 92 | change status and navigation bar mode 93 | 94 | **Note**. mode sticky will be disabled bar color. 95 | 96 | | name | type | description | 97 | | ---- | ---- | ------------| 98 | | mode | string | [Bar Mode](#bar-mode) | 99 | 100 | ```javascript 101 | import ImmersiveMode from 'react-native-immersive-mode'; 102 | 103 | ImmersiveMode.setBarMode('Normal'); 104 | ``` 105 | 106 | ### setBarStyle 107 | `setBarStyle(style: string): void` 108 | chnage status and navigation style 109 | 110 | **Note**. To change system Navigation(bottom) to Light, must be change bar color `setBarColor` to other color first. 111 | 112 | | name | type | description | 113 | | ---- | ---- | ------------| 114 | | mode | string | [Bar Style](#bar-style) | 115 | 116 | ```javascript 117 | import ImmersiveMode from 'react-native-immersive-mode'; 118 | 119 | ImmersiveMode.setBarStyle('Dark'); 120 | ``` 121 | 122 | ### setBarTranslucent 123 | `setBarTranslucent(translucent: boolean): void` 124 | change status and navigation bar is transparent 50%. 125 | 126 | **Note**. When `true` bar color will be disabled. 127 | 128 | | name | type | description | 129 | | ---- | ---- | ------------| 130 | | translucent | booelan | | 131 | 132 | ```javascript 133 | import ImmersiveMode from 'react-native-immersive-mode'; 134 | 135 | ImmersiveMode.setBarTranslucent(true); 136 | ``` 137 | 138 | ### setBarColor 139 | `setBarColor(color: string): void` 140 | change status and navigation bar is transparent 50%. 141 | 142 | | name | type | description | 143 | | ---- | ---- | ------------| 144 | | color | string | `#rgb`, `#rrggbb`, `#rrggbbaa` | 145 | 146 | ```javascript 147 | import ImmersiveMode from 'react-native-immersive-mode'; 148 | 149 | ImmersiveMode.setBarColor('#ff0000'); 150 | ``` 151 | 152 | **Note**. still can passing `null` to set default color 153 | 154 | ### setBarDefaultColor 155 | `setBarDefaultColor(): void` 156 | 157 | > default color is color before changed by `setBarColor` 158 | 159 | ```javascript 160 | import ImmersiveMode from 'react-native-immersive-mode'; 161 | 162 | ImmersiveMode.setBarDefaultColor(); 163 | ``` 164 | 165 | ### addEventListener 166 | `addEventListener(callback: function): EmitterSubscription` 167 | trigger event when bar visibility change (mode sticky not trigged) 168 | 169 | | name | type | params | description | 170 | | ---- | ---- | ------ | ------------| 171 | | callback | function | (statusBar: boolean, navigationBottomBar: boolean) | `true`: show, `false`: hidden | 172 | 173 | ```javascript 174 | import ImmersiveMode from 'react-native-immersive-mode'; 175 | 176 | // ... 177 | 178 | componentDidMount() { 179 | this.listen = ImmersiveMode.addEventListener((e) => { 180 | console.log(e) 181 | }) 182 | } 183 | 184 | componentWillUnmount() { 185 | this.listen.remove(); 186 | } 187 | ``` 188 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | classpath "com.android.tools.build:gradle:7.2.1" 9 | } 10 | } 11 | 12 | apply plugin: 'com.android.library' 13 | 14 | android { 15 | compileSdkVersion 33 16 | 17 | defaultConfig { 18 | minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : 16 19 | targetSdkVersion 33 20 | versionCode 1 21 | versionName "1.0" 22 | } 23 | lintOptions { 24 | abortOnError false 25 | } 26 | } 27 | 28 | repositories { 29 | mavenCentral() 30 | } 31 | 32 | dependencies { 33 | implementation 'com.facebook.react:react-native:+' 34 | } 35 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnimmersivemode/BarMode.java: -------------------------------------------------------------------------------- 1 | package com.rnimmersivemode; 2 | 3 | class BarMode { 4 | static final String Normal = "Normal"; // show status and navigation 5 | static final String Full = "Full"; // hide status and navigation 6 | static final String FullSticky = "FullSticky"; // hide status and navigation with sticky 7 | static final String Bottom = "Bottom"; // hide navigation 8 | static final String BottomSticky = "BottomSticky"; // hide navigation with sticky 9 | } 10 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnimmersivemode/BarStyle.java: -------------------------------------------------------------------------------- 1 | package com.rnimmersivemode; 2 | 3 | public class BarStyle { 4 | static final String Light = "Light"; // hide navigation with sticky 5 | static final String Dark = "Dark"; // hide navigation with sticky 6 | } 7 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnimmersivemode/ImmersiveEvent.java: -------------------------------------------------------------------------------- 1 | package com.rnimmersivemode; 2 | 3 | class ImmersiveEvent { 4 | static final String OnSystemUiVisibilityChange = "OnSystemUiVisibilityChange"; 5 | } 6 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnimmersivemode/ImmersiveMode.java: -------------------------------------------------------------------------------- 1 | package com.rnimmersivemode; 2 | 3 | class ImmersiveMode { 4 | static final int Normal = 1; // show status and navigation 5 | static final int Full = 2; // hide status and navigation 6 | static final int FullSticky = 3; // hide status and navigation with sticky 7 | static final int Bottom = 4; // hide navigation 8 | static final int BottomSticky = 5; // hide navigation with sticky 9 | } 10 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnimmersivemode/RNImmersiveModeModule.java: -------------------------------------------------------------------------------- 1 | package com.rnimmersivemode; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Build; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.view.Window; 9 | import android.view.WindowManager; 10 | 11 | import com.facebook.react.bridge.Arguments; 12 | import com.facebook.react.bridge.ReactApplicationContext; 13 | import com.facebook.react.bridge.ReactContext; 14 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 15 | import com.facebook.react.bridge.ReactMethod; 16 | import com.facebook.react.bridge.WritableMap; 17 | import com.facebook.react.modules.core.DeviceEventManagerModule; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | import javax.annotation.Nonnull; 23 | 24 | import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread; 25 | 26 | public class RNImmersiveModeModule extends ReactContextBaseJavaModule { 27 | 28 | private static final String ModuleName = "RNImmersiveMode"; 29 | private int currentLayout = View.VISIBLE | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 30 | private int currentMode = View.VISIBLE | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 31 | private int currentStatusStyle = View.VISIBLE | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 32 | private int currentNavigationStyle = View.VISIBLE | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 33 | 34 | private boolean hasColorChange = false; 35 | private int defaultStatusColor; 36 | private int defaultNavigationColor; 37 | 38 | RNImmersiveModeModule(ReactApplicationContext reactContext) { 39 | super(reactContext); 40 | } 41 | 42 | @Nonnull 43 | @Override 44 | public String getName() { 45 | return ModuleName; 46 | } 47 | 48 | @Override 49 | public Map getConstants() { 50 | final Map constants = new HashMap<>(); 51 | 52 | //mode 53 | constants.put("Normal", ImmersiveMode.Normal); 54 | constants.put("Full", ImmersiveMode.Full); 55 | constants.put("FullSticky", ImmersiveMode.FullSticky); 56 | constants.put("Bottom", ImmersiveMode.Bottom); 57 | constants.put("BottomSticky", ImmersiveMode.BottomSticky); 58 | 59 | //event 60 | constants.put("OnSystemUiVisibilityChange", ImmersiveEvent.OnSystemUiVisibilityChange); 61 | return constants; 62 | } 63 | 64 | @ReactMethod 65 | public void fullLayout(final Boolean fullscreen) { 66 | if (fullscreen) { 67 | // set layout fullscreen (including layout navigation bar on bottom) 68 | this.currentLayout = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 69 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 70 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 71 | } else { 72 | // set layout normal (not including layout navigation bar on bottom) 73 | this.currentLayout = View.VISIBLE 74 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 75 | } 76 | 77 | this.setUiOnUiThread(); 78 | } 79 | 80 | @ReactMethod 81 | public void setBarMode(String immersive) { 82 | 83 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 84 | Log.w(ModuleName, "Sdk Version must be >= " + Build.VERSION_CODES.KITKAT); 85 | return; 86 | } 87 | 88 | switch (immersive) { 89 | case BarMode.Normal: 90 | this.currentMode = View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 91 | break; 92 | case BarMode.Full: 93 | this.currentMode = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar 94 | | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar 95 | | View.SYSTEM_UI_FLAG_IMMERSIVE; 96 | break; 97 | case BarMode.FullSticky: 98 | this.currentMode = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 99 | | View.SYSTEM_UI_FLAG_FULLSCREEN 100 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; 101 | break; 102 | case BarMode.Bottom: 103 | this.currentMode = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 104 | | View.SYSTEM_UI_FLAG_IMMERSIVE; 105 | break; 106 | case BarMode.BottomSticky: 107 | this.currentMode = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 108 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; 109 | break; 110 | default: 111 | } 112 | 113 | this.setUiOnUiThread(); 114 | } 115 | 116 | @ReactMethod 117 | public void setBarColor(final String hexColor) { 118 | runOnUiThread(new Runnable() { 119 | @Override 120 | public void run() { 121 | Activity activity = getCurrentActivity(); 122 | if (activity != null) { 123 | Window window = activity.getWindow(); 124 | if (window != null) { 125 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 126 | 127 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); 128 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 129 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 130 | 131 | try { 132 | if (hexColor != null) { 133 | if (!hasColorChange) { 134 | hasColorChange = true; 135 | defaultStatusColor = window.getStatusBarColor(); 136 | defaultNavigationColor = window.getNavigationBarColor(); 137 | } 138 | 139 | window.setStatusBarColor(Color.parseColor(hexColor)); 140 | window.setNavigationBarColor(Color.parseColor(hexColor)); 141 | } else if (hasColorChange) { 142 | hasColorChange = false; 143 | window.setStatusBarColor(defaultStatusColor); 144 | window.setNavigationBarColor(defaultNavigationColor); 145 | } 146 | } catch (Exception e) { 147 | Log.e(ModuleName, e.getMessage()); 148 | } 149 | 150 | } else { 151 | Log.w(ModuleName, "Sdk Version must be >= " + Build.VERSION_CODES.LOLLIPOP); 152 | } 153 | } 154 | } 155 | } 156 | }); 157 | } 158 | 159 | @ReactMethod 160 | public void setBarStyle(final String style) { 161 | switch (style) { 162 | case BarStyle.Dark: 163 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 164 | this.currentStatusStyle = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 165 | } else { 166 | Log.w(ModuleName, "Sdk Version must be >= " + Build.VERSION_CODES.M); 167 | } 168 | 169 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 170 | this.currentNavigationStyle = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; 171 | } else { 172 | Log.w(ModuleName, "Sdk Version must be >= " + Build.VERSION_CODES.O); 173 | } 174 | break; 175 | case BarStyle.Light: 176 | this.currentStatusStyle = View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 177 | this.currentNavigationStyle = View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 178 | break; 179 | } 180 | 181 | this.setUiOnUiThread(); 182 | } 183 | 184 | @ReactMethod 185 | public void setBarTranslucent(final boolean enable) { 186 | runOnUiThread(new Runnable() { 187 | @Override 188 | public void run() { 189 | Activity activity = getCurrentActivity(); 190 | if (activity != null) { 191 | Window window = activity.getWindow(); 192 | if (window != null) { 193 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 194 | if (enable) { 195 | window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 196 | window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); 197 | } else { 198 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 199 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); 200 | } 201 | } 202 | } 203 | } 204 | } 205 | }); 206 | } 207 | 208 | @ReactMethod 209 | private void setOnSystemUiVisibilityChangeListener() { 210 | runOnUiThread(new Runnable() { 211 | @Override 212 | public void run() { 213 | Activity activity = getCurrentActivity(); 214 | if (activity != null) { 215 | Window window = activity.getWindow(); 216 | if (window != null) { 217 | View view = window.getDecorView(); 218 | view.setOnSystemUiVisibilityChangeListener( 219 | new View.OnSystemUiVisibilityChangeListener() { 220 | @Override 221 | public void onSystemUiVisibilityChange(int visibility) { 222 | WritableMap params = Arguments.createMap(); 223 | 224 | if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { 225 | params.putBoolean("statusBar", true); 226 | } else { 227 | params.putBoolean("statusBar", false); 228 | } 229 | 230 | if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { 231 | params.putBoolean("navigationBottomBar", true); 232 | } else { 233 | params.putBoolean("navigationBottomBar", false); 234 | } 235 | 236 | sendEvent(getReactApplicationContext(), 237 | params); 238 | } 239 | }); 240 | } 241 | } 242 | } 243 | }); 244 | } 245 | 246 | private void setUiOnUiThread() { 247 | runOnUiThread(new Runnable() { 248 | @Override 249 | public void run() { 250 | Activity activity = getCurrentActivity(); 251 | if (activity != null) { 252 | Window window = activity.getWindow(); 253 | if (window != null) { 254 | View view = window.getDecorView(); 255 | view.setSystemUiVisibility(currentLayout | currentMode | currentStatusStyle | currentNavigationStyle); 256 | } 257 | } 258 | } 259 | }); 260 | } 261 | 262 | private void sendEvent(ReactContext reactContext, 263 | WritableMap params) { 264 | reactContext 265 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 266 | .emit(ImmersiveEvent.OnSystemUiVisibilityChange, params); 267 | } 268 | } -------------------------------------------------------------------------------- /android/src/main/java/com/rnimmersivemode/RNImmersiveModePackage.java: -------------------------------------------------------------------------------- 1 | package com.rnimmersivemode; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.uimanager.ViewManager; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | import javax.annotation.Nonnull; 12 | 13 | public class RNImmersiveModePackage implements ReactPackage { 14 | @Nonnull 15 | @Override 16 | public List createNativeModules(@Nonnull ReactApplicationContext reactContext) { 17 | return Collections.singletonList(new RNImmersiveModeModule(reactContext)); 18 | } 19 | 20 | @Nonnull 21 | @Override 22 | public List createViewManagers(@Nonnull ReactApplicationContext reactContext) { 23 | return Collections.emptyList(); 24 | } 25 | } -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { EmitterSubscription } from 'react-native' 2 | 3 | type BarViisibilityType = { 4 | statusBar: boolean, 5 | navigationBottomBar: boolean 6 | } 7 | 8 | type ImmersiveBarStyleType = 'Dark' | 'Light'; 9 | type ImmersiveBarModeType = 10 | 'Normal' | 11 | 'Full' | 12 | 'FullSticky' | 13 | 'Bottom' | 14 | 'BottomSticky' 15 | 16 | 17 | interface ImmersiveModeStatic { 18 | fullLayout(full: boolean): void; 19 | 20 | /** 21 | * Set system ui mode. 22 | * @param mode 23 | */ 24 | setBarMode(mode: ImmersiveBarModeType): void; 25 | 26 | /** 27 | * Set color of system bar. 28 | * When set color translucent will be disabled. 29 | * 30 | * @param color color hex #rrggbbaa. if color is null will set default color 31 | */ 32 | setBarColor(color: string): void; 33 | 34 | /** 35 | * Set default color of system bar. 36 | * When set default color translucent will be disabled. 37 | */ 38 | setBarDefaultColor(): void; 39 | 40 | /** 41 | * Set style of system bar. 42 | * System Navigation will be Light, must be change bar color `setBarColor` to other color first. 43 | * 44 | * @param style 45 | */ 46 | setBarStyle(style: ImmersiveBarStyleType): void; 47 | 48 | /** 49 | * System bar background color is transparent 50%. 50 | * When `true` bar color will be disabled. 51 | * 52 | * @param enable 53 | */ 54 | setBarTranslucent(enable: boolean): void; 55 | 56 | addEventListener(callback: (viisibility: BarViisibilityType) => void): EmitterSubscription; 57 | } 58 | 59 | declare const ImmersiveMode: ImmersiveModeStatic; 60 | export default ImmersiveMode; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { NativeModules, DeviceEventEmitter, Platform } from 'react-native'; 2 | 3 | const { RNImmersiveMode } = NativeModules; 4 | 5 | const checkModule = () => { 6 | if (Platform.OS === 'android' && !RNImmersiveMode) { 7 | throw Error('RNImmersiveMode is not properly linked'); 8 | } 9 | // else: (maybe iOS) 10 | 11 | return Platform.OS === 'android' && RNImmersiveMode; 12 | } 13 | 14 | const ImmersiveMode = { 15 | 16 | fullLayout(full) { 17 | if (checkModule()) { 18 | RNImmersiveMode.fullLayout(full); 19 | } 20 | }, 21 | 22 | setBarMode(mode) { 23 | if (checkModule()) { 24 | RNImmersiveMode.setBarMode(mode); 25 | } 26 | }, 27 | 28 | setBarStyle(style) { 29 | if (checkModule()) { 30 | RNImmersiveMode.setBarStyle(style); 31 | } 32 | }, 33 | 34 | setBarTranslucent(enable) { 35 | if (checkModule()) { 36 | RNImmersiveMode.setBarTranslucent(enable); 37 | } 38 | }, 39 | 40 | setBarColor(color) { 41 | if (checkModule()) { 42 | if (typeof color === 'string') { 43 | if (color.length === 9) { 44 | // convert #rgba to #argb 45 | color = '#' + color.substr(7, 2) + color.substr(1, 6); 46 | } else if (color.length === 4) { 47 | color = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3]; 48 | } 49 | } 50 | 51 | RNImmersiveMode.setBarColor(color); 52 | } 53 | }, 54 | 55 | setBarDefaultColor() { 56 | if (checkModule()) { 57 | RNImmersiveMode.setBarColor(null); 58 | } 59 | }, 60 | 61 | addEventListener(callback) { 62 | if (checkModule()) { 63 | if (typeof callback !== 'function') return; 64 | 65 | RNImmersiveMode.setOnSystemUiVisibilityChangeListener(); 66 | 67 | const subscription = DeviceEventEmitter.addListener( 68 | RNImmersiveMode.OnSystemUiVisibilityChange, 69 | (e) => callback(e)); 70 | 71 | return subscription; 72 | } 73 | } 74 | } 75 | 76 | export default ImmersiveMode; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-immersive-mode", 3 | "version": "2.0.2", 4 | "description": "Set Immersive mode for Android (hide status bar or navigation bar bottom)", 5 | "main": "index.js", 6 | "keywords": [ 7 | "react-native", 8 | "android", 9 | "immersive", 10 | "navigation-bar-bottom", 11 | "status-bar" 12 | ], 13 | "homepage": "https://github.com/wrathyz/react-native-immersive-mode", 14 | "bugs": { 15 | "url": "https://github.com/wrathyz/react-native-immersive-mode/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/wrathyz/react-native-immersive-mode.git" 20 | }, 21 | "author": "Wrathyz", 22 | "license": "MIT", 23 | "peerDependencies": { 24 | "react-native": ">=0.60.5" 25 | }, 26 | "types": "./index.d.ts", 27 | "scripts": { 28 | "test": "echo \"Error: no test specified\" && exit 1" 29 | } 30 | } 31 | --------------------------------------------------------------------------------