Settings: Fix notifications checkbox logic (#6211)

This commit is contained in:
Alexander Zinchuk 2025-09-19 14:35:07 +02:00
parent d3b4c57aab
commit ba80452ac1
19 changed files with 123 additions and 79 deletions

View File

@ -27,7 +27,6 @@ import {
ARCHIVED_FOLDER_ID, ARCHIVED_FOLDER_ID,
DEBUG, DEBUG,
GENERAL_TOPIC_ID, GENERAL_TOPIC_ID,
MAX_INT_32,
MEMBERS_LOAD_SLICE, MEMBERS_LOAD_SLICE,
SERVICE_NOTIFICATIONS_USER_ID, SERVICE_NOTIFICATIONS_USER_ID,
TOPICS_SLICE, TOPICS_SLICE,
@ -201,7 +200,9 @@ export async function fetchChats({
if (Object.values(omitUndefined(notifySettings)).length) { if (Object.values(omitUndefined(notifySettings)).length) {
notifyExceptionById[chat.id] = notifySettings; notifyExceptionById[chat.id] = notifySettings;
scheduleMutedChatUpdate(chat.id, notifySettings.mutedUntil, sendApiUpdate); if (notifySettings.mutedUntil) {
scheduleMutedChatUpdate(chat.id, notifySettings.mutedUntil, sendApiUpdate);
}
} }
if (withPinned && dialog.pinned) { if (withPinned && dialog.pinned) {
@ -512,7 +513,9 @@ export async function requestChatUpdate({
const notifySettings = buildApiPeerNotifySettings(dialog.notifySettings); const notifySettings = buildApiPeerNotifySettings(dialog.notifySettings);
scheduleMutedChatUpdate(chatUpdate.id, notifySettings.mutedUntil, sendApiUpdate); if (notifySettings.mutedUntil) {
scheduleMutedChatUpdate(chatUpdate.id, notifySettings.mutedUntil, sendApiUpdate);
}
} }
export function saveDraft({ export function saveDraft({
@ -798,13 +801,10 @@ export function updateChatNotifySettings({
} }
export function updateTopicMutedState({ export function updateTopicMutedState({
chat, topicId, isMuted, mutedUntil = 0, chat, topicId, mutedUntil,
}: { }: {
chat: ApiChat; topicId: number; isMuted?: boolean; mutedUntil?: number; chat: ApiChat; topicId: number; mutedUntil: number;
}) { }) {
if (isMuted && !mutedUntil) {
mutedUntil = MAX_INT_32;
}
invokeRequest(new GramJs.account.UpdateNotifySettings({ invokeRequest(new GramJs.account.UpdateNotifySettings({
peer: new GramJs.InputNotifyForumTopic({ peer: new GramJs.InputNotifyForumTopic({
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),

View File

@ -19,7 +19,8 @@ import type {
import { import {
ACCEPTABLE_USERNAME_ERRORS, ACCEPTABLE_USERNAME_ERRORS,
LANG_PACK, LANG_PACK,
MAX_INT_32, MUTE_INDEFINITE_TIMESTAMP,
UNMUTE_TIMESTAMP,
} from '../../../config'; } from '../../../config';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
import { BLOCKED_LIST_LIMIT } from '../../../limits'; import { BLOCKED_LIST_LIMIT } from '../../../limits';
@ -394,7 +395,7 @@ export function updateNotificationSettings(peerType: ApiNotifyPeerType, {
const settings = { const settings = {
showPreviews: shouldShowPreviews, showPreviews: shouldShowPreviews,
muteUntil: isMuted ? MAX_INT_32 : 0, muteUntil: isMuted ? MUTE_INDEFINITE_TIMESTAMP : UNMUTE_TIMESTAMP,
}; };
return invokeRequest(new GramJs.account.UpdateNotifySettings({ return invokeRequest(new GramJs.account.UpdateNotifySettings({

View File

@ -1,6 +1,6 @@
import type { OnApiUpdate } from '../types'; import type { OnApiUpdate } from '../types';
import { MAX_INT_32 } from '../../config'; import { MUTE_INDEFINITE_TIMESTAMP } from '../../config';
import { getServerTime } from '../../util/serverTime'; import { getServerTime } from '../../util/serverTime';
type UnmuteQueueItem = { chatId: string; topicId?: number; mutedUntil: number }; type UnmuteQueueItem = { chatId: string; topicId?: number; mutedUntil: number };
@ -12,7 +12,7 @@ const scheduleUnmute = (item: UnmuteQueueItem, onUpdate: NoneToVoidFunction) =>
clearTimeout(unmuteTimers.get(id)); clearTimeout(unmuteTimers.get(id));
unmuteTimers.delete(id); unmuteTimers.delete(id);
} }
if (item.mutedUntil === MAX_INT_32 || item.mutedUntil <= getServerTime()) return; if (item.mutedUntil === MUTE_INDEFINITE_TIMESTAMP || item.mutedUntil <= getServerTime()) return;
unmuteQueue.push(item); unmuteQueue.push(item);
unmuteQueue.sort((a, b) => b.mutedUntil - a.mutedUntil); unmuteQueue.sort((a, b) => b.mutedUntil - a.mutedUntil);
const next = unmuteQueue.pop(); const next = unmuteQueue.pop();
@ -27,7 +27,7 @@ const scheduleUnmute = (item: UnmuteQueueItem, onUpdate: NoneToVoidFunction) =>
unmuteTimers.set(id, timer); unmuteTimers.set(id, timer);
}; };
export function scheduleMutedChatUpdate(chatId: string, mutedUntil = 0, onUpdate: OnApiUpdate) { export function scheduleMutedChatUpdate(chatId: string, mutedUntil: number, onUpdate: OnApiUpdate) {
scheduleUnmute({ scheduleUnmute({
chatId, chatId,
mutedUntil, mutedUntil,
@ -35,12 +35,12 @@ export function scheduleMutedChatUpdate(chatId: string, mutedUntil = 0, onUpdate
'@type': 'updateChatNotifySettings', '@type': 'updateChatNotifySettings',
chatId, chatId,
settings: { settings: {
mutedUntil: 0, mutedUntil: undefined,
}, },
})); }));
} }
export function scheduleMutedTopicUpdate(chatId: string, topicId: number, mutedUntil = 0, onUpdate: OnApiUpdate) { export function scheduleMutedTopicUpdate(chatId: string, topicId: number, mutedUntil: number, onUpdate: OnApiUpdate) {
scheduleUnmute({ scheduleUnmute({
chatId, chatId,
topicId, topicId,
@ -50,7 +50,7 @@ export function scheduleMutedTopicUpdate(chatId: string, topicId: number, mutedU
chatId, chatId,
topicId, topicId,
settings: { settings: {
mutedUntil: 0, mutedUntil: undefined,
}, },
})); }));
} }

View File

@ -803,7 +803,9 @@ export function updater(update: Update) {
if (notifyPeer instanceof GramJs.NotifyPeer) { if (notifyPeer instanceof GramJs.NotifyPeer) {
const peerId = getApiChatIdFromMtpPeer(notifyPeer.peer); const peerId = getApiChatIdFromMtpPeer(notifyPeer.peer);
scheduleMutedChatUpdate(peerId, settings.mutedUntil, sendApiUpdate); if (settings.mutedUntil) {
scheduleMutedChatUpdate(peerId, settings.mutedUntil, sendApiUpdate);
}
sendApiUpdate({ sendApiUpdate({
'@type': 'updateChatNotifySettings', '@type': 'updateChatNotifySettings',
chatId: peerId, chatId: peerId,
@ -814,7 +816,9 @@ export function updater(update: Update) {
if (notifyPeer instanceof GramJs.NotifyForumTopic) { if (notifyPeer instanceof GramJs.NotifyForumTopic) {
const peerId = getApiChatIdFromMtpPeer(notifyPeer.peer); const peerId = getApiChatIdFromMtpPeer(notifyPeer.peer);
scheduleMutedTopicUpdate(peerId, notifyPeer.topMsgId, settings.mutedUntil, sendApiUpdate); if (settings.mutedUntil) {
scheduleMutedTopicUpdate(peerId, notifyPeer.topMsgId, settings.mutedUntil, sendApiUpdate);
}
sendApiUpdate({ sendApiUpdate({
'@type': 'updateTopicNotifySettings', '@type': 'updateTopicNotifySettings',
chatId: peerId, chatId: peerId,

View File

@ -15,7 +15,9 @@ import type {
import type { BotAppPermissions } from '../../../types'; import type { BotAppPermissions } from '../../../types';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
import { FRAGMENT_PHONE_CODE, FRAGMENT_PHONE_LENGTH } from '../../../config'; import {
FRAGMENT_PHONE_CODE, FRAGMENT_PHONE_LENGTH, MUTE_INDEFINITE_TIMESTAMP, UNMUTE_TIMESTAMP,
} from '../../../config';
import { import {
buildStaticMapHash, buildStaticMapHash,
getChatLink, getChatLink,
@ -202,15 +204,16 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
openMapModal({ geoPoint: geo, zoom }); openMapModal({ geoPoint: geo, zoom });
}); });
const handleNotificationChange = useLastCallback(() => { const handleToggleNotifications = useLastCallback(() => {
const mutedUntil = isMuted ? UNMUTE_TIMESTAMP : MUTE_INDEFINITE_TIMESTAMP;
if (isTopicInfo) { if (isTopicInfo) {
updateTopicMutedState({ updateTopicMutedState({
chatId: chatId!, chatId: chatId!,
topicId: topicId!, topicId: topicId!,
isMuted: !isMuted, mutedUntil,
}); });
} else { } else {
updateChatMutedState({ chatId: chatId!, isMuted: !isMuted }); updateChatMutedState({ chatId: chatId!, mutedUntil });
} }
}); });
@ -415,7 +418,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
</ListItem> </ListItem>
)} )}
{!isInSettings && ( {!isInSettings && (
<ListItem icon="unmute" narrow ripple onClick={handleNotificationChange}> <ListItem icon={isMuted ? 'unmute' : 'mute'} narrow ripple onClick={handleToggleNotifications}>
<span>{oldLang('Notifications')}</span> <span>{oldLang('Notifications')}</span>
<Switcher <Switcher
id="group-notifications" id="group-notifications"

View File

@ -4,7 +4,7 @@ import {
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions } from '../../global'; import { getActions } from '../../global';
import { MAX_INT_32 } from '../../config'; import { MUTE_INDEFINITE_TIMESTAMP } from '../../config';
import useOldLang from '../../hooks/useOldLang'; import useOldLang from '../../hooks/useOldLang';
@ -53,7 +53,7 @@ const MuteChatModal: FC<OwnProps> = ({
const handleSubmit = useCallback(() => { const handleSubmit = useCallback(() => {
let mutedUntil: number; let mutedUntil: number;
if (muteUntilOption === MuteDuration.Forever) { if (muteUntilOption === MuteDuration.Forever) {
mutedUntil = MAX_INT_32; mutedUntil = MUTE_INDEFINITE_TIMESTAMP;
} else { } else {
mutedUntil = Math.floor(Date.now() / 1000) + Number(muteUntilOption); mutedUntil = Math.floor(Date.now() / 1000) + Number(muteUntilOption);
} }

View File

@ -20,6 +20,7 @@ import type { ChatAnimationTypes } from './hooks';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
import { StoryViewerOrigin } from '../../../types'; import { StoryViewerOrigin } from '../../../types';
import { UNMUTE_TIMESTAMP } from '../../../config';
import { import {
groupStatefulContent, groupStatefulContent,
isUserOnline, isUserOnline,
@ -81,6 +82,7 @@ import ChatCallStatus from './ChatCallStatus';
import ChatTags from './ChatTags'; import ChatTags from './ChatTags';
import './Chat.scss'; import './Chat.scss';
type OwnProps = { type OwnProps = {
chatId: string; chatId: string;
folderId?: number; folderId?: number;
@ -185,6 +187,7 @@ const Chat: FC<OwnProps & StateProps> = ({
setShouldCloseRightColumn, setShouldCloseRightColumn,
reportMessages, reportMessages,
openFrozenAccountModal, openFrozenAccountModal,
updateChatMutedState,
} = getActions(); } = getActions();
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
@ -292,6 +295,15 @@ const Chat: FC<OwnProps & StateProps> = ({
openMuteModal(); openMuteModal();
}); });
const handleUnmute = useLastCallback(() => {
if (isAccountFrozen) {
openFrozenAccountModal();
return;
}
updateChatMutedState({ chatId, mutedUntil: UNMUTE_TIMESTAMP });
});
const handleChatFolderChange = useLastCallback(() => { const handleChatFolderChange = useLastCallback(() => {
markRenderChatFolderModal(); markRenderChatFolderModal();
openChatFolderModal(); openChatFolderModal();
@ -312,6 +324,7 @@ const Chat: FC<OwnProps & StateProps> = ({
user, user,
handleDelete, handleDelete,
handleMute, handleMute,
handleUnmute,
handleChatFolderChange, handleChatFolderChange,
handleReport, handleReport,
folderId, folderId,

View File

@ -9,6 +9,7 @@ import type {
import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { ChatAnimationTypes } from './hooks'; import type { ChatAnimationTypes } from './hooks';
import { UNMUTE_TIMESTAMP } from '../../../config';
import { groupStatefulContent } from '../../../global/helpers'; import { groupStatefulContent } from '../../../global/helpers';
import { getIsChatMuted } from '../../../global/helpers/notifications'; import { getIsChatMuted } from '../../../global/helpers/notifications';
import { import {
@ -101,6 +102,7 @@ const Topic: FC<OwnProps & StateProps> = ({
deleteTopic, deleteTopic,
focusLastMessage, focusLastMessage,
setViewForumAsMessages, setViewForumAsMessages,
updateTopicMutedState,
} = getActions(); } = getActions();
const lang = useOldLang(); const lang = useOldLang();
@ -129,6 +131,10 @@ const Topic: FC<OwnProps & StateProps> = ({
openMuteModal(); openMuteModal();
}); });
const handleUnmute = useLastCallback(() => {
updateTopicMutedState({ chatId, topicId: topic.id, mutedUntil: UNMUTE_TIMESTAMP });
});
const { renderSubtitle, ref } = useChatListEntry({ const { renderSubtitle, ref } = useChatListEntry({
chat, chat,
chatId, chatId,
@ -164,6 +170,7 @@ const Topic: FC<OwnProps & StateProps> = ({
canDelete, canDelete,
handleDelete: handleOpenDeleteModal, handleDelete: handleOpenDeleteModal,
handleMute, handleMute,
handleUnmute,
}); });
return ( return (

View File

@ -18,6 +18,7 @@ export default function useTopicContextActions({
canDelete, canDelete,
handleDelete, handleDelete,
handleMute, handleMute,
handleUnmute,
}: { }: {
topic: ApiTopic; topic: ApiTopic;
chat: ApiChat; chat: ApiChat;
@ -26,6 +27,7 @@ export default function useTopicContextActions({
canDelete?: boolean; canDelete?: boolean;
handleDelete?: NoneToVoidFunction; handleDelete?: NoneToVoidFunction;
handleMute?: NoneToVoidFunction; handleMute?: NoneToVoidFunction;
handleUnmute?: NoneToVoidFunction;
}) { }) {
const lang = useOldLang(); const lang = useOldLang();
@ -40,7 +42,6 @@ export default function useTopicContextActions({
editTopic, editTopic,
toggleTopicPinned, toggleTopicPinned,
markTopicRead, markTopicRead,
updateTopicMutedState,
openChatInNewTab, openChatInNewTab,
} = getActions(); } = getActions();
@ -81,7 +82,7 @@ export default function useTopicContextActions({
? { ? {
title: lang('ChatList.Unmute'), title: lang('ChatList.Unmute'),
icon: 'unmute', icon: 'unmute',
handler: () => updateTopicMutedState({ chatId, topicId, isMuted: false }), handler: handleUnmute,
} }
: { : {
title: `${lang('ChatList.Mute')}...`, title: `${lang('ChatList.Mute')}...`,
@ -116,5 +117,5 @@ export default function useTopicContextActions({
actionCloseTopic, actionCloseTopic,
actionDelete, actionDelete,
]) as MenuItemContextAction[]; ]) as MenuItemContextAction[];
}, [topic, chat, isChatMuted, wasOpened, lang, canDelete, handleDelete, handleMute]); }, [topic, chat, isChatMuted, wasOpened, lang, canDelete, handleDelete, handleMute, handleUnmute]);
} }

View File

@ -1,11 +1,11 @@
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact';
import type React from '../../../lib/teact/teact';
import { memo, useCallback } from '../../../lib/teact/teact'; import { memo, useCallback } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ApiChat, ApiUser } from '../../../api/types'; import type { ApiChat, ApiUser } from '../../../api/types';
import { StoryViewerOrigin } from '../../../types'; import { StoryViewerOrigin } from '../../../types';
import { UNMUTE_TIMESTAMP } from '../../../config';
import { getIsChatMuted } from '../../../global/helpers/notifications'; import { getIsChatMuted } from '../../../global/helpers/notifications';
import { import {
selectChat, selectIsChatPinned, selectNotifyDefaults, selectNotifyException, selectUser, selectChat, selectIsChatPinned, selectNotifyDefaults, selectNotifyException, selectUser,
@ -52,7 +52,7 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
withOpenAppButton, withOpenAppButton,
onClick, onClick,
}) => { }) => {
const { requestMainWebView } = getActions(); const { requestMainWebView, updateChatMutedState } = getActions();
const oldLang = useOldLang(); const oldLang = useOldLang();
const [isMuteModalOpen, openMuteModal, closeMuteModal] = useFlag(); const [isMuteModalOpen, openMuteModal, closeMuteModal] = useFlag();
@ -70,6 +70,10 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
openMuteModal(); openMuteModal();
}, [markRenderMuteModal, openMuteModal]); }, [markRenderMuteModal, openMuteModal]);
const handleUnmute = useLastCallback(() => {
updateChatMutedState({ chatId, mutedUntil: UNMUTE_TIMESTAMP });
});
const contextActions = useChatContextActions({ const contextActions = useChatContextActions({
chat, chat,
user, user,
@ -77,6 +81,7 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
isMuted, isMuted,
canChangeFolder, canChangeFolder,
handleMute, handleMute,
handleUnmute,
handleChatFolderChange, handleChatFolderChange,
}, true); }, true);

View File

@ -56,6 +56,10 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
const areNotificationsSupported = checkIfNotificationsSupported(); const areNotificationsSupported = checkIfNotificationsSupported();
const areOfflineNotificationsSupported = areNotificationsSupported && !checkIfOfflinePushFailed(); const areOfflineNotificationsSupported = areNotificationsSupported && !checkIfOfflinePushFailed();
const areChannelsMuted = Boolean(notifyDefaults?.channels?.mutedUntil);
const areGroupsMuted = Boolean(notifyDefaults?.groups?.mutedUntil);
const areUsersMuted = Boolean(notifyDefaults?.users?.mutedUntil);
const handleSettingsChange = useCallback(( const handleSettingsChange = useCallback((
e: ChangeEvent<HTMLInputElement>, e: ChangeEvent<HTMLInputElement>,
peerType: ApiNotifyPeerType, peerType: ApiNotifyPeerType,
@ -169,14 +173,13 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
<Checkbox <Checkbox
label={lang('NotificationsForPrivateChats')} label={lang('NotificationsForPrivateChats')}
subLabel={lang(notifyDefaults?.users?.mutedUntil subLabel={lang(!areUsersMuted ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')} checked={!areUsersMuted}
checked={Boolean(notifyDefaults?.users?.mutedUntil)}
onChange={handlePrivateChatsNotificationsChange} onChange={handlePrivateChatsNotificationsChange}
/> />
<Checkbox <Checkbox
label={lang('MessagePreview')} label={lang('MessagePreview')}
disabled={!notifyDefaults?.users?.mutedUntil} disabled={areUsersMuted}
subLabel={lang(notifyDefaults?.users?.shouldShowPreviews subLabel={lang(notifyDefaults?.users?.shouldShowPreviews
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')} ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={Boolean(notifyDefaults?.users?.shouldShowPreviews)} checked={Boolean(notifyDefaults?.users?.shouldShowPreviews)}
@ -189,14 +192,13 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
<Checkbox <Checkbox
label={lang('NotificationsForGroups')} label={lang('NotificationsForGroups')}
subLabel={lang(notifyDefaults?.groups?.mutedUntil subLabel={lang(!areGroupsMuted ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')} checked={!areGroupsMuted}
checked={Boolean(notifyDefaults?.groups?.mutedUntil)}
onChange={handleGroupsNotificationsChange} onChange={handleGroupsNotificationsChange}
/> />
<Checkbox <Checkbox
label={lang('MessagePreview')} label={lang('MessagePreview')}
disabled={!notifyDefaults?.groups?.mutedUntil} disabled={areGroupsMuted}
subLabel={lang(notifyDefaults?.groups?.shouldShowPreviews subLabel={lang(notifyDefaults?.groups?.shouldShowPreviews
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')} ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={Boolean(notifyDefaults?.groups?.shouldShowPreviews)} checked={Boolean(notifyDefaults?.groups?.shouldShowPreviews)}
@ -209,14 +211,13 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
<Checkbox <Checkbox
label={lang('NotificationsForChannels')} label={lang('NotificationsForChannels')}
subLabel={lang(notifyDefaults?.channels?.mutedUntil subLabel={lang(!areChannelsMuted ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')} checked={!areChannelsMuted}
checked={Boolean(notifyDefaults?.channels?.mutedUntil)}
onChange={handleChannelsNotificationsChange} onChange={handleChannelsNotificationsChange}
/> />
<Checkbox <Checkbox
label={lang('MessagePreview')} label={lang('MessagePreview')}
disabled={!notifyDefaults?.channels?.mutedUntil} disabled={areChannelsMuted}
subLabel={lang(notifyDefaults?.channels?.shouldShowPreviews subLabel={lang(notifyDefaults?.channels?.shouldShowPreviews
? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')} ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={Boolean(notifyDefaults?.channels?.shouldShowPreviews)} checked={Boolean(notifyDefaults?.channels?.shouldShowPreviews)}
@ -244,6 +245,7 @@ export default memo(withGlobal<OwnProps>(
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,
notifyDefaults: global.settings.notifyDefaults,
}; };
}, },
)(SettingsNotifications)); )(SettingsNotifications));

View File

@ -11,6 +11,7 @@ import type { IAnchorPosition, ThreadId } from '../../types';
import type { IconName } from '../../types/icons'; import type { IconName } from '../../types/icons';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
import { UNMUTE_TIMESTAMP } from '../../config';
import { import {
getCanAddContact, getCanAddContact,
getCanDeleteChat, getCanDeleteChat,
@ -303,7 +304,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
if (isAccountFrozen) { if (isAccountFrozen) {
openFrozenAccountModal(); openFrozenAccountModal();
} else { } else {
updateChatMutedState({ chatId, isMuted: false }); updateChatMutedState({ chatId, mutedUntil: UNMUTE_TIMESTAMP });
} }
closeMenu(); closeMenu();
}); });

View File

@ -8,7 +8,7 @@ import { getActions, withGlobal } from '../../../global';
import type { ApiPhoto, ApiUser } from '../../../api/types'; import type { ApiPhoto, ApiUser } from '../../../api/types';
import { ManagementProgress } from '../../../types'; import { ManagementProgress } from '../../../types';
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; import { MUTE_INDEFINITE_TIMESTAMP, SERVICE_NOTIFICATIONS_USER_ID, UNMUTE_TIMESTAMP } from '../../../config';
import { isUserBot } from '../../../global/helpers'; import { isUserBot } from '../../../global/helpers';
import { getIsChatMuted } from '../../../global/helpers/notifications'; import { getIsChatMuted } from '../../../global/helpers/notifications';
import { import {
@ -68,12 +68,14 @@ const ManageUser: FC<OwnProps & StateProps> = ({
deleteContact, deleteContact,
closeManagement, closeManagement,
uploadContactProfilePhoto, uploadContactProfilePhoto,
updateChatMutedState,
} = getActions(); } = getActions();
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag(); const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
const [isResetPersonalPhotoDialogOpen, openResetPersonalPhotoDialog, closeResetPersonalPhotoDialog] = useFlag(); const [isResetPersonalPhotoDialogOpen, openResetPersonalPhotoDialog, closeResetPersonalPhotoDialog] = useFlag();
const [isProfileFieldsTouched, setIsProfileFieldsTouched] = useState(false); const [isProfileFieldsTouched, markProfileFieldsTouched, unmarkProfileFieldsTouched] = useFlag();
const [error, setError] = useState<string | undefined>(); const [error, setError] = useState<string | undefined>();
const [isNotificationsTouched, markNotificationsTouched, unmarkNotificationsTouched] = useFlag();
const lang = useOldLang(); const lang = useOldLang();
useHistoryBack({ useHistoryBack({
@ -93,7 +95,8 @@ const ManageUser: FC<OwnProps & StateProps> = ({
}, [isMuted]); }, [isMuted]);
useEffect(() => { useEffect(() => {
setIsProfileFieldsTouched(false); unmarkProfileFieldsTouched();
unmarkNotificationsTouched();
closeDeleteDialog(); closeDeleteDialog();
}, [closeDeleteDialog, userId]); }, [closeDeleteDialog, userId]);
@ -104,7 +107,7 @@ const ManageUser: FC<OwnProps & StateProps> = ({
useEffect(() => { useEffect(() => {
if (progress === ManagementProgress.Complete) { if (progress === ManagementProgress.Complete) {
setIsProfileFieldsTouched(false); unmarkProfileFieldsTouched();
setError(undefined); setError(undefined);
closeDeleteDialog(); closeDeleteDialog();
} }
@ -112,7 +115,7 @@ const ManageUser: FC<OwnProps & StateProps> = ({
const handleFirstNameChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleFirstNameChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setFirstName(e.target.value); setFirstName(e.target.value);
setIsProfileFieldsTouched(true); markProfileFieldsTouched();
if (error === ERROR_FIRST_NAME_MISSING) { if (error === ERROR_FIRST_NAME_MISSING) {
setError(undefined); setError(undefined);
@ -121,12 +124,13 @@ const ManageUser: FC<OwnProps & StateProps> = ({
const handleLastNameChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleLastNameChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setLastName(e.target.value); setLastName(e.target.value);
setIsProfileFieldsTouched(true); markProfileFieldsTouched();
}, []); }, []);
const handleNotificationChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleNotificationChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setIsNotificationsEnabled(e.target.checked); setIsNotificationsEnabled(e.target.checked);
setIsProfileFieldsTouched(true); markNotificationsTouched();
markProfileFieldsTouched();
}, []); }, []);
const handleProfileSave = useCallback(() => { const handleProfileSave = useCallback(() => {
@ -140,11 +144,16 @@ const ManageUser: FC<OwnProps & StateProps> = ({
updateContact({ updateContact({
userId, userId,
isMuted: !isNotificationsEnabled,
firstName: trimmedFirstName, firstName: trimmedFirstName,
lastName: trimmedLastName, lastName: trimmedLastName,
}); });
}, [firstName, lastName, updateContact, userId, isNotificationsEnabled]);
if (isNotificationsTouched) {
updateChatMutedState({
chatId: userId, mutedUntil: isNotificationsEnabled ? UNMUTE_TIMESTAMP : MUTE_INDEFINITE_TIMESTAMP,
});
}
}, [firstName, isNotificationsEnabled, isNotificationsTouched, lastName, userId]);
const handleDeleteContact = useCallback(() => { const handleDeleteContact = useCallback(() => {
deleteContact({ userId }); deleteContact({ userId });
@ -167,12 +176,12 @@ const ManageUser: FC<OwnProps & StateProps> = ({
const handleResetPersonalAvatar = useCallback(() => { const handleResetPersonalAvatar = useCallback(() => {
closeResetPersonalPhotoDialog(); closeResetPersonalPhotoDialog();
setIsProfileFieldsTouched(true); markProfileFieldsTouched();
uploadContactProfilePhoto({ userId }); uploadContactProfilePhoto({ userId });
}, [closeResetPersonalPhotoDialog, uploadContactProfilePhoto, userId]); }, [closeResetPersonalPhotoDialog, uploadContactProfilePhoto, userId]);
const handleSelectAvatar = useCallback((file: File) => { const handleSelectAvatar = useCallback((file: File) => {
setIsProfileFieldsTouched(true); markProfileFieldsTouched();
uploadContactProfilePhoto({ userId, file, isSuggest: isSuggestRef.current }); uploadContactProfilePhoto({ userId, file, isSuggest: isSuggestRef.current });
}, [uploadContactProfilePhoto, userId]); }, [uploadContactProfilePhoto, userId]);

View File

@ -180,6 +180,9 @@ export const MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT = 450; // px
export const MAX_INT_32 = 2 ** 31 - 1; export const MAX_INT_32 = 2 ** 31 - 1;
export const TMP_CHAT_ID = '0'; export const TMP_CHAT_ID = '0';
export const MUTE_INDEFINITE_TIMESTAMP = MAX_INT_32;
export const UNMUTE_TIMESTAMP = 0;
export const ANIMATION_END_DELAY = 100; export const ANIMATION_END_DELAY = 100;
export const ANIMATION_WAVE_MIN_INTERVAL = 200; export const ANIMATION_WAVE_MIN_INTERVAL = 200;
export const MESSAGE_APPEARANCE_DELAY = 10; export const MESSAGE_APPEARANCE_DELAY = 10;

View File

@ -24,7 +24,6 @@ import {
CHAT_LIST_LOAD_SLICE, CHAT_LIST_LOAD_SLICE,
DEBUG, DEBUG,
GLOBAL_SUGGESTED_CHANNELS_ID, GLOBAL_SUGGESTED_CHANNELS_ID,
MAX_INT_32,
RE_TG_LINK, RE_TG_LINK,
SAVED_FOLDER_ID, SAVED_FOLDER_ID,
SERVICE_NOTIFICATIONS_USER_ID, SERVICE_NOTIFICATIONS_USER_ID,
@ -689,8 +688,7 @@ addActionHandler('requestSavedDialogUpdate', async (global, actions, payload): P
}); });
addActionHandler('updateChatMutedState', (global, actions, payload): ActionReturnType => { addActionHandler('updateChatMutedState', (global, actions, payload): ActionReturnType => {
const { chatId, isMuted } = payload; const { chatId, mutedUntil } = payload;
let { mutedUntil } = payload;
if (selectIsCurrentUserFrozen(global)) { if (selectIsCurrentUserFrozen(global)) {
actions.openFrozenAccountModal({ tabId: getCurrentTabId() }); actions.openFrozenAccountModal({ tabId: getCurrentTabId() });
@ -701,9 +699,6 @@ addActionHandler('updateChatMutedState', (global, actions, payload): ActionRetur
if (!chat) { if (!chat) {
return; return;
} }
if (isMuted && !mutedUntil) {
mutedUntil = MAX_INT_32;
}
void callApi('updateChatNotifySettings', { chat, settings: { mutedUntil } }); void callApi('updateChatNotifySettings', { chat, settings: { mutedUntil } });
}); });
@ -721,7 +716,7 @@ addActionHandler('updateChatSilentPosting', (global, actions, payload): ActionRe
addActionHandler('updateTopicMutedState', (global, actions, payload): ActionReturnType => { addActionHandler('updateTopicMutedState', (global, actions, payload): ActionReturnType => {
const { const {
chatId, topicId, isMuted, mutedUntil, chatId, topicId, mutedUntil,
} = payload; } = payload;
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
if (!chat) { if (!chat) {
@ -729,7 +724,7 @@ addActionHandler('updateTopicMutedState', (global, actions, payload): ActionRetu
} }
void callApi('updateTopicMutedState', { void callApi('updateTopicMutedState', {
chat, topicId, isMuted, mutedUntil, chat, topicId, mutedUntil,
}); });
}); });

View File

@ -5,7 +5,12 @@ import {
UPLOADING_WALLPAPER_SLUG, UPLOADING_WALLPAPER_SLUG,
} from '../../../types'; } from '../../../types';
import { APP_CONFIG_REFETCH_INTERVAL, COUNTRIES_WITH_12H_TIME_FORMAT, MAX_INT_32 } from '../../../config'; import {
APP_CONFIG_REFETCH_INTERVAL,
COUNTRIES_WITH_12H_TIME_FORMAT,
MUTE_INDEFINITE_TIMESTAMP,
UNMUTE_TIMESTAMP,
} from '../../../config';
import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
import { requestPermission, subscribe, unsubscribe } from '../../../util/notifications'; import { requestPermission, subscribe, unsubscribe } from '../../../util/notifications';
@ -16,9 +21,9 @@ import { callApi } from '../../../api/gramjs';
import { buildApiInputPrivacyRules } from '../../helpers'; import { buildApiInputPrivacyRules } from '../../helpers';
import { addActionHandler, getGlobal, setGlobal } from '../../index'; import { addActionHandler, getGlobal, setGlobal } from '../../index';
import { import {
addBlockedUser, addNotifyException, addNotifyExceptions, deletePeerPhoto, addBlockedUser, addNotifyExceptions, deletePeerPhoto,
removeBlockedUser, replaceSettings, updateChat, removeBlockedUser, replaceSettings, updateChat,
updateSharedSettings, updateUser, updateUserFullInfo, updateNotifyDefaults, updateSharedSettings, updateUser, updateUserFullInfo,
} from '../../reducers'; } from '../../reducers';
import { updateTabState } from '../../reducers/tabs'; import { updateTabState } from '../../reducers/tabs';
import { import {
@ -338,8 +343,8 @@ addActionHandler('updateNotificationSettings', async (global, actions, payload):
} }
global = getGlobal(); global = getGlobal();
global = addNotifyException(global, peerType, { global = updateNotifyDefaults(global, peerType, {
mutedUntil: isMuted ? MAX_INT_32 : undefined, mutedUntil: isMuted ? MUTE_INDEFINITE_TIMESTAMP : UNMUTE_TIMESTAMP,
shouldShowPreviews, shouldShowPreviews,
}); });
setGlobal(global); setGlobal(global);

View File

@ -235,7 +235,7 @@ addActionHandler('openChatRefundModal', async (global, actions, payload): Promis
addActionHandler('updateContact', async (global, actions, payload): Promise<void> => { addActionHandler('updateContact', async (global, actions, payload): Promise<void> => {
const { const {
userId, isMuted = false, firstName, lastName, shouldSharePhoneNumber, userId, firstName, lastName, shouldSharePhoneNumber,
tabId = getCurrentTabId(), tabId = getCurrentTabId(),
} = payload; } = payload;
@ -244,8 +244,6 @@ addActionHandler('updateContact', async (global, actions, payload): Promise<void
return; return;
} }
actions.updateChatMutedState({ chatId: userId, isMuted });
global = getGlobal(); global = getGlobal();
global = updateManagementProgress(global, ManagementProgress.InProgress, tabId); global = updateManagementProgress(global, ManagementProgress.InProgress, tabId);
setGlobal(global); setGlobal(global);

View File

@ -1122,8 +1122,7 @@ export interface ActionPayloads {
}; };
updateChatMutedState: { updateChatMutedState: {
chatId: string; chatId: string;
isMuted?: boolean; mutedUntil: number;
mutedUntil?: number;
}; };
updateChatSilentPosting: { updateChatSilentPosting: {
chatId: string; chatId: string;
@ -1823,7 +1822,6 @@ export interface ActionPayloads {
name?: string | undefined; name?: string | undefined;
about?: string | undefined; about?: string | undefined;
description?: string | undefined; description?: string | undefined;
isMuted?: boolean;
} & WithTabId; } & WithTabId;
startBotFatherConversation: { startBotFatherConversation: {
param: string; param: string;
@ -1853,7 +1851,6 @@ export interface ActionPayloads {
userId: string; userId: string;
firstName: string; firstName: string;
lastName?: string; lastName?: string;
isMuted?: boolean;
shouldSharePhoneNumber?: boolean; shouldSharePhoneNumber?: boolean;
} & WithTabId; } & WithTabId;
toggleNoPaidMessagesException: { toggleNoPaidMessagesException: {
@ -2759,8 +2756,7 @@ export interface ActionPayloads {
updateTopicMutedState: { updateTopicMutedState: {
chatId: string; chatId: string;
topicId: number; topicId: number;
isMuted?: boolean; mutedUntil: number;
mutedUntil?: number;
}; };
setViewForumAsMessages: { setViewForumAsMessages: {
chatId: string; chatId: string;

View File

@ -27,6 +27,7 @@ const useChatContextActions = ({
topics, topics,
handleDelete, handleDelete,
handleMute, handleMute,
handleUnmute,
handleChatFolderChange, handleChatFolderChange,
handleReport, handleReport,
}: { }: {
@ -42,6 +43,7 @@ const useChatContextActions = ({
topics?: Record<number, ApiTopic>; topics?: Record<number, ApiTopic>;
handleDelete?: NoneToVoidFunction; handleDelete?: NoneToVoidFunction;
handleMute?: NoneToVoidFunction; handleMute?: NoneToVoidFunction;
handleUnmute?: NoneToVoidFunction;
handleChatFolderChange: NoneToVoidFunction; handleChatFolderChange: NoneToVoidFunction;
handleReport?: NoneToVoidFunction; handleReport?: NoneToVoidFunction;
}, isInSearch = false) => { }, isInSearch = false) => {
@ -80,7 +82,6 @@ const useChatContextActions = ({
const { const {
toggleChatPinned, toggleChatPinned,
toggleSavedDialogPinned, toggleSavedDialogPinned,
updateChatMutedState,
toggleChatArchived, toggleChatArchived,
markChatMessagesRead, markChatMessagesRead,
markChatUnread, markChatUnread,
@ -140,7 +141,7 @@ const useChatContextActions = ({
? { ? {
title: lang('ChatsUnmute'), title: lang('ChatsUnmute'),
icon: 'unmute', icon: 'unmute',
handler: () => updateChatMutedState({ chatId: chat.id, isMuted: false }), handler: handleUnmute,
} }
: { : {
title: `${lang('ChatsMute')}...`, title: `${lang('ChatsMute')}...`,
@ -188,7 +189,7 @@ const useChatContextActions = ({
}, [ }, [
chat, user, canChangeFolder, lang, handleChatFolderChange, isPinned, isInSearch, isMuted, currentUserId, chat, user, canChangeFolder, lang, handleChatFolderChange, isPinned, isInSearch, isMuted, currentUserId,
handleDelete, handleMute, handleReport, folderId, isSelf, isServiceNotifications, isSavedDialog, deleteTitle, handleDelete, handleMute, handleReport, folderId, isSelf, isServiceNotifications, isSavedDialog, deleteTitle,
isPreview, topics, isPreview, topics, handleUnmute,
]); ]);
}; };