@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.
Files changed (45) hide show
  1. package/lib/module/components/messages/linkMessage/index.js +33 -29
  2. package/lib/module/components/messages/linkMessage/index.js.map +1 -1
  3. package/lib/module/components/messages/quotedMessage/index.js +79 -55
  4. package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
  5. package/lib/module/components/messages/stickerMessage/index.js +38 -13
  6. package/lib/module/components/messages/stickerMessage/index.js.map +1 -1
  7. package/lib/module/components/messages/textMessage/index.js +41 -22
  8. package/lib/module/components/messages/textMessage/index.js.map +1 -1
  9. package/lib/module/components/messages/types.js.map +1 -1
  10. package/lib/module/screens/chat-detail/ChatListLegend.js +16 -8
  11. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  12. package/lib/module/screens/chat-detail/components/ChatMessageInput.js +2 -2
  13. package/lib/module/screens/chat-detail/components/ChatMessageInput.js.map +1 -1
  14. package/lib/module/screens/chat-detail/legend/LegendChatDay.js +4 -9
  15. package/lib/module/screens/chat-detail/legend/LegendChatDay.js.map +1 -1
  16. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +9 -1
  17. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  18. package/lib/module/utils/legendListMessage.js +9 -1
  19. package/lib/module/utils/legendListMessage.js.map +1 -1
  20. package/lib/typescript/src/components/messages/linkMessage/index.d.ts +2 -2
  21. package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
  22. package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
  23. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts +2 -2
  24. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts.map +1 -1
  25. package/lib/typescript/src/components/messages/textMessage/index.d.ts +2 -2
  26. package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
  27. package/lib/typescript/src/components/messages/types.d.ts +1 -0
  28. package/lib/typescript/src/components/messages/types.d.ts.map +1 -1
  29. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  30. package/lib/typescript/src/screens/chat-detail/legend/LegendChatDay.d.ts.map +1 -1
  31. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +2 -1
  32. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  33. package/lib/typescript/src/utils/legendListMessage.d.ts +4 -3
  34. package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -1
  35. package/package.json +1 -1
  36. package/src/components/messages/linkMessage/index.tsx +41 -34
  37. package/src/components/messages/quotedMessage/index.tsx +95 -64
  38. package/src/components/messages/stickerMessage/index.tsx +40 -11
  39. package/src/components/messages/textMessage/index.tsx +55 -33
  40. package/src/components/messages/types.ts +1 -0
  41. package/src/screens/chat-detail/ChatListLegend.tsx +19 -5
  42. package/src/screens/chat-detail/components/ChatMessageInput.tsx +2 -2
  43. package/src/screens/chat-detail/legend/LegendChatDay.tsx +6 -9
  44. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +18 -1
  45. 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 { BaseLegendMessageProps } from '../types';
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
- <BubbleTailRow isOutgoing={isOutgoing} groupPosition={groupPosition}>
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
- style={[
54
- styles.bubble,
55
- isOutgoing ? styles.bubbleSent : styles.bubbleReceived,
56
- isHighlighted && messageStyles.bubbleHighlighted,
57
- getTailCornerStyle(isOutgoing, groupPosition),
58
- ]}
77
+ row
78
+ alignItems
79
+ style={[styles.timeLabel, isOutgoing && styles.timeLabelRight]}
59
80
  >
60
- {!!content?.trim() && (
61
- <KContainer.View style={styles.textRow}>
62
- {renderTextWithLinks(content, urls, onPressUrl)}
63
- </KContainer.View>
64
- )}
65
-
66
- {!!metadata && (
67
- <LinkPreviewCard metadata={metadata} onPressUrl={onPressUrl} />
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
- </BubbleTailRow>
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
- card: {
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 { message, isOutgoing, isHighlighted, groupPosition } = props || {};
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
- <BubbleTailRow isOutgoing={isOutgoing} groupPosition={groupPosition}>
68
- <KContainer.View
69
- style={[
70
- messageStyles.bubble,
71
- styles.container,
72
- isOutgoing ? messageStyles.bubbleSent : messageStyles.bubbleReceived,
73
- isHighlighted && messageStyles.bubbleHighlighted,
74
- getTailCornerStyle(isOutgoing, groupPosition),
75
- ]}
76
- >
77
- <KContainer.Touchable
78
- style={styles.quotedContainer}
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.View style={styles.dot} />
82
- <KContainer.VisibleView visible={!!imageUrl}>
83
- <KContainer.View style={styles.imageContainer}>
84
- <KImage.Base uri={imageUrl} style={styles.imageContainer} />
85
- <KContainer.VisibleView
86
- visible={
87
- quotedMessage?.contentType === MessageType.VideoMessage
88
- }
89
- >
90
- <KContainer.View style={styles.playIconContainer}>
91
- <KContainer.View style={styles.playIcon}>
92
- <KImage.VectorIcons
93
- name="play-b"
94
- size={12}
95
- color={KColors.white}
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.View>
99
- </KContainer.VisibleView>
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.VisibleView>
102
- <KContainer.VisibleView visible={!!iconSource}>
103
- <KImage.Base
104
- uri={iconSource}
105
- style={styles.icon}
106
- resizeMode="contain"
107
- />
108
- </KContainer.VisibleView>
109
- <KContainer.View flexS={1} style={styles.content}>
110
- <KLabel.Text typo="TextNmMedium" numberOfLines={1}>
111
- {getReplySenderName(
112
- quotedMessage,
113
- currentUserId,
114
- conversation,
115
- true
116
- )}
117
- </KLabel.Text>
118
- <KLabel.Text
119
- typo="TextSmNormal"
120
- color={KColors.gray.light}
121
- numberOfLines={1}
122
- {...messagePreview.textProps}
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
- </BubbleTailRow>
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 { BaseLegendMessageProps } from '../types';
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
- ({ message, isOutgoing }: BaseLegendMessageProps) => {
10
+ ({
11
+ message,
12
+ isOutgoing,
13
+ createdAtTime,
14
+ isTimeRevealed,
15
+ }: BaseLegendMessageProps) => {
16
+ const timeLabel = formatMessageTime(createdAtTime);
17
+
10
18
  return (
11
- <BubbleTailRow isOutgoing={isOutgoing} withTail={false}>
12
- <KContainer.View style={styles.message}>
13
- <KImage.Base
14
- uri={message?.stickerElem?.content || ''}
15
- width={STICKER_SIZE}
16
- height={STICKER_SIZE}
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
- </BubbleTailRow>
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 { BaseLegendMessageProps } from '../types';
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
- <BubbleTailRow isOutgoing={isOutgoing} groupPosition={groupPosition}>
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
- style={[
112
- messageStyles.bubble,
113
- styles.bubbleContent,
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
- {quotedMessage && (
122
- <QuotedMessagePreviewStrip quotedMessage={quotedMessage} />
123
- )}
124
- <KLabel.Text typo="TextMdNormal" color={CHAT_BUBBLE_COLORS.text}>
125
- {segments
126
- ? segments.map((segment, index) =>
127
- segment.type === 'mention' ? (
128
- <Text key={segment.mention.id} style={mentionTextStyle}>
129
- {`@${segment.mention.displayName}`}
130
- </Text>
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
- </BubbleTailRow>
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
- console.info('dwqdqwdqw');
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
- case MessageType.StickerMessage:
379
- candidateSize = STICKER_SIZE + rowMargin;
380
- break;
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)} ${date.toLocaleDateString('vi-VN', {
35
- day: 'numeric',
36
- month: 'long',
37
- })}`;
38
+ return `${formatTime(date)} ${dayMonth}`;
38
39
  }
39
40
 
40
- return `${formatTime(date)} ${date.toLocaleDateString('vi-VN', {
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 {