Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Calculate badges in the new room list more reliably
Browse files Browse the repository at this point in the history
Fixes element-hq/element-web#13757

This uses a different way of calculating the badges by using about 6 less variables, and consolidating the remaining ones down.
  • Loading branch information
turt2live committed May 23, 2020
1 parent 696f158 commit a19e27d
Showing 1 changed file with 73 additions and 38 deletions.
111 changes: 73 additions & 38 deletions src/components/views/rooms/RoomTile2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ import * as FormattingUtils from "../../../utils/FormattingUtils";
* warning disappears. *
*******************************************************************/

enum NotificationColor {
// Inverted (None -> Red) because we do integer comparisons on this
None, // nothing special
Bold, // no badge, show as unread
Grey, // unread notified messages
Red, // unread pings
}

interface IProps {
room: Room;

Expand All @@ -47,16 +55,14 @@ interface IProps {
// TODO: Incoming call boxes?
}

interface IBadgeState {
showBadge: boolean; // if numUnread > 0 && !showBadge -> bold room
numUnread: number; // used only if showBadge or showBadgeHighlight is true
hasUnread: number; // used to make the room bold
showBadgeHighlight: boolean; // make the badge red
isInvite: boolean; // show a `!` instead of a number
interface INotificationState {
symbol: string;
color: NotificationColor;
}

interface IState extends IBadgeState {
interface IState {
hover: boolean;
notificationState: INotificationState;
}

export default class RoomTile2 extends React.Component<IProps, IState> {
Expand All @@ -78,37 +84,65 @@ export default class RoomTile2 extends React.Component<IProps, IState> {

this.state = {
hover: false,

...this.getBadgeState(),
notificationState: this.getNotificationState(),
};
}

public componentWillUnmount() {
// TODO: Listen for changes to the badge count and update as needed
}

private updateBadgeCount() {
this.setState({...this.getBadgeState()});
// XXX: This is a bit of an awful-looking hack. We should probably be using state for
// this, but instead we're kinda forced to either duplicate the code or thread a variable
// through the code paths. This feels like the least evil
private get roomIsInvite(): boolean {
return getEffectiveMembership(this.props.room.getMyMembership()) === EffectiveMembership.Invite;
}

private updateNotificationState() {
this.setState({notificationState: this.getNotificationState()});
}

private getBadgeState(): IBadgeState {
// TODO: Make this code path faster
const highlightCount = RoomNotifs.getUnreadNotificationCount(this.props.room, 'highlight');
const numUnread = RoomNotifs.getUnreadNotificationCount(this.props.room);
const showBadge = Unread.doesRoomHaveUnreadMessages(this.props.room);
const myMembership = getEffectiveMembership(this.props.room.getMyMembership());
const isInvite = myMembership === EffectiveMembership.Invite;
const notifState = RoomNotifs.getRoomNotifsState(this.props.room.roomId);
const shouldShowNotifBadge = RoomNotifs.shouldShowNotifBadge(notifState);
const shouldShowHighlightBadge = RoomNotifs.shouldShowMentionBadge(notifState);

return {
showBadge: (showBadge && shouldShowNotifBadge) || isInvite,
numUnread,
hasUnread: showBadge,
showBadgeHighlight: (highlightCount > 0 && shouldShowHighlightBadge) || isInvite,
isInvite,
private getNotificationState(): INotificationState {
const state: INotificationState = {
color: NotificationColor.None,
symbol: null,
};

if (this.roomIsInvite) {
state.color = NotificationColor.Red;
state.symbol = "!";
} else {
const redNotifs = RoomNotifs.getUnreadNotificationCount(this.props.room, 'highlight');
const greyNotifs = RoomNotifs.getUnreadNotificationCount(this.props.room, 'total');

// For a 'true count' we pick the grey notifications first because they include the
// red notifications. If we don't have a grey count for some reason we use the red
// count. If that count is broken for some reason, assume zero. This avoids us showing
// a badge for 'NaN' (which formats as 'NaNB' for NaN Billion).
const trueCount = greyNotifs ? greyNotifs : (redNotifs ? redNotifs : 0);

// Note: we only set the symbol if we have an actual count. We don't want to show
// zero on badges.

if (redNotifs > 0) {
state.color = NotificationColor.Red;
state.symbol = FormattingUtils.formatCount(trueCount);
} else if (greyNotifs > 0) {
state.color = NotificationColor.Grey;
state.symbol = FormattingUtils.formatCount(trueCount);
} else {
// We don't have any notified messages, but we might have unread messages. Let's
// find out.
const hasUnread = Unread.doesRoomHaveUnreadMessages(this.props.room);
if (hasUnread) {
state.color = NotificationColor.Bold;
// no symbol for this state
}
}
}

return state;
}

private onTileMouseEnter = () => {
Expand All @@ -135,15 +169,17 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
// TODO: a11y proper
// TODO: Render more than bare minimum

const hasBadge = this.state.notificationState.color > NotificationColor.Bold;
const isUnread = this.state.notificationState.color > NotificationColor.None;
const classes = classNames({
'mx_RoomTile': true,
// 'mx_RoomTile_selected': this.state.selected,
'mx_RoomTile_unread': this.state.numUnread > 0 || this.state.hasUnread,
'mx_RoomTile_unreadNotify': this.state.showBadge,
'mx_RoomTile_highlight': this.state.showBadgeHighlight,
'mx_RoomTile_invited': this.state.isInvite,
'mx_RoomTile_unread': isUnread,
'mx_RoomTile_unreadNotify': this.state.notificationState.color >= NotificationColor.Grey,
'mx_RoomTile_highlight': this.state.notificationState.color >= NotificationColor.Red,
'mx_RoomTile_invited': this.roomIsInvite,
// 'mx_RoomTile_menuDisplayed': isMenuDisplayed,
'mx_RoomTile_noBadges': !this.state.showBadge,
'mx_RoomTile_noBadges': !hasBadge,
// 'mx_RoomTile_transparent': this.props.transparent,
// 'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
});
Expand All @@ -154,13 +190,12 @@ export default class RoomTile2 extends React.Component<IProps, IState> {


let badge;
if (this.state.showBadge) {
if (hasBadge) {
const badgeClasses = classNames({
'mx_RoomTile_badge': true,
'mx_RoomTile_badgeButton': false, // this.state.badgeHover || isMenuDisplayed
});
const formattedCount = this.state.isInvite ? `!` : FormattingUtils.formatCount(this.state.numUnread);
badge = <div className={badgeClasses}>{formattedCount}</div>;
badge = <div className={badgeClasses}>{this.state.notificationState.symbol}</div>;
}

// TODO: the original RoomTile uses state for the room name. Do we need to?
Expand All @@ -170,8 +205,8 @@ export default class RoomTile2 extends React.Component<IProps, IState> {

const nameClasses = classNames({
'mx_RoomTile_name': true,
'mx_RoomTile_invite': this.state.isInvite,
'mx_RoomTile_badgeShown': this.state.showBadge,
'mx_RoomTile_invite': this.roomIsInvite,
'mx_RoomTile_badgeShown': hasBadge,
});

// TODO: Support collapsed state properly
Expand Down

0 comments on commit a19e27d

Please sign in to comment.