@droppii-org/chat-mobile 0.2.35 → 0.2.38

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 (160) hide show
  1. package/lib/module/components/messages/linkMessage/index.js +1 -10
  2. package/lib/module/components/messages/linkMessage/index.js.map +1 -1
  3. package/lib/module/components/messages/quotedMessage/index.js +173 -0
  4. package/lib/module/components/messages/quotedMessage/index.js.map +1 -0
  5. package/lib/module/context/ChatContext.js +26 -22
  6. package/lib/module/context/ChatContext.js.map +1 -1
  7. package/lib/module/context/ChatDetailContext.js +7 -4
  8. package/lib/module/context/ChatDetailContext.js.map +1 -1
  9. package/lib/module/core/useChatListener.js +0 -1
  10. package/lib/module/core/useChatListener.js.map +1 -1
  11. package/lib/module/core/useUserListener.js +12 -21
  12. package/lib/module/core/useUserListener.js.map +1 -1
  13. package/lib/module/hooks/message/useSendMessage.js +26 -4
  14. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  15. package/lib/module/hooks/useChatMessages.js.map +1 -1
  16. package/lib/module/hooks/useFileAttachment.js +3 -1
  17. package/lib/module/hooks/useFileAttachment.js.map +1 -1
  18. package/lib/module/index.js +0 -7
  19. package/lib/module/index.js.map +1 -1
  20. package/lib/module/screens/chat-detail/ChatComposer.js +2 -1
  21. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  22. package/lib/module/screens/chat-detail/ChatCurrentQuotedMessage.js +142 -0
  23. package/lib/module/screens/chat-detail/ChatCurrentQuotedMessage.js.map +1 -0
  24. package/lib/module/screens/chat-detail/ChatDetail.js +10 -1
  25. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  26. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  27. package/lib/module/screens/chat-detail/PinnedMessageBar.js +8 -3
  28. package/lib/module/screens/chat-detail/PinnedMessageBar.js.map +1 -1
  29. package/lib/module/screens/chat-detail/PinnedMessagesSheet.js +173 -0
  30. package/lib/module/screens/chat-detail/PinnedMessagesSheet.js.map +1 -0
  31. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +2 -1
  32. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  33. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js +17 -1
  34. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxMenu.js.map +1 -1
  35. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +11 -1
  36. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
  37. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js +1 -0
  38. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js.map +1 -1
  39. package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js +5 -2
  40. package/lib/module/screens/chat-detail/messageToolbox/messageToolbox.utils.js.map +1 -1
  41. package/lib/module/screens/inbox/Inbox.js +1 -5
  42. package/lib/module/screens/inbox/Inbox.js.map +1 -1
  43. package/lib/module/services/apis.js +0 -3
  44. package/lib/module/services/apis.js.map +1 -1
  45. package/lib/module/services/message.js.map +1 -1
  46. package/lib/module/store/message.js.map +1 -1
  47. package/lib/module/store/pinnedMessage.js.map +1 -1
  48. package/lib/module/store/user.js +2 -3
  49. package/lib/module/store/user.js.map +1 -1
  50. package/lib/module/translation/resources/i18n.js +9 -2
  51. package/lib/module/translation/resources/i18n.js.map +1 -1
  52. package/lib/module/types/auth.js +0 -7
  53. package/lib/module/types/auth.js.map +1 -1
  54. package/lib/module/types/chat.js +12 -0
  55. package/lib/module/types/chat.js.map +1 -1
  56. package/lib/module/utils/UIUtils.js +6 -0
  57. package/lib/module/utils/UIUtils.js.map +1 -1
  58. package/lib/module/utils/legendListMessage.js +1 -0
  59. package/lib/module/utils/legendListMessage.js.map +1 -1
  60. package/lib/module/utils/message.js.map +1 -1
  61. package/lib/module/utils/messageUtils.js +18 -1
  62. package/lib/module/utils/messageUtils.js.map +1 -1
  63. package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
  64. package/lib/typescript/src/components/messages/quotedMessage/index.d.ts +7 -0
  65. package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -0
  66. package/lib/typescript/src/components/messages/types.d.ts +2 -2
  67. package/lib/typescript/src/components/messages/types.d.ts.map +1 -1
  68. package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
  69. package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -1
  70. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  71. package/lib/typescript/src/core/useUserListener.d.ts.map +1 -1
  72. package/lib/typescript/src/hooks/message/usePinMessage.d.ts +1 -1
  73. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +2 -1
  74. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  75. package/lib/typescript/src/hooks/useChatMessages.d.ts +2 -2
  76. package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
  77. package/lib/typescript/src/hooks/useFileAttachment.d.ts.map +1 -1
  78. package/lib/typescript/src/index.d.ts +1 -1
  79. package/lib/typescript/src/index.d.ts.map +1 -1
  80. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  81. package/lib/typescript/src/screens/chat-detail/ChatCurrentQuotedMessage.d.ts +2 -0
  82. package/lib/typescript/src/screens/chat-detail/ChatCurrentQuotedMessage.d.ts.map +1 -0
  83. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  84. package/lib/typescript/src/screens/chat-detail/PinnedMessageBar.d.ts +4 -3
  85. package/lib/typescript/src/screens/chat-detail/PinnedMessageBar.d.ts.map +1 -1
  86. package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheet.d.ts +9 -0
  87. package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheet.d.ts.map +1 -0
  88. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +2 -3
  89. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  90. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts +11 -7
  91. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.d.ts.map +1 -1
  92. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts +4 -4
  93. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
  94. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts.map +1 -1
  95. package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts +5 -3
  96. package/lib/typescript/src/screens/chat-detail/messageToolbox/messageToolbox.utils.d.ts.map +1 -1
  97. package/lib/typescript/src/screens/chat-detail/messageToolbox/types.d.ts +2 -2
  98. package/lib/typescript/src/screens/chat-detail/messageToolbox/types.d.ts.map +1 -1
  99. package/lib/typescript/src/screens/chat-detail/types.d.ts +5 -5
  100. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  101. package/lib/typescript/src/screens/inbox/Inbox.d.ts.map +1 -1
  102. package/lib/typescript/src/services/apis.d.ts +0 -1
  103. package/lib/typescript/src/services/apis.d.ts.map +1 -1
  104. package/lib/typescript/src/services/message.d.ts +1 -2
  105. package/lib/typescript/src/services/message.d.ts.map +1 -1
  106. package/lib/typescript/src/store/message.d.ts.map +1 -1
  107. package/lib/typescript/src/store/pinnedMessage.d.ts +4 -4
  108. package/lib/typescript/src/store/pinnedMessage.d.ts.map +1 -1
  109. package/lib/typescript/src/store/user.d.ts +4 -4
  110. package/lib/typescript/src/store/user.d.ts.map +1 -1
  111. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  112. package/lib/typescript/src/types/auth.d.ts +0 -5
  113. package/lib/typescript/src/types/auth.d.ts.map +1 -1
  114. package/lib/typescript/src/types/chat.d.ts +19 -36
  115. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  116. package/lib/typescript/src/utils/UIUtils.d.ts.map +1 -1
  117. package/lib/typescript/src/utils/legendListMessage.d.ts +4 -4
  118. package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -1
  119. package/lib/typescript/src/utils/message.d.ts +4 -5
  120. package/lib/typescript/src/utils/message.d.ts.map +1 -1
  121. package/lib/typescript/src/utils/messageUtils.d.ts +9 -7
  122. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  123. package/package.json +2 -2
  124. package/src/components/messages/linkMessage/index.tsx +1 -11
  125. package/src/components/messages/quotedMessage/index.tsx +197 -0
  126. package/src/components/messages/types.ts +2 -2
  127. package/src/context/ChatContext.tsx +30 -25
  128. package/src/context/ChatDetailContext.tsx +9 -3
  129. package/src/core/useChatListener.ts +0 -1
  130. package/src/core/useUserListener.ts +9 -25
  131. package/src/hooks/message/useSendMessage.ts +25 -4
  132. package/src/hooks/useChatMessages.ts +4 -6
  133. package/src/hooks/useFileAttachment.ts +3 -1
  134. package/src/index.tsx +5 -6
  135. package/src/screens/chat-detail/ChatComposer.tsx +2 -0
  136. package/src/screens/chat-detail/ChatCurrentQuotedMessage.tsx +150 -0
  137. package/src/screens/chat-detail/ChatDetail.tsx +13 -2
  138. package/src/screens/chat-detail/ChatListLegend.tsx +3 -3
  139. package/src/screens/chat-detail/PinnedMessageBar.tsx +53 -49
  140. package/src/screens/chat-detail/PinnedMessagesSheet.tsx +243 -0
  141. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +4 -4
  142. package/src/screens/chat-detail/messageToolbox/MessageToolboxMenu.tsx +30 -6
  143. package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +15 -6
  144. package/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx +1 -0
  145. package/src/screens/chat-detail/messageToolbox/messageToolbox.utils.ts +21 -12
  146. package/src/screens/chat-detail/messageToolbox/types.ts +2 -2
  147. package/src/screens/chat-detail/types.ts +5 -5
  148. package/src/screens/inbox/Inbox.tsx +1 -5
  149. package/src/services/apis.ts +0 -4
  150. package/src/services/message.ts +1 -2
  151. package/src/store/message.ts +3 -3
  152. package/src/store/pinnedMessage.ts +4 -4
  153. package/src/store/user.ts +5 -5
  154. package/src/translation/resources/i18n.ts +10 -1
  155. package/src/types/auth.ts +0 -6
  156. package/src/types/chat.ts +19 -40
  157. package/src/utils/UIUtils.ts +5 -0
  158. package/src/utils/legendListMessage.ts +4 -5
  159. package/src/utils/message.ts +10 -11
  160. package/src/utils/messageUtils.ts +37 -9
@@ -0,0 +1,150 @@
1
+ import {
2
+ KColors,
3
+ KContainer,
4
+ KImage,
5
+ KLabel,
6
+ KRadiusValue,
7
+ KSpacingValue,
8
+ } from '@droppii/libs';
9
+ import { memo, useMemo } from 'react';
10
+ import { StyleSheet } from 'react-native';
11
+ import { useChatDetailContext } from '../../context';
12
+ import {
13
+ getMessagePreview,
14
+ getReplySenderName,
15
+ parseUrlMetadata,
16
+ } from '../../utils/messageUtils';
17
+ import { trans } from '../../translation';
18
+ import { useUserStore } from '../../store';
19
+ import { MessageType } from '@droppii/openim-rn-client-sdk';
20
+ import { getFileIcon, getMimeType } from '../../utils/imageUtils';
21
+
22
+ export const ChatCurrentQuotedMessage = memo(() => {
23
+ const { quotedMessage, conversation, setQuotedMessage } =
24
+ useChatDetailContext();
25
+
26
+ const currentUserId = useUserStore((s) => s.user?.userID);
27
+
28
+ const imageUrl = useMemo(() => {
29
+ switch (quotedMessage?.contentType) {
30
+ case MessageType.PictureMessage:
31
+ return quotedMessage?.pictureElem?.snapshotPicture?.url || '';
32
+ case MessageType.VideoMessage:
33
+ return quotedMessage?.videoElem?.snapshotUrl || '';
34
+ case MessageType.UrlTextMessage:
35
+ const metadata = parseUrlMetadata(quotedMessage?.ex);
36
+ return metadata?.image || '';
37
+ default:
38
+ return '';
39
+ }
40
+ }, [quotedMessage]);
41
+
42
+ const iconSource = useMemo(() => {
43
+ if (quotedMessage?.contentType !== MessageType.FileMessage) return '';
44
+ const mimeType = getMimeType(quotedMessage?.fileElem?.fileName || '');
45
+ return getFileIcon(mimeType);
46
+ }, [quotedMessage]);
47
+
48
+ if (!quotedMessage) return null;
49
+
50
+ return (
51
+ <KContainer.View style={styles.container}>
52
+ <KContainer.View style={styles.dot} />
53
+ <KContainer.VisibleView visible={!!imageUrl}>
54
+ <KContainer.View style={styles.imageContainer}>
55
+ <KImage.Base uri={imageUrl} style={styles.imageContainer} />
56
+ <KContainer.VisibleView
57
+ visible={quotedMessage?.contentType === MessageType.VideoMessage}
58
+ >
59
+ <KContainer.View style={styles.playIconContainer}>
60
+ <KContainer.View style={styles.playIcon}>
61
+ <KImage.VectorIcons
62
+ name="play-b"
63
+ size={12}
64
+ color={KColors.white}
65
+ />
66
+ </KContainer.View>
67
+ </KContainer.View>
68
+ </KContainer.VisibleView>
69
+ </KContainer.View>
70
+ </KContainer.VisibleView>
71
+ <KContainer.VisibleView visible={!!iconSource}>
72
+ <KImage.Base
73
+ uri={iconSource}
74
+ style={styles.icon}
75
+ resizeMode="contain"
76
+ />
77
+ </KContainer.VisibleView>
78
+ <KContainer.View style={styles.content}>
79
+ <KLabel.Text typo="TextNmMedium" numberOfLines={1}>
80
+ {trans('chat:reply_to', {
81
+ name: getReplySenderName(
82
+ quotedMessage,
83
+ currentUserId,
84
+ conversation
85
+ ),
86
+ })}
87
+ </KLabel.Text>
88
+ <KLabel.Text
89
+ typo="TextSmNormal"
90
+ color={KColors.gray.light}
91
+ numberOfLines={1}
92
+ >
93
+ {getMessagePreview(quotedMessage)}
94
+ </KLabel.Text>
95
+ </KContainer.View>
96
+ <KContainer.Touchable onPress={() => setQuotedMessage(undefined)}>
97
+ <KImage.VectorIcons name="close-o" size={16} />
98
+ </KContainer.Touchable>
99
+ </KContainer.View>
100
+ );
101
+ });
102
+
103
+ ChatCurrentQuotedMessage.displayName = 'ChatCurrentQuotedMessage';
104
+
105
+ const styles = StyleSheet.create({
106
+ container: {
107
+ paddingVertical: KSpacingValue['0.25rem'],
108
+ paddingHorizontal: KSpacingValue['0.5rem'],
109
+ backgroundColor: KColors.opacity.grayLight[10],
110
+ flexDirection: 'row',
111
+ alignItems: 'center',
112
+ gap: KSpacingValue['0.5rem'],
113
+ justifyContent: 'space-between',
114
+ },
115
+ dot: {
116
+ width: 3,
117
+ height: '100%',
118
+ backgroundColor: KColors.palette.grayCool.w200,
119
+ borderRadius: KRadiusValue.x,
120
+ },
121
+ content: {
122
+ flex: 1,
123
+ paddingHorizontal: KSpacingValue['0.25rem'],
124
+ },
125
+ icon: {
126
+ width: 24,
127
+ height: 24,
128
+ },
129
+ imageContainer: {
130
+ width: 42,
131
+ height: 42,
132
+ borderRadius: KRadiusValue['2x'],
133
+ },
134
+ playIconContainer: {
135
+ position: 'absolute',
136
+ top: 0,
137
+ left: 0,
138
+ right: 0,
139
+ bottom: 0,
140
+ justifyContent: 'center',
141
+ alignItems: 'center',
142
+ },
143
+ playIcon: {
144
+ borderRadius: 8,
145
+ padding: KSpacingValue['0.25rem'],
146
+ backgroundColor: KColors.hexToRgba(KColors.black, 0.6),
147
+ justifyContent: 'center',
148
+ alignItems: 'center',
149
+ },
150
+ });
@@ -8,6 +8,7 @@ import { useNavigation } from '@react-navigation/native';
8
8
  import { useChatMessages } from '../../hooks/useChatMessages';
9
9
  import { usePinMessage } from '../../hooks/message/usePinMessage';
10
10
  import { PinnedMessageBar } from './PinnedMessageBar';
11
+ import { openPinnedMessagesSheet } from './PinnedMessagesSheet';
11
12
  import {
12
13
  MediaViewerModal,
13
14
  type MediaViewerModalHandle,
@@ -21,12 +22,13 @@ import type { ChatDetailProps } from './types';
21
22
  import { normalizeUrl } from '../../utils/url';
22
23
  import { MessageToolboxOverlay } from './messageToolbox/MessageToolboxOverlay';
23
24
  import type { MeasuredRect, MessageToolboxRef } from './messageToolbox/types';
24
- import { DBotStatusValue, type DMessageItem } from '../../types/chat';
25
+ import { DBotStatusValue } from '../../types/chat';
25
26
  import {
26
27
  useGetConversationDetail,
27
28
  useGetStatusConversationBot,
28
29
  } from '../../hooks/conversation/useGetConversationDetail';
29
30
  import { PeerType } from '@droppii/openim-rn-client-sdk';
31
+ import type { MessageItem } from '@droppii/openim-rn-client-sdk';
30
32
 
31
33
  const ChatDetail = memo(
32
34
  ({
@@ -133,12 +135,20 @@ const ChatDetail = memo(
133
135
  );
134
136
 
135
137
  const handleMessageLongPress = useCallback(
136
- (message: DMessageItem, rect: MeasuredRect) => {
138
+ (message: MessageItem, rect: MeasuredRect) => {
137
139
  messageToolboxRef.current?.show(message, rect);
138
140
  },
139
141
  []
140
142
  );
141
143
 
144
+ const handlePressPinnedMessageBar = useCallback(() => {
145
+ openPinnedMessagesSheet({
146
+ currentUserId,
147
+ unpinMessage,
148
+ customMessageItemFactory,
149
+ });
150
+ }, [currentUserId, unpinMessage, customMessageItemFactory]);
151
+
142
152
  return (
143
153
  <KContainer.Page flex edges={['bottom']}>
144
154
  <ChatDetailHeader
@@ -154,6 +164,7 @@ const ChatDetail = memo(
154
164
  <PinnedMessageBar
155
165
  messages={pinnedMessages}
156
166
  currentUserId={currentUserId}
167
+ onPress={handlePressPinnedMessageBar}
157
168
  />
158
169
 
159
170
  <KeyboardAvoidingView
@@ -9,7 +9,6 @@ import { LegendList, type LegendListRef } from '@legendapp/list/react-native';
9
9
  import { KContainer, KColors, KSpacingValue, KLabel } from '@droppii/libs';
10
10
  import { precomputeLegendListData } from '../../utils/legendListMessage';
11
11
  import type { ChatListProps } from './types';
12
- import type { DMessageItem } from '../../types/chat';
13
12
  import { LegendChatMessage } from './legend/LegendChatMessage';
14
13
  import { LegendChatDay } from './legend/LegendChatDay';
15
14
  import { LegendChatLoadEarlier } from './legend/LegendChatLoadEarlier';
@@ -20,6 +19,7 @@ import { useResetFlow, useRunFlow } from '../../hooks/flows/useFlow';
20
19
  import type { IRunConversationFlowPayload } from '../../types/flows';
21
20
  import { useSendMessage } from '../../hooks/message/useSendMessage';
22
21
  import { messageStyles } from './legend/messageTypes';
22
+ import type { MessageItem } from '@droppii/openim-rn-client-sdk';
23
23
 
24
24
  export const ChatListLegend = memo(
25
25
  ({
@@ -90,7 +90,7 @@ export const ChatListLegend = memo(
90
90
  );
91
91
 
92
92
  const handleMessageLongPress = useCallback(
93
- (messageId: string, message: DMessageItem) => {
93
+ (messageId: string, message: MessageItem) => {
94
94
  const node = rowRefsMap.current.get(messageId);
95
95
  node?.measureInWindow((x, y, width, height) => {
96
96
  onLongPress?.(message, { x, y, width, height });
@@ -115,7 +115,7 @@ export const ChatListLegend = memo(
115
115
  try {
116
116
  if (!item) return null;
117
117
 
118
- const message = item as DMessageItem;
118
+ const message = item as MessageItem;
119
119
  const isOutgoing = message.sendID === currentUserId;
120
120
  const isLastIncoming =
121
121
  !isOutgoing &&
@@ -13,69 +13,68 @@ import {
13
13
  getMessagePreview,
14
14
  getPinnedMessagePreview,
15
15
  } from '../../utils/messageUtils';
16
- import type { DMessageItem } from '../../types/chat';
16
+ import type { MessageItem } from '@droppii/openim-rn-client-sdk';
17
17
 
18
18
  interface PinnedMessageBarProps {
19
- messages: DMessageItem[];
19
+ messages: MessageItem[];
20
20
  currentUserId?: string;
21
+ onPress?: () => void;
21
22
  }
22
23
 
23
- const PinnedMessageThumbnail = memo(
24
- ({ message }: { message: DMessageItem }) => {
25
- const preview = useMemo(() => getPinnedMessagePreview(message), [message]);
24
+ const PinnedMessageThumbnail = memo(({ message }: { message: MessageItem }) => {
25
+ const preview = useMemo(() => getPinnedMessagePreview(message), [message]);
26
26
 
27
- if (preview.mediaType === 'image' && preview.thumbnailUrl) {
28
- return (
29
- <KImage.Base
30
- uri={preview.thumbnailUrl}
31
- style={styles.thumbnail}
32
- resizeMode="cover"
33
- />
34
- );
35
- }
36
-
37
- if (preview.mediaType === 'video') {
38
- return (
39
- <KContainer.View style={styles.thumbnail}>
40
- {preview.thumbnailUrl && (
41
- <KImage.Base
42
- uri={preview.thumbnailUrl}
43
- style={styles.thumbnail}
44
- resizeMode="cover"
45
- />
46
- )}
47
- <KContainer.View style={styles.videoOverlay} center>
48
- <KImage.VectorIcons
49
- name="play-circle"
50
- provider="MaterialCommunityIcons"
51
- size={16}
52
- color={KColors.white}
53
- />
54
- </KContainer.View>
55
- </KContainer.View>
56
- );
57
- }
27
+ if (preview.mediaType === 'image' && preview.thumbnailUrl) {
28
+ return (
29
+ <KImage.Base
30
+ uri={preview.thumbnailUrl}
31
+ style={styles.thumbnail}
32
+ resizeMode="cover"
33
+ />
34
+ );
35
+ }
58
36
 
59
- if (preview.mediaType === 'file' && preview.fileIcon) {
60
- return (
61
- <KContainer.View marginL="0.75rem">
37
+ if (preview.mediaType === 'video') {
38
+ return (
39
+ <KContainer.View style={styles.thumbnail}>
40
+ {preview.thumbnailUrl && (
62
41
  <KImage.Base
63
- uri={preview.fileIcon}
64
- style={styles.fileIcon}
65
- resizeMode="contain"
42
+ uri={preview.thumbnailUrl}
43
+ style={styles.thumbnail}
44
+ resizeMode="cover"
45
+ />
46
+ )}
47
+ <KContainer.View style={styles.videoOverlay} center>
48
+ <KImage.VectorIcons
49
+ name="play-circle"
50
+ provider="MaterialCommunityIcons"
51
+ size={16}
52
+ color={KColors.white}
66
53
  />
67
54
  </KContainer.View>
68
- );
69
- }
55
+ </KContainer.View>
56
+ );
57
+ }
70
58
 
71
- return null;
59
+ if (preview.mediaType === 'file' && preview.fileIcon) {
60
+ return (
61
+ <KContainer.View marginL="0.75rem">
62
+ <KImage.Base
63
+ uri={preview.fileIcon}
64
+ style={styles.fileIcon}
65
+ resizeMode="contain"
66
+ />
67
+ </KContainer.View>
68
+ );
72
69
  }
73
- );
70
+
71
+ return null;
72
+ });
74
73
 
75
74
  PinnedMessageThumbnail.displayName = 'PinnedMessageThumbnail';
76
75
 
77
76
  export const PinnedMessageBar = memo(
78
- ({ messages, currentUserId }: PinnedMessageBarProps) => {
77
+ ({ messages, currentUserId, onPress }: PinnedMessageBarProps) => {
79
78
  if (messages.length === 0) {
80
79
  return null;
81
80
  }
@@ -87,7 +86,11 @@ export const PinnedMessageBar = memo(
87
86
  const { subtitleKey } = getPinnedMessagePreview(latest);
88
87
 
89
88
  return (
90
- <KContainer.View style={styles.container}>
89
+ <KContainer.Touchable
90
+ activeOpacity={0.7}
91
+ onPress={onPress}
92
+ style={styles.container}
93
+ >
91
94
  <PinnedMessageThumbnail message={latest} />
92
95
 
93
96
  <KContainer.View row alignItems flex paddingR="0.75rem">
@@ -118,6 +121,7 @@ export const PinnedMessageBar = memo(
118
121
  badge={`+${remainingCount}`}
119
122
  skipConvertBadge
120
123
  badgeSize={{ width: 26, height: 20 }}
124
+ // eslint-disable-next-line react-native/no-inline-styles
121
125
  wrapperLabelTextStyle={{ paddingHorizontal: 3 }}
122
126
  />
123
127
  )}
@@ -127,7 +131,7 @@ export const PinnedMessageBar = memo(
127
131
  color={KColors.gray.normal}
128
132
  />
129
133
  </KContainer.View>
130
- </KContainer.View>
134
+ </KContainer.Touchable>
131
135
  );
132
136
  }
133
137
  );
@@ -0,0 +1,243 @@
1
+ import {
2
+ forwardRef,
3
+ memo,
4
+ useCallback,
5
+ useImperativeHandle,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+ import type { ReactNode } from 'react';
10
+ import { Modal, Pressable, StyleSheet, type View } from 'react-native';
11
+ import type { MessageItem } from '@droppii/openim-rn-client-sdk';
12
+ import { KContainer, KLabel, KColors } from '@droppii/libs';
13
+ import { UIUtils } from '../../utils/UIUtils';
14
+ import { trans } from '../../translation';
15
+ import { usePinnedMessageStore } from '../../store/pinnedMessage';
16
+ import { LegendChatMessage } from './legend/LegendChatMessage';
17
+ import { formatMessageTime, messageStyles } from './legend/messageTypes';
18
+ import { MessageToolboxWrapper } from './messageToolbox/MessageToolboxWrapper';
19
+ import { MessageToolboxMenu } from './messageToolbox/MessageToolboxMenu';
20
+ import type { MeasuredRect } from './messageToolbox/types';
21
+
22
+ interface PinnedMessageToolboxRef {
23
+ show: (message: MessageItem, rect: MeasuredRect) => void;
24
+ hide: () => void;
25
+ }
26
+
27
+ interface PinnedMessageToolboxOverlayProps {
28
+ currentUserId?: string;
29
+ unpinMessage: (message: MessageItem) => void | Promise<void>;
30
+ customMessageItemFactory?: (item: MessageItem) => ReactNode;
31
+ }
32
+
33
+ // Mirrors MessageToolboxOverlay (used for the main chat list's long-press
34
+ // menu) but restricted to the 2 actions relevant to an already-pinned
35
+ // message: unpin and jump to the original message.
36
+ const PinnedMessageToolboxOverlay = memo(
37
+ forwardRef<PinnedMessageToolboxRef, PinnedMessageToolboxOverlayProps>(
38
+ ({ currentUserId, unpinMessage, customMessageItemFactory }, ref) => {
39
+ const [visible, setVisible] = useState(false);
40
+ const [message, setMessage] = useState<MessageItem | undefined>(
41
+ undefined
42
+ );
43
+ const [rect, setRect] = useState<MeasuredRect | null>(null);
44
+
45
+ const hide = useCallback(() => {
46
+ setVisible(false);
47
+ setMessage(undefined);
48
+ setRect(null);
49
+ }, []);
50
+
51
+ const show = useCallback((msg: MessageItem, msgRect: MeasuredRect) => {
52
+ setMessage(msg);
53
+ setRect(msgRect);
54
+ setVisible(true);
55
+ }, []);
56
+
57
+ useImperativeHandle(ref, () => ({ show, hide }), [show, hide]);
58
+
59
+ const handleUnpinMessage = useCallback(() => {
60
+ if (!message) return;
61
+ hide();
62
+ unpinMessage(message);
63
+ }, [message, hide, unpinMessage]);
64
+
65
+ const handleViewOriginalMessage = useCallback(() => {
66
+ if (!message) return;
67
+ hide();
68
+ // TODO: implement navigating to / highlighting the original message
69
+ }, [message, hide]);
70
+
71
+ if (!visible || !message || !rect) {
72
+ return null;
73
+ }
74
+
75
+ const isOutgoing = message.sendID === currentUserId;
76
+
77
+ return (
78
+ <Modal
79
+ visible={visible}
80
+ transparent
81
+ statusBarTranslucent
82
+ animationType="fade"
83
+ onRequestClose={hide}
84
+ >
85
+ <Pressable style={styles.backdrop} onPress={hide} />
86
+
87
+ <MessageToolboxWrapper
88
+ rect={rect}
89
+ isOutgoing={isOutgoing}
90
+ message={
91
+ <LegendChatMessage
92
+ message={message}
93
+ isOutgoing={isOutgoing}
94
+ createdAtTime={message.sendTime || message.createTime}
95
+ customMessageItemFactory={customMessageItemFactory}
96
+ />
97
+ }
98
+ toolbox={
99
+ <MessageToolboxMenu
100
+ showUnpinMessage
101
+ showViewOriginalMessage
102
+ onUnpinMessage={handleUnpinMessage}
103
+ onViewOriginalMessage={handleViewOriginalMessage}
104
+ />
105
+ }
106
+ />
107
+ </Modal>
108
+ );
109
+ }
110
+ )
111
+ );
112
+
113
+ PinnedMessageToolboxOverlay.displayName = 'PinnedMessageToolboxOverlay';
114
+
115
+ const styles = StyleSheet.create({
116
+ backdrop: {
117
+ position: 'absolute',
118
+ top: 0,
119
+ left: 0,
120
+ right: 0,
121
+ bottom: 0,
122
+ backgroundColor: 'rgba(0,0,0,0.75)',
123
+ },
124
+ });
125
+
126
+ interface PinnedMessagesSheetContentProps {
127
+ currentUserId?: string;
128
+ unpinMessage: (message: MessageItem) => void | Promise<void>;
129
+ customMessageItemFactory?: (item: MessageItem) => ReactNode;
130
+ }
131
+
132
+ const PinnedMessagesSheetContent = memo(
133
+ ({
134
+ currentUserId,
135
+ unpinMessage,
136
+ customMessageItemFactory,
137
+ }: PinnedMessagesSheetContentProps) => {
138
+ const messages = usePinnedMessageStore((state) => state.messages);
139
+ const toolboxRef = useRef<PinnedMessageToolboxRef>(null);
140
+ const rowRefsMap = useRef<Map<string, View | null>>(new Map());
141
+
142
+ const handleLongPress = useCallback(
143
+ (messageId: string, message: MessageItem) => {
144
+ const node = rowRefsMap.current.get(messageId);
145
+ node?.measureInWindow((x, y, width, height) => {
146
+ toolboxRef.current?.show(message, { x, y, width, height });
147
+ });
148
+ },
149
+ []
150
+ );
151
+
152
+ return (
153
+ <KContainer.View paddingV="0.5rem">
154
+ <KLabel.Text
155
+ typo="TextNmNormal"
156
+ color={KColors.gray.normal}
157
+ paddingH="0.75rem"
158
+ marginB="0.75rem"
159
+ >
160
+ {trans('chat:pinned_messages_hint')}
161
+ </KLabel.Text>
162
+
163
+ {messages.map((message) => {
164
+ const isOutgoing = message.sendID === currentUserId;
165
+ const messageId = message.clientMsgID || message.serverMsgID;
166
+ const createdAtTime = message.sendTime || message.createTime;
167
+ const timeLabel = formatMessageTime(createdAtTime);
168
+
169
+ return (
170
+ <KContainer.View
171
+ key={messageId}
172
+ ref={(node: View | null) => {
173
+ if (node) rowRefsMap.current.set(messageId, node);
174
+ else rowRefsMap.current.delete(messageId);
175
+ }}
176
+ style={
177
+ isOutgoing
178
+ ? messageStyles.messageRowRight
179
+ : messageStyles.messageRowLeft
180
+ }
181
+ >
182
+ <LegendChatMessage
183
+ message={message}
184
+ isOutgoing={isOutgoing}
185
+ createdAtTime={createdAtTime}
186
+ onLongPress={() => handleLongPress(messageId, message)}
187
+ customMessageItemFactory={customMessageItemFactory}
188
+ />
189
+ {!!timeLabel && (
190
+ <KLabel.Text
191
+ typo="TextXsNormal"
192
+ color={KColors.gray.light}
193
+ marginT="0.25rem"
194
+ >
195
+ {timeLabel}
196
+ </KLabel.Text>
197
+ )}
198
+ </KContainer.View>
199
+ );
200
+ })}
201
+
202
+ <PinnedMessageToolboxOverlay
203
+ ref={toolboxRef}
204
+ currentUserId={currentUserId}
205
+ unpinMessage={unpinMessage}
206
+ customMessageItemFactory={customMessageItemFactory}
207
+ />
208
+ </KContainer.View>
209
+ );
210
+ }
211
+ );
212
+
213
+ PinnedMessagesSheetContent.displayName = 'PinnedMessagesSheetContent';
214
+
215
+ export interface OpenPinnedMessagesSheetParams {
216
+ currentUserId?: string;
217
+ unpinMessage: (message: MessageItem) => void | Promise<void>;
218
+ customMessageItemFactory?: (item: MessageItem) => ReactNode;
219
+ }
220
+
221
+ export const openPinnedMessagesSheet = ({
222
+ currentUserId,
223
+ unpinMessage,
224
+ customMessageItemFactory,
225
+ }: OpenPinnedMessagesSheetParams) => {
226
+ UIUtils.bottomSheet.open({
227
+ header: {
228
+ title: { text: trans('chat:pinned_messages_title') },
229
+ alignment: 'left',
230
+ showCloseButton: true,
231
+ },
232
+ touchOutsideToDismiss: true,
233
+ body: {
234
+ renderContent: () => (
235
+ <PinnedMessagesSheetContent
236
+ currentUserId={currentUserId}
237
+ unpinMessage={unpinMessage}
238
+ customMessageItemFactory={customMessageItemFactory}
239
+ />
240
+ ),
241
+ },
242
+ });
243
+ };
@@ -6,7 +6,6 @@ import { MessageType } from '@droppii/openim-rn-client-sdk';
6
6
  import type { MessageItem } from '@droppii/openim-rn-client-sdk';
7
7
  import { Gesture, GestureDetector } from 'react-native-gesture-handler';
8
8
  import { runOnJS } from 'react-native-reanimated';
9
- import type { DMessageItem } from '../../../types/chat';
10
9
  import type { MediaItem } from '../../../hooks/useMediaViewer';
11
10
  import { TextMessage } from '../../../components/messages/textMessage';
12
11
  import { ImageMessage } from '../../../components/messages/imageMessage';
@@ -18,6 +17,7 @@ import { RevokedMessage } from '../../../components/messages/revokedMessage';
18
17
  import { MessageStatusIndicator } from '../../../components/messages/MessageStatusIndicator';
19
18
  import type { BaseLegendMessageProps } from '../../../components/messages/types';
20
19
  import { messageStyles } from './messageTypes';
20
+ import QuotedMessage from '../../../components/messages/quotedMessage';
21
21
 
22
22
  type MessageProps = BaseLegendMessageProps & {
23
23
  onPressUrl?: (url: string) => void;
@@ -25,14 +25,14 @@ type MessageProps = BaseLegendMessageProps & {
25
25
  };
26
26
 
27
27
  interface LegendChatMessageProps {
28
- message: DMessageItem;
28
+ message: MessageItem;
29
29
  isOutgoing: boolean;
30
30
  createdAtTime: number;
31
31
  onMediaPress?: (items: MediaItem[], index?: number) => void;
32
32
  onPressUrl?: (url: string) => void;
33
33
  onRetry?: (message: MessageItem) => void;
34
34
  onLongPress?: () => void;
35
- customMessageItemFactory?: (m: DMessageItem) => ReactNode;
35
+ customMessageItemFactory?: (m: MessageItem) => ReactNode;
36
36
  }
37
37
 
38
38
  // OpenIM message types (not all exposed as MessageType enum)
@@ -43,7 +43,7 @@ const messageComponentMap: Partial<
43
43
  > = {
44
44
  [MessageType.TextMessage]: TextMessage,
45
45
  [MessageType.AtTextMessage]: TextMessage,
46
- [MessageType.QuoteMessage]: TextMessage,
46
+ [MessageType.QuoteMessage]: QuotedMessage,
47
47
  [MessageType.UrlTextMessage]: LinkMessage,
48
48
  [MessageType.PictureMessage]: ImageMessage,
49
49
  [MessageType.VideoMessage]: VideoMessage,