import React, { useEffect, useRef, useState } from 'react'; import { Animated, Easing, I18nManager, Pressable, ScrollView, View, useWindowDimensions } from 'react-native'; import { Avatar, Divider, Image, Modal, Text, createStyleSheet, useUIKitTheme, } from '@sendbird/uikit-react-native-foundation'; import { SendbirdEmoji, SendbirdReaction, getReactionCount, truncatedCount, useSafeAreaPadding, } from '@sendbird/uikit-utils'; import type { ReactionBottomSheetProps } from './index'; const ReactionUserListBottomSheet = ({ visible, onClose, onDismiss, reactionCtx, chatCtx, localizationCtx, onPressUserProfile, }: ReactionBottomSheetProps) => { const { width } = useWindowDimensions(); const safeArea = useSafeAreaPadding(['left', 'right', 'bottom']); const { colors } = useUIKitTheme(); const [tabIndex, setTabIndex] = useState(0); const scrollRef = useRef(undefined); const tabIndicatorValue = useRef>([]); const tabIndicatorAnimated = useRef({ x: new Animated.Value(0), width: new Animated.Value(0) }).current; const focusedWithLayoutCalculated = useRef(false); const { emojiManager } = chatCtx; const { channel, message, focusIndex } = reactionCtx; const { STRINGS } = localizationCtx; const color = colors.ui.reaction.default; const reactions = message?.reactions ?? []; const focusedReaction = reactions[tabIndex] as SendbirdReaction | undefined; const containerSafeArea = { paddingStart: safeArea.paddingStart + styles.layout.paddingHorizontal, paddingEnd: safeArea.paddingEnd + styles.layout.paddingHorizontal, }; const focusTab = (index: number, animated = true) => { const indicatorValue = tabIndicatorValue.current[index]; if (indicatorValue) { setTabIndex(index); animateTabIndicator(indicatorValue.x, indicatorValue.width, animated); scrollRef.current?.scrollTo({ x: indicatorValue.x, animated }); } }; const animateTabIndicator = (x: number, width: number, animated = true) => { const baseConfig = { duration: animated ? 300 : 0, easing: Easing.inOut(Easing.ease), useNativeDriver: false }; Animated.parallel([ Animated.timing(tabIndicatorAnimated.x, { toValue: x, ...baseConfig }), Animated.timing(tabIndicatorAnimated.width, { toValue: width, ...baseConfig }), ]).start(); }; const layoutCalculated = () => { return tabIndicatorValue.current.filter(Boolean).length === reactions.length; }; useEffect(() => { if (!visible) { tabIndicatorValue.current = []; tabIndicatorAnimated.x = new Animated.Value(0); tabIndicatorAnimated.width = new Animated.Value(0); focusedWithLayoutCalculated.current = false; } }, [visible]); const renderTabs = () => { return ( {reactions.map((reaction, index) => { const isFocused = focusedReaction?.key === reaction.key; const isLastItem = reactions.length - 1 === index; const emoji = emojiManager.allEmojiMap[reaction.key] as SendbirdEmoji | undefined; return ( focusTab(index)} onLayout={(e) => { const indexForLayout = I18nManager.isRTL ? reactions.length - 1 - index : index; tabIndicatorValue.current[indexForLayout] = e.nativeEvent.layout; if (layoutCalculated()) { if (focusedWithLayoutCalculated.current) { // re-calculating layout when screen rotation focusTab(tabIndex, false); } else { focusedWithLayoutCalculated.current = true; focusTab(focusIndex); } } }} > {truncatedCount(getReactionCount(reaction))} ); })} ); }; const renderPage = () => { const userCountDifference = (focusedReaction?.count || 0) - (focusedReaction?.sampledUserInfoList.length || 0); return ( <> {focusedReaction?.sampledUserInfoList.map((reactedUserInfo) => { if (channel?.isGroupChannel()) { return ( { await onClose(); onPressUserProfile(reactedUserInfo); }} style={styles.pageItem} > {reactedUserInfo?.nickname || STRINGS.LABELS.USER_NO_NAME} ); } return null; })} {userCountDifference > 0 && ( {STRINGS.REACTION.MORE_USERS(userCountDifference)} )} ); }; return ( {renderTabs()} {renderPage()} ); }; const styles = createStyleSheet({ layout: { paddingHorizontal: 16, marginEnd: 0, }, container: { overflow: 'hidden', borderTopStartRadius: 8, borderTopEndRadius: 8, paddingTop: 16, alignItems: 'center', }, modal: { alignItems: 'center', justifyContent: 'flex-end', }, tabsContainer: { flexGrow: 1, }, tabsWrapper: { flexGrow: 1, flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'center', height: 44, }, tabItem: { marginEnd: 16, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, tabEmoji: { width: 28, height: 28, marginEnd: 4, }, tabIndicator: { position: 'absolute', bottom: 0, height: 3, }, pageContainer: { height: 216, width: '100%', }, pageItem: { flexDirection: 'row', width: '100%', height: 48, alignItems: 'center', }, avatar: { marginEnd: 16, }, }); export default ReactionUserListBottomSheet;