@droppii-org/chat-mobile 0.2.81 → 0.2.83

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 (175) hide show
  1. package/lib/module/components/ThreadCard/MentionBadge.js +24 -0
  2. package/lib/module/components/ThreadCard/MentionBadge.js.map +1 -0
  3. package/lib/module/components/ThreadCard/ThreadCard.js +5 -1
  4. package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
  5. package/lib/module/components/messages/linkMessage/LinkPreviewCard.js +67 -0
  6. package/lib/module/components/messages/linkMessage/LinkPreviewCard.js.map +1 -0
  7. package/lib/module/components/messages/linkMessage/index.js +49 -92
  8. package/lib/module/components/messages/linkMessage/index.js.map +1 -1
  9. package/lib/module/components/messages/quotedMessage/QuotedMessagePreviewStrip.js +152 -0
  10. package/lib/module/components/messages/quotedMessage/QuotedMessagePreviewStrip.js.map +1 -0
  11. package/lib/module/components/messages/quotedMessage/index.js +79 -55
  12. package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
  13. package/lib/module/components/messages/stickerMessage/index.js +38 -13
  14. package/lib/module/components/messages/stickerMessage/index.js.map +1 -1
  15. package/lib/module/components/messages/textMessage/index.js +126 -13
  16. package/lib/module/components/messages/textMessage/index.js.map +1 -1
  17. package/lib/module/components/messages/types.js.map +1 -1
  18. package/lib/module/core/useChatListener.js +1 -1
  19. package/lib/module/core/useChatListener.js.map +1 -1
  20. package/lib/module/hooks/message/useSendMessage.js +64 -3
  21. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  22. package/lib/module/hooks/query-keys.js +4 -0
  23. package/lib/module/hooks/query-keys.js.map +1 -1
  24. package/lib/module/hooks/useChatCompose.js +3 -2
  25. package/lib/module/hooks/useChatCompose.js.map +1 -1
  26. package/lib/module/screens/chat-detail/ChatComposer.js +170 -48
  27. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  28. package/lib/module/screens/chat-detail/ChatListLegend.js +16 -7
  29. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  30. package/lib/module/screens/chat-detail/components/ChatMessageInput.js +23 -2
  31. package/lib/module/screens/chat-detail/components/ChatMessageInput.js.map +1 -1
  32. package/lib/module/screens/chat-detail/components/MentionHighlightAdapter.js +51 -0
  33. package/lib/module/screens/chat-detail/components/MentionHighlightAdapter.js.map +1 -0
  34. package/lib/module/screens/chat-detail/components/MentionSuggestionItem.js +49 -0
  35. package/lib/module/screens/chat-detail/components/MentionSuggestionItem.js.map +1 -0
  36. package/lib/module/screens/chat-detail/components/MentionSuggestionList.js +114 -0
  37. package/lib/module/screens/chat-detail/components/MentionSuggestionList.js.map +1 -0
  38. package/lib/module/screens/chat-detail/hooks/useChatComposerState.js +8 -0
  39. package/lib/module/screens/chat-detail/hooks/useChatComposerState.js.map +1 -1
  40. package/lib/module/screens/chat-detail/hooks/useMentionComposer.js +251 -0
  41. package/lib/module/screens/chat-detail/hooks/useMentionComposer.js.map +1 -0
  42. package/lib/module/screens/chat-detail/legend/LegendChatDay.js +4 -9
  43. package/lib/module/screens/chat-detail/legend/LegendChatDay.js.map +1 -1
  44. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +14 -2
  45. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  46. package/lib/module/services/mentionSearch.js +49 -0
  47. package/lib/module/services/mentionSearch.js.map +1 -0
  48. package/lib/module/services/message.js +6 -1
  49. package/lib/module/services/message.js.map +1 -1
  50. package/lib/module/translation/resources/i18n.js +5 -0
  51. package/lib/module/translation/resources/i18n.js.map +1 -1
  52. package/lib/module/types/chat.js.map +1 -1
  53. package/lib/module/types/mention.js +2 -0
  54. package/lib/module/types/mention.js.map +1 -0
  55. package/lib/module/utils/legendListMessage.js +9 -1
  56. package/lib/module/utils/legendListMessage.js.map +1 -1
  57. package/lib/module/utils/mentionSerializer.js +113 -0
  58. package/lib/module/utils/mentionSerializer.js.map +1 -0
  59. package/lib/module/utils/mentions/index.js +7 -0
  60. package/lib/module/utils/mentions/index.js.map +1 -0
  61. package/lib/module/utils/mentions/mentionDetector.js +49 -0
  62. package/lib/module/utils/mentions/mentionDetector.js.map +1 -0
  63. package/lib/module/utils/mentions/mentionDiff.js +66 -0
  64. package/lib/module/utils/mentions/mentionDiff.js.map +1 -0
  65. package/lib/module/utils/mentions/mentionRange.js +188 -0
  66. package/lib/module/utils/mentions/mentionRange.js.map +1 -0
  67. package/lib/module/utils/mentions/mentionTokenize.js +50 -0
  68. package/lib/module/utils/mentions/mentionTokenize.js.map +1 -0
  69. package/lib/module/utils/messageUtils.js +6 -1
  70. package/lib/module/utils/messageUtils.js.map +1 -1
  71. package/lib/module/utils/url.js +33 -0
  72. package/lib/module/utils/url.js.map +1 -1
  73. package/lib/typescript/src/components/ThreadCard/MentionBadge.d.ts +7 -0
  74. package/lib/typescript/src/components/ThreadCard/MentionBadge.d.ts.map +1 -0
  75. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  76. package/lib/typescript/src/components/messages/linkMessage/LinkPreviewCard.d.ts +16 -0
  77. package/lib/typescript/src/components/messages/linkMessage/LinkPreviewCard.d.ts.map +1 -0
  78. package/lib/typescript/src/components/messages/linkMessage/index.d.ts +2 -2
  79. package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
  80. package/lib/typescript/src/components/messages/quotedMessage/QuotedMessagePreviewStrip.d.ts +13 -0
  81. package/lib/typescript/src/components/messages/quotedMessage/QuotedMessagePreviewStrip.d.ts.map +1 -0
  82. package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
  83. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts +2 -2
  84. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts.map +1 -1
  85. package/lib/typescript/src/components/messages/textMessage/index.d.ts +4 -2
  86. package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
  87. package/lib/typescript/src/components/messages/types.d.ts +1 -0
  88. package/lib/typescript/src/components/messages/types.d.ts.map +1 -1
  89. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +5 -2
  90. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  91. package/lib/typescript/src/hooks/query-keys.d.ts +4 -0
  92. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  93. package/lib/typescript/src/hooks/useChatCompose.d.ts +2 -1
  94. package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -1
  95. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  96. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  97. package/lib/typescript/src/screens/chat-detail/components/ChatMessageInput.d.ts +10 -2
  98. package/lib/typescript/src/screens/chat-detail/components/ChatMessageInput.d.ts.map +1 -1
  99. package/lib/typescript/src/screens/chat-detail/components/MentionHighlightAdapter.d.ts +29 -0
  100. package/lib/typescript/src/screens/chat-detail/components/MentionHighlightAdapter.d.ts.map +1 -0
  101. package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionItem.d.ts +8 -0
  102. package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionItem.d.ts.map +1 -0
  103. package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionList.d.ts +12 -0
  104. package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionList.d.ts.map +1 -0
  105. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts +1 -1
  106. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts.map +1 -1
  107. package/lib/typescript/src/screens/chat-detail/hooks/useMentionComposer.d.ts +48 -0
  108. package/lib/typescript/src/screens/chat-detail/hooks/useMentionComposer.d.ts.map +1 -0
  109. package/lib/typescript/src/screens/chat-detail/legend/LegendChatDay.d.ts.map +1 -1
  110. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +2 -1
  111. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  112. package/lib/typescript/src/services/mentionSearch.d.ts +15 -0
  113. package/lib/typescript/src/services/mentionSearch.d.ts.map +1 -0
  114. package/lib/typescript/src/services/message.d.ts +1 -1
  115. package/lib/typescript/src/services/message.d.ts.map +1 -1
  116. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  117. package/lib/typescript/src/types/chat.d.ts +23 -0
  118. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  119. package/lib/typescript/src/types/mention.d.ts +65 -0
  120. package/lib/typescript/src/types/mention.d.ts.map +1 -0
  121. package/lib/typescript/src/utils/legendListMessage.d.ts +4 -3
  122. package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -1
  123. package/lib/typescript/src/utils/mentionSerializer.d.ts +78 -0
  124. package/lib/typescript/src/utils/mentionSerializer.d.ts.map +1 -0
  125. package/lib/typescript/src/utils/mentions/index.d.ts +5 -0
  126. package/lib/typescript/src/utils/mentions/index.d.ts.map +1 -0
  127. package/lib/typescript/src/utils/mentions/mentionDetector.d.ts +18 -0
  128. package/lib/typescript/src/utils/mentions/mentionDetector.d.ts.map +1 -0
  129. package/lib/typescript/src/utils/mentions/mentionDiff.d.ts +20 -0
  130. package/lib/typescript/src/utils/mentions/mentionDiff.d.ts.map +1 -0
  131. package/lib/typescript/src/utils/mentions/mentionRange.d.ts +86 -0
  132. package/lib/typescript/src/utils/mentions/mentionRange.d.ts.map +1 -0
  133. package/lib/typescript/src/utils/mentions/mentionTokenize.d.ts +29 -0
  134. package/lib/typescript/src/utils/mentions/mentionTokenize.d.ts.map +1 -0
  135. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  136. package/lib/typescript/src/utils/url.d.ts +12 -0
  137. package/lib/typescript/src/utils/url.d.ts.map +1 -1
  138. package/package.json +1 -1
  139. package/src/components/ThreadCard/MentionBadge.tsx +28 -0
  140. package/src/components/ThreadCard/ThreadCard.tsx +3 -0
  141. package/src/components/messages/linkMessage/LinkPreviewCard.tsx +92 -0
  142. package/src/components/messages/linkMessage/index.tsx +64 -121
  143. package/src/components/messages/quotedMessage/QuotedMessagePreviewStrip.tsx +168 -0
  144. package/src/components/messages/quotedMessage/index.tsx +95 -64
  145. package/src/components/messages/stickerMessage/index.tsx +40 -11
  146. package/src/components/messages/textMessage/index.tsx +159 -17
  147. package/src/components/messages/types.ts +1 -0
  148. package/src/core/useChatListener.ts +1 -1
  149. package/src/hooks/message/useSendMessage.ts +60 -1
  150. package/src/hooks/query-keys.ts +6 -0
  151. package/src/hooks/useChatCompose.ts +115 -107
  152. package/src/screens/chat-detail/ChatComposer.tsx +201 -67
  153. package/src/screens/chat-detail/ChatListLegend.tsx +19 -4
  154. package/src/screens/chat-detail/components/ChatMessageInput.tsx +35 -2
  155. package/src/screens/chat-detail/components/MentionHighlightAdapter.tsx +57 -0
  156. package/src/screens/chat-detail/components/MentionSuggestionItem.tsx +53 -0
  157. package/src/screens/chat-detail/components/MentionSuggestionList.tsx +133 -0
  158. package/src/screens/chat-detail/hooks/useChatComposerState.ts +7 -1
  159. package/src/screens/chat-detail/hooks/useMentionComposer.ts +344 -0
  160. package/src/screens/chat-detail/legend/LegendChatDay.tsx +6 -9
  161. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +25 -2
  162. package/src/services/mentionSearch.ts +57 -0
  163. package/src/services/message.ts +9 -1
  164. package/src/translation/resources/i18n.ts +6 -0
  165. package/src/types/chat.ts +22 -0
  166. package/src/types/mention.ts +70 -0
  167. package/src/utils/legendListMessage.ts +13 -4
  168. package/src/utils/mentionSerializer.ts +155 -0
  169. package/src/utils/mentions/index.ts +16 -0
  170. package/src/utils/mentions/mentionDetector.ts +58 -0
  171. package/src/utils/mentions/mentionDiff.ts +95 -0
  172. package/src/utils/mentions/mentionRange.ts +207 -0
  173. package/src/utils/mentions/mentionTokenize.ts +53 -0
  174. package/src/utils/messageUtils.ts +6 -1
  175. package/src/utils/url.ts +30 -0
@@ -1,16 +1,12 @@
1
1
  import { memo } from 'react';
2
2
  import { StyleSheet } from 'react-native';
3
- import {
4
- KContainer,
5
- KImage,
6
- KLabel,
7
- KColors,
8
- KSpacingValue,
9
- } from '@droppii/libs';
10
- import type { BaseLegendMessageProps } from '../types';
3
+ import { KContainer, KLabel, KColors, KSpacingValue } from '@droppii/libs';
4
+ import { formatMessageTime, type BaseLegendMessageProps } from '../types';
11
5
  import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
12
6
  import { parseUrlMetadata } from '../../../utils/messageUtils';
7
+ import { splitTextByUrls } from '../../../utils/url';
13
8
  import { messageStyles } from '../styles';
9
+ import { LinkPreviewCard } from './LinkPreviewCard';
14
10
  import { getTailCornerStyle } from '../bubbleTail.utils';
15
11
  import { BubbleTailRow } from '../BubbleTailRow';
16
12
 
@@ -18,121 +14,82 @@ const renderTextWithLinks = (
18
14
  content: string,
19
15
  urls: string[],
20
16
  onPressUrl?: (url: string) => void
21
- ) => {
22
- if (!urls.length) {
23
- return <KLabel.Text typo="TextMdNormal">{content}</KLabel.Text>;
24
- }
25
-
26
- const parts: { text: string; isUrl: boolean }[] = [];
27
- let remaining = content;
28
-
29
- for (const url of urls) {
30
- const idx = remaining.indexOf(url);
31
- if (idx === -1) continue;
32
- if (idx > 0) parts.push({ text: remaining.slice(0, idx), isUrl: false });
33
- parts.push({ text: url, isUrl: true });
34
- remaining = remaining.slice(idx + url.length);
35
- }
36
-
37
- if (remaining) parts.push({ text: remaining, isUrl: false });
38
-
39
- return (
40
- <KLabel.Text typo="TextMdNormal">
41
- {parts.map((part, i) =>
42
- part.isUrl ? (
43
- <KLabel.Text
44
- key={i}
45
- onPress={() => onPressUrl?.(part.text)}
46
- color={KColors.primary.normal}
47
- typo="TextMdMedium"
48
- >
49
- {part.text}
50
- </KLabel.Text>
51
- ) : (
52
- <KLabel.Text typo="TextMdNormal" key={i}>
53
- {part.text}
54
- </KLabel.Text>
55
- )
56
- )}
57
- </KLabel.Text>
58
- );
59
- };
17
+ ) => (
18
+ <KLabel.Text typo="TextMdNormal">
19
+ {splitTextByUrls(content, urls).map((part, i) =>
20
+ part.isUrl ? (
21
+ <KLabel.Text
22
+ key={i}
23
+ onPress={() => onPressUrl?.(part.text)}
24
+ color={KColors.primary.normal}
25
+ typo="TextMdMedium"
26
+ >
27
+ {part.text}
28
+ </KLabel.Text>
29
+ ) : (
30
+ <KLabel.Text typo="TextMdNormal" key={i}>
31
+ {part.text}
32
+ </KLabel.Text>
33
+ )
34
+ )}
35
+ </KLabel.Text>
36
+ );
60
37
 
61
38
  export const LinkMessage = memo(
62
39
  ({
63
40
  message,
64
41
  isOutgoing,
42
+ createdAtTime,
65
43
  onPressUrl,
66
44
  isHighlighted,
45
+ isTimeRevealed,
67
46
  groupPosition,
68
47
  }: BaseLegendMessageProps & { onPressUrl?: (url: string) => void }) => {
69
48
  const content = message?.urlTextElem?.content;
70
49
  const urls = message?.urlTextElem?.urls ?? [];
71
50
  const metadata = parseUrlMetadata(message.ex);
51
+ const timeLabel = formatMessageTime(createdAtTime);
72
52
 
73
53
  return (
74
- <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
+
75
76
  <KContainer.View
76
- style={[
77
- styles.bubble,
78
- isOutgoing ? styles.bubbleSent : styles.bubbleReceived,
79
- isHighlighted && messageStyles.bubbleHighlighted,
80
- getTailCornerStyle(isOutgoing, groupPosition),
81
- ]}
77
+ row
78
+ alignItems
79
+ style={[styles.timeLabel, isOutgoing && styles.timeLabelRight]}
82
80
  >
83
- {!!content?.trim() && (
84
- <KContainer.View style={styles.textRow}>
85
- {renderTextWithLinks(content, urls, onPressUrl)}
86
- </KContainer.View>
87
- )}
88
-
89
- {!!metadata && (
90
- <KContainer.Touchable
91
- style={styles.card}
92
- onPress={() => metadata.url && onPressUrl?.(metadata.url)}
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}
93
87
  >
94
- {!!metadata.image && (
95
- <KContainer.View style={styles.cardThumb}>
96
- <KImage.Base
97
- uri={metadata.image}
98
- style={StyleSheet.absoluteFill}
99
- resizeMode="cover"
100
- />
101
- </KContainer.View>
102
- )}
103
- <KContainer.View style={styles.cardBody}>
104
- {!!metadata.title && (
105
- <KLabel.Text
106
- typo="TextNmMedium"
107
- color={KColors.palette.gray.w900}
108
- numberOfLines={1}
109
- >
110
- {metadata.title}
111
- </KLabel.Text>
112
- )}
113
- {!!metadata.url && (
114
- <KLabel.Text
115
- typo="TextXsMedium"
116
- color={KColors.palette.primary.w400}
117
- numberOfLines={1}
118
- >
119
- {metadata.url}
120
- </KLabel.Text>
121
- )}
122
- {!!metadata.description && (
123
- <KLabel.Text
124
- typo="TextXsNormal"
125
- color={KColors.gray.normal}
126
- numberOfLines={2}
127
- >
128
- {metadata.description}
129
- </KLabel.Text>
130
- )}
131
- </KContainer.View>
132
- </KContainer.Touchable>
133
- )}
88
+ {timeLabel}
89
+ </KLabel.Text>
90
+ ) : null}
134
91
  </KContainer.View>
135
- </BubbleTailRow>
92
+ </>
136
93
  );
137
94
  }
138
95
  );
@@ -157,20 +114,6 @@ const styles = StyleSheet.create({
157
114
  textRow: {
158
115
  paddingHorizontal: KSpacingValue['0.25rem'],
159
116
  },
160
- card: {
161
- borderRadius: KSpacingValue['0.75rem'],
162
- borderWidth: 1,
163
- borderColor: 'rgba(57,62,64,0.1)',
164
- backgroundColor: KColors.white,
165
- overflow: 'hidden',
166
- },
167
- cardThumb: {
168
- width: '100%',
169
- aspectRatio: 16 / 9,
170
- },
171
- cardBody: {
172
- paddingHorizontal: KSpacingValue['0.75rem'],
173
- paddingVertical: KSpacingValue['0.5rem'],
174
- gap: 2,
175
- },
117
+ timeLabel: { gap: 4, marginTop: 2 },
118
+ timeLabelRight: { justifyContent: 'flex-end' },
176
119
  });
@@ -0,0 +1,168 @@
1
+ import { memo, useCallback, useMemo } from 'react';
2
+ import { DeviceEventEmitter, StyleSheet } from 'react-native';
3
+ import {
4
+ KColors,
5
+ KContainer,
6
+ KImage,
7
+ KLabel,
8
+ KRadiusValue,
9
+ KSpacingValue,
10
+ } from '@droppii/libs';
11
+ import { MessageType, type MessageItem } from '@droppii/openim-rn-client-sdk';
12
+ import {
13
+ getMessagePreviewParts,
14
+ getReplySenderName,
15
+ parseUrlMetadata,
16
+ } from '../../../utils/messageUtils';
17
+ import { getFileIcon, getMimeType } from '../../../utils/imageUtils';
18
+ import { useUserStore } from '../../../store';
19
+ import { useChatDetailContext } from '../../../context';
20
+ import { ChatEvent } from '../../../types/events';
21
+
22
+ interface QuotedMessagePreviewStripProps {
23
+ quotedMessage?: MessageItem;
24
+ }
25
+
26
+ /**
27
+ * The "replying to ..." strip — shared by QuotedMessage (message.quoteElem)
28
+ * and TextMessage (message.atTextElem?.quoteMessage, for the mention+quote
29
+ * combo). Extracted so both content types render the exact same preview
30
+ * instead of two copies drifting apart.
31
+ */
32
+ export const QuotedMessagePreviewStrip = memo(
33
+ ({ quotedMessage }: QuotedMessagePreviewStripProps) => {
34
+ const { conversation } = useChatDetailContext();
35
+ const currentUserId = useUserStore((s) => s.user?.userID);
36
+ const messagePreview = getMessagePreviewParts(quotedMessage);
37
+
38
+ const imageUrl = useMemo(() => {
39
+ switch (quotedMessage?.contentType) {
40
+ case MessageType.PictureMessage:
41
+ return quotedMessage?.pictureElem?.snapshotPicture?.url || '';
42
+ case MessageType.VideoMessage:
43
+ return quotedMessage?.videoElem?.snapshotUrl || '';
44
+ case MessageType.UrlTextMessage: {
45
+ const metadata = parseUrlMetadata(quotedMessage?.ex);
46
+ return metadata?.image || '';
47
+ }
48
+ default:
49
+ return '';
50
+ }
51
+ }, [quotedMessage]);
52
+
53
+ const iconSource = useMemo(() => {
54
+ if (quotedMessage?.contentType !== MessageType.FileMessage) return '';
55
+ const mimeType = getMimeType(quotedMessage?.fileElem?.fileName || '');
56
+ return getFileIcon(mimeType);
57
+ }, [quotedMessage]);
58
+
59
+ const handlePress = useCallback(() => {
60
+ DeviceEventEmitter.emit(ChatEvent.ScrollToMessage, {
61
+ clientMessageId: quotedMessage?.clientMsgID || '',
62
+ });
63
+ }, [quotedMessage]);
64
+
65
+ return (
66
+ <KContainer.Touchable
67
+ style={styles.quotedContainer}
68
+ onPress={handlePress}
69
+ >
70
+ <KContainer.View style={styles.dot} />
71
+ <KContainer.VisibleView visible={!!imageUrl}>
72
+ <KContainer.View style={styles.imageContainer}>
73
+ <KImage.Base uri={imageUrl} style={styles.imageContainer} />
74
+ <KContainer.VisibleView
75
+ visible={quotedMessage?.contentType === MessageType.VideoMessage}
76
+ >
77
+ <KContainer.View style={styles.playIconContainer}>
78
+ <KContainer.View style={styles.playIcon}>
79
+ <KImage.VectorIcons
80
+ name="play-b"
81
+ size={12}
82
+ color={KColors.white}
83
+ />
84
+ </KContainer.View>
85
+ </KContainer.View>
86
+ </KContainer.VisibleView>
87
+ </KContainer.View>
88
+ </KContainer.VisibleView>
89
+ <KContainer.VisibleView visible={!!iconSource}>
90
+ <KImage.Base
91
+ uri={iconSource}
92
+ style={styles.icon}
93
+ resizeMode="contain"
94
+ />
95
+ </KContainer.VisibleView>
96
+ <KContainer.View flexS={1} style={styles.content}>
97
+ <KLabel.Text typo="TextNmMedium" numberOfLines={1}>
98
+ {getReplySenderName(
99
+ quotedMessage,
100
+ currentUserId,
101
+ conversation,
102
+ true
103
+ )}
104
+ </KLabel.Text>
105
+ <KLabel.Text
106
+ typo="TextSmNormal"
107
+ color={KColors.gray.light}
108
+ numberOfLines={1}
109
+ {...messagePreview.textProps}
110
+ >
111
+ {messagePreview.text}
112
+ </KLabel.Text>
113
+ </KContainer.View>
114
+ </KContainer.Touchable>
115
+ );
116
+ }
117
+ );
118
+
119
+ QuotedMessagePreviewStrip.displayName = 'QuotedMessagePreviewStrip';
120
+
121
+ const styles = StyleSheet.create({
122
+ quotedContainer: {
123
+ backgroundColor: KColors.white,
124
+ flexDirection: 'row',
125
+ alignItems: 'center',
126
+ gap: KSpacingValue['0.5rem'],
127
+ borderTopRightRadius: KRadiusValue['3x'],
128
+ borderBottomRightRadius: KRadiusValue['3x'],
129
+ paddingRight: KSpacingValue['0.5rem'],
130
+ maxWidth: '100%',
131
+ },
132
+ dot: {
133
+ width: 3,
134
+ height: '100%',
135
+ backgroundColor: KColors.palette.grayCool.w200,
136
+ borderRadius: KRadiusValue.x,
137
+ marginRight: -2,
138
+ },
139
+ content: {
140
+ paddingHorizontal: KSpacingValue['0.25rem'],
141
+ paddingVertical: KSpacingValue['0.25rem'],
142
+ },
143
+ icon: {
144
+ width: 24,
145
+ height: 24,
146
+ },
147
+ imageContainer: {
148
+ width: 42,
149
+ height: 42,
150
+ borderRadius: KRadiusValue['2x'],
151
+ },
152
+ playIconContainer: {
153
+ position: 'absolute',
154
+ top: 0,
155
+ left: 0,
156
+ right: 0,
157
+ bottom: 0,
158
+ justifyContent: 'center',
159
+ alignItems: 'center',
160
+ },
161
+ playIcon: {
162
+ borderRadius: 8,
163
+ padding: KSpacingValue['0.25rem'],
164
+ backgroundColor: KColors.hexToRgba(KColors.black, 0.6),
165
+ justifyContent: 'center',
166
+ alignItems: 'center',
167
+ },
168
+ });
@@ -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
  });