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.
- package/package.json +2 -2
- package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +1 -3
- package/template/agora-rn-uikit/src/Contexts/RtcContext.tsx +1 -2
- package/template/agora-rn-uikit/src/Reducer/index.ts +0 -2
- package/template/agora-rn-uikit/src/Rtc/Join.tsx +11 -25
- package/template/agora-rn-uikit/src/RtcConfigure.tsx +1 -14
- package/template/agora-rn-uikit/src/Utils/isBotUser.ts +1 -1
- package/template/android/app/build.gradle +0 -7
- package/template/bridge/rtm/web/Types.ts +0 -183
- package/template/bridge/rtm/web/index.ts +491 -423
- package/template/defaultConfig.js +3 -3
- package/template/ios/Podfile +0 -41
- package/template/package.json +5 -5
- package/template/src/assets/font-styles.css +4 -0
- package/template/src/assets/fonts/icomoon.ttf +0 -0
- package/template/src/assets/selection.json +1 -1
- package/template/src/atoms/ActionMenu.tsx +93 -13
- package/template/src/atoms/CustomIcon.tsx +1 -0
- package/template/src/atoms/DropDownMulti.tsx +80 -29
- package/template/src/atoms/Input.tsx +2 -1
- package/template/src/components/Controls.tsx +148 -143
- package/template/src/components/EventsConfigure.tsx +152 -97
- package/template/src/components/RTMConfigure.tsx +426 -644
- package/template/src/components/precall/joinCallBtn.native.tsx +7 -2
- package/template/src/components/precall/joinCallBtn.tsx +7 -2
- package/template/src/components/precall/joinWaitingRoomBtn.native.tsx +8 -3
- package/template/src/components/precall/joinWaitingRoomBtn.tsx +22 -4
- package/template/src/components/precall/textInput.tsx +45 -22
- package/template/src/components/precall/usePreCall.tsx +7 -0
- package/template/src/components/room-info/useRoomInfo.tsx +5 -0
- package/template/src/language/default-labels/videoCallScreenLabels.ts +27 -4
- package/template/src/pages/video-call/ActionSheetContent.tsx +77 -77
- package/template/src/pages/video-call/SidePanelHeader.tsx +81 -36
- package/template/src/rtm/RTMEngine.ts +33 -130
- package/template/src/rtm-events/constants.ts +6 -0
- package/template/src/rtm-events-api/Events.ts +30 -106
- package/template/src/subComponents/caption/Caption.tsx +48 -7
- package/template/src/subComponents/caption/CaptionContainer.tsx +324 -51
- package/template/src/subComponents/caption/CaptionIcon.tsx +35 -34
- package/template/src/subComponents/caption/CaptionText.tsx +103 -2
- package/template/src/subComponents/caption/LanguageSelectorPopup.tsx +179 -69
- package/template/src/subComponents/caption/Transcript.tsx +46 -11
- package/template/src/subComponents/caption/TranscriptIcon.tsx +27 -35
- package/template/src/subComponents/caption/TranscriptText.tsx +78 -3
- package/template/src/subComponents/caption/proto/ptoto.js +38 -4
- package/template/src/subComponents/caption/proto/test.proto +34 -19
- package/template/src/subComponents/caption/useCaption.tsx +753 -10
- package/template/src/subComponents/caption/useSTTAPI.tsx +118 -205
- package/template/src/subComponents/caption/useStreamMessageUtils.native.ts +152 -33
- package/template/src/subComponents/caption/useStreamMessageUtils.ts +165 -34
- package/template/src/subComponents/caption/utils.ts +171 -3
- package/template/src/utils/SdkEvents.ts +3 -0
- package/template/src/utils/useEndCall.ts +3 -5
- package/template/src/utils/useSpeechToText.ts +31 -20
- package/template/agora-rn-uikit/src/Reducer/Spotlight.ts +0 -11
- package/template/agora-rn-uikit/src/Reducer/UserBanned.ts +0 -11
- package/template/bridge/rtm/web/index-legacy.ts +0 -540
- 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 {
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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:
|
|
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:
|
|
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 (
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
92
|
+
closeActionSheet();
|
|
93
|
+
setLanguagePopup(false);
|
|
94
|
+
setIsCaptionON(prev => !prev);
|
|
95
|
+
await handleTranslateConfigChange(inputTranslateConfig);
|
|
97
96
|
} catch (error) {
|
|
98
|
-
|
|
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
|
-
{
|
|
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
|
|
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: (
|
|
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
|
|
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 [
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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={
|
|
76
|
-
setSelectedValues={
|
|
77
|
-
|
|
78
|
-
|
|
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={
|
|
83
|
-
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
|
-
|
|
88
|
-
{
|
|
89
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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:
|
|
256
|
+
maxWidth: 500,
|
|
166
257
|
width: '100%',
|
|
167
258
|
},
|
|
168
|
-
|
|
169
|
-
|
|
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:
|
|
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',
|