diff --git a/src/components/Channel.js b/src/components/Channel.js index 6254a06b1..d50f74908 100644 --- a/src/components/Channel.js +++ b/src/components/Channel.js @@ -9,6 +9,7 @@ import PropTypes from 'prop-types'; // import { MessageSimple } from './MessageSimple'; // import { Attachment } from './Attachment'; import { ChannelInner } from './ChannelInner'; +import { KeyboardCompatibleView } from './KeyboardCompatibleView'; /** * Channel - Wrapper component for a channel. It needs to be place inside of the Chat component. @@ -83,12 +84,42 @@ const Channel = withChatContext( * Override update message request (Advanced usage only) * */ doUpdateMessageRequest: PropTypes.func, + /** + * If true, KeyboardCompatibleView wrapper is disabled. + * + * Channel component internally uses [KeyboardCompatibleView](https://github.com/GetStream/stream-chat-react-native/blob/master/src/components/KeyboardCompatibleView.js) component + * internally to adjust the height of Channel component when keyboard is opened or dismissed. This prop gives you ability to disable this functionality, in case if you + * want to use [KeyboardAvoidingView](https://facebook.github.io/react-native/docs/keyboardavoidingview) or you want to handle keyboard dismissal yourself. + * KeyboardAvoidingView works well when your component occupies 100% of screen height, otherwise it may raise some issues. + * */ + disableKeyboardCompatibleView: PropTypes.bool, + /** + * Custom wrapper component that handles height adjustment of Channel component when keyboard is opened or dismissed. + * Defaults to [KeyboardCompatibleView](https://github.com/GetStream/stream-chat-react-native/blob/master/src/components/KeyboardCompatibleView.js) + * + * This prop can be used to configure default KeyboardCompatibleView component. + * e.g., + * { + * return ( + * + * {props.children} + * + * ) + * }} + * /> + */ + KeyboardCompatibleView: PropTypes.oneOfType([ + PropTypes.node, + PropTypes.elementType, + ]), }; static defaultProps = { - // LoadingIndicator, - // Message: MessageSimple, - // Attachment, + disableKeyboardCompatibleView: false, + KeyboardCompatibleView, }; render() { diff --git a/src/components/ChannelInner.js b/src/components/ChannelInner.js index 5210de1dd..0a617fbb5 100644 --- a/src/components/ChannelInner.js +++ b/src/components/ChannelInner.js @@ -13,7 +13,6 @@ import { emojiData } from '../utils'; import { LoadingIndicator } from './LoadingIndicator'; import { LoadingErrorIndicator } from './LoadingErrorIndicator'; import { EmptyStateIndicator } from './EmptyStateIndicator'; -import { KeyboardCompatibleView } from './KeyboardCompatibleView'; import { logChatPromiseExecution } from 'stream-chat'; /** @@ -621,7 +620,7 @@ export class ChannelInner extends PureComponent { render() { let core; - + const { KeyboardCompatibleView } = this.props; if (this.state.error) { this.props.logger( 'Channel component', @@ -645,7 +644,9 @@ export class ChannelInner extends PureComponent { ); } else { core = ( - + { diff --git a/src/components/KeyboardCompatibleView.js b/src/components/KeyboardCompatibleView.js index bb6faaf50..a1f6c3202 100644 --- a/src/components/KeyboardCompatibleView.js +++ b/src/components/KeyboardCompatibleView.js @@ -8,6 +8,7 @@ import { StatusBar, } from 'react-native'; import { KeyboardContext } from '../context'; +import PropTypes from 'prop-types'; /** * KeyboardCompatibleView is HOC component similar to [KeyboardAvoidingView](https://facebook.github.io/react-native/docs/keyboardavoidingview), @@ -25,13 +26,40 @@ import { KeyboardContext } from '../context'; * ``` */ export class KeyboardCompatibleView extends React.PureComponent { + static propTypes = { + keyboardDismissAnimationDuration: PropTypes.number, + keyboardOpenAnimationDuration: PropTypes.number, + enabled: PropTypes.bool, + }; + static defaultProps = { + keyboardDismissAnimationDuration: 500, + keyboardOpenAnimationDuration: 500, + enabled: true, + }; + constructor(props) { super(props); this.state = { channelHeight: new Animated.Value('100%'), + // For some reason UI doesn't update sometimes, when state is updated using setValue. + // So to force update the component, I am using following key, which will be increamented + // for every keyboard slide up and down. + key: 0, }; + this.setupListeners(); + + this._keyboardOpen = false; + // Following variable takes care of race condition between keyboardDidHide and keyboardDidShow. + this._hidingKeyboardInProgress = false; + this.rootChannelView = false; + this.initialHeight = undefined; + } + + setupListeners = () => { + if (!this.props.enabled) return; + if (Platform.OS === 'ios') { this.keyboardDidShowListener = Keyboard.addListener( 'keyboardWillShow', @@ -51,14 +79,7 @@ export class KeyboardCompatibleView extends React.PureComponent { 'keyboardDidHide', this.keyboardDidHide, ); - - this._keyboardOpen = false; - // Following variable takes care of race condition between keyboardDidHide and keyboardDidShow. - this._hidingKeyboardInProgress = false; - this.rootChannelView = false; - this.initialHeight = undefined; - } - + }; componentWillUnmount() { this.keyboardDidShowListener.remove(); this.keyboardDidHideListener.remove(); @@ -66,6 +87,7 @@ export class KeyboardCompatibleView extends React.PureComponent { // TODO: Better to extract following functions to different HOC. keyboardDidShow = (e) => { + if (!this.props.enabled) return; const keyboardHidingInProgressBeforeMeasure = this ._hidingKeyboardInProgress; const keyboardHeight = e.endCoordinates.height; @@ -79,9 +101,6 @@ export class KeyboardCompatibleView extends React.PureComponent { !keyboardHidingInProgressBeforeMeasure && this._hidingKeyboardInProgress ) { - console.log( - 'Aborting keyboardDidShow operation since hide is in progress!', - ); return; } @@ -97,10 +116,13 @@ export class KeyboardCompatibleView extends React.PureComponent { Animated.timing(this.state.channelHeight, { toValue: finalHeight, - duration: 500, + duration: this.props.keyboardOpenAnimationDuration, }).start(() => { // Force the final value, in case animation halted in between. this.state.channelHeight.setValue(finalHeight); + this.setState({ + key: this.state.key + 1, + }); }); }); this._keyboardOpen = true; @@ -110,12 +132,15 @@ export class KeyboardCompatibleView extends React.PureComponent { this._hidingKeyboardInProgress = true; Animated.timing(this.state.channelHeight, { toValue: this.initialHeight, - duration: 500, + duration: this.props.keyboardDismissAnimationDuration, }).start(() => { // Force the final value, in case animation halted in between. this.state.channelHeight.setValue(this.initialHeight); this._hidingKeyboardInProgress = false; this._keyboardOpen = false; + this.setState({ + key: this.state.key + 1, + }); }); }; @@ -128,7 +153,7 @@ export class KeyboardCompatibleView extends React.PureComponent { Animated.timing(this.state.channelHeight, { toValue: this.initialHeight, - duration: 500, + duration: this.props.keyboardDismissAnimationDuration, }).start((response) => { this.state.channelHeight.setValue(this.initialHeight); if (response && !response.finished) { @@ -138,7 +163,7 @@ export class KeyboardCompatibleView extends React.PureComponent { // during keyboard dismissal. setTimeout(() => { resolve(); - }, 500); + }, this.props.keyboardDismissAnimationDuration); return; } @@ -164,7 +189,7 @@ export class KeyboardCompatibleView extends React.PureComponent { dismissKeyboard = async () => { Keyboard.dismiss(); - await this.keyboardWillDismiss(); + if (this.props.enabled) await this.keyboardWillDismiss(); }; getContext = () => ({