import React, { useMemo } from 'react'; import { Image, ImageProps, ImageStyle, Pressable, StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle, } from 'react-native'; import type { Attachment } from 'stream-chat'; import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, } from '../../../contexts/messageContext/MessageContext'; import { MessagesContextValue, useMessagesContext, } from '../../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { Link } from '../../../icons/link'; import { primitives } from '../../../theme'; import { FileTypes } from '../../../types/types'; import { makeImageCompatibleUrl } from '../../../utils/utils'; import { VideoPlayIndicator } from '../../ui'; import { ImageBackground } from '../../UIComponents/ImageBackground'; import { openUrlSafely } from '../utils/openUrlSafely'; export type URLPreviewPropsWithContext = { ImageComponent?: React.ComponentType; } & Pick & Pick< MessagesContextValue, 'additionalPressableProps' | 'myMessageTheme' | 'isAttachmentEqual' > & { attachment: Attachment; channelId: string | undefined; messageId: string | undefined; // TODO: Think of a better way to handle styles styles?: Partial<{ cardCover: StyleProp; cardFooter: StyleProp; container: StyleProp; description: StyleProp; linkPreview: StyleProp; linkPreviewText: StyleProp; title: StyleProp; }>; }; const URLPreviewWithContext = (props: URLPreviewPropsWithContext) => { const { attachment, additionalPressableProps, ImageComponent = Image, onLongPress, onPress, onPressIn, preventPress, styles: stylesProp = {}, } = props; const { theme: { semantics }, } = useTheme(); const { image_url, og_scrape_url, text, thumb_url, title, type } = attachment; const { theme: { messageItemView: { card: { container, cover, footer: { description, title: titleStyle, ...footerStyle }, linkPreview, linkPreviewText, }, }, }, } = useTheme(); const styles = useStyles(); const uri = image_url || thumb_url; const defaultOnPress = () => openUrlSafely(og_scrape_url || uri); const isVideoCard = type === FileTypes.Video && og_scrape_url; return ( { if (onLongPress) { onLongPress({ additionalInfo: { url: og_scrape_url }, emitter: 'urlPreview', event, }); } }} onPress={(event) => { if (onPress) { onPress({ additionalInfo: { url: og_scrape_url }, defaultHandler: defaultOnPress, emitter: 'urlPreview', event, }); } }} onPressIn={(event) => { if (onPressIn) { onPressIn({ additionalInfo: { url: og_scrape_url }, defaultHandler: defaultOnPress, emitter: 'urlPreview', event, }); } }} style={[styles.container, container, stylesProp.container]} testID='card-attachment' {...additionalPressableProps} > {uri && ( {isVideoCard ? : null} )} {title ? ( {title} ) : null} {text ? ( {text} ) : null} {og_scrape_url} ); }; const areEqual = (prevProps: URLPreviewPropsWithContext, nextProps: URLPreviewPropsWithContext) => { const { attachment: prevAttachment, myMessageTheme: prevMyMessageTheme, isAttachmentEqual, } = prevProps; const { attachment: nextAttachment, myMessageTheme: nextMyMessageTheme } = nextProps; const attachmentEqual = prevAttachment.image_url === nextAttachment.image_url && prevAttachment.thumb_url === nextAttachment.thumb_url && prevAttachment.type === nextAttachment.type && prevAttachment.og_scrape_url === nextAttachment.og_scrape_url && prevAttachment.text === nextAttachment.text && prevAttachment.title === nextAttachment.title; if (!attachmentEqual) { return false; } if (isAttachmentEqual) { return isAttachmentEqual(prevAttachment, nextAttachment); } const messageThemeEqual = JSON.stringify(prevMyMessageTheme) === JSON.stringify(nextMyMessageTheme); if (!messageThemeEqual) { return false; } return true; }; const MemoizedURLPreview = React.memo( URLPreviewWithContext, areEqual, ) as typeof URLPreviewWithContext; export type URLPreviewProps = Partial & { attachment: Attachment; }; /** * UI component for card in attachments. */ export const URLPreview = (props: URLPreviewProps) => { const { ImageComponent } = useComponentsContext(); const { message, onLongPress, onPress, onPressIn, preventPress } = useMessageContext(); const { additionalPressableProps, isAttachmentEqual, myMessageTheme } = useMessagesContext(); return ( ); }; const useStyles = () => { const { theme: { semantics }, } = useTheme(); const { isMyMessage } = useMessageContext(); return useMemo( () => StyleSheet.create({ container: { maxWidth: 256, // TODO: Fix this borderRadius: primitives.radiusLg, backgroundColor: isMyMessage ? semantics.chatBgAttachmentOutgoing : semantics.chatBgAttachmentIncoming, overflow: 'hidden', }, cardCover: { minWidth: 256, minHeight: 144, alignSelf: 'stretch', alignItems: 'center', justifyContent: 'center', }, cardFooter: { justifyContent: 'space-between', gap: primitives.spacingXxs, padding: primitives.spacingSm, }, title: { color: semantics.chatTextIncoming, fontSize: primitives.typographyFontSizeSm, fontWeight: primitives.typographyFontWeightSemiBold, lineHeight: primitives.typographyLineHeightTight, }, description: { color: semantics.chatTextIncoming, fontSize: primitives.typographyFontSizeXs, fontWeight: primitives.typographyFontWeightRegular, lineHeight: primitives.typographyLineHeightTight, }, linkPreview: { flexDirection: 'row', alignItems: 'center', gap: primitives.spacingXxs, }, linkPreviewText: { color: semantics.chatTextIncoming, fontSize: primitives.typographyFontSizeXs, fontWeight: primitives.typographyFontWeightRegular, lineHeight: primitives.typographyLineHeightTight, flexShrink: 1, }, }), [isMyMessage, semantics], ); }; URLPreview.displayName = 'URLPreview{messageItemView{card}}';