Skip to content

Commit

Permalink
fix: KeyboardAvoidingView with secureTextEntry (#331)
Browse files Browse the repository at this point in the history
## 📜 Description

Fixed choppy animation for `KeyboardAvoidingView` if child `TextInputs`
are using `secureTextEntry` property.

## 💡 Motivation and Context

The problem which causes jumpy behavior is hidden in fact, that 2
`keyboardWillShow` + `keyboardWillHide` events are emitted instead of a
single one. If we add logger we'll see:

```bash
onStart height 291
onEnd   height 291
onStart height 336
... // onMove
onEnd   height 336
```

And such events are very similar to what happens on Android when
keyboard gets resized. So basically fix consist from two parts.

### Part 1️⃣ 

In `useKeyboardInterpolation` we are not using Android specific
interpolation and are using "progress"-based animation (interpolation
between current keyboard frame and final keyboard frame). It allows us
to have positive value and make animation smooth. However is some cases
we may see a jump occurring for one frame, and for that we apply second
fix.

### Part 2️⃣ 

In second part we are handling "unnecessary" `onEnd` event. Before we
were simply updating `height`/`progress` values in `onEnd` handler - it
was needed to handle cases when `onMove` handler is not called but we
still need to synchronize the state.

In this PR I slightly re-worked approach and now I'm updating it only if
`duration === 0` (i. e. transition was instant).

Closes
#327

## 📢 Changelog

<!-- High level overview of important changes -->
<!-- For example: fixed status bar manipulation; added new types
declarations; -->
<!-- If your changes don't affect one of platform/language below - then
remove this platform/language -->

### JS

- use "progress"-based interpolation for iOS in
`useKeyboardInterpolation`;
- update values in `onEnd` only if `duration` is `0`.

## 🤔 How Has This Been Tested?

Tested on iPhone 15 Pro.

## 📸 Screenshots (if appropriate):

|Before|After|
|-------|-----|
|<video
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/b75f7ca4-fa84-448d-92f8-517b5886b84e">|<video
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/fe3b5cf0-8a61-4af6-ac75-ae3ce64cca72">|

## 📝 Checklist

- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changed
  • Loading branch information
kirillzyusko authored Jan 15, 2024
1 parent 391e293 commit ceb6bfd
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export default function KeyboardAvoidingViewExample({ navigation }: Props) {
placeholder="Password"
placeholderTextColor="#7C7C7C"
style={styles.textInput}
secureTextEntry
/>
</View>
<TouchableOpacity style={styles.button}>
Expand Down
15 changes: 13 additions & 2 deletions src/components/KeyboardAvoidingView/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,19 @@ export const useKeyboardAnimation = () => {

isClosed.value = e.height === 0;

progress.value = e.progress;
height.value = e.height;
// `height` update happens in `onMove` handler
// in `onEnd` we need to update only if `onMove`
// wasn't called (i. e. duration === 0)
//
// we can not call this code without condition below
// because in some corner cases (iOS with `secureTextEntry`)
// `onEnd` can be emitted before `onMove` and in this case
// it may lead to choppy/glitchy/jumpy UI
// see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/327
if (e.duration === 0) {
progress.value = e.progress;
height.value = e.height;
}
},
},
[],
Expand Down
12 changes: 12 additions & 0 deletions src/components/hooks/useKeyboardInterpolation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Platform } from "react-native";
import {
interpolate as interpolateREA,
useSharedValue,
Expand Down Expand Up @@ -37,6 +38,17 @@ const useKeyboardInterpolation = () => {
) => {
"worklet";

// on iOS it's safe to interpolate between 0 and `fullKeyboardSize` because when
// keyboard resized we will not have intermediate values and transition will be instant
// see: https://github.com/kirillzyusko/react-native-keyboard-controller/issues/327
if (Platform.OS === "ios") {
return interpolateREA(
keyboardPosition,
[0, nextKeyboardHeight.value],
output,
);
}

lastInterpolation.value = interpolateREA(
keyboardPosition,
[prevKeyboardHeight.value, nextKeyboardHeight.value],
Expand Down

0 comments on commit ceb6bfd

Please sign in to comment.