Skip to content

Commit 8b04250

Browse files
authored
Merge pull request Expensify#66747 from Eskalifer1/fix/65746
Fix/65746 Enable scroll for per diem when keyboard is open
2 parents 45120f9 + 70ae3ae commit 8b04250

6 files changed

Lines changed: 85 additions & 63 deletions

File tree

src/components/DestinationPicker.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import useDebouncedState from '@hooks/useDebouncedState';
33
import useLocalize from '@hooks/useLocalize';
44
import useOnyx from '@hooks/useOnyx';
55
import usePolicy from '@hooks/usePolicy';
6-
import * as OptionsListUtils from '@libs/OptionsListUtils';
7-
import * as PerDiemRequestUtils from '@libs/PerDiemRequestUtils';
6+
import {getHeaderMessageForNonUserList} from '@libs/OptionsListUtils';
7+
import {getDestinationListSections} from '@libs/PerDiemRequestUtils';
88
import type {Destination} from '@libs/PerDiemRequestUtils';
9-
import * as PolicyUtils from '@libs/PolicyUtils';
9+
import {getPerDiemCustomUnit} from '@libs/PolicyUtils';
1010
import CONST from '@src/CONST';
1111
import ONYXKEYS from '@src/ONYXKEYS';
1212
import SelectionList from './SelectionList';
@@ -21,8 +21,8 @@ type DestinationPickerProps = {
2121

2222
function DestinationPicker({selectedDestination, policyID, onSubmit}: DestinationPickerProps) {
2323
const policy = usePolicy(policyID);
24-
const customUnit = PolicyUtils.getPerDiemCustomUnit(policy);
25-
const [policyRecentlyUsedDestinations] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS}${policyID}`);
24+
const customUnit = getPerDiemCustomUnit(policy);
25+
const [policyRecentlyUsedDestinations] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS}${policyID}`, {canBeMissing: true});
2626

2727
const {translate} = useLocalize();
2828
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
@@ -49,15 +49,15 @@ function DestinationPicker({selectedDestination, policyID, onSubmit}: Destinatio
4949
}, [customUnit?.rates, selectedDestination]);
5050

5151
const [sections, headerMessage, shouldShowTextInput] = useMemo(() => {
52-
const destinationOptions = PerDiemRequestUtils.getDestinationListSections({
52+
const destinationOptions = getDestinationListSections({
5353
searchValue: debouncedSearchValue,
5454
selectedOptions,
5555
destinations: Object.values(customUnit?.rates ?? {}),
5656
recentlyUsedDestinations: policyRecentlyUsedDestinations,
5757
});
5858

5959
const destinationData = destinationOptions?.at(0)?.data ?? [];
60-
const header = OptionsListUtils.getHeaderMessageForNonUserList(destinationData.length > 0, debouncedSearchValue);
60+
const header = getHeaderMessageForNonUserList(destinationData.length > 0, debouncedSearchValue);
6161
const destinationsCount = Object.values(customUnit?.rates ?? {}).length;
6262
const isDestinationsCountBelowThreshold = destinationsCount < CONST.STANDARD_LIST_ITEM_LIMIT;
6363
const showInput = !isDestinationsCountBelowThreshold;
@@ -81,6 +81,7 @@ function DestinationPicker({selectedDestination, policyID, onSubmit}: Destinatio
8181
ListItem={RadioListItem}
8282
initiallyFocusedOptionKey={selectedOptionKey ?? undefined}
8383
isRowMultilineSupported
84+
shouldHideKeyboardOnScroll={false}
8485
/>
8586
);
8687
}

src/components/SelectionList/index.native.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@ import {Keyboard} from 'react-native';
44
import BaseSelectionList from './BaseSelectionList';
55
import type {ListItem, SelectionListHandle, SelectionListProps} from './types';
66

7-
function SelectionList<TItem extends ListItem>(props: SelectionListProps<TItem>, ref: ForwardedRef<SelectionListHandle>) {
7+
function SelectionList<TItem extends ListItem>({shouldHideKeyboardOnScroll = true, ...props}: SelectionListProps<TItem>, ref: ForwardedRef<SelectionListHandle>) {
88
return (
99
<BaseSelectionList
1010
// eslint-disable-next-line react/jsx-props-no-spreading
1111
{...props}
1212
ref={ref}
13-
onScrollBeginDrag={() => Keyboard.dismiss()}
13+
onScrollBeginDrag={() => {
14+
if (!shouldHideKeyboardOnScroll) {
15+
return;
16+
}
17+
Keyboard.dismiss();
18+
}}
1419
/>
1520
);
1621
}

src/components/SelectionList/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import CONST from '@src/CONST';
77
import BaseSelectionList from './BaseSelectionList';
88
import type {ListItem, SelectionListHandle, SelectionListProps} from './types';
99

10-
function SelectionList<TItem extends ListItem>({onScroll, ...props}: SelectionListProps<TItem>, ref: ForwardedRef<SelectionListHandle>) {
10+
function SelectionList<TItem extends ListItem>({onScroll, shouldHideKeyboardOnScroll = true, ...props}: SelectionListProps<TItem>, ref: ForwardedRef<SelectionListHandle>) {
1111
const [isScreenTouched, setIsScreenTouched] = useState(false);
1212

1313
const touchStart = () => setIsScreenTouched(true);
@@ -58,8 +58,8 @@ function SelectionList<TItem extends ListItem>({onScroll, ...props}: SelectionLi
5858

5959
// In SearchPageBottomTab we use useAnimatedScrollHandler from reanimated(for performance reasons) and it returns object instead of function. In that case we cannot change it to a function call, that's why we have to choose between onScroll and defaultOnScroll.
6060
const defaultOnScroll = () => {
61-
// Only dismiss the keyboard whenever the user scrolls the screen
62-
if (!isScreenTouched) {
61+
// Only dismiss the keyboard whenever the user scrolls the screen or `shouldHideKeyboardOnScroll` is true
62+
if (!isScreenTouched || !shouldHideKeyboardOnScroll) {
6363
return;
6464
}
6565
Keyboard.dismiss();

src/components/SelectionList/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,9 @@ type SelectionListProps<TItem extends ListItem> = Partial<ChildrenProps> & {
826826

827827
/** Whether product training tooltips can be displayed */
828828
canShowProductTrainingTooltip?: boolean;
829+
830+
/** Whether to hide the keyboard when scrolling a list */
831+
shouldHideKeyboardOnScroll?: boolean;
829832
} & TRightHandSideComponent<TItem>;
830833

831834
type SelectionListHandle = {

src/pages/iou/request/IOURequestStartPage.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {useFocusEffect} from '@react-navigation/native';
22
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
3-
import {View} from 'react-native';
3+
import {Keyboard, View} from 'react-native';
44
import DragAndDropProvider from '@components/DragAndDrop/Provider';
55
import FocusTrapContainerElement from '@components/FocusTrap/FocusTrapContainerElement';
66
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -14,6 +14,7 @@ import usePolicy from '@hooks/usePolicy';
1414
import usePrevious from '@hooks/usePrevious';
1515
import useThemeStyles from '@hooks/useThemeStyles';
1616
import {dismissProductTraining} from '@libs/actions/Welcome';
17+
import {isMobile} from '@libs/Browser';
1718
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
1819
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
1920
import Navigation from '@libs/Navigation/Navigation';
@@ -117,6 +118,7 @@ function IOURequestStartPage({
117118

118119
const resetIOUTypeIfChanged = useCallback(
119120
(newIOUType: IOURequestType) => {
121+
Keyboard.dismiss();
120122
if (transaction?.iouRequestType === newIOUType) {
121123
return;
122124
}
@@ -179,6 +181,7 @@ function IOURequestStartPage({
179181
>
180182
<ScreenWrapper
181183
shouldEnableKeyboardAvoidingView={false}
184+
shouldEnableMaxHeight={selectedTab === CONST.TAB_REQUEST.PER_DIEM}
182185
shouldEnableMinHeight={canUseTouchScreen()}
183186
headerGapStyles={isDraggingOver ? styles.dropWrapper : []}
184187
testID={IOURequestStartPage.displayName}
@@ -211,7 +214,8 @@ function IOURequestStartPage({
211214
shouldShowProductTrainingTooltip={shouldShowProductTrainingTooltip}
212215
renderProductTrainingTooltip={renderProductTrainingTooltip}
213216
lazyLoadEnabled
214-
disableSwipe={isMultiScanEnabled && selectedTab === CONST.TAB_REQUEST.SCAN}
217+
// We're disabling swipe on mWeb fo the Per Diem tab because the keyboard will hang on the other tab after switching
218+
disableSwipe={(isMultiScanEnabled && selectedTab === CONST.TAB_REQUEST.SCAN) || (selectedTab === CONST.TAB_REQUEST.PER_DIEM && isMobile())}
215219
>
216220
<TopTab.Screen name={CONST.TAB_REQUEST.MANUAL}>
217221
{() => (

src/pages/iou/request/step/IOURequestStepDestination.tsx

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ import Button from '@components/Button';
55
import DestinationPicker from '@components/DestinationPicker';
66
import FixedFooter from '@components/FixedFooter';
77
import * as Illustrations from '@components/Icon/Illustrations';
8+
import ScreenWrapper from '@components/ScreenWrapper';
89
import type {ListItem} from '@components/SelectionList/types';
910
import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection';
1011
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
1112
import useLocalize from '@hooks/useLocalize';
1213
import useNetwork from '@hooks/useNetwork';
1314
import useOnyx from '@hooks/useOnyx';
15+
import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
1416
import useTheme from '@hooks/useTheme';
1517
import useThemeStyles from '@hooks/useThemeStyles';
1618
import Navigation from '@libs/Navigation/Navigation';
1719
import {getPerDiemCustomUnit, isPolicyAdmin} from '@libs/PolicyUtils';
1820
import {getPolicyExpenseChat} from '@libs/ReportUtils';
21+
import variables from '@styles/variables';
1922
import {
2023
clearSubrates,
2124
getIOURequestPolicyID,
@@ -55,7 +58,7 @@ function IOURequestStepDestination({
5558
const [policy, policyMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${explicitPolicyID ?? getIOURequestPolicyID(transaction, report)}`, {canBeMissing: false});
5659
const {accountID} = useCurrentUserPersonalDetails();
5760
const policyExpenseReport = policy?.id ? getPolicyExpenseChat(accountID, policy.id) : undefined;
58-
61+
const {top} = useSafeAreaInsets();
5962
const customUnit = getPerDiemCustomUnit(policy);
6063
const selectedDestination = transaction?.comment?.customUnit?.customUnitRateID;
6164

@@ -112,56 +115,62 @@ function IOURequestStepDestination({
112115
};
113116

114117
return (
115-
<StepScreenWrapper
116-
headerTitle={backTo ? translate('common.destination') : tabTitles[iouType]}
117-
onBackButtonPress={navigateBack}
118-
shouldShowWrapper={!openedFromStartPage}
119-
shouldShowNotFoundPage={shouldShowNotFoundPage}
120-
testID={IOURequestStepDestination.displayName}
118+
<ScreenWrapper
119+
includePaddingTop={false}
120+
keyboardVerticalOffset={variables.contentHeaderHeight + top + variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding}
121+
testID={`${IOURequestStepDestination.displayName}-container`}
121122
>
122-
{isLoading && (
123-
<ActivityIndicator
124-
size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE}
125-
style={[styles.flex1]}
126-
color={theme.spinner}
127-
/>
128-
)}
129-
{shouldShowOfflineView && <FullPageOfflineBlockingView>{null}</FullPageOfflineBlockingView>}
130-
{shouldShowEmptyState && (
131-
<View style={[styles.flex1]}>
132-
<WorkspaceEmptyStateSection
133-
shouldStyleAsCard={false}
134-
icon={Illustrations.EmptyStateExpenses}
135-
title={translate('workspace.perDiem.emptyList.title')}
136-
subtitle={translate('workspace.perDiem.emptyList.subtitle')}
137-
containerStyle={[styles.flex1, styles.justifyContentCenter]}
123+
<StepScreenWrapper
124+
headerTitle={backTo ? translate('common.destination') : tabTitles[iouType]}
125+
onBackButtonPress={navigateBack}
126+
shouldShowWrapper={!openedFromStartPage}
127+
shouldShowNotFoundPage={shouldShowNotFoundPage}
128+
testID={IOURequestStepDestination.displayName}
129+
>
130+
{isLoading && (
131+
<ActivityIndicator
132+
size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE}
133+
style={[styles.flex1]}
134+
color={theme.spinner}
135+
/>
136+
)}
137+
{shouldShowOfflineView && <FullPageOfflineBlockingView>{null}</FullPageOfflineBlockingView>}
138+
{shouldShowEmptyState && (
139+
<View style={[styles.flex1]}>
140+
<WorkspaceEmptyStateSection
141+
shouldStyleAsCard={false}
142+
icon={Illustrations.EmptyStateExpenses}
143+
title={translate('workspace.perDiem.emptyList.title')}
144+
subtitle={translate('workspace.perDiem.emptyList.subtitle')}
145+
containerStyle={[styles.flex1, styles.justifyContentCenter]}
146+
/>
147+
{isPolicyAdmin(policy) && !!policy?.areCategoriesEnabled && (
148+
<FixedFooter style={[styles.mtAuto, styles.pt5]}>
149+
<Button
150+
large
151+
success
152+
style={[styles.w100]}
153+
onPress={() => {
154+
InteractionManager.runAfterInteractions(() => {
155+
Navigation.navigate(ROUTES.WORKSPACE_PER_DIEM.getRoute(policy.id, Navigation.getActiveRoute()));
156+
});
157+
}}
158+
text={translate('workspace.perDiem.editPerDiemRates')}
159+
pressOnEnter
160+
/>
161+
</FixedFooter>
162+
)}
163+
</View>
164+
)}
165+
{!shouldShowEmptyState && !isLoading && !shouldShowOfflineView && !!policy?.id && (
166+
<DestinationPicker
167+
selectedDestination={selectedDestination}
168+
policyID={policy.id}
169+
onSubmit={updateDestination}
138170
/>
139-
{isPolicyAdmin(policy) && !!policy?.areCategoriesEnabled && (
140-
<FixedFooter style={[styles.mtAuto, styles.pt5]}>
141-
<Button
142-
large
143-
success
144-
style={[styles.w100]}
145-
onPress={() => {
146-
InteractionManager.runAfterInteractions(() => {
147-
Navigation.navigate(ROUTES.WORKSPACE_PER_DIEM.getRoute(policy.id, Navigation.getActiveRoute()));
148-
});
149-
}}
150-
text={translate('workspace.perDiem.editPerDiemRates')}
151-
pressOnEnter
152-
/>
153-
</FixedFooter>
154-
)}
155-
</View>
156-
)}
157-
{!shouldShowEmptyState && !isLoading && !shouldShowOfflineView && !!policy?.id && (
158-
<DestinationPicker
159-
selectedDestination={selectedDestination}
160-
policyID={policy.id}
161-
onSubmit={updateDestination}
162-
/>
163-
)}
164-
</StepScreenWrapper>
171+
)}
172+
</StepScreenWrapper>
173+
</ScreenWrapper>
165174
);
166175
}
167176

0 commit comments

Comments
 (0)