Skip to content

Commit 5a90376

Browse files
authored
feat: add support for isNavigationBarTranslucentAndroid (#6431)
## Summary Resolves #6043. For StatusBar we have a flag that removes top padding when we want to render RN stuff "under" StatusBar. A similar problem was raised in #6043, but for NavigationBar, so I added another flag `isNavigationBarTranslucentAndroid` that removes bottom padding as well. Currently, I see no way to make it automatic. I tried one approach to remove the flags in #5889, but it doesn't seem to be the performant solution. ## Preview |Before|After without flag|After with flag| |-|-|-| |<img width="602" alt="before" src="https://github.com/user-attachments/assets/0032efbe-342a-4e06-81ee-f7351bb032f7">|<img width="602" alt="after-without-flag" src="https://github.com/user-attachments/assets/6f259329-9bf4-4bbb-8ce4-32d671e65a5a">|<img width="602" alt="after-with-flag" src="https://github.com/user-attachments/assets/81c43052-f3b3-4a3b-a9cb-bd974e851c43">| ## Test plan Testing is a bit tricky, because an example from the issue uses a navigation bar package from Expo, which requires Expo and our example app doesn't have Expo. What I did: <details> <summary>Steps</summary> ```bash # clone reanimated from current branch git clone https://github.com/software-mansion/react-native-reanimated.git # checkout current branch git checkout @maciekstosio/Add-support-for-isNavigationBarTranslucentAndroid # install yarn && yarn build # build npm package cd packages/react-native-reanimated ./createNPMPackage.sh --fresh # next, create simple expo project npx create-expo-stack # install library for navigation bar npx expo install expo-navigation-bar # install reanimated npx expo install react-native-reanimated # replace in package.json path to locally-build package i.e.: "react-native-reanimated": "file:react-native-reanimated.tgz" # yarn install # npx expo run:android ``` </details> <details> <summary>App.tsx to test</summary> ```jsx import * as NavigationBar from 'expo-navigation-bar'; import React, { useEffect } from 'react'; import Animated, { useAnimatedKeyboard, useAnimatedStyle, } from 'react-native-reanimated'; import { Platform, StatusBar, StyleSheet, TextInput, View, Text, Button, } from 'react-native'; export default function EmptyExample() { const keyboard = useAnimatedKeyboard({ isNavigationBarTranslucentAndroid: true, }); const animatedStyles = useAnimatedStyle(() => ({ transform: [{ translateY: -keyboard.height.value }], })); useEffect(() => { StatusBar.setBarStyle('dark-content'); NavigationBar.setBackgroundColorAsync('transparent'); }, []); return ( <Animated.View style={[ styles.container, animatedStyles, { justifyContent: 'flex-end', borderWidth: 5, borderColor: '#ff0' }, ]}> <View style={[ styles.center, { height: 200, backgroundColor: '#f0f', borderWidth: 5, borderColor: '#0ff', }, ]}> <Text>{`Android ${Platform.constants['Release']}`}</Text> <TextInput placeholder="Test" /> </View> </Animated.View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: 'rgba(255,0,255,0.2)' }, center: { justifyContent: 'center', alignItems: 'center' }, }); ``` </details>
1 parent e63f7cc commit 5a90376

File tree

19 files changed

+75
-36
lines changed

19 files changed

+75
-36
lines changed

packages/docs-reanimated/docs/device/useAnimatedKeyboard.mdx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,13 @@ import AnimatedKeyboardSrc from '!!raw-loader!@site/src/examples/AnimatedKeyboar
101101

102102
- On Android, using the `useAnimatedKeyboard` hook expands root view to full screen ([immersive mode](https://developer.android.com/develop/ui/views/layout/immersive)) and takes control over insets management.
103103

104-
- When `isStatusBarTranslucentAndroid` is `false` it applies the top and bottom margins according to the insets.
104+
- When `isStatusBarTranslucentAndroid` is `false` it applies the top margin according to the insets.
105105

106-
- When `isStatusBarTranslucentAndroid` is `true` it applies bottom padding according to the navigation inset and sets top margin to `0`.
106+
- When `isStatusBarTranslucentAndroid` is `true` it sets top margin to `0`.
107+
108+
- When `isNavigationBarTranslucentAndroid` is `false` it applies the bottom margin according to the insets.
109+
110+
- When `isNavigationBarTranslucentAndroid` is `true` it sets bottom margin to `0`.
107111

108112
- On Android, when using navigation with native header, `isStatusBarTranslucentAndroid` doesn't affect the top inset.
109113

packages/docs-reanimated/versioned_docs/version-2.x/api/hooks/useAnimatedKeyboard.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ Properties:
4848

4949
- `isStatusBarTranslucentAndroid`[bool] - if you want to use translucent status bar on Android, set this option to `true`. Defaults to `false`. Ignored on iOS.
5050

51+
- `isNavigationBarTranslucentAndroid`[bool] - if you want to use translucent navigation bar on Android, set this option to `true`. Defaults to `false`. Ignored on iOS.
52+
5153
### Example
5254

5355
```js

packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,8 @@ void NativeReanimatedModule::initializeLayoutAnimations() {
870870
jsi::Value NativeReanimatedModule::subscribeForKeyboardEvents(
871871
jsi::Runtime &rt,
872872
const jsi::Value &handlerWorklet,
873-
const jsi::Value &isStatusBarTranslucent) {
873+
const jsi::Value &isStatusBarTranslucent,
874+
const jsi::Value &isNavigationBarTranslucent) {
874875
auto shareableHandler = extractShareableOrThrow<ShareableWorklet>(
875876
rt,
876877
handlerWorklet,
@@ -880,7 +881,8 @@ jsi::Value NativeReanimatedModule::subscribeForKeyboardEvents(
880881
uiWorkletRuntime_->runGuarded(
881882
shareableHandler, jsi::Value(keyboardState), jsi::Value(height));
882883
},
883-
isStatusBarTranslucent.getBool());
884+
isStatusBarTranslucent.getBool(),
885+
isNavigationBarTranslucent.getBool());
884886
}
885887

886888
void NativeReanimatedModule::unsubscribeFromKeyboardEvents(

packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec {
157157
jsi::Value subscribeForKeyboardEvents(
158158
jsi::Runtime &rt,
159159
const jsi::Value &keyboardEventContainer,
160-
const jsi::Value &isStatusBarTranslucent) override;
160+
const jsi::Value &isStatusBarTranslucent,
161+
const jsi::Value &isNavigationBarTranslucent) override;
161162
void unsubscribeFromKeyboardEvents(
162163
jsi::Runtime &rt,
163164
const jsi::Value &listenerId) override;

packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModuleSpec.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ static jsi::Value SPEC_PREFIX(subscribeForKeyboardEvents)(
138138
const jsi::Value *args,
139139
size_t) {
140140
return static_cast<NativeReanimatedModuleSpec *>(&turboModule)
141-
->subscribeForKeyboardEvents(rt, std::move(args[0]), std::move(args[1]));
141+
->subscribeForKeyboardEvents(
142+
rt, std::move(args[0]), std::move(args[1]), std::move(args[2]));
142143
}
143144

144145
static jsi::Value SPEC_PREFIX(unsubscribeFromKeyboardEvents)(

packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModuleSpec.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule {
7777
virtual jsi::Value subscribeForKeyboardEvents(
7878
jsi::Runtime &rt,
7979
const jsi::Value &keyboardEventContainer,
80-
const jsi::Value &isStatusBarTranslucent) = 0;
80+
const jsi::Value &isStatusBarTranslucent,
81+
const jsi::Value &isNavigationBarTranslucent) = 0;
8182
virtual void unsubscribeFromKeyboardEvents(
8283
jsi::Runtime &rt,
8384
const jsi::Value &listenerId) = 0;

packages/react-native-reanimated/Common/cpp/reanimated/Tools/PlatformDepMethodsHolder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ using ConfigurePropsFunction = std::function<void(
7272
const jsi::Value &uiProps,
7373
const jsi::Value &nativeProps)>;
7474
using KeyboardEventSubscribeFunction =
75-
std::function<int(std::function<void(int, int)>, bool)>;
75+
std::function<int(std::function<void(int, int)>, bool, bool)>;
7676
using KeyboardEventUnsubscribeFunction = std::function<void(int)>;
7777
using MaybeFlushUIUpdatesQueueFunction = std::function<void()>;
7878

packages/react-native-reanimated/android/src/main/cpp/reanimated/NativeProxy.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,14 +416,16 @@ void NativeProxy::setGestureState(int handlerTag, int newState) {
416416

417417
int NativeProxy::subscribeForKeyboardEvents(
418418
std::function<void(int, int)> callback,
419-
bool isStatusBarTranslucent) {
419+
bool isStatusBarTranslucent,
420+
bool isNavigationBarTranslucent) {
420421
static const auto method =
421-
getJniMethod<int(KeyboardWorkletWrapper::javaobject, bool)>(
422+
getJniMethod<int(KeyboardWorkletWrapper::javaobject, bool, bool)>(
422423
"subscribeForKeyboardEvents");
423424
return method(
424425
javaPart_.get(),
425426
KeyboardWorkletWrapper::newObjectCxxArgs(std::move(callback)).get(),
426-
isStatusBarTranslucent);
427+
isStatusBarTranslucent,
428+
isNavigationBarTranslucent);
427429
}
428430

429431
void NativeProxy::unsubscribeFromKeyboardEvents(int listenerId) {

packages/react-native-reanimated/android/src/main/cpp/reanimated/NativeProxy.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,8 @@ class NativeProxy : public jni::HybridClass<NativeProxy> {
221221
void unregisterSensor(int sensorId);
222222
int subscribeForKeyboardEvents(
223223
std::function<void(int, int)> callback,
224-
bool isStatusBarTranslucent);
224+
bool isStatusBarTranslucent,
225+
bool isNavigationBarTranslucent);
225226
void unsubscribeFromKeyboardEvents(int listenerId);
226227
#ifdef RCT_NEW_ARCH_ENABLED
227228
// nothing

packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/keyboard/Keyboard.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ public int getHeight() {
1818
return mHeight;
1919
}
2020

21-
public void updateHeight(WindowInsetsCompat insets) {
21+
public void updateHeight(WindowInsetsCompat insets, boolean isNavigationBarTranslucent) {
2222
int contentBottomInset = insets.getInsets(CONTENT_TYPE_MASK).bottom;
23-
int systemBarBottomInset = insets.getInsets(SYSTEM_BAR_TYPE_MASK).bottom;
23+
int systemBarBottomInset =
24+
isNavigationBarTranslucent ? 0 : insets.getInsets(SYSTEM_BAR_TYPE_MASK).bottom;
2425
int keyboardHeightDip = contentBottomInset - systemBarBottomInset;
2526
int keyboardHeight = (int) PixelUtil.toDIPFromPixel(Math.max(0, keyboardHeightDip));
2627
if (keyboardHeight <= 0 && mState == KeyboardState.OPEN) {

0 commit comments

Comments
 (0)