Skip to content

Commit

Permalink
CRNS-74: KeyboardCompatibleView fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
vishalnarkhede committed Feb 20, 2020
1 parent ec248a0 commit 6650109
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 22 deletions.
37 changes: 34 additions & 3 deletions src/components/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.,
* <Channel
* channel={channel}
* ...
* KeyboardCompatibleView={(props) => {
* return (
* <KeyboardCompatibleView keyboardDismissAnimationDuration={200} keyboardOpenAnimationDuration={200}>
* {props.children}
* </KeyboardCompatibleView>
* )
* }}
* />
*/
KeyboardCompatibleView: PropTypes.oneOfType([
PropTypes.node,
PropTypes.elementType,
]),
};

static defaultProps = {
// LoadingIndicator,
// Message: MessageSimple,
// Attachment,
disableKeyboardCompatibleView: false,
KeyboardCompatibleView,
};

render() {
Expand Down
7 changes: 4 additions & 3 deletions src/components/ChannelInner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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',
Expand All @@ -645,7 +644,9 @@ export class ChannelInner extends PureComponent {
);
} else {
core = (
<KeyboardCompatibleView>
<KeyboardCompatibleView
enabled={!this.props.disableKeyboardCompatibleView}
>
<ChannelContext.Provider value={this.getContext()}>
<SuggestionsProvider
handleKeyboardAvoidingViewEnabled={(trueOrFalse) => {
Expand Down
57 changes: 41 additions & 16 deletions src/components/KeyboardCompatibleView.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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',
Expand All @@ -51,21 +79,15 @@ 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();
}

// 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;
Expand All @@ -79,9 +101,6 @@ export class KeyboardCompatibleView extends React.PureComponent {
!keyboardHidingInProgressBeforeMeasure &&
this._hidingKeyboardInProgress
) {
console.log(
'Aborting keyboardDidShow operation since hide is in progress!',
);
return;
}

Expand All @@ -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;
Expand All @@ -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,
});
});
};

Expand All @@ -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) {
Expand All @@ -138,7 +163,7 @@ export class KeyboardCompatibleView extends React.PureComponent {
// during keyboard dismissal.
setTimeout(() => {
resolve();
}, 500);
}, this.props.keyboardDismissAnimationDuration);
return;
}

Expand All @@ -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 = () => ({
Expand Down

0 comments on commit 6650109

Please sign in to comment.