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.
- package/package.json +1 -1
- package/template/defaultConfig.js +2 -2
- 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 +1 -1
- package/template/src/atoms/CustomIcon.tsx +1 -0
- package/template/src/atoms/Popup.tsx +1 -1
- package/template/src/components/Controls.tsx +21 -32
- package/template/src/language/default-labels/videoCallScreenLabels.ts +9 -9
- package/template/src/pages/VideoCall.tsx +2 -1
- package/template/src/pages/video-call/ActionSheetContent.tsx +0 -7
- package/template/src/pages/video-call/SidePanelHeader.tsx +80 -63
- package/template/src/rtm-events/constants.ts +9 -0
- package/template/src/subComponents/caption/Caption.tsx +4 -20
- package/template/src/subComponents/caption/CaptionContainer.tsx +262 -250
- package/template/src/subComponents/caption/CaptionIcon.tsx +6 -4
- package/template/src/subComponents/caption/CaptionText.tsx +26 -20
- package/template/src/subComponents/caption/LanguageSelectorPopup.tsx +30 -142
- package/template/src/subComponents/caption/Transcript.tsx +77 -32
- package/template/src/subComponents/caption/TranscriptIcon.tsx +7 -6
- package/template/src/subComponents/caption/TranslateActionMenu.tsx +128 -0
- package/template/src/subComponents/caption/useCaption.tsx +629 -482
- package/template/src/subComponents/caption/useSTTAPI.tsx +25 -4
- package/template/src/subComponents/caption/useStreamMessageUtils.native.ts +1 -1
- package/template/src/subComponents/caption/useStreamMessageUtils.ts +1 -1
- 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 {
|
|
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
|
|
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
|
-
|
|
122
|
-
|
|
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)
|
|
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.
|
|
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 {
|
|
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
|
|
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: (
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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(
|
|
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(
|
|
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={
|
|
155
|
-
selectedValue={
|
|
156
|
-
onSelect={item =>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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={
|
|
90
|
+
<Spacer size={8} />
|
|
165
91
|
<Text style={styles.infoText}>
|
|
166
|
-
|
|
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('[
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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={
|
|
185
|
+
user={speakerName}
|
|
141
186
|
time={item?.time}
|
|
142
187
|
value={item.text}
|
|
143
|
-
translations={
|
|
188
|
+
translations={translation ? [translation] : []}
|
|
144
189
|
searchQuery={searchQuery}
|
|
145
|
-
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('[
|
|
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 {
|
|
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,
|
|
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:
|
|
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 (
|
|
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
|
|
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
|
+
};
|