-
-
Notifications
You must be signed in to change notification settings - Fork 152
Expand file tree
/
Copy pathuseSmoothKeyboardHandler.ts
More file actions
150 lines (136 loc) · 4.21 KB
/
useSmoothKeyboardHandler.ts
File metadata and controls
150 lines (136 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { Platform } from "react-native";
import {
Easing,
useAnimatedReaction,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { useKeyboardHandler } from "../../hooks";
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.
*
* @param handler - Object containing keyboard event handlers.
* @param [deps] - Dependencies array for the effect.
* @example
* ```ts
* useSmoothKeyboardHandler(
* {
* onStart: (e) => {
* "worklet";
*
* // your handler for keyboard start
* },
* },
* [],
* );
* ```
*/
export const useSmoothKeyboardHandler: typeof useKeyboardHandler = (
handler,
deps,
) => {
const target = useSharedValue(-1);
const height = useSharedValue(0);
const persistedHeight = useSharedValue(0);
const animatedKeyboardHeight = useSharedValue(0);
useAnimatedReaction(
() => {
if (IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS) {
return;
}
if (persistedHeight.value === 0) {
return;
}
const event = {
// it'll be always `TELEGRAM_ANDROID_TIMING_CONFIG.duration`, since we're running animation via `withTiming`
duration: TELEGRAM_ANDROID_TIMING_CONFIG.duration,
target: target.value,
height: animatedKeyboardHeight.value,
progress: animatedKeyboardHeight.value / persistedHeight.value,
};
return event;
},
(evt) => {
if (!evt) {
return;
}
handler.onMove?.(evt);
// dispatch `onEnd`
if (evt.height === height.value) {
handler.onEnd?.(evt);
// eslint-disable-next-line react-compiler/react-compiler
persistedHeight.value = height.value;
}
},
// REA uses own version of `DependencyList` and it's not compatible with the same type from React
deps as unknown[],
);
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;
height.value = e.height;
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,
duration: IS_ANDROID_ELEVEN_OR_HIGHER_OR_IOS
? e.duration
: TELEGRAM_ANDROID_TIMING_CONFIG.duration,
});
},
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);
}
},
},
deps,
);
};