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

Feat: Implement to use a 👍icon next to approved report preview #50387

Merged
merged 13 commits into from
Nov 19, 2024
Merged
3 changes: 3 additions & 0 deletions src/components/ProcessMoneyReportHoldMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ function ProcessMoneyReportHoldMenu({

const onSubmit = (full: boolean) => {
if (isApprove) {
if (startAnimation) {
startAnimation();
}
IOU.approveMoneyRequest(moneyRequestReport, full);
if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId() ?? '-1', moneyRequestReport?.reportID ?? '')) {
Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport?.reportID ?? ''));
Expand Down
56 changes: 49 additions & 7 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ function ReportPreview({
);

const [isPaidAnimationRunning, setIsPaidAnimationRunning] = useState(false);
const [isApprovedAnimationRunning, setIsApprovedAnimationRunning] = useState(false);
const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false);
const [requestType, setRequestType] = useState<ActionHandledType>();
const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(iouReport, policy);
Expand All @@ -140,12 +141,18 @@ function ReportPreview({
}));
const checkMarkScale = useSharedValue(iouSettled ? 1 : 0);

const isApproved = ReportUtils.isReportApproved(iouReport, action);
const thumbsUpScale = useSharedValue(isApproved ? 1 : 0.25);
Copy link
Contributor

Choose a reason for hiding this comment

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

0.25 - where does this value come from? what issue with 0?

Copy link
Contributor

Choose a reason for hiding this comment

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

bump ^

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we use 0, it will look less subtle than this . #49847 (comment)

const thumbsUpStyle = useAnimatedStyle(() => ({
...styles.defaultCheckmarkWrapper,
transform: [{scale: thumbsUpScale.value}],
}));

const moneyRequestComment = action?.childLastMoneyRequestComment ?? '';
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);
const isInvoiceRoom = ReportUtils.isInvoiceRoom(chatReport);
const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport);

const isApproved = ReportUtils.isReportApproved(iouReport, action);
const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport, policy);
const numberOfRequests = allTransactions.length;
const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID);
Expand Down Expand Up @@ -195,11 +202,19 @@ function ReportPreview({
const {isDelegateAccessRestricted, delegatorEmail} = useDelegateUserDetails();
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);

const stopAnimation = useCallback(() => setIsPaidAnimationRunning(false), []);
const stopAnimation = useCallback(() => {
setIsPaidAnimationRunning(false);
setIsApprovedAnimationRunning(false);
}, []);

const startAnimation = useCallback(() => {
setIsPaidAnimationRunning(true);
HapticFeedback.longPress();
}, []);
const startApprovedAnimation = useCallback(() => {
setIsApprovedAnimationRunning(true);
HapticFeedback.longPress();
}, []);
const confirmPayment = useCallback(
(type: PaymentMethodType | undefined, payAsBusiness?: boolean) => {
if (!type) {
Expand Down Expand Up @@ -231,6 +246,8 @@ function ReportPreview({
} else if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) {
setIsHoldMenuVisible(true);
} else {
setIsApprovedAnimationRunning(true);
HapticFeedback.longPress();
IOU.approveMoneyRequest(iouReport, true);
}
};
Expand Down Expand Up @@ -328,14 +345,15 @@ function ReportPreview({

const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
const getCanIOUBePaid = useCallback(
(onlyShowPayElsewhere = false) => IOU.canIOUBePaid(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere),
(onlyShowPayElsewhere = false, shouldCheckApprovedState = true) => IOU.canIOUBePaid(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere, shouldCheckApprovedState),
[iouReport, chatReport, policy, allTransactions],
);

const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]);
const canIOUBePaidAndApproved = useMemo(() => getCanIOUBePaid(false, false), [getCanIOUBePaid]);
const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]);
const shouldShowPayButton = isPaidAnimationRunning || canIOUBePaid || onlyShowPayElsewhere;
const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, policy), [iouReport, policy]);
const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, policy), [iouReport, policy]) || isApprovedAnimationRunning;
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need this || isApprovedAnimationRunning;?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we don't add || isApprovedAnimationRunning, the button will be hidden as soon as we approve the expense, and IOU.canApproveIOU(iouReport, policy) becomes false, interrupting the animation.


const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport);

Expand Down Expand Up @@ -422,7 +440,7 @@ function ReportPreview({
const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin && ReportUtils.canBeExported(iouReport);

useEffect(() => {
if (!isPaidAnimationRunning) {
if (!isPaidAnimationRunning || isApprovedAnimationRunning) {
return;
}

Expand All @@ -448,6 +466,14 @@ function ReportPreview({
}
}, [isPaidAnimationRunning, iouSettled, checkMarkScale]);

useEffect(() => {
if (!isApproved) {
return;
}

thumbsUpScale.value = withSpring(1, {duration: 200});
}, [isApproved, thumbsUpScale]);

return (
<OfflineWithFeedback
pendingAction={iouReport?.pendingFields?.preview}
Expand Down Expand Up @@ -482,7 +508,7 @@ function ReportPreview({
<View style={styles.expenseAndReportPreviewTextContainer}>
<View style={styles.flexRow}>
<Animated.View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, previewMessageStyle]}>
<Text style={[styles.textLabelSupporting, styles.lh16]}>{previewMessage}</Text>
<Text style={[styles.textLabelSupporting, styles.lh20]}>{previewMessage}</Text>
</Animated.View>
{shouldShowRBR && (
<Icon
Expand Down Expand Up @@ -510,6 +536,14 @@ function ReportPreview({
/>
</Animated.View>
)}
{isApproved && (
<Animated.View style={thumbsUpStyle}>
<Icon
src={Expensicons.ThumbsUp}
fill={theme.icon}
/>
</Animated.View>
)}
</View>
</View>
{shouldShowSubtitle && !!supportText && (
Expand All @@ -536,6 +570,8 @@ function ReportPreview({
<AnimatedSettlementButton
onlyShowPayElsewhere={onlyShowPayElsewhere}
isPaidAnimationRunning={isPaidAnimationRunning}
isApprovedAnimationRunning={isApprovedAnimationRunning}
canIOUBePaid={canIOUBePaidAndApproved || isPaidAnimationRunning}
onAnimationFinish={stopAnimation}
formattedAmount={getSettlementAmount() ?? ''}
currency={iouReport?.currency}
Expand Down Expand Up @@ -604,7 +640,13 @@ function ReportPreview({
chatReport={chatReport}
moneyRequestReport={iouReport}
transactionCount={numberOfRequests}
startAnimation={startAnimation}
startAnimation={() => {
if (requestType === CONST.IOU.REPORT_ACTION_TYPE.APPROVE) {
startApprovedAnimation();
} else {
startAnimation();
}
}}
/>
)}
</OfflineWithFeedback>
Expand Down
56 changes: 44 additions & 12 deletions src/components/SettlementButton/AnimatedSettlementButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,18 @@ import type SettlementButtonProps from './types';
type AnimatedSettlementButtonProps = SettlementButtonProps & {
isPaidAnimationRunning: boolean;
onAnimationFinish: () => void;
isApprovedAnimationRunning: boolean;
canIOUBePaid: boolean;
};

function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, isDisabled, ...settlementButtonProps}: AnimatedSettlementButtonProps) {
function AnimatedSettlementButton({
isPaidAnimationRunning,
onAnimationFinish,
isApprovedAnimationRunning,
isDisabled,
canIOUBePaid,
...settlementButtonProps
}: AnimatedSettlementButtonProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const buttonScale = useSharedValue(1);
Expand All @@ -38,12 +47,13 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is
overflow: 'hidden',
marginTop: buttonMarginTop.value,
}));
const buttonDisabledStyle = isPaidAnimationRunning
? {
opacity: 1,
...styles.cursorDefault,
}
: undefined;
const buttonDisabledStyle =
isPaidAnimationRunning || isApprovedAnimationRunning
? {
opacity: 1,
...styles.cursorDefault,
}
: undefined;

const resetAnimation = useCallback(() => {
// eslint-disable-next-line react-compiler/react-compiler
Expand All @@ -56,7 +66,7 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is
}, [buttonScale, buttonOpacity, paymentCompleteTextScale, paymentCompleteTextOpacity, height, buttonMarginTop, styles.expenseAndReportPreviewTextButtonContainer.gap]);

useEffect(() => {
if (!isPaidAnimationRunning) {
if (!isApprovedAnimationRunning && !isPaidAnimationRunning) {
resetAnimation();
return;
}
Expand All @@ -67,13 +77,30 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is

// Wait for the above animation + 1s delay before hiding the component
const totalDelay = CONST.ANIMATION_PAID_DURATION + CONST.ANIMATION_PAID_BUTTON_HIDE_DELAY;
const willShowPaymentButton = canIOUBePaid && isApprovedAnimationRunning;
height.value = withDelay(
totalDelay,
withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}, () => runOnJS(onAnimationFinish)()),
withTiming(willShowPaymentButton ? variables.componentSizeNormal : 0, {duration: CONST.ANIMATION_PAID_DURATION}, () => runOnJS(onAnimationFinish)()),
);
buttonMarginTop.value = withDelay(
totalDelay,
withTiming(willShowPaymentButton ? styles.expenseAndReportPreviewTextButtonContainer.gap : 0, {duration: CONST.ANIMATION_PAID_DURATION}),
);
buttonMarginTop.value = withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}));
paymentCompleteTextOpacity.value = withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}));
}, [isPaidAnimationRunning, onAnimationFinish, buttonOpacity, buttonScale, height, paymentCompleteTextOpacity, paymentCompleteTextScale, buttonMarginTop, resetAnimation]);
}, [
isPaidAnimationRunning,
isApprovedAnimationRunning,
onAnimationFinish,
buttonOpacity,
buttonScale,
height,
paymentCompleteTextOpacity,
paymentCompleteTextScale,
buttonMarginTop,
resetAnimation,
canIOUBePaid,
styles.expenseAndReportPreviewTextButtonContainer.gap,
]);

return (
<Animated.View style={containerStyles}>
Expand All @@ -82,11 +109,16 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is
<Text style={[styles.buttonMediumText]}>{translate('iou.paymentComplete')}</Text>
</Animated.View>
)}
{isApprovedAnimationRunning && (
<Animated.View style={paymentCompleteTextStyles}>
<Text style={[styles.buttonMediumText]}>{translate('iou.approved')}</Text>
</Animated.View>
)}
<Animated.View style={buttonStyles}>
<SettlementButton
// eslint-disable-next-line react/jsx-props-no-spreading
{...settlementButtonProps}
isDisabled={isPaidAnimationRunning || isDisabled}
isDisabled={isPaidAnimationRunning || isApprovedAnimationRunning || isDisabled}
disabledStyle={buttonDisabledStyle}
/>
</Animated.View>
Expand Down
3 changes: 2 additions & 1 deletion src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6777,6 +6777,7 @@ function canIOUBePaid(
policy: OnyxTypes.OnyxInputOrEntry<OnyxTypes.Policy>,
transactions?: OnyxTypes.Transaction[],
onlyShowPayElsewhere = false,
shouldCheckApprovedState = true,
) {
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);
const reportNameValuePairs = ReportUtils.getReportNameValuePairs(chatReport?.reportID);
Expand Down Expand Up @@ -6830,7 +6831,7 @@ function canIOUBePaid(
reimbursableSpend !== 0 &&
!isChatReportArchived &&
!isAutoReimbursable &&
!shouldBeApproved &&
(!shouldBeApproved || !shouldCheckApprovedState) &&
!isPayAtEndExpenseReport
);
}
Expand Down
Loading