Bladeren bron

feat(prejoin) native prejoin screen and other navigation updates

* feat(prejoin) created native Prejoin screen

* feat(prejoin) fixed useState callback and updates warnings

* feat(prejoin) created styles file

* feat(prejoin) moved nav from middleware to appNavigate, created native DeviceStatus

* feat(prejoin) updated styles

* feat(prejoin) review remarks pt. 1

* feat(prejoin) removed unused styles

* feat(prejoin) review remarks pt. 2

* feat(prejoin) comment fix

* feat(prejoin) added header title

* feat(prejoin) review remarks

* feat(lobby) updated styles

* feat(prejoin) updated lobby screen header button functionality

* feat(prejoin) review remarks pt 3

* feat(welcome) removed VideoSwitch component

* feat(mobile/navigation) fixed linter

* feat(welcome) moved isWelcomePageEnabled to functions.ts

* feat(mobile/navigation) screen options and order updates

* feat(app) review remark

* feat(welcome) added translation for screen header title and fixed build

* feat(mobile/navigation) added screen title translation and created screen option

* feat(mobile/navigation) fixed screenOptions import

* feat(mobile/navigation) added DialInSummary title translation, fixed animation and close button

* feat(welcome) fixed build

* feat(welcome) removed extra check

* feat(prejoin) review remarks pt 4

* feat(prejoin) added Join in low bandwidth mode btn

* feat(welcome) changed welcome screen header title

* fixup lobby close
factor2
Calinteodor 3 jaren geleden
bovenliggende
commit
dbf7bf4750
No account linked to committer's email address

+ 5
- 0
lang/main.json Bestand weergeven

@@ -208,6 +208,9 @@
208 208
         "selectADevice": "Select a device",
209 209
         "testAudio": "Play a test sound"
210 210
     },
211
+    "dialIn": {
212
+        "screenTitle": "Dial-in summary"
213
+    },
211 214
     "dialOut": {
212 215
         "statusMessage": "is now {{status}}"
213 216
     },
@@ -815,6 +818,7 @@
815 818
         "initiated": "Call initiated",
816 819
         "joinAudioByPhone": "Join with phone audio",
817 820
         "joinMeeting": "Join meeting",
821
+        "joinMeetingInLowBandwidthMode": "Join in low bandwidth mode",
818 822
         "joinWithoutAudio": "Join without audio",
819 823
         "keyboardShortcuts": "Enable Keyboard shortcuts",
820 824
         "linkCopied": "Link copied to clipboard",
@@ -949,6 +953,7 @@
949 953
         "playSounds": "Play sound on",
950 954
         "reactions": "Meeting reactions",
951 955
         "sameAsSystem": "Same as system ({{label}})",
956
+        "screenTitle": "Settings",
952 957
         "selectAudioOutput": "Audio output",
953 958
         "selectCamera": "Camera",
954 959
         "selectMic": "Microphone",

+ 14
- 2
react/features/app/actions.native.js Bestand weergeven

@@ -19,7 +19,11 @@ import {
19 19
     parseURIString,
20 20
     toURLString
21 21
 } from '../base/util';
22
-import { navigateRoot } from '../mobile/navigation/rootNavigationContainerRef';
22
+import { isPrejoinPageEnabled } from '../mobile/navigation/functions';
23
+import {
24
+    goBackToRoot,
25
+    navigateRoot
26
+} from '../mobile/navigation/rootNavigationContainerRef';
23 27
 import { screen } from '../mobile/navigation/routes';
24 28
 import { setFatalError } from '../overlay';
25 29
 
@@ -128,7 +132,15 @@ export function appNavigate(uri: ?string) {
128 132
 
129 133
         if (room) {
130 134
             dispatch(createDesiredLocalTracks());
131
-            dispatch(connect());
135
+
136
+            if (isPrejoinPageEnabled(getState())) {
137
+                navigateRoot(screen.preJoin);
138
+            } else {
139
+                dispatch(connect());
140
+                navigateRoot(screen.conference.root);
141
+            }
142
+        } else {
143
+            goBackToRoot(getState(), dispatch);
132 144
         }
133 145
     };
134 146
 }

+ 3
- 2
react/features/app/getRouteToRender.web.js Bestand weergeven

@@ -7,7 +7,8 @@ import { toState } from '../base/redux';
7 7
 import { Conference } from '../conference';
8 8
 import { getDeepLinkingPage } from '../deep-linking';
9 9
 import { UnsupportedDesktopBrowser } from '../unsupported-browser';
10
-import { BlankPage, isWelcomePageUserEnabled, WelcomePage } from '../welcome';
10
+import { BlankPage, WelcomePage } from '../welcome';
11
+import { isWelcomePageEnabled } from '../welcome/functions';
11 12
 
12 13
 /**
13 14
  * Determines which route is to be rendered in order to depict a specific Redux
@@ -72,7 +73,7 @@ function _getWebConferenceRoute(state) {
72 73
 function _getWebWelcomePageRoute(state) {
73 74
     const route = _getEmptyRoute();
74 75
 
75
-    if (isWelcomePageUserEnabled(state)) {
76
+    if (isWelcomePageEnabled(state)) {
76 77
         if (isSupportedBrowser()) {
77 78
             route.component = WelcomePage;
78 79
         } else {

+ 6
- 0
react/features/base/flags/constants.js Bestand weergeven

@@ -160,6 +160,12 @@ export const OVERFLOW_MENU_ENABLED = 'overflow-menu.enabled';
160 160
  */
161 161
 export const PIP_ENABLED = 'pip.enabled';
162 162
 
163
+/**
164
+ * Flag indicating if the prejoin page should be enabled.
165
+ * Default: enabled (true).
166
+ */
167
+export const PREJOIN_PAGE_ENABLED = 'prejoinpage.enabled';
168
+
163 169
 /**
164 170
  * Flag indicating if raise hand feature should be enabled.
165 171
  * Default: enabled.

+ 3
- 24
react/features/invite/components/dial-in-summary/native/DialInSummary.js Bestand weergeven

@@ -1,23 +1,21 @@
1 1
 // @flow
2 2
 
3 3
 import React, { PureComponent } from 'react';
4
-import { Linking, Platform, View } from 'react-native';
4
+import { Linking, View } from 'react-native';
5 5
 import { WebView } from 'react-native-webview';
6 6
 import { type Dispatch } from 'redux';
7 7
 
8 8
 import { openDialog } from '../../../../base/dialog';
9 9
 import { translate } from '../../../../base/i18n';
10
-import { IconClose } from '../../../../base/icons';
11 10
 import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
12 11
 import { LoadingIndicator } from '../../../../base/react';
13 12
 import { connect } from '../../../../base/redux';
14
-import HeaderNavigationButton
15
-    from '../../../../mobile/navigation/components/HeaderNavigationButton';
16 13
 import { getDialInfoPageURLForURIString } from '../../../functions';
17 14
 
18 15
 import DialInSummaryErrorDialog from './DialInSummaryErrorDialog';
19 16
 import styles, { INDICATOR_COLOR } from './styles';
20 17
 
18
+
21 19
 type Props = {
22 20
 
23 21
     dispatch: Dispatch<any>,
@@ -65,28 +63,9 @@ class DialInSummary extends PureComponent<Props> {
65 63
      */
66 64
     componentDidMount() {
67 65
         const { navigation, t } = this.props;
68
-        const onNavigationClose = () => {
69
-            navigation.goBack();
70
-        };
71 66
 
72 67
         navigation.setOptions({
73
-            headerLeft: () => {
74
-                if (Platform.OS === 'ios') {
75
-                    return (
76
-                        <HeaderNavigationButton
77
-                            label = { t('dialog.close') }
78
-                            // eslint-disable-next-line react/jsx-no-bind
79
-                            onPress = { onNavigationClose } />
80
-                    );
81
-                }
82
-
83
-                return (
84
-                    <HeaderNavigationButton
85
-                        // eslint-disable-next-line react/jsx-no-bind
86
-                        onPress = { onNavigationClose }
87
-                        src = { IconClose } />
88
-                );
89
-            }
68
+            headerTitle: t('dialIn.screenTitle')
90 69
         });
91 70
     }
92 71
 

+ 6
- 8
react/features/lobby/components/native/styles.js Bestand weergeven

@@ -15,12 +15,10 @@ export default {
15 15
 
16 16
     buttonStylesBorderless: {
17 17
         iconStyle: {
18
-            backgroundColor: 'transparent',
19 18
             color: BaseTheme.palette.icon01,
20 19
             fontSize: 24
21 20
         },
22 21
         style: {
23
-            backgroundColor: 'transparent',
24 22
             flexDirection: 'row',
25 23
             justifyContent: 'center',
26 24
             marginHorizontal: BaseTheme.spacing[3],
@@ -70,25 +68,25 @@ export default {
70 68
     },
71 69
 
72 70
     largeVideoContainerWide: {
73
-        position: 'absolute',
74
-        marginRight: 'auto',
75 71
         height: '100%',
72
+        marginRight: 'auto',
73
+        position: 'absolute',
76 74
         width: '50%'
77 75
     },
78 76
 
79 77
     contentContainer: {
80
-        display: 'flex',
81 78
         alignItems: 'center',
79
+        display: 'flex',
82 80
         justifyContent: 'center',
83 81
         minHeight: '50%'
84 82
     },
85 83
 
86 84
     contentContainerWide: {
85
+        height: '100%',
87 86
         justifyContent: 'center',
87
+        left: '50%',
88 88
         marginHorizontal: BaseTheme.spacing[6],
89 89
         marginVertical: BaseTheme.spacing[3],
90
-        height: '100%',
91
-        left: '50%',
92 90
         position: 'absolute',
93 91
         width: '50%'
94 92
     },
@@ -124,7 +122,7 @@ export default {
124 122
 
125 123
     formWrapper: {
126 124
         alignSelf: 'stretch',
127
-        marginTop: 45
125
+        justifyContent: 'center'
128 126
     },
129 127
 
130 128
     field: {

+ 10
- 0
react/features/mobile/external-api/functions.js Bestand weergeven

@@ -1,8 +1,11 @@
1 1
 // @flow
2 2
 
3
+import debounce from 'lodash/debounce';
3 4
 import { NativeModules } from 'react-native';
4 5
 
5 6
 import { getAppProp } from '../../base/app';
7
+import { readyToClose } from '../external-api/actions';
8
+
6 9
 
7 10
 /**
8 11
  * Sends a specific event to the native counterpart of the External API. Native
@@ -24,3 +27,10 @@ export function sendEvent(store: Object, name: string, data: Object) {
24 27
     externalAPIScope
25 28
         && NativeModules.ExternalAPI.sendEvent(name, data, externalAPIScope);
26 29
 }
30
+
31
+/**
32
+ * Debounced sending of `readyToClose`.
33
+ */
34
+export const _sendReadyToClose = debounce(dispatch => {
35
+    dispatch(readyToClose());
36
+}, 2500, { leading: true });

+ 15
- 12
react/features/mobile/navigation/components/RootNavigationContainer.js Bestand weergeven

@@ -4,20 +4,25 @@ import React, { useCallback } from 'react';
4 4
 
5 5
 import { connect } from '../../../base/redux';
6 6
 import { DialInSummary } from '../../../invite';
7
+import Prejoin from '../../../prejoin/components/Prejoin.native';
8
+import { isWelcomePageEnabled } from '../../../welcome/functions';
7 9
 import { _ROOT_NAVIGATION_READY } from '../actionTypes';
8 10
 import { rootNavigationRef } from '../rootNavigationContainerRef';
9 11
 import { screen } from '../routes';
10 12
 import {
13
+    conferenceNavigationContainerScreenOptions,
14
+    connectingScreenOptions,
11 15
     dialInSummaryScreenOptions,
12 16
     drawerNavigatorScreenOptions,
13
-    navigationContainerTheme
17
+    navigationContainerTheme,
18
+    preJoinScreenOptions
14 19
 } from '../screenOptions';
15 20
 
16 21
 import ConnectingPage from './ConnectingPage';
17 22
 import ConferenceNavigationContainer
18 23
     from './conference/components/ConferenceNavigationContainer';
19
-import WelcomePageNavigationContainer from './welcome/components/WelcomePageNavigationContainer';
20
-import { isWelcomePageAppEnabled } from './welcome/functions';
24
+import WelcomePageNavigationContainer
25
+    from './welcome/components/WelcomePageNavigationContainer';
21 26
 
22 27
 const RootStack = createNativeStackNavigator();
23 28
 
@@ -70,17 +75,15 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
70 75
                 <RootStack.Screen
71 76
                     component = { ConnectingPage }
72 77
                     name = { screen.connecting }
73
-                    options = {{
74
-                        gestureEnabled: false,
75
-                        headerShown: false
76
-                    }} />
78
+                    options = { connectingScreenOptions } />
79
+                <RootStack.Screen
80
+                    component = { Prejoin }
81
+                    name = { screen.preJoin }
82
+                    options = { preJoinScreenOptions } />
77 83
                 <RootStack.Screen
78 84
                     component = { ConferenceNavigationContainer }
79 85
                     name = { screen.conference.root }
80
-                    options = {{
81
-                        gestureEnabled: false,
82
-                        headerShown: false
83
-                    }} />
86
+                    options = { conferenceNavigationContainerScreenOptions } />
84 87
             </RootStack.Navigator>
85 88
         </NavigationContainer>
86 89
     );
@@ -94,7 +97,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
94 97
  */
95 98
 function mapStateToProps(state: Object) {
96 99
     return {
97
-        isWelcomePageAvailable: isWelcomePageAppEnabled(state)
100
+        isWelcomePageAvailable: isWelcomePageEnabled(state)
98 101
     };
99 102
 }
100 103
 

+ 5
- 11
react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx Bestand weergeven

@@ -31,6 +31,7 @@ import {
31 31
     gifsMenuOptions,
32 32
     inviteScreenOptions,
33 33
     liveStreamScreenOptions,
34
+    lobbyNavigationContainerScreenOptions,
34 35
     navigationContainerTheme,
35 36
     participantsScreenOptions,
36 37
     recordingScreenOptions,
@@ -101,15 +102,11 @@ const ConferenceNavigationContainer = () => {
101 102
                 <ConferenceStack.Screen
102 103
                     component = { StartRecordingDialog }
103 104
                     name = { screen.conference.recording }
104
-                    options = {{
105
-                        ...recordingScreenOptions
106
-                    }} />
105
+                    options = { recordingScreenOptions } />
107 106
                 <ConferenceStack.Screen
108 107
                     component = { StartLiveStreamDialog }
109 108
                     name = { screen.conference.liveStream }
110
-                    options = {{
111
-                        ...liveStreamScreenOptions
112
-                    }} />
109
+                    options = { liveStreamScreenOptions } />
113 110
                 <ConferenceStack.Screen
114 111
                     component = { SpeakerStats }
115 112
                     name = { screen.conference.speakerStats }
@@ -134,10 +131,7 @@ const ConferenceNavigationContainer = () => {
134 131
                 <ConferenceStack.Screen
135 132
                     component = { LobbyNavigationContainer }
136 133
                     name = { screen.lobby.root }
137
-                    options = {{
138
-                        gestureEnabled: false,
139
-                        headerShown: false
140
-                    }} />
134
+                    options = { lobbyNavigationContainerScreenOptions } />
141 135
                 <ConferenceStack.Screen
142 136
                     component = { AddPeopleDialog }
143 137
                     name = { screen.conference.invite }
@@ -158,7 +152,7 @@ const ConferenceNavigationContainer = () => {
158 152
                     options = {{
159 153
                         ...carmodeScreenOptions,
160 154
                         title: t('carmode.labels.title')
161
-                    }}/>
155
+                    }} />
162 156
             </ConferenceStack.Navigator>
163 157
         </NavigationContainer>
164 158
     );

+ 0
- 14
react/features/mobile/navigation/components/welcome/functions.js Bestand weergeven

@@ -2,25 +2,11 @@
2 2
 
3 3
 import React from 'react';
4 4
 
5
-import { getFeatureFlag, WELCOME_PAGE_ENABLED } from '../../../../base/flags';
6 5
 import { IconArrowBack } from '../../../../base/icons';
7 6
 import HeaderNavigationButton
8 7
     from '../HeaderNavigationButton';
9 8
 import { navigationStyles } from '../styles';
10 9
 
11
-/**
12
- * Determines whether the {@code WelcomePage} is enabled by the app itself
13
- * (e.g. Programmatically via the Jitsi Meet SDK for Android and iOS). Not to be
14
- * confused with {@link isWelcomePageUserEnabled}.
15
- *
16
- * @param {Function|Object} stateful - The redux state or {@link getState}
17
- * function.
18
- * @returns {boolean} If the {@code WelcomePage} is enabled by the app, then
19
- * {@code true}; otherwise, {@code false}.
20
- */
21
-export function isWelcomePageAppEnabled(stateful: Function | Object) {
22
-    return Boolean(getFeatureFlag(stateful, WELCOME_PAGE_ENABLED));
23
-}
24 10
 
25 11
 /**
26 12
  * Render header arrow back button for navigation.

+ 50
- 1
react/features/mobile/navigation/functions.js Bestand weergeven

@@ -1,11 +1,19 @@
1
-import React from 'react';
1
+import React, { useCallback } from 'react';
2 2
 import { useTranslation } from 'react-i18next';
3 3
 import { Platform } from 'react-native';
4
+import { useDispatch } from 'react-redux';
4 5
 
6
+import { appNavigate } from '../../app/actions';
7
+import {
8
+    getFeatureFlag,
9
+    PREJOIN_PAGE_ENABLED
10
+} from '../../base/flags';
5 11
 import { IconClose } from '../../base/icons';
12
+import { cancelKnocking } from '../../lobby/actions.native';
6 13
 
7 14
 import HeaderNavigationButton from './components/HeaderNavigationButton';
8 15
 
16
+
9 17
 /**
10 18
  * Close icon/text button based on platform.
11 19
  *
@@ -29,3 +37,44 @@ export function screenHeaderCloseButton(goBack: Function) {
29 37
             src = { IconClose } />
30 38
     );
31 39
 }
40
+
41
+/**
42
+ * Determines whether the {@code Prejoin page} is enabled by the app itself
43
+ * (e.g. Programmatically via the Jitsi Meet SDK for Android and iOS).
44
+ *
45
+ * @param {Function|Object} stateful - The redux state or {@link getState}
46
+ * function.
47
+ * @returns {boolean} If the {@code Prejoin} is enabled by the app, then
48
+ * {@code true}; otherwise, {@code false}.
49
+ */
50
+export function isPrejoinPageEnabled(stateful: Function | Object) {
51
+    return getFeatureFlag(stateful, PREJOIN_PAGE_ENABLED, true);
52
+}
53
+
54
+/**
55
+ * Close icon/text button for lobby screen based on platform.
56
+ *
57
+ * @returns {React.Component}
58
+ */
59
+export function lobbyScreenHeaderCloseButton() {
60
+    const dispatch = useDispatch();
61
+    const { t } = useTranslation();
62
+    const goBack = useCallback(() => {
63
+        dispatch(cancelKnocking());
64
+        dispatch(appNavigate(undefined));
65
+    }, [ dispatch ]);
66
+
67
+    if (Platform.OS === 'ios') {
68
+        return (
69
+            <HeaderNavigationButton
70
+                label = { t('dialog.close') }
71
+                onPress = { goBack } />
72
+        );
73
+    }
74
+
75
+    return (
76
+        <HeaderNavigationButton
77
+            onPress = { goBack }
78
+            src = { IconClose } />
79
+    );
80
+}

+ 4
- 61
react/features/mobile/navigation/middleware.js Bestand weergeven

@@ -1,20 +1,11 @@
1
-import debounce from 'lodash/debounce';
2
-
3
-import { CONFERENCE_FAILED, SET_ROOM } from '../../base/conference/actionTypes';
1
+import { appNavigate } from '../../app/actions';
2
+import { CONFERENCE_FAILED } from '../../base/conference/actionTypes';
4 3
 import { JitsiConferenceErrors } from '../../base/lib-jitsi-meet';
5 4
 import { MiddlewareRegistry } from '../../base/redux';
6
-import { readyToClose } from '../external-api/actions';
7
-
8
-
9
-import { isWelcomePageAppEnabled } from './components/welcome/functions';
10
-import { navigateRoot } from './rootNavigationContainerRef';
11
-import { screen } from './routes';
12 5
 
13 6
 
14 7
 MiddlewareRegistry.register(store => next => action => {
15 8
     switch (action.type) {
16
-    case SET_ROOM:
17
-        return _setRoom(store, next, action);
18 9
 
19 10
     case CONFERENCE_FAILED:
20 11
         return _conferenceFailed(store, next, action);
@@ -23,47 +14,6 @@ MiddlewareRegistry.register(store => next => action => {
23 14
     return next(action);
24 15
 });
25 16
 
26
-/**
27
- * Debounced sending of `readyToClose`.
28
- */
29
-const _sendReadyToClose = debounce(dispatch => {
30
-    dispatch(readyToClose());
31
-}, 2500, { leading: true });
32
-
33
-/**
34
- * Notifies the feature base/conference that the action
35
- * {@code SET_ROOM} is being dispatched within a specific
36
- *  redux store.
37
- *
38
- * @param {Store} store - The redux store in which the specified {@code action}
39
- * is being dispatched.
40
- * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
41
- * specified {@code action} to the specified {@code store}.
42
- * @param {Action} action - The redux action {@code SET_ROOM}
43
- * which is being dispatched in the specified {@code store}.
44
- * @private
45
- * @returns {Object} The value returned by {@code next(action)}.
46
- */
47
-function _setRoom({ dispatch, getState }, next, action) {
48
-    const { room: oldRoom } = getState()['features/base/conference'];
49
-    const result = next(action);
50
-    const { room: newRoom } = getState()['features/base/conference'];
51
-    const isWelcomePageEnabled = isWelcomePageAppEnabled(getState());
52
-
53
-    if (!oldRoom && newRoom) {
54
-        navigateRoot(screen.conference.root);
55
-    } else if (!newRoom) {
56
-        if (isWelcomePageEnabled) {
57
-            navigateRoot(screen.root);
58
-        } else {
59
-            // For JitsiSDK, WelcomePage is not available
60
-            _sendReadyToClose(dispatch);
61
-        }
62
-    }
63
-
64
-    return result;
65
-}
66
-
67 17
 /**
68 18
  * Function to handle the conference failed event and navigate the user to the lobby screen
69 19
  * based on the failure reason.
@@ -73,20 +23,13 @@ function _setRoom({ dispatch, getState }, next, action) {
73 23
  * @param {Object} action - The Redux action.
74 24
  * @returns {Object}
75 25
  */
76
-function _conferenceFailed({ dispatch, getState }, next, action) {
77
-    const state = getState();
78
-    const isWelcomePageEnabled = isWelcomePageAppEnabled(state);
26
+function _conferenceFailed({ dispatch }, next, action) {
79 27
     const { error } = action;
80 28
 
81 29
     // We need to cover the case where knocking participant
82 30
     // is rejected from entering the conference
83 31
     if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
84
-        if (isWelcomePageEnabled) {
85
-            navigateRoot(screen.root);
86
-        } else {
87
-            // For JitsiSDK, WelcomePage is not available
88
-            _sendReadyToClose(dispatch);
89
-        }
32
+        dispatch(appNavigate(undefined));
90 33
     }
91 34
 
92 35
     return next(action);

+ 33
- 4
react/features/mobile/navigation/rootNavigationContainerRef.js Bestand weergeven

@@ -1,10 +1,14 @@
1
-// @flow
2
-
3 1
 import React from 'react';
4 2
 
5
-// $FlowExpectedError
3
+import { toState } from '../../base/redux';
4
+import { isWelcomePageEnabled } from '../../welcome/functions';
5
+import { _sendReadyToClose } from '../external-api/functions';
6
+
7
+import { screen } from './routes';
8
+
6 9
 export const rootNavigationRef = React.createRef();
7 10
 
11
+
8 12
 /**
9 13
  * User defined navigation action included inside the reference to the container.
10 14
  *
@@ -13,7 +17,32 @@ export const rootNavigationRef = React.createRef();
13 17
  * @returns {Function}
14 18
  */
15 19
 export function navigateRoot(name: string, params: Object) {
16
-    // $FlowExpectedError
17 20
     return rootNavigationRef.current?.navigate(name, params);
18 21
 }
19 22
 
23
+/**
24
+ * User defined navigation action included inside the reference to the container.
25
+ *
26
+ * @returns {Function}
27
+ */
28
+export function goBack() {
29
+    return rootNavigationRef.current?.goBack();
30
+}
31
+
32
+/**
33
+ * Navigates back to Welcome page, if it's available.
34
+ *
35
+ * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
36
+ * @param {Function} dispatch - Redux dispatch function.
37
+ * @returns {void}
38
+ */
39
+export function goBackToRoot(stateful: Function | Object, dispatch: Function) {
40
+    const state = toState(stateful);
41
+
42
+    if (isWelcomePageEnabled(state)) {
43
+        navigateRoot(screen.root);
44
+    } else {
45
+        // For JitsiSDK, WelcomePage is not available
46
+        _sendReadyToClose(dispatch);
47
+    }
48
+}

+ 1
- 0
react/features/mobile/navigation/routes.js Bestand weergeven

@@ -13,6 +13,7 @@ export const screen = {
13 13
     },
14 14
     dialInSummary: 'Dial-In Info',
15 15
     connecting: 'Connecting',
16
+    preJoin: 'Pre-Join',
16 17
     conference: {
17 18
         root: 'Conference root',
18 19
         main: 'Conference',

+ 46
- 6
react/features/mobile/navigation/screenOptions.js Bestand weergeven

@@ -12,7 +12,8 @@ import BaseTheme from '../../base/ui/components/BaseTheme.native';
12 12
 
13 13
 import { goBack } from './components/conference/ConferenceNavigationContainerRef';
14 14
 import { goBack as goBackToLobbyScreen } from './components/lobby/LobbyNavigationContainerRef';
15
-import { screenHeaderCloseButton } from './functions';
15
+import { lobbyScreenHeaderCloseButton, screenHeaderCloseButton } from './functions';
16
+import { goBack as goBackToWelcomeScreen } from './rootNavigationContainerRef';
16 17
 
17 18
 
18 19
 /**
@@ -81,8 +82,9 @@ export const welcomeScreenOptions = {
81 82
     headerStyle: {
82 83
         backgroundColor: BaseTheme.palette.screen01Header
83 84
     },
84
-    // eslint-disable-next-line no-empty-function
85
-    headerTitle: () => {}
85
+    headerTitleStyle: {
86
+        color: BaseTheme.palette.text01
87
+    }
86 88
 };
87 89
 
88 90
 /**
@@ -164,7 +166,6 @@ export const chatTabBarOptions = {
164 166
  * Screen options for presentation type modals.
165 167
  */
166 168
 export const presentationScreenOptions = {
167
-    animation: 'slide_from_right',
168 169
     headerBackTitleVisible: false,
169 170
     headerLeft: () => screenHeaderCloseButton(goBack),
170 171
     headerStatusBarHeight: 0,
@@ -195,7 +196,8 @@ export const chatScreenOptions = presentationScreenOptions;
195 196
  */
196 197
 export const dialInSummaryScreenOptions = {
197 198
     ...presentationScreenOptions,
198
-    headerLeft: undefined
199
+    animation: 'slide_from_bottom',
200
+    headerLeft: () => screenHeaderCloseButton(goBackToWelcomeScreen)
199 201
 };
200 202
 
201 203
 /**
@@ -231,7 +233,10 @@ export const liveStreamScreenOptions = presentationScreenOptions;
231 233
 /**
232 234
  * Screen options for lobby modal.
233 235
  */
234
-export const lobbyScreenOptions = presentationScreenOptions;
236
+export const lobbyScreenOptions = {
237
+    ...presentationScreenOptions,
238
+    headerLeft: () => lobbyScreenHeaderCloseButton()
239
+};
235 240
 
236 241
 /**
237 242
  * Screen options for lobby chat modal.
@@ -269,3 +274,38 @@ export const sharedDocumentScreenOptions = {
269 274
         android: 'all'
270 275
     })
271 276
 };
277
+
278
+/**
279
+ * Screen options for connecting screen.
280
+ */
281
+export const connectingScreenOptions = {
282
+    gestureEnabled: false,
283
+    headerShown: false
284
+};
285
+
286
+/**
287
+ * Screen options for pre-join screen.
288
+ */
289
+export const preJoinScreenOptions = {
290
+    gestureEnabled: false,
291
+    headerStyle: {
292
+        backgroundColor: BaseTheme.palette.screen02Header
293
+    },
294
+    headerTitle: ''
295
+};
296
+
297
+/**
298
+ * Screen options for conference navigation container screen.
299
+ */
300
+export const conferenceNavigationContainerScreenOptions = {
301
+    gestureEnabled: false,
302
+    headerShown: false
303
+};
304
+
305
+/**
306
+ * Screen options for lobby navigation container screen.
307
+ */
308
+export const lobbyNavigationContainerScreenOptions = {
309
+    gestureEnabled: false,
310
+    headerShown: false
311
+};

+ 147
- 0
react/features/prejoin/components/Prejoin.native.tsx Bestand weergeven

@@ -0,0 +1,147 @@
1
+import React, { useCallback, useLayoutEffect, useState } from 'react';
2
+import { useTranslation } from 'react-i18next';
3
+import { Text, View, TouchableOpacity, TextInput, Platform } from 'react-native';
4
+import { useDispatch, useSelector } from 'react-redux';
5
+
6
+import { appNavigate } from '../../app/actions.native';
7
+import { setAudioOnly } from '../../base/audio-only/actions';
8
+import { connect } from '../../base/connection/actions.native';
9
+import { IconClose } from '../../base/icons';
10
+import JitsiScreen from '../../base/modal/components/JitsiScreen';
11
+import { getLocalParticipant } from '../../base/participants';
12
+import { getFieldValue } from '../../base/react';
13
+import { ASPECT_RATIO_NARROW } from '../../base/responsive-ui';
14
+import { updateSettings } from '../../base/settings';
15
+import { LargeVideo } from '../../large-video/components';
16
+import HeaderNavigationButton from '../../mobile/navigation/components/HeaderNavigationButton';
17
+import { navigateRoot } from '../../mobile/navigation/rootNavigationContainerRef';
18
+import { screen } from '../../mobile/navigation/routes';
19
+import AudioMuteButton from '../../toolbox/components/AudioMuteButton';
20
+import VideoMuteButton from '../../toolbox/components/VideoMuteButton';
21
+
22
+import styles from './styles';
23
+
24
+
25
+interface Props {
26
+    navigation: any;
27
+}
28
+
29
+const Prejoin: ({ navigation }: Props) => JSX.Element = ({ navigation }: Props) => {
30
+    const dispatch = useDispatch();
31
+    const { t } = useTranslation();
32
+    const aspectRatio = useSelector(
33
+        (state: any) => state['features/base/responsive-ui']?.aspectRatio
34
+    );
35
+    const localParticipant = useSelector(state => getLocalParticipant(state));
36
+    const participantName = localParticipant?.name;
37
+    const [ displayName, setDisplayName ]
38
+        = useState(participantName || '');
39
+    const onChangeDisplayName = useCallback(event => {
40
+        const fieldValue = getFieldValue(event);
41
+
42
+        setDisplayName(fieldValue);
43
+        dispatch(updateSettings({
44
+            displayName: fieldValue
45
+        }));
46
+    }, [ displayName ]);
47
+
48
+    const onJoin = useCallback(() => {
49
+        dispatch(connect());
50
+        navigateRoot(screen.conference.root);
51
+    }, [ dispatch ]);
52
+
53
+    const onJoinLowBandwidth = useCallback(() => {
54
+        dispatch(setAudioOnly(true));
55
+        onJoin();
56
+    }, [ dispatch ]);
57
+
58
+    const goBack = useCallback(() => {
59
+        dispatch(appNavigate(undefined));
60
+    }, [ dispatch ]);
61
+
62
+    let contentWrapperStyles;
63
+    let contentContainerStyles;
64
+    let largeVideoContainerStyles;
65
+    let toolboxContainerStyles;
66
+
67
+    if (aspectRatio === ASPECT_RATIO_NARROW) {
68
+        contentWrapperStyles = styles.contentWrapper;
69
+        contentContainerStyles = styles.contentContainer;
70
+        largeVideoContainerStyles = styles.largeVideoContainer;
71
+        toolboxContainerStyles = styles.toolboxContainer;
72
+    } else {
73
+        contentWrapperStyles = styles.contentWrapperWide;
74
+        contentContainerStyles = styles.contentContainerWide;
75
+        largeVideoContainerStyles = styles.largeVideoContainerWide;
76
+        toolboxContainerStyles = styles.toolboxContainerWide;
77
+    }
78
+
79
+    const headerLeft = useCallback(() => {
80
+        if (Platform.OS === 'ios') {
81
+            return (
82
+                <HeaderNavigationButton
83
+                    label = { t('dialog.close') }
84
+                    onPress = { goBack } />
85
+            );
86
+        }
87
+
88
+        return (
89
+            <HeaderNavigationButton
90
+                onPress = { goBack }
91
+                src = { IconClose } />
92
+        );
93
+    }, []);
94
+
95
+    useLayoutEffect(() => {
96
+        navigation.setOptions({
97
+            headerLeft
98
+        });
99
+    }, [ navigation ]);
100
+
101
+    return (
102
+        <JitsiScreen
103
+            safeAreaInsets = { [ 'right' ] }
104
+            style = { contentWrapperStyles }>
105
+            <View style = { largeVideoContainerStyles }>
106
+                <LargeVideo />
107
+            </View>
108
+            <View style = { contentContainerStyles }>
109
+                <View style = { styles.formWrapper }>
110
+                    <TextInput
111
+                        onChangeText = { onChangeDisplayName }
112
+                        placeholder = { t('dialog.enterDisplayName') }
113
+                        style = { styles.field }
114
+                        value = { displayName } />
115
+                    <TouchableOpacity
116
+                        onPress = { onJoin }
117
+                        style = { [
118
+                            styles.button,
119
+                            styles.primaryButton
120
+                        ] }>
121
+                        <Text style = { styles.primaryButtonText }>
122
+                            { t('prejoin.joinMeeting') }
123
+                        </Text>
124
+                    </TouchableOpacity>
125
+                    <TouchableOpacity
126
+                        onPress = { onJoinLowBandwidth }
127
+                        style = { [
128
+                            styles.button,
129
+                            styles.secondaryButton
130
+                        ] }>
131
+                        <Text style = { styles.secondaryButtonText }>
132
+                            { t('prejoin.joinMeetingInLowBandwidthMode') }
133
+                        </Text>
134
+                    </TouchableOpacity>
135
+                </View>
136
+                <View style = { toolboxContainerStyles }>
137
+                    <AudioMuteButton
138
+                        styles = { styles.buttonStylesBorderless } />
139
+                    <VideoMuteButton
140
+                        styles = { styles.buttonStylesBorderless } />
141
+                </View>
142
+            </View>
143
+        </JitsiScreen>
144
+    );
145
+};
146
+
147
+export default Prejoin;

+ 121
- 0
react/features/prejoin/components/styles.js Bestand weergeven

@@ -0,0 +1,121 @@
1
+import BaseTheme from '../../base/ui/components/BaseTheme.native';
2
+
3
+const SECONDARY_COLOR = BaseTheme.palette.border04;
4
+const btn = {
5
+    marginTop: BaseTheme.spacing[4]
6
+};
7
+const btnText = {
8
+    ...BaseTheme.typography.labelButtonLarge,
9
+    color: BaseTheme.palette.text01,
10
+    lineHeight: 30
11
+};
12
+
13
+export default {
14
+    button: {
15
+        alignItems: 'center',
16
+        borderRadius: BaseTheme.shape.borderRadius,
17
+        padding: BaseTheme.spacing[2],
18
+        height: BaseTheme.spacing[7]
19
+    },
20
+
21
+    primaryButton: {
22
+        ...btn,
23
+        backgroundColor: BaseTheme.palette.action01
24
+    },
25
+
26
+
27
+    primaryButtonText: {
28
+        ...btnText
29
+    },
30
+
31
+    secondaryButton: {
32
+        ...btn,
33
+        backgroundColor: BaseTheme.palette.action02
34
+    },
35
+
36
+
37
+    secondaryButtonText: {
38
+        ...btnText
39
+    },
40
+
41
+    buttonStylesBorderless: {
42
+        iconStyle: {
43
+            color: BaseTheme.palette.icon01,
44
+            fontSize: 24
45
+        },
46
+        style: {
47
+            flexDirection: 'row',
48
+            justifyContent: 'center',
49
+            marginHorizontal: BaseTheme.spacing[3],
50
+            height: 24,
51
+            width: 24
52
+        }
53
+    },
54
+
55
+    contentWrapper: {
56
+        flex: 1
57
+    },
58
+
59
+    contentWrapperWide: {
60
+        flex: 1,
61
+        flexDirection: 'row'
62
+    },
63
+
64
+    largeVideoContainer: {
65
+        minHeight: '50%'
66
+    },
67
+
68
+    largeVideoContainerWide: {
69
+        height: '100%',
70
+        marginRight: 'auto',
71
+        position: 'absolute',
72
+        width: '50%'
73
+    },
74
+
75
+    contentContainer: {
76
+        alignItems: 'center',
77
+        display: 'flex',
78
+        justifyContent: 'center',
79
+        minHeight: '50%'
80
+    },
81
+
82
+    contentContainerWide: {
83
+        height: '100%',
84
+        justifyContent: 'center',
85
+        left: '50%',
86
+        marginHorizontal: BaseTheme.spacing[6],
87
+        marginVertical: BaseTheme.spacing[3],
88
+        position: 'absolute',
89
+        width: '50%'
90
+    },
91
+
92
+    toolboxContainer: {
93
+        alignSelf: 'center',
94
+        display: 'flex',
95
+        flexDirection: 'row',
96
+        justifyContent: 'center',
97
+        marginTop: BaseTheme.spacing[4]
98
+    },
99
+
100
+    toolboxContainerWide: {
101
+        flexDirection: 'row',
102
+        justifyContent: 'center',
103
+        marginTop: BaseTheme.spacing[4]
104
+    },
105
+
106
+    formWrapper: {
107
+        alignSelf: 'stretch',
108
+        justifyContent: 'center',
109
+        marginHorizontal: BaseTheme.spacing[3]
110
+    },
111
+
112
+    field: {
113
+        backgroundColor: BaseTheme.palette.field02,
114
+        borderColor: SECONDARY_COLOR,
115
+        borderRadius: BaseTheme.shape.borderRadius,
116
+        borderWidth: 2,
117
+        height: BaseTheme.spacing[7],
118
+        marginTop: BaseTheme.spacing[2],
119
+        textAlign: 'center'
120
+    }
121
+};

+ 0
- 145
react/features/welcome/components/VideoSwitch.js Bestand weergeven

@@ -1,145 +0,0 @@
1
-// @flow
2
-
3
-import React, { Component } from 'react';
4
-import { Switch, TouchableWithoutFeedback, View } from 'react-native';
5
-
6
-import { ColorSchemeRegistry } from '../../base/color-scheme';
7
-import { translate } from '../../base/i18n';
8
-import { Text } from '../../base/react';
9
-import { connect } from '../../base/redux';
10
-import { updateSettings } from '../../base/settings';
11
-
12
-import styles, { SWITCH_THUMB_COLOR, SWITCH_UNDER_COLOR } from './styles';
13
-
14
-/**
15
- * The type of the React {@code Component} props of {@link VideoSwitch}.
16
- */
17
-type Props = {
18
-
19
-    /**
20
-     * The redux {@code dispatch} function.
21
-     */
22
-    dispatch: Function,
23
-
24
-    /**
25
-     * The i18n translate function.
26
-     */
27
-    t: Function,
28
-
29
-    /**
30
-     * Color schemed style of the header component.
31
-     */
32
-    _headerStyles: Object,
33
-
34
-    /**
35
-     * The current settings from redux.
36
-     */
37
-    _settings: Object
38
-};
39
-
40
-/**
41
- * Renders the "Video <-> Voice" switch on the {@code WelcomePage}.
42
- */
43
-class VideoSwitch extends Component<Props> {
44
-    /**
45
-     * Initializes a new {@code VideoSwitch} instance.
46
-     *
47
-     * @inheritdoc
48
-     */
49
-    constructor(props) {
50
-        super(props);
51
-
52
-        // Bind event handlers so they are only bound once per instance.
53
-        this._onStartAudioOnlyChange = this._onStartAudioOnlyChange.bind(this);
54
-        this._onStartAudioOnlyFalse = this._onStartAudioOnlyChangeFn(false);
55
-        this._onStartAudioOnlyTrue = this._onStartAudioOnlyChangeFn(true);
56
-    }
57
-
58
-    /**
59
-     * Implements React's {@link Component#render}.
60
-     *
61
-     * @inheritdoc
62
-     */
63
-    render() {
64
-        const { t, _headerStyles, _settings } = this.props;
65
-
66
-        return (
67
-            <View style = { styles.audioVideoSwitchContainer }>
68
-                <TouchableWithoutFeedback
69
-                    onPress = { this._onStartAudioOnlyFalse }>
70
-                    <View style = { styles.switchLabel }>
71
-                        <Text style = { _headerStyles.headerText }>
72
-                            { t('welcomepage.audioVideoSwitch.video') }
73
-                        </Text>
74
-                    </View>
75
-                </TouchableWithoutFeedback>
76
-                <Switch
77
-                    onValueChange = { this._onStartAudioOnlyChange }
78
-                    style = { styles.audioVideoSwitch }
79
-                    thumbColor = { SWITCH_THUMB_COLOR }
80
-                    trackColor = {{ true: SWITCH_UNDER_COLOR }}
81
-                    value = { _settings.startAudioOnly } />
82
-                <TouchableWithoutFeedback
83
-                    onPress = { this._onStartAudioOnlyTrue }>
84
-                    <View style = { styles.switchLabel }>
85
-                        <Text style = { _headerStyles.headerText }>
86
-                            { t('welcomepage.audioVideoSwitch.audio') }
87
-                        </Text>
88
-                    </View>
89
-                </TouchableWithoutFeedback>
90
-            </View>
91
-        );
92
-    }
93
-
94
-    _onStartAudioOnlyChange: boolean => void;
95
-
96
-    /**
97
-     * Handles the audio-video switch changes.
98
-     *
99
-     * @private
100
-     * @param {boolean} startAudioOnly - The new startAudioOnly value.
101
-     * @returns {void}
102
-     */
103
-    _onStartAudioOnlyChange(startAudioOnly) {
104
-        const { dispatch } = this.props;
105
-
106
-        dispatch(updateSettings({
107
-            startAudioOnly
108
-        }));
109
-    }
110
-
111
-    /**
112
-     * Creates a function that forwards the {@code startAudioOnly} changes to
113
-     * the function that handles it.
114
-     *
115
-     * @private
116
-     * @param {boolean} startAudioOnly - The new {@code startAudioOnly} value.
117
-     * @returns {void}
118
-     */
119
-    _onStartAudioOnlyChangeFn(startAudioOnly) {
120
-        return () => this._onStartAudioOnlyChange(startAudioOnly);
121
-    }
122
-
123
-    _onStartAudioOnlyFalse: () => void;
124
-
125
-    _onStartAudioOnlyTrue: () => void;
126
-}
127
-
128
-/**
129
- * Maps (parts of) the redux state to the React {@code Component} props of
130
- * {@code VideoSwitch}.
131
- *
132
- * @param {Object} state - The redux state.
133
- * @protected
134
- * @returns {{
135
- *     _settings: Object
136
- * }}
137
- */
138
-export function _mapStateToProps(state: Object) {
139
-    return {
140
-        _headerStyles: ColorSchemeRegistry.get(state, 'Header'),
141
-        _settings: state['features/base/settings']
142
-    };
143
-}
144
-
145
-export default translate(connect(_mapStateToProps)(VideoSwitch));

+ 3
- 5
react/features/welcome/components/WelcomePage.native.js Bestand weergeven

@@ -27,7 +27,6 @@ import {
27 27
     _mapStateToProps as _abstractMapStateToProps,
28 28
     type Props as AbstractProps
29 29
 } from './AbstractWelcomePage';
30
-import VideoSwitch from './VideoSwitch';
31 30
 import styles, { PLACEHOLDER_TEXT_COLOR } from './styles';
32 31
 
33 32
 
@@ -101,7 +100,8 @@ class WelcomePage extends AbstractWelcomePage<*> {
101 100
 
102 101
         const {
103 102
             _headerStyles,
104
-            navigation
103
+            navigation,
104
+            t
105 105
         } = this.props;
106 106
 
107 107
         navigation.setOptions({
@@ -118,9 +118,7 @@ class WelcomePage extends AbstractWelcomePage<*> {
118 118
                         style = { _headerStyles.headerButtonIcon } />
119 119
                 </TouchableOpacity>
120 120
             ),
121
-            // eslint-disable-next-line react/no-multi-comp
122
-            headerRight: () =>
123
-                <VideoSwitch />
121
+            headerTitle: t('welcomepage.headerTitle')
124 122
         });
125 123
 
126 124
         navigation.addListener('focus', () => {

+ 4
- 2
react/features/welcome/components/native/settings/components/SettingsView.js Bestand weergeven

@@ -159,13 +159,15 @@ class SettingsView extends AbstractSettingsView<Props, State> {
159 159
      */
160 160
     componentDidMount() {
161 161
         const {
162
-            navigation
162
+            navigation,
163
+            t
163 164
         } = this.props;
164 165
 
165 166
         navigation.setOptions({
166 167
             headerLeft: () =>
167 168
                 renderArrowBackButton(() =>
168
-                    navigation.navigate(screen.welcome.main))
169
+                    navigation.navigate(screen.welcome.main)),
170
+            headerTitle: t('settings.screenTitle')
169 171
         });
170 172
     }
171 173
 

+ 0
- 16
react/features/welcome/components/styles.js Bestand weergeven

@@ -26,22 +26,6 @@ const TEXT_COLOR = BaseTheme.palette.text01;
26 26
  */
27 27
 export default {
28 28
 
29
-    /**
30
-     * The audio-video switch itself.
31
-     */
32
-    audioVideoSwitch: {
33
-        marginHorizontal: 5
34
-    },
35
-
36
-    /**
37
-     * View that contains the audio-video switch and the labels.
38
-     */
39
-    audioVideoSwitchContainer: {
40
-        alignItems: 'center',
41
-        flexDirection: 'row',
42
-        marginRight: BaseTheme.spacing[3]
43
-    },
44
-
45 29
     blankPageText: {
46 30
         color: TEXT_COLOR,
47 31
         fontSize: 18

+ 11
- 15
react/features/welcome/functions.js Bestand weergeven

@@ -1,24 +1,20 @@
1
-// @flow
1
+import { WELCOME_PAGE_ENABLED } from '../base/flags/constants';
2
+import { getFeatureFlag } from '../base/flags/functions';
3
+import { toState } from '../base/redux/functions';
2 4
 
3
-import { toState } from '../base/redux';
4
-
5
-declare var APP: Object;
6 5
 
7 6
 /**
8
- * Determines whether the {@code WelcomePage} is enabled by the user either
9
- * herself or through her deployment config(uration). Not to be confused with
10
- * {@link isWelcomePageAppEnabled}.
7
+ * Determines whether the {@code WelcomePage} is enabled.
11 8
  *
12 9
  * @param {Function|Object} stateful - The redux state or {@link getState}
13 10
  * function.
14
- * @returns {boolean} If the {@code WelcomePage} is enabled by the user, then
11
+ * @returns {boolean} If the {@code WelcomePage} is enabled by the app, then
15 12
  * {@code true}; otherwise, {@code false}.
16 13
  */
17
-export function isWelcomePageUserEnabled(stateful: Function | Object) {
18
-    return (
19
-        typeof APP === 'undefined'
20
-            ? true
21
-            : toState(stateful)['features/base/config'].enableWelcomePage);
22
-}
23
-
14
+export function isWelcomePageEnabled(stateful: Function | Object) {
15
+    if (navigator.product === 'ReactNative') {
16
+        return getFeatureFlag(stateful, WELCOME_PAGE_ENABLED, false);
17
+    }
24 18
 
19
+    return toState(stateful)['features/base/config'].enableWelcomePage;
20
+}

+ 0
- 1
react/features/welcome/index.js Bestand weergeven

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

Laden…
Annuleren
Opslaan