diff --git a/src/components/ScreenWrapper.js b/src/components/ScreenWrapper.js index 3e141b3b2379..58e95b1b42e4 100644 --- a/src/components/ScreenWrapper.js +++ b/src/components/ScreenWrapper.js @@ -24,8 +24,13 @@ const propTypes = { // Whether to include padding top includePaddingTop: PropTypes.bool, - // react-navigation object that will allow us to goBack() + // Called when navigated Screen's transition is finished. + onTransitionEnd: PropTypes.func, + + // react-navigation navigation object available to screen components navigation: PropTypes.shape({ + // Method to attach listner to Navigaton state. + addListener: PropTypes.func.isRequired, // Returns to the previous navigation state e.g. if this is inside a Modal we will dismiss it goBack: PropTypes.func, @@ -36,24 +41,39 @@ const defaultProps = { style: [], includePaddingBottom: true, includePaddingTop: true, + onTransitionEnd: () => {}, navigation: { + addListener: () => {}, goBack: () => {}, }, }; class ScreenWrapper extends React.Component { + constructor(props) { + super(props); + this.state = { + didScreenTransitionEnd: false, + }; + } + componentDidMount() { - this.unsubscribe = KeyboardShortcut.subscribe('Escape', () => { + this.unsubscribeEscapeKey = KeyboardShortcut.subscribe('Escape', () => { this.props.navigation.goBack(); }, [], true); + + this.unsubscribeTransitionEnd = this.props.navigation.addListener('transitionEnd', () => { + this.setState({didScreenTransitionEnd: true}); + this.props.onTransitionEnd(); + }); } componentWillUnmount() { - if (!this.unsubscribe) { - return; + if (this.unsubscribeEscapeKey) { + this.unsubscribeEscapeKey(); + } + if (this.unsubscribeTransitionEnd) { + this.unsubscribeTransitionEnd(); } - - this.unsubscribe(); } render() { @@ -81,7 +101,10 @@ class ScreenWrapper extends React.Component { {// If props.children is a function, call it to provide the insets to the children. _.isFunction(this.props.children) - ? this.props.children(insets) + ? this.props.children({ + insets, + didScreenTransitionEnd: this.state.didScreenTransitionEnd, + }) : this.props.children } diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index ef06075b3cf3..1f244fabec95 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -68,10 +68,12 @@ Onyx.connect({ const RootStack = createCustomModalStackNavigator(); -// When modal screen gets focused, update modal visibility in Onyx +// We want to delay the re-rendering for components(e.g. ReportActionCompose) +// that depends on modal visibility until Modal is completely closed or its transition has ended +// When modal screen is focused and animation transition is ended, update modal visibility in Onyx // https://reactnavigation.org/docs/navigation-events/ const modalScreenListeners = { - focus: () => { + transitionEnd: () => { setModalVisibility(true); }, beforeRemove: () => { @@ -159,7 +161,7 @@ class AuthScreens extends React.Component { const modalScreenOptions = { headerShown: false, cardStyle: getNavigationModalCardStyle(this.props.isSmallScreenWidth), - cardStyleInterpolator: modalCardStyleInterpolator, + cardStyleInterpolator: props => modalCardStyleInterpolator(this.props.isSmallScreenWidth, props), animationEnabled: true, gestureDirection: 'horizontal', cardOverlayEnabled: true, diff --git a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js index 28b85c68b7ec..8ad5b1603cdd 100644 --- a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js +++ b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js @@ -1,15 +1,19 @@ import {Animated} from 'react-native'; +import variables from '../../../styles/variables'; -export default ({ - current: {progress}, - inverted, - layouts: { - screen, +export default ( + isSmallScreen, + { + current: {progress}, + inverted, + layouts: { + screen, + }, }, -}) => { +) => { const translateX = Animated.multiply(progress.interpolate({ inputRange: [0, 1], - outputRange: [screen.width, 0], + outputRange: [isSmallScreen ? screen.width : variables.sideBarWidth, 0], extrapolate: 'clamp', }), inverted); diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 2eae67d9f2e6..17762d37df91 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -12,6 +12,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../components/wit import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; import Navigation from '../libs/Navigation/Navigation'; import ScreenWrapper from '../components/ScreenWrapper'; +import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator'; import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; import compose from '../libs/compose'; @@ -52,7 +53,6 @@ class NewChatPage extends Component { super(props); this.createNewChat = this.createNewChat.bind(this); - const { personalDetails, userToInvite, @@ -117,38 +117,45 @@ class NewChatPage extends Component { return ( - Navigation.dismissModal(true)} - /> - - { - const { - personalDetails, - userToInvite, - } = getNewChatOptions( - this.props.reports, - this.props.personalDetails, - searchValue, - ); - this.setState({ - searchValue, - userToInvite, - personalDetails, - }); - }} - headerMessage={headerMessage} - hideSectionHeaders - disableArrowKeysActions - hideAdditionalOptionStates - forceTextUnreadStyle - /> - - + {({didScreenTransitionEnd}) => ( + <> + Navigation.dismissModal(true)} + /> + + + {didScreenTransitionEnd && ( + { + const { + personalDetails, + userToInvite, + } = getNewChatOptions( + this.props.reports, + this.props.personalDetails, + searchValue, + ); + this.setState({ + searchValue, + userToInvite, + personalDetails, + }); + }} + headerMessage={headerMessage} + hideSectionHeaders + disableArrowKeysActions + hideAdditionalOptionStates + forceTextUnreadStyle + /> + )} + + + + )} ); } diff --git a/src/pages/NewGroupPage.js b/src/pages/NewGroupPage.js index f1e83de73ae5..f1584647f1f2 100755 --- a/src/pages/NewGroupPage.js +++ b/src/pages/NewGroupPage.js @@ -14,6 +14,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../components/wit import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; import ScreenWrapper from '../components/ScreenWrapper'; import Navigation from '../libs/Navigation/Navigation'; +import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator'; import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; import compose from '../libs/compose'; @@ -55,7 +56,6 @@ class NewGroupPage extends Component { this.toggleOption = this.toggleOption.bind(this); this.createGroup = this.createGroup.bind(this); - const { recentReports, personalDetails, @@ -185,60 +185,69 @@ class NewGroupPage extends Component { ); return ( - Navigation.dismissModal(true)} - /> - - { - const { - recentReports, - personalDetails, - userToInvite, - } = getNewGroupOptions( - this.props.reports, - this.props.personalDetails, - searchValue, - [], - ); - this.setState({ - searchValue, - userToInvite, - recentReports, - personalDetails, - }); - }} - headerMessage={headerMessage} - disableArrowKeysActions - hideAdditionalOptionStates - forceTextUnreadStyle - shouldFocusOnSelectRow - /> - {this.state.selectedOptions?.length > 0 && ( - - [ - styles.button, - styles.buttonSuccess, - styles.w100, - hovered && styles.buttonSuccessHovered, - ]} - > - - {this.props.translate('newGroupPage.createGroup')} - - + {({didScreenTransitionEnd}) => ( + <> + Navigation.dismissModal(true)} + /> + + + {didScreenTransitionEnd && ( + <> + { + const { + recentReports, + personalDetails, + userToInvite, + } = getNewGroupOptions( + this.props.reports, + this.props.personalDetails, + searchValue, + [], + ); + this.setState({ + searchValue, + userToInvite, + recentReports, + personalDetails, + }); + }} + headerMessage={headerMessage} + disableArrowKeysActions + hideAdditionalOptionStates + forceTextUnreadStyle + shouldFocusOnSelectRow + /> + {this.state.selectedOptions?.length > 0 && ( + + [ + styles.button, + styles.buttonSuccess, + styles.w100, + hovered && styles.buttonSuccessHovered, + ]} + > + + {this.props.translate('newGroupPage.createGroup')} + + + + )} + + )} - )} - - + + + )} ); } diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 2ba3ebc84e4a..5405e6d8fd47 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -15,6 +15,7 @@ import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; import ScreenWrapper from '../components/ScreenWrapper'; import Timing from '../libs/actions/Timing'; import CONST from '../CONST'; +import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator'; import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; import compose from '../libs/compose'; @@ -60,7 +61,6 @@ class SearchPage extends Component { Timing.start(CONST.TIMING.SEARCH_RENDER); this.selectReport = this.selectReport.bind(this); - const { recentReports, personalDetails, @@ -141,39 +141,46 @@ class SearchPage extends Component { ); return ( - Navigation.dismissModal(true)} - /> - - { - const { - recentReports, - personalDetails, - userToInvite, - } = getSearchOptions( - this.props.reports, - this.props.personalDetails, - searchValue, - ); - this.setState({ - searchValue, - userToInvite, - recentReports, - personalDetails, - }); - }} - headerMessage={headerMessage} - hideSectionHeaders - hideAdditionalOptionStates - showTitleTooltip - /> - - + {({didScreenTransitionEnd}) => ( + <> + Navigation.dismissModal(true)} + /> + + + {didScreenTransitionEnd && ( + { + const { + recentReports, + personalDetails, + userToInvite, + } = getSearchOptions( + this.props.reports, + this.props.personalDetails, + searchValue, + ); + this.setState({ + searchValue, + userToInvite, + recentReports, + personalDetails, + }); + }} + headerMessage={headerMessage} + hideSectionHeaders + hideAdditionalOptionStates + showTitleTooltip + /> + )} + + + + )} ); } diff --git a/src/pages/home/sidebar/SidebarScreen.js b/src/pages/home/sidebar/SidebarScreen.js index 1e90b7aa98a4..27533ebf67a4 100755 --- a/src/pages/home/sidebar/SidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen.js @@ -81,7 +81,7 @@ class SidebarScreen extends Component { includePaddingBottom={false} style={[styles.sidebar]} > - {insets => ( + {({insets}) => ( <> ( - - {() => ( - // eslint-disable-next-line react/jsx-props-no-spreading - - )} - -); +// eslint-disable-next-line react/jsx-props-no-spreading +export default props => ; diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index 05fd51b55c11..a31d9c1acd1d 100755 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -16,6 +16,8 @@ import ONYXKEYS from '../../ONYXKEYS'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import compose from '../../libs/compose'; import {getPersonalDetailsForLogins} from '../../libs/OptionsListUtils'; +import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator'; +import ScreenWrapper from '../../components/ScreenWrapper'; /** * IOU modal for requesting money and splitting bills. @@ -37,6 +39,9 @@ const propTypes = { // Whether or not transaction creation has resulted to error error: PropTypes.bool, + + // is loading + loading: PropTypes.bool, }).isRequired, // Personal details of all the users @@ -78,6 +83,7 @@ class IOUModal extends Component { this.createTransaction = this.createTransaction.bind(this); this.updateComment = this.updateComment.bind(this); this.addParticipants = this.addParticipants.bind(this); + this.getReady = this.getReady.bind(this); const participants = lodashGet(props, 'report.participants', []); const participantsWithDetails = getPersonalDetailsForLogins(participants, props.personalDetails) .map(personalDetails => ({ @@ -107,10 +113,6 @@ class IOUModal extends Component { } } - componentDidMount() { - getPreferredCurrency(); - } - componentDidUpdate(prevProps) { // Successfully close the modal if transaction creation has ended and there is no error if (prevProps.iou.creatingIOUTransaction && !this.props.iou.creatingIOUTransaction && !this.props.iou.error) { @@ -118,6 +120,12 @@ class IOUModal extends Component { } } + + getReady() { + getPreferredCurrency(); + } + + /** * Retrieve title for current step, based upon current step and type of IOU * @@ -216,67 +224,79 @@ class IOUModal extends Component { render() { const currentStep = this.steps[this.state.currentStepIndex]; return ( - <> - - - {this.state.currentStepIndex > 0 - && ( - + {({didScreenTransitionEnd}) => ( + <> + + - - - )} -
- - Navigation.dismissModal()} - style={[styles.touchableButtonImage]} - > - - + {this.state.currentStepIndex > 0 + && ( + + + + )} +
+ + Navigation.dismissModal()} + style={[styles.touchableButtonImage]} + > + + + + - - - {currentStep === Steps.IOUAmount && ( - { - this.setState({amount}); - this.navigateToNextStep(); - }} - currencySelected={this.currencySelected} - selectedCurrency={this.state.selectedCurrency} - /> - )} - {currentStep === Steps.IOUParticipants && ( - - )} - {currentStep === Steps.IOUConfirm && ( - + + + {didScreenTransitionEnd && ( + <> + {currentStep === Steps.IOUAmount && ( + { + this.setState({amount}); + this.navigateToNextStep(); + }} + currencySelected={this.currencySelected} + selectedCurrency={this.state.selectedCurrency} + /> + )} + {currentStep === Steps.IOUParticipants && ( + + )} + {currentStep === Steps.IOUConfirm && ( + + )} + + )} + + + )} - + ); } } diff --git a/src/pages/iou/IOURequestPage.js b/src/pages/iou/IOURequestPage.js index 67ee9d0f4dd4..6cacd162c105 100644 --- a/src/pages/iou/IOURequestPage.js +++ b/src/pages/iou/IOURequestPage.js @@ -1,12 +1,5 @@ import React from 'react'; import IOUModal from './IOUModal'; -import ScreenWrapper from '../../components/ScreenWrapper'; -export default props => ( - - {() => ( - // eslint-disable-next-line react/jsx-props-no-spreading - - )} - -); +// eslint-disable-next-line react/jsx-props-no-spreading +export default props => ;