Skip to content

Commit

Permalink
refactor: simplify KeyboardAwareScrollView component by moving keyb…
Browse files Browse the repository at this point in the history
…oard movement logic into separate hook (#192)

## 📜 Description

Moved the code for managing different keyboard animations into separate
hook.

## 💡 Motivation and Context

The next implementation of `KeyboardAwareScrollView` will be much more
complex, so right now it's a good time to simplify implementation.

## 📢 Changelog

### JS
- add `useSmoothKeyboardHandler` hook;
- simplify `KeyboardAwareScrollView` by removing unused code;

## 🤔 How Has This Been Tested?

Tested on:
- iPhone 14 Pro (iOS 16.5);
- Pixel 7 Pro (Android 13);

## 📝 Checklist

- [x] CI successfully passed
  • Loading branch information
kirillzyusko authored Jul 18, 2023
1 parent e1f0716 commit c2a9776
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 96 deletions.
Original file line number Diff line number Diff line change
@@ -1,44 +1,21 @@
import React, { FC, useCallback } from 'react';
import {
GestureResponderEvent,
Platform,
ScrollViewProps,
useWindowDimensions,
} from 'react-native';
import {
useKeyboardHandler,
useResizeMode,
} from 'react-native-keyboard-controller';
import { useResizeMode } from 'react-native-keyboard-controller';
import Reanimated, {
Easing,
interpolate,
scrollTo,
useAnimatedRef,
useAnimatedScrollHandler,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
useWorkletCallback,
withTiming,
} from 'react-native-reanimated';
import { useSmoothKeyboardHandler } from './useSmoothKeyboardHandler';

const IS_ANDROID_ELEVEN_OR_HIGHER =
Platform.OS === 'android' && Platform.Version >= 30;
// on these platforms keyboard transitions will be smooth
const IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS =
IS_ANDROID_ELEVEN_OR_HIGHER || Platform.OS === 'ios';
// on Android Telegram is not using androidx.core values and uses custom interpolation
// duration is taken from here: https://github.com/DrKLO/Telegram/blob/e9a35cea54c06277c69d41b8e25d94b5d7ede065/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AdjustPanLayoutHelper.java#L39
// and bezier is taken from: https://github.com/DrKLO/Telegram/blob/e9a35cea54c06277c69d41b8e25d94b5d7ede065/TMessagesProj/src/main/java/androidx/recyclerview/widget/ChatListItemAnimator.java#L40
const TELEGRAM_ANDROID_TIMING_CONFIG = {
duration: 250,
easing: Easing.bezier(
0.19919472913616398,
0.010644531250000006,
0.27920937042459737,
0.91025390625
),
};
const BOTTOM_OFFSET = 50;

const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
Expand All @@ -53,7 +30,6 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
const position = useSharedValue(0);
const fakeViewHeight = useSharedValue(0);
const keyboardHeight = useSharedValue(0);
const animatedKeyboardHeight = useSharedValue(0);

const { height } = useWindowDimensions();

Expand Down Expand Up @@ -97,7 +73,7 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
}
}, []);

useKeyboardHandler(
useSmoothKeyboardHandler(
{
onStart: (e) => {
'worklet';
Expand All @@ -106,25 +82,11 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
// just persist height - later will be used in interpolation
keyboardHeight.value = e.height;
}
// if we are running on Android < 9, then we are using custom interpolation
// to achieve smoother animation and use `animatedKeyboardHeight` as animation
// driver
if (!IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
animatedKeyboardHeight.value = withTiming(
e.height,
TELEGRAM_ANDROID_TIMING_CONFIG
);
}
},
onMove: (e) => {
'worklet';

// if animation will be smooth - we can handle it here
// otherwise we'll use a `animatedKeyboardHeight` value
// in `useDerivedValue`
if (IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
maybeScroll(e.height);
}
maybeScroll(e.height);
},
onEnd: (e) => {
'worklet';
Expand All @@ -135,12 +97,6 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
[height]
);

useDerivedValue(() => {
if (!IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
maybeScroll(animatedKeyboardHeight.value);
}
}, []);

const view = useAnimatedStyle(
() => ({
height: fakeViewHeight.value,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Platform } from 'react-native';
import {
Easing,
useDerivedValue,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import { useKeyboardHandler } from 'react-native-keyboard-controller';

const IS_ANDROID_ELEVEN_OR_HIGHER =
Platform.OS === 'android' && Platform.Version >= 30;
// on these platforms keyboard transitions will be smooth
const IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS =
IS_ANDROID_ELEVEN_OR_HIGHER || Platform.OS === 'ios';
// on Android Telegram is not using androidx.core values and uses custom interpolation
// duration is taken from here: https://github.com/DrKLO/Telegram/blob/e9a35cea54c06277c69d41b8e25d94b5d7ede065/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AdjustPanLayoutHelper.java#L39
// and bezier is taken from: https://github.com/DrKLO/Telegram/blob/e9a35cea54c06277c69d41b8e25d94b5d7ede065/TMessagesProj/src/main/java/androidx/recyclerview/widget/ChatListItemAnimator.java#L40
const TELEGRAM_ANDROID_TIMING_CONFIG = {
duration: 250,
easing: Easing.bezier(
0.19919472913616398,
0.010644531250000006,
0.27920937042459737,
0.91025390625
),
};

/**
* Hook that uses default transitions for iOS and Android > 11, and uses
* custom interpolation on Android < 11 to achieve more smooth animation
*/
export const useSmoothKeyboardHandler: typeof useKeyboardHandler = (
handler,
deps
) => {
const target = useSharedValue(-1);
const persistedHeight = useSharedValue(0);
const animatedKeyboardHeight = useSharedValue(0);

useDerivedValue(() => {
if (!IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
const event = {
// it'll be always 250, since we're running animation via `withTiming` where
// duration in config (TELEGRAM_ANDROID_TIMING_CONFIG.duration) = 250ms
duration: 250,
target: target.value,
height: animatedKeyboardHeight.value,
progress: animatedKeyboardHeight.value / persistedHeight.value,
};
handler.onMove?.(event);

// dispatch `onEnd`
if (animatedKeyboardHeight.value === persistedHeight.value) {
handler.onEnd?.(event);
}
}
}, []);

useKeyboardHandler(
{
onStart: (e) => {
'worklet';

// immediately dispatch onStart/onEnd events if onStart dispatched with the same height
// and don't wait for animation 250ms
if (
!IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS &&
e.height === persistedHeight.value
) {
handler.onStart?.(e);
handler.onEnd?.(e);

return;
}

target.value = e.target;

if (e.height > 0) {
persistedHeight.value = e.height;
}
// if we are running on Android < 9, then we are using custom interpolation
// to achieve smoother animation and use `animatedKeyboardHeight` as animation
// driver
if (!IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
animatedKeyboardHeight.value = withTiming(
e.height,
TELEGRAM_ANDROID_TIMING_CONFIG
);
}

handler.onStart?.(e);
},
onMove: (e) => {
'worklet';

if (IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
handler.onMove?.(e);
}
},
onEnd: (e) => {
'worklet';

if (IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
handler.onEnd?.(e);
}

persistedHeight.value = e.height;
},
},
deps
);
};
Original file line number Diff line number Diff line change
@@ -1,44 +1,21 @@
import React, { FC, useCallback } from 'react';
import {
GestureResponderEvent,
Platform,
ScrollViewProps,
useWindowDimensions,
} from 'react-native';
import {
useKeyboardHandler,
useResizeMode,
} from 'react-native-keyboard-controller';
import { useResizeMode } from 'react-native-keyboard-controller';
import Reanimated, {
Easing,
interpolate,
scrollTo,
useAnimatedRef,
useAnimatedScrollHandler,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
useWorkletCallback,
withTiming,
} from 'react-native-reanimated';
import { useSmoothKeyboardHandler } from './useSmoothKeyboardHandler';

const IS_ANDROID_ELEVEN_OR_HIGHER =
Platform.OS === 'android' && Platform.Version >= 30;
// on these platforms keyboard transitions will be smooth
const IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS =
IS_ANDROID_ELEVEN_OR_HIGHER || Platform.OS === 'ios';
// on Android Telegram is not using androidx.core values and uses custom interpolation
// duration is taken from here: https://github.com/DrKLO/Telegram/blob/e9a35cea54c06277c69d41b8e25d94b5d7ede065/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AdjustPanLayoutHelper.java#L39
// and bezier is taken from: https://github.com/DrKLO/Telegram/blob/e9a35cea54c06277c69d41b8e25d94b5d7ede065/TMessagesProj/src/main/java/androidx/recyclerview/widget/ChatListItemAnimator.java#L40
const TELEGRAM_ANDROID_TIMING_CONFIG = {
duration: 250,
easing: Easing.bezier(
0.19919472913616398,
0.010644531250000006,
0.27920937042459737,
0.91025390625
),
};
const BOTTOM_OFFSET = 50;

const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
Expand All @@ -53,7 +30,6 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
const position = useSharedValue(0);
const fakeViewHeight = useSharedValue(0);
const keyboardHeight = useSharedValue(0);
const animatedKeyboardHeight = useSharedValue(0);

const { height } = useWindowDimensions();

Expand Down Expand Up @@ -97,7 +73,7 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
}
}, []);

useKeyboardHandler(
useSmoothKeyboardHandler(
{
onStart: (e) => {
'worklet';
Expand All @@ -106,25 +82,11 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
// just persist height - later will be used in interpolation
keyboardHeight.value = e.height;
}
// if we are running on Android < 9, then we are using custom interpolation
// to achieve smoother animation and use `animatedKeyboardHeight` as animation
// driver
if (!IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
animatedKeyboardHeight.value = withTiming(
e.height,
TELEGRAM_ANDROID_TIMING_CONFIG
);
}
},
onMove: (e) => {
'worklet';

// if animation will be smooth - we can handle it here
// otherwise we'll use a `animatedKeyboardHeight` value
// in `useDerivedValue`
if (IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
maybeScroll(e.height);
}
maybeScroll(e.height);
},
onEnd: (e) => {
'worklet';
Expand All @@ -135,12 +97,6 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
[height]
);

useDerivedValue(() => {
if (!IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
maybeScroll(animatedKeyboardHeight.value);
}
}, []);

const view = useAnimatedStyle(
() => ({
height: fakeViewHeight.value,
Expand Down
Loading

0 comments on commit c2a9776

Please sign in to comment.