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 "user is typing..." indicator - key by accountID #21029

Merged
merged 10 commits into from
Jun 19, 2023
1 change: 1 addition & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export default {
mi: 'mile',
km: 'kilometer',
copied: 'Copied!',
someone: 'Someone',
},
anonymousReportFooter: {
logoTagline: 'Join in on the discussion.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export default {
mi: 'milla',
km: 'kilómetro',
copied: '¡Copiado!',
someone: 'Alguien',
},
anonymousReportFooter: {
logoTagline: 'Únete a la discussion.',
Expand Down
34 changes: 28 additions & 6 deletions src/libs/actions/PersonalDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ Onyx.connect({
},
});

let personalDetails;
let allPersonalDetails;
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (val) => (personalDetails = val),
callback: (val) => (allPersonalDetails = val),
});

/**
Expand All @@ -37,7 +37,7 @@ function getDisplayName(login, personalDetail) {
// If we have a number like [email protected] then let's remove @expensify.sms and format it
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
// so that the option looks cleaner in our UI.
const userLogin = LocalePhoneNumber.formatPhoneNumber(login);
const userDetails = personalDetail || lodashGet(personalDetails, login);
const userDetails = personalDetail || lodashGet(allPersonalDetails, login);

if (!userDetails) {
return userLogin;
Expand All @@ -50,6 +50,27 @@ function getDisplayName(login, personalDetail) {
return fullName || userLogin;
}

/**
* @param {String} userAccountIDOrLogin
* @param {String} [defaultDisplayName] display name to use if user details don't exist in Onyx or if
* found details don't include the user's displayName or login
* @returns {String}
*/
function getDisplayNameForTypingIndicator(userAccountIDOrLogin, defaultDisplayName = '') {
// Try to convert to a number, which means we have an accountID
const accountID = Number(userAccountIDOrLogin);
marcaaron marked this conversation as resolved.
Show resolved Hide resolved

// If the user is typing on OldDot, userAccountIDOrLogin will be a string (the user's login),
// so Number(string) is NaN. Search for personalDetails by login to get the display name.
if (_.isNaN(accountID)) {
const detailsByLogin = _.findWhere(allPersonalDetails, {login: userAccountIDOrLogin}) || {};
return detailsByLogin.displayName || userAccountIDOrLogin;
}

const detailsByAccountID = lodashGet(allPersonalDetails, accountID, {});
return detailsByAccountID.displayName || detailsByAccountID.login || defaultDisplayName;
}

/**
* Gets the first and last name from the user's personal details.
* If the login is the same as the displayName, then they don't exist,
Expand Down Expand Up @@ -402,8 +423,8 @@ function updateAvatar(file) {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
value: {
[currentUserAccountID]: {
avatar: personalDetails[currentUserAccountID].avatar,
avatarThumbnail: personalDetails[currentUserAccountID].avatarThumbnail || personalDetails[currentUserAccountID].avatar,
avatar: allPersonalDetails[currentUserAccountID].avatar,
avatarThumbnail: allPersonalDetails[currentUserAccountID].avatarThumbnail || allPersonalDetails[currentUserAccountID].avatar,
pendingFields: {
avatar: null,
},
Expand Down Expand Up @@ -439,7 +460,7 @@ function deleteAvatar() {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
value: {
[currentUserAccountID]: {
avatar: personalDetails[currentUserAccountID].avatar,
avatar: allPersonalDetails[currentUserAccountID].avatar,
},
},
},
Expand All @@ -466,6 +487,7 @@ function clearAvatarErrors() {

export {
getDisplayName,
getDisplayNameForTypingIndicator,
updateAvatar,
deleteAvatar,
openMoneyRequestModalPage,
Expand Down
19 changes: 11 additions & 8 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,27 +117,30 @@ function subscribeToReportTypingEvents(reportID) {

const pusherChannelName = getReportChannelName(reportID);
Pusher.subscribe(pusherChannelName, Pusher.TYPE.USER_IS_TYPING, (typingStatus) => {
// If the pusher message comes from OldDot, we expect the typing status to be keyed by user
// login OR by 'Concierge'. If the pusher message comes from NewDot, it is keyed by accountID
// since personal details are keyed by accountID.
const normalizedTypingStatus = getNormalizedTypingStatus(typingStatus);
const login = _.first(_.keys(normalizedTypingStatus));
const accountIDOrLogin = _.first(_.keys(normalizedTypingStatus));

if (!login) {
if (!accountIDOrLogin) {
return;
}

// Don't show the typing indicator if a user is typing on another platform
if (login === currentUserEmail) {
// Don't show the typing indicator if the user is typing on another platform
if (Number(accountIDOrLogin) === currentUserAccountID) {
return;
}

// Use a combo of the reportID and the login as a key for holding our timers.
const reportUserIdentifier = `${reportID}-${login}`;
// Use a combo of the reportID and the accountID or login as a key for holding our timers.
const reportUserIdentifier = `${reportID}-${accountIDOrLogin}`;
clearTimeout(typingWatchTimers[reportUserIdentifier]);
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`, normalizedTypingStatus);

// Wait for 1.5s of no additional typing events before setting the status back to false.
typingWatchTimers[reportUserIdentifier] = setTimeout(() => {
const typingStoppedStatus = {};
typingStoppedStatus[login] = false;
typingStoppedStatus[accountIDOrLogin] = false;
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`, typingStoppedStatus);
delete typingWatchTimers[reportUserIdentifier];
}, 1500);
Expand Down Expand Up @@ -747,7 +750,7 @@ function setReportWithDraft(reportID, hasDraft) {
function broadcastUserIsTyping(reportID) {
const privateReportChannelName = getReportChannelName(reportID);
const typingStatus = {};
typingStatus[currentUserEmail] = true;
typingStatus[currentUserAccountID] = true;
Pusher.sendEvent(privateReportChannelName, Pusher.TYPE.USER_IS_TYPING, typingStatus);
}

Expand Down
6 changes: 3 additions & 3 deletions src/pages/home/report/ReportTypingIndicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Text from '../../../components/Text';
import TextWithEllipsis from '../../../components/TextWithEllipsis';

const propTypes = {
/** Key-value pairs of user logins and whether or not they are typing. Keys are logins. */
/** Key-value pairs of user accountIDs/logins and whether or not they are typing. Keys are accountIDs or logins. */
userTypingStatuses: PropTypes.objectOf(PropTypes.bool),

/** Information about the network */
Expand All @@ -27,7 +27,7 @@ const defaultProps = {
};

function ReportTypingIndicator(props) {
const usersTyping = useMemo(() => _.filter(_.keys(props.userTypingStatuses), (login) => props.userTypingStatuses[login]), [props.userTypingStatuses]);
const usersTyping = useMemo(() => _.filter(_.keys(props.userTypingStatuses), (loginOrAccountID) => props.userTypingStatuses[loginOrAccountID]), [props.userTypingStatuses]);
// If we are offline, the user typing statuses are not up-to-date so do not show them
if (props.network.isOffline) {
return null;
Expand All @@ -43,7 +43,7 @@ function ReportTypingIndicator(props) {
case 1:
return (
<TextWithEllipsis
leadingText={PersonalDetails.getDisplayName(usersTyping[0])}
leadingText={PersonalDetails.getDisplayNameForTypingIndicator(usersTyping[0], props.translate('common.someone'))}
trailingText={` ${props.translate('reportTypingIndicator.isTyping')}`}
textStyle={[styles.chatItemComposeSecondaryRowSubText]}
wrapperStyle={[styles.chatItemComposeSecondaryRow, styles.flex1]}
Expand Down