Skip to content

Instantly share code, notes, and snippets.

@Jalson1982
Created August 29, 2023 08:42
Show Gist options
  • Save Jalson1982/1863a16f5908134de064ecd36e020e3e to your computer and use it in GitHub Desktop.
Save Jalson1982/1863a16f5908134de064ecd36e020e3e to your computer and use it in GitHub Desktop.
import React, {FC, useCallback} from 'react';
import {
GestureResponderEvent,
ScrollViewProps,
useWindowDimensions,
StyleSheet,
} from 'react-native';
import {useResizeMode} from 'react-native-keyboard-controller';
import Reanimated, {
interpolate,
scrollTo,
useAnimatedRef,
useAnimatedScrollHandler,
useAnimatedStyle,
useSharedValue,
useWorkletCallback,
} from 'react-native-reanimated';
import {useSmoothKeyboardHandler} from './useSmoothKeyboardHandler';
const BOTTOM_OFFSET = 50;
const KeyboardAwareScrollView: FC<ScrollViewProps> = ({children, ...rest}) => {
useResizeMode();
const scrollViewAnimatedRef = useAnimatedRef<Reanimated.ScrollView>();
const scrollPosition = useSharedValue(0);
const click = useSharedValue(0);
const position = useSharedValue(0);
const fakeViewHeight = useSharedValue(0);
const keyboardHeight = useSharedValue(0);
const {height} = useWindowDimensions();
const onScroll = useAnimatedScrollHandler(
{
onScroll: e => {
position.value = e.contentOffset.y;
},
},
[],
);
const onContentTouch = useCallback(
(e: GestureResponderEvent) => {
// to prevent clicks when keyboard is animating
if (keyboardHeight.value === 0) {
click.value = e.nativeEvent.pageY;
scrollPosition.value = position.value;
}
},
[click, keyboardHeight, position, scrollPosition],
);
/**
* Function that will scroll a ScrollView as keyboard gets moving
*/
const maybeScroll = useWorkletCallback((e: number) => {
'worklet';
fakeViewHeight.value = e;
const visibleRect = height - keyboardHeight.value;
if (visibleRect - click.value <= BOTTOM_OFFSET) {
const interpolatedScrollTo = interpolate(
e,
[0, keyboardHeight.value],
[0, keyboardHeight.value - (height - click.value) + BOTTOM_OFFSET],
);
const targetScrollY =
Math.max(interpolatedScrollTo, 0) + scrollPosition.value;
scrollTo(scrollViewAnimatedRef, 0, targetScrollY, false);
}
}, []);
useSmoothKeyboardHandler(
{
onStart: e => {
'worklet';
if (e.height > 0) {
keyboardHeight.value = e.height;
}
},
onMove: e => {
'worklet';
maybeScroll(e.height);
},
onEnd: e => {
'worklet';
keyboardHeight.value = e.height;
},
},
[height],
);
const view = useAnimatedStyle(
() => ({
height: fakeViewHeight.value,
}),
[],
);
return (
<Reanimated.ScrollView
ref={scrollViewAnimatedRef}
{...rest}
onScroll={onScroll}
onTouchStart={onContentTouch}
scrollEventThrottle={16}>
{children}
<Reanimated.View style={view} />
</Reanimated.ScrollView>
);
};
export const styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default KeyboardAwareScrollView;
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;
const IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS =
IS_ANDROID_ELEVEN_OR_HIGHER || Platform.OS === 'ios';
const TELEGRAM_ANDROID_TIMING_CONFIG = {
duration: 250,
easing: Easing.bezier(
0.19919472913616398,
0.010644531250000006,
0.27920937042459737,
0.91025390625,
),
};
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 = {
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';
if (
!IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS &&
e.height === persistedHeight.value
) {
handler.onStart?.(e);
handler.onEnd?.(e);
return;
}
if (e.height > 0) {
persistedHeight.value = e.height;
}
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,
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment