diff --git a/NOTICE.txt b/NOTICE.txt index 1ae5f6e74..d3621c3f6 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1023,3 +1023,37 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- + +## react-native-search-box + +This product contains a modified portion of 'react-native-search-box', a simple search box with animation for React Native. + +* HOMEPAGE: + * https://github.com/crabstudio + +* LICENSE: + +The MIT License + +Copyright (c) 2017 Crabstudio. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- diff --git a/app/actions/views/login.js b/app/actions/views/login.js index b4aa4baf1..4bc2d3100 100644 --- a/app/actions/views/login.js +++ b/app/actions/views/login.js @@ -27,7 +27,6 @@ export function handleSuccessfulLogin() { return async (dispatch, getState) => { const {currentUserId} = getState().entities.users; const token = Client4.getToken(); - const session = await Client4.getSessions(currentUserId, token); dispatch({ type: GeneralTypes.RECEIVED_APP_CREDENTIALS, @@ -37,6 +36,7 @@ export function handleSuccessfulLogin() { } }); + const session = await Client4.getSessions(currentUserId, token); if (Array.isArray(session) && session[0]) { const s = session[0]; return s.expires_at; diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js index 88fb46ce5..1153eeb27 100644 --- a/app/components/channel_drawer/channel_drawer.js +++ b/app/components/channel_drawer/channel_drawer.js @@ -304,6 +304,7 @@ export default class ChannelDrawer extends PureComponent { acceptPan={true} negotiatePan={true} useInteractionManager={false} + tweenDuration={100} tweenHandler={this.handleDrawerTween} elevation={-5} styles={{ diff --git a/app/components/channel_drawer/swiper/swiper.android.js b/app/components/channel_drawer/swiper/swiper.android.js index 8605dca98..a84624223 100644 --- a/app/components/channel_drawer/swiper/swiper.android.js +++ b/app/components/channel_drawer/swiper/swiper.android.js @@ -23,6 +23,7 @@ export default class SwiperAndroid extends PureComponent { showTeamsPage = () => { this.refs.swiper.setPage(0); + this.props.onPageSelected(0); }; resetPage = () => { diff --git a/app/components/search_bar/search_bar.android.js b/app/components/search_bar/search_bar.android.js index fd881fe63..5abc4950f 100644 --- a/app/components/search_bar/search_bar.android.js +++ b/app/components/search_bar/search_bar.android.js @@ -16,6 +16,7 @@ import {changeOpacity} from 'app/utils/theme'; export default class SearchBarAndroid extends PureComponent { static propTypes = { + autoFocus: PropTypes.bool, onCancelButtonPress: PropTypes.func, onChangeText: PropTypes.func, onFocus: PropTypes.func, @@ -35,7 +36,8 @@ export default class SearchBarAndroid extends PureComponent { autoCapitalize: PropTypes.string, inputHeight: PropTypes.number, inputBorderRadius: PropTypes.number, - blurOnSubmit: PropTypes.bool + blurOnSubmit: PropTypes.bool, + value: PropTypes.string }; static defaultProps = { @@ -53,7 +55,7 @@ export default class SearchBarAndroid extends PureComponent { super(props); this.state = { isFocused: false, - value: '' + value: props.value || '' }; } @@ -62,46 +64,37 @@ export default class SearchBarAndroid extends PureComponent { }; onSearchButtonPress = () => { - const {onSearchButtonPress} = this.props; const {value} = this.state; - if (value && onSearchButtonPress) { - onSearchButtonPress(value); + if (value) { + this.props.onSearchButtonPress(value); } }; onCancelButtonPress = () => { - const {onCancelButtonPress} = this.props; - Keyboard.dismiss(); InteractionManager.runAfterInteractions(() => { this.setState({ isFocused: false, value: '' }, () => { - if (onCancelButtonPress) { - onCancelButtonPress(); - } + this.props.onCancelButtonPress(); }); }); }; onChangeText = (value) => { - const {onChangeText} = this.props; this.setState({value}); - if (onChangeText) { - onChangeText(value); - } + this.props.onChangeText(value); }; onFocus = () => { - const {onFocus} = this.props; - this.setState({isFocused: true}); + this.props.onFocus(); + }; - if (onFocus) { - onFocus(); - } + focus = () => { + this.refs.input.focus(); }; render() { @@ -169,6 +162,7 @@ export default class SearchBarAndroid extends PureComponent { /> } { - return new Promise((resolve) => { - this.refs.search.focus(); - resolve(); - }); + static defaultProps = { + onSearchButtonPress: () => true, + onCancelButtonPress: () => true, + onChangeText: () => true, + onFocus: () => true }; cancel = () => { @@ -62,84 +51,51 @@ export default class SearchBarIos extends Component { }; onCancel = () => { - return new Promise((resolve) => { - Keyboard.dismiss(); - InteractionManager.runAfterInteractions(() => { - if (this.props.onCancelButtonPress) { - this.props.onCancelButtonPress(); - } - resolve(); - }); + Keyboard.dismiss(); + InteractionManager.runAfterInteractions(() => { + this.props.onCancelButtonPress(); }); }; onChangeText = (text) => { - return new Promise((resolve) => { - if (this.props.onChangeText) { - this.props.onChangeText(text); - } - resolve(); - }); + this.props.onChangeText(text); }; onDelete = () => { - return new Promise((resolve) => { - if (this.props.onChangeText) { - this.props.onChangeText(''); - } - resolve(); - }); + this.props.onChangeText(''); }; onFocus = () => { - return new Promise((resolve) => { - if (this.props.onFocus) { - this.props.onFocus(); - } - resolve(); - }); + this.props.onFocus(); }; onSearch = (text) => { - return new Promise((resolve) => { - if (this.props.onSearchButtonPress) { - this.props.onSearchButtonPress(text); - } - resolve(); - }); + if (text) { + this.props.onSearchButtonPress(text); + } + }; + + focus = () => { + this.refs.search.focus(); }; render() { - if (this.state.placeholderWidth) { - return ( - - ); - } - return ( - { - const placeholderWidth = (event.nativeEvent.layout.width / 2); - this.setState({placeholderWidth}); - }} - > - {this.props.placeholder} - + ); } } diff --git a/app/components/search_bar/search_box.js b/app/components/search_bar/search_box.js new file mode 100644 index 000000000..9b676ede8 --- /dev/null +++ b/app/components/search_bar/search_box.js @@ -0,0 +1,476 @@ +// Copyright (c) 2017 Crabstudio. +// Modified work: Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import { + Animated, + Dimensions, + InteractionManager, + Keyboard, + Text, + TextInput, + TouchableWithoutFeedback, + StyleSheet, + View +} from 'react-native'; +import IonIcon from 'react-native-vector-icons/Ionicons'; + +const AnimatedTextInput = Animated.createAnimatedComponent(TextInput); +const AnimatedIcon = Animated.createAnimatedComponent(IonIcon); +const containerHeight = 40; +const middleHeight = 20; + +export default class Search extends Component { + static propTypes = { + onFocus: PropTypes.func, + onSearch: PropTypes.func, + onChangeText: PropTypes.func, + onCancel: PropTypes.func, + onDelete: PropTypes.func, + backgroundColor: PropTypes.string, + placeholderTextColor: PropTypes.string, + titleCancelColor: PropTypes.string, + tintColorSearch: PropTypes.string, + tintColorDelete: PropTypes.string, + inputStyle: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.object, + View.propTypes.style + ]), + onLayout: PropTypes.func, + cancelButtonStyle: View.propTypes.style, + autoFocus: PropTypes.bool, + placeholder: PropTypes.string, + cancelTitle: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object + ]), + iconDelete: PropTypes.object, + iconSearch: PropTypes.object, + returnKeyType: PropTypes.string, + keyboardType: PropTypes.string, + autoCapitalize: PropTypes.string, + inputHeight: PropTypes.number, + inputBorderRadius: PropTypes.number, + contentWidth: PropTypes.number, + middleWidth: PropTypes.number, + editable: PropTypes.bool, + blurOnSubmit: PropTypes.bool, + keyboardShouldPersist: PropTypes.bool, + value: PropTypes.string, + positionRightDelete: PropTypes.number, + searchIconCollapsedMargin: PropTypes.number, + searchIconExpandedMargin: PropTypes.number, + placeholderCollapsedMargin: PropTypes.number, + placeholderExpandedMargin: PropTypes.number, + shadowOffsetHeightCollapsed: PropTypes.number, + shadowOffsetHeightExpanded: PropTypes.number, + shadowOffsetWidth: PropTypes.number, + shadowColor: PropTypes.string, + shadowOpacityCollapsed: PropTypes.number, + shadowOpacityExpanded: PropTypes.number, + shadowRadius: PropTypes.number, + shadowVisible: PropTypes.bool + }; + + static defaultProps = { + editable: true, + blurOnSubmit: false, + keyboardShouldPersist: false, + placeholderTextColor: 'grey', + searchIconCollapsedMargin: 25, + searchIconExpandedMargin: 10, + placeholderCollapsedMargin: 15, + placeholderExpandedMargin: 20, + shadowOffsetWidth: 0, + shadowOffsetHeightCollapsed: 2, + shadowOffsetHeightExpanded: 4, + shadowColor: '#000', + shadowOpacityCollapsed: 0.12, + shadowOpacityExpanded: 0.24, + shadowRadius: 4, + shadowVisible: false + }; + + constructor(props) { + super(props); + + this.state = { + keyword: props.value || '', + expanded: false + }; + const {width} = Dimensions.get('window'); + this.contentWidth = width; + this.middleWidth = width / 2; + + this.iconSearchAnimated = new Animated.Value(this.props.searchIconCollapsedMargin); + this.iconDeleteAnimated = new Animated.Value(0); + this.inputFocusWidthAnimated = new Animated.Value(this.contentWidth - 10); + this.inputFocusPlaceholderAnimated = new Animated.Value(this.props.placeholderCollapsedMargin); + this.btnCancelAnimated = new Animated.Value(this.contentWidth); + this.shadowOpacityAnimated = new Animated.Value(this.props.shadowOpacityCollapsed); + + this.placeholder = this.props.placeholder || 'Search'; + this.cancelTitle = this.props.cancelTitle || 'Cancel'; + this.shadowHeight = this.props.shadowOffsetHeightCollapsed; + } + + onLayout = (event) => { + const contentWidth = event.nativeEvent.layout.width; + this.contentWidth = contentWidth; + this.middleWidth = contentWidth / 2; + if (this.state.expanded) { + this.expandAnimation(); + } else { + this.collapseAnimation(); + } + }; + + onSearch = async () => { + if (this.props.keyboardShouldPersist === false) { + await Keyboard.dismiss(); + } + + if (this.props.onSearch) { + this.props.onSearch(this.state.keyword); + } + }; + + onChangeText = (text) => { + this.setState({keyword: text}); + Animated.timing( + this.iconDeleteAnimated, + { + toValue: (text.length > 0) ? 1 : 0, + duration: 200 + } + ).start(); + + if (this.props.onChangeText) { + this.props.onChangeText(this.state.keyword); + } + }; + + onFocus = () => { + InteractionManager.runAfterInteractions(async () => { + const input = this.refs.input_keyword.getNode(); + if (!input.isFocused()) { + input.focus(); + } + + this.setState({expanded: true}); + await this.expandAnimation(); + + if (this.props.onFocus) { + this.props.onFocus(this.state.keyword); + } + }); + }; + + focus = async () => { + this.refs.input_keyword.getNode().focus(); + }; + + onDelete = async () => { + Animated.timing( + this.iconDeleteAnimated, + { + toValue: 0, + duration: 200 + } + ).start(); + this.setState({keyword: ''}); + + if (this.props.onDelete) { + this.props.onDelete(); + } + }; + + onCancel = async () => { + this.setState({keyword: '', expanded: false}); + await this.collapseAnimation(true); + + if (this.props.onCancel) { + this.props.onCancel(); + } + }; + + expandAnimation = () => { + return new Promise((resolve) => { + Animated.parallel([ + Animated.timing( + this.inputFocusWidthAnimated, + { + toValue: this.contentWidth - 70, + duration: 200 + } + ).start(), + Animated.timing( + this.btnCancelAnimated, + { + toValue: 10, + duration: 200 + } + ).start(), + Animated.timing( + this.inputFocusPlaceholderAnimated, + { + toValue: this.props.placeholderExpandedMargin, + duration: 200 + } + ).start(), + Animated.timing( + this.iconSearchAnimated, + { + toValue: this.props.searchIconExpandedMargin, + duration: 200 + } + ).start(), + Animated.timing( + this.iconDeleteAnimated, + { + toValue: (this.state.keyword.length > 0) ? 1 : 0, + duration: 200 + } + ).start(), + Animated.timing( + this.shadowOpacityAnimated, + { + toValue: this.props.shadowOpacityExpanded, + duration: 200 + } + ).start() + ]); + this.shadowHeight = this.props.shadowOffsetHeightExpanded; + resolve(); + }); + }; + + collapseAnimation = (isForceAnim = false) => { + return new Promise((resolve) => { + Animated.parallel([ + ((this.props.keyboardShouldPersist === false) ? Keyboard.dismiss() : null), + Animated.timing( + this.inputFocusWidthAnimated, + { + toValue: this.contentWidth - 10, + duration: 200 + } + ).start(), + Animated.timing( + this.btnCancelAnimated, + { + toValue: this.contentWidth, + duration: 200 + } + ).start(), + ((this.props.keyboardShouldPersist === false) ? + Animated.timing( + this.inputFocusPlaceholderAnimated, + { + toValue: this.props.placeholderCollapsedMargin, + duration: 200 + } + ).start() : null), + ((this.props.keyboardShouldPersist === false || isForceAnim === true) ? + Animated.timing( + this.iconSearchAnimated, + { + toValue: this.props.searchIconCollapsedMargin, + duration: 200 + } + ).start() : null), + Animated.timing( + this.iconDeleteAnimated, + { + toValue: 0, + duration: 200 + } + ).start(), + Animated.timing( + this.shadowOpacityAnimated, + { + toValue: this.props.shadowOpacityCollapsed, + duration: 200 + } + ).start() + ]); + this.shadowHeight = this.props.shadowOffsetHeightCollapsed; + resolve(); + }); + }; + + render() { + let iconSize = 16; + if (this.props.inputStyle && this.props.inputStyle.fontSize) { + iconSize = this.props.inputStyle.fontSize + 2; + } + + return ( + + + + {((this.props.iconSearch) ? + + {this.props.iconSearch} + : + + )} + + + {((this.props.iconDelete) ? + + {this.props.iconDelete} + : + + )} + + + + + {this.cancelTitle} + + + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + backgroundColor: 'grey', + height: containerHeight, + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + padding: 5 + }, + input: { + height: containerHeight - 10, + paddingTop: 5, + paddingBottom: 5, + paddingRight: 20, + borderColor: '#444', + backgroundColor: '#f7f7f7', + borderRadius: 5, + fontSize: 13 + }, + iconSearch: { + flex: 1, + position: 'absolute' + }, + iconSearchDefault: { + color: 'grey' + }, + iconDelete: { + position: 'absolute', + right: 70 + }, + iconDeleteDefault: { + color: 'grey' + }, + cancelButton: { + justifyContent: 'center', + alignItems: 'flex-start', + backgroundColor: 'transparent', + width: 60, + height: 50 + }, + cancelButtonText: { + fontSize: 14, + color: '#fff' + } +}); + diff --git a/package.json b/package.json index 6664c68f4..9eb9b5d1e 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "react-native-notifications": "enahum/react-native-notifications.git", "react-native-orientation": "enahum/react-native-orientation.git", "react-native-search-bar": "enahum/react-native-search-bar.git", - "react-native-search-box": "0.0.9", "react-native-svg": "5.1.8", "react-native-swiper": "1.5.4", "react-native-tooltip": "enahum/react-native-tooltip", diff --git a/yarn.lock b/yarn.lock index 0610a152f..bc5d3d39a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3645,7 +3645,7 @@ makeerror@1.0.x: mattermost-redux@mattermost/mattermost-redux#master: version "0.0.1" - resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/936aaa53c442c2074c7a85069cf4c4531118c55a" + resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/a31265e306e862ba75b30c3d396847d21319b524" dependencies: deep-equal "1.0.1" harmony-reflect "1.5.1" @@ -4556,10 +4556,6 @@ react-native-search-bar@enahum/react-native-search-bar.git: version "2.16.0" resolved "https://codeload.github.com/enahum/react-native-search-bar/tar.gz/88e6f5ba68012440ec21bde8388ca9e3124921c0" -react-native-search-box@0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/react-native-search-box/-/react-native-search-box-0.0.9.tgz#c1677e127d6d2b346850b031b2cb216447fa66f0" - react-native-svg-mock@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/react-native-svg-mock/-/react-native-svg-mock-1.0.2.tgz#2dc35fb97ca1e0ea393ece6d23782ba4f58e178f"