agora-appbuilder-core 4.1.12 → 4.1.13-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 (27) hide show
  1. package/package.json +1 -1
  2. package/template/defaultConfig.js +2 -2
  3. package/template/src/assets/font-styles.css +4 -0
  4. package/template/src/assets/fonts/icomoon.ttf +0 -0
  5. package/template/src/assets/selection.json +1 -1
  6. package/template/src/atoms/ActionMenu.tsx +1 -1
  7. package/template/src/atoms/CustomIcon.tsx +1 -0
  8. package/template/src/atoms/Popup.tsx +1 -1
  9. package/template/src/components/Controls.tsx +21 -32
  10. package/template/src/language/default-labels/videoCallScreenLabels.ts +9 -9
  11. package/template/src/pages/VideoCall.tsx +2 -1
  12. package/template/src/pages/video-call/ActionSheetContent.tsx +0 -7
  13. package/template/src/pages/video-call/SidePanelHeader.tsx +80 -63
  14. package/template/src/rtm-events/constants.ts +9 -0
  15. package/template/src/subComponents/caption/Caption.tsx +4 -20
  16. package/template/src/subComponents/caption/CaptionContainer.tsx +262 -250
  17. package/template/src/subComponents/caption/CaptionIcon.tsx +6 -4
  18. package/template/src/subComponents/caption/CaptionText.tsx +26 -20
  19. package/template/src/subComponents/caption/LanguageSelectorPopup.tsx +30 -142
  20. package/template/src/subComponents/caption/Transcript.tsx +77 -32
  21. package/template/src/subComponents/caption/TranscriptIcon.tsx +7 -6
  22. package/template/src/subComponents/caption/TranslateActionMenu.tsx +128 -0
  23. package/template/src/subComponents/caption/useCaption.tsx +629 -482
  24. package/template/src/subComponents/caption/useSTTAPI.tsx +25 -4
  25. package/template/src/subComponents/caption/useStreamMessageUtils.native.ts +1 -1
  26. package/template/src/subComponents/caption/useStreamMessageUtils.ts +1 -1
  27. package/template/src/subComponents/caption/utils.ts +48 -40
@@ -52,12 +52,10 @@ const CaptionText = ({
52
52
  setInActiveLinesAvaialble,
53
53
  captionUserStyle = {},
54
54
  captionTextStyle = {},
55
- speakerUid,
56
- userLocalUid,
57
- spokenLanguageCode,
58
55
  }: CaptionTextProps) => {
59
56
  const isMobile = isMobileUA();
60
- const {translationConfig, captionViewMode} = useCaption();
57
+ const {globalSttState, captionViewMode, selectedTranslationLanguageRef} =
58
+ useCaption();
61
59
 
62
60
  const LINE_HEIGHT = isMobile ? MOBILE_LINE_HEIGHT : DESKTOP_LINE_HEIGHT;
63
61
 
@@ -100,7 +98,7 @@ const CaptionText = ({
100
98
  */
101
99
 
102
100
  // Get the appropriate source text for display based on viewer's language preferences
103
- const viewerSourceLanguage = translationConfig.source[0];
101
+ const globalSourceLanguage = globalSttState?.globalSpokenLanguage;
104
102
  // const localUserText = value;
105
103
  // const remoteUserTranslatedText: {
106
104
  // text: string,
@@ -115,13 +113,17 @@ const CaptionText = ({
115
113
  // return value;
116
114
  // };
117
115
 
116
+ // If translation is selected then show both local and remote in that translated language
118
117
  const displayTranslatedViewText = getUserTranslatedText(
119
118
  value,
120
119
  translations,
121
- viewerSourceLanguage,
122
- speakerUid,
123
- userLocalUid,
120
+ globalSourceLanguage,
121
+ selectedTranslationLanguageRef.current,
124
122
  );
123
+ // const displayTranslatedViewText = {
124
+ // value,
125
+ // langCode: getLanguageLabel([globalSourceLanguage]) || '',
126
+ // };
125
127
 
126
128
  /**
127
129
  * ROBUST TEXT EXTRACTION LOGIC
@@ -131,7 +133,9 @@ const CaptionText = ({
131
133
  * - Without translation: 3 lines source (~150-180 chars)
132
134
  */
133
135
  const getLatestTextPortion = (text: string, maxChars: number) => {
134
- if (!text || text.length <= maxChars) return text;
136
+ if (!text || text.length <= maxChars) {
137
+ return text;
138
+ }
135
139
 
136
140
  // Take last maxChars, try to find sentence boundary for cleaner cut
137
141
  const portion = text.slice(-maxChars);
@@ -211,24 +215,26 @@ const CaptionText = ({
211
215
  isAndroid() && {lineHeight: MOBILE_LINE_HEIGHT - 2},
212
216
  captionTextStyle,
213
217
  ]}>
218
+ {/* View mode when "Original and Translated" is selected - show original on top*/}
219
+ {selectedTranslationLanguageRef.current &&
220
+ captionViewMode === 'original-and-translated' && (
221
+ <>
222
+ <Text style={styles.languageLabel}>
223
+ ({getLanguageLabel([globalSourceLanguage])}){' '}
224
+ </Text>
225
+ {getLatestTextPortion(value, sourceCharLimit)}
226
+ {'\n'}
227
+ </>
228
+ )}
229
+
214
230
  {/* Default view when view mode is : translated */}
215
231
  <Text style={styles.languageLabel}>
216
- ({displayTranslatedViewText.langCode}
217
- ):{' '}
232
+ ({displayTranslatedViewText.langLabel}){' '}
218
233
  </Text>
219
234
  {getLatestTextPortion(
220
235
  displayTranslatedViewText.value,
221
236
  sourceCharLimit,
222
237
  )}
223
- {/* View mode when "Original and Translated" is selected - show original for remote users only */}
224
- {speakerUid !== userLocalUid &&
225
- captionViewMode === 'original-and-translated' && (
226
- <>
227
- {'\n'}
228
- <Text style={styles.languageLabel}>(Original): </Text>
229
- {getLatestTextPortion(value, sourceCharLimit)}
230
- </>
231
- )}
232
238
  </Text>
233
239
  </View>
234
240
  </View>
@@ -1,4 +1,4 @@
1
- import React, {SetStateAction} from 'react';
1
+ import React, {SetStateAction, useState} from 'react';
2
2
  import {StyleSheet, Text, View} from 'react-native';
3
3
  import Spacer from '../../atoms/Spacer';
4
4
  import Popup from '../../atoms/Popup';
@@ -6,27 +6,23 @@ import TertiaryButton from '../../atoms/TertiaryButton';
6
6
  import PrimaryButton from '../../atoms/PrimaryButton';
7
7
  import ThemeConfig from '../../theme';
8
8
  import {useIsDesktop} from '../../utils/common';
9
- import {LanguageTranslationConfig, useCaption} from './useCaption';
9
+ import {useCaption} from './useCaption';
10
10
  import Dropdown from '../../atoms/Dropdown';
11
- import DropdownMulti from '../../atoms/DropDownMulti';
12
11
  import hexadecimalTransparency from '../../utils/hexadecimalTransparency';
13
12
  import Loading from '../Loading';
14
- import {LanguageType, langData, hasConfigChanged} from './utils';
13
+ import {LanguageType, langData} from './utils';
15
14
  import {useString} from '../../utils/useString';
16
- import {useLocalUid} from '../../../agora-rn-uikit';
17
- // import {useRoomInfo} from '../../components/room-info/useRoomInfo';
18
15
  import {
19
16
  sttChangeLanguagePopupHeading,
20
17
  sttChangeLanguagePopupPrimaryBtnText,
21
18
  sttLanguageChangeInProgress,
22
- sttChangeLanguagePopupDropdownInfo,
23
19
  } from '../../language/default-labels/videoCallScreenLabels';
24
20
  import {cancelText} from '../../language/default-labels/commonLabels';
25
21
 
26
22
  interface LanguageSelectorPopup {
27
23
  modalVisible: boolean;
28
24
  setModalVisible: React.Dispatch<SetStateAction<boolean>>;
29
- onConfirm: (inputTranslationConfig: LanguageTranslationConfig) => void;
25
+ onConfirm: (newLang: LanguageType) => void;
30
26
  }
31
27
 
32
28
  const LanguageSelectorPopup = (props: LanguageSelectorPopup) => {
@@ -35,91 +31,19 @@ const LanguageSelectorPopup = (props: LanguageSelectorPopup) => {
35
31
  const cancelBtnLabel = useString(cancelText)();
36
32
  const ConfirmBtnLabel = useString(sttChangeLanguagePopupPrimaryBtnText)();
37
33
  const languageChangeInProgress = useString(sttLanguageChangeInProgress)();
38
- const maxLangValInfo = useString(sttChangeLanguagePopupDropdownInfo)();
39
34
 
40
- const {
41
- translationConfig,
42
- isLangChangeInProgress,
43
- isSTTActive,
44
- remoteSpokenLanguages,
45
- } = useCaption();
46
- const localUid = useLocalUid();
47
- console.log(
48
- '[STT_PER_USER_BOT] remoteSpokenLanguages',
49
- remoteSpokenLanguages,
50
- );
51
-
52
- // const {sttLanguage} = useRoomInfo();
53
- const [error, setError] = React.useState<boolean>(false);
54
- const [isTargetOpen, setIsTargetOpen] = React.useState(false);
55
-
56
- const [inputTranslationConfig, setInputTranslationConfig] =
57
- React.useState<LanguageTranslationConfig>({
58
- source: [],
59
- targets: [],
60
- });
61
-
62
- const isNotValidated =
63
- inputTranslationConfig?.source.length === 0 ||
64
- inputTranslationConfig?.targets.length === 0 ||
65
- inputTranslationConfig?.targets.length > 10;
66
-
67
- // Create source language options with "None" prepended
68
- const sourceLanguageOptions = React.useMemo(() => {
69
- // return [{label: 'None', value: 'none'}, ...langData];
70
- return [...langData];
71
- }, []);
72
-
73
- // All remote languages except for own user
74
- const suggestedRemoteTargetLangs = React.useMemo(() => {
75
- const remoteLangs = Object.entries(remoteSpokenLanguages)
76
- .filter(([uid, lang]) => uid !== String(localUid) && lang)
77
- .map(([, lang]) => lang);
78
-
79
- return Array.from(new Set(remoteLangs));
80
- }, [remoteSpokenLanguages, localUid]);
35
+ const {globalSttState, isLangChangeInProgress} = useCaption();
81
36
 
82
- // Initialize or update source/targets dynamically when modal opens
83
- React.useEffect(() => {
84
- if (!props.modalVisible) {
85
- return;
86
- }
87
- console.log(
88
- '[STT_PER_USER_BOT] language selecter popup opened',
89
- translationConfig,
90
- );
91
-
92
- const mergedTargets = Array.from(
93
- new Set([
94
- ...(translationConfig.targets || []),
95
- ...suggestedRemoteTargetLangs,
96
- ]),
97
- );
98
-
99
- setInputTranslationConfig({
100
- source: translationConfig.source,
101
- targets: mergedTargets,
102
- });
37
+ const [draftSpokenLanguage, setDraftSpokenLanguage] = useState<LanguageType>(
38
+ globalSttState?.globalSpokenLanguage || 'en-US',
39
+ );
103
40
 
104
- console.log('[STT_PER_USER_BOT] mergedTargets —', mergedTargets);
105
- console.log(
106
- '[STT_PER_USER_BOT] all target langs —',
107
- suggestedRemoteTargetLangs,
108
- );
109
- }, [props.modalVisible, translationConfig, suggestedRemoteTargetLangs]);
41
+ const hasSpokenLanguageChanged =
42
+ draftSpokenLanguage !== globalSttState?.globalSpokenLanguage;
110
43
 
111
44
  const onConfirmPress = async () => {
112
- if (isNotValidated) {
113
- return;
114
- }
115
-
116
- console.log('[LANG_SELECTOR] Confirm pressed:', {
117
- inputTranslationConfig,
118
- });
119
-
120
45
  try {
121
- props?.onConfirm(inputTranslationConfig);
122
- // props.setModalVisible(false);
46
+ props?.onConfirm(draftSpokenLanguage);
123
47
  } catch (err) {
124
48
  console.error('[LANG_SELECTOR] Error confirming STT config:', err);
125
49
  }
@@ -131,7 +55,12 @@ const LanguageSelectorPopup = (props: LanguageSelectorPopup) => {
131
55
  setModalVisible={props.setModalVisible}
132
56
  showCloseIcon={true}
133
57
  contentContainerStyle={styles.contentContainer}
134
- title={heading(isSTTActive ? false : true)}>
58
+ title={heading(globalSttState?.globalSttEnabled ? false : true)}
59
+ subtitle={
60
+ globalSttState?.globalSttEnabled
61
+ ? ''
62
+ : 'Speech-to-text service will turn on for everyone in the call.'
63
+ }>
135
64
  {isLangChangeInProgress ? (
136
65
  <View style={styles.changeInProgress}>
137
66
  <Loading
@@ -145,67 +74,29 @@ const LanguageSelectorPopup = (props: LanguageSelectorPopup) => {
145
74
  <>
146
75
  {/* Source Language */}
147
76
  <View>
148
- <Text style={styles.labelText}>
149
- What language will you speak in this meeting?
150
- </Text>
77
+ <Text style={styles.labelText}>Meeting Spoken language</Text>
151
78
  <Spacer size={8} />
152
79
  <Dropdown
153
80
  label="Select language"
154
- data={sourceLanguageOptions}
155
- selectedValue={inputTranslationConfig.source[0] || ''}
156
- onSelect={item =>
157
- setInputTranslationConfig(prev => ({
158
- ...prev,
159
- source: [item.value as LanguageType],
160
- }))
161
- }
81
+ data={[...langData]}
82
+ selectedValue={draftSpokenLanguage}
83
+ onSelect={item => {
84
+ if (item.value) {
85
+ setDraftSpokenLanguage(item.value as LanguageType);
86
+ }
87
+ }}
162
88
  enabled={true}
163
89
  />
164
- <Spacer size={2} />
90
+ <Spacer size={8} />
165
91
  <Text style={styles.infoText}>
166
- Captions and transcript will appear in this language for you.
92
+ {globalSttState?.globalSttEnabled
93
+ ? 'Updates the spoken-language for everyone in the call.'
94
+ : 'Sets the spoken-language for everyone in the call.'}
167
95
  </Text>
168
96
  </View>
169
97
 
170
98
  <Spacer size={20} />
171
99
 
172
- {/* Target Languages */}
173
- <View>
174
- <Text style={styles.labelText}>
175
- Spoken languages in the meeting
176
- </Text>
177
- <Spacer size={8} />
178
- <DropdownMulti
179
- selectedValues={inputTranslationConfig.targets}
180
- setSelectedValues={(val: LanguageType[]) =>
181
- setInputTranslationConfig(prev => ({
182
- ...prev,
183
- targets: val,
184
- }))
185
- }
186
- defaultSelectedValues={inputTranslationConfig.targets}
187
- error={error}
188
- setError={setError}
189
- isOpen={isTargetOpen}
190
- setIsOpen={setIsTargetOpen}
191
- maxAllowedSelection={10}
192
- protectedLanguages={Array.from(
193
- new Set([...suggestedRemoteTargetLangs]),
194
- )}
195
- />
196
- <Spacer size={2} />
197
- <Text style={styles.infoText}>
198
- Auto populated by spoken languages of other users once they join
199
- the room.
200
- </Text>
201
- </View>
202
- <Spacer size={8} />
203
- {inputTranslationConfig?.targets.length === 10 && (
204
- <Text style={[styles.subHeading, styles.errorTxt]}>
205
- {maxLangValInfo}
206
- </Text>
207
- )}
208
- <Spacer size={32} />
209
100
  <View
210
101
  style={isDesktop ? styles.btnContainer : styles.btnContainerMobile}>
211
102
  <View style={isDesktop && {flex: 1}}>
@@ -223,10 +114,7 @@ const LanguageSelectorPopup = (props: LanguageSelectorPopup) => {
223
114
  <View style={isDesktop && {flex: 1}}>
224
115
  <PrimaryButton
225
116
  containerStyle={styles.button}
226
- disabled={
227
- isNotValidated ||
228
- !hasConfigChanged(translationConfig, inputTranslationConfig)
229
- }
117
+ disabled={!hasSpokenLanguageChanged}
230
118
  text={ConfirmBtnLabel}
231
119
  textStyle={styles.btnText}
232
120
  onPress={onConfirmPress}
@@ -22,7 +22,7 @@ import {
22
22
  } from '../../utils/common';
23
23
  import {TranscriptHeader} from '../../pages/video-call/SidePanelHeader';
24
24
  import {useRtc, useContent} from 'customization-api';
25
- import {useCaption} from './useCaption';
25
+ import {TranscriptItem, useCaption} from './useCaption';
26
26
  import {TranscriptText} from './TranscriptText';
27
27
  import PrimaryButton from '../../atoms/PrimaryButton';
28
28
  import ThemeConfig from '../../theme';
@@ -61,6 +61,8 @@ const Transcript = (props: TranscriptProps) => {
61
61
  setIsSTTListenerAdded,
62
62
  getBotOwnerUid,
63
63
  isSTTActive,
64
+ selectedTranslationLanguage,
65
+ transcriptViewMode,
64
66
  } = useCaption();
65
67
 
66
68
  const settingSpokenLanguageLabel = useString(sttSettingSpokenLanguageText)();
@@ -72,7 +74,7 @@ const Transcript = (props: TranscriptProps) => {
72
74
  const viewlatest = useString(sttTranscriptPanelViewLatestText)();
73
75
 
74
76
  const data = meetingTranscript; // Object.entries(transcript);
75
- console.log('[STT_PER_USER_BOT] meetingTranscript data: ', data);
77
+ console.log('[STT_GLOBAL] meetingTranscript data: ', data);
76
78
 
77
79
  const [showButton, setShowButton] = React.useState(false);
78
80
 
@@ -108,41 +110,84 @@ const Transcript = (props: TranscriptProps) => {
108
110
  }
109
111
  };
110
112
 
111
- const renderItem = ({item}) => {
112
- return item.uid.toString().indexOf('langUpdate') !== -1 ? (
113
- <View style={styles.langChangeContainer}>
114
- <ImageIcon
115
- iconType="plain"
116
- iconSize={20}
117
- tintColor={$config.PRIMARY_ACTION_BRAND_COLOR}
118
- name={'globe'}
119
- />
113
+ const renderItem = ({item}: {item: TranscriptItem}) => {
114
+ const speakerName =
115
+ defaultContent[getBotOwnerUid(item.uid)]?.name || 'Speaker';
116
+ const mode = transcriptViewMode;
117
+ // Find translation (always return TranslationItem | null)
118
+ const translation =
119
+ item.selectedTranslationLanguage &&
120
+ item.translations?.find(
121
+ tr => tr.lang === item.selectedTranslationLanguage,
122
+ )
123
+ ? item.translations.find(
124
+ tr => tr.lang === item.selectedTranslationLanguage,
125
+ )
126
+ : null;
127
+
128
+ // system messages - spoken lang update
129
+ if (item.uid.toString().indexOf('langUpdate') !== -1) {
130
+ return (
131
+ <View style={styles.langChangeContainer}>
132
+ <ImageIcon
133
+ iconType="plain"
134
+ iconSize={20}
135
+ tintColor={$config.PRIMARY_ACTION_BRAND_COLOR}
136
+ name={'globe'}
137
+ />
120
138
 
121
- <Text style={styles.langChange}>
122
- {defaultContent[item?.uid?.split('-')[1]]?.name + ' ' + item.text}
123
- </Text>
124
- </View>
125
- ) : item.uid.toString().indexOf('translationUpdate') !== -1 ? (
126
- <View style={styles.langChangeContainer}>
127
- <ImageIcon
128
- iconType="plain"
129
- iconSize={20}
130
- tintColor={$config.PRIMARY_ACTION_BRAND_COLOR}
131
- name={'lang-select'}
132
- />
139
+ <Text style={styles.langChange}>
140
+ {/* {defaultContent[item?.uid?.split('-')[1]]?.name + ' ' + item.text} */}
141
+ {item.text}
142
+ </Text>
143
+ </View>
144
+ );
145
+ }
146
+ // system messages - target update
147
+ if (item.uid.toString().indexOf('translationUpdate') !== -1) {
148
+ return (
149
+ <View style={styles.langChangeContainer}>
150
+ <ImageIcon
151
+ iconType="plain"
152
+ iconSize={20}
153
+ tintColor={$config.PRIMARY_ACTION_BRAND_COLOR}
154
+ name={'lang-select'}
155
+ />
133
156
 
134
- <Text style={styles.langChange}>
135
- {defaultContent[item?.uid?.split('-')[1]]?.name + ' has ' + item.text}
136
- </Text>
137
- </View>
138
- ) : (
157
+ <Text style={styles.langChange}>
158
+ {/* {defaultContent[item?.uid?.split('-')[1]]?.name +
159
+ ' has ' +
160
+ item.text} */}
161
+ {item.text}
162
+ </Text>
163
+ </View>
164
+ );
165
+ }
166
+ // Translated mode (default)
167
+ if (mode === 'translated' && selectedTranslationLanguage) {
168
+ if (!translation) {
169
+ return null;
170
+ } // hide if translation not available
171
+ return (
172
+ <TranscriptText
173
+ user={speakerName}
174
+ time={item.time}
175
+ value={translation.text}
176
+ translations={[]}
177
+ searchQuery={searchQuery}
178
+ selectedTranslationLanguage={selectedTranslationLanguage}
179
+ />
180
+ );
181
+ }
182
+ // original and translated
183
+ return (
139
184
  <TranscriptText
140
- user={defaultContent[getBotOwnerUid(item.uid)]?.name || 'Speaker'}
185
+ user={speakerName}
141
186
  time={item?.time}
142
187
  value={item.text}
143
- translations={item.translations}
188
+ translations={translation ? [translation] : []}
144
189
  searchQuery={searchQuery}
145
- selectedTranslationLanguage={item.selectedTranslationLanguage}
190
+ selectedTranslationLanguage={selectedTranslationLanguage}
146
191
  />
147
192
  );
148
193
  };
@@ -213,7 +258,7 @@ const Transcript = (props: TranscriptProps) => {
213
258
  };
214
259
 
215
260
  const handleStreamMessageCallback = (...args: StreamMessageArgs) => {
216
- console.log('[STT_PER_USER_BOT] handleStreamMessageCallback', args);
261
+ console.log('[STT_GLOBAL] handleStreamMessageCallback', args);
217
262
  setIsSTTListenerAdded(true);
218
263
  if (isWebInternal()) {
219
264
  const [uid, data] = args as WebStreamMessageArgs;
@@ -3,11 +3,11 @@ import React from 'react';
3
3
  import {SidePanelType, useSidePanel} from 'customization-api';
4
4
  import IconButton, {IconButtonProps} from '../../atoms/IconButton';
5
5
  import LanguageSelectorPopup from './LanguageSelectorPopup';
6
- import {LanguageTranslationConfig, useCaption} from './useCaption';
7
- import useSTTAPI from './useSTTAPI';
6
+ import {useCaption} from './useCaption';
8
7
  import {useString} from '../../utils/useString';
9
8
  import {toolbarItemTranscriptText} from '../../language/default-labels/videoCallScreenLabels';
10
9
  import {useToolbarProps} from '../../atoms/ToolbarItem';
10
+ import {LanguageType} from './utils';
11
11
 
12
12
  interface TranscriptIconProps {
13
13
  plainIconHoverEffect?: boolean;
@@ -31,7 +31,8 @@ const TranscriptIcon = (props: TranscriptIconProps) => {
31
31
  } = props;
32
32
 
33
33
  // const {start, restart, isAuthorizedTranscriptUser} = useSTTAPI();
34
- const {isSTTActive, isSTTError, handleTranslateConfigChange} = useCaption();
34
+ const {isSTTActive, isSTTError, sttDepsReady, confirmSpokenLanguageChange} =
35
+ useCaption();
35
36
  // const isDisabled = !isAuthorizedTranscriptUser();
36
37
  const [isLanguagePopupOpen, setLanguagePopup] =
37
38
  React.useState<boolean>(false);
@@ -61,7 +62,7 @@ const TranscriptIcon = (props: TranscriptIconProps) => {
61
62
  ? $config.PRIMARY_ACTION_TEXT_COLOR
62
63
  : $config.SECONDARY_ACTION_COLOR,
63
64
  },
64
- disabled: false,
65
+ disabled: !sttDepsReady,
65
66
  btnTextProps: {
66
67
  text: showLabel
67
68
  ? isOnActionSheet
@@ -77,7 +78,7 @@ const TranscriptIcon = (props: TranscriptIconProps) => {
77
78
  iconButtonProps.toolTipMessage = label(isTranscriptON);
78
79
  }
79
80
 
80
- const onConfirm = async (inputTranslateConfig: LanguageTranslationConfig) => {
81
+ const onConfirm = async (newSpokenLang: LanguageType) => {
81
82
  // isFirstTimePopupOpen.current = false;
82
83
  // const method = isTranscriptON ? 'stop' : 'start';
83
84
  // if (method === 'stop') return; // not closing the stt service as it will stop for whole channel
@@ -89,7 +90,7 @@ const TranscriptIcon = (props: TranscriptIconProps) => {
89
90
  // // channel is already started now restart
90
91
  // await restart(language, userOwnLanguages);
91
92
  // }
92
- await handleTranslateConfigChange(inputTranslateConfig);
93
+ await confirmSpokenLanguageChange(newSpokenLang);
93
94
  if (!isTranscriptON) {
94
95
  setSidePanel(SidePanelType.Transcript);
95
96
  } else {
@@ -0,0 +1,128 @@
1
+ import {useWindowDimensions, View} from 'react-native';
2
+ import React from 'react';
3
+ import {useCaption} from './useCaption';
4
+ import {calculatePosition} from '../../utils/common';
5
+ import ActionMenu, {ActionMenuItem} from '../../../src/atoms/ActionMenu';
6
+ import {langData} from './utils';
7
+ import hexadecimalTransparency from '../../utils/hexadecimalTransparency';
8
+
9
+ export interface TranslateActionMenuProps {
10
+ actionMenuVisible: boolean;
11
+ setActionMenuVisible: (actionMenuVisible: boolean) => void;
12
+ btnRef: React.RefObject<View>;
13
+ }
14
+
15
+ export const TranslateActionMenu = (props: TranslateActionMenuProps) => {
16
+ const {actionMenuVisible, setActionMenuVisible, btnRef} = props;
17
+ const [modalPosition, setModalPosition] = React.useState({});
18
+ const [isPosCalculated, setIsPosCalculated] = React.useState(false);
19
+ const {width: globalWidth, height: globalHeight} = useWindowDimensions();
20
+ const {
21
+ selectedTranslationLanguage,
22
+ confirmTargetLanguageChange,
23
+ globalSttState,
24
+ isLangChangeInProgress,
25
+ } = useCaption();
26
+
27
+ const sourceLang = globalSttState.globalSpokenLanguage;
28
+ const globalTargets = globalSttState.globalTranslationTargets || [];
29
+ const isGlobalTargetsFull = globalTargets.length >= 10;
30
+
31
+ const actionMenuitems: ActionMenuItem[] = [];
32
+
33
+ // Off option
34
+ actionMenuitems.push({
35
+ title: 'Off',
36
+ endIcon: !selectedTranslationLanguage ? 'tick-fill' : undefined,
37
+ iconColor: $config.PRIMARY_ACTION_BRAND_COLOR,
38
+ textColor: $config.FONT_COLOR,
39
+ iconPosition: 'end',
40
+ disabled: isLangChangeInProgress,
41
+ titleStyle: {
42
+ paddingLeft: 14,
43
+ },
44
+ onPress: () => {
45
+ confirmTargetLanguageChange(null);
46
+ setActionMenuVisible(false);
47
+ },
48
+ });
49
+
50
+ // Move selected translation language to the top
51
+ const sortedLangData = [...langData].sort((a, b) => {
52
+ if (a.value === selectedTranslationLanguage) {
53
+ return -1;
54
+ }
55
+ if (b.value === selectedTranslationLanguage) {
56
+ return 1;
57
+ }
58
+ return 0;
59
+ });
60
+
61
+ sortedLangData.forEach(lang => {
62
+ const selectedLang = lang.value === selectedTranslationLanguage;
63
+ // If global is full or if its a source disable all langs
64
+ const disabled = isGlobalTargetsFull || lang.value === sourceLang;
65
+ actionMenuitems.push({
66
+ title: lang.label,
67
+ endIcon: selectedLang ? 'tick-fill' : undefined,
68
+ iconPosition: 'end',
69
+ iconColor: disabled
70
+ ? $config.FONT_COLOR + hexadecimalTransparency['40%']
71
+ : $config.PRIMARY_ACTION_BRAND_COLOR,
72
+ textColor: disabled
73
+ ? $config.FONT_COLOR + hexadecimalTransparency['40%']
74
+ : $config.FONT_COLOR,
75
+ disabled,
76
+ titleStyle: {
77
+ paddingLeft: 14,
78
+ },
79
+ onPress: async () => {
80
+ if (disabled) {
81
+ return;
82
+ }
83
+ confirmTargetLanguageChange(lang.value);
84
+ setActionMenuVisible(false);
85
+ },
86
+ });
87
+ });
88
+
89
+ React.useEffect(() => {
90
+ if (actionMenuVisible) {
91
+ btnRef?.current?.measure(
92
+ (
93
+ _fx: number,
94
+ _fy: number,
95
+ localWidth: number,
96
+ localHeight: number,
97
+ px: number,
98
+ py: number,
99
+ ) => {
100
+ const data = calculatePosition({
101
+ px,
102
+ py,
103
+ localWidth,
104
+ localHeight,
105
+ globalHeight,
106
+ globalWidth,
107
+ });
108
+ setModalPosition(data);
109
+ setIsPosCalculated(true);
110
+ },
111
+ );
112
+ }
113
+ }, [actionMenuVisible]);
114
+
115
+ return (
116
+ <ActionMenu
117
+ from={'translation'}
118
+ actionMenuVisible={actionMenuVisible && isPosCalculated}
119
+ setActionMenuVisible={setActionMenuVisible}
120
+ modalPosition={modalPosition}
121
+ items={actionMenuitems}
122
+ containerStyle={{
123
+ maxHeight: Math.min(440, globalHeight * 0.6),
124
+ width: 220,
125
+ }}
126
+ />
127
+ );
128
+ };