[Android] Hardware back in Conference and Dialog

This commit is contained in:
Lyubo Marinov 2017-09-25 12:26:15 -05:00
parent 46b75e5178
commit 03d337612b
2 changed files with 115 additions and 27 deletions

View File

@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { StyleSheet, TextInput } from 'react-native'; import { Modal, StyleSheet, TextInput } from 'react-native';
import Prompt from 'react-native-prompt'; import Prompt from 'react-native-prompt';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -99,13 +99,33 @@ class Dialog extends AbstractDialog {
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
element = this._mapReactElement(element, element => { element = this._mapReactElement(element, element => {
// * If this Dialog has children, they are to be rendered instead of const { type } = element;
// Prompt's TextInput.
if (element.type === TextInput) { if (type === Modal) {
// * Modal handles hardware button presses for back navigation.
// Firstly, we don't want Prompt's default behavior to merely
// hide the Modal - we want this Dialog to be canceled.
// Secondly, we cannot get Prompt's default behavior anyway
// because we've removed Prompt and we're preserving whatever
// it's rendered only.
return (
React.cloneElement(
element,
/* props */ {
onRequestClose: this._onCancel
},
...React.Children.toArray(element.props.children))
);
}
if (type === TextInput) {
// * If this Dialog has children, they are to be rendered
// instead of Prompt's TextInput.
if (children) { if (children) {
element = children; // eslint-disable-line no-param-reassign element = children; // eslint-disable-line no-param-reassign
children = undefined; children = undefined;
} }
} else { } else {
let { style } = element.props; let { style } = element.props;
@ -126,14 +146,14 @@ class Dialog extends AbstractDialog {
break; break;
} }
// eslint-disable-next-line no-param-reassign return (
element React.cloneElement(
= React.cloneElement(
element, element,
/* props */ { /* props */ {
style: set(style, _TAG_KEY, undefined) style: set(style, _TAG_KEY, undefined)
}, },
...React.Children.toArray(element.props.children)); ...React.Children.toArray(element.props.children))
);
} }
} }
@ -162,18 +182,22 @@ class Dialog extends AbstractDialog {
let mapped = f(element); let mapped = f(element);
if (mapped === element) { if (mapped) {
mapped const { children } = mapped.props;
= React.cloneElement(
element, if (mapped === element || React.Children.count(children)) {
/* props */ undefined, mapped
...React.Children.toArray(React.Children.map( = React.cloneElement(
element.props.children, mapped,
function(element) { // eslint-disable-line no-shadow /* props */ undefined,
// eslint-disable-next-line no-invalid-this ...React.Children.toArray(React.Children.map(
return this._mapReactElement(element, f); children,
}, function(element) { // eslint-disable-line no-shadow
this))); // eslint-disable-next-line no-invalid-this
return this._mapReactElement(element, f);
},
this)));
}
} }
return mapped; return mapped;

View File

@ -1,8 +1,11 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { View } from 'react-native';
// eslint-disable-next-line react-native/split-platform-components
import { BackAndroid, BackHandler, View } from 'react-native';
import { connect as reactReduxConnect } from 'react-redux'; import { connect as reactReduxConnect } from 'react-redux';
import { appNavigate } from '../../app';
import { connect, disconnect } from '../../base/connection'; import { connect, disconnect } from '../../base/connection';
import { DialogContainer } from '../../base/dialog'; import { DialogContainer } from '../../base/dialog';
import { Container, LoadingIndicator } from '../../base/react'; import { Container, LoadingIndicator } from '../../base/react';
@ -56,6 +59,17 @@ class Conference extends Component {
*/ */
_onDisconnect: PropTypes.func, _onDisconnect: PropTypes.func,
/**
* Handles a hardware button press for back navigation. Leaves the
* associated <tt>Conference</tt>.
*
* @private
* @returns {boolean} As the associated conference is unconditionally
* left and exiting the app while it renders a <tt>Conference</tt> is
* undesired, <tt>true</tt> is always returned.
*/
_onHardwareBackPress: PropTypes.func,
/** /**
* The handler which dispatches the (redux) action setToolboxVisible to * The handler which dispatches the (redux) action setToolboxVisible to
* show/hide the Toolbox. * show/hide the Toolbox.
@ -90,22 +104,36 @@ class Conference extends Component {
*/ */
this._toolboxTimeout = undefined; this._toolboxTimeout = undefined;
// Bind event handlers so they are only bound once for every instance. // Bind event handlers so they are only bound once per instance.
this._onClick = this._onClick.bind(this); this._onClick = this._onClick.bind(this);
this._onHardwareBackPress = this._onHardwareBackPress.bind(this);
} }
/** /**
* Inits the Toolbox timeout after the component is initially rendered. * Implements {@link Component#componentDidMount()}. Invoked immediately
* after this component is mounted.
* *
* @inheritdoc * @inheritdoc
* returns {void} * returns {void}
*/ */
componentDidMount() { componentDidMount() {
// Set handling any hardware button presses for back navigation up.
const backHandler = BackHandler || BackAndroid;
if (backHandler) {
this._backHandler = backHandler;
backHandler.addEventListener(
'hardwareBackPress',
this._onHardwareBackPress);
}
this._setToolboxTimeout(this.props._toolboxVisible); this._setToolboxTimeout(this.props._toolboxVisible);
} }
/** /**
* Inits new connection and conference when conference screen is entered. * Implements {@link Component#componentWillMount()}. Invoked immediately
* before mounting occurs. Connects the conference described by the redux
* store/state.
* *
* @inheritdoc * @inheritdoc
* @returns {void} * @returns {void}
@ -115,13 +143,24 @@ class Conference extends Component {
} }
/** /**
* Destroys connection, conference and local tracks when conference screen * Implements {@link Component#componentWillUnmount()}. Invoked immediately
* is left. Clears {@link #_toolboxTimeout} before the component unmounts. * before this component is unmounted and destroyed. Disconnects the
* conference described by the redux store/state.
* *
* @inheritdoc * @inheritdoc
* @returns {void} * @returns {void}
*/ */
componentWillUnmount() { componentWillUnmount() {
// Tear handling any hardware button presses for back navigation down.
const backHandler = this._backHandler;
if (backHandler) {
this._backHandler = undefined;
backHandler.removeEventListener(
'hardwareBackPress',
this._onHardwareBackPress);
}
this._clearToolboxTimeout(); this._clearToolboxTimeout();
this.props._onDisconnect(); this.props._onDisconnect();
@ -210,6 +249,17 @@ class Conference extends Component {
this._setToolboxTimeout(toolboxVisible); this._setToolboxTimeout(toolboxVisible);
} }
/**
* Handles a hardware button press for back navigation.
*
* @returns {boolean} If the hardware button press for back navigation was
* handled by this <tt>Conference</tt>, then <tt>true</tt>; otherwise,
* <tt>false</tt>.
*/
_onHardwareBackPress() {
return this._backHandler && this.props._onHardwareBackPress();
}
/** /**
* Triggers the default Toolbox timeout. * Triggers the default Toolbox timeout.
* *
@ -263,7 +313,21 @@ function _mapDispatchToProps(dispatch) {
}, },
/** /**
* Dispatches an action changing the visiblity of the Toolbox. * Handles a hardware button press for back navigation. Leaves the
* associated <tt>Conference</tt>.
*
* @returns {boolean} As the associated conference is unconditionally
* left and exiting the app while it renders a <tt>Conference</tt> is
* undesired, <tt>true</tt> is always returned.
*/
_onHardwareBackPress() {
dispatch(appNavigate(undefined));
return true;
},
/**
* Dispatches an action changing the visibility of the Toolbox.
* *
* @param {boolean} visible - True to show the Toolbox or false to hide * @param {boolean} visible - True to show the Toolbox or false to hide
* it. * it.