Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/chat/ChatScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
GiftedChat,
type GiftedChatProps,
Bubble,
BubbleProps,
} from 'react-native-gifted-chat';
import TypingIndicator from 'react-native-gifted-chat/lib/TypingIndicator';
import { FirestoreServices } from '../services/firebase';
Expand All @@ -43,6 +44,7 @@ import InputToolbar, { IInputToolbar } from './components/InputToolbar';
type ChildrenProps = {
onSend: (messages: MessageProps) => Promise<void>;
};
import { ICustomBubbleWithLinkPreviewStyles } from './components/bubble/CustomBubbleWithLinkPreview';

interface ChatScreenProps extends GiftedChatProps {
style?: StyleProp<ViewStyle>;
Expand All @@ -65,6 +67,12 @@ interface ChatScreenProps extends GiftedChatProps {
messageStatusEnable?: boolean;
customMessageStatus?: (hasUnread: boolean) => JSX.Element;
children?: (props: ChildrenProps) => ReactNode | ReactNode;
customLinkPreviewStyles?: ICustomBubbleWithLinkPreviewStyles;
customLinkPreview: (
urls: string[],
bubbleMessage: BubbleProps<MessageProps>
) => JSX.Element;
enableLinkPreview?: boolean;
}

export const ChatScreen: React.FC<ChatScreenProps> = ({
Expand All @@ -83,6 +91,9 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({
enableTyping = true,
typingTimeoutSeconds = DEFAULT_TYPING_TIMEOUT_SECONDS,
messageStatusEnable = true,
customLinkPreviewStyles,
customLinkPreview,
enableLinkPreview = true,
...props
}) => {
const { userInfo, chatDispatch } = useChatContext();
Expand Down Expand Up @@ -307,6 +318,9 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({
unReadSeenMessage={props.unReadSeenMessage}
customMessageStatus={props.customMessageStatus}
messageStatusEnable={messageStatusEnable}
customLinkPreviewStyles={customLinkPreviewStyles}
customLinkPreview={customLinkPreview}
enableLinkPreview={enableLinkPreview}
/>
);
};
Expand Down
22 changes: 20 additions & 2 deletions src/chat/components/bubble/CustomBubble.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import React from 'react';
import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native';
import { MessageTypes, type MessageProps } from '../../../interfaces';
import { Bubble } from 'react-native-gifted-chat';
import { Bubble, BubbleProps } from 'react-native-gifted-chat';
import {
CustomImageVideoBubble,
CustomImageVideoBubbleProps,
} from './CustomImageVideoBubble';
import MessageStatus from '../MessageStatus';
import {
ICustomBubbleWithLinkPreviewStyles,
CustomBubbleWithLinkPreview,
} from './CustomBubbleWithLinkPreview';

interface CustomBubbleProps {
bubbleMessage: Bubble<MessageProps>['props'];
Expand All @@ -20,6 +24,12 @@ interface CustomBubbleProps {
unReadSeenMessage?: string;
messageStatusEnable: boolean;
customMessageStatus?: (hasUnread: boolean) => JSX.Element;
customLinkPreviewStyles?: ICustomBubbleWithLinkPreviewStyles;
customLinkPreview: (
urls: string[],
bubbleMessage: BubbleProps<MessageProps>
) => JSX.Element;
enableLinkPreview: boolean;
}

export const CustomBubble: React.FC<CustomBubbleProps> = ({
Expand All @@ -34,6 +44,9 @@ export const CustomBubble: React.FC<CustomBubbleProps> = ({
unReadSentMessage,
messageStatusEnable,
customMessageStatus,
customLinkPreviewStyles,
customLinkPreview,
enableLinkPreview,
}) => {
const styleBuble = {
left: { backgroundColor: 'transparent' },
Expand Down Expand Up @@ -98,7 +111,12 @@ export const CustomBubble: React.FC<CustomBubbleProps> = ({
default: {
return (
<View>
<Bubble {...bubbleMessage} />
<CustomBubbleWithLinkPreview
bubbleMessage={bubbleMessage}
customBubbleWithLinkPreviewStyles={customLinkPreviewStyles}
customBubbleWithLinkPreview={customLinkPreview}
enableLinkPreview={enableLinkPreview}
/>
{ViewMessageStatus}
</View>
);
Expand Down
172 changes: 172 additions & 0 deletions src/chat/components/bubble/CustomBubbleWithLinkPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import React, { useCallback } from 'react';
import {
Linking,
StyleProp,
StyleSheet,
Text,
View,
ViewStyle,
} from 'react-native';
import { Bubble, BubbleProps } from 'react-native-gifted-chat';
import type { MessageProps } from '../../../interfaces';
import { LinkPreview } from '../linkPreview';

export interface ICustomBubbleWithLinkPreviewStyles {
customContainerStyle?: StyleProp<ViewStyle>;
customPreviewContainerStyle?: StyleProp<ViewStyle>;
customLinkTextStyle?: StyleProp<ViewStyle>;
customMessagePreviewStyle?: StyleProp<ViewStyle>;
}
interface ICustomBubbleWithLinkPreviewProps {
bubbleMessage: BubbleProps<MessageProps>;
customBubbleWithLinkPreviewStyles?: ICustomBubbleWithLinkPreviewStyles;
customBubbleWithLinkPreview: (
urls: string[],
bubbleMessage: BubbleProps<MessageProps>
) => JSX.Element;
enableLinkPreview: boolean;
}

const urlRegex = /(https?:\/\/[^\s]+)/g;

const handleLinkPress = (url: string) => {
Linking.openURL(url).catch((err) => console.error('Error opening URL:', err));
};

export const CustomBubbleWithLinkPreview: React.FC<
ICustomBubbleWithLinkPreviewProps
> = (props) => {
const {
bubbleMessage,
customBubbleWithLinkPreviewStyles,
customBubbleWithLinkPreview,
enableLinkPreview,
} = props;
const { currentMessage } = bubbleMessage;
const urls = currentMessage?.text.match(urlRegex);

const {
customContainerStyle,
customPreviewContainerStyle,
customLinkTextStyle,
customMessagePreviewStyle,
} = customBubbleWithLinkPreviewStyles || {};

const renderTextWithLinks = useCallback(
(text: string) => {
const parts = text.split(urlRegex);
return parts.map((part, index) => {
if (urlRegex.test(part)) {
return (
<Text
key={index}
onPress={() => handleLinkPress(part)}
style={[styles.linkText, customLinkTextStyle]}
>
{part}
</Text>
);
}
return <Text key={index}>{part}</Text>;
});
},
[customLinkTextStyle]
);

const renderPreview = useCallback(
(urlsLink: string[]) => {
const firstUrl = urlsLink[0];

return (
<View
style={[
styles.bubbleContainer,
customContainerStyle,
bubbleMessage.position === 'left'
? styles.flexStart
: styles.flexEnd,
]}
>
<View style={styles.bubble}>
<Text style={[styles.messagePreview, customMessagePreviewStyle]}>
{!!currentMessage?.text &&
renderTextWithLinks(currentMessage.text)}
</Text>
{!!firstUrl && enableLinkPreview && (
<LinkPreview
containerStyle={[
styles.previewContainer,
customPreviewContainerStyle,
]}
enableAnimation
text={firstUrl}
/>
)}
</View>
</View>
);
},
[
bubbleMessage.position,
currentMessage?.text,
customContainerStyle,
customMessagePreviewStyle,
customPreviewContainerStyle,
enableLinkPreview,
renderTextWithLinks,
]
);

if (!urls) {
return <Bubble {...bubbleMessage} />;
}

return customBubbleWithLinkPreview
? customBubbleWithLinkPreview(urls, bubbleMessage)
: renderPreview(urls);
};

const styles = StyleSheet.create({
container: {
flex: 1,
},
bubble: {
padding: 15,
maxWidth: '70%',
backgroundColor: '#e1ffc7',
borderRadius: 20,
overflow: 'hidden',
borderColor: '#d3d3d3',
borderWidth: 1,
},
bubbleContainer: {
flexDirection: 'row',
marginVertical: 5,
},
flexEnd: {
justifyContent: 'flex-end',
},
flexStart: {
justifyContent: 'flex-start',
},
containerPreview: {
padding: 10,
},
previewContainer: {
backgroundColor: 'white',
borderRadius: 20,
marginTop: 16,
},
messagePreview: {
color: 'black',
fontSize: 16,
},
textPreview: {
color: 'blue',
textDecorationLine: 'underline',
},
linkText: {
color: 'blue',
textDecorationLine: 'underline',
},
});
1 change: 1 addition & 0 deletions src/chat/components/bubble/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './CustomBubble';
export * from './CustomImageVideoBubble';
export * from './CustomBubbleWithLinkPreview';
Loading