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

Mobile Selection Mode #46096

Merged
Changes from 19 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e957a29
Add Onyx key
filip-solecki Jul 24, 2024
a55571b
Move translations
filip-solecki Jul 24, 2024
3b149a1
Refactor Search Selection Mode
filip-solecki Jul 24, 2024
8c4578e
Add Selection Mode to Workspace Members page
filip-solecki Jul 24, 2024
a9248e0
Add Selection Mode to Workspace Categories page
filip-solecki Jul 24, 2024
9f40d33
Add Selection Mode to Workspace Distance Rates page
filip-solecki Jul 24, 2024
d5956f8
Add Selection Mode to Workspace multi level tags page
filip-solecki Jul 24, 2024
25b0250
Clean SearchListWithHeader
filip-solecki Jul 24, 2024
e8c43ef
Add selection mode to Workspace Tags page
filip-solecki Jul 24, 2024
1fec030
Add selection mode to Workspace Taxes page
filip-solecki Jul 24, 2024
7e22023
Add selection mode to Workspace Report Fields page
filip-solecki Jul 24, 2024
382d104
Fixes for header buttons
filip-solecki Jul 24, 2024
95b6d66
Add selection mode to Group chat members page
filip-solecki Jul 24, 2024
6b0dbd9
Fix tags page
filip-solecki Jul 25, 2024
eed24fc
Fix headers in Tags and Distance Rates pages
filip-solecki Jul 25, 2024
d5393b3
Fix Workspace Members page
filip-solecki Jul 25, 2024
12693c5
Fix self selecting for group chat admin
filip-solecki Jul 25, 2024
3b19a67
Merge branch 'main' into filip-solecki/mobile-selection-mode
filip-solecki Jul 25, 2024
14feef4
Turn off selection mode on large screen
filip-solecki Jul 25, 2024
edc7547
Merge branch 'main' into filip-solecki/mobile-selection-mode
filip-solecki Jul 26, 2024
8923b83
Resolve merge changes
filip-solecki Jul 26, 2024
b4a81a3
Add mobile selection mode to add list type report field
filip-solecki Jul 26, 2024
88cb815
Merge branch 'main' into filip-solecki/mobile-selection-mode
filip-solecki Jul 29, 2024
e981e36
Merge branch 'main' into filip-solecki/mobile-selection-mode
filip-solecki Jul 30, 2024
7a0e5a6
Move turning on selection mode logic on resize to main SelectionListW…
filip-solecki Jul 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: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
@@ -385,6 +385,9 @@ const ONYXKEYS = {
/** Stores the information about the state of issuing a new card */
ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard',

/** Stores the information if mobile selection mode is active */
MOBILE_SELECTION_MODE: 'mobileSelectionMode',

NVP_PRIVATE_CANCELLATION_DETAILS: 'nvp_private_cancellationDetails',

/** Collection Keys */
@@ -850,6 +853,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.NVP_TRAVEL_SETTINGS]: OnyxTypes.TravelSettings;
[ONYXKEYS.REVIEW_DUPLICATES]: OnyxTypes.ReviewDuplicates;
[ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard;
[ONYXKEYS.MOBILE_SELECTION_MODE]: OnyxTypes.MobileSelectionMode;
[ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: string;
[ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL]: string;
[ONYXKEYS.NVP_BILLING_FUND_ID]: number;
2 changes: 1 addition & 1 deletion src/components/ButtonWithDropdownMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@ function ButtonWithDropdownMenu<IValueType>({
}}
onPress={(event) => (!isSplitButton ? setIsMenuVisible(!isMenuVisible) : onPress(event, selectedItem.value))}
text={customText ?? selectedItem.text}
isDisabled={isDisabled || !!selectedItem.disabled}
isDisabled={isDisabled || !!selectedItem?.disabled}
isLoading={isLoading}
shouldRemoveRightBorderRadius
style={[styles.flex1, styles.pr0]}
63 changes: 5 additions & 58 deletions src/components/Search/SearchListWithHeader.tsx
Original file line number Diff line number Diff line change
@@ -2,11 +2,8 @@ import type {ForwardedRef} from 'react';
import React, {forwardRef, useCallback, useEffect, useMemo, useState} from 'react';
import ConfirmModal from '@components/ConfirmModal';
import DecisionModal from '@components/DecisionModal';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
import Modal from '@components/Modal';
import SelectionList from '@components/SelectionList';
import type {BaseSelectionListProps, ReportListItemType, SelectionListHandle, TransactionListItemType} from '@components/SelectionList/types';
import SelectionListWithModal from '@components/SelectionListWithModal';
import useLocalize from '@hooks/useLocalize';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as SearchActions from '@libs/actions/Search';
@@ -21,8 +18,6 @@ type SearchListWithHeaderProps = Omit<BaseSelectionListProps<ReportListItemType
hash: number;
data: TransactionListItemType[] | ReportListItemType[];
searchType: SearchDataTypes;
isMobileSelectionModeActive?: boolean;
setIsMobileSelectionModeActive?: (isMobileSelectionModeActive: boolean) => void;
};

function mapTransactionItemToSelectedEntry(item: TransactionListItemType): [string, SelectedTransactionInfo] {
@@ -43,14 +38,9 @@ function mapToItemWithSelectionInfo(item: TransactionListItemType | ReportListIt
};
}

function SearchListWithHeader(
{ListItem, onSelectRow, query, hash, data, searchType, isMobileSelectionModeActive, setIsMobileSelectionModeActive, ...props}: SearchListWithHeaderProps,
ref: ForwardedRef<SelectionListHandle>,
) {
function SearchListWithHeader({ListItem, onSelectRow, query, hash, data, searchType, ...props}: SearchListWithHeaderProps, ref: ForwardedRef<SelectionListHandle>) {
const {isSmallScreenWidth} = useWindowDimensions();
const {translate} = useLocalize();
const [isModalVisible, setIsModalVisible] = useState(false);
const [longPressedItem, setLongPressedItem] = useState<TransactionListItemType | ReportListItemType | null>(null);
const [selectedTransactions, setSelectedTransactions] = useState<SelectedTransactions>({});
const [selectedTransactionsToDelete, setSelectedTransactionsToDelete] = useState<string[]>([]);
const [deleteExpensesConfirmModalVisible, setDeleteExpensesConfirmModalVisible] = useState(false);
@@ -132,36 +122,6 @@ function SearchListWithHeader(
[selectedTransactions],
);

const openBottomModal = (item: TransactionListItemType | ReportListItemType | null) => {
if (!isSmallScreenWidth) {
return;
}

setLongPressedItem(item);
setIsModalVisible(true);
};

const turnOnSelectionMode = useCallback(() => {
setIsMobileSelectionModeActive?.(true);
setIsModalVisible(false);

if (longPressedItem) {
toggleTransaction(longPressedItem);
}
}, [longPressedItem, setIsMobileSelectionModeActive, toggleTransaction]);

const closeBottomModal = useCallback(() => {
setIsModalVisible(false);
}, []);

useEffect(() => {
if (isMobileSelectionModeActive) {
return;
}

setSelectedTransactions({});
}, [setSelectedTransactions, isMobileSelectionModeActive]);

const toggleAllTransactions = () => {
const areItemsOfReportType = searchType === CONST.SEARCH.DATA_TYPES.REPORT;
const flattenedItems = areItemsOfReportType ? (data as ReportListItemType[]).flatMap((item) => item.transactions) : data;
@@ -191,23 +151,21 @@ function SearchListWithHeader(
query={query}
hash={hash}
onSelectDeleteOption={handleOnSelectDeleteOption}
isMobileSelectionModeActive={isMobileSelectionModeActive}
setIsMobileSelectionModeActive={setIsMobileSelectionModeActive}
selectedReports={selectedReports}
setOfflineModalOpen={() => setOfflineModalVisible(true)}
setDownloadErrorModalOpen={() => setDownloadErrorModalVisible(true)}
/>
<SelectionList<ReportListItemType | TransactionListItemType>
<SelectionListWithModal<ReportListItemType | TransactionListItemType>
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
sections={[{data: sortedSelectedData, isDisabled: false}]}
ListItem={ListItem}
onSelectRow={onSelectRow}
onLongPressRow={openBottomModal}
turnOnSelectionModeOnLongPress
onTurnOnSelectionMode={(item) => item && toggleTransaction(item)}
ref={ref}
onCheckboxPress={toggleTransaction}
onSelectAll={toggleAllTransactions}
isMobileSelectionModeActive={isMobileSelectionModeActive}
/>
<ConfirmModal
isVisible={deleteExpensesConfirmModalVisible}
@@ -237,17 +195,6 @@ function SearchListWithHeader(
isVisible={downloadErrorModalVisible}
onClose={() => setDownloadErrorModalVisible(false)}
/>
<Modal
isVisible={isModalVisible}
type={CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED}
onClose={closeBottomModal}
>
<MenuItem
title={translate('common.select')}
icon={Expensicons.Checkmark}
onPress={turnOnSelectionMode}
/>
</Modal>
</>
);
}
21 changes: 10 additions & 11 deletions src/components/Search/SearchPageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {useMemo} from 'react';
import {useOnyx} from 'react-native-onyx';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -10,11 +11,13 @@ import useNetwork from '@hooks/useNetwork';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import * as SearchActions from '@libs/actions/Search';
import Navigation from '@libs/Navigation/Navigation';
import SearchSelectedNarrow from '@pages/Search/SearchSelectedNarrow';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {SearchQuery, SearchReport} from '@src/types/onyx/SearchResults';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
@@ -30,8 +33,6 @@ type SearchPageHeaderProps = {
clearSelectedItems?: () => void;
hash: number;
onSelectDeleteOption?: (itemsToDelete: string[]) => void;
isMobileSelectionModeActive?: boolean;
setIsMobileSelectionModeActive?: (isMobileSelectionModeActive: boolean) => void;
setOfflineModalOpen?: () => void;
setDownloadErrorModalOpen?: () => void;
};
@@ -44,8 +45,6 @@ function SearchPageHeader({
hash,
clearSelectedItems,
onSelectDeleteOption,
isMobileSelectionModeActive,
setIsMobileSelectionModeActive,
setOfflineModalOpen,
setDownloadErrorModalOpen,
selectedReports,
@@ -57,6 +56,7 @@ function SearchPageHeader({
const {activeWorkspaceID} = useActiveWorkspace();
const {isSmallScreenWidth} = useResponsiveLayout();
const {setSelectedTransactionIDs} = useSearchContext();
const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE);

const headerContent: {[key in SearchQuery]: {icon: IconAsset; title: string}} = {
all: {icon: Illustrations.MoneyReceipts, title: translate('common.expenses')},
@@ -105,8 +105,8 @@ function SearchPageHeader({
}

clearSelectedItems?.();
if (isMobileSelectionModeActive) {
setIsMobileSelectionModeActive?.(false);
if (selectionMode?.isEnabled) {
turnOffMobileSelectionMode();
}
setSelectedTransactionIDs(selectedTransactionsKeys);
Navigation.navigate(ROUTES.TRANSACTION_HOLD_REASON_RHP.getRoute(query));
@@ -129,8 +129,8 @@ function SearchPageHeader({
}

clearSelectedItems?.();
if (isMobileSelectionModeActive) {
setIsMobileSelectionModeActive?.(false);
if (selectionMode?.isEnabled) {
turnOffMobileSelectionMode();
}
SearchActions.unholdMoneyRequestOnSearch(hash, selectedTransactionsKeys);
},
@@ -181,9 +181,7 @@ function SearchPageHeader({
translate,
onSelectDeleteOption,
clearSelectedItems,
isMobileSelectionModeActive,
hash,
setIsMobileSelectionModeActive,
theme.icon,
styles.colorMuted,
styles.fontWeightNormal,
@@ -195,10 +193,11 @@ function SearchPageHeader({
selectedReports,
styles.textWrap,
setSelectedTransactionIDs,
selectionMode?.isEnabled,
]);

if (isSmallScreenWidth) {
if (isMobileSelectionModeActive) {
if (selectionMode?.isEnabled) {
return (
<SearchSelectedNarrow
options={headerButtonsOptions}
9 changes: 3 additions & 6 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
@@ -34,22 +34,21 @@ type SearchProps = {
policyIDs?: string;
sortBy?: SearchColumnType;
sortOrder?: SortOrder;
isMobileSelectionModeActive?: boolean;
setIsMobileSelectionModeActive?: (isMobileSelectionModeActive: boolean) => void;
};

const sortableSearchTabs: SearchQuery[] = [CONST.SEARCH.TAB.ALL];
const transactionItemMobileHeight = 100;
const reportItemTransactionHeight = 52;
const listItemPadding = 12; // this is equivalent to 'mb3' on every transaction/report list item
const searchHeaderHeight = 54;
function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActive, setIsMobileSelectionModeActive}: SearchProps) {
function Search({query, policyIDs, sortBy, sortOrder}: SearchProps) {
const {isOffline} = useNetwork();
const styles = useThemeStyles();
const {isLargeScreenWidth, isSmallScreenWidth} = useWindowDimensions();
const navigation = useNavigation<StackNavigationProp<AuthScreensParamList>>();
const lastSearchResultsRef = useRef<OnyxEntry<SearchResults>>();
const {setCurrentSearchHash} = useSearchContext();
const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE);

const hash = SearchUtils.getQueryHash(query, policyIDs, sortBy, sortOrder);
const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`);
@@ -177,7 +176,7 @@ function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActiv

const shouldShowYear = SearchUtils.shouldShowYear(searchResults?.data);

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

return (
<SearchListWithHeader
@@ -221,8 +220,6 @@ function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActiv
showScrollIndicator={false}
onEndReachedThreshold={0.75}
onEndReached={fetchMoreResults}
setIsMobileSelectionModeActive={setIsMobileSelectionModeActive}
isMobileSelectionModeActive={isMobileSelectionModeActive}
listFooterContent={
shouldShowLoadingMoreItems ? (
<SearchRowSkeleton
2 changes: 0 additions & 2 deletions src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
@@ -97,7 +97,6 @@ function BaseSelectionList<TItem extends ListItem>(
shouldDelayFocus = true,
shouldUpdateFocusedIndex = false,
onLongPressRow,
isMobileSelectionModeActive,
}: BaseSelectionListProps<TItem>,
ref: ForwardedRef<SelectionListHandle>,
) {
@@ -458,7 +457,6 @@ function BaseSelectionList<TItem extends ListItem>(
showTooltip={showTooltip}
canSelectMultiple={canSelectMultiple}
onLongPressRow={onLongPressRow}
isMobileSelectionModeActive={isMobileSelectionModeActive}
onSelectRow={() => selectRow(item, index)}
onCheckboxPress={handleOnCheckboxPress()}
onDismissError={() => onDismissError?.(item)}
2 changes: 2 additions & 0 deletions src/components/SelectionList/TableListItem.tsx
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ function TableListItem<TItem extends ListItem>({
onDismissError,
rightHandSideComponent,
onFocus,
onLongPressRow,
shouldSyncFocus,
}: TableListItemProps<TItem>) {
const styles = useThemeStyles();
@@ -50,6 +51,7 @@ function TableListItem<TItem extends ListItem>({
isDisabled={isDisabled}
showTooltip={showTooltip}
canSelectMultiple={canSelectMultiple}
onLongPressRow={onLongPressRow}
onSelectRow={onSelectRow}
onDismissError={onDismissError}
rightHandSideComponent={rightHandSideComponent}
6 changes: 0 additions & 6 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
@@ -66,9 +66,6 @@ type CommonListItemProps<TItem extends ListItem> = {

/** Callback to fire when the item is long pressed */
onLongPressRow?: (item: TItem) => void;

/** Whether Selection Mode is active - used only on small screens */
isMobileSelectionModeActive?: boolean;
} & TRightHandSideComponent<TItem>;

type ListItem = {
@@ -492,9 +489,6 @@ type BaseSelectionListProps<TItem extends ListItem> = Partial<ChildrenProps> & {

/** Callback to fire when the item is long pressed */
onLongPressRow?: (item: TItem) => void;

/** Whether Selection Mode is active - used only on small screens */
isMobileSelectionModeActive?: boolean;
} & TRightHandSideComponent<TItem>;

type SelectionListHandle = {
Loading