[Perf] Extract users.statusesById to a separate global store
This commit is contained in:
parent
5563b0b4f4
commit
865ed08d82
@ -47,7 +47,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
||||
...(lastName && { lastName }),
|
||||
username: mtpUser.username || '',
|
||||
phoneNumber: mtpUser.phone || '',
|
||||
status: buildApiUserStatus(mtpUser.status),
|
||||
noStatus: !mtpUser.status,
|
||||
...(mtpUser.accessHash && { accessHash: String(mtpUser.accessHash) }),
|
||||
...(avatarHash && { avatarHash }),
|
||||
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
|
||||
@ -88,3 +88,23 @@ function buildApiBotCommands(botId: string, botInfo: GramJs.BotInfo) {
|
||||
description,
|
||||
})) as ApiBotCommand[];
|
||||
}
|
||||
|
||||
export function buildApiUsersAndStatuses(mtpUsers: GramJs.TypeUser[]) {
|
||||
const userStatusesById: Record<string, ApiUserStatus> = {};
|
||||
const users: ApiUser[] = [];
|
||||
|
||||
mtpUsers.forEach((mtpUser) => {
|
||||
const user = buildApiUser(mtpUser);
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
users.push(user);
|
||||
|
||||
if ('status' in mtpUser) {
|
||||
userStatusesById[user.id] = buildApiUserStatus(mtpUser.status);
|
||||
}
|
||||
});
|
||||
|
||||
return { users, userStatusesById };
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ import {
|
||||
buildApiChatBotCommands,
|
||||
} from '../apiBuilders/chats';
|
||||
import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
|
||||
import { buildApiUser } from '../apiBuilders/users';
|
||||
import { buildApiUser, buildApiUsersAndStatuses } from '../apiBuilders/users';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import localDb from '../localDb';
|
||||
import {
|
||||
@ -146,13 +146,11 @@ export async function fetchChats({
|
||||
}
|
||||
});
|
||||
|
||||
const users = (resultPinned ? resultPinned.users : []).concat(result.users)
|
||||
.map(buildApiUser)
|
||||
.filter<ApiUser>(Boolean as any);
|
||||
const chatIds = chats.map((chat) => chat.id);
|
||||
|
||||
let totalChatCount: number;
|
||||
const { users, userStatusesById } = buildApiUsersAndStatuses((resultPinned?.users || []).concat(result.users));
|
||||
|
||||
let totalChatCount: number;
|
||||
if (result instanceof GramJs.messages.DialogsSlice) {
|
||||
totalChatCount = result.count;
|
||||
} else {
|
||||
@ -163,6 +161,7 @@ export async function fetchChats({
|
||||
chatIds,
|
||||
chats,
|
||||
users,
|
||||
userStatusesById,
|
||||
draftsById,
|
||||
replyingToById,
|
||||
orderedPinnedIds: withPinned ? orderedPinnedIds : undefined,
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
buildMtpPeerId,
|
||||
getEntityTypeById,
|
||||
} from '../gramjsBuilders';
|
||||
import { buildApiUser, buildApiUserFromFull } from '../apiBuilders/users';
|
||||
import { buildApiUser, buildApiUserFromFull, buildApiUsersAndStatuses } from '../apiBuilders/users';
|
||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { buildApiPhoto } from '../apiBuilders/common';
|
||||
import localDb from '../localDb';
|
||||
@ -139,7 +139,7 @@ export async function fetchUsers({ users }: { users: ApiUser[] }) {
|
||||
}
|
||||
});
|
||||
|
||||
return result.map(buildApiUser).filter<ApiUser>(Boolean as any);
|
||||
return buildApiUsersAndStatuses(result);
|
||||
}
|
||||
|
||||
export function updateContact({
|
||||
|
||||
@ -10,7 +10,7 @@ export interface ApiUser {
|
||||
type: ApiUserType;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
status?: ApiUserStatus;
|
||||
noStatus?: boolean;
|
||||
username: string;
|
||||
phoneNumber: string;
|
||||
accessHash?: string;
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { MouseEvent as ReactMouseEvent } from 'react';
|
||||
import React, { FC, memo, useCallback } from '../../lib/teact/teact';
|
||||
|
||||
import { ApiChat, ApiMediaFormat, ApiUser } from '../../api/types';
|
||||
import {
|
||||
ApiChat, ApiMediaFormat, ApiUser, ApiUserStatus,
|
||||
} from '../../api/types';
|
||||
|
||||
import { IS_TEST } from '../../config';
|
||||
import {
|
||||
@ -26,9 +28,9 @@ import './Avatar.scss';
|
||||
type OwnProps = {
|
||||
className?: string;
|
||||
size?: 'micro' | 'tiny' | 'small' | 'medium' | 'large' | 'jumbo';
|
||||
withOnlineStatus?: boolean;
|
||||
chat?: ApiChat;
|
||||
user?: ApiUser;
|
||||
userStatus?: ApiUserStatus;
|
||||
text?: string;
|
||||
isSavedMessages?: boolean;
|
||||
lastSyncTime?: number;
|
||||
@ -40,8 +42,8 @@ const Avatar: FC<OwnProps> = ({
|
||||
size = 'large',
|
||||
chat,
|
||||
user,
|
||||
userStatus,
|
||||
text,
|
||||
withOnlineStatus,
|
||||
isSavedMessages,
|
||||
lastSyncTime,
|
||||
onClick,
|
||||
@ -86,7 +88,7 @@ const Avatar: FC<OwnProps> = ({
|
||||
content = getFirstLetters(text, 2);
|
||||
}
|
||||
|
||||
const isOnline = !isSavedMessages && user && isUserOnline(user);
|
||||
const isOnline = !isSavedMessages && user && userStatus && isUserOnline(user, userStatus);
|
||||
const fullClassName = buildClassName(
|
||||
`Avatar size-${size}`,
|
||||
className,
|
||||
@ -94,7 +96,7 @@ const Avatar: FC<OwnProps> = ({
|
||||
isSavedMessages && 'saved-messages',
|
||||
isDeleted && 'deleted-account',
|
||||
isReplies && 'replies-bot-account',
|
||||
withOnlineStatus && isOnline && 'online',
|
||||
isOnline && 'online',
|
||||
onClick && 'interactive',
|
||||
(!isSavedMessages && !blobUrl) && 'no-photo',
|
||||
);
|
||||
|
||||
@ -4,11 +4,11 @@ import React, {
|
||||
} from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { ApiUser, ApiTypingStatus } from '../../api/types';
|
||||
import { ApiUser, ApiTypingStatus, ApiUserStatus } from '../../api/types';
|
||||
import { GlobalActions, GlobalState } from '../../global/types';
|
||||
import { MediaViewerOrigin } from '../../types';
|
||||
|
||||
import { selectChatMessages, selectUser } from '../../modules/selectors';
|
||||
import { selectChatMessages, selectUser, selectUserStatus } from '../../modules/selectors';
|
||||
import { getUserFullName, getUserStatus, isUserOnline } from '../../modules/helpers';
|
||||
import renderText from './helpers/renderText';
|
||||
import { pick } from '../../util/iteratees';
|
||||
@ -34,6 +34,7 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
user?: ApiUser;
|
||||
userStatus?: ApiUserStatus;
|
||||
isSavedMessages?: boolean;
|
||||
areMessagesLoaded: boolean;
|
||||
serverTimeOffset: number;
|
||||
@ -52,6 +53,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
noStatusOrTyping,
|
||||
noRtl,
|
||||
user,
|
||||
userStatus,
|
||||
isSavedMessages,
|
||||
areMessagesLoaded,
|
||||
lastSyncTime,
|
||||
@ -106,9 +108,9 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
|
||||
<div className={`status ${isUserOnline(user, userStatus) ? 'online' : ''}`}>
|
||||
{withUsername && user.username && <span className="handle">{user.username}</span>}
|
||||
<span className="user-status" dir="auto">{getUserStatus(lang, user, serverTimeOffset)}</span>
|
||||
<span className="user-status" dir="auto">{getUserStatus(lang, user, userStatus, serverTimeOffset)}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -143,11 +145,12 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global, { userId, forceShowSelf }): StateProps => {
|
||||
const { lastSyncTime, serverTimeOffset } = global;
|
||||
const user = selectUser(global, userId);
|
||||
const userStatus = selectUserStatus(global, userId);
|
||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||
const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
|
||||
|
||||
return {
|
||||
lastSyncTime, user, isSavedMessages, areMessagesLoaded, serverTimeOffset,
|
||||
lastSyncTime, user, userStatus, isSavedMessages, areMessagesLoaded, serverTimeOffset,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadFullUser', 'openMediaViewer']),
|
||||
|
||||
@ -3,12 +3,12 @@ import React, {
|
||||
} from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { ApiUser, ApiChat } from '../../api/types';
|
||||
import { ApiUser, ApiChat, ApiUserStatus } from '../../api/types';
|
||||
import { GlobalActions, GlobalState } from '../../global/types';
|
||||
import { MediaViewerOrigin } from '../../types';
|
||||
|
||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||
import { selectChat, selectUser } from '../../modules/selectors';
|
||||
import { selectChat, selectUser, selectUserStatus } from '../../modules/selectors';
|
||||
import {
|
||||
getUserFullName, getUserStatus, isChatChannel, isUserOnline,
|
||||
} from '../../modules/helpers';
|
||||
@ -32,6 +32,7 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
user?: ApiUser;
|
||||
userStatus?: ApiUserStatus;
|
||||
chat?: ApiChat;
|
||||
isSavedMessages?: boolean;
|
||||
animationLevel: 0 | 1 | 2;
|
||||
@ -43,6 +44,7 @@ type DispatchProps = Pick<GlobalActions, 'loadFullUser' | 'openMediaViewer'>;
|
||||
const ProfileInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
forceShowSelf,
|
||||
user,
|
||||
userStatus,
|
||||
chat,
|
||||
isSavedMessages,
|
||||
connectionState,
|
||||
@ -162,8 +164,8 @@ const ProfileInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
function renderStatus() {
|
||||
if (user) {
|
||||
return (
|
||||
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
|
||||
<span className="user-status" dir="auto">{getUserStatus(lang, user, serverTimeOffset)}</span>
|
||||
<div className={`status ${isUserOnline(user, userStatus) ? 'online' : ''}`}>
|
||||
<span className="user-status" dir="auto">{getUserStatus(lang, user, userStatus, serverTimeOffset)}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -227,6 +229,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global, { userId, forceShowSelf }): StateProps => {
|
||||
const { connectionState, serverTimeOffset } = global;
|
||||
const user = selectUser(global, userId);
|
||||
const userStatus = selectUserStatus(global, userId);
|
||||
const chat = selectChat(global, userId);
|
||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||
const { animationLevel } = global.settings.byKey;
|
||||
@ -234,6 +237,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
return {
|
||||
connectionState,
|
||||
user,
|
||||
userStatus,
|
||||
chat,
|
||||
isSavedMessages,
|
||||
animationLevel,
|
||||
|
||||
@ -7,7 +7,7 @@ import useLang, { LangFn } from '../../../hooks/useLang';
|
||||
|
||||
import { GlobalActions } from '../../../global/types';
|
||||
import {
|
||||
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus, ApiFormattedText, MAIN_THREAD_ID,
|
||||
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus, ApiFormattedText, MAIN_THREAD_ID, ApiUserStatus,
|
||||
} from '../../../api/types';
|
||||
|
||||
import { ANIMATION_END_DELAY } from '../../../config';
|
||||
@ -30,7 +30,7 @@ import {
|
||||
} from '../../../modules/helpers';
|
||||
import {
|
||||
selectChat, selectUser, selectChatMessage, selectOutgoingStatus, selectDraft, selectCurrentMessageList,
|
||||
selectNotifySettings, selectNotifyExceptions,
|
||||
selectNotifySettings, selectNotifyExceptions, selectUserStatus,
|
||||
} from '../../../modules/selectors';
|
||||
import { renderActionMessageText } from '../../common/helpers/renderActionMessageText';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
@ -67,7 +67,8 @@ type OwnProps = {
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
isMuted?: boolean;
|
||||
privateChatUser?: ApiUser;
|
||||
user?: ApiUser;
|
||||
userStatus?: ApiUserStatus;
|
||||
actionTargetUserIds?: string[];
|
||||
usersById?: Record<string, ApiUser>;
|
||||
actionTargetMessage?: ApiMessage;
|
||||
@ -94,7 +95,8 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isPinned,
|
||||
chat,
|
||||
isMuted,
|
||||
privateChatUser,
|
||||
user,
|
||||
userStatus,
|
||||
actionTargetUserIds,
|
||||
usersById,
|
||||
lastMessageSender,
|
||||
@ -196,7 +198,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const contextActions = useChatContextActions({
|
||||
chat,
|
||||
privateChatUser,
|
||||
user,
|
||||
handleDelete,
|
||||
handleChatFolderChange,
|
||||
folderId,
|
||||
@ -281,9 +283,9 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<div className="status">
|
||||
<Avatar
|
||||
chat={chat}
|
||||
user={privateChatUser}
|
||||
withOnlineStatus
|
||||
isSavedMessages={privateChatUser?.isSelf}
|
||||
user={user}
|
||||
userStatus={userStatus}
|
||||
isSavedMessages={user?.isSelf}
|
||||
lastSyncTime={lastSyncTime}
|
||||
/>
|
||||
{chat.isCallActive && chat.isCallNotEmpty && (
|
||||
@ -292,7 +294,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
</div>
|
||||
<div className="info">
|
||||
<div className="title">
|
||||
<h3>{renderText(getChatTitle(lang, chat, privateChatUser))}</h3>
|
||||
<h3>{renderText(getChatTitle(lang, chat, user))}</h3>
|
||||
{chat.isVerified && <VerifiedIcon />}
|
||||
{isMuted && <i className="icon-muted" />}
|
||||
{chat.lastMessage && (
|
||||
@ -377,7 +379,10 @@ export default memo(withGlobal<OwnProps>(
|
||||
canScrollDown: isSelected && messageListType === 'thread',
|
||||
lastSyncTime: global.lastSyncTime,
|
||||
...(isOutgoing && { lastMessageOutgoingStatus: selectOutgoingStatus(global, chat.lastMessage) }),
|
||||
...(privateChatUserId && { privateChatUser: selectUser(global, privateChatUserId) }),
|
||||
...(privateChatUserId && {
|
||||
user: selectUser(global, privateChatUserId),
|
||||
userStatus: selectUserStatus(global, privateChatUserId),
|
||||
}),
|
||||
...(actionTargetUserIds && { usersById }),
|
||||
};
|
||||
},
|
||||
|
||||
@ -4,7 +4,7 @@ import React, {
|
||||
import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../../global/types';
|
||||
import { ApiUser } from '../../../api/types';
|
||||
import { ApiUser, ApiUserStatus } from '../../../api/types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
@ -27,6 +27,7 @@ export type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
usersById: Record<string, ApiUser>;
|
||||
userStatusesById: Record<string, ApiUserStatus>;
|
||||
contactIds?: string[];
|
||||
serverTimeOffset: number;
|
||||
};
|
||||
@ -36,8 +37,15 @@ type DispatchProps = Pick<GlobalActions, 'loadContactList' | 'openChat'>;
|
||||
const runThrottled = throttle((cb) => cb(), 60000, true);
|
||||
|
||||
const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive, onReset,
|
||||
filter, usersById, contactIds, loadContactList, openChat, serverTimeOffset,
|
||||
isActive,
|
||||
filter,
|
||||
usersById,
|
||||
userStatusesById,
|
||||
contactIds,
|
||||
serverTimeOffset,
|
||||
onReset,
|
||||
loadContactList,
|
||||
openChat,
|
||||
}) => {
|
||||
// Due to the parent Transition, this component never gets unmounted,
|
||||
// that's why we use throttled API call on every update.
|
||||
@ -67,8 +75,8 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
return fullName && searchWords(fullName, filter);
|
||||
}) : contactIds;
|
||||
|
||||
return sortUserIds(resultIds, usersById, undefined, serverTimeOffset);
|
||||
}, [contactIds, filter, usersById, serverTimeOffset]);
|
||||
return sortUserIds(resultIds, usersById, userStatusesById, undefined, serverTimeOffset);
|
||||
}, [contactIds, filter, usersById, userStatusesById, serverTimeOffset]);
|
||||
|
||||
const [viewportIds, getMore] = useInfiniteScroll(undefined, listIds, Boolean(filter));
|
||||
|
||||
@ -99,10 +107,11 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const { userIds: contactIds } = global.contactList || {};
|
||||
const { byId: usersById } = global.users;
|
||||
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
||||
|
||||
return {
|
||||
usersById,
|
||||
userStatusesById,
|
||||
contactIds,
|
||||
serverTimeOffset: global.serverTimeOffset,
|
||||
};
|
||||
|
||||
@ -83,7 +83,6 @@ const ChatMessage: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<Avatar
|
||||
chat={chat}
|
||||
user={privateChatUser}
|
||||
withOnlineStatus
|
||||
isSavedMessages={privateChatUser?.isSelf}
|
||||
lastSyncTime={lastSyncTime}
|
||||
/>
|
||||
|
||||
@ -27,7 +27,7 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
privateChatUser?: ApiUser;
|
||||
user?: ApiUser;
|
||||
isPinned?: boolean;
|
||||
isMuted?: boolean;
|
||||
};
|
||||
@ -35,7 +35,7 @@ type StateProps = {
|
||||
const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
chat,
|
||||
privateChatUser,
|
||||
user,
|
||||
isPinned,
|
||||
isMuted,
|
||||
withUsername,
|
||||
@ -46,7 +46,7 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const contextActions = useChatContextActions({
|
||||
chat,
|
||||
privateChatUser,
|
||||
user,
|
||||
isPinned,
|
||||
isMuted,
|
||||
handleDelete: openDeleteModal,
|
||||
@ -93,7 +93,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }): StateProps => {
|
||||
const chat = selectChat(global, chatId);
|
||||
const privateChatUserId = chat && getPrivateChatUserId(chat);
|
||||
const privateChatUser = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
||||
const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
||||
const isPinned = selectIsChatPinned(global, chatId);
|
||||
const isMuted = chat
|
||||
? selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global))
|
||||
@ -101,7 +101,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
return {
|
||||
chat,
|
||||
privateChatUser,
|
||||
user,
|
||||
isPinned,
|
||||
isMuted,
|
||||
};
|
||||
|
||||
@ -37,6 +37,7 @@ import {
|
||||
selectChatBot,
|
||||
selectChatUser,
|
||||
selectChatMessage,
|
||||
selectUserStatus,
|
||||
} from '../../../modules/selectors';
|
||||
import {
|
||||
getAllowedAttachmentOptions,
|
||||
@ -106,38 +107,40 @@ type OwnProps = {
|
||||
onDropHide: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
editingMessage?: ApiMessage;
|
||||
chat?: ApiChat;
|
||||
draft?: ApiFormattedText;
|
||||
isChatWithBot?: boolean;
|
||||
isChatWithSelf?: boolean;
|
||||
isRightColumnShown?: boolean;
|
||||
isSelectModeActive?: boolean;
|
||||
isForwarding?: boolean;
|
||||
isPollModalOpen?: boolean;
|
||||
botKeyboardMessageId?: number;
|
||||
botKeyboardPlaceholder?: string;
|
||||
withScheduledButton?: boolean;
|
||||
shouldSchedule?: boolean;
|
||||
canScheduleUntilOnline?: boolean;
|
||||
stickersForEmoji?: ApiSticker[];
|
||||
groupChatMembers?: ApiChatMember[];
|
||||
currentUserId?: string;
|
||||
usersById?: Record<string, ApiUser>;
|
||||
recentEmojis: string[];
|
||||
lastSyncTime?: number;
|
||||
contentToBeScheduled?: GlobalState['messages']['contentToBeScheduled'];
|
||||
shouldSuggestStickers?: boolean;
|
||||
baseEmojiKeywords?: Record<string, string[]>;
|
||||
emojiKeywords?: Record<string, string[]>;
|
||||
serverTimeOffset: number;
|
||||
topInlineBotIds?: string[];
|
||||
isInlineBotLoading: boolean;
|
||||
inlineBots?: Record<string, false | InlineBotSettings>;
|
||||
botCommands?: ApiBotCommand[] | false;
|
||||
chatBotCommands?: ApiBotCommand[];
|
||||
} & Pick<GlobalState, 'connectionState'>;
|
||||
type StateProps =
|
||||
{
|
||||
editingMessage?: ApiMessage;
|
||||
chat?: ApiChat;
|
||||
draft?: ApiFormattedText;
|
||||
isChatWithBot?: boolean;
|
||||
isChatWithSelf?: boolean;
|
||||
isRightColumnShown?: boolean;
|
||||
isSelectModeActive?: boolean;
|
||||
isForwarding?: boolean;
|
||||
isPollModalOpen?: boolean;
|
||||
botKeyboardMessageId?: number;
|
||||
botKeyboardPlaceholder?: string;
|
||||
withScheduledButton?: boolean;
|
||||
shouldSchedule?: boolean;
|
||||
canScheduleUntilOnline?: boolean;
|
||||
stickersForEmoji?: ApiSticker[];
|
||||
groupChatMembers?: ApiChatMember[];
|
||||
currentUserId?: string;
|
||||
usersById?: Record<string, ApiUser>;
|
||||
recentEmojis: string[];
|
||||
lastSyncTime?: number;
|
||||
contentToBeScheduled?: GlobalState['messages']['contentToBeScheduled'];
|
||||
shouldSuggestStickers?: boolean;
|
||||
baseEmojiKeywords?: Record<string, string[]>;
|
||||
emojiKeywords?: Record<string, string[]>;
|
||||
serverTimeOffset: number;
|
||||
topInlineBotIds?: string[];
|
||||
isInlineBotLoading: boolean;
|
||||
inlineBots?: Record<string, false | InlineBotSettings>;
|
||||
botCommands?: ApiBotCommand[] | false;
|
||||
chatBotCommands?: ApiBotCommand[];
|
||||
}
|
||||
& Pick<GlobalState, 'connectionState'>;
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
'sendMessage' | 'editMessage' | 'saveDraft' | 'forwardMessages' |
|
||||
@ -1071,9 +1074,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
chat,
|
||||
isChatWithBot,
|
||||
isChatWithSelf,
|
||||
canScheduleUntilOnline: (
|
||||
!isChatWithSelf && !isChatWithBot
|
||||
&& (chat && chatUser && isUserId(chatId) && chatUser.status && Boolean(chatUser.status.wasOnline))
|
||||
canScheduleUntilOnline: Boolean(
|
||||
!isChatWithSelf && !isChatWithBot && chat && chatUser
|
||||
&& isUserId(chatId) && selectUserStatus(global, chatId)?.wasOnline,
|
||||
),
|
||||
isRightColumnShown: selectIsRightColumnShown(global),
|
||||
isSelectModeActive: selectIsInSelectMode(global),
|
||||
|
||||
@ -4,11 +4,12 @@ import React, {
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import {
|
||||
MAIN_THREAD_ID,
|
||||
ApiMessage,
|
||||
ApiChat,
|
||||
ApiChatMember,
|
||||
ApiUser,
|
||||
ApiChat,
|
||||
MAIN_THREAD_ID,
|
||||
ApiUserStatus,
|
||||
} from '../../api/types';
|
||||
import { GlobalActions } from '../../global/types';
|
||||
import {
|
||||
@ -87,6 +88,7 @@ type StateProps = {
|
||||
commonChatIds?: string[];
|
||||
chatsById: Record<string, ApiChat>;
|
||||
usersById: Record<string, ApiUser>;
|
||||
userStatusesById: Record<string, ApiUserStatus>;
|
||||
isRightColumnShown: boolean;
|
||||
isRestricted?: boolean;
|
||||
lastSyncTime?: number;
|
||||
@ -129,6 +131,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
commonChatIds,
|
||||
members,
|
||||
usersById,
|
||||
userStatusesById,
|
||||
chatsById,
|
||||
isRightColumnShown,
|
||||
isRestricted,
|
||||
@ -168,7 +171,8 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds(
|
||||
isRightColumnShown, loadMoreMembers, loadCommonChats, searchMediaMessagesLocal, tabType, mediaSearchType, members,
|
||||
commonChatIds, usersById, chatsById, chatMessages, foundIds, chatId, lastSyncTime, serverTimeOffset,
|
||||
commonChatIds, usersById, userStatusesById, chatsById, chatMessages, foundIds, chatId, lastSyncTime,
|
||||
serverTimeOffset,
|
||||
);
|
||||
const activeKey = tabs.findIndex(({ type }) => type === resultType);
|
||||
|
||||
@ -487,7 +491,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
const { currentType: mediaSearchType, resultsByType } = selectCurrentMediaSearch(global) || {};
|
||||
const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {};
|
||||
|
||||
const { byId: usersById } = global.users;
|
||||
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
||||
const { byId: chatsById } = global.chats;
|
||||
|
||||
const isGroup = chat && isChatGroup(chat);
|
||||
@ -532,6 +536,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
serverTimeOffset: global.serverTimeOffset,
|
||||
activeDownloadIds,
|
||||
usersById,
|
||||
userStatusesById,
|
||||
chatsById,
|
||||
...(hasMembersTab && members && { members }),
|
||||
...(hasCommonChatsTab && user && { commonChatIds: user.commonChats?.ids }),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useMemo, useRef } from '../../../lib/teact/teact';
|
||||
|
||||
import {
|
||||
ApiChat, ApiChatMember, ApiMessage, ApiUser,
|
||||
ApiChat, ApiChatMember, ApiMessage, ApiUser, ApiUserStatus,
|
||||
} from '../../../api/types';
|
||||
import { ProfileTabType, SharedMediaType } from '../../../types';
|
||||
|
||||
@ -20,6 +20,7 @@ export default function useProfileViewportIds(
|
||||
groupChatMembers?: ApiChatMember[],
|
||||
commonChatIds?: string[],
|
||||
usersById?: Record<string, ApiUser>,
|
||||
userStatusesById?: Record<string, ApiUserStatus>,
|
||||
chatsById?: Record<string, ApiChat>,
|
||||
chatMessages?: Record<number, ApiMessage>,
|
||||
foundIds?: number[],
|
||||
@ -30,12 +31,18 @@ export default function useProfileViewportIds(
|
||||
const resultType = tabType === 'members' || !mediaSearchType ? tabType : mediaSearchType;
|
||||
|
||||
const memberIds = useMemo(() => {
|
||||
if (!groupChatMembers || !usersById) {
|
||||
if (!groupChatMembers || !usersById || !userStatusesById) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return sortUserIds(groupChatMembers.map(({ userId }) => userId), usersById, undefined, serverTimeOffset);
|
||||
}, [groupChatMembers, serverTimeOffset, usersById]);
|
||||
return sortUserIds(
|
||||
groupChatMembers.map(({ userId }) => userId),
|
||||
usersById,
|
||||
userStatusesById,
|
||||
undefined,
|
||||
serverTimeOffset,
|
||||
);
|
||||
}, [groupChatMembers, serverTimeOffset, usersById, userStatusesById]);
|
||||
|
||||
const chatIds = useMemo(() => {
|
||||
if (!commonChatIds || !chatsById) {
|
||||
|
||||
@ -3,7 +3,7 @@ import React, {
|
||||
} from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { ApiChatMember, ApiUser } from '../../../api/types';
|
||||
import { ApiChatMember, ApiUser, ApiUserStatus } from '../../../api/types';
|
||||
import { GlobalActions } from '../../../global/types';
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
import { sortUserIds, isChatChannel } from '../../../modules/helpers';
|
||||
@ -22,6 +22,7 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
usersById: Record<string, ApiUser>;
|
||||
userStatusesById: Record<string, ApiUserStatus>;
|
||||
members?: ApiChatMember[];
|
||||
isChannel?: boolean;
|
||||
serverTimeOffset: number;
|
||||
@ -32,6 +33,7 @@ type DispatchProps = Pick<GlobalActions, 'openUserInfo'>;
|
||||
const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
members,
|
||||
usersById,
|
||||
userStatusesById,
|
||||
isChannel,
|
||||
openUserInfo,
|
||||
onClose,
|
||||
@ -43,8 +45,14 @@ const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return sortUserIds(members.map(({ userId }) => userId), usersById, undefined, serverTimeOffset);
|
||||
}, [members, serverTimeOffset, usersById]);
|
||||
return sortUserIds(
|
||||
members.map(({ userId }) => userId),
|
||||
usersById,
|
||||
userStatusesById,
|
||||
undefined,
|
||||
serverTimeOffset,
|
||||
);
|
||||
}, [members, serverTimeOffset, usersById, userStatusesById]);
|
||||
|
||||
const handleMemberClick = useCallback((id: string) => {
|
||||
openUserInfo({ id });
|
||||
@ -83,13 +91,14 @@ const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }): StateProps => {
|
||||
const chat = selectChat(global, chatId);
|
||||
const { byId: usersById } = global.users;
|
||||
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
||||
const members = chat?.fullInfo?.members;
|
||||
const isChannel = chat && isChatChannel(chat);
|
||||
|
||||
return {
|
||||
members,
|
||||
usersById,
|
||||
userStatusesById,
|
||||
isChannel,
|
||||
serverTimeOffset: global.serverTimeOffset,
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import React, {
|
||||
} from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { ApiChatMember, ApiUser } from '../../../api/types';
|
||||
import { ApiChatMember, ApiUser, ApiUserStatus } from '../../../api/types';
|
||||
import { ManagementScreens } from '../../../types';
|
||||
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
@ -24,6 +24,7 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
usersById: Record<string, ApiUser>;
|
||||
userStatusesById: Record<string, ApiUserStatus>;
|
||||
members?: ApiChatMember[];
|
||||
isChannel?: boolean;
|
||||
serverTimeOffset: number;
|
||||
@ -31,6 +32,7 @@ type StateProps = {
|
||||
|
||||
const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
||||
usersById,
|
||||
userStatusesById,
|
||||
members,
|
||||
isChannel,
|
||||
onScreenSelect,
|
||||
@ -48,9 +50,12 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
||||
|
||||
return sortUserIds(
|
||||
members.filter((member) => !member.isOwner).map(({ userId }) => userId),
|
||||
usersById, undefined, serverTimeOffset,
|
||||
usersById,
|
||||
userStatusesById,
|
||||
undefined,
|
||||
serverTimeOffset,
|
||||
);
|
||||
}, [members, serverTimeOffset, usersById]);
|
||||
}, [members, serverTimeOffset, usersById, userStatusesById]);
|
||||
|
||||
const handleExceptionMemberClick = useCallback((memberId: string) => {
|
||||
onChatMemberSelect(memberId);
|
||||
@ -88,13 +93,14 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }): StateProps => {
|
||||
const chat = selectChat(global, chatId);
|
||||
const { byId: usersById } = global.users;
|
||||
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
||||
const members = chat?.fullInfo?.members;
|
||||
const isChannel = chat && isChatChannel(chat);
|
||||
|
||||
return {
|
||||
members,
|
||||
usersById,
|
||||
userStatusesById,
|
||||
isChannel,
|
||||
serverTimeOffset: global.serverTimeOffset,
|
||||
};
|
||||
|
||||
@ -190,9 +190,13 @@ function migrateCache(cached: GlobalState, initialState: GlobalState) {
|
||||
cached.audioPlayer.playbackRate = DEFAULT_PLAYBACK_RATE;
|
||||
}
|
||||
|
||||
if (cached.groupCalls === undefined) {
|
||||
if (!cached.groupCalls) {
|
||||
cached.groupCalls = initialState.groupCalls;
|
||||
}
|
||||
|
||||
if (!cached.users.statusesById) {
|
||||
cached.users.statusesById = {};
|
||||
}
|
||||
}
|
||||
|
||||
function updateCache() {
|
||||
@ -251,7 +255,7 @@ function reduceShowChatInfo(global: GlobalState): boolean {
|
||||
}
|
||||
|
||||
function reduceUsers(global: GlobalState): GlobalState['users'] {
|
||||
const { users: { byId, selectedId } } = global;
|
||||
const { users: { byId, statusesById, selectedId } } = global;
|
||||
const idsToSave = [
|
||||
...(global.chats.listIds.active || []).slice(0, GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT).filter(isUserId),
|
||||
...Object.keys(byId),
|
||||
@ -259,6 +263,7 @@ function reduceUsers(global: GlobalState): GlobalState['users'] {
|
||||
|
||||
return {
|
||||
byId: pick(byId, idsToSave),
|
||||
statusesById: pick(statusesById, idsToSave),
|
||||
selectedId: window.innerWidth > MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN ? selectedId : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ export const INITIAL_STATE: GlobalState = {
|
||||
|
||||
users: {
|
||||
byId: {},
|
||||
statusesById: {},
|
||||
},
|
||||
|
||||
chats: {
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
ApiMessage,
|
||||
ApiThreadInfo,
|
||||
ApiUser,
|
||||
ApiUserStatus,
|
||||
ApiUpdateAuthorizationStateType,
|
||||
ApiUpdateConnectionStateType,
|
||||
ApiStickerSet,
|
||||
@ -127,6 +128,7 @@ export type GlobalState = {
|
||||
|
||||
users: {
|
||||
byId: Record<string, ApiUser>;
|
||||
statusesById: Record<string, ApiUserStatus>;
|
||||
// TODO Remove
|
||||
selectedId?: string;
|
||||
};
|
||||
|
||||
@ -11,7 +11,7 @@ import useLang from './useLang';
|
||||
|
||||
export default ({
|
||||
chat,
|
||||
privateChatUser,
|
||||
user,
|
||||
handleDelete,
|
||||
handleChatFolderChange,
|
||||
folderId,
|
||||
@ -19,7 +19,7 @@ export default ({
|
||||
isMuted,
|
||||
}: {
|
||||
chat: ApiChat | undefined;
|
||||
privateChatUser: ApiUser | undefined;
|
||||
user: ApiUser | undefined;
|
||||
handleDelete: () => void;
|
||||
handleChatFolderChange: () => void;
|
||||
folderId?: number;
|
||||
@ -28,7 +28,7 @@ export default ({
|
||||
}, isInSearch = false) => {
|
||||
const lang = useLang();
|
||||
|
||||
const { isSelf } = privateChatUser || {};
|
||||
const { isSelf } = user || {};
|
||||
|
||||
return useMemo(() => {
|
||||
if (!chat) {
|
||||
|
||||
@ -22,6 +22,7 @@ import { callApi } from '../../../api/gramjs';
|
||||
import {
|
||||
addChats,
|
||||
addUsers,
|
||||
addUserStatuses,
|
||||
replaceThreadParam,
|
||||
updateChatListIds,
|
||||
updateChats,
|
||||
@ -1007,6 +1008,8 @@ async function loadChats(listType: 'active' | 'archived', offsetId?: string, off
|
||||
global = getGlobal();
|
||||
|
||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||
global = addUserStatuses(global, result.userStatusesById);
|
||||
|
||||
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||
global = updateChatListIds(global, listType, chatIds);
|
||||
global = updateChatListSecondaryInfo(global, listType, result);
|
||||
|
||||
@ -17,7 +17,9 @@ import {
|
||||
replaceChatListIds,
|
||||
replaceChats,
|
||||
replaceUsers,
|
||||
replaceUserStatuses,
|
||||
updateUsers,
|
||||
addUserStatuses,
|
||||
updateChats,
|
||||
updateChatListSecondaryInfo,
|
||||
updateThreadInfos,
|
||||
@ -146,16 +148,10 @@ async function loadAndReplaceChats() {
|
||||
savedUsers.push(...result.users);
|
||||
savedChats.push(...result.chats);
|
||||
|
||||
global = replaceUserStatuses(global, result.userStatusesById);
|
||||
|
||||
global = replaceChats(global, buildCollectionByKey(savedChats, 'id'));
|
||||
global = replaceChatListIds(global, 'active', result.chatIds);
|
||||
|
||||
global = {
|
||||
...global,
|
||||
chats: {
|
||||
...global.chats,
|
||||
},
|
||||
};
|
||||
|
||||
global = updateChatListSecondaryInfo(global, 'active', result);
|
||||
|
||||
Object.keys(result.draftsById).forEach((chatId) => {
|
||||
@ -190,10 +186,14 @@ async function loadAndReplaceArchivedChats() {
|
||||
}
|
||||
|
||||
let global = getGlobal();
|
||||
|
||||
global = updateUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||
global = addUserStatuses(global, result.userStatusesById);
|
||||
|
||||
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||
global = replaceChatListIds(global, 'archived', result.chatIds);
|
||||
global = updateChatListSecondaryInfo(global, 'archived', result);
|
||||
|
||||
setGlobal(global);
|
||||
}
|
||||
|
||||
@ -337,13 +337,16 @@ async function loadAndUpdateUsers() {
|
||||
...(contactIds || []),
|
||||
].map((id) => selectUser(global, id)).filter<ApiUser>(Boolean as any);
|
||||
|
||||
const updatedUsers = await callApi('fetchUsers', { users });
|
||||
if (!updatedUsers) {
|
||||
const result = await callApi('fetchUsers', { users });
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { users: updatedUsers, userStatusesById } = result;
|
||||
|
||||
global = getGlobal();
|
||||
global = updateUsers(global, buildCollectionByKey(updatedUsers, 'id'));
|
||||
global = addUserStatuses(global, userStatusesById);
|
||||
setGlobal(global);
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import { isUserBot, isUserId } from '../../helpers';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { selectChat, selectCurrentMessageList, selectUser } from '../../selectors';
|
||||
import {
|
||||
addChats, addUsers, updateChat, updateManagementProgress, updateUser, updateUsers,
|
||||
addChats, addUsers, replaceUserStatuses, updateChat, updateManagementProgress, updateUser, updateUsers,
|
||||
updateUserSearch, updateUserSearchFetchingStatus,
|
||||
} from '../../reducers';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
@ -40,13 +40,21 @@ addReducer('loadUser', (global, actions, payload) => {
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const updatedUsers = await callApi('fetchUsers', { users: [user] });
|
||||
if (!updatedUsers) {
|
||||
const result = await callApi('fetchUsers', { users: [user] });
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { users, userStatusesById } = result;
|
||||
|
||||
global = getGlobal();
|
||||
global = updateUsers(global, buildCollectionByKey(updatedUsers, 'id'));
|
||||
|
||||
global = updateUsers(global, buildCollectionByKey(users, 'id'));
|
||||
setGlobal(replaceUserStatuses(global, {
|
||||
...global.users.statusesById,
|
||||
...userStatusesById,
|
||||
}));
|
||||
|
||||
setGlobal(global);
|
||||
})();
|
||||
});
|
||||
|
||||
@ -2,30 +2,29 @@ import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { ApiUpdate, ApiUserStatus } from '../../../api/types';
|
||||
|
||||
import { deleteUser, updateUser } from '../../reducers';
|
||||
import { deleteUser, replaceUserStatuses, updateUser } from '../../reducers';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
|
||||
const STATUS_UPDATE_THROTTLE = 3000;
|
||||
|
||||
const flushStatusUpdatesThrottled = throttle(flushStatusUpdates, STATUS_UPDATE_THROTTLE, true);
|
||||
|
||||
let pendingStatusUpdates: [string, ApiUserStatus][] = [];
|
||||
let pendingStatusUpdates: Record<string, ApiUserStatus> = {};
|
||||
|
||||
function scheduleStatusUpdate(userId: string, statusUpdate: ApiUserStatus) {
|
||||
pendingStatusUpdates.push([userId, statusUpdate]);
|
||||
pendingStatusUpdates[userId] = statusUpdate;
|
||||
flushStatusUpdatesThrottled();
|
||||
}
|
||||
|
||||
function flushStatusUpdates() {
|
||||
let global = getGlobal();
|
||||
pendingStatusUpdates.forEach(([userId, statusUpdate]) => {
|
||||
global = updateUser(global, userId, {
|
||||
status: statusUpdate,
|
||||
});
|
||||
});
|
||||
setGlobal(global);
|
||||
const global = getGlobal();
|
||||
|
||||
pendingStatusUpdates = [];
|
||||
setGlobal(replaceUserStatuses(global, {
|
||||
...global.users.statusesById,
|
||||
...pendingStatusUpdates,
|
||||
}));
|
||||
|
||||
pendingStatusUpdates = {};
|
||||
}
|
||||
|
||||
addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ApiChat, ApiUser } from '../../api/types';
|
||||
import { ApiChat, ApiUser, ApiUserStatus } from '../../api/types';
|
||||
|
||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
||||
import { formatFullDate, formatTime } from '../../util/dateFormat';
|
||||
@ -65,7 +65,9 @@ export function getUserFullName(user?: ApiUser) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: number) {
|
||||
export function getUserStatus(
|
||||
lang: LangFn, user: ApiUser, userStatus: ApiUserStatus | undefined, serverTimeOffset: number,
|
||||
) {
|
||||
if (user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
||||
return lang('ServiceNotifications').toLowerCase();
|
||||
}
|
||||
@ -74,11 +76,11 @@ export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: num
|
||||
return lang('Bot');
|
||||
}
|
||||
|
||||
if (!user.status) {
|
||||
if (!userStatus) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch (user.status.type) {
|
||||
switch (userStatus.type) {
|
||||
case 'userStatusEmpty': {
|
||||
return lang('ALongTimeAgo');
|
||||
}
|
||||
@ -92,7 +94,7 @@ export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: num
|
||||
}
|
||||
|
||||
case 'userStatusOffline': {
|
||||
const { wasOnline } = user.status;
|
||||
const { wasOnline } = userStatus;
|
||||
|
||||
if (!wasOnline) return lang('LastSeen.Offline');
|
||||
|
||||
@ -156,10 +158,10 @@ export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: num
|
||||
}
|
||||
}
|
||||
|
||||
export function isUserOnline(user: ApiUser) {
|
||||
const { id, status, type } = user;
|
||||
export function isUserOnline(user: ApiUser, userStatus?: ApiUserStatus) {
|
||||
const { id, type } = user;
|
||||
|
||||
if (!status) {
|
||||
if (!userStatus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -167,11 +169,11 @@ export function isUserOnline(user: ApiUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return status.type === 'userStatusOnline' && type !== 'userTypeBot';
|
||||
return userStatus.type === 'userStatusOnline' && type !== 'userTypeBot';
|
||||
}
|
||||
|
||||
export function isDeletedUser(user: ApiUser) {
|
||||
if (!user.status || user.type === 'userTypeBot' || user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
||||
if (user.noStatus || user.type === 'userTypeBot' || user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -190,6 +192,7 @@ export function getCanAddContact(user: ApiUser) {
|
||||
export function sortUserIds(
|
||||
userIds: string[],
|
||||
usersById: Record<string, ApiUser>,
|
||||
userStatusesById: Record<string, ApiUserStatus>,
|
||||
priorityIds?: string[],
|
||||
serverTimeOffset = 0,
|
||||
) {
|
||||
@ -204,17 +207,18 @@ export function sortUserIds(
|
||||
}
|
||||
|
||||
const user = usersById[id];
|
||||
if (!user || !user.status) {
|
||||
const userStatus = userStatusesById[id];
|
||||
if (!user || !userStatus) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (user.status.type === 'userStatusOnline') {
|
||||
return user.status.expires;
|
||||
} else if (user.status.type === 'userStatusOffline' && user.status.wasOnline) {
|
||||
return user.status.wasOnline;
|
||||
if (userStatus.type === 'userStatusOnline') {
|
||||
return userStatus.expires;
|
||||
} else if (userStatus.type === 'userStatusOffline' && userStatus.wasOnline) {
|
||||
return userStatus.wasOnline;
|
||||
}
|
||||
|
||||
switch (user.status.type) {
|
||||
switch (userStatus.type) {
|
||||
case 'userStatusRecently':
|
||||
return now - 60 * 60 * 24;
|
||||
case 'userStatusLastWeek':
|
||||
|
||||
@ -2,7 +2,7 @@ import { GlobalState } from '../../global/types';
|
||||
import { ApiChat, ApiPhoto } from '../../api/types';
|
||||
|
||||
import { ARCHIVED_FOLDER_ID } from '../../config';
|
||||
import { omit } from '../../util/iteratees';
|
||||
import { mapValues, omit } from '../../util/iteratees';
|
||||
import { selectChatListType } from '../selectors';
|
||||
|
||||
export function replaceChatListIds(
|
||||
@ -48,26 +48,6 @@ export function replaceChats(global: GlobalState, newById: Record<string, ApiCha
|
||||
};
|
||||
}
|
||||
|
||||
// @optimization Don't spread/unspread global for each element, do it in a batch
|
||||
function getUpdatedChat(
|
||||
global: GlobalState, chatId: string, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
||||
): ApiChat {
|
||||
const { byId } = global.chats;
|
||||
const chat = byId[chatId];
|
||||
const shouldOmitMinInfo = chatUpdate.isMin && chat && !chat.isMin;
|
||||
const updatedChat = {
|
||||
...chat,
|
||||
...(shouldOmitMinInfo ? omit(chatUpdate, ['isMin', 'accessHash']) : chatUpdate),
|
||||
...(photo && { photos: [photo, ...(chat.photos || [])] }),
|
||||
};
|
||||
|
||||
if (!updatedChat.id || !updatedChat.type) {
|
||||
return updatedChat;
|
||||
}
|
||||
|
||||
return updatedChat;
|
||||
}
|
||||
|
||||
export function updateChat(
|
||||
global: GlobalState, chatId: string, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
||||
): GlobalState {
|
||||
@ -81,52 +61,67 @@ export function updateChat(
|
||||
});
|
||||
}
|
||||
|
||||
export function updateChats(global: GlobalState, updatedById: Record<string, ApiChat>): GlobalState {
|
||||
const updatedChats = Object.keys(updatedById).reduce<Record<string, ApiChat>>((acc, id) => {
|
||||
const updatedChat = getUpdatedChat(global, id, updatedById[id]);
|
||||
if (updatedChat) {
|
||||
acc[id] = updatedChat;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
export function updateChats(global: GlobalState, newById: Record<string, ApiChat>): GlobalState {
|
||||
const updatedById = mapValues(newById, (chat, id) => {
|
||||
return getUpdatedChat(global, id, chat);
|
||||
});
|
||||
|
||||
global = replaceChats(global, {
|
||||
...global.chats.byId,
|
||||
...updatedChats,
|
||||
...updatedById,
|
||||
});
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
||||
export function addChats(global: GlobalState, addedById: Record<string, ApiChat>): GlobalState {
|
||||
export function addChats(global: GlobalState, newById: Record<string, ApiChat>): GlobalState {
|
||||
const { byId } = global.chats;
|
||||
let isAdded = false;
|
||||
|
||||
const addedChats = Object.keys(addedById).reduce<Record<string, ApiChat>>((acc, id) => {
|
||||
if (!byId[id] || (byId[id].isMin && !addedById[id].isMin)) {
|
||||
const updatedChat = getUpdatedChat(global, id, addedById[id]);
|
||||
if (updatedChat) {
|
||||
acc[id] = updatedChat;
|
||||
const addedById = Object.keys(newById).reduce<Record<string, ApiChat>>((acc, id) => {
|
||||
if (!byId[id] || (byId[id].isMin && !newById[id].isMin)) {
|
||||
acc[id] = getUpdatedChat(global, id, newById[id]);
|
||||
|
||||
if (!isAdded) {
|
||||
isAdded = true;
|
||||
}
|
||||
if (!isAdded) {
|
||||
isAdded = true;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (isAdded) {
|
||||
global = replaceChats(global, {
|
||||
...global.chats.byId,
|
||||
...addedChats,
|
||||
});
|
||||
if (!isAdded) {
|
||||
return global;
|
||||
}
|
||||
|
||||
global = replaceChats(global, {
|
||||
...byId,
|
||||
...addedById,
|
||||
});
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
// @optimization Don't spread/unspread global for each element, do it in a batch
|
||||
function getUpdatedChat(
|
||||
global: GlobalState, chatId: string, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
||||
) {
|
||||
const { byId } = global.chats;
|
||||
const chat = byId[chatId];
|
||||
const shouldOmitMinInfo = chatUpdate.isMin && chat && !chat.isMin;
|
||||
const updatedChat: ApiChat = {
|
||||
...chat,
|
||||
...(shouldOmitMinInfo ? omit(chatUpdate, ['isMin', 'accessHash']) : chatUpdate),
|
||||
...(photo && { photos: [photo, ...(chat.photos || [])] }),
|
||||
};
|
||||
|
||||
if (!updatedChat.id || !updatedChat.type) {
|
||||
return updatedChat;
|
||||
}
|
||||
|
||||
return updatedChat;
|
||||
}
|
||||
|
||||
export function updateChatListType(
|
||||
global: GlobalState,
|
||||
chatId: string,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { GlobalState } from '../../global/types';
|
||||
import { ApiUser } from '../../api/types';
|
||||
import { ApiUser, ApiUserStatus } from '../../api/types';
|
||||
|
||||
import { omit } from '../../util/iteratees';
|
||||
import { mapValues, omit, pick } from '../../util/iteratees';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||
|
||||
export function replaceUsers(global: GlobalState, newById: Record<string, ApiUser>): GlobalState {
|
||||
@ -14,24 +14,6 @@ export function replaceUsers(global: GlobalState, newById: Record<string, ApiUse
|
||||
};
|
||||
}
|
||||
|
||||
// @optimization Don't spread/unspread global for each element, do it in a batch
|
||||
function getUpdatedUser(global: GlobalState, userId: string, userUpdate: Partial<ApiUser>): ApiUser {
|
||||
const { byId } = global.users;
|
||||
const user = byId[userId];
|
||||
const shouldOmitMinInfo = userUpdate.isMin && user && !user.isMin;
|
||||
|
||||
const updatedUser = {
|
||||
...user,
|
||||
...(shouldOmitMinInfo ? omit(userUpdate, ['isMin', 'accessHash']) : userUpdate),
|
||||
};
|
||||
|
||||
if (!updatedUser.id || !updatedUser.type) {
|
||||
return user;
|
||||
}
|
||||
|
||||
return updatedUser;
|
||||
}
|
||||
|
||||
function updateContactList(global: GlobalState, updatedUsers: ApiUser[]): GlobalState {
|
||||
const { userIds: contactUserIds } = global.contactList || {};
|
||||
|
||||
@ -67,56 +49,69 @@ export function updateUser(global: GlobalState, userId: string, userUpdate: Part
|
||||
});
|
||||
}
|
||||
|
||||
export function updateUsers(global: GlobalState, updatedById: Record<string, ApiUser>): GlobalState {
|
||||
const updatedUsers = Object.keys(updatedById).reduce<Record<string, ApiUser>>((acc, id) => {
|
||||
const updatedUser = getUpdatedUser(global, id, updatedById[id]);
|
||||
if (updatedUser) {
|
||||
acc[id] = updatedUser;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
global = updateContactList(global, Object.values(updatedUsers));
|
||||
export function updateUsers(global: GlobalState, newById: Record<string, ApiUser>): GlobalState {
|
||||
const updatedById = mapValues(newById, (user, id) => {
|
||||
return getUpdatedUser(global, id, user);
|
||||
});
|
||||
|
||||
global = replaceUsers(global, {
|
||||
...global.users.byId,
|
||||
...updatedUsers,
|
||||
...updatedById,
|
||||
});
|
||||
|
||||
global = updateContactList(global, Object.values(updatedById));
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
||||
export function addUsers(global: GlobalState, addedById: Record<string, ApiUser>): GlobalState {
|
||||
export function addUsers(global: GlobalState, newById: Record<string, ApiUser>): GlobalState {
|
||||
const { byId } = global.users;
|
||||
let isAdded = false;
|
||||
|
||||
const addedUsers = Object.keys(addedById).reduce<Record<string, ApiUser>>((acc, id) => {
|
||||
if (!byId[id] || (byId[id].isMin && !addedById[id].isMin)) {
|
||||
const updatedUser = getUpdatedUser(global, id, addedById[id]);
|
||||
if (updatedUser) {
|
||||
acc[id] = updatedUser;
|
||||
const addedById = Object.keys(newById).reduce<Record<string, ApiUser>>((acc, id) => {
|
||||
if (!byId[id] || (byId[id].isMin && !newById[id].isMin)) {
|
||||
acc[id] = getUpdatedUser(global, id, newById[id]);
|
||||
|
||||
if (!isAdded) {
|
||||
isAdded = true;
|
||||
}
|
||||
if (!isAdded) {
|
||||
isAdded = true;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (isAdded) {
|
||||
global = replaceUsers(global, {
|
||||
...global.users.byId,
|
||||
...addedUsers,
|
||||
});
|
||||
|
||||
global = updateContactList(global, Object.values(addedUsers));
|
||||
if (!isAdded) {
|
||||
return global;
|
||||
}
|
||||
|
||||
global = replaceUsers(global, {
|
||||
...byId,
|
||||
...addedById,
|
||||
});
|
||||
|
||||
global = updateContactList(global, Object.values(addedById));
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
// @optimization Don't spread/unspread global for each element, do it in a batch
|
||||
function getUpdatedUser(global: GlobalState, userId: string, userUpdate: Partial<ApiUser>) {
|
||||
const { byId } = global.users;
|
||||
const user = byId[userId];
|
||||
const shouldOmitMinInfo = userUpdate.isMin && user && !user.isMin;
|
||||
|
||||
const updatedUser = {
|
||||
...user,
|
||||
...(shouldOmitMinInfo ? omit(userUpdate, ['isMin', 'accessHash']) : userUpdate),
|
||||
};
|
||||
|
||||
if (!updatedUser.id || !updatedUser.type) {
|
||||
return user;
|
||||
}
|
||||
|
||||
return updatedUser;
|
||||
}
|
||||
|
||||
export function updateSelectedUserId(global: GlobalState, selectedId?: string): GlobalState {
|
||||
if (global.users.selectedId === selectedId) {
|
||||
return global;
|
||||
@ -182,3 +177,30 @@ export function updateUserBlockedState(global: GlobalState, userId: string, isBl
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function replaceUserStatuses(global: GlobalState, newById: Record<string, ApiUserStatus>): GlobalState {
|
||||
return {
|
||||
...global,
|
||||
users: {
|
||||
...global.users,
|
||||
statusesById: newById,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
||||
export function addUserStatuses(global: GlobalState, newById: Record<string, ApiUserStatus>): GlobalState {
|
||||
const { statusesById } = global.users;
|
||||
|
||||
const newKeys = Object.keys(newById).filter((id) => !statusesById[id]);
|
||||
if (!newKeys.length) {
|
||||
return global;
|
||||
}
|
||||
|
||||
global = replaceUserStatuses(global, {
|
||||
...statusesById,
|
||||
...pick(newById, newKeys),
|
||||
});
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
@ -46,7 +46,11 @@ export function selectChatOnlineCount(global: GlobalState, chat: ApiChat) {
|
||||
}
|
||||
|
||||
return chat.fullInfo.members.reduce((onlineCount, { userId }) => {
|
||||
if (global.users.byId[userId] && isUserOnline(global.users.byId[userId]) && userId !== global.currentUserId) {
|
||||
if (
|
||||
userId !== global.currentUserId
|
||||
&& global.users.byId[userId]
|
||||
&& isUserOnline(global.users.byId[userId], global.users.statusesById[userId])
|
||||
) {
|
||||
return onlineCount + 1;
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import { GlobalState } from '../../global/types';
|
||||
import { ApiChat, ApiUser } from '../../api/types';
|
||||
import { ApiChat, ApiUser, ApiUserStatus } from '../../api/types';
|
||||
|
||||
export function selectUser(global: GlobalState, userId: string): ApiUser | undefined {
|
||||
return global.users.byId[userId];
|
||||
}
|
||||
|
||||
export function selectUserStatus(global: GlobalState, userId: string): ApiUserStatus | undefined {
|
||||
return global.users.statusesById[userId];
|
||||
}
|
||||
|
||||
export function selectIsUserBlocked(global: GlobalState, userId: string) {
|
||||
const user = selectUser(global, userId);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user