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/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/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/MenuItem.js b/src/components/MenuItem.js
index 9e40683893ba..004fd45ad092 100644
--- a/src/components/MenuItem.js
+++ b/src/components/MenuItem.js
@@ -39,6 +39,7 @@ const defaultProps = {
iconType: 'icon',
onPress: () => {},
interactive: true,
+ fallbackIcon: Expensicons.FallbackAvatar,
};
const MenuItem = props => (
@@ -87,6 +88,7 @@ const MenuItem = props => (
)}
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/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/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/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/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,
};
}