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

Money request header scrollable #21918

Merged
merged 22 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
780477c
Make part of MoneyRequestHeader scrollable, the top of the header sta…
ShogunFire Jun 12, 2023
bc80a96
ListFooterComponentStyle was overwritten so the header was at the bottom
ShogunFire Jun 20, 2023
710dbd0
Merge remote-tracking branch 'origin/main' into moneyRequestHeaderScr…
ShogunFire Jun 20, 2023
367261d
Merge was wrong, it looks like we use the Id now instead of the email
ShogunFire Jun 20, 2023
7bab3f1
Another problem with the merge, we use the id now
ShogunFire Jun 29, 2023
795295a
Merge branch 'main' into moneyRequestHeaderScrollable
ShogunFire Jun 29, 2023
f60c83c
Changing names of files
ShogunFire Jun 29, 2023
bce157a
Fix problem with merging
ShogunFire Jun 29, 2023
89cedaf
Fixing the merge again
ShogunFire Jun 29, 2023
1c0af6e
I changed that but I don't know why so I changed it back
ShogunFire Jun 29, 2023
6b961a1
Correct one lint error
ShogunFire Jun 30, 2023
fc5b73a
Correct Lint errors
ShogunFire Jun 30, 2023
8e1432a
just to fix lint errors, for the props validation
ShogunFire Jun 30, 2023
57806b1
Merge remote-tracking branch 'origin/main' into moneyRequestHeaderScr…
ShogunFire Jun 30, 2023
837f690
Try with former lint version because new one seem to bug
ShogunFire Jun 30, 2023
61985bc
After run prettier
ShogunFire Jun 30, 2023
54f6edd
Revert "Try with former lint version because new one seem to bug"
ShogunFire Jun 30, 2023
86d2272
Resolve the errors about prop type in the android console
ShogunFire Jul 3, 2023
b211f98
Merge remote-tracking branch 'origin/main' into moneyRequestHeaderScr…
ShogunFire Jul 3, 2023
8e10409
Resolve lint errors and merging errors
ShogunFire Jul 3, 2023
bd3ac02
Reverting to the good prop type and fixing the console error on android
ShogunFire Jul 3, 2023
c24c9d7
Merge branch 'main' into moneyRequestHeaderScrollable
ShogunFire Jul 7, 2023
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: 1 addition & 1 deletion .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# See https://github.com/rhysd/actionlint/blob/main/docs/config.md
self-hosted-runner:
labels:
- ubuntu-latest-xl
- ubuntu-20.04-64core
ShogunFire marked this conversation as resolved.
Show resolved Hide resolved
- macos-12-xl
64 changes: 55 additions & 9 deletions src/components/InvertedFlatList/index.android.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
import React, {forwardRef} from 'react';
import {View} from 'react-native';
import {View, FlatList} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import BaseInvertedFlatList from './BaseInvertedFlatList';
import styles from '../../styles/styles';
import stylePropTypes from '../../styles/stylePropTypes';

const propTypes = {
/** Passed via forwardRef so we can access the FlatList ref */
innerRef: PropTypes.shape({
current: PropTypes.instanceOf(FlatList),
}).isRequired,

/** The style of the footer of the list */
ListFooterComponentStyle: stylePropTypes,
};

const defaultProps = {
ListFooterComponentStyle: {},
};

function InvertedCell(props) {
return (
Expand All @@ -13,16 +30,45 @@ function InvertedCell(props) {
);
}

class InvertedFlatList extends React.Component {
constructor(props) {
super(props);

this.list = undefined;
}

componentDidMount() {
if (!_.isFunction(this.props.innerRef)) {
// eslint-disable-next-line no-param-reassign
this.props.innerRef.current = this.list;
} else {
this.props.innerRef(this.list);
}
}

render() {
return (
<BaseInvertedFlatList
// eslint-disable-next-line react/jsx-props-no-spreading
{...this.props}
ref={(el) => (this.list = el)}
// Manually invert the FlatList to circumvent a react-native bug that causes ANR (application not responding) on android 13
inverted={false}
style={styles.invert}
ListFooterComponentStyle={[styles.invert, this.props.ListFooterComponentStyle]}
verticalScrollbarPosition="left" // We are mirroring the X and Y axis, so we need to swap the scrollbar position
CellRendererComponent={InvertedCell}
/>
);
}
}
InvertedFlatList.propTypes = propTypes;
InvertedFlatList.defaultProps = defaultProps;

export default forwardRef((props, ref) => (
<BaseInvertedFlatList
<InvertedFlatList
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
ref={ref}
// Manually invert the FlatList to circumvent a react-native bug that causes ANR (application not responding) on android 13
inverted={false}
style={styles.invert}
ListFooterComponentStyle={styles.invert}
verticalScrollbarPosition="left" // We are mirroring the X and Y axis, so we need to swap the scrollbar position
CellRendererComponent={InvertedCell}
innerRef={ref}
/>
));
214 changes: 214 additions & 0 deletions src/components/MoneyRequestDetails.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import React from 'react';
import {withOnyx} from 'react-native-onyx';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import iouReportPropTypes from '../pages/iouReportPropTypes';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import * as ReportUtils from '../libs/ReportUtils';
import * as Expensicons from './Icon/Expensicons';
import Text from './Text';
import participantPropTypes from './participantPropTypes';
import Avatar from './Avatar';
import styles from '../styles/styles';
import themeColors from '../styles/themes/default';
import CONST from '../CONST';
import withWindowDimensions from './withWindowDimensions';
import compose from '../libs/compose';
import ROUTES from '../ROUTES';
import Icon from './Icon';
import SettlementButton from './SettlementButton';
import * as Policy from '../libs/actions/Policy';
import ONYXKEYS from '../ONYXKEYS';
import * as IOU from '../libs/actions/IOU';
import * as CurrencyUtils from '../libs/CurrencyUtils';
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
import DateUtils from '../libs/DateUtils';
import reportPropTypes from '../pages/reportPropTypes';
import * as UserUtils from '../libs/UserUtils';

const propTypes = {
/** The report currently being looked at */
report: iouReportPropTypes.isRequired,

/** The expense report or iou report (only will have a value if this is a transaction thread) */
parentReport: iouReportPropTypes,

/** The policy object for the current route */
policy: PropTypes.shape({
/** The name of the policy */
name: PropTypes.string,

/** The URL for the policy avatar */
avatar: PropTypes.string,
}),

/** The chat report this report is linked to */
chatReport: reportPropTypes,

/** Personal details so we can get the ones for the report participants */
personalDetails: PropTypes.objectOf(participantPropTypes).isRequired,

/** Whether we're viewing a report with a single transaction in it */
isSingleTransactionView: PropTypes.bool,

/** Session info for the currently logged in user. */
session: PropTypes.shape({
/** Currently logged in user email */
email: PropTypes.string,
}),

...withLocalizePropTypes,
};

const defaultProps = {
isSingleTransactionView: false,
chatReport: {},
session: {
email: null,
},
parentReport: {},
policy: null,
};

function MoneyRequestDetails(props) {
// These are only used for the single transaction view and not for expense and iou reports
const {amount: transactionAmount, currency: transactionCurrency, comment: transactionDescription} = ReportUtils.getMoneyRequestAction(props.parentReportAction);
const formattedTransactionAmount = transactionAmount && transactionCurrency && CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency);
const transactionDate = lodashGet(props.parentReportAction, ['created']);
const formattedTransactionDate = DateUtils.getDateStringFromISOTimestamp(transactionDate);

const formattedAmount = CurrencyUtils.convertToDisplayString(ReportUtils.getMoneyRequestTotal(props.report), props.report.currency);
const moneyRequestReport = props.isSingleTransactionView ? props.parentReport : props.report;
const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID);
const isExpenseReport = ReportUtils.isExpenseReport(moneyRequestReport);
const payeeName = isExpenseReport ? ReportUtils.getPolicyName(moneyRequestReport) : ReportUtils.getDisplayNameForParticipant(moneyRequestReport.managerID);
const payeeAvatar = isExpenseReport
? ReportUtils.getWorkspaceAvatar(moneyRequestReport)
: UserUtils.getAvatar(lodashGet(props.personalDetails, [moneyRequestReport.managerID, 'avatar']), moneyRequestReport.managerID);
const isPayer =
Policy.isAdminOfFreePolicy([props.policy]) || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && lodashGet(props.session, 'accountID', null) === moneyRequestReport.managerID);
const shouldShowSettlementButton = !isSettled && !props.isSingleTransactionView && isPayer;
const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport);
const shouldShowPaypal = Boolean(lodashGet(props.personalDetails, [moneyRequestReport.managerID, 'payPalMeAddress']));
return (
<View style={[{backgroundColor: themeColors.highlightBG}, styles.pl0]}>
<View style={[styles.ph5, styles.pb2]}>
<Text style={[styles.textLabelSupporting, styles.lh16]}>{props.translate('common.to')}</Text>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.justifyContentBetween, styles.pv3]}>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.justifyContentBetween]}>
<Avatar
source={payeeAvatar}
type={isExpenseReport ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR}
name={payeeName}
size={CONST.AVATAR_SIZE.DEFAULT}
/>
<View style={[styles.flex1, styles.flexColumn, styles.ml3]}>
<Text
style={[styles.headerText, styles.pre]}
numberOfLines={1}
>
{payeeName}
</Text>
{isExpenseReport && (
<Text
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
numberOfLines={1}
>
{props.translate('workspace.common.workspace')}
</Text>
)}
</View>
</View>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
{!props.isSingleTransactionView && <Text style={[styles.newKansasLarge]}>{formattedAmount}</Text>}
{!props.isSingleTransactionView && isSettled && (
<View style={styles.defaultCheckmarkWrapper}>
<Icon
src={Expensicons.Checkmark}
fill={themeColors.iconSuccessFill}
/>
</View>
)}
{shouldShowSettlementButton && !props.isSmallScreenWidth && (
<View style={[styles.ml4]}>
<SettlementButton
currency={props.report.currency}
policyID={props.report.policyID}
shouldShowPaypal={shouldShowPaypal}
chatReportID={props.chatReport.reportID}
iouReport={props.report}
onPress={(paymentType) => IOU.payMoneyRequest(paymentType, props.chatReport, props.report)}
enablePaymentsRoute={ROUTES.BANK_ACCOUNT_NEW}
addBankAccountRoute={bankAccountRoute}
shouldShowPaymentOptions
/>
</View>
)}
</View>
</View>
{shouldShowSettlementButton && props.isSmallScreenWidth && (
<SettlementButton
currency={props.report.currency}
policyID={props.report.policyID}
shouldShowPaypal={shouldShowPaypal}
chatReportID={props.report.chatReportID}
iouReport={props.report}
onPress={(paymentType) => IOU.payMoneyRequest(paymentType, props.chatReport, props.report)}
enablePaymentsRoute={ROUTES.BANK_ACCOUNT_NEW}
addBankAccountRoute={bankAccountRoute}
shouldShowPaymentOptions
/>
)}
</View>
{props.isSingleTransactionView && (
<>
<MenuItemWithTopDescription
title={formattedTransactionAmount}
shouldShowTitleIcon={isSettled}
titleIcon={Expensicons.Checkmark}
description={`${props.translate('iou.amount')} • ${props.translate('iou.cash')}${isSettled ? ` • ${props.translate('iou.settledExpensify')}` : ''}`}
titleStyle={styles.newKansasLarge}
disabled={isSettled}
// Note: These options are temporarily disabled while we figure out the required API changes
// shouldShowRightIcon={!isSettled}
// onPress={() => Navigation.navigate(ROUTES.getEditRequestRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))}
/>
<MenuItemWithTopDescription
description={props.translate('common.description')}
title={transactionDescription}
disabled={isSettled}
// shouldShowRightIcon={!isSettled}
// onPress={() => Navigation.navigate(ROUTES.getEditRequestRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION))}
/>
<MenuItemWithTopDescription
description={props.translate('common.date')}
title={formattedTransactionDate}
// shouldShowRightIcon={!isSettled}
// onPress={() => Navigation.navigate(ROUTES.getEditRequestRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.DATE))}
/>
</>
)}
</View>
);
}

MoneyRequestDetails.displayName = 'MoneyRequestDetails';
MoneyRequestDetails.propTypes = propTypes;
MoneyRequestDetails.defaultProps = defaultProps;

export default compose(
withWindowDimensions,
withLocalize,
withOnyx({
chatReport: {
key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`,
},
session: {
key: ONYXKEYS.SESSION,
},
parentReport: {
key: (props) => `${ONYXKEYS.COLLECTION.REPORT}${props.report.parentReportID}`,
},
}),
)(MoneyRequestDetails);
Loading