agora-appbuilder-core 4.1.9 → 4.1.11-beta.2

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 (58) hide show
  1. package/package.json +2 -2
  2. package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +1 -3
  3. package/template/agora-rn-uikit/src/Contexts/RtcContext.tsx +1 -2
  4. package/template/agora-rn-uikit/src/Reducer/index.ts +0 -2
  5. package/template/agora-rn-uikit/src/Rtc/Join.tsx +11 -25
  6. package/template/agora-rn-uikit/src/RtcConfigure.tsx +1 -14
  7. package/template/agora-rn-uikit/src/Utils/isBotUser.ts +1 -1
  8. package/template/android/app/build.gradle +0 -7
  9. package/template/bridge/rtm/web/Types.ts +0 -183
  10. package/template/bridge/rtm/web/index.ts +491 -423
  11. package/template/defaultConfig.js +3 -3
  12. package/template/ios/Podfile +0 -41
  13. package/template/package.json +5 -5
  14. package/template/src/assets/font-styles.css +4 -0
  15. package/template/src/assets/fonts/icomoon.ttf +0 -0
  16. package/template/src/assets/selection.json +1 -1
  17. package/template/src/atoms/ActionMenu.tsx +93 -13
  18. package/template/src/atoms/CustomIcon.tsx +1 -0
  19. package/template/src/atoms/DropDownMulti.tsx +80 -29
  20. package/template/src/atoms/Input.tsx +2 -1
  21. package/template/src/components/Controls.tsx +148 -143
  22. package/template/src/components/EventsConfigure.tsx +152 -97
  23. package/template/src/components/RTMConfigure.tsx +426 -644
  24. package/template/src/components/precall/joinCallBtn.native.tsx +7 -2
  25. package/template/src/components/precall/joinCallBtn.tsx +7 -2
  26. package/template/src/components/precall/joinWaitingRoomBtn.native.tsx +8 -3
  27. package/template/src/components/precall/joinWaitingRoomBtn.tsx +22 -4
  28. package/template/src/components/precall/textInput.tsx +45 -22
  29. package/template/src/components/precall/usePreCall.tsx +7 -0
  30. package/template/src/components/room-info/useRoomInfo.tsx +5 -0
  31. package/template/src/language/default-labels/videoCallScreenLabels.ts +27 -4
  32. package/template/src/pages/video-call/ActionSheetContent.tsx +77 -77
  33. package/template/src/pages/video-call/SidePanelHeader.tsx +81 -36
  34. package/template/src/rtm/RTMEngine.ts +33 -130
  35. package/template/src/rtm-events/constants.ts +6 -0
  36. package/template/src/rtm-events-api/Events.ts +30 -106
  37. package/template/src/subComponents/caption/Caption.tsx +48 -7
  38. package/template/src/subComponents/caption/CaptionContainer.tsx +324 -51
  39. package/template/src/subComponents/caption/CaptionIcon.tsx +35 -34
  40. package/template/src/subComponents/caption/CaptionText.tsx +103 -2
  41. package/template/src/subComponents/caption/LanguageSelectorPopup.tsx +179 -69
  42. package/template/src/subComponents/caption/Transcript.tsx +46 -11
  43. package/template/src/subComponents/caption/TranscriptIcon.tsx +27 -35
  44. package/template/src/subComponents/caption/TranscriptText.tsx +78 -3
  45. package/template/src/subComponents/caption/proto/ptoto.js +38 -4
  46. package/template/src/subComponents/caption/proto/test.proto +34 -19
  47. package/template/src/subComponents/caption/useCaption.tsx +753 -10
  48. package/template/src/subComponents/caption/useSTTAPI.tsx +118 -205
  49. package/template/src/subComponents/caption/useStreamMessageUtils.native.ts +152 -33
  50. package/template/src/subComponents/caption/useStreamMessageUtils.ts +165 -34
  51. package/template/src/subComponents/caption/utils.ts +171 -3
  52. package/template/src/utils/SdkEvents.ts +3 -0
  53. package/template/src/utils/useEndCall.ts +3 -5
  54. package/template/src/utils/useSpeechToText.ts +31 -20
  55. package/template/agora-rn-uikit/src/Reducer/Spotlight.ts +0 -11
  56. package/template/agora-rn-uikit/src/Reducer/UserBanned.ts +0 -11
  57. package/template/bridge/rtm/web/index-legacy.ts +0 -540
  58. package/template/src/components/RTMConfigure-legacy.tsx +0 -848
@@ -8,7 +8,11 @@ import {isWebInternal} from '../../utils/common';
8
8
  import useStreamMessageUtils from './useStreamMessageUtils';
9
9
  import hexadecimalTransparency from '../../utils/hexadecimalTransparency';
10
10
  import {useString} from '../../utils/useString';
11
- import {sttSettingSpokenLanguageText} from '../../language/default-labels/videoCallScreenLabels';
11
+ import {useLocalUid} from '../../../agora-rn-uikit';
12
+ import {
13
+ sttSettingSpokenLanguageText,
14
+ sttSettingTranslationLanguageText,
15
+ } from '../../language/default-labels/videoCallScreenLabels';
12
16
 
13
17
  export type WebStreamMessageArgs = [number, Uint8Array];
14
18
  export type NativeStreamMessageArgs = [
@@ -33,13 +37,22 @@ const Caption: React.FC<CaptionProps> = ({
33
37
  const {RtcEngineUnsafe} = useRtc();
34
38
  const {
35
39
  isLangChangeInProgress,
40
+ isTranslationChangeInProgress,
36
41
  captionObj, //state for current live caption for all users
37
42
  isSTTListenerAdded,
38
43
  setIsSTTListenerAdded,
39
44
  activeSpeakerRef,
40
45
  prevSpeakerRef,
46
+ getBotOwnerUid,
47
+ isSTTActive,
48
+ translationConfig,
49
+ remoteSpokenLanguages,
41
50
  } = useCaption();
51
+ const currentUserUid = useLocalUid();
42
52
  const ssLabel = useString(sttSettingSpokenLanguageText)();
53
+ const stLabel = useString<boolean>(sttSettingTranslationLanguageText)(
54
+ isSTTActive,
55
+ );
43
56
  const {streamMessageCallback} = useStreamMessageUtils();
44
57
  const {defaultContent} = useContent();
45
58
 
@@ -66,18 +79,20 @@ const Caption: React.FC<CaptionProps> = ({
66
79
  // eslint-disable-next-line react-hooks/exhaustive-deps
67
80
  }, []);
68
81
 
69
- if (isLangChangeInProgress)
82
+ if (isLangChangeInProgress || isTranslationChangeInProgress) {
70
83
  return (
71
84
  <Loading
72
- text={ssLabel}
85
+ text={stLabel}
73
86
  background="transparent"
74
87
  indicatorColor={$config.FONT_COLOR + hexadecimalTransparency['70%']}
75
88
  textColor={$config.FONT_COLOR + hexadecimalTransparency['70%']}
76
89
  />
77
90
  );
91
+ }
78
92
 
79
- console.log('current speaker uid', activeSpeakerRef.current);
80
- console.log('prev current uid ', prevSpeakerRef.current);
93
+ console.log('[STT_PER_USER_BOT] speaker uid', activeSpeakerRef.current);
94
+ console.log('[STT_PER_USER_BOT] prev current uid ', prevSpeakerRef.current);
95
+ console.log('[STT_PER_USER_BOT] captionObj ', captionObj);
81
96
 
82
97
  const speakerCount = prevSpeakerRef.current === '' ? 1 : 2;
83
98
 
@@ -104,8 +119,12 @@ const Caption: React.FC<CaptionProps> = ({
104
119
  {captionObj[prevSpeakerRef.current] &&
105
120
  captionObj[prevSpeakerRef.current].text ? (
106
121
  <CaptionText
107
- user={defaultContent[prevSpeakerRef.current].name || 'Speaker'}
122
+ user={
123
+ defaultContent[getBotOwnerUid(prevSpeakerRef.current)]?.name ||
124
+ 'Speaker'
125
+ }
108
126
  value={captionObj[prevSpeakerRef.current].text}
127
+ translations={captionObj[prevSpeakerRef.current].translations}
109
128
  activeSpeakersCount={speakerCount}
110
129
  isActiveSpeaker={false}
111
130
  activelinesAvailable={activelinesAvailable}
@@ -114,6 +133,15 @@ const Caption: React.FC<CaptionProps> = ({
114
133
  setInActiveLinesAvaialble={setInActiveLinesAvaialble}
115
134
  captionUserStyle={captionUserStyle}
116
135
  captionTextStyle={captionTextStyle}
136
+ speakerUid={getBotOwnerUid(prevSpeakerRef.current)}
137
+ userLocalUid={currentUserUid}
138
+ spokenLanguageCode={
139
+ getBotOwnerUid(prevSpeakerRef.current) === currentUserUid
140
+ ? translationConfig.source[0]
141
+ : remoteSpokenLanguages[
142
+ getBotOwnerUid(prevSpeakerRef.current)
143
+ ]
144
+ }
117
145
  />
118
146
  ) : (
119
147
  <></>
@@ -121,8 +149,12 @@ const Caption: React.FC<CaptionProps> = ({
121
149
  {captionObj[activeSpeakerRef.current] &&
122
150
  captionObj[activeSpeakerRef.current].text ? (
123
151
  <CaptionText
124
- user={defaultContent[activeSpeakerRef.current].name || 'Speaker'}
152
+ user={
153
+ defaultContent[getBotOwnerUid(activeSpeakerRef.current)]
154
+ ?.name || 'Speaker'
155
+ }
125
156
  value={captionObj[activeSpeakerRef.current].text}
157
+ translations={captionObj[activeSpeakerRef.current].translations}
126
158
  activeSpeakersCount={speakerCount}
127
159
  isActiveSpeaker={true}
128
160
  activelinesAvailable={activelinesAvailable}
@@ -131,6 +163,15 @@ const Caption: React.FC<CaptionProps> = ({
131
163
  setInActiveLinesAvaialble={setInActiveLinesAvaialble}
132
164
  captionUserStyle={captionUserStyle}
133
165
  captionTextStyle={captionTextStyle}
166
+ speakerUid={getBotOwnerUid(activeSpeakerRef.current)}
167
+ userLocalUid={currentUserUid}
168
+ spokenLanguageCode={
169
+ getBotOwnerUid(activeSpeakerRef.current) === currentUserUid
170
+ ? translationConfig.source[0]
171
+ : remoteSpokenLanguages[
172
+ getBotOwnerUid(activeSpeakerRef.current)
173
+ ]
174
+ }
134
175
  />
135
176
  ) : (
136
177
  <></>
@@ -9,7 +9,7 @@ import {
9
9
  import React from 'react';
10
10
 
11
11
  import Caption from './Caption';
12
- import {useCaption} from './useCaption';
12
+ import {useCaption, LanguageTranslationConfig} from './useCaption';
13
13
  import ThemeConfig from '../../../src/theme';
14
14
  import {
15
15
  calculatePosition,
@@ -27,6 +27,8 @@ import LanguageSelectorPopup from './LanguageSelectorPopup';
27
27
  import useSTTAPI from './useSTTAPI';
28
28
  import useGetName from '../../utils/useGetName';
29
29
  import {useRoomInfo} from 'customization-api';
30
+ import {useLocalUid} from '../../../agora-rn-uikit';
31
+ import {getLanguageLabel} from './utils';
30
32
  import {
31
33
  SIDE_PANEL_MAX_WIDTH,
32
34
  SIDE_PANEL_GAP,
@@ -35,11 +37,13 @@ import {
35
37
  MOBILE_CAPTION_CONTAINER_HEIGHT,
36
38
  } from '../../../src/components/CommonStyles';
37
39
  import useCaptionWidth from './useCaptionWidth';
38
- import {LanguageType} from './utils';
40
+ import {LanguageType, langData, mergeTranslationConfigs} from './utils';
39
41
  import hexadecimalTransparency from '../../utils/hexadecimalTransparency';
40
42
  import {useString} from '../../utils/useString';
41
43
  import {
42
44
  sttChangeSpokenLanguageText,
45
+ sttOriginalTranslatedText,
46
+ sttStopTranslationText,
43
47
  toolbarItemCaptionText,
44
48
  } from '../../language/default-labels/videoCallScreenLabels';
45
49
  import {logger, LogSource} from '../../logger/AppBuilderLogger';
@@ -56,8 +60,11 @@ const CaptionContainer: React.FC<CaptionContainerProps> = ({
56
60
  captionTextStyle = {},
57
61
  }) => {
58
62
  const moreIconRef = React.useRef<View>(null);
63
+ const langSelectIconRef = React.useRef<View>(null);
59
64
  const [actionMenuVisible, setActionMenuVisible] =
60
65
  React.useState<boolean>(false);
66
+ const [langActionMenuVisible, setLangActionMenuVisible] =
67
+ React.useState<boolean>(false);
61
68
  const [isHovered, setIsHovered] = React.useState<boolean>(false);
62
69
  const isDesktop = useIsDesktop();
63
70
  const isSmall = useIsSmall();
@@ -115,11 +122,23 @@ const CaptionContainer: React.FC<CaptionContainerProps> = ({
115
122
  btnRef={moreIconRef}
116
123
  />
117
124
 
125
+ {/* <TranslateActionMenu
126
+ actionMenuVisible={langActionMenuVisible}
127
+ setActionMenuVisible={setLangActionMenuVisible}
128
+ btnRef={langSelectIconRef}
129
+ /> */}
130
+
118
131
  {(isHovered || isMobileUA()) && !isLangChangeInProgress && (
119
- <MoreMenu
120
- ref={moreIconRef}
121
- setActionMenuVisible={setActionMenuVisible}
122
- />
132
+ <>
133
+ {/* <LanguageSelectMenu
134
+ ref={langSelectIconRef}
135
+ setActionMenuVisible={setLangActionMenuVisible}
136
+ /> */}
137
+ <MoreMenu
138
+ ref={moreIconRef}
139
+ setActionMenuVisible={setActionMenuVisible}
140
+ />
141
+ </>
123
142
  )}
124
143
 
125
144
  <Caption
@@ -199,6 +218,55 @@ const MoreMenu = React.forwardRef<View, MoreMenuProps>((props, ref) => {
199
218
  );
200
219
  });
201
220
 
221
+ const LanguageSelectMenu = React.forwardRef<View, MoreMenuProps>(
222
+ (props, ref) => {
223
+ const {setActionMenuVisible} = props;
224
+ const isMobile = isMobileUA();
225
+ return (
226
+ <View
227
+ ref={ref}
228
+ collapsable={false}
229
+ style={{
230
+ width: 32,
231
+ height: 32,
232
+ alignSelf: 'center',
233
+ justifyContent: 'center',
234
+ alignItems: 'center',
235
+ borderRadius: 20,
236
+ position: 'absolute',
237
+ right: isMobile ? 45 : 50,
238
+ top: isMobile ? 3 : 8,
239
+ zIndex: 999,
240
+ }}>
241
+ <IconButton
242
+ hoverEffect={true}
243
+ hoverEffectStyle={{
244
+ backgroundColor:
245
+ $config.CARD_LAYER_5_COLOR + hexadecimalTransparency['25%'],
246
+ borderRadius: 20,
247
+ }}
248
+ iconProps={{
249
+ iconType: 'plain',
250
+ name: 'lang-select',
251
+ iconSize: isMobile ? 18 : 20,
252
+ tintColor: $config.SECONDARY_ACTION_COLOR,
253
+ iconContainerStyle: {
254
+ padding: isMobile ? 6 : 8,
255
+ borderRadius: 20,
256
+ backgroundColor: isMobile
257
+ ? $config.CARD_LAYER_5_COLOR + hexadecimalTransparency['25%']
258
+ : 'transparent',
259
+ },
260
+ }}
261
+ onPress={() => {
262
+ setActionMenuVisible(true);
263
+ }}
264
+ />
265
+ </View>
266
+ );
267
+ },
268
+ );
269
+
202
270
  interface CaptionsActionMenuProps {
203
271
  actionMenuVisible: boolean;
204
272
  setActionMenuVisible: (actionMenuVisible: boolean) => void;
@@ -209,9 +277,15 @@ const CaptionsActionMenu = (props: CaptionsActionMenuProps) => {
209
277
  const {actionMenuVisible, setActionMenuVisible, btnRef} = props;
210
278
  const {
211
279
  setIsCaptionON,
212
- language: prevLang,
280
+ // language: prevLang,
213
281
  isLangChangeInProgress,
214
- setLanguage,
282
+ // setLanguage,
283
+ // selectedTranslationLanguage,
284
+ updateSTTBotSession,
285
+ translationConfig,
286
+ handleTranslateConfigChange,
287
+ captionViewMode,
288
+ setCaptionViewMode,
215
289
  } = useCaption();
216
290
  const actionMenuitems: ActionMenuItem[] = [];
217
291
  const [modalPosition, setModalPosition] = React.useState({});
@@ -219,32 +293,51 @@ const CaptionsActionMenu = (props: CaptionsActionMenuProps) => {
219
293
  const {width: globalWidth, height: globalHeight} = useWindowDimensions();
220
294
  const [isLanguagePopupOpen, setLanguagePopup] =
221
295
  React.useState<boolean>(false);
222
- const {restart} = useSTTAPI();
223
- const username = useGetName();
224
- const {
225
- data: {isHost},
226
- } = useRoomInfo();
296
+ // const {restart} = useSTTAPI();
297
+ // const username = useGetName();
298
+ // const {
299
+ // data: {isHost},
300
+ // sttLanguage,
301
+ // } = useRoomInfo();
227
302
 
228
303
  const changeSpokenLangLabel = useString<boolean>(
229
304
  sttChangeSpokenLanguageText,
230
305
  )();
306
+ const sttStopTranslationLabel = useString<boolean>(sttStopTranslationText)();
307
+ const sttOriginalTranslatedLabel = useString(sttOriginalTranslatedText)();
231
308
 
232
309
  const hideCaptionLabel = useString<boolean>(toolbarItemCaptionText)(true);
233
310
 
234
- // only Host is authorized to start/stop stt
235
- isHost &&
236
- actionMenuitems.push({
237
- icon: 'lang-select',
238
- iconColor: $config.SECONDARY_ACTION_COLOR,
239
- textColor: $config.FONT_COLOR,
240
- title: changeSpokenLangLabel + ' ',
241
- disabled: isLangChangeInProgress,
242
- onPress: () => {
243
- setActionMenuVisible(false);
244
- setLanguagePopup(true);
245
- },
246
- });
311
+ // Anyone can start/stop stt
312
+ actionMenuitems.push({
313
+ icon: 'globe',
314
+ iconColor: $config.SECONDARY_ACTION_COLOR,
315
+ textColor: $config.FONT_COLOR,
316
+ title: changeSpokenLangLabel + ' ',
317
+ disabled: isLangChangeInProgress,
318
+ onPress: () => {
319
+ setActionMenuVisible(false);
320
+ setLanguagePopup(true);
321
+ },
322
+ });
323
+
324
+ // Stop Translation (not STT bot, pass empty targets)
325
+ actionMenuitems.push({
326
+ icon: 'lang-select',
327
+ iconColor: $config.SECONDARY_ACTION_COLOR,
328
+ textColor: $config.FONT_COLOR,
329
+ title: sttStopTranslationLabel,
330
+ disabled: isLangChangeInProgress,
331
+ onPress: async () => {
332
+ setActionMenuVisible(false);
333
+ await updateSTTBotSession({
334
+ source: translationConfig.source,
335
+ targets: [], // Empty targets = no translation
336
+ });
337
+ },
338
+ });
247
339
 
340
+ // Hide Caption Panel
248
341
  actionMenuitems.push({
249
342
  icon: 'captions-off',
250
343
  iconColor: $config.SECONDARY_ACTION_COLOR,
@@ -256,32 +349,32 @@ const CaptionsActionMenu = (props: CaptionsActionMenuProps) => {
256
349
  },
257
350
  });
258
351
 
259
- const onLanguageChange = (langChanged = false, language: LanguageType[]) => {
260
- setLanguagePopup(false);
261
- if (langChanged) {
262
- logger.log(
263
- LogSource.Internals,
264
- 'STT',
265
- `Language changed to ${language}. Restarting STT`,
352
+ // View Mode Options
353
+ actionMenuitems.push({
354
+ icon: 'lang-translate',
355
+ iconColor: $config.SECONDARY_ACTION_COLOR,
356
+ endIcon:
357
+ captionViewMode === 'original-and-translated' ? 'tick-fill' : undefined,
358
+ endIconColor: $config.SEMANTIC_SUCCESS,
359
+ textColor: $config.FONT_COLOR,
360
+ title: sttOriginalTranslatedLabel,
361
+ onPress: () => {
362
+ setCaptionViewMode(
363
+ captionViewMode === 'translated'
364
+ ? 'original-and-translated'
365
+ : 'translated',
266
366
  );
267
- restart(language)
268
- .then(() => {
269
- logger.debug(
270
- LogSource.Internals,
271
- 'STT',
272
- 'stt restarted successfully',
273
- );
274
- })
275
- .catch(error => {
276
- logger.error(
277
- LogSource.Internals,
278
- 'STT',
279
- 'Error in restarting',
280
- error,
281
- );
282
- // Handle the error case
283
- });
284
- }
367
+ setActionMenuVisible(false);
368
+ },
369
+ });
370
+
371
+ const onLanguageChange = async (
372
+ inputTranslateConfig: LanguageTranslationConfig,
373
+ ) => {
374
+ setLanguagePopup(false);
375
+ try {
376
+ await handleTranslateConfigChange(inputTranslateConfig);
377
+ } catch (error) {}
285
378
  };
286
379
 
287
380
  React.useEffect(() => {
@@ -310,6 +403,7 @@ const CaptionsActionMenu = (props: CaptionsActionMenuProps) => {
310
403
  );
311
404
  }
312
405
  }, [actionMenuVisible]);
406
+
313
407
  return (
314
408
  <>
315
409
  <ActionMenu
@@ -328,6 +422,185 @@ const CaptionsActionMenu = (props: CaptionsActionMenuProps) => {
328
422
  );
329
423
  };
330
424
 
425
+ export interface TranslateActionMenuProps {
426
+ actionMenuVisible: boolean;
427
+ setActionMenuVisible: (actionMenuVisible: boolean) => void;
428
+ btnRef: React.RefObject<View>;
429
+ }
430
+
431
+ export const TranslateActionMenu = (props: TranslateActionMenuProps) => {
432
+ const {actionMenuVisible, setActionMenuVisible, btnRef} = props;
433
+ const [modalPosition, setModalPosition] = React.useState({});
434
+ const [isPosCalculated, setIsPosCalculated] = React.useState(false);
435
+ const {width: globalWidth, height: globalHeight} = useWindowDimensions();
436
+ const {
437
+ language: currentSpokenLanguages,
438
+ selectedTranslationLanguage,
439
+ setSelectedTranslationLanguage,
440
+ setMeetingTranscript,
441
+ translationConfig,
442
+ } = useCaption();
443
+ const {update} = useSTTAPI();
444
+ const localUid = useLocalUid();
445
+ const {sttLanguage} = useRoomInfo();
446
+
447
+ // Reset selected translation language if there are no targets configured
448
+ const targetLanguages = translationConfig?.targets || [];
449
+ React.useEffect(() => {
450
+ if (targetLanguages.length === 0 && selectedTranslationLanguage !== '') {
451
+ setSelectedTranslationLanguage('');
452
+ }
453
+ }, [
454
+ targetLanguages.length,
455
+ selectedTranslationLanguage,
456
+ setSelectedTranslationLanguage,
457
+ ]);
458
+
459
+ const actionMenuitems: ActionMenuItem[] = [];
460
+
461
+ actionMenuitems.push({
462
+ iconColor: $config.SECONDARY_ACTION_COLOR,
463
+ textColor: $config.FONT_COLOR,
464
+ title: 'Add Another Translation',
465
+ iconPosition: 'end',
466
+ disabled: true,
467
+ onPress: () => {},
468
+ });
469
+
470
+ const handleTranslationToggle = (targetLanguage: string) => {
471
+ // Simply update the selected translation language locally
472
+ // No API call needed - we're just switching between already-configured target languages
473
+ // const prevTranslationLanguage = selectedTranslationLanguage;
474
+ setSelectedTranslationLanguage(targetLanguage);
475
+
476
+ // // Add translation language change notification to transcript
477
+ // const getLanguageName = (langCode: string) => {
478
+ // if (!langCode) return '';
479
+ // const lang = langData.find(data => data.value === langCode);
480
+ // return lang ? lang.label : langCode;
481
+ // };
482
+
483
+ // const actionText =
484
+ // targetLanguage === ''
485
+ // ? 'turned off translation'
486
+ // : prevTranslationLanguage === ''
487
+ // ? `set the translation language to "${getLanguageName(targetLanguage)}"`
488
+ // : `changed the translation language from "${getLanguageName(
489
+ // prevTranslationLanguage,
490
+ // )}" to "${getLanguageName(targetLanguage)}"`;
491
+
492
+ // setMeetingTranscript(prev => [
493
+ // ...prev,
494
+ // {
495
+ // name: 'translationUpdate',
496
+ // time: new Date().getTime(),
497
+ // uid: `translationUpdate-${localUid}`,
498
+ // text: actionText,
499
+ // },
500
+ // ]);
501
+
502
+ setActionMenuVisible(false);
503
+ };
504
+
505
+ // Check if there are any target languages configured
506
+ if (targetLanguages.length === 0) {
507
+ // No target languages - show a disabled message
508
+ actionMenuitems.push({
509
+ icon: undefined,
510
+ iconColor: $config.FONT_COLOR,
511
+ textColor: $config.FONT_COLOR + hexadecimalTransparency['50%'],
512
+ title: 'No languages configured',
513
+ iconPosition: 'end',
514
+ disabled: true,
515
+ onPress: () => {},
516
+ });
517
+ } else {
518
+ // Show "Off" option and target languages
519
+ actionMenuitems.push({
520
+ icon: selectedTranslationLanguage === '' ? 'tick-fill' : undefined,
521
+ iconColor: $config.PRIMARY_ACTION_BRAND_COLOR,
522
+ textColor: $config.FONT_COLOR,
523
+ title: 'Off',
524
+ iconPosition: 'end',
525
+ onPress: () => handleTranslationToggle(''),
526
+ });
527
+
528
+ // Add selected translation language right after "Off" if one is selected
529
+ if (selectedTranslationLanguage && selectedTranslationLanguage !== '') {
530
+ const selectedLanguage = langData.find(
531
+ lang => lang.value === selectedTranslationLanguage,
532
+ );
533
+ if (selectedLanguage) {
534
+ actionMenuitems.push({
535
+ icon: 'tick-fill',
536
+ iconColor: $config.PRIMARY_ACTION_BRAND_COLOR,
537
+ textColor: $config.FONT_COLOR,
538
+ title: selectedLanguage.label,
539
+ iconPosition: 'end',
540
+ onPress: () => handleTranslationToggle(selectedLanguage.value),
541
+ });
542
+ }
543
+ }
544
+
545
+ // Add remaining Translation language options from translationConfig.targets (excluding the selected one)
546
+ targetLanguages.forEach(targetLangCode => {
547
+ if (targetLangCode !== selectedTranslationLanguage) {
548
+ const language = langData.find(lang => lang.value === targetLangCode);
549
+ if (language) {
550
+ actionMenuitems.push({
551
+ icon: undefined,
552
+ iconColor: $config.PRIMARY_ACTION_BRAND_COLOR,
553
+ textColor: $config.FONT_COLOR,
554
+ title: language.label,
555
+ iconPosition: 'end',
556
+ onPress: () => handleTranslationToggle(language.value),
557
+ });
558
+ }
559
+ }
560
+ });
561
+ }
562
+
563
+ React.useEffect(() => {
564
+ if (actionMenuVisible) {
565
+ btnRef?.current?.measure(
566
+ (
567
+ _fx: number,
568
+ _fy: number,
569
+ localWidth: number,
570
+ localHeight: number,
571
+ px: number,
572
+ py: number,
573
+ ) => {
574
+ const data = calculatePosition({
575
+ px,
576
+ py,
577
+ localWidth,
578
+ localHeight,
579
+ globalHeight,
580
+ globalWidth,
581
+ });
582
+ setModalPosition(data);
583
+ setIsPosCalculated(true);
584
+ },
585
+ );
586
+ }
587
+ }, [actionMenuVisible]);
588
+
589
+ return (
590
+ <ActionMenu
591
+ from={'translation'}
592
+ actionMenuVisible={actionMenuVisible && isPosCalculated}
593
+ setActionMenuVisible={setActionMenuVisible}
594
+ modalPosition={modalPosition}
595
+ items={actionMenuitems}
596
+ containerStyle={{
597
+ maxHeight: Math.min(440, globalHeight * 0.6),
598
+ width: 220,
599
+ }}
600
+ />
601
+ );
602
+ };
603
+
331
604
  export default CaptionContainer;
332
605
 
333
606
  const styles = StyleSheet.create({