From ffe756414c7a7cf026c9aaa33af8ff004e016d98 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Thu, 3 Jun 2021 21:22:49 +0300 Subject: [PATCH] More fixes for muted chats; PWA badge (#1133) --- src/@types/global.d.ts | 5 +++++ src/components/middle/MiddleHeader.tsx | 16 ++++++++++++++-- src/modules/actions/api/initial.ts | 2 ++ src/modules/actions/apiUpdaters/chats.ts | 11 ++++++++++- src/modules/helpers/chats.ts | 2 +- src/modules/selectors/chats.ts | 9 +++++++-- src/util/appBadge.ts | 14 ++++++++++++++ src/util/notifications.ts | 12 +++++++++--- 8 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 src/util/appBadge.ts diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index e5267aeaf..d6551099b 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -100,3 +100,8 @@ interface HTMLElement { webkitEnterFullscreen?: () => Promise; webkitRequestFullscreen?: () => Promise; } + +interface Navigator { + // PWA badging extensions https://w3c.github.io/badging/ + setAppBadge?(count: number): Promise; +} diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index a2810350a..0203f8849 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -11,6 +11,7 @@ import { ApiTypingStatus, MAIN_THREAD_ID, ApiUser, } from '../../api/types'; +import { NotifyException, NotifySettings } from '../../types'; import { MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN, @@ -27,6 +28,7 @@ import { getMessageKey, getChatTitle, getSenderTitle, + selectIsChatMuted, } from '../../modules/helpers'; import { selectChat, @@ -43,6 +45,8 @@ import { selectScheduledIds, selectIsInSelectMode, selectIsChatWithBot, + selectNotifySettings, + selectNotifyExceptions, } from '../../modules/selectors'; import useEnsureMessage from '../../hooks/useEnsureMessage'; import useWindowSize from '../../hooks/useWindowSize'; @@ -88,6 +92,8 @@ type StateProps = { isChatWithSelf?: boolean; isChatWithBot?: boolean; lastSyncTime?: number; + notifySettings: NotifySettings; + notifyExceptions?: Record; }; type DispatchProps = Pick = ({ isChatWithSelf, isChatWithBot, lastSyncTime, + notifySettings, + notifyExceptions, openChatWithInfo, pinMessage, focusMessage, @@ -211,7 +219,9 @@ const MiddleHeader: FC = ({ } const count = currentChat.unreadCount || 0; - if (count && (!currentChat.isMuted || currentChat.unreadMentionsCount)) { + if ( + count && (!selectIsChatMuted(currentChat, notifySettings, notifyExceptions) || currentChat.unreadMentionsCount) + ) { isActive = true; } @@ -226,7 +236,7 @@ const MiddleHeader: FC = ({ isActive, totalCount, }; - }, [isLeftColumnHideable, chatsById]); + }, [isLeftColumnHideable, chatsById, notifySettings, notifyExceptions]); const canToolsCollideWithChatInfo = ( windowWidth >= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN @@ -444,6 +454,8 @@ export default memo(withGlobal( isChatWithSelf: selectIsChatWithSelf(global, chatId), isChatWithBot: chat && selectIsChatWithBot(global, chat), lastSyncTime, + notifySettings: selectNotifySettings(global), + notifyExceptions: selectNotifyExceptions(global), }; const messagesById = selectChatMessages(global, chatId); diff --git a/src/modules/actions/api/initial.ts b/src/modules/actions/api/initial.ts index 12c59b44f..e5b2cb5db 100644 --- a/src/modules/actions/api/initial.ts +++ b/src/modules/actions/api/initial.ts @@ -15,6 +15,7 @@ import { import { initApi, callApi } from '../../../api/gramjs'; import { unsubscribe } from '../../../util/notifications'; import * as cacheApi from '../../../util/cacheApi'; +import { updateAppBadge } from '../../../util/appBadge'; addReducer('initApi', (global: GlobalState, actions) => { const sessionId = localStorage.getItem(GRAMJS_SESSION_ID_KEY) || undefined; @@ -111,6 +112,7 @@ addReducer('signOut', () => { addReducer('reset', () => { localStorage.removeItem(GRAMJS_SESSION_ID_KEY); + updateAppBadge(0); cacheApi.clear(MEDIA_CACHE_NAME); cacheApi.clear(MEDIA_CACHE_NAME_AVATARS); diff --git a/src/modules/actions/apiUpdaters/chats.ts b/src/modules/actions/apiUpdaters/chats.ts index 151d03267..80ec94a9b 100644 --- a/src/modules/actions/apiUpdaters/chats.ts +++ b/src/modules/actions/apiUpdaters/chats.ts @@ -5,6 +5,7 @@ import { ApiUpdate, MAIN_THREAD_ID } from '../../../api/types'; import { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config'; import { pick } from '../../../util/iteratees'; import { showNewMessageNotification } from '../../../util/notifications'; +import { updateAppBadge } from '../../../util/appBadge'; import { updateChat, replaceChatListIds, @@ -17,12 +18,15 @@ import { selectIsChatListed, selectChatListType, selectCurrentMessageList, + selectCountNotMutedUnread, } from '../../selectors'; +import { throttle } from '../../../util/schedulers'; const TYPING_STATUS_CLEAR_DELAY = 6000; // 6 seconds // Enough to animate and mark as read in Message List const CURRENT_CHAT_UNREAD_DELAY = 1000; +const runThrottledForUpdateAppBadge = throttle((cb) => cb(), CURRENT_CHAT_UNREAD_DELAY, true); addReducer('apiUpdate', (global, actions, update: ApiUpdate) => { switch (update['@type']) { @@ -32,8 +36,11 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => { actions.loadTopChats(); } - setGlobal(updateChat(global, update.id, update.chat, update.newProfilePhoto)); + const newGlobal = updateChat(global, update.id, update.chat, update.newProfilePhoto); + setGlobal(newGlobal); + const unreadCount = selectCountNotMutedUnread(newGlobal); + runThrottledForUpdateAppBadge(() => updateAppBadge(unreadCount)); break; } @@ -125,6 +132,8 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => { })); } + const unreadCount = selectCountNotMutedUnread(getGlobal()); + updateAppBadge(unreadCount); showNewMessageNotification({ chat, message, isActiveChat }); break; diff --git a/src/modules/helpers/chats.ts b/src/modules/helpers/chats.ts index a8d6dc57f..7fb827760 100644 --- a/src/modules/helpers/chats.ts +++ b/src/modules/helpers/chats.ts @@ -377,7 +377,7 @@ export function getFolderUnreadDialogs( const hasActiveDialogs = listedChats.some((chat) => ( chat.unreadMentionsCount - || (!chat.isMuted && (chat.unreadCount || chat.hasUnreadMark)) + || (!selectIsChatMuted(chat, notifySettings, notifyExceptions) && (chat.unreadCount || chat.hasUnreadMark)) )); return { diff --git a/src/modules/selectors/chats.ts b/src/modules/selectors/chats.ts index c0b1a77e0..1f2b66bf3 100644 --- a/src/modules/selectors/chats.ts +++ b/src/modules/selectors/chats.ts @@ -2,10 +2,11 @@ import { ApiChat, MAIN_THREAD_ID } from '../../api/types'; import { GlobalState } from '../../global/types'; import { - getPrivateChatUserId, isChatChannel, isChatPrivate, isHistoryClearMessage, isUserBot, isUserOnline, + getPrivateChatUserId, isChatChannel, isChatPrivate, isHistoryClearMessage, isUserBot, isUserOnline, selectIsChatMuted, } from '../helpers'; import { selectUser } from './users'; import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE } from '../../config'; +import { selectNotifyExceptions, selectNotifySettings } from './settings'; export function selectChat(global: GlobalState, chatId: number): ApiChat | undefined { return global.chats.byId[chatId]; @@ -157,7 +158,11 @@ export function selectCountNotMutedUnread(global: GlobalState) { return activeChatIds.reduce((acc, chatId) => { const chat = chats[chatId]; - if (chat && chat.unreadCount && !chat.isMuted) { + if ( + chat + && chat.unreadCount + && !selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)) + ) { return acc + chat.unreadCount; } diff --git a/src/util/appBadge.ts b/src/util/appBadge.ts new file mode 100644 index 000000000..87f3c537e --- /dev/null +++ b/src/util/appBadge.ts @@ -0,0 +1,14 @@ +import { DEBUG } from '../config'; + +export function updateAppBadge(unreadCount: number) { + if (typeof window.navigator.setAppBadge !== 'function') { + return; + } + + window.navigator.setAppBadge(unreadCount).catch((err) => { + if (DEBUG) { + // eslint-disable-next-line no-console + console.error(err); + } + }); +} diff --git a/src/util/notifications.ts b/src/util/notifications.ts index f7bf3482b..3d30761b9 100644 --- a/src/util/notifications.ts +++ b/src/util/notifications.ts @@ -11,10 +11,13 @@ import { getPrivateChatUserId, isActionMessage, isChatChannel, + selectIsChatMuted, } from '../modules/helpers'; import { getTranslation } from './langProvider'; import { replaceSettings } from '../modules/reducers'; -import { selectChatMessage, selectUser } from '../modules/selectors'; +import { + selectChatMessage, selectNotifyExceptions, selectNotifySettings, selectUser, +} from '../modules/selectors'; import { IS_SERVICE_WORKER_SUPPORTED } from './environment'; function getDeviceToken(subscription: PushSubscription) { @@ -187,12 +190,15 @@ export async function subscribe() { } function checkIfShouldNotify(chat: ApiChat, isActive: boolean) { - if (chat.isMuted || chat.isNotJoined) return false; + const global = getGlobal(); + + if (selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)) || chat.isNotJoined) { + return false; + } // Dont show notification for active chat if client has focus if (isActive && document.hasFocus()) return false; - const global = getGlobal(); switch (chat.type) { case 'chatTypePrivate': case 'chatTypeSecret':