瀏覽代碼

[RN] Make full-screen more resilient on Android

On Android we go into "immersive mode" when in a conference, this is our way of
being full-creen. There are occasions, however, in which Android takes us out of
immerfive mode without us (the application / SDK) knowing: when a child activity
is started, a modal window shown, etc.

In order to be resilient to any possible change in the immersive mode, register
a listener which will be called when Android changes it, so we can re-eavluate
if we need it and thus re-enable it.
j8
Saúl Ibarra Corretgé 7 年之前
父節點
當前提交
4757c1ebca

+ 19
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java 查看文件

@@ -32,6 +32,7 @@ import com.facebook.react.bridge.NativeModule;
32 32
 import com.facebook.react.bridge.ReactApplicationContext;
33 33
 import com.facebook.react.common.LifecycleState;
34 34
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
35
+import com.rnimmersive.RNImmersiveModule;
35 36
 
36 37
 import java.net.URL;
37 38
 import java.util.Arrays;
@@ -414,6 +415,24 @@ public class JitsiMeetView extends FrameLayout {
414 415
         loadURLObject(urlObject);
415 416
     }
416 417
 
418
+    /**
419
+     * Handler for focus changes which the window where this view is attached to
420
+     * is experiencing. Here we call into the Immersive mode plugin, so it
421
+     * triggers an event.
422
+     *
423
+     * @param hasFocus - Whether the window / view has focus or not.
424
+     */
425
+    @Override
426
+    public void onWindowFocusChanged(boolean hasFocus) {
427
+        super.onWindowFocusChanged(hasFocus);
428
+
429
+        RNImmersiveModule module = RNImmersiveModule.getInstance();
430
+
431
+        if (hasFocus && module != null) {
432
+            module.emitImmersiveStateChangeEvent();
433
+        }
434
+    }
435
+
417 436
     /**
418 437
      * Sets the default base {@code URL} used to join a conference when a
419 438
      * partial URL (e.g. a room name only) is specified to

+ 21
- 15
react/features/mobile/background/reducer.js 查看文件

@@ -5,20 +5,26 @@ import {
5 5
     APP_STATE_CHANGED
6 6
 } from './actionTypes';
7 7
 
8
-ReducerRegistry.register('features/background', (state = {}, action) => {
9
-    switch (action.type) {
10
-    case _SET_APP_STATE_LISTENER:
11
-        return {
12
-            ...state,
13
-            appStateListener: action.listener
14
-        };
8
+const INITIAL_STATE = {
9
+    appState: 'active'
10
+};
15 11
 
16
-    case APP_STATE_CHANGED:
17
-        return {
18
-            ...state,
19
-            appState: action.appState
20
-        };
21
-    }
12
+ReducerRegistry.register(
13
+    'features/background',
14
+    (state = INITIAL_STATE, action) => {
15
+        switch (action.type) {
16
+        case _SET_APP_STATE_LISTENER:
17
+            return {
18
+                ...state,
19
+                appStateListener: action.listener
20
+            };
22 21
 
23
-    return state;
24
-});
22
+        case APP_STATE_CHANGED:
23
+            return {
24
+                ...state,
25
+                appState: action.appState
26
+            };
27
+        }
28
+
29
+        return state;
30
+    });

+ 11
- 0
react/features/mobile/full-screen/actionTypes.js 查看文件

@@ -0,0 +1,11 @@
1
+/**
2
+ * The type of redux action to set the Immersive change event listener.
3
+ *
4
+ * {
5
+ *     type: _SET_IMMERSIVE_LISTENER,
6
+ *     listener: Function
7
+ * }
8
+ *
9
+ * @protected
10
+ */
11
+export const _SET_IMMERSIVE_LISTENER = Symbol('_SET_IMMERSIVE_LISTENER');

+ 20
- 0
react/features/mobile/full-screen/actions.js 查看文件

@@ -0,0 +1,20 @@
1
+// @flow
2
+
3
+import { _SET_IMMERSIVE_LISTENER } from './actionTypes';
4
+
5
+/**
6
+ * Sets the listener to be used with React Native's Immersive API.
7
+ *
8
+ * @param {Function} listener - Function to be set as the change event listener.
9
+ * @protected
10
+ * @returns {{
11
+ *     type: _SET_IMMERSIVE_LISTENER,
12
+ *     listener: Function
13
+ * }}
14
+ */
15
+export function _setImmersiveListener(listener: ?Function) {
16
+    return {
17
+        type: _SET_IMMERSIVE_LISTENER,
18
+        listener
19
+    };
20
+}

+ 1
- 0
react/features/mobile/full-screen/index.js 查看文件

@@ -1 +1,2 @@
1 1
 import './middleware';
2
+import './reducer';

+ 63
- 26
react/features/mobile/full-screen/middleware.js 查看文件

@@ -1,20 +1,22 @@
1
-/* @flow */
1
+// @flow
2 2
 
3 3
 import { StatusBar } from 'react-native';
4 4
 import { Immersive } from 'react-native-immersive';
5 5
 
6
-import { APP_STATE_CHANGED } from '../background';
6
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
7 7
 import {
8 8
     CONFERENCE_FAILED,
9
+    CONFERENCE_JOINED,
9 10
     CONFERENCE_LEFT,
10 11
     CONFERENCE_WILL_JOIN,
11 12
     SET_AUDIO_ONLY
12 13
 } from '../../base/conference';
13
-import { HIDE_DIALOG } from '../../base/dialog';
14 14
 import { Platform } from '../../base/react';
15
-import { SET_REDUCED_UI } from '../../base/responsive-ui';
16 15
 import { MiddlewareRegistry } from '../../base/redux';
17 16
 
17
+import { _setImmersiveListener } from './actions';
18
+import { _SET_IMMERSIVE_LISTENER } from './actionTypes';
19
+
18 20
 /**
19 21
  * Middleware that captures conference actions and activates or deactivates the
20 22
  * full screen mode. On iOS it hides the status bar, and on Android it uses the
@@ -26,26 +28,43 @@ import { MiddlewareRegistry } from '../../base/redux';
26 28
  * @param {Store} store - The redux store.
27 29
  * @returns {Function}
28 30
  */
29
-MiddlewareRegistry.register(({ getState }) => next => action => {
31
+MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
30 32
     const result = next(action);
31 33
 
32 34
     let fullScreen = null;
33 35
 
34 36
     switch (action.type) {
35
-    case APP_STATE_CHANGED:
36
-    case CONFERENCE_WILL_JOIN:
37
-    case HIDE_DIALOG:
38
-    case SET_AUDIO_ONLY:
39
-    case SET_REDUCED_UI: {
40
-        // FIXME: Simplify this by listening to Immediate events.
41
-        // Check if we just came back from the background and re-enable full
42
-        // screen mode if necessary.
43
-        const { appState } = action;
44
-
45
-        if (typeof appState !== 'undefined' && appState !== 'active') {
46
-            break;
37
+    case _SET_IMMERSIVE_LISTENER:
38
+        // XXX The React Native module Immersive is only implemented on Android
39
+        // and throws on other platforms.
40
+        if (Platform.OS === 'android') {
41
+            // Remove the current/old Immersive listener.
42
+            const { listener } = getState()['features/full-screen'];
43
+
44
+            listener && Immersive.removeImmersiveListener(listener);
45
+
46
+            // Add the new listener.
47
+            action.listener && Immersive.addImmersiveListener(action.listener);
47 48
         }
49
+        break;
50
+
51
+    case APP_WILL_MOUNT: {
52
+        const context = {
53
+            dispatch,
54
+            getState
55
+        };
48 56
 
57
+        dispatch(
58
+            _setImmersiveListener(_onImmersiveChange.bind(undefined, context)));
59
+        break;
60
+    }
61
+    case APP_WILL_UNMOUNT:
62
+        _setImmersiveListener(undefined);
63
+        break;
64
+
65
+    case CONFERENCE_WILL_JOIN:
66
+    case CONFERENCE_JOINED:
67
+    case SET_AUDIO_ONLY: {
49 68
         const { audioOnly, conference, joining }
50 69
             = getState()['features/base/conference'];
51 70
 
@@ -59,15 +78,33 @@ MiddlewareRegistry.register(({ getState }) => next => action => {
59 78
         break;
60 79
     }
61 80
 
62
-    if (fullScreen !== null) {
63
-        _setFullScreen(fullScreen)
64
-            .catch(err =>
65
-                console.warn(`Failed to set full screen mode: ${err}`));
66
-    }
81
+    fullScreen !== null && _setFullScreen(fullScreen);
67 82
 
68 83
     return result;
69 84
 });
70 85
 
86
+/**
87
+ * Handler for Immersive mode changes. This will be called when Android's
88
+ * immersive mode changes. This can happen without us wanting, so re-evaluate if
89
+ * immersive mode is desired and reactivate it if needed.
90
+ *
91
+ * @param {Object} store - The redux store.
92
+ * @private
93
+ * @returns {void}
94
+ */
95
+function _onImmersiveChange({ getState }) {
96
+    const state = getState();
97
+    const { appState } = state['features/background'];
98
+
99
+    if (appState === 'active') {
100
+        const { audioOnly, conference, joining }
101
+            = state['features/base/conference'];
102
+        const fullScreen = conference || joining ? !audioOnly : false;
103
+
104
+        _setFullScreen(fullScreen);
105
+    }
106
+}
107
+
71 108
 /**
72 109
  * Activates/deactivates the full screen mode. On iOS it will hide the status
73 110
  * bar, and on Android it will turn immersive mode on.
@@ -75,18 +112,18 @@ MiddlewareRegistry.register(({ getState }) => next => action => {
75 112
  * @param {boolean} fullScreen - True to set full screen mode, false to
76 113
  * deactivate it.
77 114
  * @private
78
- * @returns {Promise}
115
+ * @returns {void}
79 116
  */
80 117
 function _setFullScreen(fullScreen: boolean) {
81 118
     // XXX The React Native module Immersive is only implemented on Android and
82 119
     // throws on other platforms.
83 120
     if (Platform.OS === 'android') {
84
-        return fullScreen ? Immersive.on() : Immersive.off();
121
+        fullScreen ? Immersive.on() : Immersive.off();
122
+
123
+        return;
85 124
     }
86 125
 
87 126
     // On platforms other than Android go with whatever React Native itself
88 127
     // supports.
89 128
     StatusBar.setHidden(fullScreen, 'slide');
90
-
91
-    return Promise.resolve();
92 129
 }

+ 21
- 0
react/features/mobile/full-screen/reducer.js 查看文件

@@ -0,0 +1,21 @@
1
+import { ReducerRegistry } from '../../base/redux';
2
+
3
+import { _SET_IMMERSIVE_LISTENER } from './actionTypes';
4
+
5
+const INITIAL_STATE = {
6
+    listener: undefined
7
+};
8
+
9
+ReducerRegistry.register(
10
+    'features/full-screen',
11
+    (state = INITIAL_STATE, action) => {
12
+        switch (action.type) {
13
+        case _SET_IMMERSIVE_LISTENER:
14
+            return {
15
+                ...state,
16
+                listener: action.listener
17
+            };
18
+        }
19
+
20
+        return state;
21
+    });

Loading…
取消
儲存