@ethora/chat-component-rn 26.10.2 → 26.10.4
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/lib/commonjs/components/Header/HeaderRoomList.js +72 -69
- package/lib/commonjs/components/Header/HeaderRoomList.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatHeader.js +13 -11
- package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MessageContainer.js +10 -2
- package/lib/commonjs/components/MainComponents/MessageContainer.js.map +1 -1
- package/lib/commonjs/components/MainComponents/RoomList.js +8 -18
- package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +17 -14
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +131 -17
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/ChatRoomItem.js +6 -1
- package/lib/commonjs/components/RoomComponents/ChatRoomItem.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/ActiveCallBar.js +203 -0
- package/lib/commonjs/components/VideoCalls/ActiveCallBar.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/CallButtons.js +3 -1
- package/lib/commonjs/components/VideoCalls/CallButtons.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +123 -129
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js +212 -414
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/callActions.js +61 -0
- package/lib/commonjs/components/VideoCalls/callActions.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/callSessionController.js +461 -0
- package/lib/commonjs/components/VideoCalls/callSessionController.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/callUiStore.js +91 -0
- package/lib/commonjs/components/VideoCalls/callUiStore.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/livekitRuntime.js +1 -0
- package/lib/commonjs/components/VideoCalls/livekitRuntime.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/useDraggable.js +59 -0
- package/lib/commonjs/components/VideoCalls/useDraggable.js.map +1 -0
- package/lib/commonjs/helpers/createRoomFromApi.js +4 -0
- package/lib/commonjs/helpers/createRoomFromApi.js.map +1 -1
- package/lib/commonjs/helpers/incomingCallFromPush.js +1 -1
- package/lib/commonjs/helpers/incomingCallFromPush.js.map +1 -1
- package/lib/commonjs/helpers/roomUserCount.js +7 -1
- package/lib/commonjs/helpers/roomUserCount.js.map +1 -1
- package/lib/commonjs/helpers/uniqueMembers.js +36 -0
- package/lib/commonjs/helpers/uniqueMembers.js.map +1 -0
- package/lib/commonjs/hooks/useLeaveRoom.js +52 -0
- package/lib/commonjs/hooks/useLeaveRoom.js.map +1 -0
- package/lib/commonjs/i18n/strings.js +24 -0
- package/lib/commonjs/i18n/strings.js.map +1 -1
- package/lib/commonjs/networking/api-requests/rooms.api.js +36 -1
- package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/commonjs/networking/callTokenStanza.js +47 -3
- package/lib/commonjs/networking/callTokenStanza.js.map +1 -1
- package/lib/commonjs/networking/xmppClient.js +9 -2
- package/lib/commonjs/networking/xmppClient.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/module/components/Header/HeaderRoomList.js +73 -70
- package/lib/module/components/Header/HeaderRoomList.js.map +1 -1
- package/lib/module/components/MainComponents/ChatHeader.js +15 -13
- package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/module/components/MainComponents/MessageContainer.js +10 -2
- package/lib/module/components/MainComponents/MessageContainer.js.map +1 -1
- package/lib/module/components/MainComponents/RoomList.js +8 -18
- package/lib/module/components/MainComponents/RoomList.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +18 -15
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +132 -18
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/module/components/RoomComponents/ChatRoomItem.js +6 -1
- package/lib/module/components/RoomComponents/ChatRoomItem.js.map +1 -1
- package/lib/module/components/VideoCalls/ActiveCallBar.js +195 -0
- package/lib/module/components/VideoCalls/ActiveCallBar.js.map +1 -0
- package/lib/module/components/VideoCalls/CallButtons.js +3 -1
- package/lib/module/components/VideoCalls/CallButtons.js.map +1 -1
- package/lib/module/components/VideoCalls/VideoCallOverlay.js +125 -131
- package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -1
- package/lib/module/components/VideoCalls/VideoCallSession.js +213 -417
- package/lib/module/components/VideoCalls/VideoCallSession.js.map +1 -1
- package/lib/module/components/VideoCalls/callActions.js +55 -0
- package/lib/module/components/VideoCalls/callActions.js.map +1 -0
- package/lib/module/components/VideoCalls/callSessionController.js +450 -0
- package/lib/module/components/VideoCalls/callSessionController.js.map +1 -0
- package/lib/module/components/VideoCalls/callUiStore.js +84 -0
- package/lib/module/components/VideoCalls/callUiStore.js.map +1 -0
- package/lib/module/components/VideoCalls/livekitRuntime.js +1 -0
- package/lib/module/components/VideoCalls/livekitRuntime.js.map +1 -1
- package/lib/module/components/VideoCalls/useDraggable.js +55 -0
- package/lib/module/components/VideoCalls/useDraggable.js.map +1 -0
- package/lib/module/helpers/createRoomFromApi.js +4 -0
- package/lib/module/helpers/createRoomFromApi.js.map +1 -1
- package/lib/module/helpers/incomingCallFromPush.js +1 -1
- package/lib/module/helpers/incomingCallFromPush.js.map +1 -1
- package/lib/module/helpers/roomUserCount.js +7 -1
- package/lib/module/helpers/roomUserCount.js.map +1 -1
- package/lib/module/helpers/uniqueMembers.js +31 -0
- package/lib/module/helpers/uniqueMembers.js.map +1 -0
- package/lib/module/hooks/useLeaveRoom.js +48 -0
- package/lib/module/hooks/useLeaveRoom.js.map +1 -0
- package/lib/module/i18n/strings.js +24 -0
- package/lib/module/i18n/strings.js.map +1 -1
- package/lib/module/networking/api-requests/rooms.api.js +35 -1
- package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/module/networking/callTokenStanza.js +47 -3
- package/lib/module/networking/callTokenStanza.js.map +1 -1
- package/lib/module/networking/xmppClient.js +8 -1
- package/lib/module/networking/xmppClient.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/components/Header/HeaderRoomList.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MessageContainer.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/ActiveCallBar.d.ts +21 -0
- package/lib/typescript/components/VideoCalls/ActiveCallBar.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/CallButtons.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts +0 -6
- package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts +15 -22
- package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/callActions.d.ts +15 -0
- package/lib/typescript/components/VideoCalls/callActions.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/callSessionController.d.ts +63 -0
- package/lib/typescript/components/VideoCalls/callSessionController.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/callUiStore.d.ts +24 -0
- package/lib/typescript/components/VideoCalls/callUiStore.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts +2 -0
- package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/useDraggable.d.ts +19 -0
- package/lib/typescript/components/VideoCalls/useDraggable.d.ts.map +1 -0
- package/lib/typescript/helpers/createRoomFromApi.d.ts.map +1 -1
- package/lib/typescript/helpers/roomUserCount.d.ts +1 -0
- package/lib/typescript/helpers/roomUserCount.d.ts.map +1 -1
- package/lib/typescript/helpers/uniqueMembers.d.ts +11 -0
- package/lib/typescript/helpers/uniqueMembers.d.ts.map +1 -0
- package/lib/typescript/hooks/useLeaveRoom.d.ts +9 -0
- package/lib/typescript/hooks/useLeaveRoom.d.ts.map +1 -0
- package/lib/typescript/i18n/strings.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/rooms.api.d.ts +8 -0
- package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
- package/lib/typescript/networking/callTokenStanza.d.ts.map +1 -1
- package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
- package/lib/typescript/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Header/HeaderRoomList.tsx +66 -61
- package/src/components/MainComponents/ChatHeader.tsx +13 -8
- package/src/components/MainComponents/MessageContainer.tsx +14 -1
- package/src/components/MainComponents/RoomList.tsx +8 -14
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +16 -13
- package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +167 -36
- package/src/components/RoomComponents/ChatRoomItem.tsx +8 -1
- package/src/components/VideoCalls/ActiveCallBar.tsx +176 -0
- package/src/components/VideoCalls/CallButtons.tsx +5 -1
- package/src/components/VideoCalls/VideoCallOverlay.tsx +153 -183
- package/src/components/VideoCalls/VideoCallSession.tsx +227 -439
- package/src/components/VideoCalls/callActions.ts +63 -0
- package/src/components/VideoCalls/callSessionController.ts +439 -0
- package/src/components/VideoCalls/callUiStore.ts +76 -0
- package/src/components/VideoCalls/livekitRuntime.ts +3 -0
- package/src/components/VideoCalls/useDraggable.ts +46 -0
- package/src/helpers/createRoomFromApi.tsx +2 -0
- package/src/helpers/incomingCallFromPush.ts +2 -2
- package/src/helpers/roomUserCount.ts +6 -2
- package/src/helpers/uniqueMembers.ts +26 -0
- package/src/hooks/useLeaveRoom.ts +42 -0
- package/src/i18n/strings.ts +24 -0
- package/src/networking/api-requests/rooms.api.ts +28 -1
- package/src/networking/callTokenStanza.ts +46 -6
- package/src/networking/xmppClient.ts +10 -1
- package/src/version.ts +1 -1
|
@@ -44,15 +44,25 @@ import { runLogoutFlow } from '../../Menu/HeaderRoomListMenu';
|
|
|
44
44
|
import { useLogout } from '../../../hooks/useLogout';
|
|
45
45
|
import {
|
|
46
46
|
ApiRoom,
|
|
47
|
+
createChatCall,
|
|
47
48
|
ownAppId,
|
|
48
49
|
postPrivateRoom,
|
|
49
50
|
} from '../../../networking/api-requests/rooms.api';
|
|
50
51
|
import EncryptionCard from '../../MainComponents/EncryptionCard';
|
|
52
|
+
import { useSelector } from 'react-redux';
|
|
53
|
+
import { store, type RootState } from '../../../roomStore';
|
|
54
|
+
import { setCallError, startOutgoingCall } from '../../../roomStore/callSlice';
|
|
55
|
+
import { sendCallInviteSignal } from '../../../networking/callTokenStanza';
|
|
56
|
+
import { AudioCallIcon, VideoCallIcon } from '../../VideoCalls/CallIcons';
|
|
51
57
|
import { accountNameOf } from '../../../helpers/accountName';
|
|
52
58
|
import { setChatLanguage } from '../../../services/languageSettings';
|
|
53
59
|
import { composeName } from '../../../helpers/displayName';
|
|
54
60
|
import { isUnresolvedSenderId } from '../../../helpers/isUnresolvedSenderId';
|
|
55
61
|
|
|
62
|
+
/** The user id part of `${appId}_${userId}[@host]` (or a bare id). */
|
|
63
|
+
const userIdOf = (value?: string | null): string =>
|
|
64
|
+
String(value || '').split('@')[0].split('_').pop() || '';
|
|
65
|
+
|
|
56
66
|
/** A name, unless it is only the account's id standing in for one. */
|
|
57
67
|
const nameOrNothing = (name?: string | null): string => {
|
|
58
68
|
const trimmed = String(name ?? '').trim();
|
|
@@ -239,7 +249,7 @@ const UserProfileModal: React.FC<UserProfileModalProps> = ({
|
|
|
239
249
|
usersArrayLength
|
|
240
250
|
);
|
|
241
251
|
|
|
242
|
-
if (!normalizedChat || !client) {return;}
|
|
252
|
+
if (!normalizedChat || !client) {return null;}
|
|
243
253
|
|
|
244
254
|
dispatch(addRoomViaApi({ room: normalizedChat, xmpp: client }));
|
|
245
255
|
dispatch(setCurrentRoom({ roomJID: normalizedChat?.jid || '' }));
|
|
@@ -251,11 +261,93 @@ const UserProfileModal: React.FC<UserProfileModalProps> = ({
|
|
|
251
261
|
type: 'success',
|
|
252
262
|
duration: 3000,
|
|
253
263
|
});
|
|
264
|
+
return normalizedChat;
|
|
254
265
|
} catch (error) {
|
|
255
266
|
console.error('Error handling room creation:', error);
|
|
267
|
+
return null;
|
|
256
268
|
}
|
|
257
269
|
};
|
|
258
270
|
|
|
271
|
+
// Open (or create) the 1:1 room with the profile's user. Shared by
|
|
272
|
+
// "Message" and the call buttons, which need the room's JID to dial.
|
|
273
|
+
const ensurePrivateRoom = useCallback(async (
|
|
274
|
+
e2ee = false
|
|
275
|
+
): Promise<{
|
|
276
|
+
jid: string;
|
|
277
|
+
bareName: string;
|
|
278
|
+
peerXmppUsername: string | null;
|
|
279
|
+
peerDisplay: string;
|
|
280
|
+
} | null> => {
|
|
281
|
+
const peerDisplay = nameOrNothing(selectedUser?.name);
|
|
282
|
+
if (config?.newArch) {
|
|
283
|
+
const target = selectedUser?.userJID ?? (selectedUser?.id || '');
|
|
284
|
+
// Reuse the 1:1 room we already have with this user instead of
|
|
285
|
+
// POSTing /chats/private again: repeating it for an existing room
|
|
286
|
+
// has been seen to inflate the server's usersCnt and member list.
|
|
287
|
+
const peerLocal = String(target).split('@')[0];
|
|
288
|
+
const existing = Object.values(store.getState().rooms.rooms || {}).find(
|
|
289
|
+
(room: any) =>
|
|
290
|
+
room?.type === 'private' &&
|
|
291
|
+
!!peerLocal &&
|
|
292
|
+
[...(room.roomMembers || []), ...(room.members || [])].some(
|
|
293
|
+
// REST rows carry the bare user id, stanza rows `${appId}_${id}`:
|
|
294
|
+
// compare on the user id part.
|
|
295
|
+
(m: any) => userIdOf(m?.xmppUsername || m?._id) === userIdOf(peerLocal)
|
|
296
|
+
)
|
|
297
|
+
) as any;
|
|
298
|
+
if (existing?.jid && !e2ee) {
|
|
299
|
+
dispatch(setCurrentRoom({ roomJID: existing.jid }));
|
|
300
|
+
return {
|
|
301
|
+
jid: existing.jid,
|
|
302
|
+
bareName: existing.jid.split('@')[0],
|
|
303
|
+
peerXmppUsername: peerLocal || null,
|
|
304
|
+
peerDisplay: peerDisplay || nameOrNothing(existing.title),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const newRoom = await postPrivateRoom(target, e2ee);
|
|
308
|
+
const created = await handleRoomCreation(newRoom, 2);
|
|
309
|
+
if (!created?.jid) {return null;}
|
|
310
|
+
return {
|
|
311
|
+
jid: created.jid,
|
|
312
|
+
bareName: created.jid.split('@')[0],
|
|
313
|
+
peerXmppUsername: String(target).split('@')[0] || null,
|
|
314
|
+
peerDisplay: peerDisplay || nameOrNothing((created as any).title),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const selectedUserUsername = walletToUsername(selectedUser?.id || '');
|
|
319
|
+
const myUsername = walletToUsername(user.defaultWallet.walletAddress);
|
|
320
|
+
|
|
321
|
+
const roomJid = [myUsername, selectedUserUsername]
|
|
322
|
+
.sort()
|
|
323
|
+
.join('.')
|
|
324
|
+
.toLowerCase();
|
|
325
|
+
|
|
326
|
+
const combinedUsersName = [
|
|
327
|
+
user.firstName,
|
|
328
|
+
selectedUser?.name?.split(' ')?.[0] || '',
|
|
329
|
+
]
|
|
330
|
+
.sort()
|
|
331
|
+
.join(' and ');
|
|
332
|
+
|
|
333
|
+
const newRoomJid =
|
|
334
|
+
(await client?.createPrivateRoomStanza(
|
|
335
|
+
combinedUsersName,
|
|
336
|
+
`Private chat ${combinedUsersName}`,
|
|
337
|
+
roomJid
|
|
338
|
+
)) || '';
|
|
339
|
+
|
|
340
|
+
if (!newRoomJid) {return null;}
|
|
341
|
+
await client?.inviteRoomRequestStanza(selectedUserUsername, newRoomJid);
|
|
342
|
+
await client?.getRoomsStanza();
|
|
343
|
+
return {
|
|
344
|
+
jid: newRoomJid,
|
|
345
|
+
bareName: newRoomJid.split('@')[0],
|
|
346
|
+
peerXmppUsername: selectedUserUsername || null,
|
|
347
|
+
peerDisplay,
|
|
348
|
+
};
|
|
349
|
+
}, [selectedUser, config?.newArch, client, user]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
350
|
+
|
|
259
351
|
const openPrivateRoom = useCallback(async (e2ee: boolean) => {
|
|
260
352
|
showToast({
|
|
261
353
|
id: Date.now().toString(),
|
|
@@ -264,43 +356,62 @@ const UserProfileModal: React.FC<UserProfileModalProps> = ({
|
|
|
264
356
|
type: 'info',
|
|
265
357
|
duration: 3000,
|
|
266
358
|
});
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
359
|
+
await ensurePrivateRoom(e2ee);
|
|
360
|
+
dispatch(setActiveModal(undefined));
|
|
361
|
+
}, [ensurePrivateRoom, t]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
362
|
+
|
|
363
|
+
// Calls from the profile, as on web: open the 1:1 room, then dial it the
|
|
364
|
+
// same way the chat header does.
|
|
365
|
+
const videoCallsConfig = config?.videoCalls;
|
|
366
|
+
const canCall =
|
|
367
|
+
videoCallsConfig?.enabled === true &&
|
|
368
|
+
Boolean(videoCallsConfig?.livekitUrl?.trim()) &&
|
|
369
|
+
(videoCallsConfig?.allowedRoomTypes || ['private']).includes('private');
|
|
370
|
+
const isAudioCallsEnabled = canCall && videoCallsConfig?.enableAudioCalls === true;
|
|
371
|
+
const isCallBusy = useSelector((state: RootState) => state.call.phase !== 'idle');
|
|
372
|
+
|
|
373
|
+
const handleCall = useCallback(async (kind: 'audio' | 'video') => {
|
|
374
|
+
if (isCallBusy) {return;}
|
|
375
|
+
let room: Awaited<ReturnType<typeof ensurePrivateRoom>> = null;
|
|
376
|
+
try {
|
|
377
|
+
room = await ensurePrivateRoom();
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.warn('Could not open the private chat for a call:', error);
|
|
380
|
+
}
|
|
381
|
+
if (!room) {return;}
|
|
382
|
+
|
|
383
|
+
dispatch(
|
|
384
|
+
startOutgoingCall({
|
|
385
|
+
roomJid: room.jid,
|
|
386
|
+
roomName: room.peerDisplay || room.bareName,
|
|
387
|
+
roomBareName: room.bareName,
|
|
388
|
+
kind,
|
|
389
|
+
peerXmppUsername: room.peerXmppUsername,
|
|
390
|
+
})
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
// The server drops `kind` on the relayed call-token, so signal the peer
|
|
394
|
+
// directly first, as CallButtons does.
|
|
395
|
+
if (room.peerXmppUsername) {
|
|
396
|
+
sendCallInviteSignal(kind, {
|
|
397
|
+
peerXmppUsername: room.peerXmppUsername,
|
|
398
|
+
roomBareName: room.bareName,
|
|
399
|
+
});
|
|
300
400
|
}
|
|
301
401
|
|
|
402
|
+
// Close the profile so the call screen isn't behind it.
|
|
302
403
|
dispatch(setActiveModal(undefined));
|
|
303
|
-
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
await createChatCall(room.bareName, { kind });
|
|
407
|
+
} catch (error) {
|
|
408
|
+
dispatch(
|
|
409
|
+
setCallError(
|
|
410
|
+
error instanceof Error ? error.message : t('call.error.connectFailed')
|
|
411
|
+
)
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
}, [ensurePrivateRoom, isCallBusy, t]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
304
415
|
|
|
305
416
|
const handlePrivateMessage = useCallback(
|
|
306
417
|
() => openPrivateRoom(false),
|
|
@@ -374,6 +485,26 @@ const UserProfileModal: React.FC<UserProfileModalProps> = ({
|
|
|
374
485
|
]
|
|
375
486
|
: []),
|
|
376
487
|
]),
|
|
488
|
+
...(canCall && !isCallBusy
|
|
489
|
+
? [
|
|
490
|
+
{
|
|
491
|
+
key: 'video-call',
|
|
492
|
+
label: isAudioCallsEnabled ? t('action.videoCall') : t('action.call'),
|
|
493
|
+
icon: (color: string) => <VideoCallIcon color={color} />,
|
|
494
|
+
onPress: () => void handleCall('video'),
|
|
495
|
+
},
|
|
496
|
+
...(isAudioCallsEnabled
|
|
497
|
+
? [
|
|
498
|
+
{
|
|
499
|
+
key: 'audio-call',
|
|
500
|
+
label: t('action.audioCall'),
|
|
501
|
+
icon: (color: string) => <AudioCallIcon color={color} />,
|
|
502
|
+
onPress: () => void handleCall('audio'),
|
|
503
|
+
},
|
|
504
|
+
]
|
|
505
|
+
: []),
|
|
506
|
+
]
|
|
507
|
+
: []),
|
|
377
508
|
{
|
|
378
509
|
key: 'share',
|
|
379
510
|
label: t('action.share'),
|
|
@@ -381,7 +512,7 @@ const UserProfileModal: React.FC<UserProfileModalProps> = ({
|
|
|
381
512
|
onPress: handleShare,
|
|
382
513
|
},
|
|
383
514
|
],
|
|
384
|
-
[isOwnProfile, t, handleShare, handlePrivateMessage, handleEncryptedPrivateMessage, canStartEncrypted, handleLogout] // eslint-disable-line react-hooks/exhaustive-deps
|
|
515
|
+
[isOwnProfile, t, handleShare, handlePrivateMessage, handleEncryptedPrivateMessage, canStartEncrypted, handleLogout, canCall, isCallBusy, isAudioCallsEnabled, handleCall] // eslint-disable-line react-hooks/exhaustive-deps
|
|
385
516
|
);
|
|
386
517
|
|
|
387
518
|
// Collapsed, the hero's round buttons are gone — the "…" menu carries
|
|
@@ -22,6 +22,7 @@ import { useTheme } from '../../hooks/useTheme';
|
|
|
22
22
|
import { resolveTheme } from '../../theme/theme';
|
|
23
23
|
import { BellOffIcon, LockIcon } from '../../assets/icons';
|
|
24
24
|
import { useT } from '../../i18n/useT';
|
|
25
|
+
import { formatCallLogLabel } from '../../helpers/callLogMessage';
|
|
25
26
|
import { composeName } from '../../helpers/displayName';
|
|
26
27
|
import { isUnresolvedSenderId } from '../../helpers/isUnresolvedSenderId';
|
|
27
28
|
|
|
@@ -75,13 +76,19 @@ const ChatRoomItem: React.FC<ChatRoomItemProps> = ({
|
|
|
75
76
|
|
|
76
77
|
return {
|
|
77
78
|
...message,
|
|
79
|
+
// A call-log entry's body is English frozen at receive time; rebuild
|
|
80
|
+
// it from the callLog meta so the preview follows the current
|
|
81
|
+
// language like the transcript does (mirrors web's ChatRoomItem).
|
|
82
|
+
body: message.callLog
|
|
83
|
+
? formatCallLogLabel(message.callLog, t, message.body)
|
|
84
|
+
: message.body,
|
|
78
85
|
user: {
|
|
79
86
|
...message.user,
|
|
80
87
|
name: safeName,
|
|
81
88
|
},
|
|
82
89
|
};
|
|
83
90
|
},
|
|
84
|
-
[usersSet]
|
|
91
|
+
[usersSet, t]
|
|
85
92
|
);
|
|
86
93
|
|
|
87
94
|
const lastMessage = useMemo(() => {
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import React, { FC, useEffect, useState } from 'react';
|
|
2
|
+
import { Pressable, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import { useSelector } from 'react-redux';
|
|
4
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
|
+
import { RootState } from '../../roomStore';
|
|
6
|
+
import { useChatSettingState } from '../../hooks/useChatSettingState';
|
|
7
|
+
import { useTheme } from '../../hooks/useTheme';
|
|
8
|
+
import { useT } from '../../i18n/useT';
|
|
9
|
+
import { ExpandIcon, HangUpIcon, MicOffIcon, MicOnIcon } from './CallIcons';
|
|
10
|
+
import { toggleCallMic, useCallSession } from './callSessionController';
|
|
11
|
+
import { terminateActiveCall } from './callActions';
|
|
12
|
+
import { registerCallBarHost, setCallMinimized, useCallUi } from './callUiStore';
|
|
13
|
+
import { formatCallDuration, useSharedCallElapsed } from './VideoCallSession';
|
|
14
|
+
|
|
15
|
+
const DANGER = '#E53935';
|
|
16
|
+
const ON_BAR = '#FFFFFF';
|
|
17
|
+
const CONTROL = 'rgba(255, 255, 255, 0.2)';
|
|
18
|
+
|
|
19
|
+
/** True while a call is up and minimized, i.e. the strip should show. */
|
|
20
|
+
const useShowCallBar = (): boolean => {
|
|
21
|
+
const { minimized } = useCallUi();
|
|
22
|
+
const active = useSelector(
|
|
23
|
+
(state: RootState) =>
|
|
24
|
+
!!state.call.token &&
|
|
25
|
+
(state.call.phase === 'connecting' || state.call.phase === 'in-call')
|
|
26
|
+
);
|
|
27
|
+
const { config } = useChatSettingState();
|
|
28
|
+
return minimized && active && config?.videoCalls?.enabled === true;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The minimized call: a strip with who, how long, mic, expand and hang up.
|
|
33
|
+
* Tapping anywhere on it (outside the buttons) brings the call back
|
|
34
|
+
* full-screen, like Telegram's "return to call" bar.
|
|
35
|
+
*/
|
|
36
|
+
export const ActiveCallBar: FC<{ topInset?: number }> = ({ topInset = 0 }) => {
|
|
37
|
+
const t = useT();
|
|
38
|
+
const theme = useTheme();
|
|
39
|
+
const { icons } = useChatSettingState().config?.videoCalls || {};
|
|
40
|
+
const peerName = useSelector((state: RootState) => state.call.roomName);
|
|
41
|
+
const session = useCallSession();
|
|
42
|
+
const elapsed = useSharedCallElapsed(session.anchorAt);
|
|
43
|
+
|
|
44
|
+
const name = peerName || session.remoteName || t('call.unknownCaller');
|
|
45
|
+
const status =
|
|
46
|
+
elapsed !== null
|
|
47
|
+
? formatCallDuration(elapsed)
|
|
48
|
+
: !session.connected
|
|
49
|
+
? t('call.status.connecting')
|
|
50
|
+
: !session.remoteJoined
|
|
51
|
+
? t('call.status.waitingForPeer')
|
|
52
|
+
: t('call.status.inCall');
|
|
53
|
+
const statusLine = session.remoteMicMuted
|
|
54
|
+
? `${status} · ${t('call.peerMuted', { name })}`
|
|
55
|
+
: status;
|
|
56
|
+
const expand = () => setCallMinimized(false);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Pressable
|
|
60
|
+
onPress={expand}
|
|
61
|
+
accessibilityRole="button"
|
|
62
|
+
accessibilityLabel={t('call.action.expand')}
|
|
63
|
+
style={[styles.bar, { backgroundColor: theme.success, paddingTop: 8 + topInset }]}
|
|
64
|
+
>
|
|
65
|
+
<View style={styles.info}>
|
|
66
|
+
<Text numberOfLines={1} style={styles.name}>
|
|
67
|
+
{name}
|
|
68
|
+
</Text>
|
|
69
|
+
<Text numberOfLines={1} style={styles.status}>
|
|
70
|
+
{statusLine}
|
|
71
|
+
</Text>
|
|
72
|
+
</View>
|
|
73
|
+
<TouchableOpacity
|
|
74
|
+
accessibilityRole="button"
|
|
75
|
+
accessibilityLabel={session.micOn ? t('call.action.muteMic') : t('call.action.unmuteMic')}
|
|
76
|
+
onPress={toggleCallMic}
|
|
77
|
+
style={[styles.button, { backgroundColor: session.micOn ? CONTROL : DANGER }]}
|
|
78
|
+
hitSlop={6}
|
|
79
|
+
>
|
|
80
|
+
{session.micOn
|
|
81
|
+
? (icons?.micOn ?? <MicOnIcon color={ON_BAR} size={18} />)
|
|
82
|
+
: (icons?.micOff ?? <MicOffIcon color={ON_BAR} size={18} />)}
|
|
83
|
+
</TouchableOpacity>
|
|
84
|
+
<TouchableOpacity
|
|
85
|
+
accessibilityRole="button"
|
|
86
|
+
accessibilityLabel={t('call.action.expand')}
|
|
87
|
+
onPress={expand}
|
|
88
|
+
style={[styles.button, { backgroundColor: CONTROL }]}
|
|
89
|
+
hitSlop={6}
|
|
90
|
+
>
|
|
91
|
+
<ExpandIcon color={ON_BAR} size={18} />
|
|
92
|
+
</TouchableOpacity>
|
|
93
|
+
<TouchableOpacity
|
|
94
|
+
accessibilityRole="button"
|
|
95
|
+
accessibilityLabel={t('call.action.hangUp')}
|
|
96
|
+
onPress={() => terminateActiveCall('ended')}
|
|
97
|
+
style={[styles.button, { backgroundColor: DANGER }]}
|
|
98
|
+
hitSlop={6}
|
|
99
|
+
>
|
|
100
|
+
{icons?.hangup ?? <HangUpIcon color={ON_BAR} size={18} />}
|
|
101
|
+
</TouchableOpacity>
|
|
102
|
+
</Pressable>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Mounted under each SDK header (chat and room list). Registers the header
|
|
108
|
+
* as a place for the strip and renders it while this header is the current
|
|
109
|
+
* host and a call is minimized; renders nothing otherwise.
|
|
110
|
+
*/
|
|
111
|
+
export const HeaderCallBar: FC = () => {
|
|
112
|
+
const [hostId, setHostId] = useState<number | null>(null);
|
|
113
|
+
const { barHost } = useCallUi();
|
|
114
|
+
const show = useShowCallBar();
|
|
115
|
+
|
|
116
|
+
// Returns its own unregister, which is the effect's cleanup.
|
|
117
|
+
useEffect(() => registerCallBarHost(setHostId), []);
|
|
118
|
+
|
|
119
|
+
if (!show || hostId === null || barHost !== hostId) {return null;}
|
|
120
|
+
return <ActiveCallBar />;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The overlay's fallback when no SDK header is on screen: the same strip,
|
|
125
|
+
* docked to the top under the status bar.
|
|
126
|
+
*/
|
|
127
|
+
export const DockedCallBar: FC = () => {
|
|
128
|
+
const insets = useSafeAreaInsets();
|
|
129
|
+
const { barHost } = useCallUi();
|
|
130
|
+
const show = useShowCallBar();
|
|
131
|
+
if (!show || barHost !== null) {return null;}
|
|
132
|
+
return (
|
|
133
|
+
<View style={styles.docked} pointerEvents="box-none">
|
|
134
|
+
<ActiveCallBar topInset={insets.top} />
|
|
135
|
+
</View>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const styles = StyleSheet.create({
|
|
140
|
+
bar: {
|
|
141
|
+
flexDirection: 'row',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
gap: 10,
|
|
144
|
+
paddingHorizontal: 16,
|
|
145
|
+
paddingBottom: 8,
|
|
146
|
+
},
|
|
147
|
+
info: {
|
|
148
|
+
flex: 1,
|
|
149
|
+
minWidth: 0,
|
|
150
|
+
},
|
|
151
|
+
name: {
|
|
152
|
+
color: ON_BAR,
|
|
153
|
+
fontSize: 15,
|
|
154
|
+
fontWeight: '600',
|
|
155
|
+
},
|
|
156
|
+
status: {
|
|
157
|
+
color: 'rgba(255, 255, 255, 0.85)',
|
|
158
|
+
fontSize: 12,
|
|
159
|
+
fontVariant: ['tabular-nums'],
|
|
160
|
+
},
|
|
161
|
+
button: {
|
|
162
|
+
width: 34,
|
|
163
|
+
height: 34,
|
|
164
|
+
borderRadius: 17,
|
|
165
|
+
alignItems: 'center',
|
|
166
|
+
justifyContent: 'center',
|
|
167
|
+
},
|
|
168
|
+
docked: {
|
|
169
|
+
position: 'absolute',
|
|
170
|
+
top: 0,
|
|
171
|
+
left: 0,
|
|
172
|
+
right: 0,
|
|
173
|
+
zIndex: 1001,
|
|
174
|
+
elevation: 12,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
@@ -71,7 +71,11 @@ export const CallButtons: FC = () => {
|
|
|
71
71
|
// Don't show "deleted" / "Deleted User" / "Unknown" as the dial
|
|
72
72
|
// screen title when the chat title is one of the server sentinels.
|
|
73
73
|
// Prefer the peer's first+last name, falling back to the bare chat name.
|
|
74
|
-
|
|
74
|
+
// `title` is what the header shows ("John Pauliller"); on new-arch
|
|
75
|
+
// private rooms `name` is the bare room id.
|
|
76
|
+
const rawTitle = String(
|
|
77
|
+
(currentRoom as any).title || currentRoom.name || ''
|
|
78
|
+
).trim();
|
|
75
79
|
const isBadTitle =
|
|
76
80
|
!rawTitle ||
|
|
77
81
|
['deleted', 'deleted user', 'unknown', 'null'].includes(
|