diff --git a/src/components/appSearchSuper..ts b/src/components/appSearchSuper..ts
index aceed795..2d73f217 100644
--- a/src/components/appSearchSuper..ts
+++ b/src/components/appSearchSuper..ts
@@ -718,7 +718,7 @@ export default class AppSearchSuper {
if(showMembersCount && (peer.participants_count || peer.participants)) {
const regExp = new RegExp(`(${escapeRegExp(query)}|${escapeRegExp(cleanSearchText(query))})`, 'gi');
dom.titleSpan.innerHTML = dom.titleSpan.innerHTML.replace(regExp, '$1');
- dom.lastMessageSpan.append(appChatsManager.getChatMembersString(-peerId));
+ dom.lastMessageSpan.append(appProfileManager.getChatMembersString(-peerId));
} else if(peerId === rootScope.myId) {
dom.lastMessageSpan.append(i18n('Presence.YourChat'));
} else {
@@ -811,7 +811,7 @@ export default class AppSearchSuper {
autonomous: true
});
- dom.lastMessageSpan.append(peerId > 0 ? appUsersManager.getUserStatusString(peerId) : appChatsManager.getChatMembersString(peerId));
+ dom.lastMessageSpan.append(peerId > 0 ? appUsersManager.getUserStatusString(peerId) : appProfileManager.getChatMembersString(-peerId));
});
if(!state.recentSearch.length) {
diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts
index 8cf85a6e..921a87f9 100644
--- a/src/components/appSelectPeers.ts
+++ b/src/components/appSelectPeers.ts
@@ -447,7 +447,7 @@ export default class AppSelectPeers {
let subtitleEl: HTMLElement;
if(peerId < 0) {
- subtitleEl = appChatsManager.getChatMembersString(-peerId);
+ subtitleEl = appProfileManager.getChatMembersString(-peerId);
} else if(peerId === rootScope.myId) {
subtitleEl = i18n('Presence.YourChat');
} else {
diff --git a/src/components/avatar.ts b/src/components/avatar.ts
index 642ee807..0f4f10c1 100644
--- a/src/components/avatar.ts
+++ b/src/components/avatar.ts
@@ -14,9 +14,10 @@ import appPhotosManager from "../lib/appManagers/appPhotosManager";
import type { LazyLoadQueueIntersector } from "./lazyLoadQueue";
import { attachClickEvent } from "../helpers/dom/clickEvent";
import { cancelEvent } from "../helpers/dom/cancelEvent";
+import appAvatarsManager from "../lib/appManagers/appAvatarsManager";
const onAvatarUpdate = (peerId: number) => {
- appProfileManager.removeFromAvatarsCache(peerId);
+ appAvatarsManager.removeFromAvatarsCache(peerId);
(Array.from(document.querySelectorAll('avatar-element[peer="' + peerId + '"]')) as AvatarElement[]).forEach(elem => {
//console.log('updating avatar:', elem);
elem.update();
@@ -180,7 +181,7 @@ export default class AvatarElement extends HTMLElement {
}
private r(onlyThumb = false) {
- const res = appProfileManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle, onlyThumb);
+ const res = appAvatarsManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle, onlyThumb);
const promise = res ? res.loadPromise : Promise.resolve();
if(this.loadPromises) {
if(res && res.cached) {
diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts
index 0adf3e3e..cbf97dc8 100644
--- a/src/components/sidebarRight/tabs/sharedMedia.ts
+++ b/src/components/sidebarRight/tabs/sharedMedia.ts
@@ -48,6 +48,7 @@ import ButtonCorner from "../../buttonCorner";
import { cancelEvent } from "../../../helpers/dom/cancelEvent";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
import replaceContent from "../../../helpers/dom/replaceContent";
+import appAvatarsManager from "../../../lib/appManagers/appAvatarsManager";
let setText = (text: string, row: Row) => {
//fastRaf(() => {
@@ -437,7 +438,7 @@ class PeerProfileAvatars {
});
} else {
const photo = appPeersManager.getPeerPhoto(this.peerId);
- appProfileManager.putAvatar(avatar, this.peerId, photo, 'photo_big', img);
+ appAvatarsManager.putAvatar(avatar, this.peerId, photo, 'photo_big', img);
}
this.avatars.append(avatar);
diff --git a/src/lib/appManagers/appAvatarsManager.ts b/src/lib/appManagers/appAvatarsManager.ts
new file mode 100644
index 00000000..d9f5a7dd
--- /dev/null
+++ b/src/lib/appManagers/appAvatarsManager.ts
@@ -0,0 +1,202 @@
+/*
+ * https://github.com/morethanwords/tweb
+ * Copyright (C) 2019-2021 Eduard Kuzmenko
+ * https://github.com/morethanwords/tweb/blob/master/LICENSE
+ */
+
+import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl";
+import replaceContent from "../../helpers/dom/replaceContent";
+import sequentialDom from "../../helpers/sequentialDom";
+import { UserProfilePhoto, ChatPhoto, InputFileLocation } from "../../layer";
+import RichTextProcessor from "../richtextprocessor";
+import rootScope from "../rootScope";
+import appDownloadManager from "./appDownloadManager";
+import appPeersManager from "./appPeersManager";
+import appPhotosManager from "./appPhotosManager";
+import appUsersManager from "./appUsersManager";
+
+type PeerPhotoSize = 'photo_small' | 'photo_big';
+
+export class AppAvatarsManager {
+ private savedAvatarURLs: {
+ [peerId: number]: {
+ [size in PeerPhotoSize]?: string | Promise
+ }
+ } = {};
+
+ public removeFromAvatarsCache(peerId: number) {
+ if(this.savedAvatarURLs[peerId]) {
+ delete this.savedAvatarURLs[peerId];
+ }
+ }
+
+ public loadAvatar(peerId: number, photo: UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto, size: PeerPhotoSize) {
+ const inputPeer = appPeersManager.getInputPeerById(peerId);
+
+ let cached = false;
+ let getAvatarPromise: Promise;
+ let saved = this.savedAvatarURLs[peerId];
+ if(!saved || !saved[size]) {
+ if(!saved) {
+ saved = this.savedAvatarURLs[peerId] = {};
+ }
+
+ //console.warn('will invoke downloadSmallFile:', peerId);
+ const peerPhotoFileLocation: InputFileLocation.inputPeerPhotoFileLocation = {
+ _: 'inputPeerPhotoFileLocation',
+ pFlags: {},
+ peer: inputPeer,
+ photo_id: photo.photo_id
+ };
+
+ if(size === 'photo_big') {
+ peerPhotoFileLocation.pFlags.big = true;
+ }
+
+ const downloadOptions = {dcId: photo.dc_id, location: peerPhotoFileLocation};
+
+ /* let str: string;
+ const time = Date.now();
+ if(peerId === 0) {
+ str = `download avatar ${peerId}`;
+ } */
+
+ const promise = appDownloadManager.download(downloadOptions);
+ getAvatarPromise = saved[size] = promise.then(blob => {
+ return saved[size] = URL.createObjectURL(blob);
+
+ /* if(str) {
+ console.log(str, Date.now() / 1000, Date.now() - time);
+ } */
+ });
+ } else if(typeof(saved[size]) !== 'string') {
+ getAvatarPromise = saved[size] as Promise;
+ } else {
+ getAvatarPromise = Promise.resolve(saved[size]);
+ cached = true;
+ }
+
+ return {cached, loadPromise: getAvatarPromise};
+ }
+
+ public putAvatar(div: HTMLElement, peerId: number, photo: UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto, size: PeerPhotoSize, img = new Image(), onlyThumb = false) {
+ let {cached, loadPromise} = this.loadAvatar(peerId, photo, size);
+
+ let renderThumbPromise: Promise;
+ let callback: () => void;
+ if(cached) {
+ // смотри в misc.ts: renderImageFromUrl
+ callback = () => {
+ replaceContent(div, img);
+ div.dataset.color = '';
+ };
+ } else {
+ const animate = rootScope.settings.animationsEnabled;
+ if(animate) {
+ img.classList.add('fade-in');
+ }
+
+ let thumbImage: HTMLImageElement;
+ if(photo.stripped_thumb) {
+ thumbImage = new Image();
+ div.classList.add('avatar-relative');
+ thumbImage.classList.add('avatar-photo', 'avatar-photo-thumbnail');
+ img.classList.add('avatar-photo');
+ const url = appPhotosManager.getPreviewURLFromBytes(photo.stripped_thumb);
+ renderThumbPromise = renderImageFromUrl(thumbImage, url).then(() => {
+ replaceContent(div, thumbImage);
+ });
+ }
+
+ callback = () => {
+ if(photo.stripped_thumb) {
+ div.append(img);
+ } else {
+ replaceContent(div, img);
+ }
+
+ setTimeout(() => {
+ if(div.childElementCount) {
+ sequentialDom.mutateElement(img, () => {
+ div.dataset.color = '';
+
+ if(animate) {
+ img.classList.remove('fade-in');
+ }
+
+ if(thumbImage) {
+ thumbImage.remove();
+ }
+ });
+ }
+ }, animate ? 200 : 0);
+ };
+ }
+
+ const renderPromise = loadPromise
+ .then((url) => renderImageFromUrl(img, url/* , false */))
+ .then(() => callback());
+
+ return {cached, loadPromise: renderThumbPromise || renderPromise};
+ }
+
+ // peerId === peerId || title
+ public putPhoto(div: HTMLElement, peerId: number, isDialog = false, title = '', onlyThumb = false) {
+ const photo = appPeersManager.getPeerPhoto(peerId);
+
+ const size: PeerPhotoSize = 'photo_small';
+ const avatarAvailable = !!photo;
+ const avatarRendered = div.firstElementChild && !(div.firstElementChild as HTMLElement).classList.contains('emoji');
+
+ const myId = rootScope.myId;
+
+ //console.log('loadDialogPhoto location:', location, inputPeer);
+ if(peerId === myId && isDialog) {
+ div.innerText = '';
+ div.dataset.color = '';
+ div.classList.add('tgico-saved');
+ div.classList.remove('tgico-deletedaccount');
+ return;
+ }
+
+ if(peerId > 0) {
+ const user = appUsersManager.getUser(peerId);
+ if(user && user.pFlags && user.pFlags.deleted) {
+ div.innerText = '';
+ div.dataset.color = appPeersManager.getPeerColorById(peerId);
+ div.classList.add('tgico-deletedaccount');
+ div.classList.remove('tgico-saved');
+ return;
+ }
+ }
+
+ if(!avatarAvailable || !avatarRendered || !this.savedAvatarURLs[peerId]) {
+ let color = '';
+ if(peerId && (peerId !== myId || !isDialog)) {
+ color = appPeersManager.getPeerColorById(peerId);
+ }
+
+ div.innerText = '';
+ div.classList.remove('tgico-saved', 'tgico-deletedaccount');
+ div.dataset.color = color;
+
+ let abbr: string;
+ if(!title) {
+ const peer = appPeersManager.getPeer(peerId);
+ abbr = peer.initials ?? '';
+ } else {
+ abbr = RichTextProcessor.getAbbreviation(title);
+ }
+
+ div.innerHTML = abbr;
+ //return Promise.resolve(true);
+ }
+
+ if(avatarAvailable/* && false */) {
+ return this.putAvatar(div, peerId, photo, size, undefined, onlyThumb);
+ }
+ }
+}
+
+const appAvatarsManager = new AppAvatarsManager();
+export default appAvatarsManager;
diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts
index e6b9aa0b..f5916ae6 100644
--- a/src/lib/appManagers/appChatsManager.ts
+++ b/src/lib/appManagers/appChatsManager.ts
@@ -10,17 +10,14 @@
*/
import { MOUNT_CLASS_TO } from "../../config/debug";
-import { numberThousandSplitter } from "../../helpers/number";
import { isObject, safeReplaceObject, copy, deepEqual } from "../../helpers/object";
-import { ChannelParticipant, Chat, ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipant, ChatParticipants, ChatPhoto, InputChannel, InputChatPhoto, InputFile, InputPeer, SendMessageAction, Update, Updates } from "../../layer";
-import { i18n, LangPackKey } from "../langPack";
+import { ChannelParticipant, Chat, ChatAdminRights, ChatBannedRights, ChatParticipant, ChatPhoto, InputChannel, InputChatPhoto, InputFile, InputPeer, Update, Updates } from "../../layer";
import apiManagerProxy from "../mtproto/mtprotoworker";
import apiManager from '../mtproto/mtprotoworker';
import { RichTextProcessor } from "../richtextprocessor";
import rootScope from "../rootScope";
import apiUpdatesManager from "./apiUpdatesManager";
import appPeersManager from "./appPeersManager";
-import appProfileManager from "./appProfileManager";
import appStateManager from "./appStateManager";
import appUsersManager from "./appUsersManager";
@@ -28,8 +25,6 @@ export type Channel = Chat.channel;
export type ChatRights = keyof ChatBannedRights['pFlags'] | keyof ChatAdminRights['pFlags'] | 'change_type' | 'change_permissions' | 'delete_chat' | 'view_participants';
-export type UserTyping = Partial<{userId: number, action: SendMessageAction, timeout: number}>;
-
export class AppChatsManager {
private storage = appStateManager.storages.chats;
@@ -38,10 +33,6 @@ export class AppChatsManager {
//private channelAccess: any;
//private megagroups: {[id: number]: true};
- private megagroupOnlines: {[id: number]: {timestamp: number, onlines: number}};
-
- private typingsInPeer: {[peerId: number]: UserTyping[]};
-
constructor() {
this.clear();
@@ -65,11 +56,7 @@ export class AppChatsManager {
chat.default_banned_rights = update.default_banned_rights;
rootScope.dispatchEvent('chat_update', chatId);
}
- },
-
- updateUserTyping: this.onUpdateUserTyping,
- updateChatUserTyping: this.onUpdateUserTyping,
- updateChannelUserTyping: this.onUpdateUserTyping
+ }
});
appStateManager.getState().then((state) => {
@@ -119,81 +106,6 @@ export class AppChatsManager {
} else {
this.chats = {};
}
-
- this.megagroupOnlines = {};
- this.typingsInPeer = {};
- }
-
- private onUpdateUserTyping = (update: Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChannelUserTyping) => {
- const fromId = (update as Update.updateUserTyping).user_id || appPeersManager.getPeerId((update as Update.updateChatUserTyping).from_id);
- if(rootScope.myId === fromId || update.action._ === 'speakingInGroupCallAction') {
- return;
- }
-
- const peerId = update._ === 'updateUserTyping' ?
- fromId :
- -((update as Update.updateChatUserTyping).chat_id || (update as Update.updateChannelUserTyping).channel_id);
- const typings = this.typingsInPeer[peerId] ?? (this.typingsInPeer[peerId] = []);
- let typing = typings.find(t => t.userId === fromId);
-
- const cancelAction = () => {
- delete typing.timeout;
- //typings.findAndSplice(t => t === typing);
- const idx = typings.indexOf(typing);
- if(idx !== -1) {
- typings.splice(idx, 1);
- }
-
- rootScope.dispatchEvent('peer_typings', {peerId, typings});
-
- if(!typings.length) {
- delete this.typingsInPeer[peerId];
- }
- };
-
- if(typing && typing.timeout !== undefined) {
- clearTimeout(typing.timeout);
- }
-
- if(update.action._ === 'sendMessageCancelAction') {
- if(!typing) {
- return;
- }
-
- cancelAction();
- return;
- } else {
- if(!typing) {
- typing = {
- userId: fromId
- };
-
- typings.push(typing);
- }
-
- //console.log('updateChatUserTyping', update, typings);
-
- typing.action = update.action;
-
- if(!appUsersManager.hasUser(fromId)) {
- if(update._ === 'updateChatUserTyping') {
- if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) {
- appProfileManager.getChatFull(update.chat_id);
- }
- }
-
- //return;
- }
-
- appUsersManager.forceUserOnline(fromId);
-
- typing.timeout = window.setTimeout(cancelAction, 6000);
- rootScope.dispatchEvent('peer_typings', {peerId, typings});
- }
- };
-
- public getPeerTypings(peerId: number) {
- return this.typingsInPeer[peerId];
}
public saveApiChats(apiChats: any[], override?: boolean) {
@@ -486,27 +398,6 @@ export class AppChatsManager {
return 'g' + id;
}
- public getChatMembersString(id: number) {
- const chat = this.getChat(id);
- const chatFull = appProfileManager.chatsFull[id];
- let count: number;
- if(chatFull) {
- if(chatFull._ === 'channelFull') {
- count = chatFull.participants_count;
- } else {
- count = (chatFull.participants as ChatParticipants.chatParticipants).participants?.length;
- }
- } else {
- count = chat.participants_count || chat.participants?.participants.length;
- }
-
- const isChannel = this.isBroadcast(id);
- count = count || 1;
-
- let key: LangPackKey = isChannel ? 'Peer.Status.Subscribers' : 'Peer.Status.Member';
- return i18n(key, [numberThousandSplitter(count)]);
- }
-
/* public wrapForFull(id: number, fullChat: any) {
const chatFull = copy(fullChat);
const chat = this.getChat(id);
@@ -600,45 +491,6 @@ export class AppChatsManager {
});
}
- public async getOnlines(id: number): Promise {
- if(this.isMegagroup(id)) {
- const timestamp = Date.now() / 1000 | 0;
- const cached = this.megagroupOnlines[id] ?? (this.megagroupOnlines[id] = {timestamp: 0, onlines: 1});
- if((timestamp - cached.timestamp) < 60) {
- return cached.onlines;
- }
-
- const res = await apiManager.invokeApi('messages.getOnlines', {
- peer: this.getChannelInputPeer(id)
- });
-
- const onlines = res.onlines ?? 1;
- cached.timestamp = timestamp;
- cached.onlines = onlines;
-
- return onlines;
- } else if(this.isBroadcast(id)) {
- return 1;
- }
-
- const chatInfo = await appProfileManager.getChatFull(id);
- const _participants = (chatInfo as ChatFull.chatFull).participants as ChatParticipants.chatParticipants;
- if(_participants && _participants.participants) {
- const participants = _participants.participants;
-
- return participants.reduce((acc: number, participant) => {
- const user = appUsersManager.getUser(participant.user_id);
- if(user && user.status && user.status._ === 'userStatusOnline') {
- return acc + 1;
- }
-
- return acc;
- }, 0);
- } else {
- return 1;
- }
- }
-
private onChatUpdated = (chatId: number, updates: any) => {
//console.log('onChatUpdated', chatId, updates);
@@ -647,7 +499,7 @@ export class AppChatsManager {
/* updates.updates &&
updates.updates.length && */
this.isChannel(chatId)) {
- appProfileManager.invalidateChannelParticipants(chatId);
+ rootScope.dispatchEvent('invalidate_participants', chatId);
}
};
diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts
index 1789cce5..408d7eed 100644
--- a/src/lib/appManagers/appImManager.ts
+++ b/src/lib/appManagers/appImManager.ts
@@ -103,6 +103,7 @@ export class AppImManager {
constructor() {
apiUpdatesManager.attach();
+ appNotificationsManager.start();
this.log = logger('IM', LogTypes.Log | LogTypes.Warn | LogTypes.Debug | LogTypes.Error);
@@ -1035,7 +1036,7 @@ export class AppImManager {
public getPeerTyping(peerId: number, container?: HTMLElement) {
if(!appUsersManager.isBot(peerId)) {
- const typings = appChatsManager.getPeerTypings(peerId);
+ const typings = appProfileManager.getPeerTypings(peerId);
if(!typings || !typings.length) {
return;
}
@@ -1152,7 +1153,7 @@ export class AppImManager {
const participants_count = chatInfo.participants_count || (chatInfo.participants && chatInfo.participants.participants && chatInfo.participants.participants.length) || 1;
//if(participants_count) {
- subtitle = appChatsManager.getChatMembersString(-peerId);
+ subtitle = appProfileManager.getChatMembersString(-peerId);
if(participants_count < 2) return subtitle;
/* const onlines = await appChatsManager.getOnlines(chat.id);
diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts
index 531eadcd..1a4d1d63 100644
--- a/src/lib/appManagers/appMessagesManager.ts
+++ b/src/lib/appManagers/appMessagesManager.ts
@@ -53,6 +53,7 @@ import htmlToDocumentFragment from "../../helpers/dom/htmlToDocumentFragment";
import htmlToSpan from "../../helpers/dom/htmlToSpan";
import { REPLIES_PEER_ID } from "../mtproto/mtproto_config";
import formatCallDuration from "../../helpers/formatCallDuration";
+import appAvatarsManager from "./appAvatarsManager";
//console.trace('include');
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
@@ -304,8 +305,6 @@ export class AppMessagesManager {
this.maxSeenId = state.maxSeenMsgId;
}
});
-
- appNotificationsManager.start();
}
public construct() {
@@ -4546,7 +4545,7 @@ export class AppMessagesManager {
const peerPhoto = appPeersManager.getPeerPhoto(peerId);
if(peerPhoto) {
- appProfileManager.loadAvatar(peerId, peerPhoto, 'photo_small').loadPromise.then(url => {
+ appAvatarsManager.loadAvatar(peerId, peerPhoto, 'photo_small').loadPromise.then(url => {
if(message.pFlags.unread) {
notification.image = url;
appNotificationsManager.notify(notification);
diff --git a/src/lib/appManagers/appNotificationsManager.ts b/src/lib/appManagers/appNotificationsManager.ts
index 25f8fea2..d60f4bdf 100644
--- a/src/lib/appManagers/appNotificationsManager.ts
+++ b/src/lib/appManagers/appNotificationsManager.ts
@@ -706,7 +706,6 @@ export class AppNotificationsManager {
}
private registerDevice(tokenData: PushSubscriptionNotify) {
- return;
if(this.registeredDevice && deepEqual(this.registeredDevice, tokenData)) {
return false;
}
@@ -725,7 +724,6 @@ export class AppNotificationsManager {
}
private unregisterDevice(tokenData: PushSubscriptionNotify) {
- return;
if(!this.registeredDevice) {
return false;
}
diff --git a/src/lib/appManagers/appProfileManager.ts b/src/lib/appManagers/appProfileManager.ts
index 901e7fd7..25305f5a 100644
--- a/src/lib/appManagers/appProfileManager.ts
+++ b/src/lib/appManagers/appProfileManager.ts
@@ -11,10 +11,9 @@
import { MOUNT_CLASS_TO } from "../../config/debug";
import { tsNow } from "../../helpers/date";
-import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl";
-import replaceContent from "../../helpers/dom/replaceContent";
-import sequentialDom from "../../helpers/sequentialDom";
-import { ChannelParticipantsFilter, ChannelsChannelParticipants, Chat, ChatFull, ChatParticipants, ChatPhoto, ExportedChatInvite, InputChannel, InputFile, InputFileLocation, PhotoSize, Update, UserFull, UserProfilePhoto } from "../../layer";
+import { numberThousandSplitter } from "../../helpers/number";
+import { ChannelParticipantsFilter, ChannelsChannelParticipants, Chat, ChatFull, ChatParticipants, ChatPhoto, ExportedChatInvite, InputChannel, InputFile, InputFileLocation, PhotoSize, SendMessageAction, Update, UserFull, UserProfilePhoto } from "../../layer";
+import { LangPackKey, i18n } from "../langPack";
//import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker';
import { RichTextProcessor } from "../richtextprocessor";
@@ -22,13 +21,12 @@ import rootScope from "../rootScope";
import SearchIndex from "../searchIndex";
import apiUpdatesManager from "./apiUpdatesManager";
import appChatsManager from "./appChatsManager";
-import appDownloadManager from "./appDownloadManager";
import appNotificationsManager from "./appNotificationsManager";
import appPeersManager from "./appPeersManager";
-import appPhotosManager, { MyPhoto } from "./appPhotosManager";
+import appPhotosManager from "./appPhotosManager";
import appUsersManager, { User } from "./appUsersManager";
-type PeerPhotoSize = 'photo_small' | 'photo_big';
+export type UserTyping = Partial<{userId: number, action: SendMessageAction, timeout: number}>;
export class AppProfileManager {
//private botInfos: any = {};
@@ -36,11 +34,9 @@ export class AppProfileManager {
public chatsFull: {[id: string]: ChatFull} = {};
private fullPromises: {[peerId: string]: Promise} = {};
- private savedAvatarURLs: {
- [peerId: number]: {
- [size in PeerPhotoSize]?: string | Promise
- }
- } = {};
+ private megagroupOnlines: {[id: number]: {timestamp: number, onlines: number}};
+
+ private typingsInPeer: {[peerId: number]: UserTyping[]};
constructor() {
rootScope.addMultipleEventsListeners({
@@ -93,7 +89,11 @@ export class AppProfileManager {
}
}
}
- }
+ },
+
+ updateUserTyping: this.onUpdateUserTyping,
+ updateChatUserTyping: this.onUpdateUserTyping,
+ updateChannelUserTyping: this.onUpdateUserTyping
});
rootScope.addEventListener('chat_update', (chatId) => {
@@ -121,6 +121,13 @@ export class AppProfileManager {
rootScope.dispatchEvent('chat_full_update', chatId);
}
});
+
+ rootScope.addEventListener('invalidate_participants', chatId => {
+ this.invalidateChannelParticipants(chatId);
+ });
+
+ this.megagroupOnlines = {};
+ this.typingsInPeer = {};
}
/* public saveBotInfo(botInfo: any) {
@@ -457,177 +464,144 @@ export class AppProfileManager {
});
}
- public removeFromAvatarsCache(peerId: number) {
- if(this.savedAvatarURLs[peerId]) {
- delete this.savedAvatarURLs[peerId];
+ public getChatMembersString(id: number) {
+ const chat = appChatsManager.getChat(id);
+ const chatFull = this.chatsFull[id];
+ let count: number;
+ if(chatFull) {
+ if(chatFull._ === 'channelFull') {
+ count = chatFull.participants_count;
+ } else {
+ count = (chatFull.participants as ChatParticipants.chatParticipants).participants?.length;
+ }
+ } else {
+ count = chat.participants_count || chat.participants?.participants.length;
}
- }
- public loadAvatar(peerId: number, photo: UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto, size: PeerPhotoSize) {
- const inputPeer = appPeersManager.getInputPeerById(peerId);
+ const isChannel = appChatsManager.isBroadcast(id);
+ count = count || 1;
+
+ let key: LangPackKey = isChannel ? 'Peer.Status.Subscribers' : 'Peer.Status.Member';
+ return i18n(key, [numberThousandSplitter(count)]);
+ }
- let cached = false;
- let getAvatarPromise: Promise;
- let saved = this.savedAvatarURLs[peerId];
- if(!saved || !saved[size]) {
- if(!saved) {
- saved = this.savedAvatarURLs[peerId] = {};
+ public async getOnlines(id: number): Promise {
+ if(appChatsManager.isMegagroup(id)) {
+ const timestamp = Date.now() / 1000 | 0;
+ const cached = this.megagroupOnlines[id] ?? (this.megagroupOnlines[id] = {timestamp: 0, onlines: 1});
+ if((timestamp - cached.timestamp) < 60) {
+ return cached.onlines;
}
- //console.warn('will invoke downloadSmallFile:', peerId);
- const peerPhotoFileLocation: InputFileLocation.inputPeerPhotoFileLocation = {
- _: 'inputPeerPhotoFileLocation',
- pFlags: {},
- peer: inputPeer,
- photo_id: photo.photo_id
- };
+ const res = await apiManager.invokeApi('messages.getOnlines', {
+ peer: appChatsManager.getChannelInputPeer(id)
+ });
- if(size === 'photo_big') {
- peerPhotoFileLocation.pFlags.big = true;
- }
+ const onlines = res.onlines ?? 1;
+ cached.timestamp = timestamp;
+ cached.onlines = onlines;
- const downloadOptions = {dcId: photo.dc_id, location: peerPhotoFileLocation};
+ return onlines;
+ } else if(appChatsManager.isBroadcast(id)) {
+ return 1;
+ }
- /* let str: string;
- const time = Date.now();
- if(peerId === 0) {
- str = `download avatar ${peerId}`;
- } */
+ const chatInfo = await this.getChatFull(id);
+ const _participants = (chatInfo as ChatFull.chatFull).participants as ChatParticipants.chatParticipants;
+ if(_participants && _participants.participants) {
+ const participants = _participants.participants;
- const promise = appDownloadManager.download(downloadOptions);
- getAvatarPromise = saved[size] = promise.then(blob => {
- return saved[size] = URL.createObjectURL(blob);
+ return participants.reduce((acc: number, participant) => {
+ const user = appUsersManager.getUser(participant.user_id);
+ if(user && user.status && user.status._ === 'userStatusOnline') {
+ return acc + 1;
+ }
- /* if(str) {
- console.log(str, Date.now() / 1000, Date.now() - time);
- } */
- });
- } else if(typeof(saved[size]) !== 'string') {
- getAvatarPromise = saved[size] as Promise;
+ return acc;
+ }, 0);
} else {
- getAvatarPromise = Promise.resolve(saved[size]);
- cached = true;
+ return 1;
}
-
- return {cached, loadPromise: getAvatarPromise};
}
- public putAvatar(div: HTMLElement, peerId: number, photo: UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto, size: PeerPhotoSize, img = new Image(), onlyThumb = false) {
- let {cached, loadPromise} = this.loadAvatar(peerId, photo, size);
-
- let renderThumbPromise: Promise;
- let callback: () => void;
- if(cached) {
- // смотри в misc.ts: renderImageFromUrl
- callback = () => {
- replaceContent(div, img);
- div.dataset.color = '';
- };
- } else {
- const animate = rootScope.settings.animationsEnabled;
- if(animate) {
- img.classList.add('fade-in');
+ private onUpdateUserTyping = (update: Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChannelUserTyping) => {
+ const fromId = (update as Update.updateUserTyping).user_id || appPeersManager.getPeerId((update as Update.updateChatUserTyping).from_id);
+ if(rootScope.myId === fromId || update.action._ === 'speakingInGroupCallAction') {
+ return;
+ }
+
+ const peerId = update._ === 'updateUserTyping' ?
+ fromId :
+ -((update as Update.updateChatUserTyping).chat_id || (update as Update.updateChannelUserTyping).channel_id);
+ const typings = this.typingsInPeer[peerId] ?? (this.typingsInPeer[peerId] = []);
+ let typing = typings.find(t => t.userId === fromId);
+
+ const cancelAction = () => {
+ delete typing.timeout;
+ //typings.findAndSplice(t => t === typing);
+ const idx = typings.indexOf(typing);
+ if(idx !== -1) {
+ typings.splice(idx, 1);
}
- let thumbImage: HTMLImageElement;
- if(photo.stripped_thumb) {
- thumbImage = new Image();
- div.classList.add('avatar-relative');
- thumbImage.classList.add('avatar-photo', 'avatar-photo-thumbnail');
- img.classList.add('avatar-photo');
- const url = appPhotosManager.getPreviewURLFromBytes(photo.stripped_thumb);
- renderThumbPromise = renderImageFromUrl(thumbImage, url).then(() => {
- replaceContent(div, thumbImage);
- });
- }
+ rootScope.dispatchEvent('peer_typings', {peerId, typings});
- callback = () => {
- if(photo.stripped_thumb) {
- div.append(img);
- } else {
- replaceContent(div, img);
- }
+ if(!typings.length) {
+ delete this.typingsInPeer[peerId];
+ }
+ };
- setTimeout(() => {
- if(div.childElementCount) {
- sequentialDom.mutateElement(img, () => {
- div.dataset.color = '';
-
- if(animate) {
- img.classList.remove('fade-in');
- }
-
- if(thumbImage) {
- thumbImage.remove();
- }
- });
- }
- }, animate ? 200 : 0);
- };
+ if(typing && typing.timeout !== undefined) {
+ clearTimeout(typing.timeout);
}
- const renderPromise = loadPromise
- .then((url) => renderImageFromUrl(img, url/* , false */))
- .then(() => callback());
-
- return {cached, loadPromise: renderThumbPromise || renderPromise};
- }
-
- // peerId === peerId || title
- public putPhoto(div: HTMLElement, peerId: number, isDialog = false, title = '', onlyThumb = false) {
- const photo = appPeersManager.getPeerPhoto(peerId);
+ if(update.action._ === 'sendMessageCancelAction') {
+ if(!typing) {
+ return;
+ }
- const size: PeerPhotoSize = 'photo_small';
- const avatarAvailable = !!photo;
- const avatarRendered = div.firstElementChild && !(div.firstElementChild as HTMLElement).classList.contains('emoji');
-
- const myId = rootScope.myId;
-
- //console.log('loadDialogPhoto location:', location, inputPeer);
- if(peerId === myId && isDialog) {
- div.innerText = '';
- div.dataset.color = '';
- div.classList.add('tgico-saved');
- div.classList.remove('tgico-deletedaccount');
+ cancelAction();
return;
}
- if(peerId > 0) {
- const user = appUsersManager.getUser(peerId);
- if(user && user.pFlags && user.pFlags.deleted) {
- div.innerText = '';
- div.dataset.color = appPeersManager.getPeerColorById(peerId);
- div.classList.add('tgico-deletedaccount');
- div.classList.remove('tgico-saved');
- return;
- }
+ if(!typing) {
+ typing = {
+ userId: fromId
+ };
+
+ typings.push(typing);
}
- if(!avatarAvailable || !avatarRendered || !this.savedAvatarURLs[peerId]) {
- let color = '';
- if(peerId && (peerId !== myId || !isDialog)) {
- color = appPeersManager.getPeerColorById(peerId);
+ //console.log('updateChatUserTyping', update, typings);
+
+ typing.action = update.action;
+
+ const hasUser = appUsersManager.hasUser(fromId);
+ if(!hasUser) {
+ // let's load user here
+ if(update._ === 'updateChatUserTyping') {
+ if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) {
+ appProfileManager.getChatFull(update.chat_id).then(() => {
+ if(typing.timeout !== undefined && appUsersManager.hasUser(fromId)) {
+ rootScope.dispatchEvent('peer_typings', {peerId, typings});
+ }
+ });
+ }
}
- div.innerText = '';
- div.classList.remove('tgico-saved', 'tgico-deletedaccount');
- div.dataset.color = color;
-
- let abbr: string;
- if(!title) {
- const peer = appPeersManager.getPeer(peerId);
- abbr = peer.initials ?? '';
- } else {
- abbr = RichTextProcessor.getAbbreviation(title);
- }
-
- div.innerHTML = abbr;
- //return Promise.resolve(true);
+ //return;
+ } else {
+ appUsersManager.forceUserOnline(fromId);
}
- if(avatarAvailable/* && false */) {
- return this.putAvatar(div, peerId, photo, size, undefined, onlyThumb);
+ typing.timeout = window.setTimeout(cancelAction, 6000);
+ if(hasUser) {
+ rootScope.dispatchEvent('peer_typings', {peerId, typings});
}
+ };
+
+ public getPeerTypings(peerId: number) {
+ return this.typingsInPeer[peerId];
}
}
diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts
index 61584d71..2af3c13b 100644
--- a/src/lib/rootScope.ts
+++ b/src/lib/rootScope.ts
@@ -10,7 +10,7 @@ import type { AppMessagesManager, Dialog, MessagesStorage } from "./appManagers/
import type { Poll, PollResults } from "./appManagers/appPollsManager";
import type { MyDialogFilter } from "./storages/filters";
import type { ConnectionStatusChange } from "../types";
-import type { UserTyping } from "./appManagers/appChatsManager";
+import type { UserTyping } from "./appManagers/appProfileManager";
import type Chat from "../components/chat/chat";
import type { UserAuth } from "./mtproto/mtproto_config";
import type { State, Theme } from "./appManagers/appStateManager";
@@ -86,6 +86,7 @@ export type BroadcastEvents = {
'chat_full_update': number,
'poll_update': {poll: Poll, results: PollResults},
'chat_update': number,
+ 'invalidate_participants': number,
//'channel_settings': {channelId: number},
'webpage_updated': {id: string, msgs: number[]},
diff --git a/src/pages/pageAuthCode.ts b/src/pages/pageAuthCode.ts
index b500946c..241ff40c 100644
--- a/src/pages/pageAuthCode.ts
+++ b/src/pages/pageAuthCode.ts
@@ -9,10 +9,7 @@ import { AuthSentCode, AuthSentCodeType, AuthSignIn } from '../layer';
import appStateManager from '../lib/appManagers/appStateManager';
import apiManager from '../lib/mtproto/mtprotoworker';
import Page from './page';
-import pageIm from './pageIm';
-import pagePassword from './pagePassword';
import pageSignIn from './pageSignIn';
-import pageSignUp from './pageSignUp';
import TrackingMonkey from '../components/monkeys/tracking';
import CodeInputField from '../components/codeInputField';
import { i18n, LangPackKey } from '../lib/langPack';
@@ -72,15 +69,19 @@ let onFirstMount = (): Promise => {
case 'auth.authorization':
apiManager.setUserAuth(response.user.id);
- pageIm.mount();
+ import('./pageIm').then(m => {
+ m.default.mount();
+ });
cleanup();
break;
case 'auth.authorizationSignUpRequired':
//console.log('Registration needed!');
- pageSignUp.mount({
- 'phone_number': authCode.phone_number,
- 'phone_code_hash': authCode.phone_code_hash
+ import('./pageSignUp').then(m => {
+ m.default.mount({
+ 'phone_number': authCode.phone_number,
+ 'phone_code_hash': authCode.phone_code_hash
+ });
});
cleanup();
@@ -96,7 +97,7 @@ let onFirstMount = (): Promise => {
//console.warn('pageAuthCode: SESSION_PASSWORD_NEEDED');
good = true;
err.handled = true;
- await pagePassword.mount();
+ await (await import('./pagePassword')).default.mount(); // lol
setTimeout(() => {
codeInput.value = '';
}, 300);
diff --git a/src/pages/pagePassword.ts b/src/pages/pagePassword.ts
index 3a15163d..94294d4a 100644
--- a/src/pages/pagePassword.ts
+++ b/src/pages/pagePassword.ts
@@ -10,7 +10,6 @@ import { AccountPassword } from '../layer';
import appStateManager from '../lib/appManagers/appStateManager';
import passwordManager from '../lib/mtproto/passwordManager';
import Page from './page';
-import pageIm from './pageIm';
import Button from '../components/button';
import PasswordInputField from '../components/passwordInputField';
import PasswordMonkey from '../components/monkeys/password';
@@ -91,7 +90,9 @@ let onFirstMount = (): Promise => {
switch(response._) {
case 'auth.authorization':
clearInterval(getStateInterval);
- pageIm.mount();
+ import('./pageIm').then(m => {
+ m.default.mount();
+ });
if(monkey) monkey.remove();
break;
default:
diff --git a/src/pages/pageSignUp.ts b/src/pages/pageSignUp.ts
index 83e87bb5..48929b9d 100644
--- a/src/pages/pageSignUp.ts
+++ b/src/pages/pageSignUp.ts
@@ -18,7 +18,6 @@ import apiManager from '../lib/mtproto/mtprotoworker';
import RichTextProcessor from '../lib/richtextprocessor';
import LoginPage from './loginPage';
import Page from './page';
-import pageIm from './pageIm';
import blurActiveElement from '../helpers/dom/blurActiveElement';
import replaceContent from '../helpers/dom/replaceContent';
@@ -135,7 +134,9 @@ const onFirstMount = () => import('../lib/appManagers/appProfileManager').then(i
apiManager.setUserAuth(response.user.id);
sendAvatar().finally(() => {
- pageIm.mount();
+ import('./pageIm').then(m => {
+ m.default.mount();
+ });
});
break;