Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix QR image show OldDot avatar for a moment after profile avatar being removed #19521

Merged
merged 17 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/components/AvatarWithIndicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -103,7 +102,7 @@ const AvatarWithIndicator = (props) => {
return (
<Tooltip text={props.tooltipText}>
<View style={[styles.sidebarAvatar]}>
<Avatar source={ReportUtils.getSmallSizeAvatar(props.source)} />
<Avatar source={UserUtils.getSmallSizeAvatar(props.source)} />
{(shouldShowErrorIndicator || shouldShowInfoIndicator) && <View style={StyleSheet.flatten(indicatorStyles)} />}
</View>
</Tooltip>
Expand Down
3 changes: 2 additions & 1 deletion src/components/MoneyRequestHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/components/TaskHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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;
Expand Down
13 changes: 7 additions & 6 deletions src/libs/OptionsListUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -181,7 +182,7 @@ function getPersonalDetailsForLogins(logins, personalDetails) {
personalDetail = {
login,
displayName: LocalePhoneNumber.formatPhoneNumber(login),
avatar: ReportUtils.getDefaultAvatar(login),
avatar: UserUtils.getDefaultAvatar(login),
};
}

Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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,
},
Expand Down
158 changes: 12 additions & 146 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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({
Expand Down Expand Up @@ -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]
Expand All @@ -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.<file-type>. 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.
Expand All @@ -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]);
}

Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -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,
};
Expand All @@ -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,
},
Expand All @@ -943,7 +816,7 @@ function getPersonalDetailsForLogin(login) {
(allPersonalDetails && allPersonalDetails[login]) || {
login,
displayName: LocalePhoneNumber.formatPhoneNumber(login),
avatar: getDefaultAvatar(login),
avatar: UserUtils.getDefaultAvatar(login),
}
);
}
Expand Down Expand Up @@ -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: [
{
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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: [
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -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,
};
Expand All @@ -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: [
{
Expand Down Expand Up @@ -2272,7 +2145,6 @@ export {
formatReportLastMessageText,
chatIncludesConcierge,
isPolicyExpenseChat,
getDefaultAvatar,
getIconsForParticipants,
getIcons,
getRoomWelcomeMessage,
Expand Down Expand Up @@ -2310,17 +2182,11 @@ export {
isTaskReport,
isMoneyRequestReport,
chatIncludesChronos,
getAvatar,
isDefaultAvatar,
getOldDotDefaultAvatar,
getNewMarkerReportActionID,
canSeeDefaultRoom,
hashLogin,
getDefaultWorkspaceAvatar,
getCommentLength,
getParsedComment,
getFullSizeAvatar,
getSmallSizeAvatar,
getMoneyRequestOptions,
canRequestMoney,
getWhisperDisplayNames,
Expand Down
Loading