MM-14541 Fix search bar animation stutter in main search bar (#2672)

* Do not expand drawer on search start

* Add state.searching for determining showTeams

* Add leftComponent prop to SearchBar to render SwitchTeamsButton

* Update snapshot tests

* Use native driver when possible and fix start calls
This commit is contained in:
Miguel Alatzar 2019-04-22 19:01:17 -07:00 committed by Miguel Alatzar
parent 76957c5ae4
commit de7b88beb2
8 changed files with 99 additions and 43 deletions

View file

@ -44,6 +44,7 @@ export default class SearchBarAndroid extends PureComponent {
showArrow: PropTypes.bool,
value: PropTypes.string,
containerStyle: CustomPropTypes.Style,
leftComponent: PropTypes.element,
};
static defaultProps = {
@ -63,6 +64,7 @@ export default class SearchBarAndroid extends PureComponent {
onBlur: () => true,
onSelectionChange: () => true,
value: '',
leftComponent: null,
};
constructor(props) {
@ -174,6 +176,7 @@ export default class SearchBarAndroid extends PureComponent {
backgroundColor && {backgroundColor},
]}
>
{!isFocused && this.props.leftComponent}
<View
style={[
styles.searchBar,

View file

@ -36,6 +36,7 @@ export default class SearchBarIos extends PureComponent {
inputBorderRadius: PropTypes.number,
blurOnSubmit: PropTypes.bool,
value: PropTypes.string,
leftComponent: PropTypes.element,
};
static defaultProps = {
@ -46,6 +47,7 @@ export default class SearchBarIos extends PureComponent {
onBlur: () => true,
onSelectionChange: () => true,
blurOnSubmit: true,
leftComponent: null,
};
cancel = () => {

View file

@ -75,6 +75,7 @@ export default class Search extends Component {
shadowOpacityExpanded: PropTypes.number,
shadowRadius: PropTypes.number,
shadowVisible: PropTypes.bool,
leftComponent: PropTypes.element,
};
static defaultProps = {
@ -97,6 +98,7 @@ export default class Search extends Component {
shadowRadius: 4,
shadowVisible: false,
value: '',
leftComponent: null,
};
constructor(props) {
@ -104,6 +106,7 @@ export default class Search extends Component {
this.state = {
expanded: false,
leftComponentWidth: 0,
};
const {width} = Dimensions.get('window');
this.contentWidth = width;
@ -111,6 +114,8 @@ export default class Search extends Component {
this.iconSearchAnimated = new Animated.Value(this.props.searchIconCollapsedMargin);
this.iconDeleteAnimated = new Animated.Value(0);
this.leftComponentAnimated = new Animated.Value(0);
this.inputFocusAnimated = 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);
@ -161,6 +166,11 @@ export default class Search extends Component {
}
};
onLeftComponentLayout = (event) => {
const leftComponentWidth = event.nativeEvent.layout.width;
this.setState({leftComponentWidth});
};
onSearch = async () => {
if (this.props.keyboardShouldPersist === false) {
await Keyboard.dismiss();
@ -177,6 +187,7 @@ export default class Search extends Component {
{
toValue: (text.length > 0) ? 1 : 0,
duration: 200,
useNativeDriver: true,
}
).start();
@ -202,6 +213,7 @@ export default class Search extends Component {
{
toValue: 0,
duration: 200,
useNativeDriver: true,
}
).start();
this.focus();
@ -233,43 +245,59 @@ export default class Search extends Component {
toValue: this.contentWidth - 70,
duration: 200,
}
).start(),
),
Animated.timing(
this.inputFocusAnimated,
{
toValue: this.state.leftComponentWidth,
duration: 200,
}
),
Animated.timing(
this.leftComponentAnimated,
{
toValue: this.contentWidth,
duration: 200,
}
),
Animated.timing(
this.btnCancelAnimated,
{
toValue: 10,
toValue: this.state.leftComponentWidth ? 15 - this.state.leftComponentWidth : 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.props.value.length > 0) ? 1 : 0,
duration: 200,
useNativeDriver: true,
}
).start(),
),
Animated.timing(
this.shadowOpacityAnimated,
{
toValue: this.props.shadowOpacityExpanded,
duration: 200,
useNativeDriver: true,
}
).start(),
]);
),
]).start();
this.shadowHeight = this.props.shadowOffsetHeightExpanded;
resolve();
});
@ -282,17 +310,31 @@ export default class Search extends Component {
Animated.timing(
this.inputFocusWidthAnimated,
{
toValue: this.contentWidth - 10,
toValue: this.contentWidth - this.state.leftComponentWidth - 10,
duration: 200,
}
).start(),
),
Animated.timing(
this.inputFocusAnimated,
{
toValue: 0,
duration: 200,
}
),
Animated.timing(
this.leftComponentAnimated,
{
toValue: 0,
duration: 200,
}
),
Animated.timing(
this.btnCancelAnimated,
{
toValue: this.contentWidth,
duration: 200,
}
).start(),
),
((this.props.keyboardShouldPersist === false) ?
Animated.timing(
this.inputFocusPlaceholderAnimated,
@ -300,30 +342,32 @@ export default class Search extends Component {
toValue: this.props.placeholderCollapsedMargin,
duration: 200,
}
).start() : null),
) : null),
((this.props.keyboardShouldPersist === false || isForceAnim === true) ?
Animated.timing(
this.iconSearchAnimated,
{
toValue: this.props.searchIconCollapsedMargin,
toValue: this.props.searchIconCollapsedMargin + this.state.leftComponentWidth,
duration: 200,
}
).start() : null),
) : null),
Animated.timing(
this.iconDeleteAnimated,
{
toValue: 0,
duration: 200,
useNativeDriver: true,
}
).start(),
),
Animated.timing(
this.shadowOpacityAnimated,
{
toValue: this.props.shadowOpacityCollapsed,
duration: 200,
useNativeDriver: true,
}
).start(),
]);
),
]).start();
this.shadowHeight = this.props.shadowOffsetHeightCollapsed;
resolve();
});
@ -331,16 +375,27 @@ export default class Search extends Component {
render() {
const {backgroundColor, ...restOfInputPropStyles} = this.props.inputStyle;
return (
<Animated.View
ref='searchContainer'
style={[
styles.container,
this.props.backgroundColor && {backgroundColor: this.props.backgroundColor},
this.state.leftComponentWidth && {padding: 0},
]}
onLayout={this.onLayout}
>
<View style={{backgroundColor}}>
{((this.props.leftComponent) ?
<Animated.View
style={{right: this.leftComponentAnimated}}
onLayout={this.onLeftComponentLayout}
>
{this.props.leftComponent}
</Animated.View> :
null
)}
<Animated.View style={{backgroundColor, right: this.inputFocusAnimated}}>
<AnimatedTextInput
ref='input_keyword'
style={[
@ -380,7 +435,7 @@ export default class Search extends Component {
underlineColorAndroid='transparent'
enablesReturnKeyAutomatically={true}
/>
</View>
</Animated.View>
<TouchableWithoutFeedback onPress={this.onFocus}>
{((this.props.iconSearch) ?
<Animated.View

View file

@ -145,6 +145,11 @@ export default class ChannelsList extends PureComponent {
onChangeText={this.onSearch}
onFocus={this.onSearchFocused}
value={term}
leftComponent={(
<SwitchTeamsButton
onShowTeams={onShowTeams}
/>
)}
/>
</View>
);
@ -155,12 +160,6 @@ export default class ChannelsList extends PureComponent {
>
<View style={styles.statusBar}>
<View style={styles.headerContainer}>
<View style={styles.switchContainer}>
<SwitchTeamsButton
searching={searching}
onShowTeams={onShowTeams}
/>
</View>
{title}
</View>
</View>

View file

@ -18,7 +18,6 @@ import TeamIcon from 'app/components/team_icon';
export default class SwitchTeamsButton extends React.PureComponent {
static propTypes = {
currentTeamId: PropTypes.string,
searching: PropTypes.bool.isRequired,
onShowTeams: PropTypes.func.isRequired,
mentionCount: PropTypes.number.isRequired,
teamsCount: PropTypes.number.isRequired,
@ -33,7 +32,6 @@ export default class SwitchTeamsButton extends React.PureComponent {
const {
currentTeamId,
mentionCount,
searching,
teamsCount,
theme,
} = this.props;
@ -42,7 +40,7 @@ export default class SwitchTeamsButton extends React.PureComponent {
return null;
}
if (searching || teamsCount < 2) {
if (teamsCount < 2) {
return null;
}

View file

@ -63,6 +63,7 @@ export default class ChannelSidebar extends Component {
show: false,
openDrawerOffset,
drawerOpened: false,
searching: false,
};
}
@ -92,9 +93,9 @@ export default class ChannelSidebar extends Component {
shouldComponentUpdate(nextProps, nextState) {
const {currentTeamId, deviceWidth, isLandscape, teamsCount} = this.props;
const {openDrawerOffset} = this.state;
const {openDrawerOffset, show, searching} = this.state;
if (nextState.openDrawerOffset !== openDrawerOffset || nextState.show !== this.state.show) {
if (nextState.openDrawerOffset !== openDrawerOffset || nextState.show !== show || nextState.searching !== searching) {
return true;
}
@ -258,24 +259,14 @@ export default class ChannelSidebar extends Component {
};
onSearchEnds = () => {
//hack to update the drawer when the offset changes
const {isLandscape, isTablet} = this.props;
let openDrawerOffset = DRAWER_INITIAL_OFFSET;
if (isLandscape || isTablet) {
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
}
if (this.refs.drawer) {
this.refs.drawer.canClose = true;
}
this.setState({openDrawerOffset});
this.setState({searching: false});
};
onSearchStart = () => {
if (this.refs.drawer) {
this.refs.drawer.canClose = false;
}
this.setState({openDrawerOffset: 0});
this.setState({searching: true});
};
showTeams = () => {
@ -300,6 +291,7 @@ export default class ChannelSidebar extends Component {
const {
show,
openDrawerOffset,
searching,
} = this.state;
if (!show) {
@ -307,7 +299,7 @@ export default class ChannelSidebar extends Component {
}
const multipleTeams = teamsCount > 1;
const showTeams = openDrawerOffset !== 0 && multipleTeams;
const showTeams = !searching && multipleTeams;
if (this.drawerSwiper) {
if (multipleTeams) {
this.drawerSwiper.getWrappedInstance().runOnLayout();

View file

@ -23,6 +23,7 @@ exports[`MoreChannels should match snapshot 1`] = `
"fontSize": 15,
}
}
leftComponent={null}
onBlur={[Function]}
onCancelButtonPress={[Function]}
onChangeText={[Function]}

View file

@ -30,6 +30,7 @@ exports[`SelectorScreen should match snapshot for channels 1`] = `
"fontSize": 15,
}
}
leftComponent={null}
onBlur={[Function]}
onCancelButtonPress={[Function]}
onChangeText={[Function]}
@ -117,6 +118,7 @@ exports[`SelectorScreen should match snapshot for channels 2`] = `
"fontSize": 15,
}
}
leftComponent={null}
onBlur={[Function]}
onCancelButtonPress={[Function]}
onChangeText={[Function]}
@ -204,6 +206,7 @@ exports[`SelectorScreen should match snapshot for explicit options 1`] = `
"fontSize": 15,
}
}
leftComponent={null}
onBlur={[Function]}
onCancelButtonPress={[Function]}
onChangeText={[Function]}
@ -298,6 +301,7 @@ exports[`SelectorScreen should match snapshot for searching 1`] = `
"fontSize": 15,
}
}
leftComponent={null}
onBlur={[Function]}
onCancelButtonPress={[Function]}
onChangeText={[Function]}
@ -385,6 +389,7 @@ exports[`SelectorScreen should match snapshot for users 1`] = `
"fontSize": 15,
}
}
leftComponent={null}
onBlur={[Function]}
onCancelButtonPress={[Function]}
onChangeText={[Function]}
@ -472,6 +477,7 @@ exports[`SelectorScreen should match snapshot for users 2`] = `
"fontSize": 15,
}
}
leftComponent={null}
onBlur={[Function]}
onCancelButtonPress={[Function]}
onChangeText={[Function]}