@ethora/chat-component-rn 26.10.2 → 26.10.3
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/MainComponents/ChatHeader.js +11 -10
- 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/CallButtons.js +3 -1
- package/lib/commonjs/components/VideoCalls/CallButtons.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +123 -111
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js +207 -346
- 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 +431 -0
- package/lib/commonjs/components/VideoCalls/callSessionController.js.map +1 -0
- 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 +18 -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/MainComponents/ChatHeader.js +13 -12
- 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/CallButtons.js +3 -1
- package/lib/module/components/VideoCalls/CallButtons.js.map +1 -1
- package/lib/module/components/VideoCalls/VideoCallOverlay.js +125 -113
- package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -1
- package/lib/module/components/VideoCalls/VideoCallSession.js +208 -348
- 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 +420 -0
- package/lib/module/components/VideoCalls/callSessionController.js.map +1 -0
- 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 +18 -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/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/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 +9 -20
- 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 +61 -0
- package/lib/typescript/components/VideoCalls/callSessionController.d.ts.map +1 -0
- 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/MainComponents/ChatHeader.tsx +10 -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/CallButtons.tsx +5 -1
- package/src/components/VideoCalls/VideoCallOverlay.tsx +156 -163
- package/src/components/VideoCalls/VideoCallSession.tsx +207 -378
- package/src/components/VideoCalls/callActions.ts +63 -0
- package/src/components/VideoCalls/callSessionController.ts +421 -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 +18 -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
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
4
|
-
import { ActivityIndicator,
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { ActivityIndicator, Animated, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
5
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
6
|
import { ProfileImagePlaceholder } from '../MainComponents/ProfileImagePlaceholder';
|
|
6
7
|
import { CameraOffIcon, CameraOnIcon, HangUpIcon, MicOffIcon, MicOnIcon, MinimizeIcon, ExpandIcon, SpeakerIcon, SpeakerOffIcon, SwitchCameraIcon } from './CallIcons';
|
|
7
|
-
import {
|
|
8
|
+
import { loadLiveKit } from './livekitRuntime';
|
|
9
|
+
import { dismissCallDeviceError, switchCallCamera, toggleCallCamera, toggleCallMic, toggleCallSpeaker, useCallSession } from './callSessionController';
|
|
10
|
+
import { useDraggable } from './useDraggable';
|
|
8
11
|
import { useT } from '../../i18n/useT';
|
|
9
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
13
|
const DEFAULT_PRIMARY = '#0052CD';
|
|
@@ -13,329 +16,59 @@ const TEXT_ON_DARK = '#FFFFFF';
|
|
|
13
16
|
const TEXT_MUTED = '#9AA4B2';
|
|
14
17
|
const DANGER = '#E53935';
|
|
15
18
|
const CONTROL_IDLE = 'rgba(255, 255, 255, 0.16)';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (name === 'NotReadableError' || text.includes('in use')) {
|
|
33
|
-
return t('call.error.deviceInUse');
|
|
19
|
+
/**
|
|
20
|
+
* Seconds since the call truly connected, counted from the server-set
|
|
21
|
+
* `joinedAt` anchor so both sides show the same time (web's
|
|
22
|
+
* useSharedCallElapsed). Null until the peer is in.
|
|
23
|
+
*/
|
|
24
|
+
const useSharedCallElapsed = anchorAt => {
|
|
25
|
+
const [, tick] = useState(0);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (anchorAt === null) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const id = setInterval(() => tick(n => n + 1), 1000);
|
|
31
|
+
return () => clearInterval(id);
|
|
32
|
+
}, [anchorAt]);
|
|
33
|
+
if (anchorAt === null) {
|
|
34
|
+
return null;
|
|
34
35
|
}
|
|
35
|
-
return
|
|
36
|
+
return Math.max(0, Math.floor((Date.now() - anchorAt) / 1000));
|
|
37
|
+
};
|
|
38
|
+
export const formatCallDuration = totalSeconds => {
|
|
39
|
+
const safe = Math.max(0, Math.floor(totalSeconds));
|
|
40
|
+
const hours = Math.floor(safe / 3600);
|
|
41
|
+
const minutes = Math.floor(safe % 3600 / 60);
|
|
42
|
+
const seconds = safe % 60;
|
|
43
|
+
const mm = String(minutes).padStart(2, '0');
|
|
44
|
+
const ss = String(seconds).padStart(2, '0');
|
|
45
|
+
return hours > 0 ? `${hours}:${mm}:${ss}` : `${mm}:${ss}`;
|
|
36
46
|
};
|
|
47
|
+
|
|
37
48
|
/**
|
|
38
|
-
* The
|
|
49
|
+
* The call screen, React Native edition. Pure view over the LiveKit session
|
|
50
|
+
* owned by callSessionController.ts: it can unmount and remount (minimize,
|
|
51
|
+
* leaving the chat screen) without touching the connection.
|
|
39
52
|
*
|
|
40
|
-
* The web SDK builds this on `@livekit/components-react` hooks
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* connection state) in local React state, re-syncing on RoomEvent. Only the
|
|
44
|
-
* actual video surface comes from `@livekit/react-native` (`<VideoTrack />`),
|
|
45
|
-
* which wraps the native renderer.
|
|
46
|
-
*
|
|
47
|
-
* Requires `@livekit/react-native` >= 2.7 for the `trackRef` prop shape used
|
|
48
|
-
* below. Both LiveKit packages are optional peer deps, see livekitRuntime.ts.
|
|
53
|
+
* The web SDK builds this on `@livekit/components-react` hooks, which
|
|
54
|
+
* assume a DOM. Only the video surface comes from `@livekit/react-native`
|
|
55
|
+
* (`<VideoTrack />`, `trackRef` prop, >= 2.7).
|
|
49
56
|
*/
|
|
50
57
|
export const VideoCallSession = ({
|
|
51
|
-
token,
|
|
52
|
-
livekitUrl,
|
|
53
58
|
kind = 'video',
|
|
54
59
|
peerName,
|
|
55
60
|
primaryColor = DEFAULT_PRIMARY,
|
|
56
61
|
icons,
|
|
57
|
-
startWithCameraOn = true,
|
|
58
|
-
startWithMicOn = true,
|
|
59
62
|
minimized = false,
|
|
60
63
|
onToggleMinimize,
|
|
61
|
-
onConnected,
|
|
62
|
-
onError,
|
|
63
64
|
onHangup
|
|
64
65
|
}) => {
|
|
65
66
|
const t = useT();
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const [cameraOn, setCameraOn] = useState(kind === 'video' && startWithCameraOn);
|
|
72
|
-
const [speakerOn, setSpeakerOn] = useState(kind === 'video');
|
|
73
|
-
const [remoteVideo, setRemoteVideo] = useState(null);
|
|
74
|
-
const [localVideo, setLocalVideo] = useState(null);
|
|
75
|
-
const [remoteName, setRemoteName] = useState(null);
|
|
76
|
-
const [remoteJoined, setRemoteJoined] = useState(false);
|
|
77
|
-
|
|
78
|
-
// Callbacks are read through a ref inside the connect effect so that a
|
|
79
|
-
// parent re-render (new inline arrow props) never re-runs connect and
|
|
80
|
-
// tears down a live call. Same guard the web session uses.
|
|
81
|
-
const callbacksRef = useRef({
|
|
82
|
-
onConnected,
|
|
83
|
-
onError,
|
|
84
|
-
onHangup
|
|
85
|
-
});
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
callbacksRef.current = {
|
|
88
|
-
onConnected,
|
|
89
|
-
onError,
|
|
90
|
-
onHangup
|
|
91
|
-
};
|
|
92
|
-
}, [onConnected, onError, onHangup]);
|
|
93
|
-
|
|
94
|
-
// ---- track plumbing -------------------------------------------------
|
|
95
|
-
|
|
96
|
-
// Build the { participant, publication, source } shape `<VideoTrack />`
|
|
97
|
-
// expects, from whatever camera publication a participant currently has.
|
|
98
|
-
const toTrackRef = useCallback((participant, Track) => {
|
|
99
|
-
if (!participant) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
const publication = participant.getTrackPublication?.(Track.Source.Camera);
|
|
103
|
-
if (!publication?.track) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
participant,
|
|
108
|
-
publication,
|
|
109
|
-
source: Track.Source.Camera
|
|
110
|
-
};
|
|
111
|
-
}, []);
|
|
112
|
-
const resyncTracks = useCallback(() => {
|
|
113
|
-
const room = roomRef.current;
|
|
114
|
-
if (!room || !runtime) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const {
|
|
118
|
-
Track
|
|
119
|
-
} = runtime;
|
|
120
|
-
const remote = Array.from(room.remoteParticipants?.values?.() || [])[0];
|
|
121
|
-
setRemoteJoined(!!remote);
|
|
122
|
-
setRemoteName(remote?.name || remote?.identity || null);
|
|
123
|
-
setRemoteVideo(toTrackRef(remote, Track));
|
|
124
|
-
setLocalVideo(toTrackRef(room.localParticipant, Track));
|
|
125
|
-
}, [runtime, toTrackRef]);
|
|
126
|
-
|
|
127
|
-
// ---- connect / teardown ---------------------------------------------
|
|
128
|
-
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
if (!runtime) {
|
|
131
|
-
callbacksRef.current.onError?.(t('call.error.sdkMissing'));
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
if (!token || !livekitUrl) {
|
|
135
|
-
callbacksRef.current.onError?.(t('call.error.missingConfig'));
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
ensureLiveKitGlobals(runtime);
|
|
139
|
-
const {
|
|
140
|
-
Room,
|
|
141
|
-
RoomEvent,
|
|
142
|
-
AudioSession
|
|
143
|
-
} = runtime;
|
|
144
|
-
let disposed = false;
|
|
145
|
-
|
|
146
|
-
// adaptiveStream + dynacast keep mobile data and battery sane: the SDK
|
|
147
|
-
// drops resolution for a small tile and stops publishing layers nobody
|
|
148
|
-
// subscribes to. Both are safe defaults for 1:1.
|
|
149
|
-
const room = new Room({
|
|
150
|
-
adaptiveStream: true,
|
|
151
|
-
dynacast: true
|
|
152
|
-
});
|
|
153
|
-
roomRef.current = room;
|
|
154
|
-
const onStateChange = () => {
|
|
155
|
-
if (disposed) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
resyncTracks();
|
|
159
|
-
};
|
|
160
|
-
room.on(RoomEvent.TrackSubscribed, onStateChange).on(RoomEvent.TrackUnsubscribed, onStateChange).on(RoomEvent.LocalTrackPublished, onStateChange).on(RoomEvent.LocalTrackUnpublished, onStateChange).on(RoomEvent.TrackMuted, onStateChange).on(RoomEvent.TrackUnmuted, onStateChange).on(RoomEvent.ParticipantConnected, onStateChange).on(RoomEvent.MediaDevicesError, error => {
|
|
161
|
-
if (disposed) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
setDeviceError(humanizeDeviceError(error, t));
|
|
165
|
-
}).on(RoomEvent.ParticipantDisconnected, () => {
|
|
166
|
-
if (disposed) {
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
resyncTracks();
|
|
170
|
-
// 1:1 semantics: the other side leaving IS the end of the call.
|
|
171
|
-
// Without this the local user sits on a black canvas until they
|
|
172
|
-
// hang up manually.
|
|
173
|
-
callbacksRef.current.onHangup?.();
|
|
174
|
-
}).on(RoomEvent.Disconnected, () => {
|
|
175
|
-
if (disposed) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
setConnected(false);
|
|
179
|
-
callbacksRef.current.onHangup?.();
|
|
180
|
-
});
|
|
181
|
-
(async () => {
|
|
182
|
-
// Joining the LiveKit room is the only step whose failure should fail
|
|
183
|
-
// the call (bad token, network, server down). Everything after this
|
|
184
|
-
// is best-effort device setup — see the separate try/catches below.
|
|
185
|
-
try {
|
|
186
|
-
// Claims audio focus and sets the right native audio mode. Must
|
|
187
|
-
// start BEFORE connecting or the first seconds route to the
|
|
188
|
-
// earpiece on Android even for a video call.
|
|
189
|
-
await AudioSession.startAudioSession();
|
|
190
|
-
await room.connect(livekitUrl, token);
|
|
191
|
-
} catch (error) {
|
|
192
|
-
if (disposed) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
callbacksRef.current.onError?.(String(error?.message || '') || t('call.error.connectFailed'));
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
if (disposed) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Devices are best-effort: if the mic/camera prompt is blocked, or —
|
|
203
|
-
// notably — this is the iOS Simulator (no real camera hardware at
|
|
204
|
-
// all), the call still connects. The user can hear/be heard on
|
|
205
|
-
// audio and re-enable the camera later; failing the whole call here
|
|
206
|
-
// (as an earlier version did, sharing one try/catch with `connect`)
|
|
207
|
-
// meant every video call died before ever reaching the ring-answered
|
|
208
|
-
// screen — indistinguishable from "Accept doesn't work".
|
|
209
|
-
try {
|
|
210
|
-
await room.localParticipant.setMicrophoneEnabled(startWithMicOn);
|
|
211
|
-
} catch (error) {
|
|
212
|
-
if (!disposed) {
|
|
213
|
-
setDeviceError(humanizeDeviceError(error, t));
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (kind === 'video') {
|
|
217
|
-
try {
|
|
218
|
-
await room.localParticipant.setCameraEnabled(startWithCameraOn);
|
|
219
|
-
} catch (error) {
|
|
220
|
-
if (!disposed) {
|
|
221
|
-
setDeviceError(humanizeDeviceError(error, t));
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (disposed) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Video calls belong on the loudspeaker, audio calls on the
|
|
230
|
-
// earpiece, matching what every native dialer does.
|
|
231
|
-
try {
|
|
232
|
-
await AudioSession.setAppleAudioConfiguration?.({
|
|
233
|
-
audioCategory: 'playAndRecord',
|
|
234
|
-
audioMode: kind === 'video' ? 'videoChat' : 'voiceChat'
|
|
235
|
-
});
|
|
236
|
-
} catch {
|
|
237
|
-
// iOS-only helper, absent on Android builds of the SDK.
|
|
238
|
-
}
|
|
239
|
-
setConnected(true);
|
|
240
|
-
resyncTracks();
|
|
241
|
-
callbacksRef.current.onConnected?.();
|
|
242
|
-
})();
|
|
243
|
-
return () => {
|
|
244
|
-
disposed = true;
|
|
245
|
-
try {
|
|
246
|
-
room.removeAllListeners?.();
|
|
247
|
-
void room.disconnect();
|
|
248
|
-
} catch {
|
|
249
|
-
// Already gone.
|
|
250
|
-
}
|
|
251
|
-
try {
|
|
252
|
-
void AudioSession.stopAudioSession();
|
|
253
|
-
} catch {
|
|
254
|
-
// Never let teardown throw, it runs during unmount.
|
|
255
|
-
}
|
|
256
|
-
roomRef.current = null;
|
|
257
|
-
};
|
|
258
|
-
// Connect exactly once per (url, token, kind). Everything else is read
|
|
259
|
-
// through refs, see callbacksRef above.
|
|
260
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
261
|
-
}, [runtime, livekitUrl, token, kind]);
|
|
262
|
-
|
|
263
|
-
// ---- controls --------------------------------------------------------
|
|
264
|
-
|
|
265
|
-
const toggleMic = useCallback(async () => {
|
|
266
|
-
const room = roomRef.current;
|
|
267
|
-
if (!room) {
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
const next = !micOn;
|
|
271
|
-
setMicOn(next);
|
|
272
|
-
try {
|
|
273
|
-
await room.localParticipant.setMicrophoneEnabled(next);
|
|
274
|
-
} catch {
|
|
275
|
-
setMicOn(!next);
|
|
276
|
-
}
|
|
277
|
-
}, [micOn]);
|
|
278
|
-
const toggleCamera = useCallback(async () => {
|
|
279
|
-
const room = roomRef.current;
|
|
280
|
-
if (!room) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
const next = !cameraOn;
|
|
284
|
-
setCameraOn(next);
|
|
285
|
-
try {
|
|
286
|
-
await room.localParticipant.setCameraEnabled(next);
|
|
287
|
-
resyncTracks();
|
|
288
|
-
} catch {
|
|
289
|
-
setCameraOn(!next);
|
|
290
|
-
}
|
|
291
|
-
}, [cameraOn, resyncTracks]);
|
|
292
|
-
const switchCamera = useCallback(async () => {
|
|
293
|
-
const room = roomRef.current;
|
|
294
|
-
if (!room || !runtime) {
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
try {
|
|
298
|
-
const publication = room.localParticipant.getTrackPublication?.(runtime.Track.Source.Camera);
|
|
299
|
-
const track = publication?.track;
|
|
300
|
-
// `restartTrack` with the flipped facingMode is the portable way to
|
|
301
|
-
// switch cameras; the RN SDK's `_switchCamera` helper is internal and
|
|
302
|
-
// has moved between versions.
|
|
303
|
-
await track?.restartTrack?.({
|
|
304
|
-
facingMode: track?.mediaStreamTrack?.getSettings?.().facingMode === 'environment' ? 'user' : 'environment'
|
|
305
|
-
});
|
|
306
|
-
resyncTracks();
|
|
307
|
-
} catch {
|
|
308
|
-
// Device has a single camera, or the track is mid-restart.
|
|
309
|
-
}
|
|
310
|
-
}, [runtime, resyncTracks]);
|
|
311
|
-
const toggleSpeaker = useCallback(async () => {
|
|
312
|
-
const next = !speakerOn;
|
|
313
|
-
setSpeakerOn(next);
|
|
314
|
-
if (!runtime) {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
try {
|
|
318
|
-
if (Platform.OS === 'android') {
|
|
319
|
-
// NB: not `setAndroidAudioConfiguration` — that method has never
|
|
320
|
-
// existed on AudioSession (the old `?.` call silently no-oped, so
|
|
321
|
-
// the speaker icon toggled while audio stayed on the earpiece).
|
|
322
|
-
// `selectAudioOutput` routes directly; on Android its device ids
|
|
323
|
-
// are the literal strings 'speaker' / 'earpiece'.
|
|
324
|
-
await runtime.AudioSession.selectAudioOutput(next ? 'speaker' : 'earpiece');
|
|
325
|
-
} else {
|
|
326
|
-
await runtime.AudioSession.setAppleAudioConfiguration?.({
|
|
327
|
-
audioCategory: 'playAndRecord',
|
|
328
|
-
audioCategoryOptions: next ? ['defaultToSpeaker'] : [],
|
|
329
|
-
audioMode: next ? 'videoChat' : 'voiceChat'
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
} catch {
|
|
333
|
-
setSpeakerOn(!next);
|
|
334
|
-
}
|
|
335
|
-
}, [runtime, speakerOn]);
|
|
336
|
-
|
|
337
|
-
// ---- render ----------------------------------------------------------
|
|
338
|
-
|
|
67
|
+
const insets = useSafeAreaInsets();
|
|
68
|
+
const runtime = loadLiveKit();
|
|
69
|
+
const session = useCallSession();
|
|
70
|
+
const selfView = useDraggable();
|
|
71
|
+
const elapsed = useSharedCallElapsed(session.anchorAt);
|
|
339
72
|
if (!runtime) {
|
|
340
73
|
return /*#__PURE__*/_jsx(View, {
|
|
341
74
|
style: [styles.canvas, styles.centered],
|
|
@@ -348,22 +81,57 @@ export const VideoCallSession = ({
|
|
|
348
81
|
const {
|
|
349
82
|
VideoTrack
|
|
350
83
|
} = runtime;
|
|
351
|
-
const
|
|
84
|
+
const {
|
|
85
|
+
connected,
|
|
86
|
+
remoteJoined,
|
|
87
|
+
remoteVideo,
|
|
88
|
+
localVideo,
|
|
89
|
+
micOn,
|
|
90
|
+
cameraOn,
|
|
91
|
+
speakerOn,
|
|
92
|
+
deviceError,
|
|
93
|
+
remoteMicMuted
|
|
94
|
+
} = session;
|
|
95
|
+
// The call slice's name (the chat title) first, as web does: LiveKit's
|
|
96
|
+
// participant name is often just the opaque xmpp identity.
|
|
97
|
+
const displayName = peerName || session.remoteName || t('call.unknownCaller');
|
|
352
98
|
const isVideo = kind === 'video';
|
|
353
99
|
const showRemoteVideo = isVideo && !!remoteVideo;
|
|
100
|
+
const statusText = elapsed !== null ? formatCallDuration(elapsed) : !connected ? t('call.status.connecting') : !remoteJoined ? t('call.status.waitingForPeer') : t('call.status.inCall');
|
|
101
|
+
const peerMutedLabel = t('call.peerMuted', {
|
|
102
|
+
name: displayName
|
|
103
|
+
});
|
|
354
104
|
if (minimized) {
|
|
355
105
|
return /*#__PURE__*/_jsxs(View, {
|
|
356
106
|
style: styles.miniBar,
|
|
357
|
-
children: [/*#__PURE__*/
|
|
107
|
+
children: [/*#__PURE__*/_jsx(ProfileImagePlaceholder, {
|
|
108
|
+
name: displayName,
|
|
109
|
+
size: 36
|
|
110
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
358
111
|
style: styles.miniInfo,
|
|
359
112
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
360
113
|
numberOfLines: 1,
|
|
361
114
|
style: styles.miniName,
|
|
362
115
|
children: displayName
|
|
363
116
|
}), /*#__PURE__*/_jsx(Text, {
|
|
117
|
+
numberOfLines: 1,
|
|
364
118
|
style: styles.miniStatus,
|
|
365
|
-
children:
|
|
119
|
+
children: remoteMicMuted ? `${statusText} · ${peerMutedLabel}` : statusText
|
|
366
120
|
})]
|
|
121
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
122
|
+
accessibilityRole: "button",
|
|
123
|
+
accessibilityLabel: micOn ? t('call.action.muteMic') : t('call.action.unmuteMic'),
|
|
124
|
+
onPress: toggleCallMic,
|
|
125
|
+
style: [styles.miniButton, {
|
|
126
|
+
backgroundColor: micOn ? CONTROL_IDLE : DANGER
|
|
127
|
+
}],
|
|
128
|
+
children: micOn ? icons?.micOn ?? /*#__PURE__*/_jsx(MicOnIcon, {
|
|
129
|
+
color: TEXT_ON_DARK,
|
|
130
|
+
size: 20
|
|
131
|
+
}) : icons?.micOff ?? /*#__PURE__*/_jsx(MicOffIcon, {
|
|
132
|
+
color: TEXT_ON_DARK,
|
|
133
|
+
size: 20
|
|
134
|
+
})
|
|
367
135
|
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
368
136
|
accessibilityRole: "button",
|
|
369
137
|
accessibilityLabel: t('call.action.expand'),
|
|
@@ -391,16 +159,7 @@ export const VideoCallSession = ({
|
|
|
391
159
|
}
|
|
392
160
|
return /*#__PURE__*/_jsxs(View, {
|
|
393
161
|
style: styles.canvas,
|
|
394
|
-
children: [
|
|
395
|
-
style: styles.deviceErrorBanner,
|
|
396
|
-
onPress: () => setDeviceError(null),
|
|
397
|
-
accessibilityRole: "button",
|
|
398
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
399
|
-
style: styles.deviceErrorText,
|
|
400
|
-
numberOfLines: 2,
|
|
401
|
-
children: deviceError
|
|
402
|
-
})
|
|
403
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
162
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
404
163
|
style: styles.stage,
|
|
405
164
|
children: [showRemoteVideo ? /*#__PURE__*/_jsx(VideoTrack, {
|
|
406
165
|
trackRef: remoteVideo,
|
|
@@ -416,14 +175,50 @@ export const VideoCallSession = ({
|
|
|
416
175
|
children: displayName
|
|
417
176
|
}), /*#__PURE__*/_jsx(Text, {
|
|
418
177
|
style: styles.stageStatus,
|
|
419
|
-
children:
|
|
178
|
+
children: isVideo && remoteJoined && elapsed !== null ? t('call.status.cameraOff') : statusText
|
|
420
179
|
}), !connected && /*#__PURE__*/_jsx(ActivityIndicator, {
|
|
421
180
|
color: primaryColor,
|
|
422
181
|
style: styles.spinner,
|
|
423
182
|
size: "small"
|
|
183
|
+
}), !micOn && /*#__PURE__*/_jsx(View, {
|
|
184
|
+
style: [styles.pill, styles.pillDanger],
|
|
185
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
186
|
+
style: styles.pillDangerText,
|
|
187
|
+
children: t('call.selfMuted')
|
|
188
|
+
})
|
|
189
|
+
}), remoteMicMuted && !showRemoteVideo && /*#__PURE__*/_jsx(View, {
|
|
190
|
+
style: styles.pill,
|
|
191
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
192
|
+
style: styles.pillText,
|
|
193
|
+
children: peerMutedLabel
|
|
194
|
+
})
|
|
424
195
|
})]
|
|
425
|
-
}),
|
|
426
|
-
style: styles.
|
|
196
|
+
}), showRemoteVideo && /*#__PURE__*/_jsxs(View, {
|
|
197
|
+
style: [styles.header, {
|
|
198
|
+
paddingTop: 14 + insets.top
|
|
199
|
+
}],
|
|
200
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
201
|
+
style: styles.headerName,
|
|
202
|
+
numberOfLines: 1,
|
|
203
|
+
children: displayName
|
|
204
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
205
|
+
style: styles.headerStatus,
|
|
206
|
+
children: statusText
|
|
207
|
+
}), remoteMicMuted && /*#__PURE__*/_jsxs(View, {
|
|
208
|
+
style: [styles.pill, styles.headerPill],
|
|
209
|
+
children: [/*#__PURE__*/_jsx(MicOffIcon, {
|
|
210
|
+
color: TEXT_ON_DARK,
|
|
211
|
+
size: 14
|
|
212
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
213
|
+
style: styles.pillText,
|
|
214
|
+
children: peerMutedLabel
|
|
215
|
+
})]
|
|
216
|
+
})]
|
|
217
|
+
}), isVideo && cameraOn && localVideo && /*#__PURE__*/_jsx(Animated.View, {
|
|
218
|
+
...selfView.panHandlers,
|
|
219
|
+
style: [styles.pip, {
|
|
220
|
+
top: 16 + insets.top
|
|
221
|
+
}, selfView.style],
|
|
427
222
|
children: /*#__PURE__*/_jsx(VideoTrack, {
|
|
428
223
|
trackRef: localVideo,
|
|
429
224
|
style: styles.pipVideo,
|
|
@@ -434,18 +229,33 @@ export const VideoCallSession = ({
|
|
|
434
229
|
accessibilityRole: "button",
|
|
435
230
|
accessibilityLabel: t('call.action.minimize'),
|
|
436
231
|
onPress: onToggleMinimize,
|
|
437
|
-
style: styles.minimizeButton,
|
|
232
|
+
style: [styles.minimizeButton, {
|
|
233
|
+
top: 16 + insets.top
|
|
234
|
+
}],
|
|
438
235
|
children: /*#__PURE__*/_jsx(MinimizeIcon, {
|
|
439
236
|
color: TEXT_ON_DARK,
|
|
440
237
|
size: 20
|
|
441
238
|
})
|
|
239
|
+
}), !!deviceError && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
240
|
+
style: [styles.deviceErrorBanner, {
|
|
241
|
+
top: 64 + insets.top
|
|
242
|
+
}],
|
|
243
|
+
onPress: dismissCallDeviceError,
|
|
244
|
+
accessibilityRole: "button",
|
|
245
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
246
|
+
style: styles.deviceErrorText,
|
|
247
|
+
numberOfLines: 2,
|
|
248
|
+
children: deviceError
|
|
249
|
+
})
|
|
442
250
|
})]
|
|
443
251
|
}), /*#__PURE__*/_jsxs(View, {
|
|
444
|
-
style: styles.controls,
|
|
252
|
+
style: [styles.controls, {
|
|
253
|
+
paddingBottom: 20 + insets.bottom
|
|
254
|
+
}],
|
|
445
255
|
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
446
256
|
accessibilityRole: "button",
|
|
447
257
|
accessibilityLabel: micOn ? t('call.action.muteMic') : t('call.action.unmuteMic'),
|
|
448
|
-
onPress:
|
|
258
|
+
onPress: toggleCallMic,
|
|
449
259
|
style: [styles.controlButton, {
|
|
450
260
|
backgroundColor: micOn ? CONTROL_IDLE : DANGER
|
|
451
261
|
}],
|
|
@@ -457,7 +267,7 @@ export const VideoCallSession = ({
|
|
|
457
267
|
}), isVideo && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
458
268
|
accessibilityRole: "button",
|
|
459
269
|
accessibilityLabel: cameraOn ? t('call.action.stopCamera') : t('call.action.startCamera'),
|
|
460
|
-
onPress:
|
|
270
|
+
onPress: toggleCallCamera,
|
|
461
271
|
style: [styles.controlButton, {
|
|
462
272
|
backgroundColor: cameraOn ? CONTROL_IDLE : DANGER
|
|
463
273
|
}],
|
|
@@ -469,7 +279,7 @@ export const VideoCallSession = ({
|
|
|
469
279
|
}), isVideo && cameraOn && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
470
280
|
accessibilityRole: "button",
|
|
471
281
|
accessibilityLabel: t('call.action.switchCamera'),
|
|
472
|
-
onPress:
|
|
282
|
+
onPress: switchCallCamera,
|
|
473
283
|
style: [styles.controlButton, {
|
|
474
284
|
backgroundColor: CONTROL_IDLE
|
|
475
285
|
}],
|
|
@@ -479,7 +289,7 @@ export const VideoCallSession = ({
|
|
|
479
289
|
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
480
290
|
accessibilityRole: "button",
|
|
481
291
|
accessibilityLabel: speakerOn ? t('call.action.speakerOff') : t('call.action.speakerOn'),
|
|
482
|
-
onPress:
|
|
292
|
+
onPress: toggleCallSpeaker,
|
|
483
293
|
style: [styles.controlButton, {
|
|
484
294
|
backgroundColor: CONTROL_IDLE
|
|
485
295
|
}],
|
|
@@ -509,7 +319,8 @@ const styles = StyleSheet.create({
|
|
|
509
319
|
flex: 1,
|
|
510
320
|
alignItems: 'center',
|
|
511
321
|
justifyContent: 'center',
|
|
512
|
-
gap: 12
|
|
322
|
+
gap: 12,
|
|
323
|
+
paddingHorizontal: 24
|
|
513
324
|
},
|
|
514
325
|
stage: {
|
|
515
326
|
flex: 1,
|
|
@@ -523,26 +334,75 @@ const styles = StyleSheet.create({
|
|
|
523
334
|
stageName: {
|
|
524
335
|
color: TEXT_ON_DARK,
|
|
525
336
|
fontSize: 22,
|
|
526
|
-
marginTop: 16
|
|
337
|
+
marginTop: 16,
|
|
338
|
+
textAlign: 'center'
|
|
527
339
|
},
|
|
528
340
|
stageStatus: {
|
|
529
341
|
color: TEXT_MUTED,
|
|
530
|
-
fontSize: 14
|
|
342
|
+
fontSize: 14,
|
|
343
|
+
fontVariant: ['tabular-nums']
|
|
531
344
|
},
|
|
532
345
|
spinner: {
|
|
533
346
|
marginTop: 8
|
|
534
347
|
},
|
|
348
|
+
header: {
|
|
349
|
+
position: 'absolute',
|
|
350
|
+
top: 0,
|
|
351
|
+
left: 0,
|
|
352
|
+
right: 0,
|
|
353
|
+
paddingHorizontal: 64,
|
|
354
|
+
paddingBottom: 14,
|
|
355
|
+
alignItems: 'center',
|
|
356
|
+
backgroundColor: 'rgba(0,0,0,0.35)',
|
|
357
|
+
gap: 2
|
|
358
|
+
},
|
|
359
|
+
headerName: {
|
|
360
|
+
color: TEXT_ON_DARK,
|
|
361
|
+
fontSize: 15,
|
|
362
|
+
fontWeight: '600'
|
|
363
|
+
},
|
|
364
|
+
headerStatus: {
|
|
365
|
+
color: 'rgba(255,255,255,0.7)',
|
|
366
|
+
fontSize: 13,
|
|
367
|
+
fontVariant: ['tabular-nums']
|
|
368
|
+
},
|
|
369
|
+
headerPill: {
|
|
370
|
+
marginTop: 4,
|
|
371
|
+
backgroundColor: 'rgba(0,0,0,0.45)'
|
|
372
|
+
},
|
|
373
|
+
pill: {
|
|
374
|
+
flexDirection: 'row',
|
|
375
|
+
alignItems: 'center',
|
|
376
|
+
gap: 6,
|
|
377
|
+
paddingVertical: 4,
|
|
378
|
+
paddingHorizontal: 10,
|
|
379
|
+
borderRadius: 999,
|
|
380
|
+
backgroundColor: CONTROL_IDLE
|
|
381
|
+
},
|
|
382
|
+
pillText: {
|
|
383
|
+
color: TEXT_ON_DARK,
|
|
384
|
+
fontSize: 12
|
|
385
|
+
},
|
|
386
|
+
pillDanger: {
|
|
387
|
+
backgroundColor: 'rgba(229, 57, 53, 0.18)',
|
|
388
|
+
borderWidth: 1,
|
|
389
|
+
borderColor: 'rgba(229, 57, 53, 0.45)'
|
|
390
|
+
},
|
|
391
|
+
pillDangerText: {
|
|
392
|
+
color: '#FF8A80',
|
|
393
|
+
fontSize: 12
|
|
394
|
+
},
|
|
535
395
|
pip: {
|
|
536
396
|
position: 'absolute',
|
|
537
397
|
right: 16,
|
|
538
|
-
top: 16,
|
|
539
398
|
width: 104,
|
|
540
399
|
height: 148,
|
|
541
400
|
borderRadius: 12,
|
|
542
401
|
overflow: 'hidden',
|
|
543
402
|
backgroundColor: '#000',
|
|
544
403
|
borderWidth: 1,
|
|
545
|
-
borderColor: 'rgba(255,255,255,0.18)'
|
|
404
|
+
borderColor: 'rgba(255,255,255,0.18)',
|
|
405
|
+
zIndex: 5
|
|
546
406
|
},
|
|
547
407
|
pipVideo: {
|
|
548
408
|
flex: 1
|
|
@@ -550,20 +410,20 @@ const styles = StyleSheet.create({
|
|
|
550
410
|
minimizeButton: {
|
|
551
411
|
position: 'absolute',
|
|
552
412
|
left: 16,
|
|
553
|
-
top: 16,
|
|
554
413
|
width: 36,
|
|
555
414
|
height: 36,
|
|
556
415
|
borderRadius: 18,
|
|
557
416
|
alignItems: 'center',
|
|
558
417
|
justifyContent: 'center',
|
|
559
|
-
backgroundColor: 'rgba(0,0,0,0.45)'
|
|
418
|
+
backgroundColor: 'rgba(0,0,0,0.45)',
|
|
419
|
+
zIndex: 6
|
|
560
420
|
},
|
|
561
421
|
controls: {
|
|
562
422
|
flexDirection: 'row',
|
|
563
423
|
alignItems: 'center',
|
|
564
424
|
justifyContent: 'center',
|
|
565
425
|
gap: 16,
|
|
566
|
-
|
|
426
|
+
paddingTop: 20,
|
|
567
427
|
paddingHorizontal: 16
|
|
568
428
|
},
|
|
569
429
|
controlButton: {
|
|
@@ -582,11 +442,10 @@ const styles = StyleSheet.create({
|
|
|
582
442
|
textAlign: 'center',
|
|
583
443
|
paddingHorizontal: 24
|
|
584
444
|
},
|
|
585
|
-
// Dismissible, non-blocking
|
|
586
|
-
//
|
|
445
|
+
// Dismissible, non-blocking: the call is still up underneath; this only
|
|
446
|
+
// flags that camera/mic didn't come up.
|
|
587
447
|
deviceErrorBanner: {
|
|
588
448
|
position: 'absolute',
|
|
589
|
-
top: 16,
|
|
590
449
|
left: 16,
|
|
591
450
|
right: 16,
|
|
592
451
|
zIndex: 10,
|
|
@@ -603,9 +462,9 @@ const styles = StyleSheet.create({
|
|
|
603
462
|
miniBar: {
|
|
604
463
|
flexDirection: 'row',
|
|
605
464
|
alignItems: 'center',
|
|
606
|
-
gap:
|
|
607
|
-
paddingHorizontal:
|
|
608
|
-
paddingVertical:
|
|
465
|
+
gap: 10,
|
|
466
|
+
paddingHorizontal: 12,
|
|
467
|
+
paddingVertical: 10,
|
|
609
468
|
borderRadius: 16,
|
|
610
469
|
backgroundColor: SURFACE_DARK
|
|
611
470
|
},
|
|
@@ -619,7 +478,8 @@ const styles = StyleSheet.create({
|
|
|
619
478
|
},
|
|
620
479
|
miniStatus: {
|
|
621
480
|
color: TEXT_MUTED,
|
|
622
|
-
fontSize: 12
|
|
481
|
+
fontSize: 12,
|
|
482
|
+
fontVariant: ['tabular-nums']
|
|
623
483
|
},
|
|
624
484
|
miniButton: {
|
|
625
485
|
width: 40,
|