[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.
pull/2446/head
Saúl Ibarra Corretgé 7 years ago committed by Lyubo Marinov
parent 59d046dca9
commit 4757c1ebca
  1. 19
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java
  2. 36
      react/features/mobile/background/reducer.js
  3. 11
      react/features/mobile/full-screen/actionTypes.js
  4. 20
      react/features/mobile/full-screen/actions.js
  5. 1
      react/features/mobile/full-screen/index.js
  6. 89
      react/features/mobile/full-screen/middleware.js
  7. 21
      react/features/mobile/full-screen/reducer.js

@ -32,6 +32,7 @@ import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.LifecycleState; import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.rnimmersive.RNImmersiveModule;
import java.net.URL; import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
@ -414,6 +415,24 @@ public class JitsiMeetView extends FrameLayout {
loadURLObject(urlObject); loadURLObject(urlObject);
} }
/**
* Handler for focus changes which the window where this view is attached to
* is experiencing. Here we call into the Immersive mode plugin, so it
* triggers an event.
*
* @param hasFocus - Whether the window / view has focus or not.
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
RNImmersiveModule module = RNImmersiveModule.getInstance();
if (hasFocus && module != null) {
module.emitImmersiveStateChangeEvent();
}
}
/** /**
* Sets the default base {@code URL} used to join a conference when a * Sets the default base {@code URL} used to join a conference when a
* partial URL (e.g. a room name only) is specified to * partial URL (e.g. a room name only) is specified to

@ -5,20 +5,26 @@ import {
APP_STATE_CHANGED APP_STATE_CHANGED
} from './actionTypes'; } from './actionTypes';
ReducerRegistry.register('features/background', (state = {}, action) => { const INITIAL_STATE = {
switch (action.type) { appState: 'active'
case _SET_APP_STATE_LISTENER: };
return {
...state,
appStateListener: action.listener
};
case APP_STATE_CHANGED: ReducerRegistry.register(
return { 'features/background',
...state, (state = INITIAL_STATE, action) => {
appState: action.appState switch (action.type) {
}; case _SET_APP_STATE_LISTENER:
} return {
...state,
appStateListener: action.listener
};
return state; case APP_STATE_CHANGED:
}); return {
...state,
appState: action.appState
};
}
return state;
});

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

@ -0,0 +1,20 @@
// @flow
import { _SET_IMMERSIVE_LISTENER } from './actionTypes';
/**
* Sets the listener to be used with React Native's Immersive API.
*
* @param {Function} listener - Function to be set as the change event listener.
* @protected
* @returns {{
* type: _SET_IMMERSIVE_LISTENER,
* listener: Function
* }}
*/
export function _setImmersiveListener(listener: ?Function) {
return {
type: _SET_IMMERSIVE_LISTENER,
listener
};
}

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

@ -1,20 +1,22 @@
/* @flow */ // @flow
import { StatusBar } from 'react-native'; import { StatusBar } from 'react-native';
import { Immersive } from 'react-native-immersive'; import { Immersive } from 'react-native-immersive';
import { APP_STATE_CHANGED } from '../background'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
import { import {
CONFERENCE_FAILED, CONFERENCE_FAILED,
CONFERENCE_JOINED,
CONFERENCE_LEFT, CONFERENCE_LEFT,
CONFERENCE_WILL_JOIN, CONFERENCE_WILL_JOIN,
SET_AUDIO_ONLY SET_AUDIO_ONLY
} from '../../base/conference'; } from '../../base/conference';
import { HIDE_DIALOG } from '../../base/dialog';
import { Platform } from '../../base/react'; import { Platform } from '../../base/react';
import { SET_REDUCED_UI } from '../../base/responsive-ui';
import { MiddlewareRegistry } from '../../base/redux'; import { MiddlewareRegistry } from '../../base/redux';
import { _setImmersiveListener } from './actions';
import { _SET_IMMERSIVE_LISTENER } from './actionTypes';
/** /**
* Middleware that captures conference actions and activates or deactivates the * Middleware that captures conference actions and activates or deactivates the
* full screen mode. On iOS it hides the status bar, and on Android it uses the * 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';
* @param {Store} store - The redux store. * @param {Store} store - The redux store.
* @returns {Function} * @returns {Function}
*/ */
MiddlewareRegistry.register(({ getState }) => next => action => { MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const result = next(action); const result = next(action);
let fullScreen = null; let fullScreen = null;
switch (action.type) { switch (action.type) {
case APP_STATE_CHANGED: case _SET_IMMERSIVE_LISTENER:
case CONFERENCE_WILL_JOIN: // XXX The React Native module Immersive is only implemented on Android
case HIDE_DIALOG: // and throws on other platforms.
case SET_AUDIO_ONLY: if (Platform.OS === 'android') {
case SET_REDUCED_UI: { // Remove the current/old Immersive listener.
// FIXME: Simplify this by listening to Immediate events. const { listener } = getState()['features/full-screen'];
// Check if we just came back from the background and re-enable full
// screen mode if necessary. listener && Immersive.removeImmersiveListener(listener);
const { appState } = action;
// Add the new listener.
if (typeof appState !== 'undefined' && appState !== 'active') { action.listener && Immersive.addImmersiveListener(action.listener);
break;
} }
break;
case APP_WILL_MOUNT: {
const context = {
dispatch,
getState
};
dispatch(
_setImmersiveListener(_onImmersiveChange.bind(undefined, context)));
break;
}
case APP_WILL_UNMOUNT:
_setImmersiveListener(undefined);
break;
case CONFERENCE_WILL_JOIN:
case CONFERENCE_JOINED:
case SET_AUDIO_ONLY: {
const { audioOnly, conference, joining } const { audioOnly, conference, joining }
= getState()['features/base/conference']; = getState()['features/base/conference'];
@ -59,15 +78,33 @@ MiddlewareRegistry.register(({ getState }) => next => action => {
break; break;
} }
if (fullScreen !== null) { fullScreen !== null && _setFullScreen(fullScreen);
_setFullScreen(fullScreen)
.catch(err =>
console.warn(`Failed to set full screen mode: ${err}`));
}
return result; return result;
}); });
/**
* Handler for Immersive mode changes. This will be called when Android's
* immersive mode changes. This can happen without us wanting, so re-evaluate if
* immersive mode is desired and reactivate it if needed.
*
* @param {Object} store - The redux store.
* @private
* @returns {void}
*/
function _onImmersiveChange({ getState }) {
const state = getState();
const { appState } = state['features/background'];
if (appState === 'active') {
const { audioOnly, conference, joining }
= state['features/base/conference'];
const fullScreen = conference || joining ? !audioOnly : false;
_setFullScreen(fullScreen);
}
}
/** /**
* Activates/deactivates the full screen mode. On iOS it will hide the status * Activates/deactivates the full screen mode. On iOS it will hide the status
* bar, and on Android it will turn immersive mode on. * bar, and on Android it will turn immersive mode on.
@ -75,18 +112,18 @@ MiddlewareRegistry.register(({ getState }) => next => action => {
* @param {boolean} fullScreen - True to set full screen mode, false to * @param {boolean} fullScreen - True to set full screen mode, false to
* deactivate it. * deactivate it.
* @private * @private
* @returns {Promise} * @returns {void}
*/ */
function _setFullScreen(fullScreen: boolean) { function _setFullScreen(fullScreen: boolean) {
// XXX The React Native module Immersive is only implemented on Android and // XXX The React Native module Immersive is only implemented on Android and
// throws on other platforms. // throws on other platforms.
if (Platform.OS === 'android') { if (Platform.OS === 'android') {
return fullScreen ? Immersive.on() : Immersive.off(); fullScreen ? Immersive.on() : Immersive.off();
return;
} }
// On platforms other than Android go with whatever React Native itself // On platforms other than Android go with whatever React Native itself
// supports. // supports.
StatusBar.setHidden(fullScreen, 'slide'); StatusBar.setHidden(fullScreen, 'slide');
return Promise.resolve();
} }

@ -0,0 +1,21 @@
import { ReducerRegistry } from '../../base/redux';
import { _SET_IMMERSIVE_LISTENER } from './actionTypes';
const INITIAL_STATE = {
listener: undefined
};
ReducerRegistry.register(
'features/full-screen',
(state = INITIAL_STATE, action) => {
switch (action.type) {
case _SET_IMMERSIVE_LISTENER:
return {
...state,
listener: action.listener
};
}
return state;
});
Loading…
Cancel
Save