RN-192 Fix Cancel button in IOS search bar (#642)

* when login get the sessions after setting the credentials

* Fix reset drawer on android

* RN-192 Fix Cancel button in IOS search bar

* feedback review
This commit is contained in:
enahum 2017-06-19 09:19:37 -04:00 committed by GitHub
parent dafe0ba7a8
commit a68d4f4579
9 changed files with 564 additions and 107 deletions

View file

@ -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.
---

View file

@ -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;

View file

@ -304,6 +304,7 @@ export default class ChannelDrawer extends PureComponent {
acceptPan={true}
negotiatePan={true}
useInteractionManager={false}
tweenDuration={100}
tweenHandler={this.handleDrawerTween}
elevation={-5}
styles={{

View file

@ -23,6 +23,7 @@ export default class SwiperAndroid extends PureComponent {
showTeamsPage = () => {
this.refs.swiper.setPage(0);
this.props.onPageSelected(0);
};
resetPage = () => {

View file

@ -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 {
/>
}
<TextInput
ref='input'
blurOnSubmit={blurOnSubmit}
value={value}
autoCapitalize={autoCapitalize}

View file

@ -2,9 +2,9 @@
// See License.txt for license information.
import React, {Component} from 'react';
import {InteractionManager, Text, Keyboard} from 'react-native';
import {InteractionManager, Keyboard} from 'react-native';
import PropTypes from 'prop-types';
import Search from 'react-native-search-box';
import Search from './search_box';
export default class SearchBarIos extends Component {
static propTypes = {
@ -35,26 +35,15 @@ export default class SearchBarIos extends Component {
autoCapitalize: PropTypes.string,
inputHeight: PropTypes.number,
inputBorderRadius: PropTypes.number,
blurOnSubmit: PropTypes.bool
blurOnSubmit: PropTypes.bool,
value: PropTypes.string
};
constructor(props) {
super(props);
this.state = {
placeholderWidth: 0
};
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.placeholderWidth !== this.state.placeholderWidth;
}
afterDelete = () => {
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 (
<Search
{...this.props}
ref='search'
placeholderCollapsedMargin={this.state.placeholderWidth - 15}
placeholderExpandedMargin={30}
searchIconCollapsedMargin={this.state.placeholderWidth}
searchIconExpandedMargin={15}
shadowVisible={false}
onCancel={this.onCancel}
onChangeText={this.onChangeText}
onFocus={this.onFocus}
onSearch={this.onSearch}
afterDelete={this.afterDelete}
onDelete={this.onDelete}
/>
);
}
return (
<Text
style={{position: 'absolute', top: -3000, left: -1000}}
onLayout={(event) => {
const placeholderWidth = (event.nativeEvent.layout.width / 2);
this.setState({placeholderWidth});
}}
>
{this.props.placeholder}
</Text>
<Search
{...this.props}
ref='search'
placeholderCollapsedMargin={25}
placeholderExpandedMargin={25}
searchIconCollapsedMargin={15}
searchIconExpandedMargin={15}
shadowVisible={false}
onCancel={this.onCancel}
onChangeText={this.onChangeText}
onFocus={this.onFocus}
onSearch={this.onSearch}
afterDelete={this.afterDelete}
onDelete={this.onDelete}
/>
);
}
}

View file

@ -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 (
<Animated.View
ref='searchContainer'
style={[
styles.container,
this.props.backgroundColor && {backgroundColor: this.props.backgroundColor}
]}
onLayout={this.onLayout}
>
<AnimatedTextInput
ref='input_keyword'
style={[
styles.input,
this.props.placeholderTextColor && {color: this.props.placeholderTextColor},
this.props.inputStyle && this.props.inputStyle,
this.props.inputHeight && {height: this.props.inputHeight},
this.props.inputBorderRadius && {borderRadius: this.props.inputBorderRadius},
{
width: this.inputFocusWidthAnimated,
paddingLeft: this.inputFocusPlaceholderAnimated
},
this.props.shadowVisible && {
shadowOffset: {width: this.props.shadowOffsetWidth, height: this.shadowHeight},
shadowColor: this.props.shadowColor,
shadowOpacity: this.shadowOpacityAnimated,
shadowRadius: this.props.shadowRadius
}
]}
autoFocus={this.props.autoFocus}
editable={this.props.editable}
value={this.state.keyword}
onChangeText={this.onChangeText}
placeholder={this.placeholder}
placeholderTextColor={this.props.placeholderTextColor}
onSubmitEditing={this.onSearch}
autoCorrect={false}
blurOnSubmit={this.props.blurOnSubmit}
returnKeyType={this.props.returnKeyType || 'search'}
keyboardType={this.props.keyboardType || 'default'}
autoCapitalize={this.props.autoCapitalize}
onFocus={this.onFocus}
underlineColorAndroid='transparent'
/>
<TouchableWithoutFeedback onPress={this.onFocus}>
{((this.props.iconSearch) ?
<Animated.View
style={[
styles.iconSearch,
{left: this.iconSearchAnimated}
]}
>
{this.props.iconSearch}
</Animated.View> :
<AnimatedIcon
name='ios-search-outline'
size={iconSize}
style={[
styles.iconSearch,
styles.iconSearchDefault,
this.props.tintColorSearch && {color: this.props.tintColorSearch},
{
left: this.iconSearchAnimated,
top: middleHeight - (iconSize / 2)
}
]}
/>
)}
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={this.onDelete}>
{((this.props.iconDelete) ?
<Animated.View
style={[
styles.iconDelete,
this.props.positionRightDelete && {right: this.props.positionRightDelete},
{opacity: this.iconDeleteAnimated}
]}
>
{this.props.iconDelete}
</Animated.View> :
<AnimatedIcon
name='ios-close-circle'
size={iconSize}
style={[
styles.iconDelete,
styles.iconDeleteDefault,
this.props.tintColorDelete && {color: this.props.tintColorDelete},
this.props.positionRightDelete && {right: this.props.positionRightDelete},
{
opacity: this.iconDeleteAnimated,
top: middleHeight - (iconSize / 2)
}
]}
/>
)}
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={this.onCancel}>
<Animated.View
style={[
styles.cancelButton,
this.props.cancelButtonStyle && this.props.cancelButtonStyle,
{left: this.btnCancelAnimated}
]}
>
<Text
style={[
styles.cancelButtonText,
this.props.titleCancelColor && {color: this.props.titleCancelColor},
this.props.cancelButtonStyle && this.props.cancelButtonStyle
]}
>
{this.cancelTitle}
</Text>
</Animated.View>
</TouchableWithoutFeedback>
</Animated.View>
);
}
}
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'
}
});

View file

@ -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",

View file

@ -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"