From 2a5e1aa8e6b2a7125e8ca75381067b484f2b0ce2 Mon Sep 17 00:00:00 2001 From: David Battersby Date: Fri, 6 Sep 2024 18:25:25 +0400 Subject: [PATCH] FEATURE: only play chat sound when chat badge icon is shown (#28387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every time a desktop chat sound plays, there should be some visual cue as to why the sound was played in the first place. This change follows the chat indicator preference: - All New Messages - a blue dot is shown for all messages, so we attempt to play a sound every time - Direct Messages, Mentions and Watched Threads - a green dot is shown for all urgent messages, so we attempt to play a sound for urgent chat notifications - Only Mentions - only play chat sounds when user is mentioned - Never - we never play chat sounds, as user wouldn’t know why the sound was played --- .../discourse/initializers/chat-audio.js | 24 ++++- .../discourse/lib/chat-constants.js | 6 ++ .../services/chat-notification-manager.js | 12 ++- .../javascripts/unit/lib/chat-audio-test.js | 101 ++++++++++++++++++ 4 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 plugins/chat/test/javascripts/unit/lib/chat-audio-test.js diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-audio.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-audio.js index 2c17a02a374..0decff95833 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-audio.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-audio.js @@ -1,4 +1,5 @@ import { withPluginApi } from "discourse/lib/plugin-api"; +import { INDICATOR_PREFERENCES } from "discourse/plugins/chat/discourse/lib/chat-constants"; const MENTION = 29; const MESSAGE = 30; @@ -16,11 +17,32 @@ export default { withPluginApi("0.12.1", (api) => { api.registerDesktopNotificationHandler((data, siteSettings, user) => { + const indicatorType = user.user_option.chat_header_indicator_preference; + const isMention = data.notification_type === MENTION; + if (user.isInDoNotDisturb()) { return; } - if (!user.chat_sound) { + if ( + !user.user_option.chat_sound || + indicatorType === INDICATOR_PREFERENCES.never + ) { + return; + } + + if ( + indicatorType === INDICATOR_PREFERENCES.only_mentions && + !isMention + ) { + return; + } + + if ( + indicatorType === INDICATOR_PREFERENCES.dm_and_mentions && + !data.isDirectMessageChannel && + !isMention + ) { return; } diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-constants.js b/plugins/chat/assets/javascripts/discourse/lib/chat-constants.js index be9e88f2318..c9544b73a26 100644 --- a/plugins/chat/assets/javascripts/discourse/lib/chat-constants.js +++ b/plugins/chat/assets/javascripts/discourse/lib/chat-constants.js @@ -8,3 +8,9 @@ export const FOOTER_NAV_ROUTES = [ "chat.channels", "chat.threads", ]; +export const INDICATOR_PREFERENCES = { + all_new: "all_new", + dm_and_mentions: "dm_and_mentions", + only_mentions: "only_mentions", + never: "never", +}; diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-notification-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-notification-manager.js index e775a59460d..7594a2343f0 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-notification-manager.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-notification-manager.js @@ -1,3 +1,4 @@ +import { action } from "@ember/object"; import Service, { service } from "@ember/service"; import { alertChannel, @@ -10,6 +11,7 @@ import { bind } from "discourse-common/utils/decorators"; export default class ChatNotificationManager extends Service { @service presence; @service chat; + @service chatChannelsManager; @service chatStateManager; @service currentUser; @service appEvents; @@ -144,13 +146,21 @@ export default class ChatNotificationManager extends Service { } } + @action + async fetchChannel(channelId) { + return await this.chatChannelsManager.find(channelId); + } + @bind - onMessage(data) { + async onMessage(data) { if (data.channel_id === this.chat.activeChannel?.id) { return; } if (this.site.desktopView) { + const channel = await this.fetchChannel(data.channel_id); + data.isDirectMessageChannel = channel.isDirectMessageChannel ?? false; + return onDesktopNotification( data, this.siteSettings, diff --git a/plugins/chat/test/javascripts/unit/lib/chat-audio-test.js b/plugins/chat/test/javascripts/unit/lib/chat-audio-test.js new file mode 100644 index 00000000000..da34453c83b --- /dev/null +++ b/plugins/chat/test/javascripts/unit/lib/chat-audio-test.js @@ -0,0 +1,101 @@ +import { getOwner } from "@ember/owner"; +import { module, test } from "qunit"; +import sinon from "sinon"; +import { withPluginApi } from "discourse/lib/plugin-api"; +import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import chatAudioInitializer from "discourse/plugins/chat/discourse/initializers/chat-audio"; + +module("Discourse Chat | Unit | chat-audio", function (hooks) { + setupRenderingTest(hooks); + + hooks.beforeEach(function () { + const chatAudioManager = getOwner(this).lookup( + "service:chat-audio-manager" + ); + + this.chat = getOwner(this).lookup("service:chat"); + sinon.stub(this.chat, "userCanChat").value(true); + + this.siteSettings = getOwner(this).lookup("service:site-settings"); + this.siteSettings.chat_enabled = true; + + this.currentUser.user_option.has_chat_enabled = true; + this.currentUser.user_option.chat_sound = "ding"; + this.currentUser.user_option.chat_header_indicator_preference = "all_new"; + + withPluginApi("0.12.1", async (api) => { + this.stub = sinon.spy(api, "registerDesktopNotificationHandler"); + chatAudioInitializer.initialize(getOwner(this)); + + this.notificationHandler = this.stub.getCall(0).callback; + this.playStub = sinon.stub(chatAudioManager, "play"); + + this.handleNotification = (data = {}) => { + if (!data.notification_type) { + data.notification_type = 30; + } + this.notificationHandler(data, this.siteSettings, this.currentUser); + }; + }); + }); + + test("it registers desktop notification handler", function (assert) { + assert.ok(this.stub.calledOnce); + }); + + test("it plays chat sound", function (assert) { + this.handleNotification(); + + assert.ok(this.playStub.calledOnce); + }); + + test("it skips chat sound for user in DND mode", function (assert) { + this.currentUser.isInDoNotDisturb = () => true; + this.handleNotification(); + + assert.ok(this.playStub.notCalled); + }); + + test("it skips chat sound for user with no chat sound set", function (assert) { + this.currentUser.user_option.chat_sound = null; + this.handleNotification(); + + assert.ok(this.playStub.notCalled); + }); + + test("it plays a chat sound for mentions", function (assert) { + this.currentUser.user_option.chat_header_indicator_preference = + "only_mentions"; + + this.handleNotification({ notification_type: 29 }); + + assert.ok(this.playStub.calledOnce); + }); + + test("it skips chat sound for non-mentions", function (assert) { + this.currentUser.user_option.chat_header_indicator_preference = + "only_mentions"; + + this.handleNotification(); + + assert.ok(this.playStub.notCalled); + }); + + test("it plays a chat sound for DMs", function (assert) { + this.currentUser.user_option.chat_header_indicator_preference = + "dm_and_mentions"; + + this.handleNotification({ isDirectMessageChannel: true }); + + assert.ok(this.playStub.calledOnce); + }); + + test("it skips chat sound for non-DM messages", function (assert) { + this.currentUser.user_option.chat_header_indicator_preference = + "dm_and_mentions"; + + this.handleNotification({ isDirectMessageChannel: false }); + + assert.ok(this.playStub.notCalled); + }); +});