Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animations for the IOU flow #4070

Merged
merged 22 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CONST.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions src/components/AnimatedStep.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import * as Animatable from 'react-native-animatable';
import CONST from '../CONST';

const propTypes = {
/** Children to wrap in AnimatedStep. */
children: PropTypes.node.isRequired,

/** Styles to be assigned to Container */
style: PropTypes.arrayOf(PropTypes.object),

/** Whether we're animating the step in or out */
direction: PropTypes.string,
Julesssss marked this conversation as resolved.
Show resolved Hide resolved
};

const defaultProps = {
direction: 'in',
style: [],
};

const AnimatedStep = (props) => {
function getAnimationStyle(direction) {
let animationStyle;

if (direction === 'in') {
animationStyle = 'slideInRight';
} else if (direction === 'out') {
animationStyle = 'slideInLeft';
}
return animationStyle;
}

return (
<Animatable.View
duration={CONST.ANIMATED_TRANSITION}
animation={getAnimationStyle(props.direction)}
useNativeDriver
style={[...props.style]}
>
{props.children}
</Animatable.View>
);
};

AnimatedStep.propTypes = propTypes;
AnimatedStep.defaultProps = defaultProps;
AnimatedStep.displayName = 'AnimatedStep';
export default AnimatedStep;
8 changes: 7 additions & 1 deletion src/components/IOUConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ class IOUConfirmationList extends Component {
};
}

componentDidMount() {
// We need to wait for the transition animation to end before focusing the TextInput,
// otherwise the TextInput isn't animated correctly
setTimeout(() => this.textInput.focus(), CONST.ANIMATED_TRANSITION);
Julesssss marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Get selected participants
* @returns {Array}
Expand Down Expand Up @@ -335,12 +341,12 @@ class IOUConfirmationList extends Component {
</ScrollView>
<View style={[styles.ph5, styles.pv5, styles.flexGrow1, styles.flexShrink0, styles.iouConfirmComment]}>
<ExpensiTextInput
ref={el => this.textInput = el}
label={this.props.translate('iOUConfirmationList.whatsItFor')}
value={this.props.comment}
onChangeText={this.props.onUpdateComment}
placeholder={this.props.translate('common.optional')}
placeholderTextColor={themeColors.placeholderText}
autoFocus
/>
</View>
<FixedFooter>
Expand Down
11 changes: 10 additions & 1 deletion src/components/OptionsSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import OptionsList from './OptionsList';
import CONST from '../CONST';
import styles from '../styles/styles';
import optionPropTypes from './optionPropTypes';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import ExpensiTextInput from './ExpensiTextInput';

const propTypes = {
/** Wether we should wait before focusing the TextInput, useful when using transitions */
shouldDelayFocus: PropTypes.bool,

/** Callback to fire when a row is tapped */
onSelectRow: PropTypes.func,

Expand Down Expand Up @@ -67,6 +71,7 @@ const propTypes = {
};

const defaultProps = {
shouldDelayFocus: false,
onSelectRow: () => {},
placeholderText: '',
selectedOptions: [],
Expand Down Expand Up @@ -94,7 +99,11 @@ class OptionsSelector extends Component {
}

componentDidMount() {
this.textInput.focus();
if (this.props.shouldDelayFocus) {
setTimeout(() => this.textInput.focus(), CONST.ANIMATED_TRANSITION);
} else {
this.textInput.focus();
}
}

/**
Expand Down
85 changes: 61 additions & 24 deletions src/pages/iou/IOUModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize
import compose from '../../libs/compose';
import {addSMSDomainIfPhoneNumber, getPersonalDetailsForLogins} from '../../libs/OptionsListUtils';
import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator';
import AnimatedStep from '../../components/AnimatedStep';
import ScreenWrapper from '../../components/ScreenWrapper';
import Tooltip from '../../components/Tooltip';
import CONST from '../../CONST';
Expand Down Expand Up @@ -116,6 +117,7 @@ class IOUModal extends Component {
}));

this.state = {
previousStepIndex: 0,
currentStepIndex: 0,
participants: participantsWithDetails,

Expand Down Expand Up @@ -157,6 +159,25 @@ class IOUModal extends Component {
}
}

/**
* Decides our animation type based on whether we're increasing or decreasing
* our step index.
* @returns {String}
*/
getDirection() {
if (this.state.previousStepIndex < this.state.currentStepIndex) {
return 'in';
}
if (this.state.previousStepIndex > this.state.currentStepIndex) {
return 'out';
}

// Doesn't animate the step when first opening the modal
if (this.state.previousStepIndex === this.state.currentStepIndex) {
return null;
}
}

/**
* Retrieve title for current step, based upon current step and type of IOU
*
Expand Down Expand Up @@ -206,6 +227,7 @@ class IOUModal extends Component {
return;
}
this.setState(prevState => ({
previousStepIndex: prevState.currentStepIndex,
currentStepIndex: prevState.currentStepIndex - 1,
}));
}
Expand All @@ -218,6 +240,7 @@ class IOUModal extends Component {
return;
}
this.setState(prevState => ({
previousStepIndex: prevState.currentStepIndex,
currentStepIndex: prevState.currentStepIndex + 1,
}));
}
Expand Down Expand Up @@ -325,34 +348,48 @@ class IOUModal extends Component {
{didScreenTransitionEnd && (
<>
{currentStep === Steps.IOUAmount && (
<IOUAmountPage
onStepComplete={(amount) => {
this.setState({amount});
this.navigateToNextStep();
}}
reportID={reportID}
hasMultipleParticipants={this.props.hasMultipleParticipants}
selectedAmount={this.state.amount}
navigation={this.props.navigation}
/>
<AnimatedStep
direction={this.getDirection()}
style={[styles.flex1, styles.pageWrapper]}
>
<IOUAmountPage
onStepComplete={(amount) => {
this.setState({amount});
this.navigateToNextStep();
}}
reportID={reportID}
hasMultipleParticipants={this.props.hasMultipleParticipants}
selectedAmount={this.state.amount}
navigation={this.props.navigation}
/>
</AnimatedStep>
)}
{currentStep === Steps.IOUParticipants && (
<IOUParticipantsPage
participants={this.state.participants}
hasMultipleParticipants={this.props.hasMultipleParticipants}
onAddParticipants={this.addParticipants}
onStepComplete={this.navigateToNextStep}
/>
<AnimatedStep
style={[styles.flex1]}
direction={this.getDirection()}
>
Comment on lines +384 to +387
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason that each of the AnimatedStep has a different style?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just moved the style from the View that was wrapping our steps (like in the previous comment) to the AnimatedStep, that's why the style is different for each step.

This was necessary because wrapping the step with a AnimatedStep (Animatable.View) without a style was changing the styling of the step.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we leave flex1 on the AnimatedStep and put pageWrapper style within this component?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I think it's bad to have the container style spread through multiple views, but yes I can change it!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, let's not do it then. It's just not obvious why the steps have different styles here and I worry this is confusing.

<IOUParticipantsPage
participants={this.state.participants}
hasMultipleParticipants={this.props.hasMultipleParticipants}
onAddParticipants={this.addParticipants}
onStepComplete={this.navigateToNextStep}
/>
</AnimatedStep>
)}
{currentStep === Steps.IOUConfirm && (
<IOUConfirmPage
onConfirm={this.createTransaction}
hasMultipleParticipants={this.props.hasMultipleParticipants}
participants={this.state.participants}
iouAmount={this.state.amount}
comment={this.state.comment}
onUpdateComment={this.updateComment}
/>
<AnimatedStep
direction={this.getDirection()}
>
<IOUConfirmPage
onConfirm={this.createTransaction}
hasMultipleParticipants={this.props.hasMultipleParticipants}
participants={this.state.participants}
iouAmount={this.state.amount}
comment={this.state.comment}
onUpdateComment={this.updateComment}
/>
</AnimatedStep>
)}
</>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/iou/steps/IOUAmountPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class IOUAmountPage extends React.Component {

render() {
return (
<View style={[styles.flex1, styles.pageWrapper]}>
<>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this view is useless now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! It's on the AnimatedStep that wraps the IOUAmountPage now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove <> in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can. Since we removed the View that was wrapping the whole component, we have to wrap it in something else (we can't return multiple childrens). The React.Fragment (<>) is default way to do this.

Let me know if you think I'm missing something.

Copy link
Contributor

@Julesssss Julesssss Sep 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it looked to me like the outer fragment was wrapping a single component. I see now there are multiple, so no changes are required.

<View style={[
styles.flex1,
styles.flexRow,
Expand Down Expand Up @@ -203,7 +203,7 @@ class IOUAmountPage extends React.Component {
text={this.props.translate('common.next')}
/>
</View>
</View>
</>
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ class IOUParticipantsRequest extends Component {
disableArrowKeysActions
hideAdditionalOptionStates
forceTextUnreadStyle
shouldDelayFocus
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ class IOUParticipantsSplit extends Component {
disableArrowKeysActions
hideAdditionalOptionStates
forceTextUnreadStyle
shouldDelayFocus
/>
</View>
{this.props.participants?.length > 0 && (
Expand Down