Avatar: Display more than 40 profile picture (#4780)
This commit is contained in:
parent
8ced8b3b05
commit
43244b6e61
@ -24,7 +24,7 @@ import type {
|
|||||||
import { omitUndefined, pick, pickTruthy } from '../../../util/iteratees';
|
import { omitUndefined, pick, pickTruthy } from '../../../util/iteratees';
|
||||||
import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
|
import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
|
||||||
import { serializeBytes } from '../helpers';
|
import { serializeBytes } from '../helpers';
|
||||||
import { buildApiUsernames } from './common';
|
import { buildApiUsernames, buildAvatarPhotoId } from './common';
|
||||||
import { omitVirtualClassFields } from './helpers';
|
import { omitVirtualClassFields } from './helpers';
|
||||||
import {
|
import {
|
||||||
buildApiEmojiStatus,
|
buildApiEmojiStatus,
|
||||||
@ -50,7 +50,7 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
const accessHash = ('accessHash' in peerEntity) ? String(peerEntity.accessHash) : undefined;
|
const accessHash = ('accessHash' in peerEntity) ? String(peerEntity.accessHash) : undefined;
|
||||||
const hasVideoAvatar = 'photo' in peerEntity && peerEntity.photo && 'hasVideo' in peerEntity.photo
|
const hasVideoAvatar = 'photo' in peerEntity && peerEntity.photo && 'hasVideo' in peerEntity.photo
|
||||||
&& peerEntity.photo.hasVideo;
|
&& peerEntity.photo.hasVideo;
|
||||||
const avatarHash = ('photo' in peerEntity) && peerEntity.photo ? buildAvatarHash(peerEntity.photo) : undefined;
|
const avatarPhotoId = ('photo' in peerEntity) && peerEntity.photo ? buildAvatarPhotoId(peerEntity.photo) : undefined;
|
||||||
const isSignaturesShown = Boolean('signatures' in peerEntity && peerEntity.signatures);
|
const isSignaturesShown = Boolean('signatures' in peerEntity && peerEntity.signatures);
|
||||||
const hasPrivateLink = Boolean('hasLink' in peerEntity && peerEntity.hasLink);
|
const hasPrivateLink = Boolean('hasLink' in peerEntity && peerEntity.hasLink);
|
||||||
const isScam = Boolean('scam' in peerEntity && peerEntity.scam);
|
const isScam = Boolean('scam' in peerEntity && peerEntity.scam);
|
||||||
@ -74,7 +74,7 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
usernames,
|
usernames,
|
||||||
accessHash,
|
accessHash,
|
||||||
hasVideoAvatar,
|
hasVideoAvatar,
|
||||||
avatarHash,
|
avatarPhotoId,
|
||||||
...('verified' in peerEntity && { isVerified: peerEntity.verified }),
|
...('verified' in peerEntity && { isVerified: peerEntity.verified }),
|
||||||
...('callActive' in peerEntity && { isCallActive: peerEntity.callActive }),
|
...('callActive' in peerEntity && { isCallActive: peerEntity.callActive }),
|
||||||
...('callNotEmpty' in peerEntity && { isCallNotEmpty: peerEntity.callNotEmpty }),
|
...('callNotEmpty' in peerEntity && { isCallNotEmpty: peerEntity.callNotEmpty }),
|
||||||
@ -308,14 +308,6 @@ function getUserName(user: GramJs.User) {
|
|||||||
: (user.lastName || '');
|
: (user.lastName || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildAvatarHash(photo: GramJs.TypeUserProfilePhoto | GramJs.TypeChatPhoto) {
|
|
||||||
if ('photoId' in photo) {
|
|
||||||
return String(photo.photoId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildChatMember(
|
export function buildChatMember(
|
||||||
member: GramJs.TypeChatParticipant | GramJs.TypeChannelParticipant,
|
member: GramJs.TypeChatParticipant | GramJs.TypeChannelParticipant,
|
||||||
): ApiChatMember | undefined {
|
): ApiChatMember | undefined {
|
||||||
|
|||||||
@ -277,3 +277,11 @@ export function buildApiMessageEntity(entity: GramJs.TypeMessageEntity): ApiMess
|
|||||||
length,
|
length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildAvatarPhotoId(photo: GramJs.TypeUserProfilePhoto | GramJs.TypeChatPhoto) {
|
||||||
|
if ('photoId' in photo) {
|
||||||
|
return photo.photoId.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|||||||
@ -14,8 +14,7 @@ import type {
|
|||||||
StatisticsStoryInteractionCounter,
|
StatisticsStoryInteractionCounter,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { buildAvatarHash } from './chats';
|
import { buildApiUsernames, buildAvatarPhotoId } from './common';
|
||||||
import { buildApiUsernames } from './common';
|
|
||||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||||
|
|
||||||
export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics {
|
export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics {
|
||||||
@ -233,6 +232,8 @@ function getOverviewPeriod(data: GramJs.StatsDateRangeDays): StatisticsOverviewP
|
|||||||
function buildApiMessagePublicForward(message: GramJs.TypeMessage, chats: GramJs.TypeChat[]): ApiMessagePublicForward {
|
function buildApiMessagePublicForward(message: GramJs.TypeMessage, chats: GramJs.TypeChat[]): ApiMessagePublicForward {
|
||||||
const peerId = getApiChatIdFromMtpPeer(message.peerId!);
|
const peerId = getApiChatIdFromMtpPeer(message.peerId!);
|
||||||
const channel = chats.find((c) => buildApiPeerId(c.id, 'channel') === peerId);
|
const channel = chats.find((c) => buildApiPeerId(c.id, 'channel') === peerId);
|
||||||
|
const channelProfilePhoto = channel && 'photo' in channel && channel.photo instanceof GramJs.Photo
|
||||||
|
? channel.photo : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
@ -243,9 +244,8 @@ function buildApiMessagePublicForward(message: GramJs.TypeMessage, chats: GramJs
|
|||||||
type: 'chatTypeChannel',
|
type: 'chatTypeChannel',
|
||||||
title: (channel as GramJs.Channel).title,
|
title: (channel as GramJs.Channel).title,
|
||||||
usernames: buildApiUsernames(channel as GramJs.Channel),
|
usernames: buildApiUsernames(channel as GramJs.Channel),
|
||||||
avatarHash: channel && 'photo' in channel
|
avatarPhotoId: channelProfilePhoto && buildAvatarPhotoId(channelProfilePhoto),
|
||||||
? buildAvatarHash((channel as GramJs.Channel).photo)
|
hasVideoAvatar: Boolean(channelProfilePhoto?.videoSizes),
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import type {
|
|||||||
|
|
||||||
import { buildApiBotInfo } from './bots';
|
import { buildApiBotInfo } from './bots';
|
||||||
import { buildApiBusinessIntro, buildApiBusinessLocation, buildApiBusinessWorkHours } from './business';
|
import { buildApiBusinessIntro, buildApiBusinessLocation, buildApiBusinessWorkHours } from './business';
|
||||||
import { buildApiPhoto, buildApiUsernames } from './common';
|
import { buildApiPhoto, buildApiUsernames, buildAvatarPhotoId } from './common';
|
||||||
import { omitVirtualClassFields } from './helpers';
|
import { omitVirtualClassFields } from './helpers';
|
||||||
import { buildApiEmojiStatus, buildApiPeerColor, buildApiPeerId } from './peers';
|
import { buildApiEmojiStatus, buildApiPeerColor, buildApiPeerId } from './peers';
|
||||||
|
|
||||||
@ -61,12 +61,8 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
const {
|
const {
|
||||||
id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId,
|
id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId,
|
||||||
} = mtpUser;
|
} = mtpUser;
|
||||||
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto
|
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined;
|
||||||
? Boolean(mtpUser.photo.hasVideo)
|
const avatarPhotoId = mtpUser.photo && buildAvatarPhotoId(mtpUser.photo);
|
||||||
: undefined;
|
|
||||||
const avatarHash = mtpUser.photo instanceof GramJs.UserProfilePhoto
|
|
||||||
? String(mtpUser.photo.photoId)
|
|
||||||
: undefined;
|
|
||||||
const userType = buildApiUserType(mtpUser);
|
const userType = buildApiUserType(mtpUser);
|
||||||
const usernames = buildApiUsernames(mtpUser);
|
const usernames = buildApiUsernames(mtpUser);
|
||||||
const emojiStatus = mtpUser.emojiStatus ? buildApiEmojiStatus(mtpUser.emojiStatus) : undefined;
|
const emojiStatus = mtpUser.emojiStatus ? buildApiEmojiStatus(mtpUser.emojiStatus) : undefined;
|
||||||
@ -90,7 +86,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
phoneNumber: mtpUser.phone || '',
|
phoneNumber: mtpUser.phone || '',
|
||||||
noStatus: !mtpUser.status,
|
noStatus: !mtpUser.status,
|
||||||
...(mtpUser.accessHash && { accessHash: String(mtpUser.accessHash) }),
|
...(mtpUser.accessHash && { accessHash: String(mtpUser.accessHash) }),
|
||||||
...(avatarHash && { avatarHash }),
|
avatarPhotoId,
|
||||||
emojiStatus,
|
emojiStatus,
|
||||||
hasVideoAvatar,
|
hasVideoAvatar,
|
||||||
areStoriesHidden: Boolean(mtpUser.storiesHidden),
|
areStoriesHidden: Boolean(mtpUser.storiesHidden),
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import type {
|
|||||||
ApiUser, OnApiUpdate,
|
ApiUser, OnApiUpdate,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { COMMON_CHATS_LIMIT, PROFILE_PHOTOS_LIMIT } from '../../../config';
|
import { COMMON_CHATS_LIMIT } from '../../../config';
|
||||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import { buildApiPhoto } from '../apiBuilders/common';
|
import { buildApiPhoto } from '../apiBuilders/common';
|
||||||
import { buildApiPeerId } from '../apiBuilders/peers';
|
import { buildApiPeerId } from '../apiBuilders/peers';
|
||||||
@ -85,10 +85,7 @@ export async function fetchFullUser({
|
|||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateUser',
|
'@type': 'updateUser',
|
||||||
id,
|
id,
|
||||||
user: {
|
user,
|
||||||
...user,
|
|
||||||
avatarHash: user?.avatarHash || undefined,
|
|
||||||
},
|
|
||||||
fullInfo,
|
fullInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -255,14 +252,24 @@ export async function deleteContact({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchProfilePhotos(user?: ApiUser, chat?: ApiChat) {
|
export async function fetchProfilePhotos({
|
||||||
|
peer,
|
||||||
|
offset = 0,
|
||||||
|
limit = 0,
|
||||||
|
}: {
|
||||||
|
peer: ApiPeer;
|
||||||
|
offset?: number;
|
||||||
|
limit?: number;
|
||||||
|
}) {
|
||||||
|
const chat = 'title' in peer ? peer as ApiChat : undefined;
|
||||||
|
const user = !chat ? peer as ApiUser : undefined;
|
||||||
if (user) {
|
if (user) {
|
||||||
const { id, accessHash } = user;
|
const { id, accessHash } = user;
|
||||||
|
|
||||||
const result = await invokeRequest(new GramJs.photos.GetUserPhotos({
|
const result = await invokeRequest(new GramJs.photos.GetUserPhotos({
|
||||||
userId: buildInputEntity(id, accessHash) as GramJs.InputUser,
|
userId: buildInputEntity(id, accessHash) as GramJs.InputUser,
|
||||||
limit: PROFILE_PHOTOS_LIMIT,
|
limit,
|
||||||
offset: 0,
|
offset,
|
||||||
maxId: BigInt('0'),
|
maxId: BigInt('0'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -272,11 +279,17 @@ export async function fetchProfilePhotos(user?: ApiUser, chat?: ApiChat) {
|
|||||||
|
|
||||||
updateLocalDb(result);
|
updateLocalDb(result);
|
||||||
|
|
||||||
|
const count = result instanceof GramJs.photos.PhotosSlice ? result.count : result.photos.length;
|
||||||
|
const proposedNextOffsetId = offset + result.photos.length;
|
||||||
|
const nextOffsetId = proposedNextOffsetId < count ? proposedNextOffsetId : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
count,
|
||||||
photos: result.photos
|
photos: result.photos
|
||||||
.filter((photo): photo is GramJs.Photo => photo instanceof GramJs.Photo)
|
.filter((photo): photo is GramJs.Photo => photo instanceof GramJs.Photo)
|
||||||
.map((photo) => buildApiPhoto(photo)),
|
.map((photo) => buildApiPhoto(photo)),
|
||||||
users: result.users.map(buildApiUser).filter(Boolean),
|
users: result.users.map(buildApiUser).filter(Boolean),
|
||||||
|
nextOffsetId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,18 +298,22 @@ export async function fetchProfilePhotos(user?: ApiUser, chat?: ApiChat) {
|
|||||||
const result = await searchMessagesLocal({
|
const result = await searchMessagesLocal({
|
||||||
chat: chat!,
|
chat: chat!,
|
||||||
type: 'profilePhoto',
|
type: 'profilePhoto',
|
||||||
limit: PROFILE_PHOTOS_LIMIT,
|
limit,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { messages, users } = result;
|
const {
|
||||||
|
messages, users, totalCount, nextOffsetId,
|
||||||
|
} = result;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
count: totalCount,
|
||||||
photos: messages.map((message) => message.content.action!.photo).filter(Boolean),
|
photos: messages.map((message) => message.content.action!.photo).filter(Boolean),
|
||||||
users,
|
users,
|
||||||
|
nextOffsetId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,12 +20,13 @@ import {
|
|||||||
buildApiChatFolder,
|
buildApiChatFolder,
|
||||||
buildApiChatFromPreview,
|
buildApiChatFromPreview,
|
||||||
buildApiChatSettings,
|
buildApiChatSettings,
|
||||||
buildAvatarHash,
|
|
||||||
buildChatMember,
|
buildChatMember,
|
||||||
buildChatMembers,
|
buildChatMembers,
|
||||||
buildChatTypingStatus,
|
buildChatTypingStatus,
|
||||||
} from '../apiBuilders/chats';
|
} from '../apiBuilders/chats';
|
||||||
import { buildApiPhoto, buildApiUsernames, buildPrivacyRules } from '../apiBuilders/common';
|
import {
|
||||||
|
buildApiPhoto, buildApiUsernames, buildPrivacyRules,
|
||||||
|
} from '../apiBuilders/common';
|
||||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||||
import {
|
import {
|
||||||
buildApiMessageExtendedMediaPreview,
|
buildApiMessageExtendedMediaPreview,
|
||||||
@ -244,8 +245,10 @@ export function updater(update: Update) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (action instanceof GramJs.MessageActionChatEditPhoto) {
|
} else if (action instanceof GramJs.MessageActionChatEditPhoto) {
|
||||||
|
const apiPhoto = action.photo instanceof GramJs.Photo && buildApiPhoto(action.photo);
|
||||||
|
if (!apiPhoto) return;
|
||||||
|
|
||||||
const photo = buildChatPhotoForLocalDb(action.photo);
|
const photo = buildChatPhotoForLocalDb(action.photo);
|
||||||
const avatarHash = buildAvatarHash(photo);
|
|
||||||
|
|
||||||
const localDbChatId = resolveMessageApiChatId(update.message)!;
|
const localDbChatId = resolveMessageApiChatId(update.message)!;
|
||||||
if (localDb.chats[localDbChatId]) {
|
if (localDb.chats[localDbChatId]) {
|
||||||
@ -253,16 +256,11 @@ export function updater(update: Update) {
|
|||||||
}
|
}
|
||||||
addPhotoToLocalDb(action.photo);
|
addPhotoToLocalDb(action.photo);
|
||||||
|
|
||||||
if (avatarHash) {
|
onUpdate({
|
||||||
onUpdate({
|
'@type': 'updateNewProfilePhoto',
|
||||||
'@type': 'updateChat',
|
peerId: message.chatId,
|
||||||
id: message.chatId,
|
photo: apiPhoto,
|
||||||
chat: {
|
});
|
||||||
avatarHash,
|
|
||||||
},
|
|
||||||
...(action.photo instanceof GramJs.Photo && { newProfilePhoto: buildApiPhoto(action.photo) }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (action instanceof GramJs.MessageActionChatDeletePhoto) {
|
} else if (action instanceof GramJs.MessageActionChatDeletePhoto) {
|
||||||
const localDbChatId = resolveMessageApiChatId(update.message)!;
|
const localDbChatId = resolveMessageApiChatId(update.message)!;
|
||||||
if (localDb.chats[localDbChatId]) {
|
if (localDb.chats[localDbChatId]) {
|
||||||
@ -270,9 +268,8 @@ export function updater(update: Update) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateChat',
|
'@type': 'updateDeleteProfilePhoto',
|
||||||
id: message.chatId,
|
peerId: message.chatId,
|
||||||
chat: { avatarHash: undefined },
|
|
||||||
});
|
});
|
||||||
} else if (action instanceof GramJs.MessageActionChatDeleteUser) {
|
} else if (action instanceof GramJs.MessageActionChatDeleteUser) {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { ApiBotCommand } from './bots';
|
|||||||
import type {
|
import type {
|
||||||
ApiChatReactions, ApiFormattedText, ApiPhoto, ApiStickerSet,
|
ApiChatReactions, ApiFormattedText, ApiPhoto, ApiStickerSet,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
import type { ApiChatInviteImporter } from './misc';
|
import type { ApiChatInviteImporter, ApiPeerPhotos } from './misc';
|
||||||
import type {
|
import type {
|
||||||
ApiEmojiStatus, ApiFakeType, ApiUser, ApiUsername,
|
ApiEmojiStatus, ApiFakeType, ApiUser, ApiUsername,
|
||||||
} from './users';
|
} from './users';
|
||||||
@ -35,12 +35,12 @@ export interface ApiChat {
|
|||||||
accessHash?: string;
|
accessHash?: string;
|
||||||
isMin?: boolean;
|
isMin?: boolean;
|
||||||
hasVideoAvatar?: boolean;
|
hasVideoAvatar?: boolean;
|
||||||
avatarHash?: string;
|
avatarPhotoId?: string;
|
||||||
usernames?: ApiUsername[];
|
usernames?: ApiUsername[];
|
||||||
membersCount?: number;
|
membersCount?: number;
|
||||||
creationDate?: number;
|
creationDate?: number;
|
||||||
isSupport?: true;
|
isSupport?: true;
|
||||||
photos?: ApiPhoto[];
|
profilePhotos?: ApiPeerPhotos;
|
||||||
draftDate?: number;
|
draftDate?: number;
|
||||||
isProtected?: boolean;
|
isProtected?: boolean;
|
||||||
fakeType?: ApiFakeType;
|
fakeType?: ApiFakeType;
|
||||||
|
|||||||
@ -277,3 +277,12 @@ export interface ApiCollectionInfo {
|
|||||||
purchaseDate: number;
|
purchaseDate: number;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiPeerPhotos {
|
||||||
|
fallbackPhoto?: ApiPhoto;
|
||||||
|
personalPhoto?: ApiPhoto;
|
||||||
|
photos: ApiPhoto[];
|
||||||
|
count: number;
|
||||||
|
nextOffset?: number;
|
||||||
|
isLoading?: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@ -100,7 +100,6 @@ export type ApiUpdateChat = {
|
|||||||
'@type': 'updateChat';
|
'@type': 'updateChat';
|
||||||
id: string;
|
id: string;
|
||||||
chat: Partial<ApiChat>;
|
chat: Partial<ApiChat>;
|
||||||
newProfilePhoto?: ApiPhoto;
|
|
||||||
noTopChatsRequest?: boolean;
|
noTopChatsRequest?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -742,6 +741,18 @@ export type ApiUpdateStarsBalance = {
|
|||||||
balance: number;
|
balance: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiUpdateDeleteProfilePhoto = {
|
||||||
|
'@type': 'updateDeleteProfilePhoto';
|
||||||
|
peerId: string;
|
||||||
|
photoId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiUpdateNewProfilePhoto = {
|
||||||
|
'@type': 'updateNewProfilePhoto';
|
||||||
|
peerId: string;
|
||||||
|
photo: ApiPhoto;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiUpdate = (
|
export type ApiUpdate = (
|
||||||
ApiUpdateReady | ApiUpdateSession | ApiUpdateWebAuthTokenFailed | ApiUpdateRequestUserUpdate |
|
ApiUpdateReady | ApiUpdateSession | ApiUpdateWebAuthTokenFailed | ApiUpdateRequestUserUpdate |
|
||||||
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
|
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
|
||||||
@ -773,7 +784,8 @@ export type ApiUpdate = (
|
|||||||
ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization | ApiUpdateGroupInvitePrivacyForbidden |
|
ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization | ApiUpdateGroupInvitePrivacyForbidden |
|
||||||
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage |
|
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage |
|
||||||
ApiUpdateDeleteSavedHistory | ApiUpdatePremiumFloodWait | ApiUpdateStarsBalance |
|
ApiUpdateDeleteSavedHistory | ApiUpdatePremiumFloodWait | ApiUpdateStarsBalance |
|
||||||
ApiUpdateQuickReplyMessage | ApiUpdateQuickReplies | ApiDeleteQuickReply | ApiUpdateDeleteQuickReplyMessages
|
ApiUpdateQuickReplyMessage | ApiUpdateQuickReplies | ApiDeleteQuickReply | ApiUpdateDeleteQuickReplyMessages |
|
||||||
|
ApiUpdateDeleteProfilePhoto | ApiUpdateNewProfilePhoto
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type { ApiBotInfo } from './bots';
|
|||||||
import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from './business';
|
import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from './business';
|
||||||
import type { ApiPeerColor } from './chats';
|
import type { ApiPeerColor } from './chats';
|
||||||
import type { ApiDocument, ApiPhoto } from './messages';
|
import type { ApiDocument, ApiPhoto } from './messages';
|
||||||
|
import type { ApiPeerPhotos } from './misc';
|
||||||
|
|
||||||
export interface ApiUser {
|
export interface ApiUser {
|
||||||
id: string;
|
id: string;
|
||||||
@ -21,8 +22,8 @@ export interface ApiUser {
|
|||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
accessHash?: string;
|
accessHash?: string;
|
||||||
hasVideoAvatar?: boolean;
|
hasVideoAvatar?: boolean;
|
||||||
avatarHash?: string;
|
avatarPhotoId?: string;
|
||||||
photos?: ApiPhoto[];
|
profilePhotos?: ApiPeerPhotos;
|
||||||
botPlaceholder?: string;
|
botPlaceholder?: string;
|
||||||
canBeInvitedToGroup?: boolean;
|
canBeInvitedToGroup?: boolean;
|
||||||
commonChats?: {
|
commonChats?: {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
getChatTitle,
|
getChatTitle,
|
||||||
getPeerStoryHtmlId,
|
getPeerStoryHtmlId,
|
||||||
getUserFullName,
|
getUserFullName,
|
||||||
getVideoAvatarMediaHash,
|
getVideoProfilePhotoMediaHash,
|
||||||
getWebDocumentHash,
|
getWebDocumentHash,
|
||||||
isAnonymousForwardsChat,
|
isAnonymousForwardsChat,
|
||||||
isChatWithRepliesBot,
|
isChatWithRepliesBot,
|
||||||
@ -121,7 +121,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
} else if (photo) {
|
} else if (photo) {
|
||||||
imageHash = `photo${photo.id}?size=m`;
|
imageHash = `photo${photo.id}?size=m`;
|
||||||
if (photo.isVideo && withVideo) {
|
if (photo.isVideo && withVideo) {
|
||||||
videoHash = getVideoAvatarMediaHash(photo);
|
videoHash = getVideoProfilePhotoMediaHash(photo);
|
||||||
}
|
}
|
||||||
} else if (webPhoto) {
|
} else if (webPhoto) {
|
||||||
imageHash = getWebDocumentHash(webPhoto);
|
imageHash = getWebDocumentHash(webPhoto);
|
||||||
|
|||||||
@ -109,7 +109,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
const {
|
const {
|
||||||
loadFullChat,
|
loadFullChat,
|
||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
loadProfilePhotos,
|
loadMoreProfilePhotos,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
@ -121,9 +121,9 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chatId && !isMin) {
|
if (chatId && !isMin) {
|
||||||
if (withFullInfo) loadFullChat({ chatId });
|
if (withFullInfo) loadFullChat({ chatId });
|
||||||
if (withMediaViewer) loadProfilePhotos({ profileId: chatId });
|
if (withMediaViewer) loadMoreProfilePhotos({ peerId: chatId, isPreload: true });
|
||||||
}
|
}
|
||||||
}, [chatId, isMin, withFullInfo, loadFullChat, loadProfilePhotos, isSuperGroup, withMediaViewer]);
|
}, [chatId, isMin, withFullInfo, isSuperGroup, withMediaViewer]);
|
||||||
|
|
||||||
const handleAvatarViewerOpen = useLastCallback(
|
const handleAvatarViewerOpen = useLastCallback(
|
||||||
(e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
|
(e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
|
||||||
|
|||||||
@ -102,7 +102,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
const {
|
const {
|
||||||
loadFullUser,
|
loadFullUser,
|
||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
loadProfilePhotos,
|
loadMoreProfilePhotos,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
@ -112,7 +112,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userId) {
|
if (userId) {
|
||||||
if (withFullInfo && isSynced) loadFullUser({ userId });
|
if (withFullInfo && isSynced) loadFullUser({ userId });
|
||||||
if (withMediaViewer) loadProfilePhotos({ profileId: userId });
|
if (withMediaViewer) loadMoreProfilePhotos({ peerId: userId, isPreload: true });
|
||||||
}
|
}
|
||||||
}, [userId, withFullInfo, withMediaViewer, isSynced]);
|
}, [userId, withFullInfo, withMediaViewer, isSynced]);
|
||||||
|
|
||||||
|
|||||||
@ -3,22 +3,19 @@ import React, { memo, useEffect, useState } from '../../lib/teact/teact';
|
|||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiPhoto, ApiSticker, ApiTopic, ApiUser, ApiUserStatus,
|
ApiChat, ApiSticker, ApiTopic, ApiUser, ApiUserStatus,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { GlobalState } from '../../global/types';
|
|
||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin } from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getUserStatus, isAnonymousForwardsChat, isChatChannel, isUserId, isUserOnline,
|
getUserStatus, isAnonymousForwardsChat, isChatChannel, isUserOnline,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatFullInfo,
|
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadMessagesCount,
|
selectThreadMessagesCount,
|
||||||
selectUser,
|
selectUser,
|
||||||
selectUserFullInfo,
|
|
||||||
selectUserStatus,
|
selectUserStatus,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -30,7 +27,6 @@ import renderText from './helpers/renderText';
|
|||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
import usePrevious from '../../hooks/usePrevious';
|
import usePrevious from '../../hooks/usePrevious';
|
||||||
import { useStateRef } from '../../hooks/useStateRef';
|
|
||||||
import usePhotosPreload from './hooks/usePhotosPreload';
|
import usePhotosPreload from './hooks/usePhotosPreload';
|
||||||
|
|
||||||
import Transition from '../ui/Transition';
|
import Transition from '../ui/Transition';
|
||||||
@ -43,7 +39,7 @@ import './ProfileInfo.scss';
|
|||||||
import styles from './ProfileInfo.module.scss';
|
import styles from './ProfileInfo.module.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
userId: string;
|
peerId: string;
|
||||||
forceShowSelf?: boolean;
|
forceShowSelf?: boolean;
|
||||||
canPlayVideo: boolean;
|
canPlayVideo: boolean;
|
||||||
};
|
};
|
||||||
@ -57,16 +53,13 @@ type StateProps =
|
|||||||
avatarOwnerId?: string;
|
avatarOwnerId?: string;
|
||||||
topic?: ApiTopic;
|
topic?: ApiTopic;
|
||||||
messagesCount?: number;
|
messagesCount?: number;
|
||||||
userPersonalPhoto?: ApiPhoto;
|
|
||||||
userProfilePhoto?: ApiPhoto;
|
|
||||||
userFallbackPhoto?: ApiPhoto;
|
|
||||||
chatProfilePhoto?: ApiPhoto;
|
|
||||||
emojiStatusSticker?: ApiSticker;
|
emojiStatusSticker?: ApiSticker;
|
||||||
}
|
};
|
||||||
& Pick<GlobalState, 'isSynced'>;
|
|
||||||
|
|
||||||
const EMOJI_STATUS_SIZE = 24;
|
const EMOJI_STATUS_SIZE = 24;
|
||||||
const EMOJI_TOPIC_SIZE = 120;
|
const EMOJI_TOPIC_SIZE = 120;
|
||||||
|
const LOAD_MORE_THRESHOLD = 3;
|
||||||
|
const MAX_PHOTO_DASH_COUNT = 30;
|
||||||
|
|
||||||
const ProfileInfo: FC<OwnProps & StateProps> = ({
|
const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||||
forceShowSelf,
|
forceShowSelf,
|
||||||
@ -74,33 +67,28 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
user,
|
user,
|
||||||
userStatus,
|
userStatus,
|
||||||
chat,
|
chat,
|
||||||
isSynced,
|
|
||||||
mediaIndex,
|
mediaIndex,
|
||||||
avatarOwnerId,
|
avatarOwnerId,
|
||||||
topic,
|
topic,
|
||||||
messagesCount,
|
messagesCount,
|
||||||
userPersonalPhoto,
|
|
||||||
userProfilePhoto,
|
|
||||||
userFallbackPhoto,
|
|
||||||
chatProfilePhoto,
|
|
||||||
emojiStatusSticker,
|
emojiStatusSticker,
|
||||||
|
peerId,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadFullUser,
|
|
||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
openPremiumModal,
|
openPremiumModal,
|
||||||
openStickerSet,
|
openStickerSet,
|
||||||
openPrivacySettingsNoticeModal,
|
openPrivacySettingsNoticeModal,
|
||||||
|
loadMoreProfilePhotos,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
const { id: userId } = user || {};
|
const userProfilePhotos = user?.profilePhotos;
|
||||||
const { id: chatId } = chat || {};
|
const chatProfilePhotos = chat?.profilePhotos;
|
||||||
const photos = user?.photos || chat?.photos || MEMO_EMPTY_ARRAY;
|
const photos = userProfilePhotos?.photos || chatProfilePhotos?.photos || MEMO_EMPTY_ARRAY;
|
||||||
const prevMediaIndex = usePrevious(mediaIndex);
|
const prevMediaIndex = usePrevious(mediaIndex);
|
||||||
const prevAvatarOwnerId = usePrevious(avatarOwnerId);
|
const prevAvatarOwnerId = usePrevious(avatarOwnerId);
|
||||||
const mediaIndexRef = useStateRef(mediaIndex);
|
|
||||||
const [hasSlideAnimation, setHasSlideAnimation] = useState(true);
|
const [hasSlideAnimation, setHasSlideAnimation] = useState(true);
|
||||||
// slideOptimized doesn't work well when animation is dynamically disabled
|
// slideOptimized doesn't work well when animation is dynamically disabled
|
||||||
const slideAnimation = hasSlideAnimation ? (lang.isRtl ? 'slideRtl' : 'slide') : 'none';
|
const slideAnimation = hasSlideAnimation ? (lang.isRtl ? 'slideRtl' : 'slide') : 'none';
|
||||||
@ -109,6 +97,12 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
const isFirst = photos.length <= 1 || currentPhotoIndex === 0;
|
const isFirst = photos.length <= 1 || currentPhotoIndex === 0;
|
||||||
const isLast = photos.length <= 1 || currentPhotoIndex === photos.length - 1;
|
const isLast = photos.length <= 1 || currentPhotoIndex === photos.length - 1;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (photos.length - currentPhotoIndex <= LOAD_MORE_THRESHOLD) {
|
||||||
|
loadMoreProfilePhotos({ peerId });
|
||||||
|
}
|
||||||
|
}, [currentPhotoIndex, peerId, photos.length]);
|
||||||
|
|
||||||
// Set the current avatar photo to the last selected photo in Media Viewer after it is closed
|
// Set the current avatar photo to the last selected photo in Media Viewer after it is closed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prevAvatarOwnerId && prevMediaIndex !== undefined && mediaIndex === undefined) {
|
if (prevAvatarOwnerId && prevMediaIndex !== undefined && mediaIndex === undefined) {
|
||||||
@ -117,12 +111,6 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [mediaIndex, prevMediaIndex, prevAvatarOwnerId]);
|
}, [mediaIndex, prevMediaIndex, prevAvatarOwnerId]);
|
||||||
|
|
||||||
// Reset the current avatar photo to the one selected in Media Viewer if photos have changed
|
|
||||||
useEffect(() => {
|
|
||||||
setHasSlideAnimation(false);
|
|
||||||
setCurrentPhotoIndex(mediaIndexRef.current || 0);
|
|
||||||
}, [mediaIndexRef, photos]);
|
|
||||||
|
|
||||||
// Deleting the last profile photo may result in an error
|
// Deleting the last profile photo may result in an error
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentPhotoIndex > photos.length) {
|
if (currentPhotoIndex > photos.length) {
|
||||||
@ -131,32 +119,26 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [currentPhotoIndex, photos.length]);
|
}, [currentPhotoIndex, photos.length]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isSynced && userId && !forceShowSelf) {
|
|
||||||
loadFullUser({ userId });
|
|
||||||
}
|
|
||||||
}, [userId, loadFullUser, isSynced, forceShowSelf]);
|
|
||||||
|
|
||||||
usePhotosPreload(photos, currentPhotoIndex);
|
usePhotosPreload(photos, currentPhotoIndex);
|
||||||
|
|
||||||
const handleProfilePhotoClick = useLastCallback(() => {
|
const handleProfilePhotoClick = useLastCallback(() => {
|
||||||
openMediaViewer({
|
openMediaViewer({
|
||||||
isAvatarView: true,
|
isAvatarView: true,
|
||||||
chatId: userId || chatId,
|
chatId: peerId,
|
||||||
mediaIndex: currentPhotoIndex,
|
mediaIndex: currentPhotoIndex,
|
||||||
origin: forceShowSelf ? MediaViewerOrigin.SettingsAvatar : MediaViewerOrigin.ProfileAvatar,
|
origin: forceShowSelf ? MediaViewerOrigin.SettingsAvatar : MediaViewerOrigin.ProfileAvatar,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleStatusClick = useLastCallback(() => {
|
const handleStatusClick = useLastCallback(() => {
|
||||||
if (!userId) {
|
if (!peerId) {
|
||||||
openStickerSet({
|
openStickerSet({
|
||||||
stickerSetInfo: emojiStatusSticker!.stickerSetInfo,
|
stickerSetInfo: emojiStatusSticker!.stickerSetInfo,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
openPremiumModal({ fromUserId: userId });
|
openPremiumModal({ fromUserId: peerId });
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectPreviousMedia = useLastCallback(() => {
|
const selectPreviousMedia = useLastCallback(() => {
|
||||||
@ -231,14 +213,18 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderPhotoTabs() {
|
function renderPhotoTabs() {
|
||||||
if (!photos || photos.length <= 1) {
|
const totalPhotosLength = Math.max(photos.length, userProfilePhotos?.count || 0, chatProfilePhotos?.count || 0);
|
||||||
|
if (!photos || totalPhotosLength <= 1) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enumerator = Array.from({ length: Math.min(totalPhotosLength, MAX_PHOTO_DASH_COUNT) });
|
||||||
|
const activeDashIndex = currentPhotoIndex >= MAX_PHOTO_DASH_COUNT ? MAX_PHOTO_DASH_COUNT - 1 : currentPhotoIndex;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.photoDashes}>
|
<div className={styles.photoDashes}>
|
||||||
{photos.map((_, i) => (
|
{enumerator.map((_, i) => (
|
||||||
<span className={buildClassName(styles.photoDash, i === currentPhotoIndex && styles.photoDash_current)} />
|
<span className={buildClassName(styles.photoDash, i === activeDashIndex && styles.photoDash_current)} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -248,14 +234,13 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
const photo = photos.length > 0
|
const photo = photos.length > 0
|
||||||
? photos[currentPhotoIndex]
|
? photos[currentPhotoIndex]
|
||||||
: undefined;
|
: undefined;
|
||||||
const profilePhoto = photo || userPersonalPhoto || userProfilePhoto || chatProfilePhoto || userFallbackPhoto;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProfilePhoto
|
<ProfilePhoto
|
||||||
key={currentPhotoIndex}
|
key={currentPhotoIndex}
|
||||||
user={user}
|
user={user}
|
||||||
chat={chat}
|
chat={chat}
|
||||||
photo={profilePhoto}
|
photo={photo}
|
||||||
canPlayVideo={Boolean(isActive && canPlayVideo)}
|
canPlayVideo={Boolean(isActive && canPlayVideo)}
|
||||||
onClick={handleProfilePhotoClick}
|
onClick={handleProfilePhotoClick}
|
||||||
/>
|
/>
|
||||||
@ -263,8 +248,6 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderStatus() {
|
function renderStatus() {
|
||||||
const peerId = (chatId || userId)!;
|
|
||||||
|
|
||||||
const isAnonymousForwards = isAnonymousForwardsChat(peerId);
|
const isAnonymousForwards = isAnonymousForwardsChat(peerId);
|
||||||
if (isAnonymousForwards) return undefined;
|
if (isAnonymousForwards) return undefined;
|
||||||
|
|
||||||
@ -310,18 +293,18 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
<div className={styles.photoWrapper}>
|
<div className={styles.photoWrapper}>
|
||||||
{renderPhotoTabs()}
|
{renderPhotoTabs()}
|
||||||
{!forceShowSelf && userPersonalPhoto && (
|
{!forceShowSelf && userProfilePhotos?.personalPhoto && (
|
||||||
<div className={buildClassName(
|
<div className={buildClassName(
|
||||||
styles.fallbackPhoto,
|
styles.fallbackPhoto,
|
||||||
isFirst && styles.fallbackPhotoVisible,
|
isFirst && styles.fallbackPhotoVisible,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={styles.fallbackPhotoContents}>
|
<div className={styles.fallbackPhotoContents}>
|
||||||
{lang(userPersonalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')}
|
{lang(userProfilePhotos.personalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{forceShowSelf && userFallbackPhoto && (
|
{forceShowSelf && userProfilePhotos?.fallbackPhoto && (
|
||||||
<div className={buildClassName(
|
<div className={buildClassName(
|
||||||
styles.fallbackPhoto,
|
styles.fallbackPhoto,
|
||||||
(isFirst || isLast) && styles.fallbackPhotoVisible,
|
(isFirst || isLast) && styles.fallbackPhotoVisible,
|
||||||
@ -330,12 +313,12 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
<div className={styles.fallbackPhotoContents} onClick={handleSelectFallbackPhoto}>
|
<div className={styles.fallbackPhotoContents} onClick={handleSelectFallbackPhoto}>
|
||||||
{!isLast && (
|
{!isLast && (
|
||||||
<Avatar
|
<Avatar
|
||||||
photo={userFallbackPhoto}
|
photo={userProfilePhotos.fallbackPhoto}
|
||||||
className={styles.fallbackPhotoAvatar}
|
className={styles.fallbackPhotoAvatar}
|
||||||
size="mini"
|
size="mini"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{lang(userFallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')}
|
{lang(userProfilePhotos.fallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -381,37 +364,28 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { userId }): StateProps => {
|
(global, { peerId }): StateProps => {
|
||||||
const { isSynced } = global;
|
const user = selectUser(global, peerId);
|
||||||
const user = selectUser(global, userId);
|
const userStatus = selectUserStatus(global, peerId);
|
||||||
const isPrivate = isUserId(userId);
|
const chat = selectChat(global, peerId);
|
||||||
const userStatus = selectUserStatus(global, userId);
|
|
||||||
const chat = selectChat(global, userId);
|
|
||||||
const { mediaIndex, chatId: avatarOwnerId } = selectTabState(global).mediaViewer;
|
const { mediaIndex, chatId: avatarOwnerId } = selectTabState(global).mediaViewer;
|
||||||
const isForum = chat?.isForum;
|
const isForum = chat?.isForum;
|
||||||
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
||||||
const topic = isForum && currentTopicId ? chat?.topics?.[currentTopicId] : undefined;
|
const topic = isForum && currentTopicId ? chat?.topics?.[currentTopicId] : undefined;
|
||||||
const userFullInfo = isPrivate ? selectUserFullInfo(global, userId) : undefined;
|
|
||||||
const chatFullInfo = !isPrivate ? selectChatFullInfo(global, userId) : undefined;
|
|
||||||
|
|
||||||
const emojiStatus = (user || chat)?.emojiStatus;
|
const emojiStatus = (user || chat)?.emojiStatus;
|
||||||
const emojiStatusSticker = emojiStatus ? global.customEmojis.byId[emojiStatus.documentId] : undefined;
|
const emojiStatusSticker = emojiStatus ? global.customEmojis.byId[emojiStatus.documentId] : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSynced,
|
|
||||||
user,
|
user,
|
||||||
userStatus,
|
userStatus,
|
||||||
chat,
|
chat,
|
||||||
userPersonalPhoto: userFullInfo?.personalPhoto,
|
|
||||||
userProfilePhoto: userFullInfo?.profilePhoto,
|
|
||||||
userFallbackPhoto: userFullInfo?.fallbackPhoto,
|
|
||||||
chatProfilePhoto: chatFullInfo?.profilePhoto,
|
|
||||||
mediaIndex,
|
mediaIndex,
|
||||||
avatarOwnerId,
|
avatarOwnerId,
|
||||||
emojiStatusSticker,
|
emojiStatusSticker,
|
||||||
...(topic && {
|
...(topic && {
|
||||||
topic,
|
topic,
|
||||||
messagesCount: selectThreadMessagesCount(global, userId, currentTopicId!),
|
messagesCount: selectThreadMessagesCount(global, peerId, currentTopicId!),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,8 +8,10 @@ import type { ApiChat, ApiPhoto, ApiUser } from '../../api/types';
|
|||||||
import {
|
import {
|
||||||
getChatAvatarHash,
|
getChatAvatarHash,
|
||||||
getChatTitle,
|
getChatTitle,
|
||||||
|
getPhotoMediaHash,
|
||||||
|
getProfilePhotoMediaHash,
|
||||||
getUserFullName,
|
getUserFullName,
|
||||||
getVideoAvatarMediaHash,
|
getVideoProfilePhotoMediaHash,
|
||||||
isAnonymousForwardsChat,
|
isAnonymousForwardsChat,
|
||||||
isChatWithRepliesBot,
|
isChatWithRepliesBot,
|
||||||
isDeletedUser,
|
isDeletedUser,
|
||||||
@ -62,28 +64,30 @@ const ProfilePhoto: FC<OwnProps> = ({
|
|||||||
const isDeleted = user && isDeletedUser(user);
|
const isDeleted = user && isDeletedUser(user);
|
||||||
const isRepliesChat = chat && isChatWithRepliesBot(chat.id);
|
const isRepliesChat = chat && isChatWithRepliesBot(chat.id);
|
||||||
const isAnonymousForwards = chat && isAnonymousForwardsChat(chat.id);
|
const isAnonymousForwards = chat && isAnonymousForwardsChat(chat.id);
|
||||||
const peer = user || chat;
|
const peer = (user || chat)!;
|
||||||
const canHaveMedia = peer && !isSavedMessages && !isDeleted && !isRepliesChat && !isAnonymousForwards;
|
const canHaveMedia = peer && !isSavedMessages && !isDeleted && !isRepliesChat && !isAnonymousForwards;
|
||||||
const { isVideo } = photo || {};
|
const { isVideo } = photo || {};
|
||||||
|
|
||||||
const avatarHash = canHaveMedia && getChatAvatarHash(peer, 'normal');
|
const avatarHash = (!photo || photo.id === peer.avatarPhotoId) && getChatAvatarHash(peer, 'normal');
|
||||||
const avatarBlobUrl = useMedia(avatarHash);
|
|
||||||
|
|
||||||
const photoHash = canHaveMedia && photo && !isVideo && `photo${photo.id}?size=c`;
|
const previewHash = canHaveMedia && photo && !avatarHash && getPhotoMediaHash(photo, 'pictogram');
|
||||||
|
const previewBlobUrl = useMedia(previewHash || avatarHash);
|
||||||
|
|
||||||
|
const photoHash = canHaveMedia && photo && !isVideo && getProfilePhotoMediaHash(photo);
|
||||||
const photoBlobUrl = useMedia(photoHash);
|
const photoBlobUrl = useMedia(photoHash);
|
||||||
|
|
||||||
const videoHash = canHaveMedia && photo && isVideo && getVideoAvatarMediaHash(photo);
|
const videoHash = canHaveMedia && photo && isVideo && getVideoProfilePhotoMediaHash(photo);
|
||||||
const videoBlobUrl = useMedia(videoHash);
|
const videoBlobUrl = useMedia(videoHash);
|
||||||
|
|
||||||
const fullMediaData = videoBlobUrl || photoBlobUrl;
|
const fullMediaData = videoBlobUrl || photoBlobUrl;
|
||||||
const [isVideoReady, markVideoReady] = useFlag();
|
const [isVideoReady, markVideoReady] = useFlag();
|
||||||
const isFullMediaReady = Boolean(fullMediaData && (!isVideo || isVideoReady));
|
const isFullMediaReady = Boolean(fullMediaData && (!isVideo || isVideoReady));
|
||||||
const transitionClassNames = useMediaTransition(isFullMediaReady);
|
const transitionClassNames = useMediaTransition(isFullMediaReady);
|
||||||
const isBlurredThumb = canHaveMedia && !isFullMediaReady && !avatarBlobUrl && photo?.thumbnail?.dataUri;
|
const isBlurredThumb = canHaveMedia && !isFullMediaReady && !previewBlobUrl && photo?.thumbnail?.dataUri;
|
||||||
const blurredThumbCanvasRef = useCanvasBlur(
|
const blurredThumbCanvasRef = useCanvasBlur(
|
||||||
photo?.thumbnail?.dataUri, !isBlurredThumb, isMobile && !IS_CANVAS_FILTER_SUPPORTED,
|
photo?.thumbnail?.dataUri, !isBlurredThumb, isMobile && !IS_CANVAS_FILTER_SUPPORTED,
|
||||||
);
|
);
|
||||||
const hasMedia = photo || avatarBlobUrl || isBlurredThumb;
|
const hasMedia = photo || previewBlobUrl || isBlurredThumb;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (videoRef.current && !canPlayVideo) {
|
if (videoRef.current && !canPlayVideo) {
|
||||||
@ -121,7 +125,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
|||||||
{isBlurredThumb ? (
|
{isBlurredThumb ? (
|
||||||
<canvas ref={blurredThumbCanvasRef} className="thumb canvas-blur-setup" />
|
<canvas ref={blurredThumbCanvasRef} className="thumb canvas-blur-setup" />
|
||||||
) : (
|
) : (
|
||||||
<img src={avatarBlobUrl} draggable={false} className="thumb" alt="" />
|
<img src={previewBlobUrl} draggable={false} className="thumb" alt="" />
|
||||||
)}
|
)}
|
||||||
{photo && (
|
{photo && (
|
||||||
isVideo ? (
|
isVideo ? (
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useEffect } from '../../../lib/teact/teact';
|
|||||||
import type { ApiPhoto } from '../../../api/types';
|
import type { ApiPhoto } from '../../../api/types';
|
||||||
import { ApiMediaFormat } from '../../../api/types';
|
import { ApiMediaFormat } from '../../../api/types';
|
||||||
|
|
||||||
|
import { getProfilePhotoMediaHash } from '../../../global/helpers';
|
||||||
import * as mediaLoader from '../../../util/mediaLoader';
|
import * as mediaLoader from '../../../util/mediaLoader';
|
||||||
|
|
||||||
const PHOTOS_TO_PRELOAD = 4;
|
const PHOTOS_TO_PRELOAD = 4;
|
||||||
@ -13,9 +14,10 @@ export default function usePhotosPreload(
|
|||||||
) {
|
) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
photos.slice(currentIndex, currentIndex + PHOTOS_TO_PRELOAD).forEach((photo) => {
|
photos.slice(currentIndex, currentIndex + PHOTOS_TO_PRELOAD).forEach((photo) => {
|
||||||
const mediaData = mediaLoader.getFromMemory(`photo${photo.id}?size=c`);
|
const mediaHash = getProfilePhotoMediaHash(photo);
|
||||||
|
const mediaData = mediaLoader.getFromMemory(mediaHash);
|
||||||
if (!mediaData) {
|
if (!mediaData) {
|
||||||
mediaLoader.fetch(`photo${photo.id}?size=c`, ApiMediaFormat.BlobUrl);
|
mediaLoader.fetch(mediaHash, ApiMediaFormat.BlobUrl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [currentIndex, photos]);
|
}, [currentIndex, photos]);
|
||||||
|
|||||||
@ -49,7 +49,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
|||||||
onReset,
|
onReset,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadProfilePhotos,
|
loadMoreProfilePhotos,
|
||||||
openPremiumModal,
|
openPremiumModal,
|
||||||
openSupportChat,
|
openSupportChat,
|
||||||
openUrl,
|
openUrl,
|
||||||
@ -63,9 +63,9 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentUserId) {
|
if (currentUserId) {
|
||||||
loadProfilePhotos({ profileId: currentUserId });
|
loadMoreProfilePhotos({ peerId: currentUserId, isPreload: true });
|
||||||
}
|
}
|
||||||
}, [currentUserId, loadProfilePhotos]);
|
}, [currentUserId]);
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive,
|
isActive,
|
||||||
@ -82,7 +82,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="settings-main-menu">
|
<div className="settings-main-menu">
|
||||||
{currentUserId && (
|
{currentUserId && (
|
||||||
<ProfileInfo
|
<ProfileInfo
|
||||||
userId={currentUserId}
|
peerId={currentUserId}
|
||||||
canPlayVideo={Boolean(isActive)}
|
canPlayVideo={Boolean(isActive)}
|
||||||
forceShowSelf
|
forceShowSelf
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -80,6 +80,7 @@ type StateProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ANIMATION_DURATION = 250;
|
const ANIMATION_DURATION = 250;
|
||||||
|
const AVATAR_LOAD_TRIGGER = 4;
|
||||||
|
|
||||||
const MediaViewer = ({
|
const MediaViewer = ({
|
||||||
chatId,
|
chatId,
|
||||||
@ -109,6 +110,7 @@ const MediaViewer = ({
|
|||||||
focusMessage,
|
focusMessage,
|
||||||
toggleChatInfo,
|
toggleChatInfo,
|
||||||
searchChatMediaMessages,
|
searchChatMediaMessages,
|
||||||
|
loadMoreProfilePhotos,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const isOpen = Boolean(avatarOwner || message || standaloneMedia);
|
const isOpen = Boolean(avatarOwner || message || standaloneMedia);
|
||||||
@ -255,9 +257,17 @@ const MediaViewer = ({
|
|||||||
}, [isGif, isVideo]);
|
}, [isGif, isVideo]);
|
||||||
|
|
||||||
const loadMoreItemsIfNeeded = useLastCallback((item?: MediaViewerItem) => {
|
const loadMoreItemsIfNeeded = useLastCallback((item?: MediaViewerItem) => {
|
||||||
if (!item || !withDynamicLoading || isLoadingMoreMedia) return;
|
if (!item || isLoadingMoreMedia) return;
|
||||||
if (item.type !== 'message') return;
|
|
||||||
searchChatMediaMessages({ chatId, threadId, currentMediaMessageId: item.message.id });
|
if (item.type === 'avatar') {
|
||||||
|
const isNearEnd = item.mediaIndex >= item.avatarOwner.profilePhotos!.photos.length - AVATAR_LOAD_TRIGGER;
|
||||||
|
if (!isNearEnd) return;
|
||||||
|
loadMoreProfilePhotos({ peerId: item.avatarOwner.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === 'message' && withDynamicLoading) {
|
||||||
|
searchChatMediaMessages({ chatId, threadId, currentMediaMessageId: item.message.id });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const getNextItem = useLastCallback((from: MediaViewerItem, direction: number): MediaViewerItem | undefined => {
|
const getNextItem = useLastCallback((from: MediaViewerItem, direction: number): MediaViewerItem | undefined => {
|
||||||
@ -276,7 +286,7 @@ const MediaViewer = ({
|
|||||||
if (from.type === 'avatar') {
|
if (from.type === 'avatar') {
|
||||||
const { avatarOwner: fromAvatarOwner, mediaIndex: fromMediaIndex } = from;
|
const { avatarOwner: fromAvatarOwner, mediaIndex: fromMediaIndex } = from;
|
||||||
const nextIndex = fromMediaIndex + direction;
|
const nextIndex = fromMediaIndex + direction;
|
||||||
if (nextIndex >= 0 && fromAvatarOwner.photos && nextIndex < fromAvatarOwner.photos.length) {
|
if (nextIndex >= 0 && fromAvatarOwner.profilePhotos && nextIndex < fromAvatarOwner.profilePhotos.photos.length) {
|
||||||
return { type: 'avatar', avatarOwner: fromAvatarOwner, mediaIndex: nextIndex };
|
return { type: 'avatar', avatarOwner: fromAvatarOwner, mediaIndex: nextIndex };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,7 +342,8 @@ const MediaViewer = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleBeforeDelete = useLastCallback(() => {
|
const handleBeforeDelete = useLastCallback(() => {
|
||||||
const mediaCount = avatarOwner?.photos?.length || standaloneMedia?.length || messageMediaIds?.length || 0;
|
const mediaCount = avatarOwner?.profilePhotos?.photos.length
|
||||||
|
|| standaloneMedia?.length || messageMediaIds?.length || 0;
|
||||||
if (mediaCount <= 1 || !currentItem) {
|
if (mediaCount <= 1 || !currentItem) {
|
||||||
handleClose();
|
handleClose();
|
||||||
return;
|
return;
|
||||||
@ -344,7 +355,7 @@ const MediaViewer = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentItem.type === 'avatar' || currentItem.type === 'standalone') {
|
if ((currentItem.type === 'avatar' && isUserId(currentItem.avatarOwner.id)) || currentItem.type === 'standalone') {
|
||||||
// Keep current item, it'll update when indexes shift
|
// Keep current item, it'll update when indexes shift
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -451,9 +462,12 @@ export default memo(withGlobal(
|
|||||||
canUpdateMedia = isUserId(peer.id) ? peer.id === currentUserId : isChatAdmin(peer as ApiChat);
|
canUpdateMedia = isUserId(peer.id) ? peer.id === currentUserId : isChatAdmin(peer as ApiChat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const profilePhotos = peer?.profilePhotos;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
avatar: peer?.photos?.[mediaIndex!],
|
avatar: profilePhotos?.photos[mediaIndex!],
|
||||||
avatarOwner: peer,
|
avatarOwner: peer,
|
||||||
|
isLoadingMoreMedia: profilePhotos?.isLoading,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
canUpdateMedia,
|
canUpdateMedia,
|
||||||
withAnimation,
|
withAnimation,
|
||||||
@ -462,6 +476,7 @@ export default memo(withGlobal(
|
|||||||
isHidden,
|
isHidden,
|
||||||
standaloneMedia,
|
standaloneMedia,
|
||||||
mediaIndex,
|
mediaIndex,
|
||||||
|
isSynced,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -129,7 +129,7 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
|||||||
const handleUpdate = useLastCallback(() => {
|
const handleUpdate = useLastCallback(() => {
|
||||||
if (item?.type !== 'avatar') return;
|
if (item?.type !== 'avatar') return;
|
||||||
const { avatarOwner, mediaIndex } = item;
|
const { avatarOwner, mediaIndex } = item;
|
||||||
const avatarPhoto = avatarOwner.photos?.[mediaIndex]!;
|
const avatarPhoto = avatarOwner.profilePhotos?.photos[mediaIndex]!;
|
||||||
if (isUserId(avatarOwner.id)) {
|
if (isUserId(avatarOwner.id)) {
|
||||||
updateProfilePhoto({ photo: avatarPhoto });
|
updateProfilePhoto({ photo: avatarPhoto });
|
||||||
} else {
|
} else {
|
||||||
@ -170,7 +170,7 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
|||||||
onClose={closeDeleteModal}
|
onClose={closeDeleteModal}
|
||||||
onConfirm={onBeforeDelete}
|
onConfirm={onBeforeDelete}
|
||||||
profileId={item.avatarOwner.id}
|
profileId={item.avatarOwner.id}
|
||||||
photo={item.avatarOwner.photos![item.mediaIndex!]}
|
photo={item.avatarOwner.profilePhotos!.photos[item.mediaIndex!]}
|
||||||
/>
|
/>
|
||||||
) : undefined;
|
) : undefined;
|
||||||
}
|
}
|
||||||
@ -212,7 +212,8 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
|||||||
if (item?.type === 'message') {
|
if (item?.type === 'message') {
|
||||||
openDeleteMessageModal({
|
openDeleteMessageModal({
|
||||||
isSchedule: messageListType === 'scheduled',
|
isSchedule: messageListType === 'scheduled',
|
||||||
message: item.message, onConfirm: onBeforeDelete,
|
message: item.message,
|
||||||
|
onConfirm: onBeforeDelete,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
openDeleteModal();
|
openDeleteModal();
|
||||||
@ -389,7 +390,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const message = item?.type === 'message' ? item.message : undefined;
|
const message = item?.type === 'message' ? item.message : undefined;
|
||||||
const avatarOwner = item?.type === 'avatar' ? item.avatarOwner : undefined;
|
const avatarOwner = item?.type === 'avatar' ? item.avatarOwner : undefined;
|
||||||
const avatarPhoto = avatarOwner?.photos?.[item!.mediaIndex!];
|
const avatarPhoto = avatarOwner?.profilePhotos?.photos[item!.mediaIndex!];
|
||||||
|
|
||||||
const currentMessageList = selectCurrentMessageList(global);
|
const currentMessageList = selectCurrentMessageList(global);
|
||||||
const { threadId } = selectCurrentMessageList(global) || {};
|
const { threadId } = selectCurrentMessageList(global) || {};
|
||||||
@ -398,10 +399,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const isChatProtected = message && selectIsChatProtected(global, message?.chatId);
|
const isChatProtected = message && selectIsChatProtected(global, message?.chatId);
|
||||||
const { canDelete: canDeleteMessage } = (threadId
|
const { canDelete: canDeleteMessage } = (threadId
|
||||||
&& message && selectAllowedMessageActions(global, message, threadId)) || {};
|
&& message && selectAllowedMessageActions(global, message, threadId)) || {};
|
||||||
const isCurrentAvatar = avatarPhoto && (avatarPhoto.id === avatarOwner?.avatarHash);
|
const isCurrentAvatar = avatarPhoto && (avatarPhoto.id === avatarOwner?.avatarPhotoId);
|
||||||
const canDeleteAvatar = canUpdateMedia && !!avatarPhoto;
|
const canDeleteAvatar = canUpdateMedia && Boolean(avatarPhoto);
|
||||||
const canDelete = canDeleteMessage || canDeleteAvatar;
|
const canDelete = canDeleteMessage || canDeleteAvatar;
|
||||||
const canUpdate = canUpdateMedia && !!avatarPhoto && !isCurrentAvatar;
|
const canUpdate = canUpdateMedia && Boolean(avatarPhoto) && !isCurrentAvatar;
|
||||||
const messageListType = currentMessageList?.type;
|
const messageListType = currentMessageList?.type;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import {
|
|||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectSender,
|
selectSender,
|
||||||
selectUserFullInfo,
|
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { formatMediaDateTime } from '../../util/dates/dateFormat';
|
import { formatMediaDateTime } from '../../util/dates/dateFormat';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
@ -29,7 +28,6 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
owner?: ApiPeer;
|
owner?: ApiPeer;
|
||||||
isFallbackAvatar?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const BULLET = '\u2022';
|
const BULLET = '\u2022';
|
||||||
@ -38,7 +36,6 @@ const ANIMATION_DURATION = 350;
|
|||||||
const SenderInfo: FC<OwnProps & StateProps> = ({
|
const SenderInfo: FC<OwnProps & StateProps> = ({
|
||||||
owner,
|
owner,
|
||||||
item,
|
item,
|
||||||
isFallbackAvatar,
|
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
closeMediaViewer,
|
closeMediaViewer,
|
||||||
@ -71,11 +68,15 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
|
|||||||
if (!item || item.type === 'standalone') return undefined;
|
if (!item || item.type === 'standalone') return undefined;
|
||||||
|
|
||||||
const avatarOwner = item.type === 'avatar' ? item.avatarOwner : undefined;
|
const avatarOwner = item.type === 'avatar' ? item.avatarOwner : undefined;
|
||||||
const avatar = avatarOwner?.photos?.[item.mediaIndex!];
|
const avatar = avatarOwner?.profilePhotos?.photos[item.mediaIndex!];
|
||||||
|
const isFallbackAvatar = avatar?.id === avatarOwner?.profilePhotos?.fallbackPhoto?.id;
|
||||||
const date = item.type === 'message' ? item.message.date : avatar?.date;
|
const date = item.type === 'message' ? item.message.date : avatar?.date;
|
||||||
if (!date) return undefined;
|
if (!date) return undefined;
|
||||||
|
|
||||||
const formattedDate = formatMediaDateTime(lang, date * 1000, true);
|
const formattedDate = formatMediaDateTime(lang, date * 1000, true);
|
||||||
|
const count = avatarOwner?.profilePhotos?.count
|
||||||
|
&& (avatarOwner.profilePhotos.count + (avatarOwner?.profilePhotos?.fallbackPhoto ? 1 : 0));
|
||||||
|
const countText = count && lang('Of', [item.mediaIndex! + 1, count]);
|
||||||
|
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
if (avatar) {
|
if (avatar) {
|
||||||
@ -89,10 +90,12 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (countText) parts.push(countText);
|
||||||
|
|
||||||
parts.push(formattedDate);
|
parts.push(formattedDate);
|
||||||
|
|
||||||
return parts.join(` ${BULLET} `);
|
return parts.join(` ${BULLET} `);
|
||||||
}, [item, isFallbackAvatar, lang]);
|
}, [item, lang]);
|
||||||
|
|
||||||
if (!owner) {
|
if (!owner) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -122,15 +125,8 @@ export default withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const owner = item?.type === 'avatar' ? item.avatarOwner : messageSender;
|
const owner = item?.type === 'avatar' ? item.avatarOwner : messageSender;
|
||||||
|
|
||||||
const fallbackAvatar = item?.type === 'avatar'
|
|
||||||
? selectUserFullInfo(global, item.avatarOwner.id)?.fallbackPhoto : undefined;
|
|
||||||
|
|
||||||
const isFallbackAvatar = fallbackAvatar && item?.type === 'avatar'
|
|
||||||
&& item.avatarOwner.photos?.[item.mediaIndex].id === fallbackAvatar.id;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
owner,
|
owner,
|
||||||
isFallbackAvatar,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(SenderInfo);
|
)(SenderInfo);
|
||||||
|
|||||||
@ -68,7 +68,7 @@ export default function getViewableMedia(params?: MediaViewerItem): ViewableMedi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params.type === 'avatar') {
|
if (params.type === 'avatar') {
|
||||||
const avatar = params.avatarOwner.photos?.[params.mediaIndex];
|
const avatar = params.avatarOwner.profilePhotos?.photos[params.mediaIndex];
|
||||||
if (avatar) {
|
if (avatar) {
|
||||||
return {
|
return {
|
||||||
media: avatar,
|
media: avatar,
|
||||||
|
|||||||
@ -10,8 +10,9 @@ import {
|
|||||||
getMediaHash,
|
getMediaHash,
|
||||||
getMediaThumbUri,
|
getMediaThumbUri,
|
||||||
getPhotoFullDimensions,
|
getPhotoFullDimensions,
|
||||||
getVideoAvatarMediaHash,
|
getProfilePhotoMediaHash,
|
||||||
getVideoDimensions,
|
getVideoDimensions,
|
||||||
|
getVideoProfilePhotoMediaHash,
|
||||||
isDocumentPhoto,
|
isDocumentPhoto,
|
||||||
isDocumentVideo,
|
isDocumentVideo,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
@ -36,6 +37,7 @@ export const useMediaProps = ({
|
|||||||
origin,
|
origin,
|
||||||
delay,
|
delay,
|
||||||
}: UseMediaProps) => {
|
}: UseMediaProps) => {
|
||||||
|
const isPhotoAvatar = isAvatar && media?.mediaType === 'photo' && !media.isVideo;
|
||||||
const isVideoAvatar = isAvatar && media?.mediaType === 'photo' && media.isVideo;
|
const isVideoAvatar = isAvatar && media?.mediaType === 'photo' && media.isVideo;
|
||||||
const isDocument = media?.mediaType === 'document';
|
const isDocument = media?.mediaType === 'document';
|
||||||
const isVideo = (media?.mediaType === 'video' && !media.isRound) || (isDocument && isDocumentVideo(media));
|
const isVideo = (media?.mediaType === 'video' && !media.isRound) || (isDocument && isDocumentVideo(media));
|
||||||
@ -47,12 +49,16 @@ export const useMediaProps = ({
|
|||||||
const getMediaOrAvatarHash = useMemo(() => (isFull?: boolean) => {
|
const getMediaOrAvatarHash = useMemo(() => (isFull?: boolean) => {
|
||||||
if (!media) return undefined;
|
if (!media) return undefined;
|
||||||
|
|
||||||
|
if ((isPhotoAvatar || isVideoAvatar) && !isFull) {
|
||||||
|
return getProfilePhotoMediaHash(media);
|
||||||
|
}
|
||||||
|
|
||||||
if (isVideoAvatar && isFull) {
|
if (isVideoAvatar && isFull) {
|
||||||
return getVideoAvatarMediaHash(media);
|
return getVideoProfilePhotoMediaHash(media);
|
||||||
}
|
}
|
||||||
|
|
||||||
return getMediaHash(media, isFull ? 'full' : 'preview');
|
return getMediaHash(media, isFull ? 'full' : 'preview');
|
||||||
}, [isVideoAvatar, media]);
|
}, [isVideoAvatar, isPhotoAvatar, media]);
|
||||||
|
|
||||||
const pictogramBlobUrl = useMedia(
|
const pictogramBlobUrl = useMedia(
|
||||||
media
|
media
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import type { TextPart } from '../../types';
|
|||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
import { MediaViewerOrigin, SettingsScreens } from '../../types';
|
import { MediaViewerOrigin, SettingsScreens } from '../../types';
|
||||||
|
|
||||||
import { getPhotoMediaHash, getVideoAvatarMediaHash } from '../../global/helpers';
|
import { getPhotoMediaHash, getVideoProfilePhotoMediaHash } from '../../global/helpers';
|
||||||
import { fetchBlob } from '../../util/files';
|
import { fetchBlob } from '../../util/files';
|
||||||
|
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -39,7 +39,7 @@ const ActionMessageSuggestedAvatar: FC<OwnProps> = ({
|
|||||||
const [isVideoModalOpen, openVideoModal, closeVideoModal] = useFlag(false);
|
const [isVideoModalOpen, openVideoModal, closeVideoModal] = useFlag(false);
|
||||||
const photo = message.content.action!.photo!;
|
const photo = message.content.action!.photo!;
|
||||||
const suggestedPhotoUrl = useMedia(getPhotoMediaHash(photo, 'full'));
|
const suggestedPhotoUrl = useMedia(getPhotoMediaHash(photo, 'full'));
|
||||||
const suggestedVideoUrl = useMedia(getVideoAvatarMediaHash(photo));
|
const suggestedVideoUrl = useMedia(getVideoProfilePhotoMediaHash(photo));
|
||||||
const isVideo = message.content.action!.photo?.isVideo;
|
const isVideo = message.content.action!.photo?.isVideo;
|
||||||
|
|
||||||
const showAvatarNotification = useLastCallback(() => {
|
const showAvatarNotification = useLastCallback(() => {
|
||||||
|
|||||||
@ -204,7 +204,6 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
openAudioPlayer,
|
openAudioPlayer,
|
||||||
focusMessage,
|
focusMessage,
|
||||||
loadProfilePhotos,
|
|
||||||
setNewChatMembersDialogState,
|
setNewChatMembersDialogState,
|
||||||
loadPeerProfileStories,
|
loadPeerProfileStories,
|
||||||
loadStoriesArchive,
|
loadStoriesArchive,
|
||||||
@ -342,10 +341,6 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
setSharedMediaSearchType({ mediaType: tabType as SharedMediaType });
|
setSharedMediaSearchType({ mediaType: tabType as SharedMediaType });
|
||||||
}, [setSharedMediaSearchType, tabType, threadId]);
|
}, [setSharedMediaSearchType, tabType, threadId]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadProfilePhotos({ profileId });
|
|
||||||
}, [profileId]);
|
|
||||||
|
|
||||||
const handleSelectMedia = useLastCallback((messageId: number) => {
|
const handleSelectMedia = useLastCallback((messageId: number) => {
|
||||||
openMediaViewer({
|
openMediaViewer({
|
||||||
chatId: profileId,
|
chatId: profileId,
|
||||||
@ -681,7 +676,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
function renderProfileInfo(profileId: string, isReady: boolean, isSavedDialog?: boolean) {
|
function renderProfileInfo(profileId: string, isReady: boolean, isSavedDialog?: boolean) {
|
||||||
return (
|
return (
|
||||||
<div className="profile-info">
|
<div className="profile-info">
|
||||||
<ProfileInfo userId={profileId} canPlayVideo={isReady} />
|
<ProfileInfo peerId={profileId} canPlayVideo={isReady} />
|
||||||
<ChatExtra chatOrUserId={profileId} isSavedDialog={isSavedDialog} />
|
<ChatExtra chatOrUserId={profileId} isSavedDialog={isSavedDialog} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -83,7 +83,6 @@ export const MEMBERS_SLICE = 30;
|
|||||||
export const MEMBERS_LOAD_SLICE = 200;
|
export const MEMBERS_LOAD_SLICE = 200;
|
||||||
export const PINNED_MESSAGES_LIMIT = 50;
|
export const PINNED_MESSAGES_LIMIT = 50;
|
||||||
export const BLOCKED_LIST_LIMIT = 100;
|
export const BLOCKED_LIST_LIMIT = 100;
|
||||||
export const PROFILE_PHOTOS_LIMIT = 40;
|
|
||||||
export const PROFILE_SENSITIVE_AREA = 500;
|
export const PROFILE_SENSITIVE_AREA = 500;
|
||||||
export const TOPIC_LIST_SENSITIVE_AREA = 600;
|
export const TOPIC_LIST_SENSITIVE_AREA = 600;
|
||||||
export const COMMON_CHATS_LIMIT = 100;
|
export const COMMON_CHATS_LIMIT = 100;
|
||||||
|
|||||||
@ -63,6 +63,7 @@ import {
|
|||||||
addUsers,
|
addUsers,
|
||||||
addUserStatuses,
|
addUserStatuses,
|
||||||
deleteChatMessages,
|
deleteChatMessages,
|
||||||
|
deletePeerPhoto,
|
||||||
deleteTopic,
|
deleteTopic,
|
||||||
leaveChat,
|
leaveChat,
|
||||||
removeChatFromChatLists,
|
removeChatFromChatLists,
|
||||||
@ -574,7 +575,7 @@ addActionHandler('loadFullChat', (global, actions, payload): ActionReturnType =>
|
|||||||
const loadChat = async () => {
|
const loadChat = async () => {
|
||||||
await loadFullChat(global, actions, chat, tabId);
|
await loadFullChat(global, actions, chat, tabId);
|
||||||
if (withPhotos) {
|
if (withPhotos) {
|
||||||
actions.loadProfilePhotos({ profileId: chatId });
|
actions.loadMoreProfilePhotos({ peerId: chatId, shouldInvalidateCache: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1727,17 +1728,12 @@ addActionHandler('updateChatPhoto', async (global, actions, payload): Promise<vo
|
|||||||
const { photo, chatId, tabId = getCurrentTabId() } = payload;
|
const { photo, chatId, tabId = getCurrentTabId() } = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
global = updateChat(global, chatId, { avatarHash: undefined });
|
|
||||||
global = updateChatFullInfo(global, chatId, { profilePhoto: undefined });
|
|
||||||
setGlobal(global);
|
|
||||||
// This method creates a new entry in photos array
|
|
||||||
await callApi('editChatPhoto', {
|
await callApi('editChatPhoto', {
|
||||||
chatId,
|
chatId,
|
||||||
accessHash: chat.accessHash,
|
accessHash: chat.accessHash,
|
||||||
photo,
|
photo,
|
||||||
});
|
});
|
||||||
// Explicitly delete the old photo reference
|
|
||||||
await callApi('deleteProfilePhotos', [photo]);
|
|
||||||
actions.loadFullChat({ chatId, tabId, withPhotos: true });
|
actions.loadFullChat({ chatId, tabId, withPhotos: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1745,35 +1741,22 @@ addActionHandler('deleteChatPhoto', async (global, actions, payload): Promise<vo
|
|||||||
const { photo, chatId, tabId = getCurrentTabId() } = payload;
|
const { photo, chatId, tabId = getCurrentTabId() } = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
const photosToDelete = [photo];
|
|
||||||
if (chat.avatarHash === photo.id) {
|
let isDeleted;
|
||||||
// Select next photo to set as avatar
|
if (photo.id === chat.avatarPhotoId) {
|
||||||
const nextPhoto = chat.photos?.[1];
|
isDeleted = await callApi('editChatPhoto', {
|
||||||
if (nextPhoto) {
|
|
||||||
photosToDelete.push(nextPhoto);
|
|
||||||
}
|
|
||||||
global = updateChat(global, chatId, { avatarHash: undefined });
|
|
||||||
global = updateChatFullInfo(global, chatId, { profilePhoto: undefined });
|
|
||||||
setGlobal(global);
|
|
||||||
// Set next photo as avatar
|
|
||||||
await callApi('editChatPhoto', {
|
|
||||||
chatId,
|
chatId,
|
||||||
accessHash: chat.accessHash,
|
accessHash: chat.accessHash,
|
||||||
photo: nextPhoto,
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
isDeleted = await callApi('deleteProfilePhotos', [photo]);
|
||||||
}
|
}
|
||||||
|
if (!isDeleted) return;
|
||||||
|
|
||||||
const { photos = [] } = chat;
|
|
||||||
|
|
||||||
const newPhotos = photos.filter((p) => photosToDelete.some((toDelete) => toDelete.id !== p.id));
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateChat(global, chatId, { photos: newPhotos });
|
global = deletePeerPhoto(global, chatId, photo.id);
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
// Delete references to the old photos
|
|
||||||
const result = await callApi('deleteProfilePhotos', photosToDelete);
|
|
||||||
if (!result) return;
|
|
||||||
actions.loadFullChat({ chatId, tabId, withPhotos: true });
|
actions.loadFullChat({ chatId, tabId, withPhotos: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -455,7 +455,7 @@ addActionHandler('uploadContactProfilePhoto', async (global, actions, payload):
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.loadProfilePhotos({ profileId: userId });
|
actions.loadMoreProfilePhotos({ peerId: userId, shouldInvalidateCache: true });
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateManagementProgress(global, ManagementProgress.Complete, tabId);
|
global = updateManagementProgress(global, ManagementProgress.Complete, tabId);
|
||||||
|
|||||||
@ -19,12 +19,13 @@ import { callApi } from '../../../api/gramjs';
|
|||||||
import { buildApiInputPrivacyRules } from '../../helpers';
|
import { buildApiInputPrivacyRules } from '../../helpers';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
addBlockedUser, addNotifyExceptions, addUsers, removeBlockedUser, replaceSettings, updateChat, updateChats,
|
addBlockedUser, addNotifyExceptions, addUsers, deletePeerPhoto,
|
||||||
|
removeBlockedUser, replaceSettings, updateChat, updateChats,
|
||||||
updateNotifySettings, updateUser, updateUserFullInfo,
|
updateNotifySettings, updateUser, updateUserFullInfo,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
selectChat, selectTabState, selectUser, selectUserFullInfo,
|
selectChat, selectTabState, selectUser,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
addActionHandler('updateProfile', async (global, actions, payload): Promise<void> => {
|
addActionHandler('updateProfile', async (global, actions, payload): Promise<void> => {
|
||||||
@ -110,7 +111,7 @@ addActionHandler('updateProfilePhoto', async (global, actions, payload): Promise
|
|||||||
const currentUser = selectUser(global, currentUserId);
|
const currentUser = selectUser(global, currentUserId);
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
|
|
||||||
global = updateUser(global, currentUserId, { avatarHash: undefined });
|
global = updateUser(global, currentUserId, { avatarPhotoId: undefined });
|
||||||
global = updateUserFullInfo(global, currentUserId, { profilePhoto: undefined });
|
global = updateUserFullInfo(global, currentUserId, { profilePhoto: undefined });
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
@ -129,32 +130,14 @@ addActionHandler('deleteProfilePhoto', async (global, actions, payload): Promise
|
|||||||
const { photo } = payload;
|
const { photo } = payload;
|
||||||
const { currentUserId } = global;
|
const { currentUserId } = global;
|
||||||
if (!currentUserId) return;
|
if (!currentUserId) return;
|
||||||
const currentUser = selectUser(global, currentUserId);
|
|
||||||
if (!currentUser) return;
|
|
||||||
|
|
||||||
const fullInfo = selectUserFullInfo(global, currentUserId);
|
const isDeleted = await callApi('deleteProfilePhotos', [photo]);
|
||||||
|
if (!isDeleted) return;
|
||||||
if (currentUser.avatarHash === photo.id || fullInfo?.profilePhoto?.id === photo.id) {
|
|
||||||
global = updateUser(global, currentUserId, { avatarHash: undefined });
|
|
||||||
global = updateUserFullInfo(global, currentUserId, { profilePhoto: undefined });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fullInfo?.fallbackPhoto?.id === photo.id) {
|
|
||||||
global = updateUserFullInfo(global, currentUserId, { fallbackPhoto: undefined });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fullInfo?.personalPhoto?.id === photo.id) {
|
|
||||||
global = updateUserFullInfo(global, currentUserId, { personalPhoto: undefined });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { photos = [] } = currentUser;
|
|
||||||
|
|
||||||
const newPhotos = photos.filter((p) => p.id !== photo.id);
|
|
||||||
global = updateUser(global, currentUserId, { photos: newPhotos });
|
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = deletePeerPhoto(global, currentUserId, photo.id);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
await callApi('deleteProfilePhotos', [photo]);
|
|
||||||
actions.loadFullUser({ userId: currentUserId, withPhotos: true });
|
actions.loadFullUser({ userId: currentUserId, withPhotos: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -20,9 +20,10 @@ import {
|
|||||||
addUserStatuses,
|
addUserStatuses,
|
||||||
closeNewContactDialog,
|
closeNewContactDialog,
|
||||||
replaceUserStatuses,
|
replaceUserStatuses,
|
||||||
updateChat,
|
|
||||||
updateChats,
|
updateChats,
|
||||||
updateManagementProgress,
|
updateManagementProgress,
|
||||||
|
updatePeerPhotos,
|
||||||
|
updatePeerPhotosIsLoading,
|
||||||
updateUser,
|
updateUser,
|
||||||
updateUserFullInfo,
|
updateUserFullInfo,
|
||||||
updateUsers,
|
updateUsers,
|
||||||
@ -30,14 +31,21 @@ import {
|
|||||||
updateUserSearchFetchingStatus,
|
updateUserSearchFetchingStatus,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import {
|
import {
|
||||||
selectChat, selectChatFullInfo, selectCurrentMessageList, selectPeer, selectTabState, selectUser, selectUserFullInfo,
|
selectChat,
|
||||||
|
selectChatFullInfo,
|
||||||
|
selectCurrentMessageList,
|
||||||
|
selectPeer,
|
||||||
|
selectTabState,
|
||||||
|
selectUser,
|
||||||
|
selectUserFullInfo,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
|
const PROFILE_PHOTOS_FIRST_LOAD_LIMIT = 10;
|
||||||
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
||||||
const runThrottledForSearch = throttle((cb) => cb(), 500, false);
|
const runThrottledForSearch = throttle((cb) => cb(), 500, false);
|
||||||
|
|
||||||
addActionHandler('loadFullUser', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadFullUser', async (global, actions, payload): Promise<void> => {
|
||||||
const { userId, withPhotos } = payload!;
|
const { userId, withPhotos } = payload;
|
||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return;
|
return;
|
||||||
@ -50,11 +58,11 @@ addActionHandler('loadFullUser', async (global, actions, payload): Promise<void>
|
|||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
const fullInfo = selectUserFullInfo(global, userId);
|
const fullInfo = selectUserFullInfo(global, userId);
|
||||||
const { user: newUser, fullInfo: newFullInfo } = result;
|
const { user: newUser, fullInfo: newFullInfo } = result;
|
||||||
const hasChangedAvatarHash = user.avatarHash !== newUser.avatarHash;
|
const hasChangedAvatar = user.avatarPhotoId !== newUser.avatarPhotoId;
|
||||||
const hasChangedProfilePhoto = fullInfo?.profilePhoto?.id !== newFullInfo?.profilePhoto?.id;
|
const hasChangedProfilePhoto = fullInfo?.profilePhoto?.id !== newFullInfo?.profilePhoto?.id;
|
||||||
const hasChangedFallbackPhoto = fullInfo?.fallbackPhoto?.id !== newFullInfo?.fallbackPhoto?.id;
|
const hasChangedFallbackPhoto = fullInfo?.fallbackPhoto?.id !== newFullInfo?.fallbackPhoto?.id;
|
||||||
const hasChangedPersonalPhoto = fullInfo?.personalPhoto?.id !== newFullInfo?.personalPhoto?.id;
|
const hasChangedPersonalPhoto = fullInfo?.personalPhoto?.id !== newFullInfo?.personalPhoto?.id;
|
||||||
const hasChangedPhoto = hasChangedAvatarHash
|
const hasChangedPhoto = hasChangedAvatar
|
||||||
|| hasChangedProfilePhoto
|
|| hasChangedProfilePhoto
|
||||||
|| hasChangedFallbackPhoto
|
|| hasChangedFallbackPhoto
|
||||||
|| hasChangedPersonalPhoto;
|
|| hasChangedPersonalPhoto;
|
||||||
@ -65,13 +73,13 @@ addActionHandler('loadFullUser', async (global, actions, payload): Promise<void>
|
|||||||
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
if (withPhotos || (user.photos?.length && hasChangedPhoto)) {
|
if (withPhotos || (user.profilePhotos?.count && hasChangedPhoto)) {
|
||||||
actions.loadProfilePhotos({ profileId: userId });
|
actions.loadMoreProfilePhotos({ peerId: userId, shouldInvalidateCache: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadUser', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadUser', async (global, actions, payload): Promise<void> => {
|
||||||
const { userId } = payload!;
|
const { userId } = payload;
|
||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return;
|
return;
|
||||||
@ -251,30 +259,36 @@ addActionHandler('deleteContact', async (global, actions, payload): Promise<void
|
|||||||
await callApi('deleteContact', { id, accessHash });
|
await callApi('deleteContact', { id, accessHash });
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadProfilePhotos', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadMoreProfilePhotos', async (global, actions, payload): Promise<void> => {
|
||||||
const { profileId } = payload!;
|
const { peerId, shouldInvalidateCache, isPreload } = payload;
|
||||||
const isPrivate = isUserId(profileId);
|
const isPrivate = isUserId(peerId);
|
||||||
|
|
||||||
let user = isPrivate ? selectUser(global, profileId) : undefined;
|
const user = isPrivate ? selectUser(global, peerId) : undefined;
|
||||||
const chat = !isPrivate ? selectChat(global, profileId) : undefined;
|
const chat = !isPrivate ? selectChat(global, peerId) : undefined;
|
||||||
if (!user && !chat) {
|
const peer = user || chat;
|
||||||
|
if (!peer?.avatarPhotoId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let userFullInfo = selectUserFullInfo(global, profileId);
|
if (peer.profilePhotos && !shouldInvalidateCache && (isPreload || !peer.profilePhotos.nextOffset)) return;
|
||||||
let chatFullInfo = selectChatFullInfo(global, profileId);
|
|
||||||
if (user && !userFullInfo?.profilePhoto) {
|
global = updatePeerPhotosIsLoading(global, peerId, true);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
|
let userFullInfo = selectUserFullInfo(global, peerId);
|
||||||
|
let chatFullInfo = selectChatFullInfo(global, peerId);
|
||||||
|
if (user && !userFullInfo) {
|
||||||
const { id, accessHash } = user;
|
const { id, accessHash } = user;
|
||||||
const result = await callApi('fetchFullUser', { id, accessHash });
|
const result = await callApi('fetchFullUser', { id, accessHash });
|
||||||
if (!result?.user) {
|
if (!result?.user) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
user = result.user;
|
|
||||||
userFullInfo = result.fullInfo;
|
userFullInfo = result.fullInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat && !chatFullInfo?.profilePhoto) {
|
if (chat && !chatFullInfo) {
|
||||||
const result = await callApi('fetchFullChat', chat);
|
const result = await callApi('fetchFullChat', chat);
|
||||||
if (!result?.fullInfo) {
|
if (!result?.fullInfo) {
|
||||||
return;
|
return;
|
||||||
@ -283,38 +297,42 @@ addActionHandler('loadProfilePhotos', async (global, actions, payload): Promise<
|
|||||||
chatFullInfo = result.fullInfo;
|
chatFullInfo = result.fullInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await callApi('fetchProfilePhotos', user, chat);
|
const peerFullInfo = userFullInfo || chatFullInfo;
|
||||||
|
if (!peerFullInfo) return;
|
||||||
|
|
||||||
|
const offset = peer.profilePhotos?.nextOffset;
|
||||||
|
const limit = !offset || isPreload || shouldInvalidateCache ? PROFILE_PHOTOS_FIRST_LOAD_LIMIT : undefined;
|
||||||
|
|
||||||
|
const result = await callApi('fetchProfilePhotos', {
|
||||||
|
peer,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
if (!result || !result.photos) {
|
if (!result || !result.photos) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
const userOrChat = user || chat;
|
const {
|
||||||
const { photos, users } = result;
|
photos, users, count, nextOffsetId,
|
||||||
|
} = result;
|
||||||
const fallbackPhoto = userFullInfo?.fallbackPhoto;
|
|
||||||
const personalPhoto = userFullInfo?.personalPhoto;
|
|
||||||
const chatCurrentPhoto = chatFullInfo?.profilePhoto;
|
|
||||||
if (fallbackPhoto) photos.push(fallbackPhoto);
|
|
||||||
if (personalPhoto) photos.unshift(personalPhoto);
|
|
||||||
if (chatCurrentPhoto && photos[0]?.id !== chatCurrentPhoto.id) photos.unshift(chatCurrentPhoto);
|
|
||||||
|
|
||||||
photos.sort((a) => (a.id === userOrChat?.avatarHash ? -1 : 1));
|
|
||||||
|
|
||||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||||
|
|
||||||
if (isPrivate) {
|
global = updatePeerPhotos(global, peerId, {
|
||||||
global = updateUser(global, profileId, { photos });
|
newPhotos: photos,
|
||||||
} else {
|
count,
|
||||||
global = updateChat(global, profileId, { photos });
|
nextOffset: nextOffsetId,
|
||||||
}
|
fullInfo: peerFullInfo,
|
||||||
|
shouldInvalidateCache,
|
||||||
|
});
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('setUserSearchQuery', (global, actions, payload): ActionReturnType => {
|
addActionHandler('setUserSearchQuery', (global, actions, payload): ActionReturnType => {
|
||||||
const { query, tabId = getCurrentTabId() } = payload!;
|
const { query, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
if (!query) return;
|
if (!query) return;
|
||||||
|
|
||||||
@ -371,7 +389,7 @@ addActionHandler('importContact', async (global, actions, payload): Promise<void
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('reportSpam', (global, actions, payload): ActionReturnType => {
|
addActionHandler('reportSpam', (global, actions, payload): ActionReturnType => {
|
||||||
const { chatId } = payload!;
|
const { chatId } = payload;
|
||||||
const peer = selectPeer(global, chatId);
|
const peer = selectPeer(global, chatId);
|
||||||
if (!peer) {
|
if (!peer) {
|
||||||
return;
|
return;
|
||||||
@ -381,13 +399,13 @@ addActionHandler('reportSpam', (global, actions, payload): ActionReturnType => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('setEmojiStatus', (global, actions, payload): ActionReturnType => {
|
addActionHandler('setEmojiStatus', (global, actions, payload): ActionReturnType => {
|
||||||
const { emojiStatus, expires } = payload!;
|
const { emojiStatus, expires } = payload;
|
||||||
|
|
||||||
void callApi('updateEmojiStatus', emojiStatus, expires);
|
void callApi('updateEmojiStatus', emojiStatus, expires);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('saveCloseFriends', async (global, actions, payload): Promise<void> => {
|
addActionHandler('saveCloseFriends', async (global, actions, payload): Promise<void> => {
|
||||||
const { userIds } = payload!;
|
const { userIds } = payload;
|
||||||
|
|
||||||
const result = await callApi('saveCloseFriends', userIds);
|
const result = await callApi('saveCloseFriends', userIds);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
addUnreadMentions,
|
addUnreadMentions,
|
||||||
deleteChatMessages,
|
deleteChatMessages,
|
||||||
|
deletePeerPhoto,
|
||||||
leaveChat,
|
leaveChat,
|
||||||
removeUnreadMentions,
|
removeUnreadMentions,
|
||||||
replaceThreadParam,
|
replaceThreadParam,
|
||||||
@ -35,6 +36,7 @@ import {
|
|||||||
selectCommonBoxChatId,
|
selectCommonBoxChatId,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectIsChatListed,
|
selectIsChatListed,
|
||||||
|
selectPeer,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadParam,
|
selectThreadParam,
|
||||||
selectTopicFromMessage,
|
selectTopicFromMessage,
|
||||||
@ -57,7 +59,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
|
|
||||||
const localChat = selectChat(global, update.id);
|
const localChat = selectChat(global, update.id);
|
||||||
|
|
||||||
global = updateChat(global, update.id, update.chat, update.newProfilePhoto);
|
global = updateChat(global, update.id, update.chat);
|
||||||
|
|
||||||
if (localChat?.areStoriesHidden !== update.chat.areStoriesHidden) {
|
if (localChat?.areStoriesHidden !== update.chat.areStoriesHidden) {
|
||||||
global = updatePeerStoriesHidden(global, update.id, update.chat.areStoriesHidden || false);
|
global = updatePeerStoriesHidden(global, update.id, update.chat.areStoriesHidden || false);
|
||||||
@ -534,6 +536,43 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
isForumAsMessages: isEnabled,
|
isForumAsMessages: isEnabled,
|
||||||
});
|
});
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'updateNewProfilePhoto': {
|
||||||
|
const { peerId, photo } = update;
|
||||||
|
|
||||||
|
global = updateChat(global, peerId, {
|
||||||
|
avatarPhotoId: photo.id,
|
||||||
|
});
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
actions.loadMoreProfilePhotos({ peerId, shouldInvalidateCache: true });
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'updateDeleteProfilePhoto': {
|
||||||
|
const { peerId, photoId } = update;
|
||||||
|
|
||||||
|
const peer = selectPeer(global, peerId);
|
||||||
|
if (!peer) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!photoId || peer.avatarPhotoId === photoId) {
|
||||||
|
global = updateChat(global, peerId, {
|
||||||
|
avatarPhotoId: undefined,
|
||||||
|
profilePhotos: undefined,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
global = deletePeerPhoto(global, peerId, photoId);
|
||||||
|
}
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
actions.loadMoreProfilePhotos({ peerId, shouldInvalidateCache: true });
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import {
|
|||||||
clearMessageTranslation,
|
clearMessageTranslation,
|
||||||
deleteChatMessages,
|
deleteChatMessages,
|
||||||
deleteChatScheduledMessages,
|
deleteChatScheduledMessages,
|
||||||
|
deletePeerPhoto,
|
||||||
deleteQuickReply,
|
deleteQuickReply,
|
||||||
deleteQuickReplyMessages,
|
deleteQuickReplyMessages,
|
||||||
deleteTopic,
|
deleteTopic,
|
||||||
@ -1069,6 +1070,10 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.content.action?.photo) {
|
||||||
|
global = deletePeerPhoto(global, chatId, message.content.action.photo.id, true);
|
||||||
|
}
|
||||||
|
|
||||||
global = updateThreadUnread(global, actions, message, true);
|
global = updateThreadUnread(global, actions, message, true);
|
||||||
|
|
||||||
const threadId = selectThreadIdFromMessage(global, message);
|
const threadId = selectThreadIdFromMessage(global, message);
|
||||||
@ -1144,6 +1149,10 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message?.content.action?.photo) {
|
||||||
|
global = deletePeerPhoto(global, commonBoxChatId, message.content.action.photo.id, true);
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = deleteChatMessages(global, commonBoxChatId, [id]);
|
global = deleteChatMessages(global, commonBoxChatId, [id]);
|
||||||
|
|||||||
@ -104,22 +104,22 @@ export function getChatLink(chat: ApiChat) {
|
|||||||
export function getChatAvatarHash(
|
export function getChatAvatarHash(
|
||||||
owner: ApiPeer,
|
owner: ApiPeer,
|
||||||
size: 'normal' | 'big' = 'normal',
|
size: 'normal' | 'big' = 'normal',
|
||||||
avatarHash = owner.avatarHash,
|
avatarPhotoId = owner.avatarPhotoId,
|
||||||
) {
|
) {
|
||||||
if (!avatarHash) {
|
if (!avatarPhotoId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 'big':
|
case 'big':
|
||||||
return `profile${owner.id}?${avatarHash}`;
|
return `profile${owner.id}?${avatarPhotoId}`;
|
||||||
default:
|
default:
|
||||||
return `avatar${owner.id}?${avatarHash}`;
|
return `avatar${owner.id}?${avatarPhotoId}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isChatAdmin(chat: ApiChat) {
|
export function isChatAdmin(chat: ApiChat) {
|
||||||
return Boolean(chat.adminRights);
|
return Boolean(chat.adminRights || chat.isCreator);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHasAdminRight(chat: ApiChat, key: keyof ApiChatAdminRights) {
|
export function getHasAdminRight(chat: ApiChat, key: keyof ApiChatAdminRights) {
|
||||||
|
|||||||
@ -284,14 +284,18 @@ export function getPhotoMediaHash(photo: ApiPhoto | ApiDocument, target: Target,
|
|||||||
case 'preview':
|
case 'preview':
|
||||||
return `${base}?size=${isAction ? 'b' : 'x'}`;
|
return `${base}?size=${isAction ? 'b' : 'x'}`;
|
||||||
case 'download':
|
case 'download':
|
||||||
return !isVideo ? base : getVideoAvatarMediaHash(photo);
|
return !isVideo ? base : getVideoProfilePhotoMediaHash(photo);
|
||||||
case 'full':
|
case 'full':
|
||||||
default:
|
default:
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getVideoAvatarMediaHash(photo: ApiPhoto) {
|
export function getProfilePhotoMediaHash(photo: ApiPhoto) {
|
||||||
|
return `photo${photo.id}?size=c`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVideoProfilePhotoMediaHash(photo: ApiPhoto) {
|
||||||
if (!photo.isVideo) return undefined;
|
if (!photo.isVideo) return undefined;
|
||||||
return `photo${photo.id}?size=u`;
|
return `photo${photo.id}?size=u`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiChatFullInfo, ApiChatMember, ApiPhoto, ApiTopic,
|
ApiChat, ApiChatFullInfo, ApiChatMember, ApiTopic,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { ChatListType, GlobalState } from '../types';
|
import type { ChatListType, GlobalState } from '../types';
|
||||||
|
|
||||||
@ -138,12 +138,11 @@ export function removeUnreadMentions<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateChat<T extends GlobalState>(
|
export function updateChat<T extends GlobalState>(
|
||||||
global: T, chatId: string, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
global: T, chatId: string, chatUpdate: Partial<ApiChat>, noOmitUnreadReactionCount = false,
|
||||||
noOmitUnreadReactionCount = false,
|
|
||||||
): T {
|
): T {
|
||||||
const { byId } = global.chats;
|
const { byId } = global.chats;
|
||||||
|
|
||||||
const updatedChat = getUpdatedChat(global, chatId, chatUpdate, photo, noOmitUnreadReactionCount);
|
const updatedChat = getUpdatedChat(global, chatId, chatUpdate, noOmitUnreadReactionCount);
|
||||||
if (!updatedChat) {
|
if (!updatedChat) {
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
@ -257,7 +256,7 @@ export function addChats<T extends GlobalState>(global: T, newById: Record<strin
|
|||||||
|
|
||||||
// @optimization Don't spread/unspread global for each element, do it in a batch
|
// @optimization Don't spread/unspread global for each element, do it in a batch
|
||||||
function getUpdatedChat<T extends GlobalState>(
|
function getUpdatedChat<T extends GlobalState>(
|
||||||
global: T, chatId: string, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
global: T, chatId: string, chatUpdate: Partial<ApiChat>,
|
||||||
noOmitUnreadReactionCount = false,
|
noOmitUnreadReactionCount = false,
|
||||||
) {
|
) {
|
||||||
const { byId } = global.chats;
|
const { byId } = global.chats;
|
||||||
@ -279,7 +278,6 @@ function getUpdatedChat<T extends GlobalState>(
|
|||||||
const updatedChat: ApiChat = {
|
const updatedChat: ApiChat = {
|
||||||
...chat,
|
...chat,
|
||||||
...omit(chatUpdate, omitProps),
|
...omit(chatUpdate, omitProps),
|
||||||
...(photo && { photos: [photo, ...(chat.photos || [])] }),
|
|
||||||
} as ApiChat;
|
} as ApiChat;
|
||||||
|
|
||||||
if (!updatedChat.id || !updatedChat.type) {
|
if (!updatedChat.id || !updatedChat.type) {
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiChatFullInfo, ApiUser, ApiUserFullInfo,
|
ApiChat, ApiChatFullInfo, ApiPhoto, ApiUser, ApiUserFullInfo,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { GlobalState } from '../types';
|
import type { GlobalState } from '../types';
|
||||||
|
|
||||||
import { isUserId } from '../helpers';
|
import { uniqueByField } from '../../util/iteratees';
|
||||||
|
import { isChatChannel, isUserId } from '../helpers';
|
||||||
|
import { selectChatFullInfo, selectPeer, selectUserFullInfo } from '../selectors';
|
||||||
import { updateChat, updateChatFullInfo } from './chats';
|
import { updateChat, updateChatFullInfo } from './chats';
|
||||||
import { updateUser, updateUserFullInfo } from './users';
|
import { updateUser, updateUserFullInfo } from './users';
|
||||||
|
|
||||||
@ -30,3 +32,137 @@ export function updatePeerFullInfo<T extends GlobalState>(
|
|||||||
|
|
||||||
return updateChatFullInfo(global, peerId, peerFullInfoUpdate);
|
return updateChatFullInfo(global, peerId, peerFullInfoUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updatePeerPhotosIsLoading<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
peerId: string,
|
||||||
|
isLoading: boolean,
|
||||||
|
) {
|
||||||
|
const peer = selectPeer(global, peerId);
|
||||||
|
if (!peer || !peer.profilePhotos) {
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatePeer(global, peerId, {
|
||||||
|
profilePhotos: {
|
||||||
|
...peer.profilePhotos,
|
||||||
|
isLoading,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updatePeerPhotos<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
peerId: string,
|
||||||
|
update: {
|
||||||
|
newPhotos: ApiPhoto[];
|
||||||
|
count: number;
|
||||||
|
nextOffset?: number;
|
||||||
|
fullInfo: ApiChatFullInfo | ApiUserFullInfo;
|
||||||
|
shouldInvalidateCache?: boolean;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const peer = selectPeer(global, peerId);
|
||||||
|
if (!peer) {
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
newPhotos, count, nextOffset, fullInfo, shouldInvalidateCache,
|
||||||
|
} = update;
|
||||||
|
const currentPhotos = peer.profilePhotos;
|
||||||
|
const profilePhoto = fullInfo.profilePhoto;
|
||||||
|
const fallbackPhoto = 'fallbackPhoto' in fullInfo ? fullInfo.fallbackPhoto : undefined;
|
||||||
|
const personalPhoto = 'personalPhoto' in fullInfo ? fullInfo.personalPhoto : undefined;
|
||||||
|
|
||||||
|
if (!currentPhotos || shouldInvalidateCache) {
|
||||||
|
// In some channels, last service message with photo change is deleted, so we need to patch it in
|
||||||
|
if (profilePhoto && profilePhoto.id !== newPhotos[0]?.id) {
|
||||||
|
newPhotos.unshift(profilePhoto);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (personalPhoto && personalPhoto.id !== newPhotos[0]?.id) {
|
||||||
|
newPhotos.unshift(personalPhoto);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fallbackPhoto) {
|
||||||
|
newPhotos.push(fallbackPhoto);
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatePeer(global, peerId, {
|
||||||
|
profilePhotos: {
|
||||||
|
fallbackPhoto,
|
||||||
|
personalPhoto,
|
||||||
|
photos: newPhotos,
|
||||||
|
count,
|
||||||
|
nextOffset,
|
||||||
|
isLoading: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasFallbackPhoto = currentPhotos.photos[currentPhotos.photos.length - 1].id === fallbackPhoto?.id;
|
||||||
|
const currentPhotoArray = hasFallbackPhoto ? currentPhotos.photos.slice(0, -1) : currentPhotos.photos;
|
||||||
|
|
||||||
|
const photos = uniqueByField([...currentPhotoArray, ...newPhotos, fallbackPhoto].filter(Boolean), 'id');
|
||||||
|
return updatePeer(global, peerId, {
|
||||||
|
profilePhotos: {
|
||||||
|
fallbackPhoto,
|
||||||
|
personalPhoto,
|
||||||
|
photos,
|
||||||
|
count,
|
||||||
|
nextOffset,
|
||||||
|
isLoading: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePeerPhoto<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
peerId: string,
|
||||||
|
photoId: string,
|
||||||
|
isFromActionMessage?: boolean,
|
||||||
|
) {
|
||||||
|
const peer = selectPeer(global, peerId);
|
||||||
|
if (!peer || !peer.profilePhotos) {
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
const isChannel = 'title' in peer && isChatChannel(peer);
|
||||||
|
|
||||||
|
const userFullInfo = selectUserFullInfo(global, peerId);
|
||||||
|
const chatFullInfo = selectChatFullInfo(global, peerId);
|
||||||
|
|
||||||
|
const isAvatar = peer.avatarPhotoId === photoId && (!isChannel || isFromActionMessage);
|
||||||
|
const nextAvatarPhoto = isAvatar ? peer.profilePhotos.photos[1] : undefined;
|
||||||
|
|
||||||
|
if (userFullInfo) {
|
||||||
|
const newFallbackPhoto = userFullInfo.fallbackPhoto?.id === photoId ? undefined : userFullInfo.fallbackPhoto;
|
||||||
|
const newPersonalPhoto = userFullInfo.personalPhoto?.id === photoId ? undefined : userFullInfo.personalPhoto;
|
||||||
|
const newProfilePhoto = userFullInfo.profilePhoto?.id === photoId ? nextAvatarPhoto : userFullInfo.profilePhoto;
|
||||||
|
global = updateUserFullInfo(global, peerId, {
|
||||||
|
fallbackPhoto: newFallbackPhoto,
|
||||||
|
personalPhoto: newPersonalPhoto,
|
||||||
|
profilePhoto: newProfilePhoto,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chatFullInfo) {
|
||||||
|
const newProfilePhoto = chatFullInfo.profilePhoto?.id === photoId ? nextAvatarPhoto : chatFullInfo.profilePhoto;
|
||||||
|
global = updateChatFullInfo(global, peerId, {
|
||||||
|
profilePhoto: newProfilePhoto,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarPhotoId = isAvatar ? nextAvatarPhoto?.id : peer.avatarPhotoId;
|
||||||
|
const shouldKeepInPhotos = isAvatar && 'title' in peer && isChatChannel(peer);
|
||||||
|
const photos = shouldKeepInPhotos
|
||||||
|
? peer.profilePhotos.photos.filter((photo) => photo.id !== photoId) : peer.profilePhotos.photos.slice();
|
||||||
|
return updatePeer(global, peerId, {
|
||||||
|
avatarPhotoId,
|
||||||
|
profilePhotos: avatarPhotoId ? {
|
||||||
|
...peer.profilePhotos,
|
||||||
|
photos,
|
||||||
|
count: peer.profilePhotos.count - 1,
|
||||||
|
} : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -77,5 +77,5 @@ export function addMessageReaction<T extends GlobalState>(
|
|||||||
export function updateUnreadReactions<T extends GlobalState>(
|
export function updateUnreadReactions<T extends GlobalState>(
|
||||||
global: T, chatId: string, update: Pick<ApiChat, 'unreadReactionsCount' | 'unreadReactions'>,
|
global: T, chatId: string, update: Pick<ApiChat, 'unreadReactionsCount' | 'unreadReactions'>,
|
||||||
): T {
|
): T {
|
||||||
return updateChat(global, chatId, update, undefined, true);
|
return updateChat(global, chatId, update, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2608,8 +2608,10 @@ export interface ActionPayloads {
|
|||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
shouldSharePhoneNumber?: boolean;
|
shouldSharePhoneNumber?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
loadProfilePhotos: {
|
loadMoreProfilePhotos: {
|
||||||
profileId: string;
|
peerId: string;
|
||||||
|
isPreload?: boolean;
|
||||||
|
shouldInvalidateCache?: boolean;
|
||||||
};
|
};
|
||||||
deleteProfilePhoto: {
|
deleteProfilePhoto: {
|
||||||
photo: ApiPhoto;
|
photo: ApiPhoto;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user