Mute conversations

This commit is contained in:
Josh Perez 2020-08-27 15:45:08 -04:00 committed by Josh Perez
parent de7a69dee9
commit 84e52c948b
12 changed files with 185 additions and 3 deletions

View file

@ -2745,5 +2745,39 @@
"callingDeviceSelection__select--no-device": {
"message": "No devices available",
"description": "Message for when there are no available devices to select for input/output audio or video"
},
"muteNotificationsTitle": {
"message": "Mute notifications",
"description": "Label for the mute notifications drop-down selector"
},
"muteHour": {
"message": "Mute for one hour",
"description": "Label for muting the conversation"
},
"muteDay": {
"message": "Mute for one day",
"description": "Label for muting the conversation"
},
"muteWeek": {
"message": "Mute for one week",
"description": "Label for muting the conversation"
},
"muteYear": {
"message": "Mute for one year",
"description": "Label for muting the conversation"
},
"unmute": {
"message": "Unmute",
"description": "Label for unmuting the conversation"
},
"muteExpirationLabel": {
"message": "Muted until $duration$",
"description": "Shown in the mute notifications submenu whenever a conversation has been muted",
"placeholders": {
"duration": {
"content": "$1",
"example": "10/23/2023, 7:10 PM"
}
}
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>sound-off-outline-24</title><path d="M20.061,12l2.469,2.47-1.06,1.06L19,13.061,16.53,15.53l-1.06-1.06L17.939,12,15.47,9.53l1.06-1.06L19,10.939,21.47,8.47l1.06,1.06ZM13.5,2.636a.494.494,0,0,0-.335.132L8,7.5H4a2,2,0,0,0-2,2v5a2,2,0,0,0,2,2H8l5.162,4.732a.494.494,0,0,0,.335.132.5.5,0,0,0,.5-.5V3.137A.5.5,0,0,0,13.5,2.636ZM12.75,19.25l-1.41-1.723L8.583,15H4a.5.5,0,0,1-.5-.5v-5A.5.5,0,0,1,4,9H8.583L11.34,6.473,12.75,4.75,12.5,7.5v9Z"/></svg>

After

Width:  |  Height:  |  Size: 530 B

View file

@ -499,6 +499,7 @@
? undefined
: (this.get('members') || []).length,
messageRequestsEnabled,
muteExpiresAt: this.get('muteExpiresAt'),
name: this.get('name'),
phoneNumber: this.getNumber(),
profileName: this.getProfileName(),
@ -2844,6 +2845,10 @@
},
async notify(message, reaction) {
if (this.get('muteExpiresAt') && Date.now() < this.get('muteExpiresAt')) {
return;
}
if (!message.isIncoming() && !reaction) {
return;
}

View file

@ -357,6 +357,24 @@
paste: 'onPaste',
},
getMuteExpirationLabel() {
const muteExpiresAt = this.model.get('muteExpiresAt');
if (!muteExpiresAt) {
return;
}
const today = window.moment(Date.now());
const expires = window.moment(muteExpiresAt);
if (today.isSame(expires, 'day')) {
// eslint-disable-next-line consistent-return
return expires.format('hh:mm A');
}
// eslint-disable-next-line consistent-return
return expires.format('M/D/YY, hh:mm A');
},
setupHeader() {
const getHeaderProps = () => {
const expireTimer = this.model.get('expireTimer');
@ -376,6 +394,8 @@
value: item.get('seconds'),
})),
muteExpirationLabel: this.getMuteExpirationLabel(),
onSetDisappearingMessages: seconds =>
this.setDisappearingMessages(seconds),
onDeleteMessages: () => this.destroyMessages(),
@ -387,6 +407,7 @@
: this.model.getTitle();
searchInConversation(this.model.id, name);
},
onSetMuteNotifications: ms => this.setMuteNotifications(ms),
// These are view only and don't update the Conversation model, so they
// need a manual update call.
@ -2485,6 +2506,12 @@
}
},
setMuteNotifications(ms) {
this.model.set({
muteExpiresAt: ms > 0 ? Date.now() + ms : undefined,
});
},
async destroyMessages() {
try {
await this.confirm(i18n('deleteConversationConfirmation'));

View file

@ -3621,6 +3621,27 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
}
}
.module-conversation-list-item__muted {
display: inline-block;
height: 14px;
margin-right: 4px;
vertical-align: middle;
width: 14px;
@include light-theme {
@include color-svg(
'../images/icons/v2/sound-off-outline-24.svg',
$color-gray-60
);
}
@include dark-theme {
@include color-svg(
'../images/icons/v2/sound-off-outline-24.svg',
$color-gray-25
);
}
}
.module-conversation-list-item--has-unread {
padding-left: 12px;

View file

@ -246,3 +246,10 @@ story.add('Missing Text', () => {
/>
);
});
story.add('Muted Conversation', () => {
const props = createProps();
const muteExpiresAt = Date.now() + 1000 * 60 * 60;
return <ConversationListItem {...props} muteExpiresAt={muteExpiresAt} />;
});

View file

@ -33,6 +33,7 @@ export type PropsData = {
type: 'group' | 'direct';
avatarPath?: string;
isMe?: boolean;
muteExpiresAt?: number;
lastUpdated: number;
unreadCount?: number;
@ -167,6 +168,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
i18n,
isAccepted,
lastMessage,
muteExpiresAt,
shouldShowDraft,
typingContact,
unreadCount,
@ -201,6 +203,9 @@ export class ConversationListItem extends React.PureComponent<Props> {
: null
)}
>
{muteExpiresAt && (
<span className="module-conversation-list-item__muted" />
)}
{!isAccepted ? (
<span className="module-conversation-list-item__message-request">
{i18n('ConversationListItem--message-request')}

View file

@ -34,6 +34,7 @@ const actionProps: PropsActionsType = {
onDeleteMessages: action('onDeleteMessages'),
onResetSession: action('onResetSession'),
onSearchInConversation: action('onSearchInConversation'),
onSetMuteNotifications: action('onSetMuteNotifications'),
onOutgoingAudioCallInConversation: action(
'onOutgoingAudioCallInConversation'
),
@ -172,6 +173,20 @@ const stories: Array<ConversationHeaderStory> = [
...housekeepingProps,
},
},
{
title: 'Muting Conversation',
props: {
color: 'ultramarine',
title: '(202) 555-0006',
phoneNumber: '(202) 555-0006',
type: 'direct',
id: '6',
muteExpirationLabel: '10/18/3000, 11:11 AM',
isAccepted: true,
...actionProps,
...housekeepingProps,
},
},
],
},
{

View file

@ -13,6 +13,7 @@ import { InContactsIcon } from '../InContactsIcon';
import { LocalizerType } from '../../types/Util';
import { ColorType } from '../../types/Colors';
import { getMuteOptions } from '../../util/getMuteOptions';
interface TimerOption {
name: string;
@ -37,11 +38,13 @@ export interface PropsDataType {
leftGroup?: boolean;
expirationSettingName?: string;
muteExpirationLabel?: string;
showBackButton?: boolean;
timerOptions?: Array<TimerOption>;
}
export interface PropsActionsType {
onSetMuteNotifications: (seconds: number) => void;
onSetDisappearingMessages: (seconds: number) => void;
onDeleteMessages: () => void;
onResetSession: () => void;
@ -289,9 +292,11 @@ export class ConversationHeader extends React.Component<PropsType> {
type,
isArchived,
leftGroup,
muteExpirationLabel,
onDeleteMessages,
onResetSession,
onSetDisappearingMessages,
onSetMuteNotifications,
onShowAllMedia,
onShowGroupMembers,
onShowSafetyNumber,
@ -300,7 +305,26 @@ export class ConversationHeader extends React.Component<PropsType> {
timerOptions,
} = this.props;
const muteOptions = [];
if (muteExpirationLabel) {
muteOptions.push(
...[
{
name: i18n('muteExpirationLabel', [muteExpirationLabel]),
disabled: true,
value: 0,
},
{
name: i18n('unmute'),
value: 0,
},
]
);
}
muteOptions.push(...getMuteOptions(i18n));
const disappearingTitle = i18n('disappearingMessages') as any;
const muteTitle = i18n('muteNotificationsTitle') as any;
const isGroup = type === 'group';
return (
@ -319,6 +343,19 @@ export class ConversationHeader extends React.Component<PropsType> {
))}
</SubMenu>
) : null}
<SubMenu title={muteTitle}>
{muteOptions.map(item => (
<MenuItem
key={item.name}
disabled={item.disabled}
onClick={() => {
onSetMuteNotifications(item.value);
}}
>
{item.name}
</MenuItem>
))}
</SubMenu>
<MenuItem onClick={onShowAllMedia}>{i18n('viewRecentMedia')}</MenuItem>
{isGroup ? (
<MenuItem onClick={onShowGroupMembers}>

View file

@ -49,6 +49,7 @@ export type ConversationType = {
};
phoneNumber?: string;
membersCount?: number;
muteExpiresAt?: number;
type: 'direct' | 'group';
isMe?: boolean;
lastUpdated: number;

29
ts/util/getMuteOptions.ts Normal file
View file

@ -0,0 +1,29 @@
import moment from 'moment';
import { LocalizerType } from '../types/Util';
type MuteOption = {
name: string;
disabled?: boolean;
value: number;
};
export function getMuteOptions(i18n: LocalizerType): Array<MuteOption> {
return [
{
name: i18n('muteHour'),
value: moment.duration(1, 'hour').as('milliseconds'),
},
{
name: i18n('muteDay'),
value: moment.duration(1, 'day').as('milliseconds'),
},
{
name: i18n('muteWeek'),
value: moment.duration(1, 'week').as('milliseconds'),
},
{
name: i18n('muteYear'),
value: moment.duration(1, 'year').as('milliseconds'),
},
];
}

View file

@ -223,7 +223,7 @@
"rule": "jQuery-wrap(",
"path": "js/models/conversations.js",
"line": " await wrap(",
"lineNumber": 673,
"lineNumber": 674,
"reasonCategory": "falseMatch",
"updated": "2020-06-09T20:26:46.515Z"
},
@ -10634,7 +10634,7 @@
"rule": "React-createRef",
"path": "ts/components/conversation/ConversationHeader.js",
"line": " this.menuTriggerRef = react_1.default.createRef();",
"lineNumber": 15,
"lineNumber": 16,
"reasonCategory": "usageTrusted",
"updated": "2019-07-31T00:19:18.696Z",
"reasonDetail": "Used to reference popup menu"
@ -10643,7 +10643,7 @@
"rule": "React-createRef",
"path": "ts/components/conversation/ConversationHeader.tsx",
"line": " this.menuTriggerRef = React.createRef();",
"lineNumber": 76,
"lineNumber": 79,
"reasonCategory": "usageTrusted",
"updated": "2020-05-20T20:10:43.540Z",
"reasonDetail": "Used to reference popup menu"