Notification: Fix sound playback conditions (#5734)

This commit is contained in:
zubiden 2025-03-21 14:02:16 +04:00 committed by Alexander Zinchuk
parent 37eb65344c
commit a65d6e0697
44 changed files with 620 additions and 537 deletions

View File

@ -24,13 +24,14 @@ import type {
} from '../../types';
import { pick, pickTruthy } from '../../../util/iteratees';
import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
import { getServerTimeOffset } from '../../../util/serverTime';
import { addPhotoToLocalDb, addUserToLocalDb } from '../helpers/localDb';
import { serializeBytes } from '../helpers/misc';
import {
buildApiBotVerification, buildApiFormattedText, buildApiPhoto, buildApiUsernames, buildAvatarPhotoId,
} from './common';
import { omitVirtualClassFields } from './helpers';
import { buildApiPeerNotifySettings } from './misc';
import {
buildApiEmojiStatus,
buildApiPeerColor,
@ -119,10 +120,8 @@ export function buildApiChatFromDialog(
): ApiChat {
const {
peer, folderId, unreadMark, unreadCount, unreadMentionsCount, unreadReactionsCount,
notifySettings: { silent, muteUntil },
readOutboxMaxId, readInboxMaxId, draft, viewForumAsMessages,
} = dialog;
const isMuted = silent || (typeof muteUntil === 'number' && getServerTime() < muteUntil);
return {
id: getApiChatIdFromMtpPeer(peer),
@ -134,8 +133,6 @@ export function buildApiChatFromDialog(
unreadCount,
unreadMentionsCount,
unreadReactionsCount,
isMuted,
muteUntil,
...(unreadMark && { hasUnreadMark: true }),
...(draft instanceof GramJs.DraftMessage && { draftDate: draft.date }),
...(viewForumAsMessages && { isForumAsMessages: true }),
@ -579,9 +576,7 @@ export function buildApiTopic(forumTopic: GramJs.TypeForumTopic): ApiTopic | und
unreadMentionsCount,
unreadReactionsCount,
fromId,
notifySettings: {
silent, muteUntil,
},
notifySettings,
} = forumTopic;
return {
@ -600,8 +595,7 @@ export function buildApiTopic(forumTopic: GramJs.TypeForumTopic): ApiTopic | und
unreadMentionsCount,
unreadReactionsCount,
fromId: getApiChatIdFromMtpPeer(fromId),
isMuted: silent || (typeof muteUntil === 'number' ? getServerTime() < muteUntil : undefined),
muteUntil,
notifySettings: buildApiPeerNotifySettings(notifySettings),
};
}

View File

@ -163,6 +163,7 @@ export function buildApiMessageFromNotification(
chatId: SERVICE_NOTIFICATIONS_USER_ID,
date: notification.inboxDate || currentDate,
content,
isInvertedMedia: notification.invertMedia,
isOutgoing: false,
};
}

View File

@ -8,6 +8,7 @@ import type {
ApiLanguage,
ApiOldLangString,
ApiPeerColors,
ApiPeerNotifySettings,
ApiPrivacyKey,
ApiSession,
ApiTimezone,
@ -21,7 +22,6 @@ import { numberToHexColor } from '../../../util/colors';
import {
buildCollectionByCallback, omit, omitUndefined, pick,
} from '../../../util/iteratees';
import { getServerTime } from '../../../util/serverTime';
import { addUserToLocalDb } from '../helpers/localDb';
import { omitVirtualClassFields } from './helpers';
import { buildApiDocument, buildMessageTextContent } from './messageContent';
@ -106,40 +106,20 @@ export function buildPrivacyKey(key: GramJs.TypePrivacyKey): ApiPrivacyKey | und
return undefined;
}
export function buildApiNotifyException(
notifySettings: GramJs.TypePeerNotifySettings, peer: GramJs.TypePeer,
) {
export function buildApiPeerNotifySettings(
notifySettings: GramJs.TypePeerNotifySettings,
): ApiPeerNotifySettings {
const {
silent, muteUntil, showPreviews, otherSound,
} = notifySettings;
const hasSound = Boolean(otherSound && !(otherSound instanceof GramJs.NotificationSoundNone));
const hasSound = !(otherSound instanceof GramJs.NotificationSoundNone);
return {
chatId: getApiChatIdFromMtpPeer(peer),
isMuted: silent || (typeof muteUntil === 'number' && getServerTime() < muteUntil),
...(!hasSound && { isSilent: true }),
...(showPreviews !== undefined && { shouldShowPreviews: Boolean(showPreviews) }),
muteUntil,
};
}
export function buildApiNotifyExceptionTopic(
notifySettings: GramJs.TypePeerNotifySettings, peer: GramJs.TypePeer, topicId: number,
) {
const {
silent, muteUntil, showPreviews, otherSound,
} = notifySettings;
const hasSound = Boolean(otherSound && !(otherSound instanceof GramJs.NotificationSoundNone));
return {
chatId: getApiChatIdFromMtpPeer(peer),
topicId,
isMuted: silent || (typeof muteUntil === 'number' && getServerTime() < muteUntil),
...(!hasSound && { isSilent: true }),
...(showPreviews !== undefined && { shouldShowPreviews: Boolean(showPreviews) }),
muteUntil,
hasSound,
isSilentPosting: silent,
mutedUntil: muteUntil,
shouldShowPreviews: showPreviews,
};
}

View File

@ -14,6 +14,7 @@ import type {
ApiMessage,
ApiMissingInvitedUser,
ApiPeer,
ApiPeerNotifySettings,
ApiPhoto,
ApiTopic,
ApiUser,
@ -31,7 +32,7 @@ import {
SERVICE_NOTIFICATIONS_USER_ID,
TOPICS_SLICE,
} from '../../../config';
import { buildCollectionByKey } from '../../../util/iteratees';
import { buildCollectionByKey, omitUndefined } from '../../../util/iteratees';
import {
buildApiChatBotCommands,
buildApiChatFolder,
@ -52,6 +53,7 @@ import {
} from '../apiBuilders/chats';
import { buildApiBotVerification, buildApiPhoto } from '../apiBuilders/common';
import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
import { buildApiPeerNotifySettings } from '../apiBuilders/misc';
import { buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
import { buildStickerSet } from '../apiBuilders/symbols';
import { buildApiUser, buildApiUserStatuses } from '../apiBuilders/users';
@ -96,6 +98,7 @@ type ChatListData = {
orderedPinnedIds: string[] | undefined;
totalChatCount: number;
messages: ApiMessage[];
notifyExceptionById: Record<string, ApiPeerNotifySettings>;
lastMessageByChatId: Record<string, number>;
nextOffsetId?: number;
nextOffsetPeerId?: string;
@ -150,6 +153,7 @@ export async function fetchChats({
const chats: ApiChat[] = [];
const draftsById: Record<string, ApiDraft> = {};
const notifyExceptionById: Record<string, ApiPeerNotifySettings> = {};
const dialogs = (resultPinned?.dialogs || []).concat(result.dialogs);
@ -186,7 +190,12 @@ export async function fetchChats({
chats.push(chat);
scheduleMutedChatUpdate(chat.id, chat.muteUntil, sendApiUpdate);
const notifySettings = buildApiPeerNotifySettings(dialog.notifySettings);
if (Object.values(omitUndefined(notifySettings)).length) {
notifyExceptionById[chat.id] = notifySettings;
scheduleMutedChatUpdate(chat.id, notifySettings.mutedUntil, sendApiUpdate);
}
if (withPinned && dialog.pinned) {
orderedPinnedIds.push(chat.id);
@ -229,6 +238,7 @@ export async function fetchChats({
totalChatCount,
lastMessageByChatId,
messages,
notifyExceptionById,
nextOffsetId,
nextOffsetPeerId,
nextOffsetDate,
@ -327,6 +337,7 @@ export async function fetchSavedChats({
lastMessageByChatId,
messages,
draftsById: {},
notifyExceptionById: {},
nextOffsetId,
nextOffsetPeerId,
nextOffsetDate,
@ -474,7 +485,9 @@ export async function requestChatUpdate({
applyState(result.state);
scheduleMutedChatUpdate(chatUpdate.id, chatUpdate.muteUntil, sendApiUpdate);
const notifySettings = buildApiPeerNotifySettings(dialog.notifySettings);
scheduleMutedChatUpdate(chatUpdate.id, notifySettings.mutedUntil, sendApiUpdate);
}
export function saveDraft({
@ -722,25 +735,27 @@ async function getFullChannelInfo(
};
}
export async function updateChatMutedState({
chat, isMuted, muteUntil = 0,
export function updateChatMutedState({
chat, isMuted, mutedUntil = 0,
}: {
chat: ApiChat; isMuted: boolean; muteUntil?: number;
chat: ApiChat; isMuted?: boolean; mutedUntil?: number;
}) {
if (isMuted && !muteUntil) {
muteUntil = MAX_INT_32;
if (isMuted && !mutedUntil) {
mutedUntil = MAX_INT_32;
}
await invokeRequest(new GramJs.account.UpdateNotifySettings({
invokeRequest(new GramJs.account.UpdateNotifySettings({
peer: new GramJs.InputNotifyPeer({
peer: buildInputPeer(chat.id, chat.accessHash),
}),
settings: new GramJs.InputPeerNotifySettings({ muteUntil }),
settings: new GramJs.InputPeerNotifySettings({ muteUntil: mutedUntil }),
}));
sendApiUpdate({
'@type': 'updateNotifyExceptions',
'@type': 'updateChatNotifySettings',
chatId: chat.id,
isMuted,
settings: {
mutedUntil,
},
});
void requestChatUpdate({
@ -749,27 +764,29 @@ export async function updateChatMutedState({
});
}
export async function updateTopicMutedState({
chat, topicId, isMuted, muteUntil = 0,
export function updateTopicMutedState({
chat, topicId, isMuted, mutedUntil = 0,
}: {
chat: ApiChat; topicId: number; isMuted: boolean; muteUntil?: number;
chat: ApiChat; topicId: number; isMuted?: boolean; mutedUntil?: number;
}) {
if (isMuted && !muteUntil) {
muteUntil = MAX_INT_32;
if (isMuted && !mutedUntil) {
mutedUntil = MAX_INT_32;
}
await invokeRequest(new GramJs.account.UpdateNotifySettings({
invokeRequest(new GramJs.account.UpdateNotifySettings({
peer: new GramJs.InputNotifyForumTopic({
peer: buildInputPeer(chat.id, chat.accessHash),
topMsgId: topicId,
}),
settings: new GramJs.InputPeerNotifySettings({ muteUntil }),
settings: new GramJs.InputPeerNotifySettings({ muteUntil: mutedUntil }),
}));
sendApiUpdate({
'@type': 'updateTopicNotifyExceptions',
'@type': 'updateTopicNotifySettings',
chatId: chat.id,
topicId,
isMuted,
settings: {
mutedUntil,
},
});
// TODO[forums] Request forum topic thread update

View File

@ -1834,11 +1834,14 @@ export async function reportSponsoredMessage({
export async function readAllMentions({
chat,
threadId,
}: {
chat: ApiChat;
threadId?: ThreadId;
}) {
const result = await invokeRequest(new GramJs.messages.ReadMentions({
peer: buildInputPeer(chat.id, chat.accessHash),
topMsgId: threadId ? Number(threadId) : undefined,
}));
if (!result) return;
@ -1846,17 +1849,20 @@ export async function readAllMentions({
processAffectedHistory(chat, result);
if (result.offset) {
await readAllMentions({ chat });
await readAllMentions({ chat, threadId });
}
}
export async function readAllReactions({
chat,
threadId,
}: {
chat: ApiChat;
threadId?: ThreadId;
}) {
const result = await invokeRequest(new GramJs.messages.ReadReactions({
peer: buildInputPeer(chat.id, chat.accessHash),
topMsgId: threadId ? Number(threadId) : undefined,
}));
if (!result) return;
@ -1864,14 +1870,15 @@ export async function readAllReactions({
processAffectedHistory(chat, result);
if (result.offset) {
await readAllReactions({ chat });
await readAllReactions({ chat, threadId });
}
}
export async function fetchUnreadMentions({
chat, ...pagination
chat, threadId, ...pagination
}: {
chat: ApiChat;
threadId?: ThreadId;
offsetId?: number;
addOffset?: number;
maxId?: number;
@ -1879,6 +1886,7 @@ export async function fetchUnreadMentions({
}) {
const result = await invokeRequest(new GramJs.messages.GetUnreadMentions({
peer: buildInputPeer(chat.id, chat.accessHash),
topMsgId: threadId ? Number(threadId) : undefined,
limit: MENTION_UNREAD_SLICE,
...pagination,
}));
@ -1899,9 +1907,10 @@ export async function fetchUnreadMentions({
}
export async function fetchUnreadReactions({
chat, ...pagination
chat, threadId, ...pagination
}: {
chat: ApiChat;
threadId?: ThreadId;
offsetId?: number;
addOffset?: number;
maxId?: number;
@ -1909,6 +1918,7 @@ export async function fetchUnreadReactions({
}) {
const result = await invokeRequest(new GramJs.messages.GetUnreadReactions({
peer: buildInputPeer(chat.id, chat.accessHash),
topMsgId: threadId ? Number(threadId) : undefined,
limit: REACTION_UNREAD_SLICE,
...pagination,
}));

View File

@ -8,7 +8,8 @@ import type {
ApiConfig,
ApiInputPrivacyRules,
ApiLanguage,
ApiNotifyException,
ApiNotifyPeerType,
ApiPeerNotifySettings,
ApiPhoto,
ApiPrivacyKey,
ApiUser,
@ -21,15 +22,14 @@ import {
MAX_INT_32,
} from '../../../config';
import { buildCollectionByKey } from '../../../util/iteratees';
import { getServerTime } from '../../../util/serverTime';
import { buildAppConfig } from '../apiBuilders/appConfig';
import { buildApiPhoto, buildPrivacyRules } from '../apiBuilders/common';
import {
buildApiConfig,
buildApiCountryList,
buildApiLanguage,
buildApiNotifyException,
buildApiPeerColors,
buildApiPeerNotifySettings,
buildApiSession,
buildApiTimezone,
buildApiWallpaper,
@ -323,20 +323,26 @@ export async function fetchNotificationExceptions() {
return acc;
}
acc.push(buildApiNotifyException(update.notifySettings, update.peer.peer));
const peerId = getApiChatIdFromMtpPeer(update.peer.peer);
acc[peerId] = buildApiPeerNotifySettings(update.notifySettings);
return acc;
}, [] as ApiNotifyException[]);
}, {} as Record<string, ApiPeerNotifySettings>);
}
export async function fetchNotificationSettings() {
export async function fetchContactSignUpSetting() {
const hasContactJoinedNotifications = await invokeRequest(new GramJs.account.GetContactSignUpNotification());
return hasContactJoinedNotifications;
}
export async function fetchNotifyDefaultSettings() {
const [
isMutedContactSignUpNotification,
privateContactNotificationsSettings,
groupNotificationsSettings,
broadcastNotificationsSettings,
usersSettings,
groupsSettings,
channelsSettings,
] = await Promise.all([
invokeRequest(new GramJs.account.GetContactSignUpNotification()),
invokeRequest(new GramJs.account.GetNotifySettings({
peer: new GramJs.InputNotifyUsers(),
})),
@ -348,37 +354,14 @@ export async function fetchNotificationSettings() {
})),
]);
if (!privateContactNotificationsSettings || !groupNotificationsSettings || !broadcastNotificationsSettings) {
return false;
if (!usersSettings || !groupsSettings || !channelsSettings) {
return undefined;
}
const {
silent: privateSilent, muteUntil: privateMuteUntil, showPreviews: privateShowPreviews,
} = privateContactNotificationsSettings;
const {
silent: groupSilent, muteUntil: groupMuteUntil, showPreviews: groupShowPreviews,
} = groupNotificationsSettings;
const {
silent: broadcastSilent, muteUntil: broadcastMuteUntil, showPreviews: broadcastShowPreviews,
} = broadcastNotificationsSettings;
return {
hasContactJoinedNotifications: !isMutedContactSignUpNotification,
hasPrivateChatsNotifications: !(
privateSilent
|| (typeof privateMuteUntil === 'number' && getServerTime() < privateMuteUntil)
),
hasPrivateChatsMessagePreview: privateShowPreviews,
hasGroupNotifications: !(
groupSilent || (typeof groupMuteUntil === 'number'
&& getServerTime() < groupMuteUntil)
),
hasGroupMessagePreview: groupShowPreviews,
hasBroadcastNotifications: !(
broadcastSilent || (typeof broadcastMuteUntil === 'number'
&& getServerTime() < broadcastMuteUntil)
),
hasBroadcastMessagePreview: broadcastShowPreviews,
users: buildApiPeerNotifySettings(usersSettings),
groups: buildApiPeerNotifySettings(groupsSettings),
channels: buildApiPeerNotifySettings(channelsSettings),
};
}
@ -386,17 +369,17 @@ export function updateContactSignUpNotification(isSilent: boolean) {
return invokeRequest(new GramJs.account.SetContactSignUpNotification({ silent: isSilent }));
}
export function updateNotificationSettings(peerType: 'contact' | 'group' | 'broadcast', {
isSilent,
export function updateNotificationSettings(peerType: ApiNotifyPeerType, {
isMuted,
shouldShowPreviews,
}: {
isSilent?: boolean;
isMuted?: boolean;
shouldShowPreviews?: boolean;
}) {
let peer: GramJs.TypeInputNotifyPeer;
if (peerType === 'contact') {
if (peerType === 'users') {
peer = new GramJs.InputNotifyUsers();
} else if (peerType === 'group') {
} else if (peerType === 'groups') {
peer = new GramJs.InputNotifyChats();
} else {
peer = new GramJs.InputNotifyBroadcasts();
@ -404,8 +387,7 @@ export function updateNotificationSettings(peerType: 'contact' | 'group' | 'broa
const settings = {
showPreviews: shouldShowPreviews,
silent: isSilent,
muteUntil: isSilent ? MAX_INT_32 : 0,
muteUntil: isMuted ? MAX_INT_32 : 0,
};
return invokeRequest(new GramJs.account.UpdateNotifySettings({

View File

@ -3,7 +3,7 @@ import type { OnApiUpdate } from '../types';
import { MAX_INT_32 } from '../../config';
import { getServerTime } from '../../util/serverTime';
type UnmuteQueueItem = { chatId: string; topicId?: number; muteUntil: number };
type UnmuteQueueItem = { chatId: string; topicId?: number; mutedUntil: number };
const unmuteTimers = new Map<string, any>();
const unmuteQueue: Array<UnmuteQueueItem> = [];
const scheduleUnmute = (item: UnmuteQueueItem, onUpdate: NoneToVoidFunction) => {
@ -12,9 +12,9 @@ const scheduleUnmute = (item: UnmuteQueueItem, onUpdate: NoneToVoidFunction) =>
clearTimeout(unmuteTimers.get(id));
unmuteTimers.delete(id);
}
if (item.muteUntil === MAX_INT_32 || item.muteUntil <= getServerTime()) return;
if (item.mutedUntil === MAX_INT_32 || item.mutedUntil <= getServerTime()) return;
unmuteQueue.push(item);
unmuteQueue.sort((a, b) => b.muteUntil - a.muteUntil);
unmuteQueue.sort((a, b) => b.mutedUntil - a.mutedUntil);
const next = unmuteQueue.pop();
if (!next) return;
const timer = setTimeout(() => {
@ -23,30 +23,34 @@ const scheduleUnmute = (item: UnmuteQueueItem, onUpdate: NoneToVoidFunction) =>
const afterNext = unmuteQueue.pop();
if (afterNext) scheduleUnmute(afterNext, onUpdate);
}
}, (item.muteUntil - getServerTime()) * 1000);
}, (item.mutedUntil - getServerTime()) * 1000);
unmuteTimers.set(id, timer);
};
export function scheduleMutedChatUpdate(chatId: string, muteUntil = 0, onUpdate: OnApiUpdate) {
export function scheduleMutedChatUpdate(chatId: string, mutedUntil = 0, onUpdate: OnApiUpdate) {
scheduleUnmute({
chatId,
muteUntil,
mutedUntil,
}, () => onUpdate({
'@type': 'updateNotifyExceptions',
'@type': 'updateChatNotifySettings',
chatId,
isMuted: false,
settings: {
mutedUntil: 0,
},
}));
}
export function scheduleMutedTopicUpdate(chatId: string, topicId: number, muteUntil = 0, onUpdate: OnApiUpdate) {
export function scheduleMutedTopicUpdate(chatId: string, topicId: number, mutedUntil = 0, onUpdate: OnApiUpdate) {
scheduleUnmute({
chatId,
topicId,
muteUntil,
mutedUntil,
}, () => onUpdate({
'@type': 'updateTopicNotifyExceptions',
'@type': 'updateTopicNotifySettings',
chatId,
topicId,
isMuted: false,
settings: {
mutedUntil: 0,
},
}));
}

View File

@ -47,8 +47,7 @@ import {
buildMessageDraft,
} from '../apiBuilders/messages';
import {
buildApiNotifyException,
buildApiNotifyExceptionTopic,
buildApiPeerNotifySettings,
buildLangStrings,
buildPrivacyKey,
} from '../apiBuilders/misc';
@ -631,28 +630,6 @@ export function updater(update: Update) {
messageIds: update.messages,
isPinned: update.pinned,
});
} else if (
update instanceof GramJs.UpdateNotifySettings
&& update.peer instanceof GramJs.NotifyPeer
) {
const payload = buildApiNotifyException(update.notifySettings, update.peer.peer);
scheduleMutedChatUpdate(payload.chatId, payload.muteUntil, sendApiUpdate);
sendApiUpdate({
'@type': 'updateNotifyExceptions',
...payload,
});
} else if (
update instanceof GramJs.UpdateNotifySettings
&& update.peer instanceof GramJs.NotifyForumTopic
) {
const payload = buildApiNotifyExceptionTopic(
update.notifySettings, update.peer.peer, update.peer.topMsgId,
);
scheduleMutedTopicUpdate(payload.chatId, payload.topicId, payload.muteUntil, sendApiUpdate);
sendApiUpdate({
'@type': 'updateTopicNotifyExceptions',
...payload,
});
} else if (
update instanceof GramJs.UpdateUserTyping
|| update instanceof GramJs.UpdateChatUserTyping
@ -837,18 +814,41 @@ export function updater(update: Update) {
// Settings
} else if (update instanceof GramJs.UpdateNotifySettings) {
const {
notifySettings: {
showPreviews, silent, muteUntil,
},
peer: { className },
notifySettings,
peer: notifyPeer,
} = update;
const className = notifyPeer.className;
const settings = buildApiPeerNotifySettings(notifySettings);
if (notifyPeer instanceof GramJs.NotifyPeer) {
const peerId = getApiChatIdFromMtpPeer(notifyPeer.peer);
scheduleMutedChatUpdate(peerId, settings.mutedUntil, sendApiUpdate);
sendApiUpdate({
'@type': 'updateChatNotifySettings',
chatId: peerId,
settings,
});
return;
}
if (notifyPeer instanceof GramJs.NotifyForumTopic) {
const peerId = getApiChatIdFromMtpPeer(notifyPeer.peer);
scheduleMutedTopicUpdate(peerId, notifyPeer.topMsgId, settings.mutedUntil, sendApiUpdate);
sendApiUpdate({
'@type': 'updateTopicNotifySettings',
chatId: peerId,
topicId: notifyPeer.topMsgId,
settings,
});
return;
}
const peerType = className === 'NotifyUsers'
? 'contact'
? 'users'
: (className === 'NotifyChats'
? 'group'
? 'groups'
: (className === 'NotifyBroadcasts'
? 'broadcast'
? 'channels'
: undefined
)
);
@ -858,11 +858,9 @@ export function updater(update: Update) {
}
sendApiUpdate({
'@type': 'updateNotifySettings',
'@type': 'updateDefaultNotifySettings',
peerType,
isSilent: Boolean(silent
|| (typeof muteUntil === 'number' && Date.now() + getServerTimeOffset() * 1000 < muteUntil * 1000)),
shouldShowPreviews: Boolean(showPreviews),
settings,
});
} else if (update instanceof GramJs.UpdatePeerBlocked) {
sendApiUpdate({

View File

@ -2,7 +2,7 @@ import type { ApiBotCommand } from './bots';
import type {
ApiChatReactions, ApiFormattedText, ApiInputMessageReplyInfo, ApiPhoto, ApiStickerSet,
} from './messages';
import type { ApiBotVerification, ApiChatInviteImporter } from './misc';
import type { ApiBotVerification, ApiChatInviteImporter, ApiPeerNotifySettings } from './misc';
import type {
ApiEmojiStatusType, ApiFakeType, ApiUser, ApiUsername,
} from './users';
@ -27,8 +27,6 @@ export interface ApiChat {
unreadMentionsCount?: number;
unreadReactionsCount?: number;
isVerified?: true;
isMuted?: boolean;
muteUntil?: number;
areSignaturesShown?: boolean;
areProfilesShown?: boolean;
hasPrivateLink?: boolean;
@ -262,8 +260,7 @@ export interface ApiTopic {
unreadMentionsCount: number;
unreadReactionsCount: number;
fromId: string;
isMuted?: boolean;
muteUntil?: number;
notifySettings: ApiPeerNotifySettings;
}
export interface ApiChatlistInviteNew {

View File

@ -106,13 +106,6 @@ export interface ApiSessionData {
isTest?: true;
}
export type ApiNotifyException = {
chatId: string;
isMuted: boolean;
isSilent?: boolean;
shouldShowPreviews?: boolean;
};
export type ApiNotification = {
localId: string;
containerSelector?: string;
@ -352,3 +345,12 @@ export type ApiLimitTypeWithModal = Exclude<ApiLimitType, (
export type ApiLimitTypeForPromo = Exclude<ApiLimitType,
'uploadMaxFileparts' | 'chatlistInvites' | 'chatlistJoined' | 'savedDialogsPinned'
>;
export type ApiPeerNotifySettings = {
mutedUntil?: number;
hasSound?: boolean;
isSilentPosting?: boolean;
shouldShowPreviews?: boolean;
};
export type ApiNotifyPeerType = 'users' | 'groups' | 'channels';

View File

@ -35,7 +35,11 @@ import type {
BoughtPaidMedia,
} from './messages';
import type {
ApiEmojiInteraction, ApiError, ApiNotifyException, ApiSessionData,
ApiEmojiInteraction,
ApiError,
ApiNotifyPeerType,
ApiPeerNotifySettings,
ApiSessionData,
} from './misc';
import type { ApiStarsAmount } from './payments';
import type { ApiPrivacyKey, LangPackStringValue, PrivacyVisibility } from './settings';
@ -501,21 +505,24 @@ export type ApiUpdateTwoFaError = {
messageKey: RegularLangFnParameters;
};
export type ApiUpdateNotifySettings = {
'@type': 'updateNotifySettings';
peerType: 'contact' | 'group' | 'broadcast';
isSilent: boolean;
shouldShowPreviews: boolean;
export type ApiUpdateDefaultNotifySettings = {
'@type': 'updateDefaultNotifySettings';
peerType: ApiNotifyPeerType;
settings: Partial<ApiPeerNotifySettings>;
};
export type ApiUpdateNotifyExceptions = {
'@type': 'updateNotifyExceptions';
} & ApiNotifyException;
export type ApiUpdatePeerNotifySettings = {
'@type': 'updateChatNotifySettings';
chatId: string;
settings: Partial<ApiPeerNotifySettings>;
};
export type ApiUpdateTopicNotifyExceptions = {
'@type': 'updateTopicNotifyExceptions';
export type ApiUpdateTopicNotifySettings = {
'@type': 'updateTopicNotifySettings';
chatId: string;
topicId: number;
} & ApiNotifyException;
settings: Partial<ApiPeerNotifySettings>;
};
export type ApiUpdateTwoFaStateWaitCode = {
'@type': 'updateTwoFaStateWaitCode';
@ -823,14 +830,14 @@ export type ApiUpdate = (
ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage | ApiUpdateStarPaymentStateCompleted |
ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages | ApiUpdateMessageTranslations |
ApiUpdateTwoFaError | ApiUpdateTwoFaStateWaitCode | ApiUpdateWebViewResultSent |
ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
ApiUpdateDefaultNotifySettings | ApiUpdatePeerNotifySettings | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
ApiUpdateServerTimeOffset | ApiUpdateMessageReactions | ApiUpdateSavedReactionTags |
ApiUpdateGroupCallParticipants | ApiUpdateGroupCallConnection | ApiUpdateGroupCall | ApiUpdateGroupCallStreams |
ApiUpdateGroupCallConnectionState | ApiUpdateGroupCallLeavePresentation | ApiUpdateGroupCallChatId |
ApiUpdatePendingJoinRequests | ApiUpdatePaymentVerificationNeeded | ApiUpdatePaymentStateCompleted |
ApiUpdatePhoneCall | ApiUpdatePhoneCallSignalingData | ApiUpdatePhoneCallMediaState |
ApiUpdatePhoneCallConnectionState | ApiUpdateBotMenuButton | ApiUpdateTranscribedAudio | ApiUpdateUserEmojiStatus |
ApiUpdateMessageExtendedMedia | ApiUpdateConfig | ApiUpdateTopicNotifyExceptions | ApiUpdatePinnedTopic |
ApiUpdateMessageExtendedMedia | ApiUpdateConfig | ApiUpdateTopicNotifySettings | ApiUpdatePinnedTopic |
ApiUpdatePinnedTopicsOrder | ApiUpdateTopic | ApiUpdateTopics | ApiUpdateRecentEmojiStatuses |
ApiUpdateRecentReactions | ApiUpdateStory | ApiUpdateReadStories | ApiUpdateDeleteStory | ApiUpdateSentStoryReaction |
ApiRequestReconnectApi | ApiRequestSync | ApiUpdateFetchingDifference | ApiUpdateChannelMessages |

View File

@ -22,15 +22,15 @@ import {
getHasAdminRight,
isChatChannel,
isUserRightBanned,
selectIsChatMuted,
} from '../../../global/helpers';
import { getIsChatMuted } from '../../../global/helpers/notifications';
import {
selectBotAppPermissions,
selectChat,
selectChatFullInfo,
selectCurrentMessageList,
selectNotifyExceptions,
selectNotifySettings,
selectNotifyDefaults,
selectNotifyException,
selectTopicLink,
selectUser,
selectUserFullInfo,
@ -419,7 +419,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
<Switcher
id="group-notifications"
label={userId ? 'Toggle User Notifications' : 'Toggle Chat Notifications'}
checked={isMuted}
checked={!isMuted}
inactive
/>
</ListItem>
@ -487,7 +487,7 @@ export default memo(withGlobal<OwnProps>(
const user = chatOrUserId ? selectUser(global, chatOrUserId) : undefined;
const botAppPermissions = chatOrUserId ? selectBotAppPermissions(global, chatOrUserId) : undefined;
const isForum = chat?.isForum;
const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global));
const isMuted = chat && getIsChatMuted(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id));
const { threadId } = selectCurrentMessageList(global) || {};
const topicId = isForum && threadId ? Number(threadId) : undefined;

View File

@ -51,16 +51,16 @@ const MuteChatModal: FC<OwnProps> = ({
], [lang]);
const handleSubmit = useCallback(() => {
let muteUntil: number;
let mutedUntil: number;
if (muteUntilOption === MuteDuration.Forever) {
muteUntil = MAX_INT_32;
mutedUntil = MAX_INT_32;
} else {
muteUntil = Math.floor(Date.now() / 1000) + Number(muteUntilOption);
mutedUntil = Math.floor(Date.now() / 1000) + Number(muteUntilOption);
}
if (topicId) {
updateTopicMutedState({ chatId, topicId, muteUntil });
updateTopicMutedState({ chatId, topicId, mutedUntil });
} else {
updateChatMutedState({ chatId, muteUntil });
updateChatMutedState({ chatId, mutedUntil });
}
onClose();
}, [chatId, muteUntilOption, onClose, topicId]);

View File

@ -23,8 +23,8 @@ import {
groupStatefulContent,
isUserId,
isUserOnline,
selectIsChatMuted,
} from '../../../global/helpers';
import { getIsChatMuted } from '../../../global/helpers/notifications';
import {
selectCanAnimateInterface,
selectChat,
@ -35,8 +35,8 @@ import {
selectDraft,
selectIsForumPanelClosed,
selectIsForumPanelOpen,
selectNotifyExceptions,
selectNotifySettings,
selectNotifyDefaults,
selectNotifyException,
selectOutgoingStatus,
selectPeer,
selectPeerStory,
@ -470,7 +470,7 @@ export default memo(withGlobal<OwnProps>(
return {
chat,
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
isMuted: getIsChatMuted(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id)),
lastMessageSender,
draft: selectDraft(global, chatId, MAIN_THREAD_ID),
isSelected,

View File

@ -6,6 +6,7 @@ import type { ApiChat, ApiTopic } from '../../../api/types';
import type { Signal } from '../../../util/signals';
import buildClassName from '../../../util/buildClassName';
import { getServerTime } from '../../../util/serverTime';
import { isSignal } from '../../../util/signals';
import { formatIntegerCompact } from '../../../util/textFormat';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
@ -62,20 +63,29 @@ const ChatBadge: FC<OwnProps> = ({
isForum && topics ? Object.values(topics).filter(({ unreadCount }) => unreadCount) : undefined
), [topics, isForum]);
const unreadCount = useMemo(() => (
isForum
// If we have unmuted topics, display the count of those. Otherwise, display the count of all topics.
? ((isMuted && topicsWithUnread?.filter((acc) => acc.isMuted === false).length)
|| topicsWithUnread?.length)
: (topic || chat).unreadCount
), [chat, topic, topicsWithUnread, isForum, isMuted]);
const unreadCount = useMemo(() => {
if (!isForum) {
return (topic || chat).unreadCount;
}
const shouldBeMuted = useMemo(() => {
const hasUnmutedUnreadTopics = topics
&& Object.values(topics).some((acc) => !acc.isMuted && acc.unreadCount);
return topicsWithUnread?.length;
}, [chat, topic, topicsWithUnread, isForum]);
return isMuted || (topics && !hasUnmutedUnreadTopics);
}, [topics, isMuted]);
const shouldBeUnMuted = useMemo(() => {
if (!isForum) {
return !isMuted || topic?.notifySettings.mutedUntil === 0;
}
if (isMuted) {
return topicsWithUnread?.some((acc) => acc.notifySettings.mutedUntil === 0);
}
const isEveryUnreadMuted = topicsWithUnread?.every((acc) => (
acc.notifySettings.mutedUntil && acc.notifySettings.mutedUntil > getServerTime()
));
return !isEveryUnreadMuted;
}, [isForum, isMuted, topicsWithUnread, topic?.notifySettings.mutedUntil]);
const hasUnreadMark = topic ? false : chat.hasUnreadMark;
@ -91,7 +101,7 @@ const ChatBadge: FC<OwnProps> = ({
const isUnread = Boolean((unreadCount || hasUnreadMark) && !isSavedDialog);
const className = buildClassName(
'ChatBadge',
shouldBeMuted && 'muted',
!shouldBeUnMuted && 'muted',
!isUnread && isPinned && 'pinned',
isUnread && 'unread',
);
@ -109,7 +119,7 @@ const ChatBadge: FC<OwnProps> = ({
function renderContent() {
const unreadReactionsElement = unreadReactionsCount && (
<div className={buildClassName('ChatBadge reaction', shouldBeMuted && 'muted')}>
<div className={buildClassName('ChatBadge reaction', !shouldBeUnMuted && 'muted')}>
<Icon name="heart" />
</div>
);
@ -121,7 +131,7 @@ const ChatBadge: FC<OwnProps> = ({
);
const unopenedTopicElement = isTopicUnopened && (
<div className={buildClassName('ChatBadge unopened', shouldBeMuted && 'muted')} />
<div className={buildClassName('ChatBadge unopened', !shouldBeUnMuted && 'muted')} />
);
const unreadCountElement = (hasUnreadMark || unreadCount) ? (

View File

@ -10,6 +10,7 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { ChatAnimationTypes } from './hooks';
import { groupStatefulContent } from '../../../global/helpers';
import { getIsChatMuted } from '../../../global/helpers/notifications';
import {
selectCanAnimateInterface,
selectCanDeleteTopic,
@ -17,6 +18,8 @@ import {
selectChatMessage,
selectCurrentMessageList,
selectDraft,
selectNotifyDefaults,
selectNotifyException,
selectOutgoingStatus,
selectPeerStory,
selectSender,
@ -57,6 +60,7 @@ type OwnProps = {
type StateProps = {
chat: ApiChat;
isChatMuted?: boolean;
canDelete?: boolean;
lastMessage?: ApiMessage;
lastMessageStory?: ApiTypeStory;
@ -75,6 +79,7 @@ const Topic: FC<OwnProps & StateProps> = ({
isSelected,
chatId,
chat,
isChatMuted,
style,
lastMessage,
lastMessageStory,
@ -106,9 +111,9 @@ const Topic: FC<OwnProps & StateProps> = ({
const [shouldRenderMuteModal, markRenderMuteModal, unmarkRenderMuteModal] = useFlag();
const {
isPinned, isClosed,
isPinned, isClosed, notifySettings,
} = topic;
const isMuted = topic.isMuted || (topic.isMuted === undefined && chat.isMuted);
const isMuted = Boolean(notifySettings.mutedUntil || (notifySettings.mutedUntil === undefined && isChatMuted));
const handleOpenDeleteModal = useLastCallback(() => {
markRenderDeleteModal();
@ -154,6 +159,7 @@ const Topic: FC<OwnProps & StateProps> = ({
const contextActions = useTopicContextActions({
topic,
chat,
isChatMuted,
wasOpened: wasTopicOpened,
canDelete,
handleDelete: handleOpenDeleteModal,
@ -181,7 +187,7 @@ const Topic: FC<OwnProps & StateProps> = ({
<TopicIcon topic={topic} className={styles.topicIcon} observeIntersection={observeIntersection} />
<h3 dir="auto" className="fullName">{renderText(topic.title)}</h3>
</div>
{topic.isMuted && <Icon name="muted" />}
{Boolean(notifySettings.mutedUntil) && <Icon name="muted" />}
<div className="separator" />
{isClosed && (
<Icon name="lock-badge" className={styles.closedIcon} />
@ -247,11 +253,16 @@ export default memo(withGlobal<OwnProps>(
const storyData = lastMessage?.content.storyData;
const lastMessageStory = storyData && selectPeerStory(global, storyData.peerId, storyData.id);
const isChatMuted = chat && getIsChatMuted(
chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id),
);
return {
chat,
lastMessage,
lastMessageSender,
typingStatus,
isChatMuted,
canDelete: selectCanDeleteTopic(global, chatId, topic.id),
withInterfaceAnimations: selectCanAnimateInterface(global),
draft,

View File

@ -13,6 +13,7 @@ import useOldLang from '../../../../hooks/useOldLang';
export default function useTopicContextActions({
topic,
chat,
isChatMuted,
wasOpened,
canDelete,
handleDelete,
@ -20,6 +21,7 @@ export default function useTopicContextActions({
}: {
topic: ApiTopic;
chat: ApiChat;
isChatMuted?: boolean;
wasOpened?: boolean;
canDelete?: boolean;
handleDelete?: NoneToVoidFunction;
@ -29,7 +31,7 @@ export default function useTopicContextActions({
return useMemo(() => {
const {
isPinned, isMuted, isClosed, id: topicId,
isPinned, notifySettings, isClosed, id: topicId,
} = topic;
const chatId = chat.id;
@ -75,7 +77,7 @@ export default function useTopicContextActions({
handler: () => toggleTopicPinned({ chatId, topicId, isPinned: true }),
}) : undefined;
const actionMute = ((chat.isMuted && isMuted !== false) || isMuted === true)
const actionMute = ((isChatMuted && notifySettings.mutedUntil === undefined) || notifySettings.mutedUntil)
? {
title: lang('ChatList.Unmute'),
icon: 'unmute',
@ -114,5 +116,5 @@ export default function useTopicContextActions({
actionCloseTopic,
actionDelete,
]) as MenuItemContextAction[];
}, [topic, chat, wasOpened, lang, canDelete, handleDelete, handleMute]);
}, [topic, chat, isChatMuted, wasOpened, lang, canDelete, handleDelete, handleMute]);
}

View File

@ -5,10 +5,10 @@ import { getActions, withGlobal } from '../../../global';
import type { ApiChat, ApiUser } from '../../../api/types';
import { StoryViewerOrigin } from '../../../types';
import { isUserId, selectIsChatMuted } from '../../../global/helpers';
import { isUserId } from '../../../global/helpers';
import { getIsChatMuted } from '../../../global/helpers/notifications';
import {
selectChat, selectIsChatPinned, selectNotifyExceptions,
selectNotifySettings, selectUser,
selectChat, selectIsChatPinned, selectNotifyDefaults, selectNotifyException, selectUser,
} from '../../../global/selectors';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
@ -156,9 +156,7 @@ export default memo(withGlobal<OwnProps>(
const chat = selectChat(global, chatId);
const user = selectUser(global, chatId);
const isPinned = selectIsChatPinned(global, chatId);
const isMuted = chat
? selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global))
: undefined;
const isMuted = chat && getIsChatMuted(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id));
return {
chat,

View File

@ -3,6 +3,8 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiNotifyPeerType, ApiPeerNotifySettings } from '../../../api/types';
import {
checkIfNotificationsSupported,
checkIfOfflinePushFailed,
@ -22,12 +24,7 @@ type OwnProps = {
};
type StateProps = {
hasPrivateChatsNotifications: boolean;
hasPrivateChatsMessagePreview: boolean;
hasGroupNotifications: boolean;
hasGroupMessagePreview: boolean;
hasBroadcastNotifications: boolean;
hasBroadcastMessagePreview: boolean;
notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>;
hasContactJoinedNotifications: boolean;
hasWebNotifications: boolean;
hasPushNotifications: boolean;
@ -37,12 +34,7 @@ type StateProps = {
const SettingsNotifications: FC<OwnProps & StateProps> = ({
isActive,
onReset,
hasPrivateChatsNotifications,
hasPrivateChatsMessagePreview,
hasGroupNotifications,
hasGroupMessagePreview,
hasBroadcastNotifications,
hasBroadcastMessagePreview,
notifyDefaults,
hasContactJoinedNotifications,
hasPushNotifications,
hasWebNotifications,
@ -66,27 +58,18 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
const handleSettingsChange = useCallback((
e: ChangeEvent<HTMLInputElement>,
peerType: 'contact' | 'group' | 'broadcast',
setting: 'silent' | 'showPreviews',
peerType: ApiNotifyPeerType,
setting: 'mute' | 'showPreviews',
) => {
const currentIsSilent = peerType === 'contact'
? !hasPrivateChatsNotifications
: !(peerType === 'group' ? hasGroupNotifications : hasBroadcastNotifications);
const currentShouldShowPreviews = peerType === 'contact'
? hasPrivateChatsMessagePreview
: (peerType === 'group' ? hasGroupMessagePreview : hasBroadcastMessagePreview);
const currentIsMuted = Boolean(notifyDefaults?.[peerType]?.mutedUntil);
const currentShouldShowPreviews = Boolean(notifyDefaults?.[peerType]?.shouldShowPreviews);
updateNotificationSettings({
peerType,
...(setting === 'silent' && { isSilent: !e.target.checked, shouldShowPreviews: currentShouldShowPreviews }),
...(setting === 'showPreviews' && { shouldShowPreviews: e.target.checked, isSilent: currentIsSilent }),
isMuted: setting === 'mute' ? !e.target.checked : currentIsMuted,
shouldShowPreviews: setting === 'showPreviews' ? e.target.checked : currentShouldShowPreviews,
});
}, [
hasBroadcastMessagePreview, hasBroadcastNotifications,
hasGroupMessagePreview, hasGroupNotifications,
hasPrivateChatsMessagePreview, hasPrivateChatsNotifications,
updateNotificationSettings,
]);
}, [notifyDefaults]);
const handleWebNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const isEnabled = e.target.checked;
@ -103,27 +86,27 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
}, [updateWebNotificationSettings]);
const handlePrivateChatsNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'contact', 'silent');
handleSettingsChange(e, 'users', 'mute');
}, [handleSettingsChange]);
const handlePrivateChatsPreviewChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'contact', 'showPreviews');
handleSettingsChange(e, 'users', 'showPreviews');
}, [handleSettingsChange]);
const handleGroupsNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'group', 'silent');
handleSettingsChange(e, 'groups', 'mute');
}, [handleSettingsChange]);
const handleGroupsPreviewChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'group', 'showPreviews');
handleSettingsChange(e, 'groups', 'showPreviews');
}, [handleSettingsChange]);
const handleChannelsNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'broadcast', 'silent');
handleSettingsChange(e, 'channels', 'mute');
}, [handleSettingsChange]);
const handleChannelsPreviewChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'broadcast', 'showPreviews');
handleSettingsChange(e, 'channels', 'showPreviews');
}, [handleSettingsChange]);
const handleContactNotificationChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
@ -186,17 +169,17 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
<Checkbox
label={lang('NotificationsForPrivateChats')}
subLabel={lang(hasPrivateChatsNotifications
subLabel={lang(notifyDefaults?.users?.mutedUntil
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={hasPrivateChatsNotifications}
checked={Boolean(notifyDefaults?.users?.mutedUntil)}
onChange={handlePrivateChatsNotificationsChange}
/>
<Checkbox
label={lang('MessagePreview')}
disabled={!hasPrivateChatsNotifications}
subLabel={lang(hasPrivateChatsMessagePreview
disabled={!notifyDefaults?.users?.mutedUntil}
subLabel={lang(notifyDefaults?.users?.shouldShowPreviews
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={hasPrivateChatsMessagePreview}
checked={Boolean(notifyDefaults?.users?.shouldShowPreviews)}
onChange={handlePrivateChatsPreviewChange}
/>
</div>
@ -206,15 +189,17 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
<Checkbox
label={lang('NotificationsForGroups')}
subLabel={lang(hasGroupNotifications ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={hasGroupNotifications}
subLabel={lang(notifyDefaults?.groups?.mutedUntil
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={Boolean(notifyDefaults?.groups?.mutedUntil)}
onChange={handleGroupsNotificationsChange}
/>
<Checkbox
label={lang('MessagePreview')}
disabled={!hasGroupNotifications}
subLabel={lang(hasGroupMessagePreview ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={hasGroupMessagePreview}
disabled={!notifyDefaults?.groups?.mutedUntil}
subLabel={lang(notifyDefaults?.groups?.shouldShowPreviews
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={Boolean(notifyDefaults?.groups?.shouldShowPreviews)}
onChange={handleGroupsPreviewChange}
/>
</div>
@ -224,15 +209,17 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
<Checkbox
label={lang('NotificationsForChannels')}
subLabel={lang(hasBroadcastNotifications ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={hasBroadcastNotifications}
subLabel={lang(notifyDefaults?.channels?.mutedUntil
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={Boolean(notifyDefaults?.channels?.mutedUntil)}
onChange={handleChannelsNotificationsChange}
/>
<Checkbox
label={lang('MessagePreview')}
disabled={!hasBroadcastNotifications}
subLabel={lang(hasBroadcastMessagePreview ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={hasBroadcastMessagePreview}
disabled={!notifyDefaults?.channels?.mutedUntil}
subLabel={lang(notifyDefaults?.channels?.shouldShowPreviews
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={Boolean(notifyDefaults?.channels?.shouldShowPreviews)}
onChange={handleChannelsPreviewChange}
/>
</div>
@ -253,12 +240,6 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
return {
hasPrivateChatsNotifications: Boolean(global.settings.byKey.hasPrivateChatsNotifications),
hasPrivateChatsMessagePreview: Boolean(global.settings.byKey.hasPrivateChatsMessagePreview),
hasGroupNotifications: Boolean(global.settings.byKey.hasGroupNotifications),
hasGroupMessagePreview: Boolean(global.settings.byKey.hasGroupMessagePreview),
hasBroadcastNotifications: Boolean(global.settings.byKey.hasBroadcastNotifications),
hasBroadcastMessagePreview: Boolean(global.settings.byKey.hasBroadcastMessagePreview),
hasContactJoinedNotifications: Boolean(global.settings.byKey.hasContactJoinedNotifications),
hasWebNotifications: global.settings.byKey.hasWebNotifications,
hasPushNotifications: global.settings.byKey.hasPushNotifications,

View File

@ -2,7 +2,7 @@ import type { FC } from '../../lib/teact/teact';
import React, { memo, useEffect, useRef } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { MessageListType } from '../../types';
import type { MessageListType, ThreadId } from '../../types';
import { MAIN_THREAD_ID } from '../../api/types';
import { selectChat, selectCurrentMessageList, selectCurrentMiddleSearch } from '../../global/selectors';
@ -24,6 +24,7 @@ type OwnProps = {
type StateProps = {
chatId?: string;
messageListType?: MessageListType;
threadId?: ThreadId;
unreadCount?: number;
unreadReactions?: number[];
unreadMentions?: number[];
@ -38,6 +39,7 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
canPost,
messageListType,
chatId,
threadId,
unreadCount,
unreadReactions,
unreadMentions,
@ -56,6 +58,16 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
const hasUnreadReactions = Boolean(reactionsCount);
const hasUnreadMentions = Boolean(mentionsCount);
const handleReadAllReactions = useLastCallback(() => {
if (!chatId) return;
readAllReactions({ chatId, threadId });
});
const handleReadAllMentions = useLastCallback(() => {
if (!chatId) return;
readAllMentions({ chatId, threadId });
});
useEffect(() => {
if (hasUnreadReactions && chatId && !unreadReactions?.length) {
fetchUnreadReactions({ chatId });
@ -120,7 +132,7 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
icon="heart-outline"
ariaLabelLang="AccDescrReactionMentionDown"
onClick={focusNextReaction}
onReadAll={readAllReactions}
onReadAll={handleReadAllReactions}
unreadCount={reactionsCount}
className={buildClassName(
styles.reactions,
@ -133,7 +145,7 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
icon="mention"
ariaLabelLang="AccDescrMentionDown"
onClick={focusNextMention}
onReadAll={readAllMentions}
onReadAll={handleReadAllMentions}
unreadCount={mentionsCount}
className={!hasUnreadMentions && styles.hidden}
/>
@ -166,6 +178,7 @@ export default memo(withGlobal<OwnProps>(
return {
messageListType,
chatId,
threadId,
reactionsCount: shouldShowCount ? chat.unreadReactionsCount : undefined,
unreadReactions: shouldShowCount ? chat.unreadReactions : undefined,
unreadMentions: shouldShowCount ? chat.unreadMentions : undefined,

View File

@ -20,8 +20,8 @@ import {
isSystemBot,
isUserId,
isUserRightBanned,
selectIsChatMuted,
} from '../../global/helpers';
import { getIsChatMuted } from '../../global/helpers/notifications';
import {
selectBot,
selectCanGift,
@ -32,8 +32,8 @@ import {
selectCurrentMessageList,
selectIsChatWithSelf,
selectIsRightColumnShown,
selectNotifyExceptions,
selectNotifySettings,
selectNotifyDefaults,
selectNotifyException,
selectTabState,
selectTopic,
selectUser,
@ -759,7 +759,7 @@ export default memo(withGlobal<OwnProps>(
return {
chat,
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
isMuted: getIsChatMuted(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id)),
isPrivate,
isTopic: chat?.isForum && !isMainThread,
isForum: chat?.isForum,

View File

@ -86,7 +86,7 @@ export default function useMessageObservers(
}
if (mentionIds.length) {
markMentionsRead({ messageIds: mentionIds });
markMentionsRead({ chatId, messageIds: mentionIds });
}
if (reactionIds.length) {

View File

@ -227,9 +227,9 @@ const ActionMessage = ({
}
if (message.hasUnreadMention) {
markMentionsRead({ messageIds: [id] });
markMentionsRead({ chatId, messageIds: [id] });
}
}, [hasUnreadReaction, id, animateUnreadReaction, message.hasUnreadMention]);
}, [hasUnreadReaction, chatId, id, animateUnreadReaction, message.hasUnreadMention]);
useEffect(() => {
if (action.type !== 'giftPremium') return;

View File

@ -869,9 +869,9 @@ const Message: FC<OwnProps & StateProps> = ({
}
if (unreadMentionIds.length) {
markMentionsRead({ messageIds: unreadMentionIds });
markMentionsRead({ chatId, messageIds: unreadMentionIds });
}
}, [hasUnreadReaction, album, messageId, animateUnreadReaction, message.hasUnreadMention]);
}, [hasUnreadReaction, album, chatId, messageId, animateUnreadReaction, message.hasUnreadMention]);
const albumLayout = useMemo(() => {
return isAlbum

View File

@ -9,11 +9,12 @@ import type { ApiPhoto, ApiUser } from '../../../api/types';
import { ManagementProgress } from '../../../types';
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
import { isUserBot, selectIsChatMuted } from '../../../global/helpers';
import { isUserBot } from '../../../global/helpers';
import { getIsChatMuted } from '../../../global/helpers/notifications';
import {
selectChat,
selectNotifyExceptions,
selectNotifySettings,
selectNotifyDefaults,
selectNotifyException,
selectTabState,
selectUser,
selectUserFullInfo,
@ -296,7 +297,7 @@ export default memo(withGlobal<OwnProps>(
const chat = selectChat(global, userId);
const userFullInfo = selectUserFullInfo(global, userId);
const { progress } = selectTabState(global).management;
const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global));
const isMuted = chat && getIsChatMuted(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id));
const personalPhoto = userFullInfo?.personalPhoto;
const notPersonalPhoto = userFullInfo?.profilePhoto || userFullInfo?.fallbackPhoto;

View File

@ -1,6 +1,7 @@
import type {
ApiChat, ApiChatFolder, ApiChatlistExportedInvite,
ApiChatMember, ApiError, ApiMissingInvitedUser,
ApiTopic,
} from '../../../api/types';
import type { RequiredGlobalActions } from '../../index';
import type {
@ -60,6 +61,7 @@ import {
addChatMembers,
addChats,
addMessages,
addNotifyExceptions,
addSimilarBots,
addUsers,
addUserStatuses,
@ -221,7 +223,7 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
const chat = selectChat(global, id);
if (chat?.hasUnreadMark) {
actions.toggleChatUnread({ id });
actions.markChatRead({ id });
}
const isChatOnlySummary = !selectChatLastMessageId(global, id);
@ -623,32 +625,26 @@ addActionHandler('requestSavedDialogUpdate', async (global, actions, payload): P
});
addActionHandler('updateChatMutedState', (global, actions, payload): ActionReturnType => {
const { chatId, muteUntil = 0 } = payload;
const { chatId, isMuted, mutedUntil } = payload;
const chat = selectChat(global, chatId);
if (!chat) {
return;
}
const isMuted = payload.isMuted ?? muteUntil > 0;
global = updateChat(global, chatId, { isMuted });
setGlobal(global);
void callApi('updateChatMutedState', { chat, isMuted, muteUntil });
void callApi('updateChatMutedState', { chat, isMuted, mutedUntil });
});
addActionHandler('updateTopicMutedState', (global, actions, payload): ActionReturnType => {
const { chatId, topicId, muteUntil = 0 } = payload;
const {
chatId, topicId, isMuted, mutedUntil,
} = payload;
const chat = selectChat(global, chatId);
if (!chat) {
return;
}
const isMuted = payload.isMuted ?? muteUntil > 0;
global = updateTopic(global, chatId, topicId, { isMuted });
setGlobal(global);
void callApi('updateTopicMutedState', {
chat, topicId, isMuted, muteUntil,
chat, topicId, isMuted, mutedUntil,
});
});
@ -1155,17 +1151,47 @@ addActionHandler('deleteChatFolder', async (global, actions, payload): Promise<v
}
});
addActionHandler('toggleChatUnread', (global, actions, payload): ActionReturnType => {
addActionHandler('markChatUnread', (global, actions, payload): ActionReturnType => {
const { id } = payload;
const chat = selectChat(global, id);
if (chat) {
if (chat.unreadCount) {
void callApi('markMessageListRead', { chat, threadId: MAIN_THREAD_ID });
} else {
void callApi('toggleDialogUnread', {
chat,
hasUnreadMark: !chat.hasUnreadMark,
});
if (!chat) return;
void callApi('toggleDialogUnread', {
chat,
hasUnreadMark: !chat.hasUnreadMark,
});
});
addActionHandler('markChatRead', async (global, actions, payload): Promise<void> => {
const { id } = payload;
const chat = selectChat(global, id);
if (!chat) return;
if (!chat.isForum) {
await callApi('markMessageListRead', { chat, threadId: MAIN_THREAD_ID });
actions.readAllMentions({ chatId: id });
actions.readAllReactions({ chatId: id });
return;
}
let hasMoreTopics = true;
let lastTopic: ApiTopic | undefined;
let processedCount = 0;
while (hasMoreTopics) {
const result = await callApi('fetchTopics', {
chat, offsetDate: lastTopic?.date, offsetTopicId: lastTopic?.id, offsetId: lastTopic?.lastMessageId, limit: 100,
});
if (!result?.topics?.length) return;
result.topics.forEach((topic) => {
if (!topic.unreadCount && !topic.unreadMentionsCount && !topic.unreadReactionsCount) return;
actions.markTopicRead({ chatId: id, topicId: topic.id });
});
lastTopic = result.topics[result.topics.length - 1];
processedCount += result.topics.length;
if (result.count <= processedCount) {
hasMoreTopics = false;
}
}
});
@ -1185,6 +1211,8 @@ addActionHandler('markTopicRead', (global, actions, payload): ActionReturnType =
threadId: topicId,
maxId: lastTopicMessageId,
});
actions.readAllMentions({ chatId, threadId: topicId });
actions.readAllReactions({ chatId, threadId: topicId });
global = getGlobal();
global = updateTopic(global, chatId, topicId, {
@ -2910,6 +2938,7 @@ async function loadChats(
global = updateChatListSecondaryInfo(global, listType, result);
global = replaceMessages(global, result.messages);
global = updateChatsLastMessageId(global, result.lastMessageByChatId, listType);
global = addNotifyExceptions(global, result.notifyExceptionById);
if (!shouldIgnorePagination) {
global = replaceChatListLoadingParameters(

View File

@ -1818,12 +1818,11 @@ async function fetchUnreadMentions<T extends GlobalState>(global: T, chatId: str
}
addActionHandler('markMentionsRead', (global, actions, payload): ActionReturnType => {
const { messageIds, tabId = getCurrentTabId() } = payload;
const chat = selectCurrentChat(global, tabId);
const { chatId, messageIds, tabId = getCurrentTabId() } = payload;
const chat = selectChat(global, chatId);
if (!chat) return;
global = removeUnreadMentions(global, chat.id, chat, messageIds, true);
global = removeUnreadMentions(global, chatId, chat, messageIds, true);
setGlobal(global);
actions.markMessagesRead({ messageIds, tabId });
@ -1848,17 +1847,22 @@ addActionHandler('focusNextMention', async (global, actions, payload): Promise<v
});
addActionHandler('readAllMentions', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {};
const { chatId, threadId = MAIN_THREAD_ID } = payload;
const chat = selectCurrentChat(global, tabId);
const chat = selectChat(global, chatId);
if (!chat) return undefined;
callApi('readAllMentions', { chat });
callApi('readAllMentions', { chat, threadId: threadId === MAIN_THREAD_ID ? undefined : threadId });
return updateChat(global, chat.id, {
unreadMentionsCount: undefined,
unreadMentions: undefined,
});
if (threadId === MAIN_THREAD_ID) {
return updateChat(global, chat.id, {
unreadMentionsCount: undefined,
unreadMentions: undefined,
});
}
// TODO[Forums]: Support mentions in threads
return undefined;
});
addActionHandler('openUrl', (global, actions, payload): ActionReturnType => {

View File

@ -1,6 +1,6 @@
import type { ApiError, ApiReaction, ApiReactionEmoji } from '../../../api/types';
import type { ActionReturnType } from '../../types';
import { ApiMediaFormat } from '../../../api/types';
import { ApiMediaFormat, MAIN_THREAD_ID } from '../../../api/types';
import { GENERAL_REFETCH_INTERVAL } from '../../../config';
import { getCurrentTabId } from '../../../util/establishMultitabRole';
@ -557,16 +557,21 @@ addActionHandler('focusNextReaction', (global, actions, payload): ActionReturnTy
});
addActionHandler('readAllReactions', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {};
const chat = selectCurrentChat(global, tabId);
const { chatId, threadId = MAIN_THREAD_ID } = payload;
const chat = selectChat(global, chatId);
if (!chat) return undefined;
callApi('readAllReactions', { chat });
callApi('readAllReactions', { chat, threadId: threadId === MAIN_THREAD_ID ? undefined : threadId });
return updateUnreadReactions(global, chat.id, {
unreadReactionsCount: undefined,
unreadReactions: undefined,
});
if (threadId === MAIN_THREAD_ID) {
return updateUnreadReactions(global, chat.id, {
unreadReactionsCount: undefined,
unreadReactions: undefined,
});
}
// TODO[Forums]: Support unread reactions in threads
return undefined;
});
addActionHandler('loadTopReactions', async (global): Promise<void> => {

View File

@ -5,7 +5,7 @@ import {
UPLOADING_WALLPAPER_SLUG,
} from '../../../types';
import { APP_CONFIG_REFETCH_INTERVAL, COUNTRIES_WITH_12H_TIME_FORMAT } from '../../../config';
import { APP_CONFIG_REFETCH_INTERVAL, COUNTRIES_WITH_12H_TIME_FORMAT, MAX_INT_32 } from '../../../config';
import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { buildCollectionByKey } from '../../../util/iteratees';
import { requestPermission, subscribe, unsubscribe } from '../../../util/notifications';
@ -16,9 +16,9 @@ import { callApi } from '../../../api/gramjs';
import { buildApiInputPrivacyRules } from '../../helpers';
import { addActionHandler, getGlobal, setGlobal } from '../../index';
import {
addBlockedUser, addNotifyExceptions, deletePeerPhoto,
addBlockedUser, addNotifyException, addNotifyExceptions, deletePeerPhoto,
removeBlockedUser, replaceSettings, updateChat,
updateNotifySettings, updateUser, updateUserFullInfo,
updateUser, updateUserFullInfo,
} from '../../reducers';
import { updateTabState } from '../../reducers/tabs';
import {
@ -306,26 +306,40 @@ addActionHandler('loadNotificationExceptions', async (global): Promise<void> =>
});
addActionHandler('loadNotificationSettings', async (global): Promise<void> => {
const result = await callApi('fetchNotificationSettings');
if (!result) {
return;
}
const [signUpNotification, notifyDefaults] = await Promise.all([
callApi('fetchContactSignUpSetting'),
callApi('fetchNotifyDefaultSettings'),
]);
if (!notifyDefaults) return;
global = getGlobal();
global = replaceSettings(global, result);
global = replaceSettings(global, {
hasContactJoinedNotifications: signUpNotification,
});
global = {
...global,
settings: {
...global.settings,
notifyDefaults,
},
};
setGlobal(global);
});
addActionHandler('updateNotificationSettings', async (global, actions, payload): Promise<void> => {
const { peerType, isSilent, shouldShowPreviews } = payload!;
const { peerType, isMuted, shouldShowPreviews } = payload!;
const result = await callApi('updateNotificationSettings', peerType, { isSilent, shouldShowPreviews });
const result = await callApi('updateNotificationSettings', peerType, { isMuted, shouldShowPreviews });
if (!result) {
return;
}
global = getGlobal();
global = updateNotifySettings(global, peerType, isSilent, shouldShowPreviews);
global = addNotifyException(global, peerType, {
mutedUntil: isMuted ? MAX_INT_32 : undefined,
shouldShowPreviews,
});
setGlobal(global);
});
@ -582,12 +596,11 @@ addActionHandler('loadCountryList', async (global, actions, payload): Promise<vo
setGlobal(global);
});
addActionHandler('ensureTimeFormat', async (global, actions, payload): Promise<void> => {
const { tabId = getCurrentTabId() } = payload || {};
addActionHandler('ensureTimeFormat', async (global, actions): Promise<void> => {
if (global.authNearestCountry) {
const timeFormat = COUNTRIES_WITH_12H_TIME_FORMAT
.has(global.authNearestCountry.toUpperCase()) ? '12h' : '24h';
actions.setSettingOption({ timeFormat, tabId });
actions.setSettingOption({ timeFormat });
setTimeFormat(timeFormat);
}
@ -598,7 +611,7 @@ addActionHandler('ensureTimeFormat', async (global, actions, payload): Promise<v
const nearestCountryCode = await callApi('fetchNearestCountry');
if (nearestCountryCode) {
const timeFormat = COUNTRIES_WITH_12H_TIME_FORMAT.has(nearestCountryCode.toUpperCase()) ? '12h' : '24h';
actions.setSettingOption({ timeFormat, tabId });
actions.setSettingOption({ timeFormat });
setTimeFormat(timeFormat);
}
});

View File

@ -2,37 +2,33 @@ import type { ActionReturnType } from '../../types';
import { addActionHandler, setGlobal } from '../../index';
import {
addNotifyException, updateChat, updateNotifySettings,
addNotifyException,
updateNotifyDefaults,
updateTopic,
} from '../../reducers';
addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
switch (update['@type']) {
case 'updateNotifySettings': {
return updateNotifySettings(global, update.peerType, update.isSilent, update.shouldShowPreviews);
case 'updateDefaultNotifySettings': {
return updateNotifyDefaults(global, update.peerType, update.settings);
}
case 'updateNotifyExceptions': {
case 'updateChatNotifySettings': {
const {
chatId, isMuted, isSilent, shouldShowPreviews,
chatId, settings,
} = update;
const chat = global.chats.byId[chatId];
if (chat) {
global = updateChat(global, chatId, { isMuted });
}
global = addNotifyException(global, chatId, { isMuted, isSilent, shouldShowPreviews });
global = addNotifyException(global, chatId, settings);
setGlobal(global);
break;
}
case 'updateTopicNotifyExceptions': {
case 'updateTopicNotifySettings': {
const {
chatId, topicId, isMuted,
chatId, topicId, settings,
} = update;
global = updateTopic(global, chatId, topicId, { isMuted });
global = updateTopic(global, chatId, topicId, { notifySettings: settings });
setGlobal(global);
break;

View File

@ -24,7 +24,10 @@ import { replaceSettings } from '../../reducers';
import { updateTabState } from '../../reducers/tabs';
import {
selectCanAnimateInterface,
selectNotifySettings, selectPerformanceSettings, selectTabState, selectTheme,
selectPerformanceSettings,
selectSettingsKeys,
selectTabState,
selectTheme,
} from '../../selectors';
const HISTORY_ANIMATION_DURATION = 450;
@ -100,7 +103,7 @@ addActionHandler('initShared', (): ActionReturnType => {
});
addActionHandler('initMain', (global): ActionReturnType => {
const { hasWebNotifications, hasPushNotifications } = selectNotifySettings(global);
const { hasWebNotifications, hasPushNotifications } = selectSettingsKeys(global);
if (hasWebNotifications && hasPushNotifications) {
// Most of the browsers only show the notifications permission prompt after the first user gesture.
const events = ['click', 'keypress'];

View File

@ -300,6 +300,10 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
cached.cacheVersion = 2;
}
if (!cached.chats.notifyExceptionById) {
cached.chats.notifyExceptionById = initialState.chats.notifyExceptionById;
}
}
function updateCache(force?: boolean) {
@ -494,6 +498,7 @@ function reduceChats<T extends GlobalState>(global: T): GlobalState['chats'] {
similarChannelsById: {},
similarBotsById: {},
isFullyLoaded: {},
notifyExceptionById: {},
loadingParameters: INITIAL_GLOBAL_STATE.chats.loadingParameters,
byId: pickTruthy(global.chats.byId, idsToSave),
fullInfoById: pickTruthy(global.chats.fullInfoById, idsToSave),
@ -662,7 +667,6 @@ function reduceSettings<T extends GlobalState>(global: T): GlobalState['settings
themes,
performance,
privacy: {},
notifyExceptions: {},
botVerificationShownPeerIds,
miniAppsCachedPosition,
miniAppsCachedSize,

View File

@ -13,7 +13,7 @@ import type {
} from '../../api/types';
import type { OldLangFn } from '../../hooks/useOldLang';
import type {
CustomPeer, NotifyException, NotifySettings, ThreadId,
CustomPeer, ThreadId,
} from '../../types';
import type { LangFn } from '../../util/localization';
import { MAIN_THREAD_ID } from '../../api/types';
@ -306,40 +306,6 @@ export function isChatArchived(chat: ApiChat) {
return chat.folderId === ARCHIVED_FOLDER_ID;
}
export function selectIsChatMuted(
chat: ApiChat, notifySettings: NotifySettings, notifyExceptions: Record<string, NotifyException> = {},
) {
// If this chat is in exceptions they take precedence
if (notifyExceptions[chat.id] && notifyExceptions[chat.id].isMuted !== undefined) {
return notifyExceptions[chat.id].isMuted;
}
return (
chat.isMuted
|| (isUserId(chat.id) && !notifySettings.hasPrivateChatsNotifications)
|| (isChatChannel(chat) && !notifySettings.hasBroadcastNotifications)
|| (isChatGroup(chat) && !notifySettings.hasGroupNotifications)
);
}
export function selectShouldShowMessagePreview(
chat: ApiChat, notifySettings: NotifySettings, notifyExceptions: Record<string, NotifyException> = {},
) {
const {
hasPrivateChatsMessagePreview = true,
hasBroadcastMessagePreview = true,
hasGroupMessagePreview = true,
} = notifySettings;
// If this chat is in exceptions they take precedence
if (notifyExceptions[chat.id] && notifyExceptions[chat.id].shouldShowPreviews !== undefined) {
return notifyExceptions[chat.id].shouldShowPreviews;
}
return (isUserId(chat.id) && hasPrivateChatsMessagePreview)
|| (isChatChannel(chat) && hasBroadcastMessagePreview)
|| (isChatGroup(chat) && hasGroupMessagePreview);
}
export function getCanDeleteChat(chat: ApiChat) {
return isChatBasicGroup(chat) || ((isChatSuperGroup(chat) || isChatChannel(chat)) && chat.isCreator);
}

View File

@ -1,4 +1,8 @@
import type { ApiInputPrivacyRules, BotsPrivacyType, PrivacyVisibility } from '../../api/types';
import type {
ApiInputPrivacyRules,
BotsPrivacyType,
PrivacyVisibility,
} from '../../api/types';
import type { GlobalState } from '../types';
import { partition } from '../../util/iteratees';

View File

@ -0,0 +1,65 @@
import type {
ApiChat,
ApiNotifyPeerType,
ApiPeer,
ApiPeerNotifySettings,
} from '../../api/types';
import { omitUndefined } from '../../util/iteratees';
import { getServerTime } from '../../util/serverTime';
import { isChatChannel, isUserId } from './chats';
export function getIsChatMuted(
chat: ApiChat,
notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>,
notifyException?: ApiPeerNotifySettings,
) {
const settings = getChatNotifySettings(chat, notifyDefaults, notifyException);
if (!settings?.mutedUntil) return false;
return getServerTime() < settings.mutedUntil;
}
export function getIsChatSilent(
chat: ApiChat,
notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>,
notifyException?: ApiPeerNotifySettings,
) {
const settings = getChatNotifySettings(chat, notifyDefaults, notifyException);
if (!settings) return false;
return !settings.hasSound;
}
export function getShouldShowMessagePreview(
chat: ApiChat,
notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>,
notifyException?: ApiPeerNotifySettings,
) {
const settings = getChatNotifySettings(chat, notifyDefaults, notifyException);
return Boolean(settings?.shouldShowPreviews);
}
export function getChatNotifySettings(
chat: ApiChat,
notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>,
notifyException?: ApiPeerNotifySettings,
): ApiPeerNotifySettings | undefined {
const defaults = notifyDefaults?.[getNotificationPeerType(chat)];
if (!notifyException && !defaults) {
return undefined;
}
return {
...defaults,
...(notifyException && omitUndefined(notifyException)),
};
}
export function getNotificationPeerType(peer: ApiPeer): ApiNotifyPeerType {
if (isUserId(peer.id)) {
return 'users';
}
const chat = peer as ApiChat;
return isChatChannel(chat) ? 'channels' : 'groups';
}

View File

@ -122,6 +122,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
similarChannelsById: {},
similarBotsById: {},
topicsInfoById: {},
notifyExceptionById: {},
loadingParameters: {
active: {},
archived: {},
@ -297,7 +298,6 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
},
performance: INITIAL_PERFORMANCE_STATE_MAX,
privacy: {},
notifyExceptions: {},
botVerificationShownPeerIds: [],
},

View File

@ -1,6 +1,6 @@
import type { ApiNotifyException } from '../../api/types';
import type { ApiNotifyPeerType, ApiPeerNotifySettings } from '../../api/types';
import type {
ISettings, IThemeSettings, NotifyException,
ISettings, IThemeSettings,
ThemeKey,
} from '../../types';
import type { GlobalState } from '../types';
@ -39,52 +39,51 @@ export function replaceThemeSettings<T extends GlobalState>(
}
export function addNotifyExceptions<T extends GlobalState>(
global: T, notifyExceptions: ApiNotifyException[],
): T {
notifyExceptions.forEach((notifyException) => {
const { chatId, ...exceptionData } = notifyException;
global = addNotifyException(global, chatId, exceptionData);
});
return global;
}
export function addNotifyException<T extends GlobalState>(
global: T, id: string, notifyException: NotifyException,
global: T, notifyExceptionById: Record<string, ApiPeerNotifySettings>,
): T {
return {
...global,
settings: {
...global.settings,
notifyExceptions: {
...global.settings.notifyExceptions,
chats: {
...global.chats,
notifyExceptionById: {
...global.chats.notifyExceptionById,
...notifyExceptionById,
},
},
};
}
export function addNotifyException<T extends GlobalState>(
global: T, id: string, notifyException: ApiPeerNotifySettings,
): T {
return {
...global,
chats: {
...global.chats,
notifyExceptionById: {
...global.chats.notifyExceptionById,
[id]: notifyException,
},
},
};
}
// eslint-disable-next-line consistent-return
export function updateNotifySettings<T extends GlobalState>(
global: T, peerType: 'contact' | 'group' | 'broadcast', isSilent?: boolean, shouldShowPreviews?: boolean,
export function updateNotifyDefaults<T extends GlobalState>(
global: T, peerType: ApiNotifyPeerType, settings: Partial<ApiPeerNotifySettings>,
): T {
switch (peerType) {
case 'contact':
return replaceSettings(global, {
...(typeof isSilent !== 'undefined' && { hasPrivateChatsNotifications: !isSilent }),
...(typeof shouldShowPreviews !== 'undefined' && { hasPrivateChatsMessagePreview: shouldShowPreviews }),
});
case 'group':
return replaceSettings(global, {
...(typeof isSilent !== 'undefined' && { hasGroupNotifications: !isSilent }),
...(typeof shouldShowPreviews !== 'undefined' && { hasGroupMessagePreview: shouldShowPreviews }),
});
case 'broadcast':
return replaceSettings(global, {
...(typeof isSilent !== 'undefined' && { hasBroadcastNotifications: !isSilent }),
...(typeof shouldShowPreviews !== 'undefined' && { hasBroadcastMessagePreview: shouldShowPreviews }),
});
}
return {
...global,
settings: {
...global.settings,
notifyDefaults: {
...global.settings.notifyDefaults,
[peerType]: {
...global.settings.notifyDefaults?.[peerType],
...settings,
},
},
},
};
}
export function addBlockedUser<T extends GlobalState>(global: T, contactId: string): T {

View File

@ -1,11 +1,11 @@
import type { GlobalState } from '../types';
export function selectNotifySettings<T extends GlobalState>(global: T) {
return global.settings.byKey;
export function selectNotifyDefaults<T extends GlobalState>(global: T) {
return global.settings.notifyDefaults;
}
export function selectNotifyExceptions<T extends GlobalState>(global: T) {
return global.settings.notifyExceptions;
export function selectNotifyException<T extends GlobalState>(global: T, chatId: string) {
return global.chats.notifyExceptionById?.[chatId];
}
export function selectLanguageCode<T extends GlobalState>(global: T) {
@ -13,7 +13,7 @@ export function selectLanguageCode<T extends GlobalState>(global: T) {
}
export function selectCanSetPasscode<T extends GlobalState>(global: T) {
return global.authRememberMe && global.isCacheApiSupported;
return global.authRememberMe;
}
export function selectTranslationLanguage<T extends GlobalState>(global: T) {
@ -27,3 +27,7 @@ export function selectNewNoncontactPeersRequirePremium<T extends GlobalState>(gl
export function selectShouldHideReadMarks<T extends GlobalState>(global: T) {
return global.settings.byKey.shouldHideReadMarks;
}
export function selectSettingsKeys<T extends GlobalState>(global: T) {
return global.settings.byKey;
}

View File

@ -25,6 +25,7 @@ import type {
ApiMessageSearchContext,
ApiNewPoll,
ApiNotification,
ApiNotifyPeerType,
ApiPaymentStatus,
ApiPhoto,
ApiPremiumSection,
@ -220,8 +221,8 @@ export interface ActionPayloads {
isSilent: boolean;
};
updateNotificationSettings: {
peerType: 'contact' | 'group' | 'broadcast';
isSilent?: boolean;
peerType: ApiNotifyPeerType;
isMuted?: boolean;
shouldShowPreviews?: boolean;
};
@ -255,7 +256,7 @@ export interface ActionPayloads {
loadCountryList: {
langCode?: string;
};
ensureTimeFormat: WithTabId | undefined;
ensureTimeFormat: undefined;
// misc
loadWebPagePreview: {
@ -365,7 +366,8 @@ export interface ActionPayloads {
toggleChatArchived: {
id: string;
};
toggleChatUnread: { id: string };
markChatUnread: { id: string };
markChatRead: { id: string };
loadChatFolders: undefined;
loadRecommendedChatFolders: undefined;
editChatFolder: {
@ -1047,7 +1049,7 @@ export interface ActionPayloads {
updateChatMutedState: {
chatId: string;
isMuted?: boolean;
muteUntil?: number;
mutedUntil?: number;
};
updateChat: {
@ -1313,9 +1315,16 @@ export interface ActionPayloads {
} & WithTabId;
focusNextReaction: WithTabId | undefined;
focusNextMention: WithTabId | undefined;
readAllReactions: WithTabId | undefined;
readAllMentions: WithTabId | undefined;
readAllReactions: {
chatId: string;
threadId?: ThreadId;
};
readAllMentions: {
chatId: string;
threadId?: ThreadId;
};
markMentionsRead: {
chatId: string;
messageIds: number[];
} & WithTabId;
copyMessageLink: {
@ -2505,7 +2514,7 @@ export interface ActionPayloads {
chatId: string;
topicId: number;
isMuted?: boolean;
muteUntil?: number;
mutedUntil?: number;
};
setViewForumAsMessages: {
chatId: string;

View File

@ -15,8 +15,10 @@ import type {
ApiGroupCall,
ApiLanguage,
ApiMessage,
ApiNotifyPeerType,
ApiPaidReactionPrivacyType,
ApiPeerColors,
ApiPeerNotifySettings,
ApiPeerPhotos,
ApiPeerStories,
ApiPhoneCall,
@ -54,7 +56,6 @@ import type {
EmojiKeywords,
ISettings,
IThemeSettings,
NotifyException,
PerformanceType,
Point,
ServiceNotification,
@ -221,6 +222,7 @@ export type GlobalState = {
similarChannelIds?: string[];
count?: number;
}>>;
notifyExceptionById: Record<string, ApiPeerNotifySettings>;
similarBotsById: Record<string, SimilarBotsInfo>;
};
@ -411,7 +413,7 @@ export type GlobalState = {
loadedWallpapers?: ApiWallpaper[];
themes: Partial<Record<ThemeKey, IThemeSettings>>;
privacy: Partial<Record<ApiPrivacyKey, ApiPrivacySettings>>;
notifyExceptions?: Record<number, NotifyException>;
notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>;
lastPremiumBandwithNotificationDate?: number;
paidReactionPrivacy?: ApiPaidReactionPrivacyType;
languages?: ApiLanguage[];

View File

@ -79,7 +79,8 @@ const useChatContextActions = ({
toggleSavedDialogPinned,
updateChatMutedState,
toggleChatArchived,
toggleChatUnread,
markChatRead,
markChatUnread,
openChatInNewTab,
} = getActions();
@ -149,10 +150,10 @@ const useChatContextActions = ({
}
const actionMaskAsRead = (chat.unreadCount || chat.hasUnreadMark)
? { title: lang('MarkAsRead'), icon: 'readchats', handler: () => toggleChatUnread({ id: chat.id }) }
? { title: lang('MarkAsRead'), icon: 'readchats', handler: () => markChatRead({ id: chat.id }) }
: undefined;
const actionMarkAsUnread = !(chat.unreadCount || chat.hasUnreadMark) && !chat.isForum
? { title: lang('MarkAsUnread'), icon: 'unread', handler: () => toggleChatUnread({ id: chat.id }) }
? { title: lang('MarkAsUnread'), icon: 'unread', handler: () => markChatUnread({ id: chat.id }) }
: undefined;
const actionArchive = isChatArchived(chat)

View File

@ -76,19 +76,6 @@ export interface IThemeSettings {
isBlurred?: boolean;
}
export type NotifySettings = {
hasPrivateChatsNotifications?: boolean;
hasPrivateChatsMessagePreview?: boolean;
hasGroupNotifications?: boolean;
hasGroupMessagePreview?: boolean;
hasBroadcastNotifications?: boolean;
hasBroadcastMessagePreview?: boolean;
hasContactJoinedNotifications?: boolean;
hasWebNotifications: boolean;
hasPushNotifications: boolean;
notificationSoundVolume: number;
};
export type LangCode = (
'en' | 'ar' | 'be' | 'ca' | 'nl' | 'fr' | 'de' | 'id' | 'it' | 'ko' | 'ms' | 'fa' | 'pl' | 'pt-br' | 'ru' | 'es'
| 'tr' | 'uk' | 'uz'
@ -96,7 +83,7 @@ export type LangCode = (
export type TimeFormat = '24h' | '12h';
export interface ISettings extends NotifySettings, Record<string, any> {
export interface ISettings {
theme: ThemeKey;
shouldUseSystemTheme: boolean;
messageTextSize: number;
@ -139,6 +126,10 @@ export interface ISettings extends NotifySettings, Record<string, any> {
shouldDebugExportedSenders?: boolean;
shouldWarnAboutSvg?: boolean;
shouldSkipWebAppCloseConfirmation: boolean;
hasContactJoinedNotifications?: boolean;
hasWebNotifications: boolean;
hasPushNotifications: boolean;
notificationSoundVolume: number;
}
export type IAnchorPosition = {
@ -461,12 +452,6 @@ export enum ManagementScreens {
export type ManagementType = 'user' | 'group' | 'channel' | 'bot';
export type NotifyException = {
isMuted: boolean;
isSilent?: boolean;
shouldShowPreviews?: boolean;
};
export type EmojiKeywords = {
isLoading?: boolean;
version?: number;

View File

@ -3,20 +3,18 @@ import { addCallback } from '../lib/teact/teactn';
import { addActionHandler, getGlobal } from '../global';
import type {
ApiChat, ApiChatFolder, ApiUser,
ApiChat, ApiChatFolder, ApiNotifyPeerType, ApiPeerNotifySettings, ApiUser,
} from '../api/types';
import type { GlobalState } from '../global/types';
import type { NotifyException, NotifySettings } from '../types';
import type { CallbackManager } from './callbacks';
import {
ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, DEBUG, SAVED_FOLDER_ID, SERVICE_NOTIFICATIONS_USER_ID,
} from '../config';
import { selectIsChatMuted } from '../global/helpers';
import { getIsChatMuted } from '../global/helpers/notifications';
import {
selectChatLastMessage,
selectNotifyExceptions,
selectNotifySettings,
selectNotifyDefaults,
selectTabState,
selectTopics,
} from '../global/selectors';
@ -79,8 +77,8 @@ let prevGlobal: {
chatsById: Record<string, ApiChat>;
foldersById: Record<string, ApiChatFolder>;
usersById: Record<string, ApiUser>;
notifySettings: NotifySettings;
notifyExceptions?: Record<number, NotifyException>;
notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>;
notifyExceptions?: Record<number, ApiPeerNotifySettings>;
} = initials.prevGlobal;
let prepared: {
@ -219,8 +217,8 @@ function updateFolderManager(global: GlobalState) {
const areAllLastMessageIdsChanged = global.chats.lastMessageIds.all !== prevGlobal.lastAllMessageIds;
const areTopicsChanged = global.chats.topicsInfoById !== prevGlobal.topicsInfoById;
const areUsersChanged = global.users.byId !== prevGlobal.usersById;
const areNotifySettingsChanged = selectNotifySettings(global) !== prevGlobal.notifySettings;
const areNotifyExceptionsChanged = selectNotifyExceptions(global) !== prevGlobal.notifyExceptions;
const areNotifyDefaultsChanged = selectNotifyDefaults(global) !== prevGlobal.notifyDefaults;
const areNotifyExceptionsChanged = global.chats.notifyExceptionById !== prevGlobal.notifyExceptions;
let affectedFolderIds: number[] = [];
@ -232,7 +230,7 @@ function updateFolderManager(global: GlobalState) {
if (!(
isAllFolderChanged || isArchivedFolderChanged || isSavedFolderChanged || areFoldersChanged
|| areChatsChanged || areUsersChanged || areTopicsChanged || areNotifySettingsChanged || areNotifyExceptionsChanged
|| areChatsChanged || areUsersChanged || areTopicsChanged || areNotifyDefaultsChanged || areNotifyExceptionsChanged
|| areSavedLastMessageIdsChanged || areAllLastMessageIdsChanged
)
) {
@ -252,7 +250,7 @@ function updateFolderManager(global: GlobalState) {
affectedFolderIds = affectedFolderIds.concat(updateChats(
global,
areFoldersChanged || isAllFolderChanged || isArchivedFolderChanged || isSavedFolderChanged,
areNotifySettingsChanged,
areNotifyDefaultsChanged,
areNotifyExceptionsChanged,
prevAllFolderListIds,
prevArchivedFolderListIds,
@ -411,7 +409,7 @@ function buildFolderSummary(folder: ApiChatFolder): FolderSummary {
function updateChats(
global: GlobalState,
areFoldersChanged: boolean,
areNotifySettingsChanged: boolean,
areNotifyDefaultsChanged: boolean,
areNotifyExceptionsChanged: boolean,
prevAllFolderListIds?: string[],
prevArchivedFolderListIds?: string[],
@ -421,8 +419,8 @@ function updateChats(
const newUsersById = global.users.byId;
const newAllLastMessageIds = global.chats.lastMessageIds.all;
const newSavedLastMessageIds = global.chats.lastMessageIds.saved;
const newNotifySettings = selectNotifySettings(global);
const newNotifyExceptions = selectNotifyExceptions(global);
const newNotifyDefaults = selectNotifyDefaults(global);
const newNotifyExceptions = global.chats.notifyExceptionById;
const folderSummaries = Object.values(prepared.folderSummariesById);
const affectedFolderIds = new Set<number>();
@ -445,7 +443,7 @@ function updateChats(
if (
!areFoldersChanged
&& !areNotifySettingsChanged
&& !areNotifyDefaultsChanged
&& !areNotifyExceptionsChanged
&& chat === prevGlobal.chatsById[chatId]
&& newUsersById[chatId] === prevGlobal.usersById[chatId]
@ -463,7 +461,7 @@ function updateChats(
const newSummary = buildChatSummary(
global,
chat,
newNotifySettings,
newNotifyDefaults,
newNotifyExceptions,
newUsersById[chatId],
isRemovedFromAll,
@ -500,7 +498,7 @@ function updateChats(
prevGlobal.usersById = newUsersById;
prevGlobal.lastAllMessageIds = newAllLastMessageIds;
prevGlobal.lastSavedMessageIds = newSavedLastMessageIds;
prevGlobal.notifySettings = newNotifySettings;
prevGlobal.notifyDefaults = newNotifyDefaults;
prevGlobal.notifyExceptions = newNotifyExceptions;
return Array.from(affectedFolderIds);
@ -509,8 +507,8 @@ function updateChats(
function buildChatSummary<T extends GlobalState>(
global: T,
chat: ApiChat,
notifySettings: NotifySettings,
notifyExceptions?: Record<number, NotifyException>,
notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>,
notifyExceptions?: Record<string, ApiPeerNotifySettings>,
user?: ApiUser,
isRemovedFromAll?: boolean,
isRemovedFromSaved?: boolean,
@ -548,7 +546,7 @@ function buildChatSummary<T extends GlobalState>(
isListedInAll: Boolean(!isRestricted && !isNotJoined && !migratedTo && !shouldHideServiceChat && !isRemovedFromAll),
isListedInSaved: !isRemovedFromSaved,
isArchived: folderId === ARCHIVED_FOLDER_ID,
isMuted: selectIsChatMuted(chat, notifySettings, notifyExceptions),
isMuted: getIsChatMuted(chat, notifyDefaults, notifyExceptions?.[chat.id]),
isUnread: Boolean(unreadCount || unreadMentionsCount || hasUnreadMark),
unreadCount,
unreadMentionsCount,
@ -810,8 +808,6 @@ function buildInitials() {
chatsById: {},
usersById: {},
topicsInfoById: {},
notifySettings: {} as NotifySettings,
notifyExceptions: {},
},
prepared: {

View File

@ -16,16 +16,16 @@ import {
getPrivateChatUserId,
getUserFullName,
isChatChannel,
selectIsChatMuted,
selectShouldShowMessagePreview,
} from '../global/helpers';
import { addNotifyExceptions, replaceSettings } from '../global/reducers';
import { getIsChatMuted, getIsChatSilent, getShouldShowMessagePreview } from '../global/helpers/notifications';
import {
selectChat,
selectCurrentMessageList,
selectIsChatWithSelf,
selectNotifyExceptions,
selectNotifySettings,
selectNotifyDefaults,
selectNotifyException,
selectSettingsKeys,
selectTopicFromMessage,
selectUser,
} from '../global/selectors';
import { callApi } from '../api/gramjs';
@ -34,6 +34,7 @@ import { buildCollectionByKey } from './iteratees';
import * as mediaLoader from './mediaLoader';
import { oldTranslate } from './oldLangProvider';
import { debounce } from './schedulers';
import { getServerTime } from './serverTime';
import { IS_ELECTRON, IS_SERVICE_WORKER_SUPPORTED, IS_TOUCH_ENV } from './windowEnvironment';
import MessageSummary from '../components/common/MessageSummary';
@ -107,7 +108,7 @@ notificationSound.setAttribute('mozaudiochannel', 'notification');
export async function playNotifySound(id?: string, volume?: number) {
if (id !== undefined && soundPlayedIds.has(id)) return;
const { notificationSoundVolume } = selectNotifySettings(getGlobal());
const { notificationSoundVolume } = selectSettingsKeys(getGlobal());
const currentVolume = volume ? volume / 10 : notificationSoundVolume / 10;
if (currentVolume === 0) return;
notificationSound.volume = currentVolume;
@ -181,27 +182,6 @@ export async function unsubscribe() {
await unsubscribeFromPush(subscription);
}
// Indicates if notification settings are loaded from the api
let areSettingsLoaded = false;
// Load notification settings from the api
async function loadNotificationSettings() {
if (areSettingsLoaded) return selectNotifySettings(getGlobal());
const [resultSettings, resultExceptions] = await Promise.all([
callApi('fetchNotificationSettings'),
callApi('fetchNotificationExceptions'),
]);
if (!resultSettings) return selectNotifySettings(getGlobal());
let global = replaceSettings(getGlobal(), resultSettings);
if (resultExceptions) {
global = addNotifyExceptions(global, resultExceptions);
}
setGlobal(global);
areSettingsLoaded = true;
return selectNotifySettings(global);
}
// Load custom emoji from the api if it's not cached already
async function loadCustomEmoji(id: string) {
let global = getGlobal();
@ -293,9 +273,12 @@ export async function subscribe() {
}
function checkIfShouldNotify(chat: ApiChat, message: Partial<ApiMessage>) {
if (!areSettingsLoaded) return false;
const global = getGlobal();
const isMuted = selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global));
const isChatMuted = getIsChatMuted(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id));
const topic = selectTopicFromMessage(global, message as ApiMessage);
const topicMutedUntil = topic?.notifySettings.mutedUntil;
const isMuted = topicMutedUntil === undefined ? isChatMuted : topicMutedUntil > getServerTime();
const shouldNotifyAboutMessage = message.content?.action?.type !== 'phoneCall';
if (isMuted || !shouldNotifyAboutMessage
|| chat.isNotJoined || !chat.isListed || selectIsChatWithSelf(global, chat.id)) {
@ -330,7 +313,7 @@ function getNotificationContent(chat: ApiChat, message: ApiMessage, reaction?: A
let body: string;
if (
!isScreenLocked
&& selectShouldShowMessagePreview(chat, selectNotifySettings(global), selectNotifyExceptions(global))
&& getShouldShowMessagePreview(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id))
) {
const isChat = chat && (isChatChannel(chat) || message.senderId === message.chatId);
@ -386,7 +369,7 @@ export async function notifyAboutCall({
}: {
call: ApiPhoneCall; user: ApiUser;
}) {
const { hasWebNotifications } = await loadNotificationSettings();
const { hasWebNotifications } = selectSettingsKeys(getGlobal());
if (document.hasFocus() || !hasWebNotifications) return;
const areNotificationsSupported = checkIfNotificationsSupported();
if (!areNotificationsSupported) return;
@ -420,11 +403,18 @@ export async function notifyAboutMessage({
message,
isReaction = false,
}: { chat: ApiChat; message: Partial<ApiMessage>; isReaction?: boolean }) {
const { hasWebNotifications } = await loadNotificationSettings();
const global = getGlobal();
const { hasWebNotifications } = selectSettingsKeys(global);
if (!checkIfShouldNotify(chat, message)) return;
const isChatSilent = getIsChatSilent(
chat, selectNotifyDefaults(getGlobal()), selectNotifyException(getGlobal(), chat.id),
);
const topic = selectTopicFromMessage(global, message as ApiMessage);
const isSilent = topic?.notifySettings.hasSound === undefined ? isChatSilent : !topic.notifySettings.hasSound;
const areNotificationsSupported = checkIfNotificationsSupported();
if (!hasWebNotifications || !areNotificationsSupported) {
if (!message.isSilent && !isReaction && !IS_ELECTRON) {
if (!isSilent && !message.isSilent && !isReaction && !IS_ELECTRON) {
// Only play sound if web notifications are disabled
playNotifySoundDebounced(String(message.id) || chat.id);
}
@ -463,7 +453,7 @@ export async function notifyAboutMessage({
chatId: chat.id,
messageId: message.id,
shouldReplaceHistory: true,
isSilent: message.isSilent,
isSilent: isSilent || message.isSilent,
reaction: activeReaction?.reaction,
},
});