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

Follow up to comments on PR1559 #1756

Merged
merged 8 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 3 additions & 0 deletions src/Expensify.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ const propTypes = {

// Session info for the currently logged in user.
session: PropTypes.shape({
// Currently logged in user authToken
authToken: PropTypes.string,

// Currently logged in user accountID
accountID: PropTypes.number,
}),

Expand Down
35 changes: 17 additions & 18 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@
* This is a file containing constants for all of the routes we want to be able to go to
*/
export default {
HOME: '/',
SETTINGS: '/settings',
SETTINGS_PROFILE: '/settings/profile',
SETTINGS_PREFERENCES: '/settings/preferences',
SETTINGS_PASSWORD: '/settings/password',
SETTINGS_PAYMENTS: '/settings/payments',
NEW_GROUP: '/new/group',
NEW_CHAT: '/new/chat',
REPORT: '/r',
IOU_REQUEST: '/iou/request',
IOU_BILL: '/iou/split',
getReportRoute: reportID => `/r/${reportID}`,
SEARCH: '/search',
SET_PASSWORD: '/setpassword/:validateCode',
SIGNIN: '/signin',
NOT_FOUND: '/404',
PROFILE: '/profile/:login',
getProfileRoute: login => `/profile/${login}`,
HOME: '',
SETTINGS: 'settings',
SETTINGS_PROFILE: 'settings/profile',
SETTINGS_PREFERENCES: 'settings/preferences',
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB: Random thought; these could be nested to make them a little more organized.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only problem with that is that ROUTES.SETTINGS is already 'settings' so what convention should we use then? Perhaps something like:

SETTINGS: {
    ROOT: 'settings',
},

I'm kind of partial to the underscore convention.

Copy link
Contributor

Choose a reason for hiding this comment

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

Cool, works for me!

SETTINGS_PASSWORD: 'settings/password',
SETTINGS_PAYMENTS: 'settings/payments',
NEW_GROUP: 'new/group',
NEW_CHAT: 'new/chat',
REPORT: 'r',
IOU_REQUEST: 'iou/request',
IOU_BILL: 'iou/split',
getReportRoute: reportID => `r/${reportID}`,
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
SEARCH: 'search',
SIGNIN: 'signin',
SET_PASSWORD: 'setpassword/:validateCode',
PROFILE: 'profile',
getProfileRoute: login => `profile/${login}`,
};
14 changes: 11 additions & 3 deletions src/components/ScreenWrapper.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import _ from 'underscore';
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
Expand All @@ -9,8 +10,11 @@ const propTypes = {
// Array of additional styles to add
style: PropTypes.arrayOf(PropTypes.object),

// Returns a function as a child to pass insets to
children: PropTypes.func.isRequired,
// Returns a function as a child to pass insets to or a node to render without insets
Copy link
Contributor

Choose a reason for hiding this comment

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

I love how this solution turned out. It's much better than the dual components that I suggested.

children: PropTypes.oneOfType([
PropTypes.node,
PropTypes.func,
]).isRequired,

// Whether to include padding bottom
includePaddingBottom: PropTypes.bool,
Expand Down Expand Up @@ -47,7 +51,11 @@ const ScreenWrapper = props => (
]}
>
<HeaderGap />
{props.children(insets)}
{// If props.children is a function, call it to provide the insets to the children.
_.isFunction(props.children)
? props.children(insets)
: props.children
}
</View>
);
}}
Expand Down
15 changes: 8 additions & 7 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';

import styles from '../../../styles/styles';
import ROUTES from '../../../ROUTES';
import {
SettingsModalStack,
NewChatModalStack,
Expand Down Expand Up @@ -29,7 +30,7 @@ const defaultSubRouteOptions = {

const IOUBillStackNavigator = () => (
<IOUBillModalStack.Navigator
path="/iou/split"
path={ROUTES.IOU_BILL}
>
<IOUBillModalStack.Screen
name="IOU_Bill_Root"
Expand All @@ -44,7 +45,7 @@ const IOUBillStackNavigator = () => (

const IOURequestModalStackNavigator = () => (
<IOURequestModalStack.Navigator
path="/iou/request"
path={ROUTES.IOU_REQUEST}
>
<IOURequestModalStack.Screen
name="IOU_Request_Root"
Expand All @@ -59,7 +60,7 @@ const IOURequestModalStackNavigator = () => (

const ProfileModalStackNavigator = () => (
<ProfileModalStack.Navigator
path="/profile"
path={ROUTES.PROFILE}
>
<ProfileModalStack.Screen
name="Profile_Root"
Expand All @@ -74,7 +75,7 @@ const ProfileModalStackNavigator = () => (

const SearchModalStackNavigator = () => (
<SearchModalStack.Navigator
path="/search"
path={ROUTES.SEARCH}
>
<SearchModalStack.Screen
name="Search_Root"
Expand All @@ -89,7 +90,7 @@ const SearchModalStackNavigator = () => (

const NewGroupModalStackNavigator = () => (
<NewGroupModalStack.Navigator
path="/new/group"
path={ROUTES.NEW_GROUP}
>
<NewGroupModalStack.Screen
name="NewGroup_Root"
Expand All @@ -104,7 +105,7 @@ const NewGroupModalStackNavigator = () => (

const NewChatModalStackNavigator = () => (
<NewChatModalStack.Navigator
path="/new/chat"
path={ROUTES.NEW_CHAT}
>
<NewChatModalStack.Screen
name="NewChat_Root"
Expand All @@ -119,7 +120,7 @@ const NewChatModalStackNavigator = () => (

const SettingsModalStackNavigator = () => (
<SettingsModalStack.Navigator
path="/settings"
path={ROUTES.SETTINGS}
>
<SettingsModalStack.Screen
name="Settings_Root"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,27 @@ import compose from '../../compose';
import CONST from '../../../CONST';

const propTypes = {
// Internal react-navigation stuff used to determine which view we should display
// Navigation state for this navigator
// See: https://reactnavigation.org/docs/navigation-state/
state: PropTypes.shape({
// Index of the focused route object in the routes array
index: PropTypes.number,

// List of route objects (screens) which are rendered in the navigator. It also represents the history in a
// stack navigator. There should be at least one item present in this array.
routes: PropTypes.arrayOf(PropTypes.shape({

// A unique key name for a screen. Created automatically by react-nav.
key: PropTypes.string,
})),
}).isRequired,

// The current view object that has a render method to call
// Object containing descriptors for each route with the route keys as its properties
Copy link
Contributor

Choose a reason for hiding this comment

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

I love these verbose comments!

// See: https://reactnavigation.org/docs/custom-navigators/#usenavigationbuilder
descriptors: PropTypes.objectOf(PropTypes.shape({

// A function which can be used to render the actual screen. Calling descriptors[route.key].render() will return
// a React element containing the screen content.
render: PropTypes.func,
})).isRequired,

Expand All @@ -38,6 +49,13 @@ const defaultProps = {


class ResponsiveView extends React.Component {
/**
* Returns the current descriptor for the focused screen in this navigators state. The descriptor has a function
* called render() that we must call each time this navigator updates. It's important to use this method to render
* a screen, otherwise any child navigators won't be connected to the navigation tree properly.
*
* @returns {Object}
*/
getCurrentViewDescriptor() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I remember I wanted to comment about this in the original PR, and I never did, and then when I remembered about it, I couldn't find the spot. So, sorry this is a late comment!

My thought was that it looks like this could be a functional component and just have this logic as part of the function before the return. I'm fine with this being a NAB, but I just didn't think there was any benefit to making this a class component and wanted to get your thoughts on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, this is an easy enough change to make and enforces the preference to use function components.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks!

const currentRoute = this.props.state.routes[this.props.state.index];
const currentRouteKey = currentRoute.key;
Expand Down
6 changes: 1 addition & 5 deletions src/libs/Navigation/Navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ function goBack() {
* Main navigation method for redirecting to a route.
* @param {String} route
*/
function navigate(route) {
if (!route) {
return;
}

function navigate(route = ROUTES.HOME) {
if (route === ROUTES.HOME) {
openDrawer();
return;
Expand Down
15 changes: 10 additions & 5 deletions src/libs/Navigation/NavigationRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
getPathFromState,
NavigationContainer,
} from '@react-navigation/native';
import Onyx, {withOnyx} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import {navigationRef} from './Navigation';
import linkingConfig from './linkingConfig';
import AppNavigator from './AppNavigator';
Expand All @@ -16,9 +16,14 @@ import ONYXKEYS from '../../ONYXKEYS';
import ROUTES from '../../ROUTES';
import styles from '../../styles/styles';
import themeColors from '../../styles/themes/default';
import {updateCurrentlyViewedReportID} from '../actions/Report';
import {setCurrentURL} from '../actions/App';

const propTypes = {
// Whether the current user is logged in with an authToken
authenticated: PropTypes.bool.isRequired,

// The current reportID that we are navigated to or should show in the ReportScreen
currentlyViewedReportID: PropTypes.string,
};

Expand All @@ -43,11 +48,11 @@ class NavigationRoot extends Component {
// hooked up
const path = getPathName(initialUrl);
let initialState = getStateFromPath(path, linkingConfig.config);
Onyx.set(ONYXKEYS.CURRENT_URL, path);
setCurrentURL(path);

// If we are landing on something other than the report screen or site root then we MUST set the
// initial route to the currently viewed report so there some history to navigate back from
if (path !== ROUTES.HOME && !path.includes(ROUTES.REPORT)) {
if (path !== `/${ROUTES.HOME}` && !path.includes(`/${ROUTES.REPORT}`)) {
const homeRoute = {
name: 'Home',
};
Expand Down Expand Up @@ -99,11 +104,11 @@ class NavigationRoot extends Component {
if (path.includes(ROUTES.REPORT)) {
const reportID = Number(_.last(path.split('/')));
if (!_.isNaN(reportID)) {
Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, String(reportID));
updateCurrentlyViewedReportID(reportID);
}
}

Onyx.merge(ONYXKEYS.CURRENT_URL, path);
setCurrentURL(path);
}}
ref={navigationRef}
linking={linkingConfig}
Expand Down
41 changes: 19 additions & 22 deletions src/libs/Navigation/linkTo.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,33 @@ import {
import linkingConfig from './linkingConfig';

export default function linkTo(navigation, path) {
if (!path.startsWith('/')) {
throw new Error(`The path must start with '/' (${path}).`);
}

const normalizedPath = !path.startsWith('/') ? `/${path}` : path;
Copy link
Contributor

Choose a reason for hiding this comment

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

I could have sworn we had a utility for adding slashes to the beginning of things, but for the life of me, I can't find it. I can't remember where it was used either... Does anything come to your mind? If not, then ignore this comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is one used in config.js I think, but it adds them to the end not the the front

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah yes! That's the one. It might be nice to add these to Str some day.

if (navigation === undefined) {
throw new Error("Couldn't find a navigation object. Is your component inside a screen in a navigator?");
}

const state = linkingConfig?.getStateFromPath
? linkingConfig.getStateFromPath(path, linkingConfig.config)
: getStateFromPath(path, linkingConfig?.config);
const state = linkingConfig.getStateFromPath
? linkingConfig.getStateFromPath(normalizedPath, linkingConfig.config)
: getStateFromPath(normalizedPath, linkingConfig.config);

if (state) {
let root = navigation;
let current;
if (!state) {
throw new Error('Failed to parse the path to a navigation state.');
}

// Traverse up to get the root navigation
// eslint-disable-next-line no-cond-assign
while ((current = root.dangerouslyGetParent())) {
root = current;
}
let root = navigation;
let current;

const action = getActionFromState(state, linkingConfig?.config);
// Traverse up to get the root navigation
// eslint-disable-next-line no-cond-assign
while ((current = root.dangerouslyGetParent())) {
root = current;
}

const action = getActionFromState(state, linkingConfig.config);

if (action !== undefined) {
root.dispatch(action);
} else {
root.reset(state);
}
if (action !== undefined) {
root.dispatch(action);
} else {
throw new Error('Failed to parse the path to a navigation state.');
root.reset(state);
}
}
Loading