diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 465906a1a..08d966c50 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -1909,6 +1909,7 @@ "ActionPinnedTextYou" = "You pinned \"{text}\""; "ActionPinnedNotFound" = "{from} pinned a message"; "ActionPinnedNotFoundYou" = "You pinned a message"; +"PinnedMessagesNotifications" = "Pinned Messages"; "ActionPinnedMedia" = "{from} pinned {media}"; "ActionPinnedMediaYou" = "You pinned {media}"; "ActionPinnedMediaPhoto" = "a photo"; diff --git a/src/components/left/settings/SettingsNotifications.tsx b/src/components/left/settings/SettingsNotifications.tsx index 5ea7feb62..1a97f74d4 100644 --- a/src/components/left/settings/SettingsNotifications.tsx +++ b/src/components/left/settings/SettingsNotifications.tsx @@ -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 { getActions, withGlobal } from '../../../global'; @@ -26,22 +24,25 @@ type OwnProps = { type StateProps = { notifyDefaults?: Record; hasContactJoinedNotifications: boolean; + shouldNotifyAboutPinnedMessages: boolean; hasWebNotifications: boolean; hasPushNotifications: boolean; notificationSoundVolume: number; }; -const SettingsNotifications: FC = ({ +const SettingsNotifications = ({ isActive, onReset, notifyDefaults, hasContactJoinedNotifications, + shouldNotifyAboutPinnedMessages, hasPushNotifications, hasWebNotifications, notificationSoundVolume, -}) => { +}: OwnProps & StateProps) => { const { loadNotificationSettings, + setSettingOption, updateContactSignUpNotification, updateNotificationSettings, updateWebNotificationSettings, @@ -61,7 +62,7 @@ const SettingsNotifications: FC = ({ const areUsersMuted = Boolean(notifyDefaults?.users?.mutedUntil); const handleSettingsChange = useCallback(( - e: ChangeEvent, + e: React.ChangeEvent, peerType: ApiNotifyPeerType, setting: 'mute' | 'showPreviews', ) => { @@ -73,52 +74,56 @@ const SettingsNotifications: FC = ({ isMuted: setting === 'mute' ? !e.target.checked : currentIsMuted, shouldShowPreviews: setting === 'showPreviews' ? e.target.checked : currentShouldShowPreviews, }); - }, [notifyDefaults]); + }, [notifyDefaults, updateNotificationSettings]); - const handleWebNotificationsChange = useCallback((e: ChangeEvent) => { + const handleWebNotificationsChange = useCallback((e: React.ChangeEvent) => { const isEnabled = e.target.checked; updateWebNotificationSettings({ hasWebNotifications: isEnabled, - ...(!isEnabled && { hasPushNotifications: false }), + hasPushNotifications: isEnabled ? undefined : false, }); }, [updateWebNotificationSettings]); - const handlePushNotificationsChange = useCallback((e: ChangeEvent) => { + const handlePushNotificationsChange = useCallback((e: React.ChangeEvent) => { updateWebNotificationSettings({ hasPushNotifications: e.target.checked, }); }, [updateWebNotificationSettings]); - const handlePrivateChatsNotificationsChange = useCallback((e: ChangeEvent) => { + const handlePrivateChatsNotificationsChange = useCallback((e: React.ChangeEvent) => { handleSettingsChange(e, 'users', 'mute'); }, [handleSettingsChange]); - const handlePrivateChatsPreviewChange = useCallback((e: ChangeEvent) => { + const handlePrivateChatsPreviewChange = useCallback((e: React.ChangeEvent) => { handleSettingsChange(e, 'users', 'showPreviews'); }, [handleSettingsChange]); - const handleGroupsNotificationsChange = useCallback((e: ChangeEvent) => { + const handleGroupsNotificationsChange = useCallback((e: React.ChangeEvent) => { handleSettingsChange(e, 'groups', 'mute'); }, [handleSettingsChange]); - const handleGroupsPreviewChange = useCallback((e: ChangeEvent) => { + const handleGroupsPreviewChange = useCallback((e: React.ChangeEvent) => { handleSettingsChange(e, 'groups', 'showPreviews'); }, [handleSettingsChange]); - const handleChannelsNotificationsChange = useCallback((e: ChangeEvent) => { + const handleChannelsNotificationsChange = useCallback((e: React.ChangeEvent) => { handleSettingsChange(e, 'channels', 'mute'); }, [handleSettingsChange]); - const handleChannelsPreviewChange = useCallback((e: ChangeEvent) => { + const handleChannelsPreviewChange = useCallback((e: React.ChangeEvent) => { handleSettingsChange(e, 'channels', 'showPreviews'); }, [handleSettingsChange]); - const handleContactNotificationChange = useCallback((e: ChangeEvent) => { + const handleContactNotificationChange = useCallback((e: React.ChangeEvent) => { updateContactSignUpNotification({ isSilent: !e.target.checked, }); }, [updateContactSignUpNotification]); + const handlePinnedMessagesNotificationChange = useCallback((e: React.ChangeEvent) => { + setSettingOption({ shouldNotifyAboutPinnedMessages: e.target.checked }); + }, [setSettingOption]); + const handleVolumeChange = useCallback((volume: number) => { updateWebNotificationSettings({ notificationSoundVolume: volume, @@ -233,6 +238,11 @@ const SettingsNotifications: FC = ({ checked={hasContactJoinedNotifications} onChange={handleContactNotificationChange} /> + ); @@ -242,6 +252,7 @@ export default memo(withGlobal( (global): Complete => { return { hasContactJoinedNotifications: Boolean(global.settings.byKey.hasContactJoinedNotifications), + shouldNotifyAboutPinnedMessages: global.settings.byKey.shouldNotifyAboutPinnedMessages, hasWebNotifications: global.settings.byKey.hasWebNotifications, hasPushNotifications: global.settings.byKey.hasPushNotifications, notificationSoundVolume: global.settings.byKey.notificationSoundVolume, diff --git a/src/global/helpers/notifications.ts b/src/global/helpers/notifications.ts index c25613918..92f0d5942 100644 --- a/src/global/helpers/notifications.ts +++ b/src/global/helpers/notifications.ts @@ -1,5 +1,6 @@ import type { ApiChat, + ApiMessage, ApiNotifyPeerType, ApiPeer, ApiPeerNotifySettings, @@ -39,6 +40,20 @@ export function getShouldShowMessagePreview( return Boolean(settings?.shouldShowPreviews); } +export function getShouldIgnoreNotificationMute( + message: Partial, + 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( chat: ApiChat, notifyDefaults?: Record, diff --git a/src/global/initialState.ts b/src/global/initialState.ts index dbf71f385..27777cac2 100644 --- a/src/global/initialState.ts +++ b/src/global/initialState.ts @@ -300,6 +300,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = { autoLoadFileMaxSizeMb: 10, hasWebNotifications: true, hasPushNotifications: true, + shouldNotifyAboutPinnedMessages: true, notificationSoundVolume: 5, shouldSuggestStickers: true, shouldSuggestCustomEmoji: true, diff --git a/src/types/index.ts b/src/types/index.ts index 5f41cdb2d..199fc9f9f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -139,6 +139,7 @@ export interface AccountSettings { hasWebNotifications: boolean; hasPushNotifications: boolean; hasContactJoinedNotifications?: boolean; + shouldNotifyAboutPinnedMessages: boolean; notificationSoundVolume: number; canAutoLoadPhotoFromContacts: boolean; canAutoLoadPhotoInPrivateChats: boolean; diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 31692c43a..9dbf83e7a 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1543,6 +1543,7 @@ export interface LangPair { 'ActionFallbackSomeone': undefined; 'ActionUnsupported': undefined; 'ActionPinnedNotFoundYou': undefined; + 'PinnedMessagesNotifications': undefined; 'ActionPinnedMediaPhoto': undefined; 'ActionPinnedMediaVideo': undefined; 'ActionPinnedMediaAudio': undefined; diff --git a/src/util/notifications.tsx b/src/util/notifications.tsx index 8e3ba64ba..50cd15c4c 100644 --- a/src/util/notifications.tsx +++ b/src/util/notifications.tsx @@ -13,7 +13,12 @@ import { getMessageRecentReaction, getUserFullName, } 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 { selectCurrentMessageList, @@ -291,10 +296,13 @@ function checkIfShouldNotify(chat: ApiChat, message: Partial) { const topic = selectTopicFromMessage(global, message as ApiMessage); const topicMutedUntil = topic?.notifySettings.mutedUntil; 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'; - if ((isMuted && !shouldIgnoreMute) || !shouldNotifyAboutMessage + if (shouldSkipPinnedMessageNotification || (isMuted && !shouldIgnoreMute) || !shouldNotifyAboutMessage || chat.isNotJoined || !chat.isListed || selectIsChatWithSelf(global, chat.id)) { return false; } @@ -496,7 +504,7 @@ export async function notifyAboutMessage({ // Play sound when notification is displayed notification.onshow = () => { // 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); }; }