浏览代码

rn: add a new advanced settings section

Currently only 2 options are implemented, mainly aimed at helping troubleshoot
audio related problems:

- Disable native call integration (it disables CallKit / ConnectionService)
- Disable P2P
master
Saúl Ibarra Corretgé 5 年前
父节点
当前提交
6d16e087d9

+ 4
- 0
lang/main.json 查看文件

526
         "title": "Settings"
526
         "title": "Settings"
527
     },
527
     },
528
     "settingsView": {
528
     "settingsView": {
529
+        "advanced": "Advanced",
529
         "alertOk": "OK",
530
         "alertOk": "OK",
530
         "alertTitle": "Warning",
531
         "alertTitle": "Warning",
531
         "alertURLText": "The entered server URL is invalid",
532
         "alertURLText": "The entered server URL is invalid",
532
         "buildInfoSection": "Build Information",
533
         "buildInfoSection": "Build Information",
533
         "conferenceSection": "Conference",
534
         "conferenceSection": "Conference",
535
+        "disableCallIntegration": "Disable native call integration",
536
+        "disableP2P": "Disable Peer-To-Peer mode",
534
         "displayName": "Display name",
537
         "displayName": "Display name",
535
         "email": "Email",
538
         "email": "Email",
536
         "header": "Settings",
539
         "header": "Settings",
537
         "profileSection": "Profile",
540
         "profileSection": "Profile",
538
         "serverURL": "Server URL",
541
         "serverURL": "Server URL",
542
+        "showAdvanced": "Show advanced settings",
539
         "startWithAudioMuted": "Start with audio muted",
543
         "startWithAudioMuted": "Start with audio muted",
540
         "startWithVideoMuted": "Start with video muted",
544
         "startWithVideoMuted": "Start with video muted",
541
         "version": "Version"
545
         "version": "Version"

+ 8
- 1
react/features/app/components/App.native.js 查看文件

6
 import '../../authentication';
6
 import '../../authentication';
7
 import { setColorScheme } from '../../base/color-scheme';
7
 import { setColorScheme } from '../../base/color-scheme';
8
 import { DialogContainer } from '../../base/dialog';
8
 import { DialogContainer } from '../../base/dialog';
9
-import { updateFlags } from '../../base/flags';
9
+import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
10
 import '../../base/jwt';
10
 import '../../base/jwt';
11
 import { Platform } from '../../base/react';
11
 import { Platform } from '../../base/react';
12
 import {
12
 import {
100
             dispatch(setColorScheme(this.props.colorScheme));
100
             dispatch(setColorScheme(this.props.colorScheme));
101
             dispatch(updateFlags(this.props.flags));
101
             dispatch(updateFlags(this.props.flags));
102
             dispatch(updateSettings(this.props.userInfo || {}));
102
             dispatch(updateSettings(this.props.userInfo || {}));
103
+
104
+            // Update settings with feature-flag.
105
+            const callIntegrationEnabled = this.props.flags[CALL_INTEGRATION_ENABLED];
106
+
107
+            if (typeof callIntegrationEnabled !== 'undefined') {
108
+                dispatch(updateSettings({ disableCallIntegration: !callIntegrationEnabled }));
109
+            }
103
         });
110
         });
104
     }
111
     }
105
 
112
 

+ 14
- 0
react/features/base/config/actionTypes.js 查看文件

34
  * }
34
  * }
35
  */
35
  */
36
 export const SET_CONFIG = 'SET_CONFIG';
36
 export const SET_CONFIG = 'SET_CONFIG';
37
+
38
+/**
39
+ * The redux action which updates the configuration represented by the feature
40
+ * base/config. The configuration is defined and consumed by the library
41
+ * lib-jitsi-meet but some of its properties are consumed by the application
42
+ * jitsi-meet as well. A merge operation is performed between the existing config
43
+ * and the passed object.
44
+ *
45
+ * {
46
+ *     type: _UPDATE_CONFIG,
47
+ *     config: Object
48
+ * }
49
+ */
50
+export const _UPDATE_CONFIG = '_UPDATE_CONFIG';

+ 17
- 3
react/features/base/config/middleware.js 查看文件

5
 import { MiddlewareRegistry } from '../redux';
5
 import { MiddlewareRegistry } from '../redux';
6
 import { parseURIString } from '../util';
6
 import { parseURIString } from '../util';
7
 
7
 
8
-import { SET_CONFIG } from './actionTypes';
8
+import { _UPDATE_CONFIG, SET_CONFIG } from './actionTypes';
9
 import { _CONFIG_STORE_PREFIX } from './constants';
9
 import { _CONFIG_STORE_PREFIX } from './constants';
10
 
10
 
11
 /**
11
 /**
93
  * @private
93
  * @private
94
  * @returns {*} The return value of {@code next(action)}.
94
  * @returns {*} The return value of {@code next(action)}.
95
  */
95
  */
96
-function _setConfig({ getState }, next, action) {
96
+function _setConfig({ dispatch, getState }, next, action) {
97
     // The reducer is doing some alterations to the config passed in the action,
97
     // The reducer is doing some alterations to the config passed in the action,
98
     // so make sure it's the final state by waiting for the action to be
98
     // so make sure it's the final state by waiting for the action to be
99
     // reduced.
99
     // reduced.
100
     const result = next(action);
100
     const result = next(action);
101
+    const state = getState();
102
+
103
+    // Update the config with user defined settings.
104
+    const settings = state['features/base/settings'];
105
+    const config = {};
106
+
107
+    if (typeof settings.disableP2P !== 'undefined') {
108
+        config.p2p = { enabled: !settings.disableP2P };
109
+    }
110
+
111
+    dispatch({
112
+        type: _UPDATE_CONFIG,
113
+        config
114
+    });
101
 
115
 
102
     // FIXME On Web we rely on the global 'config' variable which gets altered
116
     // FIXME On Web we rely on the global 'config' variable which gets altered
103
     // multiple times, before it makes it to the reducer. At some point it may
117
     // multiple times, before it makes it to the reducer. At some point it may
105
     // different merge methods being used along the way. The global variable
119
     // different merge methods being used along the way. The global variable
106
     // must be synchronized with the final state resolved by the reducer.
120
     // must be synchronized with the final state resolved by the reducer.
107
     if (typeof window.config !== 'undefined') {
121
     if (typeof window.config !== 'undefined') {
108
-        window.config = getState()['features/base/config'];
122
+        window.config = state['features/base/config'];
109
     }
123
     }
110
 
124
 
111
     return result;
125
     return result;

+ 58
- 42
react/features/base/config/reducer.js 查看文件

5
 import Platform from '../react/Platform';
5
 import Platform from '../react/Platform';
6
 import { equals, ReducerRegistry, set } from '../redux';
6
 import { equals, ReducerRegistry, set } from '../redux';
7
 
7
 
8
-import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
8
+import { _UPDATE_CONFIG, CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
9
 import { _cleanupConfig } from './functions';
9
 import { _cleanupConfig } from './functions';
10
 
10
 
11
 /**
11
 /**
56
     }
56
     }
57
 };
57
 };
58
 
58
 
59
-ReducerRegistry.register(
60
-    'features/base/config',
61
-    (state = _getInitialState(), action) => {
62
-        switch (action.type) {
63
-        case CONFIG_WILL_LOAD:
59
+ReducerRegistry.register('features/base/config', (state = _getInitialState(), action) => {
60
+    switch (action.type) {
61
+    case _UPDATE_CONFIG:
62
+        return _updateConfig(state, action);
63
+
64
+    case CONFIG_WILL_LOAD:
65
+        return {
66
+            error: undefined,
67
+
68
+            /**
69
+                * The URL of the location associated with/configured by this
70
+                * configuration.
71
+                *
72
+                * @type URL
73
+                */
74
+            locationURL: action.locationURL
75
+        };
76
+
77
+    case LOAD_CONFIG_ERROR:
78
+        // XXX LOAD_CONFIG_ERROR is one of the settlement execution paths of
79
+        // the asynchronous "loadConfig procedure/process" started with
80
+        // CONFIG_WILL_LOAD. Due to the asynchronous nature of it, whoever
81
+        // is settling the process needs to provide proof that they have
82
+        // started it and that the iteration of the process being completed
83
+        // now is still of interest to the app.
84
+        if (state.locationURL === action.locationURL) {
64
             return {
85
             return {
65
-                error: undefined,
66
-
67
                 /**
86
                 /**
68
-                 * The URL of the location associated with/configured by this
69
-                 * configuration.
70
-                 *
71
-                 * @type URL
72
-                 */
73
-                locationURL: action.locationURL
87
+                    * The {@link Error} which prevented the loading of the
88
+                    * configuration of the associated {@code locationURL}.
89
+                    *
90
+                    * @type Error
91
+                    */
92
+                error: action.error
74
             };
93
             };
75
-
76
-        case LOAD_CONFIG_ERROR:
77
-            // XXX LOAD_CONFIG_ERROR is one of the settlement execution paths of
78
-            // the asynchronous "loadConfig procedure/process" started with
79
-            // CONFIG_WILL_LOAD. Due to the asynchronous nature of it, whoever
80
-            // is settling the process needs to provide proof that they have
81
-            // started it and that the iteration of the process being completed
82
-            // now is still of interest to the app.
83
-            if (state.locationURL === action.locationURL) {
84
-                return {
85
-                    /**
86
-                     * The {@link Error} which prevented the loading of the
87
-                     * configuration of the associated {@code locationURL}.
88
-                     *
89
-                     * @type Error
90
-                     */
91
-                    error: action.error
92
-                };
93
-            }
94
-            break;
95
-
96
-        case SET_CONFIG:
97
-            return _setConfig(state, action);
98
         }
94
         }
95
+        break;
99
 
96
 
100
-        return state;
101
-    });
97
+    case SET_CONFIG:
98
+        return _setConfig(state, action);
99
+    }
100
+
101
+    return state;
102
+});
102
 
103
 
103
 /**
104
 /**
104
  * Gets the initial state of the feature base/config. The mandatory
105
  * Gets the initial state of the feature base/config. The mandatory
120
  * Reduces a specific Redux action SET_CONFIG of the feature
121
  * Reduces a specific Redux action SET_CONFIG of the feature
121
  * base/lib-jitsi-meet.
122
  * base/lib-jitsi-meet.
122
  *
123
  *
123
- * @param {Object} state - The Redux state of the feature base/lib-jitsi-meet.
124
+ * @param {Object} state - The Redux state of the feature base/config.
124
  * @param {Action} action - The Redux action SET_CONFIG to reduce.
125
  * @param {Action} action - The Redux action SET_CONFIG to reduce.
125
  * @private
126
  * @private
126
- * @returns {Object} The new state of the feature base/lib-jitsi-meet after the
127
- * reduction of the specified action.
127
+ * @returns {Object} The new state after the reduction of the specified action.
128
  */
128
  */
129
 function _setConfig(state, { config }) {
129
 function _setConfig(state, { config }) {
130
     // The mobile app bundles jitsi-meet and lib-jitsi-meet at build time and
130
     // The mobile app bundles jitsi-meet and lib-jitsi-meet at build time and
207
 
207
 
208
     return newValue;
208
     return newValue;
209
 }
209
 }
210
+
211
+/**
212
+ * Updates the stored configuration with the given extra options.
213
+ *
214
+ * @param {Object} state - The Redux state of the feature base/config.
215
+ * @param {Action} action - The Redux action to reduce.
216
+ * @private
217
+ * @returns {Object} The new state after the reduction of the specified action.
218
+ */
219
+function _updateConfig(state, { config }) {
220
+    const newState = _.merge({}, state, config);
221
+
222
+    _cleanupConfig(newState);
223
+
224
+    return equals(state, newState) ? state : newState;
225
+}

+ 7
- 0
react/features/base/flags/constants.js 查看文件

6
  */
6
  */
7
 export const CALENDAR_ENABLED = 'calendar.enabled';
7
 export const CALENDAR_ENABLED = 'calendar.enabled';
8
 
8
 
9
+/**
10
+ * Flag indicating if call integration (CallKit on iOS, ConnectionService on Android)
11
+ * should be enabled.
12
+ * Default: enabled (true).
13
+ */
14
+export const CALL_INTEGRATION_ENABLED = 'call-integration.enabled';
15
+
9
 /**
16
 /**
10
  * Flag indicating if chat should be enabled.
17
  * Flag indicating if chat should be enabled.
11
  * Default: enabled (true).
18
  * Default: enabled (true).

react/features/base/settings/functions.js → react/features/base/settings/functions.any.js 查看文件


+ 21
- 0
react/features/base/settings/functions.native.js 查看文件

1
+// @flow
2
+
3
+import { NativeModules } from 'react-native';
4
+
5
+export * from './functions.any';
6
+
7
+const { AudioMode } = NativeModules;
8
+
9
+/**
10
+ * Handles changes to the `disableCallIntegration` setting.
11
+ * On Android (where `AudioMode.setUseConnectionService` is defined) we must update
12
+ * the native side too, since audio routing works differently.
13
+ *
14
+ * @param {boolean} disabled - Whether call integration is disabled or not.
15
+ * @returns {void}
16
+ */
17
+export function handleCallIntegrationChange(disabled: boolean) {
18
+    if (AudioMode.setUseConnectionService) {
19
+        AudioMode.setUseConnectionService(!disabled);
20
+    }
21
+}

+ 13
- 0
react/features/base/settings/functions.web.js 查看文件

1
+// @flow
2
+
3
+export * from './functions.any';
4
+
5
+/**
6
+ * Handles changes to the `disableCallIntegration` setting.
7
+ * Noop on web.
8
+ *
9
+ * @param {boolean} disabled - Whether call integration is disabled or not.
10
+ * @returns {void}
11
+ */
12
+export function handleCallIntegrationChange(disabled: boolean) { // eslint-disable-line no-unused-vars
13
+}

+ 15
- 0
react/features/base/settings/middleware.js 查看文件

5
 import { MiddlewareRegistry } from '../redux';
5
 import { MiddlewareRegistry } from '../redux';
6
 
6
 
7
 import { SETTINGS_UPDATED } from './actionTypes';
7
 import { SETTINGS_UPDATED } from './actionTypes';
8
+import { handleCallIntegrationChange } from './functions';
8
 
9
 
9
 /**
10
 /**
10
  * The middleware of the feature base/settings. Distributes changes to the state
11
  * The middleware of the feature base/settings. Distributes changes to the state
19
 
20
 
20
     switch (action.type) {
21
     switch (action.type) {
21
     case SETTINGS_UPDATED:
22
     case SETTINGS_UPDATED:
23
+        _maybeHandleCallIntegrationChange(action);
22
         _maybeSetAudioOnly(store, action);
24
         _maybeSetAudioOnly(store, action);
23
         _updateLocalParticipant(store, action);
25
         _updateLocalParticipant(store, action);
24
     }
26
     }
43
     return settingsField;
45
     return settingsField;
44
 }
46
 }
45
 
47
 
48
+/**
49
+ * Updates {@code startAudioOnly} flag if it's updated in the settings.
50
+ *
51
+ * @param {Object} action - The redux action.
52
+ * @private
53
+ * @returns {void}
54
+ */
55
+function _maybeHandleCallIntegrationChange({ settings: { disableCallIntegration } }) {
56
+    if (typeof disableCallIntegration === 'boolean') {
57
+        handleCallIntegrationChange(disableCallIntegration);
58
+    }
59
+}
60
+
46
 /**
61
 /**
47
  * Updates {@code startAudioOnly} flag if it's updated in the settings.
62
  * Updates {@code startAudioOnly} flag if it's updated in the settings.
48
  *
63
  *

+ 2
- 0
react/features/base/settings/reducer.js 查看文件

22
     avatarID: undefined,
22
     avatarID: undefined,
23
     avatarURL: undefined,
23
     avatarURL: undefined,
24
     cameraDeviceId: undefined,
24
     cameraDeviceId: undefined,
25
+    disableCallIntegration: undefined,
26
+    disableP2P: undefined,
25
     displayName: undefined,
27
     displayName: undefined,
26
     email: undefined,
28
     email: undefined,
27
     localFlipX: true,
29
     localFlipX: true,

+ 20
- 0
react/features/mobile/call-integration/functions.js 查看文件

1
+// @flow
2
+
3
+import { CALL_INTEGRATION_ENABLED, getFeatureFlag } from '../../base/flags';
4
+import { toState } from '../../base/redux';
5
+
6
+/**
7
+ * Checks if call integration is enabled or not.
8
+ *
9
+ * @param {Function|Object} stateful - The redux store or {@code getState}
10
+ * function.
11
+ * @returns {string} - Default URL for the app.
12
+ */
13
+export function isCallIntegrationEnabled(stateful: Function | Object) {
14
+    const state = toState(stateful);
15
+    const { disableCallIntegration } = state['features/base/settings'];
16
+    const flag = getFeatureFlag(state, CALL_INTEGRATION_ENABLED);
17
+
18
+    // The feature flag has precedence.
19
+    return flag ?? !disableCallIntegration;
20
+}

+ 29
- 2
react/features/mobile/call-integration/middleware.js 查看文件

34
 
34
 
35
 import CallKit from './CallKit';
35
 import CallKit from './CallKit';
36
 import ConnectionService from './ConnectionService';
36
 import ConnectionService from './ConnectionService';
37
+import { isCallIntegrationEnabled } from './functions';
37
 
38
 
38
 const CallIntegration = CallKit || ConnectionService;
39
 const CallIntegration = CallKit || ConnectionService;
39
 
40
 
139
  * @private
140
  * @private
140
  * @returns {*} The value returned by {@code next(action)}.
141
  * @returns {*} The value returned by {@code next(action)}.
141
  */
142
  */
142
-function _conferenceFailed(store, next, action) {
143
+function _conferenceFailed({ getState }, next, action) {
143
     const result = next(action);
144
     const result = next(action);
144
 
145
 
146
+    if (!isCallIntegrationEnabled(getState)) {
147
+        return result;
148
+    }
149
+
145
     // XXX Certain CONFERENCE_FAILED errors are recoverable i.e. they have
150
     // XXX Certain CONFERENCE_FAILED errors are recoverable i.e. they have
146
     // prevented the user from joining a specific conference but the app may be
151
     // prevented the user from joining a specific conference but the app may be
147
     // able to eventually join the conference.
152
     // able to eventually join the conference.
173
 function _conferenceJoined({ getState }, next, action) {
178
 function _conferenceJoined({ getState }, next, action) {
174
     const result = next(action);
179
     const result = next(action);
175
 
180
 
181
+    if (!isCallIntegrationEnabled(getState)) {
182
+        return result;
183
+    }
184
+
176
     const { callUUID } = action.conference;
185
     const { callUUID } = action.conference;
177
 
186
 
178
     if (callUUID) {
187
     if (callUUID) {
201
  * @private
210
  * @private
202
  * @returns {*} The value returned by {@code next(action)}.
211
  * @returns {*} The value returned by {@code next(action)}.
203
  */
212
  */
204
-function _conferenceLeft(store, next, action) {
213
+function _conferenceLeft({ getState }, next, action) {
205
     const result = next(action);
214
     const result = next(action);
206
 
215
 
216
+    if (!isCallIntegrationEnabled(getState)) {
217
+        return result;
218
+    }
219
+
207
     const { callUUID } = action.conference;
220
     const { callUUID } = action.conference;
208
 
221
 
209
     if (callUUID) {
222
     if (callUUID) {
230
 function _conferenceWillJoin({ dispatch, getState }, next, action) {
243
 function _conferenceWillJoin({ dispatch, getState }, next, action) {
231
     const result = next(action);
244
     const result = next(action);
232
 
245
 
246
+    if (!isCallIntegrationEnabled(getState)) {
247
+        return result;
248
+    }
249
+
233
     const { conference } = action;
250
     const { conference } = action;
234
     const state = getState();
251
     const state = getState();
235
     const { callHandle, callUUID } = state['features/base/config'];
252
     const { callHandle, callUUID } = state['features/base/config'];
341
 function _setAudioOnly({ getState }, next, action) {
358
 function _setAudioOnly({ getState }, next, action) {
342
     const result = next(action);
359
     const result = next(action);
343
     const state = getState();
360
     const state = getState();
361
+
362
+    if (!isCallIntegrationEnabled(state)) {
363
+        return result;
364
+    }
365
+
344
     const conference = getCurrentConference(state);
366
     const conference = getCurrentConference(state);
345
 
367
 
346
     if (conference && conference.callUUID) {
368
     if (conference && conference.callUUID) {
393
  */
415
  */
394
 function _syncTrackState({ getState }, next, action) {
416
 function _syncTrackState({ getState }, next, action) {
395
     const result = next(action);
417
     const result = next(action);
418
+
419
+    if (!isCallIntegrationEnabled(getState)) {
420
+        return result;
421
+    }
422
+
396
     const { jitsiTrack } = action.track;
423
     const { jitsiTrack } = action.track;
397
     const state = getState();
424
     const state = getState();
398
     const conference = getCurrentConference(state);
425
     const conference = getCurrentConference(state);

+ 1
- 1
react/features/settings/components/AbstractSettingsView.js 查看文件

48
  *
48
  *
49
  * @abstract
49
  * @abstract
50
  */
50
  */
51
-export class AbstractSettingsView<P: Props> extends Component<P> {
51
+export class AbstractSettingsView<P: Props, S: *> extends Component<P, S> {
52
 
52
 
53
     /**
53
     /**
54
      * Initializes a new {@code AbstractSettingsView} instance.
54
      * Initializes a new {@code AbstractSettingsView} instance.

+ 107
- 2
react/features/settings/components/native/SettingsView.js 查看文件

32
     _headerStyles: Object
32
     _headerStyles: Object
33
 }
33
 }
34
 
34
 
35
+type State = {
36
+
37
+    /**
38
+     * Whether to show advanced settings or not.
39
+     */
40
+    showAdvanced: boolean
41
+}
42
+
35
 /**
43
 /**
36
  * The native container rendering the app settings page.
44
  * The native container rendering the app settings page.
37
  *
45
  *
38
  * @extends AbstractSettingsView
46
  * @extends AbstractSettingsView
39
  */
47
  */
40
-class SettingsView extends AbstractSettingsView<Props> {
48
+class SettingsView extends AbstractSettingsView<Props, State> {
41
     _urlField: Object;
49
     _urlField: Object;
42
 
50
 
43
     /**
51
     /**
48
     constructor(props) {
56
     constructor(props) {
49
         super(props);
57
         super(props);
50
 
58
 
59
+        this.state = {
60
+            showAdvanced: false
61
+        };
62
+
51
         // Bind event handlers so they are only bound once per instance.
63
         // Bind event handlers so they are only bound once per instance.
52
         this._onBlurServerURL = this._onBlurServerURL.bind(this);
64
         this._onBlurServerURL = this._onBlurServerURL.bind(this);
65
+        this._onDisableCallIntegration = this._onDisableCallIntegration.bind(this);
66
+        this._onDisableP2P = this._onDisableP2P.bind(this);
53
         this._onRequestClose = this._onRequestClose.bind(this);
67
         this._onRequestClose = this._onRequestClose.bind(this);
68
+        this._onShowAdvanced = this._onShowAdvanced.bind(this);
54
         this._setURLFieldReference = this._setURLFieldReference.bind(this);
69
         this._setURLFieldReference = this._setURLFieldReference.bind(this);
55
         this._showURLAlert = this._showURLAlert.bind(this);
70
         this._showURLAlert = this._showURLAlert.bind(this);
56
     }
71
     }
94
 
109
 
95
     _onChangeServerURL: (string) => void;
110
     _onChangeServerURL: (string) => void;
96
 
111
 
112
+    _onDisableCallIntegration: (boolean) => void;
113
+
114
+    /**
115
+     * Handles the disable call integration change event.
116
+     *
117
+     * @param {boolean} newValue - The new value
118
+     * option.
119
+     * @private
120
+     * @returns {void}
121
+     */
122
+    _onDisableCallIntegration(newValue) {
123
+        this._updateSettings({
124
+            disableCallIntegration: newValue
125
+        });
126
+    }
127
+
128
+    _onDisableP2P: (boolean) => void;
129
+
130
+    /**
131
+     * Handles the disable P2P change event.
132
+     *
133
+     * @param {boolean} newValue - The new value
134
+     * option.
135
+     * @private
136
+     * @returns {void}
137
+     */
138
+    _onDisableP2P(newValue) {
139
+        this._updateSettings({
140
+            disableP2P: newValue
141
+        });
142
+    }
143
+
97
     _onRequestClose: () => void;
144
     _onRequestClose: () => void;
98
 
145
 
99
     /**
146
     /**
103
      * @returns {void}
150
      * @returns {void}
104
      */
151
      */
105
     _onRequestClose() {
152
     _onRequestClose() {
153
+        this.setState({ showAdvanced: false });
106
         this._processServerURL(true /* hideOnSuccess */);
154
         this._processServerURL(true /* hideOnSuccess */);
107
     }
155
     }
108
 
156
 
157
+    _onShowAdvanced: () => void;
158
+
159
+    /**
160
+     * Handles the advanced settings button.
161
+     *
162
+     * @returns {void}
163
+     */
164
+    _onShowAdvanced() {
165
+        this.setState({ showAdvanced: !this.state.showAdvanced });
166
+    }
167
+
109
     _onStartAudioMutedChange: (boolean) => void;
168
     _onStartAudioMutedChange: (boolean) => void;
110
 
169
 
111
     _onStartVideoMutedChange: (boolean) => void;
170
     _onStartVideoMutedChange: (boolean) => void;
133
         }
192
         }
134
     }
193
     }
135
 
194
 
195
+    /**
196
+     * Renders the advanced settings options.
197
+     *
198
+     * @private
199
+     * @returns {React$Element}
200
+     */
201
+    _renderAdvancedSettings() {
202
+        const { _settings } = this.props;
203
+        const { showAdvanced } = this.state;
204
+
205
+        if (!showAdvanced) {
206
+            return (
207
+                <FormRow
208
+                    fieldSeparator = { true }
209
+                    label = 'settingsView.showAdvanced'>
210
+                    <Switch
211
+                        onValueChange = { this._onShowAdvanced }
212
+                        value = { showAdvanced } />
213
+                </FormRow>
214
+            );
215
+        }
216
+
217
+        return (
218
+            <>
219
+                <FormRow
220
+                    fieldSeparator = { true }
221
+                    label = 'settingsView.disableCallIntegration'>
222
+                    <Switch
223
+                        onValueChange = { this._onDisableCallIntegration }
224
+                        value = { _settings.disableCallIntegration } />
225
+                </FormRow>
226
+                <FormRow
227
+                    fieldSeparator = { true }
228
+                    label = 'settingsView.disableP2P'>
229
+                    <Switch
230
+                        onValueChange = { this._onDisableP2P }
231
+                        value = { _settings.disableP2P } />
232
+                </FormRow>
233
+            </>
234
+        );
235
+    }
236
+
136
     /**
237
     /**
137
      * Renders the body (under the header) of {@code SettingsView}.
238
      * Renders the body (under the header) of {@code SettingsView}.
138
      *
239
      *
193
                     <FormSectionHeader
294
                     <FormSectionHeader
194
                         label = 'settingsView.buildInfoSection' />
295
                         label = 'settingsView.buildInfoSection' />
195
                     <FormRow
296
                     <FormRow
196
-                        fieldSeparator = { true }
197
                         label = 'settingsView.version'>
297
                         label = 'settingsView.version'>
198
                         <Text>
298
                         <Text>
199
                             { `${AppInfo.version} build ${AppInfo.buildNumber}` }
299
                             { `${AppInfo.version} build ${AppInfo.buildNumber}` }
200
                         </Text>
300
                         </Text>
201
                     </FormRow>
301
                     </FormRow>
302
+                    <FormSectionHeader
303
+                        label = 'settingsView.advanced' />
304
+                    { this._renderAdvancedSettings() }
202
                 </ScrollView>
305
                 </ScrollView>
203
             </SafeAreaView>
306
             </SafeAreaView>
204
         );
307
         );
252
             ]
355
             ]
253
         );
356
         );
254
     }
357
     }
358
+
359
+    _updateSettings: (Object) => void;
255
 }
360
 }
256
 
361
 
257
 /**
362
 /**

正在加载...
取消
保存