@codewithvincent/react-native-love-chat 0.1.0 → 0.2.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.
- package/dist/components/FooterReplyPreview.js +2 -3
- package/dist/components/Message.js +116 -34
- package/dist/components/ReactionBubble.d.ts +1 -1
- package/dist/components/ReactionBubble.js +18 -9
- package/dist/components/ReplyPreview.js +1 -1
- package/dist/components/media/FileCard.d.ts +1 -1
- package/dist/components/media/FileCard.js +3 -3
- package/dist/components/media/ImageCard.js +7 -5
- package/dist/types.d.ts +1 -0
- package/dist/utils/theme.d.ts +28 -0
- package/dist/utils/theme.js +14 -0
- package/package.json +2 -2
- package/dist/components/adapters/UniversalAudio.d.ts +0 -8
- package/dist/components/adapters/UniversalAudio.js +0 -23
- package/dist/components/adapters/UniversalVideo.d.ts +0 -8
- package/dist/components/adapters/UniversalVideo.js +0 -20
- package/dist/components/media/AudioCard.d.ts +0 -7
- package/dist/components/media/AudioCard.js +0 -187
- package/dist/components/media/VideoCard.d.ts +0 -10
- package/dist/components/media/VideoCard.js +0 -232
|
@@ -7,7 +7,6 @@ const theme_1 = require("../utils/theme");
|
|
|
7
7
|
const Icons_1 = require("./Icons");
|
|
8
8
|
const FooterReplyPreview = ({ chatMessage, clearReply, userId }) => {
|
|
9
9
|
var _a, _b;
|
|
10
|
-
console.log('chatMessage', chatMessage);
|
|
11
10
|
const theme = (0, theme_1.useTheme)();
|
|
12
11
|
if (!chatMessage)
|
|
13
12
|
return null;
|
|
@@ -81,7 +80,7 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
81
80
|
borderLeftWidth: 4,
|
|
82
81
|
borderLeftColor: theme_1.defaultTheme.colors.primary,
|
|
83
82
|
borderBottomWidth: 0.3,
|
|
84
|
-
borderBottomColor:
|
|
83
|
+
borderBottomColor: theme_1.defaultTheme.colors.replyPreviewBorder,
|
|
85
84
|
},
|
|
86
85
|
replyLine: {
|
|
87
86
|
width: 4,
|
|
@@ -115,7 +114,7 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
115
114
|
height: 40,
|
|
116
115
|
justifyContent: 'center',
|
|
117
116
|
alignItems: 'center',
|
|
118
|
-
backgroundColor:
|
|
117
|
+
backgroundColor: theme_1.defaultTheme.colors.replyMediaBg,
|
|
119
118
|
borderRadius: 4,
|
|
120
119
|
},
|
|
121
120
|
replyFileContainer: {
|
|
@@ -1,4 +1,37 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -6,26 +39,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
39
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
40
|
const react_1 = require("react");
|
|
8
41
|
const react_native_1 = require("react-native");
|
|
9
|
-
const
|
|
42
|
+
const react_native_gesture_handler_1 = require("react-native-gesture-handler");
|
|
43
|
+
const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
|
|
10
44
|
const Bubble_1 = __importDefault(require("./Bubble"));
|
|
11
45
|
const MessageStatus_1 = __importDefault(require("./MessageStatus"));
|
|
12
46
|
const theme_1 = require("../utils/theme");
|
|
13
47
|
const ReplyPreview_1 = __importDefault(require("./ReplyPreview"));
|
|
14
48
|
const ImageCard_1 = __importDefault(require("./media/ImageCard"));
|
|
15
|
-
const VideoCard_1 = __importDefault(require("./media/VideoCard"));
|
|
16
|
-
const AudioCard_1 = __importDefault(require("./media/AudioCard"));
|
|
17
49
|
const FileCard_1 = __importDefault(require("./media/FileCard"));
|
|
18
50
|
const ReactionBubble_1 = __importDefault(require("./ReactionBubble"));
|
|
19
51
|
const Message = (props) => {
|
|
20
52
|
var _a, _b, _c;
|
|
21
53
|
const [isFullScreen, setFullScreen] = (0, react_1.useState)(false);
|
|
54
|
+
const [isVideo, setVideo] = (0, react_1.useState)(false);
|
|
22
55
|
const theme = (0, theme_1.useTheme)();
|
|
23
56
|
const { currentMessage, user, onLongPress, onPress, renderBubble, onReply, onReaction, onDeleteMessage, onDownloadFile, } = props;
|
|
24
57
|
const isMine = currentMessage.user._id === user._id || currentMessage.isMine;
|
|
25
|
-
const swipeableRef = (0, react_1.useRef)(null);
|
|
26
58
|
const fileType = (_a = currentMessage.fileType) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
27
|
-
const isMedia = !!(currentMessage.image ||
|
|
28
|
-
|
|
59
|
+
const isMedia = !!(currentMessage.image ||
|
|
60
|
+
currentMessage.video ||
|
|
61
|
+
currentMessage.audio ||
|
|
62
|
+
fileType === 'image' ||
|
|
63
|
+
fileType === 'video' ||
|
|
64
|
+
fileType === 'file' ||
|
|
65
|
+
fileType === 'audio');
|
|
66
|
+
const timestamp = new Date(currentMessage.createdAt).toLocaleTimeString([], {
|
|
67
|
+
hour: '2-digit',
|
|
68
|
+
minute: '2-digit',
|
|
69
|
+
});
|
|
29
70
|
const handleLongPress = () => {
|
|
30
71
|
onLongPress === null || onLongPress === void 0 ? void 0 : onLongPress(null, currentMessage);
|
|
31
72
|
};
|
|
@@ -35,6 +76,11 @@ const Message = (props) => {
|
|
|
35
76
|
return;
|
|
36
77
|
if (currentMessage.image || fileType === 'image') {
|
|
37
78
|
setFullScreen(!isFullScreen);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (currentMessage.video || currentMessage.fileType === 'video') {
|
|
82
|
+
setVideo(true);
|
|
83
|
+
return;
|
|
38
84
|
}
|
|
39
85
|
};
|
|
40
86
|
const handleReactionPress = (reaction) => {
|
|
@@ -42,47 +88,83 @@ const Message = (props) => {
|
|
|
42
88
|
onReaction(currentMessage, reaction);
|
|
43
89
|
}
|
|
44
90
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
91
|
+
// =============================
|
|
92
|
+
// Universal Swipe Implementation
|
|
93
|
+
// =============================
|
|
94
|
+
const translateX = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
95
|
+
const threshold = 60;
|
|
96
|
+
const panGesture = react_native_gesture_handler_1.Gesture.Pan()
|
|
97
|
+
.activeOffsetX([-20, 20])
|
|
98
|
+
.failOffsetY([-15, 15])
|
|
99
|
+
.minDistance(15)
|
|
100
|
+
.maxPointers(1)
|
|
101
|
+
.onUpdate((event) => {
|
|
102
|
+
if (event.translationX > 0) {
|
|
103
|
+
translateX.value = event.translationX;
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
.onEnd(() => {
|
|
107
|
+
if (translateX.value > threshold && onReply) {
|
|
108
|
+
(0, react_native_reanimated_1.runOnJS)(onReply)(currentMessage);
|
|
109
|
+
}
|
|
110
|
+
translateX.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 200 });
|
|
111
|
+
});
|
|
112
|
+
const animatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({
|
|
113
|
+
transform: [{ translateX: translateX.value }],
|
|
114
|
+
}));
|
|
115
|
+
// =============================
|
|
116
|
+
// Content Rendering
|
|
117
|
+
// =============================
|
|
54
118
|
const renderContent = () => {
|
|
55
119
|
if (currentMessage.image || fileType === 'image') {
|
|
56
120
|
if (props.renderMessageImage)
|
|
57
|
-
return props.renderMessageImage(
|
|
121
|
+
return props.renderMessageImage(currentMessage);
|
|
58
122
|
return ((0, jsx_runtime_1.jsx)(ImageCard_1.default, { uri: currentMessage.image || currentMessage.fileUrl || '', time: timestamp, setFullScreen: setFullScreen, isFullScreen: isFullScreen }));
|
|
59
123
|
}
|
|
60
124
|
if (currentMessage.video || fileType === 'video') {
|
|
125
|
+
const currentProps = {
|
|
126
|
+
...currentMessage,
|
|
127
|
+
setFullScreen: setVideo,
|
|
128
|
+
isFullScreen: isVideo,
|
|
129
|
+
};
|
|
61
130
|
if (props.renderMessageVideo)
|
|
62
|
-
return props.renderMessageVideo(
|
|
63
|
-
return
|
|
131
|
+
return props.renderMessageVideo(currentProps);
|
|
132
|
+
return null;
|
|
64
133
|
}
|
|
65
134
|
if (currentMessage.audio || fileType === 'audio') {
|
|
66
135
|
if (props.renderMessageAudio)
|
|
67
|
-
return props.renderMessageAudio(
|
|
68
|
-
return
|
|
136
|
+
return props.renderMessageAudio(currentMessage);
|
|
137
|
+
return null;
|
|
69
138
|
}
|
|
70
139
|
if (fileType === 'file') {
|
|
71
|
-
return (0, jsx_runtime_1.jsx)(FileCard_1.default, { fileName: currentMessage.fileName || 'File', isMine: !!isMine, time: timestamp });
|
|
140
|
+
return ((0, jsx_runtime_1.jsx)(FileCard_1.default, { fileName: currentMessage.fileName || 'File', isMine: !!isMine, time: timestamp }));
|
|
72
141
|
}
|
|
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: [
|
|
142
|
+
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: [
|
|
143
|
+
styles.text,
|
|
144
|
+
isMine
|
|
145
|
+
? { color: theme.colors.ownMessageText }
|
|
146
|
+
: { color: theme.colors.otherMessageText },
|
|
147
|
+
], children: currentMessage.text }))) : null }));
|
|
74
148
|
};
|
|
75
|
-
//
|
|
149
|
+
// =============================
|
|
150
|
+
// Reactions
|
|
151
|
+
// =============================
|
|
76
152
|
const reactions = ['👍', '❤️', '😂', '😮', '😢', '🤲'];
|
|
77
153
|
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
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
154
|
+
const contentEl = renderContent();
|
|
155
|
+
const bubbleContent = contentEl ? ((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 }), contentEl, !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 })] }))] })) : null;
|
|
156
|
+
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
|
|
157
|
+
? renderBubble({
|
|
158
|
+
...props,
|
|
159
|
+
isMine,
|
|
160
|
+
children: contentEl,
|
|
161
|
+
})
|
|
162
|
+
: bubbleContent }) }));
|
|
163
|
+
// =============================
|
|
164
|
+
// Conditional Swipe Wrapper
|
|
165
|
+
// =============================
|
|
84
166
|
if (onReply) {
|
|
85
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
167
|
+
return ((0, jsx_runtime_1.jsx)(react_native_gesture_handler_1.GestureDetector, { gesture: react_native_gesture_handler_1.Gesture.Simultaneous(panGesture, react_native_gesture_handler_1.Gesture.Native()), children: (0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.View, { style: animatedStyle, children: messageView }) }));
|
|
86
168
|
}
|
|
87
169
|
return messageView;
|
|
88
170
|
};
|
|
@@ -119,13 +201,13 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
119
201
|
marginRight: 4,
|
|
120
202
|
},
|
|
121
203
|
timeMine: {
|
|
122
|
-
color:
|
|
204
|
+
color: theme_1.defaultTheme.colors.timestampMine,
|
|
123
205
|
},
|
|
124
206
|
timeOther: {
|
|
125
|
-
color:
|
|
207
|
+
color: theme_1.defaultTheme.colors.timestamp,
|
|
126
208
|
},
|
|
127
209
|
reactionPopup: {
|
|
128
|
-
backgroundColor:
|
|
129
|
-
}
|
|
210
|
+
backgroundColor: theme_1.defaultTheme.colors.reactionPopupBg,
|
|
211
|
+
},
|
|
130
212
|
});
|
|
131
213
|
exports.default = Message;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export declare const computePositions: ({ pos, reactionsCount, isMine, screenW, screenH,
|
|
2
|
+
export declare const computePositions: ({ pos, reactionsCount, isMine, screenW, screenH, popupHeight, menuWidth, menuItemCount, menuItemHeight, verticalGap, horizontalMargin, reactionCoreWidth, reactionHorizontalPadding, popupHorizontalPadding, }: {
|
|
3
3
|
pos: {
|
|
4
4
|
x: number;
|
|
5
5
|
y: number;
|
|
@@ -50,7 +50,7 @@ const Icons_1 = require("./Icons");
|
|
|
50
50
|
const SCREEN = react_native_1.Dimensions.get('window');
|
|
51
51
|
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
52
52
|
// Helper exported for tests (non-breaking)
|
|
53
|
-
const computePositions = ({ pos, reactionsCount, isMine, screenW, screenH,
|
|
53
|
+
const computePositions = ({ pos, reactionsCount, isMine, screenW, screenH, popupHeight = 60, menuWidth = 200, menuItemCount = 3, menuItemHeight = 44, verticalGap = 12, horizontalMargin = 8, reactionCoreWidth = 28, reactionHorizontalPadding = 8, popupHorizontalPadding = 12, }) => {
|
|
54
54
|
// More accurate width estimation: per-reaction width + container paddings
|
|
55
55
|
const perReactionWidth = reactionCoreWidth + reactionHorizontalPadding * 2;
|
|
56
56
|
const emojiPanelWidth = reactionsCount * perReactionWidth + popupHorizontalPadding * 2;
|
|
@@ -148,10 +148,10 @@ function ReactionBubble({ reactions, selectedReaction, onReactionPress, style, b
|
|
|
148
148
|
}
|
|
149
149
|
left = clamp(left, 10, screenW - popupWidth - 10);
|
|
150
150
|
menuTop = pos.y + pos.height + 10;
|
|
151
|
-
let _menuLeft = isMine ?
|
|
151
|
+
let _menuLeft = isMine ? pos.x + pos.width - menuWidth : pos.x;
|
|
152
152
|
menuLeft = clamp(_menuLeft, 10, screenW - menuWidth - 10);
|
|
153
153
|
}
|
|
154
|
-
siblingRef.current = new react_native_root_siblings_1.default((
|
|
154
|
+
siblingRef.current = new react_native_root_siblings_1.default((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.overlayContainer, children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: react_native_1.StyleSheet.absoluteFill, onPress: hidePopup, children: (0, jsx_runtime_1.jsx)(UniversalBlurView_1.UniversalBlurView, { style: react_native_1.StyleSheet.absoluteFill, blurType: 'dark', blurAmount: 12, tint: "dark", intensity: 60, reducedTransparencyFallbackColor: theme.colors.blurFallback }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
155
155
|
styles.messageDuplicateWrapper,
|
|
156
156
|
{
|
|
157
157
|
top: pos.y,
|
|
@@ -168,8 +168,17 @@ function ReactionBubble({ reactions, selectedReaction, onReactionPress, style, b
|
|
|
168
168
|
top: menuTop,
|
|
169
169
|
left: menuLeft,
|
|
170
170
|
width: menuWidth,
|
|
171
|
-
}
|
|
172
|
-
], children: [onReply && ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { style: styles.menuItem, onPress: () => {
|
|
171
|
+
},
|
|
172
|
+
], children: [onReply && ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { style: styles.menuItem, onPress: () => {
|
|
173
|
+
hidePopup();
|
|
174
|
+
onReply();
|
|
175
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.menuText, children: "Reply" }), (0, jsx_runtime_1.jsx)(Icons_1.ReplyIcon, { size: 18, color: theme.colors.white })] })), isFile && onDownload && ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { style: styles.menuItem, onPress: () => {
|
|
176
|
+
hidePopup();
|
|
177
|
+
onDownload();
|
|
178
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.menuText, children: "Download" }), (0, jsx_runtime_1.jsx)(Icons_1.DownloadIcon, { size: 18, color: theme.colors.white })] })), onDelete && ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { style: [styles.menuItem, { borderBottomWidth: 0 }], onPress: () => {
|
|
179
|
+
hidePopup();
|
|
180
|
+
onDelete();
|
|
181
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.menuText, { color: theme_1.defaultTheme.colors.darkRed }], children: "Delete" }), (0, jsx_runtime_1.jsx)(Icons_1.TrashIcon, { size: 18 })] }))] })] }));
|
|
173
182
|
requestAnimationFrame(() => {
|
|
174
183
|
scale.value = 1;
|
|
175
184
|
});
|
|
@@ -219,7 +228,7 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
219
228
|
paddingVertical: 8,
|
|
220
229
|
paddingHorizontal: 12,
|
|
221
230
|
borderRadius: 40,
|
|
222
|
-
backgroundColor:
|
|
231
|
+
backgroundColor: theme_1.defaultTheme.colors.reactionPopupBg,
|
|
223
232
|
zIndex: 100,
|
|
224
233
|
},
|
|
225
234
|
reaction: {
|
|
@@ -239,7 +248,7 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
239
248
|
},
|
|
240
249
|
contextMenu: {
|
|
241
250
|
position: 'absolute',
|
|
242
|
-
backgroundColor:
|
|
251
|
+
backgroundColor: theme_1.defaultTheme.colors.reactionMenuBg,
|
|
243
252
|
borderRadius: 12,
|
|
244
253
|
paddingVertical: 4,
|
|
245
254
|
width: 200,
|
|
@@ -252,10 +261,10 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
252
261
|
paddingVertical: 12,
|
|
253
262
|
paddingHorizontal: 16,
|
|
254
263
|
borderBottomWidth: react_native_1.StyleSheet.hairlineWidth,
|
|
255
|
-
borderBottomColor:
|
|
264
|
+
borderBottomColor: theme_1.defaultTheme.colors.reactionMenuSeparator,
|
|
256
265
|
},
|
|
257
266
|
menuText: {
|
|
258
|
-
color:
|
|
267
|
+
color: theme_1.defaultTheme.colors.white,
|
|
259
268
|
fontSize: 16,
|
|
260
269
|
fontWeight: '400',
|
|
261
270
|
},
|
|
@@ -86,7 +86,7 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
86
86
|
borderRadius: 6,
|
|
87
87
|
marginLeft: 10,
|
|
88
88
|
overflow: 'hidden',
|
|
89
|
-
backgroundColor:
|
|
89
|
+
backgroundColor: theme_1.defaultTheme.colors.mediaThumbBg,
|
|
90
90
|
justifyContent: 'center',
|
|
91
91
|
alignItems: 'center',
|
|
92
92
|
},
|
|
@@ -4,5 +4,5 @@ interface FileCardProps {
|
|
|
4
4
|
isMine?: boolean;
|
|
5
5
|
time?: string;
|
|
6
6
|
}
|
|
7
|
-
export default function FileCard({ fileName, isMine, time
|
|
7
|
+
export default function FileCard({ fileName, isMine, time }: FileCardProps): import("react/jsx-runtime").JSX.Element;
|
|
8
8
|
export {};
|
|
@@ -6,14 +6,14 @@ const react_native_1 = require("react-native");
|
|
|
6
6
|
const utils_1 = require("../../utils");
|
|
7
7
|
const theme_1 = require("../../utils/theme");
|
|
8
8
|
const Icons_1 = require("../Icons");
|
|
9
|
-
function FileCard({ fileName, isMine = false, time
|
|
9
|
+
function FileCard({ fileName, isMine = false, time }) {
|
|
10
10
|
const theme = (0, theme_1.useTheme)();
|
|
11
11
|
// WhatsApp-style colors
|
|
12
12
|
// Sent: Lighter green bubble, file card is slightly darker green/transparent
|
|
13
13
|
// Received: White bubble, file card is light gray
|
|
14
14
|
const cardBackgroundColor = isMine ? theme.colors.ownFileBg : theme.colors.otherFileBg;
|
|
15
15
|
const textColor = isMine ? theme.colors.ownMessageText : theme.colors.otherMessageText;
|
|
16
|
-
const subTextColor = isMine ?
|
|
16
|
+
const subTextColor = isMine ? theme.colors.timestampMine : theme.colors.mediumGray;
|
|
17
17
|
const iconColor = isMine ? theme.colors.white : theme.colors.darkRed;
|
|
18
18
|
const getExtension = (name) => {
|
|
19
19
|
var _a;
|
|
@@ -64,5 +64,5 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
64
64
|
},
|
|
65
65
|
timeText: {
|
|
66
66
|
fontSize: 10,
|
|
67
|
-
}
|
|
67
|
+
},
|
|
68
68
|
});
|
|
@@ -7,10 +7,12 @@ const react_native_1 = require("react-native");
|
|
|
7
7
|
const utils_1 = require("../../utils");
|
|
8
8
|
const Icons_1 = require("../Icons");
|
|
9
9
|
const react_native_safe_area_context_1 = require("react-native-safe-area-context");
|
|
10
|
+
const theme_1 = require("../../utils/theme");
|
|
10
11
|
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = react_native_1.Dimensions.get('window');
|
|
11
12
|
const ImageComponent = react_native_1.Image;
|
|
12
13
|
function ImageCard({ uri, maxWidth, time, isFullScreen, setFullScreen, }) {
|
|
13
14
|
const inset = (0, react_native_safe_area_context_1.useSafeAreaInsets)();
|
|
15
|
+
const theme = (0, theme_1.useTheme)();
|
|
14
16
|
const bubbleWidth = maxWidth || utils_1.appSize.width(65);
|
|
15
17
|
const [bubbleHeight, setBubbleHeight] = (0, react_1.useState)(bubbleWidth * 0.75);
|
|
16
18
|
(0, react_1.useEffect)(() => {
|
|
@@ -30,13 +32,13 @@ function ImageCard({ uri, maxWidth, time, isFullScreen, setFullScreen, }) {
|
|
|
30
32
|
], children: [(0, jsx_runtime_1.jsx)(ImageComponent, { source: { uri }, style: styles.image, resizeMode: "cover" }), time && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.timeOverlay, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.timeText, children: time }) }))] }), (0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: isFullScreen, transparent: false, onRequestClose: () => setFullScreen(false), animationType: "fade", children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.modal, children: [(0, jsx_runtime_1.jsx)(ImageComponent, { source: { uri }, style: styles.fullScreenImage, resizeMode: "contain" }), (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [
|
|
31
33
|
styles.closeButton,
|
|
32
34
|
{ top: inset.top + 10 },
|
|
33
|
-
], onPress: () => setFullScreen(false), children: (0, jsx_runtime_1.jsx)(Icons_1.CloseIcon, { size: 24, color:
|
|
35
|
+
], onPress: () => setFullScreen(false), children: (0, jsx_runtime_1.jsx)(Icons_1.CloseIcon, { size: 24, color: theme.colors.white }) })] }) })] }));
|
|
34
36
|
}
|
|
35
37
|
const styles = react_native_1.StyleSheet.create({
|
|
36
38
|
container: {
|
|
37
39
|
borderRadius: utils_1.appSize.width(2),
|
|
38
40
|
overflow: 'hidden',
|
|
39
|
-
backgroundColor:
|
|
41
|
+
backgroundColor: theme_1.defaultTheme.colors.black,
|
|
40
42
|
margin: -2,
|
|
41
43
|
},
|
|
42
44
|
image: {
|
|
@@ -47,7 +49,7 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
47
49
|
flex: 1,
|
|
48
50
|
justifyContent: 'center',
|
|
49
51
|
alignItems: 'center',
|
|
50
|
-
backgroundColor:
|
|
52
|
+
backgroundColor: theme_1.defaultTheme.colors.black,
|
|
51
53
|
},
|
|
52
54
|
fullScreenImage: {
|
|
53
55
|
width: SCREEN_WIDTH,
|
|
@@ -63,13 +65,13 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
63
65
|
position: 'absolute',
|
|
64
66
|
bottom: 5,
|
|
65
67
|
right: 5,
|
|
66
|
-
backgroundColor:
|
|
68
|
+
backgroundColor: theme_1.defaultTheme.colors.overlayBlack50,
|
|
67
69
|
borderRadius: 10,
|
|
68
70
|
paddingHorizontal: 6,
|
|
69
71
|
paddingVertical: 2,
|
|
70
72
|
},
|
|
71
73
|
timeText: {
|
|
72
|
-
color:
|
|
74
|
+
color: theme_1.defaultTheme.colors.white,
|
|
73
75
|
fontSize: 10,
|
|
74
76
|
fontWeight: '600',
|
|
75
77
|
},
|
package/dist/types.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ export interface ChatProps {
|
|
|
45
45
|
renderMessageImage?: (props: any) => React.ReactNode;
|
|
46
46
|
renderMessageVideo?: (props: any) => React.ReactNode;
|
|
47
47
|
renderMessageAudio?: (props: any) => React.ReactNode;
|
|
48
|
+
renderAudio?: (props: any) => React.ReactNode;
|
|
48
49
|
renderFooter?: () => React.ReactNode;
|
|
49
50
|
renderChatFooter?: () => React.ReactNode;
|
|
50
51
|
renderUploadFooter?: (props: any) => React.ReactNode;
|
package/dist/utils/theme.d.ts
CHANGED
|
@@ -23,6 +23,20 @@ export declare const defaultTheme: {
|
|
|
23
23
|
otherMessageText: string;
|
|
24
24
|
ownFileBg: string;
|
|
25
25
|
otherFileBg: string;
|
|
26
|
+
timestampMine: string;
|
|
27
|
+
reactionPopupBg: string;
|
|
28
|
+
reactionMenuBg: string;
|
|
29
|
+
reactionMenuSeparator: string;
|
|
30
|
+
overlayBlack50: string;
|
|
31
|
+
overlayBlack35: string;
|
|
32
|
+
overlayBlack55: string;
|
|
33
|
+
overlayWhite20: string;
|
|
34
|
+
mediaThumbBg: string;
|
|
35
|
+
replyMediaBg: string;
|
|
36
|
+
replyPreviewBorder: string;
|
|
37
|
+
progressTrackBg: string;
|
|
38
|
+
mediumGray: string;
|
|
39
|
+
blurFallback: string;
|
|
26
40
|
};
|
|
27
41
|
fonts: {
|
|
28
42
|
light: string;
|
|
@@ -61,6 +75,20 @@ export declare const useTheme: () => {
|
|
|
61
75
|
otherMessageText: string;
|
|
62
76
|
ownFileBg: string;
|
|
63
77
|
otherFileBg: string;
|
|
78
|
+
timestampMine: string;
|
|
79
|
+
reactionPopupBg: string;
|
|
80
|
+
reactionMenuBg: string;
|
|
81
|
+
reactionMenuSeparator: string;
|
|
82
|
+
overlayBlack50: string;
|
|
83
|
+
overlayBlack35: string;
|
|
84
|
+
overlayBlack55: string;
|
|
85
|
+
overlayWhite20: string;
|
|
86
|
+
mediaThumbBg: string;
|
|
87
|
+
replyMediaBg: string;
|
|
88
|
+
replyPreviewBorder: string;
|
|
89
|
+
progressTrackBg: string;
|
|
90
|
+
mediumGray: string;
|
|
91
|
+
blurFallback: string;
|
|
64
92
|
};
|
|
65
93
|
fonts: {
|
|
66
94
|
light: string;
|
package/dist/utils/theme.js
CHANGED
|
@@ -27,6 +27,20 @@ exports.defaultTheme = {
|
|
|
27
27
|
otherMessageText: '#000',
|
|
28
28
|
ownFileBg: 'rgba(0, 0, 0, 0.1)',
|
|
29
29
|
otherFileBg: 'rgba(0, 0, 0, 0.05)',
|
|
30
|
+
timestampMine: 'rgba(255, 255, 255, 0.7)',
|
|
31
|
+
reactionPopupBg: '#202A30',
|
|
32
|
+
reactionMenuBg: 'rgba(30, 30, 30, 0.95)',
|
|
33
|
+
reactionMenuSeparator: 'rgba(255, 255, 255, 0.1)',
|
|
34
|
+
overlayBlack50: 'rgba(0,0,0,0.5)',
|
|
35
|
+
overlayBlack35: 'rgba(0,0,0,0.35)',
|
|
36
|
+
overlayBlack55: 'rgba(0,0,0,0.55)',
|
|
37
|
+
overlayWhite20: 'rgba(255,255,255,0.2)',
|
|
38
|
+
mediaThumbBg: '#ccc',
|
|
39
|
+
replyMediaBg: '#eee',
|
|
40
|
+
replyPreviewBorder: '#ddd',
|
|
41
|
+
progressTrackBg: '#e0e0e0',
|
|
42
|
+
mediumGray: '#666',
|
|
43
|
+
blurFallback: '#000000CC',
|
|
30
44
|
},
|
|
31
45
|
fonts: {
|
|
32
46
|
light: 'System',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codewithvincent/react-native-love-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A fully featured React Native chat UI component with replies, reactions, media, and theming.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"react": ">=18.0.0",
|
|
24
24
|
"react-native": ">=0.73.0",
|
|
25
25
|
"react-native-gesture-handler": ">=2.12.0",
|
|
26
|
-
"react-native-reanimated": ">=3.6.0",
|
|
26
|
+
"react-native-reanimated": ">=3.6.0 <5.0.0",
|
|
27
27
|
"react-native-safe-area-context": ">=4.10.0",
|
|
28
28
|
"react-native-root-siblings": ">=4.2.1",
|
|
29
29
|
"@shopify/flash-list": ">=1.6.3",
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// src/adapters/UniversalAudio.ts
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.AudioEngine = void 0;
|
|
5
|
-
let ExpoAudio = null;
|
|
6
|
-
let RNSound = null;
|
|
7
|
-
// Try expo-audio first
|
|
8
|
-
try {
|
|
9
|
-
ExpoAudio = require('expo-audio');
|
|
10
|
-
}
|
|
11
|
-
catch { }
|
|
12
|
-
// Fallback to react-native-sound
|
|
13
|
-
if (!ExpoAudio) {
|
|
14
|
-
try {
|
|
15
|
-
RNSound = require('react-native-sound');
|
|
16
|
-
}
|
|
17
|
-
catch { }
|
|
18
|
-
}
|
|
19
|
-
exports.AudioEngine = {
|
|
20
|
-
type: ExpoAudio ? 'expo-audio' : RNSound ? 'rnsound' : 'none',
|
|
21
|
-
ExpoAudio,
|
|
22
|
-
RNSound,
|
|
23
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VideoEngine = void 0;
|
|
4
|
-
let ExpoVideo = null;
|
|
5
|
-
let RNVideo = null;
|
|
6
|
-
try {
|
|
7
|
-
ExpoVideo = require('expo-video');
|
|
8
|
-
}
|
|
9
|
-
catch { }
|
|
10
|
-
if (!ExpoVideo) {
|
|
11
|
-
try {
|
|
12
|
-
RNVideo = require('react-native-video').default;
|
|
13
|
-
}
|
|
14
|
-
catch { }
|
|
15
|
-
}
|
|
16
|
-
exports.VideoEngine = {
|
|
17
|
-
type: ExpoVideo ? 'expo-video' : RNVideo ? 'rn-video' : 'none',
|
|
18
|
-
ExpoVideo,
|
|
19
|
-
RNVideo,
|
|
20
|
-
};
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.default = AudioCard;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const react_1 = require("react");
|
|
6
|
-
const react_native_1 = require("react-native");
|
|
7
|
-
const utils_1 = require("../../utils");
|
|
8
|
-
const theme_1 = require("../../utils/theme");
|
|
9
|
-
const Icons_1 = require("../Icons");
|
|
10
|
-
const UniversalAudio_1 = require("../adapters/UniversalAudio");
|
|
11
|
-
function AudioCard({ uri, maxWidth, isMine }) {
|
|
12
|
-
const soundRef = (0, react_1.useRef)(null);
|
|
13
|
-
const intervalRef = (0, react_1.useRef)(null);
|
|
14
|
-
const progressAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
15
|
-
const totalDuration = (0, react_1.useRef)(0);
|
|
16
|
-
const [isPlaying, setIsPlaying] = (0, react_1.useState)(false);
|
|
17
|
-
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
18
|
-
const [currentTime, setCurrentTime] = (0, react_1.useState)('0:00');
|
|
19
|
-
const theme = (0, theme_1.useTheme)();
|
|
20
|
-
(0, react_1.useEffect)(() => {
|
|
21
|
-
return () => {
|
|
22
|
-
cleanup();
|
|
23
|
-
};
|
|
24
|
-
}, []);
|
|
25
|
-
const cleanup = async () => {
|
|
26
|
-
var _a, _b;
|
|
27
|
-
if (UniversalAudio_1.AudioEngine.type === 'expo-audio' && soundRef.current) {
|
|
28
|
-
await ((_b = (_a = soundRef.current) === null || _a === void 0 ? void 0 : _a.stop) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
29
|
-
}
|
|
30
|
-
if (UniversalAudio_1.AudioEngine.type === 'rnsound' && soundRef.current) {
|
|
31
|
-
soundRef.current.release();
|
|
32
|
-
}
|
|
33
|
-
if (intervalRef.current)
|
|
34
|
-
clearInterval(intervalRef.current);
|
|
35
|
-
};
|
|
36
|
-
const formatTime = (sec) => {
|
|
37
|
-
const m = Math.floor(sec / 60);
|
|
38
|
-
const s = Math.floor(sec % 60);
|
|
39
|
-
return `${m}:${s < 10 ? '0' : ''}${s}`;
|
|
40
|
-
};
|
|
41
|
-
const onFinish = () => {
|
|
42
|
-
setIsPlaying(false);
|
|
43
|
-
progressAnim.setValue(0);
|
|
44
|
-
setCurrentTime('0:00');
|
|
45
|
-
if (intervalRef.current)
|
|
46
|
-
clearInterval(intervalRef.current);
|
|
47
|
-
};
|
|
48
|
-
const play = async () => {
|
|
49
|
-
var _a;
|
|
50
|
-
if (!uri)
|
|
51
|
-
return;
|
|
52
|
-
// 🔵 EXPO-AUDIO MODE
|
|
53
|
-
if (UniversalAudio_1.AudioEngine.type === 'expo-audio') {
|
|
54
|
-
try {
|
|
55
|
-
setIsLoading(true);
|
|
56
|
-
const { createAudioPlayer } = UniversalAudio_1.AudioEngine.ExpoAudio;
|
|
57
|
-
const player = createAudioPlayer({ uri });
|
|
58
|
-
soundRef.current = player;
|
|
59
|
-
player.play();
|
|
60
|
-
setIsPlaying(true);
|
|
61
|
-
setIsLoading(false);
|
|
62
|
-
totalDuration.current = (_a = player.duration) !== null && _a !== void 0 ? _a : 0;
|
|
63
|
-
intervalRef.current = setInterval(() => {
|
|
64
|
-
var _a;
|
|
65
|
-
const pos = (_a = player.currentTime) !== null && _a !== void 0 ? _a : 0;
|
|
66
|
-
if (totalDuration.current > 0) {
|
|
67
|
-
progressAnim.setValue(pos / totalDuration.current);
|
|
68
|
-
}
|
|
69
|
-
setCurrentTime(formatTime(pos));
|
|
70
|
-
if (player.didJustFinish) {
|
|
71
|
-
onFinish();
|
|
72
|
-
}
|
|
73
|
-
}, 200);
|
|
74
|
-
}
|
|
75
|
-
catch (e) {
|
|
76
|
-
utils_1.Logger.error('expo-audio error', e);
|
|
77
|
-
setIsLoading(false);
|
|
78
|
-
}
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
// 🟠 RN SOUND MODE
|
|
82
|
-
if (UniversalAudio_1.AudioEngine.type === 'rnsound') {
|
|
83
|
-
const Sound = UniversalAudio_1.AudioEngine.RNSound;
|
|
84
|
-
if (!soundRef.current) {
|
|
85
|
-
setIsLoading(true);
|
|
86
|
-
const sound = new Sound(uri, '', (err) => {
|
|
87
|
-
setIsLoading(false);
|
|
88
|
-
if (err) {
|
|
89
|
-
utils_1.Logger.error('RN Sound error', err);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
soundRef.current = sound;
|
|
93
|
-
totalDuration.current = sound.getDuration();
|
|
94
|
-
sound.play(onFinish);
|
|
95
|
-
setIsPlaying(true);
|
|
96
|
-
intervalRef.current = setInterval(() => {
|
|
97
|
-
sound.getCurrentTime((sec) => {
|
|
98
|
-
if (totalDuration.current > 0) {
|
|
99
|
-
progressAnim.setValue(sec / totalDuration.current);
|
|
100
|
-
}
|
|
101
|
-
setCurrentTime(formatTime(sec));
|
|
102
|
-
});
|
|
103
|
-
}, 200);
|
|
104
|
-
});
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
soundRef.current.play(onFinish);
|
|
108
|
-
setIsPlaying(true);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
utils_1.Logger.error('No audio engine available.');
|
|
112
|
-
};
|
|
113
|
-
const pause = async () => {
|
|
114
|
-
var _a, _b;
|
|
115
|
-
if (!soundRef.current)
|
|
116
|
-
return;
|
|
117
|
-
if (UniversalAudio_1.AudioEngine.type === 'expo-audio') {
|
|
118
|
-
(_b = (_a = soundRef.current).pause) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
119
|
-
}
|
|
120
|
-
if (UniversalAudio_1.AudioEngine.type === 'rnsound') {
|
|
121
|
-
soundRef.current.pause();
|
|
122
|
-
}
|
|
123
|
-
setIsPlaying(false);
|
|
124
|
-
if (intervalRef.current)
|
|
125
|
-
clearInterval(intervalRef.current);
|
|
126
|
-
};
|
|
127
|
-
const progressWidth = progressAnim.interpolate({
|
|
128
|
-
inputRange: [0, 1],
|
|
129
|
-
outputRange: ['0%', '100%'],
|
|
130
|
-
});
|
|
131
|
-
const cardBackgroundColor = isMine ? theme.colors.ownFileBg : theme.colors.otherFileBg;
|
|
132
|
-
const textColor = isMine ? theme.colors.ownMessageText : theme.colors.otherMessageText;
|
|
133
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
134
|
-
styles.container,
|
|
135
|
-
{
|
|
136
|
-
maxWidth: maxWidth || utils_1.appSize.width(60),
|
|
137
|
-
backgroundColor: cardBackgroundColor
|
|
138
|
-
},
|
|
139
|
-
], children: [(0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [styles.playButton, { backgroundColor: theme.colors.darkRed }], onPress: isPlaying ? pause : play, disabled: isLoading, children: isLoading ? ((0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: "small", color: "#fff" })) : isPlaying ? ((0, jsx_runtime_1.jsx)(Icons_1.PauseIcon, { size: 18, color: "#fff" })) : ((0, jsx_runtime_1.jsx)(Icons_1.PlayIcon, { size: 18, color: "#fff" })) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.middle, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.progressBackground }), (0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { style: [
|
|
140
|
-
styles.progressForeground,
|
|
141
|
-
{ width: progressWidth, backgroundColor: theme.colors.darkRed },
|
|
142
|
-
] })] }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.timeText, { color: textColor }], children: currentTime })] }));
|
|
143
|
-
}
|
|
144
|
-
const styles = react_native_1.StyleSheet.create({
|
|
145
|
-
container: {
|
|
146
|
-
flexDirection: 'row',
|
|
147
|
-
minWidth: utils_1.appSize.width(60),
|
|
148
|
-
alignItems: 'center',
|
|
149
|
-
backgroundColor: theme_1.defaultTheme.colors.white,
|
|
150
|
-
paddingVertical: 8,
|
|
151
|
-
paddingHorizontal: 10,
|
|
152
|
-
borderRadius: utils_1.appSize.width(2),
|
|
153
|
-
flexShrink: 1,
|
|
154
|
-
},
|
|
155
|
-
playButton: {
|
|
156
|
-
width: 38,
|
|
157
|
-
height: 38,
|
|
158
|
-
borderRadius: 19,
|
|
159
|
-
backgroundColor: theme_1.defaultTheme.colors.darkRed,
|
|
160
|
-
justifyContent: 'center',
|
|
161
|
-
alignItems: 'center',
|
|
162
|
-
marginRight: 12,
|
|
163
|
-
},
|
|
164
|
-
timeText: {
|
|
165
|
-
marginLeft: 12,
|
|
166
|
-
width: 45,
|
|
167
|
-
fontSize: 12,
|
|
168
|
-
color: '#666',
|
|
169
|
-
textAlign: 'right',
|
|
170
|
-
},
|
|
171
|
-
middle: {
|
|
172
|
-
flex: 1,
|
|
173
|
-
justifyContent: 'center',
|
|
174
|
-
height: 4,
|
|
175
|
-
borderRadius: 2,
|
|
176
|
-
backgroundColor: '#e0e0e0',
|
|
177
|
-
overflow: 'hidden',
|
|
178
|
-
},
|
|
179
|
-
progressBackground: {
|
|
180
|
-
...react_native_1.StyleSheet.absoluteFillObject,
|
|
181
|
-
backgroundColor: '#e0e0e0',
|
|
182
|
-
},
|
|
183
|
-
progressForeground: {
|
|
184
|
-
...react_native_1.StyleSheet.absoluteFillObject,
|
|
185
|
-
backgroundColor: theme_1.defaultTheme.colors.darkRed,
|
|
186
|
-
},
|
|
187
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
interface VideoMessageProps {
|
|
2
|
-
file: {
|
|
3
|
-
uri: string;
|
|
4
|
-
};
|
|
5
|
-
maxWidth?: number;
|
|
6
|
-
time?: string;
|
|
7
|
-
onLongPress?: () => void;
|
|
8
|
-
}
|
|
9
|
-
export default function VideoCard({ file, maxWidth, time, onLongPress, }: VideoMessageProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
-
export {};
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.default = VideoCard;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const react_1 = require("react");
|
|
6
|
-
const react_native_1 = require("react-native");
|
|
7
|
-
const utils_1 = require("../../utils");
|
|
8
|
-
const theme_1 = require("../../utils/theme");
|
|
9
|
-
const Icons_1 = require("../Icons");
|
|
10
|
-
const react_native_safe_area_context_1 = require("react-native-safe-area-context");
|
|
11
|
-
const UniversalVideo_1 = require("../adapters/UniversalVideo");
|
|
12
|
-
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = react_native_1.Dimensions.get('window');
|
|
13
|
-
const DEFAULT_ASPECT_RATIO = 16 / 9;
|
|
14
|
-
function VideoCard({ file, maxWidth, time, onLongPress, // ✅ ADDED
|
|
15
|
-
}) {
|
|
16
|
-
const inset = (0, react_native_safe_area_context_1.useSafeAreaInsets)();
|
|
17
|
-
const videoRef = (0, react_1.useRef)(null);
|
|
18
|
-
const theme = (0, theme_1.useTheme)();
|
|
19
|
-
const [isPlaying, setIsPlaying] = (0, react_1.useState)(false);
|
|
20
|
-
const [duration, setDuration] = (0, react_1.useState)(0);
|
|
21
|
-
const [current, setCurrent] = (0, react_1.useState)(0);
|
|
22
|
-
const [isFullScreen, setFullScreen] = (0, react_1.useState)(false);
|
|
23
|
-
const progressAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
24
|
-
const bubbleWidth = maxWidth || utils_1.appSize.width(65);
|
|
25
|
-
const initialHeight = Math.min(Math.max(bubbleWidth / DEFAULT_ASPECT_RATIO, 120), 350);
|
|
26
|
-
const [videoHeight, setVideoHeight] = (0, react_1.useState)(initialHeight);
|
|
27
|
-
(0, react_1.useEffect)(() => {
|
|
28
|
-
progressAnim.setValue(current / (duration || 1));
|
|
29
|
-
}, [current, duration]);
|
|
30
|
-
const formatTime = (secs) => {
|
|
31
|
-
if (!secs)
|
|
32
|
-
return '0:00';
|
|
33
|
-
const m = Math.floor(secs / 60);
|
|
34
|
-
const s = Math.floor(secs % 60);
|
|
35
|
-
return `${m}:${s < 10 ? '0' + s : s}`;
|
|
36
|
-
};
|
|
37
|
-
const progressWidth = progressAnim.interpolate({
|
|
38
|
-
inputRange: [0, 1],
|
|
39
|
-
outputRange: ['0%', '100%'],
|
|
40
|
-
});
|
|
41
|
-
if (UniversalVideo_1.VideoEngine.type === 'none') {
|
|
42
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
43
|
-
styles.wrapper,
|
|
44
|
-
{
|
|
45
|
-
width: bubbleWidth,
|
|
46
|
-
height: 120,
|
|
47
|
-
justifyContent: 'center',
|
|
48
|
-
alignItems: 'center',
|
|
49
|
-
backgroundColor: theme.colors.black,
|
|
50
|
-
},
|
|
51
|
-
], children: [(0, jsx_runtime_1.jsx)(Icons_1.VideoIcon, { size: 40, color: theme.colors.white }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: { color: 'white', marginTop: 10 }, children: "Video not supported" })] }));
|
|
52
|
-
}
|
|
53
|
-
const VideoComponent = UniversalVideo_1.VideoEngine.type === 'expo-video'
|
|
54
|
-
? UniversalVideo_1.VideoEngine.ExpoVideo.Video
|
|
55
|
-
: UniversalVideo_1.VideoEngine.RNVideo;
|
|
56
|
-
const togglePlayPause = () => {
|
|
57
|
-
setIsPlaying(prev => !prev);
|
|
58
|
-
};
|
|
59
|
-
const renderVideoPlayer = (isFull) => ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: isFull
|
|
60
|
-
? styles.fullScreenWrapper
|
|
61
|
-
: [styles.videoArea, { width: bubbleWidth, height: videoHeight }], children: [(0, jsx_runtime_1.jsx)(VideoComponent, { ref: videoRef, source: { uri: file.uri }, style: isFull ? styles.fullScreenVideo : styles.video, resizeMode: isFull ? 'contain' : 'cover', paused: !isPlaying, onLoad: (meta) => {
|
|
62
|
-
var _a;
|
|
63
|
-
const dur = (_a = meta === null || meta === void 0 ? void 0 : meta.duration) !== null && _a !== void 0 ? _a : ((meta === null || meta === void 0 ? void 0 : meta.durationMillis)
|
|
64
|
-
? meta.durationMillis / 1000
|
|
65
|
-
: 0);
|
|
66
|
-
setDuration(dur);
|
|
67
|
-
if (!isFull && (meta === null || meta === void 0 ? void 0 : meta.naturalSize)) {
|
|
68
|
-
const { width: w, height: h } = meta.naturalSize;
|
|
69
|
-
if (w && h) {
|
|
70
|
-
const ratio = h / w;
|
|
71
|
-
let finalHeight = bubbleWidth * ratio;
|
|
72
|
-
finalHeight = Math.min(Math.max(finalHeight, 120), 350);
|
|
73
|
-
setVideoHeight(finalHeight);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}, onProgress: (data) => {
|
|
77
|
-
const time = (data === null || data === void 0 ? void 0 : data.currentTime) ||
|
|
78
|
-
(data === null || data === void 0 ? void 0 : data.positionMillis) / 1000 ||
|
|
79
|
-
0;
|
|
80
|
-
if (time !== undefined && time !== null) {
|
|
81
|
-
setCurrent(time);
|
|
82
|
-
}
|
|
83
|
-
}, onEnd: () => {
|
|
84
|
-
setIsPlaying(false);
|
|
85
|
-
setCurrent(duration);
|
|
86
|
-
progressAnim.setValue(1);
|
|
87
|
-
} }), (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: react_native_1.StyleSheet.absoluteFill, onPress: togglePlayPause, onLongPress: onLongPress, delayLongPress: 250, activeOpacity: 1, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.centerButton, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.controlCircle, children: isPlaying ? ((0, jsx_runtime_1.jsx)(Icons_1.PauseIcon, { size: 24 })) : ((0, jsx_runtime_1.jsx)(Icons_1.PlayIcon, { size: 24 })) }) }) }), !isFull && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.progressContainer, children: (0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { style: [
|
|
88
|
-
styles.progress,
|
|
89
|
-
{
|
|
90
|
-
width: progressWidth,
|
|
91
|
-
backgroundColor: theme.colors.darkRed,
|
|
92
|
-
},
|
|
93
|
-
] }) }), time && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.timeOverlay, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.timeText, children: time }) }))] })), isFull && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
94
|
-
styles.modalCloseButton,
|
|
95
|
-
{ top: inset.top + 10 },
|
|
96
|
-
], children: (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: () => setFullScreen(false), children: (0, jsx_runtime_1.jsx)(Icons_1.CloseIcon, { size: 24, color: "#fff" }) }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.modalProgressWrapper, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.modalTime, children: formatTime(current) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.modalProgressBackground, children: (0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { style: [
|
|
97
|
-
styles.modalProgressForeground,
|
|
98
|
-
{
|
|
99
|
-
width: progressWidth,
|
|
100
|
-
backgroundColor: theme.colors.darkRed,
|
|
101
|
-
},
|
|
102
|
-
] }) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.modalTime, children: formatTime(duration) })] })] }))] }));
|
|
103
|
-
if (isFullScreen) {
|
|
104
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: react_native_1.StyleSheet.absoluteFill, children: renderVideoPlayer(true) }));
|
|
105
|
-
}
|
|
106
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.wrapper, { width: bubbleWidth }], children: renderVideoPlayer(false) }));
|
|
107
|
-
}
|
|
108
|
-
const styles = react_native_1.StyleSheet.create({
|
|
109
|
-
wrapper: {
|
|
110
|
-
borderRadius: utils_1.appSize.width(2),
|
|
111
|
-
overflow: 'hidden',
|
|
112
|
-
backgroundColor: theme_1.defaultTheme.colors.black,
|
|
113
|
-
margin: -2,
|
|
114
|
-
},
|
|
115
|
-
videoArea: {
|
|
116
|
-
borderRadius: utils_1.appSize.width(2),
|
|
117
|
-
overflow: 'hidden',
|
|
118
|
-
backgroundColor: theme_1.defaultTheme.colors.black,
|
|
119
|
-
},
|
|
120
|
-
video: {
|
|
121
|
-
width: '100%',
|
|
122
|
-
height: '100%',
|
|
123
|
-
},
|
|
124
|
-
bottomGradient: {
|
|
125
|
-
position: 'absolute',
|
|
126
|
-
bottom: 0,
|
|
127
|
-
width: '100%',
|
|
128
|
-
height: utils_1.appSize.height(5),
|
|
129
|
-
backgroundColor: 'rgba(0,0,0,0.35)',
|
|
130
|
-
justifyContent: 'flex-end',
|
|
131
|
-
paddingBottom: 6,
|
|
132
|
-
paddingHorizontal: 8,
|
|
133
|
-
},
|
|
134
|
-
durationBadge: {
|
|
135
|
-
flexDirection: 'row',
|
|
136
|
-
alignItems: 'center',
|
|
137
|
-
alignSelf: 'flex-end',
|
|
138
|
-
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
139
|
-
paddingHorizontal: 8,
|
|
140
|
-
paddingVertical: 3,
|
|
141
|
-
borderRadius: 10,
|
|
142
|
-
},
|
|
143
|
-
durationText: {
|
|
144
|
-
color: theme_1.defaultTheme.colors.white,
|
|
145
|
-
fontSize: 13,
|
|
146
|
-
marginLeft: 5,
|
|
147
|
-
},
|
|
148
|
-
centerButton: {
|
|
149
|
-
position: 'absolute',
|
|
150
|
-
top: '40%',
|
|
151
|
-
left: '40%',
|
|
152
|
-
},
|
|
153
|
-
controlCircle: {
|
|
154
|
-
width: utils_1.appSize.width(10),
|
|
155
|
-
height: utils_1.appSize.width(10),
|
|
156
|
-
borderRadius: 31,
|
|
157
|
-
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
158
|
-
justifyContent: 'center',
|
|
159
|
-
alignItems: 'center',
|
|
160
|
-
},
|
|
161
|
-
progressContainer: {
|
|
162
|
-
height: 4,
|
|
163
|
-
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
164
|
-
justifyContent: 'center',
|
|
165
|
-
},
|
|
166
|
-
progressBackground: {
|
|
167
|
-
...react_native_1.StyleSheet.absoluteFillObject,
|
|
168
|
-
backgroundColor: theme_1.defaultTheme.colors.black,
|
|
169
|
-
},
|
|
170
|
-
progress: {
|
|
171
|
-
height: 4,
|
|
172
|
-
backgroundColor: theme_1.defaultTheme.colors.darkRed,
|
|
173
|
-
},
|
|
174
|
-
fullScreenWrapper: {
|
|
175
|
-
width: SCREEN_WIDTH,
|
|
176
|
-
height: SCREEN_HEIGHT,
|
|
177
|
-
backgroundColor: '#000',
|
|
178
|
-
justifyContent: 'center',
|
|
179
|
-
alignItems: 'center',
|
|
180
|
-
},
|
|
181
|
-
fullScreenVideo: {
|
|
182
|
-
width: SCREEN_WIDTH,
|
|
183
|
-
height: SCREEN_HEIGHT,
|
|
184
|
-
},
|
|
185
|
-
modalPlayButton: {
|
|
186
|
-
position: 'absolute',
|
|
187
|
-
alignSelf: 'center',
|
|
188
|
-
top: '45%',
|
|
189
|
-
},
|
|
190
|
-
modalCloseButton: {
|
|
191
|
-
position: 'absolute',
|
|
192
|
-
right: 20,
|
|
193
|
-
zIndex: 9999,
|
|
194
|
-
},
|
|
195
|
-
modalProgressWrapper: {
|
|
196
|
-
position: 'absolute',
|
|
197
|
-
bottom: 40,
|
|
198
|
-
width: SCREEN_WIDTH - 40,
|
|
199
|
-
flexDirection: 'row',
|
|
200
|
-
alignItems: 'center',
|
|
201
|
-
},
|
|
202
|
-
modalProgressBackground: {
|
|
203
|
-
flex: 1,
|
|
204
|
-
height: 6,
|
|
205
|
-
backgroundColor: '#e0e0e0',
|
|
206
|
-
borderRadius: 3,
|
|
207
|
-
marginHorizontal: 10,
|
|
208
|
-
overflow: 'hidden',
|
|
209
|
-
},
|
|
210
|
-
modalProgressForeground: {
|
|
211
|
-
height: '100%',
|
|
212
|
-
backgroundColor: theme_1.defaultTheme.colors.darkRed,
|
|
213
|
-
},
|
|
214
|
-
modalTime: {
|
|
215
|
-
color: '#fff',
|
|
216
|
-
fontSize: 14,
|
|
217
|
-
},
|
|
218
|
-
timeOverlay: {
|
|
219
|
-
position: 'absolute',
|
|
220
|
-
bottom: 25, // Above the progress bar
|
|
221
|
-
right: 5,
|
|
222
|
-
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
223
|
-
borderRadius: 10,
|
|
224
|
-
paddingHorizontal: 6,
|
|
225
|
-
paddingVertical: 2,
|
|
226
|
-
},
|
|
227
|
-
timeText: {
|
|
228
|
-
color: '#fff',
|
|
229
|
-
fontSize: 10,
|
|
230
|
-
fontWeight: '600',
|
|
231
|
-
},
|
|
232
|
-
});
|