[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.
This commit is contained in:
parent
59d046dca9
commit
4757c1ebca
|
@ -32,6 +32,7 @@ import com.facebook.react.bridge.NativeModule;
|
|||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
|
@ -414,6 +415,24 @@ public class JitsiMeetView extends FrameLayout {
|
|||
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
|
||||
* partial URL (e.g. a room name only) is specified to
|
||||
|
|
|
@ -5,20 +5,26 @@ import {
|
|||
APP_STATE_CHANGED
|
||||
} from './actionTypes';
|
||||
|
||||
ReducerRegistry.register('features/background', (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case _SET_APP_STATE_LISTENER:
|
||||
return {
|
||||
...state,
|
||||
appStateListener: action.listener
|
||||
};
|
||||
const INITIAL_STATE = {
|
||||
appState: 'active'
|
||||
};
|
||||
|
||||
case APP_STATE_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
appState: action.appState
|
||||
};
|
||||
}
|
||||
ReducerRegistry.register(
|
||||
'features/background',
|
||||
(state = INITIAL_STATE, action) => {
|
||||
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 './reducer';
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
/* @flow */
|
||||
// @flow
|
||||
|
||||
import { StatusBar } from 'react-native';
|
||||
import { Immersive } from 'react-native-immersive';
|
||||
|
||||
import { APP_STATE_CHANGED } from '../background';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
SET_AUDIO_ONLY
|
||||
} from '../../base/conference';
|
||||
import { HIDE_DIALOG } from '../../base/dialog';
|
||||
import { Platform } from '../../base/react';
|
||||
import { SET_REDUCED_UI } from '../../base/responsive-ui';
|
||||
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
|
||||
* 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.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(({ getState }) => next => action => {
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
let fullScreen = null;
|
||||
|
||||
switch (action.type) {
|
||||
case APP_STATE_CHANGED:
|
||||
case CONFERENCE_WILL_JOIN:
|
||||
case HIDE_DIALOG:
|
||||
case SET_AUDIO_ONLY:
|
||||
case SET_REDUCED_UI: {
|
||||
// FIXME: Simplify this by listening to Immediate events.
|
||||
// Check if we just came back from the background and re-enable full
|
||||
// screen mode if necessary.
|
||||
const { appState } = action;
|
||||
case _SET_IMMERSIVE_LISTENER:
|
||||
// XXX The React Native module Immersive is only implemented on Android
|
||||
// and throws on other platforms.
|
||||
if (Platform.OS === 'android') {
|
||||
// Remove the current/old Immersive listener.
|
||||
const { listener } = getState()['features/full-screen'];
|
||||
|
||||
if (typeof appState !== 'undefined' && appState !== 'active') {
|
||||
break;
|
||||
listener && Immersive.removeImmersiveListener(listener);
|
||||
|
||||
// Add the new listener.
|
||||
action.listener && Immersive.addImmersiveListener(action.listener);
|
||||
}
|
||||
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 }
|
||||
= getState()['features/base/conference'];
|
||||
|
||||
|
@ -59,15 +78,33 @@ MiddlewareRegistry.register(({ getState }) => next => action => {
|
|||
break;
|
||||
}
|
||||
|
||||
if (fullScreen !== null) {
|
||||
_setFullScreen(fullScreen)
|
||||
.catch(err =>
|
||||
console.warn(`Failed to set full screen mode: ${err}`));
|
||||
}
|
||||
fullScreen !== null && _setFullScreen(fullScreen);
|
||||
|
||||
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
|
||||
* 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
|
||||
* deactivate it.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
* @returns {void}
|
||||
*/
|
||||
function _setFullScreen(fullScreen: boolean) {
|
||||
// XXX The React Native module Immersive is only implemented on Android and
|
||||
// throws on other platforms.
|
||||
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
|
||||
// supports.
|
||||
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…
Reference in New Issue