Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Mobile-Expensify
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
versionCode 1009032701
versionName "9.3.27-1"
versionCode 1009032702
versionName "9.3.27-2"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
Expand Down
2 changes: 1 addition & 1 deletion ios/NewExpensify/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>9.3.27.1</string>
<string>9.3.27.2</string>
<key>FullStory</key>
<dict>
<key>OrgId</key>
Expand Down
2 changes: 1 addition & 1 deletion ios/NotificationServiceExtension/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<key>CFBundleShortVersionString</key>
<string>9.3.27</string>
<key>CFBundleVersion</key>
<string>9.3.27.1</string>
<string>9.3.27.2</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
Expand Down
2 changes: 1 addition & 1 deletion ios/ShareViewController/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<key>CFBundleShortVersionString</key>
<string>9.3.27</string>
<key>CFBundleVersion</key>
<string>9.3.27.1</string>
<string>9.3.27.2</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
"version": "9.3.27-1",
"version": "9.3.27-2",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
Expand Down
52 changes: 32 additions & 20 deletions src/hooks/useLoadReportActions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useIsFocused} from '@react-navigation/native';
import {useCallback, useMemo} from 'react';
import {useCallback, useMemo, useRef} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {getNewerActions, getOlderActions} from '@userActions/Report';
import CONST from '@src/CONST';
Expand All @@ -11,6 +11,9 @@ type UseLoadReportActionsArguments = {
/** The id of the current report */
reportID: string;

/** The id of the reportAction (if specific action was linked to */
reportActionID?: string;

/** The list of reportActions linked to the current report */
reportActions: ReportAction[];

Expand All @@ -31,14 +34,16 @@ type UseLoadReportActionsArguments = {
* Provides reusable logic to get the functions for loading older/newer reportActions.
* Used in the report displaying components
*/
function useLoadReportActions({reportID, reportActions, allReportActionIDs, transactionThreadReport, hasOlderActions, hasNewerActions}: UseLoadReportActionsArguments) {
function useLoadReportActions({reportID, reportActionID, reportActions, allReportActionIDs, transactionThreadReport, hasOlderActions, hasNewerActions}: UseLoadReportActionsArguments) {
const didLoadOlderChats = useRef(false);
const didLoadNewerChats = useRef(false);

const {isOffline} = useNetwork();
const isFocused = useIsFocused();

const newestReportAction = useMemo(() => reportActions?.at(0), [reportActions]);
const oldestReportAction = useMemo(() => reportActions?.at(-1), [reportActions]);

const isTransactionThreadReport = !isEmptyObject(transactionThreadReport);

// Track oldest/newest actions per report in a single pass
const {currentReportOldest, currentReportNewest, transactionThreadOldest, transactionThreadNewest} = useMemo(() => {
let currentReportNewestAction = null;
Expand All @@ -61,7 +66,7 @@ function useLoadReportActions({reportID, reportActions, allReportActionIDs, tran
}
// Oldest = last matching action we encounter
currentReportOldestAction = action;
} else if (isTransactionThreadReport && transactionThreadReport?.reportID === targetReportID) {
} else if (!isEmptyObject(transactionThreadReport) && transactionThreadReport?.reportID === targetReportID) {
// Same logic for transaction thread
if (!transactionThreadNewestAction) {
transactionThreadNewestAction = action;
Expand All @@ -76,7 +81,7 @@ function useLoadReportActions({reportID, reportActions, allReportActionIDs, tran
transactionThreadOldest: transactionThreadOldestAction,
transactionThreadNewest: transactionThreadNewestAction,
};
}, [allReportActionIDs, isTransactionThreadReport, reportActions, reportID, transactionThreadReport?.reportID]);
}, [reportActions, allReportActionIDs, reportID, transactionThreadReport]);

/**
* Retrieves the next set of reportActions for the chat once we are nearing the end of what we are currently
Expand All @@ -94,48 +99,55 @@ function useLoadReportActions({reportID, reportActions, allReportActionIDs, tran
return;
}

if (isTransactionThreadReport) {
didLoadOlderChats.current = true;

if (!isEmptyObject(transactionThreadReport)) {
getOlderActions(reportID, currentReportOldest?.reportActionID);
getOlderActions(transactionThreadReport?.reportID, transactionThreadOldest?.reportActionID);
getOlderActions(transactionThreadReport.reportID, transactionThreadOldest?.reportActionID);
} else {
getOlderActions(reportID, currentReportOldest?.reportActionID);
}
},
[
currentReportOldest?.reportActionID,
hasOlderActions,
isOffline,
isTransactionThreadReport,
oldestReportAction,
reportID,
transactionThreadOldest?.reportActionID,
transactionThreadReport?.reportID,
],
[isOffline, oldestReportAction, hasOlderActions, transactionThreadReport, reportID, currentReportOldest?.reportActionID, transactionThreadOldest?.reportActionID],
);

const loadNewerChats = useCallback(
(force = false) => {
if (
!force &&
(!isFocused ||
(!reportActionID ||
!isFocused ||
!newestReportAction ||
!hasNewerActions ||
isOffline ||
// If there was an error only try again once on initial mount. We should also still load
// more in case we have cached messages.
didLoadNewerChats.current ||
newestReportAction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)
) {
return;
}

didLoadNewerChats.current = true;

if (!isEmptyObject(transactionThreadReport)) {
getNewerActions(reportID, currentReportNewest?.reportActionID);
getNewerActions(transactionThreadReport.reportID, transactionThreadNewest?.reportActionID);
} else if (newestReportAction) {
getNewerActions(reportID, newestReportAction.reportActionID);
}
},
[currentReportNewest?.reportActionID, hasNewerActions, isFocused, isOffline, newestReportAction, reportID, transactionThreadNewest?.reportActionID, transactionThreadReport],
[
reportActionID,
isFocused,
newestReportAction,
hasNewerActions,
isOffline,
transactionThreadReport,
reportID,
currentReportNewest?.reportActionID,
transactionThreadNewest?.reportActionID,
],
);

return {
Expand Down
6 changes: 4 additions & 2 deletions src/libs/API/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,13 @@ function paginate<TRequestType extends ApiRequestType, TCommand extends CommandO

switch (type) {
case CONST.API_REQUEST_TYPE.WRITE:
return processRequest(request, type);
processRequest(request, type);
return;
case CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS:
return processRequest(request, type);
case CONST.API_REQUEST_TYPE.READ:
return waitForWrites(command as ReadCommand).then(() => processRequest(request, type));
waitForWrites(command as ReadCommand).then(() => processRequest(request, type));
return;
default:
throw new Error('Unknown API request type');
}
Expand Down
1 change: 0 additions & 1 deletion src/libs/API/parameters/OpenReportParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ type OpenReportParams = {
*/
moneyRequestPreviewReportActionID?: string;
includePartiallySetupBankAccounts?: boolean;
useLastUnreadReportAction?: boolean;
};

export default OpenReportParams;
7 changes: 3 additions & 4 deletions src/libs/actions/Report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,6 @@ function openReport(
parentReportActionID,
transactionID: transaction?.transactionID,
includePartiallySetupBankAccounts: true,
useLastUnreadReportAction: true,
};

if (optimisticSelfDMReport) {
Expand Down Expand Up @@ -2028,7 +2027,7 @@ function explain(
*/
function getOlderActions(reportID: string | undefined, reportActionID: string | undefined) {
if (!reportID || !reportActionID) {
return Promise.resolve();
return;
}

const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_METADATA>> = [
Expand Down Expand Up @@ -2068,7 +2067,7 @@ function getOlderActions(reportID: string | undefined, reportActionID: string |
reportActionID,
};

return API.paginate(
API.paginate(
CONST.API_REQUEST_TYPE.READ,
READ_COMMANDS.GET_OLDER_ACTIONS,
parameters,
Expand Down Expand Up @@ -2126,7 +2125,7 @@ function getNewerActions(reportID: string | undefined, reportActionID: string |
reportActionID,
};

return API.paginate(
API.paginate(
CONST.API_REQUEST_TYPE.READ,
READ_COMMANDS.GET_NEWER_ACTIONS,
parameters,
Expand Down
24 changes: 19 additions & 5 deletions src/pages/inbox/report/ReportActionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import InvertedFlatList from '@components/InvertedFlatList';
import {usePersonalDetails} from '@components/OnyxListItemProvider';
import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@hooks/useFlatListScrollKey';
import useIsAnonymousUser from '@hooks/useIsAnonymousUser';
import useLocalize from '@hooks/useLocalize';
import useNetworkWithOfflineStatus from '@hooks/useNetworkWithOfflineStatus';
Expand Down Expand Up @@ -344,10 +345,13 @@ function ReportActionsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lastAction?.created]);

const lastActionIndex = lastAction?.reportActionID;
const reportActionSize = useRef(sortedVisibleReportActions.length);
const lastVisibleActionCreated = getReportLastVisibleActionCreated(report, transactionThreadReport);
const hasNewestReportAction = lastAction?.created === lastVisibleActionCreated || isReportPreviewAction(lastAction);
const hasNewestReportActionRef = useRef(hasNewestReportAction);
hasNewestReportActionRef.current = hasNewestReportAction;
const previousLastIndex = useRef(lastActionIndex);
const sortedVisibleReportActionsRef = useRef(sortedVisibleReportActions);

const {isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible, trackVerticalScrolling, onViewableItemsChanged} = useReportUnreadMessageScrollTracking({
Expand All @@ -366,6 +370,20 @@ function ReportActionsList({
hasOnceLoadedReportActions: !!reportMetadata?.hasOnceLoadedReportActions,
});

useEffect(() => {
if (
scrollOffsetRef.current < AUTOSCROLL_TO_TOP_THRESHOLD &&
previousLastIndex.current !== lastActionIndex &&
reportActionSize.current !== sortedVisibleReportActions.length &&
hasNewestReportAction
) {
setIsFloatingMessageCounterVisible(false);
reportScrollManager.scrollToBottom();
}
previousLastIndex.current = lastActionIndex;
reportActionSize.current = sortedVisibleReportActions.length;
}, [lastActionIndex, sortedVisibleReportActions.length, reportScrollManager, hasNewestReportAction, linkedReportActionID, setIsFloatingMessageCounterVisible, scrollOffsetRef]);

useEffect(() => {
const shouldTriggerScroll = shouldFocusToTopOnMount && prevHasCreatedActionAdded && !hasCreatedActionAdded;
if (!shouldTriggerScroll) {
Expand Down Expand Up @@ -526,13 +544,9 @@ function ReportActionsList({
if (action?.reportActionID) {
setActionIdToHighlight(action.reportActionID);
}
} else if (Navigation.getReportRHPActiveRoute()) {
} else {
setIsFloatingMessageCounterVisible(false);
reportScrollManager.scrollToBottom();
} else {
Navigation.setNavigationActionToMicrotaskQueue(() => {
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID));
});
}

setIsScrollToBottomEnabled(true);
Expand Down
1 change: 1 addition & 0 deletions src/pages/inbox/report/ReportActionsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ function ReportActionsView({

const {loadOlderChats, loadNewerChats} = useLoadReportActions({
reportID,
reportActionID,
reportActions,
allReportActionIDs,
transactionThreadReport,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type ChatContentScrollViewPlatformStyles from './types';

const chatContentScrollViewPlatformStyles: ChatContentScrollViewPlatformStyles = {
overflow: 'clip',
overflow: 'hidden',
};

export default chatContentScrollViewPlatformStyles;
11 changes: 5 additions & 6 deletions tests/ui/PaginationTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,6 @@ describe('Pagination', () => {
// Here we have 5 messages from the initial OpenReport and 5 from the initial GetNewerActions.
expect(getReportActions()).toHaveLength(10);

// Simulate the backend returning no new messages to simulate reaching the start of the chat.
mockGetNewerActions(0);

// There is 1 extra call here because of the comment linking report.
TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 3);
TestHelper.expectAPICommandToHaveBeenCalledWith('OpenReport', 1, {reportID: REPORT_ID, reportActionID: '5'});
Expand All @@ -393,20 +390,22 @@ describe('Pagination', () => {

TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 3);
TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 0);
TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 2);
TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 1);

// We now have 10 messages. 5 from the initial OpenReport and 5 from the GetNewerActions call.
expect(getReportActions()).toHaveLength(10);

// Simulate the backend returning no new messages to simulate reaching the start of the chat.
mockGetNewerActions(0);

scrollToOffset(500);
await waitForBatchedUpdatesWithAct();
scrollToOffset(0);
await waitForBatchedUpdatesWithAct();

// When there are no newer actions, we don't want to trigger GetNewerActions again.
TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 3);
TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 0);
TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 2);
TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 1);

// We still have 15 messages. 5 from the initial OpenReport and 5 from the GetNewerActions call.
expect(getReportActions()).toHaveLength(10);
Expand Down
1 change: 1 addition & 0 deletions tests/unit/useLoadReportActionsTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ describe('useLoadReportActions', () => {
const props = {
...baseProps,
hasNewerActions: true,
reportActionID: 'EXISTING_ACTION_ID',
};

const {result} = renderHook(() => useLoadReportActions(props));
Expand Down
Loading