@codewithvincent/react-native-love-chat 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +152 -0
  4. package/dist/Chat.d.ts +3 -0
  5. package/dist/Chat.js +78 -0
  6. package/dist/components/Bubble.d.ts +21 -0
  7. package/dist/components/Bubble.js +55 -0
  8. package/dist/components/Composer.d.ts +14 -0
  9. package/dist/components/Composer.js +44 -0
  10. package/dist/components/FooterReplyPreview.d.ts +7 -0
  11. package/dist/components/FooterReplyPreview.js +130 -0
  12. package/dist/components/Icons.d.ts +21 -0
  13. package/dist/components/Icons.js +71 -0
  14. package/dist/components/InputToolbar.d.ts +17 -0
  15. package/dist/components/InputToolbar.js +110 -0
  16. package/dist/components/Message.d.ts +21 -0
  17. package/dist/components/Message.js +131 -0
  18. package/dist/components/MessageList.d.ts +6 -0
  19. package/dist/components/MessageList.js +111 -0
  20. package/dist/components/MessageStatus.d.ts +6 -0
  21. package/dist/components/MessageStatus.js +59 -0
  22. package/dist/components/ReactionBubble.d.ts +55 -0
  23. package/dist/components/ReactionBubble.js +262 -0
  24. package/dist/components/ReplyPreview.d.ts +7 -0
  25. package/dist/components/ReplyPreview.js +102 -0
  26. package/dist/components/UploadFooter.d.ts +4 -0
  27. package/dist/components/UploadFooter.js +33 -0
  28. package/dist/components/adapters/UniversalAudio.d.ts +8 -0
  29. package/dist/components/adapters/UniversalAudio.js +23 -0
  30. package/dist/components/adapters/UniversalBlurView.d.ts +3 -0
  31. package/dist/components/adapters/UniversalBlurView.js +26 -0
  32. package/dist/components/adapters/UniversalVideo.d.ts +8 -0
  33. package/dist/components/adapters/UniversalVideo.js +20 -0
  34. package/dist/components/media/AudioCard.d.ts +7 -0
  35. package/dist/components/media/AudioCard.js +187 -0
  36. package/dist/components/media/FileCard.d.ts +8 -0
  37. package/dist/components/media/FileCard.js +68 -0
  38. package/dist/components/media/ImageCard.d.ts +9 -0
  39. package/dist/components/media/ImageCard.js +76 -0
  40. package/dist/components/media/VideoCard.d.ts +10 -0
  41. package/dist/components/media/VideoCard.js +232 -0
  42. package/dist/index.d.ts +9 -0
  43. package/dist/index.js +36 -0
  44. package/dist/types.d.ts +71 -0
  45. package/dist/types.js +2 -0
  46. package/dist/utils/index.d.ts +14 -0
  47. package/dist/utils/index.js +44 -0
  48. package/dist/utils/theme.d.ts +77 -0
  49. package/dist/utils/theme.js +55 -0
  50. package/package.json +67 -0
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { IMessage } from '../types';
3
+ interface InputToolbarProps {
4
+ onSend?: (messages: any[]) => void;
5
+ text?: string;
6
+ onTextChanged?: (text: string) => void;
7
+ renderComposer?: (props: any) => React.ReactNode;
8
+ renderSend?: (props: any) => React.ReactNode;
9
+ user: any;
10
+ placeholder?: string;
11
+ onPressAttachment?: (type: string) => void;
12
+ replyMessage?: IMessage | null;
13
+ onClearReply?: () => void;
14
+ renderUploadFooter?: (props: any) => React.ReactNode;
15
+ }
16
+ declare const InputToolbar: (props: InputToolbarProps) => import("react/jsx-runtime").JSX.Element;
17
+ export default InputToolbar;
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const react_native_1 = require("react-native");
9
+ const Composer_1 = __importDefault(require("./Composer"));
10
+ const theme_1 = require("../utils/theme");
11
+ const Icons_1 = require("./Icons");
12
+ const UploadFooter_1 = __importDefault(require("./UploadFooter"));
13
+ const FooterReplyPreview_1 = __importDefault(require("./FooterReplyPreview"));
14
+ const InputToolbar = (props) => {
15
+ const theme = (0, theme_1.useTheme)();
16
+ const { onSend, text, renderComposer, renderSend, user, onPressAttachment, replyMessage, onClearReply, renderUploadFooter, ...rest } = props;
17
+ const [showUploadFooter, setShowUploadFooter] = (0, react_1.useState)(false);
18
+ const [keyboardHeight] = (0, react_1.useState)(new react_native_1.Animated.Value(0));
19
+ (0, react_1.useEffect)(() => {
20
+ const showSub = react_native_1.Keyboard.addListener(react_native_1.Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', e => {
21
+ react_native_1.Animated.timing(keyboardHeight, {
22
+ toValue: e.endCoordinates.height,
23
+ duration: e.duration || 250,
24
+ useNativeDriver: false,
25
+ }).start();
26
+ setShowUploadFooter(false);
27
+ });
28
+ const hideSub = react_native_1.Keyboard.addListener(react_native_1.Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', e => {
29
+ react_native_1.Animated.timing(keyboardHeight, {
30
+ toValue: 0,
31
+ duration: e.duration || 250,
32
+ useNativeDriver: false,
33
+ }).start();
34
+ });
35
+ return () => {
36
+ showSub.remove();
37
+ hideSub.remove();
38
+ };
39
+ }, []);
40
+ const handleSend = () => {
41
+ if (text && text.trim().length > 0) {
42
+ const message = {
43
+ _id: Math.round(Math.random() * 1000000),
44
+ text: text.trim(),
45
+ createdAt: new Date(),
46
+ user: user,
47
+ replyTo: replyMessage,
48
+ };
49
+ onSend === null || onSend === void 0 ? void 0 : onSend([message]);
50
+ onClearReply === null || onClearReply === void 0 ? void 0 : onClearReply();
51
+ }
52
+ };
53
+ const toggleUploadFooter = () => {
54
+ if (showUploadFooter) {
55
+ setShowUploadFooter(false);
56
+ react_native_1.Keyboard.dismiss();
57
+ }
58
+ else {
59
+ setShowUploadFooter(true);
60
+ react_native_1.Keyboard.dismiss();
61
+ }
62
+ };
63
+ const handleActionPress = (type) => {
64
+ onPressAttachment === null || onPressAttachment === void 0 ? void 0 : onPressAttachment(type);
65
+ setShowUploadFooter(false);
66
+ };
67
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { children: [replyMessage && ((0, jsx_runtime_1.jsx)(FooterReplyPreview_1.default, { chatMessage: replyMessage, clearReply: onClearReply, userId: user._id })), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, { borderTopColor: theme.colors.borderGray, backgroundColor: theme.colors.white }], children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: toggleUploadFooter, style: styles.addButton, children: (0, jsx_runtime_1.jsx)(Icons_1.PlusIcon, { size: 24, color: theme.colors.darkRed }) }), renderComposer ? (renderComposer(props)) : ((0, jsx_runtime_1.jsx)(Composer_1.default, { ...rest, text: text, onSend: handleSend, textInputProps: {
68
+ onFocus: () => setShowUploadFooter(false)
69
+ } })), renderSend ? (renderSend({ ...props, onSend: handleSend })) : ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: handleSend, style: styles.sendButton, disabled: !text || text.trim().length === 0, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.sendIconWrapper, { backgroundColor: theme.colors.darkRed }, (!text || text.trim().length === 0) && { backgroundColor: theme.colors.gray }], children: (0, jsx_runtime_1.jsx)(Icons_1.SendIcon, { size: 18, color: theme.colors.white }) }) }))] }), showUploadFooter && (renderUploadFooter ? (renderUploadFooter({
70
+ onActionPress: handleActionPress,
71
+ theme,
72
+ })) : ((0, jsx_runtime_1.jsx)(UploadFooter_1.default, { onActionPress: handleActionPress })))] }));
73
+ };
74
+ const styles = react_native_1.StyleSheet.create({
75
+ container: {
76
+ borderTopWidth: react_native_1.StyleSheet.hairlineWidth,
77
+ borderTopColor: theme_1.defaultTheme.colors.borderGray,
78
+ backgroundColor: theme_1.defaultTheme.colors.white,
79
+ flexDirection: 'row',
80
+ alignItems: 'flex-end',
81
+ paddingVertical: 6,
82
+ paddingHorizontal: 8,
83
+ },
84
+ addButton: {
85
+ paddingHorizontal: 8,
86
+ paddingVertical: 10,
87
+ justifyContent: 'center',
88
+ alignItems: 'center',
89
+ marginBottom: 0,
90
+ },
91
+ sendButton: {
92
+ paddingHorizontal: 8,
93
+ paddingVertical: 10,
94
+ justifyContent: 'center',
95
+ alignItems: 'center',
96
+ marginBottom: 0,
97
+ },
98
+ sendIconWrapper: {
99
+ backgroundColor: theme_1.defaultTheme.colors.darkRed,
100
+ width: 36,
101
+ height: 36,
102
+ borderRadius: 18,
103
+ justifyContent: 'center',
104
+ alignItems: 'center',
105
+ },
106
+ disabledSendWrapper: {
107
+ backgroundColor: theme_1.defaultTheme.colors.gray,
108
+ },
109
+ });
110
+ exports.default = InputToolbar;
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { IMessage, IUser } from '../types';
3
+ interface MessageProps {
4
+ currentMessage: IMessage;
5
+ nextMessage?: IMessage;
6
+ previousMessage?: IMessage;
7
+ user: IUser;
8
+ renderBubble?: (props: any) => React.ReactNode;
9
+ onLongPress?: (context: any, message: IMessage) => void;
10
+ onPress?: (context: any, message: IMessage) => void;
11
+ onReply?: (message: IMessage) => void;
12
+ onReaction?: (message: IMessage, reaction: string) => void;
13
+ onDeleteMessage?: (message: IMessage) => void;
14
+ onDownloadFile?: (message: IMessage) => void;
15
+ renderMessageText?: (props: any) => React.ReactNode;
16
+ renderMessageImage?: (props: any) => React.ReactNode;
17
+ renderMessageVideo?: (props: any) => React.ReactNode;
18
+ renderMessageAudio?: (props: any) => React.ReactNode;
19
+ }
20
+ declare const Message: (props: MessageProps) => import("react/jsx-runtime").JSX.Element;
21
+ export default Message;
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const react_native_1 = require("react-native");
9
+ const ReanimatedSwipeable_1 = __importDefault(require("react-native-gesture-handler/ReanimatedSwipeable"));
10
+ const Bubble_1 = __importDefault(require("./Bubble"));
11
+ const MessageStatus_1 = __importDefault(require("./MessageStatus"));
12
+ const theme_1 = require("../utils/theme");
13
+ const ReplyPreview_1 = __importDefault(require("./ReplyPreview"));
14
+ const ImageCard_1 = __importDefault(require("./media/ImageCard"));
15
+ const VideoCard_1 = __importDefault(require("./media/VideoCard"));
16
+ const AudioCard_1 = __importDefault(require("./media/AudioCard"));
17
+ const FileCard_1 = __importDefault(require("./media/FileCard"));
18
+ const ReactionBubble_1 = __importDefault(require("./ReactionBubble"));
19
+ const Message = (props) => {
20
+ var _a, _b, _c;
21
+ const [isFullScreen, setFullScreen] = (0, react_1.useState)(false);
22
+ const theme = (0, theme_1.useTheme)();
23
+ const { currentMessage, user, onLongPress, onPress, renderBubble, onReply, onReaction, onDeleteMessage, onDownloadFile, } = props;
24
+ const isMine = currentMessage.user._id === user._id || currentMessage.isMine;
25
+ const swipeableRef = (0, react_1.useRef)(null);
26
+ const fileType = (_a = currentMessage.fileType) === null || _a === void 0 ? void 0 : _a.toLowerCase();
27
+ const isMedia = !!(currentMessage.image || currentMessage.video || currentMessage.audio || fileType === 'image' || fileType === 'video' || fileType === 'file' || fileType === 'audio');
28
+ const timestamp = new Date(currentMessage.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
29
+ const handleLongPress = () => {
30
+ onLongPress === null || onLongPress === void 0 ? void 0 : onLongPress(null, currentMessage);
31
+ };
32
+ const handlePress = () => {
33
+ onPress === null || onPress === void 0 ? void 0 : onPress(null, currentMessage);
34
+ if (!isMedia)
35
+ return;
36
+ if (currentMessage.image || fileType === 'image') {
37
+ setFullScreen(!isFullScreen);
38
+ }
39
+ };
40
+ const handleReactionPress = (reaction) => {
41
+ if (reaction && onReaction) {
42
+ onReaction(currentMessage, reaction);
43
+ }
44
+ };
45
+ const handleSwipeReply = () => {
46
+ var _a;
47
+ (_a = swipeableRef.current) === null || _a === void 0 ? void 0 : _a.close();
48
+ onReply === null || onReply === void 0 ? void 0 : onReply(currentMessage);
49
+ };
50
+ const renderLeftActions = () => {
51
+ // Invisible view for swipe action trigger
52
+ return (0, jsx_runtime_1.jsx)(react_native_1.View, { style: { width: 50 } });
53
+ };
54
+ const renderContent = () => {
55
+ if (currentMessage.image || fileType === 'image') {
56
+ if (props.renderMessageImage)
57
+ return props.renderMessageImage(props);
58
+ return ((0, jsx_runtime_1.jsx)(ImageCard_1.default, { uri: currentMessage.image || currentMessage.fileUrl || '', time: timestamp, setFullScreen: setFullScreen, isFullScreen: isFullScreen }));
59
+ }
60
+ if (currentMessage.video || fileType === 'video') {
61
+ if (props.renderMessageVideo)
62
+ return props.renderMessageVideo(props);
63
+ return ((0, jsx_runtime_1.jsx)(VideoCard_1.default, { file: { uri: currentMessage.video || currentMessage.fileUrl || '' }, time: timestamp, onLongPress: handleLongPress }));
64
+ }
65
+ if (currentMessage.audio || fileType === 'audio') {
66
+ if (props.renderMessageAudio)
67
+ return props.renderMessageAudio(props);
68
+ return (0, jsx_runtime_1.jsx)(AudioCard_1.default, { uri: currentMessage.audio || currentMessage.fileUrl || '', isMine: !!isMine });
69
+ }
70
+ if (fileType === 'file') {
71
+ return (0, jsx_runtime_1.jsx)(FileCard_1.default, { fileName: currentMessage.fileName || 'File', isMine: !!isMine, time: timestamp });
72
+ }
73
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { children: currentMessage.text ? (props.renderMessageText ? (props.renderMessageText(props)) : ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.text, isMine ? { color: theme.colors.ownMessageText } : { color: theme.colors.otherMessageText }], children: currentMessage.text }))) : null }));
74
+ };
75
+ // Reactions logic
76
+ const reactions = ['👍', '❤️', '😂', '😮', '😢', '🤲'];
77
+ const myReaction = (_c = (_b = currentMessage.reactions) === null || _b === void 0 ? void 0 : _b.find((r) => r.userId === user._id)) === null || _c === void 0 ? void 0 : _c.emoji;
78
+ const bubbleContent = ((0, jsx_runtime_1.jsxs)(Bubble_1.default, { isOwnMessage: !!isMine, isMedia: isMedia, children: [currentMessage.replyTo && ((0, jsx_runtime_1.jsx)(ReplyPreview_1.default, { replyTo: currentMessage.replyTo })), renderContent(), !isMedia && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.footer, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.time, isMine ? styles.timeMine : { color: theme.colors.timestamp }], children: timestamp }), isMine && (0, jsx_runtime_1.jsx)(MessageStatus_1.default, { status: currentMessage.status, isMine: !!isMine })] }))] }));
79
+ const messageView = ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.container, isMine ? styles.containerMine : styles.containerOther], children: (0, jsx_runtime_1.jsx)(ReactionBubble_1.default, { reactions: reactions, isMine: !!isMine, selectedReaction: myReaction, onReactionPress: handleReactionPress, onPress: handlePress, onLongPress: handleLongPress, bubbleStyle: styles.reactionPopup, onReply: () => onReply === null || onReply === void 0 ? void 0 : onReply(currentMessage), onDelete: onDeleteMessage ? () => onDeleteMessage(currentMessage) : undefined, onDownload: onDownloadFile ? () => onDownloadFile(currentMessage) : undefined, isFile: isMedia, children: renderBubble ? renderBubble({
80
+ ...props,
81
+ isMine,
82
+ children: renderContent(),
83
+ }) : bubbleContent }) }));
84
+ if (onReply) {
85
+ return ((0, jsx_runtime_1.jsx)(ReanimatedSwipeable_1.default, { ref: swipeableRef, onSwipeableOpen: handleSwipeReply, renderLeftActions: renderLeftActions, leftThreshold: 40, overshootLeft: false, children: messageView }));
86
+ }
87
+ return messageView;
88
+ };
89
+ const styles = react_native_1.StyleSheet.create({
90
+ container: {
91
+ marginBottom: 10,
92
+ flexDirection: 'row',
93
+ paddingHorizontal: 8,
94
+ },
95
+ containerMine: {
96
+ justifyContent: 'flex-end',
97
+ },
98
+ containerOther: {
99
+ justifyContent: 'flex-start',
100
+ },
101
+ text: {
102
+ fontSize: 16,
103
+ lineHeight: 20,
104
+ },
105
+ textMine: {
106
+ color: theme_1.defaultTheme.colors.ownMessageText,
107
+ },
108
+ textOther: {
109
+ color: theme_1.defaultTheme.colors.otherMessageText,
110
+ },
111
+ footer: {
112
+ flexDirection: 'row',
113
+ justifyContent: 'flex-end',
114
+ alignItems: 'center',
115
+ marginTop: 4,
116
+ },
117
+ time: {
118
+ fontSize: 10,
119
+ marginRight: 4,
120
+ },
121
+ timeMine: {
122
+ color: 'rgba(255, 255, 255, 0.7)',
123
+ },
124
+ timeOther: {
125
+ color: '#aaa',
126
+ },
127
+ reactionPopup: {
128
+ backgroundColor: '#202A30',
129
+ }
130
+ });
131
+ exports.default = Message;
@@ -0,0 +1,6 @@
1
+ import { ChatProps } from '../types';
2
+ interface MessageListProps extends ChatProps {
3
+ inverted?: boolean;
4
+ }
5
+ declare const MessageList: (props: MessageListProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default MessageList;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const react_native_1 = require("react-native");
9
+ const flash_list_1 = require("@shopify/flash-list");
10
+ const Message_1 = __importDefault(require("./Message"));
11
+ const utils_1 = require("../utils");
12
+ const theme_1 = require("../utils/theme");
13
+ const MessageList = (props) => {
14
+ const { messages, user, onLoadEarlier, loadEarlier, isLoadingEarlier } = props;
15
+ const listRef = (0, react_1.useRef)(null);
16
+ const isMounted = (0, react_1.useRef)(false);
17
+ const theme = (0, theme_1.useTheme)();
18
+ const listData = (0, react_1.useMemo)(() => {
19
+ return Array.isArray(messages) ? [...messages].reverse() : [];
20
+ }, [messages]);
21
+ // Automatic scrolling logic
22
+ (0, react_1.useEffect)(() => {
23
+ if (listData.length === 0)
24
+ return;
25
+ if (!isMounted.current) {
26
+ // Scenario 1: Initial mount - scroll to bottom immediately
27
+ isMounted.current = true;
28
+ // Use a small timeout to ensure list is ready
29
+ setTimeout(() => {
30
+ var _a;
31
+ (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.scrollToEnd({ animated: false });
32
+ }, 0);
33
+ }
34
+ else {
35
+ // Scenario 2: New message added - smooth scroll to bottom
36
+ // We can also check if we are already at the bottom to avoid annoyance,
37
+ // but requirements state "always displays the most recent message".
38
+ setTimeout(() => {
39
+ var _a;
40
+ (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.scrollToEnd({ animated: true });
41
+ }, 200);
42
+ }
43
+ }, [listData.length]);
44
+ const renderItem = (0, react_1.useCallback)(({ item, index }) => {
45
+ const previousMessage = listData[index - 1];
46
+ const nextMessage = listData[index + 1];
47
+ const messageProps = {
48
+ ...props,
49
+ currentMessage: item,
50
+ previousMessage,
51
+ nextMessage,
52
+ user,
53
+ };
54
+ if (props.renderMessage) {
55
+ return props.renderMessage(messageProps);
56
+ }
57
+ const showDateHeader = shouldRenderDateHeader(item, previousMessage);
58
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { children: [showDateHeader && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.dateHeader, { backgroundColor: theme.colors.lightGrey }], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.dateText, { color: theme.colors.gray }], children: new Date(item.createdAt).toLocaleDateString(undefined, {
59
+ year: 'numeric',
60
+ month: 'short',
61
+ day: 'numeric',
62
+ }) }) })), (0, jsx_runtime_1.jsx)(Message_1.default, { ...messageProps })] }));
63
+ }, [listData, user, props]);
64
+ const keyExtractor = (0, react_1.useCallback)((item) => item._id.toString(), []);
65
+ const renderHeader = () => {
66
+ if (loadEarlier) {
67
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.loadEarlierContainer, children: isLoadingEarlier ? ((0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: "small", color: theme.colors.primary })) : ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: onLoadEarlier, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.loadEarlierText, { color: theme.colors.darkRed }], children: "Load earlier messages" }) })) }));
68
+ }
69
+ return null;
70
+ };
71
+ return ((0, jsx_runtime_1.jsx)(flash_list_1.FlashList, { ref: listRef, data: listData, renderItem: renderItem, keyExtractor: keyExtractor, contentContainerStyle: styles.listContent, ListHeaderComponent: renderHeader, keyboardShouldPersistTaps: props.keyboardShouldPersistTaps, maintainVisibleContentPosition: {
72
+ autoscrollToBottomThreshold: 0.2,
73
+ animateAutoScrollToBottom: true,
74
+ startRenderingFromBottom: true,
75
+ }, onStartReached: loadEarlier ? onLoadEarlier : undefined, onStartReachedThreshold: 0.1, estimatedItemSize: 72, ...props.listViewProps }));
76
+ };
77
+ const shouldRenderDateHeader = (currentMessage, previousMessage) => {
78
+ if (!previousMessage)
79
+ return true;
80
+ return !(0, utils_1.isSameDay)(currentMessage, previousMessage);
81
+ };
82
+ const styles = react_native_1.StyleSheet.create({
83
+ listContent: {
84
+ paddingVertical: 10,
85
+ paddingHorizontal: 10,
86
+ paddingBottom: 20,
87
+ },
88
+ dateHeader: {
89
+ alignSelf: 'center',
90
+ backgroundColor: theme_1.defaultTheme.colors.lightGrey,
91
+ paddingVertical: 4,
92
+ paddingHorizontal: 12,
93
+ borderRadius: 16,
94
+ marginBottom: 16,
95
+ marginTop: 16,
96
+ },
97
+ dateText: {
98
+ fontSize: 12,
99
+ color: theme_1.defaultTheme.colors.gray,
100
+ fontWeight: '500',
101
+ },
102
+ loadEarlierContainer: {
103
+ alignItems: 'center',
104
+ paddingVertical: 10,
105
+ },
106
+ loadEarlierText: {
107
+ color: theme_1.defaultTheme.colors.darkRed,
108
+ fontWeight: '600',
109
+ },
110
+ });
111
+ exports.default = MessageList;
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ status?: 'pending' | 'sent' | 'delivered' | 'read';
3
+ isMine?: boolean;
4
+ }
5
+ declare const MessageStatus: ({ status, isMine }: Props) => import("react/jsx-runtime").JSX.Element;
6
+ export default MessageStatus;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const jsx_runtime_1 = require("react/jsx-runtime");
37
+ const react_native_svg_1 = __importStar(require("react-native-svg"));
38
+ const theme_1 = require("../utils/theme");
39
+ const ClockIcon = ({ color }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M12 6V12L16 14" })] }));
40
+ const CheckIcon = ({ color }) => ((0, jsx_runtime_1.jsx)(react_native_svg_1.default, { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M20 6L9 17L4 12" }) }));
41
+ const DoubleCheckIcon = ({ color }) => ((0, jsx_runtime_1.jsx)(react_native_svg_1.default, { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M7 12l5 5L22 7M2 12l5 5m5-5l5-5" }) }));
42
+ const MessageStatus = ({ status, isMine }) => {
43
+ const theme = (0, theme_1.useTheme)();
44
+ if (!isMine)
45
+ return null;
46
+ switch (status) {
47
+ case 'pending':
48
+ return (0, jsx_runtime_1.jsx)(ClockIcon, { color: theme.colors.gray });
49
+ case 'sent':
50
+ return (0, jsx_runtime_1.jsx)(CheckIcon, { color: theme.colors.gray });
51
+ case 'delivered':
52
+ return (0, jsx_runtime_1.jsx)(DoubleCheckIcon, { color: theme.colors.gray });
53
+ case 'read':
54
+ return (0, jsx_runtime_1.jsx)(DoubleCheckIcon, { color: theme.colors.lightRed }); // Use theme color or blue
55
+ default:
56
+ return null;
57
+ }
58
+ };
59
+ exports.default = MessageStatus;
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ export declare const computePositions: ({ pos, reactionsCount, isMine, screenW, screenH, popupItemWidth, popupHeight, menuWidth, menuItemCount, menuItemHeight, verticalGap, horizontalMargin, reactionCoreWidth, reactionHorizontalPadding, popupHorizontalPadding, }: {
3
+ pos: {
4
+ x: number;
5
+ y: number;
6
+ width: number;
7
+ height: number;
8
+ };
9
+ reactionsCount: number;
10
+ isMine: boolean;
11
+ screenW: number;
12
+ screenH: number;
13
+ popupItemWidth?: number;
14
+ popupHeight?: number;
15
+ menuWidth?: number;
16
+ menuItemCount?: number;
17
+ menuItemHeight?: number;
18
+ verticalGap?: number;
19
+ horizontalMargin?: number;
20
+ reactionCoreWidth?: number;
21
+ reactionHorizontalPadding?: number;
22
+ popupHorizontalPadding?: number;
23
+ }) => {
24
+ emojiPanel: {
25
+ left: number;
26
+ top: number;
27
+ width: number;
28
+ height: number;
29
+ };
30
+ menu: {
31
+ left: number;
32
+ top: number;
33
+ width: number;
34
+ height: number;
35
+ above: boolean;
36
+ };
37
+ };
38
+ export type ReactionBubbleProps = {
39
+ reactions: string[];
40
+ isMine: boolean;
41
+ selectedReaction?: string;
42
+ onReactionPress: (reaction: string | undefined) => void;
43
+ style?: any;
44
+ bubbleStyle?: any;
45
+ reactionStyle?: any;
46
+ highlightColor?: string;
47
+ children: React.ReactNode;
48
+ onPress?: () => void;
49
+ onLongPress?: () => void;
50
+ onReply?: () => void;
51
+ onDelete?: () => void;
52
+ onDownload?: () => void;
53
+ isFile?: boolean;
54
+ };
55
+ export default function ReactionBubble({ reactions, selectedReaction, onReactionPress, style, bubbleStyle, reactionStyle, highlightColor, children, isMine, onPress, onLongPress, onReply, onDelete, onDownload, isFile, ...rest }: ReactionBubbleProps): import("react/jsx-runtime").JSX.Element;