Media Viewer: Support selecting main photo for profile and chats (#2162)

This commit is contained in:
Alexander Zinchuk 2022-12-06 13:29:46 +01:00
parent be044c2aa1
commit f5b8b09f37
14 changed files with 236 additions and 59 deletions

View File

@ -12,7 +12,7 @@ import type {
ApiChatBannedRights, ApiChatBannedRights,
ApiChatAdminRights, ApiChatAdminRights,
ApiGroupCall, ApiGroupCall,
ApiUserStatus, ApiUserStatus, ApiPhoto,
} from '../../types'; } from '../../types';
import { import {
@ -41,7 +41,7 @@ import {
isMessageWithMedia, isMessageWithMedia,
buildChatBannedRights, buildChatBannedRights,
buildChatAdminRights, buildChatAdminRights,
buildInputChatReactions, buildInputChatReactions, buildInputPhoto,
} from '../gramjsBuilders'; } from '../gramjsBuilders';
import { addEntitiesWithPhotosToLocalDb, addMessageToLocalDb, addPhotoToLocalDb } from '../helpers'; import { addEntitiesWithPhotosToLocalDb, addMessageToLocalDb, addPhotoToLocalDb } from '../helpers';
import { buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import { buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
@ -693,24 +693,33 @@ export async function createGroupChat({
export async function editChatPhoto({ export async function editChatPhoto({
chatId, accessHash, photo, chatId, accessHash, photo,
}: { }: {
chatId: string; accessHash?: string; photo: File; chatId: string; accessHash?: string; photo?: File | ApiPhoto;
}) { }) {
const uploadedPhoto = await uploadFile(photo);
const inputEntity = buildInputEntity(chatId, accessHash); const inputEntity = buildInputEntity(chatId, accessHash);
let inputPhoto: GramJs.TypeInputChatPhoto;
if (photo instanceof File) {
const uploadedPhoto = await uploadFile(photo);
inputPhoto = new GramJs.InputChatUploadedPhoto({
file: uploadedPhoto,
});
} else if (photo) {
const photoId = buildInputPhoto(photo);
if (!photoId) return false;
inputPhoto = new GramJs.InputChatPhoto({
id: photoId,
});
} else {
inputPhoto = new GramJs.InputChatPhotoEmpty();
}
return invokeRequest( return invokeRequest(
inputEntity instanceof GramJs.InputChannel inputEntity instanceof GramJs.InputChannel
? new GramJs.channels.EditPhoto({ ? new GramJs.channels.EditPhoto({
channel: inputEntity as GramJs.InputChannel, channel: inputEntity as GramJs.InputChannel,
photo: new GramJs.InputChatUploadedPhoto({ photo: inputPhoto,
file: uploadedPhoto,
}),
}) })
: new GramJs.messages.EditChatPhoto({ : new GramJs.messages.EditChatPhoto({
chatId: inputEntity as BigInt.BigInteger, chatId: inputEntity as BigInt.BigInteger,
photo: new GramJs.InputChatUploadedPhoto({ photo: inputPhoto,
file: uploadedPhoto,
}),
}), }),
true, true,
); );

View File

@ -54,7 +54,7 @@ export {
export { export {
updateProfile, checkUsername, updateUsername, fetchBlockedContacts, blockContact, unblockContact, updateProfile, checkUsername, updateUsername, fetchBlockedContacts, blockContact, unblockContact,
updateProfilePhoto, uploadProfilePhoto, deleteProfilePhoto, fetchWallpapers, uploadWallpaper, updateProfilePhoto, uploadProfilePhoto, deleteProfilePhotos, fetchWallpapers, uploadWallpaper,
fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations, fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations,
fetchWebAuthorizations, terminateWebAuthorization, terminateAllWebAuthorizations, fetchWebAuthorizations, terminateWebAuthorization, terminateAllWebAuthorizations,
fetchNotificationExceptions, fetchNotificationSettings, updateContactSignUpNotification, updateNotificationSettings, fetchNotificationExceptions, fetchNotificationSettings, updateContactSignUpNotification, updateNotificationSettings,

View File

@ -21,6 +21,7 @@ import {
buildPrivacyRules, buildPrivacyRules,
} from '../apiBuilders/misc'; } from '../apiBuilders/misc';
import { buildApiPhoto } from '../apiBuilders/common';
import { buildApiUser } from '../apiBuilders/users'; import { buildApiUser } from '../apiBuilders/users';
import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiChatFromPreview } from '../apiBuilders/chats';
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
@ -32,7 +33,7 @@ import {
import { getClient, invokeRequest, uploadFile } from './client'; import { getClient, invokeRequest, uploadFile } from './client';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
import { getServerTime } from '../../../util/serverTime'; import { getServerTime } from '../../../util/serverTime';
import { addEntitiesWithPhotosToLocalDb } from '../helpers'; import { addEntitiesWithPhotosToLocalDb, addPhotoToLocalDb } from '../helpers';
import localDb from '../localDb'; import localDb from '../localDb';
const MAX_INT_32 = 2 ** 31 - 1; const MAX_INT_32 = 2 ** 31 - 1;
@ -67,26 +68,32 @@ export function updateUsername(username: string) {
return invokeRequest(new GramJs.account.UpdateUsername({ username }), true); return invokeRequest(new GramJs.account.UpdateUsername({ username }), true);
} }
export async function updateProfilePhoto(file: File) { export async function updateProfilePhoto(photo?: ApiPhoto) {
const photoId = photo ? buildInputPhoto(photo) : new GramJs.InputPhotoEmpty();
const result = await invokeRequest(new GramJs.photos.UpdateProfilePhoto({
id: photoId,
}));
if (result?.photo instanceof GramJs.Photo) {
addPhotoToLocalDb(result.photo);
return buildApiPhoto(result.photo);
}
return undefined;
}
export async function uploadProfilePhoto(file: File) {
const inputFile = await uploadFile(file); const inputFile = await uploadFile(file);
return invokeRequest(new GramJs.photos.UploadProfilePhoto({ return invokeRequest(new GramJs.photos.UploadProfilePhoto({
file: inputFile, file: inputFile,
}), true); }), true);
} }
export async function uploadProfilePhoto(file: File) { export async function deleteProfilePhotos(photos: ApiPhoto[]) {
const inputFile = await uploadFile(file); const photoIds = photos.map(buildInputPhoto).filter(Boolean);
await invokeRequest(new GramJs.photos.UploadProfilePhoto({ const isDeleted = await invokeRequest(new GramJs.photos.DeletePhotos({ id: photoIds }), true);
file: inputFile,
}));
}
export async function deleteProfilePhoto(photo: ApiPhoto) {
const photoId = buildInputPhoto(photo);
if (!photoId) return false;
const isDeleted = await invokeRequest(new GramJs.photos.DeletePhotos({ id: [photoId] }), true);
if (isDeleted) { if (isDeleted) {
delete localDb.photos[photo.id]; photos.forEach((photo) => {
delete localDb.photos[photo.id];
});
} }
return isDeleted; return isDeleted;
} }

View File

@ -7,6 +7,7 @@ import useLang from '../../hooks/useLang';
import Modal from '../ui/Modal'; import Modal from '../ui/Modal';
import Button from '../ui/Button'; import Button from '../ui/Button';
import { isUserId } from '../../global/helpers';
export type OwnProps = { export type OwnProps = {
isOpen: boolean; isOpen: boolean;
@ -25,13 +26,21 @@ const DeleteProfilePhotoModal: FC<OwnProps> = ({
}) => { }) => {
const { const {
deleteProfilePhoto, deleteProfilePhoto,
deleteChatPhoto,
} = getActions(); } = getActions();
const handleDeletePhoto = useCallback(() => { const handleDeletePhoto = useCallback(() => {
onConfirm?.(); onConfirm?.();
deleteProfilePhoto({ photo, profileId }); if (isUserId(profileId)) {
deleteProfilePhoto({ photo });
} else {
deleteChatPhoto({
photo,
chatId: profileId,
});
}
onClose(); onClose();
}, [onConfirm, deleteProfilePhoto, photo, profileId, onClose]); }, [onConfirm, profileId, onClose, deleteProfilePhoto, photo, deleteChatPhoto]);
const lang = useLang(); const lang = useLang();

View File

@ -179,7 +179,9 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
} }
function renderPhoto(isActive?: boolean) { function renderPhoto(isActive?: boolean) {
const photo = !isSavedMessages && photos.length > 0 ? photos[currentPhotoIndex] : undefined; const photo = !isSavedMessages && photos.length > 0
? photos[currentPhotoIndex]
: undefined;
return ( return (
<ProfilePhoto <ProfilePhoto
key={currentPhotoIndex} key={currentPhotoIndex}

View File

@ -11,7 +11,7 @@ import { MediaViewerOrigin } from '../../types';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import { import {
getChatMediaMessageIds, getChatMediaMessageIds, isChatAdmin,
} from '../../global/helpers'; } from '../../global/helpers';
import { import {
selectChat, selectChat,
@ -59,7 +59,7 @@ type StateProps = {
mediaId?: number; mediaId?: number;
senderId?: string; senderId?: string;
isChatWithSelf?: boolean; isChatWithSelf?: boolean;
canDeleteMedia?: boolean; canUpdateMedia?: boolean;
origin?: MediaViewerOrigin; origin?: MediaViewerOrigin;
avatarOwner?: ApiChat | ApiUser; avatarOwner?: ApiChat | ApiUser;
message?: ApiMessage; message?: ApiMessage;
@ -78,7 +78,7 @@ const MediaViewer: FC<StateProps> = ({
mediaId, mediaId,
senderId, senderId,
isChatWithSelf, isChatWithSelf,
canDeleteMedia, canUpdateMedia,
origin, origin,
avatarOwner, avatarOwner,
message, message,
@ -336,11 +336,12 @@ const MediaViewer: FC<StateProps> = ({
mediaData={bestData} mediaData={bestData}
isVideo={isVideo} isVideo={isVideo}
message={message} message={message}
canDeleteAvatar={canDeleteMedia && !!avatarPhoto} canUpdateMedia={canUpdateMedia}
avatarPhoto={avatarPhoto} avatarPhoto={avatarPhoto}
avatarOwnerId={avatarOwner?.id} avatarOwner={avatarOwner}
fileName={fileName} fileName={fileName}
canReport={canReport} canReport={canReport}
selectMedia={selectMedia}
onBeforeDelete={handleBeforeDelete} onBeforeDelete={handleBeforeDelete}
onReport={openReportModal} onReport={openReportModal}
onCloseMediaViewer={handleClose} onCloseMediaViewer={handleClose}
@ -422,10 +423,13 @@ export default memo(withGlobal(
if (avatarOwnerId) { if (avatarOwnerId) {
const user = selectUser(global, avatarOwnerId); const user = selectUser(global, avatarOwnerId);
const chat = selectChat(global, avatarOwnerId); const chat = selectChat(global, avatarOwnerId);
let canDeleteMedia = false; let canUpdateMedia = false;
if (user) canDeleteMedia = avatarOwnerId === currentUserId; if (user) {
// TODO Support deleting chat photos canUpdateMedia = avatarOwnerId === currentUserId;
// if (chat) canDeleteMedia = isChatAdmin(chat); } else if (chat) {
canUpdateMedia = isChatAdmin(chat);
}
isChatWithSelf = selectIsChatWithSelf(global, avatarOwnerId); isChatWithSelf = selectIsChatWithSelf(global, avatarOwnerId);
return { return {
@ -433,7 +437,7 @@ export default memo(withGlobal(
senderId: avatarOwnerId, senderId: avatarOwnerId,
avatarOwner: user || chat, avatarOwner: user || chat,
isChatWithSelf, isChatWithSelf,
canDeleteMedia, canUpdateMedia,
animationLevel, animationLevel,
origin, origin,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,

View File

@ -7,7 +7,7 @@ import { getActions, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import type { import type {
ApiMessage, ApiPhoto, ApiMessage, ApiPhoto, ApiChat, ApiUser,
} from '../../api/types'; } from '../../api/types';
import type { MessageListType } from '../../global/types'; import type { MessageListType } from '../../global/types';
import type { MenuItemProps } from '../ui/MenuItem'; import type { MenuItemProps } from '../ui/MenuItem';
@ -20,7 +20,7 @@ import {
selectIsChatProtected, selectIsChatProtected,
} from '../../global/selectors'; } from '../../global/selectors';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
import { getMessageMediaFormat, getMessageMediaHash } from '../../global/helpers'; import { getMessageMediaFormat, getMessageMediaHash, isUserId } from '../../global/helpers';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress'; import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
@ -40,7 +40,9 @@ type StateProps = {
isProtected?: boolean; isProtected?: boolean;
isChatProtected?: boolean; isChatProtected?: boolean;
canDelete?: boolean; canDelete?: boolean;
canUpdate?: boolean;
messageListType?: MessageListType; messageListType?: MessageListType;
avatarOwnerId?: string;
}; };
type OwnProps = { type OwnProps = {
@ -48,11 +50,13 @@ type OwnProps = {
isVideo: boolean; isVideo: boolean;
zoomLevelChange: number; zoomLevelChange: number;
message?: ApiMessage; message?: ApiMessage;
canDeleteAvatar?: boolean; canUpdateMedia?: boolean;
isSingleMedia?: boolean;
avatarPhoto?: ApiPhoto; avatarPhoto?: ApiPhoto;
avatarOwnerId?: string; avatarOwner?: ApiChat | ApiUser;
fileName?: string; fileName?: string;
canReport?: boolean; canReport?: boolean;
selectMedia: (mediaId?: number) => void;
onReport: NoneToVoidFunction; onReport: NoneToVoidFunction;
onBeforeDelete: NoneToVoidFunction; onBeforeDelete: NoneToVoidFunction;
onCloseMediaViewer: NoneToVoidFunction; onCloseMediaViewer: NoneToVoidFunction;
@ -73,7 +77,9 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
canReport, canReport,
zoomLevelChange, zoomLevelChange,
canDelete, canDelete,
canUpdate,
messageListType, messageListType,
selectMedia,
onReport, onReport,
onCloseMediaViewer, onCloseMediaViewer,
onBeforeDelete, onBeforeDelete,
@ -85,6 +91,8 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
const { const {
downloadMessageMedia, downloadMessageMedia,
cancelMessageMediaDownload, cancelMessageMediaDownload,
updateProfilePhoto,
updateChatPhoto,
} = getActions(); } = getActions();
const { loadProgress: downloadProgress } = useMediaWithLoadProgress( const { loadProgress: downloadProgress } = useMediaWithLoadProgress(
@ -111,6 +119,16 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
setZoomLevelChange(change + 1); setZoomLevelChange(change + 1);
}, [setZoomLevelChange, zoomLevelChange]); }, [setZoomLevelChange, zoomLevelChange]);
const handleUpdate = useCallback(() => {
if (!avatarPhoto || !avatarOwnerId) return;
if (isUserId(avatarOwnerId)) {
updateProfilePhoto({ photo: avatarPhoto });
} else {
updateChatPhoto({ chatId: avatarOwnerId, photo: avatarPhoto });
}
selectMedia(0);
}, [avatarPhoto, avatarOwnerId, selectMedia, updateProfilePhoto, updateChatPhoto]);
const lang = useLang(); const lang = useLang();
const MenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => { const MenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
@ -217,6 +235,14 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
}); });
} }
if (canUpdate) {
menuItems.push({
icon: 'copy-media',
onClick: handleUpdate,
children: lang('ProfilePhoto.SetMainPhoto'),
});
}
if (canDelete) { if (canDelete) {
menuItems.push({ menuItems.push({
icon: 'delete', icon: 'delete',
@ -298,6 +324,17 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
<i className="icon-flag" /> <i className="icon-flag" />
</Button> </Button>
)} )}
{canUpdate && (
<Button
round
size="smaller"
color="translucent-white"
ariaLabel={lang('ProfilePhoto.SetMainPhoto')}
onClick={handleUpdate}
>
<i className="icon-copy-media" />
</Button>
)}
{canDelete && ( {canDelete && (
<Button <Button
round round
@ -324,7 +361,9 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
}; };
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { message, canDeleteAvatar }): StateProps => { (global, {
message, canUpdateMedia, avatarPhoto, avatarOwner,
}): StateProps => {
const currentMessageList = selectCurrentMessageList(global); const currentMessageList = selectCurrentMessageList(global);
const { threadId } = selectCurrentMessageList(global) || {}; const { threadId } = selectCurrentMessageList(global) || {};
const isDownloading = message ? selectIsDownloading(global, message) : false; const isDownloading = message ? selectIsDownloading(global, message) : false;
@ -332,7 +371,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 canDeleteAvatar = canUpdateMedia && !!avatarPhoto;
const canDelete = canDeleteMessage || canDeleteAvatar; const canDelete = canDeleteMessage || canDeleteAvatar;
const canUpdate = canUpdateMedia && !!avatarPhoto && !isCurrentAvatar;
const messageListType = currentMessageList?.type; const messageListType = currentMessageList?.type;
return { return {
@ -340,7 +382,9 @@ export default memo(withGlobal<OwnProps>(
isProtected, isProtected,
isChatProtected, isChatProtected,
canDelete, canDelete,
canUpdate,
messageListType, messageListType,
avatarOwnerId: avatarOwner?.id,
}; };
}, },
)(MediaViewerActions)); )(MediaViewerActions));

View File

@ -923,6 +923,56 @@ addActionHandler('updateChat', async (global, actions, payload) => {
setGlobal(updateManagementProgress(getGlobal(), ManagementProgress.Complete)); setGlobal(updateManagementProgress(getGlobal(), ManagementProgress.Complete));
}); });
addActionHandler('updateChatPhoto', async (global, actions, payload) => {
const { photo, chatId } = payload;
const chat = selectChat(global, chatId);
if (!chat) return;
setGlobal(updateChat(global, chatId, {
avatarHash: undefined,
fullInfo: {
...chat.fullInfo,
profilePhoto: undefined,
},
}));
// This method creates a new entry in photos array
await callApi('editChatPhoto', {
chatId,
accessHash: chat.accessHash,
photo,
});
// Explicitly delete the old photo reference
await callApi('deleteProfilePhotos', [photo]);
actions.loadFullChat({ chatId });
actions.loadProfilePhotos({ profileId: chatId });
});
addActionHandler('deleteChatPhoto', async (global, actions, payload) => {
const { photo, chatId } = payload;
const chat = selectChat(global, chatId);
if (!chat) return;
// Select next photo to set as avatar
const nextPhoto = chat.photos?.[1];
setGlobal(updateChat(global, chatId, {
avatarHash: undefined,
fullInfo: {
...chat.fullInfo,
profilePhoto: undefined,
},
}));
// Set next photo as avatar
await callApi('editChatPhoto', {
chatId,
accessHash: chat.accessHash,
photo: nextPhoto,
});
// Delete references to the old photos
const photosToDelete = [photo, nextPhoto].filter(Boolean);
const result = await callApi('deleteProfilePhotos', photosToDelete);
if (!result) return;
actions.loadFullChat({ chatId });
actions.loadProfilePhotos({ profileId: chatId });
});
addActionHandler('toggleSignatures', (global, actions, payload) => { addActionHandler('toggleSignatures', (global, actions, payload) => {
const { chatId, isEnabled } = payload!; const { chatId, isEnabled } = payload!;
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);

View File

@ -14,7 +14,7 @@ import { callApi } from '../../../api/gramjs';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
import { subscribe, unsubscribe } from '../../../util/notifications'; import { subscribe, unsubscribe } from '../../../util/notifications';
import { setTimeFormat } from '../../../util/langProvider'; import { setTimeFormat } from '../../../util/langProvider';
import { selectUser } from '../../selectors'; import { selectUser, selectChat } from '../../selectors';
import { import {
addUsers, addBlockedContact, updateChats, updateUser, removeBlockedContact, replaceSettings, updateNotifySettings, addUsers, addBlockedContact, updateChats, updateUser, removeBlockedContact, replaceSettings, updateNotifySettings,
addNotifyExceptions, addNotifyExceptions,
@ -39,7 +39,7 @@ addActionHandler('updateProfile', async (global, actions, payload) => {
}); });
if (photo) { if (photo) {
const result = await callApi('updateProfilePhoto', photo); const result = await callApi('uploadProfilePhoto', photo);
if (result) { if (result) {
actions.loadProfilePhotos({ profileId: currentUserId }); actions.loadProfilePhotos({ profileId: currentUserId });
} }
@ -83,16 +83,52 @@ addActionHandler('updateProfile', async (global, actions, payload) => {
}); });
}); });
addActionHandler('deleteProfilePhoto', async (global, actions, payload) => { addActionHandler('updateProfilePhoto', async (global, actions, payload) => {
const { photo, profileId } = payload; const { photo } = payload;
const result = await callApi('deleteProfilePhoto', photo); const { currentUserId } = global;
if (!result) return; if (!currentUserId) return;
if (isUserId(profileId)) { const currentUser = selectChat(global, currentUserId);
actions.loadFullUser({ userId: profileId }); if (!currentUser) return;
} else { setGlobal(updateUser(global, currentUserId, {
actions.loadFullChat({ chatId: profileId }); avatarHash: undefined,
fullInfo: {
...currentUser.fullInfo,
profilePhoto: undefined,
},
}));
const newPhoto = await callApi('updateProfilePhoto', photo);
if (newPhoto) {
setGlobal(updateUser(getGlobal(), currentUserId, {
avatarHash: newPhoto.id,
fullInfo: {
...currentUser.fullInfo,
profilePhoto: newPhoto,
},
}));
actions.loadFullUser({ userId: currentUserId });
actions.loadProfilePhotos({ profileId: currentUserId });
} }
actions.loadProfilePhotos({ profileId }); });
addActionHandler('deleteProfilePhoto', async (global, actions, payload) => {
const { photo } = payload;
const { currentUserId } = global;
if (!currentUserId) return;
const currentUser = selectChat(global, currentUserId);
if (!currentUser) return;
if (currentUser.avatarHash === photo.id) {
setGlobal(updateUser(global, currentUserId, {
avatarHash: undefined,
fullInfo: {
...currentUser.fullInfo,
profilePhoto: undefined,
},
}));
}
const result = await callApi('deleteProfilePhotos', [photo]);
if (!result) return;
actions.loadFullUser({ userId: currentUserId });
actions.loadProfilePhotos({ profileId: currentUserId });
}); });
addActionHandler('checkUsername', async (global, actions, payload) => { addActionHandler('checkUsername', async (global, actions, payload) => {

View File

@ -251,11 +251,15 @@ addActionHandler('loadProfilePhotos', async (global, actions, payload) => {
global = getGlobal(); global = getGlobal();
const userOrChat = user || chat;
const { photos } = result;
photos.sort((a) => (a.id === userOrChat?.avatarHash ? -1 : 1));
if (isPrivate) { if (isPrivate) {
global = updateUser(global, profileId, { photos: result.photos }); global = updateUser(global, profileId, { photos });
} else { } else {
global = addUsers(global, buildCollectionByKey(result.users!, 'id')); global = addUsers(global, buildCollectionByKey(result.users!, 'id'));
global = updateChat(global, profileId, { photos: result.photos }); global = updateChat(global, profileId, { photos });
} }
setGlobal(global); setGlobal(global);

View File

@ -741,6 +741,14 @@ export interface ActionPayloads {
chatId: string; chatId: string;
force?: boolean; force?: boolean;
}; };
updateChatPhoto: {
chatId: string;
photo: ApiPhoto;
};
deleteChatPhoto: {
chatId: string;
photo: ApiPhoto;
};
openChatWithDraft: { openChatWithDraft: {
chatId?: string; chatId?: string;
text: string; text: string;
@ -884,7 +892,9 @@ export interface ActionPayloads {
profileId: string; profileId: string;
}; };
deleteProfilePhoto: { deleteProfilePhoto: {
profileId: string; photo: ApiPhoto;
};
updateProfilePhoto: {
photo: ApiPhoto; photo: ApiPhoto;
}; };

View File

@ -8,7 +8,7 @@
import { default as Api } from '../tl/api'; import { default as Api } from '../tl/api';
import { SecurityError } from '../errors'; import { SecurityError } from '../errors';
// eslint-disable-next-line import/no-named-default // eslint-disable-next-line import/no-named-default
import { default as MTProtoPlainSender } from './MTProtoPlainSender'; import type { default as MTProtoPlainSender } from './MTProtoPlainSender';
import { SERVER_KEYS } from '../crypto/RSA'; import { SERVER_KEYS } from '../crypto/RSA';
const bigInt = require('big-integer'); const bigInt = require('big-integer');

View File

@ -1232,6 +1232,7 @@ messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector<int> = Updates;
updates.getState#edd4882a = updates.State; updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
photos.updateProfilePhoto#72d4742c id:InputPhoto = photos.Photo;
photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo;
photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>; photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;

View File

@ -163,6 +163,7 @@
"updates.getDifference", "updates.getDifference",
"updates.getChannelDifference", "updates.getChannelDifference",
"photos.uploadProfilePhoto", "photos.uploadProfilePhoto",
"photos.updateProfilePhoto",
"photos.getUserPhotos", "photos.getUserPhotos",
"photos.deletePhotos", "photos.deletePhotos",
"upload.saveFilePart", "upload.saveFilePart",