Skip to content

Commit 169af06

Browse files
authored
Merge branch 'Expensify:main' into fix/90269
2 parents d744ca4 + af304c0 commit 169af06

58 files changed

Lines changed: 622 additions & 243 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Mobile-Expensify

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ android {
111111
minSdkVersion rootProject.ext.minSdkVersion
112112
targetSdkVersion rootProject.ext.targetSdkVersion
113113
multiDexEnabled rootProject.ext.multiDexEnabled
114-
versionCode 1009037100
115-
versionName "9.3.71-0"
114+
versionCode 1009037300
115+
versionName "9.3.73-0"
116116
// Supported language variants must be declared here to avoid from being removed during the compilation.
117117
// This also helps us to not include unnecessary language variants in the APK.
118118
resConfigs "en", "es"

ios/NewExpensify/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<key>CFBundlePackageType</key>
2424
<string>APPL</string>
2525
<key>CFBundleShortVersionString</key>
26-
<string>9.3.71</string>
26+
<string>9.3.73</string>
2727
<key>CFBundleSignature</key>
2828
<string>????</string>
2929
<key>CFBundleURLTypes</key>
@@ -44,7 +44,7 @@
4444
</dict>
4545
</array>
4646
<key>CFBundleVersion</key>
47-
<string>9.3.71.0</string>
47+
<string>9.3.73.0</string>
4848
<key>FullStory</key>
4949
<dict>
5050
<key>OrgId</key>

ios/NotificationServiceExtension/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<key>CFBundleName</key>
1212
<string>$(PRODUCT_NAME)</string>
1313
<key>CFBundleShortVersionString</key>
14-
<string>9.3.71</string>
14+
<string>9.3.73</string>
1515
<key>CFBundleVersion</key>
16-
<string>9.3.71.0</string>
16+
<string>9.3.73.0</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionPointIdentifier</key>

ios/ShareViewController/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<key>CFBundleName</key>
1212
<string>$(PRODUCT_NAME)</string>
1313
<key>CFBundleShortVersionString</key>
14-
<string>9.3.71</string>
14+
<string>9.3.73</string>
1515
<key>CFBundleVersion</key>
16-
<string>9.3.71.0</string>
16+
<string>9.3.73.0</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionAttributes</key>

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "new.expensify",
3-
"version": "9.3.71-0",
3+
"version": "9.3.73-0",
44
"author": "Expensify, Inc.",
55
"homepage": "https://new.expensify.com",
66
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",

src/components/CollapsibleHeaderOnKeyboard/index.native.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import React, {useEffect, useRef} from 'react';
33
import type {LayoutChangeEvent} from 'react-native';
44
import {useReanimatedKeyboardAnimation} from 'react-native-keyboard-controller';
55
import Reanimated, {Easing, useAnimatedReaction, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
6+
import useDebounce from '@hooks/useDebounce';
67
import usePrevious from '@hooks/usePrevious';
78
import useWindowDimensions from '@hooks/useWindowDimensions';
89
import isInLandscapeModeUtil from '@libs/isInLandscapeMode';
910
import type {CollapsibleHeaderOnKeyboardProps} from './types';
1011

1112
const COLLAPSE_DURATION = 100;
1213
const RESTORE_DURATION = 300;
14+
const HEADER_CONTENT_LAYOUT_STABILIZATION_TIME_ON_ORIENTATION_CHANGE = 300;
1315
// Assumed vertical space for the focused input field — used to reserve space above the keyboard.
1416
const VERTICAL_SPACE_FOR_FOCUSED_INPUT = 120;
1517
const KEYBOARD_OPENING_PROGRESS_THRESHOLDS = [0.5, 0.7, 0.8, 0.85, 0.9, 0.95, 0.99];
@@ -60,12 +62,33 @@ function CollapsibleHeaderOnKeyboard({children, collapsibleHeaderOffset = 0}: Co
6062
isInLandscapeModeSV.set(isInLandscapeMode);
6163
}, [isInLandscapeMode, isInLandscapeModeSV]);
6264

65+
const debouncedUpdateNaturalHeightRef = useDebounce((height: number) => {
66+
if (isInLandscapeMode) {
67+
return;
68+
}
69+
naturalHeightRef.current = height;
70+
}, HEADER_CONTENT_LAYOUT_STABILIZATION_TIME_ON_ORIENTATION_CHANGE);
71+
6372
const onLayout = (e: LayoutChangeEvent) => {
6473
const height = e.nativeEvent.layout.height;
6574

6675
if (height <= 0) {
6776
return;
6877
}
78+
79+
// Header re-measured in portrait mode and the content is taller than the previous measurement,
80+
// set the natural height to the new height and animate the height to the new height.
81+
// This can happen when header has different height on portrait and landscape mode.
82+
if (!isInLandscapeMode && height > naturalHeightRef.current && naturalHeightRef.current !== -1) {
83+
naturalHeight.set(height);
84+
animatedHeight.set(withTiming(height, {duration: RESTORE_DURATION}));
85+
86+
// we need to debounce the update of the natural height ref to make sure we don't block height updates
87+
// on landscape -> portrait mode change where header layout might not be stable at first.
88+
debouncedUpdateNaturalHeightRef(height);
89+
return;
90+
}
91+
6992
// First measurement, or content changed while header is fully open
7093
// (to skip onLayout calls triggered by our own height animation collapsing the view to 0)
7194
if (naturalHeightRef.current === -1 || animatedHeight.get() >= naturalHeightRef.current) {
@@ -77,19 +100,20 @@ function CollapsibleHeaderOnKeyboard({children, collapsibleHeaderOffset = 0}: Co
77100

78101
// Restores the header when the screen goes from landscape to portrait mode.
79102
useEffect(() => {
80-
const naturalHeightValue = naturalHeightRef.current;
103+
const naturalHeightValue = naturalHeight.get();
81104
if (!isInLandscapeMode && isFocused && naturalHeightValue !== -1) {
82105
animatedHeight.set(withTiming(naturalHeightValue, {duration: RESTORE_DURATION}));
83106
}
84-
}, [isInLandscapeMode, isFocused, animatedHeight]);
107+
// eslint-disable-next-line react-hooks/exhaustive-deps
108+
}, [isInLandscapeMode]);
85109

86110
// Restores the header when the screen loses focus
87111
useEffect(() => {
88112
if (!prevIsFocused || isFocused) {
89113
return;
90114
}
91115

92-
const naturalHeightValue = naturalHeightRef.current;
116+
const naturalHeightValue = naturalHeight.get();
93117
if (naturalHeightValue === -1) {
94118
return;
95119
}
@@ -159,7 +183,7 @@ function CollapsibleHeaderOnKeyboard({children, collapsibleHeaderOffset = 0}: Co
159183
// When fully open, leave height undefined so the view sizes itself naturally.
160184
// This avoids fighting the layout engine during orientation changes.
161185
if (animatedHeight.get() >= naturalHeight.get()) {
162-
return {overflow: 'hidden'};
186+
return {overflow: 'hidden', height: 'auto'};
163187
}
164188
return {height: animatedHeight.get(), overflow: 'hidden'};
165189
});

src/components/LHNOptionsList/OptionRowLHN/OptionRowAvatar.tsx renamed to src/components/LHNOptionsList/OptionRowLHN/OptionRow/Avatar.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import {getDelegateAccountIDFromReportAction} from '@libs/ReportActionsUtils';
77
import type {OptionData} from '@libs/ReportUtils';
88
import CONST from '@src/CONST';
99

10-
type OptionRowAvatarProps = {
10+
type AvatarProps = {
1111
optionItem: OptionData;
1212
isInFocusMode: boolean;
1313
subscriptAvatarBorderColor: ColorValue;
1414
secondaryAvatarBackgroundColor: ColorValue;
1515
singleAvatarContainerStyle: ViewStyle[];
1616
};
1717

18-
function OptionRowAvatarInner({optionItem, isInFocusMode, subscriptAvatarBorderColor, secondaryAvatarBackgroundColor, singleAvatarContainerStyle}: OptionRowAvatarProps) {
18+
function AvatarInner({optionItem, isInFocusMode, subscriptAvatarBorderColor, secondaryAvatarBackgroundColor, singleAvatarContainerStyle}: AvatarProps) {
1919
const personalDetails = usePersonalDetails();
2020

2121
const delegateAccountID = getDelegateAccountIDFromReportAction(optionItem?.parentReportAction);
@@ -64,15 +64,15 @@ function OptionRowAvatarInner({optionItem, isInFocusMode, subscriptAvatarBorderC
6464
);
6565
}
6666

67-
OptionRowAvatarInner.displayName = 'OptionRowAvatarInner';
67+
AvatarInner.displayName = 'OptionRow.AvatarInner';
6868

69-
function OptionRowAvatar({optionItem, isInFocusMode, subscriptAvatarBorderColor, secondaryAvatarBackgroundColor, singleAvatarContainerStyle}: OptionRowAvatarProps) {
69+
function Avatar({optionItem, isInFocusMode, subscriptAvatarBorderColor, secondaryAvatarBackgroundColor, singleAvatarContainerStyle}: AvatarProps) {
7070
// Bail out before subscribing to personal details when the row has no avatar to render.
7171
if (!optionItem.icons?.length || !optionItem.icons.at(0)) {
7272
return null;
7373
}
7474
return (
75-
<OptionRowAvatarInner
75+
<AvatarInner
7676
optionItem={optionItem}
7777
isInFocusMode={isInFocusMode}
7878
subscriptAvatarBorderColor={subscriptAvatarBorderColor}
@@ -82,6 +82,6 @@ function OptionRowAvatar({optionItem, isInFocusMode, subscriptAvatarBorderColor,
8282
);
8383
}
8484

85-
OptionRowAvatar.displayName = 'OptionRowAvatar';
85+
Avatar.displayName = 'OptionRow.Avatar';
8686

87-
export default OptionRowAvatar;
87+
export default Avatar;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import type {ReactNode} from 'react';
3+
import OfflineWithFeedback from '@components/OfflineWithFeedback';
4+
import type {OptionData} from '@libs/ReportUtils';
5+
6+
type OfflineWrapperProps = {
7+
/** Pending action forwarded to OfflineWithFeedback to drive opacity and strikethrough. */
8+
pendingAction: OptionData['pendingAction'];
9+
10+
/** Errors forwarded to OfflineWithFeedback. Error messages themselves are hidden in the LHN. */
11+
errors: OptionData['allReportErrors'];
12+
13+
/** Row content to wrap. */
14+
children: ReactNode;
15+
};
16+
17+
function OfflineWrapper({pendingAction, errors, children}: OfflineWrapperProps) {
18+
return (
19+
<OfflineWithFeedback
20+
pendingAction={pendingAction}
21+
errors={errors}
22+
shouldShowErrorMessages={false}
23+
needsOffscreenAlphaCompositing
24+
>
25+
{children}
26+
</OfflineWithFeedback>
27+
);
28+
}
29+
30+
OfflineWrapper.displayName = 'OptionRow.OfflineWrapper';
31+
32+
export default OfflineWrapper;

0 commit comments

Comments
 (0)