@droppii-org/chat-mobile 0.2.82 → 0.2.84
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/components/messages/linkMessage/index.js +33 -29
- package/lib/module/components/messages/linkMessage/index.js.map +1 -1
- package/lib/module/components/messages/quotedMessage/index.js +79 -55
- package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
- package/lib/module/components/messages/stickerMessage/index.js +38 -13
- package/lib/module/components/messages/stickerMessage/index.js.map +1 -1
- package/lib/module/components/messages/textMessage/index.js +41 -22
- package/lib/module/components/messages/textMessage/index.js.map +1 -1
- package/lib/module/components/messages/types.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +16 -8
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/components/ChatMessageInput.js +2 -2
- package/lib/module/screens/chat-detail/components/ChatMessageInput.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatDay.js +4 -9
- package/lib/module/screens/chat-detail/legend/LegendChatDay.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +9 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/utils/legendListMessage.js +9 -1
- package/lib/module/utils/legendListMessage.js.map +1 -1
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts +2 -2
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/stickerMessage/index.d.ts +2 -2
- package/lib/typescript/src/components/messages/stickerMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts +2 -2
- package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/types.d.ts +1 -0
- package/lib/typescript/src/components/messages/types.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatDay.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +2 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/utils/legendListMessage.d.ts +4 -3
- package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/messages/linkMessage/index.tsx +41 -34
- package/src/components/messages/quotedMessage/index.tsx +95 -64
- package/src/components/messages/stickerMessage/index.tsx +40 -11
- package/src/components/messages/textMessage/index.tsx +55 -33
- package/src/components/messages/types.ts +1 -0
- package/src/screens/chat-detail/ChatListLegend.tsx +19 -5
- package/src/screens/chat-detail/components/ChatMessageInput.tsx +2 -2
- package/src/screens/chat-detail/legend/LegendChatDay.tsx +6 -9
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +18 -1
- package/src/utils/legendListMessage.ts +13 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
2
|
import { StyleSheet } from 'react-native';
|
|
3
3
|
import { KContainer, KLabel, KColors, KSpacingValue } from '@droppii/libs';
|
|
4
|
-
import type
|
|
4
|
+
import { formatMessageTime, type BaseLegendMessageProps } from '../types';
|
|
5
5
|
import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
|
|
6
6
|
import { parseUrlMetadata } from '../../../utils/messageUtils';
|
|
7
7
|
import { splitTextByUrls } from '../../../utils/url';
|
|
@@ -39,35 +39,57 @@ export const LinkMessage = memo(
|
|
|
39
39
|
({
|
|
40
40
|
message,
|
|
41
41
|
isOutgoing,
|
|
42
|
+
createdAtTime,
|
|
42
43
|
onPressUrl,
|
|
43
44
|
isHighlighted,
|
|
45
|
+
isTimeRevealed,
|
|
44
46
|
groupPosition,
|
|
45
47
|
}: BaseLegendMessageProps & { onPressUrl?: (url: string) => void }) => {
|
|
46
48
|
const content = message?.urlTextElem?.content;
|
|
47
49
|
const urls = message?.urlTextElem?.urls ?? [];
|
|
48
50
|
const metadata = parseUrlMetadata(message.ex);
|
|
51
|
+
const timeLabel = formatMessageTime(createdAtTime);
|
|
49
52
|
|
|
50
53
|
return (
|
|
51
|
-
|
|
54
|
+
<>
|
|
55
|
+
<BubbleTailRow isOutgoing={isOutgoing} groupPosition={groupPosition}>
|
|
56
|
+
<KContainer.View
|
|
57
|
+
style={[
|
|
58
|
+
styles.bubble,
|
|
59
|
+
isOutgoing ? styles.bubbleSent : styles.bubbleReceived,
|
|
60
|
+
isHighlighted && messageStyles.bubbleHighlighted,
|
|
61
|
+
getTailCornerStyle(isOutgoing, groupPosition),
|
|
62
|
+
]}
|
|
63
|
+
>
|
|
64
|
+
{!!content?.trim() && (
|
|
65
|
+
<KContainer.View style={styles.textRow}>
|
|
66
|
+
{renderTextWithLinks(content, urls, onPressUrl)}
|
|
67
|
+
</KContainer.View>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{!!metadata && (
|
|
71
|
+
<LinkPreviewCard metadata={metadata} onPressUrl={onPressUrl} />
|
|
72
|
+
)}
|
|
73
|
+
</KContainer.View>
|
|
74
|
+
</BubbleTailRow>
|
|
75
|
+
|
|
52
76
|
<KContainer.View
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
isHighlighted && messageStyles.bubbleHighlighted,
|
|
57
|
-
getTailCornerStyle(isOutgoing, groupPosition),
|
|
58
|
-
]}
|
|
77
|
+
row
|
|
78
|
+
alignItems
|
|
79
|
+
style={[styles.timeLabel, isOutgoing && styles.timeLabelRight]}
|
|
59
80
|
>
|
|
60
|
-
{
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
{isTimeRevealed && timeLabel ? (
|
|
82
|
+
<KLabel.Text
|
|
83
|
+
typo="TextXsNormal"
|
|
84
|
+
color={CHAT_BUBBLE_COLORS.timestamp}
|
|
85
|
+
marginL={isOutgoing ? undefined : '0.25rem'}
|
|
86
|
+
marginR={isOutgoing ? '0.25rem' : undefined}
|
|
87
|
+
>
|
|
88
|
+
{timeLabel}
|
|
89
|
+
</KLabel.Text>
|
|
90
|
+
) : null}
|
|
69
91
|
</KContainer.View>
|
|
70
|
-
|
|
92
|
+
</>
|
|
71
93
|
);
|
|
72
94
|
}
|
|
73
95
|
);
|
|
@@ -93,20 +115,5 @@ const styles = StyleSheet.create({
|
|
|
93
115
|
paddingHorizontal: KSpacingValue['0.25rem'],
|
|
94
116
|
},
|
|
95
117
|
timeLabel: { gap: 4, marginTop: 2 },
|
|
96
|
-
|
|
97
|
-
borderRadius: KSpacingValue['0.75rem'],
|
|
98
|
-
borderWidth: 1,
|
|
99
|
-
borderColor: 'rgba(57,62,64,0.1)',
|
|
100
|
-
backgroundColor: KColors.white,
|
|
101
|
-
overflow: 'hidden',
|
|
102
|
-
},
|
|
103
|
-
cardThumb: {
|
|
104
|
-
width: '100%',
|
|
105
|
-
aspectRatio: 16 / 9,
|
|
106
|
-
},
|
|
107
|
-
cardBody: {
|
|
108
|
-
paddingHorizontal: KSpacingValue['0.75rem'],
|
|
109
|
-
paddingVertical: KSpacingValue['0.5rem'],
|
|
110
|
-
gap: 2,
|
|
111
|
-
},
|
|
118
|
+
timeLabelRight: { justifyContent: 'flex-end' },
|
|
112
119
|
});
|
|
@@ -2,7 +2,7 @@ import { memo, useCallback, useMemo } from 'react';
|
|
|
2
2
|
import { messageStyles } from '../styles';
|
|
3
3
|
import { getTailCornerStyle } from '../bubbleTail.utils';
|
|
4
4
|
import { BubbleTailRow } from '../BubbleTailRow';
|
|
5
|
-
import { type BaseLegendMessageProps } from '../types';
|
|
5
|
+
import { formatMessageTime, type BaseLegendMessageProps } from '../types';
|
|
6
6
|
import {
|
|
7
7
|
KColors,
|
|
8
8
|
KContainer,
|
|
@@ -27,7 +27,14 @@ import { ChatEvent } from '../../../types/events';
|
|
|
27
27
|
interface QuotedMessageProps extends BaseLegendMessageProps {}
|
|
28
28
|
|
|
29
29
|
const QuotedMessage = memo((props: QuotedMessageProps) => {
|
|
30
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
message,
|
|
32
|
+
isOutgoing,
|
|
33
|
+
createdAtTime,
|
|
34
|
+
isHighlighted,
|
|
35
|
+
isTimeRevealed,
|
|
36
|
+
groupPosition,
|
|
37
|
+
} = props || {};
|
|
31
38
|
|
|
32
39
|
const { conversation } = useChatDetailContext();
|
|
33
40
|
|
|
@@ -36,6 +43,7 @@ const QuotedMessage = memo((props: QuotedMessageProps) => {
|
|
|
36
43
|
const quotedMessage = message?.quoteElem?.quoteMessage;
|
|
37
44
|
|
|
38
45
|
const messagePreview = getMessagePreviewParts(quotedMessage);
|
|
46
|
+
const timeLabel = formatMessageTime(createdAtTime);
|
|
39
47
|
|
|
40
48
|
const imageUrl = useMemo(() => {
|
|
41
49
|
switch (quotedMessage?.contentType) {
|
|
@@ -64,72 +72,93 @@ const QuotedMessage = memo((props: QuotedMessageProps) => {
|
|
|
64
72
|
}, [quotedMessage]);
|
|
65
73
|
|
|
66
74
|
return (
|
|
67
|
-
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
onPress={handlePressQuotedMessage}
|
|
75
|
+
<>
|
|
76
|
+
<BubbleTailRow isOutgoing={isOutgoing} groupPosition={groupPosition}>
|
|
77
|
+
<KContainer.View
|
|
78
|
+
style={[
|
|
79
|
+
messageStyles.bubble,
|
|
80
|
+
styles.container,
|
|
81
|
+
isOutgoing
|
|
82
|
+
? messageStyles.bubbleSent
|
|
83
|
+
: messageStyles.bubbleReceived,
|
|
84
|
+
isHighlighted && messageStyles.bubbleHighlighted,
|
|
85
|
+
getTailCornerStyle(isOutgoing, groupPosition),
|
|
86
|
+
]}
|
|
80
87
|
>
|
|
81
|
-
<KContainer.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
<KContainer.Touchable
|
|
89
|
+
style={styles.quotedContainer}
|
|
90
|
+
onPress={handlePressQuotedMessage}
|
|
91
|
+
>
|
|
92
|
+
<KContainer.View style={styles.dot} />
|
|
93
|
+
<KContainer.VisibleView visible={!!imageUrl}>
|
|
94
|
+
<KContainer.View style={styles.imageContainer}>
|
|
95
|
+
<KImage.Base uri={imageUrl} style={styles.imageContainer} />
|
|
96
|
+
<KContainer.VisibleView
|
|
97
|
+
visible={
|
|
98
|
+
quotedMessage?.contentType === MessageType.VideoMessage
|
|
99
|
+
}
|
|
100
|
+
>
|
|
101
|
+
<KContainer.View style={styles.playIconContainer}>
|
|
102
|
+
<KContainer.View style={styles.playIcon}>
|
|
103
|
+
<KImage.VectorIcons
|
|
104
|
+
name="play-b"
|
|
105
|
+
size={12}
|
|
106
|
+
color={KColors.white}
|
|
107
|
+
/>
|
|
108
|
+
</KContainer.View>
|
|
97
109
|
</KContainer.View>
|
|
98
|
-
</KContainer.
|
|
99
|
-
</KContainer.
|
|
110
|
+
</KContainer.VisibleView>
|
|
111
|
+
</KContainer.View>
|
|
112
|
+
</KContainer.VisibleView>
|
|
113
|
+
<KContainer.VisibleView visible={!!iconSource}>
|
|
114
|
+
<KImage.Base
|
|
115
|
+
uri={iconSource}
|
|
116
|
+
style={styles.icon}
|
|
117
|
+
resizeMode="contain"
|
|
118
|
+
/>
|
|
119
|
+
</KContainer.VisibleView>
|
|
120
|
+
<KContainer.View flexS={1} style={styles.content}>
|
|
121
|
+
<KLabel.Text typo="TextNmMedium" numberOfLines={1}>
|
|
122
|
+
{getReplySenderName(
|
|
123
|
+
quotedMessage,
|
|
124
|
+
currentUserId,
|
|
125
|
+
conversation,
|
|
126
|
+
true
|
|
127
|
+
)}
|
|
128
|
+
</KLabel.Text>
|
|
129
|
+
<KLabel.Text
|
|
130
|
+
typo="TextSmNormal"
|
|
131
|
+
color={KColors.gray.light}
|
|
132
|
+
numberOfLines={1}
|
|
133
|
+
{...messagePreview.textProps}
|
|
134
|
+
>
|
|
135
|
+
{messagePreview.text}
|
|
136
|
+
</KLabel.Text>
|
|
100
137
|
</KContainer.View>
|
|
101
|
-
</KContainer.
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
>
|
|
124
|
-
{messagePreview.text}
|
|
125
|
-
</KLabel.Text>
|
|
126
|
-
</KContainer.View>
|
|
127
|
-
</KContainer.Touchable>
|
|
128
|
-
<KLabel.Text typo="TextMdNormal" color={CHAT_BUBBLE_COLORS.text}>
|
|
129
|
-
{message?.quoteElem?.text || ''}
|
|
130
|
-
</KLabel.Text>
|
|
138
|
+
</KContainer.Touchable>
|
|
139
|
+
<KLabel.Text typo="TextMdNormal" color={CHAT_BUBBLE_COLORS.text}>
|
|
140
|
+
{message?.quoteElem?.text || ''}
|
|
141
|
+
</KLabel.Text>
|
|
142
|
+
</KContainer.View>
|
|
143
|
+
</BubbleTailRow>
|
|
144
|
+
|
|
145
|
+
<KContainer.View
|
|
146
|
+
row
|
|
147
|
+
alignItems
|
|
148
|
+
style={[styles.timeLabel, isOutgoing && styles.timeLabelRight]}
|
|
149
|
+
>
|
|
150
|
+
{isTimeRevealed && timeLabel ? (
|
|
151
|
+
<KLabel.Text
|
|
152
|
+
typo="TextXsNormal"
|
|
153
|
+
color={CHAT_BUBBLE_COLORS.timestamp}
|
|
154
|
+
marginL={isOutgoing ? undefined : '0.25rem'}
|
|
155
|
+
marginR={isOutgoing ? '0.25rem' : undefined}
|
|
156
|
+
>
|
|
157
|
+
{timeLabel}
|
|
158
|
+
</KLabel.Text>
|
|
159
|
+
) : null}
|
|
131
160
|
</KContainer.View>
|
|
132
|
-
|
|
161
|
+
</>
|
|
133
162
|
);
|
|
134
163
|
});
|
|
135
164
|
|
|
@@ -187,4 +216,6 @@ const styles = StyleSheet.create({
|
|
|
187
216
|
justifyContent: 'center',
|
|
188
217
|
alignItems: 'center',
|
|
189
218
|
},
|
|
219
|
+
timeLabel: { gap: 4, marginTop: 2 },
|
|
220
|
+
timeLabelRight: { justifyContent: 'flex-end' },
|
|
190
221
|
});
|
|
@@ -1,22 +1,49 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
|
-
import type
|
|
3
|
-
import { KContainer, KImage } from '@droppii/libs';
|
|
2
|
+
import { formatMessageTime, type BaseLegendMessageProps } from '../types';
|
|
3
|
+
import { KContainer, KImage, KLabel } from '@droppii/libs';
|
|
4
4
|
import { StyleSheet } from 'react-native';
|
|
5
5
|
import { BubbleTailRow } from '../BubbleTailRow';
|
|
6
|
+
import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
|
|
6
7
|
|
|
7
8
|
export const STICKER_SIZE = 120;
|
|
8
9
|
export const StickerMessage = memo(
|
|
9
|
-
({
|
|
10
|
+
({
|
|
11
|
+
message,
|
|
12
|
+
isOutgoing,
|
|
13
|
+
createdAtTime,
|
|
14
|
+
isTimeRevealed,
|
|
15
|
+
}: BaseLegendMessageProps) => {
|
|
16
|
+
const timeLabel = formatMessageTime(createdAtTime);
|
|
17
|
+
|
|
10
18
|
return (
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
<>
|
|
20
|
+
<BubbleTailRow isOutgoing={isOutgoing} withTail={false}>
|
|
21
|
+
<KContainer.View style={styles.message}>
|
|
22
|
+
<KImage.Base
|
|
23
|
+
uri={message?.stickerElem?.content || ''}
|
|
24
|
+
width={STICKER_SIZE}
|
|
25
|
+
height={STICKER_SIZE}
|
|
26
|
+
/>
|
|
27
|
+
</KContainer.View>
|
|
28
|
+
</BubbleTailRow>
|
|
29
|
+
|
|
30
|
+
<KContainer.View
|
|
31
|
+
row
|
|
32
|
+
alignItems
|
|
33
|
+
style={[styles.timeLabel, isOutgoing && styles.timeLabelRight]}
|
|
34
|
+
>
|
|
35
|
+
{isTimeRevealed && timeLabel ? (
|
|
36
|
+
<KLabel.Text
|
|
37
|
+
typo="TextXsNormal"
|
|
38
|
+
color={CHAT_BUBBLE_COLORS.timestamp}
|
|
39
|
+
marginL={isOutgoing ? undefined : '0.25rem'}
|
|
40
|
+
marginR={isOutgoing ? '0.25rem' : undefined}
|
|
41
|
+
>
|
|
42
|
+
{timeLabel}
|
|
43
|
+
</KLabel.Text>
|
|
44
|
+
) : null}
|
|
18
45
|
</KContainer.View>
|
|
19
|
-
|
|
46
|
+
</>
|
|
20
47
|
);
|
|
21
48
|
}
|
|
22
49
|
);
|
|
@@ -28,4 +55,6 @@ const styles = StyleSheet.create({
|
|
|
28
55
|
justifyContent: 'center',
|
|
29
56
|
alignItems: 'center',
|
|
30
57
|
},
|
|
58
|
+
timeLabel: { gap: 4, marginTop: 2 },
|
|
59
|
+
timeLabelRight: { justifyContent: 'flex-end' },
|
|
31
60
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { memo, useMemo } from 'react';
|
|
2
|
-
import { Text } from 'react-native';
|
|
2
|
+
import { Text, StyleSheet } from 'react-native';
|
|
3
3
|
import { MessageType } from '@droppii/openim-rn-client-sdk';
|
|
4
4
|
import { KContainer, KLabel, KColors } from '@droppii/libs';
|
|
5
5
|
import { getMessageText } from '../../../utils/legendListMessage';
|
|
@@ -7,8 +7,7 @@ import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
|
|
|
7
7
|
import { messageStyles } from '../styles';
|
|
8
8
|
import { getTailCornerStyle } from '../bubbleTail.utils';
|
|
9
9
|
import { BubbleTailRow } from '../BubbleTailRow';
|
|
10
|
-
import type
|
|
11
|
-
import { StyleSheet } from 'react-native';
|
|
10
|
+
import { formatMessageTime, type BaseLegendMessageProps } from '../types';
|
|
12
11
|
import { tokenize } from '../../../utils/mentions';
|
|
13
12
|
import { deserializeMentionRanges } from '../../../utils/mentionSerializer';
|
|
14
13
|
import type { IMessageItemEx } from '../../../types/chat';
|
|
@@ -29,11 +28,14 @@ export const TextMessage = memo(
|
|
|
29
28
|
({
|
|
30
29
|
message,
|
|
31
30
|
isOutgoing,
|
|
31
|
+
createdAtTime,
|
|
32
32
|
isHighlighted,
|
|
33
|
+
isTimeRevealed,
|
|
33
34
|
onPressUrl,
|
|
34
35
|
groupPosition,
|
|
35
36
|
}: BaseLegendMessageProps & { onPressUrl?: (url: string) => void }) => {
|
|
36
37
|
const messageText = getMessageText(message);
|
|
38
|
+
const timeLabel = formatMessageTime(createdAtTime);
|
|
37
39
|
|
|
38
40
|
const isAtTextMessage = message.contentType === MessageType.AtTextMessage;
|
|
39
41
|
|
|
@@ -106,39 +108,58 @@ export const TextMessage = memo(
|
|
|
106
108
|
};
|
|
107
109
|
|
|
108
110
|
return (
|
|
109
|
-
|
|
111
|
+
<>
|
|
112
|
+
<BubbleTailRow isOutgoing={isOutgoing} groupPosition={groupPosition}>
|
|
113
|
+
<KContainer.View
|
|
114
|
+
style={[
|
|
115
|
+
messageStyles.bubble,
|
|
116
|
+
styles.bubbleContent,
|
|
117
|
+
isOutgoing
|
|
118
|
+
? messageStyles.bubbleSent
|
|
119
|
+
: messageStyles.bubbleReceived,
|
|
120
|
+
isHighlighted && messageStyles.bubbleHighlighted,
|
|
121
|
+
getTailCornerStyle(isOutgoing, groupPosition),
|
|
122
|
+
]}
|
|
123
|
+
>
|
|
124
|
+
{quotedMessage && (
|
|
125
|
+
<QuotedMessagePreviewStrip quotedMessage={quotedMessage} />
|
|
126
|
+
)}
|
|
127
|
+
<KLabel.Text typo="TextMdNormal" color={CHAT_BUBBLE_COLORS.text}>
|
|
128
|
+
{segments
|
|
129
|
+
? segments.map((segment, index) =>
|
|
130
|
+
segment.type === 'mention' ? (
|
|
131
|
+
<Text key={segment.mention.id} style={mentionTextStyle}>
|
|
132
|
+
{`@${segment.mention.displayName}`}
|
|
133
|
+
</Text>
|
|
134
|
+
) : (
|
|
135
|
+
renderTextRun(segment.text, `seg-${index}`)
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
: messageText}
|
|
139
|
+
</KLabel.Text>
|
|
140
|
+
{!!urlMetadata && (
|
|
141
|
+
<LinkPreviewCard metadata={urlMetadata} onPressUrl={onPressUrl} />
|
|
142
|
+
)}
|
|
143
|
+
</KContainer.View>
|
|
144
|
+
</BubbleTailRow>
|
|
145
|
+
|
|
110
146
|
<KContainer.View
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
isOutgoing
|
|
115
|
-
? messageStyles.bubbleSent
|
|
116
|
-
: messageStyles.bubbleReceived,
|
|
117
|
-
isHighlighted && messageStyles.bubbleHighlighted,
|
|
118
|
-
getTailCornerStyle(isOutgoing, groupPosition),
|
|
119
|
-
]}
|
|
147
|
+
row
|
|
148
|
+
alignItems
|
|
149
|
+
style={[styles.timeLabel, isOutgoing && styles.timeLabelRight]}
|
|
120
150
|
>
|
|
121
|
-
{
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
?
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
) : (
|
|
132
|
-
renderTextRun(segment.text, `seg-${index}`)
|
|
133
|
-
)
|
|
134
|
-
)
|
|
135
|
-
: messageText}
|
|
136
|
-
</KLabel.Text>
|
|
137
|
-
{!!urlMetadata && (
|
|
138
|
-
<LinkPreviewCard metadata={urlMetadata} onPressUrl={onPressUrl} />
|
|
139
|
-
)}
|
|
151
|
+
{isTimeRevealed && timeLabel ? (
|
|
152
|
+
<KLabel.Text
|
|
153
|
+
typo="TextXsNormal"
|
|
154
|
+
color={CHAT_BUBBLE_COLORS.timestamp}
|
|
155
|
+
marginL={isOutgoing ? undefined : '0.25rem'}
|
|
156
|
+
marginR={isOutgoing ? '0.25rem' : undefined}
|
|
157
|
+
>
|
|
158
|
+
{timeLabel}
|
|
159
|
+
</KLabel.Text>
|
|
160
|
+
) : null}
|
|
140
161
|
</KContainer.View>
|
|
141
|
-
|
|
162
|
+
</>
|
|
142
163
|
);
|
|
143
164
|
}
|
|
144
165
|
);
|
|
@@ -148,6 +169,7 @@ TextMessage.displayName = 'TextMessage';
|
|
|
148
169
|
const styles = StyleSheet.create({
|
|
149
170
|
bubbleContent: { gap: 4 },
|
|
150
171
|
timeLabel: { gap: 4, marginTop: 2 },
|
|
172
|
+
timeLabelRight: { justifyContent: 'flex-end' },
|
|
151
173
|
});
|
|
152
174
|
|
|
153
175
|
// Same highlight color as the composer (MentionHighlightAdapter) and the
|
|
@@ -7,6 +7,7 @@ export interface BaseLegendMessageProps {
|
|
|
7
7
|
isOutgoing: boolean;
|
|
8
8
|
createdAtTime: number;
|
|
9
9
|
isHighlighted?: boolean;
|
|
10
|
+
isTimeRevealed?: boolean;
|
|
10
11
|
/** Position within its same-sender cluster — drives the bubble tail. */
|
|
11
12
|
groupPosition?: GroupPosition;
|
|
12
13
|
onMediaPress?: (items: MediaItem[], index?: number) => void;
|
|
@@ -59,7 +59,6 @@ import type { MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
|
59
59
|
import { useUserStore } from '../../store';
|
|
60
60
|
import { ChatMessageAPI } from '../../services';
|
|
61
61
|
import { resolveImageDisplayDims } from '../../components/messages/imageMessage';
|
|
62
|
-
import { STICKER_SIZE } from '../../components/messages/stickerMessage';
|
|
63
62
|
import ChatImageDimens, {
|
|
64
63
|
CHAT_IMAGE_VIEW_SOURCE,
|
|
65
64
|
} from '../../utils/chatImageDimens';
|
|
@@ -151,6 +150,11 @@ export const ChatListLegend = memo(
|
|
|
151
150
|
const highlightTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
|
|
152
151
|
null
|
|
153
152
|
);
|
|
153
|
+
// Single revealed message at a time: tapping a message shows its own
|
|
154
|
+
// time below it, tapping it again (or a different message) hides/moves it.
|
|
155
|
+
const [revealedMessageId, setRevealedMessageId] = useState<string | null>(
|
|
156
|
+
null
|
|
157
|
+
);
|
|
154
158
|
|
|
155
159
|
const { applicationType } = useChatContext();
|
|
156
160
|
const searchMessages = useMessageStore((s) => s.searchMessages);
|
|
@@ -176,7 +180,10 @@ export const ChatListLegend = memo(
|
|
|
176
180
|
const onResetFlow = useCallback(() => {
|
|
177
181
|
resetFlow();
|
|
178
182
|
}, [resetFlow]);
|
|
179
|
-
|
|
183
|
+
|
|
184
|
+
const handleMessagePress = useCallback((messageId: string) => {
|
|
185
|
+
setRevealedMessageId((prev) => (prev === messageId ? null : messageId));
|
|
186
|
+
}, []);
|
|
180
187
|
|
|
181
188
|
const renderItem = useCallback(
|
|
182
189
|
({ item, index }: any) => {
|
|
@@ -241,6 +248,7 @@ export const ChatListLegend = memo(
|
|
|
241
248
|
onMediaPress={onMediaPress}
|
|
242
249
|
onPressUrl={onPressUrl}
|
|
243
250
|
onRetry={retryMessage}
|
|
251
|
+
onPress={() => handleMessagePress(messageId)}
|
|
244
252
|
onLongPress={
|
|
245
253
|
onLongPress
|
|
246
254
|
? () => handleMessageLongPress(messageId, message)
|
|
@@ -248,6 +256,7 @@ export const ChatListLegend = memo(
|
|
|
248
256
|
}
|
|
249
257
|
customMessageItemFactory={customMessageItemFactory}
|
|
250
258
|
isHighlighted={highlightedId === messageId}
|
|
259
|
+
isTimeRevealed={revealedMessageId === messageId}
|
|
251
260
|
showName={showName}
|
|
252
261
|
showAvatar={showAvatar}
|
|
253
262
|
showSenderGutter={showSenderGutter}
|
|
@@ -292,6 +301,8 @@ export const ChatListLegend = memo(
|
|
|
292
301
|
conversationFlow?.havingBotMessage,
|
|
293
302
|
retryMessage,
|
|
294
303
|
highlightedId,
|
|
304
|
+
revealedMessageId,
|
|
305
|
+
handleMessagePress,
|
|
295
306
|
]
|
|
296
307
|
);
|
|
297
308
|
|
|
@@ -375,9 +386,10 @@ export const ChatListLegend = memo(
|
|
|
375
386
|
CHAT_IMAGE_VIEW_SOURCE.MessageListThumbnail
|
|
376
387
|
).height + rowMargin;
|
|
377
388
|
break;
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
389
|
+
// StickerMessage is intentionally NOT fixed-size here: it now renders
|
|
390
|
+
// an optional revealed-time label below it (tap-to-reveal-time), so
|
|
391
|
+
// its true height can change after the first render — falls through
|
|
392
|
+
// to dynamic measurement like text/link/quote/file do.
|
|
381
393
|
default:
|
|
382
394
|
return undefined;
|
|
383
395
|
}
|
|
@@ -429,12 +441,14 @@ export const ChatListLegend = memo(
|
|
|
429
441
|
flowStepId: conversationFlow?.flowCurrentStep?.id ?? '',
|
|
430
442
|
lastMessageId,
|
|
431
443
|
highlightedId,
|
|
444
|
+
revealedMessageId,
|
|
432
445
|
}),
|
|
433
446
|
[
|
|
434
447
|
messageDataMap,
|
|
435
448
|
conversationFlow?.flowCurrentStep?.id,
|
|
436
449
|
lastMessageId,
|
|
437
450
|
highlightedId,
|
|
451
|
+
revealedMessageId,
|
|
438
452
|
]
|
|
439
453
|
);
|
|
440
454
|
|
|
@@ -125,12 +125,12 @@ export const ChatMessageInput = memo(
|
|
|
125
125
|
textAlignVertical={isMultilineInput ? 'top' : 'center'}
|
|
126
126
|
style={[styles.textInput]}
|
|
127
127
|
>
|
|
128
|
-
{hasMentions
|
|
128
|
+
{hasMentions ? (
|
|
129
129
|
<MentionHighlightAdapter
|
|
130
130
|
text={value ?? ''}
|
|
131
131
|
mentions={mentions!}
|
|
132
132
|
/>
|
|
133
|
-
)}
|
|
133
|
+
) : null}
|
|
134
134
|
</TextInput>
|
|
135
135
|
|
|
136
136
|
{renderEmojiNode()}
|
|
@@ -9,6 +9,8 @@ const formatTime = (date: Date) =>
|
|
|
9
9
|
hour12: false,
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
+
const pad2 = (value: number): string => String(value).padStart(2, '0');
|
|
13
|
+
|
|
12
14
|
const formatDayLabel = (createdAt: number): string | null => {
|
|
13
15
|
const date = new Date(createdAt);
|
|
14
16
|
if (Number.isNaN(date.getTime())) return null;
|
|
@@ -30,18 +32,13 @@ const formatDayLabel = (createdAt: number): string | null => {
|
|
|
30
32
|
return `${formatTime(date)} Hôm qua`;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
const dayMonth = `${pad2(date.getDate())}/${pad2(date.getMonth() + 1)}`;
|
|
36
|
+
|
|
33
37
|
if (messageDay.getFullYear() === today.getFullYear()) {
|
|
34
|
-
return `${formatTime(date)} ${
|
|
35
|
-
day: 'numeric',
|
|
36
|
-
month: 'long',
|
|
37
|
-
})}`;
|
|
38
|
+
return `${formatTime(date)} ${dayMonth}`;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
return `${formatTime(date)} ${date.
|
|
41
|
-
day: 'numeric',
|
|
42
|
-
month: 'long',
|
|
43
|
-
year: 'numeric',
|
|
44
|
-
})}`;
|
|
41
|
+
return `${formatTime(date)} ${dayMonth}/${date.getFullYear()}`;
|
|
45
42
|
};
|
|
46
43
|
|
|
47
44
|
interface LegendChatDayProps {
|