@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
@@ -0,0 +1,144 @@
1
+ import React, { useCallback, useMemo } from 'react';
2
+ import { StyleProp, StyleSheet, Text, TouchableOpacity, View, ViewStyle } from 'react-native';
3
+ import { useTheme } from '../../../hooks/useTheme';
4
+ import { useT } from '../../../i18n/useT';
5
+ import { useChatSettingState } from '../../../hooks/useChatSettingState';
6
+ import {
7
+ appLanguageChoices,
8
+ chatLanguageChoices,
9
+ setAppLanguage,
10
+ setChatLanguage,
11
+ type LanguageChoice,
12
+ } from '../../../services/languageSettings';
13
+
14
+ interface LanguageCardProps {
15
+ style?: StyleProp<ViewStyle>;
16
+ testID?: string;
17
+ }
18
+
19
+ /**
20
+ * The user's two languages: the interface, and what messages are translated
21
+ * into. See services/languageSettings for what each one does and where it
22
+ * is kept.
23
+ */
24
+ const LanguageCard: React.FC<LanguageCardProps> = ({ style, testID }) => {
25
+ const theme = useTheme();
26
+ const t = useT();
27
+ const { config, langSource, uiLocale } = useChatSettingState();
28
+
29
+ const appChoices = useMemo(() => appLanguageChoices(config), [config]);
30
+ const chatChoices = useMemo(() => chatLanguageChoices(config), [config]);
31
+ const appCurrent = uiLocale || config?.i18n?.locale || '';
32
+ // Unset follows the interface language - the backend's own rule.
33
+ const chatCurrent = langSource || appCurrent;
34
+
35
+ const pick = useCallback(
36
+ (kind: 'app' | 'chat', id: string) => {
37
+ void (kind === 'app' ? setAppLanguage(id) : setChatLanguage(id));
38
+ },
39
+ []
40
+ );
41
+
42
+ const same = (a: string, b: string) => a.toLowerCase() === b.toLowerCase();
43
+
44
+ const group = (
45
+ kind: 'app' | 'chat',
46
+ label: string,
47
+ hint: string,
48
+ choices: LanguageChoice[],
49
+ current: string
50
+ ) => (
51
+ <View style={styles.group}>
52
+ <Text style={[styles.label, { color: theme.text }]}>{label}</Text>
53
+ <Text style={[styles.hint, { color: theme.textSecondary }]}>{hint}</Text>
54
+ <View style={styles.pills} accessibilityRole="radiogroup">
55
+ {choices.map((choice) => {
56
+ const selected = same(choice.id, current);
57
+ return (
58
+ <TouchableOpacity
59
+ key={choice.id}
60
+ testID={`settings-language-${kind}-${choice.id}`}
61
+ accessibilityRole="radio"
62
+ accessibilityState={{ selected }}
63
+ activeOpacity={0.7}
64
+ onPress={() => pick(kind, choice.id)}
65
+ style={[
66
+ styles.pill,
67
+ {
68
+ borderColor: selected ? theme.primary : theme.border,
69
+ backgroundColor: selected ? theme.primary : theme.surfaceSecondary,
70
+ },
71
+ ]}
72
+ >
73
+ <Text
74
+ style={[
75
+ styles.pillLabel,
76
+ { color: selected ? theme.textOnPrimary : theme.text },
77
+ ]}
78
+ >
79
+ {choice.name}
80
+ </Text>
81
+ </TouchableOpacity>
82
+ );
83
+ })}
84
+ </View>
85
+ </View>
86
+ );
87
+
88
+ return (
89
+ <View style={[style, styles.card]} testID={testID || 'settings-languages'}>
90
+ {group(
91
+ 'app',
92
+ t('settings.language.app'),
93
+ t('settings.language.appHint'),
94
+ appChoices,
95
+ appCurrent
96
+ )}
97
+ {group(
98
+ 'chat',
99
+ t('settings.language.chat'),
100
+ t('settings.language.chatHint'),
101
+ chatChoices,
102
+ chatCurrent
103
+ )}
104
+ </View>
105
+ );
106
+ };
107
+
108
+ export default LanguageCard;
109
+
110
+ const styles = StyleSheet.create({
111
+ // Applied over the host's card style: its surface, this layout.
112
+ card: {
113
+ flexDirection: 'column',
114
+ alignItems: 'stretch',
115
+ gap: 18,
116
+ },
117
+ group: {
118
+ gap: 6,
119
+ },
120
+ label: {
121
+ fontSize: 17,
122
+ fontWeight: '500',
123
+ },
124
+ hint: {
125
+ fontSize: 13,
126
+ lineHeight: 18,
127
+ },
128
+ pills: {
129
+ flexDirection: 'row',
130
+ flexWrap: 'wrap',
131
+ gap: 8,
132
+ marginTop: 6,
133
+ },
134
+ pill: {
135
+ paddingHorizontal: 14,
136
+ paddingVertical: 8,
137
+ borderRadius: 999,
138
+ borderWidth: 1,
139
+ },
140
+ pillLabel: {
141
+ fontSize: 14,
142
+ fontWeight: '600',
143
+ },
144
+ });
@@ -1,7 +1,7 @@
1
1
  /** @format */
2
2
 
3
- import EncryptionCard from '../../MainComponents/EncryptionCard';
4
3
  import React, { useCallback, useMemo } from 'react';
4
+ import LanguageCard from './LanguageCard';
5
5
  import {
6
6
  StyleSheet,
7
7
  Switch,
@@ -64,12 +64,17 @@ const UserSettingsModal: React.FC<UserSettingsModalProps> = ({
64
64
  [t]
65
65
  );
66
66
 
67
+ const showPassword = config?.settings?.changePassword === true;
67
68
  const options = useMemo(
68
69
  () => [
69
70
  { label: t('settings.manageData.title'), key: MODAL_TYPES.MANAGE_DATA },
70
71
  { label: t('settings.visibility.title'), key: MODAL_TYPES.VISIBILITY },
72
+ // Off unless the host turned it on: not every backend has the endpoint.
73
+ ...(showPassword
74
+ ? [{ label: t('settings.password.title'), key: MODAL_TYPES.CHANGE_PASSWORD }]
75
+ : []),
71
76
  ],
72
- [t]
77
+ [t, showPassword]
73
78
  );
74
79
 
75
80
  const handleClick = useCallback(
@@ -160,8 +165,10 @@ const UserSettingsModal: React.FC<UserSettingsModalProps> = ({
160
165
  </View>
161
166
  ) : null}
162
167
 
163
- {config?.e2ee?.enabled === true ? (
164
- <EncryptionCard style={styles.card} testID="settings-e2ee" />
168
+ {/* The interface language and the translation language; off
169
+ unless the host turned it on, as it writes to the profile. */}
170
+ {config?.settings?.languages?.enabled === true ? (
171
+ <LanguageCard style={styles.card} />
165
172
  ) : null}
166
173
 
167
174
  {options.map((option) => (
@@ -0,0 +1,133 @@
1
+ import React, { useCallback, useMemo, useRef } from 'react';
2
+ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3
+ import ReanimatedSwipeable, {
4
+ type SwipeableMethods,
5
+ } from 'react-native-gesture-handler/ReanimatedSwipeable';
6
+ import { LeaveIcon, ReportIcon } from '../../assets/icons';
7
+ import { useTheme } from '../../hooks/useTheme';
8
+ import { useT } from '../../i18n/useT';
9
+ import type { ChatTheme } from '../../theme/theme';
10
+
11
+ /** Width of one action button behind the row. */
12
+ export const ROOM_ACTION_WIDTH = 84;
13
+
14
+ /**
15
+ * Only one row shows its actions at a time, as in the messengers: opening
16
+ * another one, or the list being scrolled, folds the open one back.
17
+ */
18
+ let openRow: SwipeableMethods | null = null;
19
+
20
+ export const closeOpenRoomRow = (): void => {
21
+ openRow?.close();
22
+ openRow = null;
23
+ };
24
+
25
+ interface SwipeableRoomRowProps {
26
+ /** Identifies the row to the host's callbacks. */
27
+ roomJid: string;
28
+ onReport: (roomJid: string) => void;
29
+ onLeave: (roomJid: string) => void;
30
+ children: React.ReactNode;
31
+ }
32
+
33
+ /**
34
+ * A room-list row that slides left to show Report and Leave behind it,
35
+ * the way chats do in the big messengers. A tap on an action folds the
36
+ * row back first, so the list is tidy under whatever the action opens.
37
+ */
38
+ const SwipeableRoomRow: React.FC<SwipeableRoomRowProps> = ({
39
+ roomJid,
40
+ onReport,
41
+ onLeave,
42
+ children,
43
+ }) => {
44
+ const theme = useTheme();
45
+ const t = useT();
46
+ const styles = useMemo(() => createStyles(theme), [theme]);
47
+ const ref = useRef<SwipeableMethods>(null);
48
+
49
+ const onWillOpen = useCallback(() => {
50
+ if (openRow && openRow !== ref.current) {openRow.close();}
51
+ openRow = ref.current;
52
+ }, []);
53
+ const onClose = useCallback(() => {
54
+ if (openRow === ref.current) {openRow = null;}
55
+ }, []);
56
+
57
+ const act = useCallback(
58
+ (what: (jid: string) => void) => {
59
+ ref.current?.close();
60
+ what(roomJid);
61
+ },
62
+ [roomJid]
63
+ );
64
+
65
+ const renderRightActions = useCallback(
66
+ () => (
67
+ <View style={styles.actions}>
68
+ <TouchableOpacity
69
+ testID={`room-action-report-${roomJid.split('@')[0]}`}
70
+ accessibilityRole="button"
71
+ accessibilityLabel={t('action.report')}
72
+ activeOpacity={0.8}
73
+ style={[styles.action, { backgroundColor: theme.textSecondary }]}
74
+ onPress={() => act(onReport)}
75
+ >
76
+ <ReportIcon color={theme.textOnPrimary} width={22} height={22} />
77
+ <Text style={styles.label}>{t('action.report')}</Text>
78
+ </TouchableOpacity>
79
+ <TouchableOpacity
80
+ testID={`room-action-leave-${roomJid.split('@')[0]}`}
81
+ accessibilityRole="button"
82
+ accessibilityLabel={t('action.leave')}
83
+ activeOpacity={0.8}
84
+ style={[styles.action, { backgroundColor: theme.danger }]}
85
+ onPress={() => act(onLeave)}
86
+ >
87
+ <LeaveIcon color={theme.textOnPrimary} width={22} height={22} />
88
+ <Text style={styles.label}>{t('action.leave')}</Text>
89
+ </TouchableOpacity>
90
+ </View>
91
+ ),
92
+ [act, onLeave, onReport, roomJid, styles, t, theme]
93
+ );
94
+
95
+ return (
96
+ <ReanimatedSwipeable
97
+ ref={ref}
98
+ testID={`room-swipe-${roomJid.split('@')[0]}`}
99
+ friction={2}
100
+ rightThreshold={ROOM_ACTION_WIDTH / 2}
101
+ overshootRight={false}
102
+ enableTrackpadTwoFingerGesture
103
+ renderRightActions={renderRightActions}
104
+ onSwipeableWillOpen={onWillOpen}
105
+ onSwipeableClose={onClose}
106
+ // The row must cover the actions until it moves.
107
+ childrenContainerStyle={{ backgroundColor: theme.listBackground }}
108
+ >
109
+ {children}
110
+ </ReanimatedSwipeable>
111
+ );
112
+ };
113
+
114
+ export default SwipeableRoomRow;
115
+
116
+ const createStyles = (theme: ChatTheme) =>
117
+ StyleSheet.create({
118
+ actions: {
119
+ flexDirection: 'row',
120
+ width: ROOM_ACTION_WIDTH * 2,
121
+ },
122
+ action: {
123
+ width: ROOM_ACTION_WIDTH,
124
+ alignItems: 'center',
125
+ justifyContent: 'center',
126
+ gap: 4,
127
+ },
128
+ label: {
129
+ color: theme.textOnPrimary,
130
+ fontSize: 12,
131
+ fontWeight: '600',
132
+ },
133
+ });
@@ -29,6 +29,7 @@ import { useSendMessage } from '../../hooks/useSendMessage';
29
29
  import { createMainMessageForThread } from '../../helpers/createMainMessageForThread';
30
30
  import { useChatSettingState } from '../../hooks/useChatSettingState';
31
31
  import { useTheme } from '../../hooks/useTheme';
32
+ import { useT } from '../../i18n/useT';
32
33
  import CustomTypingIndicator from '../styled/StyledInputComponents/CustomTypingIndicator';
33
34
  import { KeyboardInputDock } from '../MainComponents/KeyboardInputDock';
34
35
  import {
@@ -69,6 +70,7 @@ const ThreadWrapper: FC<ThreadWrapperProps> = ({
69
70
  const reduxStore = useStore<RootState>();
70
71
  const { config } = useChatSettingState();
71
72
  const theme = useTheme();
73
+ const t = useT();
72
74
  const insets = useSafeAreaInsets();
73
75
  const { width: windowWidth } = useWindowDimensions();
74
76
  const roomJid = activeMessage.roomJid;
@@ -217,7 +219,7 @@ const ThreadWrapper: FC<ThreadWrapperProps> = ({
217
219
  style={[styles.fill, { backgroundColor: theme.surface }]}
218
220
  >
219
221
  <View style={[styles.fill, { backgroundColor: theme.chatBackground }]}>
220
- <ModalHeaderComponent headerTitle="Thread" handleCloseModal={close} />
222
+ <ModalHeaderComponent headerTitle={t('thread.title')} handleCloseModal={close} />
221
223
  <MessageList
222
224
  loadMoreMessages={loadMoreMessages}
223
225
  CustomMessage={CustomMessageComponent}
@@ -267,7 +269,7 @@ const ThreadWrapper: FC<ThreadWrapperProps> = ({
267
269
  numberOfLines={1}
268
270
  style={[styles.alsoText, { color: theme.textSecondary }]}
269
271
  >
270
- Also send to{' '}
272
+ {t('thread.alsoSendTo')}{' '}
271
273
  <Text style={{ color: theme.primary, fontWeight: '600' }}>
272
274
  {roomDisplayName}
273
275
  </Text>
@@ -39,7 +39,12 @@ import { getRooms as prefetchRoomsViaRest } from '../networking/api-requests/roo
39
39
  import { allRoomPresences } from '../networking/xmpp/allRoomPresences.xmpp';
40
40
  import { pushSubscriptionService } from '../services/pushSubscriptionService';
41
41
  import { store } from '../roomStore';
42
- import { logout, setStoreClient, setConfig } from '../roomStore/chatSettingsSlice';
42
+ import {
43
+ logout,
44
+ setStoreClient,
45
+ setConfig,
46
+ setConnectionState,
47
+ } from '../roomStore/chatSettingsSlice';
43
48
  import { logoutService } from '../hooks/useLogout';
44
49
  import {
45
50
  setLogoutState,
@@ -260,7 +265,12 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
260
265
  created.setOnOnline(() => {
261
266
  const underlying = (created as any).client;
262
267
  if (!underlying) {return;}
263
- allRoomPresences(underlying)
268
+ // The headers say "Updating…" until the rooms are joined again and
269
+ // the archive caught up - a stream that is up is not yet a chat
270
+ // that is. Same tick as the client's own 'online', so the title
271
+ // never shows the plain state in between.
272
+ store.dispatch(setConnectionState('syncing'));
273
+ const rejoin = allRoomPresences(underlying)
264
274
  .catch((e) =>
265
275
  devPushLog('warn', 'reconnect: allRoomPresences re-join failed', e)
266
276
  )
@@ -268,7 +278,7 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
268
278
  // Also refresh the private store so unread / lastViewed markers
269
279
  // are accurate after a long reconnect — the MUC re-join above only
270
280
  // restores delivery, not unread state. Idempotent on first connect.
271
- created
281
+ const markers = created
272
282
  .getChatsPrivateStoreRequestStanza()
273
283
  .catch((e: unknown) =>
274
284
  devPushLog('warn', 'reconnect: privateStore refresh failed', e)
@@ -283,9 +293,16 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
283
293
  // point, so this is a no-op until the bootstrap's own history load
284
294
  // populates it. Customer-reported #32.
285
295
  const rooms = store.getState().rooms?.rooms || {};
286
- updateMessagesTillLast(rooms, created).catch((e: unknown) =>
296
+ const catchUp = updateMessagesTillLast(rooms, created).catch((e: unknown) =>
287
297
  devPushLog('warn', 'reconnect: offline catch-up sync failed', e)
288
298
  );
299
+ Promise.allSettled([rejoin, markers, catchUp]).then(() => {
300
+ // Only if this stream is still the live one: a drop meanwhile
301
+ // has already put the headers back to "Connecting…".
302
+ if (created.status === 'online') {
303
+ store.dispatch(setConnectionState('online'));
304
+ }
305
+ });
289
306
  });
290
307
 
291
308
  setClient(created);
@@ -48,7 +48,7 @@ function placeholder(error: E2eeError): Element {
48
48
  return new Element('body', {}).t(
49
49
  translateKey(
50
50
  placeholderKey(error),
51
- settings?.config?.i18n?.locale || settings?.langSource,
51
+ settings?.uiLocale || settings?.config?.i18n?.locale || settings?.langSource,
52
52
  settings?.config?.i18n?.strings
53
53
  )
54
54
  );
@@ -14,6 +14,7 @@ import ManageDataModal from '../../components/Modals/SettingsModals/ManageDataMo
14
14
  import ProfileSharesModal from '../../components/Modals/SettingsModals/ProfileShares/ProfileShares';
15
15
  import ReferralsModal from '../../components/Modals/SettingsModals/Referrals/Referrals';
16
16
  import VisibilityModal from '../../components/Modals/SettingsModals/Visibility/VisibilityModal';
17
+ import ChangePasswordModal from '../../components/Modals/SettingsModals/ChangePassword/ChangePasswordModal';
17
18
  import UserProfileModal from '../../components/Modals/UserProfileModal/UserProfileModal';
18
19
  import UserSettingsModal from '../../components/Modals/UserSettingsModal/UserSettingsModal';
19
20
 
@@ -27,6 +28,7 @@ export const MODAL_COMPONENTS: Record<
27
28
  [MODAL_TYPES.CHAT_PROFILE]: ChatProfileModal,
28
29
  [MODAL_TYPES.MANAGE_DATA]: ManageDataModal,
29
30
  [MODAL_TYPES.VISIBILITY]: VisibilityModal,
31
+ [MODAL_TYPES.CHANGE_PASSWORD]: ChangePasswordModal,
30
32
  [MODAL_TYPES.REFERRALS]: ReferralsModal,
31
33
  [MODAL_TYPES.DOCUMENT_SHARES]: DocumentSharesModal,
32
34
  [MODAL_TYPES.PROFILE_SHARES]: ProfileSharesModal,
@@ -15,6 +15,7 @@ export const MODAL_TYPES = {
15
15
  CHAT_PROFILE: 'chatprofile',
16
16
  MANAGE_DATA: 'managedata',
17
17
  VISIBILITY: 'visibility',
18
+ CHANGE_PASSWORD: 'change_password',
18
19
 
19
20
  PROFILE_SHARES: 'profile_shares',
20
21
  DOCUMENT_SHARES: 'document_shares',
@@ -9,6 +9,10 @@ const KEY = '@ethora/preferences';
9
9
  export interface StoredPreferences {
10
10
  theme?: ThemePreference;
11
11
  pushEnabled?: boolean;
12
+ /** Interface language picked in Settings (BCP-47). */
13
+ uiLocale?: string;
14
+ /** Language messages are translated into, picked in Settings (BCP-47). */
15
+ chatLanguage?: string;
12
16
  }
13
17
 
14
18
  export const loadPreferences = async (): Promise<StoredPreferences> => {
@@ -9,6 +9,12 @@ export const useChatSettingState = () => {
9
9
  const activeModal = useSelector(
10
10
  (state: RootState) => state.chatSettingStore.activeModal
11
11
  );
12
+ const uiLocale = useSelector(
13
+ (state: RootState) => state.chatSettingStore.uiLocale
14
+ );
15
+ const connection = useSelector(
16
+ (state: RootState) => state.chatSettingStore.connection
17
+ );
12
18
  const config = useSelector(
13
19
  (state: RootState) => state.chatSettingStore.config
14
20
  );
@@ -29,6 +35,8 @@ export const useChatSettingState = () => {
29
35
  );
30
36
 
31
37
  return {
38
+ uiLocale,
39
+ connection,
32
40
  user,
33
41
  activeFile,
34
42
  activeModal,
@@ -12,6 +12,7 @@ import { initRoomsPresence } from '../helpers/initRoomsPresence';
12
12
  import { updateMessagesTillLast } from '../helpers/updateMessagesTillLast';
13
13
  import { refreshAuthTokensQuietly } from '../networking/authRefresh';
14
14
  import { setLangSource, setConfig } from '../roomStore/chatSettingsSlice';
15
+ import { applyProfileLanguages } from '../services/languageSettings';
15
16
  import { setIsLoading } from '../roomStore/roomsSlice';
16
17
  import { useRoomState } from './useRoomState';
17
18
  import { useChatSettingState } from './useChatSettingState';
@@ -120,6 +121,9 @@ const useChatWrapperInit = ({
120
121
  }
121
122
 
122
123
  dispatch(setConfig(config));
124
+ // The profile's languages are the account's truth, over what the
125
+ // device remembered.
126
+ applyProfileLanguages(user as any, config);
123
127
  try {
124
128
  if (!user.xmppUsername) {
125
129
  setShowModal(true);
@@ -0,0 +1,16 @@
1
+ import { useChatSettingState } from './useChatSettingState';
2
+ import { useT } from '../i18n/useT';
3
+
4
+ /**
5
+ * What the headers say in place of their usual title while the session is
6
+ * not usable yet: "Connecting…" until the stream is up (first connect and
7
+ * every reconnect), "Updating…" while rooms are re-joined and the archive
8
+ * caught up. Nothing once the chat is live.
9
+ */
10
+ export const useConnectionLabel = (): string | undefined => {
11
+ const { connection } = useChatSettingState();
12
+ const t = useT();
13
+ if (connection === 'connecting') {return t('connection.connecting');}
14
+ if (connection === 'syncing') {return t('connection.updating');}
15
+ return undefined;
16
+ };