Grow features/settings from features/app-settings and features/settings-menu

This commit is contained in:
Lyubo Marinov 2018-02-26 10:14:46 -06:00
parent e23d4317eb
commit 9f69c4d730
55 changed files with 648 additions and 674 deletions

View File

@ -33,14 +33,14 @@ import java.net.URL;
/**
* Base Activity for applications integrating Jitsi Meet at a higher level. It
* contains all the required wiring between the {@code JKConferenceView} and
* contains all the required wiring between the {@code JitsiMeetView} and
* the Activity lifecycle methods already implemented.
*
* In this activity we use a single {@code JKConferenceView} instance. This
* In this activity we use a single {@code JitsiMeetView} instance. This
* instance gives us access to a view which displays the welcome page and the
* conference itself. All lifetime methods associated with this Activity are
* hooked to the React Native subsystem via proxy calls through the
* {@code JKConferenceView} static methods.
* {@code JitsiMeetView} static methods.
*/
public class JitsiMeetActivity extends AppCompatActivity {
/**

View File

@ -47,16 +47,18 @@
},
"welcomepage":{
"appDescription": "Go ahead, video chat with the whole team. In fact, invite everyone you know. __app__ is a fully encrypted, 100% open source video conferencing solution that you can use all day, every day, for free — with no account needed.",
"audioOnlyLabel": "Voice",
"audioVideoSwitch": {
"audio": "Voice",
"video": "Video"
},
"go": "GO",
"hintText": "Enter a room name you want to join to, or simply create a new room name, eg. MeetingWithJohn",
"join": "JOIN",
"privacy": "Privacy",
"roomname": "Enter room name",
"roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.",
"sendFeedback": "Send feedback",
"terms": "Terms",
"title": "More secure, more flexible, and completely free video conferencing",
"videoEnabledLabel": "Video"
"title": "More secure, more flexible, and completely free video conferencing"
},
"startupoverlay": {
"policyText": " ",
@ -503,7 +505,7 @@
"title": "Call info",
"tooltip": "Get access info about the meeting"
},
"settingsScreen": {
"settingsView": {
"alertOk": "OK",
"alertTitle": "Warning",
"alertURLText": "The entered server URL is invalid",
@ -515,8 +517,5 @@
"serverURL": "Server URL",
"startWithAudioMuted": "Start with audio muted",
"startWithVideoMuted": "Start with video muted"
},
"sideBar": {
"settings": "Settings"
}
}

View File

@ -1,17 +1,18 @@
/* global $, APP, interfaceConfig */
/* eslint-disable no-unused-vars */
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { i18next } from '../../../../react/features/base/i18n';
import { SettingsMenu } from '../../../../react/features/settings-menu';
/* eslint-enable no-unused-vars */
import { SettingsMenu } from '../../../../react/features/settings';
import UIUtil from '../../util/UIUtil';
/* eslint-enable no-unused-vars */
export default {
init() {
const settingsMenuContainer = document.createElement('div');
@ -31,8 +32,7 @@ export default {
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<SettingsMenu
{ ...props } />
<SettingsMenu { ...props } />
</I18nextProvider>
</Provider>,
settingsMenuContainer

View File

@ -76,13 +76,13 @@ export const VIDEO_MUTE = 'video.mute';
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createApiEvent = function(action, attributes = {}) {
export function createApiEvent(action, attributes = {}) {
return {
action,
attributes,
source: 'jitsi-meet-api'
};
};
}
/**
* Creates an event which indicates that the audio-only mode has been changed.
@ -91,11 +91,11 @@ export const createApiEvent = function(action, attributes = {}) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createAudioOnlyChangedEvent = function(enabled) {
export function createAudioOnlyChangedEvent(enabled) {
return {
action: `audio.only.${enabled ? 'enabled' : 'disabled'}`
};
};
}
/**
* Creates an event which indicates that a device was changed.
@ -106,7 +106,7 @@ export const createAudioOnlyChangedEvent = function(enabled) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createDeviceChangedEvent = function(mediaType, deviceType) {
export function createDeviceChangedEvent(mediaType, deviceType) {
return {
action: 'device.changed',
attributes: {
@ -114,7 +114,7 @@ export const createDeviceChangedEvent = function(mediaType, deviceType) {
'media_type': mediaType
}
};
};
}
/**
* Creates an event which specifies that the feedback dialog has been opened.
@ -122,11 +122,11 @@ export const createDeviceChangedEvent = function(mediaType, deviceType) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createFeedbackOpenEvent = function() {
export function createFeedbackOpenEvent() {
return {
action: 'feedback.opened'
};
};
}
/**
* Creates an event which indicates that the invite dialog was closed. This is
@ -136,11 +136,11 @@ export const createFeedbackOpenEvent = function() {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createInviteDialogClosedEvent = function() {
export function createInviteDialogClosedEvent() {
return {
action: 'invite.dialog.closed'
};
};
}
/**
* Creates a "page reload" event.
@ -152,17 +152,16 @@ export const createInviteDialogClosedEvent = function() {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createPageReloadScheduledEvent
= function(reason, timeout, details) {
return {
action: 'page.reload.scheduled',
attributes: {
reason,
timeout,
...details
}
};
export function createPageReloadScheduledEvent(reason, timeout, details) {
return {
action: 'page.reload.scheduled',
attributes: {
reason,
timeout,
...details
}
};
}
/**
* Creates a "pinned" or "unpinned" event.
@ -173,17 +172,16 @@ export const createPageReloadScheduledEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createPinnedEvent
= function(action, participantId, attributes) {
return {
type: TYPE_TRACK,
action,
actionSubject: 'participant',
objectType: 'participant',
objectId: participantId,
attributes
};
};
export function createPinnedEvent(action, participantId, attributes) {
return {
type: TYPE_TRACK,
action,
actionSubject: 'participant',
objectType: 'participant',
objectId: participantId,
attributes
};
}
/**
* Creates an event which indicates that a button in the profile panel was
@ -194,16 +192,15 @@ export const createPinnedEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createProfilePanelButtonEvent
= function(buttonName, attributes = {}) {
return {
action: 'clicked',
actionSubject: buttonName,
attributes,
source: 'profile.panel',
type: TYPE_UI
};
export function createProfilePanelButtonEvent(buttonName, attributes = {}) {
return {
action: 'clicked',
actionSubject: buttonName,
attributes,
source: 'profile.panel',
type: TYPE_UI
};
}
/**
* Creates an event which indicates that a specific button on one of the
@ -215,14 +212,14 @@ export const createProfilePanelButtonEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createRecordingDialogEvent = function(dialogName, buttonName) {
export function createRecordingDialogEvent(dialogName, buttonName) {
return {
action: 'clicked',
actionSubject: buttonName,
source: `${dialogName}.recording.dialog`,
type: TYPE_UI
};
};
}
/**
* Creates an event which specifies that the "confirm" button on the remote
@ -233,7 +230,7 @@ export const createRecordingDialogEvent = function(dialogName, buttonName) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createRemoteMuteConfirmedEvent = function(participantId) {
export function createRemoteMuteConfirmedEvent(participantId) {
return {
action: 'clicked',
actionSubject: 'remote.mute.dialog.confirm.button',
@ -243,7 +240,7 @@ export const createRemoteMuteConfirmedEvent = function(participantId) {
source: 'remote.mute.dialog',
type: TYPE_UI
};
};
}
/**
* Creates an event which indicates that one of the buttons in the "remote
@ -254,16 +251,15 @@ export const createRemoteMuteConfirmedEvent = function(participantId) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createRemoteVideoMenuButtonEvent
= function(buttonName, attributes) {
return {
action: 'clicked',
actionSubject: buttonName,
attributes,
source: 'remote.video.menu',
type: TYPE_UI
};
export function createRemoteVideoMenuButtonEvent(buttonName, attributes) {
return {
action: 'clicked',
actionSubject: buttonName,
attributes,
source: 'remote.video.menu',
type: TYPE_UI
};
}
/**
* Creates an event indicating that an action related to screen sharing
@ -273,12 +269,12 @@ export const createRemoteVideoMenuButtonEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createScreenSharingEvent = function(action) {
export function createScreenSharingEvent(action) {
return {
action,
actionSubject: 'screen.sharing'
};
};
}
/**
* The local participant failed to send a "selected endpoint" message to the
@ -288,7 +284,7 @@ export const createScreenSharingEvent = function(action) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createSelectParticipantFailedEvent = function(error) {
export function createSelectParticipantFailedEvent(error) {
const event = {
action: 'select.participant.failed'
};
@ -298,7 +294,7 @@ export const createSelectParticipantFailedEvent = function(error) {
}
return event;
};
}
/**
* Creates an event associated with the "shared video" feature.
@ -308,13 +304,13 @@ export const createSelectParticipantFailedEvent = function(error) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createSharedVideoEvent = function(action, attributes = {}) {
export function createSharedVideoEvent(action, attributes = {}) {
return {
action,
attributes,
actionSubject: 'shared.video'
};
};
}
/**
* Creates an event associated with a shortcut being pressed, released or
@ -331,17 +327,19 @@ export const createSharedVideoEvent = function(action, attributes = {}) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createShortcutEvent
= function(shortcut, action = ACTION_SHORTCUT_TRIGGERED, attributes = {}) {
return {
action,
actionSubject: 'keyboard.shortcut',
actionSubjectId: shortcut,
attributes,
source: 'keyboard.shortcut',
type: TYPE_UI
};
export function createShortcutEvent(
shortcut,
action = ACTION_SHORTCUT_TRIGGERED,
attributes = {}) {
return {
action,
actionSubject: 'keyboard.shortcut',
actionSubjectId: shortcut,
attributes,
source: 'keyboard.shortcut',
type: TYPE_UI
};
}
/**
* Creates an event which indicates the "start audio only" configuration.
@ -350,14 +348,14 @@ export const createShortcutEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createStartAudioOnlyEvent = function(audioOnly) {
export function createStartAudioOnlyEvent(audioOnly) {
return {
action: 'start.audio.only',
attributes: {
enabled: audioOnly
}
};
};
}
/**
* Creates an event which indicates the "start muted" configuration.
@ -372,17 +370,19 @@ export const createStartAudioOnlyEvent = function(audioOnly) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createStartMutedConfigurationEvent
= function(source, audioMute, videoMute) {
return {
action: 'start.muted.configuration',
attributes: {
source,
'audio_mute': audioMute,
'video_mute': videoMute
}
};
export function createStartMutedConfigurationEvent(
source,
audioMute,
videoMute) {
return {
action: 'start.muted.configuration',
attributes: {
source,
'audio_mute': audioMute,
'video_mute': videoMute
}
};
}
/**
* Creates an event which indicates the delay for switching between simulcast
@ -392,12 +392,12 @@ export const createStartMutedConfigurationEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createStreamSwitchDelayEvent = function(attributes) {
export function createStreamSwitchDelayEvent(attributes) {
return {
action: 'stream.switch.delay',
attributes
};
};
}
/**
* Automatically changing the mute state of a media track in order to match
@ -409,7 +409,7 @@ export const createStreamSwitchDelayEvent = function(attributes) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createSyncTrackStateEvent = function(mediaType, muted) {
export function createSyncTrackStateEvent(mediaType, muted) {
return {
action: 'sync.track.state',
attributes: {
@ -417,7 +417,7 @@ export const createSyncTrackStateEvent = function(mediaType, muted) {
muted
}
};
};
}
/**
* Creates an event associated with a toolbar button being clicked/pressed. By
@ -431,7 +431,7 @@ export const createSyncTrackStateEvent = function(mediaType, muted) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createToolbarEvent = function(buttonName, attributes = {}) {
export function createToolbarEvent(buttonName, attributes = {}) {
return {
action: 'clicked',
actionSubject: buttonName,
@ -439,7 +439,7 @@ export const createToolbarEvent = function(buttonName, attributes = {}) {
source: 'toolbar.button',
type: TYPE_UI
};
};
}
/**
* Creates an event which indicates that a local track was muted.
@ -452,7 +452,7 @@ export const createToolbarEvent = function(buttonName, attributes = {}) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createTrackMutedEvent = function(mediaType, reason, muted = true) {
export function createTrackMutedEvent(mediaType, reason, muted = true) {
return {
action: 'track.muted',
attributes: {
@ -461,7 +461,7 @@ export const createTrackMutedEvent = function(mediaType, reason, muted = true) {
reason
}
};
};
}
/**
* Creates an event for an action on the welcome page.
@ -472,12 +472,11 @@ export const createTrackMutedEvent = function(mediaType, reason, muted = true) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createWelcomePageEvent
= function(action, actionSubject, attributes = {}) {
return {
action,
actionSubject,
attributes,
source: 'welcomePage'
};
export function createWelcomePageEvent(action, actionSubject, attributes = {}) {
return {
action,
actionSubject,
attributes,
source: 'welcomePage'
};
}

View File

@ -1,19 +0,0 @@
/**
* The type of (redux) action which signals the request
* to hide the app settings screen.
*
* {
* type: HIDE_APP_SETTINGS
* }
*/
export const HIDE_APP_SETTINGS = Symbol('HIDE_APP_SETTINGS');
/**
* The type of (redux) action which signals the request
* to show the app settings screen where available.
*
* {
* type: SHOW_APP_SETTINGS
* }
*/
export const SHOW_APP_SETTINGS = Symbol('SHOW_APP_SETTINGS');

View File

@ -1,29 +0,0 @@
// @flow
import { HIDE_APP_SETTINGS, SHOW_APP_SETTINGS } from './actionTypes';
/**
* Redux-signals the request to hide the app settings screen.
*
* @returns {{
* type: HIDE_APP_SETTINGS
* }}
*/
export function hideAppSettings() {
return {
type: HIDE_APP_SETTINGS
};
}
/**
* Redux-signals the request to open the app settings screen.
*
* @returns {{
* type: SHOW_APP_SETTINGS
* }}
*/
export function showAppSettings() {
return {
type: SHOW_APP_SETTINGS
};
}

View File

@ -1,238 +0,0 @@
// @flow
import React from 'react';
import {
Alert,
Modal,
SafeAreaView,
ScrollView,
Switch,
Text,
TextInput,
View
} from 'react-native';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { Header } from '../../base/react';
import { PlatformElements } from '../../base/styles';
import { hideAppSettings } from '../actions';
import { normalizeUserInputURL } from '../functions';
import { BackButton, FormRow, FormSectionHeader } from './_';
import { _mapStateToProps, AbstractAppSettings } from './AbstractAppSettings';
import styles from './styles';
/**
* The native container rendering the app settings page.
*
* @extends AbstractAppSettings
*/
class AppSettings extends AbstractAppSettings {
_urlField: Object;
/**
* Instantiates a new {@code AppSettings} instance.
*
* @inheritdoc
*/
constructor(props) {
super(props);
this._onBlurServerURL = this._onBlurServerURL.bind(this);
this._onRequestClose = this._onRequestClose.bind(this);
this._setURLFieldReference = this._setURLFieldReference.bind(this);
this._showURLAlert = this._showURLAlert.bind(this);
}
/**
* Implements React's {@link Component#render()}, renders the settings page.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { _profile, t } = this.props;
return (
<Modal
animationType = 'slide'
onRequestClose = { this._onRequestClose }
presentationStyle = 'fullScreen'
supportedOrientations = { [
'landscape',
'portrait'
] }
visible = { this.props._visible }>
<View style = { PlatformElements.page }>
<Header>
<BackButton
onPress = { this._onRequestClose } />
<Text
style = { [
styles.text,
PlatformElements.headerText
] } >
{ t('settingsScreen.header') }
</Text>
</Header>
<SafeAreaView style = { styles.settingsForm }>
<ScrollView>
<FormSectionHeader
i18nLabel = 'settingsScreen.profileSection' />
<FormRow
fieldSeparator = { true }
i18nLabel = 'settingsScreen.displayName' >
<TextInput
onChangeText = { this._onChangeDisplayName }
placeholder = 'John Doe'
value = { _profile.displayName } />
</FormRow>
<FormRow
i18nLabel = 'settingsScreen.email' >
<TextInput
keyboardType = { 'email-address' }
onChangeText = { this._onChangeEmail }
placeholder = 'email@example.com'
value = { _profile.email } />
</FormRow>
<FormSectionHeader
i18nLabel
= 'settingsScreen.conferenceSection' />
<FormRow
fieldSeparator = { true }
i18nLabel = 'settingsScreen.serverURL' >
<TextInput
autoCapitalize = 'none'
onBlur = { this._onBlurServerURL }
onChangeText = { this._onChangeServerURL }
placeholder = { this.props._serverURL }
value = { _profile.serverURL } />
</FormRow>
<FormRow
fieldSeparator = { true }
i18nLabel
= 'settingsScreen.startWithAudioMuted' >
<Switch
onValueChange = {
this._onStartAudioMutedChange
}
value = {
_profile.startWithAudioMuted
} />
</FormRow>
<FormRow
i18nLabel
= 'settingsScreen.startWithVideoMuted' >
<Switch
onValueChange = {
this._onStartVideoMutedChange
}
value = {
_profile.startWithVideoMuted
} />
</FormRow>
</ScrollView>
</SafeAreaView>
</View>
</Modal>
);
}
_onBlurServerURL: () => void;
/**
* Handler the server URL lose focus event. Here we validate the server URL
* and update it to the normalized version, or show an error if incorrect.
*
* @private
* @returns {void}
*/
_onBlurServerURL() {
this._processServerURL(false /* hideOnSuccess */);
}
_onChangeDisplayName: (string) => void;
_onChangeEmail: (string) => void;
_onChangeServerURL: (string) => void;
_onStartAudioMutedChange: (boolean) => void;
_onStartVideoMutedChange: (boolean) => void;
/**
* Processes the server URL. It normalizes it and an error alert is
* displayed in case it's incorrect.
*
* @param {boolean} hideOnSuccess - True if the dialog should be hidden if
* normalization / validation succeeds, false otherwise.
* @private
* @returns {void}
*/
_processServerURL(hideOnSuccess: boolean) {
const { serverURL } = this.props._profile;
const normalizedURL = normalizeUserInputURL(serverURL);
if (normalizedURL === null) {
this._showURLAlert();
} else {
this._onChangeServerURL(normalizedURL);
if (hideOnSuccess) {
this.props.dispatch(hideAppSettings());
}
}
}
_onRequestClose: () => void;
/**
* Handles the back button.
* Also invokes normalizeUserInputURL to validate the URL entered
* by the user.
*
* @returns {void}
*/
_onRequestClose() {
this._processServerURL(true /* hideOnSuccess */);
}
_setURLFieldReference: (React$ElementRef<*> | null) => void;
/**
* Stores a reference to the URL field for later use.
*
* @protected
* @param {Object} component - The field component.
* @returns {void}
*/
_setURLFieldReference(component) {
this._urlField = component;
}
_showURLAlert: () => void;
/**
* Shows an alert telling the user that the URL he/she entered was invalid.
*
* @returns {void}
*/
_showURLAlert() {
const { t } = this.props;
Alert.alert(
t('settingsScreen.alertTitle'),
t('settingsScreen.alertURLText'),
[
{
onPress: () => this._urlField.focus(),
text: t('settingsScreen.alertOk')
}
]
);
}
}
export default translate(connect(_mapStateToProps)(AppSettings));

View File

@ -1 +0,0 @@
export { default as AppSettings } from './AppSettings';

View File

@ -1,3 +0,0 @@
export { default as BackButton } from './BackButton';
export { default as FormRow } from './FormRow';
export { default as FormSectionHeader } from './FormSectionHeader';

View File

@ -1,33 +0,0 @@
import {
BoxModel,
ColorPalette,
createStyleSheet
} from '../../base/styles';
/**
* The styles of the React {@code Components} of the feature
* {@code app-settings}.
*/
export default createStyleSheet({
/**
* Style of the ScrollView to be able to scroll the content.
*/
scrollView: {
flex: 1
},
/**
* Style of the settings screen content (form).
*/
settingsForm: {
flex: 1,
margin: BoxModel.margin
},
/**
* Global {@code Text} color for the page.
*/
text: {
color: ColorPalette.black
}
});

View File

@ -1,31 +0,0 @@
// @flow
import {
HIDE_APP_SETTINGS,
SHOW_APP_SETTINGS
} from './actionTypes';
import { ReducerRegistry } from '../base/redux';
const DEFAULT_STATE = {
visible: false
};
ReducerRegistry.register(
'features/app-settings', (state = DEFAULT_STATE, action) => {
switch (action.type) {
case HIDE_APP_SETTINGS:
return {
...state,
visible: false
};
case SHOW_APP_SETTINGS:
return {
...state,
visible: true
};
}
return state;
});

View File

@ -125,17 +125,20 @@ function _connectionEstablished({ dispatch }, next, action) {
*/
function _conferenceFailedOrLeft({ dispatch, getState }, next, action) {
const result = next(action);
const state = getState();
const { audioOnly } = state['features/base/conference'];
const { startAudioOnly } = state['features/base/profile'].profile;
// FIXME: Consider implementing a standalone audio-only feature
// that handles all these state changes.
if (audioOnly && !startAudioOnly) {
sendAnalytics(createAudioOnlyChangedEvent(false));
logger.log('Audio only disabled');
dispatch(setAudioOnly(false));
} else if (!audioOnly && startAudioOnly) {
// FIXME: Consider implementing a standalone audio-only feature that handles
// all these state changes.
if (audioOnly) {
if (!startAudioOnly) {
sendAnalytics(createAudioOnlyChangedEvent(false));
logger.log('Audio only disabled');
dispatch(setAudioOnly(false));
}
} else if (startAudioOnly) {
sendAnalytics(createAudioOnlyChangedEvent(true));
logger.log('Audio only enabled');
dispatch(setAudioOnly(true));

View File

@ -32,7 +32,36 @@ type Props = {
export default class Header extends Component<Props> {
/**
* Constructor of the Header component.
* The style of button-like React {@code Component}s rendered in
* {@code Header}.
*
* @returns {Object}
*/
static get buttonStyle() {
return styles.headerButton;
}
/**
* The style of a React {@code Component} rendering a {@code Header} as its
* child.
*
* @returns {Object}
*/
static get pageStyle() {
return styles.page;
}
/**
* The style of text rendered in {@code Header}.
*
* @returns {Object}
*/
static get textStyle() {
return styles.headerText;
}
/**
* Initializes a new {@code Header} instance.
*
* @inheritdoc
*/
@ -77,8 +106,8 @@ export default class Header extends Component<Props> {
_getIOS10CompatiblePadding: () => Object;
/**
* Adds a padding for iOS 10 (and older) devices to avoid clipping
* with the status bar.
* Adds a padding for iOS 10 (and older) devices to avoid clipping with the
* status bar.
* Note: This is a workaround for iOS 10 (and older) devices only to fix
* usability, but it doesn't take orientation into account, so unnecessary
* padding is rendered in some cases.
@ -99,5 +128,4 @@ export default class Header extends Component<Props> {
return null;
}
}

View File

@ -1,4 +1,4 @@
/* @flow */
// @flow
import React, { Component } from 'react';
import {
@ -10,9 +10,8 @@ import {
import styles, { SIDEBAR_WIDTH } from './styles';
/**
* The type of the React {@code Component} props of {@link SideBar}
* The type of the React {@code Component} props of {@link SideBar}.
*/
type Props = {
@ -38,18 +37,21 @@ type Props = {
show: boolean
}
/**
* The type of the React {@code Component} state of {@link SideBar}.
*/
type State = {
/**
* Indicates whether the side bar is visible or not.
*/
showSideBar: boolean,
/**
* Indicates whether the side overlay should be rendered or not.
*/
showOverlay: boolean,
/**
* Indicates whether the side bar is visible or not.
*/
showSideBar: boolean,
/**
* The native animation object.
*/
@ -63,7 +65,7 @@ export default class SideBar extends Component<Props, State> {
_mounted: boolean;
/**
* Component's contructor.
* Initializes a new {@code SideBar} instance.
*
* @inheritdoc
*/
@ -71,15 +73,15 @@ export default class SideBar extends Component<Props, State> {
super(props);
this.state = {
showSideBar: false,
showOverlay: false,
showSideBar: false,
sliderAnimation: new Animated.Value(-SIDEBAR_WIDTH)
};
this._setShow = this._setShow.bind(this);
this._getContainerStyle = this._getContainerStyle.bind(this);
this._onHideMenu = this._onHideMenu.bind(this);
this._setShow = this._setShow.bind(this);
this._setShow(props.show);
}
@ -171,8 +173,8 @@ export default class SideBar extends Component<Props, State> {
/**
* Sets the side menu visible or hidden.
*
* @private
* @param {boolean} show - The new expected visibility value.
* @private
* @returns {void}
*/
_setShow(show) {
@ -183,15 +185,17 @@ export default class SideBar extends Component<Props, State> {
});
}
Animated.timing(this.state.sliderAnimation, {
toValue: show ? 0 : -SIDEBAR_WIDTH
}).start(animationState => {
if (animationState.finished && !show) {
this.setState({
showOverlay: false
});
}
});
Animated
.timing(
this.state.sliderAnimation,
{ toValue: show ? 0 : -SIDEBAR_WIDTH })
.start(animationState => {
if (animationState.finished && !show) {
this.setState({
showOverlay: false
});
}
});
}
if (this._mounted) {
@ -200,5 +204,4 @@ export default class SideBar extends Component<Props, State> {
});
}
}
}

View File

@ -1,8 +1,7 @@
export { default as Container } from './Container';
export { default as Header } from './Header';
export { default as Link } from './Link';
export { default as LoadingIndicator } from './LoadingIndicator';
export { default as Header } from './Header';
export { default as SideBar } from './SideBar';
export * from './styles';
export { default as TintedView } from './TintedView';
export { default as Text } from './Text';
export { default as TintedView } from './TintedView';

View File

@ -14,11 +14,20 @@ export const STATUSBAR_COLOR = ColorPalette.blueHighlight;
export const SIDEBAR_WIDTH = 250;
/**
* The styles of the React {@code Components} of the generic components
* in the app.
* The styles of the generic React {@code Components} of the app.
*/
export default createStyleSheet({
/**
* Platform specific header button (e.g. back, menu...etc).
*/
headerButton: {
alignSelf: 'center',
color: ColorPalette.white,
fontSize: 26,
paddingRight: 22
},
/**
* Style of the header overlay to cover the unsafe areas.
*/
@ -26,6 +35,29 @@ export default createStyleSheet({
backgroundColor: HEADER_COLOR
},
/**
* Generic style for a label placed in the header.
*/
headerText: {
color: ColorPalette.white,
fontSize: 20
},
/**
* The top-level element of a page.
*/
page: {
alignItems: 'stretch',
bottom: 0,
flex: 1,
flexDirection: 'column',
left: 0,
overflow: 'hidden',
position: 'absolute',
right: 0,
top: 0
},
/**
* Base style of Header
*/

View File

@ -1,41 +0,0 @@
import { ColorPalette } from './ColorPalette';
import {
createStyleSheet
} from '../../functions';
export const PlatformElements = createStyleSheet({
/**
* Platform specific header button (e.g. back, menu...etc).
*/
headerButton: {
alignSelf: 'center',
color: ColorPalette.white,
fontSize: 26,
paddingRight: 22
},
/**
* Generic style for a label placed in the header.
*/
headerText: {
color: ColorPalette.white,
fontSize: 20
},
/**
* The topmost level element of a page.
*/
page: {
alignItems: 'stretch',
bottom: 0,
flex: 1,
flexDirection: 'column',
left: 0,
overflow: 'hidden',
position: 'absolute',
right: 0,
top: 0
}
});

View File

@ -1,3 +1,2 @@
export * from './BoxModel';
export * from './ColorPalette';
export * from './PlatformElements';

View File

@ -1 +0,0 @@
export * from './components';

View File

@ -0,0 +1,10 @@
/**
* The type of (redux) action which sets the visibility of the view/UI rendering
* the app's settings.
*
* {
* type: SET_SETTINGS_VIEW_VISIBLE
* visible: boolean
* }
*/
export const SET_SETTINGS_VIEW_VISIBLE = Symbol('SET_SETTINGS_VIEW_VISIBLE');

View File

@ -0,0 +1,20 @@
// @flow
import { SET_SETTINGS_VIEW_VISIBLE } from './actionTypes';
/**
* Sets the visibility of the view/UI which renders the app's settings.
*
* @param {boolean} visible - If the view/UI which renders the app's settings is
* to be made visible, {@code true}; otherwise, {@code false}.
* @returns {{
* type: SET_SETTINGS_VIEW_VISIBLE,
* visible: boolean
* }}
*/
export function setSettingsViewVisible(visible: boolean) {
return {
type: SET_SETTINGS_VIEW_VISIBLE,
visible
};
}

View File

@ -5,7 +5,8 @@ import { Component } from 'react';
import { getProfile, updateProfile } from '../../base/profile';
/**
* The type of the React {@code Component} props of {@link AbstractAppSettings}
* The type of the React {@code Component} props of
* {@link AbstractSettingsView}.
*/
type Props = {
@ -20,7 +21,7 @@ type Props = {
_serverURL: string,
/**
* The visibility prop of the settings screen.
* Whether {@link AbstractSettingsView} is visible.
*/
_visible: boolean,
@ -41,10 +42,10 @@ type Props = {
*
* @abstract
*/
export class AbstractAppSettings extends Component<Props> {
export class AbstractSettingsView extends Component<Props> {
/**
* Initializes a new {@code AbstractAppSettings} instance.
* Initializes a new {@code AbstractSettingsView} instance.
*
* @param {Props} props - The React {@code Component} props to initialize
* the component.
@ -52,6 +53,7 @@ export class AbstractAppSettings extends Component<Props> {
constructor(props: Props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onChangeDisplayName = this._onChangeDisplayName.bind(this);
this._onChangeEmail = this._onChangeEmail.bind(this);
this._onChangeServerURL = this._onChangeServerURL.bind(this);
@ -66,8 +68,8 @@ export class AbstractAppSettings extends Component<Props> {
/**
* Handles the display name field value change.
*
* @protected
* @param {string} text - The value typed in the name field.
* @protected
* @returns {void}
*/
_onChangeDisplayName(text) {
@ -81,8 +83,8 @@ export class AbstractAppSettings extends Component<Props> {
/**
* Handles the email field value change.
*
* @protected
* @param {string} text - The value typed in the email field.
* @protected
* @returns {void}
*/
_onChangeEmail(text) {
@ -96,8 +98,8 @@ export class AbstractAppSettings extends Component<Props> {
/**
* Handles the server name field value change.
*
* @protected
* @param {string} text - The server URL typed in the server field.
* @protected
* @returns {void}
*/
_onChangeServerURL(text) {
@ -111,9 +113,9 @@ export class AbstractAppSettings extends Component<Props> {
/**
* Handles the start audio muted change event.
*
* @param {boolean} newValue - The new value for the start audio muted
* option.
* @protected
* @param {boolean} newValue - The new value for the
* start audio muted option.
* @returns {void}
*/
_onStartAudioMutedChange(newValue) {
@ -127,9 +129,9 @@ export class AbstractAppSettings extends Component<Props> {
/**
* Handles the start video muted change event.
*
* @param {boolean} newValue - The new value for the start video muted
* option.
* @protected
* @param {boolean} newValue - The new value for the
* start video muted option.
* @returns {void}
*/
_onStartVideoMutedChange(newValue) {
@ -143,8 +145,8 @@ export class AbstractAppSettings extends Component<Props> {
/**
* Updates the persisted profile on any change.
*
* @private
* @param {Object} updateObject - The partial update object for the profile.
* @private
* @returns {void}
*/
_updateProfile(updateObject: Object) {
@ -157,19 +159,20 @@ export class AbstractAppSettings extends Component<Props> {
/**
* Maps (parts of) the redux state to the React {@code Component} props of
* {@code AbstractAppSettings}.
* {@code AbstractSettingsView}.
*
* @param {Object} state - The redux state.
* @protected
* @returns {Object}
* @returns {{
* _profile: Object,
* _serverURL: string,
* _visible: boolean
* }}
*/
export function _mapStateToProps(state: Object) {
const _serverURL = state['features/app'].app._getDefaultURL();
const _profile = getProfile(state);
return {
_profile,
_serverURL,
_visible: state['features/app-settings'].visible
_profile: getProfile(state),
_serverURL: state['features/app'].app._getDefaultURL(),
_visible: state['features/settings'].visible
};
}

View File

@ -0,0 +1 @@
export * from './web';

View File

@ -0,0 +1 @@
export * from './_';

View File

@ -4,7 +4,7 @@ import React, { Component } from 'react';
import { TouchableOpacity } from 'react-native';
import { Icon } from '../../../base/font-icons';
import { PlatformElements } from '../../../base/styles';
import { Header } from '../../../base/react';
/**
* The type of the React {@code Component} props of {@link BackButton}
@ -40,7 +40,7 @@ export default class BackButton extends Component<Props> {
<Icon
name = { 'arrow_back' }
style = { [
PlatformElements.headerButton,
Header.buttonStyle,
this.props.style
] } />
</TouchableOpacity>

View File

@ -47,6 +47,7 @@ class FormRow extends Component<Props> {
super(props);
React.Children.only(this.props.children);
this._getDefaultFieldProps = this._getDefaultFieldProps.bind(this);
this._getRowStyle = this._getRowStyle.bind(this);
}
@ -63,10 +64,10 @@ class FormRow extends Component<Props> {
// Some field types need additional props to look good and standardized
// on a form.
const newChild = React.cloneElement(
this.props.children,
this._getDefaultFieldProps(this.props.children)
);
const newChild
= React.cloneElement(
this.props.children,
this._getDefaultFieldProps(this.props.children));
return (
<View
@ -74,7 +75,8 @@ class FormRow extends Component<Props> {
<View style = { styles.fieldLabelContainer } >
<Text
style = { [
styles.text, styles.fieldLabelText
styles.text,
styles.fieldLabelText
] } >
{ t(this.props.i18nLabel) }
</Text>
@ -96,8 +98,8 @@ class FormRow extends Component<Props> {
* - TextInput
* - Switch (needs no addition props ATM).
*
* @private
* @param {Object} field - The field (child) component.
* @private
* @returns {Object}
*/
_getDefaultFieldProps(field: Object) {

View File

@ -0,0 +1,251 @@
// @flow
import React from 'react';
import {
Alert,
Modal,
SafeAreaView,
ScrollView,
Switch,
Text,
TextInput,
View
} from 'react-native';
import { connect } from 'react-redux';
import { translate } from '../../../base/i18n';
import { Header } from '../../../base/react';
import {
AbstractSettingsView,
_mapStateToProps
} from '../AbstractSettingsView';
import { setSettingsViewVisible } from '../../actions';
import BackButton from './BackButton';
import FormRow from './FormRow';
import FormSectionHeader from './FormSectionHeader';
import { normalizeUserInputURL } from '../../functions';
import styles from './styles';
/**
* The native container rendering the app settings page.
*
* @extends AbstractSettingsView
*/
class SettingsView extends AbstractSettingsView {
_urlField: Object;
/**
* Initializes a new {@code SettingsView} instance.
*
* @inheritdoc
*/
constructor(props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onBlurServerURL = this._onBlurServerURL.bind(this);
this._onRequestClose = this._onRequestClose.bind(this);
this._setURLFieldReference = this._setURLFieldReference.bind(this);
this._showURLAlert = this._showURLAlert.bind(this);
}
/**
* Implements React's {@link Component#render()}, renders the settings page.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<Modal
animationType = 'slide'
onRequestClose = { this._onRequestClose }
presentationStyle = 'fullScreen'
supportedOrientations = { [
'landscape',
'portrait'
] }
visible = { this.props._visible }>
<View style = { Header.pageStyle }>
{ this._renderHeader() }
{ this._renderBody() }
</View>
</Modal>
);
}
_onBlurServerURL: () => void;
/**
* Handler the server URL lose focus event. Here we validate the server URL
* and update it to the normalized version, or show an error if incorrect.
*
* @private
* @returns {void}
*/
_onBlurServerURL() {
this._processServerURL(false /* hideOnSuccess */);
}
_onChangeDisplayName: (string) => void;
_onChangeEmail: (string) => void;
_onChangeServerURL: (string) => void;
_onRequestClose: () => void;
/**
* Handles the back button. Also invokes normalizeUserInputURL to validate
* the URL entered by the user.
*
* @returns {void}
*/
_onRequestClose() {
this._processServerURL(true /* hideOnSuccess */);
}
_onStartAudioMutedChange: (boolean) => void;
_onStartVideoMutedChange: (boolean) => void;
/**
* Processes the server URL. It normalizes it and an error alert is
* displayed in case it's incorrect.
*
* @param {boolean} hideOnSuccess - True if the dialog should be hidden if
* normalization / validation succeeds, false otherwise.
* @private
* @returns {void}
*/
_processServerURL(hideOnSuccess: boolean) {
const { serverURL } = this.props._profile;
const normalizedURL = normalizeUserInputURL(serverURL);
if (normalizedURL === null) {
this._showURLAlert();
} else {
this._onChangeServerURL(normalizedURL);
if (hideOnSuccess) {
this.props.dispatch(setSettingsViewVisible(false));
}
}
}
/**
* Renders the body (under the header) of {@code SettingsView}.
*
* @private
* @returns {React$Element}
*/
_renderBody() {
const { _profile } = this.props;
return (
<SafeAreaView style = { styles.settingsForm }>
<ScrollView>
<FormSectionHeader
i18nLabel = 'settingsView.profileSection' />
<FormRow
fieldSeparator = { true }
i18nLabel = 'settingsView.displayName'>
<TextInput
onChangeText = { this._onChangeDisplayName }
placeholder = 'John Doe'
value = { _profile.displayName } />
</FormRow>
<FormRow i18nLabel = 'settingsView.email'>
<TextInput
keyboardType = { 'email-address' }
onChangeText = { this._onChangeEmail }
placeholder = 'email@example.com'
value = { _profile.email } />
</FormRow>
<FormSectionHeader
i18nLabel = 'settingsView.conferenceSection' />
<FormRow
fieldSeparator = { true }
i18nLabel = 'settingsView.serverURL'>
<TextInput
autoCapitalize = 'none'
onBlur = { this._onBlurServerURL }
onChangeText = { this._onChangeServerURL }
placeholder = { this.props._serverURL }
value = { _profile.serverURL } />
</FormRow>
<FormRow
fieldSeparator = { true }
i18nLabel = 'settingsView.startWithAudioMuted'>
<Switch
onValueChange = { this._onStartAudioMutedChange }
value = { _profile.startWithAudioMuted } />
</FormRow>
<FormRow i18nLabel = 'settingsView.startWithVideoMuted'>
<Switch
onValueChange = { this._onStartVideoMutedChange }
value = { _profile.startWithVideoMuted } />
</FormRow>
</ScrollView>
</SafeAreaView>
);
}
/**
* Renders the header of {@code SettingsView}.
*
* @private
* @returns {React$Element}
*/
_renderHeader() {
return (
<Header>
<BackButton onPress = { this._onRequestClose } />
<Text
style = { [
styles.text,
Header.textStyle
] }>
{ this.props.t('settingsView.header') }
</Text>
</Header>
);
}
_setURLFieldReference: (React$ElementRef<*> | null) => void;
/**
* Stores a reference to the URL field for later use.
*
* @param {Object} component - The field component.
* @protected
* @returns {void}
*/
_setURLFieldReference(component) {
this._urlField = component;
}
_showURLAlert: () => void;
/**
* Shows an alert telling the user that the URL he/she entered was invalid.
*
* @returns {void}
*/
_showURLAlert() {
const { t } = this.props;
Alert.alert(
t('settingsView.alertTitle'),
t('settingsView.alertURLText'),
[
{
onPress: () => this._urlField.focus(),
text: t('settingsView.alertOk')
}
]
);
}
}
export default translate(connect(_mapStateToProps)(SettingsView));

View File

@ -0,0 +1 @@
export { default as SettingsView } from './SettingsView';

View File

@ -7,8 +7,7 @@ export const ANDROID_UNDERLINE_COLOR = 'transparent';
const TEXT_SIZE = 17;
/**
* The styles of the native components of the feature
* {@code app-settings}.
* The styles of the native components of the feature {@code settings}.
*/
export default createStyleSheet({
/**

View File

@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { openDeviceSelectionDialog } from '../../device-selection';
import { translate } from '../../../base/i18n';
import { openDeviceSelectionDialog } from '../../../device-selection';
/**
* Implements a React {@link Component} which displays a button for opening the

View File

@ -5,7 +5,7 @@ import DropdownMenu, {
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { DEFAULT_LANGUAGE, LANGUAGES, translate } from '../../base/i18n';
import { DEFAULT_LANGUAGE, LANGUAGES, translate } from '../../../base/i18n';
/**
* Implements a React {@link Component} which displays a dropdown for changing

View File

@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setFollowMe, setStartMutedPolicy } from '../../base/conference';
import { translate } from '../../base/i18n';
import { setFollowMe, setStartMutedPolicy } from '../../../base/conference';
import { translate } from '../../../base/i18n';
/**
* Implements a React {@link Component} which displays checkboxes for enabling

View File

@ -2,8 +2,11 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants';
import { translate } from '../../../base/i18n';
import {
getLocalParticipant,
PARTICIPANT_ROLE
} from '../../../base/participants';
import DeviceSelectionButton from './DeviceSelectionButton';
import LanguageSelectDropdown from './LanguageSelectDropdown';

View File

@ -14,6 +14,7 @@ export function normalizeUserInputURL(url: string) {
if (url) {
url = url.replace(/\s/g, '').toLowerCase();
const urlRegExp = new RegExp('^(\\w+://)?(.+)$');
const urlComponents = urlRegExp.exec(url);
@ -21,8 +22,7 @@ export function normalizeUserInputURL(url: string) {
url = `https://${urlComponents[2]}`;
}
const parsedURI
= parseStandardURIString(url);
const parsedURI = parseStandardURIString(url);
if (!parsedURI.host) {
return null;

View File

@ -1,4 +1,6 @@
export * from './actions';
export * from './actionTypes';
export * from './components';
import './middleware';
import './reducer';

View File

@ -3,35 +3,34 @@
import { SET_ROOM } from '../base/conference';
import { MiddlewareRegistry } from '../base/redux';
import { hideAppSettings } from './actions';
import { setSettingsViewVisible } from './actions';
/**
* The Redux middleware to trigger settings screen show or hide
* when necessary.
* The redux middleware to set the visibility of {@link SettingsView}.
*
* @param {Store} store - The Redux store.
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case SET_ROOM:
return _closeAppSettings(store, next, action);
return _hideSettingsView(store, next, action);
}
return next(action);
});
/**
* Hides the settings screen.
* Hides {@link SettingsView}.
*
* @param {Store} store - The redux store.
* @param {Dispatch} next - The redux dispatch function.
* @param {Dispatch} next - The redux {@code dispatch} function.
* @param {Action} action - The redux action.
* @private
* @returns {Object} The new state.
*/
function _closeAppSettings({ dispatch }, next, action) {
dispatch(hideAppSettings());
function _hideSettingsView({ dispatch }, next, action) {
dispatch(setSettingsViewVisible(false));
return next(action);
}

View File

@ -0,0 +1,17 @@
// @flow
import { ReducerRegistry } from '../base/redux';
import { SET_SETTINGS_VIEW_VISIBLE } from './actionTypes';
ReducerRegistry.register('features/settings', (state = {}, action) => {
switch (action.type) {
case SET_SETTINGS_VIEW_VISIBLE:
return {
...state,
visible: action.visible
};
}
return state;
});

View File

@ -1,4 +1,10 @@
/**
* Action type to signal the need to hide or show the side bar.
* The type of the (redux) action which sets the visibility of
* {@link WelcomePageSideBar}.
*
* {
* type: SET_SIDEBAR_VISIBLE,
* visible: boolean
* }
*/
export const SET_SIDEBAR_VISIBILITY = Symbol('SET_SIDEBAR_VISIBILITY');
export const SET_SIDEBAR_VISIBLE = Symbol('SET_SIDEBAR_VISIBLE');

View File

@ -1,19 +1,20 @@
// @flow
import { SET_SIDEBAR_VISIBILITY } from './actionTypes';
import { SET_SIDEBAR_VISIBLE } from './actionTypes';
/**
* Redux action to hide or show the status bar.
* Sets the visibility of {@link WelcomePageSideBar}.
*
* @param {boolean} visible - The new value of the visibility.
* @param {boolean} visible - If the {@code WelcomePageSideBar} is to be made
* visible, {@code true}; otherwise, {@code false}.
* @returns {{
* type: SET_SIDEBAR_VISIBILITY,
* sideBarVisible: boolean
* type: SET_SIDEBAR_VISIBLE,
* visible: boolean
* }}
*/
export function setSideBarVisibility(visible: boolean) {
export function setSideBarVisible(visible: boolean) {
return {
type: SET_SIDEBAR_VISIBILITY,
sideBarVisible: visible
type: SET_SIDEBAR_VISIBLE,
visible
};
}

View File

@ -3,11 +3,11 @@
import React, { Component } from 'react';
import { Linking, Text, TouchableOpacity, View } from 'react-native';
import styles from './styles';
import { Icon } from '../../base/font-icons';
import { translate } from '../../base/i18n';
import styles from './styles';
type Props = {
/**
@ -43,13 +43,14 @@ type Props = {
class SideBarItem extends Component<Props> {
/**
* Contructor of the SideBarItem Component.
* Initializes a new {@code SideBarItem} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onOpenURL = this._onOpenURL.bind(this);
}

View File

@ -11,26 +11,21 @@ import {
} from 'react-native';
import { connect } from 'react-redux';
import { AppSettings } from '../../app-settings';
import { translate } from '../../base/i18n';
import { Icon } from '../../base/font-icons';
import { MEDIA_TYPE } from '../../base/media';
import { updateProfile } from '../../base/profile';
import {
LoadingIndicator,
Header,
Text
} from '../../base/react';
import { ColorPalette, PlatformElements } from '../../base/styles';
import { LoadingIndicator, Header, Text } from '../../base/react';
import { ColorPalette } from '../../base/styles';
import {
createDesiredLocalTracks,
destroyLocalTracks
} from '../../base/tracks';
import { RecentList } from '../../recent-list';
import { setSideBarVisibility } from '../actions';
import { SettingsView } from '../../settings';
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
import { setSideBarVisible } from '../actions';
import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
import styles, {
PLACEHOLDER_TEXT_COLOR,
@ -62,6 +57,7 @@ class WelcomePage extends AbstractWelcomePage {
this.state.hintBoxAnimation = new Animated.Value(0);
// Bind event handlers so they are only bound once per instance.
this._getHintBoxStyle = this._getHintBoxStyle.bind(this);
this._onFieldFocusChange = this._onFieldFocusChange.bind(this);
this._onShowSideBar = this._onShowSideBar.bind(this);
@ -97,20 +93,21 @@ class WelcomePage extends AbstractWelcomePage {
* @returns {ReactElement}
*/
render() {
const { buttonStyle, pageStyle, textStyle } = Header;
const { t, _profile } = this.props;
return (
<LocalVideoTrackUnderlay style = { styles.welcomePage }>
<View style = { PlatformElements.page }>
<View style = { pageStyle }>
<Header style = { styles.header }>
<TouchableOpacity onPress = { this._onShowSideBar } >
<Icon
name = 'menu'
style = { PlatformElements.headerButton } />
style = { buttonStyle } />
</TouchableOpacity>
<View style = { styles.audioVideoSwitchContainer }>
<Text style = { PlatformElements.headerText } >
{ t('welcomepage.videoEnabledLabel') }
<Text style = { textStyle } >
{ t('welcomepage.audioVideoSwitch.video') }
</Text>
<Switch
onTintColor = { SWITCH_UNDER_COLOR }
@ -118,8 +115,8 @@ class WelcomePage extends AbstractWelcomePage {
style = { styles.audioVideoSwitch }
thumbTintColor = { SWITCH_THUMB_COLOR }
value = { _profile.startAudioOnly } />
<Text style = { PlatformElements.headerText } >
{ t('welcomepage.audioOnlyLabel') }
<Text style = { textStyle } >
{ t('welcomepage.audioVideoSwitch.audio') }
</Text>
</View>
</Header>
@ -145,7 +142,7 @@ class WelcomePage extends AbstractWelcomePage {
}
<RecentList disabled = { this.state._fieldFocused } />
</SafeAreaView>
<AppSettings />
<SettingsView />
</View>
<WelcomePageSideBar />
</LocalVideoTrackUnderlay>
@ -204,7 +201,7 @@ class WelcomePage extends AbstractWelcomePage {
*/
_onShowSideBar() {
Keyboard.dismiss();
this.props.dispatch(setSideBarVisibility(true));
this.props.dispatch(setSideBarVisible(true));
}
/**
@ -234,17 +231,14 @@ class WelcomePage extends AbstractWelcomePage {
const { t } = this.props;
return (
<Animated.View
style = { this._getHintBoxStyle() }>
<Animated.View style = { this._getHintBoxStyle() }>
<View style = { styles.hintTextContainer } >
<Text>
{ t('welcomepage.hintText') }
{ t('welcomepage.roomnameHint') }
</Text>
</View>
<View style = { styles.hintButtonContainer } >
{
this._renderJoinButton()
}
{ this._renderJoinButton() }
</View>
</Animated.View>
);

View File

@ -4,12 +4,6 @@ import React, { Component } from 'react';
import { SafeAreaView, ScrollView, Text } from 'react-native';
import { connect } from 'react-redux';
import SideBarItem from './SideBarItem';
import styles from './styles';
import { setSideBarVisibility } from '../actions';
import { showAppSettings } from '../../app-settings';
import {
Avatar,
getAvatarURL,
@ -20,6 +14,11 @@ import {
Header,
SideBar
} from '../../base/react';
import { setSettingsViewVisible } from '../../settings';
import { setSideBarVisible } from '../actions';
import SideBarItem from './SideBarItem';
import styles from './styles';
/**
* The URL at which the privacy policy is available to the user.
@ -71,6 +70,7 @@ class WelcomePageSideBar extends Component<Props> {
constructor(props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onHideSideBar = this._onHideSideBar.bind(this);
this._onOpenSettings = this._onOpenSettings.bind(this);
}
@ -122,19 +122,19 @@ class WelcomePageSideBar extends Component<Props> {
_onHideSideBar: () => void;
/**
* Invoked when the sidebar has closed itslef (e.g. overlay pressed).
* Invoked when the sidebar has closed itself (e.g. overlay pressed).
*
* @private
* @returns {void}
*/
_onHideSideBar() {
this.props.dispatch(setSideBarVisibility(false));
this.props.dispatch(setSideBarVisible(false));
}
_onOpenSettings: () => void;
/**
* Opens the settings screen.
* Shows the {@link SettingsView}.
*
* @private
* @returns {void}
@ -142,8 +142,8 @@ class WelcomePageSideBar extends Component<Props> {
_onOpenSettings() {
const { dispatch } = this.props;
dispatch(setSideBarVisibility(false));
dispatch(showAppSettings());
dispatch(setSideBarVisible(false));
dispatch(setSettingsViewVisible(true));
}
}

View File

@ -98,14 +98,7 @@ export default createStyleSheet({
*/
hintButtonContainer: {
flexDirection: 'row',
justifyContent: 'flex-end'
},
/**
* Container for the text on the hint box.
*/
hintTextContainer: {
marginBottom: 2 * BoxModel.margin
justifyContent: 'center'
},
/**
@ -123,6 +116,13 @@ export default createStyleSheet({
paddingVertical: 2 * BoxModel.padding
},
/**
* Container for the text on the hint box.
*/
hintTextContainer: {
marginBottom: 2 * BoxModel.margin
},
/**
* Container for the items in the side bar.
*/
@ -142,7 +142,7 @@ export default createStyleSheet({
},
/**
* Top level screen style
* Top-level screen style.
*/
page: {
flex: 1,

View File

@ -1,5 +1,5 @@
import './reducer';
import './route';
export * from './components';
export * from './functions';
import './reducer';
import './route';

View File

@ -1,23 +1,20 @@
import { ReducerRegistry } from '../base/redux';
import { SET_SIDEBAR_VISIBILITY } from './actionTypes';
// @flow
const DEFAULT_STATE = {
sideBarVisible: false
};
import { ReducerRegistry } from '../base/redux';
import { SET_SIDEBAR_VISIBLE } from './actionTypes';
/**
* Reduces the Redux actions of the feature features/recording.
* Reduces redux actions for the purposes of {@code features/welcome}.
*/
ReducerRegistry.register('features/welcome',
(state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_SIDEBAR_VISIBILITY:
return {
...state,
sideBarVisible: action.sideBarVisible
};
ReducerRegistry.register('features/welcome', (state = {}, action) => {
switch (action.type) {
case SET_SIDEBAR_VISIBLE:
return {
...state,
sideBarVisible: action.visible
};
default:
return state;
}
});
default:
return state;
}
});