diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml
index 608aa21204e1..5575d05609f2 100644
--- a/.github/workflows/platformDeploy.yml
+++ b/.github/workflows/platformDeploy.yml
@@ -209,13 +209,12 @@ jobs:
max_attempts: 5
command: npm ci
- - uses: actions/cache@v2
- with:
- path: ios/Pods
- key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
-
- name: Install cocoapods
- run: cd ios && pod install
+ uses: nick-invision/retry@7c68161adf97a48beb850a595b8784ec57a98cbb
+ with:
+ timeout_minutes: 10
+ max_attempts: 5
+ command: cd ios && pod install
- name: Decrypt profile
run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output chat_expensify_appstore.mobileprovision chat_expensify_appstore.mobileprovision.gpg
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 6536d77a4f38..1b16a06056ea 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -152,8 +152,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001016103
- versionName "1.1.61-3"
+ versionCode 1001016400
+ versionName "1.1.64-0"
}
splits {
abi {
diff --git a/assets/images/avatars/fallback-avatar.svg b/assets/images/avatars/fallback-avatar.svg
new file mode 100644
index 000000000000..dc1a1497cfe5
--- /dev/null
+++ b/assets/images/avatars/fallback-avatar.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/assets/images/avatars/fallback-workspace-avatar.svg b/assets/images/avatars/fallback-workspace-avatar.svg
new file mode 100644
index 000000000000..ac2f58122a0f
--- /dev/null
+++ b/assets/images/avatars/fallback-workspace-avatar.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 11022248ac59..d96b4d1a572d 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.1.61
+ 1.1.64
CFBundleSignature
????
CFBundleURLTypes
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.1.61.3
+ 1.1.64.0
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index aeca405ac99a..cad8e15bb84d 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.1.61
+ 1.1.64
CFBundleSignature
????
CFBundleVersion
- 1.1.61.3
+ 1.1.64.0
diff --git a/package-lock.json b/package-lock.json
index f05b89eb37fb..5abbb23cd0a5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.61-3",
+ "version": "1.1.64-0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index e3974a4f014f..236bb9790632 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.61-3",
+ "version": "1.1.64-0",
"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/Avatar.js b/src/components/Avatar.js
index 71ecd130ed9d..ff51d2c427b8 100644
--- a/src/components/Avatar.js
+++ b/src/components/Avatar.js
@@ -7,6 +7,7 @@ import Icon from './Icon';
import themeColors from '../styles/themes/default';
import CONST from '../CONST';
import * as StyleUtils from '../styles/StyleUtils';
+import * as Expensicons from './Icon/Expensicons';
const propTypes = {
/** Source for the avatar. Can be a URL or an icon. */
@@ -23,6 +24,9 @@ const propTypes = {
/** The fill color for the icon. Can be hex, rgb, rgba, or valid react-native named color such as 'red' or 'blue' */
fill: PropTypes.string,
+
+ /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
+ fallbackIcon: PropTypes.func,
};
const defaultProps = {
@@ -31,9 +35,17 @@ const defaultProps = {
containerStyles: [],
size: CONST.AVATAR_SIZE.DEFAULT,
fill: themeColors.icon,
+ fallbackIcon: Expensicons.FallbackAvatar,
};
class Avatar extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ imageError: false,
+ };
+ }
+
render() {
if (!this.props.source) {
return null;
@@ -45,13 +57,21 @@ class Avatar extends PureComponent {
];
const iconSize = StyleUtils.getAvatarSize(this.props.size);
+
return (
- {
- _.isFunction(this.props.source)
- ?
- :
- }
+ {_.isFunction(this.props.source) || this.state.imageError
+ ? (
+
+ )
+ : (
+ this.setState({imageError: true})} />
+ )}
);
}
diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js
index 02644c75fec2..2e0d4f3c8792 100644
--- a/src/components/AvatarWithImagePicker.js
+++ b/src/components/AvatarWithImagePicker.js
@@ -53,6 +53,9 @@ const propTypes = {
/** Size of Indicator */
size: PropTypes.oneOf([CONST.AVATAR_SIZE.LARGE, CONST.AVATAR_SIZE.DEFAULT]),
+ /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
+ fallbackIcon: PropTypes.func,
+
...withLocalizePropTypes,
};
@@ -65,6 +68,7 @@ const defaultProps = {
isUsingDefaultAvatar: false,
isUploading: false,
size: CONST.AVATAR_SIZE.DEFAULT,
+ fallbackIcon: Expensicons.FallbackAvatar,
};
class AvatarWithImagePicker extends React.Component {
@@ -181,6 +185,8 @@ class AvatarWithImagePicker extends React.Component {
containerStyles={styles.avatarLarge}
imageStyles={[styles.avatarLarge, styles.alignSelfCenter]}
source={this.props.avatarURL}
+ fallbackIcon={this.props.fallbackIcon}
+ size={this.props.size}
/>
)
: (
diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js
index 0db3ff1253ef..aa37769156e3 100644
--- a/src/components/AvatarWithIndicator.js
+++ b/src/components/AvatarWithIndicator.js
@@ -101,6 +101,7 @@ class AvatarWithIndicator extends PureComponent {
diff --git a/src/components/Button.js b/src/components/Button.js
index 85254d4c1e75..a315abc160fc 100644
--- a/src/components/Button.js
+++ b/src/components/Button.js
@@ -247,7 +247,7 @@ class Button extends Component {
onPressOut={this.props.onPressOut}
disabled={this.props.isLoading || this.props.isDisabled}
style={[
- this.props.isDisabled ? styles.cursorDisabled : {},
+ this.props.isDisabled ? {...styles.cursorDisabled, ...styles.noSelect} : {},
...this.additionalStyles,
]}
>
diff --git a/src/components/ConfirmContent.js b/src/components/ConfirmContent.js
index 78e01b756a61..164a21ce9a8e 100644
--- a/src/components/ConfirmContent.js
+++ b/src/components/ConfirmContent.js
@@ -76,7 +76,7 @@ const ConfirmContent = props => (
/>
{props.shouldShowCancelButton && (
diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js
index a7e4c910f938..874a7364d084 100755
--- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js
+++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js
@@ -53,7 +53,6 @@ const BaseHTMLEngineProvider = (props) => {
baseStyle={styles.webViewStyles.baseFontStyle}
tagsStyles={styles.webViewStyles.tagStyles}
enableCSSInlineProcessing={false}
- dangerouslyDisableWhitespaceCollapsing
systemFonts={_.values(fontFamily)}
>
node.offsetWidth;
- if ((event.currentTarget === node) && horizontalOverflow) {
+ // Account for vertical scrolling variation when horizontally scrolling via touchpad by checking a large delta.
+ const isVerticalScrolling = Math.abs(event.deltaY) > 3; // This is for touchpads sensitive
+ if ((event.currentTarget === node) && horizontalOverflow && !isVerticalScrolling) {
node.scrollLeft += event.deltaX;
event.preventDefault();
event.stopPropagation();
diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js
index 84de26b310f1..7468e2138ca4 100755
--- a/src/components/IOUConfirmationList.js
+++ b/src/components/IOUConfirmationList.js
@@ -62,8 +62,8 @@ const propTypes = {
phoneNumber: PropTypes.string,
})).isRequired,
- /** Whether this is an IOU split and belongs to a group report */
- isGroupSplit: PropTypes.bool.isRequired,
+ /** Is this IOU associated with existing report */
+ isIOUAttachedToExistingChatReport: PropTypes.bool.isRequired,
...windowDimensionsPropTypes,
@@ -225,6 +225,7 @@ class IOUConfirmationList extends Component {
data: [formattedMyPersonalDetails],
shouldShow: true,
indexOffset: 0,
+ isDisabled: true,
}, {
title: this.props.translate('iOUConfirmationList.whoWasThere'),
data: formattedSelectedParticipants,
@@ -350,6 +351,7 @@ class IOUConfirmationList extends Component {
const shouldDisableButton = selectedParticipants.length === 0 || this.props.network.isOffline;
const isLoading = this.props.iou.loading && !this.props.network.isOffline;
const recipient = this.state.participants[0];
+ const canModifyParticipants = this.props.isIOUAttachedToExistingChatReport && this.props.hasMultipleParticipants;
return (
<>
@@ -362,7 +364,7 @@ class IOUConfirmationList extends Component {
canSelectMultipleOptions={this.props.hasMultipleParticipants}
selectedOptions={this.getSelectedOptions()}
onSelectRow={toggleOption}
- isDisabled={!this.props.isGroupSplit}
+ isDisabled={!canModifyParticipants}
optionHoveredStyle={hoverStyle}
/>
diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js
index 90c47808b188..617bd2dcb7b5 100644
--- a/src/components/Icon/Expensicons.js
+++ b/src/components/Icon/Expensicons.js
@@ -76,6 +76,8 @@ import AdminRoomAvatar from '../../../assets/images/avatars/admin-room.svg';
import AnnounceRoomAvatar from '../../../assets/images/avatars/announce-room.svg';
import Connect from '../../../assets/images/connect.svg';
import DomainRoomAvatar from '../../../assets/images/avatars/domain-room.svg';
+import FallbackAvatar from '../../../assets/images/avatars/fallback-avatar.svg';
+import FallbackWorkspaceAvatar from '../../../assets/images/avatars/fallback-workspace-avatar.svg';
export {
ActiveRoomAvatar,
@@ -112,6 +114,8 @@ export {
Eye,
EyeDisabled,
ExpensifyCard,
+ FallbackAvatar,
+ FallbackWorkspaceAvatar,
Gallery,
Gear,
Hashtag,
diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js
index 2017962b5e83..45cfe8c6c35a 100644
--- a/src/components/ImageView/index.js
+++ b/src/components/ImageView/index.js
@@ -23,7 +23,6 @@ class ImageView extends PureComponent {
this.onContainerLayoutChanged = this.onContainerLayoutChanged.bind(this);
this.onContainerPressIn = this.onContainerPressIn.bind(this);
this.onContainerPress = this.onContainerPress.bind(this);
- this.onContainerPressOut = this.onContainerPressOut.bind(this);
this.imageLoadingStart = this.imageLoadingStart.bind(this);
this.imageLoadingEnd = this.imageLoadingEnd.bind(this);
this.state = {
@@ -40,10 +39,6 @@ class ImageView extends PureComponent {
imgWidth: 0,
imgHeight: 0,
zoomScale: 0,
- imageLeft: 0,
- imageTop: 0,
- imageRight: 0,
- imageBottom: 0,
};
}
@@ -98,85 +93,51 @@ class ImageView extends PureComponent {
* @param {SyntheticEvent} e
*/
onContainerPress(e) {
- if (this.state.isZoomed && !this.state.isDragging) {
+ let scrollX;
+ let scrollY;
+ if (!this.state.isZoomed && !this.state.isDragging) {
const {offsetX, offsetY} = e.nativeEvent;
- const delta = this.getScrollOffset(offsetX, offsetY);
- const sX = delta.offsetX;
- const sY = delta.offsetY;
- this.scrollableRef.scrollTop = sY * this.state.zoomScale;
- this.scrollableRef.scrollLeft = sX * this.state.zoomScale;
+
+ // Dividing clicked positions by the zoom scale to get coordinates
+ // so that once we zoom we will scroll to the clicked location.
+ const delta = this.getScrollOffset(offsetX / this.state.zoomScale, offsetY / this.state.zoomScale);
+ scrollX = delta.offsetX;
+ scrollY = delta.offsetY;
}
if (this.state.isZoomed && this.state.isDragging && this.state.isMouseDown) {
this.setState({isDragging: false, isMouseDown: false});
+ } else {
+ // We first zoom and once its done then we scroll to the location the user clicked.
+ this.setState(prevState => ({
+ isZoomed: !prevState.isZoomed,
+ isMouseDown: false,
+ }), () => {
+ this.scrollableRef.scrollTop = scrollY;
+ this.scrollableRef.scrollLeft = scrollX;
+ });
}
}
- onContainerPressOut() {
- if (this.state.isDragging) {
- return;
- }
-
- this.setState(prevState => ({
- isZoomed: !prevState.isZoomed,
- isMouseDown: false,
- }));
- }
-
/**
- * When open image, set image left/right/top/bottom point and width, height
+ * When open image, set image width, height and zoomScale.
* @param {Number} imageWidth
* @param {Number} imageHeight
*/
setImageRegion(imageWidth, imageHeight) {
- let width = imageWidth;
- let height = imageHeight;
- const containerHeight = this.state.containerHeight;
- const containerWidth = this.state.containerWidth;
-
- // return if image not loaded yet
- if (imageHeight <= 0 || containerHeight <= 0) {
+ if (imageHeight <= 0) {
return;
}
-
- // Fit the image to container size if image small than container.
- const aspectRatio = Math.min(containerHeight / imageHeight, containerWidth / imageWidth);
- if (aspectRatio > 1) {
- width *= (aspectRatio);
- height *= (aspectRatio);
- }
- let imgLeft = (this.props.windowWidth - width) / 2;
- let imgRight = ((this.props.windowWidth - width) / 2) + width;
- let imgTop = (this.props.windowHeight - height) / 2;
- let imgBottom = ((this.props.windowHeight - height) / 2) + height;
- const isScreenWiderThanImage = (this.props.windowWidth / width) > 1;
- const isScreenTallerThanImage = (this.props.windowHeight / height) > 1;
- const aspect = width / height;
- if (aspect > 1 && !isScreenWiderThanImage) {
- // In case Width fit Screen width and Height not fit the Screen height
- const fitRate = this.props.windowWidth / width;
- imgLeft = 0;
- imgRight = this.props.windowWidth;
- imgTop = (this.props.windowHeight - (fitRate * height)) / 2;
- imgBottom = imgTop + (fitRate * height);
- } else if (aspect <= 1 && !isScreenTallerThanImage) {
- // In case Height fit Screen height and Width not fit the Screen width
- const fitRate = this.props.windowHeight / height;
- imgTop = 0;
- imgBottom = this.props.windowHeight;
- imgLeft = (this.props.windowWidth - (fitRate * width)) / 2;
- imgRight = imgLeft + (fitRate * width);
- }
-
+ const containerHeight = this.state.containerHeight;
+ const containerWidth = this.state.containerWidth;
+ const width = imageWidth;
+ const height = imageHeight;
const newZoomScale = Math.min(containerWidth / width, containerHeight / height);
+
this.setState({
imgWidth: width,
- zoomScale: newZoomScale,
imgHeight: height,
- imageLeft: imgLeft,
- imageTop: imgTop,
- imageRight: imgRight,
- imageBottom: imgBottom,
+ zoomScale: newZoomScale,
});
}
@@ -187,31 +148,23 @@ class ImageView extends PureComponent {
* @returns {Object} converted touch point
*/
getScrollOffset(x, y) {
- let fitRatio = 1;
- if (this.state.imageTop === 0) {
- // Fit Height
- fitRatio = this.props.windowHeight / this.state.imgHeight;
- } else if (this.state.imageLeft === 0) {
- // Fit Width
- fitRatio = this.props.windowWidth / this.state.imgWidth;
- }
- let sx = (x - this.state.imageLeft) / fitRatio;
- let sy = (y - this.state.imageTop) / fitRatio;
+ let offsetX;
+ let offsetY;
- // White blank touch
- if (x < this.state.imageLeft) {
- sx = 0;
- }
- if (x > this.state.imageRight) {
- sx = this.state.imgWidth;
- }
- if (y < this.state.imageTop) {
- sy = 0;
+ // Container size bigger than clicked position offset
+ if (x <= this.state.containerWidth / 2) {
+ offsetX = 0;
+ } else if (x > this.state.containerWidth / 2) {
+ // Minus half of container size because we want to be center clicked position
+ offsetX = x - (this.state.containerWidth / 2);
}
- if (y > this.state.imageBottom) {
- sy = this.state.imgHeight;
+ if (y <= this.state.containerHeight / 2) {
+ offsetY = 0;
+ } else if (y > this.state.containerHeight / 2) {
+ // Minus half of container size because we want to be center clicked position
+ offsetY = y - (this.state.containerHeight / 2);
}
- return {offsetX: sx, offsetY: sy};
+ return {offsetX, offsetY};
}
trackMovement(e) {
@@ -284,7 +237,6 @@ class ImageView extends PureComponent {
}}
onPressIn={this.onContainerPressIn}
onPress={this.onContainerPress}
- onPressOut={this.onContainerPressOut}
>
{
+ const layout = event.nativeEvent.layout;
+ this.setState({
+ containerHeight: layout.height,
+ });
+ }}
>
{},
interactive: true,
+ fallbackIcon: Expensicons.FallbackAvatar,
};
const MenuItem = props => (
@@ -87,6 +88,7 @@ const MenuItem = props => (
)}
diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js
index d6f0a7cbc424..3779304830f0 100644
--- a/src/components/Modal/BaseModal.js
+++ b/src/components/Modal/BaseModal.js
@@ -7,8 +7,8 @@ import styles from '../../styles/styles';
import * as StyleUtils from '../../styles/StyleUtils';
import themeColors from '../../styles/themes/default';
import {propTypes as modalPropTypes, defaultProps as modalDefaultProps} from './modalPropTypes';
-import getModalStyles from '../../styles/getModalStyles';
import * as Modal from '../../libs/actions/Modal';
+import getModalStyles from '../../styles/getModalStyles';
const propTypes = {
...modalPropTypes,
diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js
index f6049a886c5a..1b66eec802dd 100644
--- a/src/components/OptionsList/BaseOptionsList.js
+++ b/src/components/OptionsList/BaseOptionsList.js
@@ -111,7 +111,7 @@ class BaseOptionsList extends Component {
showSelectedState={this.props.canSelectMultipleOptions}
hideAdditionalOptionStates={this.props.hideAdditionalOptionStates}
forceTextUnreadStyle={this.props.forceTextUnreadStyle}
- isDisabled={this.props.isDisabled}
+ isDisabled={this.props.isDisabled || section.isDisabled}
/>
);
}
diff --git a/src/components/OptionsSelector.js b/src/components/OptionsSelector.js
index 48c34b1fcb15..81a83cbdf19e 100755
--- a/src/components/OptionsSelector.js
+++ b/src/components/OptionsSelector.js
@@ -30,6 +30,9 @@ const propTypes = {
/** Whether this section should show or not */
shouldShow: PropTypes.bool,
+
+ /** Whether this section items disabled for selection */
+ isDisabled: PropTypes.bool,
})).isRequired,
/** Value in the search input field */
diff --git a/src/components/ReportWelcomeText.js b/src/components/ReportWelcomeText.js
index 25fc18318c27..528397ade9c6 100644
--- a/src/components/ReportWelcomeText.js
+++ b/src/components/ReportWelcomeText.js
@@ -10,6 +10,9 @@ import compose from '../libs/compose';
import * as ReportUtils from '../libs/ReportUtils';
import * as OptionsListUtils from '../libs/OptionsListUtils';
import ONYXKEYS from '../ONYXKEYS';
+import Navigation from '../libs/Navigation/Navigation';
+import ROUTES from '../ROUTES';
+import Tooltip from './Tooltip';
const personalDetailsPropTypes = PropTypes.shape({
/** The login of the person (either email or phone number) */
@@ -85,7 +88,7 @@ const ReportWelcomeText = (props) => {
{roomWelcomeMessage.phrase1}
-
+ Navigation.navigate(ROUTES.getReportDetailsRoute(props.report.reportID))}>
{props.report.reportName}
@@ -99,11 +102,15 @@ const ReportWelcomeText = (props) => {
{props.translate('reportActionsView.beginningOfChatHistory')}
- {_.map(displayNamesWithTooltips, ({displayName, pronouns}, index) => (
+ {_.map(displayNamesWithTooltips, ({
+ displayName, pronouns, tooltip,
+ }, index) => (
-
- {displayName}
-
+
+ Navigation.navigate(ROUTES.getDetailsRoute(participants[index]))}>
+ {displayName}
+
+
{!_.isEmpty(pronouns) && {` (${pronouns})`}}
{(index === displayNamesWithTooltips.length - 1) && .}
{(index === displayNamesWithTooltips.length - 2) && {` ${props.translate('common.and')} `}}
diff --git a/src/components/Tooltip/index.native.js b/src/components/Tooltip/index.native.js
index ea7556928c77..89fb88bd8a39 100644
--- a/src/components/Tooltip/index.native.js
+++ b/src/components/Tooltip/index.native.js
@@ -1,32 +1,21 @@
-import React from 'react';
-import {View} from 'react-native';
import PropTypes from 'prop-types';
// We can't use the common component for the Tooltip as Web implementation uses DOM specific method to
// render the View which is not present on the Mobile.
const propTypes = {
- /** Styles to be assigned to the Tooltip wrapper views */
- containerStyles: PropTypes.arrayOf(PropTypes.object),
-
/** Children to wrap with Tooltip. */
children: PropTypes.node.isRequired,
};
-const defaultProps = {
- containerStyles: [],
-};
-
/**
* @param {propTypes} props
* @returns {ReactNodeLike}
*/
const Tooltip = props => (
-
- {props.children}
-
+ props.children
);
Tooltip.propTypes = propTypes;
-Tooltip.defaultProps = defaultProps;
Tooltip.displayName = 'Tooltip';
+
export default Tooltip;
diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js
index 0ad5a43498af..0dd6b954aa4f 100644
--- a/src/components/menuItemPropTypes.js
+++ b/src/components/menuItemPropTypes.js
@@ -63,6 +63,9 @@ const propTypes = {
/** Whether the menu item should be interactive at all */
interactive: PropTypes.bool,
+
+ /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
+ fallbackIcon: PropTypes.func,
};
export default propTypes;
diff --git a/src/libs/ActiveClientManager/index.js b/src/libs/ActiveClientManager/index.js
index 661cc38fb777..7f0d4bf0cd91 100644
--- a/src/libs/ActiveClientManager/index.js
+++ b/src/libs/ActiveClientManager/index.js
@@ -3,22 +3,22 @@ import Onyx from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
import ONYXKEYS from '../../ONYXKEYS';
import * as ActiveClients from '../actions/ActiveClients';
-import createOnReadyTask from '../createOnReadyTask';
const clientID = Str.guid();
const maxClients = 20;
let activeClients;
-// Keeps track of the ActiveClientManager's readiness in one place
-// so that multiple calls of isReady resolve the same promise
-const activeClientsReadyTask = createOnReadyTask();
+let resolveIsReadyPromise;
+const isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+});
/**
* @returns {Promise}
*/
function isReady() {
- return activeClientsReadyTask.isReady();
+ return isReadyPromise;
}
Onyx.connect({
@@ -37,7 +37,7 @@ Onyx.connect({
*/
function init() {
ActiveClients.addClient(clientID)
- .then(activeClientsReadyTask.setIsReady);
+ .then(resolveIsReadyPromise);
}
/**
diff --git a/src/libs/Growl.js b/src/libs/Growl.js
index 325a4ecd213c..3da2b7543532 100644
--- a/src/libs/Growl.js
+++ b/src/libs/Growl.js
@@ -1,12 +1,14 @@
import React from 'react';
import CONST from '../CONST';
-import createOnReadyTask from './createOnReadyTask';
const growlRef = React.createRef();
-const growlReadyTask = createOnReadyTask();
+let resolveIsReadyPromise;
+const isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+});
function setIsReady() {
- growlReadyTask.setIsReady();
+ resolveIsReadyPromise();
}
/**
@@ -17,7 +19,7 @@ function setIsReady() {
* @param {Number} [duration]
*/
function show(bodyText, type, duration = CONST.GROWL.DURATION) {
- growlReadyTask.isReady().then(() => growlRef.current.show(bodyText, type, duration));
+ isReadyPromise.then(() => growlRef.current.show(bodyText, type, duration));
}
/**
diff --git a/src/libs/Network/NetworkStore.js b/src/libs/Network/NetworkStore.js
index 81fd59e4c8a8..8420d27f6667 100644
--- a/src/libs/Network/NetworkStore.js
+++ b/src/libs/Network/NetworkStore.js
@@ -3,7 +3,6 @@ import Onyx from 'react-native-onyx';
import _ from 'underscore';
import ONYXKEYS from '../../ONYXKEYS';
import createCallback from '../createCallback';
-import createOnReadyTask from '../createOnReadyTask';
let credentials;
let authToken;
@@ -12,7 +11,11 @@ let offline = false;
let authenticating = false;
const [triggerConnectivityResumed, onConnectivityResumed] = createCallback();
-const requiredDataReadyTask = createOnReadyTask();
+
+let resolveIsReadyPromise;
+let isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+});
/**
* This is a hack to workaround the fact that Onyx may not yet have read these values from storage by the time Network starts processing requests.
@@ -23,11 +26,14 @@ function checkRequiredData() {
return;
}
- requiredDataReadyTask.setIsReady();
+ resolveIsReadyPromise();
}
function resetHasReadRequiredDataFromStorage() {
- requiredDataReadyTask.reset();
+ // Create a new promise and a new resolve function
+ isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+ });
}
Onyx.connect({
@@ -104,7 +110,7 @@ function getCurrentUserEmail() {
* @returns {Promise}
*/
function hasReadRequiredDataFromStorage() {
- return requiredDataReadyTask.isReady();
+ return isReadyPromise;
}
/**
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index 04692d69780f..144962be04ca 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -8,6 +8,8 @@ import * as Localize from './Localize';
import * as LocalePhoneNumber from './LocalePhoneNumber';
import * as Expensicons from '../components/Icon/Expensicons';
import md5 from './md5';
+import Navigation from './Navigation/Navigation';
+import ROUTES from '../ROUTES';
let sessionEmail;
Onyx.connect({
@@ -481,6 +483,25 @@ function getReportName(report, personalDetailsForParticipants = {}, policies = {
return _.map(displayNamesWithTooltips, ({displayName}) => displayName).join(', ');
}
+/**
+ * Navigate to the details page of a given report
+ *
+ * @param {Object} report
+ */
+function navigateToDetailsPage(report) {
+ const participants = lodashGet(report, 'participants', []);
+
+ if (isChatRoom(report) || isPolicyExpenseChat(report)) {
+ Navigation.navigate(ROUTES.getReportDetailsRoute(report.reportID));
+ return;
+ }
+ if (participants.length === 1) {
+ Navigation.navigate(ROUTES.getDetailsRoute(participants[0]));
+ return;
+ }
+ Navigation.navigate(ROUTES.getReportParticipantsRoute(report.reportID));
+}
+
export {
getReportParticipantsTitle,
isReportMessageAttachment,
@@ -507,4 +528,5 @@ export {
getRoomWelcomeMessage,
getDisplayNamesWithTooltips,
getReportName,
+ navigateToDetailsPage,
};
diff --git a/src/libs/fileDownload/getAttachmentDetails.js b/src/libs/fileDownload/getAttachmentDetails.js
index 8b8e777ab911..8a465403f8a8 100644
--- a/src/libs/fileDownload/getAttachmentDetails.js
+++ b/src/libs/fileDownload/getAttachmentDetails.js
@@ -6,11 +6,12 @@ import Config from '../../CONFIG';
* @param {String} html
* @returns {Object}
*/
-const PREVIEW_SOURCE_REGEX = new RegExp(`${CONST.ATTACHMENT_PREVIEW_ATTRIBUTE}*=*"(.+?)"`, 'i');
-const SOURCE_REGEX = new RegExp(`${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}*=*"(.+?)"`, 'i');
-const ORIGINAL_FILENAME_REGEX = new RegExp(`${CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE}*=*"(.+?)"`, 'i');
-
export default function getAttachmentName(html) {
+ // Files can be rendered either as anchor tag or as an image so based on that we have to form regex.
+ const IS_IMAGE_TAG = //i.test(html);
+ const PREVIEW_SOURCE_REGEX = new RegExp(`${CONST.ATTACHMENT_PREVIEW_ATTRIBUTE}*=*"(.+?)"`, 'i');
+ const SOURCE_REGEX = new RegExp(`${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}*=*"(.+?)"`, 'i');
+ const ORIGINAL_FILENAME_REGEX = IS_IMAGE_TAG ? new RegExp(`${CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE}*=*"(.+?)"`, 'i') : new RegExp(']*>([^<]+)', 'i');
if (!html) {
return {
previewSourceURL: null,
@@ -18,11 +19,11 @@ export default function getAttachmentName(html) {
originalFileName: null,
};
}
- const previewSourceURL = html.match(PREVIEW_SOURCE_REGEX)[1].replace(
+ const sourceURL = html.match(SOURCE_REGEX)[1].replace(
Config.EXPENSIFY.EXPENSIFY_URL,
Config.EXPENSIFY.URL_API_ROOT,
);
- const sourceURL = html.match(SOURCE_REGEX)[1].replace(
+ const previewSourceURL = (IS_IMAGE_TAG ? html.match(PREVIEW_SOURCE_REGEX)[1] : sourceURL).replace(
Config.EXPENSIFY.EXPENSIFY_URL,
Config.EXPENSIFY.URL_API_ROOT,
);
diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js
index c0b3eb0827b9..b46e6dd68dfc 100755
--- a/src/pages/DetailsPage.js
+++ b/src/pages/DetailsPage.js
@@ -114,6 +114,7 @@ const DetailsPage = (props) => {
containerStyles={[styles.avatarLarge, styles.mb3]}
imageStyles={[styles.avatarLarge]}
source={details.avatar}
+ size={CONST.AVATAR_SIZE.LARGE}
/>
)}
diff --git a/src/pages/ReimbursementAccount/EnableStep.js b/src/pages/ReimbursementAccount/EnableStep.js
index a96e495cf8d4..7c0b58bf0157 100644
--- a/src/pages/ReimbursementAccount/EnableStep.js
+++ b/src/pages/ReimbursementAccount/EnableStep.js
@@ -24,11 +24,16 @@ import * as Illustrations from '../../components/Icon/Illustrations';
import * as BankAccounts from '../../libs/actions/BankAccounts';
import * as Link from '../../libs/actions/Link';
import * as User from '../../libs/actions/User';
+import {withNetwork} from '../../components/OnyxProvider';
+import networkPropTypes from '../../components/networkPropTypes';
const propTypes = {
/** Are we loading payment methods? */
isLoadingPaymentMethods: PropTypes.bool,
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
/** List of bank accounts */
bankAccountList: PropTypes.objectOf(bankAccountPropTypes),
@@ -42,6 +47,18 @@ const defaultProps = {
class EnableStep extends React.Component {
componentDidMount() {
+ this.fetchData();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (!prevProps.network.isOffline || this.props.network.isOffline) {
+ return;
+ }
+
+ this.fetchData();
+ }
+
+ fetchData() {
PaymentMethods.getPaymentMethods();
}
@@ -133,6 +150,7 @@ EnableStep.defaultProps = defaultProps;
export default compose(
withLocalize,
+ withNetwork(),
withOnyx({
isLoadingPaymentMethods: {
key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS,
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index a8f6209d1190..88a88b7d10b2 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -14,8 +14,6 @@ import * as Report from '../../libs/actions/Report';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions';
import MultipleAvatars from '../../components/MultipleAvatars';
import SubscriptAvatar from '../../components/SubscriptAvatar';
-import Navigation from '../../libs/Navigation/Navigation';
-import ROUTES from '../../ROUTES';
import DisplayNames from '../../components/DisplayNames';
import * as OptionsListUtils from '../../libs/OptionsListUtils';
import participantPropTypes from '../../components/participantPropTypes';
@@ -28,9 +26,6 @@ import Text from '../../components/Text';
import Tooltip from '../../components/Tooltip';
const propTypes = {
- /** The ID of the report */
- reportID: PropTypes.number.isRequired,
-
/** Toggles the navigationMenu open and closed */
onNavigationMenuButtonClicked: PropTypes.func.isRequired,
@@ -113,15 +108,7 @@ const HeaderView = (props) => {
]}
>
{
- if (isChatRoom || isPolicyExpenseChat) {
- return Navigation.navigate(ROUTES.getReportDetailsRoute(props.reportID));
- }
- if (participants.length === 1) {
- return Navigation.navigate(ROUTES.getDetailsRoute(participants[0]));
- }
- Navigation.navigate(ROUTES.getReportParticipantsRoute(props.reportID));
- }}
+ onPress={() => ReportUtils.navigateToDetailsPage(props.report)}
style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]}
>
{shouldShowSubscript ? (
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index a3f2c17ab409..3206d9e69a35 100755
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -589,7 +589,7 @@ class ReportActionCompose extends React.Component {
-
+
{this.props.network.isOffline ? (
{
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.report);
const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policies);
+
return (
{
]}
>
-
+ ReportUtils.navigateToDetailsPage(props.report)}>
+
+
diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js
index c0d8cf70449f..e9500756bd6c 100755
--- a/src/pages/iou/IOUModal.js
+++ b/src/pages/iou/IOUModal.js
@@ -26,6 +26,8 @@ import CONST from '../../CONST';
import KeyboardAvoidingView from '../../components/KeyboardAvoidingView';
import * as PersonalDetails from '../../libs/actions/PersonalDetails';
import ROUTES from '../../ROUTES';
+import networkPropTypes from '../../components/networkPropTypes';
+import {withNetwork} from '../../components/OnyxProvider';
/**
* IOU modal for requesting money and splitting bills.
@@ -50,6 +52,9 @@ const propTypes = {
localCurrencyCode: PropTypes.string,
}),
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
// Holds data related to IOU view state, rather than the underlying IOU data.
iou: PropTypes.shape({
/** Whether or not transaction creation has started */
@@ -138,11 +143,15 @@ class IOUModal extends Component {
}
componentDidMount() {
- PersonalDetails.fetchLocalCurrency();
+ this.fetchData();
IOU.setIOUSelectedCurrency(this.props.myPersonalDetails.localCurrencyCode);
}
componentDidUpdate(prevProps) {
+ if (prevProps.network.isOffline && !this.props.network.isOffline) {
+ this.fetchData();
+ }
+
// Successfully close the modal if transaction creation has ended and there is no error
if (prevProps.iou.creatingIOUTransaction && !this.props.iou.creatingIOUTransaction && !this.props.iou.error) {
Navigation.dismissModal();
@@ -215,6 +224,10 @@ class IOUModal extends Component {
return this.props.translate(this.steps[currentStepIndex]) || '';
}
+ fetchData() {
+ PersonalDetails.fetchLocalCurrency();
+ }
+
/**
* Update comment whenever user enters any new text
*
@@ -434,7 +447,7 @@ class IOUModal extends Component {
comment={this.state.comment}
onUpdateComment={this.updateComment}
iouType={this.props.iouType}
- isGroupSplit={this.steps.length === 2}
+ isIOUAttachedToExistingChatReport={!_.isEmpty(reportID)}
/>
)}
@@ -453,6 +466,7 @@ IOUModal.defaultProps = defaultProps;
export default compose(
withLocalize,
+ withNetwork(),
withOnyx({
report: {
key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`,
diff --git a/src/pages/iou/steps/IOUConfirmPage.js b/src/pages/iou/steps/IOUConfirmPage.js
index 6d59ed01c7ce..5352ccfdcf83 100644
--- a/src/pages/iou/steps/IOUConfirmPage.js
+++ b/src/pages/iou/steps/IOUConfirmPage.js
@@ -42,8 +42,8 @@ const propTypes = {
/** IOU type */
iouType: PropTypes.string,
- /** Whether this is an IOU split and belongs to a group report */
- isGroupSplit: PropTypes.bool.isRequired,
+ /** Is this IOU associated with existing report */
+ isIOUAttachedToExistingChatReport: PropTypes.bool.isRequired,
};
const defaultProps = {
@@ -62,7 +62,7 @@ const IOUConfirmPage = props => (
onConfirm={props.onConfirm}
onSendMoney={props.onSendMoney}
iouType={props.iouType}
- isGroupSplit={props.isGroupSplit}
+ isIOUAttachedToExistingChatReport={props.isIOUAttachedToExistingChatReport}
/>
);
diff --git a/src/pages/settings/AppDownloadLinks.js b/src/pages/settings/AppDownloadLinks.js
index d395d375f304..a724fe0ddf74 100644
--- a/src/pages/settings/AppDownloadLinks.js
+++ b/src/pages/settings/AppDownloadLinks.js
@@ -7,15 +7,25 @@ import CONST from '../../CONST';
import * as Expensicons from '../../components/Icon/Expensicons';
import ScreenWrapper from '../../components/ScreenWrapper';
import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+import compose from '../../libs/compose';
import MenuItem from '../../components/MenuItem';
import styles from '../../styles/styles';
import * as Link from '../../libs/actions/Link';
+import PressableWithSecondaryInteraction from '../../components/PressableWithSecondaryInteraction';
+import ControlSelection from '../../libs/ControlSelection';
+import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions';
+import canUseTouchScreen from '../../libs/canUseTouchscreen';
+import * as ReportActionContextMenu from '../home/report/ContextMenu/ReportActionContextMenu';
+import * as ContextMenuActions from '../home/report/ContextMenu/ContextMenuActions';
const propTypes = {
...withLocalizePropTypes,
+ ...windowDimensionsPropTypes,
};
const AppDownloadLinksPage = (props) => {
+ let popoverAnchor;
+
const menuItems = [
{
translationKey: 'initialSettingsPage.appDownloadLinks.android.label',
@@ -24,6 +34,7 @@ const AppDownloadLinksPage = (props) => {
action: () => {
Link.openExternalLink(CONST.APP_DOWNLOAD_LINKS.ANDROID);
},
+ link: CONST.APP_DOWNLOAD_LINKS.ANDROID,
},
{
translationKey: 'initialSettingsPage.appDownloadLinks.ios.label',
@@ -32,6 +43,7 @@ const AppDownloadLinksPage = (props) => {
action: () => {
Link.openExternalLink(CONST.APP_DOWNLOAD_LINKS.IOS);
},
+ link: CONST.APP_DOWNLOAD_LINKS.IOS,
},
{
translationKey: 'initialSettingsPage.appDownloadLinks.desktop.label',
@@ -40,9 +52,25 @@ const AppDownloadLinksPage = (props) => {
action: () => {
Link.openExternalLink(CONST.APP_DOWNLOAD_LINKS.DESKTOP);
},
+ link: CONST.APP_DOWNLOAD_LINKS.DESKTOP,
},
];
+ /**
+ * Show the ReportActionContextMenu modal popover.
+ *
+ * @param {Object} [event] - A press event.
+ * @param {string} [selection] - A copy text.
+ */
+ const showPopover = (event, selection) => {
+ ReportActionContextMenu.showContextMenu(
+ ContextMenuActions.CONTEXT_MENU_TYPES.LINK,
+ event,
+ selection,
+ popoverAnchor,
+ );
+ };
+
return (
{
/>
{_.map(menuItems, item => (
-
@@ -70,4 +108,7 @@ const AppDownloadLinksPage = (props) => {
AppDownloadLinksPage.propTypes = propTypes;
AppDownloadLinksPage.displayName = 'AppDownloadLinksPage';
-export default withLocalize(AppDownloadLinksPage);
+export default compose(
+ withWindowDimensions,
+ withLocalize,
+)(AppDownloadLinksPage);
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index 2bb4544fabb2..e9374be7f609 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -141,6 +141,7 @@ const InitialSettingsPage = (props) => {
action: () => Navigation.navigate(ROUTES.getWorkspaceInitialRoute(policy.id)),
iconStyles: [styles.popoverMenuIconEmphasized],
iconFill: themeColors.iconReversed,
+ fallbackIcon: Expensicons.FallbackWorkspaceAvatar,
}))
.value();
menuItems.push(...defaultMenuItems);
@@ -195,6 +196,7 @@ const InitialSettingsPage = (props) => {
iconFill={item.iconFill}
shouldShowRightIcon
badgeText={(isPaymentItem && Permissions.canUseWallet(props.betas)) ? walletBalance : undefined}
+ fallbackIcon={item.fallbackIcon}
/>
);
})}
diff --git a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js
index f04d34ba6caf..e0b6b61d4668 100644
--- a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js
+++ b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js
@@ -30,6 +30,7 @@ import * as Expensicons from '../../../../components/Icon/Expensicons';
import ConfirmModal from '../../../../components/ConfirmModal';
import KYCWall from '../../../../components/KYCWall';
import {propTypes, defaultProps} from './paymentsPagePropTypes';
+import {withNetwork} from '../../../../components/OnyxProvider';
class BasePaymentsPage extends React.Component {
constructor(props) {
@@ -60,12 +61,20 @@ class BasePaymentsPage extends React.Component {
}
componentDidMount() {
- PaymentMethods.getPaymentMethods();
+ this.fetchData();
if (this.props.shouldListenForResize) {
this.dimensionsSubscription = Dimensions.addEventListener('change', this.setMenuPosition);
}
}
+ componentDidUpdate(prevProps) {
+ if (!prevProps.network.isOffline || this.props.network.isOffline) {
+ return;
+ }
+
+ this.fetchData();
+ }
+
componentWillUnmount() {
if (!this.props.shouldListenForResize || !this.dimensionsSubscription) {
return;
@@ -186,6 +195,10 @@ class BasePaymentsPage extends React.Component {
throw new Error('Invalid payment method type selected');
}
+ fetchData() {
+ PaymentMethods.getPaymentMethods();
+ }
+
/**
* Hide the add payment modal
*/
@@ -432,6 +445,7 @@ BasePaymentsPage.defaultProps = defaultProps;
export default compose(
withWindowDimensions,
withLocalize,
+ withNetwork(),
withOnyx({
betas: {
key: ONYXKEYS.BETAS,
diff --git a/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js b/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js
index 5e9da7e3e19a..92158d06204c 100644
--- a/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js
+++ b/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import walletTransferPropTypes from '../walletTransferPropTypes';
import {withLocalizePropTypes} from '../../../../components/withLocalize';
import {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions';
+import networkPropTypes from '../../../../components/networkPropTypes';
const propTypes = {
/** Wallet balance transfer props */
@@ -22,6 +23,9 @@ const propTypes = {
currentBalance: PropTypes.number,
}),
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
...withLocalizePropTypes,
...windowDimensionsPropTypes,
diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js
index 1aaf232db0bf..fbeb7b69b16c 100755
--- a/src/pages/settings/Profile/ProfilePage.js
+++ b/src/pages/settings/Profile/ProfilePage.js
@@ -231,7 +231,7 @@ class ProfilePage extends Component {
(
{
);
+ const hasRedirect = !_.isEmpty(backgroundStyle.redirectUri);
+
+ const graphicLayout = (
+ {
+ Link.openExternalLink(backgroundStyle.redirectUri);
+ }}
+ disabled={!hasRedirect}
+ >
+
+
+ );
+
if (props.isSmallScreenWidth) {
return content;
}
- const hasRedirect = !_.isEmpty(backgroundStyle.redirectUri);
+ if (props.isMediumScreenWidth && props.windowHeight >= variables.minHeigthToShowGraphics) {
+ return (
+
+ {graphicLayout}
+
+ {content}
+
+
+ );
+ }
return (
{content}
- {
- Link.openExternalLink(backgroundStyle.redirectUri);
- }}
- disabled={!hasRedirect}
- >
-
-
+ {graphicLayout}
);
diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js
index 3e43faf56c14..9cb442edb586 100644
--- a/src/pages/workspace/WorkspaceInitialPage.js
+++ b/src/pages/workspace/WorkspaceInitialPage.js
@@ -156,6 +156,8 @@ class WorkspaceInitialPage extends React.Component {
containerStyles={styles.avatarLarge}
imageStyles={[styles.avatarLarge, styles.alignSelfCenter]}
source={this.props.policy.avatarURL}
+ fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
+ size={CONST.AVATAR_SIZE.LARGE}
/>
)
: (
diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js
index a4ae23252d78..764da2999e21 100644
--- a/src/pages/workspace/WorkspaceMembersPage.js
+++ b/src/pages/workspace/WorkspaceMembersPage.js
@@ -141,14 +141,24 @@ class WorkspaceMembersPage extends React.Component {
* @returns {Boolean} Return true if the tooltip was displayed so we can use the state of it in other functions.
*/
willTooltipShowForLogin(login, wasHovered = false) {
+ const isSmallOrMediumScreen = this.props.isSmallScreenWidth || this.props.isMediumScreenWidth;
+
// Small screens only show the tooltip on press, so ignore hovered event on those cases.
- if (wasHovered && (this.props.isSmallScreenWidth || this.props.isMediumScreenWidth)) {
+ if (wasHovered && isSmallOrMediumScreen) {
return false;
}
const canBeRemoved = this.props.policy.owner !== login && this.props.session.email !== login;
if (!canBeRemoved) {
- this.setState({showTooltipForLogin: login});
+ this.setState({
+ showTooltipForLogin: login,
+ }, () => {
+ // Immediately reset the login to deactivate the tooltip trigger, otherwise, the tooltip will not open again on further interactions on small screens.
+ if (!isSmallOrMediumScreen) {
+ return;
+ }
+ this.setState({showTooltipForLogin: ''});
+ });
}
return !canBeRemoved;
diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js
index 9f84eaf14b94..c9692d464ea4 100644
--- a/src/pages/workspace/WorkspaceSettingsPage.js
+++ b/src/pages/workspace/WorkspaceSettingsPage.js
@@ -153,6 +153,7 @@ class WorkspaceSettingsPage extends React.Component {
fill={defaultTheme.iconSuccessFill}
/>
)}
+ fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
style={[styles.mb3]}
anchorPosition={{top: 172, right: 18}}
isUsingDefaultAvatar={!this.state.previewAvatarURL}
diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js
index 7b1ced1f7ec6..4da69f75d427 100644
--- a/src/styles/StyleUtils.js
+++ b/src/styles/StyleUtils.js
@@ -36,6 +36,7 @@ function getAvatarStyle(size) {
height: avatarSize,
width: avatarSize,
borderRadius: avatarSize,
+ backgroundColor: themeColors.offline,
};
}
diff --git a/src/styles/getModalStyles.js b/src/styles/getModalStyles/getBaseModalStyles.js
similarity index 98%
rename from src/styles/getModalStyles.js
rename to src/styles/getModalStyles/getBaseModalStyles.js
index 32ca24fa9c84..f61d7968644f 100644
--- a/src/styles/getModalStyles.js
+++ b/src/styles/getModalStyles/getBaseModalStyles.js
@@ -1,7 +1,7 @@
-import CONST from '../CONST';
-import colors from './colors';
-import variables from './variables';
-import themeColors from './themes/default';
+import CONST from '../../CONST';
+import colors from '../colors';
+import variables from '../variables';
+import themeColors from '../themes/default';
export default (type, windowDimensions, popoverAnchorPosition = {}, containerStyle = {}) => {
const {isSmallScreenWidth, windowWidth} = windowDimensions;
diff --git a/src/styles/getModalStyles/index.android.js b/src/styles/getModalStyles/index.android.js
new file mode 100644
index 000000000000..e7e3531171ae
--- /dev/null
+++ b/src/styles/getModalStyles/index.android.js
@@ -0,0 +1,7 @@
+import getBaseModalStyles from './getBaseModalStyles';
+
+// Only apply top padding on iOS since it's the only platform using SafeAreaView
+export default (type, windowDimensions, popoverAnchorPosition = {}, containerStyle = {}) => ({
+ ...getBaseModalStyles(type, windowDimensions, popoverAnchorPosition, containerStyle),
+ shouldAddTopSafeAreaPadding: false,
+});
diff --git a/src/styles/getModalStyles/index.js b/src/styles/getModalStyles/index.js
new file mode 100644
index 000000000000..4e35fa589119
--- /dev/null
+++ b/src/styles/getModalStyles/index.js
@@ -0,0 +1,3 @@
+import getBaseModalStyles from './getBaseModalStyles';
+
+export default getBaseModalStyles;
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 4859c0470350..dbce0a6b052d 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -135,6 +135,7 @@ const webViewStyles = {
fontSize: variables.fontSizeNormal,
fontFamily: fontFamily.GTA,
flex: 1,
+ whiteSpace: 'pre',
},
};
@@ -2189,6 +2190,11 @@ const styles = {
cursor: 'not-allowed',
},
+ noSelect: {
+ boxShadow: 'none',
+ outline: 'none',
+ },
+
cursorPointer: {
cursor: 'pointer',
},
diff --git a/src/styles/variables.js b/src/styles/variables.js
index 506755712154..02a4fe2e80b7 100644
--- a/src/styles/variables.js
+++ b/src/styles/variables.js
@@ -46,4 +46,5 @@ export default {
tooltipzIndex: 10050,
gutterWidth: 16,
popoverMenuShadow: '0px 4px 12px 0px rgba(0, 0, 0, 0.06)',
+ minHeightToShowGraphics: 854, // below this height UI was broken on login form layout as there isn't enough height to show forma and graphics.
};