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