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:
parent
76957c5ae4
commit
de7b88beb2
8 changed files with 99 additions and 43 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ exports[`MoreChannels should match snapshot 1`] = `
|
|||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
leftComponent={null}
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
|
|
|
|||
|
|
@ -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]}
|
||||
|
|
|
|||
Loading…
Reference in a new issue