Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4668b8a
Search - Highlight newly added expense
ikevin127 Sep 13, 2024
14818c8
fixed type issues
ikevin127 Sep 13, 2024
43a49eb
Merge branch 'main' of https://github.com/Expensify/App into ikevin12…
ikevin127 Sep 13, 2024
b38ba48
adjusted logic for new account (empty Search transaction list)
ikevin127 Sep 16, 2024
db55ff0
Merge branch 'main' of https://github.com/Expensify/App into ikevin12…
ikevin127 Sep 16, 2024
3da3f78
Merge branch 'main' of https://github.com/Expensify/App into ikevin12…
ikevin127 Sep 18, 2024
4595e26
added useScreenWrapperTransitionStatus mock to fix failing tests
ikevin127 Sep 19, 2024
24c9e8f
added BootSplash mock and adjusted previously added mock
ikevin127 Sep 19, 2024
aa9317b
targeted useScreenWrapperTransitionStatus mock to failing test files
ikevin127 Sep 19, 2024
babb58b
mocked useScreenWrapperTransitionStatus in failing perf test
ikevin127 Sep 20, 2024
72fd612
lint fix
ikevin127 Sep 20, 2024
9e43cbc
scroll to newly added expense functionality
ikevin127 Sep 24, 2024
05786c9
Merge branch 'main' of https://github.com/Expensify/App into ikevin12…
ikevin127 Sep 24, 2024
0f1bb60
hooks refactoring, fixed highlight animation issues
ikevin127 Sep 25, 2024
34c133f
Merge branch 'main' of https://github.com/Expensify/App into ikevin12…
ikevin127 Sep 25, 2024
cc892d8
fixed lint and prettier
ikevin127 Sep 26, 2024
90ddc4d
refactoring and bugfixing
ikevin127 Sep 27, 2024
265916d
Merge branch 'main' of https://github.com/Expensify/App into ikevin12…
ikevin127 Sep 27, 2024
c66f148
prettier
ikevin127 Sep 27, 2024
52c515a
Merge branch 'main' of https://github.com/Expensify/App into ikevin12…
ikevin127 Sep 30, 2024
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
4 changes: 3 additions & 1 deletion help/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
---
title: New Expensify Help
---

Pages:
* [Expensify Superapp](/superapp.html)

- [Expensify Superapp](/superapp.html)
45 changes: 38 additions & 7 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
import useNetwork from '@hooks/useNetwork';
import usePrevious from '@hooks/usePrevious';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSearchHighlightAndScroll from '@hooks/useSearchHighlightAndScroll';
import useThemeStyles from '@hooks/useThemeStyles';
import {turnOffMobileSelectionMode, turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import * as SearchActions from '@libs/actions/Search';
Expand Down Expand Up @@ -49,19 +50,26 @@ function mapTransactionItemToSelectedEntry(item: TransactionListItemType): [stri
return [item.keyForList, {isSelected: true, canDelete: item.canDelete, canHold: item.canHold, canUnhold: item.canUnhold, action: item.action}];
}

function mapToTransactionItemWithSelectionInfo(item: TransactionListItemType, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean) {
return {...item, isSelected: selectedTransactions[item.keyForList]?.isSelected && canSelectMultiple};
function mapToTransactionItemWithSelectionInfo(item: TransactionListItemType, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean, shouldAnimateInHighlight: boolean) {
return {...item, shouldAnimateInHighlight, isSelected: selectedTransactions[item.keyForList]?.isSelected && canSelectMultiple};
}

function mapToItemWithSelectionInfo(item: TransactionListItemType | ReportListItemType | ReportActionListItemType, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean) {
function mapToItemWithSelectionInfo(
item: TransactionListItemType | ReportListItemType | ReportActionListItemType,
selectedTransactions: SelectedTransactions,
canSelectMultiple: boolean,
shouldAnimateInHighlight: boolean,
) {
if (SearchUtils.isReportActionListItemType(item)) {
return item;
}

return SearchUtils.isTransactionListItemType(item)
? mapToTransactionItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple)
? mapToTransactionItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight)
: {
...item,
transactions: item.transactions?.map((transaction) => mapToTransactionItemWithSelectionInfo(transaction, selectedTransactions, canSelectMultiple)),
shouldAnimateInHighlight,
transactions: item.transactions?.map((transaction) => mapToTransactionItemWithSelectionInfo(transaction, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight)),
isSelected: item.transactions.every((transaction) => selectedTransactions[transaction.keyForList]?.isSelected && canSelectMultiple),
};
}
Expand Down Expand Up @@ -90,6 +98,8 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr
const {type, status, sortBy, sortOrder, hash} = queryJSON;

const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`);
const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION);
const previousTransactions = usePrevious(transactions);

const canSelectMultiple = isSmallScreenWidth ? !!selectionMode?.isEnabled : true;

Expand Down Expand Up @@ -117,7 +127,6 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr
}

SearchActions.search({queryJSON, offset});
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, [isOffline, offset, queryJSON]);

const getItemHeight = useCallback(
Expand Down Expand Up @@ -156,6 +165,14 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr

const searchResults = currentSearchResults?.data ? currentSearchResults : lastSearchResultsRef.current;

const {newSearchResultKey, handleSelectionListScroll} = useSearchHighlightAndScroll({
searchResults,
transactions,
previousTransactions,
queryJSON,
offset,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We probably should have added a offsetForRecentChat here as well which would have been always 0. This later caused #54785

});

// There's a race condition in Onyx which makes it return data from the previous Search, so in addition to checking that the data is loaded
// we also need to check that the searchResults matches the type and status of the current search
const isDataLoaded = searchResults?.data !== undefined && searchResults?.search?.type === type && searchResults?.search?.status === status;
Expand Down Expand Up @@ -193,7 +210,20 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr
const ListItem = SearchUtils.getListItem(type, status);
const data = SearchUtils.getSections(type, status, searchResults.data, searchResults.search);
const sortedData = SearchUtils.getSortedSections(type, status, data, sortBy, sortOrder);
const sortedSelectedData = sortedData.map((item) => mapToItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple));
const sortedSelectedData = sortedData.map((item) => {
const baseKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}`;
// Check if the base key matches the newSearchResultKey (TransactionListItemType)
const isBaseKeyMatch = baseKey === newSearchResultKey;
// Check if any transaction within the transactions array (ReportListItemType) matches the newSearchResultKey
const isAnyTransactionMatch = (item as ReportListItemType)?.transactions?.some((transaction) => {
const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`;
return transactionKey === newSearchResultKey;
});
// Determine if either the base key or any transaction key matches
const shouldAnimateInHighlight = isBaseKeyMatch || isAnyTransactionMatch;

return mapToItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight);
});

const shouldShowEmptyState = !isDataLoaded || data.length === 0;

Expand Down Expand Up @@ -299,6 +329,7 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr

return (
<SelectionListWithModal<ReportListItemType | TransactionListItemType | ReportActionListItemType>
ref={handleSelectionListScroll(sortedSelectedData)}
sections={[{data: sortedSelectedData, isDisabled: false}]}
turnOnSelectionModeOnLongPress={type !== CONST.SEARCH.DATA_TYPES.CHAT}
onTurnOnSelectionMode={(item) => item && toggleTransaction(item)}
Expand Down
11 changes: 11 additions & 0 deletions src/components/SelectionList/BaseListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle';
import useHover from '@hooks/useHover';
import {useMouseContext} from '@hooks/useMouseContext';
import useSyncFocus from '@hooks/useSyncFocus';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {BaseListItemProps, ListItem} from './types';

Expand All @@ -34,6 +36,7 @@ function BaseListItem<TItem extends ListItem>({
onFocus = () => {},
hoverStyle,
onLongPressRow,
hasAnimateInHighlightStyle = false,
}: BaseListItemProps<TItem>) {
const theme = useTheme();
const styles = useThemeStyles();
Expand Down Expand Up @@ -61,6 +64,13 @@ function BaseListItem<TItem extends ListItem>({
return rightHandSideComponent;
};

const animatedHighlightStyle = useAnimatedHighlightStyle({
borderRadius: variables.componentBorderRadius,
shouldHighlight: item?.shouldAnimateInHighlight ?? false,
highlightColor: theme.messageHighlightBG,
backgroundColor: theme.highlightBG,
});

return (
<OfflineWithFeedback
onClose={() => onDismissError(item)}
Expand Down Expand Up @@ -99,6 +109,7 @@ function BaseListItem<TItem extends ListItem>({
onFocus={onFocus}
onMouseLeave={handleMouseLeave}
tabIndex={item.tabIndex}
wrapperStyle={hasAnimateInHighlightStyle ? [styles.mh5, animatedHighlightStyle] : []}
>
<View style={wrapperStyle}>
{typeof children === 'function' ? children(hovered) : children}
Expand Down
2 changes: 1 addition & 1 deletion src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ function BaseSelectionList<TItem extends ListItem>(
[flattenedSections.allOptions, setFocusedIndex, updateAndScrollToFocusedIndex],
);

useImperativeHandle(ref, () => ({scrollAndHighlightItem, clearInputAfterSelect}), [scrollAndHighlightItem, clearInputAfterSelect]);
useImperativeHandle(ref, () => ({scrollAndHighlightItem, clearInputAfterSelect, scrollToIndex}), [scrollAndHighlightItem, clearInputAfterSelect, scrollToIndex]);

/** Selects row when pressing Enter */
useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, selectFocusedOption, {
Expand Down
4 changes: 4 additions & 0 deletions src/components/SelectionList/Search/ReportListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ function ReportListItem<TItem extends ListItem>({
styles.overflowHidden,
item.isSelected && styles.activeComponentBG,
isFocused && styles.sidebarLinkActive,
// Removing some of the styles because they are added to the parent OpacityView via animatedHighlightStyle
{backgroundColor: 'unset'},
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This style overriding caused the following issue:

Which we fixed by moving the style up, under the overflowHidden and changing it to styles.bgTransparent for compatibility with native.

styles.mh0,
];

const handleOnButtonPress = () => {
Expand Down Expand Up @@ -140,6 +143,7 @@ function ReportListItem<TItem extends ListItem>({
onFocus={onFocus}
shouldSyncFocus={shouldSyncFocus}
hoverStyle={item.isSelected && styles.activeComponentBG}
hasAnimateInHighlightStyle
>
<View style={styles.flex1}>
{!isLargeScreenWidth && (
Expand Down
12 changes: 11 additions & 1 deletion src/components/SelectionList/Search/TransactionListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@ function TransactionListItem<TItem extends ListItem>({

const {isLargeScreenWidth} = useResponsiveLayout();

const listItemPressableStyle = [styles.selectionListPressableItemWrapper, styles.pv3, styles.ph3, item.isSelected && styles.activeComponentBG, isFocused && styles.sidebarLinkActive];
const listItemPressableStyle = [
styles.selectionListPressableItemWrapper,
styles.pv3,
styles.ph3,
item.isSelected && styles.activeComponentBG,
isFocused && styles.sidebarLinkActive,
// Removing some of the styles because they are added to the parent OpacityView via animatedHighlightStyle
{backgroundColor: 'unset'},
styles.mh0,
];

const listItemWrapperStyle = [
styles.flex1,
Expand All @@ -50,6 +59,7 @@ function TransactionListItem<TItem extends ListItem>({
onLongPressRow={onLongPressRow}
shouldSyncFocus={shouldSyncFocus}
hoverStyle={item.isSelected && styles.activeComponentBG}
hasAnimateInHighlightStyle
>
<TransactionListItemRow
item={transactionItem}
Expand Down
6 changes: 6 additions & 0 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ type ListItem = {

/** The style to override the cursor appearance */
cursorStyle?: CursorStyles[keyof CursorStyles];

/** Determines whether the newly added item should animate in / highlight */
shouldAnimateInHighlight?: boolean;
};

type TransactionListItemType = ListItem &
Expand Down Expand Up @@ -288,6 +291,8 @@ type BaseListItemProps<TItem extends ListItem> = CommonListItemProps<TItem> & {
children?: ReactElement<ListItemProps<TItem>> | ((hovered: boolean) => ReactElement<ListItemProps<TItem>>);
shouldSyncFocus?: boolean;
hoverStyle?: StyleProp<ViewStyle>;
hasAnimateInHighlightStyle?: boolean;
/** Errors that this user may contain */
shouldDisplayRBR?: boolean;
};

Expand Down Expand Up @@ -565,6 +570,7 @@ type BaseSelectionListProps<TItem extends ListItem> = Partial<ChildrenProps> & {
type SelectionListHandle = {
scrollAndHighlightItem?: (items: string[], timeout: number) => void;
clearInputAfterSelect?: () => void;
scrollToIndex: (index: number, animated?: boolean) => void;
};

type ItemLayout = {
Expand Down
17 changes: 14 additions & 3 deletions src/hooks/useAnimatedHighlightStyle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type Props = {
borderRadius: number;

/** Height of the item that is to be faded */
height: number;
height?: number;

/** Delay before the highlighted item enters */
itemEnterDelay?: number;
Expand All @@ -32,6 +32,15 @@ type Props = {

/** Whether the item should be highlighted */
shouldHighlight: boolean;

/** The base backgroundColor used for the highlight animation, defaults to theme.appBG
* @default theme.appBG
*/
backgroundColor?: string;
/** The base highlightColor used for the highlight animation, defaults to theme.border
* @default theme.border
*/
highlightColor?: string;
};

/**
Expand All @@ -47,6 +56,8 @@ export default function useAnimatedHighlightStyle({
highlightEndDelay = CONST.ANIMATED_HIGHLIGHT_END_DELAY,
highlightEndDuration = CONST.ANIMATED_HIGHLIGHT_END_DURATION,
height,
highlightColor,
backgroundColor,
}: Props) {
const [startHighlight, setStartHighlight] = useState(false);
const repeatableProgress = useSharedValue(0);
Expand All @@ -55,8 +66,8 @@ export default function useAnimatedHighlightStyle({
const theme = useTheme();

const highlightBackgroundStyle = useAnimatedStyle(() => ({
backgroundColor: interpolateColor(repeatableProgress.value, [0, 1], [theme.appBG, theme.border]),
height: interpolate(nonRepeatableProgress.value, [0, 1], [0, height]),
backgroundColor: interpolateColor(repeatableProgress.value, [0, 1], [backgroundColor ?? theme.appBG, highlightColor ?? theme.border]),
height: height ? interpolate(nonRepeatableProgress.value, [0, 1], [0, height]) : 'auto',
opacity: interpolate(nonRepeatableProgress.value, [0, 1], [0, 1]),
borderRadius,
}));
Expand Down
Loading