fix(android,back-button) rework back button handling on Android

We used to have a registry which registered a single handlerwith RN.
THis was registered really early in the app.

When React Navigation was introduced we ddidn't realize it interacts
with the back button. In a stack nagigator it will navigate to the
previous screen. This meant our back button handling was broken.

This commit removes our previous registry and uses the RN back button
handler directly in the 2 components that use it: the conference and
bottom sheets.

Since these handlers are registered after navigation, our handlers are
going to run first so we cna implement the behavior we need, namely to
dismiss an open botom sheet or set the conference in PiP mode.
This commit is contained in:
Saúl Ibarra Corretgé 2022-03-29 13:37:32 +02:00
parent d0836ff651
commit beb301e691
6 changed files with 8 additions and 119 deletions

View File

@ -2,7 +2,6 @@
import '../authentication/middleware';
import '../mobile/audio-mode/middleware';
import '../mobile/back-button/middleware';
import '../mobile/background/middleware';
import '../mobile/call-integration/middleware';
import '../mobile/external-api/middleware';

View File

@ -3,12 +3,12 @@
import React, { PureComponent, type Node } from 'react';
import {
Animated,
BackHandler,
Dimensions,
TouchableWithoutFeedback,
View
} from 'react-native';
import { BackButtonRegistry } from '../../../../mobile/back-button';
import { type StyleType } from '../../../styles';
import styles from './slidingviewstyles';
@ -121,7 +121,7 @@ export default class SlidingView extends PureComponent<Props, State> {
* @inheritdoc
*/
componentDidMount() {
BackButtonRegistry.addListener(this._onHardwareBackPress, true);
BackHandler.addEventListener('hardwareBackPress', this._onHardwareBackPress);
this._mounted = true;
this._setShow(this.props.show);
@ -146,7 +146,7 @@ export default class SlidingView extends PureComponent<Props, State> {
* @inheritdoc
*/
componentWillUnmount() {
BackButtonRegistry.removeListener(this._onHardwareBackPress);
BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
this._mounted = false;
}
@ -229,13 +229,9 @@ export default class SlidingView extends PureComponent<Props, State> {
* @returns {boolean}
*/
_onHardwareBackPress() {
const { onHide } = this.props;
this._onHide();
if (typeof onHide === 'function') {
return onHide();
}
return false;
return true;
}
_onHide: () => void;

View File

@ -1,7 +1,7 @@
// @flow
import React from 'react';
import { NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
import { BackHandler, NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import { appNavigate } from '../../../app/actions';
@ -22,7 +22,6 @@ import { CalleeInfoContainer } from '../../../invite';
import { LargeVideo } from '../../../large-video';
import { KnockingParticipantList } from '../../../lobby/components/native';
import { getIsLobbyVisible } from '../../../lobby/functions';
import { BackButtonRegistry } from '../../../mobile/back-button';
import { navigate }
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
@ -166,7 +165,7 @@ class Conference extends AbstractConference<Props, State> {
* @returns {void}
*/
componentDidMount() {
BackButtonRegistry.addListener(this._onHardwareBackPress);
BackHandler.addEventListener('hardwareBackPress', this._onHardwareBackPress);
}
/**
@ -196,7 +195,7 @@ class Conference extends AbstractConference<Props, State> {
*/
componentWillUnmount() {
// Tear handling any hardware button presses for back navigation down.
BackButtonRegistry.removeListener(this._onHardwareBackPress);
BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
clearTimeout(this._expandedLabelTimeout.current);
}

View File

@ -1,66 +0,0 @@
// @flow
/**
* An registry that dispatches hardware back button events for subscribers with a custom logic.
*/
class BackButtonRegistry {
_listeners: Array<Function>;
/**
* Instantiates a new instance of the registry.
*/
constructor() {
this._listeners = [];
}
/**
* Adds a listener to the registry.
*
* NOTE: Due to the different order of component mounts, we allow a component to register
* its listener to the top of the list, so then that will be invoked before other, 'non-top'
* listeners. For example a 'non-top' listener can be the one that puts the app into PiP mode,
* while a 'top' listener is the one that closes a modal in a conference.
*
* @param {Function} listener - The listener function.
* @param {boolean?} top - If true, the listener will be put on the top (eg for modal-like components).
* @returns {void}
*/
addListener(listener: Function, top: boolean = false) {
if (top) {
this._listeners.splice(0, 0, listener);
} else {
this._listeners.push(listener);
}
}
/**
* Removes a listener from the registry.
*
* @param {Function} listener - The listener to remove.
* @returns {void}
*/
removeListener(listener: Function) {
this._listeners = this._listeners.filter(f => f !== listener);
}
onHardwareBackPress: () => boolean;
/**
* Callback for the back button press event.
*
* @returns {boolean}
*/
onHardwareBackPress() {
for (const listener of this._listeners) {
const result = listener();
if (result === true) {
return true;
}
}
return false;
}
}
export default new BackButtonRegistry();

View File

@ -1,3 +0,0 @@
// @flow
export { default as BackButtonRegistry } from './BackButtonRegistry';

View File

@ -1,36 +0,0 @@
// @flow
import { BackHandler } from 'react-native';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
import { MiddlewareRegistry } from '../../base/redux';
import BackButtonRegistry from './BackButtonRegistry';
// Binding function to the proper instance, so then the event emitter won't replace the
// underlying instance.
BackButtonRegistry.onHardwareBackPress = BackButtonRegistry.onHardwareBackPress.bind(BackButtonRegistry);
/**
* Middleware that captures App lifetime actions and subscribes to application
* state changes. When the application state changes it will fire the action
* required to mute or unmute the local video in case the application goes to
* the background or comes back from it.
*
* @param {Store} store - The redux store.
* @returns {Function}
* @see {@link https://facebook.github.io/react-native/docs/appstate.html}
*/
MiddlewareRegistry.register(() => next => action => {
switch (action.type) {
case APP_WILL_MOUNT:
BackHandler.addEventListener('hardwareBackPress', BackButtonRegistry.onHardwareBackPress);
break;
case APP_WILL_UNMOUNT:
BackHandler.removeEventListener('hardwareBackPress', BackButtonRegistry.onHardwareBackPress);
break;
}
return next(action);
});