Compare commits

..

17 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
d2521bc67a feat(android) bump minimum API level to 24
Some of our dependencies (most notably WebRTC) have dropped it and we
can no longer claim to support API level 23).
2023-05-01 11:06:44 +02:00
damencho
38a293f8f6 fix(chat): Fixes long display name that overflow the message bubble. 2023-04-28 13:31:14 -05:00
damencho
13e8f992b5 fix(chat): White square when both scrolls are visible. 2023-04-28 13:31:14 -05:00
damencho
c384d0d3a9 Revert "fix(chat) Make the name fit the chat bubble"
This reverts commit e56c7070c2.
2023-04-28 13:31:14 -05:00
Дамян Минков
2b71fa512b feat: Adds conference duration to the amplitude stat. (#13294) 2023-04-28 11:09:13 -05:00
damencho
d381ceb040 feat: Shows html in notification warning. 2023-04-27 12:42:28 -05:00
damencho
e18c428f52 fix: Hide virtual background for unsupported browsers from vide preview. 2023-04-27 12:42:12 -05:00
Gabriel Borlea
9fc32dc59b fix: multiselect for invite people (#13287)
* fix: multiselect duplicates

* set multiselect height to 200px
2023-04-27 10:14:16 -05:00
robertpin
6f45622ef1 fix(dialog) Fix close animation moves whole body 2023-04-27 10:06:17 -05:00
robertpin
e56c7070c2 fix(chat) Make the name fit the chat bubble 2023-04-27 09:40:27 -05:00
damencho
0aef7a36aa fix: Fixes unresolved function. 2023-04-27 09:25:07 -05:00
damencho
c030cf941e feat: Implements amplitude events for messages count. 2023-04-27 09:24:59 -05:00
Robert Pintilii
f6760e4ac7 fix(chat) Focus input on chat open (#13285) 2023-04-27 17:23:47 +03:00
Robert Pintilii
9060c77307 ref(TS) Convert some native components to TS (#13266) 2023-04-27 08:44:20 +03:00
Дамян Минков
a1d018eef4 feat: Disables sending localstats in visitor mode. (#13279) 2023-04-27 07:38:57 +02:00
Jaya Allamsetty
3f724d8fb7 fix(visitors) Do not add tracks in redux to conference. 2023-04-26 16:51:19 -04:00
Jaya Allamsetty
49d69a5a02 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1623.0.0+c520877a...v1624.0.0+e3a8472f
2023-04-26 13:04:01 -04:00
45 changed files with 469 additions and 373 deletions

View File

@@ -19,7 +19,7 @@ buildscript {
ext {
buildToolsVersion = "31.0.0"
compileSdkVersion = 32
minSdkVersion = 23
minSdkVersion = 24
targetSdkVersion = 32
supportLibVersion = "28.0.0"

View File

@@ -1406,6 +1406,7 @@ var config = {
disableAGC
disableAP
disableHPF
disableLocalStats
disableNS
enableTalkWhileMuted
forceJVB121Ratio

View File

@@ -74,6 +74,10 @@
a:active {
color: black;
}
&::-webkit-scrollbar-corner {
background: #3a3a3a;
}
}

View File

@@ -108,6 +108,10 @@
#largeVideoContainer {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
margin: 0 !important;
}
#largeVideoWrapper {

View File

@@ -11,11 +11,11 @@ import { setColorAlpha } from '../../react/features/base/util/helpers';
import { setDocumentUrl } from '../../react/features/etherpad/actions';
import { setFilmstripVisible } from '../../react/features/filmstrip/actions.any';
import {
joinLeaveNotificationsDisabled,
setNotificationsEnabled,
showNotification
} from '../../react/features/notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../react/features/notifications/constants';
import { joinLeaveNotificationsDisabled } from '../../react/features/notifications/functions';
import {
dockToolbox,
setToolboxEnabled,

45
package-lock.json generated
View File

@@ -60,7 +60,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1623.0.0+c520877a/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -12739,8 +12739,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1623.0.0+c520877a/lib-jitsi-meet.tgz",
"integrity": "sha512-dMNFJOfL3xFeh+HsZ4HoyWQY3ARU0Sz5tacIhEtg2Y2KJXGkAih+d8agHSZVAkTCU5S8JmX75q0gPc3MNZ5kWg==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
"integrity": "sha512-0LK5EMCvtsAEQkjkrWzyniTl8BKtshAZxI3e9hGnA/RVDuULLXre7GEWd2zCP3tCfdxclkhqt7skQpfNhCTdqg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -12758,7 +12758,7 @@
"patch-package": "6.5.1",
"promise.allsettled": "1.0.4",
"sdp-transform": "2.3.0",
"strophe.js": "1.6.0",
"strophe.js": "1.5.0",
"strophejs-plugin-disco": "0.0.2",
"strophejs-plugin-stream-management": "git+https://github.com/jitsi/strophejs-plugin-stream-management#679be5902097ed612fb5062b5549f3f32b6f5f47",
"uuid": "8.1.0",
@@ -17615,18 +17615,27 @@
}
},
"node_modules/strophe.js": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.6.0.tgz",
"integrity": "sha512-LE2B6nEJNUbF2Cl/p1tLIsXVJ9l86B/Z12HYYiO3n92VwYkhJ/5vJ+1ZMdwP9eN9GP8a3nbqfS5zE9umcK0FdA==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.5.0.tgz",
"integrity": "sha512-H5tE/tZxPR5xP3jhXyQwsjnMSwQMf7vrn9r1OkufTApyGHYe8WjzhsfxtL3AFhVu7vFjXPPZBrmUOTm1ccYgOA==",
"dependencies": {
"abab": "^2.0.3",
"karma-rollup-preprocessor": "^7.0.8"
},
"optionalDependencies": {
"@xmldom/xmldom": "0.8.3",
"@xmldom/xmldom": "0.8.2",
"ws": "^8.5.0"
}
},
"node_modules/strophe.js/node_modules/@xmldom/xmldom": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.2.tgz",
"integrity": "sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ==",
"optional": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/strophe.js/node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
@@ -29108,8 +29117,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1623.0.0+c520877a/lib-jitsi-meet.tgz",
"integrity": "sha512-dMNFJOfL3xFeh+HsZ4HoyWQY3ARU0Sz5tacIhEtg2Y2KJXGkAih+d8agHSZVAkTCU5S8JmX75q0gPc3MNZ5kWg==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
"integrity": "sha512-0LK5EMCvtsAEQkjkrWzyniTl8BKtshAZxI3e9hGnA/RVDuULLXre7GEWd2zCP3tCfdxclkhqt7skQpfNhCTdqg==",
"requires": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
@@ -29125,7 +29134,7 @@
"patch-package": "6.5.1",
"promise.allsettled": "1.0.4",
"sdp-transform": "2.3.0",
"strophe.js": "1.6.0",
"strophe.js": "1.5.0",
"strophejs-plugin-disco": "0.0.2",
"strophejs-plugin-stream-management": "git+https://github.com/jitsi/strophejs-plugin-stream-management#679be5902097ed612fb5062b5549f3f32b6f5f47",
"uuid": "8.1.0",
@@ -32836,16 +32845,22 @@
"dev": true
},
"strophe.js": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.6.0.tgz",
"integrity": "sha512-LE2B6nEJNUbF2Cl/p1tLIsXVJ9l86B/Z12HYYiO3n92VwYkhJ/5vJ+1ZMdwP9eN9GP8a3nbqfS5zE9umcK0FdA==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.5.0.tgz",
"integrity": "sha512-H5tE/tZxPR5xP3jhXyQwsjnMSwQMf7vrn9r1OkufTApyGHYe8WjzhsfxtL3AFhVu7vFjXPPZBrmUOTm1ccYgOA==",
"requires": {
"@xmldom/xmldom": "0.8.7",
"@xmldom/xmldom": "0.8.2",
"abab": "^2.0.3",
"karma-rollup-preprocessor": "^7.0.8",
"ws": "^8.5.0"
},
"dependencies": {
"@xmldom/xmldom": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.2.tgz",
"integrity": "sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ==",
"optional": true
},
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",

View File

@@ -65,7 +65,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1623.0.0+c520877a/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -3,6 +3,7 @@ import { sendAnalytics } from '../../analytics/functions';
import { appNavigate } from '../../app/actions';
import { IReduxState, IStore } from '../../app/types';
import { endpointMessageReceived } from '../../subtitles/actions.any';
import { iAmVisitor } from '../../visitors/functions';
import { getReplaceParticipant } from '../config/functions';
import { disconnect } from '../connection/actions';
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
@@ -450,11 +451,12 @@ export function conferenceUniqueIdSet(conference: IJitsiConference) {
*/
export function _conferenceWillJoin(conference: IJitsiConference) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const localTracks
= getLocalTracks(getState()['features/base/tracks'])
= getLocalTracks(state['features/base/tracks'])
.map(t => t.jitsiTrack);
if (localTracks.length) {
if (localTracks.length && !iAmVisitor(state)) {
_addLocalTracksToConference(conference, localTracks);
}

View File

@@ -308,6 +308,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
muc: config.oldConfig.hosts.muc
},
focusUserJid: focusJid,
disableLocalStats: false,
bosh: config.oldConfig.bosh && appendURLParam(config.oldConfig.bosh, 'customusername', username),
websocket: config.oldConfig.websocket
&& appendURLParam(config.oldConfig.websocket, 'customusername', username),
@@ -338,6 +339,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
},
focusUserJid: focusJid,
disableFocus: true, // This flag disables sending the initial conference request
disableLocalStats: true,
bosh: config.bosh && appendURLParam(config.bosh, 'vnode', vnode),
websocket: config.websocket && appendURLParam(config.websocket, 'vnode', vnode)
};

View File

@@ -29,7 +29,7 @@ interface IState {
* A react {@code Component} that implements an expanded label as tooltip-like
* component to explain the meaning of the {@code Label}.
*/
export default class ExpandedLabel<P extends IProps> extends Component<P, IState> {
export default abstract class ExpandedLabel<P extends IProps> extends Component<P, IState> {
/**
* Instantiates a new {@code ExpandedLabel} instance.
*
@@ -85,7 +85,7 @@ export default class ExpandedLabel<P extends IProps> extends Component<P, IState
*
* @returns {string}
*/
_getLabel: () => string;
abstract _getLabel(): string;
/**
* Defines the color of the expanded label. This function returns a default

View File

@@ -163,7 +163,7 @@ class MultiSelectAutocomplete extends Component<IProps, IState> {
const autoFocus = this.props.shouldFocus || false;
const disabled = this.props.isDisabled || false;
const placeholder = this.props.placeholder || '';
const noMatchesFound = this.props.noMatchesFound || '';
const noMatchesFound = this.state.loading ? this.props.loadingMessage : this.props.noMatchesFound || '';
const errorDialog = this._renderError();
return (
@@ -200,7 +200,7 @@ class MultiSelectAutocomplete extends Component<IProps, IState> {
error: this.state.error && Boolean(filterValue),
filterValue,
isOpen: Boolean(this.state.items.length) && Boolean(filterValue),
items: filterValue ? this.state.items : [],
items: [],
loading: Boolean(filterValue)
});
if (filterValue) {

View File

@@ -24,6 +24,8 @@ interface IProps {
selectedItems?: MultiSelectItem[];
}
const MULTI_SELECT_HEIGHT = 200;
const useStyles = makeStyles()(theme => {
return {
container: {
@@ -41,7 +43,7 @@ const useStyles = makeStyles()(theme => {
borderRadius: `${Number(theme.shape.borderRadius)}px`,
...withPixelLineHeight(theme.typography.bodyShortRegular),
zIndex: 2,
maxHeight: '400px',
maxHeight: `${MULTI_SELECT_HEIGHT}px`,
overflowY: 'auto',
padding: '0'
},
@@ -128,7 +130,7 @@ const MultiSelect = ({
</div>
</div>
))
: <div>{noMatchesText}</div>
: <div className = { classes.listItem }>{noMatchesText}</div>
}
</div>
), [ items ]);

View File

@@ -90,6 +90,8 @@ class ChatInput extends Component<IProps, IState> {
if (isMobileBrowser()) {
// Ensure textarea is not focused when opening chat on mobile browser.
this._textArea?.current && this._textArea.current.blur();
} else {
this._focus();
}
}

View File

@@ -29,9 +29,9 @@ const styles = (theme: Theme) => {
chatMessage: {
display: 'inline-flex',
padding: '12px',
marginRight: '12px',
backgroundColor: theme.palette.ui02,
borderRadius: '4px 12px 12px 12px',
boxSizing: 'border-box' as const,
maxWidth: '100%',
marginTop: '4px',

View File

@@ -18,6 +18,8 @@ import { getURLWithoutParamsNormalized } from '../base/connection/utils';
import { hideDialog } from '../base/dialog/actions';
import { isDialogOpen } from '../base/dialog/functions';
import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
import { translateToHTML } from '../base/i18n/functions';
import i18next from '../base/i18n/i18next';
import { browser } from '../base/lib-jitsi-meet';
import { pinParticipant } from '../base/participants/actions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
@@ -31,7 +33,7 @@ import { isCalendarEnabled } from '../calendar-sync/functions';
import FeedbackDialog from '../feedback/components/FeedbackDialog';
import { setFilmstripEnabled } from '../filmstrip/actions.any';
import { isVpaasMeeting } from '../jaas/functions';
import { hideNotification, showNotification } from '../notifications/actions';
import { hideNotification, showNotification, showWarningNotification } from '../notifications/actions';
import {
CALENDAR_NOTIFICATION_ID,
NOTIFICATION_ICON,
@@ -190,9 +192,14 @@ function _checkIframe(state: IReduxState, dispatch: IStore['dispatch']) {
if (inIframe() && state['features/base/config'].disableIframeAPI && !browser.isElectron()
&& !isVpaasMeeting(state) && !allowIframe) {
// show sticky notification and redirect in 5 minutes
dispatch(showNotification({
descriptionKey: 'notify.disabledIframe',
descriptionArguments: { timeout: IFRAME_DISABLED_TIMEOUT_MINUTES }
dispatch(showWarningNotification({
description: translateToHTML(
i18next.t.bind(i18next),
'notify.disabledIframe',
{
timeout: IFRAME_DISABLED_TIMEOUT_MINUTES
}
)
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
setTimeout(() => {

View File

@@ -1,12 +1,10 @@
// @flow
import React, { Component } from 'react';
import { Text } from 'react-native-paper';
import { connect } from 'react-redux';
import { type Dispatch } from 'redux';
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState } from '../../../app/types';
import { RAISE_HAND_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import { translate } from '../../../base/i18n/functions';
@@ -15,6 +13,7 @@ import {
getLocalParticipant,
hasRaisedHand
} from '../../../base/participants/functions';
import { ILocalParticipant } from '../../../base/participants/types';
import { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import Button from '../../../base/ui/components/native/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
@@ -24,43 +23,33 @@ import styles from './styles';
/**
* The type of the React {@code Component} props of {@link RaiseHandButton}.
*/
type Props = AbstractButtonProps & {
interface IProps extends AbstractButtonProps {
/**
* Whether this button is enabled or not.
*/
_enabled: boolean,
_enabled: boolean;
/**
* The local participant.
*/
_localParticipant: Object,
_localParticipant?: ILocalParticipant;
/**
* Whether the participant raused their hand or not.
*/
_raisedHand: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Dispatch<any>,
/**
* Used for translation.
*/
t: Function,
_raisedHand: boolean;
/**
* Used to close the overflow menu after raise hand is clicked.
*/
onCancel: Function
};
onCancel: Function;
}
/**
* An implementation of a button to raise or lower hand.
*/
class RaiseHandButton extends Component<Props, *> {
class RaiseHandButton extends Component<IProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand';
label = 'toolbar.raiseYourHand';
toggledLabel = 'toolbar.lowerYourHand';
@@ -68,10 +57,10 @@ class RaiseHandButton extends Component<Props, *> {
/**
* Initializes a new {@code RaiseHandButton} instance.
*
* @param {Props} props - The React {@code Component} props to initialize
* @param {IProps} props - The React {@code Component} props to initialize
* the new {@code RaiseHandButton} instance with.
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once per instance.
@@ -80,12 +69,6 @@ class RaiseHandButton extends Component<Props, *> {
this._getLabel = this._getLabel.bind(this);
}
_onClick: () => void;
_toggleRaisedHand: () => void;
_getLabel: () => string;
/**
* Handles clicking / pressing the button.
*
@@ -163,9 +146,9 @@ class RaiseHandButton extends Component<Props, *> {
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state): Object {
function _mapStateToProps(state: IReduxState) {
const _localParticipant = getLocalParticipant(state);
const enabled = getFeatureFlag(state, RAISE_HAND_ENABLED, true);

View File

@@ -1,7 +1,6 @@
// @flow
import React, { useCallback } from 'react';
import { Text, TouchableHighlight } from 'react-native';
import { WithTranslation } from 'react-i18next';
import { ColorValue, GestureResponderEvent, Text, TouchableHighlight, ViewStyle } from 'react-native';
import { useDispatch } from 'react-redux';
import { createReactionMenuEvent } from '../../../analytics/AnalyticsEvents';
@@ -11,71 +10,65 @@ import { StyleType } from '../../../base/styles/functions.native';
import { addReactionToBuffer } from '../../actions.any';
import { REACTIONS } from '../../constants';
export type ReactionStyles = {
/**
* Style for the gif button.
*/
gifButton: StyleType,
/**
* Style for the button.
*/
style: StyleType,
/**
* Underlay color for the button.
*/
underlayColor: StyleType,
/**
* Style for the emoji text on the button.
*/
emoji: StyleType,
/**
* Style for the label text on the button.
*/
text?: StyleType,
interface IReactionStyles {
/**
* Style for text container. Used on raise hand button.
*/
container?: StyleType
container?: StyleType;
/**
* Style for the emoji text on the button.
*/
emoji: StyleType;
/**
* Style for the gif button.
*/
gifButton: StyleType;
/**
* Style for the button.
*/
style: StyleType;
/**
* Style for the label text on the button.
*/
text?: StyleType;
/**
* Underlay color for the button.
*/
underlayColor: ColorValue;
}
/**
* The type of the React {@code Component} props of {@link ReactionButton}.
*/
type Props = {
interface IProps extends WithTranslation {
/**
* Component children.
*/
children?: ReactNode,
children?: React.ReactNode;
/**
* External click handler.
*/
onClick?: Function,
/**
* Collection of styles for the button.
*/
styles: ReactionStyles,
onClick?: (e?: GestureResponderEvent) => void;
/**
* The reaction to be sent.
*/
reaction: string,
reaction?: string;
/**
* Invoked to obtain translated strings.
* Collection of styles for the button.
*/
t: Function
};
styles: IReactionStyles;
}
/**
* An implementation of a button to send a reaction.
@@ -88,11 +81,13 @@ function ReactionButton({
styles,
reaction,
t
}: Props) {
}: IProps) {
const dispatch = useDispatch();
const _onClick = useCallback(() => {
dispatch(addReactionToBuffer(reaction));
sendAnalytics(createReactionMenuEvent(reaction));
if (reaction) {
dispatch(addReactionToBuffer(reaction));
sendAnalytics(createReactionMenuEvent(reaction));
}
}, [ reaction ]);
return (
@@ -100,9 +95,9 @@ function ReactionButton({
accessibilityLabel = { t(`toolbar.accessibilityLabel.${reaction}`) }
accessibilityRole = 'button'
onPress = { onClick || _onClick }
style = { [ styles.style, children && styles?.gifButton ] }
style = { [ styles.style, children && styles?.gifButton ] as ViewStyle[] }
underlayColor = { styles.underlayColor }>
{children ?? <Text style = { styles.emoji }>{REACTIONS[reaction].emoji}</Text>}
{children ?? <Text style = { styles.emoji }>{REACTIONS[reaction ?? ''].emoji}</Text>}
</TouchableHighlight>
);
}

View File

@@ -1,11 +1,10 @@
// @flow
import React, { useCallback } from 'react';
import { Image, View } from 'react-native';
import { useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
import { isGifEnabled } from '../../../gifs/functions';
import { isGifEnabled } from '../../../gifs/functions.native';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { REACTIONS } from '../../constants';
@@ -16,18 +15,18 @@ import ReactionButton from './ReactionButton';
/**
* The type of the React {@code Component} props of {@link ReactionMenu}.
*/
type Props = {
interface IProps {
/**
* Used to close the overflow menu after raise hand is clicked.
*/
onCancel: Function,
onCancel: Function;
/**
* Whether or not it's displayed in the overflow menu.
*/
overflowMenu: boolean
};
overflowMenu: boolean;
}
/**
* Animated reaction emoji.
@@ -37,8 +36,8 @@ type Props = {
function ReactionMenu({
onCancel,
overflowMenu
}: Props) {
const _styles = useSelector(state => ColorSchemeRegistry.get(state, 'Toolbox'));
}: IProps) {
const _styles: any = useSelector((state: IReduxState) => ColorSchemeRegistry.get(state, 'Toolbox'));
const gifEnabled = useSelector(isGifEnabled);
const openGifMenu = useCallback(() => {
@@ -62,7 +61,7 @@ function ReactionMenu({
<ReactionButton
onClick = { openGifMenu }
styles = { _styles.reactionButton }>
<Image
<Image // @ts-ignore
height = { 22 }
source = { require('../../../../../images/GIPHY_icon.png') } />
</ReactionButton>

View File

@@ -1,9 +1,8 @@
// @flow
import React, { PureComponent } from 'react';
import React, { ComponentType, PureComponent } from 'react';
import { SafeAreaView, TouchableWithoutFeedback, View } from 'react-native';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
import { hideDialog } from '../../../base/dialog/actions';
import { isDialogOpen } from '../../../base/dialog/functions';
@@ -15,38 +14,38 @@ import ReactionMenu from './ReactionMenu';
/**
* The type of the React {@code Component} props of {@link ReactionMenuDialog}.
*/
type Props = {
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* True if the dialog is currently visible, false otherwise.
*/
_isOpen: boolean,
/**
* The width of the screen.
*/
_width: number,
interface IProps {
/**
* The height of the screen.
*/
_height: number,
_height: number;
/**
* True if the dialog is currently visible, false otherwise.
*/
_isOpen: boolean;
/**
* Number of conference participants.
*/
_participantCount: number,
_participantCount: number;
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType;
/**
* The width of the screen.
*/
_width: number;
/**
* Used for hiding the dialog when the selection was completed.
*/
dispatch: Function
};
dispatch: IStore['dispatch'];
}
/**
* The exported React {@code Component}. We need it to execute
@@ -55,19 +54,19 @@ type Props = {
* XXX It does not break our coding style rule to not utilize globals for state,
* because it is merely another name for {@code export}'s {@code default}.
*/
let ReactionMenu_; // eslint-disable-line prefer-const
let ReactionMenu_: ComponentType<any>; // eslint-disable-line prefer-const
/**
* Implements a React {@code Component} with some extra actions in addition to
* those in the toolbar.
*/
class ReactionMenuDialog extends PureComponent<Props> {
class ReactionMenuDialog extends PureComponent<IProps> {
/**
* Initializes a new {@code ReactionMenuDialog} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once per instance.
@@ -103,8 +102,6 @@ class ReactionMenuDialog extends PureComponent<Props> {
);
}
_onCancel: () => boolean;
/**
* Hides this {@code ReactionMenuDialog}.
*
@@ -127,11 +124,11 @@ class ReactionMenuDialog extends PureComponent<Props> {
*
* @param {Object} state - Redux state.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
return {
_isOpen: isDialogOpen(state, ReactionMenu_),
_isOpen: isDialogOpen(state, ReactionMenu_), // @ts-ignore
_styles: ColorSchemeRegistry.get(state, 'Toolbox').reactionDialog,
_width: state['features/base/responsive-ui'].clientWidth,
_height: state['features/base/responsive-ui'].clientHeight,

View File

@@ -1,8 +1,6 @@
// @flow
import { connect } from 'react-redux';
import { type Dispatch } from 'redux';
import { IReduxState } from '../../../app/types';
import { openDialog } from '../../../base/dialog/actions';
import { isDialogOpen } from '../../../base/dialog/functions';
import { RAISE_HAND_ENABLED } from '../../../base/flags/constants';
@@ -19,28 +17,23 @@ import ReactionMenuDialog from './ReactionMenuDialog';
/**
* The type of the React {@code Component} props of {@link ReactionsMenuButton}.
*/
type Props = AbstractButtonProps & {
interface IProps extends AbstractButtonProps {
/**
* Whether the participant raised their hand or not.
*/
_raisedHand: boolean,
_raisedHand: boolean;
/**
* Whether or not the reactions menu is open.
*/
_reactionsOpen: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Dispatch<any>
};
_reactionsOpen: boolean;
}
/**
* An implementation of a button to raise or lower hand.
*/
class ReactionsMenuButton extends AbstractButton<Props, *> {
class ReactionsMenuButton extends AbstractButton<IProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.reactionsMenu';
icon = IconRaiseHand;
label = 'toolbar.openReactionsMenu';
@@ -75,9 +68,9 @@ class ReactionsMenuButton extends AbstractButton<Props, *> {
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The properties explicitly passed to the component instance.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state, ownProps): Object {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const _localParticipant = getLocalParticipant(state);
const enabled = getFeatureFlag(state, RAISE_HAND_ENABLED, true);
const { visible = enabled } = ownProps;

View File

@@ -1,4 +1,5 @@
import { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { createLiveStreamingDialogEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
@@ -10,7 +11,7 @@ import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
* The type of the React {@code Component} props of
* {@link AbstractStartLiveStreamDialog}.
*/
export interface IProps {
export interface IProps extends WithTranslation {
/**
* The {@code JitsiConference} for the current conference.
@@ -39,10 +40,7 @@ export interface IProps {
*/
dispatch: Function;
/**
* Invoked to obtain translated strings.
*/
t: Function;
navigation?: any;
}
/**

View File

@@ -38,7 +38,7 @@ export interface IProps extends WithTranslation {
*/
_liveStreaming: LiveStreamingProps;
classes: any;
classes?: any;
/**
* Callback invoked when the entered stream key has changed.

View File

@@ -1,18 +1,19 @@
// @flow
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { WithTranslation } from 'react-i18next';
import { Text, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { _abstractMapStateToProps } from '../../../../base/dialog/functions';
import { translate } from '../../../../base/i18n/functions';
import { StyleType } from '../../../../base/styles/functions.native';
import { setGoogleAPIState } from '../../../../google-api/actions';
import GoogleSignInButton from '../../../../google-api/components/GoogleSignInButton.native';
import {
GOOGLE_API_STATES,
GOOGLE_SCOPE_YOUTUBE
} from '../../../../google-api/constants';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import googleApi from '../../../../google-api/googleApi.native';
import logger from '../../../logger';
@@ -21,52 +22,47 @@ import styles from './styles';
/**
* Prop type of the component {@code GoogleSigninForm}.
*/
type Props = {
interface IProps extends WithTranslation {
/**
* Style of the dialogs feature.
*/
_dialogStyles: StyleType,
_dialogStyles: any;
/**
* The Redux dispatch Function.
*/
dispatch: Function,
dispatch: Function;
/**
* The current state of the Google api as defined in {@code constants.js}.
*/
googleAPIState: number,
googleAPIState: number;
/**
* The recently received Google response.
*/
googleResponse: Object,
googleResponse: any;
/**
* A callback to be invoked when an authenticated user changes, so
* then we can get (or clear) the YouTube stream key.
*/
onUserChanged: Function,
/**
* Function to be used to translate i18n labels.
*/
t: Function
};
onUserChanged: Function;
}
/**
* Class to render a google sign in form, or a google stream picker dialog.
*
* @augments Component
*/
class GoogleSigninForm extends Component<Props> {
class GoogleSigninForm extends Component<IProps> {
/**
* Instantiates a new {@code GoogleSigninForm} component.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._logGoogleError = this._logGoogleError.bind(this);
@@ -86,7 +82,7 @@ class GoogleSigninForm extends Component<Props> {
scopes: [ GOOGLE_SCOPE_YOUTUBE ]
});
googleApi.signInSilently().then(response => {
googleApi.signInSilently().then((response: any) => {
this._setApiState(response
? GOOGLE_API_STATES.SIGNED_IN
: GOOGLE_API_STATES.LOADED,
@@ -95,7 +91,7 @@ class GoogleSigninForm extends Component<Props> {
this._setApiState(GOOGLE_API_STATES.LOADED);
});
})
.catch(error => {
.catch((error: Error) => {
this._logGoogleError(error);
this._setApiState(GOOGLE_API_STATES.NOT_AVAILABLE);
});
@@ -109,9 +105,7 @@ class GoogleSigninForm extends Component<Props> {
render() {
const { _dialogStyles, t } = this.props;
const { googleAPIState, googleResponse } = this.props;
const signedInUser = googleResponse
&& googleResponse.user
&& googleResponse.user.email;
const signedInUser = googleResponse?.user?.email;
if (googleAPIState === GOOGLE_API_STATES.NOT_AVAILABLE
|| googleAPIState === GOOGLE_API_STATES.NEEDS_LOADING
@@ -124,8 +118,8 @@ class GoogleSigninForm extends Component<Props> {
: t('liveStreaming.signInCTA');
return (
<View style = { styles.formWrapper }>
<View style = { styles.helpText }>
<View style = { styles.formWrapper as ViewStyle }>
<View style = { styles.helpText as ViewStyle }>
<Text
style = { [
_dialogStyles.text,
@@ -142,8 +136,6 @@ class GoogleSigninForm extends Component<Props> {
);
}
_logGoogleError: Object => void;
/**
* A helper function to log developer related errors.
*
@@ -151,14 +143,12 @@ class GoogleSigninForm extends Component<Props> {
* @param {Object} error - The error to be logged.
* @returns {void}
*/
_logGoogleError(error) {
_logGoogleError(error: Error) {
// NOTE: This is a developer error message, not intended for the
// user to see.
logger.error('Google API error. Possible cause: bad config.', error);
}
_onGoogleButtonPress: () => void;
/**
* Callback to be invoked when the user presses the Google button,
* regardless of being logged in or out.
@@ -169,7 +159,7 @@ class GoogleSigninForm extends Component<Props> {
_onGoogleButtonPress() {
const { googleResponse } = this.props;
if (googleResponse && googleResponse.user) {
if (googleResponse?.user) {
// the user is signed in
this._onSignOut();
} else {
@@ -177,8 +167,6 @@ class GoogleSigninForm extends Component<Props> {
}
}
_onSignIn: () => void;
/**
* Initiates a sign in if the user is not signed in yet.
*
@@ -186,13 +174,11 @@ class GoogleSigninForm extends Component<Props> {
* @returns {void}
*/
_onSignIn() {
googleApi.signIn().then(response => {
googleApi.signIn().then((response: any) => {
this._setApiState(GOOGLE_API_STATES.SIGNED_IN, response);
}, this._logGoogleError);
}
_onSignOut: () => void;
/**
* Initiates a sign out if the user is signed in.
*
@@ -200,7 +186,7 @@ class GoogleSigninForm extends Component<Props> {
* @returns {void}
*/
_onSignOut() {
googleApi.signOut().then(response => {
googleApi.signOut().then((response: any) => {
this._setApiState(GOOGLE_API_STATES.LOADED, response);
}, this._logGoogleError);
}
@@ -213,7 +199,7 @@ class GoogleSigninForm extends Component<Props> {
* @param {?Object} googleResponse - The response from the API.
* @returns {void}
*/
_setApiState(apiState, googleResponse) {
_setApiState(apiState: number, googleResponse?: Object) {
this.props.onUserChanged(googleResponse);
this.props.dispatch(setGoogleAPIState(apiState, googleResponse));
}
@@ -230,7 +216,7 @@ class GoogleSigninForm extends Component<Props> {
* googleResponse: Object
* }}
*/
function _mapStateToProps(state: Object) {
function _mapStateToProps(state: IReduxState) {
const { googleAPIState, googleResponse } = state['features/google-api'];
return {

View File

@@ -1,7 +1,6 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { openDialog } from '../../../../base/dialog/actions';
import { LIVE_STREAMING_ENABLED } from '../../../../base/flags/constants';
import { getFeatureFlag } from '../../../../base/flags/functions';
@@ -9,12 +8,15 @@ import { translate } from '../../../../base/i18n/functions';
import { navigate }
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../../mobile/navigation/routes';
import AbstractLiveStreamButton,
{ _mapStateToProps as _abstractMapStateToProps } from '../AbstractLiveStreamButton';
import type { Props } from '../AbstractStartLiveStreamDialog';
import AbstractLiveStreamButton, {
IProps as AbstractProps,
_mapStateToProps as _abstractMapStateToProps
} from '../AbstractLiveStreamButton';
import { IProps } from '../AbstractStartLiveStreamDialog';
import StopLiveStreamDialog from './StopLiveStreamDialog';
type Props = IProps & AbstractProps;
/**
* Button for opening the live stream settings screen.
@@ -48,7 +50,7 @@ class LiveStreamButton extends AbstractLiveStreamButton<Props> {
* @private
* @returns {Props}
*/
export function mapStateToProps(state: Object, ownProps: Object) {
export function mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, LIVE_STREAMING_ENABLED, true);
const abstractProps = _abstractMapStateToProps(state, ownProps);

View File

@@ -1,18 +1,18 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import { translate } from '../../../../base/i18n/functions';
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
import { StyleType } from '../../../../base/styles/functions.any';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import googleApi from '../../../../google-api/googleApi.native';
import HeaderNavigationButton
from '../../../../mobile/navigation/components/HeaderNavigationButton';
import { goBack }
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { setLiveStreamKey } from '../../../actions';
import AbstractStartLiveStreamDialog,
{ type Props, _mapStateToProps } from '../AbstractStartLiveStreamDialog';
import AbstractStartLiveStreamDialog, { IProps, _mapStateToProps } from '../AbstractStartLiveStreamDialog';
import GoogleSigninForm from './GoogleSigninForm';
import StreamKeyForm from './StreamKeyForm';
@@ -23,13 +23,13 @@ import styles from './styles';
* A React Component for requesting a YouTube stream key to use for live
* streaming of the current conference.
*/
class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<IProps> {
/**
* Constructor of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once per instance.
@@ -60,8 +60,6 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
});
}
_onStartPress: () => void;
/**
* Starts live stream session and goes back to the previous screen.
*
@@ -78,7 +76,7 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
*/
render() {
return (
<JitsiScreen style = { styles.startLiveStreamContainer }>
<JitsiScreen style = { styles.startLiveStreamContainer as StyleType }>
<GoogleSigninForm
onUserChanged = { this._onUserChanged } />
<StreamKeyPicker
@@ -87,20 +85,12 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
<StreamKeyForm
onChange = { this._onStreamKeyChangeNative }
value = {
this.state.streamKey || this.props._streamKey
this.state.streamKey || this.props._streamKey || ''
} />
</JitsiScreen>
);
}
_onCancel: () => boolean;
_onSubmit: () => boolean;
_onStreamKeyChange: string => void;
_onStreamKeyChangeNative: string => void;
/**
* Callback to handle stream key changes.
*
@@ -113,13 +103,11 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
* @param {string} streamKey - The new key value.
* @returns {void}
*/
_onStreamKeyChangeNative(streamKey) {
_onStreamKeyChangeNative(streamKey: string) {
this.props.dispatch(setLiveStreamKey(streamKey));
this._onStreamKeyChange(streamKey);
}
_onStreamKeyPick: string => void;
/**
* Callback to be invoked when the user selects a stream from the picker.
*
@@ -127,14 +115,12 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
* @param {string} streamKey - The key of the selected stream.
* @returns {void}
*/
_onStreamKeyPick(streamKey) {
_onStreamKeyPick(streamKey: string) {
this.setState({
streamKey
});
}
_onUserChanged: Object => void;
/**
* A callback to be invoked when an authenticated user changes, so
* then we can get (or clear) the YouTube stream key.
@@ -145,12 +131,12 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
* @param {Object} response - The retrieved signin response.
* @returns {void}
*/
_onUserChanged(response) {
_onUserChanged(response: Object) {
if (response) {
googleApi.getTokens()
.then(tokens => {
.then((tokens: any) => {
googleApi.getYouTubeLiveStreams(tokens.accessToken)
.then(broadcasts => {
.then((broadcasts: any) => {
this.setState({
broadcasts
});

View File

@@ -1,5 +1,3 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
@@ -30,8 +28,6 @@ class StopLiveStreamDialog extends AbstractStopLiveStreamDialog {
onSubmit = { this._onSubmit } />
);
}
_onSubmit: () => boolean;
}
export default translate(connect(_mapStateToProps)(StopLiveStreamDialog));

View File

@@ -1,42 +1,41 @@
import React from 'react';
import { Linking, Text, View } from 'react-native';
import { Linking, Text, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { _abstractMapStateToProps } from '../../../../base/dialog/functions';
import { translate } from '../../../../base/i18n/functions';
import { StyleType } from '../../../../base/styles/functions.native';
import Button from '../../../../base/ui/components/native/Button';
import Input from '../../../../base/ui/components/native/Input';
import { BUTTON_TYPES } from '../../../../base/ui/constants.native';
import AbstractStreamKeyForm, {
type Props as AbstractProps
IProps as AbstractProps
} from '../AbstractStreamKeyForm';
import { getLiveStreaming } from '../functions';
import styles from './styles';
type Props = AbstractProps & {
interface IProps extends AbstractProps {
/**
* Style of the dialogs feature.
*/
_dialogStyles: StyleType
};
_dialogStyles: any;
}
/**
* A React Component for entering a key for starting a YouTube live stream.
*
* @augments Component
*/
class StreamKeyForm extends AbstractStreamKeyForm<Props> {
class StreamKeyForm extends AbstractStreamKeyForm<IProps> {
/**
* Initializes a new {@code StreamKeyForm} instance.
*
* @param {Props} props - The React {@code Component} props to initialize
* @param {IProps} props - The React {@code Component} props to initialize
* the new {@code StreamKeyForm} instance with.
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once per instance.
@@ -57,7 +56,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
return (
<>
<View style = { styles.formWrapper }>
<View style = { styles.formWrapper as ViewStyle }>
<Input
customStyles = {{
input: styles.streamKeyInput,
@@ -65,7 +64,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
onChange = { this._onInputChange }
placeholder = { t('liveStreaming.enterStreamKey') }
value = { this.props.value } />
<View style = { styles.formValidationItem }>
<View style = { styles.formValidationItem as ViewStyle }>
{
this.state.showValidationError && <Text
style = { [
@@ -77,7 +76,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
}
</View>
</View>
<View style = { styles.formButtonsWrapper }>
<View style = { styles.formButtonsWrapper as ViewStyle }>
<Button
accessibilityLabel = 'liveStreaming.streamIdHelp'
labelKey = 'liveStreaming.streamIdHelp'
@@ -101,10 +100,6 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
);
}
_onInputChange: Object => void;
_onOpenGooglePrivacyPolicy: () => void;
/**
* Opens the Google Privacy Policy web page.
*
@@ -115,8 +110,6 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
Linking.openURL(this.props._liveStreaming.dataPrivacyURL);
}
_onOpenHelp: () => void;
/**
* Opens the information link on how to manually locate a YouTube broadcast
* stream key.
@@ -132,8 +125,6 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
}
}
_onOpenYoutubeTerms: () => void;
/**
* Opens the YouTube terms and conditions web page.
*
@@ -155,7 +146,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
* _liveStreaming: LiveStreamingProps
* }}
*/
function _mapStateToProps(state: Object) {
function _mapStateToProps(state: IReduxState) {
return {
..._abstractMapStateToProps(state),
_liveStreaming: getLiveStreaming(state)

View File

@@ -1,52 +1,46 @@
// @flow
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import {
Linking,
Text,
TouchableHighlight,
TouchableOpacity,
View
View,
ViewStyle
} from 'react-native';
import { connect } from 'react-redux';
import { _abstractMapStateToProps } from '../../../../base/dialog/functions';
import { translate } from '../../../../base/i18n/functions';
import { StyleType } from '../../../../base/styles/functions.native';
import { YOUTUBE_LIVE_DASHBOARD_URL } from '../constants';
import styles, { ACTIVE_OPACITY, TOUCHABLE_UNDERLAY } from './styles';
type Props = {
interface IProps extends WithTranslation {
/**
* Style of the dialogs feature.
*/
_dialogStyles: StyleType,
_dialogStyles: any;
/**
* The list of broadcasts the user can pick from.
*/
broadcasts: ?Array<Object>,
broadcasts?: Array<{ key: string; title: string; }>;
/**
* Callback to be invoked when the user picked a broadcast. To be invoked
* with a single key (string).
*/
onChange: Function,
/**
* Function to be used to translate i18n labels.
*/
t: Function
onChange: Function;
}
type State = {
interface IState {
/**
* The key of the currently selected stream.
*/
streamKey: ?string
/**
* The key of the currently selected stream.
*/
streamKey?: string | null;
}
/**
@@ -57,14 +51,14 @@ type State = {
* a later point to unify mobile and web logic for this functionality. But it's
* out of the scope for now of the mobile live streaming functionality.
*/
class StreamKeyPicker extends Component<Props, State> {
class StreamKeyPicker extends Component<IProps, IState> {
/**
* Instantiates a new instance of StreamKeyPicker.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this.state = {
@@ -89,7 +83,7 @@ class StreamKeyPicker extends Component<Props, State> {
if (!broadcasts.length) {
return (
<View style = { styles.formWrapper }>
<View style = { styles.formWrapper as ViewStyle }>
<TouchableOpacity
onPress = { this._onOpenYoutubeDashboard }>
<Text
@@ -106,8 +100,8 @@ class StreamKeyPicker extends Component<Props, State> {
}
return (
<View style = { styles.formWrapper }>
<View style = { styles.streamKeyPickerCta }>
<View style = { styles.formWrapper as ViewStyle }>
<View style = { styles.streamKeyPickerCta as ViewStyle }>
<Text
style = { [
_dialogStyles.text,
@@ -116,7 +110,7 @@ class StreamKeyPicker extends Component<Props, State> {
{ this.props.t('liveStreaming.choose') }
</Text>
</View>
<View style = { styles.streamKeyPickerWrapper } >
<View style = { styles.streamKeyPickerWrapper as ViewStyle } >
{ broadcasts.map((broadcast, index) =>
(<TouchableHighlight
activeOpacity = { ACTIVE_OPACITY }
@@ -126,7 +120,7 @@ class StreamKeyPicker extends Component<Props, State> {
styles.streamKeyPickerItem,
this.state.streamKey === broadcast.key
? styles.streamKeyPickerItemHighlight : null
] }
] as ViewStyle[] }
underlayColor = { TOUCHABLE_UNDERLAY }>
<Text
style = { [
@@ -142,8 +136,6 @@ class StreamKeyPicker extends Component<Props, State> {
);
}
_onOpenYoutubeDashboard: () => void;
/**
* Opens the link which should display the YouTube broadcast live stream
* key.
@@ -155,8 +147,6 @@ class StreamKeyPicker extends Component<Props, State> {
Linking.openURL(YOUTUBE_LIVE_DASHBOARD_URL);
}
_onStreamPick: string => Function;
/**
* Callback to be invoked when the user picks a stream from the list.
*
@@ -164,7 +154,7 @@ class StreamKeyPicker extends Component<Props, State> {
* @param {string} streamKey - The key of the stream selected.
* @returns {Function}
*/
_onStreamPick(streamKey) {
_onStreamPick(streamKey: string) {
return () => {
this.setState({
streamKey

View File

@@ -1,5 +1,3 @@
// @flow
import { createStyleSheet } from '../../../../base/styles/functions.native';
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';

View File

@@ -83,6 +83,8 @@ export interface IProps extends WithTranslation {
* The redux dispatch function.
*/
dispatch: IStore['dispatch'];
navigation: any;
}
interface IState {
@@ -386,6 +388,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
* {@code StartRecordingDialog} component.
*
* @param {Object} state - The Redux state.
* @param {any} _ownProps - Component's own props.
* @private
* @returns {{
* _appKey: string,
@@ -399,7 +402,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
* _token: string
* }}
*/
export function mapStateToProps(state: IReduxState) {
export function mapStateToProps(state: IReduxState, _ownProps: any) {
const {
transcription,
recordingService,

View File

@@ -92,7 +92,7 @@ export interface IProps extends WithTranslation {
/**
* Whether or not we should only record the local streams.
*/
localRecordingOnlySelf: boolean;
localRecordingOnlySelf?: boolean;
/**
* The function will be called when there are changes related to the
@@ -103,7 +103,7 @@ export interface IProps extends WithTranslation {
/**
* Callback to change the local recording only self setting.
*/
onLocalRecordingSelfChange: () => void;
onLocalRecordingSelfChange?: () => void;
/**
* Callback to be invoked on sharing setting change.

View File

@@ -1,35 +1,31 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { translate } from '../../../../base/i18n/functions';
import { IconHighlight } from '../../../../base/icons/svg';
import Label from '../../../../base/label/components/native/Label';
import BaseTheme from '../../../../base/ui/components/BaseTheme';
import AbstractHighlightButton, {
type Props as AbstractProps,
IProps as AbstractProps,
_abstractMapStateToProps
} from '../AbstractHighlightButton';
import styles from '../styles.native';
type Props = AbstractProps & {
_disabled: boolean,
interface IProps extends AbstractProps {
_disabled: boolean;
/**
* Flag controlling visibility of the component.
*/
_visible: boolean,
dispatch: Dispatch<any>
};
_visible: boolean;
}
/**
* React {@code Component} responsible for displaying an action that
* allows users to highlight a meeting moment.
*/
export class HighlightButton extends AbstractHighlightButton<Props> {
export class HighlightButton extends AbstractHighlightButton<IProps> {
/**
* Implements React's {@link Component#render()}.
@@ -52,7 +48,6 @@ export class HighlightButton extends AbstractHighlightButton<Props> {
<Label
icon = { IconHighlight }
iconColor = { BaseTheme.palette.field01 }
id = 'highlightMeetingLabel'
style = { styles.highlightButton }
text = { t('recording.highlight') }
textStyle = { styles.highlightButtonText } />

View File

@@ -1,6 +1,6 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Text, View } from 'react-native';
import { Text, View, ViewStyle } from 'react-native';
import { batch, useDispatch } from 'react-redux';
import { hideSheet } from '../../../../base/dialog/actions';
@@ -28,7 +28,7 @@ const HighlightDialog = () => {
<Text style = { styles.highlightDialogText }>
{ t('recording.highlightMomentSucessDescription') }
</Text>
<View style = { styles.highlightDialogButtonsContainer } >
<View style = { styles.highlightDialogButtonsContainer as ViewStyle } >
<Button
accessibilityLabel = 'dialog.Cancel'
labelKey = 'dialog.Cancel'

View File

@@ -1,8 +1,7 @@
// @flow
import { Platform } from 'react-native';
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { openDialog } from '../../../../base/dialog/actions';
import { IOS_RECORDING_ENABLED, RECORDING_ENABLED } from '../../../../base/flags/constants';
import { getFeatureFlag } from '../../../../base/flags/functions';
@@ -10,12 +9,15 @@ import { translate } from '../../../../base/i18n/functions';
import { navigate }
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../../mobile/navigation/routes';
import type { Props } from '../../LiveStream/AbstractStartLiveStreamDialog';
import AbstractRecordButton,
{ _mapStateToProps as _abstractMapStateToProps } from '../AbstractRecordButton';
import { IProps } from '../../LiveStream/AbstractStartLiveStreamDialog';
import AbstractRecordButton, {
IProps as AbstractProps,
_mapStateToProps as _abstractMapStateToProps
} from '../AbstractRecordButton';
import StopRecordingDialog from './StopRecordingDialog';
type Props = IProps & AbstractProps;
/**
* Button for opening a screen where a recording session can be started.
@@ -49,10 +51,10 @@ class RecordButton extends AbstractRecordButton<Props> {
* @private
* @returns {Props}
*/
export function mapStateToProps(state: Object, ownProps: Object) {
export function mapStateToProps(state: IReduxState) {
const enabled = getFeatureFlag(state, RECORDING_ENABLED, true);
const iosEnabled = Platform.OS !== 'ios' || getFeatureFlag(state, IOS_RECORDING_ENABLED, false);
const abstractProps = _abstractMapStateToProps(state, ownProps);
const abstractProps = _abstractMapStateToProps(state);
return {
...abstractProps,

View File

@@ -9,7 +9,7 @@ import { goBack } from
'../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { RECORDING_TYPES } from '../../../constants';
import AbstractStartRecordingDialog, {
type Props,
IProps,
mapStateToProps
} from '../AbstractStartRecordingDialog';
import styles from '../styles.native';
@@ -23,14 +23,14 @@ import StartRecordingDialogContent from './StartRecordingDialogContent';
*
* @augments Component
*/
class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
class StartRecordingDialog extends AbstractStartRecordingDialog {
/**
* Constructor of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._onStartPress = this._onStartPress.bind(this);
@@ -66,7 +66,7 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
* @inheritdoc
* @returns {void}
*/
componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: IProps) {
super.componentDidUpdate(prevProps);
const { navigation, t } = this.props;
@@ -83,8 +83,6 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
});
}
_onStartPress: () => void;
/**
* Starts recording session and goes back to the previous screen.
*
@@ -94,8 +92,6 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
this._onSubmit() && goBack();
}
isStartRecordingDisabled: () => boolean;
/**
* Disables start recording button.
*
@@ -154,11 +150,6 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
</JitsiScreen>
);
}
_areIntegrationsEnabled: () => boolean;
_onSubmit: () => boolean;
_onSelectedRecordingServiceChanged: (string) => void;
_onSharingSettingChanged: () => void;
}
export default translate(connect(mapStateToProps)(StartRecordingDialog));

View File

@@ -1,12 +1,10 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import ConfirmDialog from '../../../../base/dialog/components/native/ConfirmDialog';
import { translate } from '../../../../base/i18n/functions';
import AbstractStopRecordingDialog, {
type Props,
IProps,
_mapStateToProps
} from '../AbstractStopRecordingDialog';
@@ -16,7 +14,7 @@ import AbstractStopRecordingDialog, {
*
* @augments Component
*/
class StopRecordingDialog extends AbstractStopRecordingDialog<Props> {
class StopRecordingDialog extends AbstractStopRecordingDialog<IProps> {
/**
* Implements {@code Component#render}.
@@ -30,8 +28,6 @@ class StopRecordingDialog extends AbstractStopRecordingDialog<Props> {
onSubmit = { this._onSubmit } />
);
}
_onSubmit: () => boolean;
}
export default translate(connect(_mapStateToProps)(StopRecordingDialog));

View File

@@ -1,23 +1,21 @@
// @flow
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
import { schemeColor } from '../../../base/color-scheme/functions';
import { BoxModel } from '../../../base/styles/components/styles/BoxModel';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
/* eslint-disable @typescript-eslint/no-var-requires */
export const DROPBOX_LOGO = require('../../../../../images/dropboxLogo_square.png');
export const ICON_CLOUD = require('../../../../../images/icon-cloud.png');
export const ICON_INFO = require('../../../../../images/icon-info.png');
export const ICON_USERS = require('../../../../../images/icon-users.png');
export const LOCAL_RECORDING = require('../../../../../images/downloadLocalRecording.png');
export const TRACK_COLOR = BaseTheme.palette.ui07;
/* eslint-enable @typescript-eslint/no-var-requires */
// XXX The "standard" {@code BoxModel.padding} has been deemed insufficient in
// the special case(s) of the recording feature below.
const _PADDING = BoxModel.padding * 1.5;
const header = {
alignItems: 'center',
flex: 0,

View File

@@ -109,11 +109,12 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
* Maps redux state to component props.
*
* @param {Object} state - Redux state.
* @param {any} ownProps - Component's own props.
* @returns {Object}
*/
function mapStateToProps(state: IReduxState) {
function mapStateToProps(state: IReduxState, ownProps: any) {
return {
...abstractMapStateToProps(state),
...abstractMapStateToProps(state, ownProps),
_screenshotCaptureEnabled: isScreenshotCaptureEnabled(state, true, false)
};
}

View File

@@ -23,6 +23,9 @@ import {
LOCAL_RECORDING
} from '../styles.web';
const EMPTY_FUNCTION = () => {
// empty
};
/**
* The start recording dialog content for the mobile application.
@@ -359,10 +362,10 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
{t('recording.onlyRecordSelf')}
</Text>
<Switch
checked = { localRecordingOnlySelf }
checked = { Boolean(localRecordingOnlySelf) }
className = 'recording-switch'
disabled = { isValidating }
onChange = { onLocalRecordingSelfChange } />
onChange = { onLocalRecordingSelfChange ?? EMPTY_FUNCTION } />
</Container>
</Container>
)}

View File

@@ -1,35 +1,34 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import ExpandedLabel, { Props as AbstractProps } from '../../../base/label/components/native/ExpandedLabel';
import ExpandedLabel, { IProps as AbstractProps } from '../../../base/label/components/native/ExpandedLabel';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { getSessionStatusToShow } from '../../functions';
type Props = AbstractProps & {
interface IProps extends AbstractProps {
/**
* The status of the highermost priority session.
*/
_status: ?string,
_status?: string;
/**
* The recording mode this indicator should display.
*/
mode: string,
mode: string;
/**
* Function to be used to translate i18n labels.
*/
t: Function
t: Function;
}
/**
* A react {@code Component} that implements an expanded label as tooltip-like
* component to explain the meaning of the {@code RecordingLabel}.
*/
class RecordingExpandedLabel extends ExpandedLabel<Props> {
class RecordingExpandedLabel extends ExpandedLabel<IProps> {
/**
* Returns the label specific text of this {@code ExpandedLabel}.
@@ -70,13 +69,13 @@ class RecordingExpandedLabel extends ExpandedLabel<Props> {
* {@code RecordingExpandedLabel}'s props.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The component's own props.
* @param {IProps} ownProps - The component's own props.
* @private
* @returns {{
* _status: ?string
* }}
*/
function _mapStateToProps(state: Object, ownProps: Props) {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const { mode } = ownProps;
return {

View File

@@ -1,11 +1,10 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import { translate } from '../../../base/i18n/functions';
import Label from '../../../base/label/components/native/Label';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { StyleType } from '../../../base/styles/functions.any';
import AbstractRecordingLabel, {
_mapStateToProps
} from '../AbstractRecordingLabel';
@@ -26,7 +25,7 @@ class RecordingLabel extends AbstractRecordingLabel {
* @inheritdoc
*/
_renderLabel() {
let status = 'on';
let status: 'on' | 'in_progress' | 'off' = 'on';
switch (this.props._status) {
case JitsiRecordingConstants.status.PENDING:
@@ -40,12 +39,10 @@ class RecordingLabel extends AbstractRecordingLabel {
return (
<Label
status = { status }
style = { styles.indicatorStyle }
text = { this.props.t(this._getLabelKey()) } />
style = { styles.indicatorStyle as StyleType }
text = { this.props.t(this._getLabelKey() ?? '') } />
);
}
_getLabelKey: () => ?string;
}
export default translate(connect(_mapStateToProps)(RecordingLabel));

View File

@@ -1,5 +1,3 @@
// @flow
import { createStyleSheet } from '../../../base/styles/functions.native';
import BaseTheme from '../../../base/ui/components/BaseTheme';

View File

@@ -13,6 +13,7 @@ import Checkbox from '../../../../base/ui/components/web/Checkbox';
import ContextMenu from '../../../../base/ui/components/web/ContextMenu';
import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
import ContextMenuItemGroup from '../../../../base/ui/components/web/ContextMenuItemGroup';
import { checkBlurSupport } from '../../../../virtual-background/functions';
import { openSettingsDialog } from '../../../actions';
import { SETTINGS_TABS } from '../../../constants';
import { createLocalVideoTracks } from '../../../functions.web';
@@ -286,6 +287,8 @@ const VideoSettingsContent = ({
}
}, [ videoDeviceIds ]);
const virtualBackgroundSupported = checkBlurSupport();
return (
<ContextMenu
aria-labelledby = 'video-settings-button'
@@ -298,11 +301,11 @@ const VideoSettingsContent = ({
{trackData.map((data, i) => _renderPreviewEntry(data, i))}
</ContextMenuItemGroup>
<ContextMenuItemGroup>
<ContextMenuItem
{ virtualBackgroundSupported && <ContextMenuItem
accessibilityLabel = 'virtualBackground.title'
icon = { IconImage }
onClick = { selectBackground }
text = { t('virtualBackground.title') } />
text = { t('virtualBackground.title') } /> }
<div
className = { classes.checkboxContainer }
onClick = { stopPropagation }>

View File

@@ -0,0 +1,157 @@
-- Measure the number of messages used in a meeting. Sends amplitude event.
-- Needs to be activated under the muc component where the limit needs to be applied (main muc and breakout muc)
-- Copyright (C) 2023-present 8x8, Inc.
local jid = require 'util.jid';
local http = require 'net.http';
local cjson_safe = require 'cjson.safe'
local amplitude_endpoint = module:get_option_string('amplitude_endpoint', 'https://api2.amplitude.com/2/httpapi');
local amplitude_api_key = module:get_option_string('amplitude_api_key');
if not amplitude_api_key then
module:log("warn", "No 'amplitude_api_key' option set, disabling amplitude reporting");
return
end
local muc_domain_base = module:get_option_string('muc_mapper_domain_base');
local isBreakoutRoom = module.host == 'breakout.' .. muc_domain_base;
local util = module:require 'util';
local is_healthcheck_room = util.is_healthcheck_room;
local extract_subdomain = util.extract_subdomain;
module:log('info', 'Loading measure message count');
local shard_name = module:context(muc_domain_base):get_option_string('shard_name');
local region_name = module:context(muc_domain_base):get_option_string('region_name');
local release_number = module:context(muc_domain_base):get_option_string('release_number');
local http_headers = {
['User-Agent'] = 'Prosody ('..prosody.version..'; '..prosody.platform..')',
['Content-Type'] = 'application/json'
};
local inspect = require "inspect"
function table.clone(t)
return {table.unpack(t)}
end
local function event_cb(content_, code_, response_, request_)
if code_ == 200 or code_ == 204 then
module:log('debug', 'URL Callback: Code %s, Content %s, Request (host %s, path %s, body %s), Response: %s',
code_, content_, request_.host, request_.path, inspect(request_.body), inspect(response_));
else
module:log('warn', 'URL Callback non successful: Code %s, Content %s, Request (%s), Response: %s',
code_, content_, inspect(request_), inspect(response_));
end
end
function send_event(room)
local user_properties = {
shard_name = shard_name;
region_name = region_name;
release_number = release_number;
};
local node = jid.split(room.jid);
local subdomain, room_name = extract_subdomain(node);
user_properties.tenant = subdomain or '/';
user_properties.conference_name = room_name or node;
local event_properties = {
messages_count = room._muc_messages_count or 0;
polls_count = room._muc_polls_count or 0;
};
if room.created_timestamp then
event_properties.duration = (os.time() * 1000 - room.created_timestamp) / 1000;
end
local event = {
api_key = amplitude_api_key;
events = {
{
user_id = room._data.meetingId;
device_id = room._data.meetingId;
event_type = 'conference_ended';
event_properties = event_properties;
user_properties = user_properties;
}
};
};
local request = http.request(amplitude_endpoint, {
headers = http_headers,
method = "POST",
body = cjson_safe.encode(event)
}, event_cb);
end
function on_message(event)
local stanza = event.stanza;
local body = stanza:get_child('body');
if not body then
-- we ignore messages without body - lobby, polls ...
return;
end
local session = event.origin;
if not session or not session.jitsi_web_query_room then
return false;
end
-- get room name with tenant and find room.
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if not room then
module:log('warn', 'No room found found for %s/%s',
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
return false;
end
if not room._muc_messages_count then
room._muc_messages_count = 0;
end
room._muc_messages_count = room._muc_messages_count + 1;
end
-- Conference ended, send stats
function room_destroyed(event)
local room, session = event.room, event.origin;
if is_healthcheck_room(room.jid) then
return;
end
if isBreakoutRoom then
return;
end
send_event(room);
end
function poll_created(event)
local session = event.event.origin;
-- get room name with tenant and find room.
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if not room then
module:log('warn', 'No room found found for %s/%s',
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
return false;
end
if not room._muc_polls_count then
room._muc_polls_count = 0;
end
room._muc_polls_count = room._muc_polls_count + 1;
end
module:hook('message/full', on_message); -- private messages
module:hook('message/bare', on_message); -- room messages
module:hook('muc-room-destroyed', room_destroyed, -1);
module:hook('poll-created', poll_created);