This commit is contained in:
65
react/features/subtitles/components/native/Captions.tsx
Normal file
65
react/features/subtitles/components/native/Captions.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import { GestureResponderEvent, StyleProp } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Container from '../../../base/react/components/native/Container';
|
||||
import Text from '../../../base/react/components/native/Text';
|
||||
import {
|
||||
AbstractCaptions,
|
||||
type IAbstractCaptionsProps,
|
||||
_abstractMapStateToProps
|
||||
} from '../AbstractCaptions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Captions}.
|
||||
*/
|
||||
interface IProps extends IAbstractCaptionsProps {
|
||||
onPress: (event: GestureResponderEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* React {@code Component} which can display speech-to-text results from
|
||||
* Jigasi as subtitles.
|
||||
*/
|
||||
class Captions extends AbstractCaptions<IProps> {
|
||||
/**
|
||||
* Renders the transcription text.
|
||||
*
|
||||
* @param {string} id - The ID of the transcript message from which the
|
||||
* {@code text} has been created.
|
||||
* @param {string} text - Subtitles text formatted with the participant's
|
||||
* name.
|
||||
* @protected
|
||||
* @returns {ReactElement} - The React element which displays the text.
|
||||
*/
|
||||
_renderParagraph(id: string, text: string): ReactElement {
|
||||
return (
|
||||
<Text
|
||||
key = { id }
|
||||
onPress = { this.props.onPress }
|
||||
style = { styles.captionsSubtitles as StyleProp<Object> } >
|
||||
{ text }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the subtitles container.
|
||||
*
|
||||
* @param {Array<ReactElement>} paragraphs - An array of elements created
|
||||
* for each subtitle using the {@link _renderParagraph} method.
|
||||
* @protected
|
||||
* @returns {ReactElement} - The subtitles container.
|
||||
*/
|
||||
_renderSubtitlesContainer(paragraphs: Array<ReactElement>): ReactElement {
|
||||
return (
|
||||
<Container style = { styles.captionsSubtitlesContainer } >
|
||||
{ paragraphs }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(_abstractMapStateToProps)(Captions);
|
||||
@@ -0,0 +1,59 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { CLOSE_CAPTIONS_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconSubtitles } from '../../../base/icons/svg';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import {
|
||||
AbstractClosedCaptionButton,
|
||||
_abstractMapStateToProps
|
||||
} from '../AbstractClosedCaptionButton';
|
||||
|
||||
/**
|
||||
* A button which starts/stops the transcriptions.
|
||||
*/
|
||||
class ClosedCaptionButton
|
||||
extends AbstractClosedCaptionButton {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.cc';
|
||||
override icon = IconSubtitles;
|
||||
override label = 'toolbar.startSubtitles';
|
||||
labelProps = {
|
||||
language: this.props.t(this.props._language ?? 'transcribing.subtitlesOff'),
|
||||
languages: this.props.t(this.props.languages ?? ''),
|
||||
languagesHead: this.props.t(this.props.languagesHead ?? '')
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle language selection dialog.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClickOpenLanguageSelector() {
|
||||
navigate(screen.conference.subtitles);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for this component.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component
|
||||
* instance.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const enabled = getFeatureFlag(state, CLOSE_CAPTIONS_ENABLED, true);
|
||||
const abstractProps = _abstractMapStateToProps(state, ownProps);
|
||||
|
||||
return {
|
||||
...abstractProps,
|
||||
visible: abstractProps.visible && enabled
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(ClosedCaptionButton));
|
||||
43
react/features/subtitles/components/native/LanguageList.tsx
Normal file
43
react/features/subtitles/components/native/LanguageList.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { ScrollView } from 'react-native';
|
||||
|
||||
import LanguageListItem from './LanguageListItem';
|
||||
import styles from './styles';
|
||||
|
||||
interface ILanguageListProps {
|
||||
items: Array<ILanguageItem>;
|
||||
onLanguageSelected: (lang: string) => void;
|
||||
selectedLanguage: string;
|
||||
}
|
||||
|
||||
interface ILanguageItem {
|
||||
id: string;
|
||||
lang: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that renders the security options dialog.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
const LanguageList = ({ items, onLanguageSelected }: ILanguageListProps) => {
|
||||
|
||||
const listItems = items?.map(item => (
|
||||
<LanguageListItem
|
||||
key = { item.id }
|
||||
lang = { item.lang }
|
||||
onLanguageSelected = { onLanguageSelected }
|
||||
selected = { item.selected } />
|
||||
));
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
bounces = { false }
|
||||
style = { styles.itemsContainer }>
|
||||
{ listItems }
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageList;
|
||||
@@ -0,0 +1,67 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { StyleProp, TouchableHighlight, View, ViewStyle } from 'react-native';
|
||||
import { Text } from 'react-native-paper';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconCheck } from '../../../base/icons/svg';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
interface ILanguageListItemProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Language string.
|
||||
*/
|
||||
lang: string;
|
||||
|
||||
/**
|
||||
* Callback for language selection.
|
||||
*/
|
||||
onLanguageSelected: (lang: string) => void;
|
||||
|
||||
/**
|
||||
* If language item is selected or not.
|
||||
*/
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that renders the language list item.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
|
||||
const LanguageListItem = ({ t, lang, selected, onLanguageSelected
|
||||
}: ILanguageListItemProps) => {
|
||||
|
||||
const onLanguageSelectedWrapper
|
||||
= useCallback(() => onLanguageSelected(lang), [ lang ]);
|
||||
|
||||
return (
|
||||
<View style = { styles.languageItemWrapper as StyleProp<ViewStyle> }>
|
||||
<View style = { styles.iconWrapper }>
|
||||
{
|
||||
selected
|
||||
&& <Icon
|
||||
size = { 20 }
|
||||
src = { IconCheck } />
|
||||
}
|
||||
</View>
|
||||
<TouchableHighlight
|
||||
onPress = { onLanguageSelectedWrapper }
|
||||
underlayColor = { 'transparent' } >
|
||||
<Text
|
||||
style = { [
|
||||
styles.languageItemText,
|
||||
selected && styles.activeLanguageItemText ] }>
|
||||
{ t(lang) }
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default translate(LanguageListItem);
|
||||
@@ -0,0 +1,38 @@
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { goBack }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import AbstractLanguageSelectorDialog, {
|
||||
IAbstractLanguageSelectorDialogProps
|
||||
} from '../AbstractLanguageSelectorDialog';
|
||||
|
||||
import LanguageList from './LanguageList';
|
||||
import styles from './styles';
|
||||
|
||||
const LanguageSelectorDialog = (props: IAbstractLanguageSelectorDialogProps) => {
|
||||
const { language, listItems, onLanguageSelected, subtitles } = props;
|
||||
|
||||
const onSelected = useCallback((e: string) => {
|
||||
onLanguageSelected(e);
|
||||
goBack();
|
||||
}, [ language ]);
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
disableForcedKeyboardDismiss = { true }
|
||||
style = { styles.subtitlesContainer }>
|
||||
<LanguageList
|
||||
items = { listItems }
|
||||
onLanguageSelected = { onSelected }
|
||||
selectedLanguage = { subtitles } />
|
||||
</JitsiScreen>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* We apply AbstractLanguageSelector to fill in the AbstractProps common
|
||||
* to both the web and native implementations.
|
||||
*/
|
||||
// eslint-disable-next-line new-cap
|
||||
export default AbstractLanguageSelectorDialog(LanguageSelectorDialog);
|
||||
63
react/features/subtitles/components/native/styles.ts
Normal file
63
react/features/subtitles/components/native/styles.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { BoxModel } from '../../../base/styles/components/styles/BoxModel';
|
||||
import {
|
||||
ColorPalette
|
||||
} from '../../../base/styles/components/styles/ColorPalette';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Component}s of the feature subtitles.
|
||||
*/
|
||||
export default {
|
||||
languageItemWrapper: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
iconWrapper: {
|
||||
width: 32
|
||||
},
|
||||
|
||||
activeLanguageItemText: {
|
||||
...BaseTheme.typography.bodyShortBoldLarge
|
||||
},
|
||||
|
||||
languageItemText: {
|
||||
...BaseTheme.typography.bodyShortRegularLarge,
|
||||
color: BaseTheme.palette.text01,
|
||||
marginLeft: BaseTheme.spacing[2],
|
||||
marginVertical: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
subtitlesContainer: {
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Style for subtitle paragraph.
|
||||
*/
|
||||
captionsSubtitles: {
|
||||
backgroundColor: ColorPalette.black,
|
||||
borderRadius: BoxModel.margin / 4,
|
||||
color: ColorPalette.white,
|
||||
marginBottom: BoxModel.margin,
|
||||
padding: BoxModel.padding / 2
|
||||
},
|
||||
|
||||
/**
|
||||
* Style for the subtitles container.
|
||||
*/
|
||||
captionsSubtitlesContainer: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 0,
|
||||
justifyContent: 'flex-end',
|
||||
margin: BoxModel.margin
|
||||
},
|
||||
|
||||
itemsContainer: {
|
||||
marginHorizontal: BaseTheme.spacing[4],
|
||||
marginVertical: BaseTheme.spacing[4]
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user