From b15c51d5d11bb73d538532f923599f7de7337b17 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Wed, 26 Jan 2022 17:19:23 +0400 Subject: [PATCH] Mute options Set local timeout by nearest mute_until --- src/components/chat/topbar.ts | 15 ++-- src/components/dialogsContextMenu.ts | 5 +- src/components/peerProfile.ts | 2 +- src/components/popups/mute.ts | 77 +++++++++++++++++++ .../sidebarRight/tabs/editContact.ts | 2 +- src/lang.ts | 6 ++ src/lib/appManagers/appMessagesManager.ts | 16 ++-- .../appManagers/appNotificationsManager.ts | 50 +++++++++++- src/scss/partials/popups/_mute.scss | 24 ++++++ src/scss/style.scss | 1 + 10 files changed, 180 insertions(+), 18 deletions(-) create mode 100644 src/components/popups/mute.ts create mode 100644 src/scss/partials/popups/_mute.scss diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts index 29ee68e2..ba371294 100644 --- a/src/components/chat/topbar.ts +++ b/src/components/chat/topbar.ts @@ -49,6 +49,7 @@ import { NULL_PEER_ID } from "../../lib/mtproto/mtproto_config"; import IS_GROUP_CALL_SUPPORTED from "../../environment/groupCallSupport"; import IS_CALL_SUPPORTED from "../../environment/callSupport"; import { CallType } from "../../lib/calls/types"; +import PopupMute from "../popups/mute"; type ButtonToVerify = {element?: HTMLElement, verify: () => boolean}; @@ -319,15 +320,13 @@ export default class ChatTopbar { }, */{ icon: 'mute', text: 'ChatList.Context.Mute', - onClick: () => { - this.appMessagesManager.mutePeer(this.peerId); - }, + onClick: this.onMuteClick, verify: () => this.chat.type === 'chat' && rootScope.myId !== this.peerId && !this.appNotificationsManager.isPeerLocalMuted(this.peerId, false) }, { icon: 'unmute', text: 'ChatList.Context.Unmute', onClick: () => { - this.appMessagesManager.mutePeer(this.peerId); + this.appMessagesManager.togglePeerMute(this.peerId); }, verify: () => this.chat.type === 'chat' && rootScope.myId !== this.peerId && this.appNotificationsManager.isPeerLocalMuted(this.peerId, false) }, { @@ -544,9 +543,7 @@ export default class ChatTopbar { this.openPinned(true); }); - this.attachClickEvent(this.btnMute, () => { - this.appMessagesManager.mutePeer(this.peerId); - }); + this.attachClickEvent(this.btnMute, this.onMuteClick); this.attachClickEvent(this.btnJoin, () => { const middleware = this.chat.bubbles.getMiddleware(); @@ -649,6 +646,10 @@ export default class ChatTopbar { }); } + private onMuteClick = () => { + new PopupMute(this.peerId); + }; + private onResize = () => { this.setUtilsWidth(true); this.setFloating(); diff --git a/src/components/dialogsContextMenu.ts b/src/components/dialogsContextMenu.ts index c12a3f48..daba4eb5 100644 --- a/src/components/dialogsContextMenu.ts +++ b/src/components/dialogsContextMenu.ts @@ -18,6 +18,7 @@ import PopupPeer from "./popups/peer"; import AppChatFoldersTab from "./sidebarLeft/tabs/chatFolders"; import appSidebarLeft from "./sidebarLeft"; import { toastNew } from "./toast"; +import PopupMute from "./popups/mute"; export default class DialogsContextMenu { private element: HTMLElement; @@ -123,11 +124,11 @@ export default class DialogsContextMenu { }; private onUnmuteClick = () => { - appMessagesManager.mutePeer(this.selectedId, false); + appMessagesManager.togglePeerMute(this.selectedId, false); }; private onMuteClick = () => { - appMessagesManager.mutePeer(this.selectedId, true); + new PopupMute(this.selectedId); }; private onUnreadClick = () => { diff --git a/src/components/peerProfile.ts b/src/components/peerProfile.ts index 37bfcbde..092a10bc 100644 --- a/src/components/peerProfile.ts +++ b/src/components/peerProfile.ts @@ -156,7 +156,7 @@ export default class PeerProfile { } //let checked = this.notificationsCheckbox.checked; - appMessagesManager.mutePeer(this.peerId); + appMessagesManager.togglePeerMute(this.peerId); }); rootScope.addEventListener('dialog_notify_settings', (dialog) => { diff --git a/src/components/popups/mute.ts b/src/components/popups/mute.ts new file mode 100644 index 00000000..681a3de3 --- /dev/null +++ b/src/components/popups/mute.ts @@ -0,0 +1,77 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import tsNow from "../../helpers/tsNow"; +import appMessagesManager from "../../lib/appManagers/appMessagesManager"; +import { LangPackKey } from "../../lib/langPack"; +import { MUTE_UNTIL } from "../../lib/mtproto/mtproto_config"; +import RadioField from "../radioField"; +import Row, { RadioFormFromRows } from "../row"; +import { SettingSection } from "../sidebarLeft"; +import PopupPeer from "./peer"; + +export default class PopupMute extends PopupPeer { + constructor(peerId: PeerId) { + super('popup-mute', { + peerId, + titleLangKey: 'Notifications', + buttons: [{ + langKey: 'ChatList.Context.Mute', + callback: () => { + appMessagesManager.mutePeer(peerId, time === -1 ? MUTE_UNTIL : tsNow(true) + time); + } + }], + body: true + }); + + const ONE_HOUR = 3600; + const times: {time: number, langKey: LangPackKey}[] = [{ + time: ONE_HOUR, + langKey: 'ChatList.Mute.1Hour' + }, { + time: ONE_HOUR * 4, + langKey: 'ChatList.Mute.4Hours' + }, { + time: ONE_HOUR * 8, + langKey: 'ChatList.Mute.8Hours' + }, { + time: ONE_HOUR * 24, + langKey: 'ChatList.Mute.1Day' + }, { + time: ONE_HOUR * 24 * 3, + langKey: 'ChatList.Mute.3Days' + }, { + time: -1, + langKey: 'ChatList.Mute.Forever' + }]; + + const name = 'mute-time'; + const rows = times.map((time) => { + const row = new Row({ + radioField: new RadioField({ + langKey: time.langKey, + name, + value: '' + time.time + }) + }); + + return row; + }); + + let time: number; + const radioForm = RadioFormFromRows(rows, (value) => { + time = +value; + }); + + rows[rows.length - 1].radioField.checked = true; + + const section = new SettingSection({noShadow: true, noDelimiter: true}); + section.content.append(radioForm); + this.body.append(section.container); + + this.show(); + } +} diff --git a/src/components/sidebarRight/tabs/editContact.ts b/src/components/sidebarRight/tabs/editContact.ts index a62f1808..ccd4f86d 100644 --- a/src/components/sidebarRight/tabs/editContact.ts +++ b/src/components/sidebarRight/tabs/editContact.ts @@ -90,7 +90,7 @@ export default class AppEditContactTab extends SliderSuperTab { return; } - appMessagesManager.mutePeer(this.peerId); + appMessagesManager.togglePeerMute(this.peerId); }); this.listenerSetter.add(rootScope)('notify_settings', (update) => { diff --git a/src/lang.ts b/src/lang.ts index c6a5144e..525353e6 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -814,6 +814,12 @@ const lang = { "ChatList.Filter.Exclude.LimitReached": "Sorry, you can only add up to 100 individual chats. Try using chat types.", "ChatList.Filter.Confirm.Remove.Header": "Remove Folder", "ChatList.Filter.Confirm.Remove.Text": "Are you sure you want to remove this folder? Your chats will not be deleted.", + "ChatList.Mute.1Hour": "For 1 Hour", + "ChatList.Mute.4Hours": "For 4 Hours", + "ChatList.Mute.8Hours": "For 8 Hours", + "ChatList.Mute.1Day": "For 1 Day", + "ChatList.Mute.3Days": "For 3 Days", + "ChatList.Mute.Forever": "Forever", "Channel.DescriptionHolderDescrpiton": "You can provide an optional description for your channel.", "CreateGroup.NameHolder": "Group Name", "Date.Today": "Today", diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index dfa22b06..394b08f8 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -5127,16 +5127,12 @@ export class AppMessagesManager { return pendingMessage; } - public mutePeer(peerId: PeerId, mute?: boolean) { + public mutePeer(peerId: PeerId, muteUntil: number) { const settings: InputPeerNotifySettings = { _: 'inputPeerNotifySettings' }; - if(mute === undefined) { - mute = !appNotificationsManager.isPeerLocalMuted(peerId, false); - } - - settings.mute_until = mute ? MUTE_UNTIL : 0; + settings.mute_until = muteUntil; return appNotificationsManager.updateNotifySettings({ _: 'inputNotifyPeer', @@ -5144,6 +5140,14 @@ export class AppMessagesManager { }, settings); } + public togglePeerMute(peerId: PeerId, mute?: boolean) { + if(mute === undefined) { + mute = !appNotificationsManager.isPeerLocalMuted(peerId, false); + } + + return this.mutePeer(peerId, mute ? MUTE_UNTIL : 0); + } + public canSendToPeer(peerId: PeerId, threadId?: number, action: ChatRights = 'send_messages') { if(peerId.isAnyChat()) { //const isChannel = appPeersManager.isChannel(peerId); diff --git a/src/lib/appManagers/appNotificationsManager.ts b/src/lib/appManagers/appNotificationsManager.ts index fbcb4c8f..b4de0237 100644 --- a/src/lib/appManagers/appNotificationsManager.ts +++ b/src/lib/appManagers/appNotificationsManager.ts @@ -29,6 +29,8 @@ import appRuntimeManager from "./appRuntimeManager"; import appStateManager from "./appStateManager"; import appUsersManager from "./appUsersManager"; import IS_VIBRATE_SUPPORTED from "../../environment/vibrateSupport"; +import { MUTE_UNTIL } from "../mtproto/mtproto_config"; +import throttle from "../../helpers/schedulers/throttle"; type MyNotification = Notification & { hidden?: boolean, @@ -91,6 +93,9 @@ export class AppNotificationsManager { private getNotifyPeerTypePromise: Promise; + private checkMuteUntilTimeout: number; + private checkMuteUntilThrottled: () => void; + constructor() { // @ts-ignore navigator.vibrate = navigator.vibrate || navigator.mozVibrate || navigator.webkitVibrate; @@ -103,6 +108,8 @@ export class AppNotificationsManager { this.notifySoundEl.id = 'notify-sound'; document.body.append(this.notifySoundEl); + this.checkMuteUntilThrottled = throttle(this.checkMuteUntil, 1000, false); + rootScope.addEventListener('instance_deactivated', () => { this.stop(); }); @@ -414,6 +421,45 @@ export class AppNotificationsManager { this.prevFavicon = href; } + private checkMuteUntil = () => { + if(this.checkMuteUntilTimeout !== undefined) { + clearTimeout(this.checkMuteUntilTimeout); + this.checkMuteUntilTimeout = undefined; + } + + const timestamp = tsNow(true); + let closestMuteUntil = MUTE_UNTIL; + for(const peerId in this.peerSettings.notifyPeer) { + const peerNotifySettings = this.peerSettings.notifyPeer[peerId]; + if(peerNotifySettings instanceof Promise) { + continue; + } + + const muteUntil = peerNotifySettings.mute_until; + if(muteUntil === undefined) { + continue; + } + + if(muteUntil <= timestamp) { + // ! do not delete it because peer's unique settings will be overwritten in getPeerLocalSettings with type's settings + // delete peerNotifySettings.mute_until; + + rootScope.dispatchEvent('updateNotifySettings', { + _: 'updateNotifySettings', + peer: { + _: 'notifyPeer', + peer: appPeersManager.getOutputPeer(peerId.toPeerId()) + }, + notify_settings: peerNotifySettings + }); + } else if(muteUntil < closestMuteUntil) { + closestMuteUntil = muteUntil; + } + } + + this.checkMuteUntilTimeout = window.setTimeout(this.checkMuteUntil, (closestMuteUntil - timestamp) * 1000); + }; + public savePeerSettings({key, peerId, settings}: { key?: Exclude, peerId?: PeerId, @@ -429,6 +475,8 @@ export class AppNotificationsManager { if(!peerId) { rootScope.dispatchEvent('notify_peer_type_settings', {key, settings}); + } else { + this.checkMuteUntilThrottled(); } //rootScope.broadcast('notify_settings', {peerId: peerId}); @@ -436,7 +484,7 @@ export class AppNotificationsManager { public isMuted(peerNotifySettings: PeerNotifySettings) { return peerNotifySettings._ === 'peerNotifySettings' && - ((peerNotifySettings.mute_until * 1000) > tsNow() || peerNotifySettings.silent); + (peerNotifySettings.silent || (peerNotifySettings.mute_until !== undefined && (peerNotifySettings.mute_until * 1000) > tsNow())); } public getPeerMuted(peerId: PeerId) { diff --git a/src/scss/partials/popups/_mute.scss b/src/scss/partials/popups/_mute.scss new file mode 100644 index 00000000..9b8d299a --- /dev/null +++ b/src/scss/partials/popups/_mute.scss @@ -0,0 +1,24 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +.popup-mute { + .popup-container { + width: 16rem; + } + + .popup-body { + margin: 0 -.625rem; + } + + .sidebar-left-section { + margin-bottom: 0 !important; + padding: 0 !important; + + &-content { + margin: 0 !important; + } + } +} diff --git a/src/scss/style.scss b/src/scss/style.scss index 20f27580..31527b16 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -321,6 +321,7 @@ $chat-input-inner-padding-handhelds: .25rem; @import "partials/popups/groupCall"; @import "partials/popups/call"; @import "partials/popups/sponsored"; +@import "partials/popups/mute"; @import "partials/pages/pages"; @import "partials/pages/authCode";