Browse Source

[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.
master
Saúl Ibarra Corretgé 7 years ago
parent
commit
4757c1ebca

+ 19
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java View File

32
 import com.facebook.react.bridge.ReactApplicationContext;
32
 import com.facebook.react.bridge.ReactApplicationContext;
33
 import com.facebook.react.common.LifecycleState;
33
 import com.facebook.react.common.LifecycleState;
34
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
34
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
35
+import com.rnimmersive.RNImmersiveModule;
35
 
36
 
36
 import java.net.URL;
37
 import java.net.URL;
37
 import java.util.Arrays;
38
 import java.util.Arrays;
414
         loadURLObject(urlObject);
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
      * Sets the default base {@code URL} used to join a conference when a
437
      * Sets the default base {@code URL} used to join a conference when a
419
      * partial URL (e.g. a room name only) is specified to
438
      * partial URL (e.g. a room name only) is specified to

+ 21
- 15
react/features/mobile/background/reducer.js View File

5
     APP_STATE_CHANGED
5
     APP_STATE_CHANGED
6
 } from './actionTypes';
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 View File

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

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

1
 import './middleware';
1
 import './middleware';
2
+import './reducer';

+ 63
- 26
react/features/mobile/full-screen/middleware.js View File

1
-/* @flow */
1
+// @flow
2
 
2
 
3
 import { StatusBar } from 'react-native';
3
 import { StatusBar } from 'react-native';
4
 import { Immersive } from 'react-native-immersive';
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
 import {
7
 import {
8
     CONFERENCE_FAILED,
8
     CONFERENCE_FAILED,
9
+    CONFERENCE_JOINED,
9
     CONFERENCE_LEFT,
10
     CONFERENCE_LEFT,
10
     CONFERENCE_WILL_JOIN,
11
     CONFERENCE_WILL_JOIN,
11
     SET_AUDIO_ONLY
12
     SET_AUDIO_ONLY
12
 } from '../../base/conference';
13
 } from '../../base/conference';
13
-import { HIDE_DIALOG } from '../../base/dialog';
14
 import { Platform } from '../../base/react';
14
 import { Platform } from '../../base/react';
15
-import { SET_REDUCED_UI } from '../../base/responsive-ui';
16
 import { MiddlewareRegistry } from '../../base/redux';
15
 import { MiddlewareRegistry } from '../../base/redux';
17
 
16
 
17
+import { _setImmersiveListener } from './actions';
18
+import { _SET_IMMERSIVE_LISTENER } from './actionTypes';
19
+
18
 /**
20
 /**
19
  * Middleware that captures conference actions and activates or deactivates the
21
  * Middleware that captures conference actions and activates or deactivates the
20
  * full screen mode. On iOS it hides the status bar, and on Android it uses the
22
  * full screen mode. On iOS it hides the status bar, and on Android it uses the
26
  * @param {Store} store - The redux store.
28
  * @param {Store} store - The redux store.
27
  * @returns {Function}
29
  * @returns {Function}
28
  */
30
  */
29
-MiddlewareRegistry.register(({ getState }) => next => action => {
31
+MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
30
     const result = next(action);
32
     const result = next(action);
31
 
33
 
32
     let fullScreen = null;
34
     let fullScreen = null;
33
 
35
 
34
     switch (action.type) {
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
         const { audioOnly, conference, joining }
68
         const { audioOnly, conference, joining }
50
             = getState()['features/base/conference'];
69
             = getState()['features/base/conference'];
51
 
70
 
59
         break;
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
     return result;
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
  * Activates/deactivates the full screen mode. On iOS it will hide the status
109
  * Activates/deactivates the full screen mode. On iOS it will hide the status
73
  * bar, and on Android it will turn immersive mode on.
110
  * bar, and on Android it will turn immersive mode on.
75
  * @param {boolean} fullScreen - True to set full screen mode, false to
112
  * @param {boolean} fullScreen - True to set full screen mode, false to
76
  * deactivate it.
113
  * deactivate it.
77
  * @private
114
  * @private
78
- * @returns {Promise}
115
+ * @returns {void}
79
  */
116
  */
80
 function _setFullScreen(fullScreen: boolean) {
117
 function _setFullScreen(fullScreen: boolean) {
81
     // XXX The React Native module Immersive is only implemented on Android and
118
     // XXX The React Native module Immersive is only implemented on Android and
82
     // throws on other platforms.
119
     // throws on other platforms.
83
     if (Platform.OS === 'android') {
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
     // On platforms other than Android go with whatever React Native itself
126
     // On platforms other than Android go with whatever React Native itself
88
     // supports.
127
     // supports.
89
     StatusBar.setHidden(fullScreen, 'slide');
128
     StatusBar.setHidden(fullScreen, 'slide');
90
-
91
-    return Promise.resolve();
92
 }
129
 }

+ 21
- 0
react/features/mobile/full-screen/reducer.js View File

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…
Cancel
Save