Notification: Respect global notification settings and exceptions (#1123)

This commit is contained in:
Alexander Zinchuk 2021-05-31 15:08:55 +03:00
parent 839b6b65f2
commit 33aaad6bca
26 changed files with 780 additions and 633 deletions

View File

@ -3,9 +3,8 @@ import { ApiUser, ApiUserStatus, ApiUserType } from '../../types';
export function buildApiUserFromFull(mtpUserFull: GramJs.UserFull): ApiUser {
const {
about, commonChatsCount, pinnedMsgId, botInfo, notifySettings: { silent, muteUntil },
about, commonChatsCount, pinnedMsgId, botInfo,
} = mtpUserFull;
const isMuted = silent || (typeof muteUntil === 'number' && Date.now() < muteUntil * 1000);
return {
...(buildApiUser(mtpUserFull.user) as ApiUser),
@ -13,7 +12,6 @@ export function buildApiUserFromFull(mtpUserFull: GramJs.UserFull): ApiUser {
bio: about,
commonChatsCount,
pinnedMessageId: pinnedMsgId,
isMuted,
...(botInfo && { botDescription: botInfo.description }),
},
};

View File

@ -45,7 +45,7 @@ export {
updateProfile, checkUsername, updateUsername, fetchBlockedContacts, blockContact, unblockContact,
updateProfilePhoto, uploadProfilePhoto, fetchWallpapers, uploadWallpaper,
fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations,
loadNotificationsSettings, updateContactSignUpNotification, updateNotificationSettings,
fetchNotificationExceptions, fetchNotificationSettings, updateContactSignUpNotification, updateNotificationSettings,
fetchLanguages, fetchLangPack, fetchPrivacySettings, setPrivacySettings, registerDevice, unregisterDevice,
} from './settings';

View File

@ -156,7 +156,15 @@ export function terminateAllAuthorizations() {
return invokeRequest(new GramJs.auth.ResetAuthorizations());
}
export async function loadNotificationsSettings() {
export async function fetchNotificationExceptions() {
const result = await invokeRequest(new GramJs.account.GetNotifyExceptions({ compareSound: true }), true);
if (result instanceof GramJs.Updates || result instanceof GramJs.UpdatesCombined) {
updateLocalDb(result);
}
}
export async function fetchNotificationSettings() {
const [
isMutedContactSignUpNotification,
privateContactNotificationsSettings,
@ -212,10 +220,10 @@ export function updateContactSignUpNotification(isSilent: boolean) {
export function updateNotificationSettings(peerType: 'contact' | 'group' | 'broadcast', {
isSilent,
isShowPreviews,
shouldShowPreviews,
}: {
isSilent: boolean;
isShowPreviews: boolean;
shouldShowPreviews: boolean;
}) {
let peer: GramJs.TypeInputNotifyPeer;
if (peerType === 'contact') {
@ -227,7 +235,7 @@ export function updateNotificationSettings(peerType: 'contact' | 'group' | 'broa
}
const settings = {
showPreviews: isShowPreviews,
showPreviews: shouldShowPreviews,
silent: isSilent,
muteUntil: isSilent ? MAX_INT_32 : undefined,
};
@ -358,7 +366,10 @@ export async function setPrivacySettings(
return buildPrivacyRules(result.rules);
}
function updateLocalDb(result: GramJs.account.PrivacyRules | GramJs.contacts.Blocked | GramJs.contacts.BlockedSlice) {
function updateLocalDb(
result: GramJs.account.PrivacyRules | GramJs.contacts.Blocked | GramJs.contacts.BlockedSlice |
GramJs.Updates | GramJs.UpdatesCombined,
) {
result.users.forEach((user) => {
if (user instanceof GramJs.User) {
localDb.users[user.id] = user;

View File

@ -544,14 +544,18 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
update instanceof GramJs.UpdateNotifySettings
&& update.peer instanceof GramJs.NotifyPeer
) {
const { silent, muteUntil } = update.notifySettings;
const {
silent, muteUntil, showPreviews, sound,
} = update.notifySettings;
const isMuted = silent || (typeof muteUntil === 'number' && Date.now() < muteUntil * 1000);
onUpdate({
'@type': 'updateChat',
'@type': 'updateNotifyExceptions',
id: getApiChatIdFromMtpPeer(update.peer.peer),
chat: {
isMuted: silent || (typeof muteUntil === 'number' && Date.now() < muteUntil * 1000),
},
isMuted,
...(sound === '' && { isSilent: true }),
...(showPreviews !== undefined && { shouldShowPreviews: Boolean(showPreviews) }),
});
} else if (
update instanceof GramJs.UpdateUserTyping
@ -741,7 +745,7 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
'@type': 'updateNotifySettings',
peerType,
isSilent: Boolean(silent || (typeof muteUntil === 'number' && Date.now() < muteUntil * 1000)),
isShowPreviews: Boolean(showPreviews),
shouldShowPreviews: Boolean(showPreviews),
});
} else if (update instanceof GramJs.UpdatePeerBlocked) {
onUpdate({

View File

@ -327,7 +327,15 @@ export type ApiUpdateNotifySettings = {
'@type': 'updateNotifySettings';
peerType: 'contact' | 'group' | 'broadcast';
isSilent: boolean;
isShowPreviews: boolean;
shouldShowPreviews: boolean;
};
export type ApiUpdateNotifyExceptions = {
'@type': 'updateNotifyExceptions';
id: number;
isMuted: boolean;
isSilent?: boolean;
shouldShowPreviews?: boolean;
};
export type updateTwoFaStateWaitCode = {
@ -369,7 +377,7 @@ export type ApiUpdate = (
ApiUpdateNewScheduledMessage | ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage |
ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages |
ApiUpdateTwoFaError | updateTwoFaStateWaitCode |
ApiUpdateNotifySettings | ApiUpdatePeerBlocked | ApiUpdatePrivacy
ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy
);
export type OnApiUpdate = (update: ApiUpdate) => void;

View File

@ -25,7 +25,6 @@ export interface ApiUserFullInfo {
commonChatsCount?: number;
botDescription?: string;
pinnedMessageId?: number;
isMuted?: boolean;
}
export type ApiUserType = 'userTypeBot' | 'userTypeRegular' | 'userTypeDeleted' | 'userTypeUnknown';

View File

@ -12,13 +12,14 @@ import './Badge.scss';
type OwnProps = {
chat: ApiChat;
isPinned?: boolean;
isMuted?: boolean;
};
const Badge: FC<OwnProps> = ({ chat, isPinned }) => {
const Badge: FC<OwnProps> = ({ chat, isPinned, isMuted }) => {
const isShown = Boolean(chat.unreadCount || chat.hasUnreadMark || isPinned);
const className = buildClassName(
'Badge',
chat.isMuted && 'muted',
isMuted && 'muted',
isPinned && 'pinned',
Boolean(chat.unreadCount || chat.hasUnreadMark) && 'unread',
);

View File

@ -25,9 +25,11 @@ import {
getMessageMediaThumbDataUri,
getMessageVideo,
getMessageSticker,
selectIsChatMuted,
} from '../../../modules/helpers';
import {
selectChat, selectUser, selectChatMessage, selectOutgoingStatus, selectDraft, selectCurrentMessageList,
selectNotifySettings, selectNotifyExceptions,
} from '../../../modules/selectors';
import { renderActionMessageText } from '../../common/helpers/renderActionMessageText';
import renderText from '../../common/helpers/renderText';
@ -62,6 +64,7 @@ type OwnProps = {
type StateProps = {
chat?: ApiChat;
isMuted?: boolean;
privateChatUser?: ApiUser;
actionTargetUser?: ApiUser;
actionTargetMessage?: ApiMessage;
@ -87,6 +90,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
isSelected,
isPinned,
chat,
isMuted,
privateChatUser,
actionTargetUser,
lastMessageSender,
@ -255,14 +259,14 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="title">
<h3>{renderText(getChatTitle(chat, privateChatUser))}</h3>
{chat.isVerified && <VerifiedIcon />}
{chat.isMuted && <i className="icon-muted-chat" />}
{isMuted && <i className="icon-muted-chat" />}
{chat.lastMessage && (
<LastMessageMeta message={chat.lastMessage} outgoingStatus={lastMessageOutgoingStatus} />
)}
</div>
<div className="subtitle">
{renderLastMessageOrTyping()}
<Badge chat={chat} isPinned={isPinned} />
<Badge chat={chat} isPinned={isPinned} isMuted={isMuted} />
</div>
</div>
<DeleteChatModal
@ -307,6 +311,7 @@ export default memo(withGlobal<OwnProps>(
return {
chat,
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
lastMessageSender,
...(isOutgoing && { lastMessageOutgoingStatus: selectOutgoingStatus(global, chat.lastMessage) }),
...(privateChatUserId && { privateChatUser: selectUser(global, privateChatUserId) }),

View File

@ -5,11 +5,13 @@ import { withGlobal } from '../../../lib/teact/teactn';
import { ApiChat, ApiChatFolder, ApiUser } from '../../../api/types';
import { GlobalActions } from '../../../global/types';
import { NotifyException, NotifySettings } from '../../../types';
import { IS_TOUCH_ENV } from '../../../util/environment';
import { buildCollectionByKey, pick } from '../../../util/iteratees';
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
import { getFolderUnreadDialogs } from '../../../modules/helpers';
import { selectNotifyExceptions, selectNotifySettings } from '../../../modules/selectors';
import useShowTransition from '../../../hooks/useShowTransition';
import buildClassName from '../../../util/buildClassName';
import useThrottledMemo from '../../../hooks/useThrottledMemo';
@ -24,6 +26,8 @@ type StateProps = {
chatsById: Record<number, ApiChat>;
usersById: Record<number, ApiUser>;
chatFoldersById: Record<number, ApiChatFolder>;
notifySettings: NotifySettings;
notifyExceptions: Record<number, NotifyException>;
orderedFolderIds?: number[];
lastSyncTime?: number;
};
@ -36,6 +40,8 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
chatsById,
usersById,
chatFoldersById,
notifySettings,
notifyExceptions,
orderedFolderIds,
lastSyncTime,
loadChatFolders,
@ -68,7 +74,7 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
const counters = displayedFolders.map((folder) => {
const {
unreadDialogsCount, hasActiveDialogs,
} = getFolderUnreadDialogs(chatsById, usersById, folder, chatIds) || {};
} = getFolderUnreadDialogs(chatsById, usersById, folder, notifySettings, notifyExceptions, chatIds) || {};
return {
id: folder.id,
@ -78,7 +84,7 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
});
return buildCollectionByKey(counters, 'id');
}, INFO_THROTTLE, [displayedFolders, chatsById, usersById]);
}, INFO_THROTTLE, [displayedFolders, chatsById, usersById, notifySettings, notifyExceptions]);
const folderTabs = useMemo(() => {
if (!displayedFolders || !displayedFolders.length) {
@ -185,6 +191,8 @@ export default memo(withGlobal(
chatFoldersById,
orderedFolderIds,
lastSyncTime,
notifySettings: selectNotifySettings(global),
notifyExceptions: selectNotifyExceptions(global),
};
},
(setGlobal, actions): DispatchProps => pick(actions, ['loadChatFolders']),

View File

@ -7,13 +7,16 @@ import { GlobalActions } from '../../../global/types';
import {
ApiChat, ApiChatFolder, ApiUser, MAIN_THREAD_ID,
} from '../../../api/types';
import { NotifyException, NotifySettings } from '../../../types';
import { ALL_CHATS_PRELOAD_DISABLED, CHAT_HEIGHT_PX, CHAT_LIST_SLICE } from '../../../config';
import { IS_ANDROID } from '../../../util/environment';
import usePrevious from '../../../hooks/usePrevious';
import { mapValues, pick } from '../../../util/iteratees';
import { getChatOrder, prepareChatList, prepareFolderListIds } from '../../../modules/helpers';
import { selectChatFolder, selectCurrentMessageList } from '../../../modules/selectors';
import {
selectChatFolder, selectCurrentMessageList, selectNotifyExceptions, selectNotifySettings,
} from '../../../modules/selectors';
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
import { useChatAnimationType } from './hooks';
@ -36,6 +39,8 @@ type StateProps = {
orderedPinnedIds?: number[];
lastSyncTime?: number;
isInDiscussionThread?: boolean;
notifySettings: NotifySettings;
notifyExceptions: Record<number, NotifyException>;
};
type DispatchProps = Pick<GlobalActions, 'loadMoreChats' | 'preloadTopChatMessages'>;
@ -57,14 +62,16 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
orderedPinnedIds,
lastSyncTime,
isInDiscussionThread,
notifySettings,
notifyExceptions,
loadMoreChats,
preloadTopChatMessages,
}) => {
const [currentListIds, currentPinnedIds] = useMemo(() => {
return folderType === 'folder' && chatFolder
? prepareFolderListIds(chatsById, usersById, chatFolder)
? prepareFolderListIds(chatsById, usersById, chatFolder, notifySettings, notifyExceptions)
: [listIds, orderedPinnedIds];
}, [folderType, chatsById, usersById, chatFolder, listIds, orderedPinnedIds]);
}, [folderType, chatFolder, chatsById, usersById, notifySettings, notifyExceptions, listIds, orderedPinnedIds]);
const [orderById, orderedIds] = useMemo(() => {
if (!currentListIds || (folderType === 'folder' && !chatFolder)) {
@ -202,6 +209,8 @@ export default memo(withGlobal<OwnProps>(
chatFolder,
}),
isInDiscussionThread: currentThreadId !== MAIN_THREAD_ID,
notifySettings: selectNotifySettings(global),
notifyExceptions: selectNotifyExceptions(global),
};
},
(setGlobal, actions): DispatchProps => pick(actions, ['loadMoreChats', 'preloadTopChatMessages']),

View File

@ -22,7 +22,7 @@ type StateProps = {
};
type DispatchProps = Pick<GlobalActions, (
'loadNotificationsSettings' | 'updateContactSignUpNotification' | 'updateNotificationSettings'
'loadNotificationSettings' | 'updateContactSignUpNotification' | 'updateNotificationSettings'
)>;
const SettingsNotifications: FC<StateProps & DispatchProps> = ({
@ -33,13 +33,13 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
hasBroadcastNotifications,
hasBroadcastMessagePreview,
hasContactJoinedNotifications,
loadNotificationsSettings,
loadNotificationSettings,
updateContactSignUpNotification,
updateNotificationSettings,
}) => {
useEffect(() => {
loadNotificationsSettings();
}, [loadNotificationsSettings]);
loadNotificationSettings();
}, [loadNotificationSettings]);
const handleSettingsChange = useCallback((
e: ChangeEvent<HTMLInputElement>,
@ -49,14 +49,14 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
const currentIsSilent = peerType === 'contact'
? !hasPrivateChatsNotifications
: !(peerType === 'group' ? hasGroupNotifications : hasBroadcastNotifications);
const currentIsShowPreviews = peerType === 'contact'
const currentShouldShowPreviews = peerType === 'contact'
? hasPrivateChatsMessagePreview
: (peerType === 'group' ? hasGroupMessagePreview : hasBroadcastMessagePreview);
updateNotificationSettings({
peerType,
...(setting === 'silent' && { isSilent: !e.target.checked, isShowPreviews: currentIsShowPreviews }),
...(setting === 'showPreviews' && { isShowPreviews: e.target.checked, isSilent: currentIsSilent }),
...(setting === 'silent' && { isSilent: !e.target.checked, shouldShowPreviews: currentShouldShowPreviews }),
...(setting === 'showPreviews' && { shouldShowPreviews: e.target.checked, isSilent: currentIsSilent }),
});
}, [
hasBroadcastMessagePreview, hasBroadcastNotifications,
@ -151,7 +151,7 @@ export default memo(withGlobal((global): StateProps => {
};
},
(setGlobal, actions): DispatchProps => pick(actions, [
'loadNotificationsSettings',
'loadNotificationSettings',
'updateContactSignUpNotification',
'updateNotificationSettings',
]))(SettingsNotifications));

View File

@ -5,9 +5,11 @@ import { withGlobal } from '../../../../lib/teact/teactn';
import { GlobalActions } from '../../../../global/types';
import { ApiChatFolder, ApiChat, ApiUser } from '../../../../api/types';
import { NotifyException, NotifySettings } from '../../../../types';
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
import { pick } from '../../../../util/iteratees';
import { selectNotifyExceptions, selectNotifySettings } from '../../../../modules/selectors';
import { throttle } from '../../../../util/schedulers';
import getAnimationData from '../../../common/helpers/animatedAssets';
import { getFolderDescriptionText } from '../../../../modules/helpers';
@ -29,6 +31,8 @@ type StateProps = {
orderedFolderIds?: number[];
foldersById: Record<number, ApiChatFolder>;
recommendedChatFolders?: ApiChatFolder[];
notifySettings: NotifySettings;
notifyExceptions: Record<number, NotifyException>;
};
type DispatchProps = Pick<GlobalActions, 'loadRecommendedChatFolders' | 'addChatFolder' | 'showError'>;
@ -45,6 +49,8 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
orderedFolderIds,
foldersById,
recommendedChatFolders,
notifySettings,
notifyExceptions,
loadRecommendedChatFolders,
addChatFolder,
showError,
@ -96,10 +102,12 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
return {
id: folder.id,
title: folder.title,
subtitle: getFolderDescriptionText(chatsById, usersById, folder, chatIds, lang),
subtitle: getFolderDescriptionText(
chatsById, usersById, folder, notifySettings, notifyExceptions, chatIds, lang,
),
};
});
}, [orderedFolderIds, chatsById, foldersById, usersById, lang]);
}, [orderedFolderIds, chatsById, foldersById, usersById, notifySettings, notifyExceptions, lang]);
const handleCreateFolderFromRecommended = useCallback((folder: ApiChatFolder) => {
if (Object.keys(foldersById).length >= MAX_ALLOWED_FOLDERS) {
@ -222,6 +230,8 @@ export default memo(withGlobal<OwnProps>(
orderedFolderIds,
foldersById,
recommendedChatFolders,
notifySettings: selectNotifySettings(global),
notifyExceptions: selectNotifyExceptions(global),
};
},
(setGlobal, actions): DispatchProps => pick(actions, ['loadRecommendedChatFolders', 'addChatFolder', 'showError']),

View File

@ -209,5 +209,6 @@ function reduceSettings(global: GlobalState): GlobalState['settings'] {
return {
byKey,
privacy: {},
notifyExceptions: {},
};
}

View File

@ -116,6 +116,7 @@ export const INITIAL_STATE: GlobalState = {
language: 'en',
},
privacy: {},
notifyExceptions: {},
},
twoFaSettings: {},

View File

@ -34,6 +34,7 @@ import {
Receipt,
ApiPrivacyKey,
ApiPrivacySettings,
NotifyException,
} from '../types';
export type MessageListType = 'thread' | 'pinned' | 'scheduled';
@ -365,6 +366,7 @@ export type GlobalState = {
byKey: ISettings;
loadedWallpapers?: ApiWallpaper[];
privacy: Partial<Record<ApiPrivacyKey, ApiPrivacySettings>>;
notifyExceptions: Record<number, NotifyException>;
};
twoFaSettings: {
@ -434,8 +436,9 @@ export type ActionTypes = (
'updatePassword' | 'updateRecoveryEmail' | 'clearPassword' | 'provideTwoFaEmailCode' | 'checkPassword' |
'loadBlockedContacts' | 'blockContact' | 'unblockContact' |
'loadAuthorizations' | 'terminateAuthorization' | 'terminateAllAuthorizations' |
'loadNotificationsSettings' | 'updateContactSignUpNotification' | 'updateNotificationSettings' |
'loadNotificationSettings' | 'updateContactSignUpNotification' | 'updateNotificationSettings' |
'loadLanguages' | 'loadPrivacySettings' | 'setPrivacyVisibility' | 'setPrivacySettings' |
'loadNotificationExceptions' |
// Stickers & GIFs
'loadStickerSets' | 'loadAddedStickers' | 'loadRecentStickers' | 'loadFavoriteStickers' | 'loadFeaturedStickers' |
'loadStickers' | 'setStickerSearchQuery' | 'loadSavedGifs' | 'setGifSearchQuery' | 'searchMoreGifs' |

File diff suppressed because it is too large Load Diff

View File

@ -939,6 +939,7 @@ account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings =
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
account.getContactSignUpNotification#9f07c728 = Bool;
account.setContactSignUpNotification#cff43f61 silent:Bool = Bool;
account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates;
account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#ca30a5b1 id:InputUser = UserFull;

View File

@ -939,6 +939,7 @@ account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings =
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
account.getContactSignUpNotification#9f07c728 = Bool;
account.setContactSignUpNotification#cff43f61 silent:Bool = Bool;
account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates;
account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#ca30a5b1 id:InputUser = UserFull;

View File

@ -304,9 +304,13 @@ addReducer('terminateAllAuthorizations', () => {
})();
});
addReducer('loadNotificationsSettings', () => {
addReducer('loadNotificationExceptions', () => {
callApi('fetchNotificationExceptions');
});
addReducer('loadNotificationSettings', () => {
(async () => {
const result = await callApi('loadNotificationsSettings');
const result = await callApi('fetchNotificationSettings');
if (!result) {
return;
}
@ -316,16 +320,16 @@ addReducer('loadNotificationsSettings', () => {
});
addReducer('updateNotificationSettings', (global, actions, payload) => {
const { peerType, isSilent, isShowPreviews } = payload!;
const { peerType, isSilent, shouldShowPreviews } = payload!;
(async () => {
const result = await callApi('updateNotificationSettings', peerType, { isSilent, isShowPreviews });
const result = await callApi('updateNotificationSettings', peerType, { isSilent, shouldShowPreviews });
if (!result) {
return;
}
setGlobal(updateNotifySettings(getGlobal(), peerType, isSilent, isShowPreviews));
setGlobal(updateNotifySettings(getGlobal(), peerType, isSilent, shouldShowPreviews));
})();
});

View File

@ -1,13 +1,27 @@
import { addReducer } from '../../../lib/teact/teactn';
import { addReducer, setGlobal } from '../../../lib/teact/teactn';
import { ApiUpdate } from '../../../api/types';
import { GlobalState } from '../../../global/types';
import { updateNotifySettings } from '../../reducers';
import { addNotifyException, updateChat, updateNotifySettings } from '../../reducers';
addReducer('apiUpdate', (global, actions, update: ApiUpdate): GlobalState | undefined => {
switch (update['@type']) {
case 'updateNotifySettings': {
return updateNotifySettings(global, update.peerType, update.isSilent, update.isShowPreviews);
return updateNotifySettings(global, update.peerType, update.isSilent, update.shouldShowPreviews);
}
case 'updateNotifyExceptions': {
const {
id, isMuted, isSilent, shouldShowPreviews,
} = update;
const chat = global.chats.byId[id];
if (chat) {
global = updateChat(global, id, { isMuted });
}
setGlobal(addNotifyException(global, id, { isMuted, isSilent, shouldShowPreviews }));
break;
}
}

View File

@ -6,6 +6,7 @@ import {
ApiChatFolder,
MAIN_THREAD_ID,
} from '../../api/types';
import { NotifyException, NotifySettings } from '../../types';
import { ARCHIVED_FOLDER_ID } from '../../config';
import { orderBy } from '../../util/iteratees';
@ -200,6 +201,17 @@ export function isChatArchived(chat: ApiChat) {
return chat.folderId === ARCHIVED_FOLDER_ID;
}
export function selectIsChatMuted(
chat: ApiChat, notifySettings: NotifySettings, notifyExceptions: Record<number, NotifyException>,
) {
return !(notifyExceptions[chat.id] && !notifyExceptions[chat.id].isMuted) && (
chat.isMuted
|| (isChatPrivate(chat.id) && !notifySettings.hasPrivateChatsNotifications)
|| (isChatChannel(chat) && !notifySettings.hasBroadcastNotifications)
|| (isChatGroup(chat) && !notifySettings.hasGroupNotifications)
);
}
export function getCanDeleteChat(chat: ApiChat) {
return isChatBasicGroup(chat) || ((isChatSuperGroup(chat) || isChatChannel(chat)) && chat.isCreator);
}
@ -208,6 +220,8 @@ export function prepareFolderListIds(
chatsById: Record<number, ApiChat>,
usersById: Record<number, ApiUser>,
folder: ApiChatFolder,
notifySettings: NotifySettings,
notifyExceptions: Record<number, NotifyException>,
chatIdsCache?: number[],
) {
const excludedChatIds = folder.excludedChatIds ? new Set(folder.excludedChatIds) : undefined;
@ -216,7 +230,14 @@ export function prepareFolderListIds(
const listIds = (chatIdsCache || Object.keys(chatsById).map(Number))
.filter((id) => {
return filterChatFolder(
chatsById[id], folder, usersById, excludedChatIds, includedChatIds, pinnedChatIds,
chatsById[id],
folder,
usersById,
notifySettings,
notifyExceptions,
excludedChatIds,
includedChatIds,
pinnedChatIds,
);
});
@ -227,6 +248,8 @@ function filterChatFolder(
chat: ApiChat,
folder: ApiChatFolder,
usersById: Record<number, ApiUser>,
notifySettings: NotifySettings,
notifyExceptions: Record<number, NotifyException>,
excludedChatIds?: Set<number>,
includedChatIds?: Set<number>,
pinnedChatIds?: Set<number>,
@ -247,7 +270,7 @@ function filterChatFolder(
return false;
}
if (chat.isMuted && folder.excludeMuted) {
if (folder.excludeMuted && selectIsChatMuted(chat, notifySettings, notifyExceptions)) {
return false;
}
@ -340,9 +363,11 @@ export function getFolderUnreadDialogs(
chatsById: Record<number, ApiChat>,
usersById: Record<number, ApiUser>,
folder: ApiChatFolder,
notifySettings: NotifySettings,
notifyExceptions: Record<number, NotifyException>,
chatIdsCache: number[],
) {
const [listIds] = prepareFolderListIds(chatsById, usersById, folder, chatIdsCache);
const [listIds] = prepareFolderListIds(chatsById, usersById, folder, notifySettings, notifyExceptions, chatIdsCache);
const listedChats = listIds
.map((id) => chatsById[id])
@ -366,6 +391,8 @@ export function getFolderDescriptionText(
chatsById: Record<number, ApiChat>,
usersById: Record<number, ApiUser>,
folder: ApiChatFolder,
notifySettings: NotifySettings,
notifyExceptions: Record<number, NotifyException>,
chatIdsCache: number[],
lang: LangFn,
) {
@ -383,7 +410,7 @@ export function getFolderDescriptionText(
|| (excludedChatIds && excludedChatIds.length)
|| (includedChatIds && includedChatIds.length)
) {
const length = getFolderChatsCount(chatsById, usersById, folder, chatIdsCache);
const length = getFolderChatsCount(chatsById, usersById, folder, notifySettings, notifyExceptions, chatIdsCache);
return lang('Chats', length);
}
@ -407,9 +434,13 @@ function getFolderChatsCount(
chatsById: Record<number, ApiChat>,
usersById: Record<number, ApiUser>,
folder: ApiChatFolder,
notifySettings: NotifySettings,
notifyExceptions: Record<number, NotifyException>,
chatIdsCache: number[],
) {
const [listIds, pinnedIds] = prepareFolderListIds(chatsById, usersById, folder, chatIdsCache);
const [listIds, pinnedIds] = prepareFolderListIds(
chatsById, usersById, folder, notifySettings, notifyExceptions, chatIdsCache,
);
const { pinnedChats, otherChats } = prepareChatList(chatsById, listIds, pinnedIds, 'folder');
return pinnedChats.length + otherChats.length;
}

View File

@ -1,5 +1,5 @@
import { GlobalState } from '../../global/types';
import { ISettings } from '../../types';
import { ISettings, NotifyException } from '../../types';
export function replaceSettings(global: GlobalState, newSettings?: Partial<ISettings>): GlobalState {
return {
@ -14,24 +14,39 @@ export function replaceSettings(global: GlobalState, newSettings?: Partial<ISett
};
}
export function addNotifyException(
global: GlobalState, id: number, notifyException: NotifyException,
): GlobalState {
return {
...global,
settings: {
...global.settings,
notifyExceptions: {
...global.settings.notifyExceptions,
[id]: notifyException,
},
},
};
}
export function updateNotifySettings(
global: GlobalState, peerType: 'contact' | 'group' | 'broadcast', isSilent?: boolean, isShowPreviews?: boolean,
global: GlobalState, peerType: 'contact' | 'group' | 'broadcast', isSilent?: boolean, shouldShowPreviews?: boolean,
) {
switch (peerType) {
case 'contact':
return replaceSettings(global, {
...(typeof isSilent !== 'undefined' && { hasPrivateChatsNotifications: !isSilent }),
...(typeof isShowPreviews !== 'undefined' && { hasPrivateChatsMessagePreview: isShowPreviews }),
...(typeof shouldShowPreviews !== 'undefined' && { hasPrivateChatsMessagePreview: shouldShowPreviews }),
});
case 'group':
return replaceSettings(global, {
...(typeof isSilent !== 'undefined' && { hasGroupNotifications: !isSilent }),
...(typeof isShowPreviews !== 'undefined' && { hasGroupMessagePreview: isShowPreviews }),
...(typeof shouldShowPreviews !== 'undefined' && { hasGroupMessagePreview: shouldShowPreviews }),
});
case 'broadcast':
return replaceSettings(global, {
...(typeof isSilent !== 'undefined' && { hasBroadcastNotifications: !isSilent }),
...(typeof isShowPreviews !== 'undefined' && { hasBroadcastMessagePreview: isShowPreviews }),
...(typeof shouldShowPreviews !== 'undefined' && { hasBroadcastMessagePreview: shouldShowPreviews }),
});
default:

View File

@ -7,3 +7,4 @@ export * from './localSearch';
export * from './management';
export * from './symbols';
export * from './payments';
export * from './settings';

View File

@ -0,0 +1,9 @@
import { GlobalState } from '../../global/types';
export function selectNotifySettings(global: GlobalState) {
return global.settings.byKey;
}
export function selectNotifyExceptions(global: GlobalState) {
return global.settings.notifyExceptions;
}

View File

@ -20,7 +20,17 @@ export interface IAlbum {
mainMessage: ApiMessage;
}
export interface ISettings extends Record<string, any> {
export type NotifySettings = {
hasPrivateChatsNotifications?: boolean;
hasPrivateChatsMessagePreview?: boolean;
hasGroupNotifications?: boolean;
hasGroupMessagePreview?: boolean;
hasBroadcastNotifications?: boolean;
hasBroadcastMessagePreview?: boolean;
hasContactJoinedNotifications?: boolean;
};
export interface ISettings extends NotifySettings, Record<string, any> {
messageTextSize: number;
customBackground?: string;
patternColor?: string;
@ -37,13 +47,6 @@ export interface ISettings extends Record<string, any> {
shouldSuggestStickers: boolean;
shouldLoopStickers: boolean;
hasPassword?: boolean;
hasPrivateChatsNotifications?: boolean;
hasPrivateChatsMessagePreview?: boolean;
hasGroupNotifications?: boolean;
hasGroupMessagePreview?: boolean;
hasBroadcastNotifications?: boolean;
hasBroadcastMessagePreview?: boolean;
hasContactJoinedNotifications?: boolean;
languages?: ApiLanguage[];
language: 'en' | 'fr' | 'de' | 'it' | 'pt' | 'ru' | 'es' | 'uk';
}
@ -269,3 +272,9 @@ export enum ManagementScreens {
}
export type ManagementType = 'user' | 'group' | 'channel';
export type NotifyException = {
isMuted: boolean;
isSilent?: boolean;
shouldShowPreviews?: boolean;
};

View File

@ -125,14 +125,18 @@ export async function unsubscribe() {
}
// Load notification settings from the api
async function loadNotificationsSettings() {
const result = await callApi('loadNotificationsSettings');
async function loadNotificationSettings() {
const [result] = await Promise.all([
callApi('fetchNotificationSettings'),
callApi('fetchNotificationExceptions'),
]);
if (!result) return;
setGlobal(replaceSettings(getGlobal(), result));
}
export async function subscribe() {
loadNotificationsSettings();
loadNotificationSettings();
if (!checkIfPushSupported()) {
// Ask for notification permissions only if service worker notifications are not supported