Notifications: Fix pinned message ignore mute (#6897)

This commit is contained in:
Alexander Zinchuk 2026-04-27 14:29:24 +02:00
parent d8f913b3a0
commit 881c09acdf
7 changed files with 58 additions and 20 deletions

View File

@ -1909,6 +1909,7 @@
"ActionPinnedTextYou" = "You pinned \"{text}\""; "ActionPinnedTextYou" = "You pinned \"{text}\"";
"ActionPinnedNotFound" = "{from} pinned a message"; "ActionPinnedNotFound" = "{from} pinned a message";
"ActionPinnedNotFoundYou" = "You pinned a message"; "ActionPinnedNotFoundYou" = "You pinned a message";
"PinnedMessagesNotifications" = "Pinned Messages";
"ActionPinnedMedia" = "{from} pinned {media}"; "ActionPinnedMedia" = "{from} pinned {media}";
"ActionPinnedMediaYou" = "You pinned {media}"; "ActionPinnedMediaYou" = "You pinned {media}";
"ActionPinnedMediaPhoto" = "a photo"; "ActionPinnedMediaPhoto" = "a photo";

View File

@ -1,5 +1,3 @@
import type { ChangeEvent } from 'react';
import type { FC } from '../../../lib/teact/teact';
import { memo, useCallback, useEffect } from '../../../lib/teact/teact'; import { memo, useCallback, useEffect } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
@ -26,22 +24,25 @@ type OwnProps = {
type StateProps = { type StateProps = {
notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>; notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>;
hasContactJoinedNotifications: boolean; hasContactJoinedNotifications: boolean;
shouldNotifyAboutPinnedMessages: boolean;
hasWebNotifications: boolean; hasWebNotifications: boolean;
hasPushNotifications: boolean; hasPushNotifications: boolean;
notificationSoundVolume: number; notificationSoundVolume: number;
}; };
const SettingsNotifications: FC<OwnProps & StateProps> = ({ const SettingsNotifications = ({
isActive, isActive,
onReset, onReset,
notifyDefaults, notifyDefaults,
hasContactJoinedNotifications, hasContactJoinedNotifications,
shouldNotifyAboutPinnedMessages,
hasPushNotifications, hasPushNotifications,
hasWebNotifications, hasWebNotifications,
notificationSoundVolume, notificationSoundVolume,
}) => { }: OwnProps & StateProps) => {
const { const {
loadNotificationSettings, loadNotificationSettings,
setSettingOption,
updateContactSignUpNotification, updateContactSignUpNotification,
updateNotificationSettings, updateNotificationSettings,
updateWebNotificationSettings, updateWebNotificationSettings,
@ -61,7 +62,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
const areUsersMuted = Boolean(notifyDefaults?.users?.mutedUntil); const areUsersMuted = Boolean(notifyDefaults?.users?.mutedUntil);
const handleSettingsChange = useCallback(( const handleSettingsChange = useCallback((
e: ChangeEvent<HTMLInputElement>, e: React.ChangeEvent<HTMLInputElement>,
peerType: ApiNotifyPeerType, peerType: ApiNotifyPeerType,
setting: 'mute' | 'showPreviews', setting: 'mute' | 'showPreviews',
) => { ) => {
@ -73,52 +74,56 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
isMuted: setting === 'mute' ? !e.target.checked : currentIsMuted, isMuted: setting === 'mute' ? !e.target.checked : currentIsMuted,
shouldShowPreviews: setting === 'showPreviews' ? e.target.checked : currentShouldShowPreviews, shouldShowPreviews: setting === 'showPreviews' ? e.target.checked : currentShouldShowPreviews,
}); });
}, [notifyDefaults]); }, [notifyDefaults, updateNotificationSettings]);
const handleWebNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleWebNotificationsChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const isEnabled = e.target.checked; const isEnabled = e.target.checked;
updateWebNotificationSettings({ updateWebNotificationSettings({
hasWebNotifications: isEnabled, hasWebNotifications: isEnabled,
...(!isEnabled && { hasPushNotifications: false }), hasPushNotifications: isEnabled ? undefined : false,
}); });
}, [updateWebNotificationSettings]); }, [updateWebNotificationSettings]);
const handlePushNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handlePushNotificationsChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
updateWebNotificationSettings({ updateWebNotificationSettings({
hasPushNotifications: e.target.checked, hasPushNotifications: e.target.checked,
}); });
}, [updateWebNotificationSettings]); }, [updateWebNotificationSettings]);
const handlePrivateChatsNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handlePrivateChatsNotificationsChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'users', 'mute'); handleSettingsChange(e, 'users', 'mute');
}, [handleSettingsChange]); }, [handleSettingsChange]);
const handlePrivateChatsPreviewChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handlePrivateChatsPreviewChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'users', 'showPreviews'); handleSettingsChange(e, 'users', 'showPreviews');
}, [handleSettingsChange]); }, [handleSettingsChange]);
const handleGroupsNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleGroupsNotificationsChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'groups', 'mute'); handleSettingsChange(e, 'groups', 'mute');
}, [handleSettingsChange]); }, [handleSettingsChange]);
const handleGroupsPreviewChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleGroupsPreviewChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'groups', 'showPreviews'); handleSettingsChange(e, 'groups', 'showPreviews');
}, [handleSettingsChange]); }, [handleSettingsChange]);
const handleChannelsNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleChannelsNotificationsChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'channels', 'mute'); handleSettingsChange(e, 'channels', 'mute');
}, [handleSettingsChange]); }, [handleSettingsChange]);
const handleChannelsPreviewChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleChannelsPreviewChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'channels', 'showPreviews'); handleSettingsChange(e, 'channels', 'showPreviews');
}, [handleSettingsChange]); }, [handleSettingsChange]);
const handleContactNotificationChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleContactNotificationChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
updateContactSignUpNotification({ updateContactSignUpNotification({
isSilent: !e.target.checked, isSilent: !e.target.checked,
}); });
}, [updateContactSignUpNotification]); }, [updateContactSignUpNotification]);
const handlePinnedMessagesNotificationChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setSettingOption({ shouldNotifyAboutPinnedMessages: e.target.checked });
}, [setSettingOption]);
const handleVolumeChange = useCallback((volume: number) => { const handleVolumeChange = useCallback((volume: number) => {
updateWebNotificationSettings({ updateWebNotificationSettings({
notificationSoundVolume: volume, notificationSoundVolume: volume,
@ -233,6 +238,11 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
checked={hasContactJoinedNotifications} checked={hasContactJoinedNotifications}
onChange={handleContactNotificationChange} onChange={handleContactNotificationChange}
/> />
<Checkbox
label={lang('PinnedMessagesNotifications')}
checked={shouldNotifyAboutPinnedMessages}
onChange={handlePinnedMessagesNotificationChange}
/>
</div> </div>
</div> </div>
); );
@ -242,6 +252,7 @@ export default memo(withGlobal<OwnProps>(
(global): Complete<StateProps> => { (global): Complete<StateProps> => {
return { return {
hasContactJoinedNotifications: Boolean(global.settings.byKey.hasContactJoinedNotifications), hasContactJoinedNotifications: Boolean(global.settings.byKey.hasContactJoinedNotifications),
shouldNotifyAboutPinnedMessages: global.settings.byKey.shouldNotifyAboutPinnedMessages,
hasWebNotifications: global.settings.byKey.hasWebNotifications, hasWebNotifications: global.settings.byKey.hasWebNotifications,
hasPushNotifications: global.settings.byKey.hasPushNotifications, hasPushNotifications: global.settings.byKey.hasPushNotifications,
notificationSoundVolume: global.settings.byKey.notificationSoundVolume, notificationSoundVolume: global.settings.byKey.notificationSoundVolume,

View File

@ -1,5 +1,6 @@
import type { import type {
ApiChat, ApiChat,
ApiMessage,
ApiNotifyPeerType, ApiNotifyPeerType,
ApiPeer, ApiPeer,
ApiPeerNotifySettings, ApiPeerNotifySettings,
@ -39,6 +40,20 @@ export function getShouldShowMessagePreview(
return Boolean(settings?.shouldShowPreviews); return Boolean(settings?.shouldShowPreviews);
} }
export function getShouldIgnoreNotificationMute(
message: Partial<ApiMessage>,
shouldNotifyAboutPinnedMessages: boolean,
) {
if (!message.isMentioned) return false;
const isPinMessage = message.content?.action?.type === 'pinMessage';
if (isPinMessage) {
return shouldNotifyAboutPinnedMessages && !message.isSilent;
}
return true;
}
export function getChatNotifySettings( export function getChatNotifySettings(
chat: ApiChat, chat: ApiChat,
notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>, notifyDefaults?: Record<ApiNotifyPeerType, ApiPeerNotifySettings>,

View File

@ -300,6 +300,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
autoLoadFileMaxSizeMb: 10, autoLoadFileMaxSizeMb: 10,
hasWebNotifications: true, hasWebNotifications: true,
hasPushNotifications: true, hasPushNotifications: true,
shouldNotifyAboutPinnedMessages: true,
notificationSoundVolume: 5, notificationSoundVolume: 5,
shouldSuggestStickers: true, shouldSuggestStickers: true,
shouldSuggestCustomEmoji: true, shouldSuggestCustomEmoji: true,

View File

@ -139,6 +139,7 @@ export interface AccountSettings {
hasWebNotifications: boolean; hasWebNotifications: boolean;
hasPushNotifications: boolean; hasPushNotifications: boolean;
hasContactJoinedNotifications?: boolean; hasContactJoinedNotifications?: boolean;
shouldNotifyAboutPinnedMessages: boolean;
notificationSoundVolume: number; notificationSoundVolume: number;
canAutoLoadPhotoFromContacts: boolean; canAutoLoadPhotoFromContacts: boolean;
canAutoLoadPhotoInPrivateChats: boolean; canAutoLoadPhotoInPrivateChats: boolean;

View File

@ -1543,6 +1543,7 @@ export interface LangPair {
'ActionFallbackSomeone': undefined; 'ActionFallbackSomeone': undefined;
'ActionUnsupported': undefined; 'ActionUnsupported': undefined;
'ActionPinnedNotFoundYou': undefined; 'ActionPinnedNotFoundYou': undefined;
'PinnedMessagesNotifications': undefined;
'ActionPinnedMediaPhoto': undefined; 'ActionPinnedMediaPhoto': undefined;
'ActionPinnedMediaVideo': undefined; 'ActionPinnedMediaVideo': undefined;
'ActionPinnedMediaAudio': undefined; 'ActionPinnedMediaAudio': undefined;

View File

@ -13,7 +13,12 @@ import {
getMessageRecentReaction, getMessageRecentReaction,
getUserFullName, getUserFullName,
} from '../global/helpers'; } from '../global/helpers';
import { getIsChatMuted, getIsChatSilent, getShouldShowMessagePreview } from '../global/helpers/notifications'; import {
getIsChatMuted,
getIsChatSilent,
getShouldIgnoreNotificationMute,
getShouldShowMessagePreview,
} from '../global/helpers/notifications';
import { getMessageSenderName } from '../global/helpers/peers'; import { getMessageSenderName } from '../global/helpers/peers';
import { import {
selectCurrentMessageList, selectCurrentMessageList,
@ -291,10 +296,13 @@ function checkIfShouldNotify(chat: ApiChat, message: Partial<ApiMessage>) {
const topic = selectTopicFromMessage(global, message as ApiMessage); const topic = selectTopicFromMessage(global, message as ApiMessage);
const topicMutedUntil = topic?.notifySettings.mutedUntil; const topicMutedUntil = topic?.notifySettings.mutedUntil;
const isMuted = topicMutedUntil === undefined ? isChatMuted : topicMutedUntil > getServerTime(); const isMuted = topicMutedUntil === undefined ? isChatMuted : topicMutedUntil > getServerTime();
const shouldIgnoreMute = message.isMentioned; const shouldNotifyAboutPinnedMessages = global.settings.byKey.shouldNotifyAboutPinnedMessages;
const shouldIgnoreMute = getShouldIgnoreNotificationMute(message, shouldNotifyAboutPinnedMessages);
const isPinMessage = message.content?.action?.type === 'pinMessage';
const shouldSkipPinnedMessageNotification = isPinMessage && !shouldNotifyAboutPinnedMessages;
const shouldNotifyAboutMessage = message.content?.action?.type !== 'phoneCall'; const shouldNotifyAboutMessage = message.content?.action?.type !== 'phoneCall';
if ((isMuted && !shouldIgnoreMute) || !shouldNotifyAboutMessage if (shouldSkipPinnedMessageNotification || (isMuted && !shouldIgnoreMute) || !shouldNotifyAboutMessage
|| chat.isNotJoined || !chat.isListed || selectIsChatWithSelf(global, chat.id)) { || chat.isNotJoined || !chat.isListed || selectIsChatWithSelf(global, chat.id)) {
return false; return false;
} }
@ -496,7 +504,7 @@ export async function notifyAboutMessage({
// Play sound when notification is displayed // Play sound when notification is displayed
notification.onshow = () => { notification.onshow = () => {
// TODO Update when reaction badges are implemented // TODO Update when reaction badges are implemented
if (isReaction || message.isSilent || IS_TAURI) return; if (isSilent || isReaction || message.isSilent || IS_TAURI) return;
playNotifySoundDebounced(String(message.id) || chat.id); playNotifySoundDebounced(String(message.id) || chat.id);
}; };
} }