diff --git a/app/components/autocomplete/autocomplete.js b/app/components/autocomplete/autocomplete.js index ab05feabe..09555c7a6 100644 --- a/app/components/autocomplete/autocomplete.js +++ b/app/components/autocomplete/autocomplete.js @@ -34,6 +34,7 @@ export default class Autocomplete extends PureComponent { valueEvent: PropTypes.string, cursorPositionEvent: PropTypes.string, nestedScrollEnabled: PropTypes.bool, + expandDown: PropTypes.bool, }; static defaultProps = { @@ -160,32 +161,34 @@ export default class Autocomplete extends PureComponent { } render() { - const style = getStyleFromTheme(this.props.theme); + const {theme, isSearch, expandDown} = this.props; + const style = getStyleFromTheme(theme); - const wrapperStyle = []; - const containerStyle = []; - if (this.props.isSearch) { - wrapperStyle.push(style.base, style.searchContainer); - containerStyle.push(style.content); + const wrapperStyles = []; + const containerStyles = []; + if (isSearch) { + wrapperStyles.push(style.base, style.searchContainer); + containerStyles.push(style.content); } else { - containerStyle.push(style.base, style.container); + const containerStyle = expandDown ? style.containerExpandDown : style.container; + containerStyles.push(style.base, containerStyle); } // We always need to render something, but we only draw the borders when we have results to show const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state; if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount > 0) { if (this.props.isSearch) { - wrapperStyle.push(style.bordersSearch); + wrapperStyles.push(style.bordersSearch); } else { - containerStyle.push(style.borders); + containerStyles.push(style.borders); } } const maxListHeight = this.maxListHeight(); return ( - - + + { container: { bottom: 0, }, + containerExpandDown: { + top: 0, + }, content: { flex: 1, }, diff --git a/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap b/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap index a79dc98cd..3449acd20 100644 --- a/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap +++ b/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap @@ -11,6 +11,8 @@ exports[`EditChannelInfo should match snapshot 1`] = ` extraScrollHeight={0} keyboardOpeningTime={250} keyboardShouldPersistTaps="always" + onKeyboardDidHide={[Function]} + onKeyboardDidShow={[Function]} style={ Object { "backgroundColor": "#ffffff", @@ -199,6 +201,7 @@ exports[`EditChannelInfo should match snapshot 1`] = ` - - + + { @@ -154,12 +156,37 @@ export default class EditChannelInfo extends PureComponent { } }; - scrollToEnd = () => { - if (this.scroll?.current && this.lastText?.current) { - this.scroll.current.scrollToFocusedInput(findNodeHandle(this.lastText.current)); + onHeaderLayout = ({nativeEvent}) => { + this.setState({headerPosition: nativeEvent.layout.y}); + } + + onKeyboardDidShow = () => { + this.setState({keyboardVisible: true}); + + if (this.state.headerHasFocus) { + this.setState({headerHasFocus: false}); + this.scrollHeaderToTop(); + } + } + + onKeyboardDidHide = () => { + this.setState({keyboardVisible: false}); + } + + onHeaderFocus = () => { + if (this.state.keyboardVisible) { + this.scrollHeaderToTop(); + } else { + this.setState({headerHasFocus: true}); } }; + scrollHeaderToTop = () => { + if (this.scroll.current) { + this.scroll.current.scrollToPosition(0, this.state.headerPosition); + } + } + render() { const { theme, @@ -170,8 +197,10 @@ export default class EditChannelInfo extends PureComponent { header, purpose, isLandscape, + error, + saving, } = this.props; - const {error, saving} = this.props; + const {keyboardVisible} = this.state; const style = getStyleSheet(theme); @@ -205,6 +234,9 @@ export default class EditChannelInfo extends PureComponent { ref={this.scroll} style={style.container} keyboardShouldPersistTaps={'always'} + onKeyboardDidShow={this.onKeyboardDidShow} + onKeyboardDidHide={this.onKeyboardDidHide} + enableAutomaticScroll={!keyboardVisible} > {displayError} @@ -277,7 +309,10 @@ export default class EditChannelInfo extends PureComponent { )} - + - - + + { marginTop: 10, marginHorizontal: 15, }, + headerHelpText: { + zIndex: -1, + }, }; }); diff --git a/app/components/edit_channel_info/edit_channel_info.test.js b/app/components/edit_channel_info/edit_channel_info.test.js index a5d498f32..609db1bb9 100644 --- a/app/components/edit_channel_info/edit_channel_info.test.js +++ b/app/components/edit_channel_info/edit_channel_info.test.js @@ -64,4 +64,31 @@ describe('EditChannelInfo', () => { expect(instance.enableRightButton).toHaveBeenCalledTimes(1); expect(instance.enableRightButton).toHaveBeenCalledWith(true); }); + + test('should call scrollHeaderToTop', () => { + const wrapper = shallow( + + ); + + const instance = wrapper.instance(); + instance.scrollHeaderToTop = jest.fn(); + + expect(instance.scrollHeaderToTop).not.toHaveBeenCalled(); + + wrapper.setState({keyboardVisible: false}); + instance.onHeaderFocus(); + expect(instance.scrollHeaderToTop).not.toHaveBeenCalled(); + + wrapper.setState({keyboardVisible: true}); + instance.onHeaderFocus(); + expect(instance.scrollHeaderToTop).toHaveBeenCalledTimes(1); + + wrapper.setState({headerHasFocus: false}); + instance.onKeyboardDidShow(); + expect(instance.scrollHeaderToTop).toHaveBeenCalledTimes(1); + + wrapper.setState({headerHasFocus: true}); + instance.onKeyboardDidShow(); + expect(instance.scrollHeaderToTop).toHaveBeenCalledTimes(2); + }); }); \ No newline at end of file