瀏覽代碼

Deeplinking (#2760)

* feat(Deeplinking): Implement for web.

* ref(unsupported_browser): Move the mobile version to deeplinking feature

* feat(deeplinking_mobile): Redesign.

* fix(deeplinking): Use interface.NATIVE_APP_NAME.

* feat(dial_in_summary): Add the PIN to the number link.

* fix(deep_linking): Handle use case when there isn't deep linking image.

* fix(deep_linking): css

* fix(deep_linking): deeplink -> "deep linking"

* fix(deeplinking_css): Remove position: fixed

* docs(deeplinking): Add comment for the openWebApp action.
j8
hristoterezov 7 年之前
父節點
當前提交
eb19f94598
共有 36 個檔案被更改,包括 571 行新增204 行删除
  1. 75
    0
      css/deep-linking/_desktop.scss
  2. 3
    0
      css/deep-linking/_main.scss
  3. 35
    7
      css/deep-linking/_mobile.scss
  4. 0
    0
      css/deep-linking/_no-mobile-app.scss
  5. 1
    0
      css/main.scss
  6. 0
    2
      css/unsupported-browser/_main.scss
  7. 二進制
      images/logo-deep-linking.png
  8. 3
    1
      interface_config.js
  9. 9
    5
      lang/main.json
  10. 26
    54
      react/features/app/functions.web.js
  11. 2
    2
      react/features/app/middleware.js
  12. 0
    0
      react/features/base/react/components/web/HideNotificationBarStyle.js
  13. 2
    0
      react/features/base/react/components/web/index.js
  14. 1
    1
      react/features/base/util/uri.js
  15. 1
    1
      react/features/conference/components/Conference.web.js
  16. 20
    0
      react/features/deep-linking/actionTypes.js
  17. 34
    0
      react/features/deep-linking/actions.js
  18. 165
    0
      react/features/deep-linking/components/DeepLinkingDesktopPage.js
  19. 39
    52
      react/features/deep-linking/components/DeepLinkingMobilePage.js
  20. 3
    3
      react/features/deep-linking/components/NoMobileApp.js
  21. 3
    0
      react/features/deep-linking/components/index.js
  22. 6
    0
      react/features/deep-linking/constants.js
  23. 80
    0
      react/features/deep-linking/functions.js
  24. 4
    0
      react/features/deep-linking/index.js
  25. 23
    0
      react/features/deep-linking/middleware.js
  26. 18
    0
      react/features/deep-linking/reducer.js
  27. 9
    0
      react/features/deep-linking/shouldShowDeepLinkingDesktopPage.js
  28. 1
    0
      react/features/invite/components/dial-in-summary/DialInSummary.web.js
  29. 6
    1
      react/features/invite/components/dial-in-summary/NumbersList.web.js
  30. 0
    16
      react/features/unsupported-browser/actionTypes.js
  31. 0
    21
      react/features/unsupported-browser/actions.js
  32. 1
    2
      react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js
  33. 0
    5
      react/features/unsupported-browser/components/index.js
  34. 0
    2
      react/features/unsupported-browser/index.js
  35. 0
    27
      react/features/unsupported-browser/reducer.js
  36. 1
    2
      react/features/welcome/components/WelcomePage.web.js

+ 75
- 0
css/deep-linking/_desktop.scss 查看文件

@@ -0,0 +1,75 @@
1
+.deep-linking-desktop {
2
+    background-color: #fff;
3
+    width: 100%;
4
+    height: 100%;
5
+    display: flex;
6
+    flex-flow: column;
7
+    .header {
8
+        width: 100%;
9
+        height: 55px;
10
+        background-color: #f1f2f5;
11
+        padding-top: 15px;
12
+        padding-left: 50px;
13
+        display: flex;
14
+        flex-flow: row;
15
+        flex: 0 0 55px;
16
+        .logo {
17
+            height: 40px;
18
+        }
19
+    }
20
+    .content {
21
+        padding-top: 40px;
22
+        padding-bottom: 40px;
23
+        left: 0px;
24
+        right: 0px;
25
+        display: flex;
26
+        width: 100%;
27
+        height: 100%;
28
+        flex-flow: row;
29
+        .leftColumn {
30
+            left: 0px;
31
+            width: 50%;
32
+            min-height: 156px;
33
+            display: flex;
34
+            flex-flow: column;
35
+            .leftColumnContent{
36
+                padding: 20px;
37
+                display: flex;
38
+                flex-flow: column;
39
+                height: 100%;
40
+                .image {
41
+                    background-image: url('../images/deep-linking-image.png');
42
+                    background-repeat: no-repeat;
43
+                    background-position: center;
44
+                    background-size: contain;
45
+                    height: 100%;
46
+                    width: 100%;
47
+                }
48
+            }
49
+
50
+        }
51
+        .rightColumn {
52
+            top: 0px;
53
+            width: 50%;
54
+            min-height: 156px;
55
+            display: flex;
56
+            flex-flow: row;
57
+            align-items: center;
58
+            .rightColumnContent {
59
+                display: flex;
60
+                flex-flow: column;
61
+                padding: 20px 20px 20px 60px;
62
+                .title {
63
+                    color: #1c2946;
64
+                }
65
+                .description {
66
+                    color: #606a80;
67
+                    margin-top: 8px;
68
+                }
69
+                .buttons {
70
+                    margin-top: 16px;
71
+                }
72
+            }
73
+        }
74
+    }
75
+}

+ 3
- 0
css/deep-linking/_main.scss 查看文件

@@ -0,0 +1,3 @@
1
+@import 'desktop';
2
+@import 'mobile';
3
+@import 'no-mobile-app';

css/unsupported-browser/_unsupported-mobile-browser.scss → css/deep-linking/_mobile.scss 查看文件

@@ -1,10 +1,23 @@
1
-.unsupported-mobile-browser {
1
+.deep-linking-mobile {
2 2
     background-color: #fff;
3 3
     height: 100vh;
4 4
     overflow: auto;
5 5
     position: relative;
6 6
     width: 100vw;
7 7
 
8
+    .header {
9
+        width: 100%;
10
+        height: 70px;
11
+        background-color: #f1f2f5;
12
+        text-align: center;
13
+        .logo {
14
+            margin-top: 15px;
15
+            margin-left: auto;
16
+            margin-right: auto;
17
+            height: 40px;
18
+        }
19
+    }
20
+
8 21
     a {
9 22
         text-decoration: none
10 23
     }
@@ -20,10 +33,19 @@
20 33
         a:active {
21 34
             text-decoration: none;
22 35
         }
36
+
37
+        .image {
38
+            max-width: 80%;
39
+        }
40
+    }
41
+
42
+    &__text {
43
+        font-weight: bolder;
44
+        padding: 10px 10px 0px 10px;
23 45
     }
24 46
 
25 47
     &__text,
26
-    .unsupported-dial-in  {
48
+    .deep-linking-dial-in  {
27 49
         font-size: 1.2em;
28 50
         line-height: em(29px, 21px);
29 51
         margin-bottom: 0.65em;
@@ -39,21 +61,27 @@
39 61
         }
40 62
     }
41 63
 
42
-    &__logo {
43
-        height: 108px;
44
-        width: 77px;
64
+    &__href {
65
+        height: 2.2857142857142856em;
66
+        line-height: 2.2857142857142856em;
67
+        margin: 18px auto 20px;
68
+        max-width: 300px;
69
+        width: auto;
70
+        font-weight: bolder;
45 71
     }
46 72
 
47 73
     &__button {
48 74
         border: 0;
49 75
         height: 2.2857142857142856em;
50 76
         line-height: 2.2857142857142856em;
51
-        margin: 18px auto 20px;
77
+        margin: 18px auto 10px;
78
+        padding: 0px 10px 0px 10px;
52 79
         max-width: 300px;
53 80
         width: auto;
54 81
         @include border-radius(3px);
55 82
         background-color: $unsupportedBrowserButtonBgColor;
56 83
         color: #505F79;
84
+        font-weight: bold;
57 85
 
58 86
         &:active {
59 87
             background-color: $unsupportedBrowserButtonBgColor;
@@ -69,7 +97,7 @@
69 97
         }
70 98
     }
71 99
 
72
-    .unsupported-dial-in {
100
+    .deep-linking-dial-in {
73 101
         display: none;
74 102
 
75 103
         &.has-numbers {

css/unsupported-browser/_no-mobile-app.scss → css/deep-linking/_no-mobile-app.scss 查看文件


+ 1
- 0
css/main.scss 查看文件

@@ -71,5 +71,6 @@
71 71
 @import 'unsupported-browser/main';
72 72
 @import 'modals/invite/add-people';
73 73
 @import 'vertical_filmstrip_overrides';
74
+@import 'deep-linking/main';
74 75
 
75 76
 /* Modules END */

+ 0
- 2
css/unsupported-browser/_main.scss 查看文件

@@ -1,3 +1 @@
1
-@import 'no-mobile-app';
2 1
 @import 'unsupported-desktop-browser';
3
-@import 'unsupported-mobile-browser';

二進制
images/logo-deep-linking.png 查看文件


+ 3
- 1
interface_config.js 查看文件

@@ -23,9 +23,11 @@ var interfaceConfig = {
23 23
     SHOW_BRAND_WATERMARK: false,
24 24
     BRAND_WATERMARK_LINK: '',
25 25
     SHOW_POWERED_BY: false,
26
+    SHOW_DEEP_LINKING_IMAGE: false,
26 27
     GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
27 28
     DISPLAY_WELCOME_PAGE_CONTENT: true,
28 29
     APP_NAME: 'Jitsi Meet',
30
+    NATIVE_APP_NAME: 'Jitsi Meet',
29 31
     LANG_DETECTION: false, // Allow i18n to detect the system language
30 32
     INVITATION_POWERED_BY: true,
31 33
 
@@ -161,7 +163,7 @@ var interfaceConfig = {
161 163
     /**
162 164
      * Specify mobile app scheme for opening the app from the mobile browser.
163 165
      */
164
-    // MOBILE_APP_SCHEME: 'org.jitsi.meet'
166
+    // APP_SCHEME: 'org.jitsi.meet'
165 167
 };
166 168
 
167 169
 /* eslint-enable no-unused-vars, no-var, max-len */

+ 9
- 5
lang/main.json 查看文件

@@ -107,11 +107,6 @@
107 107
         "shortcuts": "View shortcuts",
108 108
         "speakerStats": "Speaker stats"
109 109
     },
110
-    "unsupportedBrowser": {
111
-        "appNotInstalled": "Join this meeting with __app__ on your phone.",
112
-        "downloadApp": "Download the app",
113
-        "openApp": "Continue to __app__"
114
-    },
115 110
     "chat":{
116 111
         "nickname": {
117 112
             "title": "Enter a nickname in the box below",
@@ -561,5 +556,14 @@
561 556
     },
562 557
     "sectionList": {
563 558
         "pullToRefresh": "Pull to refresh"
559
+    },
560
+    "deepLinking": {
561
+        "title": "Launching your meeting in __app__...",
562
+        "description": "Nothing happened? We tried launching your meeting in the __app__ desktop app. Try again or launch it in the __app__ web app.",
563
+        "tryAgainButton": "Try again in desktop",
564
+        "launchWebButton": "Launch in web",
565
+        "appNotInstalled": "You need the __app__ mobile app to join this meeting on your phone.",
566
+        "downloadApp": "Download the app",
567
+        "openApp": "Continue to the app"
564 568
     }
565 569
 }

+ 26
- 54
react/features/app/functions.web.js 查看文件

@@ -1,12 +1,10 @@
1 1
 /* @flow */
2 2
 
3
-import { Platform } from '../base/react';
4 3
 import { toState } from '../base/redux';
4
+import { getDeepLinkingPage } from '../deep-linking';
5 5
 import {
6
-    NoMobileApp,
7 6
     PluginRequiredBrowser,
8
-    UnsupportedDesktopBrowser,
9
-    UnsupportedMobileBrowser
7
+    UnsupportedDesktopBrowser
10 8
 } from '../unsupported-browser';
11 9
 
12 10
 import {
@@ -24,50 +22,18 @@ declare var loggingConfig: Object;
24 22
  *
25 23
  * @private
26 24
  * @param {Object} state - Object containing current redux state.
27
- * @returns {ReactElement|void}
25
+ * @returns {Promise<ReactElement>|void}
28 26
  * @type {Function[]}
29 27
  */
30 28
 const _INTERCEPT_COMPONENT_RULES = [
31
-
32
-    /**
33
-     * This rule describes case when user opens application using mobile
34
-     * browser and is attempting to join a conference. In order to promote the
35
-     * app, we choose to suggest the mobile app even if the browser supports the
36
-     * app (e.g. Google Chrome with WebRTC support on Android).
37
-     *
38
-     * @param {Object} state - The redux state of the app.
39
-     * @returns {UnsupportedMobileBrowser|void} If the rule is satisfied then
40
-     * we should intercept existing component by UnsupportedMobileBrowser.
41
-     */
42
-    // eslint-disable-next-line no-unused-vars
43
-    state => {
44
-        const OS = Platform.OS;
45
-        const { room } = state['features/base/conference'];
46
-        const isUsingMobileBrowser = OS === 'android' || OS === 'ios';
47
-
48
-        /**
49
-         * Checking for presence of a room is done so that interception only
50
-         * occurs when trying to enter a meeting but pages outside of meeting,
51
-         * like WelcomePage, can still display.
52
-         */
53
-        if (room && isUsingMobileBrowser) {
54
-            const mobileAppPromo
55
-                = typeof interfaceConfig === 'object'
56
-                    && interfaceConfig.MOBILE_APP_PROMO;
57
-
58
-            return (
59
-                typeof mobileAppPromo === 'undefined' || Boolean(mobileAppPromo)
60
-                    ? UnsupportedMobileBrowser
61
-                    : NoMobileApp);
62
-        }
63
-    },
29
+    getDeepLinkingPage,
64 30
     state => {
65 31
         const { webRTCReady } = state['features/base/lib-jitsi-meet'];
66 32
 
67 33
         switch (typeof webRTCReady) {
68 34
         case 'boolean':
69 35
             if (webRTCReady === false) {
70
-                return UnsupportedDesktopBrowser;
36
+                return Promise.resolve(UnsupportedDesktopBrowser);
71 37
             }
72 38
             break;
73 39
 
@@ -76,8 +42,10 @@ const _INTERCEPT_COMPONENT_RULES = [
76 42
             break;
77 43
 
78 44
         default:
79
-            return PluginRequiredBrowser;
45
+            return Promise.resolve(PluginRequiredBrowser);
80 46
         }
47
+
48
+        return Promise.resolve();
81 49
     }
82 50
 ];
83 51
 
@@ -87,16 +55,19 @@ const _INTERCEPT_COMPONENT_RULES = [
87 55
  *
88 56
  * @param {(Object|Function)} stateOrGetState - The redux state or
89 57
  * {@link getState} function.
90
- * @returns {Route}
58
+ * @returns {Promise<Route>}
91 59
  */
92
-export function _getRouteToRender(stateOrGetState: Object | Function) {
60
+export function _getRouteToRender(stateOrGetState: Object | Function): Object {
93 61
     const route = _super_getRouteToRender(stateOrGetState);
94 62
 
95 63
     // Intercepts route components if any of component interceptor rules is
96 64
     // satisfied.
97
-    route.component = _interceptComponent(stateOrGetState, route.component);
65
+    return _interceptComponent(stateOrGetState, route.component).then(
66
+        (component: React$Element<*>) => {
67
+            route.component = component;
98 68
 
99
-    return route;
69
+            return route;
70
+        }, () => Promise.resolve(route));
100 71
 }
101 72
 
102 73
 /**
@@ -106,23 +77,24 @@ export function _getRouteToRender(stateOrGetState: Object | Function) {
106 77
  * {@link getState} function.
107 78
  * @param {ReactElement} component - Current route component to render.
108 79
  * @private
109
- * @returns {ReactElement} If any of the pre-defined rules is satisfied, returns
110
- * intercepted component.
80
+ * @returns {Promise<ReactElement>} If any of the pre-defined rules is
81
+ * satisfied, returns intercepted component.
111 82
  */
112 83
 function _interceptComponent(
113 84
         stateOrGetState: Object | Function,
114 85
         component: React$Element<*>) {
115
-    let result;
116 86
     const state = toState(stateOrGetState);
117 87
 
118
-    for (const rule of _INTERCEPT_COMPONENT_RULES) {
119
-        result = rule(state);
120
-        if (result) {
121
-            break;
122
-        }
123
-    }
88
+    const promises = [];
89
+
90
+    _INTERCEPT_COMPONENT_RULES.forEach(rule => {
91
+        promises.push(rule(state));
92
+    });
124 93
 
125
-    return result || component;
94
+    return Promise.all(promises).then(
95
+        results =>
96
+            results.find(result => typeof result !== 'undefined') || component,
97
+        () => Promise.resolve(component));
126 98
 }
127 99
 
128 100
 /**

+ 2
- 2
react/features/app/middleware.js 查看文件

@@ -77,9 +77,9 @@ function _connectionEstablished(store, next, action) {
77 77
 function _navigate({ getState }) {
78 78
     const state = getState();
79 79
     const { app } = state['features/app'];
80
-    const routeToRender = _getRouteToRender(state);
81 80
 
82
-    return app._navigate(routeToRender);
81
+    _getRouteToRender(state)
82
+        .then(routeToRender => app._navigate(routeToRender));
83 83
 }
84 84
 
85 85
 /**

react/features/unsupported-browser/components/HideNotificationBarStyle.js → react/features/base/react/components/web/HideNotificationBarStyle.js 查看文件


+ 2
- 0
react/features/base/react/components/web/index.js 查看文件

@@ -1,4 +1,6 @@
1 1
 export { default as Container } from './Container';
2
+export { default as HideNotificationBarStyle }
3
+    from './HideNotificationBarStyle';
2 4
 export { default as MultiSelectAutocomplete } from './MultiSelectAutocomplete';
3 5
 export { default as Text } from './Text';
4 6
 export { default as Watermarks } from './Watermarks';

+ 1
- 1
react/features/base/util/uri.js 查看文件

@@ -27,7 +27,7 @@ const _URI_PATH_PATTERN = '([^?#]*)';
27 27
  *
28 28
  * FIXME: The URL class exposed by JavaScript will not include the colon in
29 29
  * the protocol field. Also in other places (at the time of this writing:
30
- * the UnsupportedMobileBrowser.js) the APP_LINK_SCHEME does not include
30
+ * the DeepLinkingMobilePage.js) the APP_LINK_SCHEME does not include
31 31
  * the double dots, so things are inconsistent.
32 32
  *
33 33
  * @type {string}

+ 1
- 1
react/features/conference/components/Conference.web.js 查看文件

@@ -8,6 +8,7 @@ import { connect, disconnect } from '../../base/connection';
8 8
 import { DialogContainer } from '../../base/dialog';
9 9
 import { translate } from '../../base/i18n';
10 10
 import { CalleeInfoContainer } from '../../base/jwt';
11
+import { HideNotificationBarStyle } from '../../base/react';
11 12
 import { Filmstrip } from '../../filmstrip';
12 13
 import { LargeVideo } from '../../large-video';
13 14
 import { NotificationsContainer } from '../../notifications';
@@ -18,7 +19,6 @@ import {
18 19
     setToolboxAlwaysVisible,
19 20
     showToolbox
20 21
 } from '../../toolbox';
21
-import { HideNotificationBarStyle } from '../../unsupported-browser';
22 22
 
23 23
 import { maybeShowSuboptimalExperienceNotification } from '../functions';
24 24
 

+ 20
- 0
react/features/deep-linking/actionTypes.js 查看文件

@@ -0,0 +1,20 @@
1
+/* @flow */
2
+
3
+/**
4
+ * The type of the action which signals to open the conference in the desktop
5
+ * app.
6
+ *
7
+ * {
8
+ *     type: OPEN_DESKTOP
9
+ * }
10
+ */
11
+export const OPEN_DESKTOP_APP = Symbol('OPEN_DESKTOP_APP');
12
+
13
+/**
14
+ * The type of the action which signals to open the conference in the web app.
15
+ *
16
+ * {
17
+ *     type: OPEN_WEB_APP
18
+ * }
19
+ */
20
+export const OPEN_WEB_APP = Symbol('OPEN_WEB_APP');

+ 34
- 0
react/features/deep-linking/actions.js 查看文件

@@ -0,0 +1,34 @@
1
+/* @flow */
2
+
3
+import { appNavigate } from '../app';
4
+
5
+import { OPEN_DESKTOP_APP, OPEN_WEB_APP } from './actionTypes';
6
+
7
+/**
8
+ * Continue to the conference page.
9
+ *
10
+ * @returns {Function}
11
+ */
12
+export function openWebApp() {
13
+    return (dispatch: Dispatch<*>) => {
14
+        // In order to go to the web app we need to skip the deep linking
15
+        // interceptor. OPEN_WEB_APP action should set launchInWeb to true in
16
+        // the redux store. After this when appNavigate() is called the
17
+        // deep linking interceptor will be skipped (will return undefined).
18
+        dispatch({ type: OPEN_WEB_APP });
19
+        dispatch(appNavigate());
20
+    };
21
+}
22
+
23
+/**
24
+ * Opens the desktop app.
25
+ *
26
+ * @returns {{
27
+ *     type: OPEN_DESKTOP_APP
28
+ * }}
29
+ */
30
+export function openDesktopApp() {
31
+    return {
32
+        type: OPEN_DESKTOP_APP
33
+    };
34
+}

+ 165
- 0
react/features/deep-linking/components/DeepLinkingDesktopPage.js 查看文件

@@ -0,0 +1,165 @@
1
+/* @flow */
2
+
3
+import Button, { ButtonGroup } from '@atlaskit/button';
4
+import { AtlasKitThemeProvider } from '@atlaskit/theme';
5
+import React, { Component } from 'react';
6
+import { connect } from 'react-redux';
7
+
8
+import { translate } from '../../base/i18n';
9
+
10
+import {
11
+    openWebApp,
12
+    openDesktopApp
13
+} from '../actions';
14
+import { _TNS } from '../constants';
15
+
16
+declare var interfaceConfig: Object;
17
+
18
+/**
19
+ * The type of the React {@code Component} props of
20
+ * {@link DeepLinkingDesktopPage}.
21
+ */
22
+ type Props = {
23
+
24
+    /**
25
+     * Used to dispatch actions from the buttons.
26
+     */
27
+    dispatch: Dispatch<*>,
28
+
29
+    /**
30
+     * Used to obtain translations.
31
+     */
32
+    t: Function
33
+};
34
+
35
+/**
36
+ * React component representing the deep linking page.
37
+ *
38
+ * @class DeepLinkingDesktopPage
39
+ */
40
+class DeepLinkingDesktopPage<P : Props> extends Component<P> {
41
+    /**
42
+     * Initializes a new {@code DeepLinkingDesktopPage} instance.
43
+     *
44
+     * @param {Object} props - The read-only React {@code Component} props with
45
+     * which the new instance is to be initialized.
46
+     */
47
+    constructor(props: P) {
48
+        super(props);
49
+
50
+        // Bind event handlers so they are only bound once per instance.
51
+        this._openDesktopApp = this._openDesktopApp.bind(this);
52
+        this._onLaunchWeb = this._onLaunchWeb.bind(this);
53
+        this._onTryAgain = this._onTryAgain.bind(this);
54
+    }
55
+
56
+    /**
57
+     * Implements the Component's componentDidMount method.
58
+     *
59
+     * @inheritdoc
60
+     */
61
+    componentDidMount() {
62
+        this._openDesktopApp();
63
+    }
64
+
65
+    /**
66
+     * Renders the component.
67
+     *
68
+     * @returns {ReactElement}
69
+     */
70
+    render() {
71
+        const { t } = this.props;
72
+        const { NATIVE_APP_NAME, SHOW_DEEP_LINKING_IMAGE } = interfaceConfig;
73
+        const rightColumnStyle
74
+            = SHOW_DEEP_LINKING_IMAGE ? null : { width: '100%' };
75
+
76
+        return (
77
+
78
+            // Enabling light theme because of the color of the buttons.
79
+            <AtlasKitThemeProvider mode = 'light'>
80
+                <div className = 'deep-linking-desktop'>
81
+                    <div className = 'header'>
82
+                        <img
83
+                            className = 'logo'
84
+                            src = '../images/logo-deep-linking.png' />
85
+                    </div>
86
+                    <div className = 'content'>
87
+                        {
88
+                            SHOW_DEEP_LINKING_IMAGE
89
+                                ? <div className = 'leftColumn'>
90
+                                    <div className = 'leftColumnContent'>
91
+                                        <div className = 'image' />
92
+                                    </div>
93
+                                </div> : null
94
+                        }
95
+                        <div
96
+                            className = 'rightColumn'
97
+                            style = { rightColumnStyle }>
98
+                            <div className = 'rightColumnContent'>
99
+                                <h1 className = 'title'>
100
+                                    {
101
+                                        t(`${_TNS}.title`,
102
+                                        { app: NATIVE_APP_NAME })
103
+                                    }
104
+                                </h1>
105
+                                <p className = 'description'>
106
+                                    {
107
+                                        t(`${_TNS}.description`,
108
+                                            { app: NATIVE_APP_NAME })
109
+                                    }
110
+                                </p>
111
+                                <div className = 'buttons'>
112
+                                    <ButtonGroup>
113
+                                        <Button
114
+                                            appearance = 'default'
115
+                                            onClick = { this._onTryAgain }>
116
+                                            { t(`${_TNS}.tryAgainButton`) }
117
+                                        </Button>
118
+                                        <Button onClick = { this._onLaunchWeb }>
119
+                                            { t(`${_TNS}.launchWebButton`) }
120
+                                        </Button>
121
+                                    </ButtonGroup>
122
+                                </div>
123
+                            </div>
124
+                        </div>
125
+                    </div>
126
+                </div>
127
+            </AtlasKitThemeProvider>
128
+        );
129
+    }
130
+
131
+    _openDesktopApp: () => {}
132
+
133
+    /**
134
+     * Dispatches the <tt>openDesktopApp</tt> action.
135
+     *
136
+     * @returns {void}
137
+     */
138
+    _openDesktopApp() {
139
+        this.props.dispatch(openDesktopApp());
140
+    }
141
+
142
+    _onTryAgain: () => {}
143
+
144
+    /**
145
+     * Handles try again button clicks.
146
+     *
147
+     * @returns {void}
148
+     */
149
+    _onTryAgain() {
150
+        this._openDesktopApp();
151
+    }
152
+
153
+    _onLaunchWeb: () => {}
154
+
155
+    /**
156
+     * Handles launch web button clicks.
157
+     *
158
+     * @returns {void}
159
+     */
160
+    _onLaunchWeb() {
161
+        this.props.dispatch(openWebApp());
162
+    }
163
+}
164
+
165
+export default translate(connect()(DeepLinkingDesktopPage));

react/features/unsupported-browser/components/UnsupportedMobileBrowser.js → react/features/deep-linking/components/DeepLinkingMobilePage.js 查看文件

@@ -5,28 +5,21 @@ import React, { Component } from 'react';
5 5
 import { connect } from 'react-redux';
6 6
 
7 7
 import { translate, translateToHTML } from '../../base/i18n';
8
-import { Platform } from '../../base/react';
9
-import { URI_PROTOCOL_PATTERN } from '../../base/util';
8
+import { HideNotificationBarStyle, Platform } from '../../base/react';
10 9
 import { DialInSummary } from '../../invite';
11
-import HideNotificationBarStyle from './HideNotificationBarStyle';
12 10
 
13
-declare var interfaceConfig: Object;
11
+import { _TNS } from '../constants';
12
+import { generateDeepLinkingURL } from '../functions';
14 13
 
15
-/**
16
- * The namespace of the CSS styles of UnsupportedMobileBrowser.
17
- *
18
- * @private
19
- * @type {string}
20
- */
21
-const _SNS = 'unsupported-mobile-browser';
14
+declare var interfaceConfig: Object;
22 15
 
23 16
 /**
24
- * The namespace of the i18n/translation keys of UnsupportedMobileBrowser.
17
+ * The namespace of the CSS styles of DeepLinkingMobilePage.
25 18
  *
26 19
  * @private
27 20
  * @type {string}
28 21
  */
29
-const _TNS = 'unsupportedBrowser';
22
+const _SNS = 'deep-linking-mobile';
30 23
 
31 24
 /**
32 25
  * The map of platforms to URLs at which the mobile app for the associated
@@ -45,13 +38,13 @@ const _URLS = {
45 38
 /**
46 39
  * React component representing mobile browser page.
47 40
  *
48
- * @class UnsupportedMobileBrowser
41
+ * @class DeepLinkingMobilePage
49 42
  */
50
-class UnsupportedMobileBrowser extends Component<*, *> {
43
+class DeepLinkingMobilePage extends Component<*, *> {
51 44
     state: Object;
52 45
 
53 46
     /**
54
-     * UnsupportedMobileBrowser component's property types.
47
+     * DeepLinkingMobilePage component's property types.
55 48
      *
56 49
      * @static
57 50
      */
@@ -77,20 +70,8 @@ class UnsupportedMobileBrowser extends Component<*, *> {
77 70
      * @inheritdoc
78 71
      */
79 72
     componentWillMount() {
80
-        // If the user installed the app while this Component was displayed
81
-        // (e.g. the user clicked the Download the App button), then we would
82
-        // like to open the current URL in the mobile app. The only way to do it
83
-        // appears to be a link with an app-specific scheme, not a Universal
84
-        // Link.
85
-        const appScheme = interfaceConfig.MOBILE_APP_SCHEME || 'org.jitsi.meet';
86
-
87
-        // Replace the protocol part with the app scheme.
88
-        const joinURL
89
-            = window.location.href.replace(
90
-                new RegExp(`^${URI_PROTOCOL_PATTERN}`), `${appScheme}:`);
91
-
92 73
         this.setState({
93
-            joinURL
74
+            joinURL: generateDeepLinkingURL()
94 75
         });
95 76
     }
96 77
 
@@ -102,43 +83,49 @@ class UnsupportedMobileBrowser extends Component<*, *> {
102 83
      */
103 84
     render() {
104 85
         const { _room, t } = this.props;
105
-
106
-        const openAppButtonClassName
86
+        const { NATIVE_APP_NAME, SHOW_DEEP_LINKING_IMAGE } = interfaceConfig;
87
+        const downloadButtonClassName
107 88
             = `${_SNS}__button ${_SNS}__button_primary`;
108
-        const appName
109
-            = interfaceConfig.ADD_PEOPLE_APP_NAME || interfaceConfig.APP_NAME;
110 89
 
111 90
         return (
112 91
             <div className = { _SNS }>
113
-                <div className = { `${_SNS}__body` }>
92
+                <div className = 'header'>
114 93
                     <img
115
-                        className = { `${_SNS}__logo` }
116
-                        src = 'images/logo-blue.svg' />
94
+                        className = 'logo'
95
+                        src = '../images/logo-deep-linking.png' />
96
+                </div>
97
+                <div className = { `${_SNS}__body` }>
98
+                    {
99
+                        SHOW_DEEP_LINKING_IMAGE
100
+                            ? <img
101
+                                className = 'image'
102
+                                src = '../images/deep-linking-image.png' />
103
+                            : null
104
+                    }
117 105
                     <p className = { `${_SNS}__text` }>
118 106
                         {
119 107
                             translateToHTML(
120 108
                                 t,
121 109
                                 `${_TNS}.appNotInstalled`,
122
-                                { app: appName })
110
+                                { app: NATIVE_APP_NAME })
123 111
                         }
124 112
                     </p>
125
-                    <a href = { this.state.joinURL }>
126
-                        <button className = { openAppButtonClassName }>
127
-                            { t(`${_TNS}.openApp`,
128
-                                { app: appName }) }
129
-                        </button>
130
-                    </a>
131 113
                     <a href = { _URLS[Platform.OS] }>
132
-                        <button className = { `${_SNS}__button` }>
114
+                        <button className = { downloadButtonClassName }>
133 115
                             { t(`${_TNS}.downloadApp`) }
134 116
                         </button>
135 117
                     </a>
136
-                    { _room
137
-                        ? <DialInSummary
138
-                            className = 'unsupported-dial-in'
139
-                            clickableNumbers = { true }
140
-                            room = { _room } />
141
-                        : null }
118
+                    <a
119
+                        className = { `${_SNS}__href` }
120
+                        href = { this.state.joinURL }>
121
+                        {/* <button className = { `${_SNS}__button` }> */}
122
+                        { t(`${_TNS}.openApp`) }
123
+                        {/* </button> */}
124
+                    </a>
125
+                    <DialInSummary
126
+                        className = 'deep-linking-dial-in'
127
+                        clickableNumbers = { true }
128
+                        room = { _room } />
142 129
                 </div>
143 130
                 <HideNotificationBarStyle />
144 131
             </div>
@@ -148,7 +135,7 @@ class UnsupportedMobileBrowser extends Component<*, *> {
148 135
 
149 136
 /**
150 137
  * Maps (parts of) the Redux state to the associated props for the
151
- * {@code UnsupportedMobileBrowser} component.
138
+ * {@code DeepLinkingMobilePage} component.
152 139
  *
153 140
  * @param {Object} state - The Redux state.
154 141
  * @private
@@ -162,4 +149,4 @@ function _mapStateToProps(state) {
162 149
     };
163 150
 }
164 151
 
165
-export default translate(connect(_mapStateToProps)(UnsupportedMobileBrowser));
152
+export default translate(connect(_mapStateToProps)(DeepLinkingMobilePage));

react/features/unsupported-browser/components/NoMobileApp.js → react/features/deep-linking/components/NoMobileApp.js 查看文件

@@ -2,7 +2,7 @@
2 2
 
3 3
 import React, { Component } from 'react';
4 4
 
5
-import HideNotificationBarStyle from './HideNotificationBarStyle';
5
+import { HideNotificationBarStyle } from '../../base/react';
6 6
 
7 7
 declare var interfaceConfig: Object;
8 8
 
@@ -26,8 +26,8 @@ export default class NoMobileApp extends Component<*> {
26 26
                     Video chat isn't available on mobile.
27 27
                 </h2>
28 28
                 <p className = { `${ns}__description` }>
29
-                    Please use { interfaceConfig.APP_NAME } on desktop to join
30
-                    calls.
29
+                    Please use { interfaceConfig.NATIVE_APP_NAME } on desktop to
30
+                    join calls.
31 31
                 </p>
32 32
 
33 33
                 <HideNotificationBarStyle />

+ 3
- 0
react/features/deep-linking/components/index.js 查看文件

@@ -0,0 +1,3 @@
1
+export { default as DeepLinkingDesktopPage } from './DeepLinkingDesktopPage';
2
+export { default as DeepLinkingMobilePage } from './DeepLinkingMobilePage';
3
+export { default as NoMobileApp } from './NoMobileApp';

+ 6
- 0
react/features/deep-linking/constants.js 查看文件

@@ -0,0 +1,6 @@
1
+/**
2
+ * The namespace of the i18n/translation keys.
3
+ *
4
+ * @type {string}
5
+ */
6
+export const _TNS = 'deepLinking';

+ 80
- 0
react/features/deep-linking/functions.js 查看文件

@@ -0,0 +1,80 @@
1
+/* global interfaceConfig */
2
+
3
+import { URI_PROTOCOL_PATTERN } from '../base/util';
4
+import { Platform } from '../base/react';
5
+
6
+import {
7
+    DeepLinkingDesktopPage,
8
+    DeepLinkingMobilePage,
9
+    NoMobileApp
10
+} from './components';
11
+import { _shouldShowDeepLinkingDesktopPage }
12
+    from './shouldShowDeepLinkingDesktopPage';
13
+
14
+/**
15
+ * Generates a deep linking URL based on the current window URL.
16
+ *
17
+ * @returns {string} - The generated URL.
18
+ */
19
+export function generateDeepLinkingURL() {
20
+    // If the user installed the app while this Component was displayed
21
+    // (e.g. the user clicked the Download the App button), then we would
22
+    // like to open the current URL in the mobile app. The only way to do it
23
+    // appears to be a link with an app-specific scheme, not a Universal
24
+    // Link.
25
+    const appScheme = interfaceConfig.APP_SCHEME || 'org.jitsi.meet';
26
+
27
+    // Replace the protocol part with the app scheme.
28
+
29
+    return window.location.href.replace(
30
+            new RegExp(`^${URI_PROTOCOL_PATTERN}`), `${appScheme}:`);
31
+}
32
+
33
+/**
34
+ * Resolves with the component that should be displayed if the deep linking page
35
+ * should be shown and with <tt>undefined</tt> otherwise.
36
+ *
37
+ * @param {Object} state - Object containing current redux state.
38
+ * @returns {Promise<Component>}
39
+ */
40
+export function getDeepLinkingPage(state) {
41
+    const { room } = state['features/base/conference'];
42
+
43
+    // Show only if we are about to join a conference.
44
+    if (!room) {
45
+        return Promise.resolve();
46
+    }
47
+
48
+    const OS = Platform.OS;
49
+    const isUsingMobileBrowser = OS === 'android' || OS === 'ios';
50
+
51
+    if (isUsingMobileBrowser) { // mobile
52
+        const mobileAppPromo
53
+            = typeof interfaceConfig === 'object'
54
+                && interfaceConfig.MOBILE_APP_PROMO;
55
+
56
+        return Promise.resolve(
57
+            typeof mobileAppPromo === 'undefined' || Boolean(mobileAppPromo)
58
+                ? DeepLinkingMobilePage : NoMobileApp);
59
+    }
60
+
61
+    // desktop
62
+    const { launchInWeb } = state['features/deep-linking'];
63
+
64
+    if (launchInWeb) {
65
+        return Promise.resolve();
66
+    }
67
+
68
+    return _shouldShowDeepLinkingDesktopPage().then(
69
+        // eslint-disable-next-line no-confusing-arrow
70
+        show => show ? DeepLinkingDesktopPage : undefined);
71
+}
72
+
73
+/**
74
+ * Opens the desktop app.
75
+ *
76
+ * @returns {void}
77
+ */
78
+export function openDesktopApp() {
79
+    window.location.href = generateDeepLinkingURL();
80
+}

+ 4
- 0
react/features/deep-linking/index.js 查看文件

@@ -0,0 +1,4 @@
1
+export * from './functions';
2
+
3
+import './middleware';
4
+import './reducer';

+ 23
- 0
react/features/deep-linking/middleware.js 查看文件

@@ -0,0 +1,23 @@
1
+// @flow
2
+
3
+import { MiddlewareRegistry } from '../base/redux';
4
+
5
+import { OPEN_DESKTOP_APP } from './actionTypes';
6
+import { openDesktopApp } from './functions';
7
+
8
+/**
9
+ * Implements the middleware of the deep linking feature.
10
+ *
11
+ * @param {Store} store - The redux store.
12
+ * @returns {Function}
13
+ */
14
+// eslint-disable-next-line no-unused-vars
15
+MiddlewareRegistry.register(store => next => action => {
16
+    switch (action.type) {
17
+    case OPEN_DESKTOP_APP:
18
+        openDesktopApp();
19
+        break;
20
+    }
21
+
22
+    return next(action);
23
+});

+ 18
- 0
react/features/deep-linking/reducer.js 查看文件

@@ -0,0 +1,18 @@
1
+/* @flow */
2
+
3
+import { ReducerRegistry } from '../base/redux';
4
+
5
+import { OPEN_WEB_APP } from './actionTypes';
6
+
7
+ReducerRegistry.register('features/deep-linking', (state = {}, action) => {
8
+    switch (action.type) {
9
+    case OPEN_WEB_APP: {
10
+        return {
11
+            ...state,
12
+            launchInWeb: true
13
+        };
14
+    }
15
+    }
16
+
17
+    return state;
18
+});

+ 9
- 0
react/features/deep-linking/shouldShowDeepLinkingDesktopPage.js 查看文件

@@ -0,0 +1,9 @@
1
+/**
2
+ * Resolves with <tt>true</tt> if the deep linking page should be shown and with
3
+ * <tt>false</tt> otherwise.
4
+ *
5
+ * @returns {Promise<boolean>}
6
+ */
7
+export function _shouldShowDeepLinkingDesktopPage() {
8
+    return Promise.resolve(false);
9
+}

+ 1
- 0
react/features/invite/components/dial-in-summary/DialInSummary.web.js 查看文件

@@ -129,6 +129,7 @@ class DialInSummary extends Component {
129 129
                     : null,
130 130
                 <NumbersList
131 131
                     clickableNumbers = { this.props.clickableNumbers }
132
+                    conferenceID = { conferenceID }
132 133
                     key = 'numbers'
133 134
                     numbers = { this.state.numbers } />
134 135
             ];

+ 6
- 1
react/features/invite/components/dial-in-summary/NumbersList.web.js 查看文件

@@ -21,6 +21,11 @@ class NumbersList extends Component {
21 21
          */
22 22
         clickableNumbers: PropTypes.bool,
23 23
 
24
+        /**
25
+         * The conference ID for dialing in.
26
+         */
27
+        conferenceID: PropTypes.number,
28
+
24 29
         /**
25 30
          * The phone numbers to display. Can be an array of numbers
26 31
          * or an object with countries as keys and an array of numbers
@@ -136,7 +141,7 @@ class NumbersList extends Component {
136 141
         if (this.props.clickableNumbers) {
137 142
             return (
138 143
                 <a
139
-                    href = { `tel:${number}` }
144
+                    href = { `tel:${number}p${this.props.conferenceID}#` }
140 145
                     key = { number } >
141 146
                     { number }
142 147
                 </a>

+ 0
- 16
react/features/unsupported-browser/actionTypes.js 查看文件

@@ -1,16 +0,0 @@
1
-/**
2
- * The type of the Redux action which signals that the React Component
3
- * UnsupportedMobileBrowser which was rendered as a promotion of the mobile app
4
- * on a browser was dismissed by the user. For example, the Web app may possibly
5
- * run in Google Chrome on Android but we choose to promote the mobile app
6
- * anyway claiming the user experience provided by the Web app is inferior to
7
- * that of the mobile app. Eventually, the user may choose to dismiss the
8
- * promotion of the mobile app and take their chances with the Web app instead.
9
- * If unused, then we have chosen to force the mobile app and not allow the Web
10
- * app in mobile browsers.
11
- *
12
- * {
13
- *     type: DISMISS_MOBILE_APP_PROMO
14
- * }
15
- */
16
-export const DISMISS_MOBILE_APP_PROMO = Symbol('DISMISS_MOBILE_APP_PROMO');

+ 0
- 21
react/features/unsupported-browser/actions.js 查看文件

@@ -1,21 +0,0 @@
1
-import { DISMISS_MOBILE_APP_PROMO } from './actionTypes';
2
-
3
-/**
4
- * Returns a Redux action which signals that the UnsupportedMobileBrowser which
5
- * was rendered as a promotion of the mobile app on a browser was dismissed by
6
- * the user. For example, the Web app may possibly run in Google Chrome
7
- * on Android but we choose to promote the mobile app anyway claiming the user
8
- * experience provided by the Web app is inferior to that of the mobile app.
9
- * Eventually, the user may choose to dismiss the promotion of the mobile app
10
- * and take their chances with the Web app instead. If unused, then we have
11
- * chosen to force the mobile app and not allow the Web app in mobile browsers.
12
- *
13
- * @returns {{
14
- *     type: DISMISS_MOBILE_APP_PROMO
15
- * }}
16
- */
17
-export function dismissMobileAppPromo() {
18
-    return {
19
-        type: DISMISS_MOBILE_APP_PROMO
20
-    };
21
-}

+ 1
- 2
react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js 查看文件

@@ -4,10 +4,9 @@ import PropTypes from 'prop-types';
4 4
 import React, { Component } from 'react';
5 5
 
6 6
 import { translate } from '../../base/i18n';
7
-import { Platform } from '../../base/react';
7
+import { HideNotificationBarStyle, Platform } from '../../base/react';
8 8
 
9 9
 import { CHROME, FIREFOX, IE, SAFARI } from './browserLinks';
10
-import HideNotificationBarStyle from './HideNotificationBarStyle';
11 10
 
12 11
 /**
13 12
  * The namespace of the CSS styles of UnsupportedDesktopBrowser.

+ 0
- 5
react/features/unsupported-browser/components/index.js 查看文件

@@ -1,8 +1,3 @@
1
-export { default as HideNotificationBarStyle }
2
-    from './HideNotificationBarStyle';
3
-export { default as NoMobileApp } from './NoMobileApp';
4 1
 export { default as PluginRequiredBrowser } from './PluginRequiredBrowser';
5 2
 export { default as UnsupportedDesktopBrowser }
6 3
     from './UnsupportedDesktopBrowser';
7
-export { default as UnsupportedMobileBrowser }
8
-    from './UnsupportedMobileBrowser';

+ 0
- 2
react/features/unsupported-browser/index.js 查看文件

@@ -1,5 +1,3 @@
1
-export * from './actions';
2 1
 export * from './components';
3 2
 
4 3
 import './middleware';
5
-import './reducer';

+ 0
- 27
react/features/unsupported-browser/reducer.js 查看文件

@@ -1,27 +0,0 @@
1
-import { ReducerRegistry } from '../base/redux';
2
-
3
-import { DISMISS_MOBILE_APP_PROMO } from './actionTypes';
4
-
5
-ReducerRegistry.register(
6
-        'features/unsupported-browser',
7
-        (state = {}, action) => {
8
-            switch (action.type) {
9
-            case DISMISS_MOBILE_APP_PROMO:
10
-                return {
11
-                    ...state,
12
-
13
-                    /**
14
-                     * The indicator which determines whether the React
15
-                     * Component UnsupportedMobileBrowser which was rendered as
16
-                     * a promotion of the mobile app on a browser was dismissed
17
-                     * by the user. If unused, then we have chosen to force the
18
-                     * mobile app and not allow the Web app in mobile browsers.
19
-                     *
20
-                     * @type {boolean}
21
-                     */
22
-                    mobileAppPromoDismissed: true
23
-                };
24
-            }
25
-
26
-            return state;
27
-        });

+ 1
- 2
react/features/welcome/components/WelcomePage.web.js 查看文件

@@ -9,8 +9,7 @@ import { connect } from 'react-redux';
9 9
 import { initAnalytics } from '../../analytics';
10 10
 import { translate } from '../../base/i18n';
11 11
 import { isAnalyticsEnabled } from '../../base/lib-jitsi-meet';
12
-import { Watermarks } from '../../base/react';
13
-import { HideNotificationBarStyle } from '../../unsupported-browser';
12
+import { HideNotificationBarStyle, Watermarks } from '../../base/react';
14 13
 
15 14
 import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
16 15
 

Loading…
取消
儲存