Skip to content

Commit

Permalink
Make work with transluscent StatusBar on Android (software-mansion#3958)
Browse files Browse the repository at this point in the history
<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

## Summary

Fixes
software-mansion#3631
Fixes the issue with translucent StatusBar on Android by adding the
option `isStatusBarTranslucentAndroid` in `useAnimatedKeyboard`. It is
the same solution as the one mentioned in the issue:
kirillzyusko/react-native-keyboard-controller@2a7c92f

## Test plan

There is an excellent example in the issue that reproduces the problem:
https://github.com/ChildishForces/reanimated-bug-repro
I tested on real Android device with Android 12 and RN 71.
I think it would be nice to also test on RN < 70 because the bottom
padding may behave differently.
  • Loading branch information
graszka22 authored and fluiddot committed Jun 5, 2023
1 parent cace9c7 commit 06635f3
Show file tree
Hide file tree
Showing 17 changed files with 97 additions and 40 deletions.
17 changes: 10 additions & 7 deletions Common/cpp/NativeModules/NativeReanimatedModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -664,15 +664,18 @@ void NativeReanimatedModule::setNewestShadowNodesRegistry(

jsi::Value NativeReanimatedModule::subscribeForKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &handlerWorklet) {
const jsi::Value &handlerWorklet,
const jsi::Value &isStatusBarTranslucent) {
auto shareableHandler = extractShareableOrThrow(rt, handlerWorklet);
auto uiRuntime = runtimeHelper->uiRuntime();
return subscribeForKeyboardEventsFunction([=](int keyboardState, int height) {
jsi::Runtime &rt = *uiRuntime;
auto handler = shareableHandler->getJSValue(rt);
handler.asObject(rt).asFunction(rt).call(
rt, jsi::Value(keyboardState), jsi::Value(height));
});
return subscribeForKeyboardEventsFunction(
[=](int keyboardState, int height) {
jsi::Runtime &rt = *uiRuntime;
auto handler = shareableHandler->getJSValue(rt);
handler.asObject(rt).asFunction(rt).call(
rt, jsi::Value(keyboardState), jsi::Value(height));
},
isStatusBarTranslucent.asBool());
}

void NativeReanimatedModule::unsubscribeFromKeyboardEvents(
Expand Down
3 changes: 2 additions & 1 deletion Common/cpp/NativeModules/NativeReanimatedModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec,
void unregisterSensor(jsi::Runtime &rt, const jsi::Value &sensorId) override;
jsi::Value subscribeForKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &keyboardEventContainer) override;
const jsi::Value &keyboardEventContainer,
const jsi::Value &isStatusBarTranslucent) override;
void unsubscribeFromKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &listenerId) override;
Expand Down
4 changes: 2 additions & 2 deletions Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ static jsi::Value SPEC_PREFIX(subscribeForKeyboardEvents)(
const jsi::Value *args,
size_t count) {
return static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->subscribeForKeyboardEvents(rt, std::move(args[0]));
->subscribeForKeyboardEvents(rt, std::move(args[0]), std::move(args[1]));
}

static jsi::Value SPEC_PREFIX(unsubscribeFromKeyboardEvents)(
Expand Down Expand Up @@ -187,7 +187,7 @@ NativeReanimatedModuleSpec::NativeReanimatedModuleSpec(
MethodMetadata{1, SPEC_PREFIX(unregisterSensor)};
methodMap_["configureProps"] = MethodMetadata{2, SPEC_PREFIX(configureProps)};
methodMap_["subscribeForKeyboardEvents"] =
MethodMetadata{1, SPEC_PREFIX(subscribeForKeyboardEvents)};
MethodMetadata{2, SPEC_PREFIX(subscribeForKeyboardEvents)};
methodMap_["unsubscribeFromKeyboardEvents"] =
MethodMetadata{1, SPEC_PREFIX(unsubscribeFromKeyboardEvents)};

Expand Down
3 changes: 2 additions & 1 deletion Common/cpp/NativeModules/NativeReanimatedModuleSpec.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule {
// keyboard
virtual jsi::Value subscribeForKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &keyboardEventContainer) = 0;
const jsi::Value &keyboardEventContainer,
const jsi::Value &isStatusBarTranslucent) = 0;
virtual void unsubscribeFromKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &listenerId) = 0;
Expand Down
2 changes: 1 addition & 1 deletion Common/cpp/Tools/PlatformDepMethodsHolder.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ using ConfigurePropsFunction = std::function<void(
const jsi::Value &uiProps,
const jsi::Value &nativeProps)>;
using KeyboardEventSubscribeFunction =
std::function<int(std::function<void(int, int)>)>;
std::function<int(std::function<void(int, int)>, bool)>;
using KeyboardEventUnsubscribeFunction = std::function<void(int)>;

struct PlatformDepMethodsHolder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@ private void unregisterSensor(int sensorId) {
}

@DoNotStrip
private int subscribeForKeyboardEvents(KeyboardEventDataUpdater keyboardEventDataUpdater) {
return reanimatedKeyboardEventListener.subscribeForKeyboardEvents(keyboardEventDataUpdater);
private int subscribeForKeyboardEvents(KeyboardEventDataUpdater keyboardEventDataUpdater, boolean isStatusBarTranslucent) {
return reanimatedKeyboardEventListener.subscribeForKeyboardEvents(keyboardEventDataUpdater, isStatusBarTranslucent);
}

@DoNotStrip
Expand Down
20 changes: 13 additions & 7 deletions android/src/main/cpp/NativeProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,11 @@ void NativeProxy::installJSIBindings(
};

auto subscribeForKeyboardEventsFunction =
[this](std::function<void(int, int)> keyboardEventDataUpdater) -> int {
return subscribeForKeyboardEvents(std::move(keyboardEventDataUpdater));
[this](
std::function<void(int, int)> keyboardEventDataUpdater,
bool isStatusBarTranslucent) -> int {
return subscribeForKeyboardEvents(
std::move(keyboardEventDataUpdater), isStatusBarTranslucent);
};

auto unsubscribeFromKeyboardEventsFunction = [this](int listenerId) -> void {
Expand Down Expand Up @@ -520,15 +523,18 @@ void NativeProxy::configureProps(
}

int NativeProxy::subscribeForKeyboardEvents(
std::function<void(int, int)> keyboardEventDataUpdater) {
auto method = javaPart_->getClass()
->getMethod<int(KeyboardEventDataUpdater::javaobject)>(
"subscribeForKeyboardEvents");
std::function<void(int, int)> keyboardEventDataUpdater,
bool isStatusBarTranslucent) {
auto method =
javaPart_->getClass()
->getMethod<int(KeyboardEventDataUpdater::javaobject, bool)>(
"subscribeForKeyboardEvents");
return method(
javaPart_.get(),
KeyboardEventDataUpdater::newObjectCxxArgs(
std::move(keyboardEventDataUpdater))
.get());
.get(),
isStatusBarTranslucent);
}

void NativeProxy::unsubscribeFromKeyboardEvents(int listenerId) {
Expand Down
3 changes: 2 additions & 1 deletion android/src/main/cpp/NativeProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ class NativeProxy : public jni::HybridClass<NativeProxy> {
const jsi::Value &uiProps,
const jsi::Value &nativeProps);
int subscribeForKeyboardEvents(
std::function<void(int, int)> keyboardEventDataUpdater);
std::function<void(int, int)> keyboardEventDataUpdater,
bool isStatusBarTranslucent);
void unsubscribeFromKeyboardEvents(int listenerId);
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public int asInt() {
private int nextListenerId = 0;
private KeyboardState state;
private final HashMap<Integer, KeyboardEventDataUpdater> listeners = new HashMap<>();
private boolean isStatusBarTranslucent = false;

public ReanimatedKeyboardEventListener(WeakReference<ReactApplicationContext> reactContext) {
this.reactContext = reactContext;
Expand Down Expand Up @@ -68,7 +69,11 @@ private void setupWindowInsets() {
FrameLayout.LayoutParams params =
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
params.setMargins(0, paddingTop, 0, paddingBottom);
if (isStatusBarTranslucent) {
params.setMargins(0, 0, 0, 0);
} else {
params.setMargins(0, paddingTop, 0, paddingBottom);
}
content.setLayoutParams(params);
return insets;
});
Expand Down Expand Up @@ -127,9 +132,11 @@ private void setUpCallbacks() {
ViewCompat.setWindowInsetsAnimationCallback(rootView, new WindowInsetsCallback());
}

public int subscribeForKeyboardEvents(KeyboardEventDataUpdater updater) {
public int subscribeForKeyboardEvents(
KeyboardEventDataUpdater updater, boolean isStatusBarTranslucent) {
int listenerId = nextListenerId++;
if (listeners.isEmpty()) {
this.isStatusBarTranslucent = isStatusBarTranslucent;
setUpCallbacks();
}
listeners.put(listenerId, updater);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@ private void unregisterSensor(int sensorId) {
}

@DoNotStrip
private int subscribeForKeyboardEvents(KeyboardEventDataUpdater keyboardEventDataUpdater) {
return reanimatedKeyboardEventListener.subscribeForKeyboardEvents(keyboardEventDataUpdater);
private int subscribeForKeyboardEvents(KeyboardEventDataUpdater keyboardEventDataUpdater, boolean isStatusBarTranslucent) {
return reanimatedKeyboardEventListener.subscribeForKeyboardEvents(keyboardEventDataUpdater, isStatusBarTranslucent);
}

@DoNotStrip
Expand Down
13 changes: 10 additions & 3 deletions docs/docs/api/hooks/useAnimatedKeyboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ With the `useAnimatedKeyboard` hook, you can create animations based on current
On Android, make sure to set `android:windowSoftInputMode` in your `AndroidMainfest.xml` to `adjustResize`. Then, using the `useAnimatedKeyboard` hook disables
the default Android behavior (resizing the view to accomodate keyboard) in the whole app. Using values from `useAnimatedKeyboard` hook you can handle the keyboard yourself. Unmounting all components that use `useAnimatedKeyboard` hook brings back the default Android behavior.

```js
useAnimatedKeyboard() -> [AnimatedKeyboardInfo]
```

### Arguments

#### `options` [AnimatedKeyboardOptions]
Optional object containing additional configuration.

### Returns
Hook `useAnimatedKeyboard` returns an instance of [[AnimatedKeyboardInfo](#animatedkeyboard-object)];
Expand All @@ -31,6 +33,11 @@ Properties:
* `state`: [[SharedValue](../../api/hooks/useSharedValue)] contains `[enum]`
contains current state of the keyboard. Possible states: `{ CLOSED, OPEN, CLOSING, OPENING }`

#### `AnimatedKeyboardOptions: [object]`
Properties:
* `isStatusBarTranslucentAndroid`[bool] - if you want to use transluscent status bar on Android, set this option to `true`. Defaults to `false`. Ignored on iOS.


### Example
```js
function AnimatedKeyboardExample() {
Expand Down
3 changes: 2 additions & 1 deletion ios/native/NativeProxy.mm
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v

static REAKeyboardEventObserver *keyboardObserver = [[REAKeyboardEventObserver alloc] init];
auto subscribeForKeyboardEventsFunction =
[](std::function<void(int keyboardState, int height)> keyboardEventDataUpdater) {
[](std::function<void(int keyboardState, int height)> keyboardEventDataUpdater, bool isStatusBarTranslucent) {
// ignore isStatusBarTranslucent - it's Android only
return [keyboardObserver subscribeForKeyboardEvents:^(int keyboardState, int height) {
keyboardEventDataUpdater(keyboardState, height);
}];
Expand Down
9 changes: 8 additions & 1 deletion react-native-reanimated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,14 @@ declare module 'react-native-reanimated' {
height: SharedValue<number>;
state: SharedValue<KeyboardState>;
};
export function useAnimatedKeyboard(): AnimatedKeyboardInfo;

export interface AnimatedKeyboardOptions {
isStatusBarTranslucentAndroid?: boolean;
}

export function useAnimatedKeyboard(
options?: AnimatedKeyboardOptions
): AnimatedKeyboardInfo;

export function useScrollViewOffset(
aref: RefObject<Animated.ScrollView>
Expand Down
10 changes: 8 additions & 2 deletions src/reanimated2/NativeReanimated/NativeReanimated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,14 @@ export class NativeReanimated {
this.InnerNativeModule.configureProps(uiProps, nativeProps);
}

subscribeForKeyboardEvents(handler: ShareableRef<number>): number {
return this.InnerNativeModule.subscribeForKeyboardEvents(handler);
subscribeForKeyboardEvents(
handler: ShareableRef<number>,
isStatusBarTranslucent: boolean
): number {
return this.InnerNativeModule.subscribeForKeyboardEvents(
handler,
isStatusBarTranslucent
);
}

unsubscribeFromKeyboardEvents(listenerId: number): void {
Expand Down
4 changes: 4 additions & 0 deletions src/reanimated2/commonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,7 @@ export interface MeasuredDimensions {
pageX: number;
pageY: number;
}

export interface AnimatedKeyboardOptions {
isStatusBarTranslucentAndroid?: boolean;
}
13 changes: 10 additions & 3 deletions src/reanimated2/core.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import NativeReanimatedModule from './NativeReanimated';
import { nativeShouldBeMock, shouldBeUseWeb, isWeb } from './PlatformChecker';
import { BasicWorkletFunction, Value3D, ValueRotation } from './commonTypes';
import {
AnimatedKeyboardOptions,
BasicWorkletFunction,
Value3D,
ValueRotation,
} from './commonTypes';
import {
makeShareableCloneRecursive,
makeShareable as makeShareableUnwrapped,
Expand Down Expand Up @@ -121,10 +126,12 @@ export function unregisterEventHandler(id: string): void {
}

export function subscribeForKeyboardEvents(
eventHandler: (state: number, height: number) => void
eventHandler: (state: number, height: number) => void,
options: AnimatedKeyboardOptions
): number {
return NativeReanimatedModule.subscribeForKeyboardEvents(
makeShareableCloneRecursive(eventHandler)
makeShareableCloneRecursive(eventHandler),
options.isStatusBarTranslucentAndroid ?? false
);
}

Expand Down
14 changes: 10 additions & 4 deletions src/reanimated2/hook/useAnimatedKeyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ import {
subscribeForKeyboardEvents,
unsubscribeFromKeyboardEvents,
} from '../core';
import { AnimatedKeyboardInfo, KeyboardState } from '../commonTypes';
import {
AnimatedKeyboardInfo,
AnimatedKeyboardOptions,
KeyboardState,
} from '../commonTypes';

export function useAnimatedKeyboard(): AnimatedKeyboardInfo {
export function useAnimatedKeyboard(
options: AnimatedKeyboardOptions = { isStatusBarTranslucentAndroid: false }
): AnimatedKeyboardInfo {
const ref = useRef<AnimatedKeyboardInfo | null>(null);
const listenerId = useRef<number>(-1);
const isSubscribed = useRef<boolean>(false);
Expand All @@ -20,7 +26,7 @@ export function useAnimatedKeyboard(): AnimatedKeyboardInfo {
'worklet';
keyboardEventData.state.value = state;
keyboardEventData.height.value = height;
});
}, options);
ref.current = keyboardEventData;
isSubscribed.current = true;
}
Expand All @@ -32,7 +38,7 @@ export function useAnimatedKeyboard(): AnimatedKeyboardInfo {
'worklet';
keyboardEventData.state.value = state;
keyboardEventData.height.value = height;
});
}, options);
isSubscribed.current = true;
}
return () => {
Expand Down

0 comments on commit 06635f3

Please sign in to comment.