diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml
index 98b75f7da3de..edadad1fdca4 100644
--- a/.github/workflows/testBuild.yml
+++ b/.github/workflows/testBuild.yml
@@ -267,6 +267,6 @@ jobs:
| ![Android](https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${{fromJson(steps.set_var.outputs.android_paths).html_path}}) | ![iOS](https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${{fromJson(steps.set_var.outputs.ios_paths).html_path}}) |
| desktop :computer: | web :spider_web: |
| https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/$PULL_REQUEST_NUMBER/NewExpensify.dmg | https://$PULL_REQUEST_NUMBER.pr-testing.expensify.com |
- | ![desktop](https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/$PULL_REQUEST_NUMBER/NewExpensify.dmg) | ![web](https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://$(PULL_REQUEST_NUMBER).pr-testing.expensify.com) |"
+ | ![desktop](https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/$PULL_REQUEST_NUMBER/NewExpensify.dmg) | ![web](https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://$PULL_REQUEST_NUMBER.pr-testing.expensify.com) |"
env:
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}
diff --git a/android/app/build.gradle b/android/app/build.gradle
index fb08766d230f..36db063b9177 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -156,8 +156,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001024103
- versionName "1.2.41-3"
+ versionCode 1001024201
+ versionName "1.2.42-1"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
diff --git a/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java
index deeff81bf76d..7c1f4a245cd2 100644
--- a/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java
+++ b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java
@@ -1,5 +1,10 @@
package com.expensify.chat.customairshipextender;
+import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
+
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -8,11 +13,13 @@
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuffXfermode;
import android.graphics.PorterDuff.Mode;
+import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.WindowManager;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.Person;
@@ -48,6 +55,12 @@ public class CustomNotificationProvider extends ReactNotificationProvider {
// Logging
private static final String TAG = "NotificationProvider";
+ // Define notification channel
+ public static final String CHANNEL_MESSAGES_ID = "CHANNEL_MESSAGES";
+ public static final String CHANNEL_MESSAGES_NAME = "Message Notifications";
+ public static final String CHANNEL_GROUP_ID = "CHANNEL_GROUP_CHATS";
+ public static final String CHANNEL_GROUP_NAME = "Chats";
+
// Conversation JSON keys
private static final String PAYLOAD_KEY = "payload";
private static final String TYPE_KEY = "type";
@@ -58,6 +71,9 @@ public class CustomNotificationProvider extends ReactNotificationProvider {
public CustomNotificationProvider(@NonNull Context context, @NonNull AirshipConfigOptions configOptions) {
super(context, configOptions);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ createAndRegisterNotificationChannel(context);
+ }
}
@NonNull
@@ -66,6 +82,13 @@ protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @
super.onExtendBuilder(context, builder, arguments);
PushMessage message = arguments.getMessage();
+ // Configure the notification channel or priority to ensure it shows in foreground
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ builder.setChannelId(CHANNEL_MESSAGES_ID);
+ } else {
+ builder.setPriority(PRIORITY_MAX);
+ }
+
if (message.containsKey(PAYLOAD_KEY)) {
try {
JsonMap payload = JsonValue.parseString(message.getExtra(PAYLOAD_KEY)).optMap();
@@ -82,6 +105,17 @@ protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @
return builder;
}
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private void createAndRegisterNotificationChannel(@NonNull Context context) {
+ NotificationChannelGroup channelGroup = new NotificationChannelGroup(CHANNEL_GROUP_ID, CHANNEL_GROUP_NAME);
+ NotificationChannel channel = new NotificationChannel(CHANNEL_MESSAGES_ID, CHANNEL_MESSAGES_NAME, NotificationManager.IMPORTANCE_HIGH);
+ channel.setGroup(CHANNEL_GROUP_ID);
+
+ NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannelGroup(channelGroup);
+ notificationManager.createNotificationChannel(channel);
+ }
+
/**
* Creates a canvas to draw a circle and then draws the bitmap avatar within that circle
* to clip off the area of the bitmap outside the circular path and returns a circular
diff --git a/docs/README.md b/docs/README.md
index 0ff0d480ee41..9193439c4b59 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -41,6 +41,14 @@ ln -sf universal-darwin22 universal-darwin21
- _Note: If you see an error like `Unable to load the EventMachine C Extension...`, try running `gem uninstall eventmachine && bundle install`. If that doesn't work just removing the `--livereload` flag should work._
1. Modify a `.html` or `.md` file and save your changes, and the browser should quickly auto-refresh.
+## Troubleshooting
+
+### Android Chrome emulator
+To visit the site on the Android emulator, go to `10.0.2.2:4000`.
+
+If you're getting an error page that says "Refused to connect", try running `adb reverse tcp:4000 tcp:4000` with your emulator open.
+
+
# How the project is structured
The [docs](https://github.com/Expensify/App/tree/main/docs) folder will contain the following main folders:
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 5655a13a353c..2088673e7654 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.2.41
+ 1.2.42
CFBundleSignature
????
CFBundleURLTypes
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.2.41.3
+ 1.2.42.1
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
@@ -65,8 +65,8 @@
NSCameraUsageDescription
Your camera is used to create chat attachments, documents, and facial capture.
- NSLocationWhenInUseUsageDescription
- Your location is used to determine your default currency.
+ NSLocationAlwaysAndWhenInUseUsageDescription
+ Your location is used to determine your default currency and timezone.
NSMicrophoneUsageDescription
Required for video capture
NSPhotoLibraryAddUsageDescription
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index e82772c4259b..0ab913d00326 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.2.41
+ 1.2.42
CFBundleSignature
????
CFBundleVersion
- 1.2.41.3
+ 1.2.42.1
diff --git a/package-lock.json b/package-lock.json
index ac7cc5424abd..8a64675399b1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.2.41-3",
+ "version": "1.2.42-1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.2.41-3",
+ "version": "1.2.42-1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 918cf4ef3dc4..e40ecdc2286e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.2.41-3",
+ "version": "1.2.42-1",
"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.",
diff --git a/src/components/CheckboxWithTooltip/CheckboxWithTooltipForMobileWebAndNative.js b/src/components/CheckboxWithTooltip/CheckboxWithTooltipForMobileWebAndNative.js
index ac58d030e962..43d3c062608b 100644
--- a/src/components/CheckboxWithTooltip/CheckboxWithTooltipForMobileWebAndNative.js
+++ b/src/components/CheckboxWithTooltip/CheckboxWithTooltipForMobileWebAndNative.js
@@ -38,6 +38,7 @@ class CheckboxWithTooltipForMobileWebAndNative extends React.Component {
);
diff --git a/src/components/CheckboxWithTooltip/checkboxWithTooltipPropTypes.js b/src/components/CheckboxWithTooltip/checkboxWithTooltipPropTypes.js
index e0aa67e85b26..f764e216156b 100644
--- a/src/components/CheckboxWithTooltip/checkboxWithTooltipPropTypes.js
+++ b/src/components/CheckboxWithTooltip/checkboxWithTooltipPropTypes.js
@@ -22,6 +22,9 @@ const propTypes = {
/** Container styles */
style: stylePropTypes,
+ /** Wheter the checkbox is disabled */
+ disabled: PropTypes.bool,
+
/** Props inherited from withWindowDimensions */
...windowDimensionsPropTypes,
};
diff --git a/src/components/CheckboxWithTooltip/index.js b/src/components/CheckboxWithTooltip/index.js
index 83756feb0bc3..ced15a589acb 100644
--- a/src/components/CheckboxWithTooltip/index.js
+++ b/src/components/CheckboxWithTooltip/index.js
@@ -15,6 +15,7 @@ const CheckboxWithTooltip = (props) => {
onPress={props.onPress}
text={props.text}
toggleTooltip={props.toggleTooltip}
+ disabled={props.disabled}
/>
);
}
@@ -22,7 +23,7 @@ const CheckboxWithTooltip = (props) => {
);
return (
diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js
index 2d97a24439fa..ab68b51802c1 100755
--- a/src/components/EmojiPicker/EmojiPickerMenu/index.js
+++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js
@@ -101,6 +101,7 @@ class EmojiPickerMenu extends Component {
end: 0,
},
isFocused: false,
+ isUsingKeyboardMovement: false,
};
}
@@ -245,7 +246,10 @@ class EmojiPickerMenu extends Component {
) {
return;
}
+
+ // Blur the input and change the highlight type to keyboard
this.searchInput.blur();
+ this.setState({isUsingKeyboardMovement: true});
// We only want to hightlight the Emoji if none was highlighted already
// If we already have a highlighted Emoji, lets just skip the first navigation
@@ -313,9 +317,9 @@ class EmojiPickerMenu extends Component {
break;
}
- // Actually highlight the new emoji and scroll to it if the index was changed
+ // Actually highlight the new emoji, apply keyboard movement styles, and scroll to it if the index was changed
if (newIndex !== this.state.highlightedIndex) {
- this.setState({highlightedIndex: newIndex});
+ this.setState({highlightedIndex: newIndex, isUsingKeyboardMovement: true});
this.scrollToHighlightedIndex();
}
}
@@ -440,7 +444,7 @@ class EmojiPickerMenu extends Component {
return (
this.addToFrequentAndSelectEmoji(emoji, item)}
- onHoverIn={() => this.setState({highlightedIndex: index})}
+ onHoverIn={() => this.setState({highlightedIndex: index, isUsingKeyboardMovement: false})}
onHoverOut={() => {
if (this.state.arePointerEventsDisabled) {
return;
@@ -449,6 +453,7 @@ class EmojiPickerMenu extends Component {
}}
emoji={emojiCode}
isHighlighted={index === this.state.highlightedIndex}
+ isUsingKeyboardMovement={this.state.isUsingKeyboardMovement}
/>
);
}
@@ -472,7 +477,7 @@ class EmojiPickerMenu extends Component {
autoFocus
selectTextOnFocus={this.state.selectTextOnFocus}
onSelectionChange={this.onSelectionChange}
- onFocus={() => this.setState({isFocused: true})}
+ onFocus={() => this.setState({isFocused: true, highlightedIndex: -1, isUsingKeyboardMovement: false})}
onBlur={() => this.setState({isFocused: false})}
/>
diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem.js b/src/components/EmojiPicker/EmojiPickerMenuItem.js
index a0c0ab2f71d6..60be79adb9d5 100644
--- a/src/components/EmojiPicker/EmojiPickerMenuItem.js
+++ b/src/components/EmojiPicker/EmojiPickerMenuItem.js
@@ -21,6 +21,9 @@ const propTypes = {
/** Whether this menu item is currently highlighted or not */
isHighlighted: PropTypes.bool,
+
+ /** Whether the emoji is highlighted by the keyboard/mouse */
+ isUsingKeyboardMovement: PropTypes.bool,
};
const EmojiPickerMenuItem = props => (
@@ -32,7 +35,8 @@ const EmojiPickerMenuItem = props => (
pressed,
}) => ([
StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)),
- props.isHighlighted ? styles.emojiItemHighlighted : {},
+ props.isHighlighted && props.isUsingKeyboardMovement ? styles.emojiItemKeyboardHighlighted : {},
+ props.isHighlighted && !props.isUsingKeyboardMovement ? styles.emojiItemHighlighted : {},
styles.emojiItem,
])}
>
@@ -46,6 +50,7 @@ EmojiPickerMenuItem.propTypes = propTypes;
EmojiPickerMenuItem.displayName = 'EmojiPickerMenuItem';
EmojiPickerMenuItem.defaultProps = {
isHighlighted: false,
+ isUsingKeyboardMovement: false,
onHoverIn: () => {},
onHoverOut: () => {},
};
@@ -55,5 +60,6 @@ EmojiPickerMenuItem.defaultProps = {
export default React.memo(
EmojiPickerMenuItem,
(prevProps, nextProps) => prevProps.isHighlighted === nextProps.isHighlighted
- && prevProps.emoji === nextProps.emoji,
+ && prevProps.emoji === nextProps.emoji
+ && prevProps.isUsingKeyboardMovement === nextProps.isUsingKeyboardMovement,
);
diff --git a/src/components/ReportActionItem/IOUPreview.js b/src/components/ReportActionItem/IOUPreview.js
index c8e40e636b58..ceec76c279c6 100644
--- a/src/components/ReportActionItem/IOUPreview.js
+++ b/src/components/ReportActionItem/IOUPreview.js
@@ -1,7 +1,6 @@
import React from 'react';
import {
View,
- ActivityIndicator,
TouchableWithoutFeedback,
} from 'react-native';
import PropTypes from 'prop-types';
@@ -34,6 +33,7 @@ const propTypes = {
onPayButtonPressed: PropTypes.func,
/** The active IOUReport, used for Onyx subscription */
+ // eslint-disable-next-line react/no-unused-prop-types
iouReportID: PropTypes.string.isRequired,
/** The associated chatReport */
@@ -117,11 +117,6 @@ const IOUPreview = (props) => {
// Pay button should only be visible to the manager of the report.
const isCurrentUserManager = managerEmail === sessionEmail;
- const reportIsLoading = _.isEmpty(props.iouReport);
-
- if (reportIsLoading) {
- Report.fetchIOUReportByID(props.iouReportID, props.chatReportID);
- }
const managerName = lodashGet(props.personalDetails, [managerEmail, 'firstName'], '')
|| Str.removeSMSDomain(managerEmail);
@@ -137,72 +132,68 @@ const IOUPreview = (props) => {
return (
- {reportIsLoading
- ?
- : (
- {
- PaymentMethods.clearWalletTermsError();
- Report.clearIOUError(props.chatReportID);
- }}
- errorRowStyles={[styles.mbn1]}
- >
-
-
-
-
- {cachedTotal}
-
- {!props.iouReport.hasOutstandingIOU && (
-
-
-
- )}
+ {
+ PaymentMethods.clearWalletTermsError();
+ Report.clearIOUError(props.chatReportID);
+ }}
+ errorRowStyles={[styles.mbn1]}
+ >
+
+
+
+
+ {cachedTotal}
+
+ {!props.iouReport.hasOutstandingIOU && (
+
+
-
-
-
-
- {isCurrentUserManager
- ? (
-
- {props.iouReport.hasOutstandingIOU
- ? props.translate('iou.youowe', {owner: ownerName})
- : props.translate('iou.youpaid', {owner: ownerName})}
-
- )
- : (
-
- {props.iouReport.hasOutstandingIOU
- ? props.translate('iou.owesyou', {manager: managerName})
- : props.translate('iou.paidyou', {manager: managerName})}
-
- )}
- {(isCurrentUserManager
- && !props.shouldHidePayButton
- && props.iouReport.stateNum === CONST.REPORT.STATE_NUM.PROCESSING && (
-
- ))}
+ )}
+
+
+
-
- )}
+
+ {isCurrentUserManager
+ ? (
+
+ {props.iouReport.hasOutstandingIOU
+ ? props.translate('iou.youowe', {owner: ownerName})
+ : props.translate('iou.youpaid', {owner: ownerName})}
+
+ )
+ : (
+
+ {props.iouReport.hasOutstandingIOU
+ ? props.translate('iou.owesyou', {manager: managerName})
+ : props.translate('iou.paidyou', {manager: managerName})}
+
+ )}
+ {(isCurrentUserManager
+ && !props.shouldHidePayButton
+ && props.iouReport.stateNum === CONST.REPORT.STATE_NUM.PROCESSING && (
+
+ ))}
+
+
);
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index d23837002f74..d0122c0e0531 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -240,13 +240,16 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com
* @param {String} comment
* @param {String} currency
* @param {String} locale
+ * @param {String} existingGroupChatReportID
*
* @return {Object}
*/
-function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment, currency, locale) {
+function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment, currency, locale, existingGroupChatReportID = '') {
const currentUserEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin);
const participantLogins = _.map(participants, participant => OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login).toLowerCase());
- const existingGroupChatReport = ReportUtils.getChatByParticipants(participantLogins);
+ const existingGroupChatReport = existingGroupChatReportID
+ ? chatReports[`${ONYXKEYS.COLLECTION.REPORT}${existingGroupChatReportID}`]
+ : ReportUtils.getChatByParticipants(participantLogins);
const groupChatReport = existingGroupChatReport || ReportUtils.buildOptimisticChatReport(participantLogins);
const groupCreatedReportAction = existingGroupChatReport ? {} : ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail);
const groupChatReportMaxSequenceNumber = lodashGet(groupChatReport, 'maxSequenceNumber', 0);
@@ -331,8 +334,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment
return;
}
- // If we only have one participant, the oneOnOneChatReport is the groupChatReport
- const existingOneOnOneChatReport = hasMultipleParticipants ? ReportUtils.getChatByParticipants([email]) : groupChatReport;
+ // If we only have one participant and the request was initiated from the global create menu, i.e. !existingGroupChatReportID, the oneOnOneChatReport is the groupChatReport
+ const existingOneOnOneChatReport = (!hasMultipleParticipants && !existingGroupChatReportID) ? groupChatReport : ReportUtils.getChatByParticipants([email]);
const oneOnOneChatReport = existingOneOnOneChatReport || ReportUtils.buildOptimisticChatReport([email]);
let oneOnOneIOUReport;
let existingIOUReport = null;
@@ -380,77 +383,59 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment
createChat: existingOneOnOneChatReport ? null : CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
};
- // If we only have one other participant, we just need to update onyxData for the groupChatReport and add an iouReportAction of type = create
- // If we have more participants, we need to push the new oneOnOneChaReport, the create reportAction and iouReportAction of type = create to onyxData
- if (!hasMultipleParticipants) {
- optimisticData[0].value = oneOnOneChatReport;
- optimisticData[1].value = {
- ...optimisticData[1].value,
- [oneOnOneIOUReportAction.sequenceNumber]: oneOnOneIOUReportAction,
- };
- successData[1].value = {
- ...successData[1].value,
- [oneOnOneIOUReportAction.sequenceNumber]: {pendingAction: null},
- };
- failureData[1].value = {
- ...failureData[1].value,
- [oneOnOneIOUReportAction.sequenceNumber]: {pendingAction: null},
- };
- } else {
- optimisticData.push(
- {
- onyxMethod: existingOneOnOneChatReport ? CONST.ONYX.METHOD.MERGE : CONST.ONYX.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.reportID}`,
- value: oneOnOneChatReport,
- },
- {
- onyxMethod: existingOneOnOneChatReport ? CONST.ONYX.METHOD.MERGE : CONST.ONYX.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`,
- value: {
- ...oneOnOneCreatedReportAction,
- [oneOnOneIOUReportAction.sequenceNumber]: oneOnOneIOUReportAction,
- },
+ optimisticData.push(
+ {
+ onyxMethod: existingOneOnOneChatReport ? CONST.ONYX.METHOD.MERGE : CONST.ONYX.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.reportID}`,
+ value: oneOnOneChatReport,
+ },
+ {
+ onyxMethod: existingOneOnOneChatReport ? CONST.ONYX.METHOD.MERGE : CONST.ONYX.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`,
+ value: {
+ ...oneOnOneCreatedReportAction,
+ [oneOnOneIOUReportAction.sequenceNumber]: oneOnOneIOUReportAction,
},
- );
+ },
+ );
- successData.push(
- {
- onyxMethod: CONST.ONYX.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.reportID}`,
- value: {pendingFields: {createChat: null}},
- },
- {
- onyxMethod: CONST.ONYX.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`,
- value: {
- 0: {pendingAction: null},
- [oneOnOneIOUReportAction.sequenceNumber]: {pendingAction: null},
- },
+ successData.push(
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.reportID}`,
+ value: {pendingFields: {createChat: null}},
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`,
+ value: {
+ 0: {pendingAction: null},
+ [oneOnOneIOUReportAction.sequenceNumber]: {pendingAction: null},
},
- );
+ },
+ );
- failureData.push(
- {
- onyxMethod: CONST.ONYX.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.reportID}`,
- value: {
- pendingFields: {createChat: null},
- hasOutstandingIOU: existingOneOnOneChatReport ? existingOneOnOneChatReport.hasOutstandingIOU : false,
- iouReportID: existingOneOnOneChatReport ? existingOneOnOneChatReport.iouReportID : null,
- maxSequenceNumber: oneOnOneChatReportMaxSequenceNumber,
- lastReadSequenceNumber: oneOnOneChatReportMaxSequenceNumber,
- },
+ failureData.push(
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.reportID}`,
+ value: {
+ pendingFields: {createChat: null},
+ hasOutstandingIOU: existingOneOnOneChatReport ? existingOneOnOneChatReport.hasOutstandingIOU : false,
+ iouReportID: existingOneOnOneChatReport ? existingOneOnOneChatReport.iouReportID : null,
+ maxSequenceNumber: oneOnOneChatReportMaxSequenceNumber,
+ lastReadSequenceNumber: oneOnOneChatReportMaxSequenceNumber,
},
- {
- onyxMethod: CONST.ONYX.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`,
- value: {
- 0: {pendingAction: null},
- [oneOnOneIOUReportAction.sequenceNumber]: {pendingAction: null},
- },
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`,
+ value: {
+ 0: {pendingAction: null},
+ [oneOnOneIOUReportAction.sequenceNumber]: {pendingAction: null},
},
- );
- }
+ },
+ );
// Regardless of the number of participants, we always want to push the iouReport update to onyxData
optimisticData.push({
@@ -498,9 +483,10 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment
* @param {String} comment
* @param {String} currency
* @param {String} locale
+ * @param {String} existingGroupChatReportID
*/
-function splitBill(participants, currentUserLogin, amount, comment, currency, locale) {
- const {groupData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, amount, comment, currency, locale);
+function splitBill(participants, currentUserLogin, amount, comment, currency, locale, existingGroupChatReportID = '') {
+ const {groupData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, amount, comment, currency, locale, existingGroupChatReportID);
API.write('SplitBill', {
reportID: groupData.chatReportID,
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index c5299e660178..b4431a5cadad 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -12,15 +12,12 @@ import Navigation from '../Navigation/Navigation';
import * as ActiveClientManager from '../ActiveClientManager';
import Visibility from '../Visibility';
import ROUTES from '../../ROUTES';
-import * as DeprecatedAPI from '../deprecatedAPI';
import * as API from '../API';
import CONFIG from '../../CONFIG';
import CONST from '../../CONST';
import Log from '../Log';
import * as LoginUtils from '../LoginUtils';
import * as ReportUtils from '../ReportUtils';
-import Growl from '../Growl';
-import * as Localize from '../Localize';
import DateUtils from '../DateUtils';
import * as ReportActionsUtils from '../ReportActionsUtils';
import * as OptionsListUtils from '../OptionsListUtils';
@@ -181,76 +178,6 @@ function getSimplifiedIOUReport(reportData, chatReportID) {
};
}
-/**
- * Given IOU and chat report ID fetches most recent IOU data from DeprecatedAPI.
- *
- * @param {Number} iouReportID
- * @param {Number} chatReportID
- * @returns {Promise}
- */
-function fetchIOUReport(iouReportID, chatReportID) {
- return DeprecatedAPI.Get({
- returnValueList: 'reportStuff',
- reportIDList: iouReportID,
- shouldLoadOptionalKeys: true,
- includePinnedReports: true,
- }).then((response) => {
- if (!response) {
- return;
- }
- if (response.jsonCode !== 200) {
- console.error(response.message);
- return;
- }
- const iouReportData = response.reports[iouReportID];
- if (!iouReportData) {
- // IOU data for a report will be missing when the IOU report has already been paid.
- // This is expected and we return early as no further processing can be done.
- return;
- }
- return getSimplifiedIOUReport(iouReportData, chatReportID);
- }).catch((error) => {
- Log.hmmm('[Report] Failed to populate IOU Collection:', error.message);
- });
-}
-
-/**
- * Given IOU object, save the data to Onyx.
- *
- * @param {Object} iouReportObject
- * @param {Number} iouReportObject.stateNum
- * @param {Number} iouReportObject.total
- * @param {Number} iouReportObject.reportID
- */
-function setLocalIOUReportData(iouReportObject) {
- const iouReportKey = `${ONYXKEYS.COLLECTION.REPORT}${iouReportObject.reportID}`;
- Onyx.merge(iouReportKey, iouReportObject);
-}
-
-/**
- * Fetch the iouReport and persist the data to Onyx.
- *
- * @param {Number} iouReportID - ID of the report we are fetching
- * @param {Number} chatReportID - associated chatReportID, set as an iouReport field
- * @param {Boolean} [shouldRedirectIfEmpty=false] - Whether to redirect to Active Report Screen if IOUReport is empty
- * @returns {Promise}
- */
-function fetchIOUReportByID(iouReportID, chatReportID, shouldRedirectIfEmpty = false) {
- return fetchIOUReport(iouReportID, chatReportID)
- .then((iouReportObject) => {
- if (!iouReportObject && shouldRedirectIfEmpty) {
- Growl.error(Localize.translateLocal('notFound.iouReportNotFound'));
- Navigation.navigate(ROUTES.REPORT);
- return;
- }
- if (!iouReportObject) {
- return;
- }
- setLocalIOUReportData(iouReportObject);
- return iouReportObject;
- });
-}
-
/**
* Get the private pusher channel name for a Report.
*
@@ -1434,7 +1361,6 @@ Onyx.connect({
});
export {
- fetchIOUReportByID,
addComment,
addAttachment,
reconnect,
@@ -1449,7 +1375,6 @@ export {
handleUserDeletedLinks,
saveReportActionDraft,
deleteReportComment,
- getSimplifiedIOUReport,
syncChatAndIOUReports,
navigateToConciergeChat,
setReportWithDraft,
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index 35149943c78a..f49f3ccb8bbb 100644
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -573,7 +573,6 @@ class ReportActionCompose extends React.Component {
// Keep focus on the composer when Collapse button is clicked.
onMouseDown={e => e.preventDefault()}
style={styles.composerSizeButton}
- underlayColor={themeColors.componentBG}
disabled={isBlockedFromConcierge}
>
@@ -592,7 +591,6 @@ class ReportActionCompose extends React.Component {
// Keep focus on the composer when Expand button is clicked.
onMouseDown={e => e.preventDefault()}
style={styles.composerSizeButton}
- underlayColor={themeColors.componentBG}
disabled={isBlockedFromConcierge}
>
@@ -610,7 +608,6 @@ class ReportActionCompose extends React.Component {
this.setMenuVisibility(true);
}}
style={styles.chatItemAttachButton}
- underlayColor={themeColors.componentBG}
disabled={isBlockedFromConcierge}
>
@@ -701,7 +698,6 @@ class ReportActionCompose extends React.Component {
(this.state.isCommentEmpty || hasExceededMaxCommentLength) ? undefined : styles.buttonSuccess,
]}
onPress={this.submitForm}
- underlayColor={themeColors.componentBG}
// Keep focus on the composer when Send message is clicked.
// eslint-disable-next-line react/jsx-props-no-multi-spaces
diff --git a/src/pages/iou/IOUDetailsModal.js b/src/pages/iou/IOUDetailsModal.js
index 28673c667100..7dfaaec1197a 100644
--- a/src/pages/iou/IOUDetailsModal.js
+++ b/src/pages/iou/IOUDetailsModal.js
@@ -145,7 +145,6 @@ class IOUDetailsModal extends Component {
(
{props.translate('common.goBack')}
diff --git a/src/pages/signin/PasswordForm.js b/src/pages/signin/PasswordForm.js
index f2e2c3558481..0ae644469d6c 100755
--- a/src/pages/signin/PasswordForm.js
+++ b/src/pages/signin/PasswordForm.js
@@ -162,7 +162,6 @@ class PasswordForm extends React.Component {
{this.props.translate('passwordForm.forgot')}
diff --git a/src/pages/signin/ResendValidationForm.js b/src/pages/signin/ResendValidationForm.js
index 8854905a47e8..7753faa65294 100755
--- a/src/pages/signin/ResendValidationForm.js
+++ b/src/pages/signin/ResendValidationForm.js
@@ -80,7 +80,7 @@ const ResendValidationForm = (props) => {
)}
redirectToSignIn()}>
-
+
{props.translate('common.back')}
diff --git a/src/styles/styles.js b/src/styles/styles.js
index cea49b51e51a..706f54d80e2c 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -1496,6 +1496,13 @@ const styles = {
backgroundColor: themeColors.buttonDefaultBG,
},
+ emojiItemKeyboardHighlighted: {
+ transition: '0.2s ease',
+ borderWidth: 1,
+ borderColor: themeColors.link,
+ borderRadius: variables.buttonBorderRadius,
+ },
+
chatItemEmojiButton: {
alignSelf: 'flex-end',
borderRadius: variables.buttonBorderRadius,