Browse Source

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 years ago
parent
commit
eb19f94598
36 changed files with 571 additions and 204 deletions
  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. BIN
      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 View File

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 View File

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

css/unsupported-browser/_unsupported-mobile-browser.scss → css/deep-linking/_mobile.scss View File

1
-.unsupported-mobile-browser {
1
+.deep-linking-mobile {
2
     background-color: #fff;
2
     background-color: #fff;
3
     height: 100vh;
3
     height: 100vh;
4
     overflow: auto;
4
     overflow: auto;
5
     position: relative;
5
     position: relative;
6
     width: 100vw;
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
     a {
21
     a {
9
         text-decoration: none
22
         text-decoration: none
10
     }
23
     }
20
         a:active {
33
         a:active {
21
             text-decoration: none;
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
     &__text,
47
     &__text,
26
-    .unsupported-dial-in  {
48
+    .deep-linking-dial-in  {
27
         font-size: 1.2em;
49
         font-size: 1.2em;
28
         line-height: em(29px, 21px);
50
         line-height: em(29px, 21px);
29
         margin-bottom: 0.65em;
51
         margin-bottom: 0.65em;
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
     &__button {
73
     &__button {
48
         border: 0;
74
         border: 0;
49
         height: 2.2857142857142856em;
75
         height: 2.2857142857142856em;
50
         line-height: 2.2857142857142856em;
76
         line-height: 2.2857142857142856em;
51
-        margin: 18px auto 20px;
77
+        margin: 18px auto 10px;
78
+        padding: 0px 10px 0px 10px;
52
         max-width: 300px;
79
         max-width: 300px;
53
         width: auto;
80
         width: auto;
54
         @include border-radius(3px);
81
         @include border-radius(3px);
55
         background-color: $unsupportedBrowserButtonBgColor;
82
         background-color: $unsupportedBrowserButtonBgColor;
56
         color: #505F79;
83
         color: #505F79;
84
+        font-weight: bold;
57
 
85
 
58
         &:active {
86
         &:active {
59
             background-color: $unsupportedBrowserButtonBgColor;
87
             background-color: $unsupportedBrowserButtonBgColor;
69
         }
97
         }
70
     }
98
     }
71
 
99
 
72
-    .unsupported-dial-in {
100
+    .deep-linking-dial-in {
73
         display: none;
101
         display: none;
74
 
102
 
75
         &.has-numbers {
103
         &.has-numbers {

css/unsupported-browser/_no-mobile-app.scss → css/deep-linking/_no-mobile-app.scss View File


+ 1
- 0
css/main.scss View File

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

+ 0
- 2
css/unsupported-browser/_main.scss View File

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

BIN
images/logo-deep-linking.png View File


+ 3
- 1
interface_config.js View File

23
     SHOW_BRAND_WATERMARK: false,
23
     SHOW_BRAND_WATERMARK: false,
24
     BRAND_WATERMARK_LINK: '',
24
     BRAND_WATERMARK_LINK: '',
25
     SHOW_POWERED_BY: false,
25
     SHOW_POWERED_BY: false,
26
+    SHOW_DEEP_LINKING_IMAGE: false,
26
     GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
27
     GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
27
     DISPLAY_WELCOME_PAGE_CONTENT: true,
28
     DISPLAY_WELCOME_PAGE_CONTENT: true,
28
     APP_NAME: 'Jitsi Meet',
29
     APP_NAME: 'Jitsi Meet',
30
+    NATIVE_APP_NAME: 'Jitsi Meet',
29
     LANG_DETECTION: false, // Allow i18n to detect the system language
31
     LANG_DETECTION: false, // Allow i18n to detect the system language
30
     INVITATION_POWERED_BY: true,
32
     INVITATION_POWERED_BY: true,
31
 
33
 
161
     /**
163
     /**
162
      * Specify mobile app scheme for opening the app from the mobile browser.
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
 /* eslint-enable no-unused-vars, no-var, max-len */
169
 /* eslint-enable no-unused-vars, no-var, max-len */

+ 9
- 5
lang/main.json View File

107
         "shortcuts": "View shortcuts",
107
         "shortcuts": "View shortcuts",
108
         "speakerStats": "Speaker stats"
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
     "chat":{
110
     "chat":{
116
         "nickname": {
111
         "nickname": {
117
             "title": "Enter a nickname in the box below",
112
             "title": "Enter a nickname in the box below",
561
     },
556
     },
562
     "sectionList": {
557
     "sectionList": {
563
         "pullToRefresh": "Pull to refresh"
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 View File

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
-import { Platform } from '../base/react';
4
 import { toState } from '../base/redux';
3
 import { toState } from '../base/redux';
4
+import { getDeepLinkingPage } from '../deep-linking';
5
 import {
5
 import {
6
-    NoMobileApp,
7
     PluginRequiredBrowser,
6
     PluginRequiredBrowser,
8
-    UnsupportedDesktopBrowser,
9
-    UnsupportedMobileBrowser
7
+    UnsupportedDesktopBrowser
10
 } from '../unsupported-browser';
8
 } from '../unsupported-browser';
11
 
9
 
12
 import {
10
 import {
24
  *
22
  *
25
  * @private
23
  * @private
26
  * @param {Object} state - Object containing current redux state.
24
  * @param {Object} state - Object containing current redux state.
27
- * @returns {ReactElement|void}
25
+ * @returns {Promise<ReactElement>|void}
28
  * @type {Function[]}
26
  * @type {Function[]}
29
  */
27
  */
30
 const _INTERCEPT_COMPONENT_RULES = [
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
     state => {
30
     state => {
65
         const { webRTCReady } = state['features/base/lib-jitsi-meet'];
31
         const { webRTCReady } = state['features/base/lib-jitsi-meet'];
66
 
32
 
67
         switch (typeof webRTCReady) {
33
         switch (typeof webRTCReady) {
68
         case 'boolean':
34
         case 'boolean':
69
             if (webRTCReady === false) {
35
             if (webRTCReady === false) {
70
-                return UnsupportedDesktopBrowser;
36
+                return Promise.resolve(UnsupportedDesktopBrowser);
71
             }
37
             }
72
             break;
38
             break;
73
 
39
 
76
             break;
42
             break;
77
 
43
 
78
         default:
44
         default:
79
-            return PluginRequiredBrowser;
45
+            return Promise.resolve(PluginRequiredBrowser);
80
         }
46
         }
47
+
48
+        return Promise.resolve();
81
     }
49
     }
82
 ];
50
 ];
83
 
51
 
87
  *
55
  *
88
  * @param {(Object|Function)} stateOrGetState - The redux state or
56
  * @param {(Object|Function)} stateOrGetState - The redux state or
89
  * {@link getState} function.
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
     const route = _super_getRouteToRender(stateOrGetState);
61
     const route = _super_getRouteToRender(stateOrGetState);
94
 
62
 
95
     // Intercepts route components if any of component interceptor rules is
63
     // Intercepts route components if any of component interceptor rules is
96
     // satisfied.
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
  * {@link getState} function.
77
  * {@link getState} function.
107
  * @param {ReactElement} component - Current route component to render.
78
  * @param {ReactElement} component - Current route component to render.
108
  * @private
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
 function _interceptComponent(
83
 function _interceptComponent(
113
         stateOrGetState: Object | Function,
84
         stateOrGetState: Object | Function,
114
         component: React$Element<*>) {
85
         component: React$Element<*>) {
115
-    let result;
116
     const state = toState(stateOrGetState);
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 View File

77
 function _navigate({ getState }) {
77
 function _navigate({ getState }) {
78
     const state = getState();
78
     const state = getState();
79
     const { app } = state['features/app'];
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 View File


+ 2
- 0
react/features/base/react/components/web/index.js View File

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

+ 1
- 1
react/features/base/util/uri.js View File

27
  *
27
  *
28
  * FIXME: The URL class exposed by JavaScript will not include the colon in
28
  * FIXME: The URL class exposed by JavaScript will not include the colon in
29
  * the protocol field. Also in other places (at the time of this writing:
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
  * the double dots, so things are inconsistent.
31
  * the double dots, so things are inconsistent.
32
  *
32
  *
33
  * @type {string}
33
  * @type {string}

+ 1
- 1
react/features/conference/components/Conference.web.js View File

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

+ 20
- 0
react/features/deep-linking/actionTypes.js View File

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 View File

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 View File

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 View File

5
 import { connect } from 'react-redux';
5
 import { connect } from 'react-redux';
6
 
6
 
7
 import { translate, translateToHTML } from '../../base/i18n';
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
 import { DialInSummary } from '../../invite';
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
  * @private
19
  * @private
27
  * @type {string}
20
  * @type {string}
28
  */
21
  */
29
-const _TNS = 'unsupportedBrowser';
22
+const _SNS = 'deep-linking-mobile';
30
 
23
 
31
 /**
24
 /**
32
  * The map of platforms to URLs at which the mobile app for the associated
25
  * The map of platforms to URLs at which the mobile app for the associated
45
 /**
38
 /**
46
  * React component representing mobile browser page.
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
     state: Object;
44
     state: Object;
52
 
45
 
53
     /**
46
     /**
54
-     * UnsupportedMobileBrowser component's property types.
47
+     * DeepLinkingMobilePage component's property types.
55
      *
48
      *
56
      * @static
49
      * @static
57
      */
50
      */
77
      * @inheritdoc
70
      * @inheritdoc
78
      */
71
      */
79
     componentWillMount() {
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
         this.setState({
73
         this.setState({
93
-            joinURL
74
+            joinURL: generateDeepLinkingURL()
94
         });
75
         });
95
     }
76
     }
96
 
77
 
102
      */
83
      */
103
     render() {
84
     render() {
104
         const { _room, t } = this.props;
85
         const { _room, t } = this.props;
105
-
106
-        const openAppButtonClassName
86
+        const { NATIVE_APP_NAME, SHOW_DEEP_LINKING_IMAGE } = interfaceConfig;
87
+        const downloadButtonClassName
107
             = `${_SNS}__button ${_SNS}__button_primary`;
88
             = `${_SNS}__button ${_SNS}__button_primary`;
108
-        const appName
109
-            = interfaceConfig.ADD_PEOPLE_APP_NAME || interfaceConfig.APP_NAME;
110
 
89
 
111
         return (
90
         return (
112
             <div className = { _SNS }>
91
             <div className = { _SNS }>
113
-                <div className = { `${_SNS}__body` }>
92
+                <div className = 'header'>
114
                     <img
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
                     <p className = { `${_SNS}__text` }>
105
                     <p className = { `${_SNS}__text` }>
118
                         {
106
                         {
119
                             translateToHTML(
107
                             translateToHTML(
120
                                 t,
108
                                 t,
121
                                 `${_TNS}.appNotInstalled`,
109
                                 `${_TNS}.appNotInstalled`,
122
-                                { app: appName })
110
+                                { app: NATIVE_APP_NAME })
123
                         }
111
                         }
124
                     </p>
112
                     </p>
125
-                    <a href = { this.state.joinURL }>
126
-                        <button className = { openAppButtonClassName }>
127
-                            { t(`${_TNS}.openApp`,
128
-                                { app: appName }) }
129
-                        </button>
130
-                    </a>
131
                     <a href = { _URLS[Platform.OS] }>
113
                     <a href = { _URLS[Platform.OS] }>
132
-                        <button className = { `${_SNS}__button` }>
114
+                        <button className = { downloadButtonClassName }>
133
                             { t(`${_TNS}.downloadApp`) }
115
                             { t(`${_TNS}.downloadApp`) }
134
                         </button>
116
                         </button>
135
                     </a>
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
                 </div>
129
                 </div>
143
                 <HideNotificationBarStyle />
130
                 <HideNotificationBarStyle />
144
             </div>
131
             </div>
148
 
135
 
149
 /**
136
 /**
150
  * Maps (parts of) the Redux state to the associated props for the
137
  * Maps (parts of) the Redux state to the associated props for the
151
- * {@code UnsupportedMobileBrowser} component.
138
+ * {@code DeepLinkingMobilePage} component.
152
  *
139
  *
153
  * @param {Object} state - The Redux state.
140
  * @param {Object} state - The Redux state.
154
  * @private
141
  * @private
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 View File

2
 
2
 
3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
 
4
 
5
-import HideNotificationBarStyle from './HideNotificationBarStyle';
5
+import { HideNotificationBarStyle } from '../../base/react';
6
 
6
 
7
 declare var interfaceConfig: Object;
7
 declare var interfaceConfig: Object;
8
 
8
 
26
                     Video chat isn't available on mobile.
26
                     Video chat isn't available on mobile.
27
                 </h2>
27
                 </h2>
28
                 <p className = { `${ns}__description` }>
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
                 </p>
31
                 </p>
32
 
32
 
33
                 <HideNotificationBarStyle />
33
                 <HideNotificationBarStyle />

+ 3
- 0
react/features/deep-linking/components/index.js View File

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 View File

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 View File

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 View File

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

+ 23
- 0
react/features/deep-linking/middleware.js View File

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 View File

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 View File

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 View File

129
                     : null,
129
                     : null,
130
                 <NumbersList
130
                 <NumbersList
131
                     clickableNumbers = { this.props.clickableNumbers }
131
                     clickableNumbers = { this.props.clickableNumbers }
132
+                    conferenceID = { conferenceID }
132
                     key = 'numbers'
133
                     key = 'numbers'
133
                     numbers = { this.state.numbers } />
134
                     numbers = { this.state.numbers } />
134
             ];
135
             ];

+ 6
- 1
react/features/invite/components/dial-in-summary/NumbersList.web.js View File

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

+ 0
- 16
react/features/unsupported-browser/actionTypes.js View File

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 View File

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 View File

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

+ 0
- 5
react/features/unsupported-browser/components/index.js View File

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

+ 0
- 2
react/features/unsupported-browser/index.js View File

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

+ 0
- 27
react/features/unsupported-browser/reducer.js View File

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 View File

9
 import { initAnalytics } from '../../analytics';
9
 import { initAnalytics } from '../../analytics';
10
 import { translate } from '../../base/i18n';
10
 import { translate } from '../../base/i18n';
11
 import { isAnalyticsEnabled } from '../../base/lib-jitsi-meet';
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
 import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
14
 import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
16
 
15
 

Loading…
Cancel
Save