[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 React from 'react';
import { StyleSheet, TextInput } from 'react-native';
import { Modal, StyleSheet, TextInput } from 'react-native';
import Prompt from 'react-native-prompt';
import { connect } from 'react-redux';
@ -99,13 +99,33 @@ class Dialog extends AbstractDialog {
// eslint-disable-next-line no-shadow
element = this._mapReactElement(element, element => {
// * If this Dialog has children, they are to be rendered instead of
// Prompt's TextInput.
if (element.type === TextInput) {
const { type } = element;
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) {
element = children; // eslint-disable-line no-param-reassign
children = undefined;
}
} else {
let { style } = element.props;
@ -126,14 +146,14 @@ class Dialog extends AbstractDialog {
break;
}
// eslint-disable-next-line no-param-reassign
element
= React.cloneElement(
return (
React.cloneElement(
element,
/* props */ {
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);
if (mapped === element) {
mapped
= React.cloneElement(
element,
/* props */ undefined,
...React.Children.toArray(React.Children.map(
element.props.children,
function(element) { // eslint-disable-line no-shadow
// eslint-disable-next-line no-invalid-this
return this._mapReactElement(element, f);
},
this)));
if (mapped) {
const { children } = mapped.props;
if (mapped === element || React.Children.count(children)) {
mapped
= React.cloneElement(
mapped,
/* props */ undefined,
...React.Children.toArray(React.Children.map(
children,
function(element) { // eslint-disable-line no-shadow
// eslint-disable-next-line no-invalid-this
return this._mapReactElement(element, f);
},
this)));
}
}
return mapped;

View File

@ -1,8 +1,11 @@
import PropTypes from 'prop-types';
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 { appNavigate } from '../../app';
import { connect, disconnect } from '../../base/connection';
import { DialogContainer } from '../../base/dialog';
import { Container, LoadingIndicator } from '../../base/react';
@ -56,6 +59,17 @@ class Conference extends Component {
*/
_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
* show/hide the Toolbox.
@ -90,22 +104,36 @@ class Conference extends Component {
*/
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._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
* returns {void}
*/
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);
}
/**
* 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
* @returns {void}
@ -115,13 +143,24 @@ class Conference extends Component {
}
/**
* Destroys connection, conference and local tracks when conference screen
* is left. Clears {@link #_toolboxTimeout} before the component unmounts.
* Implements {@link Component#componentWillUnmount()}. Invoked immediately
* before this component is unmounted and destroyed. Disconnects the
* conference described by the redux store/state.
*
* @inheritdoc
* @returns {void}
*/
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.props._onDisconnect();
@ -210,6 +249,17 @@ class Conference extends Component {
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.
*
@ -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
* it.