rn,flags: add more feature flags to toggle specific behavior

- Invite funcionality (altogether)
- Recording
- Live streaming
- Meeting name
- Meeting password
This commit is contained in:
Saúl Ibarra Corretgé 2020-04-29 09:35:18 +02:00 committed by Saúl Ibarra Corretgé
parent c41047344f
commit e5b563ba46
11 changed files with 129 additions and 67 deletions

View File

@ -1,5 +1,11 @@
// @flow
/**
* Flag indicating if add-people functionality should be enabled.
* Default: enabled (true).
*/
export const ADD_PEOPLE_ENABLED = 'add-people.enabled';
/**
* Flag indicating if calendar integration should be enabled.
* Default: enabled (true) on Android, auto-detected on iOS.
@ -37,12 +43,38 @@ export const INVITE_ENABLED = 'invite.enabled';
*/
export const IOS_RECORDING_ENABLED = 'ios.recording.enabled';
/**
* Flag indicating if live-streaming should be enabled.
* Default: auto-detected.
*/
export const LIVE_STREAMING_ENABLED = 'live-streaming.enabled';
/**
* Flag indicating if displaying the meeting name should be enabled.
* Default: enabled (true).
*/
export const MEETING_NAME_ENABLED = 'meeting-name.enabled';
/**
* Flag indicating if the meeting password button should be enabled.
* Note that this flag just decides on the buttton, if a meeting has a password
* set, the password ddialog will still show up.
* Default: enabled (true).
*/
export const MEETING_PASSWORD_ENABLED = 'meeting-password.enabled';
/**
* Flag indicating if Picture-in-Picture should be enabled.
* Default: auto-detected.
*/
export const PIP_ENABLED = 'pip.enabled';
/**
* Flag indicating if recording should be enabled.
* Default: auto-detected.
*/
export const RECORDING_ENABLED = 'recording.enabled';
/**
* Flag indicating if the welcome page should be enabled.
* Default: disabled (false).

View File

@ -1,5 +1,6 @@
// @flow
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
import { IconChat, IconChatUnread } from '../../../base/icons';
import { setActiveModalId } from '../../../base/modal';
import { getLocalParticipant } from '../../../base/participants';
@ -114,16 +115,18 @@ function _mapDispatchToProps(dispatch: Function) {
* Maps part of the redux state to the component's props.
*
* @param {Object} state - The Redux state.
* @returns {{
* _unreadMessageCount
* }}
* @param {Object} ownProps - The properties explicitly passed to the component instance.
* @returns {Props}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state, ownProps) {
const localParticipant = getLocalParticipant(state);
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { visible = enabled } = ownProps;
return {
_showNamePrompt: !localParticipant.name,
_unreadMessageCount: getUnreadCount(state)
_unreadMessageCount: getUnreadCount(state),
visible
};
}

View File

@ -4,9 +4,10 @@ import React, { PureComponent } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { getFeatureFlag, INVITE_ENABLED } from '../../../base/flags';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import { translate } from '../../../base/i18n';
import { getParticipantCount } from '../../../base/participants';
import { doInvitePeople } from '../../../invite/actions.native';
@ -125,9 +126,10 @@ class LonelyMeetingExperience extends PureComponent<Props> {
*/
function _mapStateToProps(state): $Shape<Props> {
const { disableInviteFunctions } = state['features/base/config'];
const flag = getFeatureFlag(state, INVITE_ENABLED, true);
return {
_isInviteFunctionsDiabled: disableInviteFunctions,
_isInviteFunctionsDiabled: !flag || disableInviteFunctions,
_isLonelyMeeting: getParticipantCount(state) === 1,
_styles: ColorSchemeRegistry.get(state, 'Conference')
};

View File

@ -5,6 +5,7 @@ import { SafeAreaView, Text, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { getConferenceName } from '../../../base/conference';
import { getFeatureFlag, MEETING_NAME_ENABLED } from '../../../base/flags';
import { connect } from '../../../base/redux';
import { PictureInPictureButton } from '../../../mobile/picture-in-picture';
import { isToolboxVisible } from '../../../toolbox';
@ -19,6 +20,11 @@ type Props = {
*/
_meetingName: string,
/**
* Whether displaying the current meeting name is enabled or not.
*/
_meetingNameEnabled: boolean,
/**
* True if the navigation bar should be visible.
*/
@ -59,11 +65,14 @@ class NavigationBar extends Component<Props> {
<View
pointerEvents = 'box-none'
style = { styles.roomNameWrapper }>
<Text
numberOfLines = { 1 }
style = { styles.roomName }>
{ this.props._meetingName }
</Text>
{
this.props._meetingNameEnabled
&& <Text
numberOfLines = { 1 }
style = { styles.roomName }>
{ this.props._meetingName }
</Text>
}
<ConferenceTimer />
</View>
</View>
@ -76,14 +85,12 @@ class NavigationBar extends Component<Props> {
* Maps part of the Redux store to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {{
* _meetingName: string,
* _visible: boolean
* }}
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_meetingName: getConferenceName(state),
_meetingNameEnabled: getFeatureFlag(state, MEETING_NAME_ENABLED, true),
_visible: isToolboxVisible(state)
};
}

View File

@ -2,7 +2,7 @@
import type { Dispatch } from 'redux';
import { getFeatureFlag, INVITE_ENABLED } from '../base/flags';
import { getFeatureFlag, ADD_PEOPLE_ENABLED } from '../base/flags';
import { setActiveModalId } from '../base/modal';
import { beginShareRoom } from '../share-room';
@ -20,7 +20,7 @@ export * from './actions.any';
export function doInvitePeople() {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const addPeopleEnabled = getFeatureFlag(state, INVITE_ENABLED, true)
const addPeopleEnabled = getFeatureFlag(state, ADD_PEOPLE_ENABLED, true)
&& (isAddPeopleEnabled(state) || isDialOutEnabled(state));
if (addPeopleEnabled) {

View File

@ -2,6 +2,7 @@
import type { Dispatch } from 'redux';
import { getFeatureFlag, INVITE_ENABLED } from '../../../../base/flags';
import { translate } from '../../../../base/i18n';
import { IconAddPeople } from '../../../../base/icons';
import { connect } from '../../../../base/redux';
@ -47,9 +48,10 @@ class InviteButton extends AbstractButton<Props, *> {
*/
function _mapStateToProps(state, ownProps: Props) {
const { disableInviteFunctions } = state['features/base/config'];
const flag = getFeatureFlag(state, INVITE_ENABLED, true);
return {
visible: !disableInviteFunctions && ownProps.visible
visible: flag && !disableInviteFunctions && ownProps.visible
};
}

View File

@ -2,10 +2,11 @@
import { translate } from '../../../../base/i18n';
import { IconLiveStreaming } from '../../../../base/icons';
import { LIVE_STREAMING_ENABLED, getFeatureFlag } from '../../../../base/flags';
import { connect } from '../../../../base/redux';
import AbstractLiveStreamButton, {
_mapStateToProps,
_mapStateToProps as _abstractMapStateToProps,
type Props
} from '../AbstractLiveStreamButton';
@ -16,4 +17,23 @@ class LiveStreamButton extends AbstractLiveStreamButton<Props> {
icon = IconLiveStreaming;
}
export default translate(connect(_mapStateToProps)(LiveStreamButton));
/**
* 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: Object, ownProps: Object) {
const enabled = getFeatureFlag(state, LIVE_STREAMING_ENABLED, true);
const abstractProps = _abstractMapStateToProps(state, ownProps);
return {
...abstractProps,
visible: enabled && abstractProps.visible
};
}
export default translate(connect(mapStateToProps)(LiveStreamButton));

View File

@ -1,11 +1,14 @@
// @flow
import { Platform } from 'react-native';
import { IOS_RECORDING_ENABLED, RECORDING_ENABLED, getFeatureFlag } from '../../../../base/flags';
import { translate } from '../../../../base/i18n';
import { IconToggleRecording } from '../../../../base/icons';
import { connect } from '../../../../base/redux';
import AbstractRecordButton, {
_mapStateToProps,
_mapStateToProps as _abstractMapStateToProps,
type Props
} from '../AbstractRecordButton';
@ -16,4 +19,24 @@ class RecordButton extends AbstractRecordButton<Props> {
icon = IconToggleRecording;
}
export default translate(connect(_mapStateToProps)(RecordButton));
/**
* 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: Object, ownProps: Object) {
const enabled = getFeatureFlag(state, RECORDING_ENABLED, true);
const iosEnabled = Platform.OS !== 'ios' || getFeatureFlag(state, IOS_RECORDING_ENABLED, false);
const abstractProps = _abstractMapStateToProps(state, ownProps);
return {
...abstractProps,
visible: enabled && iosEnabled && abstractProps.visible
};
}
export default translate(connect(mapStateToProps)(RecordButton));

View File

@ -1,5 +1,6 @@
// @flow
import { MEETING_PASSWORD_ENABLED, getFeatureFlag } from '../../base/flags';
import { translate } from '../../base/i18n';
import { IconRoomLock, IconRoomUnlock } from '../../base/icons';
import { isLocalParticipantModerator } from '../../base/participants';
@ -83,19 +84,19 @@ class RoomLockButton extends AbstractButton<Props, *> {
* {@code RoomLockButton} component.
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The properties explicitly passed to the component instance.
* @private
* @returns {{
* _localParticipantModerator: boolean,
* _locked: boolean
* }}
* @returns {Props}
*/
function _mapStateToProps(state): Object {
function _mapStateToProps(state, ownProps): Object {
const { conference, locked } = state['features/base/conference'];
const enabled = getFeatureFlag(state, MEETING_PASSWORD_ENABLED, true);
const { visible = enabled } = ownProps;
return {
_localParticipantModerator:
Boolean(conference && isLocalParticipantModerator(state)),
_locked: Boolean(conference && locked)
_localParticipantModerator: Boolean(conference && isLocalParticipantModerator(state)),
_locked: Boolean(conference && locked),
visible
};
}

View File

@ -1,12 +1,11 @@
// @flow
import React, { PureComponent } from 'react';
import { Platform, TouchableOpacity, View } from 'react-native';
import { TouchableOpacity, View } from 'react-native';
import Collapsible from 'react-native-collapsible';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog';
import { IOS_RECORDING_ENABLED, getFeatureFlag } from '../../../base/flags';
import { IconDragHandle } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
@ -134,10 +133,7 @@ class OverflowMenu extends PureComponent<Props, State> {
<Collapsible collapsed = { !showMore }>
<ToggleCameraButton { ...buttonProps } />
<TileViewButton { ...buttonProps } />
{
this.props._recordingEnabled
&& <RecordButton { ...buttonProps } />
}
<RecordButton { ...buttonProps } />
<LiveStreamButton { ...buttonProps } />
<RoomLockButton { ...buttonProps } />
<ClosedCaptionButton { ...buttonProps } />
@ -240,8 +236,7 @@ class OverflowMenu extends PureComponent<Props, State> {
function _mapStateToProps(state) {
return {
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
_isOpen: isDialogOpen(state, OverflowMenu_),
_recordingEnabled: Platform.OS !== 'ios' || getFeatureFlag(state, IOS_RECORDING_ENABLED)
_isOpen: isDialogOpen(state, OverflowMenu_)
};
}

View File

@ -4,12 +4,10 @@ import React, { PureComponent } from 'react';
import { View } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
import { Container } from '../../../base/react';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import { ChatButton } from '../../../chat';
import { InviteButton } from '../../../invite';
import { isToolboxVisible } from '../../functions';
@ -25,11 +23,6 @@ import VideoMuteButton from '../VideoMuteButton';
*/
type Props = {
/**
* Whether the chat feature has been enabled. The meeting info button will be displayed in its place when disabled.
*/
_chatEnabled: boolean,
/**
* The color-schemed stylesheet of the feature.
*/
@ -105,7 +98,7 @@ class Toolbox extends PureComponent<Props> {
* @returns {React$Node}
*/
_renderToolbar() {
const { _chatEnabled, _styles } = this.props;
const { _styles } = this.props;
const { buttonStyles, buttonStylesBorderless, hangupButtonStyles, toggledButtonStyles } = _styles;
return (
@ -113,20 +106,9 @@ class Toolbox extends PureComponent<Props> {
accessibilityRole = 'toolbar'
pointerEvents = 'box-none'
style = { styles.toolbar }>
{
_chatEnabled
&& <ChatButton
styles = { buttonStylesBorderless }
toggledStyles = {
this._getChatButtonToggledStyle(toggledButtonStyles)
} />
}
{
!_chatEnabled
&& <InviteButton
styles = { buttonStyles }
toggledStyles = { toggledButtonStyles } />
}
<ChatButton
styles = { buttonStylesBorderless }
toggledStyles = { this._getChatButtonToggledStyle(toggledButtonStyles) } />
<AudioMuteButton
styles = { buttonStyles }
toggledStyles = { toggledButtonStyles } />
@ -150,15 +132,10 @@ class Toolbox extends PureComponent<Props> {
* @param {Object} state - The redux state of which parts are to be mapped to
* {@code Toolbox} props.
* @private
* @returns {{
* _chatEnabled: boolean,
* _styles: StyleType,
* _visible: boolean
* }}
* @returns {Props}
*/
function _mapStateToProps(state: Object): Object {
return {
_chatEnabled: getFeatureFlag(state, CHAT_ENABLED, true),
_styles: ColorSchemeRegistry.get(state, 'Toolbox'),
_visible: isToolboxVisible(state)
};