From 9f69c4d730121235fde8f8169ff28d168247994d Mon Sep 17 00:00:00 2001 From: Lyubo Marinov Date: Mon, 26 Feb 2018 10:14:46 -0600 Subject: [PATCH] Grow features/settings from features/app-settings and features/settings-menu --- .../org/jitsi/meet/sdk/JitsiMeetActivity.java | 6 +- lang/main.json | 15 +- .../UI/side_pannels/settings/SettingsMenu.js | 10 +- react/features/analytics/AnalyticsEvents.js | 193 +++++++------- react/features/app-settings/actionTypes.js | 19 -- react/features/app-settings/actions.js | 29 -- .../components/AppSettings.native.js | 238 ----------------- .../components/AppSettings.web.js | 0 .../features/app-settings/components/_.web.js | 0 .../features/app-settings/components/index.js | 1 - .../app-settings/components/native/index.js | 3 - .../app-settings/components/styles.js | 33 --- react/features/app-settings/reducer.js | 31 --- react/features/base/conference/middleware.js | 17 +- .../base/react/components/native/Header.js | 36 ++- .../base/react/components/native/SideBar.js | 49 ++-- .../base/react/components/native/index.js | 5 +- .../base/react/components/native/styles.js | 36 ++- .../styles/PlatformElements.native.js | 41 --- .../components/styles/PlatformElements.web.js | 0 .../base/styles/components/styles/index.js | 1 - .../DeviceSelectionButton.native.js | 0 .../LanguageSelectDropdown.native.js | 0 .../components/ModeratorCheckboxes.native.js | 0 .../components/SettingsMenu.native.js | 0 react/features/settings-menu/index.js | 1 - react/features/settings/actionTypes.js | 10 + react/features/settings/actions.js | 20 ++ .../components/AbstractSettingsView.js} | 43 +-- .../components/_.native.js | 0 react/features/settings/components/_.web.js | 1 + react/features/settings/components/index.js | 1 + .../components/native/BackButton.js | 4 +- .../components/native/FormRow.js | 14 +- .../components/native/FormSectionHeader.js | 0 .../components/native/SettingsView.js | 251 ++++++++++++++++++ .../settings/components/native/index.js | 1 + .../components/native/styles.js | 3 +- .../components/web/DeviceSelectionButton.js} | 4 +- .../components/web/LanguageSelectDropdown.js} | 2 +- .../components/web/ModeratorCheckboxes.js} | 4 +- .../components/web/SettingsMenu.js} | 7 +- .../components/web}/index.js | 0 .../{app-settings => settings}/functions.js | 4 +- .../{app-settings => settings}/index.js | 2 + .../{app-settings => settings}/middleware.js | 17 +- react/features/settings/reducer.js | 17 ++ react/features/welcome/actionTypes.js | 10 +- react/features/welcome/actions.js | 17 +- .../welcome/components/SideBarItem.js | 7 +- .../welcome/components/WelcomePage.native.js | 40 ++- .../components/WelcomePageSideBar.native.js | 22 +- react/features/welcome/components/styles.js | 18 +- react/features/welcome/index.js | 6 +- react/features/welcome/reducer.js | 33 ++- 55 files changed, 648 insertions(+), 674 deletions(-) delete mode 100644 react/features/app-settings/actionTypes.js delete mode 100644 react/features/app-settings/actions.js delete mode 100644 react/features/app-settings/components/AppSettings.native.js delete mode 100644 react/features/app-settings/components/AppSettings.web.js delete mode 100644 react/features/app-settings/components/_.web.js delete mode 100644 react/features/app-settings/components/index.js delete mode 100644 react/features/app-settings/components/native/index.js delete mode 100644 react/features/app-settings/components/styles.js delete mode 100644 react/features/app-settings/reducer.js delete mode 100644 react/features/base/styles/components/styles/PlatformElements.native.js delete mode 100644 react/features/base/styles/components/styles/PlatformElements.web.js delete mode 100644 react/features/settings-menu/components/DeviceSelectionButton.native.js delete mode 100644 react/features/settings-menu/components/LanguageSelectDropdown.native.js delete mode 100644 react/features/settings-menu/components/ModeratorCheckboxes.native.js delete mode 100644 react/features/settings-menu/components/SettingsMenu.native.js delete mode 100644 react/features/settings-menu/index.js create mode 100644 react/features/settings/actionTypes.js create mode 100644 react/features/settings/actions.js rename react/features/{app-settings/components/AbstractAppSettings.js => settings/components/AbstractSettingsView.js} (80%) rename react/features/{app-settings => settings}/components/_.native.js (100%) create mode 100644 react/features/settings/components/_.web.js create mode 100644 react/features/settings/components/index.js rename react/features/{app-settings => settings}/components/native/BackButton.js (90%) rename react/features/{app-settings => settings}/components/native/FormRow.js (92%) rename react/features/{app-settings => settings}/components/native/FormSectionHeader.js (100%) create mode 100644 react/features/settings/components/native/SettingsView.js create mode 100644 react/features/settings/components/native/index.js rename react/features/{app-settings => settings}/components/native/styles.js (95%) rename react/features/{settings-menu/components/DeviceSelectionButton.web.js => settings/components/web/DeviceSelectionButton.js} (94%) rename react/features/{settings-menu/components/LanguageSelectDropdown.web.js => settings/components/web/LanguageSelectDropdown.js} (98%) rename react/features/{settings-menu/components/ModeratorCheckboxes.web.js => settings/components/web/ModeratorCheckboxes.js} (98%) rename react/features/{settings-menu/components/SettingsMenu.web.js => settings/components/web/SettingsMenu.js} (95%) rename react/features/{settings-menu/components => settings/components/web}/index.js (100%) rename react/features/{app-settings => settings}/functions.js (92%) rename react/features/{app-settings => settings}/index.js (59%) rename react/features/{app-settings => settings}/middleware.js (52%) create mode 100644 react/features/settings/reducer.js diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java index ded19071b..96dba56d3 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java @@ -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 { /** diff --git a/lang/main.json b/lang/main.json index 2b884abed..0f10649e2 100644 --- a/lang/main.json +++ b/lang/main.json @@ -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" } } diff --git a/modules/UI/side_pannels/settings/SettingsMenu.js b/modules/UI/side_pannels/settings/SettingsMenu.js index 35d2106c7..fbf7ff372 100644 --- a/modules/UI/side_pannels/settings/SettingsMenu.js +++ b/modules/UI/side_pannels/settings/SettingsMenu.js @@ -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( - + , settingsMenuContainer diff --git a/react/features/analytics/AnalyticsEvents.js b/react/features/analytics/AnalyticsEvents.js index afb11f246..049bffe83 100644 --- a/react/features/analytics/AnalyticsEvents.js +++ b/react/features/analytics/AnalyticsEvents.js @@ -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' }; +} diff --git a/react/features/app-settings/actionTypes.js b/react/features/app-settings/actionTypes.js deleted file mode 100644 index 36c7e0b12..000000000 --- a/react/features/app-settings/actionTypes.js +++ /dev/null @@ -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'); diff --git a/react/features/app-settings/actions.js b/react/features/app-settings/actions.js deleted file mode 100644 index f446570ec..000000000 --- a/react/features/app-settings/actions.js +++ /dev/null @@ -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 - }; -} diff --git a/react/features/app-settings/components/AppSettings.native.js b/react/features/app-settings/components/AppSettings.native.js deleted file mode 100644 index 86040846a..000000000 --- a/react/features/app-settings/components/AppSettings.native.js +++ /dev/null @@ -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 ( - - -
- - - { t('settingsScreen.header') } - -
- - - - - - - - - - - - - - - - - - - - - -
-
- ); - } - - _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)); diff --git a/react/features/app-settings/components/AppSettings.web.js b/react/features/app-settings/components/AppSettings.web.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/app-settings/components/_.web.js b/react/features/app-settings/components/_.web.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/app-settings/components/index.js b/react/features/app-settings/components/index.js deleted file mode 100644 index 3be0dae45..000000000 --- a/react/features/app-settings/components/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as AppSettings } from './AppSettings'; diff --git a/react/features/app-settings/components/native/index.js b/react/features/app-settings/components/native/index.js deleted file mode 100644 index 4a8909939..000000000 --- a/react/features/app-settings/components/native/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { default as BackButton } from './BackButton'; -export { default as FormRow } from './FormRow'; -export { default as FormSectionHeader } from './FormSectionHeader'; diff --git a/react/features/app-settings/components/styles.js b/react/features/app-settings/components/styles.js deleted file mode 100644 index 166bf299b..000000000 --- a/react/features/app-settings/components/styles.js +++ /dev/null @@ -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 - } -}); diff --git a/react/features/app-settings/reducer.js b/react/features/app-settings/reducer.js deleted file mode 100644 index 62115d19c..000000000 --- a/react/features/app-settings/reducer.js +++ /dev/null @@ -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; - }); diff --git a/react/features/base/conference/middleware.js b/react/features/base/conference/middleware.js index 8a421bff3..e4fa97662 100644 --- a/react/features/base/conference/middleware.js +++ b/react/features/base/conference/middleware.js @@ -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)); diff --git a/react/features/base/react/components/native/Header.js b/react/features/base/react/components/native/Header.js index 420fc56d1..98d1c5e71 100644 --- a/react/features/base/react/components/native/Header.js +++ b/react/features/base/react/components/native/Header.js @@ -32,7 +32,36 @@ type Props = { export default class Header extends Component { /** - * 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 { _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 { return null; } - } diff --git a/react/features/base/react/components/native/SideBar.js b/react/features/base/react/components/native/SideBar.js index d266de1ea..9f2656ff4 100644 --- a/react/features/base/react/components/native/SideBar.js +++ b/react/features/base/react/components/native/SideBar.js @@ -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 { _mounted: boolean; /** - * Component's contructor. + * Initializes a new {@code SideBar} instance. * * @inheritdoc */ @@ -71,15 +73,15 @@ export default class SideBar extends Component { 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 { /** * 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 { }); } - 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 { }); } } - } diff --git a/react/features/base/react/components/native/index.js b/react/features/base/react/components/native/index.js index 63bcce6f9..aed450682 100644 --- a/react/features/base/react/components/native/index.js +++ b/react/features/base/react/components/native/index.js @@ -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'; diff --git a/react/features/base/react/components/native/styles.js b/react/features/base/react/components/native/styles.js index 6b177c9d7..aa4aa550e 100644 --- a/react/features/base/react/components/native/styles.js +++ b/react/features/base/react/components/native/styles.js @@ -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 */ diff --git a/react/features/base/styles/components/styles/PlatformElements.native.js b/react/features/base/styles/components/styles/PlatformElements.native.js deleted file mode 100644 index b25b3557b..000000000 --- a/react/features/base/styles/components/styles/PlatformElements.native.js +++ /dev/null @@ -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 - } -}); diff --git a/react/features/base/styles/components/styles/PlatformElements.web.js b/react/features/base/styles/components/styles/PlatformElements.web.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/base/styles/components/styles/index.js b/react/features/base/styles/components/styles/index.js index f6673b7c1..5b08709f4 100644 --- a/react/features/base/styles/components/styles/index.js +++ b/react/features/base/styles/components/styles/index.js @@ -1,3 +1,2 @@ export * from './BoxModel'; export * from './ColorPalette'; -export * from './PlatformElements'; diff --git a/react/features/settings-menu/components/DeviceSelectionButton.native.js b/react/features/settings-menu/components/DeviceSelectionButton.native.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/settings-menu/components/LanguageSelectDropdown.native.js b/react/features/settings-menu/components/LanguageSelectDropdown.native.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/settings-menu/components/ModeratorCheckboxes.native.js b/react/features/settings-menu/components/ModeratorCheckboxes.native.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/settings-menu/components/SettingsMenu.native.js b/react/features/settings-menu/components/SettingsMenu.native.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/settings-menu/index.js b/react/features/settings-menu/index.js deleted file mode 100644 index 07635cbbc..000000000 --- a/react/features/settings-menu/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './components'; diff --git a/react/features/settings/actionTypes.js b/react/features/settings/actionTypes.js new file mode 100644 index 000000000..e37aefdde --- /dev/null +++ b/react/features/settings/actionTypes.js @@ -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'); diff --git a/react/features/settings/actions.js b/react/features/settings/actions.js new file mode 100644 index 000000000..3b61bc2d6 --- /dev/null +++ b/react/features/settings/actions.js @@ -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 + }; +} diff --git a/react/features/app-settings/components/AbstractAppSettings.js b/react/features/settings/components/AbstractSettingsView.js similarity index 80% rename from react/features/app-settings/components/AbstractAppSettings.js rename to react/features/settings/components/AbstractSettingsView.js index 70e0147ee..8bfdabd89 100644 --- a/react/features/app-settings/components/AbstractAppSettings.js +++ b/react/features/settings/components/AbstractSettingsView.js @@ -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 { +export class AbstractSettingsView extends Component { /** - * 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 { 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 { /** * 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 { /** * 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 { /** * 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 { /** * 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 { /** * 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 { /** * 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 { /** * 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 }; } diff --git a/react/features/app-settings/components/_.native.js b/react/features/settings/components/_.native.js similarity index 100% rename from react/features/app-settings/components/_.native.js rename to react/features/settings/components/_.native.js diff --git a/react/features/settings/components/_.web.js b/react/features/settings/components/_.web.js new file mode 100644 index 000000000..b80c83af3 --- /dev/null +++ b/react/features/settings/components/_.web.js @@ -0,0 +1 @@ +export * from './web'; diff --git a/react/features/settings/components/index.js b/react/features/settings/components/index.js new file mode 100644 index 000000000..cda61441e --- /dev/null +++ b/react/features/settings/components/index.js @@ -0,0 +1 @@ +export * from './_'; diff --git a/react/features/app-settings/components/native/BackButton.js b/react/features/settings/components/native/BackButton.js similarity index 90% rename from react/features/app-settings/components/native/BackButton.js rename to react/features/settings/components/native/BackButton.js index e1889a45a..02be052d4 100644 --- a/react/features/app-settings/components/native/BackButton.js +++ b/react/features/settings/components/native/BackButton.js @@ -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 { diff --git a/react/features/app-settings/components/native/FormRow.js b/react/features/settings/components/native/FormRow.js similarity index 92% rename from react/features/app-settings/components/native/FormRow.js rename to react/features/settings/components/native/FormRow.js index 461a71647..f51beee49 100644 --- a/react/features/app-settings/components/native/FormRow.js +++ b/react/features/settings/components/native/FormRow.js @@ -47,6 +47,7 @@ class FormRow extends Component { 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 { // 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 ( { { t(this.props.i18nLabel) } @@ -96,8 +98,8 @@ class FormRow extends Component { * - TextInput * - Switch (needs no addition props ATM). * - * @private * @param {Object} field - The field (child) component. + * @private * @returns {Object} */ _getDefaultFieldProps(field: Object) { diff --git a/react/features/app-settings/components/native/FormSectionHeader.js b/react/features/settings/components/native/FormSectionHeader.js similarity index 100% rename from react/features/app-settings/components/native/FormSectionHeader.js rename to react/features/settings/components/native/FormSectionHeader.js diff --git a/react/features/settings/components/native/SettingsView.js b/react/features/settings/components/native/SettingsView.js new file mode 100644 index 000000000..8c2f4da19 --- /dev/null +++ b/react/features/settings/components/native/SettingsView.js @@ -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 ( + + + { this._renderHeader() } + { this._renderBody() } + + + ); + } + + _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 ( + + + + + + + + + + + + + + + + + + + + + + ); + } + + /** + * Renders the header of {@code SettingsView}. + * + * @private + * @returns {React$Element} + */ + _renderHeader() { + return ( +
+ + + { this.props.t('settingsView.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)); diff --git a/react/features/settings/components/native/index.js b/react/features/settings/components/native/index.js new file mode 100644 index 000000000..1320e1ba9 --- /dev/null +++ b/react/features/settings/components/native/index.js @@ -0,0 +1 @@ +export { default as SettingsView } from './SettingsView'; diff --git a/react/features/app-settings/components/native/styles.js b/react/features/settings/components/native/styles.js similarity index 95% rename from react/features/app-settings/components/native/styles.js rename to react/features/settings/components/native/styles.js index f82f9b445..d202d3cb8 100644 --- a/react/features/app-settings/components/native/styles.js +++ b/react/features/settings/components/native/styles.js @@ -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({ /** diff --git a/react/features/settings-menu/components/DeviceSelectionButton.web.js b/react/features/settings/components/web/DeviceSelectionButton.js similarity index 94% rename from react/features/settings-menu/components/DeviceSelectionButton.web.js rename to react/features/settings/components/web/DeviceSelectionButton.js index 0ba5c230e..63faec81e 100644 --- a/react/features/settings-menu/components/DeviceSelectionButton.web.js +++ b/react/features/settings/components/web/DeviceSelectionButton.js @@ -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 diff --git a/react/features/settings-menu/components/LanguageSelectDropdown.web.js b/react/features/settings/components/web/LanguageSelectDropdown.js similarity index 98% rename from react/features/settings-menu/components/LanguageSelectDropdown.web.js rename to react/features/settings/components/web/LanguageSelectDropdown.js index 5118b6c06..8a5827faf 100644 --- a/react/features/settings-menu/components/LanguageSelectDropdown.web.js +++ b/react/features/settings/components/web/LanguageSelectDropdown.js @@ -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 diff --git a/react/features/settings-menu/components/ModeratorCheckboxes.web.js b/react/features/settings/components/web/ModeratorCheckboxes.js similarity index 98% rename from react/features/settings-menu/components/ModeratorCheckboxes.web.js rename to react/features/settings/components/web/ModeratorCheckboxes.js index e3db3c0f6..6fb698f1d 100644 --- a/react/features/settings-menu/components/ModeratorCheckboxes.web.js +++ b/react/features/settings/components/web/ModeratorCheckboxes.js @@ -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 diff --git a/react/features/settings-menu/components/SettingsMenu.web.js b/react/features/settings/components/web/SettingsMenu.js similarity index 95% rename from react/features/settings-menu/components/SettingsMenu.web.js rename to react/features/settings/components/web/SettingsMenu.js index 4b4888e3d..c4def41c0 100644 --- a/react/features/settings-menu/components/SettingsMenu.web.js +++ b/react/features/settings/components/web/SettingsMenu.js @@ -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'; diff --git a/react/features/settings-menu/components/index.js b/react/features/settings/components/web/index.js similarity index 100% rename from react/features/settings-menu/components/index.js rename to react/features/settings/components/web/index.js diff --git a/react/features/app-settings/functions.js b/react/features/settings/functions.js similarity index 92% rename from react/features/app-settings/functions.js rename to react/features/settings/functions.js index bd0191dcd..9ca8fcf27 100644 --- a/react/features/app-settings/functions.js +++ b/react/features/settings/functions.js @@ -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; diff --git a/react/features/app-settings/index.js b/react/features/settings/index.js similarity index 59% rename from react/features/app-settings/index.js rename to react/features/settings/index.js index 582e1f9dd..a29aa08e0 100644 --- a/react/features/app-settings/index.js +++ b/react/features/settings/index.js @@ -1,4 +1,6 @@ export * from './actions'; +export * from './actionTypes'; export * from './components'; +import './middleware'; import './reducer'; diff --git a/react/features/app-settings/middleware.js b/react/features/settings/middleware.js similarity index 52% rename from react/features/app-settings/middleware.js rename to react/features/settings/middleware.js index 513189bba..e24b4cc7c 100644 --- a/react/features/app-settings/middleware.js +++ b/react/features/settings/middleware.js @@ -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); } diff --git a/react/features/settings/reducer.js b/react/features/settings/reducer.js new file mode 100644 index 000000000..cc790b3e2 --- /dev/null +++ b/react/features/settings/reducer.js @@ -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; +}); diff --git a/react/features/welcome/actionTypes.js b/react/features/welcome/actionTypes.js index 6e666c714..be0ea0b6c 100644 --- a/react/features/welcome/actionTypes.js +++ b/react/features/welcome/actionTypes.js @@ -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'); diff --git a/react/features/welcome/actions.js b/react/features/welcome/actions.js index aa20a4c50..f6983d8b3 100644 --- a/react/features/welcome/actions.js +++ b/react/features/welcome/actions.js @@ -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 }; } diff --git a/react/features/welcome/components/SideBarItem.js b/react/features/welcome/components/SideBarItem.js index b13fc1f0d..bafefb03b 100644 --- a/react/features/welcome/components/SideBarItem.js +++ b/react/features/welcome/components/SideBarItem.js @@ -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 { /** - * 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); } diff --git a/react/features/welcome/components/WelcomePage.native.js b/react/features/welcome/components/WelcomePage.native.js index 8a7023420..ab27add2c 100644 --- a/react/features/welcome/components/WelcomePage.native.js +++ b/react/features/welcome/components/WelcomePage.native.js @@ -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 ( - +
+ style = { buttonStyle } /> - - { t('welcomepage.videoEnabledLabel') } + + { t('welcomepage.audioVideoSwitch.video') } - - { t('welcomepage.audioOnlyLabel') } + + { t('welcomepage.audioVideoSwitch.audio') }
@@ -145,7 +142,7 @@ class WelcomePage extends AbstractWelcomePage { } - +
@@ -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 ( - + - { t('welcomepage.hintText') } + { t('welcomepage.roomnameHint') } - { - this._renderJoinButton() - } + { this._renderJoinButton() } ); diff --git a/react/features/welcome/components/WelcomePageSideBar.native.js b/react/features/welcome/components/WelcomePageSideBar.native.js index 684f51080..7b31e3e09 100644 --- a/react/features/welcome/components/WelcomePageSideBar.native.js +++ b/react/features/welcome/components/WelcomePageSideBar.native.js @@ -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 { 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 { _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 { _onOpenSettings() { const { dispatch } = this.props; - dispatch(setSideBarVisibility(false)); - dispatch(showAppSettings()); + dispatch(setSideBarVisible(false)); + dispatch(setSettingsViewVisible(true)); } } diff --git a/react/features/welcome/components/styles.js b/react/features/welcome/components/styles.js index c6f2c78dd..c483c613c 100644 --- a/react/features/welcome/components/styles.js +++ b/react/features/welcome/components/styles.js @@ -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, diff --git a/react/features/welcome/index.js b/react/features/welcome/index.js index ee08e02fd..f77620716 100644 --- a/react/features/welcome/index.js +++ b/react/features/welcome/index.js @@ -1,5 +1,5 @@ -import './reducer'; -import './route'; - export * from './components'; export * from './functions'; + +import './reducer'; +import './route'; diff --git a/react/features/welcome/reducer.js b/react/features/welcome/reducer.js index bf56bd98d..87f4c50a2 100644 --- a/react/features/welcome/reducer.js +++ b/react/features/welcome/reducer.js @@ -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; + } +});