@ethora/chat-component-rn 26.8.7 → 26.8.9

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 (243) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +27 -4
  3. package/lib/commonjs/components/Header/HeaderRoomList.js +34 -6
  4. package/lib/commonjs/components/Header/HeaderRoomList.js.map +1 -1
  5. package/lib/commonjs/components/MainComponents/ChatHeader.js +15 -1
  6. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  7. package/lib/commonjs/components/MainComponents/ChatWrapper.js +5 -3
  8. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  9. package/lib/commonjs/components/MainComponents/ReduxWrapper.js +8 -0
  10. package/lib/commonjs/components/MainComponents/ReduxWrapper.js.map +1 -1
  11. package/lib/commonjs/components/MainComponents/RoomList.js +63 -11
  12. package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
  13. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js +26 -13
  14. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js.map +1 -1
  15. package/lib/commonjs/components/MenuRoom/MenuRoom.js +5 -3
  16. package/lib/commonjs/components/MenuRoom/MenuRoom.js.map +1 -1
  17. package/lib/commonjs/components/MessageBubble/Message.js +3 -3
  18. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  19. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +5 -3
  20. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  21. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +1 -1
  22. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  23. package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js +5 -4
  24. package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -1
  25. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +7 -5
  26. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  27. package/lib/commonjs/components/Modals/Modal/Modal.js +33 -11
  28. package/lib/commonjs/components/Modals/Modal/Modal.js.map +1 -1
  29. package/lib/commonjs/components/Modals/NewChatModal/NewChatModal.js +8 -8
  30. package/lib/commonjs/components/Modals/NewChatModal/NewChatModal.js.map +1 -1
  31. package/lib/commonjs/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.js +279 -0
  32. package/lib/commonjs/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.js.map +1 -0
  33. package/lib/commonjs/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.js +10 -8
  34. package/lib/commonjs/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.js.map +1 -1
  35. package/lib/commonjs/components/Modals/SettingsModals/Visibility/VisibilityModal.js +13 -11
  36. package/lib/commonjs/components/Modals/SettingsModals/Visibility/VisibilityModal.js.map +1 -1
  37. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +22 -38
  38. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  39. package/lib/commonjs/components/Modals/UserSettingsModal/LanguageCard.js +121 -0
  40. package/lib/commonjs/components/Modals/UserSettingsModal/LanguageCard.js.map +1 -0
  41. package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js +11 -6
  42. package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
  43. package/lib/commonjs/components/RoomComponents/SwipeableRoomRow.js +133 -0
  44. package/lib/commonjs/components/RoomComponents/SwipeableRoomRow.js.map +1 -0
  45. package/lib/commonjs/components/Thread/ThreadWrapper.js +4 -2
  46. package/lib/commonjs/components/Thread/ThreadWrapper.js.map +1 -1
  47. package/lib/commonjs/context/xmppProvider.js +15 -3
  48. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  49. package/lib/commonjs/e2ee/stanza.js +1 -1
  50. package/lib/commonjs/e2ee/stanza.js.map +1 -1
  51. package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js +2 -0
  52. package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
  53. package/lib/commonjs/helpers/constants/MODAL_TYPES.js +1 -0
  54. package/lib/commonjs/helpers/constants/MODAL_TYPES.js.map +1 -1
  55. package/lib/commonjs/helpers/preferencesStorage.js.map +1 -1
  56. package/lib/commonjs/hooks/useChatSettingState.js +4 -0
  57. package/lib/commonjs/hooks/useChatSettingState.js.map +1 -1
  58. package/lib/commonjs/hooks/useChatWrapperInit.js +4 -0
  59. package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
  60. package/lib/commonjs/hooks/useConnectionLabel.js +29 -0
  61. package/lib/commonjs/hooks/useConnectionLabel.js.map +1 -0
  62. package/lib/commonjs/i18n/strings.js +199 -1
  63. package/lib/commonjs/i18n/strings.js.map +1 -1
  64. package/lib/commonjs/i18n/useT.js +8 -4
  65. package/lib/commonjs/i18n/useT.js.map +1 -1
  66. package/lib/commonjs/networking/api-requests/user.api.js +13 -0
  67. package/lib/commonjs/networking/api-requests/user.api.js.map +1 -1
  68. package/lib/commonjs/networking/stanzaHandlers.js +1 -1
  69. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  70. package/lib/commonjs/networking/xmppClient.js +29 -1
  71. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  72. package/lib/commonjs/roomStore/chatSettingsSlice.js +12 -2
  73. package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
  74. package/lib/commonjs/services/languageSettings.js +177 -0
  75. package/lib/commonjs/services/languageSettings.js.map +1 -0
  76. package/lib/commonjs/version.js +1 -1
  77. package/lib/module/components/Header/HeaderRoomList.js +34 -6
  78. package/lib/module/components/Header/HeaderRoomList.js.map +1 -1
  79. package/lib/module/components/MainComponents/ChatHeader.js +15 -1
  80. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  81. package/lib/module/components/MainComponents/ChatWrapper.js +5 -3
  82. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  83. package/lib/module/components/MainComponents/ReduxWrapper.js +9 -1
  84. package/lib/module/components/MainComponents/ReduxWrapper.js.map +1 -1
  85. package/lib/module/components/MainComponents/RoomList.js +64 -12
  86. package/lib/module/components/MainComponents/RoomList.js.map +1 -1
  87. package/lib/module/components/Menu/HeaderRoomListMenu.js +26 -13
  88. package/lib/module/components/Menu/HeaderRoomListMenu.js.map +1 -1
  89. package/lib/module/components/MenuRoom/MenuRoom.js +5 -3
  90. package/lib/module/components/MenuRoom/MenuRoom.js.map +1 -1
  91. package/lib/module/components/MessageBubble/Message.js +3 -3
  92. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  93. package/lib/module/components/MessageBubble/MessageInteractions.js +5 -3
  94. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  95. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +1 -1
  96. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  97. package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js +5 -4
  98. package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -1
  99. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +7 -5
  100. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  101. package/lib/module/components/Modals/Modal/Modal.js +33 -11
  102. package/lib/module/components/Modals/Modal/Modal.js.map +1 -1
  103. package/lib/module/components/Modals/NewChatModal/NewChatModal.js +8 -8
  104. package/lib/module/components/Modals/NewChatModal/NewChatModal.js.map +1 -1
  105. package/lib/module/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.js +274 -0
  106. package/lib/module/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.js.map +1 -0
  107. package/lib/module/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.js +10 -8
  108. package/lib/module/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.js.map +1 -1
  109. package/lib/module/components/Modals/SettingsModals/Visibility/VisibilityModal.js +13 -11
  110. package/lib/module/components/Modals/SettingsModals/Visibility/VisibilityModal.js.map +1 -1
  111. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +24 -40
  112. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  113. package/lib/module/components/Modals/UserSettingsModal/LanguageCard.js +116 -0
  114. package/lib/module/components/Modals/UserSettingsModal/LanguageCard.js.map +1 -0
  115. package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js +10 -5
  116. package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
  117. package/lib/module/components/RoomComponents/SwipeableRoomRow.js +126 -0
  118. package/lib/module/components/RoomComponents/SwipeableRoomRow.js.map +1 -0
  119. package/lib/module/components/Thread/ThreadWrapper.js +4 -2
  120. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  121. package/lib/module/context/xmppProvider.js +16 -4
  122. package/lib/module/context/xmppProvider.js.map +1 -1
  123. package/lib/module/e2ee/stanza.js +1 -1
  124. package/lib/module/e2ee/stanza.js.map +1 -1
  125. package/lib/module/helpers/constants/MODAL_COMPONENTS.js +2 -0
  126. package/lib/module/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
  127. package/lib/module/helpers/constants/MODAL_TYPES.js +1 -0
  128. package/lib/module/helpers/constants/MODAL_TYPES.js.map +1 -1
  129. package/lib/module/helpers/preferencesStorage.js.map +1 -1
  130. package/lib/module/hooks/useChatSettingState.js +4 -0
  131. package/lib/module/hooks/useChatSettingState.js.map +1 -1
  132. package/lib/module/hooks/useChatWrapperInit.js +4 -0
  133. package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
  134. package/lib/module/hooks/useConnectionLabel.js +25 -0
  135. package/lib/module/hooks/useConnectionLabel.js.map +1 -0
  136. package/lib/module/i18n/strings.js +199 -1
  137. package/lib/module/i18n/strings.js.map +1 -1
  138. package/lib/module/i18n/useT.js +8 -4
  139. package/lib/module/i18n/useT.js.map +1 -1
  140. package/lib/module/networking/api-requests/user.api.js +12 -0
  141. package/lib/module/networking/api-requests/user.api.js.map +1 -1
  142. package/lib/module/networking/stanzaHandlers.js +1 -1
  143. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  144. package/lib/module/networking/xmppClient.js +29 -1
  145. package/lib/module/networking/xmppClient.js.map +1 -1
  146. package/lib/module/roomStore/chatSettingsSlice.js +9 -1
  147. package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
  148. package/lib/module/services/languageSettings.js +164 -0
  149. package/lib/module/services/languageSettings.js.map +1 -0
  150. package/lib/module/version.js +1 -1
  151. package/lib/typescript/components/Header/HeaderRoomList.d.ts +1 -0
  152. package/lib/typescript/components/Header/HeaderRoomList.d.ts.map +1 -1
  153. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  154. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  155. package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts.map +1 -1
  156. package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
  157. package/lib/typescript/components/Menu/HeaderRoomListMenu.d.ts +7 -1
  158. package/lib/typescript/components/Menu/HeaderRoomListMenu.d.ts.map +1 -1
  159. package/lib/typescript/components/MenuRoom/MenuRoom.d.ts.map +1 -1
  160. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  161. package/lib/typescript/components/Modals/ChatProfileModal/ReportChatModal.d.ts +2 -0
  162. package/lib/typescript/components/Modals/ChatProfileModal/ReportChatModal.d.ts.map +1 -1
  163. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  164. package/lib/typescript/components/Modals/Modal/Modal.d.ts.map +1 -1
  165. package/lib/typescript/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.d.ts +15 -0
  166. package/lib/typescript/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.d.ts.map +1 -0
  167. package/lib/typescript/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.d.ts.map +1 -1
  168. package/lib/typescript/components/Modals/SettingsModals/Visibility/VisibilityModal.d.ts.map +1 -1
  169. package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
  170. package/lib/typescript/components/Modals/UserSettingsModal/LanguageCard.d.ts +14 -0
  171. package/lib/typescript/components/Modals/UserSettingsModal/LanguageCard.d.ts.map +1 -0
  172. package/lib/typescript/components/Modals/UserSettingsModal/UserSettingsModal.d.ts.map +1 -1
  173. package/lib/typescript/components/RoomComponents/SwipeableRoomRow.d.ts +19 -0
  174. package/lib/typescript/components/RoomComponents/SwipeableRoomRow.d.ts.map +1 -0
  175. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  176. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  177. package/lib/typescript/helpers/constants/MODAL_COMPONENTS.d.ts.map +1 -1
  178. package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts +1 -0
  179. package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts.map +1 -1
  180. package/lib/typescript/helpers/preferencesStorage.d.ts +4 -0
  181. package/lib/typescript/helpers/preferencesStorage.d.ts.map +1 -1
  182. package/lib/typescript/hooks/useChatSettingState.d.ts +2 -0
  183. package/lib/typescript/hooks/useChatSettingState.d.ts.map +1 -1
  184. package/lib/typescript/hooks/useChatWrapperInit.d.ts.map +1 -1
  185. package/lib/typescript/hooks/useConnectionLabel.d.ts +8 -0
  186. package/lib/typescript/hooks/useConnectionLabel.d.ts.map +1 -0
  187. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  188. package/lib/typescript/i18n/useT.d.ts.map +1 -1
  189. package/lib/typescript/networking/api-requests/user.api.d.ts +6 -0
  190. package/lib/typescript/networking/api-requests/user.api.d.ts.map +1 -1
  191. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  192. package/lib/typescript/networking/xmppClient.d.ts +11 -1
  193. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  194. package/lib/typescript/roomStore/chatSettingsSlice.d.ts +18 -1
  195. package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
  196. package/lib/typescript/services/languageSettings.d.ts +29 -0
  197. package/lib/typescript/services/languageSettings.d.ts.map +1 -0
  198. package/lib/typescript/types/models/user.model.d.ts +4 -0
  199. package/lib/typescript/types/models/user.model.d.ts.map +1 -1
  200. package/lib/typescript/types/types.d.ts +23 -0
  201. package/lib/typescript/types/types.d.ts.map +1 -1
  202. package/lib/typescript/version.d.ts +1 -1
  203. package/package.json +1 -1
  204. package/src/components/Header/HeaderRoomList.tsx +31 -6
  205. package/src/components/MainComponents/ChatHeader.tsx +16 -1
  206. package/src/components/MainComponents/ChatWrapper.tsx +5 -3
  207. package/src/components/MainComponents/ReduxWrapper.tsx +6 -0
  208. package/src/components/MainComponents/RoomList.tsx +78 -12
  209. package/src/components/Menu/HeaderRoomListMenu.tsx +40 -13
  210. package/src/components/MenuRoom/MenuRoom.tsx +5 -3
  211. package/src/components/MessageBubble/Message.tsx +3 -3
  212. package/src/components/MessageBubble/MessageInteractions.tsx +5 -3
  213. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +1 -1
  214. package/src/components/Modals/ChatProfileModal/ReportChatModal.tsx +6 -3
  215. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +7 -5
  216. package/src/components/Modals/Modal/Modal.tsx +44 -16
  217. package/src/components/Modals/NewChatModal/NewChatModal.tsx +8 -8
  218. package/src/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.tsx +316 -0
  219. package/src/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.tsx +10 -15
  220. package/src/components/Modals/SettingsModals/Visibility/VisibilityModal.tsx +13 -14
  221. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +19 -40
  222. package/src/components/Modals/UserSettingsModal/LanguageCard.tsx +144 -0
  223. package/src/components/Modals/UserSettingsModal/UserSettingsModal.tsx +11 -4
  224. package/src/components/RoomComponents/SwipeableRoomRow.tsx +133 -0
  225. package/src/components/Thread/ThreadWrapper.tsx +4 -2
  226. package/src/context/xmppProvider.tsx +21 -4
  227. package/src/e2ee/stanza.ts +1 -1
  228. package/src/helpers/constants/MODAL_COMPONENTS.tsx +2 -0
  229. package/src/helpers/constants/MODAL_TYPES.ts +1 -0
  230. package/src/helpers/preferencesStorage.ts +4 -0
  231. package/src/hooks/useChatSettingState.tsx +8 -0
  232. package/src/hooks/useChatWrapperInit.ts +4 -0
  233. package/src/hooks/useConnectionLabel.ts +16 -0
  234. package/src/i18n/strings.ts +199 -1
  235. package/src/i18n/useT.ts +6 -4
  236. package/src/networking/api-requests/user.api.ts +9 -0
  237. package/src/networking/stanzaHandlers.ts +1 -0
  238. package/src/networking/xmppClient.ts +33 -1
  239. package/src/roomStore/chatSettingsSlice.ts +30 -0
  240. package/src/services/languageSettings.ts +143 -0
  241. package/src/types/models/user.model.ts +4 -0
  242. package/src/types/types.ts +23 -0
  243. package/src/version.ts +1 -1
@@ -11,6 +11,8 @@ import {
11
11
  setConfig,
12
12
  setPushEnabled,
13
13
  setThemePreference,
14
+ setUiLocale,
15
+ setLangSource,
14
16
  } from '../../roomStore/chatSettingsSlice';
15
17
  import {loadPreferences} from '../../helpers/preferencesStorage';
16
18
  import {ConfigUser, IConfig, MessageProps} from '../../types/types';
@@ -90,6 +92,10 @@ const ChatThemeProvider: React.FC<{config?: IConfig; children: React.ReactNode}>
90
92
  if (typeof prefs.pushEnabled === 'boolean') {
91
93
  dispatch(setPushEnabled(prefs.pushEnabled));
92
94
  }
95
+ // The user's languages, as last picked here; the profile's values
96
+ // take over once the session is up (see languageSettings).
97
+ if (prefs.uiLocale) {dispatch(setUiLocale(prefs.uiLocale));}
98
+ if (prefs.chatLanguage) {dispatch(setLangSource(prefs.chatLanguage as any));}
93
99
  });
94
100
  }, [dispatch]);
95
101
 
@@ -8,6 +8,7 @@ import React, {
8
8
  useRef,
9
9
  } from 'react';
10
10
  import {
11
+ Alert,
11
12
  View,
12
13
  StyleSheet,
13
14
  FlatList,
@@ -16,6 +17,11 @@ import {
16
17
  NativeScrollEvent,
17
18
  NativeSyntheticEvent,
18
19
  } from 'react-native';
20
+ import { useDispatch } from 'react-redux';
21
+ import { deleteRoom } from '../../roomStore/roomsSlice';
22
+ import { useXmppClient } from '../../context/xmppProvider';
23
+ import SwipeableRoomRow, { closeOpenRoomRow } from '../RoomComponents/SwipeableRoomRow';
24
+ import ReportChatModal from '../Modals/ChatProfileModal/ReportChatModal';
19
25
  import { IRoom } from '../../types/types';
20
26
  import { SearchInput } from '../InputComponents/Search';
21
27
  import { BurgerMenuIcon, SearchIcon } from '../../assets/icons';
@@ -79,8 +85,12 @@ const RoomList: React.FC<RoomListProps> = ({
79
85
  const { config } = useChatSettingState();
80
86
  const t = useT();
81
87
  const theme = useTheme();
88
+ const dispatch = useDispatch();
89
+ const { client } = useXmppClient();
82
90
 
83
91
  const [open, setOpen] = useState(false);
92
+ // The room a swiped row's Report opened the form for.
93
+ const [reportJid, setReportJid] = useState<string | null>(null);
84
94
  const [searchTerm, setSearchTerm] = useState('');
85
95
  const [isLongPress, setIsLongPress] = useState(false);
86
96
  const [isDrawerOpen, setDrawerOpen] = useState(false);
@@ -121,6 +131,31 @@ const RoomList: React.FC<RoomListProps> = ({
121
131
  setSearchTerm(text);
122
132
  }, []);
123
133
 
134
+ // Behind a row swiped left. Leaving is the chat profile's flow: an
135
+ // unavailable presence to the MUC, then the room dropped from the store.
136
+ const swipeActions = !config?.disableRoomSwipeActions;
137
+ const handleLeaveRoom = useCallback(
138
+ (jid: string) => {
139
+ Alert.alert(t('modal.leaveChat.title'), t('modal.leaveChat.description'), [
140
+ { text: t('action.cancel'), style: 'cancel' },
141
+ {
142
+ text: t('action.leave'),
143
+ style: 'destructive',
144
+ onPress: () => {
145
+ try {
146
+ client?.leaveTheRoomStanza?.(jid);
147
+ } catch (error) {
148
+ console.error('Failed to leave the room:', error);
149
+ }
150
+ dispatch(deleteRoom({ jid }));
151
+ },
152
+ },
153
+ ]);
154
+ },
155
+ [client, dispatch, t]
156
+ );
157
+ const handleReportRoom = useCallback((jid: string) => setReportJid(jid), []);
158
+
124
159
  const activityOf = useCallback((chat: IRoom): number => {
125
160
  const last = chat?.messages?.[chat?.messages.length - 1];
126
161
  const fromLive = Number(last?.id);
@@ -237,6 +272,8 @@ const RoomList: React.FC<RoomListProps> = ({
237
272
  const handleScrollBeginDrag = useCallback(() => {
238
273
  hasDragged.current = true;
239
274
  clearSettleTimer();
275
+ // Scrolling folds a row's actions back, as in the messengers.
276
+ closeOpenRoomRow();
240
277
  }, []);
241
278
 
242
279
  const handleScrollEndDrag = useCallback(
@@ -276,18 +313,38 @@ const RoomList: React.FC<RoomListProps> = ({
276
313
  }, [isSearchFocused]);
277
314
 
278
315
  const renderRoom = useCallback(
279
- ({ item }: { item: IRoom }) => (
280
- <Pressable
281
- testID={`room-${(item.jid || '').split('@')[0]}`}
282
- accessibilityLabel={`room-${item.title || item.name}`}
283
- onPress={() => performClick(item)}
284
- onPressIn={handlePressIn}
285
- onPressOut={handlePressOut}
286
- >
287
- <ChatRoomItem chat={item} config={config} />
288
- </Pressable>
289
- ),
290
- [performClick, handlePressIn, handlePressOut, config]
316
+ ({ item }: { item: IRoom }) => {
317
+ const row = (
318
+ <Pressable
319
+ testID={`room-${(item.jid || '').split('@')[0]}`}
320
+ accessibilityLabel={`room-${item.title || item.name}`}
321
+ onPress={() => performClick(item)}
322
+ onPressIn={handlePressIn}
323
+ onPressOut={handlePressOut}
324
+ >
325
+ <ChatRoomItem chat={item} config={config} />
326
+ </Pressable>
327
+ );
328
+ if (!swipeActions || !item.jid) {return row;}
329
+ return (
330
+ <SwipeableRoomRow
331
+ roomJid={item.jid}
332
+ onReport={handleReportRoom}
333
+ onLeave={handleLeaveRoom}
334
+ >
335
+ {row}
336
+ </SwipeableRoomRow>
337
+ );
338
+ },
339
+ [
340
+ performClick,
341
+ handlePressIn,
342
+ handlePressOut,
343
+ config,
344
+ swipeActions,
345
+ handleReportRoom,
346
+ handleLeaveRoom,
347
+ ]
291
348
  );
292
349
 
293
350
  const toggleDrawer = () => {
@@ -418,6 +475,15 @@ const RoomList: React.FC<RoomListProps> = ({
418
475
  overlayAnimation={overlayAnimation}
419
476
  isDrawerOpen={isDrawerOpen}
420
477
  />
478
+ {/* Mounted only while a report is being written: the form
479
+ is a Modal of its own and wants the toast context. */}
480
+ {reportJid !== null && (
481
+ <ReportChatModal
482
+ visible
483
+ roomJid={reportJid}
484
+ onClose={() => setReportJid(null)}
485
+ />
486
+ )}
421
487
  </View>
422
488
  </>
423
489
  )}
@@ -30,14 +30,27 @@ import {
30
30
  shouldDismissOnDrag,
31
31
  } from '../../helpers/sheetGestures';
32
32
  import type { IConfig } from '../../types/types';
33
+ import { useT } from '../../i18n/useT';
33
34
 
34
35
  type LogoutConfig = NonNullable<IConfig['logout']>;
35
36
 
36
- const DEFAULT_LOGOUT_LABEL = 'Sign out';
37
+ /** The sign-out copy in the UI language; a host's `logout.label` etc. win. */
38
+ export interface LogoutStrings {
39
+ label: string;
40
+ message: string;
41
+ cancel: string;
42
+ }
43
+
44
+ const DEFAULT_LOGOUT_STRINGS: LogoutStrings = {
45
+ label: 'Sign out',
46
+ message: 'Are you sure you want to sign out?',
47
+ cancel: 'Cancel',
48
+ };
37
49
 
38
50
  const confirmLogout = (
39
51
  confirm: LogoutConfig['confirm'],
40
- label: string
52
+ label: string,
53
+ strings: LogoutStrings
41
54
  ): Promise<boolean> => {
42
55
  if (confirm === false) {
43
56
  return Promise.resolve(true);
@@ -46,10 +59,10 @@ const confirmLogout = (
46
59
  return new Promise<boolean>((resolve) => {
47
60
  Alert.alert(
48
61
  copy.title ?? label,
49
- copy.message ?? 'Are you sure you want to sign out?',
62
+ copy.message ?? strings.message,
50
63
  [
51
64
  {
52
- text: copy.cancelText ?? 'Cancel',
65
+ text: copy.cancelText ?? strings.cancel,
53
66
  style: 'cancel',
54
67
  onPress: () => resolve(false),
55
68
  },
@@ -66,10 +79,15 @@ const confirmLogout = (
66
79
 
67
80
  export const runLogoutFlow = async (
68
81
  logoutConfig: LogoutConfig,
69
- performLogout: () => Promise<void>
82
+ performLogout: () => Promise<void>,
83
+ strings: LogoutStrings = DEFAULT_LOGOUT_STRINGS
70
84
  ): Promise<void> => {
71
- const label = logoutConfig.label ?? DEFAULT_LOGOUT_LABEL;
72
- const confirmed = await confirmLogout(logoutConfig.confirm ?? true, label);
85
+ const label = logoutConfig.label ?? strings.label;
86
+ const confirmed = await confirmLogout(
87
+ logoutConfig.confirm ?? true,
88
+ label,
89
+ strings
90
+ );
73
91
  if (!confirmed) {
74
92
  return;
75
93
  }
@@ -112,7 +130,16 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
112
130
  const dispatch = useDispatch();
113
131
  const { config } = useChatSettingState();
114
132
  const performLogout = useLogout();
133
+ const t = useT();
115
134
  const logoutConfig = config?.logout?.enabled ? config.logout : undefined;
135
+ const logoutStrings = useMemo<LogoutStrings>(
136
+ () => ({
137
+ label: t('menu.logout'),
138
+ message: t('menu.logoutConfirm'),
139
+ cancel: t('action.cancel'),
140
+ }),
141
+ [t]
142
+ );
116
143
  const theme = useTheme();
117
144
  const primaryColor = theme.primary;
118
145
  const iconColor = theme.icon;
@@ -246,7 +273,7 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
246
273
  styles?: { color: string };
247
274
  }[] = [
248
275
  {
249
- label: 'New Chat',
276
+ label: t('menu.newChat'),
250
277
  icon: <AddNewIcon color={iconColor} />,
251
278
  onClick: () => {
252
279
  dispatch(setActiveModal(MODAL_TYPES.NEW_CHAT));
@@ -254,14 +281,14 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
254
281
  styles: { color: labelColor },
255
282
  },
256
283
  {
257
- label: 'Profile',
284
+ label: t('menu.profile'),
258
285
  icon: <ProfileIcon color={iconColor} />,
259
286
  onClick: () => {
260
287
  dispatch(setActiveModal(MODAL_TYPES.PROFILE));
261
288
  },
262
289
  },
263
290
  {
264
- label: 'Settings',
291
+ label: t('menu.settings'),
265
292
  icon: <SettingIcon color={iconColor} />,
266
293
  onClick: () => {
267
294
  dispatch(setActiveModal(MODAL_TYPES.SETTINGS));
@@ -270,16 +297,16 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
270
297
  ];
271
298
  if (logoutConfig) {
272
299
  options.push({
273
- label: logoutConfig.label ?? DEFAULT_LOGOUT_LABEL,
300
+ label: logoutConfig.label ?? logoutStrings.label,
274
301
  icon: <LogoutIcon color={iconColor} />,
275
302
  styles: { color: primaryColor },
276
303
  onClick: () => {
277
- runLogoutFlow(logoutConfig, performLogout).catch(() => {});
304
+ runLogoutFlow(logoutConfig, performLogout, logoutStrings).catch(() => {});
278
305
  },
279
306
  });
280
307
  }
281
308
  return options;
282
- }, [dispatch, logoutConfig, performLogout, primaryColor, iconColor, labelColor]);
309
+ }, [dispatch, logoutConfig, logoutStrings, performLogout, primaryColor, iconColor, labelColor, t]);
283
310
 
284
311
  return (
285
312
  // Presented through a real <Modal>: as an in-tree overlay the sheet was
@@ -2,16 +2,18 @@ import { FC, useMemo } from 'react';
2
2
  import DropdownMenu from '../DropdownMenu/DropdownMenu';
3
3
  import Button from '../styled/Button';
4
4
  import { LeaveIcon, MoreIcon, ReportIcon } from '../../assets/icons';
5
+ import { useT } from '../../i18n/useT';
5
6
 
6
7
  interface RoomMenuProps {
7
8
  handleLeaveClick: () => void;
8
9
  }
9
10
 
10
11
  export const RoomMenu: FC<RoomMenuProps> = ({ handleLeaveClick }) => {
12
+ const t = useT();
11
13
  const menuOptions = useMemo(
12
14
  () => [
13
15
  {
14
- label: 'Report',
16
+ label: t('action.report'),
15
17
  icon: <ReportIcon />,
16
18
  onClick: () => {
17
19
  console.log('Report clicked');
@@ -19,7 +21,7 @@ export const RoomMenu: FC<RoomMenuProps> = ({ handleLeaveClick }) => {
19
21
  styles: { color: 'red' },
20
22
  },
21
23
  {
22
- label: 'Leave',
24
+ label: t('action.leave'),
23
25
  icon: <LeaveIcon />,
24
26
  onClick: () => {
25
27
  handleLeaveClick();
@@ -27,7 +29,7 @@ export const RoomMenu: FC<RoomMenuProps> = ({ handleLeaveClick }) => {
27
29
  styles: { color: 'red' },
28
30
  },
29
31
  ],
30
- [handleLeaveClick]
32
+ [handleLeaveClick, t]
31
33
  );
32
34
 
33
35
  return (
@@ -644,7 +644,7 @@ const Message: React.FC<MessageProps> = ({ message, isUser, isReply }) => {
644
644
  <CustomTimestampRow media={message?.isMediafile === 'true'}>
645
645
  {!config?.disableSentLogic && isUser && isPending && (
646
646
  <Text style={[styles.timestampText, themedStyles.muted]}>
647
- sending...
647
+ {t('message.sending')}
648
648
  </Text>
649
649
  )}
650
650
  {!config?.disableSentLogic && isUser && isFailed && (
@@ -654,11 +654,11 @@ const Message: React.FC<MessageProps> = ({ message, isUser, isReply }) => {
654
654
  accessibilityRole="button"
655
655
  accessibilityLabel="Retry sending message"
656
656
  >
657
- ! Failed — tap to retry
657
+ {t('message.failedRetry')}
658
658
  </Text>
659
659
  )}
660
660
  {message?.isEdited && !message?.isDeleted && (
661
- <Text style={[styles.editedText, themedStyles.muted]}>edited</Text>
661
+ <Text style={[styles.editedText, themedStyles.muted]}>{t('message.edited')}</Text>
662
662
  )}
663
663
  {message?.unencrypted && !message?.isDeleted && (
664
664
  <Pressable
@@ -22,6 +22,7 @@ import * as Clipboard from 'expo-clipboard';
22
22
  import { useToast } from '../../context/ToastContext';
23
23
  import { useInteractionsOverlay } from './InteractionsOverlay';
24
24
  import { useTheme } from '../../hooks/useTheme';
25
+ import { useT } from '../../i18n/useT';
25
26
  import { getEmojiNativeById } from '../../helpers/emoji';
26
27
  import {
27
28
  quickReactionIds,
@@ -61,6 +62,7 @@ const MessageInteractions: React.FC<MessageInteractionsProps> = ({
61
62
  onOpenEmojiPicker,
62
63
  }) => {
63
64
  const { showToast } = useToast();
65
+ const t = useT();
64
66
  const { present, dismiss, originX, originY } = useInteractionsOverlay();
65
67
  const overlayId = useId();
66
68
  const theme = useTheme();
@@ -144,15 +146,15 @@ const MessageInteractions: React.FC<MessageInteractionsProps> = ({
144
146
  await Clipboard.setStringAsync(text);
145
147
  showToast({
146
148
  id: Date.now().toString(),
147
- title: 'Success',
148
- message: 'Copied to clipboard!',
149
+ title: t('toast.successTitle'),
150
+ message: t('toast.copied'),
149
151
  type: 'success',
150
152
  });
151
153
  } catch (err) {
152
154
  console.log(err);
153
155
  showToast({
154
156
  id: Date.now().toString(),
155
- title: 'Copy failed',
157
+ title: t('toast.copyFailed'),
156
158
  message: (err as Error)?.message || 'Unknown error',
157
159
  type: 'error',
158
160
  });
@@ -379,7 +379,7 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
379
379
  console.warn('[ethora-rn] mute toggle failed', error);
380
380
  showToast({
381
381
  id: Date.now().toString(),
382
- title: 'Error',
382
+ title: t('toast.error'),
383
383
  message: t('toast.muteFailed'),
384
384
  type: 'error',
385
385
  });
@@ -38,11 +38,14 @@ const CATEGORIES = [
38
38
  interface ReportChatModalProps {
39
39
  visible: boolean;
40
40
  onClose: () => void;
41
+ /** The room to report; the open one when absent. */
42
+ roomJid?: string;
41
43
  }
42
44
 
43
45
  const ReportChatModal: React.FC<ReportChatModalProps> = ({
44
46
  visible,
45
47
  onClose,
48
+ roomJid,
46
49
  }) => {
47
50
  const t = useT();
48
51
  const { showToast } = useToast();
@@ -66,13 +69,13 @@ const ReportChatModal: React.FC<ReportChatModalProps> = ({
66
69
  setSending(true);
67
70
  try {
68
71
  await postReportRoom({
69
- chatName: activeRoom?.jid?.split('@')[0] || '',
72
+ chatName: (roomJid || activeRoom?.jid || '').split('@')[0],
70
73
  category,
71
74
  text: details.trim() || undefined,
72
75
  });
73
76
  showToast({
74
77
  id: Date.now().toString(),
75
- title: 'Success',
78
+ title: t('toast.successTitle'),
76
79
  message: t('modal.report.chatTitle'),
77
80
  type: 'success',
78
81
  });
@@ -81,7 +84,7 @@ const ReportChatModal: React.FC<ReportChatModalProps> = ({
81
84
  console.error('Failed to report chat:', error);
82
85
  showToast({
83
86
  id: Date.now().toString(),
84
- title: 'Error',
87
+ title: t('toast.error'),
85
88
  message: t('modal.report.chatTitle'),
86
89
  type: 'error',
87
90
  });
@@ -30,6 +30,7 @@ import { PlayIcon } from '../../../assets/icons';
30
30
  import * as FileSystem from 'expo-file-system/legacy';
31
31
  import { getMediaLibrary } from '../../../helpers/mediaLibraryRuntime';
32
32
  import { useToast } from '../../../context/ToastContext';
33
+ import { useT } from '../../../i18n/useT';
33
34
  import PdfViewer from './PdfView';
34
35
  import DocumentViewer from './DocumentViewer';
35
36
  import AudioMessage from '../../styled/AudioMessage';
@@ -250,6 +251,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
250
251
  }) => {
251
252
  const dispatch = useDispatch();
252
253
  const { showToast } = useToast();
254
+ const t = useT();
253
255
  const { config } = useChatSettingState();
254
256
  const theme = useTheme();
255
257
  // Info / audio cards: light keeps its warm tint (not in the palette),
@@ -309,8 +311,8 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
309
311
  await getMediaLibrary()?.saveToLibraryAsync(download.uri);
310
312
  showToast({
311
313
  id: Date.now().toString(),
312
- title: 'Success',
313
- message: 'Save successful',
314
+ title: t('toast.successTitle'),
315
+ message: t('toast.saveSuccessful'),
314
316
  type: 'success',
315
317
  });
316
318
  } else {
@@ -394,8 +396,8 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
394
396
 
395
397
  showToast({
396
398
  id: Date.now().toString(),
397
- title: 'Success',
398
- message: 'Save successful',
399
+ title: t('toast.successTitle'),
400
+ message: t('toast.saveSuccessful'),
399
401
  type: 'success',
400
402
  });
401
403
  return;
@@ -576,7 +578,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
576
578
  <ModalContainerFullScreen>
577
579
  <ModalHeaderComponent
578
580
  handleCloseModal={closeModal}
579
- headerTitle={'File preview'}
581
+ headerTitle={t('modal.filePreview.title')}
580
582
  titleStyle={chatTextStyle(config?.typography?.profile?.screenTitle)}
581
583
  rightMenu={
582
584
  <>
@@ -16,6 +16,17 @@ interface ModalProps {
16
16
  setOpenModal: (value?: ModalType) => any;
17
17
  }
18
18
 
19
+ /** Screens reached from Settings: closing them goes back there. */
20
+ const SETTINGS_SCREENS = new Set<string>([
21
+ MODAL_TYPES.MANAGE_DATA,
22
+ MODAL_TYPES.VISIBILITY,
23
+ MODAL_TYPES.REFERRALS,
24
+ MODAL_TYPES.DOCUMENT_SHARES,
25
+ MODAL_TYPES.PROFILE_SHARES,
26
+ MODAL_TYPES.BLOCKED_USERS,
27
+ MODAL_TYPES.CHANGE_PASSWORD,
28
+ ]);
29
+
19
30
  const Modal: React.FC<ModalProps> = ({ children, modal, setOpenModal }) => {
20
31
  const dispatch = useDispatch();
21
32
  const handleCloseModal = () => setOpenModal();
@@ -29,15 +40,9 @@ const Modal: React.FC<ModalProps> = ({ children, modal, setOpenModal }) => {
29
40
 
30
41
  if (!ModalComponent) {return null;}
31
42
 
32
- const handleClose =
33
- modal === MODAL_TYPES.MANAGE_DATA ||
34
- modal === MODAL_TYPES.VISIBILITY ||
35
- modal === MODAL_TYPES.REFERRALS ||
36
- modal === MODAL_TYPES.DOCUMENT_SHARES ||
37
- modal === MODAL_TYPES.PROFILE_SHARES ||
38
- modal === MODAL_TYPES.BLOCKED_USERS
39
- ? handleBackButtonClick
40
- : handleCloseModal;
43
+ const handleClose = SETTINGS_SCREENS.has(modal)
44
+ ? handleBackButtonClick
45
+ : handleCloseModal;
41
46
 
42
47
  return <ModalComponent handleCloseModal={handleClose} />;
43
48
  };
@@ -75,15 +80,38 @@ const Modal: React.FC<ModalProps> = ({ children, modal, setOpenModal }) => {
75
80
  }
76
81
  }
77
82
 
83
+ // Screens under Settings are pages too: they slide in over Settings and
84
+ // a swipe from the left edge takes them back to it, same as the header's
85
+ // back button. Settings stays mounted underneath the whole time, so the
86
+ // slide reveals it - not the chat list - and nothing is rebuilt once the
87
+ // page has gone. The elements below keep the same shape as the plain
88
+ // branch, so Settings is the same instance before, during and after.
89
+ const subScreen = modal && SETTINGS_SCREENS.has(modal) ? modal : undefined;
90
+ const SubScreenComponent = subScreen ? MODAL_COMPONENTS[subScreen] : undefined;
91
+ const SettingsComponent = MODAL_COMPONENTS[MODAL_TYPES.SETTINGS];
92
+ const underneath =
93
+ SubScreenComponent && SettingsComponent ? (
94
+ <SettingsComponent handleCloseModal={handleCloseModal} />
95
+ ) : (
96
+ renderModalContent()
97
+ );
98
+
78
99
  return (
79
100
  modal && (
80
- <ModalBackground
81
- id="modal-background"
82
- style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}
83
- >
84
- {renderModalContent()}
85
- {children}
86
- </ModalBackground>
101
+ <>
102
+ <ModalBackground
103
+ id="modal-background"
104
+ style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}
105
+ >
106
+ {underneath}
107
+ {children}
108
+ </ModalBackground>
109
+ {SubScreenComponent && (
110
+ <SwipeBackLayer onClose={handleBackButtonClick}>
111
+ {(close) => <SubScreenComponent handleCloseModal={close} />}
112
+ </SwipeBackLayer>
113
+ )}
114
+ </>
87
115
  )
88
116
  );
89
117
  };
@@ -187,16 +187,16 @@ const NewChatModal: React.FC<NewChatModalProps> = ({
187
187
 
188
188
  showToast({
189
189
  id: Date.now().toString(),
190
- title: 'Success',
191
- message: 'Room created successfully',
190
+ title: t('toast.successTitle'),
191
+ message: t('toast.roomCreatedSuccess'),
192
192
  type: 'success',
193
193
  });
194
194
  } catch (error) {
195
195
  console.error('Error handling room creation:', error);
196
196
  showToast({
197
197
  id: Date.now().toString(),
198
- title: 'Error',
199
- message: 'Failed to create room',
198
+ title: t('toast.error'),
199
+ message: t('toast.failedToCreateRoom'),
200
200
  type: 'error',
201
201
  });
202
202
  }
@@ -258,8 +258,8 @@ const NewChatModal: React.FC<NewChatModalProps> = ({
258
258
 
259
259
  showToast({
260
260
  id: Date.now().toString(),
261
- title: 'Success',
262
- message: 'Room created successfully',
261
+ title: t('toast.successTitle'),
262
+ message: t('toast.roomCreatedSuccess'),
263
263
  type: 'success',
264
264
  });
265
265
  handleCloseModal();
@@ -267,8 +267,8 @@ const NewChatModal: React.FC<NewChatModalProps> = ({
267
267
  console.error('Failed to create room:', error);
268
268
  showToast({
269
269
  id: Date.now().toString(),
270
- title: 'Error',
271
- message: 'Failed to create room',
270
+ title: t('toast.error'),
271
+ message: t('toast.failedToCreateRoom'),
272
272
  type: 'error',
273
273
  });
274
274
  }