diff --git a/.github/actions/composite/setupNode/action.yml b/.github/actions/composite/setupNode/action.yml
index d475acf5380f..643c707da230 100644
--- a/.github/actions/composite/setupNode/action.yml
+++ b/.github/actions/composite/setupNode/action.yml
@@ -12,6 +12,6 @@ runs:
- name: Install node packages
uses: nick-invision/retry@0711ba3d7808574133d713a0d92d2941be03a350
with:
- timeout_minutes: 10
- max_attempts: 5
+ timeout_minutes: 30
+ max_attempts: 3
command: npm ci
diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml
index 4a8a3fd732c0..e1b38b713d7b 100644
--- a/.github/workflows/platformDeploy.yml
+++ b/.github/workflows/platformDeploy.yml
@@ -10,7 +10,7 @@ on:
env:
SHOULD_DEPLOY_PRODUCTION: ${{ github.event_name == 'release' }}
- DEVELOPER_DIR: /Applications/Xcode_14.0.1.app/Contents/Developer
+ DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
jobs:
validateActor:
diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml
index 6b068c9f6f8e..f52453dcdf40 100644
--- a/.github/workflows/testBuild.yml
+++ b/.github/workflows/testBuild.yml
@@ -11,7 +11,7 @@ on:
branches: ['*ci-test/**']
env:
- DEVELOPER_DIR: /Applications/Xcode_14.0.1.app/Contents/Developer
+ DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
jobs:
validateActor:
diff --git a/android/app/build.gradle b/android/app/build.gradle
index eebe2c40c3a4..6ddd0630e3cb 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 1001029004
- versionName "1.2.90-4"
+ versionCode 1001029006
+ versionName "1.2.90-6"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html
index ac648bfe4fed..9c256602144a 100644
--- a/docs/_layouts/default.html
+++ b/docs/_layouts/default.html
@@ -2,6 +2,7 @@
+
Expensify Help
diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj
index ee77a2595272..192571488305 100644
--- a/ios/NewExpensify.xcodeproj/project.pbxproj
+++ b/ios/NewExpensify.xcodeproj/project.pbxproj
@@ -61,28 +61,28 @@
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = NewExpensify/AppDelegate.h; sourceTree = ""; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = NewExpensify/Info.plist; sourceTree = ""; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = NewExpensify/main.m; sourceTree = ""; };
- 177D06D4BF2346EB90E37D3D /* ExpensifyMono-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-Bold.otf"; path = "../assets/fonts/native/ExpensifyMono-Bold.otf"; sourceTree = ""; };
+ 177D06D4BF2346EB90E37D3D /* ExpensifyMono-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-Bold.otf"; path = "../assets/fonts/native/ExpensifyMono-Bold.otf"; sourceTree = ""; };
18D050DF262400AF000D658B /* BridgingFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgingFile.swift; sourceTree = ""; };
- 1977066010294D51AEB35F3B /* ExpensifyNeue-Italic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Italic.otf"; path = "../assets/fonts/native/ExpensifyNeue-Italic.otf"; sourceTree = ""; };
- 1B3F09A4E4EA4CFFA5E4E7CD /* ExpensifyMono-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-Regular.otf"; path = "../assets/fonts/native/ExpensifyMono-Regular.otf"; sourceTree = ""; };
+ 1977066010294D51AEB35F3B /* ExpensifyNeue-Italic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Italic.otf"; path = "../assets/fonts/native/ExpensifyNeue-Italic.otf"; sourceTree = ""; };
+ 1B3F09A4E4EA4CFFA5E4E7CD /* ExpensifyMono-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-Regular.otf"; path = "../assets/fonts/native/ExpensifyMono-Regular.otf"; sourceTree = ""; };
374FB8D528A133A7000D84EF /* OriginImageRequestHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = OriginImageRequestHandler.h; path = NewExpensify/OriginImageRequestHandler.h; sourceTree = ""; };
374FB8D628A133FE000D84EF /* OriginImageRequestHandler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = OriginImageRequestHandler.mm; path = NewExpensify/OriginImageRequestHandler.mm; sourceTree = ""; };
- 38E61473EAA34C598CB6B345 /* ExpensifyNeue-BoldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-BoldItalic.otf"; path = "../assets/fonts/native/ExpensifyNeue-BoldItalic.otf"; sourceTree = ""; };
+ 38E61473EAA34C598CB6B345 /* ExpensifyNeue-BoldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-BoldItalic.otf"; path = "../assets/fonts/native/ExpensifyNeue-BoldItalic.otf"; sourceTree = ""; };
3EDF186626B8D2CBA203E08D /* libPods-NewExpensify.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NewExpensify.a"; sourceTree = BUILT_PRODUCTS_DIR; };
- 44BF435285B94E5B95F90994 /* ExpensifyNewKansas-Medium.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNewKansas-Medium.otf"; path = "../assets/fonts/native/ExpensifyNewKansas-Medium.otf"; sourceTree = ""; };
- 6BEDED270C49437581EBE50D /* ExpensifyNeue-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Regular.otf"; path = "../assets/fonts/native/ExpensifyNeue-Regular.otf"; sourceTree = ""; };
+ 44BF435285B94E5B95F90994 /* ExpensifyNewKansas-Medium.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNewKansas-Medium.otf"; path = "../assets/fonts/native/ExpensifyNewKansas-Medium.otf"; sourceTree = ""; };
+ 6BEDED270C49437581EBE50D /* ExpensifyNeue-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Regular.otf"; path = "../assets/fonts/native/ExpensifyNeue-Regular.otf"; sourceTree = ""; };
7041848326A8E40900E09F4D /* RCTStartupTimer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RCTStartupTimer.h; path = NewExpensify/RCTStartupTimer.h; sourceTree = ""; };
7041848426A8E47D00E09F4D /* RCTStartupTimer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RCTStartupTimer.m; path = NewExpensify/RCTStartupTimer.m; sourceTree = ""; };
70CF6E81262E297300711ADC /* BootSplash.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = BootSplash.storyboard; path = NewExpensify/BootSplash.storyboard; sourceTree = ""; };
A2695CF895D81C31AFB4A074 /* libPods-NewExpensify-NewExpensifyTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NewExpensify-NewExpensifyTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
B66D52EC75F78B8A06F1E035 /* Pods-NewExpensify.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.debug.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.debug.xcconfig"; sourceTree = ""; };
- D2AFB39EC1D44BF9B91D3227 /* ExpensifyNewKansas-MediumItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNewKansas-MediumItalic.otf"; path = "../assets/fonts/native/ExpensifyNewKansas-MediumItalic.otf"; sourceTree = ""; };
+ D2AFB39EC1D44BF9B91D3227 /* ExpensifyNewKansas-MediumItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNewKansas-MediumItalic.otf"; path = "../assets/fonts/native/ExpensifyNewKansas-MediumItalic.otf"; sourceTree = ""; };
DD7904292792E76D004484B4 /* RCTBootSplash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RCTBootSplash.h; path = NewExpensify/RCTBootSplash.h; sourceTree = ""; };
DD79042A2792E76D004484B4 /* RCTBootSplash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RCTBootSplash.m; path = NewExpensify/RCTBootSplash.m; sourceTree = ""; };
E9DF872C2525201700607FDC /* AirshipConfig.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = AirshipConfig.plist; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; };
- EDFC169F9D7A43BDB924151F /* ExpensifyNeue-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Bold.otf"; path = "../assets/fonts/native/ExpensifyNeue-Bold.otf"; sourceTree = ""; };
+ EDFC169F9D7A43BDB924151F /* ExpensifyNeue-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Bold.otf"; path = "../assets/fonts/native/ExpensifyNeue-Bold.otf"; sourceTree = ""; };
F0C450E92705020500FD2970 /* colors.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = colors.json; path = ../colors.json; sourceTree = ""; };
/* End PBXFileReference section */
@@ -744,7 +744,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.1;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
@@ -799,7 +799,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.1;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index cff7e96c5ec0..75e0a39c60c9 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.2.90.4
+ 1.2.90.6
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index bee389de6da6..bb56aa13111a 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.2.90.4
+ 1.2.90.6
diff --git a/package-lock.json b/package-lock.json
index 890859c71d19..5a79292f32ed 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.2.90-4",
+ "version": "1.2.90-6",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.2.90-4",
+ "version": "1.2.90-6",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -135,7 +135,7 @@
"css-loader": "^6.7.2",
"diff-so-fancy": "^1.3.0",
"dotenv": "^16.0.3",
- "electron": "^22.3.4",
+ "electron": "22.3.4",
"electron-builder": "23.5.0",
"electron-notarize": "^1.2.1",
"eslint": "^7.6.0",
diff --git a/package.json b/package.json
index 66c0613cdd3a..7c384a103aeb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.2.90-4",
+ "version": "1.2.90-6",
"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/CONST.js b/src/CONST.js
index 0e19417f5962..53d176719019 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -606,6 +606,10 @@ const CONST = {
WIDTH: 320,
HEIGHT: 416,
},
+ CATEGORY_SHORTCUT_BAR_HEIGHT: 40,
+ SMALL_EMOJI_PICKER_SIZE: {
+ WIDTH: '100%',
+ },
NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT: 256,
EMOJI_PICKER_ITEM_HEIGHT: 32,
EMOJI_PICKER_HEADER_HEIGHT: 32,
diff --git a/src/components/AttachmentCarousel/index.js b/src/components/AttachmentCarousel/index.js
index 144d0aaa0874..bd232526bd9b 100644
--- a/src/components/AttachmentCarousel/index.js
+++ b/src/components/AttachmentCarousel/index.js
@@ -196,6 +196,7 @@ class AttachmentCarousel extends React.Component {
this.toggleArrowsVisibility(!this.state.shouldShowArrow)}
source={authSource}
+ key={authSource}
file={this.state.file}
/>
diff --git a/src/components/EmojiPicker/EmojiPicker.js b/src/components/EmojiPicker/EmojiPicker/index.js
similarity index 90%
rename from src/components/EmojiPicker/EmojiPicker.js
rename to src/components/EmojiPicker/EmojiPicker/index.js
index 369365a8db84..5238c2dbdb1b 100644
--- a/src/components/EmojiPicker/EmojiPicker.js
+++ b/src/components/EmojiPicker/EmojiPicker/index.js
@@ -1,9 +1,17 @@
import React from 'react';
import {Dimensions, Keyboard} from 'react-native';
import _ from 'underscore';
-import EmojiPickerMenu from './EmojiPickerMenu';
-import CONST from '../../CONST';
-import PopoverWithMeasuredContent from '../PopoverWithMeasuredContent';
+import EmojiPickerMenu from '../EmojiPickerMenu';
+import CONST from '../../../CONST';
+import PopoverWithMeasuredContent from '../../PopoverWithMeasuredContent';
+import compose from '../../../libs/compose';
+import withViewportOffsetTop, {viewportOffsetTopPropTypes} from '../../withViewportOffsetTop';
+import withWindowDimensions, {windowDimensionsPropTypes} from '../../withWindowDimensions';
+
+const propTypes = {
+ ...viewportOffsetTopPropTypes,
+ ...windowDimensionsPropTypes,
+};
const DEFAULT_ANCHOR_ORIGIN = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
@@ -176,6 +184,7 @@ class EmojiPicker extends React.Component {
}}
anchorOrigin={this.state.emojiPopoverAnchorOrigin}
measureContent={this.measureContent}
+ outerStyle={{maxHeight: this.props.windowHeight, marginTop: this.props.viewportOffsetTop}}
>
- {!this.props.isSmallScreenWidth && (
-
- this.searchInput = el}
- autoFocus
- selectTextOnFocus={this.state.selectTextOnFocus}
- onSelectionChange={this.onSelectionChange}
- onFocus={() => this.setState({isFocused: true, highlightedIndex: -1, isUsingKeyboardMovement: false})}
- onBlur={() => this.setState({isFocused: false})}
- />
-
- )}
+
+ this.searchInput = el}
+ autoFocus={!this.isMobileLandscape() || this.props.isSmallScreenWidth}
+ selectTextOnFocus={this.state.selectTextOnFocus}
+ onSelectionChange={this.onSelectionChange}
+ onFocus={() => this.setState({isFocused: true, highlightedIndex: -1, isUsingKeyboardMovement: false})}
+ onBlur={() => this.setState({isFocused: false})}
+ autoCorrect={false}
+ />
+
{!isFiltered && (
this.emojiList = el}
data={this.state.filteredEmojis}
renderItem={this.renderItem}
- keyExtractor={item => `emoji_picker_${item.code}`}
+ keyExtractor={this.keyExtractor}
numColumns={CONST.EMOJI_NUM_PER_ROW}
style={[
styles.emojiPickerList,
+ StyleUtils.getEmojiPickerListHeight(isFiltered),
this.isMobileLandscape() && styles.emojiPickerListLandscape,
]}
extraData={
diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
index 702be109e5e1..149e180c7cd7 100644
--- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
+++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
@@ -16,7 +16,9 @@ import withLocalize, {withLocalizePropTypes} from '../../withLocalize';
import EmojiSkinToneList from '../EmojiSkinToneList';
import * as EmojiUtils from '../../../libs/EmojiUtils';
import * as User from '../../../libs/actions/User';
+import TextInput from '../../TextInput';
import CategoryShortcutBar from '../CategoryShortcutBar';
+import * as StyleUtils from '../../../styles/StyleUtils';
const propTypes = {
/** Function to add the selected emoji to the main compose text input */
@@ -64,14 +66,48 @@ class EmojiPickerMenu extends Component {
this.renderItem = this.renderItem.bind(this);
this.isMobileLandscape = this.isMobileLandscape.bind(this);
this.updatePreferredSkinTone = this.updatePreferredSkinTone.bind(this);
+ this.filterEmojis = _.debounce(this.filterEmojis.bind(this), 300);
this.scrollToHeader = this.scrollToHeader.bind(this);
this.getItemLayout = this.getItemLayout.bind(this);
+
+ this.state = {
+ filteredEmojis: this.emojis,
+ headerIndices: this.headerRowIndices,
+ };
}
getItemLayout(data, index) {
return {length: CONST.EMOJI_PICKER_ITEM_HEIGHT, offset: CONST.EMOJI_PICKER_ITEM_HEIGHT * index, index};
}
+ /**
+ * Filter the entire list of emojis to only emojis that have the search term in their keywords
+ *
+ * @param {String} searchTerm
+ */
+ filterEmojis(searchTerm) {
+ const normalizedSearchTerm = searchTerm.toLowerCase().trim();
+
+ if (this.emojiList) {
+ this.emojiList.scrollToOffset({offset: 0, animated: false});
+ }
+
+ if (normalizedSearchTerm === '') {
+ this.setState({
+ filteredEmojis: this.emojis,
+ headerIndices: this.headerRowIndices,
+ });
+
+ return;
+ }
+ const newFilteredEmojiList = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, this.emojis.length);
+
+ this.setState({
+ filteredEmojis: newFilteredEmojiList,
+ headerIndices: undefined,
+ });
+ }
+
/**
* @param {String} emoji
* @param {Object} emojiObject
@@ -112,6 +148,16 @@ class EmojiPickerMenu extends Component {
})();
}
+ /**
+ * Return a unique key for each emoji item
+ *
+ * @param {Object} item
+ * @returns {String}
+ */
+ keyExtractor(item) {
+ return (`emoji_picker_${item.code}`);
+ }
+
/**
* Given an emoji item object, render a component based on its type.
* Items with the code "SPACER" return nothing and are used to fill rows up to 8
@@ -149,28 +195,56 @@ class EmojiPickerMenu extends Component {
}
render() {
+ const isFiltered = this.emojis.length !== this.state.filteredEmojis.length;
return (
-
+
+
+
+ {!isFiltered && (
-
- this.emojiList = el}
- data={this.emojis}
- renderItem={this.renderItem}
- keyExtractor={item => (`emoji_picker_${item.code}`)}
- numColumns={CONST.EMOJI_NUM_PER_ROW}
- style={[
- styles.emojiPickerList,
- this.isMobileLandscape() && styles.emojiPickerListLandscape,
- ]}
- stickyHeaderIndices={this.headerRowIndices}
- getItemLayout={this.getItemLayout}
- showsVerticalScrollIndicator
- />
+ )}
+ {this.state.filteredEmojis.length === 0
+ ? (
+
+
+ {this.props.translate('common.noResultsFound')}
+
+
+ )
+ : (
+ this.emojiList = el}
+ keyboardShouldPersistTaps="handled"
+ data={this.state.filteredEmojis}
+ renderItem={this.renderItem}
+ keyExtractor={this.keyExtractor}
+ numColumns={CONST.EMOJI_NUM_PER_ROW}
+ style={[
+ styles.emojiPickerList,
+ StyleUtils.getEmojiPickerListHeight(isFiltered),
+ this.isMobileLandscape() && styles.emojiPickerListLandscape,
+ ]}
+ stickyHeaderIndices={this.state.headerIndices}
+ getItemLayout={this.getItemLayout}
+ showsVerticalScrollIndicator
+
+ // used because of a bug in RN where stickyHeaderIndices can't be updated after the list is rendered https://github.com/facebook/react-native/issues/25157
+ removeClippedSubviews={false}
+ />
+ )}
{
this.inputRefs[inputID] = node;
- // Call the original ref, if any
const {ref} = child;
if (_.isFunction(ref)) {
ref(node);
@@ -285,7 +284,7 @@ class Form extends React.Component {
},
value: this.state.inputValues[inputID],
errorText: this.state.errors[inputID] || fieldErrorMessage,
- onBlur: () => {
+ onBlur: (event) => {
// We delay the validation in order to prevent Checkbox loss of focus when
// the user are focusing a TextInput and proceeds to toggle a CheckBox in
// web and mobile web platforms.
@@ -293,6 +292,10 @@ class Form extends React.Component {
this.setTouchedInput(inputID);
this.validate(this.state.inputValues);
}, 200);
+
+ if (_.isFunction(child.props.onBlur)) {
+ child.props.onBlur(event);
+ }
},
onInputChange: (value, key) => {
const inputKey = key || inputID;
diff --git a/src/components/Hoverable/index.js b/src/components/Hoverable/index.js
index 2f10cca0c1e3..12145b93b260 100644
--- a/src/components/Hoverable/index.js
+++ b/src/components/Hoverable/index.js
@@ -69,19 +69,15 @@ class Hoverable extends Component {
onMouseEnter: (el) => {
this.setIsHovered(true);
- // Call the original onMouseEnter, if any
- const {onMouseEnter} = this.props.children;
- if (_.isFunction(onMouseEnter)) {
- onMouseEnter(el);
+ if (_.isFunction(this.props.children.props.onMouseEnter)) {
+ this.props.children.props.onMouseEnter(el);
}
},
onMouseLeave: (el) => {
this.setIsHovered(false);
- // Call the original onMouseLeave, if any
- const {onMouseLeave} = this.props.children;
- if (_.isFunction(onMouseLeave)) {
- onMouseLeave(el);
+ if (_.isFunction(this.props.children.props.onMouseLeave)) {
+ this.props.children.props.onMouseLeave(el);
}
},
onBlur: (el) => {
@@ -89,10 +85,8 @@ class Hoverable extends Component {
this.setIsHovered(false);
}
- // Call the original onBlur, if any
- const {onBlur} = this.props.children;
- if (_.isFunction(onBlur)) {
- onBlur(el);
+ if (_.isFunction(this.props.children.props.onBlur)) {
+ this.props.children.props.onBlur(el);
}
},
});
diff --git a/src/components/KeyboardAvoidingView/index.android.js b/src/components/KeyboardAvoidingView/index.android.js
new file mode 100644
index 000000000000..6f3adcc95b1f
--- /dev/null
+++ b/src/components/KeyboardAvoidingView/index.android.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import {View, KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native';
+import _ from 'underscore';
+import PropTypes from 'prop-types';
+
+const propTypes = {
+ /** In most cases, we do not need to use KeyboardAvoidingView on Android, so it is set to false by default. */
+ shouldApplyToAndroid: PropTypes.bool,
+};
+const defaultProps = {
+ shouldApplyToAndroid: false,
+};
+
+const KeyboardAvoidingView = (props) => {
+ if (props.shouldApplyToAndroid) {
+ return (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+ );
+ }
+ const viewProps = _.omit(props, ['behavior', 'contentContainerStyle', 'enabled', 'keyboardVerticalOffset']);
+ return (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+ );
+};
+
+KeyboardAvoidingView.displayName = 'KeyboardAvoidingView';
+KeyboardAvoidingView.propTypes = propTypes;
+KeyboardAvoidingView.defaultProps = defaultProps;
+
+export default KeyboardAvoidingView;
diff --git a/src/components/KeyboardAvoidingView/index.ios.js b/src/components/KeyboardAvoidingView/index.ios.js
index aeeb32e417bc..58f40b3276a3 100644
--- a/src/components/KeyboardAvoidingView/index.ios.js
+++ b/src/components/KeyboardAvoidingView/index.ios.js
@@ -1,6 +1,3 @@
-/*
- * The KeyboardAvoidingView is only used on ios
- */
import React from 'react';
import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native';
diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js
index 1e314654bba3..26018641d6c1 100644
--- a/src/components/Modal/BaseModal.js
+++ b/src/components/Modal/BaseModal.js
@@ -10,6 +10,7 @@ import {propTypes as modalPropTypes, defaultProps as modalDefaultProps} from './
import * as Modal from '../../libs/actions/Modal';
import getModalStyles from '../../styles/getModalStyles';
import variables from '../../styles/variables';
+import KeyboardAvoidingView from '../KeyboardAvoidingView';
const propTypes = {
...modalPropTypes,
@@ -132,6 +133,7 @@ class BaseModal extends PureComponent {
animationInTiming={this.props.animationInTiming}
animationOutTiming={this.props.animationOutTiming}
statusBarTranslucent={this.props.statusBarTranslucent}
+ avoidKeyboard={this.props.avoidKeyboard}
>
{(insets) => {
@@ -156,8 +158,7 @@ class BaseModal extends PureComponent {
modalContainerStylePaddingTop: modalContainerStyle.paddingTop,
modalContainerStylePaddingBottom: modalContainerStyle.paddingBottom,
});
-
- return (
+ const content = (
);
+
+ return (
+
+ {content}
+
+ );
}}
diff --git a/src/components/Reactions/AddReactionBubble.js b/src/components/Reactions/AddReactionBubble.js
index 3b57771311ab..d91af3c0ff14 100644
--- a/src/components/Reactions/AddReactionBubble.js
+++ b/src/components/Reactions/AddReactionBubble.js
@@ -79,7 +79,7 @@ const AddReactionBubble = (props) => {
};
return (
-
+
{
ref={ref}
onPress={openEmojiPicker}
isDelayButtonStateComplete={false}
- tooltipText={props.translate('reportActionContextMenu.addReactionTooltip')}
+ tooltipText={props.translate('emojiReactions.addReactionTooltip')}
>
{({hovered, pressed}) => (
{
styles.fontColorReactionLabel,
]}
>
- {`reacted with :${props.emojiName}:`}
+ {`${props.translate('emojiReactions.reactedWith')} :${props.emojiName}:`}
);
diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js
index 104098b375f0..b84fbd6cb0ed 100644
--- a/src/components/Tooltip/index.js
+++ b/src/components/Tooltip/index.js
@@ -172,10 +172,8 @@ class Tooltip extends PureComponent {
onBlur: (el) => {
this.hideTooltip();
- // Call the original onBlur, if any
- const {onBlur} = this.props.children;
- if (_.isFunction(onBlur)) {
- onBlur(el);
+ if (_.isFunction(this.props.children.props.onBlur)) {
+ this.props.children.props.onBlur(el);
}
},
focusable: true,
diff --git a/src/languages/en.js b/src/languages/en.js
index 765be98ed4dc..de94fc63e838 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -231,7 +231,10 @@ export default {
editComment: 'Edit comment',
deleteComment: 'Delete comment',
deleteConfirmation: 'Are you sure you want to delete this comment?',
+ },
+ emojiReactions: {
addReactionTooltip: 'Add reaction',
+ reactedWith: 'reacted with',
},
reportActionsView: {
beginningOfArchivedRoomPartOne: 'You missed the party in ',
diff --git a/src/languages/es.js b/src/languages/es.js
index f7012c3d6654..be0090cbcf79 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -230,7 +230,10 @@ export default {
editComment: 'Editar comentario',
deleteComment: 'Eliminar comentario',
deleteConfirmation: '¿Estás seguro de que quieres eliminar este comentario?',
+ },
+ emojiReactions: {
addReactionTooltip: 'Añadir una reacción',
+ reactedWith: 'reaccionó con',
},
reportActionsView: {
beginningOfArchivedRoomPartOne: 'Te perdiste la fiesta en ',
diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js
index 7c60e65eda3c..df55ae49faee 100644
--- a/src/libs/ReportActionsUtils.js
+++ b/src/libs/ReportActionsUtils.js
@@ -41,19 +41,8 @@ function isDeletedAction(reportAction) {
}
/**
- * @param {Object} reportAction
- * @returns {Boolean}
- */
-function isOptimisticAction(reportAction) {
- return lodashGet(reportAction, 'pendingAction') === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD;
-}
-
-/**
- * Sort an array of reportActions by:
- *
- * - Finalized actions always are "later" than optimistic actions
- * - then sort by created timestamp
- * - then sort by reportActionID. This gives us a stable order even in the case of multiple reportActions created on the same millisecond
+ * Sort an array of reportActions by their created timestamp first, and reportActionID second
+ * This gives us a stable order even in the case of multiple reportActions created on the same millisecond
*
* @param {Array} reportActions
* @param {Boolean} shouldSortInDescendingOrder
@@ -68,11 +57,6 @@ function getSortedReportActions(reportActions, shouldSortInDescendingOrder = fal
return _.chain(reportActions)
.compact()
.sort((first, second) => {
- // First, make sure that optimistic reportActions appear at the end
- if (isOptimisticAction(second) && !isOptimisticAction(first)) {
- return -1 * invertedMultiplier;
- }
-
// First sort by timestamp
if (first.created !== second.created) {
return (first.created < second.created ? -1 : 1) * invertedMultiplier;
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index d57a559c3396..df0a3209e5a0 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -21,7 +21,6 @@ import linkingConfig from './Navigation/linkingConfig';
import * as defaultAvatars from '../components/Icon/DefaultAvatars';
import isReportMessageAttachment from './isReportMessageAttachment';
import * as defaultWorkspaceAvatars from '../components/Icon/WorkspaceDefaultAvatars';
-import * as CollectionUtils from './CollectionUtils';
let sessionEmail;
Onyx.connect({
@@ -72,21 +71,6 @@ Onyx.connect({
callback: val => allReports = val,
});
-const lastReportActions = {};
-Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- callback: (actions, key) => {
- if (!key || !actions) {
- return;
- }
- const reportID = CollectionUtils.extractCollectionItemID(key);
- lastReportActions[reportID] = _.find(
- ReportActionsUtils.getSortedReportActionsForDisplay(_.toArray(actions)),
- reportAction => reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
- );
- },
-});
-
let doesDomainHaveApprovedAccountant;
Onyx.connect({
key: ONYXKEYS.ACCOUNT,
@@ -450,31 +434,6 @@ function canShowReportRecipientLocalTime(personalDetails, report) {
&& isReportParticipantValidated);
}
-/**
- * Gets the last message text from the report.
- * Looks at reportActions data as the "best source" for information, because the front-end may have optimistic reportActions that the server is not yet aware of.
- * If reportActions are not loaded for the report, then there can't be any optimistic reportActions, and the lastMessageText rNVP will be accurate as a fallback.
- *
- * @param {Object} report
- * @returns {String}
- */
-function getLastMessageText(report) {
- if (!report) {
- return '';
- }
-
- const lastReportAction = lastReportActions[report.reportID];
- let lastReportActionText = report.lastMessageText;
- let lastReportActionHtml = report.lastMessageHtml;
- if (lastReportAction && lastReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT) {
- lastReportActionText = lodashGet(lastReportAction, 'message[0].text', report.lastMessageText);
- lastReportActionHtml = lodashGet(lastReportAction, 'message[0].html', report.lastMessageHtml);
- }
- return isReportMessageAttachment({text: lastReportActionText, html: lastReportActionHtml})
- ? `[${Localize.translateLocal('common.attachment')}]`
- : lastReportActionText;
-}
-
/**
* Trim the last message text to a fixed limit.
* @param {String} lastMessageText
@@ -1714,7 +1673,6 @@ export {
isIOUOwnedByCurrentUser,
getIOUTotal,
canShowReportRecipientLocalTime,
- getLastMessageText,
formatReportLastMessageText,
chatIncludesConcierge,
isPolicyExpenseChat,
diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js
index eaa6d3929c5d..3e6fd500c98e 100644
--- a/src/libs/SidebarUtils.js
+++ b/src/libs/SidebarUtils.js
@@ -5,7 +5,6 @@ import lodashOrderBy from 'lodash/orderBy';
import Str from 'expensify-common/lib/str';
import ONYXKEYS from '../ONYXKEYS';
import * as ReportUtils from './ReportUtils';
-import * as ReportActionsUtils from './ReportActionsUtils';
import * as Localize from './Localize';
import CONST from '../CONST';
import * as OptionsListUtils from './OptionsListUtils';
@@ -62,7 +61,7 @@ Onyx.connect({
return;
}
const reportID = CollectionUtils.extractCollectionItemID(key);
- lastReportActions[reportID] = _.first(ReportActionsUtils.getSortedReportActionsForDisplay(_.toArray(actions)));
+ lastReportActions[reportID] = _.last(_.toArray(actions));
reportActions[key] = actions;
},
});
@@ -244,7 +243,12 @@ function getOptionData(reportID) {
// We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade.
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips((participantPersonalDetailList || []).slice(0, 10), hasMultipleParticipants);
- const lastMessageTextFromReport = ReportUtils.getLastMessageText(report);
+ let lastMessageTextFromReport = '';
+ if (ReportUtils.isReportMessageAttachment({text: report.lastMessageText, html: report.lastMessageHtml})) {
+ lastMessageTextFromReport = `[${Localize.translateLocal('common.attachment')}]`;
+ } else {
+ lastMessageTextFromReport = Str.htmlDecode(report ? report.lastMessageText : '');
+ }
// If the last actor's details are not currently saved in Onyx Collection,
// then try to get that from the last report action.
@@ -259,7 +263,7 @@ function getOptionData(reportID) {
let lastMessageText = hasMultipleParticipants && lastActorDetails && (lastActorDetails.login !== currentUserLogin.email)
? `${lastActorDetails.displayName}: `
: '';
- lastMessageText += lastMessageTextFromReport;
+ lastMessageText += report ? lastMessageTextFromReport : '';
if (result.isPolicyExpenseChat && result.isArchivedRoom) {
const archiveReason = (lastReportActions[report.reportID] && lastReportActions[report.reportID].originalMessage && lastReportActions[report.reportID].originalMessage.reason)
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index d06e75de566f..7415a51aff48 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -126,7 +126,7 @@ class ReportActionItem extends Component {
// Newline characters need to be removed here because getCurrentSelection() returns html mixed with newlines, and when
//
tags are converted later to markdown, it creates duplicate newline characters. This means that when the content
// is pasted, there are extra newlines in the content that we want to avoid.
- const selection = SelectionScraper.getCurrentSelection().replace(/\n/g, '');
+ const selection = SelectionScraper.getCurrentSelection().replace(/
\n/g, '
');
ReportActionContextMenu.showContextMenu(
ContextMenuActions.CONTEXT_MENU_TYPES.REPORT_ACTION,
event,
diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js
index 17893cb94c85..2ffadfc7c26d 100644
--- a/src/styles/StyleUtils.js
+++ b/src/styles/StyleUtils.js
@@ -458,7 +458,7 @@ function getFontFamilyMonospace({fontStyle, fontWeight}) {
function getEmojiPickerStyle(isSmallScreenWidth) {
if (isSmallScreenWidth) {
return {
- width: '100%',
+ width: CONST.SMALL_EMOJI_PICKER_SIZE.WIDTH,
};
}
return {
@@ -811,6 +811,18 @@ function getReportWelcomeContainerStyle(isSmallScreenWidth) {
};
}
+/**
+ * Gets the correct height for emoji picker list based on screen dimensions
+ *
+ * @param {Boolean} hasAdditionalSpace
+ * @returns {Object}
+ */
+function getEmojiPickerListHeight(hasAdditionalSpace) {
+ return {
+ height: hasAdditionalSpace ? CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT + CONST.CATEGORY_SHORTCUT_BAR_HEIGHT : CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT,
+ };
+}
+
/**
* Gets styles for Emoji Suggestion row
*
@@ -967,6 +979,7 @@ export {
getReportWelcomeBackgroundImageStyle,
getReportWelcomeTopMarginStyle,
getReportWelcomeContainerStyle,
+ getEmojiPickerListHeight,
getEmojiSuggestionItemStyle,
getEmojiSuggestionContainerStyle,
getColoredBackgroundStyle,
diff --git a/src/styles/styles.js b/src/styles/styles.js
index d59ca9ae9a25..fafa5d1267d0 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -1523,12 +1523,25 @@ const styles = {
emojiPickerContainer: {
backgroundColor: themeColors.componentBG,
},
-
emojiPickerList: {
- height: 288,
+ height: CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT,
+ width: '100%',
+ ...spacing.ph4,
+ },
+ emojiPickerListWithPadding: {
+ height: CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT + CONST.CATEGORY_SHORTCUT_BAR_HEIGHT,
width: '100%',
...spacing.ph4,
},
+ emojiPickerSearchListContainer: {
+ position: 'absolute',
+ top: 60,
+ right: 0,
+ bottom: 4,
+ left: 0,
+ backgroundColor: themeColors.appBG,
+ },
+
emojiPickerListLandscape: {
height: 240,
},
diff --git a/tests/unit/ReportActionsUtilsTest.js b/tests/unit/ReportActionsUtilsTest.js
index ad473647dc60..20bf389969b7 100644
--- a/tests/unit/ReportActionsUtilsTest.js
+++ b/tests/unit/ReportActionsUtilsTest.js
@@ -6,15 +6,7 @@ describe('ReportActionsUtils', () => {
const cases = [
[
[
- // This is the lowest created timestamp, but because it's an optimistic action it should appear last
- {
- created: '2022-11-09 20:00:00.000',
- reportActionID: '395268342',
- actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
- pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
- },
-
- // This is the highest created timestamp, so should appear 2nd-to-last
+ // This is the highest created timestamp, so should appear last
{
created: '2022-11-09 22:27:01.825',
reportActionID: '8401445780099176',
@@ -69,12 +61,6 @@ describe('ReportActionsUtils', () => {
reportActionID: '8401445780099176',
actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
},
- {
- created: '2022-11-09 20:00:00.000',
- reportActionID: '395268342',
- actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
- pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
- },
],
],
[