Skip to content

Commit

Permalink
Merge pull request #50387 from Krishna2323/krishna2323/issue/49847
Browse files Browse the repository at this point in the history
Feat: Implement to use a 👍icon next to approved report preview
  • Loading branch information
youssef-lr authored Nov 19, 2024
2 parents 812ccd7 + 9639ba6 commit a73f681
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 20 deletions.
3 changes: 3 additions & 0 deletions src/components/ProcessMoneyReportHoldMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,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, hasValidNonHeldAmount} = 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);
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 @@ -196,11 +203,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 @@ -232,6 +247,8 @@ function ReportPreview({
} else if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) {
setIsHoldMenuVisible(true);
} else {
setIsApprovedAnimationRunning(true);
HapticFeedback.longPress();
IOU.approveMoneyRequest(iouReport, true);
}
};
Expand Down Expand Up @@ -330,14 +347,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;

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

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

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

Expand All @@ -450,6 +468,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 @@ -484,7 +510,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 @@ -512,6 +538,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 @@ -538,6 +572,8 @@ function ReportPreview({
<AnimatedSettlementButton
onlyShowPayElsewhere={onlyShowPayElsewhere}
isPaidAnimationRunning={isPaidAnimationRunning}
isApprovedAnimationRunning={isApprovedAnimationRunning}
canIOUBePaid={canIOUBePaidAndApproved || isPaidAnimationRunning}
onAnimationFinish={stopAnimation}
formattedAmount={getSettlementAmount() ?? ''}
currency={iouReport?.currency}
Expand Down Expand Up @@ -606,7 +642,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 @@ -6781,6 +6781,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 @@ -6834,7 +6835,7 @@ function canIOUBePaid(
reimbursableSpend !== 0 &&
!isChatReportArchived &&
!isAutoReimbursable &&
!shouldBeApproved &&
(!shouldBeApproved || !shouldCheckApprovedState) &&
!isPayAtEndExpenseReport
);
}
Expand Down

0 comments on commit a73f681

Please sign in to comment.