@ethora/chat-component-rn 26.5.4 → 26.5.7

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 (238) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +25 -4
  3. package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js +1 -7
  4. package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js.map +1 -1
  5. package/lib/commonjs/components/MainComponents/ChatHeader.js +7 -1
  6. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  7. package/lib/commonjs/components/MainComponents/ChatRoom.js +8 -2
  8. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  9. package/lib/commonjs/components/MainComponents/ChatWrapper.js +2 -1
  10. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  11. package/lib/commonjs/components/MainComponents/MediaMessage.js +11 -5
  12. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  13. package/lib/commonjs/components/MainComponents/MessageList.js +10 -2
  14. package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
  15. package/lib/commonjs/components/MessageBubble/Message.js +60 -7
  16. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  17. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +1 -38
  18. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  19. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +20 -28
  20. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  21. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +35 -16
  22. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  23. package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js +11 -2
  24. package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
  25. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +1 -1
  26. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  27. package/lib/commonjs/components/RoomComponents/styled/LastMessageEmoji.js +9 -6
  28. package/lib/commonjs/components/RoomComponents/styled/LastMessageEmoji.js.map +1 -1
  29. package/lib/commonjs/components/UsersList/Checkbox.js +68 -0
  30. package/lib/commonjs/components/UsersList/Checkbox.js.map +1 -0
  31. package/lib/commonjs/components/UsersList/StyledComponents.js +1 -7
  32. package/lib/commonjs/components/UsersList/StyledComponents.js.map +1 -1
  33. package/lib/commonjs/components/UsersList/UsersList.js +3 -1
  34. package/lib/commonjs/components/UsersList/UsersList.js.map +1 -1
  35. package/lib/commonjs/components/styled/MediaFilePreview.js +96 -5
  36. package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
  37. package/lib/commonjs/components/styled/MessageImage.js +1 -2
  38. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  39. package/lib/commonjs/components/styled/SendInput.js +4 -2
  40. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  41. package/lib/commonjs/components/styled/VideoMessage.js +2 -10
  42. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  43. package/lib/commonjs/context/xmppProvider.js +71 -2
  44. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  45. package/lib/commonjs/helpers/mimeToExtension.js +131 -0
  46. package/lib/commonjs/helpers/mimeToExtension.js.map +1 -0
  47. package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js +73 -1
  48. package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js.map +1 -1
  49. package/lib/commonjs/hooks/useChatWrapperInit.js +22 -10
  50. package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
  51. package/lib/commonjs/hooks/useEventHandlers.js +9 -1
  52. package/lib/commonjs/hooks/useEventHandlers.js.map +1 -1
  53. package/lib/commonjs/hooks/useMessageHeapState.js +6 -2
  54. package/lib/commonjs/hooks/useMessageHeapState.js.map +1 -1
  55. package/lib/commonjs/hooks/useSendMessage.js +48 -4
  56. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  57. package/lib/commonjs/networking/api-requests/auth.api.js +7 -1
  58. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  59. package/lib/commonjs/networking/xmppClient.js +87 -1
  60. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  61. package/lib/commonjs/roomStore/roomHeapSlice.js +26 -8
  62. package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
  63. package/lib/module/components/ContextMenu/ContextMenuComponents.js +0 -6
  64. package/lib/module/components/ContextMenu/ContextMenuComponents.js.map +1 -1
  65. package/lib/module/components/MainComponents/ChatHeader.js +7 -1
  66. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  67. package/lib/module/components/MainComponents/ChatRoom.js +8 -2
  68. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  69. package/lib/module/components/MainComponents/ChatWrapper.js +2 -1
  70. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  71. package/lib/module/components/MainComponents/MediaMessage.js +11 -5
  72. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  73. package/lib/module/components/MainComponents/MessageList.js +11 -3
  74. package/lib/module/components/MainComponents/MessageList.js.map +1 -1
  75. package/lib/module/components/MessageBubble/Message.js +60 -7
  76. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  77. package/lib/module/components/MessageBubble/MessageInteractions.js +3 -40
  78. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  79. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +21 -29
  80. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  81. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +36 -17
  82. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  83. package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js +11 -2
  84. package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
  85. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +1 -1
  86. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  87. package/lib/module/components/RoomComponents/styled/LastMessageEmoji.js +9 -6
  88. package/lib/module/components/RoomComponents/styled/LastMessageEmoji.js.map +1 -1
  89. package/lib/module/components/UsersList/Checkbox.js +63 -0
  90. package/lib/module/components/UsersList/Checkbox.js.map +1 -0
  91. package/lib/module/components/UsersList/StyledComponents.js +0 -6
  92. package/lib/module/components/UsersList/StyledComponents.js.map +1 -1
  93. package/lib/module/components/UsersList/UsersList.js +2 -1
  94. package/lib/module/components/UsersList/UsersList.js.map +1 -1
  95. package/lib/module/components/styled/MediaFilePreview.js +97 -6
  96. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  97. package/lib/module/components/styled/MessageImage.js +1 -2
  98. package/lib/module/components/styled/MessageImage.js.map +1 -1
  99. package/lib/module/components/styled/SendInput.js +4 -2
  100. package/lib/module/components/styled/SendInput.js.map +1 -1
  101. package/lib/module/components/styled/VideoMessage.js +2 -10
  102. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  103. package/lib/module/context/xmppProvider.js +73 -3
  104. package/lib/module/context/xmppProvider.js.map +1 -1
  105. package/lib/module/helpers/mimeToExtension.js +124 -0
  106. package/lib/module/helpers/mimeToExtension.js.map +1 -0
  107. package/lib/module/helpers/resolveInitBeforeLoadUser.js +71 -0
  108. package/lib/module/helpers/resolveInitBeforeLoadUser.js.map +1 -1
  109. package/lib/module/hooks/useChatWrapperInit.js +22 -10
  110. package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
  111. package/lib/module/hooks/useEventHandlers.js +9 -1
  112. package/lib/module/hooks/useEventHandlers.js.map +1 -1
  113. package/lib/module/hooks/useMessageHeapState.js +6 -2
  114. package/lib/module/hooks/useMessageHeapState.js.map +1 -1
  115. package/lib/module/hooks/useSendMessage.js +49 -5
  116. package/lib/module/hooks/useSendMessage.js.map +1 -1
  117. package/lib/module/networking/api-requests/auth.api.js +7 -1
  118. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  119. package/lib/module/networking/xmppClient.js +87 -1
  120. package/lib/module/networking/xmppClient.js.map +1 -1
  121. package/lib/module/roomStore/roomHeapSlice.js +24 -7
  122. package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
  123. package/lib/typescript/components/ContextMenu/ContextMenuComponents.d.ts +0 -1
  124. package/lib/typescript/components/ContextMenu/ContextMenuComponents.d.ts.map +1 -1
  125. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  126. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  127. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  128. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  129. package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
  130. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  131. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  132. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  133. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  134. package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts +6 -0
  135. package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts.map +1 -1
  136. package/lib/typescript/components/RoomComponents/styled/LastMessageEmoji.d.ts +8 -0
  137. package/lib/typescript/components/RoomComponents/styled/LastMessageEmoji.d.ts.map +1 -1
  138. package/lib/typescript/components/UsersList/Checkbox.d.ts +16 -0
  139. package/lib/typescript/components/UsersList/Checkbox.d.ts.map +1 -0
  140. package/lib/typescript/components/UsersList/StyledComponents.d.ts +0 -1
  141. package/lib/typescript/components/UsersList/StyledComponents.d.ts.map +1 -1
  142. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  143. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  144. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  145. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  146. package/lib/typescript/helpers/mimeToExtension.d.ts +37 -0
  147. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -0
  148. package/lib/typescript/helpers/resolveInitBeforeLoadUser.d.ts +18 -0
  149. package/lib/typescript/helpers/resolveInitBeforeLoadUser.d.ts.map +1 -1
  150. package/lib/typescript/hooks/useChatWrapperInit.d.ts.map +1 -1
  151. package/lib/typescript/hooks/useEventHandlers.d.ts +6 -0
  152. package/lib/typescript/hooks/useEventHandlers.d.ts.map +1 -1
  153. package/lib/typescript/hooks/useMessageHeapState.d.ts +2 -0
  154. package/lib/typescript/hooks/useMessageHeapState.d.ts.map +1 -1
  155. package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
  156. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  157. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  158. package/lib/typescript/networking/xmppClient.d.ts +25 -1
  159. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  160. package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
  161. package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
  162. package/lib/typescript/types/types.d.ts +6 -0
  163. package/lib/typescript/types/types.d.ts.map +1 -1
  164. package/metro.js +17 -57
  165. package/package.json +1 -1
  166. package/src/components/ContextMenu/ContextMenuComponents.tsx +0 -7
  167. package/src/components/MainComponents/ChatHeader.tsx +5 -1
  168. package/src/components/MainComponents/ChatRoom.tsx +6 -1
  169. package/src/components/MainComponents/ChatWrapper.tsx +1 -0
  170. package/src/components/MainComponents/MediaMessage.tsx +15 -8
  171. package/src/components/MainComponents/MessageList.tsx +10 -3
  172. package/src/components/MessageBubble/Message.tsx +64 -10
  173. package/src/components/MessageBubble/MessageInteractions.tsx +2 -105
  174. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +20 -41
  175. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +30 -12
  176. package/src/components/Modals/ModalWrapper/ModalWrapper.tsx +22 -1
  177. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +1 -1
  178. package/src/components/RoomComponents/styled/LastMessageEmoji.tsx +9 -7
  179. package/src/components/UsersList/Checkbox.tsx +68 -0
  180. package/src/components/UsersList/StyledComponents.ts +0 -8
  181. package/src/components/UsersList/UsersList.tsx +1 -1
  182. package/src/components/styled/MediaFilePreview.tsx +91 -5
  183. package/src/components/styled/MessageImage.tsx +1 -2
  184. package/src/components/styled/SendInput.tsx +3 -0
  185. package/src/components/styled/VideoMessage.tsx +3 -10
  186. package/src/context/xmppProvider.tsx +90 -2
  187. package/src/helpers/mimeToExtension.ts +135 -0
  188. package/src/helpers/resolveInitBeforeLoadUser.ts +79 -0
  189. package/src/hooks/useChatWrapperInit.ts +51 -46
  190. package/src/hooks/useEventHandlers.tsx +24 -1
  191. package/src/hooks/useMessageHeapState.tsx +6 -2
  192. package/src/hooks/useSendMessage.tsx +75 -4
  193. package/src/networking/api-requests/auth.api.ts +5 -1
  194. package/src/networking/xmppClient.ts +95 -1
  195. package/src/roomStore/roomHeapSlice.ts +47 -4
  196. package/src/types/types.ts +10 -0
  197. package/lib/commonjs/components/InputComponents/AudioInput.js +0 -23
  198. package/lib/commonjs/components/InputComponents/AudioInput.js.map +0 -1
  199. package/lib/commonjs/components/InputComponents/AudioRecorder.js +0 -101
  200. package/lib/commonjs/components/InputComponents/AudioRecorder.js.map +0 -1
  201. package/lib/commonjs/components/InputComponents/MediaInput.js +0 -98
  202. package/lib/commonjs/components/InputComponents/MediaInput.js.map +0 -1
  203. package/lib/commonjs/components/InputComponents/RecordingIndicator.js +0 -70
  204. package/lib/commonjs/components/InputComponents/RecordingIndicator.js.map +0 -1
  205. package/lib/commonjs/components/Modals/ModalSelectMedia/ModalSelectMedia.js +0 -207
  206. package/lib/commonjs/components/Modals/ModalSelectMedia/ModalSelectMedia.js.map +0 -1
  207. package/lib/commonjs/components/OperationalModal/OperationalModal.js +0 -83
  208. package/lib/commonjs/components/OperationalModal/OperationalModal.js.map +0 -1
  209. package/lib/module/components/InputComponents/AudioInput.js +0 -18
  210. package/lib/module/components/InputComponents/AudioInput.js.map +0 -1
  211. package/lib/module/components/InputComponents/AudioRecorder.js +0 -95
  212. package/lib/module/components/InputComponents/AudioRecorder.js.map +0 -1
  213. package/lib/module/components/InputComponents/MediaInput.js +0 -92
  214. package/lib/module/components/InputComponents/MediaInput.js.map +0 -1
  215. package/lib/module/components/InputComponents/RecordingIndicator.js +0 -65
  216. package/lib/module/components/InputComponents/RecordingIndicator.js.map +0 -1
  217. package/lib/module/components/Modals/ModalSelectMedia/ModalSelectMedia.js +0 -200
  218. package/lib/module/components/Modals/ModalSelectMedia/ModalSelectMedia.js.map +0 -1
  219. package/lib/module/components/OperationalModal/OperationalModal.js +0 -77
  220. package/lib/module/components/OperationalModal/OperationalModal.js.map +0 -1
  221. package/lib/typescript/components/InputComponents/AudioInput.d.ts +0 -9
  222. package/lib/typescript/components/InputComponents/AudioInput.d.ts.map +0 -1
  223. package/lib/typescript/components/InputComponents/AudioRecorder.d.ts +0 -9
  224. package/lib/typescript/components/InputComponents/AudioRecorder.d.ts.map +0 -1
  225. package/lib/typescript/components/InputComponents/MediaInput.d.ts +0 -16
  226. package/lib/typescript/components/InputComponents/MediaInput.d.ts.map +0 -1
  227. package/lib/typescript/components/InputComponents/RecordingIndicator.d.ts +0 -4
  228. package/lib/typescript/components/InputComponents/RecordingIndicator.d.ts.map +0 -1
  229. package/lib/typescript/components/Modals/ModalSelectMedia/ModalSelectMedia.d.ts +0 -9
  230. package/lib/typescript/components/Modals/ModalSelectMedia/ModalSelectMedia.d.ts.map +0 -1
  231. package/lib/typescript/components/OperationalModal/OperationalModal.d.ts +0 -9
  232. package/lib/typescript/components/OperationalModal/OperationalModal.d.ts.map +0 -1
  233. package/src/components/InputComponents/AudioInput.tsx +0 -24
  234. package/src/components/InputComponents/AudioRecorder.tsx +0 -100
  235. package/src/components/InputComponents/MediaInput.tsx +0 -113
  236. package/src/components/InputComponents/RecordingIndicator.tsx +0 -77
  237. package/src/components/Modals/ModalSelectMedia/ModalSelectMedia.tsx +0 -283
  238. package/src/components/OperationalModal/OperationalModal.tsx +0 -78
@@ -31,6 +31,7 @@ import { parseMessageBody } from '../../helpers/parseMessageBody';
31
31
  import { useMessageHeapState } from '../../hooks/useMessageHeapState';
32
32
  import { DoubleTick } from '../../assets/icons';
33
33
  import { useXmppClient } from '../../context/xmppProvider';
34
+ import { useSendMessage } from '../../hooks/useSendMessage';
34
35
  import { MessageReaction } from './MessageReaction';
35
36
  import { MessageFooter } from '../styled/StyledComponents';
36
37
 
@@ -47,14 +48,17 @@ const CustomMessageBubble = styled.View<{
47
48
  deleted?: boolean;
48
49
  backgroundMessageUser?: string;
49
50
  backgroundMessage?: string;
51
+ isMedia?: boolean;
50
52
  }>`
51
53
  position: relative;
52
54
  max-width: 85%;
53
55
  min-width: 30%;
54
- padding: 10px;
56
+ padding: ${({isMedia}) => isMedia ? '0' : '10'}px;
57
+ padding-bottom: ${({isMedia}) => isMedia ? '6' : '10'}px;
55
58
  margin-right: ${({isUser}) => isUser ? '0' : '10px'};
56
59
  margin-left: ${({isUser}) => isUser ? '10px' : '0'};
57
60
  border-radius: 10px;
61
+ overflow: hidden;
58
62
  border-bottom-left-radius: ${({ isUser }) => (isUser ? '10' : '0')}px;
59
63
  border-bottom-right-radius: ${({ isUser }) => (isUser ? '0' : '10')}px;
60
64
  background-color: ${({
@@ -113,7 +117,8 @@ const Message: React.FC<MessageProps> = ({ message, isUser, isReply }) => {
113
117
  const dispatch = useDispatch();
114
118
  const { client } = useXmppClient();
115
119
  const { config, langSource, user} = useChatSettingState();
116
- const { idSet } = useMessageHeapState();
120
+ const { idSet, failedIdSet } = useMessageHeapState();
121
+ const { retryMessage } = useSendMessage();
117
122
 
118
123
  const [isPressed, setIsPressed] = useState(false);
119
124
 
@@ -218,14 +223,41 @@ const Message: React.FC<MessageProps> = ({ message, isUser, isReply }) => {
218
223
  if (messageRef.current) {
219
224
  const nodeHandle = findNodeHandle(messageRef.current);
220
225
  if (nodeHandle) {
221
- UIManager.measure(nodeHandle, (x, y, width, height, pageX, pageY) => {
226
+ UIManager.measure(nodeHandle, (_x, _y, _width, height, pageX, pageY) => {
222
227
  const screenHeight = Dimensions.get('window').height;
228
+ // MessageInteractions is ~5 rows (delete/edit/reply/react/copy)
229
+ // at ~48px each + padding, plus emoji bar on top. 280 covers
230
+ // both even with a couple of long labels. The legacy 150
231
+ // figure assumed a much shorter menu and stranded the popup
232
+ // off-screen on the last message of the room.
233
+ const MENU_HEIGHT = 280;
234
+ // Treat the consumer-supplied keyboardVerticalOffset as a
235
+ // proxy for tab bar + bottom safe-area when present; falls
236
+ // back to a reasonable default so the menu doesn't bump into
237
+ // the nav bar on phones without one in config.
238
+ const bottomReserve =
239
+ (config?.keyboardVerticalOffset ?? 0) + 24;
240
+ const topReserve = 16;
241
+
242
+ const spaceBelow = screenHeight - (pageY + height) - bottomReserve;
243
+ const spaceAbove = pageY - topReserve;
244
+
245
+ let menuY: number;
246
+ if (spaceBelow >= MENU_HEIGHT) {
247
+ menuY = pageY + height;
248
+ } else if (spaceAbove >= MENU_HEIGHT) {
249
+ menuY = pageY - MENU_HEIGHT;
250
+ } else {
251
+ // Neither side has room (e.g. very small phone, large
252
+ // menu) — clamp to the visible region so at least the
253
+ // top of the menu is reachable.
254
+ menuY = Math.max(
255
+ topReserve,
256
+ screenHeight - MENU_HEIGHT - bottomReserve
257
+ );
258
+ }
223
259
 
224
- const enoughSpaceBelow = screenHeight - pageY - height > 150;
225
- setContextMenuPosition({
226
- x: pageX,
227
- y: enoughSpaceBelow ? pageY + height : pageY - 150,
228
- });
260
+ setContextMenuPosition({ x: pageX, y: menuY });
229
261
  });
230
262
  }
231
263
  }
@@ -236,7 +268,13 @@ const Message: React.FC<MessageProps> = ({ message, isUser, isReply }) => {
236
268
  ? parseMessageBody(config?.messageTextFilter.filterFunction(message.body))
237
269
  : parseMessageBody(message.body);
238
270
 
239
- const isPending = idSet.has(message.id) || message?.pending || false;
271
+ const isFailed = failedIdSet.has(message.id);
272
+ const isPending =
273
+ !isFailed && (idSet.has(message.id) || message?.pending || false);
274
+
275
+ const onRetryPress = () => {
276
+ if (isFailed) {retryMessage(message.id);}
277
+ };
240
278
 
241
279
  return (
242
280
  <View>
@@ -279,6 +317,7 @@ const Message: React.FC<MessageProps> = ({ message, isUser, isReply }) => {
279
317
  <CustomMessageBubble
280
318
  isUser={isUser}
281
319
  deleted={message.isDeleted}
320
+ isMedia={message?.isMediafile === 'true' && !message?.isDeleted}
282
321
  backgroundMessageUser={config?.messageColor?.backgroundMessageUser}
283
322
  backgroundMessage={config?.messageColor?.backgroundMessage}
284
323
  >
@@ -327,11 +366,21 @@ const Message: React.FC<MessageProps> = ({ message, isUser, isReply }) => {
327
366
  )}
328
367
  <CustomMessageTimestamp>
329
368
  {!config?.disableSentLogic && isUser && isPending && <Text>sending...</Text>}
369
+ {!config?.disableSentLogic && isUser && isFailed && (
370
+ <Text
371
+ onPress={onRetryPress}
372
+ style={styles.failedText}
373
+ accessibilityRole="button"
374
+ accessibilityLabel="Retry sending message"
375
+ >
376
+ ! Failed — tap to retry
377
+ </Text>
378
+ )}
330
379
  {new Date(message.date).toLocaleTimeString([], {
331
380
  hour: '2-digit',
332
381
  minute: '2-digit',
333
382
  })}
334
- {!config?.disableSentLogic && isUser && !isPending && (
383
+ {!config?.disableSentLogic && isUser && !isPending && !isFailed && (
335
384
  <DoubleTick />
336
385
  )}
337
386
  </CustomMessageTimestamp>
@@ -401,4 +450,9 @@ const styles = StyleSheet.create({
401
450
  marginTop: 5,
402
451
  alignSelf: 'flex-end',
403
452
  },
453
+ failedText: {
454
+ color: '#E53935',
455
+ fontWeight: '600',
456
+ marginRight: 6,
457
+ },
404
458
  });
@@ -1,40 +1,21 @@
1
- import React, { useMemo, useState } from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { Delimeter, MenuItem } from '../ContextMenu/ContextMenuComponents';
3
- import { useDispatch, useSelector } from 'react-redux';
3
+ import { useSelector } from 'react-redux';
4
4
  import { RootState } from '../../roomStore';
5
5
  import {
6
6
  MESSAGE_INTERACTIONS,
7
7
  MESSAGE_INTERACTIONS_ICONS,
8
8
  } from '../../helpers/constants/MESSAGE_INTERACTIONS';
9
9
  import { IMessage } from '../../types/types';
10
- import { useXmppClient } from '../../context/xmppProvider';
11
- import { setActiveMessage } from '../../roomStore/roomsSlice';
12
10
  import {
13
- Alert,
14
11
  Modal,
15
12
  Text,
16
- TouchableWithoutFeedback,
17
13
  StyleSheet,
18
14
  View,
19
15
  Pressable,
20
16
  } from 'react-native';
21
17
  import * as Clipboard from 'expo-clipboard';
22
18
  import { useToast } from '../../context/ToastContext';
23
- import EmojiPicker from 'react-native-emoji-selector';
24
-
25
- const fixedEmojiIds = ['joy', 'heart', 'fire', '+1', 'smile', 'scream'];
26
-
27
- function convertIdToEmoji(id: string) {
28
- const mapping = {
29
- joy: '😂',
30
- heart: '❤️',
31
- fire: '🔥',
32
- '+1': '👍',
33
- smile: '😄',
34
- scream: '😱',
35
- };
36
- return mapping[id as keyof typeof mapping] || '❓';
37
- }
38
19
 
39
20
  interface MessageInteractionsProps {
40
21
  isReply?: boolean;
@@ -57,38 +38,13 @@ const MessageInteractions: React.FC<MessageInteractionsProps> = ({
57
38
  handleReplyMessage: replyMessage,
58
39
  handleDeleteMessage,
59
40
  handleEditMessage,
60
- handleReactionMessage,
61
41
  }) => {
62
- const { client } = useXmppClient();
63
- const dispatch = useDispatch();
64
42
  const { showToast } = useToast();
65
43
 
66
- const [pickerVisible, setPickerVisible] = useState(false);
67
-
68
- const handleReactionClick = (id: string) => {
69
- handleReactionMessage(id);
70
- closeMenu();
71
- };
72
-
73
- const onClose = () => {
74
- setPickerVisible(false);
75
- };
76
-
77
- // const handleDeleteMessage = (roomJid: string, messageId: string) => {
78
- // // dispatch(deleteRoomMessage({ roomJID: room, messageId: msgId }));
79
- // client.deleteMessageStanza(roomJid, messageId);
80
- // };
81
-
82
44
  const config = useSelector(
83
45
  (state: RootState) => state.chatSettingStore.config
84
46
  );
85
47
 
86
- const closeContextMenu = () => {
87
- // if (!config?.disableInteractions) {
88
- // setContextMenu({ visible: false, x: 0, y: 0 });
89
- // }
90
- };
91
-
92
48
  const handleCopyMessage = async (text: string) => {
93
49
  try {
94
50
  await Clipboard.setStringAsync(text);
@@ -143,60 +99,6 @@ const MessageInteractions: React.FC<MessageInteractionsProps> = ({
143
99
  <View style={styles.overlayFill}>
144
100
  <Pressable style={StyleSheet.absoluteFill} onPress={closeMenu} />
145
101
  <View style={[styles.contextMenu, memoPosition]}>
146
- {/* <MenuItem onClick={() => console.log(MESSAGE_INTERACTIONS.SEND_COINS)}>
147
- {MESSAGE_INTERACTIONS.SEND_COINS}
148
- <MESSAGE_INTERACTIONS_ICONS.SEND_COINS />{' '}
149
- </MenuItem>
150
- <Delimeter />
151
- <MenuItem onClick={() => console.log(MESSAGE_INTERACTIONS.SEND_ITEM)}>
152
- {MESSAGE_INTERACTIONS.SEND_ITEM}
153
- <MESSAGE_INTERACTIONS_ICONS.SEND_ITEM />{' '}
154
- </MenuItem> */}
155
- {/*
156
- {!config?.disableReactions && (
157
- <View style={{ flexDirection: 'row', paddingBottom: 10 }}>
158
- {fixedEmojiIds.map((id) => (
159
- <Pressable
160
- key={id}
161
- onPress={() => handleReactionClick(id)}
162
- style={{ marginRight: 6 }}
163
- >
164
- <Text style={{ fontSize: 26 }}>{convertIdToEmoji(id)}</Text>
165
- </Pressable>
166
- ))}
167
- <Pressable
168
- onPress={() => setPickerVisible(!pickerVisible)}
169
- style={{ marginLeft: 10 }}
170
- >
171
- <Text style={{ fontSize: 24 }}>⌄</Text>
172
- </Pressable>
173
- </View>
174
- )}
175
-
176
- {!config?.disableReactions && pickerVisible && (
177
- <View style={{ height: 250 }}>
178
- <EmojiPicker
179
- onEmojiSelected={(emoji) => {
180
- handleReactionClick(emoji);
181
- }}
182
- showSearchBar={false}
183
- showTabs={false}
184
- />
185
- </View>
186
- )}
187
- */}
188
-
189
- {/*
190
- {!isReply && (
191
- <>
192
- <MenuItem onPress={handleReplyMessage}>
193
- <Text>{MESSAGE_INTERACTIONS.REPLY}</Text>
194
- <MESSAGE_INTERACTIONS_ICONS.REPLY />
195
- </MenuItem>
196
- <Delimeter />
197
- </>
198
- )}
199
- */}
200
102
  <MenuItem onPress={() => handleCopyMessage(message.body!)}>
201
103
  <Text>{MESSAGE_INTERACTIONS.COPY}</Text>
202
104
  <MESSAGE_INTERACTIONS_ICONS.COPY />
@@ -215,11 +117,6 @@ const MessageInteractions: React.FC<MessageInteractionsProps> = ({
215
117
  </MenuItem>
216
118
  </>
217
119
  )}
218
- {/* <Delimeter />
219
- <MenuItem onClick={() => console.log(MESSAGE_INTERACTIONS.REPORT)}>
220
- {MESSAGE_INTERACTIONS.REPORT}
221
- <MESSAGE_INTERACTIONS_ICONS.REPORT />{' '}
222
- </MenuItem> */}
223
120
  </View>
224
121
  </View>
225
122
  )}
@@ -11,7 +11,7 @@ import {
11
11
  Viewider,
12
12
  Divider,
13
13
  } from '../styledModalComponents';
14
- import { Pressable, View, Text, ScrollView, Alert, Linking, Platform } from 'react-native';
14
+ import { Pressable, View, Text, ScrollView, Alert, Linking } from 'react-native';
15
15
  import ModalHeaderComponent from '../ModalHeaderComponent';
16
16
  import { ProfileImagePlaceholder } from '../../MainComponents/ProfileImagePlaceholder';
17
17
  import { useDispatch, useSelector } from 'react-redux';
@@ -19,14 +19,7 @@ import { RootState, getActiveRoom } from '../../../roomStore';
19
19
  import { uploadFile } from '../../../networking/api-requests/auth.api';
20
20
  import { useXmppClient } from '../../../context/xmppProvider';
21
21
  import { updateRoom } from '../../../roomStore/roomsSlice';
22
- import ImagePicker from 'react-native-image-crop-picker';
23
- import {
24
- check,
25
- request,
26
- PERMISSIONS,
27
- RESULTS,
28
- Permission,
29
- } from 'react-native-permissions';
22
+ import * as ImagePicker from 'expo-image-picker';
30
23
  import Loader from '../../styled/Loader';
31
24
  import Button from '../../styled/Button';
32
25
  import { DeleteIcon, MoreIcon, QrIcon } from '../../../assets/icons';
@@ -89,40 +82,24 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
89
82
  }
90
83
  }, [client, activeRoom?.jid]);
91
84
 
92
- const checkPermission = async (permission: Permission) => {
93
- const status = await check(permission);
94
- if (status === RESULTS.GRANTED) {
95
- return status;
96
- } else if (status === RESULTS.DENIED) {
97
- const requestStatus = await request(permission);
98
- return requestStatus;
99
- }
100
- return status;
101
- };
102
-
103
85
  const onUpload = async () => {
104
86
  let loadingSet = false;
105
87
  try {
106
88
  setLoading(true);
107
89
  loadingSet = true;
108
90
 
109
- const permission =
110
- Platform.OS === 'ios'
111
- ? PERMISSIONS.IOS.PHOTO_LIBRARY
112
- : PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
113
- const permissionStatus = await checkPermission(permission);
114
-
115
- if (permissionStatus !== RESULTS.GRANTED) {
91
+ // expo-image-picker owns the permission prompt internally and
92
+ // returns a `granted` flag. No separate react-native-permissions
93
+ // round-trip needed.
94
+ const perm = await ImagePicker.requestMediaLibraryPermissionsAsync();
95
+ if (!perm.granted) {
116
96
  Alert.alert(
117
97
  'Permission required',
118
98
  'Photo library permission is needed to select images.',
119
99
  [
120
100
  {
121
101
  text: 'Cancel',
122
- onPress: () => {
123
- setLoading(false);
124
- console.log('Photo permission cancelled');
125
- },
102
+ onPress: () => setLoading(false),
126
103
  style: 'cancel',
127
104
  },
128
105
  {
@@ -134,18 +111,20 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
134
111
  return;
135
112
  }
136
113
 
137
- const image = await ImagePicker.openPicker({
138
- width: 300,
139
- height: 300,
140
- cropping: true,
141
- cropperCircleOverlay: true,
142
- compressImageQuality: 0.8,
114
+ const result = await ImagePicker.launchImageLibraryAsync({
115
+ mediaTypes: ['images'],
116
+ allowsEditing: true,
117
+ aspect: [1, 1],
118
+ quality: 0.8,
143
119
  });
144
-
145
- const originalName = image.path.split('/').pop();
120
+ if (result.canceled || !result.assets?.[0]) {
121
+ return;
122
+ }
123
+ const asset = result.assets[0];
124
+ const originalName = asset.fileName || asset.uri.split('/').pop();
146
125
  const fileObject = {
147
- uri: image.path,
148
- type: image.mime || 'image/jpeg',
126
+ uri: asset.uri,
127
+ type: asset.mimeType || 'image/jpeg',
149
128
  name: originalName || `profile_${Date.now()}.jpg`,
150
129
  };
151
130
 
@@ -17,6 +17,7 @@ import * as FileSystem from 'expo-file-system/legacy';
17
17
  import * as MediaLibrary from 'expo-media-library';
18
18
  import { useToast } from '../../../context/ToastContext';
19
19
  import PdfViewer from './PdfView';
20
+ import { ensureFilenameHasExtension } from '../../../helpers/mimeToExtension';
20
21
 
21
22
  export const FullScreenVideo = styled.View`
22
23
  width: 100%;
@@ -61,10 +62,10 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
61
62
  }
62
63
 
63
64
  try {
64
- let fileName = activeFile.fileName || `media_${Date.now()}`;
65
- if (!fileName.includes('.')) {
66
- fileName += activeFile.mimetype.startsWith('image/') ? '.jpg' : '.mp4';
67
- }
65
+ const fileName = ensureFilenameHasExtension(
66
+ activeFile.fileName,
67
+ activeFile.mimetype
68
+ );
68
69
 
69
70
  const filePath = FileSystem.cacheDirectory + fileName;
70
71
  const download = await FileSystem.downloadAsync(activeFile.fileURL, filePath);
@@ -87,7 +88,14 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
87
88
 
88
89
  const saveFileToDownloads = async () => {
89
90
  try {
90
- const fileName = activeFile.fileName || `file_${Date.now()}`;
91
+ // Always include a valid extension on the cache filename. expo-media-
92
+ // library's createAssetAsync inspects the URI's extension and throws
93
+ // "Could not get the file's extension" when missing — see bug #9 in
94
+ // sdk-bug-tracker.md.
95
+ const fileName = ensureFilenameHasExtension(
96
+ activeFile.fileName,
97
+ activeFile.mimetype
98
+ );
91
99
  const filePath = FileSystem.cacheDirectory + fileName;
92
100
  const download = await FileSystem.downloadAsync(activeFile.fileURL, filePath);
93
101
 
@@ -162,23 +170,33 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
162
170
  );
163
171
  case activeFile.mimetype === 'application/pdf':
164
172
  return <PdfViewer pdfUrl={activeFile.fileURL} />;
165
- default:
173
+ default: {
174
+ const displayName = ensureFilenameHasExtension(
175
+ activeFile.fileName,
176
+ activeFile.mimetype
177
+ );
166
178
  return (
167
179
  <View
168
180
  style={{
169
181
  backgroundColor: '#FFF8ED',
170
182
  borderRadius: 16,
171
- display: 'flex',
172
- padding: 16,
183
+ padding: 20,
184
+ gap: 8,
173
185
  }}
174
186
  >
175
- <Text>
176
- Unable to open the uploaded document. The file format is not
177
- supported by the system. Please upload a file in a compatible
178
- format. You still can download this file.
187
+ <Text style={{ fontSize: 16, fontWeight: '600' }}>
188
+ {displayName}
189
+ </Text>
190
+ <Text style={{ color: '#666' }}>
191
+ {activeFile.mimetype || 'unknown type'}
192
+ </Text>
193
+ <Text style={{ marginTop: 8 }}>
194
+ This file format can't be previewed inline. Tap the save icon
195
+ above to download it.
179
196
  </Text>
180
197
  </View>
181
198
  );
199
+ }
182
200
  }
183
201
  }, [activeFile]);
184
202
 
@@ -22,6 +22,12 @@ interface ModalWrapperProps {
22
22
  setTextarea?: (value: string) => void;
23
23
  handleCloseModal: () => void;
24
24
  handleClick: () => void;
25
+ /**
26
+ * When true, render a small centered dialog instead of the default
27
+ * full-screen container. Used for confirm-style modals (Delete, Leave
28
+ * room) where edge-to-edge feels too heavy.
29
+ */
30
+ compact?: boolean;
25
31
  }
26
32
 
27
33
  export const ModalWrapper: FC<ModalWrapperProps> = ({
@@ -35,6 +41,7 @@ export const ModalWrapper: FC<ModalWrapperProps> = ({
35
41
  setTextarea,
36
42
  handleCloseModal,
37
43
  handleClick,
44
+ compact = false,
38
45
  }) => {
39
46
  const textareaRef = useRef<TextInput>(null);
40
47
 
@@ -54,7 +61,21 @@ export const ModalWrapper: FC<ModalWrapperProps> = ({
54
61
  onRequestClose={handleCloseModal}
55
62
  >
56
63
  <ModalBackground>
57
- <ModalContainer style={{ maxWidth: 640 }}>
64
+ <ModalContainer
65
+ style={
66
+ compact
67
+ ? {
68
+ flex: 0,
69
+ width: '85%',
70
+ maxWidth: 360,
71
+ height: 'auto',
72
+ padding: 24,
73
+ borderRadius: 16,
74
+ gap: 16,
75
+ }
76
+ : { maxWidth: 640 }
77
+ }
78
+ >
58
79
  <CloseButton onPress={handleCloseModal}>
59
80
  <Text style={{ fontSize: 24 }}>&times;</Text>
60
81
  </CloseButton>
@@ -285,7 +285,7 @@ const UserProfileModal: React.FC<UserProfileModalProps> = ({
285
285
  </LabelData>
286
286
  </BorderedContainer>
287
287
 
288
- {selectedUser && (
288
+ {selectedUser && !config?.disableProfilesInteractions && (
289
289
  <>
290
290
  <ActionButton
291
291
  StartIcon={<ChatIcon />}
@@ -5,20 +5,22 @@ import {
5
5
  LastRoomMessageText,
6
6
  } from './StyledRoomComponents';
7
7
  import { LastMessage } from '../../../types/types';
8
- import emojiData from 'emoji-mart';
9
8
 
10
9
  interface LastMessageEmojiProps extends Pick<LastMessage, 'user' | 'emoji'> {}
11
10
 
11
+ /**
12
+ * Renders the per-room last-message preview when the latest message
13
+ * is a reaction (emoji). The `emoji` field is expected to already be
14
+ * a unicode glyph (the reaction stanza carries the rendered character,
15
+ * not an `:id:`) so we just render it as-is — no emoji-id resolution
16
+ * library needed. If a legacy payload still ships an `:id:`, it
17
+ * renders verbatim instead of crashing.
18
+ */
12
19
  const LastMessageEmoji: FC<LastMessageEmojiProps> = ({ user, emoji }) => {
13
- const memoEmoji = (id: string) => {
14
- const emoji = (emojiData as any).emojis[id];
15
- return emoji ? emoji.skins[0].native : '';
16
- };
17
-
18
20
  return (
19
21
  <LastRoomMessageContainer>
20
22
  <LastRoomMessageName>{user?.name || ''}:</LastRoomMessageName>
21
- <LastRoomMessageText>{emoji ? memoEmoji(emoji) : ''}</LastRoomMessageText>
23
+ <LastRoomMessageText>{emoji || ''}</LastRoomMessageText>
22
24
  </LastRoomMessageContainer>
23
25
  );
24
26
  };
@@ -0,0 +1,68 @@
1
+ import React from 'react';
2
+ import { Pressable, StyleSheet, Text, View } from 'react-native';
3
+
4
+ interface CheckboxProps {
5
+ value: boolean;
6
+ onValueChange: (next: boolean) => void;
7
+ disabled?: boolean;
8
+ color?: string;
9
+ }
10
+
11
+ /**
12
+ * Minimal RN-native checkbox replacement for the user-list multi-select.
13
+ * Avoids pulling in `@react-native-community/checkbox` (legacy native
14
+ * module that needs the Metro shim) — the UI footprint is one tap-target
15
+ * with a check glyph, so a plain Pressable does the job.
16
+ */
17
+ const Checkbox: React.FC<CheckboxProps> = ({
18
+ value,
19
+ onValueChange,
20
+ disabled,
21
+ color = '#0052cd',
22
+ }) => {
23
+ return (
24
+ <Pressable
25
+ accessibilityRole="checkbox"
26
+ accessibilityState={{ checked: value, disabled: !!disabled }}
27
+ disabled={disabled}
28
+ onPress={() => onValueChange(!value)}
29
+ style={[
30
+ styles.box,
31
+ { borderColor: color, backgroundColor: value ? color : 'transparent' },
32
+ disabled && styles.disabled,
33
+ ]}
34
+ >
35
+ {value && (
36
+ <View style={styles.checkInner}>
37
+ <Text style={styles.checkGlyph}>✓</Text>
38
+ </View>
39
+ )}
40
+ </Pressable>
41
+ );
42
+ };
43
+
44
+ const styles = StyleSheet.create({
45
+ box: {
46
+ width: 18,
47
+ height: 18,
48
+ borderWidth: 1.5,
49
+ borderRadius: 3,
50
+ alignItems: 'center',
51
+ justifyContent: 'center',
52
+ },
53
+ checkInner: {
54
+ alignItems: 'center',
55
+ justifyContent: 'center',
56
+ },
57
+ checkGlyph: {
58
+ color: '#fff',
59
+ fontSize: 12,
60
+ fontWeight: '700',
61
+ lineHeight: 14,
62
+ },
63
+ disabled: {
64
+ opacity: 0.4,
65
+ },
66
+ });
67
+
68
+ export default Checkbox;
@@ -1,5 +1,4 @@
1
1
  import styled from 'styled-components/native';
2
- import CheckBox from '@react-native-community/checkbox';
3
2
 
4
3
  export const ScrollableContainer = styled.View`
5
4
  max-height: 100px;
@@ -17,13 +16,6 @@ export const UserItem = styled.TouchableHighlight`
17
16
  gap: 8px;
18
17
  `;
19
18
 
20
-
21
- export const Checkbox = styled(CheckBox)`
22
- width: 16px;
23
- height: 16px;
24
- tint-color: #0052cd;
25
- `;
26
-
27
19
  export const Label = styled.Text`
28
20
  font-size: 16px;
29
21
  color: #333;
@@ -9,9 +9,9 @@ import { ModalTitle, LabelData } from '../Modals/styledModalComponents';
9
9
  import {
10
10
  ScrollableContainer,
11
11
  UserItem,
12
- Checkbox,
13
12
  Label,
14
13
  } from './StyledComponents';
14
+ import Checkbox from './Checkbox';
15
15
  import { useSelector } from 'react-redux';
16
16
  import { RootState } from '../../roomStore';
17
17
  import { RoomMember } from '../../types/types';