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
@@ -1,7 +1,7 @@
1
1
  import {View} from 'react-native';
2
2
  import React from 'react';
3
3
  import IconButton, {IconButtonProps} from '../../atoms/IconButton';
4
- import {useCaption} from './useCaption';
4
+ import {LanguageTranslationConfig, useCaption} from './useCaption';
5
5
  import LanguageSelectorPopup from './LanguageSelectorPopup';
6
6
  import useSTTAPI from './useSTTAPI';
7
7
  import {useString} from '../../utils/useString';
@@ -26,42 +26,49 @@ const CaptionIcon = (props: CaptionIconProps) => {
26
26
  isOnActionSheet = false,
27
27
  closeActionSheet,
28
28
  } = props;
29
- const {isCaptionON, setIsCaptionON, isSTTActive, isSTTError} = useCaption();
29
+ const {
30
+ isCaptionON,
31
+ setIsCaptionON,
32
+ isSTTActive,
33
+ isSTTError,
34
+ handleTranslateConfigChange,
35
+ } = useCaption();
30
36
 
31
37
  const [isLanguagePopupOpen, setLanguagePopup] =
32
38
  React.useState<boolean>(false);
33
39
 
34
- const isFirstTimePopupOpen = React.useRef(false);
35
- const {start, restart, isAuthorizedSTTUser} = useSTTAPI();
36
- const isDisabled = !isAuthorizedSTTUser();
40
+ // const isFirstTimePopupOpen = React.useRef(false);
41
+ // const {start, restart, isAuthorizedSTTUser} = useSTTAPI();
42
+ // const isDisabled = !isAuthorizedSTTUser();
37
43
  const captionLabel = useString<boolean>(toolbarItemCaptionText);
38
44
  const label = captionLabel(isCaptionON);
39
45
  const onPress = () => {
40
- if (isSTTError) {
41
- setIsCaptionON(prev => !prev);
42
- closeActionSheet();
43
- return;
44
- }
45
- if (isSTTActive) {
46
- // is lang popup has been shown once for any user in meeting
46
+ if (isSTTError || !isSTTActive) {
47
+ // Show popup when error or STT not active
48
+ setLanguagePopup(true);
49
+ } else {
50
+ // STT is active and no error
47
51
  setIsCaptionON(prev => !prev);
48
52
  closeActionSheet();
49
- } else {
50
- isFirstTimePopupOpen.current = true;
51
- setLanguagePopup(true);
52
53
  }
54
+ // if (isSTTActive) {
55
+ // // is lang popup has been shown once for any user in meeting
56
+ // setIsCaptionON(prev => !prev);
57
+ // closeActionSheet();
58
+ // } else {
59
+ // // isFirstTimePopupOpen.current = true;
60
+ // setLanguagePopup(true);
61
+ // }
53
62
  };
54
63
  const iconButtonProps: IconButtonProps = {
55
64
  onPress: onPressCustom || onPress,
56
- disabled: isDisabled,
65
+ disabled: false,
57
66
  iconProps: {
58
67
  name: isCaptionON ? 'captions-off' : 'captions',
59
68
  iconBackgroundColor: isCaptionON
60
69
  ? $config.PRIMARY_ACTION_BRAND_COLOR
61
70
  : '',
62
- tintColor: isDisabled
63
- ? $config.SEMANTIC_NEUTRAL
64
- : isCaptionON
71
+ tintColor: isCaptionON
65
72
  ? $config.PRIMARY_ACTION_TEXT_COLOR
66
73
  : $config.SECONDARY_ACTION_COLOR,
67
74
  },
@@ -80,22 +87,16 @@ const CaptionIcon = (props: CaptionIconProps) => {
80
87
  iconButtonProps.toolTipMessage = label;
81
88
  }
82
89
 
83
- const onConfirm = async (langChanged, language) => {
84
- setLanguagePopup(false);
85
- closeActionSheet();
86
- isFirstTimePopupOpen.current = false;
87
- const method = isCaptionON ? 'stop' : 'start';
88
- if (method === 'stop') return; // not closing the stt service as it will stop for whole channel
89
- if (method === 'start' && isSTTActive === true) return; // not triggering the start service if STT Service already started by anyone else in the channel
90
- setIsCaptionON(prev => !prev);
90
+ const onConfirm = async (inputTranslateConfig: LanguageTranslationConfig) => {
91
91
  try {
92
- const res = await start(language);
93
- if (res?.message.includes('STARTED')) {
94
- // channel is already started now restart
95
- await restart(language);
96
- }
92
+ closeActionSheet();
93
+ setLanguagePopup(false);
94
+ setIsCaptionON(prev => !prev);
95
+ await handleTranslateConfigChange(inputTranslateConfig);
97
96
  } catch (error) {
98
- console.log('eror in starting stt', error);
97
+ setIsCaptionON(false);
98
+ console.log('error in starting stt', error);
99
+ // State is NOT changed on error, user can retry
99
100
  }
100
101
  };
101
102
 
@@ -106,7 +107,7 @@ const CaptionIcon = (props: CaptionIconProps) => {
106
107
  modalVisible={isLanguagePopupOpen}
107
108
  setModalVisible={setLanguagePopup}
108
109
  onConfirm={onConfirm}
109
- isFirstTimePopupOpen={isFirstTimePopupOpen.current}
110
+ // isFirstTimePopupOpen={isFirstTimePopupOpen.current}
110
111
  />
111
112
  </View>
112
113
  );
@@ -8,12 +8,21 @@ import {
8
8
  import React from 'react';
9
9
 
10
10
  import ThemeConfig from '../../../src/theme';
11
+ import {useCaption} from './useCaption';
11
12
  import hexadecimalTransparency from '../../../src/utils/hexadecimalTransparency';
12
13
  import {isAndroid, isMobileUA} from '../../utils/common';
14
+ import {getUserTranslatedText, getLanguageLabel, LanguageType} from './utils';
15
+
16
+ type TranslationItem = {
17
+ lang: string;
18
+ text: string;
19
+ isFinal: boolean;
20
+ };
13
21
 
14
22
  interface CaptionTextProps {
15
23
  user: string;
16
24
  value: string;
25
+ translations?: TranslationItem[];
17
26
  activeSpeakersCount: number;
18
27
  isActiveSpeaker?: boolean;
19
28
  activelinesAvailable?: number;
@@ -22,6 +31,9 @@ interface CaptionTextProps {
22
31
  setInActiveLinesAvaialble?: React.Dispatch<React.SetStateAction<number>>;
23
32
  captionUserStyle?: TextStyle;
24
33
  captionTextStyle?: TextStyle;
34
+ speakerUid?: string | number;
35
+ userLocalUid?: string | number;
36
+ spokenLanguageCode?: LanguageType;
25
37
  }
26
38
 
27
39
  const DESKTOP_LINE_HEIGHT = 28;
@@ -31,6 +43,7 @@ const MAX_CAPTIONS_LINES_ALLOWED = 3;
31
43
  const CaptionText = ({
32
44
  user,
33
45
  value,
46
+ translations = [],
34
47
  activeSpeakersCount,
35
48
  isActiveSpeaker = false,
36
49
  activelinesAvailable,
@@ -39,8 +52,12 @@ const CaptionText = ({
39
52
  setInActiveLinesAvaialble,
40
53
  captionUserStyle = {},
41
54
  captionTextStyle = {},
55
+ speakerUid,
56
+ userLocalUid,
57
+ spokenLanguageCode,
42
58
  }: CaptionTextProps) => {
43
59
  const isMobile = isMobileUA();
60
+ const {translationConfig, captionViewMode} = useCaption();
44
61
 
45
62
  const LINE_HEIGHT = isMobile ? MOBILE_LINE_HEIGHT : DESKTOP_LINE_HEIGHT;
46
63
 
@@ -82,6 +99,63 @@ const CaptionText = ({
82
99
  *
83
100
  */
84
101
 
102
+ // Get the appropriate source text for display based on viewer's language preferences
103
+ const viewerSourceLanguage = translationConfig.source[0];
104
+ // const localUserText = value;
105
+ // const remoteUserTranslatedText: {
106
+ // text: string,
107
+ // langcode: LanguageType
108
+ // } = () => {
109
+ // const matchingTranslation = translations.find(
110
+ // t => t.lang === viewerSourceLanguage,
111
+ // );
112
+ // if (matchingTranslation?.text) {
113
+ // return matchingTranslation.text;
114
+ // }
115
+ // return value;
116
+ // };
117
+
118
+ const displayTranslatedViewText = getUserTranslatedText(
119
+ value,
120
+ translations,
121
+ viewerSourceLanguage,
122
+ speakerUid,
123
+ userLocalUid,
124
+ );
125
+
126
+ /**
127
+ * ROBUST TEXT EXTRACTION LOGIC
128
+ * Problem: value and translationText contain FULL accumulated text (can be 1000+ chars)
129
+ * Solution: Extract only the latest portion that fits in allocated lines
130
+ * - With translation: 2 lines source (~100-120 chars) + 1 line translation (~50-75 chars)
131
+ * - Without translation: 3 lines source (~150-180 chars)
132
+ */
133
+ const getLatestTextPortion = (text: string, maxChars: number) => {
134
+ if (!text || text.length <= maxChars) return text;
135
+
136
+ // Take last maxChars, try to find sentence boundary for cleaner cut
137
+ const portion = text.slice(-maxChars);
138
+ const sentenceMatch = portion.match(/[.!?]\s+/);
139
+
140
+ // If we find a sentence boundary after position 10, start from there
141
+ if (sentenceMatch && sentenceMatch.index > 10) {
142
+ return portion.slice(sentenceMatch.index + sentenceMatch[0].length);
143
+ }
144
+
145
+ // Otherwise just return the last maxChars
146
+ return portion;
147
+ };
148
+
149
+ // Adjust char limits based on mobile vs desktop (mobile has smaller font)
150
+ const sourceCharLimit =
151
+ captionViewMode === 'original-and-translated'
152
+ ? isMobile
153
+ ? 50
154
+ : 70 // 1 line for source when showing original + translation below
155
+ : isMobile
156
+ ? 150
157
+ : 180; // 3 lines for source in translated mode (uses full space)
158
+
85
159
  return (
86
160
  <View
87
161
  style={[
@@ -125,17 +199,36 @@ const CaptionText = ({
125
199
  )) * LINE_HEIGHT,
126
200
  },
127
201
  ]}>
202
+ {/* Combined source and translation text */}
128
203
  <Text
129
204
  onLayout={handleTextLayout}
130
205
  style={[
131
206
  styles.captionText,
207
+ styles.transcriptionText,
132
208
  isMobile
133
209
  ? styles.mobileCaptionFontSize
134
210
  : styles.desktopCaptionFontSize,
135
211
  isAndroid() && {lineHeight: MOBILE_LINE_HEIGHT - 2},
136
212
  captionTextStyle,
137
213
  ]}>
138
- {value}
214
+ {/* Default view when view mode is : translated */}
215
+ <Text style={styles.languageLabel}>
216
+ ({displayTranslatedViewText.langCode}
217
+ ):{' '}
218
+ </Text>
219
+ {getLatestTextPortion(
220
+ displayTranslatedViewText.value,
221
+ sourceCharLimit,
222
+ )}
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
+ )}
139
232
  </Text>
140
233
  </View>
141
234
  </View>
@@ -155,8 +248,8 @@ const styles = StyleSheet.create({
155
248
  },
156
249
 
157
250
  captionTextContainerStyle: {
158
- overflow: 'hidden',
159
251
  width: '100%',
252
+ overflow: 'hidden',
160
253
  position: 'relative',
161
254
  },
162
255
 
@@ -168,6 +261,14 @@ const styles = StyleSheet.create({
168
261
  bottom: 0,
169
262
  },
170
263
 
264
+ transcriptionText: {
265
+ marginBottom: 2,
266
+ },
267
+
268
+ languageLabel: {
269
+ color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low,
270
+ },
271
+
171
272
  captionUserName: {
172
273
  fontWeight: '600',
173
274
  fontFamily: ThemeConfig.FontFamily.sansPro,
@@ -1,4 +1,4 @@
1
- import React, {SetStateAction, useContext} from 'react';
1
+ import React, {SetStateAction} 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,50 +6,124 @@ 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 {useCaption} from './useCaption';
9
+ import {LanguageTranslationConfig, useCaption} from './useCaption';
10
+ import Dropdown from '../../atoms/Dropdown';
10
11
  import DropdownMulti from '../../atoms/DropDownMulti';
11
12
  import hexadecimalTransparency from '../../utils/hexadecimalTransparency';
12
13
  import Loading from '../Loading';
13
- import {LanguageType} from './utils';
14
+ import {LanguageType, langData, hasConfigChanged} from './utils';
14
15
  import {useString} from '../../utils/useString';
16
+ import {useLocalUid} from '../../../agora-rn-uikit';
17
+ // import {useRoomInfo} from '../../components/room-info/useRoomInfo';
15
18
  import {
16
- sttChangeLanguagePopupDropdownError,
17
- sttChangeLanguagePopupDropdownInfo,
18
19
  sttChangeLanguagePopupHeading,
19
20
  sttChangeLanguagePopupPrimaryBtnText,
20
- sttChangeLanguagePopupSubHeading,
21
21
  sttLanguageChangeInProgress,
22
+ sttChangeLanguagePopupDropdownInfo,
22
23
  } from '../../language/default-labels/videoCallScreenLabels';
23
24
  import {cancelText} from '../../language/default-labels/commonLabels';
24
25
 
25
26
  interface LanguageSelectorPopup {
26
27
  modalVisible: boolean;
27
28
  setModalVisible: React.Dispatch<SetStateAction<boolean>>;
28
- onConfirm: (param: boolean, lang: LanguageType[]) => void;
29
- isFirstTimePopupOpen?: boolean;
29
+ onConfirm: (inputTranslationConfig: LanguageTranslationConfig) => void;
30
30
  }
31
31
 
32
32
  const LanguageSelectorPopup = (props: LanguageSelectorPopup) => {
33
- const {isFirstTimePopupOpen = false} = props;
34
- const [isOpen, setIsOpen] = React.useState(false);
35
33
  const isDesktop = useIsDesktop()('popup');
36
34
  const heading = useString<boolean>(sttChangeLanguagePopupHeading);
37
- const subHeading = useString(sttChangeLanguagePopupSubHeading)();
38
35
  const cancelBtnLabel = useString(cancelText)();
39
36
  const ConfirmBtnLabel = useString(sttChangeLanguagePopupPrimaryBtnText)();
40
- const ddError = useString(sttChangeLanguagePopupDropdownError)();
41
- const ddInfo = useString(sttChangeLanguagePopupDropdownInfo)();
42
37
  const languageChangeInProgress = useString(sttLanguageChangeInProgress)();
43
- const {language, setLanguage, isLangChangeInProgress, isSTTActive} =
44
- useCaption();
38
+ const maxLangValInfo = useString(sttChangeLanguagePopupDropdownInfo)();
45
39
 
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();
46
53
  const [error, setError] = React.useState<boolean>(false);
47
- const [selectedValues, setSelectedValues] =
48
- React.useState<LanguageType[]>(language);
54
+ const [isTargetOpen, setIsTargetOpen] = React.useState(false);
55
+
56
+ const [inputTranslationConfig, setInputTranslationConfig] =
57
+ React.useState<LanguageTranslationConfig>({
58
+ source: [],
59
+ targets: [],
60
+ });
61
+
49
62
  const isNotValidated =
50
- isOpen && (selectedValues.length === 0 || selectedValues.length === 2);
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]);
81
+
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
+ );
51
91
 
52
- // React.useEffect(() => setSelectedValues(() => language), []);
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
+ });
103
+
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]);
110
+
111
+ const onConfirmPress = async () => {
112
+ if (isNotValidated) {
113
+ return;
114
+ }
115
+
116
+ console.log('[LANG_SELECTOR] Confirm pressed:', {
117
+ inputTranslationConfig,
118
+ });
119
+
120
+ try {
121
+ props?.onConfirm(inputTranslationConfig);
122
+ // props.setModalVisible(false);
123
+ } catch (err) {
124
+ console.error('[LANG_SELECTOR] Error confirming STT config:', err);
125
+ }
126
+ };
53
127
 
54
128
  return (
55
129
  <Popup
@@ -57,8 +131,7 @@ const LanguageSelectorPopup = (props: LanguageSelectorPopup) => {
57
131
  setModalVisible={props.setModalVisible}
58
132
  showCloseIcon={true}
59
133
  contentContainerStyle={styles.contentContainer}
60
- title={heading(isFirstTimePopupOpen)}
61
- subtitle={subHeading}>
134
+ title={heading(isSTTActive ? false : true)}>
62
135
  {isLangChangeInProgress ? (
63
136
  <View style={styles.changeInProgress}>
64
137
  <Loading
@@ -70,35 +143,74 @@ const LanguageSelectorPopup = (props: LanguageSelectorPopup) => {
70
143
  </View>
71
144
  ) : (
72
145
  <>
146
+ {/* Source Language */}
147
+ <View>
148
+ <Text style={styles.labelText}>
149
+ What language will you speak in this meeting?
150
+ </Text>
151
+ <Spacer size={8} />
152
+ <Dropdown
153
+ 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
+ }
162
+ enabled={true}
163
+ />
164
+ <Spacer size={2} />
165
+ <Text style={styles.infoText}>
166
+ Captions and transcript will appear in this language for you.
167
+ </Text>
168
+ </View>
169
+
170
+ <Spacer size={20} />
171
+
172
+ {/* Target Languages */}
73
173
  <View>
174
+ <Text style={styles.labelText}>
175
+ Spoken languages in the meeting
176
+ </Text>
177
+ <Spacer size={8} />
74
178
  <DropdownMulti
75
- selectedValues={selectedValues}
76
- setSelectedValues={setSelectedValues}
77
- defaultSelectedValues={
78
- language.indexOf('') === -1 ? language : ['en-US']
179
+ selectedValues={inputTranslationConfig.targets}
180
+ setSelectedValues={(val: LanguageType[]) =>
181
+ setInputTranslationConfig(prev => ({
182
+ ...prev,
183
+ targets: val,
184
+ }))
79
185
  }
186
+ defaultSelectedValues={inputTranslationConfig.targets}
80
187
  error={error}
81
188
  setError={setError}
82
- isOpen={isOpen}
83
- setIsOpen={setIsOpen}
189
+ isOpen={isTargetOpen}
190
+ setIsOpen={setIsTargetOpen}
191
+ maxAllowedSelection={10}
192
+ protectedLanguages={Array.from(
193
+ new Set([...suggestedRemoteTargetLangs]),
194
+ )}
84
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>
85
201
  </View>
86
202
  <Spacer size={8} />
87
- <Text style={[styles.subHeading, isNotValidated && styles.errorTxt]}>
88
- {selectedValues.length === 0 ? ddError : ddInfo}
89
- </Text>
203
+ {inputTranslationConfig?.targets.length === 10 && (
204
+ <Text style={[styles.subHeading, styles.errorTxt]}>
205
+ {maxLangValInfo}
206
+ </Text>
207
+ )}
90
208
  <Spacer size={32} />
91
209
  <View
92
210
  style={isDesktop ? styles.btnContainer : styles.btnContainerMobile}>
93
211
  <View style={isDesktop && {flex: 1}}>
94
212
  <TertiaryButton
95
- containerStyle={{
96
- width: '100%',
97
- height: 48,
98
- paddingVertical: 12,
99
- paddingHorizontal: 12,
100
- borderRadius: ThemeConfig.BorderRadius.medium,
101
- }}
213
+ containerStyle={styles.button}
102
214
  text={cancelBtnLabel}
103
215
  textStyle={styles.btnText}
104
216
  onPress={() => props.setModalVisible(false)}
@@ -110,30 +222,14 @@ const LanguageSelectorPopup = (props: LanguageSelectorPopup) => {
110
222
  />
111
223
  <View style={isDesktop && {flex: 1}}>
112
224
  <PrimaryButton
113
- containerStyle={{
114
- minWidth: 'auto',
115
- width: '100%',
116
- borderRadius: ThemeConfig.BorderRadius.medium,
117
- height: 48,
118
- paddingVertical: 12,
119
- paddingHorizontal: 12,
120
- }}
121
- disabled={selectedValues.length === 0}
225
+ containerStyle={styles.button}
226
+ disabled={
227
+ isNotValidated ||
228
+ !hasConfigChanged(translationConfig, inputTranslationConfig)
229
+ }
122
230
  text={ConfirmBtnLabel}
123
231
  textStyle={styles.btnText}
124
- onPress={() => {
125
- console.log(selectedValues);
126
- console.log(language);
127
-
128
- if (selectedValues.length === 0) {
129
- // setError(true);
130
- return;
131
- }
132
- const lang1 = language.slice().sort().toString();
133
- const lang2 = selectedValues.slice().sort().toString();
134
- const isLangChanged = lang1 !== lang2 || !isSTTActive;
135
- props.onConfirm(isLangChanged, selectedValues);
136
- }}
232
+ onPress={onConfirmPress}
137
233
  />
138
234
  </View>
139
235
  </View>
@@ -152,26 +248,41 @@ const styles = StyleSheet.create({
152
248
  justifyContent: 'center',
153
249
  alignItems: 'center',
154
250
  },
155
- btnText: {
156
- fontWeight: '600',
157
- fontSize: 16,
158
- lineHeight: 24,
159
- },
160
251
  btnContainerMobile: {
161
252
  flexDirection: 'column-reverse',
162
253
  },
163
254
  contentContainer: {
164
255
  padding: 24,
165
- maxWidth: 446,
256
+ maxWidth: 500,
166
257
  width: '100%',
167
258
  },
168
- heading: {
169
- fontFamily: ThemeConfig.FontFamily.sansPro,
259
+ button: {
260
+ width: '100%',
261
+ minWidth: 'auto',
262
+ height: 48,
263
+ paddingVertical: 12,
264
+ paddingHorizontal: 12,
265
+ borderRadius: ThemeConfig.BorderRadius.medium,
266
+ },
267
+ btnText: {
170
268
  fontWeight: '600',
171
- fontSize: 22,
269
+ fontSize: 16,
172
270
  lineHeight: 24,
271
+ },
272
+ labelText: {
273
+ fontFamily: ThemeConfig.FontFamily.sansPro,
274
+ fontWeight: '400',
275
+ fontSize: ThemeConfig.FontSize.small,
276
+ lineHeight: 20,
173
277
  color: $config.FONT_COLOR,
174
278
  },
279
+ infoText: {
280
+ fontFamily: ThemeConfig.FontFamily.sansPro,
281
+ fontWeight: '400',
282
+ fontSize: ThemeConfig.FontSize.tiny,
283
+ lineHeight: 12,
284
+ color: $config.SEMANTIC_NEUTRAL,
285
+ },
175
286
  subHeading: {
176
287
  fontFamily: ThemeConfig.FontFamily.sansPro,
177
288
  fontWeight: '400',
@@ -179,7 +290,6 @@ const styles = StyleSheet.create({
179
290
  lineHeight: 20,
180
291
  color: $config.FONT_COLOR + hexadecimalTransparency['70%'],
181
292
  },
182
-
183
293
  errorTxt: {
184
294
  color: $config.SEMANTIC_WARNING,
185
295
  fontWeight: '600',