@droppii-org/chat-mobile 0.2.24 → 0.2.25
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/lib/module/assets/images/btn_resend.png +0 -0
- package/lib/module/assets/images/index.js +2 -1
- package/lib/module/assets/images/index.js.map +1 -1
- package/lib/module/components/AttachmentPreview/FilePreview.js +1 -1
- package/lib/module/components/AttachmentPreview/FilePreview.js.map +1 -1
- package/lib/module/components/messages/MessageStatusIndicator.js +70 -0
- package/lib/module/components/messages/MessageStatusIndicator.js.map +1 -0
- package/lib/module/components/messages/fileMessage/index.js.map +1 -1
- package/lib/module/components/messages/imageMessage/index.js +59 -50
- package/lib/module/components/messages/imageMessage/index.js.map +1 -1
- package/lib/module/components/messages/linkMessage/index.js +14 -6
- package/lib/module/components/messages/linkMessage/index.js.map +1 -1
- package/lib/module/components/messages/mergedMessage/index.js.map +1 -1
- package/lib/module/components/messages/textMessage/index.js +14 -6
- package/lib/module/components/messages/textMessage/index.js.map +1 -1
- package/lib/module/components/messages/videoMessage/index.js.map +1 -1
- package/lib/module/config/uploadLimits.js +1 -1
- package/lib/module/config/uploadLimits.js.map +1 -1
- package/lib/module/core/useChatListener.js +35 -4
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/core/useUserListener.js +17 -7
- package/lib/module/core/useUserListener.js.map +1 -1
- package/lib/module/hooks/message/useSendMessage.js +62 -15
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/useChatCompose.js +39 -28
- package/lib/module/hooks/useChatCompose.js.map +1 -1
- package/lib/module/hooks/useChatMessages.js +39 -8
- package/lib/module/hooks/useChatMessages.js.map +1 -1
- package/lib/module/hooks/useFileAttachment.js +1 -1
- package/lib/module/hooks/useFileAttachment.js.map +1 -1
- package/lib/module/hooks/useFileDownload.js +1 -1
- package/lib/module/hooks/useFileDownload.js.map +1 -1
- package/lib/module/hooks/useImageAttachment.js +12 -3
- package/lib/module/hooks/useImageAttachment.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +13 -0
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +20 -15
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +18 -13
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/services/attachmentOrchestrator.js +34 -12
- package/lib/module/services/attachmentOrchestrator.js.map +1 -1
- package/lib/module/services/message.js +13 -5
- package/lib/module/services/message.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +4 -3
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/types/chat.js.map +1 -1
- package/lib/module/types/global.d.js +2 -0
- package/lib/module/types/global.d.js.map +1 -0
- package/lib/module/types/index.d.js +3 -0
- package/lib/module/types/index.d.js.map +1 -0
- package/lib/module/utils/UIUtils.js +37 -0
- package/lib/module/utils/UIUtils.js.map +1 -0
- package/lib/module/utils/videoThumbnailUpload.js +1 -1
- package/lib/module/utils/videoThumbnailUpload.js.map +1 -1
- package/lib/typescript/src/assets/images/index.d.ts +1 -0
- package/lib/typescript/src/assets/images/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/MessageStatusIndicator.d.ts +9 -0
- package/lib/typescript/src/components/messages/MessageStatusIndicator.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/fileMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/imageMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts +3 -8
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/mergedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts +2 -8
- package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/videoMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
- package/lib/typescript/src/core/useUserListener.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts +2 -0
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useImageAttachment.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +3 -5
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/services/attachmentOrchestrator.d.ts.map +1 -1
- package/lib/typescript/src/services/message.d.ts +7 -0
- package/lib/typescript/src/services/message.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/chat.d.ts +11 -0
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/utils/UIUtils.d.ts +4 -0
- package/lib/typescript/src/utils/UIUtils.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/assets/images/btn_resend.png +0 -0
- package/src/assets/images/index.ts +1 -0
- package/src/components/AttachmentPreview/FilePreview.tsx +1 -1
- package/src/components/messages/MessageStatusIndicator.tsx +82 -0
- package/src/components/messages/fileMessage/index.tsx +0 -1
- package/src/components/messages/imageMessage/index.tsx +71 -57
- package/src/components/messages/linkMessage/index.tsx +13 -19
- package/src/components/messages/mergedMessage/index.tsx +0 -1
- package/src/components/messages/textMessage/index.tsx +13 -18
- package/src/components/messages/videoMessage/index.tsx +0 -1
- package/src/config/uploadLimits.ts +1 -1
- package/src/core/useChatListener.ts +38 -4
- package/src/core/useUserListener.ts +17 -7
- package/src/hooks/message/useSendMessage.ts +103 -24
- package/src/hooks/useChatCompose.ts +42 -33
- package/src/hooks/useChatMessages.ts +54 -10
- package/src/hooks/useFileAttachment.ts +1 -1
- package/src/hooks/useFileDownload.ts +1 -1
- package/src/hooks/useImageAttachment.ts +13 -5
- package/src/index.tsx +2 -0
- package/src/screens/chat-detail/ChatDetail.tsx +12 -0
- package/src/screens/chat-detail/ChatListLegend.tsx +16 -13
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +19 -12
- package/src/services/attachmentOrchestrator.ts +45 -21
- package/src/services/message.ts +21 -4
- package/src/translation/resources/i18n.ts +6 -3
- package/src/types/chat.ts +6 -0
- package/src/types/global.d.ts +299 -0
- package/src/types/index.d.ts +1 -0
- package/src/utils/UIUtils.ts +31 -0
- package/src/utils/videoThumbnailUpload.ts +1 -1
- package/lib/module/hooks/useVideoAttachment.js +0 -251
- package/lib/module/hooks/useVideoAttachment.js.map +0 -1
- package/lib/module/utils/ui.js +0 -17
- package/lib/module/utils/ui.js.map +0 -1
- package/lib/typescript/src/hooks/useVideoAttachment.d.ts +0 -29
- package/lib/typescript/src/hooks/useVideoAttachment.d.ts.map +0 -1
- package/lib/typescript/src/utils/ui.d.ts +0 -13
- package/lib/typescript/src/utils/ui.d.ts.map +0 -1
- package/src/hooks/useVideoAttachment.ts +0 -334
- package/src/utils/ui.ts +0 -19
|
@@ -1,9 +1,39 @@
|
|
|
1
1
|
import { memo, useState, useCallback, useMemo } from 'react';
|
|
2
|
-
import { StyleSheet,
|
|
2
|
+
import { StyleSheet, useWindowDimensions } from 'react-native';
|
|
3
3
|
import { KContainer, KImage } from '@droppii/libs';
|
|
4
4
|
import ChatImageDimens from '../../../utils/chatImageDimens';
|
|
5
5
|
import { messageStyles } from '../styles';
|
|
6
6
|
import type { BaseLegendMessageProps } from '../types';
|
|
7
|
+
const DEFAULT_DIMS = { width: 300, height: 300 };
|
|
8
|
+
|
|
9
|
+
// Module-level cache: survives component unmount/remount (e.g. status updates)
|
|
10
|
+
// Bounded to ~500 entries (~40KB) — evicts oldest when full
|
|
11
|
+
const MAX_CACHE = 500;
|
|
12
|
+
const dimsCache = new Map<string, { width: number; height: number }>();
|
|
13
|
+
const cacheDims = (url: string, dims: { width: number; height: number }) => {
|
|
14
|
+
if (dimsCache.size >= MAX_CACHE) {
|
|
15
|
+
dimsCache.delete(dimsCache.keys().next().value!);
|
|
16
|
+
}
|
|
17
|
+
dimsCache.set(url, dims);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const calcDisplayDims = (
|
|
21
|
+
imgWidth: number,
|
|
22
|
+
imgHeight: number,
|
|
23
|
+
windowWidth: number
|
|
24
|
+
) => {
|
|
25
|
+
const aspectRatio = imgWidth / imgHeight;
|
|
26
|
+
const horizontalBuffer = 16;
|
|
27
|
+
const maxWidth = Math.floor(windowWidth * 0.75 - horizontalBuffer);
|
|
28
|
+
let finalWidth = maxWidth;
|
|
29
|
+
let finalHeight = maxWidth / aspectRatio;
|
|
30
|
+
const maxHeight = 500;
|
|
31
|
+
if (finalHeight > maxHeight) {
|
|
32
|
+
finalHeight = maxHeight;
|
|
33
|
+
finalWidth = finalHeight * aspectRatio;
|
|
34
|
+
}
|
|
35
|
+
return { width: Math.round(finalWidth), height: Math.round(finalHeight) };
|
|
36
|
+
};
|
|
7
37
|
|
|
8
38
|
export const ImageMessage = memo(
|
|
9
39
|
({ message, onMediaPress }: BaseLegendMessageProps) => {
|
|
@@ -15,60 +45,46 @@ export const ImageMessage = memo(
|
|
|
15
45
|
pictureElem?.bigPicture?.url ||
|
|
16
46
|
pictureElem?.sourcePicture?.url;
|
|
17
47
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
const metaWidth =
|
|
49
|
+
pictureElem?.bigPicture?.width ||
|
|
50
|
+
pictureElem?.snapshotPicture?.width ||
|
|
51
|
+
pictureElem?.width;
|
|
52
|
+
const metaHeight =
|
|
53
|
+
pictureElem?.bigPicture?.height ||
|
|
54
|
+
pictureElem?.snapshotPicture?.height ||
|
|
55
|
+
pictureElem?.height;
|
|
56
|
+
|
|
57
|
+
// Fallback dims from onLoad when metadata is missing (zero extra request)
|
|
58
|
+
const cachedDims = rawImageUrl
|
|
59
|
+
? (dimsCache.get(rawImageUrl) ?? null)
|
|
60
|
+
: null;
|
|
61
|
+
const [loadedDims, setLoadedDims] = useState<{
|
|
62
|
+
width: number;
|
|
63
|
+
height: number;
|
|
64
|
+
} | null>(cachedDims);
|
|
30
65
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
66
|
+
const imageDimensions = useMemo(() => {
|
|
67
|
+
if (!rawImageUrl) return DEFAULT_DIMS;
|
|
68
|
+
const srcWidth = metaWidth || loadedDims?.width;
|
|
69
|
+
const srcHeight = metaHeight || loadedDims?.height;
|
|
70
|
+
if (!srcWidth || !srcHeight) return DEFAULT_DIMS;
|
|
71
|
+
return calcDisplayDims(srcWidth, srcHeight, windowWidth);
|
|
72
|
+
}, [metaWidth, metaHeight, loadedDims, rawImageUrl, windowWidth]);
|
|
73
|
+
|
|
74
|
+
const handleLoad = useCallback(
|
|
75
|
+
(e: any) => {
|
|
76
|
+
setIsLoading(false);
|
|
77
|
+
if (!metaWidth || !metaHeight) {
|
|
78
|
+
const { width, height } = e?.nativeEvent ?? {};
|
|
79
|
+
if (width && height) {
|
|
80
|
+
const dims = { width, height };
|
|
81
|
+
if (rawImageUrl) cacheDims(rawImageUrl, dims);
|
|
82
|
+
setLoadedDims(dims);
|
|
83
|
+
}
|
|
44
84
|
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Account for all margins/padding: use 75% of window width with buffer
|
|
49
|
-
const horizontalBuffer = 16; // Left and right combined buffer
|
|
50
|
-
const maxWidth = Math.floor(windowWidth * 0.75 - horizontalBuffer);
|
|
51
|
-
|
|
52
|
-
// Calculate final dimensions
|
|
53
|
-
let finalWidth = maxWidth;
|
|
54
|
-
let finalHeight = maxWidth / aspectRatio;
|
|
55
|
-
|
|
56
|
-
// Cap maximum height to avoid overly tall images
|
|
57
|
-
const maxHeight = 500;
|
|
58
|
-
if (finalHeight > maxHeight) {
|
|
59
|
-
finalHeight = maxHeight;
|
|
60
|
-
finalWidth = finalHeight * aspectRatio;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
width: Math.round(finalWidth),
|
|
65
|
-
height: Math.round(finalHeight),
|
|
66
|
-
};
|
|
67
|
-
}, [pictureElem, rawImageUrl, windowWidth]);
|
|
68
|
-
|
|
69
|
-
const handleLoadEnd = useCallback(() => {
|
|
70
|
-
setIsLoading(false);
|
|
71
|
-
}, []);
|
|
85
|
+
},
|
|
86
|
+
[metaWidth, metaHeight, rawImageUrl]
|
|
87
|
+
);
|
|
72
88
|
|
|
73
89
|
const handlePress = useCallback(() => {
|
|
74
90
|
onMediaPress?.([{ url: rawImageUrl }], 0);
|
|
@@ -78,12 +94,12 @@ export const ImageMessage = memo(
|
|
|
78
94
|
return null;
|
|
79
95
|
}
|
|
80
96
|
|
|
81
|
-
// Use dynamic optimization matching calculated dimensions
|
|
82
97
|
const optimizedUrl = ChatImageDimens.getOptimizedImageUrlWithDimensions(
|
|
83
98
|
rawImageUrl,
|
|
84
99
|
imageDimensions.width,
|
|
85
100
|
imageDimensions.height
|
|
86
101
|
);
|
|
102
|
+
|
|
87
103
|
return (
|
|
88
104
|
<KContainer.View style={[messageStyles.wrapper, styles.wrapper]}>
|
|
89
105
|
<KContainer.Touchable
|
|
@@ -102,9 +118,8 @@ export const ImageMessage = memo(
|
|
|
102
118
|
width={imageDimensions.width}
|
|
103
119
|
height={imageDimensions.height}
|
|
104
120
|
style={[messageStyles.imageBubble]}
|
|
105
|
-
|
|
121
|
+
onLoad={handleLoad}
|
|
106
122
|
/>
|
|
107
|
-
|
|
108
123
|
{isLoading && <KContainer.View style={styles.skeleton} />}
|
|
109
124
|
</KContainer.Touchable>
|
|
110
125
|
</KContainer.View>
|
|
@@ -116,7 +131,6 @@ ImageMessage.displayName = 'ImageMessage';
|
|
|
116
131
|
|
|
117
132
|
const styles = StyleSheet.create({
|
|
118
133
|
wrapper: {
|
|
119
|
-
// paddingRight: 8,
|
|
120
134
|
overflow: 'hidden',
|
|
121
135
|
},
|
|
122
136
|
skeleton: {
|
|
@@ -7,18 +7,10 @@ import {
|
|
|
7
7
|
KColors,
|
|
8
8
|
KSpacingValue,
|
|
9
9
|
} from '@droppii/libs';
|
|
10
|
-
import type { DMessageItem } from '../../../types/chat';
|
|
11
10
|
import type { IUrlMetadata } from '../../../types/common';
|
|
12
11
|
import { formatMessageTime } from '../types';
|
|
12
|
+
import type { BaseLegendMessageProps } from '../types';
|
|
13
13
|
import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
|
|
14
|
-
|
|
15
|
-
interface BaseLegendMessageProps {
|
|
16
|
-
message: DMessageItem;
|
|
17
|
-
isOutgoing: boolean;
|
|
18
|
-
createdAtTime: number;
|
|
19
|
-
onPressUrl?: (url: string) => void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
14
|
const parseUrlMetadata = (ex?: string): IUrlMetadata | undefined => {
|
|
23
15
|
if (!ex) return undefined;
|
|
24
16
|
try {
|
|
@@ -80,7 +72,7 @@ export const LinkMessage = memo(
|
|
|
80
72
|
isOutgoing,
|
|
81
73
|
createdAtTime,
|
|
82
74
|
onPressUrl,
|
|
83
|
-
}: BaseLegendMessageProps) => {
|
|
75
|
+
}: BaseLegendMessageProps & { onPressUrl?: (url: string) => void }) => {
|
|
84
76
|
const content = message?.urlTextElem?.content;
|
|
85
77
|
const urls = message?.urlTextElem?.urls ?? [];
|
|
86
78
|
const metadata = parseUrlMetadata(message.ex);
|
|
@@ -147,15 +139,17 @@ export const LinkMessage = memo(
|
|
|
147
139
|
)}
|
|
148
140
|
</KContainer.View>
|
|
149
141
|
|
|
150
|
-
{
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
142
|
+
<KContainer.View row alignItems style={{ gap: 4, marginTop: 2 }}>
|
|
143
|
+
{!isOutgoing && timeLabel ? (
|
|
144
|
+
<KLabel.Text
|
|
145
|
+
typo="TextXsNormal"
|
|
146
|
+
color={CHAT_BUBBLE_COLORS.timestamp}
|
|
147
|
+
marginL="0.25rem"
|
|
148
|
+
>
|
|
149
|
+
{timeLabel}
|
|
150
|
+
</KLabel.Text>
|
|
151
|
+
) : null}
|
|
152
|
+
</KContainer.View>
|
|
159
153
|
</KContainer.View>
|
|
160
154
|
);
|
|
161
155
|
}
|
|
@@ -3,7 +3,6 @@ import { KContainer } from '@droppii/libs';
|
|
|
3
3
|
import { MergedImageGrid } from '../../MergedImageGrid';
|
|
4
4
|
import { messageStyles } from '../styles';
|
|
5
5
|
import type { BaseLegendMessageProps } from '../types';
|
|
6
|
-
|
|
7
6
|
export const MergedMessage = memo(
|
|
8
7
|
({ message, isOutgoing, onMediaPress }: BaseLegendMessageProps) => {
|
|
9
8
|
const mergeElem = (message as any).mergeElem;
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
2
|
import { KContainer, KLabel } from '@droppii/libs';
|
|
3
3
|
import { getMessageText } from '../../../utils/legendListMessage';
|
|
4
|
-
import type { DMessageItem } from '../../../types/chat';
|
|
5
4
|
import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
|
|
6
5
|
import { messageStyles } from '../styles';
|
|
7
6
|
import { formatMessageTime } from '../types';
|
|
8
|
-
|
|
9
|
-
interface TextMessageProps {
|
|
10
|
-
message: DMessageItem;
|
|
11
|
-
isOutgoing: boolean;
|
|
12
|
-
createdAtTime: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
7
|
+
import type { BaseLegendMessageProps } from '../types';
|
|
15
8
|
export const TextMessage = memo(
|
|
16
|
-
({ message, isOutgoing, createdAtTime }:
|
|
9
|
+
({ message, isOutgoing, createdAtTime }: BaseLegendMessageProps) => {
|
|
17
10
|
const messageText = getMessageText(message);
|
|
18
11
|
const timeLabel = formatMessageTime(createdAtTime);
|
|
19
12
|
|
|
@@ -36,15 +29,17 @@ export const TextMessage = memo(
|
|
|
36
29
|
</KLabel.Text>
|
|
37
30
|
</KContainer.View>
|
|
38
31
|
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
<KContainer.View row alignItems style={{ gap: 4, marginTop: 2 }}>
|
|
33
|
+
{!isOutgoing && timeLabel ? (
|
|
34
|
+
<KLabel.Text
|
|
35
|
+
typo="TextXsNormal"
|
|
36
|
+
color={CHAT_BUBBLE_COLORS.timestamp}
|
|
37
|
+
marginL={'0.25rem'}
|
|
38
|
+
>
|
|
39
|
+
{timeLabel}
|
|
40
|
+
</KLabel.Text>
|
|
41
|
+
) : null}
|
|
42
|
+
</KContainer.View>
|
|
48
43
|
</KContainer.View>
|
|
49
44
|
);
|
|
50
45
|
}
|
|
@@ -7,7 +7,6 @@ import ChatImageDimens, {
|
|
|
7
7
|
import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
|
|
8
8
|
import { messageStyles } from '../styles';
|
|
9
9
|
import type { BaseLegendMessageProps } from '../types';
|
|
10
|
-
|
|
11
10
|
const formatDuration = (durationMs: number): string => {
|
|
12
11
|
const totalSeconds = Math.floor(durationMs / 1000);
|
|
13
12
|
const minutes = Math.floor(totalSeconds / 60);
|
|
@@ -2,30 +2,58 @@ import OpenIMSDK, {
|
|
|
2
2
|
OpenIMEvent,
|
|
3
3
|
type ConversationItem,
|
|
4
4
|
type GroupMemberItem,
|
|
5
|
+
type MessageItem,
|
|
5
6
|
} from '@droppii/openim-rn-client-sdk';
|
|
6
7
|
import { useCallback, useEffect } from 'react';
|
|
7
8
|
import { useConversationStore } from '../store/conversation';
|
|
9
|
+
import { useMessageStore } from '../store/message';
|
|
8
10
|
|
|
9
11
|
export const useChatListener = () => {
|
|
12
|
+
const updateOneMessage = useMessageStore((state) => state.updateOneMessage);
|
|
13
|
+
|
|
14
|
+
const onSendMessageProgress = useCallback(
|
|
15
|
+
({ progress, message }: { progress: number; message: MessageItem }) => {
|
|
16
|
+
console.log('[Listener] SendMessageProgress', {
|
|
17
|
+
clientMsgID: message.clientMsgID,
|
|
18
|
+
status: message.status,
|
|
19
|
+
progress,
|
|
20
|
+
});
|
|
21
|
+
updateOneMessage(message);
|
|
22
|
+
},
|
|
23
|
+
[updateOneMessage]
|
|
24
|
+
);
|
|
25
|
+
|
|
10
26
|
const onConversationChanged = useCallback(
|
|
11
27
|
(conversations: ConversationItem[]) => {
|
|
12
|
-
console.log('
|
|
28
|
+
console.log('[Listener] OnConversationChanged', {
|
|
29
|
+
count: conversations.length,
|
|
30
|
+
ids: conversations.map((c) => c.conversationID),
|
|
31
|
+
});
|
|
13
32
|
useConversationStore.getState().updateConversations(conversations);
|
|
14
33
|
},
|
|
15
34
|
[]
|
|
16
35
|
);
|
|
17
36
|
|
|
18
37
|
const onNewConversation = useCallback((conversations: ConversationItem[]) => {
|
|
19
|
-
console.log('
|
|
38
|
+
console.log('[Listener] OnNewConversation', {
|
|
39
|
+
count: conversations.length,
|
|
40
|
+
ids: conversations.map((c) => c.conversationID),
|
|
41
|
+
});
|
|
20
42
|
useConversationStore.getState().updateConversations(conversations);
|
|
21
43
|
}, []);
|
|
22
44
|
|
|
23
45
|
const onGroupMemberAdded = useCallback((member: GroupMemberItem) => {
|
|
24
|
-
console.log('
|
|
46
|
+
console.log('[Listener] OnGroupMemberAdded', {
|
|
47
|
+
userID: member.userID,
|
|
48
|
+
groupID: member.groupID,
|
|
49
|
+
});
|
|
25
50
|
}, []);
|
|
26
51
|
|
|
27
52
|
const onGroupMemberDeleted = useCallback((member: GroupMemberItem) => {
|
|
28
|
-
console.log('
|
|
53
|
+
console.log('[Listener] OnGroupMemberDeleted', {
|
|
54
|
+
userID: member.userID,
|
|
55
|
+
groupID: member.groupID,
|
|
56
|
+
});
|
|
29
57
|
}, []);
|
|
30
58
|
|
|
31
59
|
useEffect(() => {
|
|
@@ -37,6 +65,9 @@ export const useChatListener = () => {
|
|
|
37
65
|
OpenIMSDK.on(OpenIMEvent.OnGroupMemberAdded, onGroupMemberAdded);
|
|
38
66
|
OpenIMSDK.on(OpenIMEvent.OnGroupMemberDeleted, onGroupMemberDeleted);
|
|
39
67
|
|
|
68
|
+
// Message status
|
|
69
|
+
OpenIMSDK.on(OpenIMEvent.SendMessageProgress, onSendMessageProgress);
|
|
70
|
+
|
|
40
71
|
return () => {
|
|
41
72
|
// Conversation
|
|
42
73
|
OpenIMSDK.off(OpenIMEvent.OnConversationChanged, onConversationChanged);
|
|
@@ -45,6 +76,9 @@ export const useChatListener = () => {
|
|
|
45
76
|
// Group members
|
|
46
77
|
OpenIMSDK.off(OpenIMEvent.OnGroupMemberAdded, onGroupMemberAdded);
|
|
47
78
|
OpenIMSDK.off(OpenIMEvent.OnGroupMemberDeleted, onGroupMemberDeleted);
|
|
79
|
+
|
|
80
|
+
// Message status
|
|
81
|
+
OpenIMSDK.off(OpenIMEvent.SendMessageProgress, onSendMessageProgress);
|
|
48
82
|
};
|
|
49
83
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
50
84
|
}, []);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import OpenIMSDK, {
|
|
2
2
|
LoginStatus,
|
|
3
3
|
OpenIMEvent,
|
|
4
|
+
type OpenIMEventBaseErrorType,
|
|
4
5
|
} from '@droppii/openim-rn-client-sdk';
|
|
5
6
|
import { useCallback, useEffect } from 'react';
|
|
6
7
|
import { useUserStore } from '../store';
|
|
@@ -28,36 +29,45 @@ export const useUserListener = () => {
|
|
|
28
29
|
}, [isLoggedIn, fetchAndUpdateUser]);
|
|
29
30
|
|
|
30
31
|
const onConnecting = useCallback(() => {
|
|
32
|
+
console.log('[Listener] OnConnecting');
|
|
31
33
|
useUserStore.getState().updateConnectStatus(LoginStatus.Logging);
|
|
32
34
|
}, []);
|
|
33
35
|
|
|
34
36
|
const onConnectSuccess = useCallback(() => {
|
|
37
|
+
console.log('[Listener] OnConnectSuccess');
|
|
35
38
|
useUserStore.getState().updateConnectStatus(LoginStatus.Logged);
|
|
36
39
|
}, []);
|
|
37
40
|
|
|
38
|
-
const onConnectFailed = useCallback(() => {
|
|
39
|
-
|
|
41
|
+
const onConnectFailed = useCallback((data: OpenIMEventBaseErrorType) => {
|
|
42
|
+
console.log('[Listener] OnConnectFailed', data);
|
|
40
43
|
}, []);
|
|
41
44
|
|
|
42
|
-
const onSyncServerStart = useCallback(() => {
|
|
45
|
+
const onSyncServerStart = useCallback((isSyncing: boolean) => {
|
|
46
|
+
console.log('[Listener] OnSyncServerStart', { isSyncing });
|
|
43
47
|
useUserStore.getState().updateSyncStatus(SyncStatus.Loading);
|
|
44
48
|
}, []);
|
|
45
49
|
|
|
46
50
|
const onSyncServerFinish = useCallback(() => {
|
|
51
|
+
console.log('[Listener] OnSyncServerFinish');
|
|
47
52
|
useUserStore.getState().updateSyncStatus(SyncStatus.Success);
|
|
48
53
|
}, []);
|
|
49
54
|
|
|
50
55
|
const onSyncServerFailed = useCallback(() => {
|
|
56
|
+
console.log('[Listener] OnSyncServerFailed');
|
|
51
57
|
useUserStore.getState().updateSyncStatus(SyncStatus.Failed);
|
|
52
58
|
}, []);
|
|
53
59
|
|
|
60
|
+
const onUserToken = useCallback(() => {
|
|
61
|
+
console.log('[Listener] OnUserToken expired/invalid');
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
54
64
|
useEffect(() => {
|
|
55
65
|
// User
|
|
56
66
|
OpenIMSDK.on(OpenIMEvent.OnConnecting, onConnecting);
|
|
57
67
|
OpenIMSDK.on(OpenIMEvent.OnConnectSuccess, onConnectSuccess);
|
|
58
68
|
OpenIMSDK.on(OpenIMEvent.OnConnectFailed, onConnectFailed);
|
|
59
|
-
OpenIMSDK.on(OpenIMEvent.OnUserTokenExpired,
|
|
60
|
-
OpenIMSDK.on(OpenIMEvent.OnUserTokenInvalid,
|
|
69
|
+
OpenIMSDK.on(OpenIMEvent.OnUserTokenExpired, onUserToken);
|
|
70
|
+
OpenIMSDK.on(OpenIMEvent.OnUserTokenInvalid, onUserToken);
|
|
61
71
|
|
|
62
72
|
// Sync
|
|
63
73
|
OpenIMSDK.on(OpenIMEvent.OnSyncServerStart, onSyncServerStart);
|
|
@@ -69,8 +79,8 @@ export const useUserListener = () => {
|
|
|
69
79
|
OpenIMSDK.off(OpenIMEvent.OnConnecting, onConnecting);
|
|
70
80
|
OpenIMSDK.off(OpenIMEvent.OnConnectSuccess, onConnectSuccess);
|
|
71
81
|
OpenIMSDK.off(OpenIMEvent.OnConnectFailed, onConnectFailed);
|
|
72
|
-
OpenIMSDK.
|
|
73
|
-
OpenIMSDK.
|
|
82
|
+
OpenIMSDK.off(OpenIMEvent.OnUserTokenExpired, onUserToken);
|
|
83
|
+
OpenIMSDK.off(OpenIMEvent.OnUserTokenInvalid, onUserToken);
|
|
74
84
|
|
|
75
85
|
// Sync
|
|
76
86
|
OpenIMSDK.off(OpenIMEvent.OnSyncServerStart, onSyncServerStart);
|
|
@@ -1,23 +1,64 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import type { UploadFileItem } from 'react-native-fs';
|
|
3
|
-
import { useConversationStore } from '../../store';
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
MessageType,
|
|
7
|
-
OfflinePush,
|
|
8
|
-
} from '@droppii/openim-rn-client-sdk';
|
|
3
|
+
import { useConversationStore } from '../../store/conversation';
|
|
4
|
+
import type { MessageItem, OfflinePush } from '@droppii/openim-rn-client-sdk';
|
|
5
|
+
import { MessageStatus, MessageType } from '@droppii/openim-rn-client-sdk';
|
|
9
6
|
import { extractUrls } from '../../utils/url';
|
|
10
7
|
import OpenIMSDK from '@droppii/openim-rn-client-sdk';
|
|
11
8
|
import type { IMessageItemEx } from '../../types/chat';
|
|
12
9
|
import { useChatContext } from '../../context';
|
|
13
10
|
import { generateContentBasedOnMessageType } from '../../utils/conversation';
|
|
14
11
|
import { useMessageStore } from '../../store/message';
|
|
12
|
+
|
|
15
13
|
import type { IUrlMetadata } from '../../types/common';
|
|
16
14
|
import type { IRunConversationFlowPayload } from '../../types/flows';
|
|
17
15
|
|
|
16
|
+
const buildSendParams = (
|
|
17
|
+
message: MessageItem,
|
|
18
|
+
recvID: string,
|
|
19
|
+
groupID: string,
|
|
20
|
+
extendMessageInfo: IMessageItemEx,
|
|
21
|
+
plainText: string
|
|
22
|
+
) => ({
|
|
23
|
+
groupID,
|
|
24
|
+
recvID,
|
|
25
|
+
message: { ...message, ex: JSON.stringify(extendMessageInfo) || '{}' },
|
|
26
|
+
offlinePushInfo: generateOfflinePushInfo(message.contentType, plainText),
|
|
27
|
+
});
|
|
28
|
+
|
|
18
29
|
export const useSendMessage = () => {
|
|
19
30
|
const applicationType = useChatContext().applicationType;
|
|
20
31
|
const pushNewMessage = useMessageStore((state) => state.pushNewMessage);
|
|
32
|
+
const updateOneMessage = useMessageStore((state) => state.updateOneMessage);
|
|
33
|
+
const deleteOneMessage = useMessageStore((state) => state.deleteOneMessage);
|
|
34
|
+
const dispatchSend = useCallback(
|
|
35
|
+
async (
|
|
36
|
+
message: MessageItem,
|
|
37
|
+
recvID: string,
|
|
38
|
+
groupID: string,
|
|
39
|
+
extendMessageInfo: IMessageItemEx,
|
|
40
|
+
plainText: string
|
|
41
|
+
) => {
|
|
42
|
+
pushNewMessage({ ...message, status: MessageStatus.Sending });
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const sent = await OpenIMSDK.sendMessage(
|
|
46
|
+
buildSendParams(
|
|
47
|
+
message,
|
|
48
|
+
recvID,
|
|
49
|
+
groupID,
|
|
50
|
+
extendMessageInfo,
|
|
51
|
+
plainText
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
deleteOneMessage(message.clientMsgID);
|
|
55
|
+
pushNewMessage(sent);
|
|
56
|
+
} catch {
|
|
57
|
+
updateOneMessage({ ...message, status: MessageStatus.Failed });
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
[pushNewMessage, updateOneMessage, deleteOneMessage]
|
|
61
|
+
);
|
|
21
62
|
|
|
22
63
|
const sendMessage = useCallback(
|
|
23
64
|
async ({
|
|
@@ -27,7 +68,7 @@ export const useSendMessage = () => {
|
|
|
27
68
|
flow,
|
|
28
69
|
}: {
|
|
29
70
|
plainText?: string;
|
|
30
|
-
files?: UploadFileItem[];
|
|
71
|
+
files?: UploadFileItem[];
|
|
31
72
|
urlMetadata?: IUrlMetadata;
|
|
32
73
|
flow?: IRunConversationFlowPayload;
|
|
33
74
|
}) => {
|
|
@@ -41,7 +82,6 @@ export const useSendMessage = () => {
|
|
|
41
82
|
if (!file) continue;
|
|
42
83
|
}
|
|
43
84
|
|
|
44
|
-
// Allow empty text if urlMetadata (preview link) is present
|
|
45
85
|
const hasText = !!plainText && plainText.trim() !== '';
|
|
46
86
|
const hasPreview = !!urlMetadata?.url;
|
|
47
87
|
|
|
@@ -51,7 +91,6 @@ export const useSendMessage = () => {
|
|
|
51
91
|
let message: MessageItem | null = null;
|
|
52
92
|
|
|
53
93
|
if (isUrlMessage) {
|
|
54
|
-
// Create URL message even with empty text if preview exists
|
|
55
94
|
message = await createUrlTextMessage(plainText || '', urls);
|
|
56
95
|
} else if (hasText) {
|
|
57
96
|
message = await createTextMessage(plainText);
|
|
@@ -68,27 +107,67 @@ export const useSendMessage = () => {
|
|
|
68
107
|
flow,
|
|
69
108
|
};
|
|
70
109
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
recvID
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
await dispatchSend(
|
|
111
|
+
message,
|
|
112
|
+
recvID || '',
|
|
113
|
+
groupID || '',
|
|
114
|
+
extendMessageInfo,
|
|
115
|
+
plainText || ''
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
[applicationType, dispatchSend]
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const retryMessage = useCallback(
|
|
123
|
+
async (message: MessageItem) => {
|
|
124
|
+
const { userID: recvID, groupID } =
|
|
125
|
+
useConversationStore.getState().getCurrentConversation() || {};
|
|
126
|
+
if (!recvID && !groupID) return;
|
|
127
|
+
|
|
128
|
+
updateOneMessage({ ...message, status: MessageStatus.Sending });
|
|
129
|
+
|
|
130
|
+
// Attachment messages were sent via sendMessageNotOss (URL already uploaded).
|
|
131
|
+
// Text/URL messages use sendMessage (SDK handles upload).
|
|
132
|
+
const isAttachment = (
|
|
133
|
+
[
|
|
134
|
+
MessageType.PictureMessage,
|
|
135
|
+
MessageType.VideoMessage,
|
|
136
|
+
MessageType.FileMessage,
|
|
137
|
+
MessageType.MergeMessage,
|
|
138
|
+
] as MessageType[]
|
|
139
|
+
).includes(message.contentType);
|
|
140
|
+
|
|
141
|
+
const params = {
|
|
142
|
+
recvID: recvID || '',
|
|
143
|
+
groupID: groupID || '',
|
|
144
|
+
message,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const sent = isAttachment
|
|
149
|
+
? await OpenIMSDK.sendMessageNotOss(params)
|
|
150
|
+
: await OpenIMSDK.sendMessage(
|
|
151
|
+
buildSendParams(
|
|
152
|
+
message,
|
|
153
|
+
recvID || '',
|
|
154
|
+
groupID || '',
|
|
155
|
+
JSON.parse(message.ex || '{}') as IMessageItemEx,
|
|
156
|
+
''
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
deleteOneMessage(message.clientMsgID);
|
|
160
|
+
pushNewMessage(sent);
|
|
161
|
+
} catch {
|
|
162
|
+
updateOneMessage({ ...message, status: MessageStatus.Failed });
|
|
85
163
|
}
|
|
86
164
|
},
|
|
87
|
-
[
|
|
165
|
+
[pushNewMessage, updateOneMessage, deleteOneMessage]
|
|
88
166
|
);
|
|
89
167
|
|
|
90
168
|
return {
|
|
91
169
|
sendMessage,
|
|
170
|
+
retryMessage,
|
|
92
171
|
};
|
|
93
172
|
};
|
|
94
173
|
|