import { StyleSheet, View, ViewStyle } from 'react-native' import Svg, { Path } from 'react-native-svg' import { useTheme } from '../../hooks' import { MessageResource } from '../../types' import { CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL, MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH, } from '../../utils/styles' const MY_REPLY_CONNECTOR_WIDTH = 38 const CONNECTOR_BORDER_WIDTH = 4 const OFFSET_CONNECTOR_CONTAINER = CONNECTOR_BORDER_WIDTH / 2 // Accounts for the border width ensuring the connectors are centered under the avatar export const AVATAR_CONNECTOR_SPACING = 8 interface ReplyConnectorProps { message: MessageResource messageBubbleHeight: number } export function TheirReplyConnector({ message, messageBubbleHeight }: ReplyConnectorProps) { const styles = useStyles() const { nextRendersAuthor, threadPosition, renderAuthor, isReplyShadowMessage } = message if (messageBubbleHeight === 0) return null // Prevents UI shifting const connectorMap: Record = { shortTailCurve: , shortHeadCurve: , verticalLine: , default: null, } const getConnectorKey = () => { if (threadPosition === 'first' && !renderAuthor) return 'shortHeadCurve' if (threadPosition === 'last' && !renderAuthor) return 'shortTailCurve' if ( (threadPosition === 'first' && renderAuthor) || threadPosition === 'center' || isReplyShadowMessage ) return 'verticalLine' return 'default' } const ConnectorComponent = connectorMap[getConnectorKey()] if (!ConnectorComponent) return null const spacerStyle = nextRendersAuthor ? styles.theirReplyConnectorSpacer : null return ( {ConnectorComponent} ) } export function MyReplyConnector({ message, messageBubbleHeight }: ReplyConnectorProps) { const styles = useStyles() const { nextRendersAuthor, threadPosition, prevIsMyReply, nextIsMyReply, isReplyShadowMessage } = message if (messageBubbleHeight === 0) return null // Prevents UI shifting const connectorMap: Record = { longTailCurve: ( ), longHeadCurve: ( ), verticalLine: , swirl: , default: null, } const getConnectorKey = () => { if (threadPosition === 'first' || isReplyShadowMessage) return 'longHeadCurve' if (threadPosition === 'center' && prevIsMyReply && nextIsMyReply) return 'verticalLine' if (threadPosition === 'center' && (!prevIsMyReply || !nextIsMyReply)) return 'swirl' if (threadPosition === 'last') return 'longTailCurve' return 'default' } const ConnectorComponent = connectorMap[getConnectorKey()] if (!ConnectorComponent) return null const spacerStyle = nextRendersAuthor ? styles.myReplyConnectorSpacer : null return ( {ConnectorComponent} ) } function VerticalLineConnector({ style }: { style?: ViewStyle }) { const styles = useStyles() return } function LongHeadCurveConnector({ height, style }: { height: number; style?: ViewStyle }) { const styles = useStyles() const { colors } = useTheme() return ( ) } function LongTailCurveConnector({ height, style }: { height: number; style?: ViewStyle }) { const styles = useStyles() const { colors } = useTheme() return ( ) } function ShortHeadCurveConnector({ height }: { height: number }) { const styles = useStyles() const { colors } = useTheme() return ( ) } function ShortTailCurveConnector({ height }: { height: number }) { const styles = useStyles() const { colors } = useTheme() return ( ) } function SwirlConnector({ style, height }: { style?: ViewStyle; height: number }) { const styles = useStyles() const { colors } = useTheme() return ( ) } // TODO: Remove when Services React Native bumps to React Native 0.74 or higher. // This fixes a React Native bug where using `flexDirection: 'row-reverse'` also switches the `left`/`right` absolute positioning values. // The wrapper ensures the MyReplyConnector is always positioned to the left of the message bubble on older versions of React Native. // Long term fix is described here: https://reactnative.dev/blog/2024/04/22/release-0.74#new-layout-behaviors function EnforceLeftPositionForMyReplyConnector({ children }: { children: React.ReactNode }) { const styles = useStyles() return ( {children} ) } const useStyles = () => { const { colors } = useTheme() const borderColor = colors.borderColorDefaultBase return StyleSheet.create({ theirReplyConnectorContainer: { width: '50%', marginRight: OFFSET_CONNECTOR_CONTAINER, alignSelf: 'flex-end', flex: 1, }, theirReplyConnectorSpacer: { paddingBottom: AVATAR_CONNECTOR_SPACING, }, myReplyConnectorContainer: { width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH, position: 'absolute', left: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL - OFFSET_CONNECTOR_CONTAINER, top: 0, bottom: 0, }, myReplyConnectorPosition: { left: '50%', }, myReplyConnectorSpacer: { bottom: AVATAR_CONNECTOR_SPACING, }, verticalLineConnector: { flex: 1, borderLeftWidth: CONNECTOR_BORDER_WIDTH, borderLeftColor: borderColor, }, longHeadCurveConnector: { flex: 1, }, shortHeadCurveConnector: { flex: 1, }, swirlConnectorContainer: { flex: 1, }, svgConnnectorVerticalLineHead: { backgroundColor: borderColor, width: CONNECTOR_BORDER_WIDTH, flex: 1, marginBottom: -1, // Ensures there is no gap between the vertical line and the swirl connector }, svgConnnectorVerticalLineTail: { backgroundColor: borderColor, width: CONNECTOR_BORDER_WIDTH, flex: 1, marginTop: -1, // Ensures there is no gap between the vertical line and the swirl connector }, // TODO: Remove when Services React Native bumps to React Native 0.74 or higher. resetPosition: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, }, }) }