, policy: OnyxEntry): boolean {
reportAction?.actorAccountID === currentUserAccountID &&
isCommentOrIOU &&
canEditMoneyRequest(reportAction) && // Returns true for non-IOU actions
- !isReportMessageAttachment(reportAction?.message?.[0] ?? {type: '', text: ''}) &&
+ !ReportActionsUtils.isReportActionAttachment(reportAction) &&
!ReportActionsUtils.isDeletedAction(reportAction) &&
!ReportActionsUtils.isCreatedTaskReportAction(reportAction) &&
reportAction?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
@@ -2251,8 +2233,7 @@ function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewActio
*
*/
function hasMissingSmartscanFields(iouReportID: string): boolean {
- const transactionsWithReceipts = getTransactionsWithReceipts(iouReportID);
- return transactionsWithReceipts.some((transaction) => TransactionUtils.hasMissingSmartscanFields(transaction));
+ return TransactionUtils.getAllReportTransactions(iouReportID).some((transaction) => TransactionUtils.hasMissingSmartscanFields(transaction));
}
/**
@@ -3323,7 +3304,6 @@ function updateReportPreview(iouReport: OnyxEntry, reportPreviewAction:
const message = getReportPreviewMessage(iouReport, reportPreviewAction);
return {
...reportPreviewAction,
- created: DateUtils.getDBTime(),
message: [
{
html: message,
@@ -4005,10 +3985,10 @@ function shouldReportBeInOptionList({
/**
* Attempts to find a report in onyx with the provided list of participants. Does not include threads, task, money request, room, and policy expense chat.
*/
-function getChatByParticipants(newParticipantList: number[]): OnyxEntry {
+function getChatByParticipants(newParticipantList: number[], reports: OnyxCollection = allReports): OnyxEntry {
const sortedNewParticipantList = newParticipantList.sort();
return (
- Object.values(allReports ?? {}).find((report) => {
+ Object.values(reports ?? {}).find((report) => {
// If the report has been deleted, or there are no participants (like an empty #admins room) then skip it
if (
!report ||
@@ -4256,7 +4236,7 @@ function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, o
if (isMoneyRequestReport(report)) {
const isOwnExpenseReport = isExpenseReport(report) && isOwnPolicyExpenseChat;
if (isOwnExpenseReport && PolicyUtils.isPaidGroupPolicy(policy)) {
- return isDraftExpenseReport(report);
+ return isDraftExpenseReport(report) || isExpenseReportWithInstantSubmittedState(report);
}
return (isOwnExpenseReport || isIOUReport(report)) && !isReportApproved(report) && !isSettled(report?.reportID);
@@ -5058,6 +5038,7 @@ export {
isPublicAnnounceRoom,
isConciergeChatReport,
isProcessingReport,
+ isExpenseReportWithInstantSubmittedState,
isCurrentUserTheOnlyParticipant,
hasAutomatedExpensifyAccountIDs,
hasExpensifyGuidesEmails,
diff --git a/src/libs/Request.ts b/src/libs/Request.ts
index aa94eccbca2e..fc31160bbc1c 100644
--- a/src/libs/Request.ts
+++ b/src/libs/Request.ts
@@ -13,7 +13,9 @@ function makeXHR(request: Request): Promise {
// If we're using the Supportal token and this is not a Supportal request
// let's just return a promise that will resolve itself.
if (NetworkStore.getSupportAuthToken() && !NetworkStore.isSupportRequest(request.command)) {
- return new Promise((resolve) => resolve());
+ return new Promise((resolve) => {
+ resolve();
+ });
}
return HttpUtils.xhr(request.command, finalParameters, request.type, request.shouldUseSecure);
diff --git a/src/libs/RequestThrottle.ts b/src/libs/RequestThrottle.ts
index 36935982afbb..4c524394cb2c 100644
--- a/src/libs/RequestThrottle.ts
+++ b/src/libs/RequestThrottle.ts
@@ -26,9 +26,10 @@ function sleep(): Promise {
requestRetryCount++;
return new Promise((resolve, reject) => {
if (requestRetryCount <= CONST.NETWORK.MAX_REQUEST_RETRIES) {
- return setTimeout(resolve, getRequestWaitTime());
+ setTimeout(resolve, getRequestWaitTime());
+ return;
}
- return reject();
+ reject();
});
}
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index d9298817f6b7..35cf52a5ff99 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -234,7 +234,7 @@ function getOptionData({
result.isExpenseRequest = ReportUtils.isExpenseRequest(report);
result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report);
- result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : undefined;
+ result.pendingAction = report.pendingFields?.addWorkspaceRoom ?? report.pendingFields?.createChat;
result.brickRoadIndicator = hasErrors || hasViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '';
result.ownerAccountID = report.ownerAccountID;
result.managerID = report.managerID;
diff --git a/src/libs/Sound/playSoundExcludingMobile/index.native.ts b/src/libs/Sound/playSoundExcludingMobile/index.native.ts
new file mode 100644
index 000000000000..c41ad6998483
--- /dev/null
+++ b/src/libs/Sound/playSoundExcludingMobile/index.native.ts
@@ -0,0 +1,2 @@
+// mobile platform plays a sound when notification is delivered (in native code)
+export default function playSoundExcludingMobile() {}
diff --git a/src/libs/Sound/playSoundExcludingMobile/index.ts b/src/libs/Sound/playSoundExcludingMobile/index.ts
new file mode 100644
index 000000000000..03c5cd57a635
--- /dev/null
+++ b/src/libs/Sound/playSoundExcludingMobile/index.ts
@@ -0,0 +1,5 @@
+import playSound from '..';
+
+const playSoundExcludingMobile: typeof playSound = (sound) => playSound(sound);
+
+export default playSoundExcludingMobile;
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index ee0ec6d9755b..67e31c610369 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -4,15 +4,15 @@ import Onyx from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {RecentWaypoint, Report, ReportAction, Transaction, TransactionViolation} from '@src/types/onyx';
+import type {RecentWaypoint, Report, ReportAction, TaxRate, TaxRates, Transaction, TransactionViolation} from '@src/types/onyx';
import type {IOUMessage} from '@src/types/onyx/OriginalMessage';
-import type {PolicyTaxRate, PolicyTaxRates} from '@src/types/onyx/PolicyTaxRates';
import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {isCorporateCard, isExpensifyCard} from './CardUtils';
import DateUtils from './DateUtils';
import * as NumberUtils from './NumberUtils';
+import {getCleanedTagName} from './PolicyUtils';
import type {OptimisticIOUReportAction} from './ReportUtils';
let allTransactions: OnyxCollection = {};
@@ -410,7 +410,7 @@ function getTag(transaction: OnyxEntry, tagIndex?: number): string
}
function getTagForDisplay(transaction: OnyxEntry, tagIndex?: number): string {
- return getTag(transaction, tagIndex).replace(/[\\\\]:/g, ':');
+ return getCleanedTagName(getTag(transaction, tagIndex));
}
/**
@@ -481,7 +481,7 @@ function isReceiptBeingScanned(transaction: OnyxEntry): boolean {
* Check if the transaction has a non-smartscanning receipt and is missing required fields
*/
function hasMissingSmartscanFields(transaction: OnyxEntry): boolean {
- return Boolean(transaction && hasReceipt(transaction) && !isDistanceRequest(transaction) && !isReceiptBeingScanned(transaction) && areRequiredFieldsEmpty(transaction));
+ return Boolean(transaction && !isDistanceRequest(transaction) && !isReceiptBeingScanned(transaction) && areRequiredFieldsEmpty(transaction));
}
/**
@@ -606,8 +606,8 @@ function calculateTaxAmount(percentage: string, amount: number) {
/**
* Calculates count of all tax enabled options
*/
-function getEnabledTaxRateCount(options: PolicyTaxRates) {
- return Object.values(options).filter((option: PolicyTaxRate) => !option.isDisabled).length;
+function getEnabledTaxRateCount(options: TaxRates) {
+ return Object.values(options).filter((option: TaxRate) => !option.isDisabled).length;
}
export {
diff --git a/src/libs/Trie/index.ts b/src/libs/Trie/index.ts
index c23c7b814a22..a82c90c15772 100644
--- a/src/libs/Trie/index.ts
+++ b/src/libs/Trie/index.ts
@@ -31,7 +31,6 @@ class Trie {
}
if (!newNode.children[newWord[0]]) {
newNode.children[newWord[0]] = new TrieNode();
- this.add(newWord.substring(1), metaData, newNode.children[newWord[0]], true);
}
this.add(newWord.substring(1), metaData, newNode.children[newWord[0]], true);
}
diff --git a/src/libs/UnreadIndicatorUpdater/updateUnread/index.desktop.ts b/src/libs/UnreadIndicatorUpdater/updateUnread/index.desktop.ts
index 5cbba61542b1..75013ebe621f 100644
--- a/src/libs/UnreadIndicatorUpdater/updateUnread/index.desktop.ts
+++ b/src/libs/UnreadIndicatorUpdater/updateUnread/index.desktop.ts
@@ -1,4 +1,4 @@
-import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS';
+import ELECTRON_EVENTS from '@desktop/ELECTRON_EVENTS';
import type UpdateUnread from './types';
/**
diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts
index 12b52524f113..147343e99ceb 100644
--- a/src/libs/UserUtils.ts
+++ b/src/libs/UserUtils.ts
@@ -4,6 +4,7 @@ import type {ValueOf} from 'type-fest';
import * as defaultAvatars from '@components/Icon/DefaultAvatars';
import {ConciergeAvatar, FallbackAvatar, NotificationsAvatar} from '@components/Icon/Expensicons';
import CONST from '@src/CONST';
+import type {LoginList} from '@src/types/onyx';
import type Login from '@src/types/onyx/Login';
import type IconAsset from '@src/types/utils/IconAsset';
import hashCode from './hashCode';
@@ -12,7 +13,7 @@ type AvatarRange = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
type AvatarSource = IconAsset | string;
-type LoginListIndicator = ValueOf | '';
+type LoginListIndicator = ValueOf | undefined;
/**
* Searches through given loginList for any contact method / login with an error.
@@ -35,8 +36,8 @@ type LoginListIndicator = ValueOf | ''
* }
* }}
*/
-function hasLoginListError(loginList: Record): boolean {
- return Object.values(loginList).some((loginData) => Object.values(loginData.errorFields ?? {}).some((field) => Object.keys(field ?? {}).length > 0));
+function hasLoginListError(loginList: OnyxEntry): boolean {
+ return Object.values(loginList ?? {}).some((loginData) => Object.values(loginData.errorFields ?? {}).some((field) => Object.keys(field ?? {}).length > 0));
}
/**
@@ -44,22 +45,22 @@ function hasLoginListError(loginList: Record): boolean {
* an Info brick road status indicator. Currently this only applies if the user
* has an unvalidated contact method.
*/
-function hasLoginListInfo(loginList: Record): boolean {
- return !Object.values(loginList).every((field) => field.validatedDate);
+function hasLoginListInfo(loginList: OnyxEntry): boolean {
+ return !Object.values(loginList ?? {}).every((field) => field.validatedDate);
}
/**
* Gets the appropriate brick road indicator status for a given loginList.
* Error status is higher priority, so we check for that first.
*/
-function getLoginListBrickRoadIndicator(loginList: Record): LoginListIndicator {
+function getLoginListBrickRoadIndicator(loginList: OnyxEntry): LoginListIndicator {
if (hasLoginListError(loginList)) {
return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
}
if (hasLoginListInfo(loginList)) {
return CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
}
- return '';
+ return undefined;
}
/**
@@ -227,4 +228,4 @@ export {
hashText,
isDefaultAvatar,
};
-export type {AvatarSource};
+export type {AvatarSource, LoginListIndicator};
diff --git a/src/libs/Visibility/index.desktop.ts b/src/libs/Visibility/index.desktop.ts
index c01b6001f456..e3cab6ec44a6 100644
--- a/src/libs/Visibility/index.desktop.ts
+++ b/src/libs/Visibility/index.desktop.ts
@@ -1,4 +1,4 @@
-import ELECTRON_EVENTS from '../../../desktop/ELECTRON_EVENTS';
+import ELECTRON_EVENTS from '@desktop/ELECTRON_EVENTS';
import type {HasFocus, IsVisible, OnVisibilityChange} from './types';
/**
diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts
index d2c650d6f1c0..6442f2ec0eef 100644
--- a/src/libs/actions/App.ts
+++ b/src/libs/actions/App.ts
@@ -15,7 +15,7 @@ import type {
ReconnectAppParams,
UpdatePreferredLocaleParams,
} from '@libs/API/parameters';
-import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
+import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as Browser from '@libs/Browser';
import DateUtils from '@libs/DateUtils';
import Log from '@libs/Log';
@@ -211,7 +211,7 @@ function openApp() {
getPolicyParamsForOpenOrReconnect().then((policyParams: PolicyParamsForOpenOrReconnect) => {
const params: OpenAppParams = {enablePriorityModeFilter: true, ...policyParams};
- API.read(READ_COMMANDS.OPEN_APP, params, getOnyxDataForOpenOrReconnect(true));
+ API.write(WRITE_COMMANDS.OPEN_APP, params, getOnyxDataForOpenOrReconnect(true));
});
}
diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts
index 2cc32616562d..756ef902d913 100644
--- a/src/libs/actions/Card.ts
+++ b/src/libs/actions/Card.ts
@@ -178,3 +178,4 @@ function revealVirtualCardDetails(cardID: number): Promise {
}
export {requestReplacementExpensifyCard, activatePhysicalExpensifyCard, clearCardListErrors, reportVirtualExpensifyCardFraud, revealVirtualCardDetails};
+export type {ReplacementReason};
diff --git a/src/libs/actions/Device/generateDeviceID/index.desktop.ts b/src/libs/actions/Device/generateDeviceID/index.desktop.ts
index f8af0a7faa81..45daf7062a5a 100644
--- a/src/libs/actions/Device/generateDeviceID/index.desktop.ts
+++ b/src/libs/actions/Device/generateDeviceID/index.desktop.ts
@@ -1,4 +1,4 @@
-import ELECTRON_EVENTS from '../../../../../desktop/ELECTRON_EVENTS';
+import ELECTRON_EVENTS from '@desktop/ELECTRON_EVENTS';
import type GenerateDeviceID from './types';
/**
diff --git a/src/libs/actions/Device/index.ts b/src/libs/actions/Device/index.ts
index 761e27d95a78..e7c19d20e4fe 100644
--- a/src/libs/actions/Device/index.ts
+++ b/src/libs/actions/Device/index.ts
@@ -12,7 +12,8 @@ let deviceID: string | null = null;
function getDeviceID(): Promise {
return new Promise((resolve) => {
if (deviceID) {
- return resolve(deviceID);
+ resolve(deviceID);
+ return;
}
const connectionID = Onyx.connect({
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index bc2b59e98f6a..e6443f676681 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -50,7 +50,7 @@ import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';
import type {Participant, Split} from '@src/types/onyx/IOU';
-import type {ErrorFields, Errors, PendingFields} from '@src/types/onyx/OnyxCommon';
+import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import type ReportAction from '@src/types/onyx/ReportAction';
import type {OnyxData} from '@src/types/onyx/Request';
@@ -302,7 +302,7 @@ function setMoneyRequestMerchant(transactionID: string, merchant: string, isDraf
Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {merchant});
}
-function setMoneyRequestPendingFields(transactionID: string, pendingFields: PendingFields) {
+function setMoneyRequestPendingFields(transactionID: string, pendingFields: OnyxTypes.Transaction['pendingFields']) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {pendingFields});
}
@@ -324,9 +324,9 @@ function setMoneyRequestParticipants_temporaryForRefactor(transactionID: string,
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants});
}
-function setMoneyRequestReceipt(transactionID: string, source: string, filename: string, isDraft: boolean) {
+function setMoneyRequestReceipt(transactionID: string, source: string, filename: string, isDraft: boolean, type?: string) {
Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {
- receipt: {source},
+ receipt: {source, type: type ?? ''},
filename,
});
}
@@ -815,8 +815,8 @@ function getMoneyRequestInformation(
if (isPolicyExpenseChat) {
isFromPaidPolicy = PolicyUtils.isPaidGroupPolicy(policy ?? null);
- // If the linked expense report on paid policy is not draft, we need to create a new draft expense report
- if (iouReport && isFromPaidPolicy && !ReportUtils.isDraftExpenseReport(iouReport)) {
+ // If the linked expense report on paid policy is not draft and not instantly submitted, we need to create a new draft expense report
+ if (iouReport && isFromPaidPolicy && !ReportUtils.isDraftExpenseReport(iouReport) && !ReportUtils.isExpenseReportWithInstantSubmittedState(iouReport)) {
iouReport = null;
}
}
@@ -825,7 +825,7 @@ function getMoneyRequestInformation(
if (isPolicyExpenseChat) {
iouReport = {...iouReport};
if (iouReport?.currency === currency && typeof iouReport.total === 'number') {
- // Because of the Expense reports are stored as negative values, we substract the total from the amount
+ // Because of the Expense reports are stored as negative values, we subtract the total from the amount
iouReport.total -= amount;
}
} else {
@@ -4220,6 +4220,7 @@ function navigateToStartStepIfScanFileCannotBeRead(
iouType: ValueOf,
transactionID: string,
reportID: string,
+ receiptType: string,
) {
if (!receiptFilename || !receiptPath) {
return;
@@ -4233,7 +4234,7 @@ function navigateToStartStepIfScanFileCannotBeRead(
}
IOUUtils.navigateToStartMoneyRequestStep(requestType, iouType, transactionID, reportID);
};
- FileUtils.readFileAsync(receiptPath, receiptFilename, onSuccess, onFailure);
+ FileUtils.readFileAsync(receiptPath, receiptFilename, onSuccess, onFailure, receiptType);
}
/** Save the preferred payment method for a policy */
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 57cd4a6fc071..b9a2e8535b62 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -54,6 +54,7 @@ import type {
} from '@src/types/onyx';
import type {Errors} from '@src/types/onyx/OnyxCommon';
import type {Attributes, CustomUnit, Rate, Unit} from '@src/types/onyx/Policy';
+import type {OnyxData} from '@src/types/onyx/Request';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
@@ -2178,6 +2179,60 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string
return policyID;
}
+const setWorkspaceRequiresCategory = (policyID: string, requiresCategory: boolean) => {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ requiresCategory,
+ errors: {
+ requiresCategory: null,
+ },
+ pendingFields: {
+ requiresCategory: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ errors: {
+ requiresCategory: null,
+ },
+ pendingFields: {
+ requiresCategory: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ requiresCategory: !requiresCategory,
+ errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.genericFailureMessage'),
+ pendingFields: {
+ requiresCategory: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters = {
+ policyID,
+ requiresCategory,
+ };
+
+ API.write('SetWorkspaceRequiresCategory', parameters, onyxData);
+};
+
export {
removeMembers,
addMembersToWorkspace,
@@ -2221,4 +2276,5 @@ export {
setWorkspaceAutoReporting,
setWorkspaceApprovalMode,
updateWorkspaceDescription,
+ setWorkspaceRequiresCategory,
};
diff --git a/src/libs/actions/Session/clearCache/index.ts b/src/libs/actions/Session/clearCache/index.ts
index 6d288c6cbd3b..3daa8ec2d7d7 100644
--- a/src/libs/actions/Session/clearCache/index.ts
+++ b/src/libs/actions/Session/clearCache/index.ts
@@ -1,5 +1,8 @@
import type ClearCache from './types';
-const clearStorage: ClearCache = () => new Promise((res) => res());
+const clearStorage: ClearCache = () =>
+ new Promise((resolve) => {
+ resolve();
+ });
export default clearStorage;
diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts
index f384e38f6d55..013d86049150 100644
--- a/src/libs/actions/Session/index.ts
+++ b/src/libs/actions/Session/index.ts
@@ -856,7 +856,7 @@ function handleExitToNavigation(exitTo: Routes | HybridAppRoute) {
waitForUserSignIn().then(() => {
Navigation.waitForProtectedRoutes().then(() => {
const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo;
- Navigation.navigate(url, CONST.NAVIGATION.TYPE.FORCED_UP);
+ Navigation.navigate(url);
});
});
});
diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts
index 74e6a6c78eeb..9c9384912251 100644
--- a/src/libs/actions/Task.ts
+++ b/src/libs/actions/Task.ts
@@ -386,7 +386,7 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task
const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail);
// Sometimes title or description is undefined, so we need to check for that, and we provide it to multiple functions
- const reportName = (title ?? report?.reportName).trim();
+ const reportName = (title ?? report?.reportName)?.trim();
// Description can be unset, so we default to an empty string if so
const reportDescription = (description ?? report.description ?? '').trim();
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index 2280f6cdc0f5..d7cef2aca546 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -29,6 +29,7 @@ import * as Pusher from '@libs/Pusher/pusher';
import PusherUtils from '@libs/PusherUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import playSound, {SOUNDS} from '@libs/Sound';
+import playSoundExcludingMobile from '@libs/Sound/playSoundExcludingMobile';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -527,12 +528,12 @@ function playSoundForMessageType(pushJSON: OnyxServerUpdate[]) {
// mention user
if ('html' in message && typeof message.html === 'string' && message.html.includes(`@${currentEmail}`)) {
- return playSound(SOUNDS.ATTENTION);
+ return playSoundExcludingMobile(SOUNDS.ATTENTION);
}
// mention @here
if ('html' in message && typeof message.html === 'string' && message.html.includes('')) {
- return playSound(SOUNDS.ATTENTION);
+ return playSoundExcludingMobile(SOUNDS.ATTENTION);
}
// assign a task
@@ -552,7 +553,7 @@ function playSoundForMessageType(pushJSON: OnyxServerUpdate[]) {
// plain message
if ('html' in message) {
- return playSound(SOUNDS.RECEIVE);
+ return playSoundExcludingMobile(SOUNDS.RECEIVE);
}
}
} catch (e) {
diff --git a/src/libs/calculateAnchorPosition.ts b/src/libs/calculateAnchorPosition.ts
index 0f1e383522eb..3dc5924d023a 100644
--- a/src/libs/calculateAnchorPosition.ts
+++ b/src/libs/calculateAnchorPosition.ts
@@ -16,13 +16,15 @@ type AnchorOrigin = {
export default function calculateAnchorPosition(anchorComponent: View | RNText, anchorOrigin?: AnchorOrigin): Promise {
return new Promise((resolve) => {
if (!anchorComponent) {
- return resolve({horizontal: 0, vertical: 0});
+ resolve({horizontal: 0, vertical: 0});
+ return;
}
anchorComponent.measureInWindow((x, y, width, height) => {
if (anchorOrigin?.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP && anchorOrigin?.horizontal === CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT) {
- return resolve({horizontal: x, vertical: y + height + (anchorOrigin?.shiftVertical ?? 0)});
+ resolve({horizontal: x, vertical: y + height + (anchorOrigin?.shiftVertical ?? 0)});
+ return;
}
- return resolve({horizontal: x + width, vertical: y + (anchorOrigin?.shiftVertical ?? 0)});
+ resolve({horizontal: x + width, vertical: y + (anchorOrigin?.shiftVertical ?? 0)});
});
});
}
diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts
index 055abf140e64..06bd47f3b39b 100644
--- a/src/libs/fileDownload/FileUtils.ts
+++ b/src/libs/fileDownload/FileUtils.ts
@@ -80,14 +80,14 @@ function showCameraPermissionsAlert() {
* Extracts a filename from a given URL and sanitizes it for file system usage.
*
* This function takes a URL as input and performs the following operations:
- * 1. Extracts the last segment of the URL, which could be a file name, a path segment,
- * or a query string parameter.
+ * 1. Extracts the last segment of the URL.
* 2. Decodes the extracted segment from URL encoding to a plain string for better readability.
* 3. Replaces any characters in the decoded string that are illegal in file names
* with underscores.
*/
function getFileName(url: string): string {
- const fileName = url.split(/[#?/]/).pop() ?? '';
+ const fileName = url.split('/').pop()?.split('?')[0].split('#')[0] ?? '';
+
if (!fileName) {
Log.warn('[FileUtils] Could not get attachment name', {url});
}
@@ -111,7 +111,7 @@ function getFileType(fileUrl: string): string | undefined {
return;
}
- const fileName = fileUrl.split('/').pop()?.split('?')[0].split('#')[0];
+ const fileName = getFileName(fileUrl);
if (!fileName) {
return;
@@ -159,7 +159,7 @@ function appendTimeToFileName(fileName: string): string {
* @param path - the blob url of the locally uploaded file
* @param fileName - name of the file to read
*/
-const readFileAsync: ReadFileAsync = (path, fileName, onSuccess, onFailure = () => {}) =>
+const readFileAsync: ReadFileAsync = (path, fileName, onSuccess, onFailure = () => {}, fileType = '') =>
new Promise((resolve) => {
if (!path) {
resolve();
@@ -176,7 +176,9 @@ const readFileAsync: ReadFileAsync = (path, fileName, onSuccess, onFailure = ()
}
res.blob()
.then((blob) => {
- const file = new File([blob], cleanFileName(fileName), {type: blob.type});
+ // On Android devices, fetching blob for a file with name containing spaces fails to retrieve the type of file.
+ // In this case, let us fallback on fileType provided by the caller of this function.
+ const file = new File([blob], cleanFileName(fileName), {type: blob.type || fileType});
file.source = path;
// For some reason, the File object on iOS does not have a uri property
// so images aren't uploaded correctly to the backend
diff --git a/src/libs/fileDownload/types.ts b/src/libs/fileDownload/types.ts
index 6d92bddd5816..f09ce495795b 100644
--- a/src/libs/fileDownload/types.ts
+++ b/src/libs/fileDownload/types.ts
@@ -8,7 +8,7 @@ type GetImageResolution = (url: File | Asset) => Promise;
type ExtensionAndFileName = {fileName: string; fileExtension: string};
type SplitExtensionFromFileName = (fileName: string) => ExtensionAndFileName;
-type ReadFileAsync = (path: string, fileName: string, onSuccess: (file: File) => void, onFailure: (error?: unknown) => void) => Promise;
+type ReadFileAsync = (path: string, fileName: string, onSuccess: (file: File) => void, onFailure: (error?: unknown) => void, fileType?: string) => Promise;
type AttachmentDetails = {
previewSourceURL: null | string;
diff --git a/src/libs/getSplashBackgroundColor/index.native.ts b/src/libs/getSplashBackgroundColor/index.native.ts
new file mode 100644
index 000000000000..1d21b7a004e1
--- /dev/null
+++ b/src/libs/getSplashBackgroundColor/index.native.ts
@@ -0,0 +1,7 @@
+import colors from '@styles/theme/colors';
+
+function getSplashBackgroundColor() {
+ return colors.green400;
+}
+
+export default getSplashBackgroundColor;
diff --git a/src/libs/getSplashBackgroundColor/index.ts b/src/libs/getSplashBackgroundColor/index.ts
new file mode 100644
index 000000000000..97484d49163e
--- /dev/null
+++ b/src/libs/getSplashBackgroundColor/index.ts
@@ -0,0 +1,7 @@
+import colors from '@styles/theme/colors';
+
+function getSplashBackgroundColor() {
+ return colors.productDark100;
+}
+
+export default getSplashBackgroundColor;
diff --git a/src/libs/isReportMessageAttachment.ts b/src/libs/isReportMessageAttachment.ts
index df8a589f7bdc..fd03adcffd93 100644
--- a/src/libs/isReportMessageAttachment.ts
+++ b/src/libs/isReportMessageAttachment.ts
@@ -1,3 +1,4 @@
+import Str from 'expensify-common/lib/str';
import CONST from '@src/CONST';
import type {Message} from '@src/types/onyx/ReportAction';
@@ -17,5 +18,5 @@ export default function isReportMessageAttachment({text, html, translationKey}:
}
const regex = new RegExp(` ${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}="(.*)"`, 'i');
- return text === CONST.ATTACHMENT_MESSAGE_TEXT && (!!html.match(regex) || html === CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML);
+ return (text === CONST.ATTACHMENT_MESSAGE_TEXT || !!Str.isVideo(text)) && (!!html.match(regex) || html === CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML);
}
diff --git a/src/pages/DetailsPage.tsx b/src/pages/DetailsPage.tsx
index d4438d3141bf..a9adb5310e58 100755
--- a/src/pages/DetailsPage.tsx
+++ b/src/pages/DetailsPage.tsx
@@ -64,22 +64,13 @@ function DetailsPage({personalDetails, route, session}: DetailsPageProps) {
let details = Object.values(personalDetails ?? {}).find((personalDetail) => personalDetail?.login === login.toLowerCase());
if (!details) {
- if (login === CONST.EMAIL.CONCIERGE) {
- details = {
- accountID: CONST.ACCOUNT_ID.CONCIERGE,
- login,
- displayName: 'Concierge',
- avatar: UserUtils.getDefaultAvatar(CONST.ACCOUNT_ID.CONCIERGE),
- };
- } else {
- const optimisticAccountID = UserUtils.generateAccountID(login);
- details = {
- accountID: optimisticAccountID,
- login,
- displayName: login,
- avatar: UserUtils.getDefaultAvatar(optimisticAccountID),
- };
- }
+ const optimisticAccountID = UserUtils.generateAccountID(login);
+ details = {
+ accountID: optimisticAccountID,
+ login,
+ displayName: login,
+ avatar: UserUtils.getDefaultAvatar(optimisticAccountID),
+ };
}
const isSMSLogin = details.login ? Str.isSMSLogin(details.login) : false;
diff --git a/src/pages/EnablePayments/TermsStep.js b/src/pages/EnablePayments/TermsStep.js
index a09e1801c3b0..9fa3a4becea3 100644
--- a/src/pages/EnablePayments/TermsStep.js
+++ b/src/pages/EnablePayments/TermsStep.js
@@ -7,6 +7,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
@@ -32,6 +33,30 @@ const defaultProps = {
walletTerms: {},
};
+function HaveReadAndAgreeLabel() {
+ const {translate} = useLocalize();
+
+ return (
+
+ {`${translate('termsStep.haveReadAndAgree')}`}
+ {`${translate('termsStep.electronicDisclosures')}.`}
+
+ );
+}
+
+function AgreeToTheLabel() {
+ const {translate} = useLocalize();
+
+ return (
+
+ {`${translate('termsStep.agreeToThe')} `}
+ {`${translate('common.privacy')} `}
+ {`${translate('common.and')} `}
+ {`${translate('termsStep.walletAgreement')}.`}
+
+ );
+}
+
function TermsStep(props) {
const styles = useThemeStyles();
const [hasAcceptedDisclosure, setHasAcceptedDisclosure] = useState(false);
@@ -71,27 +96,12 @@ function TermsStep(props) {
accessibilityLabel={props.translate('termsStep.haveReadAndAgree')}
style={[styles.mb4, styles.mt4]}
onInputChange={toggleDisclosure}
- LabelComponent={() => (
-
- {`${props.translate('termsStep.haveReadAndAgree')}`}
- {`${props.translate('termsStep.electronicDisclosures')}.`}
-
- )}
+ LabelComponent={HaveReadAndAgreeLabel}
/>
(
-
- {`${props.translate('termsStep.agreeToThe')} `}
-
- {`${props.translate('common.privacy')} `}
-
- {`${props.translate('common.and')} `}
-
- {`${props.translate('termsStep.walletAgreement')}.`}
-
- )}
+ LabelComponent={AgreeToTheLabel}
/>