Skip to content

Commit 146dd00

Browse files
authored
Merge pull request Expensify#56092 from callstack-internal/feature/allow-negative
Add PlusMinus icon and allow negative values in MoneyRequestAmountInput
2 parents b150b13 + 1bba8b9 commit 146dd00

26 files changed

Lines changed: 310 additions & 95 deletions

assets/images/plus-minus.svg

Lines changed: 7 additions & 0 deletions
Loading

src/components/Icon/Expensicons.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ import Phone from '@assets/images/phone.svg';
169169
import Pin from '@assets/images/pin.svg';
170170
import Plane from '@assets/images/plane.svg';
171171
import Play from '@assets/images/play.svg';
172+
import PlusMinus from '@assets/images/plus-minus.svg';
172173
import Plus from '@assets/images/plus.svg';
173174
import Printer from '@assets/images/printer.svg';
174175
import Profile from '@assets/images/profile.svg';
@@ -462,6 +463,7 @@ export {
462463
GalleryNotFound,
463464
Train,
464465
boltSlash,
466+
PlusMinus,
465467
MagnifyingGlassSpyMouthClosed,
466468
EmptySquare,
467469
CheckSquare,

src/components/MoneyRequestAmountInput.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@ type MoneyRequestAmountInputProps = {
9292
/** The width of inner content */
9393
contentWidth?: number;
9494

95+
/** Whether the amount is negative */
96+
isNegative?: boolean;
97+
98+
/** Function to toggle the amount to negative */
99+
toggleNegative?: () => void;
100+
101+
/** Function to clear the negative amount */
102+
clearNegative?: () => void;
103+
104+
/** Whether to allow flipping amount */
105+
allowFlippingAmount?: boolean;
95106
/** The testID of the input. Used to locate this view in end-to-end tests. */
96107
testID?: string;
97108
} & Pick<TextInputWithCurrencySymbolProps, 'autoGrowExtraSpace' | 'submitBehavior'>;
@@ -131,6 +142,10 @@ function MoneyRequestAmountInput(
131142
autoGrow = true,
132143
autoGrowExtraSpace,
133144
contentWidth,
145+
isNegative = false,
146+
allowFlippingAmount = false,
147+
toggleNegative,
148+
clearNegative,
134149
testID,
135150
submitBehavior,
136151
...props
@@ -164,6 +179,10 @@ function MoneyRequestAmountInput(
164179
*/
165180
const setNewAmount = useCallback(
166181
(newAmount: string) => {
182+
if (allowFlippingAmount && newAmount.startsWith('-') && toggleNegative) {
183+
toggleNegative();
184+
}
185+
167186
// Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value
168187
// More info: https://github.com/Expensify/App/issues/16974
169188
const newAmountWithoutSpaces = stripSpacesFromAmount(newAmount);
@@ -192,7 +211,7 @@ function MoneyRequestAmountInput(
192211
return strippedAmount;
193212
});
194213
},
195-
[decimals, onAmountChange],
214+
[allowFlippingAmount, decimals, onAmountChange, toggleNegative],
196215
);
197216

198217
useImperativeHandle(moneyRequestAmountInputRef, () => ({
@@ -252,6 +271,11 @@ function MoneyRequestAmountInput(
252271
*/
253272
const textInputKeyPress = ({nativeEvent}: NativeSyntheticEvent<KeyboardEvent>) => {
254273
const key = nativeEvent?.key.toLowerCase();
274+
275+
if (!textInput.current?.value && key === 'backspace' && isNegative) {
276+
clearNegative?.();
277+
}
278+
255279
if (isMobileSafari() && key === CONST.PLATFORM_SPECIFIC_KEYS.CTRL.DEFAULT) {
256280
// Optimistically anticipate forward-delete on iOS Safari (in cases where the Mac accessibility keyboard is being
257281
// used for input). If the Control-D shortcut doesn't get sent, the ref will still be reset on the next key press.
@@ -343,6 +367,7 @@ function MoneyRequestAmountInput(
343367
onMouseDown={handleMouseDown}
344368
onMouseUp={handleMouseUp}
345369
contentWidth={contentWidth}
370+
isNegative={isNegative}
346371
testID={testID}
347372
submitBehavior={submitBehavior}
348373
/>

src/components/ReportActionItem/MoneyRequestView.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
isReportInGroupPolicy,
4444
isSettled as isSettledReportUtils,
4545
isTrackExpenseReport,
46+
shouldEnableNegative,
4647
} from '@libs/ReportUtils';
4748
import type {TransactionDetails} from '@libs/ReportUtils';
4849
import {hasEnabledTags} from '@libs/TagsOptionsListUtils';
@@ -146,6 +147,8 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
146147
const [transactionBackup] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${linkedTransactionID}`, {canBeMissing: true});
147148
const transactionViolations = useTransactionViolations(transaction?.transactionID);
148149

150+
const allowNegativeAmount = shouldEnableNegative(report, policy);
151+
149152
const {
150153
created: transactionDate,
151154
amount: transactionAmount,
@@ -160,7 +163,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals
160163
originalAmount: transactionOriginalAmount,
161164
originalCurrency: transactionOriginalCurrency,
162165
postedDate: transactionPostedDate,
163-
} = useMemo<Partial<TransactionDetails>>(() => getTransactionDetails(transaction) ?? {}, [transaction]);
166+
} = useMemo<Partial<TransactionDetails>>(() => getTransactionDetails(transaction, undefined, undefined, allowNegativeAmount) ?? {}, [allowNegativeAmount, transaction]);
164167
const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
165168
const isDistanceRequest = isDistanceRequestTransactionUtils(transaction);
166169
const isPerDiemRequest = isPerDiemRequestTransactionUtils(transaction);

src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
33
import AmountTextInput from '@components/AmountTextInput';
44
import CurrencySymbolButton from '@components/CurrencySymbolButton';
5+
import Text from '@components/Text';
56
import useLocalize from '@hooks/useLocalize';
67
import useThemeStyles from '@hooks/useThemeStyles';
78
import {getLocalizedCurrencySymbol} from '@libs/CurrencyUtils';
@@ -22,6 +23,7 @@ function BaseTextInputWithCurrencySymbol(
2223
isCurrencyPressable = true,
2324
hideCurrencySymbol = false,
2425
extraSymbol,
26+
isNegative = false,
2527
style,
2628
...rest
2729
}: BaseTextInputWithCurrencySymbolProps,
@@ -41,8 +43,11 @@ function BaseTextInputWithCurrencySymbol(
4143
onChangeAmount(newAmount);
4244
};
4345

46+
const negativeSymbol = <Text style={[styles.iouAmountText]}>-</Text>;
47+
4448
return (
4549
<>
50+
{isNegative && negativeSymbol}
4651
{!hideCurrencySymbol && (
4752
<CurrencySymbolButton
4853
currencySymbol={currencySymbol ?? ''}

src/components/TextInputWithCurrencySymbol/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ type BaseTextInputWithCurrencySymbolProps = {
7878
/** Hide the focus styles on TextInput */
7979
hideFocusedState?: boolean;
8080

81+
/** Whether the amount is negative */
82+
isNegative?: boolean;
83+
8184
/** The test ID of TextInput. Used to locate the view in end-to-end tests. */
8285
testID?: string;
8386
} & Pick<BaseTextInputProps, 'autoFocus' | 'autoGrow' | 'autoGrowExtraSpace' | 'contentWidth' | 'onPress' | 'submitBehavior'>;

src/languages/de.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,7 @@ const translations = {
11151115
payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zahle ${formattedAmount} anderswo` : `Anderswo bezahlen`),
11161116
nextStep: 'Nächste Schritte',
11171117
finished: 'Fertiggestellt',
1118+
flip: 'Umkehren',
11181119
sendInvoice: ({amount}: RequestAmountParams) => `Sende ${amount} Rechnung`,
11191120
submitAmount: ({amount}: RequestAmountParams) => `Einreichen ${amount}`,
11201121
expenseAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `${formattedAmount}${comment ? `für ${comment}` : ''}`,

src/languages/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,7 @@ const translations = {
11001100
payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} elsewhere` : `Pay elsewhere`),
11011101
nextStep: 'Next steps',
11021102
finished: 'Finished',
1103+
flip: 'Flip',
11031104
sendInvoice: ({amount}: RequestAmountParams) => `Send ${amount} invoice`,
11041105
submitAmount: ({amount}: RequestAmountParams) => `Submit ${amount}`,
11051106
expenseAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `${formattedAmount}${comment ? ` for ${comment}` : ''}`,

src/languages/es.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,7 @@ const translations = {
10951095
payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} de otra forma` : `Pagar de otra forma`),
10961096
nextStep: 'Pasos siguientes',
10971097
finished: 'Finalizado',
1098+
flip: 'Cambiar',
10981099
sendInvoice: ({amount}: RequestAmountParams) => `Enviar factura de ${amount}`,
10991100
submitAmount: ({amount}: RequestAmountParams) => `Solicitar ${amount}`,
11001101
expenseAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `${formattedAmount}${comment ? ` para ${comment}` : ''}`,

src/languages/fr.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,7 @@ const translations = {
11151115
payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Payer ${formattedAmount} ailleurs` : `Payer ailleurs`),
11161116
nextStep: 'Étapes suivantes',
11171117
finished: 'Terminé',
1118+
flip: 'Inverser',
11181119
sendInvoice: ({amount}: RequestAmountParams) => `Envoyer une facture de ${amount}`,
11191120
submitAmount: ({amount}: RequestAmountParams) => `Soumettre ${amount}`,
11201121
expenseAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `${formattedAmount}${comment ? `pour ${comment}` : ''}`,

0 commit comments

Comments
 (0)