@ethora/chat-component-rn 26.5.3 → 26.5.5
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.
- package/CHANGELOG.md +28 -0
- package/README.md +25 -4
- package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js +1 -7
- package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatWrapper.js +56 -4
- package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/MessageInteractions.js +1 -38
- package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +20 -28
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageEmoji.js +9 -6
- package/lib/commonjs/components/RoomComponents/styled/LastMessageEmoji.js.map +1 -1
- package/lib/commonjs/components/UsersList/Checkbox.js +68 -0
- package/lib/commonjs/components/UsersList/Checkbox.js.map +1 -0
- package/lib/commonjs/components/UsersList/StyledComponents.js +1 -7
- package/lib/commonjs/components/UsersList/StyledComponents.js.map +1 -1
- package/lib/commonjs/components/UsersList/UsersList.js +3 -1
- package/lib/commonjs/components/UsersList/UsersList.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +14 -0
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/module/components/ContextMenu/ContextMenuComponents.js +0 -6
- package/lib/module/components/ContextMenu/ContextMenuComponents.js.map +1 -1
- package/lib/module/components/MainComponents/ChatWrapper.js +58 -6
- package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/module/components/MessageBubble/MessageInteractions.js +3 -40
- package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +21 -29
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessageEmoji.js +9 -6
- package/lib/module/components/RoomComponents/styled/LastMessageEmoji.js.map +1 -1
- package/lib/module/components/UsersList/Checkbox.js +63 -0
- package/lib/module/components/UsersList/Checkbox.js.map +1 -0
- package/lib/module/components/UsersList/StyledComponents.js +0 -6
- package/lib/module/components/UsersList/StyledComponents.js.map +1 -1
- package/lib/module/components/UsersList/UsersList.js +2 -1
- package/lib/module/components/UsersList/UsersList.js.map +1 -1
- package/lib/module/context/xmppProvider.js +14 -0
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/typescript/components/ContextMenu/ContextMenuComponents.d.ts +0 -1
- package/lib/typescript/components/ContextMenu/ContextMenuComponents.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageEmoji.d.ts +8 -0
- package/lib/typescript/components/RoomComponents/styled/LastMessageEmoji.d.ts.map +1 -1
- package/lib/typescript/components/UsersList/Checkbox.d.ts +16 -0
- package/lib/typescript/components/UsersList/Checkbox.d.ts.map +1 -0
- package/lib/typescript/components/UsersList/StyledComponents.d.ts +0 -1
- package/lib/typescript/components/UsersList/StyledComponents.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/metro.js +17 -57
- package/package.json +1 -1
- package/src/components/ContextMenu/ContextMenuComponents.tsx +0 -7
- package/src/components/MainComponents/ChatWrapper.tsx +56 -4
- package/src/components/MessageBubble/MessageInteractions.tsx +2 -105
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +20 -41
- package/src/components/RoomComponents/styled/LastMessageEmoji.tsx +9 -7
- package/src/components/UsersList/Checkbox.tsx +68 -0
- package/src/components/UsersList/StyledComponents.ts +0 -8
- package/src/components/UsersList/UsersList.tsx +1 -1
- package/src/context/xmppProvider.tsx +14 -0
- package/lib/commonjs/components/InputComponents/AudioInput.js +0 -23
- package/lib/commonjs/components/InputComponents/AudioInput.js.map +0 -1
- package/lib/commonjs/components/InputComponents/AudioRecorder.js +0 -101
- package/lib/commonjs/components/InputComponents/AudioRecorder.js.map +0 -1
- package/lib/commonjs/components/InputComponents/MediaInput.js +0 -98
- package/lib/commonjs/components/InputComponents/MediaInput.js.map +0 -1
- package/lib/commonjs/components/InputComponents/RecordingIndicator.js +0 -70
- package/lib/commonjs/components/InputComponents/RecordingIndicator.js.map +0 -1
- package/lib/commonjs/components/Modals/ModalSelectMedia/ModalSelectMedia.js +0 -207
- package/lib/commonjs/components/Modals/ModalSelectMedia/ModalSelectMedia.js.map +0 -1
- package/lib/commonjs/components/OperationalModal/OperationalModal.js +0 -83
- package/lib/commonjs/components/OperationalModal/OperationalModal.js.map +0 -1
- package/lib/module/components/InputComponents/AudioInput.js +0 -18
- package/lib/module/components/InputComponents/AudioInput.js.map +0 -1
- package/lib/module/components/InputComponents/AudioRecorder.js +0 -95
- package/lib/module/components/InputComponents/AudioRecorder.js.map +0 -1
- package/lib/module/components/InputComponents/MediaInput.js +0 -92
- package/lib/module/components/InputComponents/MediaInput.js.map +0 -1
- package/lib/module/components/InputComponents/RecordingIndicator.js +0 -65
- package/lib/module/components/InputComponents/RecordingIndicator.js.map +0 -1
- package/lib/module/components/Modals/ModalSelectMedia/ModalSelectMedia.js +0 -200
- package/lib/module/components/Modals/ModalSelectMedia/ModalSelectMedia.js.map +0 -1
- package/lib/module/components/OperationalModal/OperationalModal.js +0 -77
- package/lib/module/components/OperationalModal/OperationalModal.js.map +0 -1
- package/lib/typescript/components/InputComponents/AudioInput.d.ts +0 -9
- package/lib/typescript/components/InputComponents/AudioInput.d.ts.map +0 -1
- package/lib/typescript/components/InputComponents/AudioRecorder.d.ts +0 -9
- package/lib/typescript/components/InputComponents/AudioRecorder.d.ts.map +0 -1
- package/lib/typescript/components/InputComponents/MediaInput.d.ts +0 -16
- package/lib/typescript/components/InputComponents/MediaInput.d.ts.map +0 -1
- package/lib/typescript/components/InputComponents/RecordingIndicator.d.ts +0 -4
- package/lib/typescript/components/InputComponents/RecordingIndicator.d.ts.map +0 -1
- package/lib/typescript/components/Modals/ModalSelectMedia/ModalSelectMedia.d.ts +0 -9
- package/lib/typescript/components/Modals/ModalSelectMedia/ModalSelectMedia.d.ts.map +0 -1
- package/lib/typescript/components/OperationalModal/OperationalModal.d.ts +0 -9
- package/lib/typescript/components/OperationalModal/OperationalModal.d.ts.map +0 -1
- package/src/components/InputComponents/AudioInput.tsx +0 -24
- package/src/components/InputComponents/AudioRecorder.tsx +0 -100
- package/src/components/InputComponents/MediaInput.tsx +0 -113
- package/src/components/InputComponents/RecordingIndicator.tsx +0 -77
- package/src/components/Modals/ModalSelectMedia/ModalSelectMedia.tsx +0 -283
- package/src/components/OperationalModal/OperationalModal.tsx +0 -78
package/package.json
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Picker } from 'emoji-mart';
|
|
2
1
|
import styled from 'styled-components/native';
|
|
3
2
|
|
|
4
3
|
export const ContainerInteractions = styled.View`
|
|
@@ -39,12 +38,6 @@ export const ArrowButton = styled.View<{ isRotated: boolean }>`
|
|
|
39
38
|
isRotated ? 'rotate(180deg)' : 'rotate(0deg)'};
|
|
40
39
|
`;
|
|
41
40
|
|
|
42
|
-
export const StyledPicker = styled(Picker)`
|
|
43
|
-
.emoji-mart-preview {
|
|
44
|
-
display: none;
|
|
45
|
-
}
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
41
|
export const ContextMenu = styled.View`
|
|
49
42
|
position: absolute;
|
|
50
43
|
z-index: 1000;
|
|
@@ -28,7 +28,7 @@ import Modal from '../Modals/Modal/Modal';
|
|
|
28
28
|
import ThreadWrapper from '../Thread/ThreadWrapper';
|
|
29
29
|
import {ModalWrapper} from '../Modals/ModalWrapper/ModalWrapper';
|
|
30
30
|
import {useChatSettingState} from '../../hooks/useChatSettingState';
|
|
31
|
-
import {Text} from 'react-native';
|
|
31
|
+
import {DeviceEventEmitter, Pressable, Text, View} from 'react-native';
|
|
32
32
|
import {pushLog as devPushLog} from '../../utils/devLogger';
|
|
33
33
|
|
|
34
34
|
interface ChatWrapperProps {
|
|
@@ -57,6 +57,7 @@ const ChatWrapper: FC<ChatWrapperProps> = ({
|
|
|
57
57
|
|
|
58
58
|
const [isInited, setInited] = useState(false);
|
|
59
59
|
const [showModal, setShowModal] = useState(false);
|
|
60
|
+
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
|
60
61
|
// const [isModalDeleteOpen, setIsModalDeleteOpen] = useState(false);
|
|
61
62
|
|
|
62
63
|
const [isChatVisible, setIsChatVisible] = useState(false);
|
|
@@ -142,6 +143,10 @@ const ChatWrapper: FC<ChatWrapperProps> = ({
|
|
|
142
143
|
if (config?.initBeforeLoad && initMode === 'provider') {
|
|
143
144
|
if (providerBootstrapStatus === 'failed') {
|
|
144
145
|
devPushLog('error', 'ChatWrapper: bootstrap failed');
|
|
146
|
+
setErrorMsg(
|
|
147
|
+
'Could not authenticate against the server.\n' +
|
|
148
|
+
'Check baseUrl, XMPP host fields, and the token / credentials you entered.'
|
|
149
|
+
);
|
|
145
150
|
setShowModal(true);
|
|
146
151
|
setInited(false);
|
|
147
152
|
return;
|
|
@@ -167,12 +172,17 @@ const ChatWrapper: FC<ChatWrapperProps> = ({
|
|
|
167
172
|
return;
|
|
168
173
|
}
|
|
169
174
|
devPushLog('error', 'ChatWrapper: no user (legacy login path)');
|
|
175
|
+
setErrorMsg(
|
|
176
|
+
'No authenticated user available.\n' +
|
|
177
|
+
'For JWT login pass `jwtLogin.token`; for email login pass the `user={{email,password}}` prop and `customAppToken`.'
|
|
178
|
+
);
|
|
170
179
|
setShowModal(true);
|
|
171
180
|
return;
|
|
172
181
|
}
|
|
173
182
|
|
|
174
183
|
// We have a user. Modal should be down.
|
|
175
184
|
setShowModal(false);
|
|
185
|
+
setErrorMsg(null);
|
|
176
186
|
|
|
177
187
|
if (!client && !storedClient) {
|
|
178
188
|
devPushLog('rn', 'ChatWrapper: initing xmpp client (legacy path)');
|
|
@@ -220,6 +230,11 @@ const ChatWrapper: FC<ChatWrapperProps> = ({
|
|
|
220
230
|
dispatch(setIsLoading({loading: false}));
|
|
221
231
|
} catch (error) {
|
|
222
232
|
devPushLog('error', 'ChatWrapper: init failed', error);
|
|
233
|
+
const msg =
|
|
234
|
+
(error as any)?.response?.data?.message ||
|
|
235
|
+
(error as any)?.message ||
|
|
236
|
+
String(error);
|
|
237
|
+
setErrorMsg(`Init failed: ${msg}`);
|
|
223
238
|
setShowModal(true);
|
|
224
239
|
setInited(false);
|
|
225
240
|
dispatch(setIsLoading({loading: false}));
|
|
@@ -257,9 +272,46 @@ const ChatWrapper: FC<ChatWrapperProps> = ({
|
|
|
257
272
|
<>
|
|
258
273
|
{showModal && (
|
|
259
274
|
<Overlay>
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
275
|
+
<View
|
|
276
|
+
style={{
|
|
277
|
+
padding: 20,
|
|
278
|
+
backgroundColor: '#fff',
|
|
279
|
+
borderRadius: 12,
|
|
280
|
+
maxWidth: 320,
|
|
281
|
+
alignItems: 'stretch',
|
|
282
|
+
}}>
|
|
283
|
+
<Text
|
|
284
|
+
style={{
|
|
285
|
+
fontSize: 16,
|
|
286
|
+
fontWeight: '600',
|
|
287
|
+
marginBottom: 8,
|
|
288
|
+
color: '#222',
|
|
289
|
+
}}>
|
|
290
|
+
Connection error
|
|
291
|
+
</Text>
|
|
292
|
+
<Text style={{fontSize: 13, color: '#444', marginBottom: 16}}>
|
|
293
|
+
{errorMsg ?? 'Something went wrong while connecting to chat.'}
|
|
294
|
+
</Text>
|
|
295
|
+
<Pressable
|
|
296
|
+
onPress={() => {
|
|
297
|
+
setShowModal(false);
|
|
298
|
+
setErrorMsg(null);
|
|
299
|
+
setInited(false);
|
|
300
|
+
// Signal a clean re-bootstrap. XmppProvider listens and
|
|
301
|
+
// resets its state machine so the next effect run resolves
|
|
302
|
+
// the user from scratch.
|
|
303
|
+
DeviceEventEmitter.emit('ethora:retryBootstrap');
|
|
304
|
+
}}
|
|
305
|
+
style={({pressed}) => ({
|
|
306
|
+
paddingVertical: 10,
|
|
307
|
+
paddingHorizontal: 16,
|
|
308
|
+
borderRadius: 6,
|
|
309
|
+
backgroundColor: pressed ? '#0040A0' : '#0052CD',
|
|
310
|
+
alignItems: 'center',
|
|
311
|
+
})}>
|
|
312
|
+
<Text style={{color: '#fff', fontWeight: '600'}}>Retry</Text>
|
|
313
|
+
</Pressable>
|
|
314
|
+
</View>
|
|
263
315
|
</Overlay>
|
|
264
316
|
)}
|
|
265
317
|
<>
|
|
@@ -1,40 +1,21 @@
|
|
|
1
|
-
import React, { useMemo
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { Delimeter, MenuItem } from '../ContextMenu/ContextMenuComponents';
|
|
3
|
-
import {
|
|
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
|
|
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 '
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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:
|
|
148
|
-
type:
|
|
126
|
+
uri: asset.uri,
|
|
127
|
+
type: asset.mimeType || 'image/jpeg',
|
|
149
128
|
name: originalName || `profile_${Date.now()}.jpg`,
|
|
150
129
|
};
|
|
151
130
|
|
|
@@ -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
|
|
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';
|
|
@@ -361,6 +361,20 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
|
|
|
361
361
|
return () => sub.remove();
|
|
362
362
|
}, [client]);
|
|
363
363
|
|
|
364
|
+
// -----------------------------------------------------------
|
|
365
|
+
// Retry-bootstrap listener — fired by ChatWrapper's error modal so
|
|
366
|
+
// a transient failure (bad endpoint, server hiccup, etc.) can be
|
|
367
|
+
// recovered without forcing the user to unmount the whole chat.
|
|
368
|
+
// -----------------------------------------------------------
|
|
369
|
+
useEffect(() => {
|
|
370
|
+
const sub = DeviceEventEmitter.addListener('ethora:retryBootstrap', () => {
|
|
371
|
+
completedBootstrapKeyRef.current = '';
|
|
372
|
+
inflightBootstrapKeyRef.current = '';
|
|
373
|
+
setProviderBootstrapStatus('idle');
|
|
374
|
+
});
|
|
375
|
+
return () => sub.remove();
|
|
376
|
+
}, []);
|
|
377
|
+
|
|
364
378
|
// -----------------------------------------------------------
|
|
365
379
|
// Logout event listener (RN equivalent of window event)
|
|
366
380
|
// -----------------------------------------------------------
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
var _react = _interopRequireDefault(require("react"));
|
|
8
|
-
var _AudioRecorder = _interopRequireDefault(require("../InputComponents/AudioRecorder"));
|
|
9
|
-
var _jsxRuntime = require("react/jsx-runtime");
|
|
10
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
-
const AudioInput = ({
|
|
12
|
-
isRecording,
|
|
13
|
-
setIsRecording,
|
|
14
|
-
handleSendClick
|
|
15
|
-
}) => {
|
|
16
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_AudioRecorder.default, {
|
|
17
|
-
setIsRecording: setIsRecording,
|
|
18
|
-
isRecording: isRecording,
|
|
19
|
-
handleSendClick: handleSendClick
|
|
20
|
-
});
|
|
21
|
-
};
|
|
22
|
-
var _default = exports.default = AudioInput;
|
|
23
|
-
//# sourceMappingURL=AudioInput.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireDefault","require","_AudioRecorder","_jsxRuntime","e","__esModule","default","AudioInput","isRecording","setIsRecording","handleSendClick","jsx","_default","exports"],"sourceRoot":"../../../../src","sources":["components/InputComponents/AudioInput.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,sBAAA,CAAAC,OAAA;AACA,IAAAC,cAAA,GAAAF,sBAAA,CAAAC,OAAA;AAA6D,IAAAE,WAAA,GAAAF,OAAA;AAAA,SAAAD,uBAAAI,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAQ7D,MAAMG,UAAqC,GAAGA,CAAC;EAC7CC,WAAW;EACXC,cAAc;EACdC;AACF,CAAC,KAAK;EACJ,oBACE,IAAAP,WAAA,CAAAQ,GAAA,EAACT,cAAA,CAAAI,OAAa;IACZG,cAAc,EAAEA,cAAe;IAC/BD,WAAW,EAAEA,WAAY;IACzBE,eAAe,EAAEA;EAAgB,CAClC,CAAC;AAEN,CAAC;AAAC,IAAAE,QAAA,GAAAC,OAAA,CAAAP,OAAA,GAEaC,UAAU","ignoreList":[]}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
-
var _StyledInputComponents = require("../styled/StyledInputComponents/StyledInputComponents");
|
|
9
|
-
var _icons = require("../../assets/icons");
|
|
10
|
-
var _Button = _interopRequireDefault(require("../styled/Button"));
|
|
11
|
-
var _RecordingIndicator = _interopRequireDefault(require("./RecordingIndicator"));
|
|
12
|
-
var _reactNative = require("react-native");
|
|
13
|
-
var _reactNativeAudioRecorderPlayer = _interopRequireDefault(require("react-native-audio-recorder-player"));
|
|
14
|
-
var _reactNativeFs = _interopRequireDefault(require("react-native-fs"));
|
|
15
|
-
var _jsxRuntime = require("react/jsx-runtime");
|
|
16
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
17
|
-
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
18
|
-
const AudioRecorder = ({
|
|
19
|
-
setIsRecording,
|
|
20
|
-
isRecording,
|
|
21
|
-
handleSendClick
|
|
22
|
-
}) => {
|
|
23
|
-
const audioRecorderPlayer = (0, _react.useRef)(new _reactNativeAudioRecorderPlayer.default()).current;
|
|
24
|
-
const [timer, setTimer] = (0, _react.useState)('00:00');
|
|
25
|
-
const [audioPath, setAudioPath] = (0, _react.useState)(null);
|
|
26
|
-
(0, _react.useEffect)(() => {
|
|
27
|
-
return () => {
|
|
28
|
-
audioRecorderPlayer.stopRecorder();
|
|
29
|
-
audioRecorderPlayer.removeRecordBackListener();
|
|
30
|
-
};
|
|
31
|
-
}, [audioRecorderPlayer]);
|
|
32
|
-
const startRecording = async () => {
|
|
33
|
-
try {
|
|
34
|
-
const path = _reactNative.Platform.select({
|
|
35
|
-
ios: `${_reactNativeFs.default.DocumentDirectoryPath}/recording.m4a`,
|
|
36
|
-
android: `${_reactNativeFs.default.CachesDirectoryPath}/recording.mp4`
|
|
37
|
-
});
|
|
38
|
-
const uri = await audioRecorderPlayer.startRecorder(path);
|
|
39
|
-
setAudioPath(uri);
|
|
40
|
-
audioRecorderPlayer.addRecordBackListener(e => {
|
|
41
|
-
const minutes = Math.floor(e.currentPosition / 60000);
|
|
42
|
-
const seconds = Math.floor(e.currentPosition % 60000 / 1000);
|
|
43
|
-
setTimer(`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`);
|
|
44
|
-
});
|
|
45
|
-
setIsRecording(true);
|
|
46
|
-
} catch (error) {
|
|
47
|
-
console.error('Error starting recording:', error);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const stopRecording = async () => {
|
|
51
|
-
try {
|
|
52
|
-
await audioRecorderPlayer.stopRecorder();
|
|
53
|
-
audioRecorderPlayer.removeRecordBackListener();
|
|
54
|
-
setIsRecording(false);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
console.error('Error stopping recording:', error);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
const sendAudio = () => {
|
|
60
|
-
if (audioPath) {
|
|
61
|
-
console.log('Audio sent:', audioPath);
|
|
62
|
-
handleSendClick(audioPath);
|
|
63
|
-
resetState();
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
const resetState = () => {
|
|
67
|
-
setAudioPath(null);
|
|
68
|
-
setTimer('00:00');
|
|
69
|
-
setIsRecording(false);
|
|
70
|
-
};
|
|
71
|
-
return isRecording ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_StyledInputComponents.RecordContainer, {
|
|
72
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
73
|
-
style: {
|
|
74
|
-
display: 'flex',
|
|
75
|
-
alignItems: 'center'
|
|
76
|
-
},
|
|
77
|
-
children: [isRecording && /*#__PURE__*/(0, _jsxRuntime.jsx)(_RecordingIndicator.default, {}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_StyledInputComponents.Timer, {
|
|
78
|
-
children: timer
|
|
79
|
-
})]
|
|
80
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
81
|
-
style: {
|
|
82
|
-
display: 'flex',
|
|
83
|
-
gap: 8
|
|
84
|
-
},
|
|
85
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
|
|
86
|
-
onPress: stopRecording,
|
|
87
|
-
EndIcon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.RemoveIcon, {}),
|
|
88
|
-
unstyled: true
|
|
89
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
|
|
90
|
-
onPress: sendAudio,
|
|
91
|
-
EndIcon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.SendIcon, {}),
|
|
92
|
-
unstyled: true
|
|
93
|
-
})]
|
|
94
|
-
})]
|
|
95
|
-
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
|
|
96
|
-
onPress: startRecording,
|
|
97
|
-
EndIcon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.RecordIcon, {})
|
|
98
|
-
});
|
|
99
|
-
};
|
|
100
|
-
var _default = exports.default = AudioRecorder;
|
|
101
|
-
//# sourceMappingURL=AudioRecorder.js.map
|