-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
[HOLD] Implement Uneven Splits #40386
Merged
Merged
Changes from 6 commits
Commits
Show all changes
118 commits
Select commit
Hold shift + click to select a range
d99170a
Add TS definitions
youssef-lr 99efbb6
Set split shares when first creating the split
youssef-lr cf65044
Add IOU functions to set split shares and adjust them
youssef-lr 97ac991
Allow configuring split amounts
youssef-lr 4bc7142
Use temporary amount input
youssef-lr e9cc173
Pass custom styles
youssef-lr b24ff87
Temporary updates to the amount input
youssef-lr 6afe0e5
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr 9e53204
Allow setting individual shares
youssef-lr 00db6ff
Show a form error if sum of shares don't match total
youssef-lr 23d8693
Prevent submitting form if total doesn't match
youssef-lr cc3de14
Adjust shares automatically
youssef-lr d98103f
Revert changes
youssef-lr 14c9ead
Use temporary input
youssef-lr f032df0
Show even splits for workspace chat splits
youssef-lr 3d2e12e
Send uneven splits to backend
youssef-lr eb5c8f1
Save splits array in the transaction optimistically
youssef-lr 39fa362
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr 2001821
Copy changes to MoneyRequestConfirmationList
youssef-lr f8116b3
Allow excluding group chat users from splits by setting amount to 0
youssef-lr 756d11a
Reset split shares when amount changes
youssef-lr 1cd5bd1
Display read only shares in splti details page
youssef-lr 2cb1d95
Show split share in split preview card
youssef-lr 959d6d0
Allow easy tab navigation for inputs
youssef-lr e75e56f
Refactoring and TS fixes
youssef-lr 7baa777
wip: Reset button
youssef-lr 6712733
Allow resetting the amounts
youssef-lr 327624e
Bug fix
youssef-lr e915d7c
Debounce form error in splits page
youssef-lr 845d1a6
Bug fix
youssef-lr 6d5762f
Lint
youssef-lr 5c1758e
Hide inputs for Smartscan splits
youssef-lr 1579091
Fix currency not shown in split amounts in details page
youssef-lr bca161b
Bug fix
youssef-lr f5b0c10
Filter out participants with shares equal to 0
youssef-lr db455e5
Prevent creating a split containing a single participant
youssef-lr cbe5d36
Prevent submitting form if there's an error
youssef-lr 5dd0aed
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr 66c367d
Lint
youssef-lr dc7f13d
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr a22f42d
Add margin next to participant emails
youssef-lr 57610d7
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr ec56107
Always show confirmation for uneven splits
youssef-lr 6cd30c2
Fix input style
youssef-lr 4e71237
Clean up code for section button
youssef-lr 076d99a
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr e064698
TS lint
youssef-lr e7ff3dc
More lint
youssef-lr 8f33768
Skip confirmation screen for smartscanned splits
youssef-lr d5da181
Remove workaround for removing participants
youssef-lr 7eba6d0
Bring back effect
youssef-lr 23e41fe
Lint
youssef-lr 061fcf1
Don't run unneeded logic unless we have modified shares
youssef-lr 946d75f
Translation
youssef-lr 6590eb4
TS
youssef-lr ed6f2e3
More TS
youssef-lr 624bad4
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr af91070
TS and comment imporvement
youssef-lr 2d0d0ba
Remove unintended changes
youssef-lr 17c172b
One last TS fix
youssef-lr 64a8525
Remove unintended change
youssef-lr 58da039
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr d49ab6d
Cleanup
youssef-lr d0ce531
Cleanup
youssef-lr 4df0953
Header new copy
youssef-lr dd6005f
Add comment
youssef-lr e46a6ce
Show current user's share in the split preview
youssef-lr 1599a86
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr 0daec60
Fix form error disappearing
youssef-lr 9a74508
Fix merchant error missing when completing a split bill
youssef-lr 9dee700
Cleanup
youssef-lr 742e4e3
Rename variable
youssef-lr 44ad9e1
Clean up code
youssef-lr f3db250
Fix selection always moving to the end
youssef-lr f2b7bf6
Proper fix for selection moving when amount changes
youssef-lr 7afaca5
Add comment
youssef-lr 5c594aa
Clarify comment
youssef-lr fbad80a
Copy update
youssef-lr 4deb9ec
Clean up code
youssef-lr 14d5775
Format amounts
youssef-lr 09cde3c
Cleanup
youssef-lr 51041d8
Fix keyboard disabled on native
youssef-lr d00fb89
Update MoneyRequestAmountInput.tsx
youssef-lr cea2cf8
Rename callback
youssef-lr 4431b47
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr dc97bfd
Bug fix
youssef-lr 4b64601
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr 3ac8cb3
TS fix
youssef-lr 9641c4d
Fix input flickering
youssef-lr dbe50b9
Bug fixes and add logic to native
youssef-lr ce825c9
Style
youssef-lr b3c6539
Add autoGrowDirection
youssef-lr eb81dff
Cleanup
youssef-lr 36c5e35
Add a missing change to native
youssef-lr fc3768b
Cleanup
youssef-lr c2303f0
TS
youssef-lr 516f2de
Bug fix
youssef-lr 9657b24
Show dot in numeric keyboard
youssef-lr 1061dfd
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr 0397b60
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr 37deba6
Add accessibility hit slope
youssef-lr 626ecda
Revert implementation of autgrow with direction to the left
youssef-lr 7305b68
Revert unintended changes
youssef-lr f745d48
Don't format amount when text input is focused
youssef-lr c4bc1fd
Don't break other existing amount inputs
youssef-lr 188e7a2
Remove unneeded prop
youssef-lr 1332a1c
Remove unused param
youssef-lr 0d04ae4
Update styles and right-align input
youssef-lr ceb81ed
Adjust padding based on prefix length relating to uppercase letters
youssef-lr ca87ee5
Replace comma input with period for native keyboards that show a comma
youssef-lr c397762
Calculate exact width of the input based on total amount length
youssef-lr 8af33a4
Add callback removed by mistake
youssef-lr 672e3ec
Allow editing formatted amount
youssef-lr 2b296bb
Use fixed width for inputs and max length
youssef-lr 2056fb0
Don't format amount on blur if it exceeds max length
youssef-lr d5ce4cf
Reset selection on blur on mobile
youssef-lr 209cac2
Merge branch 'main' into youssef_uneven_splits_2
youssef-lr c9021df
Fix paidBy section being sticky
youssef-lr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
import React, {useCallback, useEffect, useRef, useState} from 'react'; | ||
import type {ForwardedRef} from 'react'; | ||
import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native'; | ||
import useLocalize from '@hooks/useLocalize'; | ||
import * as Browser from '@libs/Browser'; | ||
import * as CurrencyUtils from '@libs/CurrencyUtils'; | ||
import getOperatingSystem from '@libs/getOperatingSystem'; | ||
import type {MaybePhraseKey} from '@libs/Localize'; | ||
import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; | ||
import CONST from '@src/CONST'; | ||
import type {SelectedTabRequest} from '@src/types/onyx'; | ||
import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; | ||
import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; | ||
|
||
type MoneyRequestAmountFormProps = { | ||
/** IOU amount saved in Onyx */ | ||
amount?: number; | ||
|
||
/** Currency chosen by user or saved in Onyx */ | ||
currency?: string; | ||
|
||
/** Whether the currency symbol is pressable */ | ||
isCurrencyPressable?: boolean; | ||
|
||
hideCurrencySymbol?: boolean; | ||
|
||
prefixCharacter?: string; | ||
|
||
/** Fired when back button pressed, navigates to currency selection page */ | ||
onCurrencyButtonPress?: () => void; | ||
|
||
/** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ | ||
selectedTab?: SelectedTabRequest; | ||
}; | ||
|
||
type Selection = { | ||
start: number; | ||
end: number; | ||
}; | ||
|
||
/** | ||
* Returns the new selection object based on the updated amount's length | ||
*/ | ||
const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: number): Selection => { | ||
const cursorPosition = oldSelection.end + (newLength - prevLength); | ||
return {start: cursorPosition, end: cursorPosition}; | ||
}; | ||
|
||
function MoneyRequestAmountTextInput( | ||
{ | ||
amount = 0, | ||
currency = CONST.CURRENCY.USD, | ||
isCurrencyPressable = true, | ||
onCurrencyButtonPress, | ||
prefixCharacter, | ||
hideCurrencySymbol, | ||
inputStyle = null, | ||
textInputContainerStyles = null, | ||
selectedTab = CONST.TAB_REQUEST.MANUAL, | ||
}: MoneyRequestAmountFormProps, | ||
forwardedRef: ForwardedRef<BaseTextInputRef>, | ||
) { | ||
const {toLocaleDigit, numberFormat} = useLocalize(); | ||
const textInput = useRef<BaseTextInputRef | null>(null); | ||
|
||
const decimals = CurrencyUtils.getCurrencyDecimals(currency); | ||
const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; | ||
|
||
const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); | ||
const [formError, setFormError] = useState<MaybePhraseKey>(''); | ||
|
||
const [selection, setSelection] = useState({ | ||
start: selectedAmountAsString.length, | ||
end: selectedAmountAsString.length, | ||
}); | ||
|
||
const forwardDeletePressedRef = useRef(false); | ||
|
||
const initializeAmount = useCallback((newAmount: number) => { | ||
const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmount(newAmount).toString() : ''; | ||
setCurrentAmount(frontendAmount); | ||
setSelection({ | ||
start: frontendAmount.length, | ||
end: frontendAmount.length, | ||
}); | ||
}, []); | ||
|
||
useEffect(() => { | ||
if (!currency || typeof amount !== 'number') { | ||
return; | ||
} | ||
initializeAmount(amount); | ||
// we want to re-initialize the state only when the amount changes | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [amount]); | ||
|
||
/** | ||
* Sets the selection and the amount accordingly to the value passed to the input | ||
* @param {String} newAmount - Changed amount from user input | ||
*/ | ||
const setNewAmount = useCallback( | ||
(newAmount: string) => { | ||
// Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value | ||
// More info: https://github.com/Expensify/App/issues/16974 | ||
const newAmountWithoutSpaces = MoneyRequestUtils.stripSpacesFromAmount(newAmount); | ||
// Use a shallow copy of selection to trigger setSelection | ||
// More info: https://github.com/Expensify/App/issues/16385 | ||
if (!MoneyRequestUtils.validateAmount(newAmountWithoutSpaces, decimals)) { | ||
setSelection((prevSelection) => ({...prevSelection})); | ||
return; | ||
} | ||
if (formError) { | ||
setFormError(''); | ||
} | ||
|
||
// setCurrentAmount contains another setState(setSelection) making it error-prone since it is leading to setSelection being called twice for a single setCurrentAmount call. This solution introducing the hasSelectionBeenSet flag was chosen for its simplicity and lower risk of future errors https://github.com/Expensify/App/issues/23300#issuecomment-1766314724. | ||
|
||
let hasSelectionBeenSet = false; | ||
setCurrentAmount((prevAmount) => { | ||
const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces); | ||
const isForwardDelete = prevAmount.length > strippedAmount.length && forwardDeletePressedRef.current; | ||
if (!hasSelectionBeenSet) { | ||
hasSelectionBeenSet = true; | ||
setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); | ||
} | ||
return strippedAmount; | ||
}); | ||
}, | ||
[decimals, formError], | ||
); | ||
|
||
useEffect(() => {}); | ||
|
||
// Modifies the amount to match the decimals for changed currency. | ||
useEffect(() => { | ||
// If the changed currency supports decimals, we can return | ||
if (MoneyRequestUtils.validateAmount(currentAmount, decimals)) { | ||
return; | ||
} | ||
|
||
// If the changed currency doesn't support decimals, we can strip the decimals | ||
setNewAmount(MoneyRequestUtils.stripDecimalsFromAmount(currentAmount)); | ||
|
||
// we want to update only when decimals change (setNewAmount also changes when decimals change). | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [setNewAmount]); | ||
|
||
/** | ||
* Input handler to check for a forward-delete key (or keyboard shortcut) press. | ||
*/ | ||
const textInputKeyPress = ({nativeEvent}: NativeSyntheticEvent<KeyboardEvent>) => { | ||
const key = nativeEvent?.key.toLowerCase(); | ||
if (Browser.isMobileSafari() && key === CONST.PLATFORM_SPECIFIC_KEYS.CTRL.DEFAULT) { | ||
// Optimistically anticipate forward-delete on iOS Safari (in cases where the Mac Accessiblity keyboard is being | ||
// used for input). If the Control-D shortcut doesn't get sent, the ref will still be reset on the next key press. | ||
forwardDeletePressedRef.current = true; | ||
return; | ||
} | ||
// Control-D on Mac is a keyboard shortcut for forward-delete. See https://support.apple.com/en-us/HT201236 for Mac keyboard shortcuts. | ||
// Also check for the keyboard shortcut on iOS in cases where a hardware keyboard may be connected to the device. | ||
const operatingSystem = getOperatingSystem(); | ||
forwardDeletePressedRef.current = key === 'delete' || ((operatingSystem === CONST.OS.MAC_OS || operatingSystem === CONST.OS.IOS) && nativeEvent?.ctrlKey && key === 'd'); | ||
}; | ||
|
||
const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); | ||
|
||
useEffect(() => { | ||
setFormError(''); | ||
}, [selectedTab]); | ||
|
||
return ( | ||
<TextInputWithCurrencySymbol | ||
formattedAmount={formattedAmount} | ||
onChangeAmount={setNewAmount} | ||
onCurrencyButtonPress={onCurrencyButtonPress} | ||
placeholder={numberFormat(0)} | ||
ref={(ref) => { | ||
if (typeof forwardedRef === 'function') { | ||
forwardedRef(ref); | ||
} else if (forwardedRef?.current) { | ||
// eslint-disable-next-line no-param-reassign | ||
forwardedRef.current = ref; | ||
} | ||
textInput.current = ref; | ||
}} | ||
selectedCurrencyCode={currency} | ||
selection={selection} | ||
onSelectionChange={(e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => { | ||
const maxSelection = formattedAmount.length; | ||
const start = Math.min(e.nativeEvent.selection.start, maxSelection); | ||
const end = Math.min(e.nativeEvent.selection.end, maxSelection); | ||
setSelection({start, end}); | ||
}} | ||
onKeyPress={textInputKeyPress} | ||
isCurrencyPressable={isCurrencyPressable} | ||
hideCurrencySymbol={hideCurrencySymbol} | ||
prefixCharacter={prefixCharacter} | ||
inputStyle={inputStyle} | ||
textInputContainerStyles={textInputContainerStyles} | ||
/> | ||
); | ||
} | ||
|
||
MoneyRequestAmountTextInput.displayName = 'MoneyRequestAmountTextInput'; | ||
|
||
export default React.forwardRef(MoneyRequestAmountTextInput); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now I copied and pasted code from
MoneyRequestAmountForm
, we're working on making the underlying amount input reusable here #40382