diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 68b1b601d..cec91c6f5 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -3628,5 +3628,9 @@ "CompositionArea--attach-file": { "message": "Attach file", "description": "Aria label for file attachment button in composition area" + }, + "countMutedConversationsDescription": { + "message": "Count muted conversations in badge count", + "description": "Description for counting muted conversations in badge setting" } } diff --git a/js/background.js b/js/background.js index 76ae4bcf5..9737bcf68 100644 --- a/js/background.js +++ b/js/background.js @@ -381,6 +381,12 @@ storage.put('notification-draw-attention', value), getAudioNotification: () => storage.get('audio-notification'), setAudioNotification: value => storage.put('audio-notification', value), + getCountMutedConversations: () => + storage.get('badge-count-muted-conversations', false), + setCountMutedConversations: value => { + storage.put('badge-count-muted-conversations', value); + window.Whisper.events.trigger('updateUnreadCount'); + }, getCallRingtoneNotification: () => storage.get('call-ringtone-notification', true), setCallRingtoneNotification: value => diff --git a/js/models/conversations.js b/js/models/conversations.js index 2e9805215..63ce67613 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -3107,8 +3107,14 @@ }); }, + isMuted() { + return ( + this.get('muteExpiresAt') && Date.now() < this.get('muteExpiresAt') + ); + }, + async notify(message, reaction) { - if (this.get('muteExpiresAt') && Date.now() < this.get('muteExpiresAt')) { + if (this.isMuted()) { return; } diff --git a/js/settings_start.js b/js/settings_start.js index d40283fbd..320e124db 100644 --- a/js/settings_start.js +++ b/js/settings_start.js @@ -30,6 +30,7 @@ const getInitialData = async () => ({ notificationSetting: await window.getNotificationSetting(), audioNotification: await window.getAudioNotification(), notificationDrawAttention: await window.getNotificationDrawAttention(), + countMutedConversations: await window.getCountMutedConversations(), spellCheck: await window.getSpellCheck(), diff --git a/js/views/settings_view.js b/js/views/settings_view.js index d76f28cd9..e0bf507a5 100644 --- a/js/views/settings_view.js +++ b/js/views/settings_view.js @@ -127,6 +127,12 @@ setFn: window.setAudioNotification, }); } + new CheckboxView({ + el: this.$('.badge-count-muted-conversations-setting'), + name: 'badge-count-muted-conversations-setting', + value: window.initialData.countMutedConversations, + setFn: window.setCountMutedConversations, + }); new CheckboxView({ el: this.$('.spell-check-setting'), name: 'spell-check-setting', @@ -224,6 +230,9 @@ clearDataButton: i18n('clearDataButton'), clearDataExplanation: i18n('clearDataExplanation'), calling: i18n('calling'), + countMutedConversationsDescription: i18n( + 'countMutedConversationsDescription' + ), alwaysRelayCallsDescription: i18n('alwaysRelayCallsDescription'), alwaysRelayCallsDetail: i18n('alwaysRelayCallsDetail'), callRingtoneNotificationDescription: i18n( diff --git a/libtextsecure/test/_test.js b/libtextsecure/test/_test.js index f5d68d555..b5d9b1d08 100644 --- a/libtextsecure/test/_test.js +++ b/libtextsecure/test/_test.js @@ -57,3 +57,9 @@ window.hexToArrayBuffer = str => { }; window.MockSocket.prototype.addEventListener = () => null; + +window.Whisper = window.Whisper || {}; +window.Whisper.events = { + on() {}, + trigger() {}, +}; diff --git a/main.js b/main.js index 1d7ea6948..eb2d11b6d 100644 --- a/main.js +++ b/main.js @@ -1262,6 +1262,8 @@ installSettingsGetter('notification-draw-attention'); installSettingsSetter('notification-draw-attention'); installSettingsGetter('audio-notification'); installSettingsSetter('audio-notification'); +installSettingsGetter('badge-count-muted-conversations'); +installSettingsSetter('badge-count-muted-conversations'); installSettingsGetter('spell-check'); installSettingsSetter('spell-check'); diff --git a/preload.js b/preload.js index d3dec89d2..ee7a28439 100644 --- a/preload.js +++ b/preload.js @@ -163,6 +163,29 @@ try { installSetter('notification-draw-attention', 'setNotificationDrawAttention'); installGetter('audio-notification', 'getAudioNotification'); installSetter('audio-notification', 'setAudioNotification'); + installGetter( + 'badge-count-muted-conversations', + 'getCountMutedConversations' + ); + installSetter( + 'badge-count-muted-conversations', + 'setCountMutedConversations' + ); + + window.getCountMutedConversations = () => + new Promise((resolve, reject) => { + ipc.once( + 'get-success-badge-count-muted-conversations', + (_event, error, value) => { + if (error) { + return reject(new Error(error)); + } + + return resolve(value); + } + ); + ipc.send('get-badge-count-muted-conversations'); + }); installGetter('spell-check', 'getSpellCheck'); installSetter('spell-check', 'setSpellCheck'); diff --git a/settings.html b/settings.html index ac1dd0323..958b416d2 100644 --- a/settings.html +++ b/settings.html @@ -102,6 +102,10 @@ {{ /isAudioNotificationSupported }} +
+ + +

{{ generalHeader }}

diff --git a/settings_preload.js b/settings_preload.js index 919160c99..cce0b20e2 100644 --- a/settings_preload.js +++ b/settings_preload.js @@ -70,6 +70,12 @@ window.getCallSystemNotification = makeGetter('call-system-notification'); window.setCallSystemNotification = makeSetter('call-system-notification'); window.getIncomingCallNotification = makeGetter('incoming-call-notification'); window.setIncomingCallNotification = makeSetter('incoming-call-notification'); +window.getCountMutedConversations = makeGetter( + 'badge-count-muted-conversations' +); +window.setCountMutedConversations = makeSetter( + 'badge-count-muted-conversations' +); window.getMediaPermissions = makeGetter('media-permissions'); window.setMediaPermissions = makeSetter('media-permissions'); diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts index c48c3cfb7..12d3be636 100644 --- a/ts/ConversationController.ts +++ b/ts/ConversationController.ts @@ -32,10 +32,13 @@ export function start(): void { this.listenTo(conversations, 'add change:active_at', this.addActive); this.listenTo(conversations, 'reset', () => this.reset([])); - this.on( - 'add remove change:unreadCount', - debounce(this.updateUnreadCount.bind(this), 1000) + const debouncedUpdateUnreadCount = debounce( + this.updateUnreadCount.bind(this), + 1000 ); + + this.on('add remove change:unreadCount', debouncedUpdateUnreadCount); + window.Whisper.events.on('updateUnreadCount', debouncedUpdateUnreadCount); }, addActive(model: ConversationModelType) { if (model.get('active_at')) { @@ -45,8 +48,13 @@ export function start(): void { } }, updateUnreadCount() { + const canCountMutedConversations = window.storage.get( + 'badge-count-muted-conversations' + ); const newUnreadCount = reduce( - this.map((m: ConversationModelType) => m.get('unreadCount')), + this.map((m: ConversationModelType) => + !canCountMutedConversations && m.isMuted() ? 0 : m.get('unreadCount') + ), (item: number, memo: number) => (item || 0) + memo, 0 ); diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index 9a76166c8..de58f4982 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -178,6 +178,7 @@ export declare class ConversationModelType extends Backbone.Model< isFromOrAddedByTrustedContact(): boolean; isBlocked(): boolean; isMe(): boolean; + isMuted(): boolean; isPrivate(): boolean; isVerified(): boolean; maybeRepairGroupV2(data: { diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index ae387890b..214083fb1 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -308,7 +308,7 @@ "rule": "jQuery-appendTo(", "path": "js/settings_start.js", "line": " window.view.$el.appendTo($body);", - "lineNumber": 55, + "lineNumber": 56, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Interacting with already-existing DOM nodes" @@ -994,17 +994,26 @@ { "rule": "jQuery-$(", "path": "js/views/settings_view.js", - "line": " el: this.$('.spell-check-setting'),", + "line": " el: this.$('.badge-count-muted-conversations-setting'),", "lineNumber": 131, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Protected from arbitrary input" }, + { + "rule": "jQuery-$(", + "path": "js/views/settings_view.js", + "line": " el: this.$('.spell-check-setting'),", + "lineNumber": 137, + "reasonCategory": "usageTrusted", + "updated": "2020-08-21T11:29:29.636Z", + "reasonDetail": "Protected from arbitrary input" + }, { "rule": "jQuery-$(", "path": "js/views/settings_view.js", "line": " const $msg = this.$('.spell-check-setting-message');", - "lineNumber": 135, + "lineNumber": 141, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Protected from arbitrary input" @@ -1013,7 +1022,7 @@ "rule": "jQuery-$(", "path": "js/views/settings_view.js", "line": " el: this.$('.menu-bar-setting'),", - "lineNumber": 148, + "lineNumber": 154, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Protected from arbitrary input" @@ -1022,15 +1031,6 @@ "rule": "jQuery-$(", "path": "js/views/settings_view.js", "line": " el: this.$('.always-relay-calls-setting'),", - "lineNumber": 155, - "reasonCategory": "usageTrusted", - "updated": "2020-08-21T11:29:29.636Z", - "reasonDetail": "Protected from arbitrary input" - }, - { - "rule": "jQuery-$(", - "path": "js/views/settings_view.js", - "line": " el: this.$('.call-ringtone-notification-setting'),", "lineNumber": 161, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", @@ -1039,7 +1039,7 @@ { "rule": "jQuery-$(", "path": "js/views/settings_view.js", - "line": " el: this.$('.call-system-notification-setting'),", + "line": " el: this.$('.call-ringtone-notification-setting'),", "lineNumber": 167, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", @@ -1048,7 +1048,7 @@ { "rule": "jQuery-$(", "path": "js/views/settings_view.js", - "line": " el: this.$('.incoming-call-notification-setting'),", + "line": " el: this.$('.call-system-notification-setting'),", "lineNumber": 173, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", @@ -1057,17 +1057,26 @@ { "rule": "jQuery-$(", "path": "js/views/settings_view.js", - "line": " el: this.$('.media-permissions'),", + "line": " el: this.$('.incoming-call-notification-setting'),", "lineNumber": 179, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Protected from arbitrary input" }, + { + "rule": "jQuery-$(", + "path": "js/views/settings_view.js", + "line": " el: this.$('.media-permissions'),", + "lineNumber": 185, + "reasonCategory": "usageTrusted", + "updated": "2020-08-21T11:29:29.636Z", + "reasonDetail": "Protected from arbitrary input" + }, { "rule": "jQuery-$(", "path": "js/views/settings_view.js", "line": " el: this.$('.media-camera-permissions'),", - "lineNumber": 184, + "lineNumber": 190, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Protected from arbitrary input" @@ -1076,7 +1085,7 @@ "rule": "jQuery-$(", "path": "js/views/settings_view.js", "line": " this.$('.sync-setting').append(syncView.el);", - "lineNumber": 190, + "lineNumber": 196, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Protected from arbitrary input" @@ -1085,7 +1094,7 @@ "rule": "jQuery-append(", "path": "js/views/settings_view.js", "line": " this.$('.sync-setting').append(syncView.el);", - "lineNumber": 190, + "lineNumber": 196, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Interacting with already-existing DOM nodes" @@ -1094,7 +1103,7 @@ "rule": "jQuery-$(", "path": "js/views/settings_view.js", "line": " this.$('.sync').text(i18n('syncNow'));", - "lineNumber": 271, + "lineNumber": 280, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Protected from arbitrary input" @@ -1112,7 +1121,7 @@ "rule": "jQuery-$(", "path": "js/views/settings_view.js", "line": " this.$('.sync').attr('disabled', 'disabled');", - "lineNumber": 275, + "lineNumber": 284, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Protected from arbitrary input" @@ -1130,7 +1139,7 @@ "rule": "jQuery-$(", "path": "js/views/settings_view.js", "line": " this.$('.synced_at').hide();", - "lineNumber": 287, + "lineNumber": 296, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Protected from arbitrary input" @@ -1148,7 +1157,7 @@ "rule": "jQuery-$(", "path": "js/views/settings_view.js", "line": " this.$('.sync_failed').hide();", - "lineNumber": 292, + "lineNumber": 301, "reasonCategory": "usageTrusted", "updated": "2020-08-21T11:29:29.636Z", "reasonDetail": "Protected from arbitrary input" diff --git a/ts/window.d.ts b/ts/window.d.ts index 2acf069db..9b300761a 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -40,6 +40,7 @@ declare global { getCallRingtoneNotification: () => Promise; getCallSystemNotification: () => Promise; getConversations: () => ConversationModelCollectionType; + getCountMutedConversations: () => Promise; getEnvironment: () => string; getExpiration: () => string; getGuid: () => string; @@ -208,6 +209,7 @@ export type LoggerType = (...args: Array) => void; export type WhisperType = { events: { + on: (name: string, callback: (param1: any, param2?: any) => void) => void; trigger: (name: string, param1: any, param2?: any) => void; }; Database: {