diff --git a/src/CONST.js b/src/CONST.js
index d169485906e4..4f05bc37e770 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -552,8 +552,14 @@ const CONST = {
AVATAR_SIZE: {
LARGE: 'large',
DEFAULT: 'default',
+ SMALL: 'small',
+ SUBSCRIPT: 'subscript',
+ SMALL_SUBSCRIPT: 'small-subscript',
+ },
+ OPTION_MODE: {
+ COMPACT: 'compact',
+ DEFAULT: 'default',
},
-
PHONE_MAX_LENGTH: 15,
PHONE_MIN_LENGTH: 5,
REGEX: {
diff --git a/src/components/Avatar.js b/src/components/Avatar.js
index 0e6aa6a381e6..fd3571ab4a9e 100644
--- a/src/components/Avatar.js
+++ b/src/components/Avatar.js
@@ -1,14 +1,17 @@
import React, {PureComponent} from 'react';
import {Image, View} from 'react-native';
import PropTypes from 'prop-types';
-import styles from '../styles/styles';
-import themeColors from '../styles/themes/default';
-import RoomAvatar from './RoomAvatar';
+import _ from 'underscore';
import stylePropTypes from '../styles/stylePropTypes';
+import Icon from './Icon';
+import themeColors from '../styles/themes/default';
+import CONST from '../CONST';
+import * as StyleUtils from '../styles/StyleUtils';
+
const propTypes = {
- /** Url source for the avatar */
- source: PropTypes.string,
+ /** Source for the avatar. Can be a URL or an icon. */
+ source: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/** Extra styles to pass to Image */
imageStyles: PropTypes.arrayOf(PropTypes.object),
@@ -17,43 +20,39 @@ const propTypes = {
containerStyles: stylePropTypes,
/** Set the size of Avatar */
- size: PropTypes.oneOf(['default', 'small']),
+ size: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)),
- /** Whether this avatar is for a chat room */
- isChatRoom: PropTypes.bool,
-
- /** Whether this avatar is for an archived default room */
- isArchivedRoom: PropTypes.bool,
+ /** 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,
};
const defaultProps = {
- source: '',
+ source: null,
imageStyles: [],
containerStyles: [],
- size: 'default',
- isChatRoom: false,
- isArchivedRoom: false,
+ size: CONST.AVATAR_SIZE.DEFAULT,
+ fill: themeColors.icon,
};
class Avatar extends PureComponent {
render() {
- if (!this.props.source && !this.props.isChatRoom) {
+ if (!this.props.source) {
return null;
}
const imageStyle = [
- this.props.size === 'small' ? styles.avatarSmall : styles.avatarNormal,
-
- // Background color isn't added for room avatar because it changes it's shape to a square
- this.props.isChatRoom ? {} : {backgroundColor: themeColors.icon},
+ StyleUtils.getAvatarStyle(this.props.size),
...this.props.imageStyles,
];
+ const iconSize = StyleUtils.getAvatarSize(this.props.size);
return (
- {this.props.isChatRoom
- ?
- : }
+ {
+ _.isFunction(this.props.source)
+ ?
+ :
+ }
);
}
diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js
index 340e8e5ad25e..40e0342153b6 100644
--- a/src/components/Icon/Expensicons.js
+++ b/src/components/Icon/Expensicons.js
@@ -70,8 +70,11 @@ import Users from '../../../assets/images/users.svg';
import Venmo from '../../../assets/images/venmo.svg';
import Wallet from '../../../assets/images/wallet.svg';
import Workspace from '../../../assets/images/workspace-default-avatar.svg';
+import ActiveRoomAvatar from '../../../assets/images/avatars/room.svg';
+import DeletedRoomAvatar from '../../../assets/images/avatars/deleted-room.svg';
export {
+ ActiveRoomAvatar,
Android,
Apple,
ArrowRight,
@@ -92,6 +95,7 @@ export {
ClosedSign,
Concierge,
CreditCard,
+ DeletedRoomAvatar,
DownArrow,
Download,
Emoji,
diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js
index 1d65a5469481..993d36123c3a 100644
--- a/src/components/MultipleAvatars.js
+++ b/src/components/MultipleAvatars.js
@@ -1,62 +1,57 @@
import React, {memo} from 'react';
import PropTypes from 'prop-types';
import {Image, View} from 'react-native';
+import _ from 'underscore';
import styles from '../styles/styles';
import Avatar from './Avatar';
import Tooltip from './Tooltip';
import Text from './Text';
+import themeColors from '../styles/themes/default';
+import * as StyleUtils from '../styles/StyleUtils';
+import CONST from '../CONST';
const propTypes = {
- /** Array of avatar URL */
- avatarImageURLs: PropTypes.arrayOf(PropTypes.string),
+ /** Array of avatar URLs or icons */
+ avatarIcons: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.func])),
- /** Set the sie of avatars */
- size: PropTypes.oneOf(['default', 'small']),
+ /** Set the size of avatars */
+ size: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)),
/** Style for Second Avatar */
// eslint-disable-next-line react/forbid-prop-types
secondAvatarStyle: PropTypes.arrayOf(PropTypes.object),
- /** Whether this avatar is for a chat room */
- isChatRoom: PropTypes.bool,
-
- /** Whether this avatar is for an archived room */
- isArchivedRoom: PropTypes.bool,
-
/** Tooltip for the Avatar */
avatarTooltips: PropTypes.arrayOf(PropTypes.string),
};
const defaultProps = {
- avatarImageURLs: [],
- size: 'default',
- secondAvatarStyle: [styles.secondAvatarHovered],
- isChatRoom: false,
- isArchivedRoom: false,
+ avatarIcons: [],
+ size: CONST.AVATAR_SIZE.DEFAULT,
+ secondAvatarStyle: [StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)],
avatarTooltips: [],
};
const MultipleAvatars = (props) => {
- const avatarContainerStyles = props.size === 'small' ? styles.emptyAvatarSmall : styles.emptyAvatar;
- const singleAvatarStyles = props.size === 'small' ? styles.singleAvatarSmall : styles.singleAvatar;
+ const avatarContainerStyles = props.size === CONST.AVATAR_SIZE.SMALL ? styles.emptyAvatarSmall : styles.emptyAvatar;
+ const singleAvatarStyles = props.size === CONST.AVATAR_SIZE.SMALL ? styles.singleAvatarSmall : styles.singleAvatar;
const secondAvatarStyles = [
- props.size === 'small' ? styles.secondAvatarSmall : styles.secondAvatar,
+ props.size === CONST.AVATAR_SIZE.SMALL ? styles.secondAvatarSmall : styles.secondAvatar,
...props.secondAvatarStyle,
];
- if (!props.avatarImageURLs.length) {
+ if (!props.avatarIcons.length) {
return null;
}
- if (props.avatarImageURLs.length === 1) {
+ if (props.avatarIcons.length === 1) {
return (
@@ -70,17 +65,17 @@ const MultipleAvatars = (props) => {
>
- {props.avatarImageURLs.length === 2 ? (
+ {props.avatarIcons.length === 2 ? (
@@ -89,11 +84,11 @@ const MultipleAvatars = (props) => {
-
- {`+${props.avatarImageURLs.length - 1}`}
+ {`+${props.avatarIcons.length - 1}`}
diff --git a/src/components/OptionsList/optionsListPropTypes.js b/src/components/OptionsList/optionsListPropTypes.js
index 4ca0cf3ab4f9..7b2e20c8828c 100644
--- a/src/components/OptionsList/optionsListPropTypes.js
+++ b/src/components/OptionsList/optionsListPropTypes.js
@@ -1,7 +1,9 @@
import PropTypes from 'prop-types';
+import _ from 'underscore';
import SectionList from '../SectionList';
import styles from '../../styles/styles';
import optionPropTypes from '../optionPropTypes';
+import CONST from '../../CONST';
const propTypes = {
/** option Background Color */
@@ -69,7 +71,7 @@ const propTypes = {
showTitleTooltip: PropTypes.bool,
/** Toggle between compact and default view of the option */
- optionMode: PropTypes.oneOf(['compact', 'default']),
+ optionMode: PropTypes.oneOf(_.values(CONST.OPTION_MODE)),
/** Whether to disable the interactivity of the list's option row(s) */
disableRowInteractivity: PropTypes.bool,
diff --git a/src/components/ReportActionItem/IOUPreview.js b/src/components/ReportActionItem/IOUPreview.js
index 4e4792c9086c..309a46bff735 100644
--- a/src/components/ReportActionItem/IOUPreview.js
+++ b/src/components/ReportActionItem/IOUPreview.js
@@ -139,7 +139,7 @@ const IOUPreview = (props) => {
diff --git a/src/components/RoomAvatar.js b/src/components/RoomAvatar.js
deleted file mode 100644
index f9675418e542..000000000000
--- a/src/components/RoomAvatar.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React, {PureComponent} from 'react';
-import {StyleSheet} from 'react-native';
-import PropTypes from 'prop-types';
-import ActiveRoomAvatar from '../../assets/images/avatars/room.svg';
-import DeletedRoomAvatar from '../../assets/images/avatars/deleted-room.svg';
-
-const propTypes = {
- /** Extra styles to pass to Image */
- avatarStyle: PropTypes.arrayOf(PropTypes.object),
-
- /** Whether the room this avatar is for is deleted or not */
- isArchived: PropTypes.bool,
-};
-
-const defaultProps = {
- avatarStyle: [],
- isArchived: false,
-};
-
-class RoomAvatar extends PureComponent {
- render() {
- return (this.props.isArchived
- ?
- :
- );
- }
-}
-
-RoomAvatar.defaultProps = defaultProps;
-RoomAvatar.propTypes = propTypes;
-export default RoomAvatar;
diff --git a/src/components/RoomHeaderAvatars.js b/src/components/RoomHeaderAvatars.js
index eb50814854b4..f9cf7351ae32 100644
--- a/src/components/RoomHeaderAvatars.js
+++ b/src/components/RoomHeaderAvatars.js
@@ -6,46 +6,69 @@ import styles from '../styles/styles';
import Text from './Text';
import CONST from '../CONST';
import Avatar from './Avatar';
+import themeColors from '../styles/themes/default';
+import * as StyleUtils from '../styles/StyleUtils';
const propTypes = {
- /** Array of avatar URL */
- avatarImageURLs: PropTypes.arrayOf(PropTypes.string),
+ /** Array of avatar URLs or icons */
+ avatarIcons: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.func])),
- /** Whether this avatar is for a custom/default room */
- isChatRoom: PropTypes.bool,
+ /** Whether show large Avatars */
+ shouldShowLargeAvatars: PropTypes.bool,
};
const defaultProps = {
- avatarImageURLs: [],
- isChatRoom: false,
+ avatarIcons: [],
+ shouldShowLargeAvatars: false,
};
const RoomHeaderAvatars = (props) => {
- if (!props.avatarImageURLs.length) {
+ if (!props.avatarIcons.length) {
return null;
}
- if (props.avatarImageURLs.length === 1 || props.isChatRoom) {
+ if (props.avatarIcons.length === 1) {
return (
);
}
- // avatarImageURLsToDisplay
- const avatarImageURLsToDisplay = props.avatarImageURLs.slice(0, CONST.REPORT.MAX_PREVIEW_AVATARS);
+ if (props.shouldShowLargeAvatars) {
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+ const avatarIconsToDisplay = props.avatarIcons.slice(0, CONST.REPORT.MAX_PREVIEW_AVATARS);
return (
- {_.map(avatarImageURLsToDisplay, (val, index) => (
+ {_.map(avatarIconsToDisplay, (val, index) => (
- {index === CONST.REPORT.MAX_PREVIEW_AVATARS - 1 && props.avatarImageURLs.length - CONST.REPORT.MAX_PREVIEW_AVATARS !== 0 && (
+ {index === CONST.REPORT.MAX_PREVIEW_AVATARS - 1 && props.avatarIcons.length - CONST.REPORT.MAX_PREVIEW_AVATARS !== 0 && (
<>
{
]}
/>
- {`+${props.avatarImageURLs.length - CONST.REPORT.MAX_PREVIEW_AVATARS}`}
+ {`+${props.avatarIcons.length - CONST.REPORT.MAX_PREVIEW_AVATARS}`}
>
)}
diff --git a/src/components/SubscriptAvatar.js b/src/components/SubscriptAvatar.js
new file mode 100644
index 000000000000..61c007b00c2d
--- /dev/null
+++ b/src/components/SubscriptAvatar.js
@@ -0,0 +1,61 @@
+import React, {memo} from 'react';
+import PropTypes from 'prop-types';
+import {View} from 'react-native';
+import _ from 'underscore';
+import styles from '../styles/styles';
+import Tooltip from './Tooltip';
+import themeColors from '../styles/themes/default';
+import Avatar from './Avatar';
+import CONST from '../CONST';
+import * as StyleUtils from '../styles/StyleUtils';
+
+const propTypes = {
+ /** Avatar URL or icon */
+ mainAvatar: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
+
+ /** Subscript avatar URL or icon */
+ secondaryAvatar: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
+
+ /** Tooltip for the main avatar */
+ mainTooltip: PropTypes.string,
+
+ /** Tooltip for the subscript avatar */
+ secondaryTooltip: PropTypes.string,
+
+ /** Set the size of avatars */
+ mode: PropTypes.oneOf(_.values(CONST.OPTION_MODE)),
+};
+
+const defaultProps = {
+ mainTooltip: '',
+ secondaryTooltip: '',
+ mode: CONST.OPTION_MODE.DEFAULT,
+};
+
+const SubscriptAvatar = props => (
+
+
+
+
+
+
+
+
+
+
+);
+
+SubscriptAvatar.displayName = 'SubscriptAvatar';
+SubscriptAvatar.propTypes = propTypes;
+SubscriptAvatar.defaultProps = defaultProps;
+export default memo(SubscriptAvatar);
diff --git a/src/components/optionPropTypes.js b/src/components/optionPropTypes.js
index ac4e1593d025..1e5440ddf4a9 100644
--- a/src/components/optionPropTypes.js
+++ b/src/components/optionPropTypes.js
@@ -7,8 +7,8 @@ export default PropTypes.shape({
// Alternate text to display
alternateText: PropTypes.string,
- // Array of icon urls
- icons: PropTypes.arrayOf(PropTypes.string),
+ // Array of URLs or icons
+ icons: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.func])),
// Login (only present when there is a single participant)
login: PropTypes.string,
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index f7a9e3441fbd..419c2ada5712 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -10,6 +10,7 @@ import * as ReportUtils from './reportUtils';
import * as Localize from './Localize';
import Permissions from './Permissions';
import md5 from './md5';
+import * as Expensicons from '../components/Icon/Expensicons';
/**
* OptionsListUtils is used to build a list options passed to the OptionsList component. Several different UI views can
@@ -164,13 +165,13 @@ function getParticipantNames(personalDetailList) {
*
* @param {Object} report
* @param {Array} personalDetailList
- * @param {Boolean} isChatRoom
+ * @param {Boolean} isChatRoomOrPolicyExpenseChat
* @return {String}
*/
-function getSearchText(report, personalDetailList, isChatRoom) {
+function getSearchText(report, personalDetailList, isChatRoomOrPolicyExpenseChat) {
const searchTerms = [];
- if (!isChatRoom) {
+ if (!isChatRoomOrPolicyExpenseChat) {
_.each(personalDetailList, (personalDetail) => {
searchTerms.push(personalDetail.displayName);
searchTerms.push(personalDetail.login);
@@ -180,7 +181,7 @@ function getSearchText(report, personalDetailList, isChatRoom) {
searchTerms.push(...report.reportName);
searchTerms.push(..._.map(report.reportName.split(','), name => name.trim()));
- if (isChatRoom) {
+ if (isChatRoomOrPolicyExpenseChat) {
const chatRoomSubtitle = ReportUtils.getChatRoomSubtitle(report, policies);
searchTerms.push(...chatRoomSubtitle);
searchTerms.push(..._.map(chatRoomSubtitle.split(','), name => name.trim()));
@@ -204,20 +205,58 @@ function hasReportDraftComment(report) {
&& lodashGet(reportsWithDraft, `${ONYXKEYS.COLLECTION.REPORTS_WITH_DRAFT}${report.reportID}`, false);
}
+/**
+ * @param {Object} report
+ * @returns {Array}
+ */
+function getParticipants(report) {
+ const participants = [...lodashGet(report, ['participants'], [])];
+ if (ReportUtils.isPolicyExpenseChat(report) && !_.contains(participants, report.ownerEmail)) {
+ participants.push(report.ownerEmail);
+ }
+ return participants;
+}
+
+/**
+ * Get the Avatar urls or return the icon according to the chat type
+ *
+ * @param {Object} report
+ * @returns {Array<*>}
+ */
+function getAvatarSources(report) {
+ return _.map(lodashGet(report, 'icons', ['']), (source) => {
+ if (source) {
+ return source;
+ }
+ if (ReportUtils.isArchivedRoom(report)) {
+ return Expensicons.DeletedRoomAvatar;
+ }
+ if (ReportUtils.isChatRoom(report)) {
+ return Expensicons.ActiveRoomAvatar;
+ }
+ if (ReportUtils.isPolicyExpenseChat(report)) {
+ return Expensicons.Workspace;
+ }
+ return Expensicons.Profile;
+ });
+}
+
/**
* Creates a report list option
*
* @param {Array