diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js index e7246d721535..1835e739c25b 100644 --- a/src/components/AvatarWithIndicator.js +++ b/src/components/AvatarWithIndicator.js @@ -16,7 +16,6 @@ import walletTermsPropTypes from '../pages/EnablePayments/walletTermsPropTypes'; import * as PolicyUtils from '../libs/PolicyUtils'; import * as PaymentMethods from '../libs/actions/PaymentMethods'; import * as ReimbursementAccountProps from '../pages/ReimbursementAccount/reimbursementAccountPropTypes'; -import * as ReportUtils from '../libs/ReportUtils'; import * as UserUtils from '../libs/UserUtils'; import themeColors from '../styles/themes/default'; @@ -103,7 +102,7 @@ const AvatarWithIndicator = (props) => { return ( - + {(shouldShowErrorIndicator || shouldShowInfoIndicator) && } diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index bdaeb2cd076e..8bf719e679d7 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -27,6 +27,7 @@ import * as CurrencyUtils from '../libs/CurrencyUtils'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import DateUtils from '../libs/DateUtils'; import reportPropTypes from '../pages/reportPropTypes'; +import * as UserUtils from '../libs/UserUtils'; const propTypes = { /** The report currently being looked at */ @@ -82,7 +83,7 @@ const MoneyRequestHeader = (props) => { const payeeName = isExpenseReport ? ReportUtils.getPolicyName(moneyRequestReport, props.policies) : ReportUtils.getDisplayNameForParticipant(moneyRequestReport.managerEmail); const payeeAvatar = isExpenseReport ? ReportUtils.getWorkspaceAvatar(moneyRequestReport) - : ReportUtils.getAvatar(lodashGet(props.personalDetails, [moneyRequestReport.managerEmail, 'avatar']), moneyRequestReport.managerEmail); + : UserUtils.getAvatar(lodashGet(props.personalDetails, [moneyRequestReport.managerEmail, 'avatar']), moneyRequestReport.managerEmail); const policy = props.policies[`${ONYXKEYS.COLLECTION.POLICY}${props.report.policyID}`]; const isPayer = Policy.isAdminOfFreePolicy([policy]) || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && lodashGet(props.session, 'email', null) === moneyRequestReport.managerEmail); diff --git a/src/components/TaskHeader.js b/src/components/TaskHeader.js index 4b21d5a2ebe5..73b7afca0957 100644 --- a/src/components/TaskHeader.js +++ b/src/components/TaskHeader.js @@ -20,6 +20,7 @@ import Icon from './Icon'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import Button from './Button'; import * as TaskUtils from '../libs/actions/Task'; +import * as UserUtils from '../libs/UserUtils'; const propTypes = { /** The report currently being looked at */ @@ -34,7 +35,7 @@ const propTypes = { function TaskHeader(props) { const title = ReportUtils.getReportName(props.report); const assigneeName = ReportUtils.getDisplayNameForParticipant(props.report.managerEmail); - const assigneeAvatar = ReportUtils.getAvatar(lodashGet(props.personalDetails, [props.report.managerEmail, 'avatar']), props.report.managerEmail); + const assigneeAvatar = UserUtils.getAvatar(lodashGet(props.personalDetails, [props.report.managerEmail, 'avatar']), props.report.managerEmail); const isOpen = props.report.stateNum === CONST.REPORT.STATE_NUM.OPEN && props.report.statusNum === CONST.REPORT.STATUS.OPEN; const isCompleted = props.report.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.report.statusNum === CONST.REPORT.STATUS.APPROVED; const parentReportID = props.report.parentReportID; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index d2a0aa0aace4..27a70e59ee5e 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -14,6 +14,7 @@ import * as CollectionUtils from './CollectionUtils'; import Navigation from './Navigation/Navigation'; import * as LoginUtils from './LoginUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; +import * as UserUtils from './UserUtils'; /** * OptionsListUtils is used to build a list options passed to the OptionsList component. Several different UI views can @@ -152,7 +153,7 @@ function getAvatarsForLogins(logins, personalDetails) { return _.map(logins, (login) => { const userPersonalDetail = lodashGet(personalDetails, login, {login, avatar: ''}); return { - source: ReportUtils.getAvatar(userPersonalDetail.avatar, userPersonalDetail.login), + source: UserUtils.getAvatar(userPersonalDetail.avatar, userPersonalDetail.login), type: CONST.ICON_TYPE_AVATAR, name: userPersonalDetail.login, }; @@ -181,7 +182,7 @@ function getPersonalDetailsForLogins(logins, personalDetails) { personalDetail = { login, displayName: LocalePhoneNumber.formatPhoneNumber(login), - avatar: ReportUtils.getDefaultAvatar(login), + avatar: UserUtils.getDefaultAvatar(login), }; } @@ -220,7 +221,7 @@ function getParticipantsOptions(report, personalDetails) { alternateText: Str.isSMSLogin(details.login || '') ? LocalePhoneNumber.formatPhoneNumber(details.login) : details.login, icons: [ { - source: ReportUtils.getAvatar(details.avatar, details.login), + source: UserUtils.getAvatar(details.avatar, details.login), name: details.login, type: CONST.ICON_TYPE_AVATAR, }, @@ -482,7 +483,7 @@ function createOption(logins, personalDetails, report, reportActions = {}, {show result.text = reportName; result.searchText = getSearchText(report, reportName, personalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); - result.icons = ReportUtils.getIcons(report, personalDetails, ReportUtils.getAvatar(personalDetail.avatar, personalDetail.login)); + result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail.avatar, personalDetail.login)); result.subtitle = subtitle; return result; @@ -727,7 +728,7 @@ function getOptions( // If user doesn't exist, use a default avatar userToInvite.icons = [ { - source: ReportUtils.getAvatar('', searchValue), + source: UserUtils.getAvatar('', searchValue), name: searchValue, type: CONST.ICON_TYPE_AVATAR, }, @@ -806,7 +807,7 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail, amount alternateText: personalDetail.login, icons: [ { - source: ReportUtils.getAvatar(personalDetail.avatar, personalDetail.login), + source: UserUtils.getAvatar(personalDetail.avatar, personalDetail.login), name: personalDetail.login, type: CONST.ICON_TYPE_AVATAR, }, diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index fc229887fb75..1b9d17029b47 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -8,7 +8,6 @@ import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import * as Localize from './Localize'; import * as Expensicons from '../components/Icon/Expensicons'; -import hashCode from './hashCode'; import Navigation from './Navigation/Navigation'; import ROUTES from '../ROUTES'; import * as NumberUtils from './NumberUtils'; @@ -17,11 +16,11 @@ import * as ReportActionsUtils from './ReportActionsUtils'; import Permissions from './Permissions'; import DateUtils from './DateUtils'; 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 LocalePhoneNumber from './LocalePhoneNumber'; import * as CurrencyUtils from './CurrencyUtils'; +import * as UserUtils from './UserUtils'; let sessionEmail; Onyx.connect({ @@ -644,36 +643,6 @@ function formatReportLastMessageText(lastMessageText) { return Str.htmlDecode(String(lastMessageText)).replace(CONST.REGEX.AFTER_FIRST_LINE_BREAK, '').substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH).trim(); } -/** - * Hashes provided string and returns a value between [0, range) - * @param {String} login - * @param {Number} range - * @returns {Number} - */ -function hashLogin(login, range) { - return Math.abs(hashCode(login.toLowerCase())) % range; -} - -/** - * Helper method to return the default avatar associated with the given login - * @param {String} [login] - * @returns {String} - */ -function getDefaultAvatar(login = '') { - if (!login) { - return Expensicons.FallbackAvatar; - } - if (login === CONST.EMAIL.CONCIERGE) { - return Expensicons.ConciergeAvatar; - } - - // There are 24 possible default avatars, so we choose which one this user has based - // on a simple hash of their login. Note that Avatar count starts at 1. - const loginHashBucket = hashLogin(login, CONST.DEFAULT_AVATAR_COUNT) + 1; - - return defaultAvatars[`Avatar${loginHashBucket}`]; -} - /** * Helper method to return the default avatar associated with the given login * @param {String} [workspaceName] @@ -698,102 +667,6 @@ function getWorkspaceAvatar(report) { return lodashGet(allPolicies, [`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, 'avatar']) || getDefaultWorkspaceAvatar(workspaceName); } -/** - * Helper method to return old dot default avatar associated with login - * - * @param {String} [login] - * @returns {String} - */ -function getOldDotDefaultAvatar(login = '') { - if (login === CONST.EMAIL.CONCIERGE) { - return CONST.CONCIERGE_ICON_URL; - } - - // There are 8 possible old dot default avatars, so we choose which one this user has based - // on a simple hash of their login. Note that Avatar count starts at 1. - const loginHashBucket = hashLogin(login, CONST.OLD_DEFAULT_AVATAR_COUNT) + 1; - - return `${CONST.CLOUDFRONT_URL}/images/avatars/avatar_${loginHashBucket}.png`; -} - -/** - * Given a user's avatar path, returns true if user doesn't have an avatar or if URL points to a default avatar - * @param {String} [avatarURL] - the avatar source from user's personalDetails - * @returns {Boolean} - */ -function isDefaultAvatar(avatarURL) { - if ( - _.isString(avatarURL) && - (avatarURL.includes('images/avatars/avatar_') || avatarURL.includes('images/avatars/default-avatar_') || avatarURL.includes('images/avatars/user/default')) - ) { - return true; - } - - // If null URL, we should also use a default avatar - if (!avatarURL) { - return true; - } - return false; -} - -/** - * Provided a source URL, if source is a default avatar, return the associated SVG. - * Otherwise, return the URL pointing to a user-uploaded avatar. - * - * @param {String} [avatarURL] - the avatar source from user's personalDetails - * @param {String} [login] - the email of the user - * @returns {String|Function} - */ -function getAvatar(avatarURL, login) { - if (isDefaultAvatar(avatarURL)) { - return getDefaultAvatar(login); - } - return avatarURL; -} - -/** - * Avatars uploaded by users will have a _128 appended so that the asset server returns a small version. - * This removes that part of the URL so the full version of the image can load. - * - * @param {String} [avatarURL] - * @param {String} [login] - * @returns {String|Function} - */ -function getFullSizeAvatar(avatarURL, login) { - const source = getAvatar(avatarURL, login); - if (!_.isString(source)) { - return source; - } - return source.replace('_128', ''); -} - -/** - * Small sized avatars end with _128.. This adds the _128 at the end of the - * source URL (before the file type) if it doesn't exist there already. - * - * @param {String} avatarURL - * @param {String} login - * @returns {String|Function} - */ -function getSmallSizeAvatar(avatarURL, login) { - const source = getAvatar(avatarURL, login); - if (!_.isString(source)) { - return source; - } - - // Because other urls than CloudFront do not support dynamic image sizing (_SIZE suffix), the current source is already what we want to use here. - if (!CONST.CLOUDFRONT_DOMAIN_REGEX.test(source)) { - return source; - } - - // If image source already has _128 at the end, the given avatar URL is already what we want to use here. - const lastPeriodIndex = source.lastIndexOf('.'); - if (source.substring(lastPeriodIndex - 4, lastPeriodIndex) === '_128') { - return source; - } - return `${source.substring(0, lastPeriodIndex)}_128${source.substring(lastPeriodIndex)}`; -} - /** * Returns the appropriate icons for the given chat report using the stored personalDetails. * The Avatar sources can be URLs or Icon components according to the chat type. @@ -808,7 +681,7 @@ function getIconsForParticipants(participants, personalDetails) { for (let i = 0; i < participantsList.length; i++) { const login = participantsList[i]; - const avatarSource = getAvatar(lodashGet(personalDetails, [login, 'avatar'], ''), login); + const avatarSource = UserUtils.getAvatar(lodashGet(personalDetails, [login, 'avatar'], ''), login); participantDetails.push([login, lodashGet(personalDetails, [login, 'firstName'], ''), avatarSource]); } @@ -863,7 +736,7 @@ function getIcons(report, personalDetails, defaultIcon = null, isPayer = false) const actorEmail = lodashGet(parentReportAction, 'actorEmail', ''); const actorIcon = { - source: getAvatar(lodashGet(personalDetails, [actorEmail, 'avatar']), actorEmail), + source: UserUtils.getAvatar(lodashGet(personalDetails, [actorEmail, 'avatar']), actorEmail), name: actorEmail, type: CONST.ICON_TYPE_AVATAR, }; @@ -900,7 +773,7 @@ function getIcons(report, personalDetails, defaultIcon = null, isPayer = false) } const adminIcon = { - source: getAvatar(lodashGet(personalDetails, [report.ownerEmail, 'avatar']), report.ownerEmail), + source: UserUtils.getAvatar(lodashGet(personalDetails, [report.ownerEmail, 'avatar']), report.ownerEmail), name: report.ownerEmail, type: CONST.ICON_TYPE_AVATAR, }; @@ -919,7 +792,7 @@ function getIcons(report, personalDetails, defaultIcon = null, isPayer = false) const email = isPayer ? report.managerEmail : report.ownerEmail; return [ { - source: getAvatar(lodashGet(personalDetails, [email, 'avatar']), email), + source: UserUtils.getAvatar(lodashGet(personalDetails, [email, 'avatar']), email), name: email, type: CONST.ICON_TYPE_AVATAR, }, @@ -943,7 +816,7 @@ function getPersonalDetailsForLogin(login) { (allPersonalDetails && allPersonalDetails[login]) || { login, displayName: LocalePhoneNumber.formatPhoneNumber(login), - avatar: getDefaultAvatar(login), + avatar: UserUtils.getDefaultAvatar(login), } ); } @@ -1227,7 +1100,7 @@ function buildOptimisticAddCommentReportAction(text, file) { }, ], automatic: false, - avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), + avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], UserUtils.getDefaultAvatar(currentUserEmail)), created: DateUtils.getDBTime(), message: [ { @@ -1449,7 +1322,7 @@ function buildOptimisticIOUReportAction(type, amount, currency, comment, partici actorAccountID: currentUserAccountID, actorEmail: currentUserEmail, automatic: false, - avatar: lodashGet(currentUserPersonalDetails, 'avatar', getDefaultAvatar(currentUserEmail)), + avatar: lodashGet(currentUserPersonalDetails, 'avatar', UserUtils.getDefaultAvatar(currentUserEmail)), isAttachment: false, originalMessage, message: getIOUReportActionMessage(type, amount, textForNewCommentDecoded, currency, paymentType, isSettlingUp), @@ -1502,7 +1375,7 @@ function buildOptimisticTaskReportAction(taskReportID, actionName, message = '') actorAccountID: currentUserAccountID, actorEmail: currentUserEmail, automatic: false, - avatar: lodashGet(currentUserPersonalDetails, 'avatar', getDefaultAvatar(currentUserEmail)), + avatar: lodashGet(currentUserPersonalDetails, 'avatar', UserUtils.getDefaultAvatar(currentUserEmail)), isAttachment: false, originalMessage, message: [ @@ -1615,7 +1488,7 @@ function buildOptimisticCreatedReportAction(ownerEmail) { }, ], automatic: false, - avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), + avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], UserUtils.getDefaultAvatar(currentUserEmail)), created: DateUtils.getDBTime(), shouldShow: true, }; @@ -1655,7 +1528,7 @@ function buildOptimisticEditedTaskReportAction(ownerEmail) { }, ], automatic: false, - avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), + avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], UserUtils.getDefaultAvatar(currentUserEmail)), created: DateUtils.getDBTime(), shouldShow: false, }; @@ -1674,7 +1547,7 @@ function buildOptimisticClosedReportAction(ownerEmail, policyName, reason = CONS actionName: CONST.REPORT.ACTIONS.TYPE.CLOSED, actorAccountID: currentUserAccountID, automatic: false, - avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), + avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], UserUtils.getDefaultAvatar(currentUserEmail)), created: DateUtils.getDBTime(), message: [ { @@ -2272,7 +2145,6 @@ export { formatReportLastMessageText, chatIncludesConcierge, isPolicyExpenseChat, - getDefaultAvatar, getIconsForParticipants, getIcons, getRoomWelcomeMessage, @@ -2310,17 +2182,11 @@ export { isTaskReport, isMoneyRequestReport, chatIncludesChronos, - getAvatar, - isDefaultAvatar, - getOldDotDefaultAvatar, getNewMarkerReportActionID, canSeeDefaultRoom, - hashLogin, getDefaultWorkspaceAvatar, getCommentLength, getParsedComment, - getFullSizeAvatar, - getSmallSizeAvatar, getMoneyRequestOptions, canRequestMoney, getWhisperDisplayNames, diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index 3f9caafcf828..7b3b15291e74 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -11,6 +11,7 @@ import CONST from '../CONST'; import * as OptionsListUtils from './OptionsListUtils'; import * as CollectionUtils from './CollectionUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; +import * as UserUtils from './UserUtils'; // Note: It is very important that the keys subscribed to here are the same // keys that are connected to SidebarLinks withOnyx(). If there was a key missing from SidebarLinks and it's data was updated @@ -332,7 +333,7 @@ function getOptionData(reportID) { result.subtitle = subtitle; result.participantsList = participantPersonalDetailList; - result.icons = ReportUtils.getIcons(result.isTaskReport ? parentReport : report, personalDetails, ReportUtils.getAvatar(personalDetail.avatar, personalDetail.login), true); + result.icons = ReportUtils.getIcons(result.isTaskReport ? parentReport : report, personalDetails, UserUtils.getAvatar(personalDetail.avatar, personalDetail.login), true); result.searchText = OptionsListUtils.getSearchText(report, reportName, participantPersonalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); result.displayNamesWithTooltips = displayNamesWithTooltips; return result; diff --git a/src/libs/UserUtils.js b/src/libs/UserUtils.js index 1abeef19cd84..e7d8235f0004 100644 --- a/src/libs/UserUtils.js +++ b/src/libs/UserUtils.js @@ -1,6 +1,9 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; import CONST from '../CONST'; +import hashCode from './hashCode'; +import * as Expensicons from '../components/Icon/Expensicons'; +import * as defaultAvatars from '../components/Icon/DefaultAvatars'; /** * Searches through given loginList for any contact method / login with an error. @@ -61,4 +64,153 @@ function getLoginListBrickRoadIndicator(loginList) { return ''; } -export {hasLoginListError, hasLoginListInfo, getLoginListBrickRoadIndicator}; +/** + * Hashes provided string and returns a value between [0, range) + * @param {String} login + * @param {Number} range + * @returns {Number} + */ +function hashLogin(login, range) { + return Math.abs(hashCode(login.toLowerCase())) % range; +} + +/** + * Helper method to return the default avatar associated with the given login + * @param {String} [login] + * @returns {String} + */ +function getDefaultAvatar(login = '') { + if (!login) { + return Expensicons.FallbackAvatar; + } + if (login === CONST.EMAIL.CONCIERGE) { + return Expensicons.ConciergeAvatar; + } + + // There are 24 possible default avatars, so we choose which one this user has based + // on a simple hash of their login. Note that Avatar count starts at 1. + const loginHashBucket = hashLogin(login, CONST.DEFAULT_AVATAR_COUNT) + 1; + + return defaultAvatars[`Avatar${loginHashBucket}`]; +} + +/** + * Helper method to return default avatar URL associated with login + * + * @param {String} [login] + * @param {Boolean} [isNewDot] + * @returns {String} + */ +function getDefaultAvatarURL(login = '', isNewDot = false) { + if (login === CONST.EMAIL.CONCIERGE) { + return CONST.CONCIERGE_ICON_URL; + } + + // The default avatar for a user is based on a simple hash of their login. + // Note that Avatar count starts at 1 which is why 1 has to be added to the result (or else 0 would result in a broken avatar link) + const loginHashBucket = hashLogin(login, isNewDot ? CONST.DEFAULT_AVATAR_COUNT : CONST.OLD_DEFAULT_AVATAR_COUNT) + 1; + const avatarPrefix = isNewDot ? `default-avatar` : `avatar`; + + return `${CONST.CLOUDFRONT_URL}/images/avatars/${avatarPrefix}_${loginHashBucket}.png`; +} + +/** + * Given a user's avatar path, returns true if user doesn't have an avatar or if URL points to a default avatar + * @param {String} [avatarURL] - the avatar source from user's personalDetails + * @returns {Boolean} + */ +function isDefaultAvatar(avatarURL) { + if ( + _.isString(avatarURL) && + (avatarURL.includes('images/avatars/avatar_') || avatarURL.includes('images/avatars/default-avatar_') || avatarURL.includes('images/avatars/user/default')) + ) { + return true; + } + + // If null URL, we should also use a default avatar + if (!avatarURL) { + return true; + } + return false; +} + +/** + * Provided a source URL, if source is a default avatar, return the associated SVG. + * Otherwise, return the URL pointing to a user-uploaded avatar. + * + * @param {String} avatarURL - the avatar source from user's personalDetails + * @param {String} login - the email of the user + * @returns {String|Function} + */ +function getAvatar(avatarURL, login) { + return isDefaultAvatar(avatarURL) ? getDefaultAvatar(login) : avatarURL; +} + +/** + * Provided an avatar URL, if avatar is a default avatar, return NewDot default avatar URL. + * Otherwise, return the URL pointing to a user-uploaded avatar. + * + * @param {String} avatarURL - the avatar source from user's personalDetails + * @param {String} login - the email of the user + * @returns {String} + */ +function getAvatarUrl(avatarURL, login) { + return isDefaultAvatar(avatarURL) ? getDefaultAvatarURL(login, true) : avatarURL; +} + +/** + * Avatars uploaded by users will have a _128 appended so that the asset server returns a small version. + * This removes that part of the URL so the full version of the image can load. + * + * @param {String} [avatarURL] + * @param {String} [login] + * @returns {String|Function} + */ +function getFullSizeAvatar(avatarURL, login) { + const source = getAvatar(avatarURL, login); + if (!_.isString(source)) { + return source; + } + return source.replace('_128', ''); +} + +/** + * Small sized avatars end with _128.. This adds the _128 at the end of the + * source URL (before the file type) if it doesn't exist there already. + * + * @param {String} avatarURL + * @param {String} login + * @returns {String|Function} + */ +function getSmallSizeAvatar(avatarURL, login) { + const source = getAvatar(avatarURL, login); + if (!_.isString(source)) { + return source; + } + + // Because other urls than CloudFront do not support dynamic image sizing (_SIZE suffix), the current source is already what we want to use here. + if (!CONST.CLOUDFRONT_DOMAIN_REGEX.test(source)) { + return source; + } + + // If image source already has _128 at the end, the given avatar URL is already what we want to use here. + const lastPeriodIndex = source.lastIndexOf('.'); + if (source.substring(lastPeriodIndex - 4, lastPeriodIndex) === '_128') { + return source; + } + return `${source.substring(0, lastPeriodIndex)}_128${source.substring(lastPeriodIndex)}`; +} + +export { + hashLogin, + hasLoginListError, + hasLoginListInfo, + getLoginListBrickRoadIndicator, + getDefaultAvatar, + getDefaultAvatarURL, + isDefaultAvatar, + getAvatar, + getAvatarUrl, + getSmallSizeAvatar, + getFullSizeAvatar, +}; diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js index c9c5889c914b..e1d983eecf95 100644 --- a/src/libs/actions/PersonalDetails.js +++ b/src/libs/actions/PersonalDetails.js @@ -5,7 +5,7 @@ import _ from 'underscore'; import ONYXKEYS from '../../ONYXKEYS'; import CONST from '../../CONST'; import * as API from '../API'; -import * as ReportUtils from '../ReportUtils'; +import * as UserUtils from '../UserUtils'; import * as LocalePhoneNumber from '../LocalePhoneNumber'; import ROUTES from '../../ROUTES'; import Navigation from '../Navigation/Navigation'; @@ -373,7 +373,7 @@ function updateAvatar(file) { */ function deleteAvatar() { // We want to use the old dot avatar here as this affects both platforms. - const defaultAvatar = ReportUtils.getOldDotDefaultAvatar(currentUserEmail); + const defaultAvatar = UserUtils.getDefaultAvatarURL(currentUserEmail); API.write( 'DeleteUserAvatar', diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index d9b134555815..da751e4209a8 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -9,6 +9,7 @@ import Navigation from '../Navigation/Navigation'; import ROUTES from '../../ROUTES'; import CONST from '../../CONST'; import DateUtils from '../DateUtils'; +import * as UserUtils from '../UserUtils'; /** * Clears out the task info from the store @@ -495,7 +496,7 @@ function getAssignee(details) { subtitle: '', }; } - const source = ReportUtils.getAvatar(lodashGet(details, 'avatar', ''), lodashGet(details, 'login', '')); + const source = UserUtils.getAvatar(lodashGet(details, 'avatar', ''), lodashGet(details, 'login', '')); return { icons: [{source, type: 'avatar', name: details.login}], displayName: details.displayName, diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js index 93bf382d7182..3c1f124aca1a 100755 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.js @@ -28,6 +28,7 @@ import * as Report from '../libs/actions/Report'; import OfflineWithFeedback from '../components/OfflineWithFeedback'; import AutoUpdateTime from '../components/AutoUpdateTime'; import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView'; +import * as UserUtils from '../libs/UserUtils'; const matchType = PropTypes.shape({ params: PropTypes.shape({ @@ -95,7 +96,7 @@ class DetailsPage extends React.PureComponent { details = { login, displayName: ReportUtils.getDisplayNameForParticipant(login), - avatar: ReportUtils.getAvatar(lodashGet(details, 'avatar', ''), login), + avatar: UserUtils.getAvatar(lodashGet(details, 'avatar', ''), login), }; } @@ -135,7 +136,7 @@ class DetailsPage extends React.PureComponent { @@ -148,7 +149,7 @@ class DetailsPage extends React.PureComponent { diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index b8f988f78eaf..81b4bb0ec2ab 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -20,6 +20,7 @@ import reportPropTypes from './reportPropTypes'; import withReportOrNotFound from './home/report/withReportOrNotFound'; import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView'; import CONST from '../CONST'; +import * as UserUtils from '../libs/UserUtils'; const propTypes = { /* Onyx Props */ @@ -65,7 +66,7 @@ const getAllParticipants = (report, personalDetails) => { displayName: userPersonalDetail.displayName, icons: [ { - source: ReportUtils.getAvatar(userPersonalDetail.avatar, login), + source: UserUtils.getAvatar(userPersonalDetail.avatar, login), name: login, type: CONST.ICON_TYPE_AVATAR, }, diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js index cec57c6e8aa9..d6c419b10882 100644 --- a/src/pages/ShareCodePage.js +++ b/src/pages/ShareCodePage.js @@ -17,6 +17,7 @@ import * as Expensicons from '../components/Icon/Expensicons'; import getPlatform from '../libs/getPlatform'; import CONST from '../CONST'; import ContextMenuItem from '../components/ContextMenuItem'; +import * as UserUtils from '../libs/UserUtils'; const propTypes = { /** The report currently being looked at */ @@ -60,7 +61,7 @@ class ShareCodePage extends React.Component { url={url} title={isReport ? this.props.report.reportName : this.props.currentUserPersonalDetails.displayName} subtitle={isReport ? subtitle : this.props.session.email} - logo={isReport ? roomAvatar : this.props.currentUserPersonalDetails.avatar} + logo={isReport ? roomAvatar : UserUtils.getAvatarUrl(this.props.currentUserPersonalDetails.avatar, this.props.currentUserPersonalDetails.login)} /> diff --git a/src/pages/home/report/ReactionList/BaseReactionList.js b/src/pages/home/report/ReactionList/BaseReactionList.js index d42a2284f22f..eda4f4db0f48 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.js +++ b/src/pages/home/report/ReactionList/BaseReactionList.js @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; import styles from '../../../../styles/styles'; import HeaderReactionList from './HeaderReactionList'; -import * as ReportUtils from '../../../../libs/ReportUtils'; +import * as UserUtils from '../../../../libs/UserUtils'; import CONST from '../../../../CONST'; import participantPropTypes from '../../../../components/participantPropTypes'; import reactionPropTypes from './reactionPropTypes'; @@ -87,7 +87,7 @@ const BaseReactionList = (props) => { participantsList: [item], icons: [ { - source: ReportUtils.getAvatar(item.avatar, item.login), + source: UserUtils.getAvatar(item.avatar, item.login), name: item.login, type: CONST.ICON_TYPE_AVATAR, }, diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 7f8b4237bbb3..3cbe52cd0e38 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -21,6 +21,7 @@ import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; import CONST from '../../../CONST'; import SubscriptAvatar from '../../../components/SubscriptAvatar'; import reportPropTypes from '../../reportPropTypes'; +import * as UserUtils from '../../../libs/UserUtils'; const propTypes = { /** All the data of the action */ @@ -63,7 +64,7 @@ const showUserDetails = (email) => { const ReportActionItemSingle = (props) => { const actorEmail = props.action.actorEmail.replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); const {avatar, displayName, pendingFields} = props.personalDetails[actorEmail] || {}; - const avatarSource = ReportUtils.getAvatar(avatar, actorEmail); + const avatarSource = UserUtils.getAvatar(avatar, actorEmail); // Since the display name for a report action message is delivered with the report history as an array of fragments // we'll need to take the displayName from personal details and have it be in the same format for now. Eventually, diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 4bf4279b51cf..912f63384034 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -21,7 +21,6 @@ import CONST from '../../../CONST'; import participantPropTypes from '../../../components/participantPropTypes'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import * as App from '../../../libs/actions/App'; -import * as ReportUtils from '../../../libs/ReportUtils'; import withCurrentUserPersonalDetails from '../../../components/withCurrentUserPersonalDetails'; import withWindowDimensions from '../../../components/withWindowDimensions'; import reportActionPropTypes from '../report/reportActionPropTypes'; @@ -34,6 +33,7 @@ import defaultTheme from '../../../styles/themes/default'; import OptionsListSkeletonView from '../../../components/OptionsListSkeletonView'; import variables from '../../../styles/variables'; import LogoComponent from '../../../../assets/images/expensify-wordmark.svg'; +import * as UserUtils from '../../../libs/UserUtils'; const propTypes = { /** Toggles the navigation menu open and closed */ @@ -178,7 +178,7 @@ class SidebarLinks extends React.Component { > @@ -256,7 +256,7 @@ const personalDetailsSelector = (personalDetails) => login: personalData.login, displayName: personalData.displayName, firstName: personalData.firstName, - avatar: ReportUtils.getAvatar(personalData.avatar, personalData.login), + avatar: UserUtils.getAvatar(personalData.avatar, personalData.login), }; return finalPersonalDetails; }, diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index ed3e4ba1f846..4e775bcd2cd2 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -332,7 +332,7 @@ class InitialSettingsPage extends React.Component { diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 1a216fd0aacb..d58cb4ceec3d 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -17,12 +17,11 @@ import CONST from '../../../CONST'; import * as PersonalDetails from '../../../libs/actions/PersonalDetails'; import compose from '../../../libs/compose'; import Navigation from '../../../libs/Navigation/Navigation'; -import * as ReportUtils from '../../../libs/ReportUtils'; +import * as UserUtils from '../../../libs/UserUtils'; import ROUTES from '../../../ROUTES'; import styles from '../../../styles/styles'; import * as Expensicons from '../../../components/Icon/Expensicons'; import ONYXKEYS from '../../../ONYXKEYS'; -import * as UserUtils from '../../../libs/UserUtils'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; const propTypes = { @@ -98,8 +97,8 @@ const ProfilePage = (props) => { onClose={PersonalDetails.clearAvatarErrors} > { <> diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index c9d6deab2b0e..b03b08ef76e5 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -28,7 +28,7 @@ import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import {withNetwork} from '../../components/OnyxProvider'; import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView'; import networkPropTypes from '../../components/networkPropTypes'; -import * as ReportUtils from '../../libs/ReportUtils'; +import * as UserUtils from '../../libs/UserUtils'; import FormHelpMessage from '../../components/FormHelpMessage'; import TextInput from '../../components/TextInput'; import KeyboardDismissingFlatList from '../../components/KeyboardDismissingFlatList'; @@ -344,7 +344,7 @@ class WorkspaceMembersPage extends React.Component { participantsList: [item], icons: [ { - source: ReportUtils.getAvatar(item.avatar, item.login), + source: UserUtils.getAvatar(item.avatar, item.login), name: item.login, type: CONST.ICON_TYPE_AVATAR, }, diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js index 586e5dcf0b31..cf7312b2580b 100644 --- a/src/styles/StyleUtils.js +++ b/src/styles/StyleUtils.js @@ -6,7 +6,7 @@ import variables from './variables'; import colors from './colors'; import positioning from './utilities/positioning'; import styles from './styles'; -import * as ReportUtils from '../libs/ReportUtils'; +import * as UserUtils from '../libs/UserUtils'; const workspaceColorOptions = [ {backgroundColor: colors.blue200, fill: colors.blue700}, @@ -162,7 +162,7 @@ function getAvatarBorderStyle(size, type) { * @returns {Object} */ function getDefaultWorkspaceAvatarColor(workspaceName) { - const colorHash = ReportUtils.hashLogin(workspaceName.trim(), workspaceColorOptions.length); + const colorHash = UserUtils.hashLogin(workspaceName.trim(), workspaceColorOptions.length); return workspaceColorOptions[colorHash]; }