diff --git a/FabricExample/package.json b/FabricExample/package.json index 5a3fde26e1..32fc649f36 100644 --- a/FabricExample/package.json +++ b/FabricExample/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@gorhom/bottom-sheet": "5.2.6", + "@legendapp/list": "3.0.0-beta.32", "@lottiefiles/dotlottie-react": "^0.17.10", "@react-native-community/blur": "^4.4.1", "@react-native-masked-view/masked-view": "^0.3.2", @@ -36,7 +37,8 @@ "react-native-toast-message": "^2.3.0", "react-native-web": "^0.21.2", "react-native-webview": "^13.16.0", - "react-native-worklets": "^0.7.1" + "react-native-worklets": "^0.7.1", + "zustand": "^5.0.10" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -88,5 +90,10 @@ }, "engines": { "node": ">=20" + }, + "reanimated": { + "staticFeatureFlags": { + "USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS": true + } } } diff --git a/FabricExample/src/constants/screenNames.ts b/FabricExample/src/constants/screenNames.ts index 7f50da4c13..4b3370632a 100644 --- a/FabricExample/src/constants/screenNames.ts +++ b/FabricExample/src/constants/screenNames.ts @@ -27,4 +27,5 @@ export enum ScreenNames { LIQUID_KEYBOARD = "LIQUID_KEYBOARD", KEYBOARD_SHARED_TRANSITIONS = "KEYBOARD_SHARED_TRANSITIONS", KEYBOARD_EXTENDER = "KEYBOARD_EXTENDER", + CHAT_KIT = "CHAT_KIT", } diff --git a/FabricExample/src/navigation/ExamplesStack/index.tsx b/FabricExample/src/navigation/ExamplesStack/index.tsx index 208e17ee95..1ee1fcc6b9 100644 --- a/FabricExample/src/navigation/ExamplesStack/index.tsx +++ b/FabricExample/src/navigation/ExamplesStack/index.tsx @@ -13,6 +13,7 @@ import InteractiveKeyboard from "../../screens/Examples/InteractiveKeyboard"; import InteractiveKeyboardIOS from "../../screens/Examples/InteractiveKeyboardIOS"; import KeyboardAnimation from "../../screens/Examples/KeyboardAnimation"; import KeyboardAvoidingViewExample from "../../screens/Examples/KeyboardAvoidingView"; +import KeyboardChatScrollViewPlayground from "../../screens/Examples/KeyboardChatScrollView"; import KeyboardExtender from "../../screens/Examples/KeyboardExtender"; import KeyboardSharedTransitionExample from "../../screens/Examples/KeyboardSharedTransitions"; import UseKeyboardState from "../../screens/Examples/KeyboardStateHook"; @@ -54,6 +55,7 @@ export type ExamplesStackParamList = { [ScreenNames.LIQUID_KEYBOARD]: undefined; [ScreenNames.KEYBOARD_SHARED_TRANSITIONS]: undefined; [ScreenNames.KEYBOARD_EXTENDER]: undefined; + [ScreenNames.CHAT_KIT]: undefined; }; const Stack = createStackNavigator(); @@ -138,6 +140,14 @@ const options = { title: "Keyboard Extender", headerShown: false, }, + [ScreenNames.CHAT_KIT]: { + title: "Chat Kit", + headerTintColor: "#fff", + headerStyle: { + backgroundColor: "#3A3A3C", + }, + headerBackTitle: "Back", + }, }; const ExamplesStack = () => ( @@ -267,6 +277,11 @@ const ExamplesStack = () => ( name={ScreenNames.KEYBOARD_EXTENDER} options={options[ScreenNames.KEYBOARD_EXTENDER]} /> + ); diff --git a/FabricExample/src/screens/Examples/KeyboardChatScrollView/VirtualizedListScrollView.tsx b/FabricExample/src/screens/Examples/KeyboardChatScrollView/VirtualizedListScrollView.tsx new file mode 100644 index 0000000000..d84487c12a --- /dev/null +++ b/FabricExample/src/screens/Examples/KeyboardChatScrollView/VirtualizedListScrollView.tsx @@ -0,0 +1,83 @@ +import React, { forwardRef, useCallback, useMemo, useState } from "react"; +import { + type LayoutChangeEvent, + type ScrollViewProps, + StyleSheet, + Text, +} from "react-native"; +import { KeyboardChatScrollView } from "react-native-keyboard-controller"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +import { useChatConfigStore } from "./store"; +import { MARGIN, TEXT_INPUT_HEIGHT } from "./styles"; + +export type VirtualizedListScrollViewRef = React.ElementRef< + typeof KeyboardChatScrollView +>; + +const VirtualizedListScrollView = forwardRef< + VirtualizedListScrollViewRef, + ScrollViewProps +>(({ onLayout: onLayoutProp, ...props }, ref) => { + const [layoutPass, setLayoutPass] = useState(0); + const { bottom } = useSafeAreaInsets(); + const chatKitOffset = bottom - MARGIN; + + const { inverted, freeze, mode, keyboardLiftBehavior } = useChatConfigStore(); + + const contentContainerStyle = useMemo( + () => ({ paddingBottom: TEXT_INPUT_HEIGHT + MARGIN }), + [], + ); + const invertedContentContainerStyle = useMemo( + () => ({ paddingTop: TEXT_INPUT_HEIGHT + MARGIN }), + [], + ); + // on new arch only FlatList supports `inverted` prop + const isInvertedSupported = inverted && mode === "flat" ? inverted : false; + const onLayout = useCallback( + (e: LayoutChangeEvent) => { + setLayoutPass((l) => l + 1); + onLayoutProp?.(e); + }, + [onLayoutProp], + ); + + return ( + <> + + + Layout pass: {layoutPass} + + + ); +}); + +const styles = StyleSheet.create({ + counter: { + position: "absolute", + color: "white", + top: 0, + padding: 12, + backgroundColor: "#3c3c3c", + }, +}); + +export default VirtualizedListScrollView; diff --git a/FabricExample/src/screens/Examples/KeyboardChatScrollView/components/Message.tsx b/FabricExample/src/screens/Examples/KeyboardChatScrollView/components/Message.tsx new file mode 100644 index 0000000000..7d31f7940d --- /dev/null +++ b/FabricExample/src/screens/Examples/KeyboardChatScrollView/components/Message.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { StyleSheet, Text, View } from "react-native"; + +import type { MessageProps } from "../../../../components/Message/types"; + +/** + * Fork of original Message component. Created in order not to break existing e2e tests (requires assets re-generation). + */ +export default function Message({ text, sender }: MessageProps) { + return ( + + {text} + + ); +} + +const container = { + borderRadius: 10, + padding: 10, + margin: 10, + marginVertical: 5, + maxWidth: "80%" as const, +}; + +const styles = StyleSheet.create({ + senderContainer: { + alignSelf: "flex-end", + backgroundColor: "#F8F8FC", + ...container, + }, + recipientContainer: { + alignSelf: "flex-start", + backgroundColor: "#64D2FF", + ...container, + }, + message: { + color: "#000000", + }, +}); diff --git a/FabricExample/src/screens/Examples/KeyboardChatScrollView/config.tsx b/FabricExample/src/screens/Examples/KeyboardChatScrollView/config.tsx new file mode 100644 index 0000000000..df71884789 --- /dev/null +++ b/FabricExample/src/screens/Examples/KeyboardChatScrollView/config.tsx @@ -0,0 +1,166 @@ +import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet"; +import { useNavigation } from "@react-navigation/native"; +import React, { useCallback, useEffect, useRef } from "react"; +import { Button, StyleSheet, Text, TouchableOpacity, View } from "react-native"; +import { KeyboardController } from "react-native-keyboard-controller"; + +import Switch from "../../../components/Switch"; + +import { useChatConfigStore } from "./store"; + +function ConfigSheet() { + const navigation = useNavigation(); + const bottomSheetModalRef = useRef(null); + const inverted = useChatConfigStore((state) => state.inverted); + const setInverted = useChatConfigStore((state) => state.setInverted); + const beginning = useChatConfigStore((state) => state.beginning); + const setBeginning = useChatConfigStore((state) => state.setBeginning); + const setFreeze = useChatConfigStore((state) => state.setFreeze); + const mode = useChatConfigStore((state) => state.mode); + const setMode = useChatConfigStore((state) => state.setMode); + const keyboardLiftBehavior = useChatConfigStore( + (state) => state.keyboardLiftBehavior, + ); + const nextLiftBehavior = useChatConfigStore( + (state) => state.nextLiftBehavior, + ); + + const handlePresentModalPress = useCallback(async () => { + setFreeze(true); + requestAnimationFrame(async () => { + await KeyboardController.dismiss({ keepFocus: true }); + bottomSheetModalRef.current?.expand(); + }); + }, [setFreeze]); + + const handleSheetChange = useCallback( + (index: number) => { + if (index < 0) { + setFreeze(false); + KeyboardController.setFocusTo("current"); + } + }, + [setFreeze], + ); + + useEffect(() => { + navigation.setOptions({ + headerRight: () => ( + + Open config + + ), + }); + }, [navigation]); + + return ( + + +