Profile: Fix avatar update/delete (#3731)
This commit is contained in:
parent
03c6e79302
commit
3a56c041cd
@ -31,9 +31,9 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
noVoiceMessages: voiceMessagesForbidden,
|
||||
hasPinnedStories: Boolean(storiesPinnedAvailable),
|
||||
isTranslationDisabled: translationsDisabled,
|
||||
...(profilePhoto instanceof GramJs.Photo && { profilePhoto: buildApiPhoto(profilePhoto) }),
|
||||
...(fallbackPhoto instanceof GramJs.Photo && { fallbackPhoto: buildApiPhoto(fallbackPhoto) }),
|
||||
...(personalPhoto instanceof GramJs.Photo && { personalPhoto: buildApiPhoto(personalPhoto) }),
|
||||
profilePhoto: profilePhoto instanceof GramJs.Photo ? buildApiPhoto(profilePhoto) : undefined,
|
||||
fallbackPhoto: fallbackPhoto instanceof GramJs.Photo ? buildApiPhoto(fallbackPhoto) : undefined,
|
||||
personalPhoto: personalPhoto instanceof GramJs.Photo ? buildApiPhoto(personalPhoto) : undefined,
|
||||
...(premiumGifts && { premiumGifts: premiumGifts.map((gift) => buildApiPremiumGiftOption(gift)) }),
|
||||
...(botInfo && { botInfo: buildApiBotInfo(botInfo, userId) }),
|
||||
};
|
||||
|
||||
@ -41,6 +41,7 @@ import Avatar from './Avatar';
|
||||
import './ProfileInfo.scss';
|
||||
|
||||
import styles from './ProfileInfo.module.scss';
|
||||
import { useStateRef } from '../../hooks/useStateRef';
|
||||
|
||||
type OwnProps = {
|
||||
userId: string;
|
||||
@ -98,10 +99,10 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
const photos = user?.photos || chat?.photos || MEMO_EMPTY_ARRAY;
|
||||
const prevMediaId = usePrevious(mediaId);
|
||||
const prevAvatarOwnerId = usePrevious(avatarOwnerId);
|
||||
const mediaIdRef = useStateRef(mediaId);
|
||||
const [hasSlideAnimation, setHasSlideAnimation] = useState(true);
|
||||
const slideAnimation = hasSlideAnimation
|
||||
? (lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized')
|
||||
: 'none';
|
||||
// slideOptimized doesn't work well when animation is dynamically disabled
|
||||
const slideAnimation = hasSlideAnimation ? (lang.isRtl ? 'slideRtl' : 'slide') : 'none';
|
||||
|
||||
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
|
||||
const isFirst = isSavedMessages || photos.length <= 1 || currentPhotoIndex === 0;
|
||||
@ -115,9 +116,16 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [mediaId, prevMediaId, prevAvatarOwnerId]);
|
||||
|
||||
// Reset the current avatar photo to the one selected in Media Viewer if photos have changed
|
||||
useEffect(() => {
|
||||
setHasSlideAnimation(false);
|
||||
setCurrentPhotoIndex(mediaIdRef.current || 0);
|
||||
}, [mediaIdRef, photos]);
|
||||
|
||||
// Deleting the last profile photo may result in an error
|
||||
useEffect(() => {
|
||||
if (currentPhotoIndex > photos.length) {
|
||||
setHasSlideAnimation(false);
|
||||
setCurrentPhotoIndex(Math.max(0, photos.length - 1));
|
||||
}
|
||||
}, [currentPhotoIndex, photos.length]);
|
||||
|
||||
@ -307,7 +307,7 @@ const MediaViewer: FC<StateProps> = ({
|
||||
chatId={avatarOwner.id}
|
||||
isAvatar
|
||||
isFallbackAvatar={isUserId(avatarOwner.id)
|
||||
&& (avatarOwner as ApiUser).photos?.[mediaId!].id === avatarOwnerFallbackPhoto?.id}
|
||||
&& (avatarOwner as ApiUser).photos?.[mediaId!]?.id === avatarOwnerFallbackPhoto?.id}
|
||||
/>
|
||||
) : (
|
||||
<SenderInfo
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ChangeEvent, RefObject } from 'react';
|
||||
import type { RefObject } from 'react';
|
||||
import React, { memo, useCallback, useState } from '../../lib/teact/teact';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
@ -6,6 +6,7 @@ import type { FC } from '../../lib/teact/teact';
|
||||
import CropModal from './CropModal';
|
||||
|
||||
import styles from './SelectAvatar.module.scss';
|
||||
import { openSystemFilesDialog } from '../../util/systemFilesDialog';
|
||||
|
||||
type OwnProps = {
|
||||
onChange: (file: File) => void;
|
||||
@ -18,17 +19,6 @@ const SelectAvatar: FC<OwnProps> = ({
|
||||
}) => {
|
||||
const [selectedFile, setSelectedFile] = useState<File | undefined>();
|
||||
|
||||
function handleSelectFile(event: ChangeEvent<HTMLInputElement>) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
|
||||
if (!target?.files?.[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedFile(target.files[0]);
|
||||
target.value = '';
|
||||
}
|
||||
|
||||
const handleAvatarCrop = useCallback((croppedImg: File) => {
|
||||
setSelectedFile(undefined);
|
||||
onChange(croppedImg);
|
||||
@ -38,15 +28,19 @@ const SelectAvatar: FC<OwnProps> = ({
|
||||
setSelectedFile(undefined);
|
||||
}, []);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
openSystemFilesDialog('image/png, image/jpeg', ((event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!target?.files?.[0]) {
|
||||
return;
|
||||
}
|
||||
setSelectedFile(target.files[0]);
|
||||
}), true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="file"
|
||||
onChange={handleSelectFile}
|
||||
accept="image/png, image/jpeg"
|
||||
ref={inputRef}
|
||||
className={styles.input}
|
||||
/>
|
||||
<input ref={inputRef} className={styles.input} onClick={handleClick} />
|
||||
<CropModal file={selectedFile} onClose={handleModalClose} onChange={handleAvatarCrop} />
|
||||
</>
|
||||
);
|
||||
|
||||
@ -343,16 +343,25 @@ addActionHandler('loadAllChats', async (global, actions, payload): Promise<void>
|
||||
});
|
||||
|
||||
addActionHandler('loadFullChat', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId, force, tabId = getCurrentTabId() } = payload!;
|
||||
const {
|
||||
chatId, force, tabId = getCurrentTabId(), withPhotos,
|
||||
} = payload!;
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loadChat = async () => {
|
||||
await loadFullChat(global, actions, chat, tabId);
|
||||
if (withPhotos) {
|
||||
actions.loadProfilePhotos({ profileId: chatId });
|
||||
}
|
||||
};
|
||||
|
||||
if (force) {
|
||||
loadFullChat(global, actions, chat, tabId);
|
||||
void loadChat();
|
||||
} else {
|
||||
runDebouncedForLoadFullChat(() => loadFullChat(global, actions, chat, tabId));
|
||||
runDebouncedForLoadFullChat(loadChat);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1354,6 +1363,10 @@ addActionHandler('updateChat', async (global, actions, payload): Promise<void> =
|
||||
global = getGlobal();
|
||||
global = updateManagementProgress(global, ManagementProgress.Complete, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
if (photo) {
|
||||
actions.loadFullChat({ chatId, tabId, withPhotos: true });
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('updateChatPhoto', async (global, actions, payload): Promise<void> => {
|
||||
@ -1371,8 +1384,7 @@ addActionHandler('updateChatPhoto', async (global, actions, payload): Promise<vo
|
||||
});
|
||||
// Explicitly delete the old photo reference
|
||||
await callApi('deleteProfilePhotos', [photo]);
|
||||
actions.loadFullChat({ chatId, tabId });
|
||||
actions.loadProfilePhotos({ profileId: chatId });
|
||||
actions.loadFullChat({ chatId, tabId, withPhotos: true });
|
||||
});
|
||||
|
||||
addActionHandler('deleteChatPhoto', async (global, actions, payload): Promise<void> => {
|
||||
@ -1396,11 +1408,19 @@ addActionHandler('deleteChatPhoto', async (global, actions, payload): Promise<vo
|
||||
photo: nextPhoto,
|
||||
});
|
||||
}
|
||||
|
||||
const { photos = [] } = chat;
|
||||
|
||||
const newPhotos = photos.filter((p) => photosToDelete.some((toDelete) => toDelete.id !== p.id));
|
||||
global = getGlobal();
|
||||
global = updateChat(global, chatId, { photos: newPhotos });
|
||||
|
||||
setGlobal(global);
|
||||
|
||||
// Delete references to the old photos
|
||||
const result = await callApi('deleteProfilePhotos', photosToDelete);
|
||||
if (!result) return;
|
||||
actions.loadFullChat({ chatId, tabId });
|
||||
actions.loadProfilePhotos({ profileId: chatId });
|
||||
actions.loadFullChat({ chatId, tabId, withPhotos: true });
|
||||
});
|
||||
|
||||
addActionHandler('toggleSignatures', (global, actions, payload): ActionReturnType => {
|
||||
|
||||
@ -17,7 +17,9 @@ import { subscribe, unsubscribe, requestPermission } from '../../../util/notific
|
||||
import { setTimeFormat } from '../../../util/langProvider';
|
||||
import requestActionTimeout from '../../../util/requestActionTimeout';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { selectChat, selectUser, selectTabState } from '../../selectors';
|
||||
import {
|
||||
selectChat, selectUser, selectTabState, selectUserFullInfo,
|
||||
} from '../../selectors';
|
||||
import {
|
||||
addUsers, addBlockedContact, updateChats, updateUser, removeBlockedContact, replaceSettings, updateNotifySettings,
|
||||
addNotifyExceptions, updateChat, updateUserFullInfo,
|
||||
@ -50,7 +52,6 @@ addActionHandler('updateProfile', async (global, actions, payload): Promise<void
|
||||
global = getGlobal();
|
||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||
setGlobal(global);
|
||||
actions.loadProfilePhotos({ profileId: currentUserId });
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,6 +98,10 @@ addActionHandler('updateProfile', async (global, actions, payload): Promise<void
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
if (photo) {
|
||||
actions.loadFullUser({ userId: currentUserId, withPhotos: true });
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('updateProfilePhoto', async (global, actions, payload): Promise<void> => {
|
||||
@ -108,6 +113,7 @@ addActionHandler('updateProfilePhoto', async (global, actions, payload): Promise
|
||||
|
||||
global = updateUser(global, currentUserId, { avatarHash: undefined });
|
||||
global = updateUserFullInfo(global, currentUserId, { profilePhoto: undefined });
|
||||
|
||||
setGlobal(global);
|
||||
|
||||
const result = await callApi('updateProfilePhoto', photo, isFallback);
|
||||
@ -117,28 +123,40 @@ addActionHandler('updateProfilePhoto', async (global, actions, payload): Promise
|
||||
global = getGlobal();
|
||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||
setGlobal(global);
|
||||
|
||||
actions.loadFullUser({ userId: currentUserId });
|
||||
actions.loadProfilePhotos({ profileId: currentUserId });
|
||||
actions.loadFullUser({ userId: currentUserId, withPhotos: true });
|
||||
});
|
||||
|
||||
addActionHandler('deleteProfilePhoto', async (global, actions, payload): Promise<void> => {
|
||||
const { photo } = payload;
|
||||
const { currentUserId } = global;
|
||||
if (!currentUserId) return;
|
||||
const currentUser = selectChat(global, currentUserId);
|
||||
const currentUser = selectUser(global, currentUserId);
|
||||
if (!currentUser) return;
|
||||
if (currentUser.avatarHash === photo.id) {
|
||||
|
||||
const fullInfo = selectUserFullInfo(global, currentUserId);
|
||||
|
||||
if (currentUser.avatarHash === photo.id || fullInfo?.profilePhoto?.id === photo.id) {
|
||||
global = updateUser(global, currentUserId, { avatarHash: undefined });
|
||||
global = updateUserFullInfo(global, currentUserId, { profilePhoto: undefined });
|
||||
setGlobal(global);
|
||||
}
|
||||
|
||||
const result = await callApi('deleteProfilePhotos', [photo]);
|
||||
if (!result) return;
|
||||
if (fullInfo?.fallbackPhoto?.id === photo.id) {
|
||||
global = updateUserFullInfo(global, currentUserId, { fallbackPhoto: undefined });
|
||||
}
|
||||
|
||||
actions.loadFullUser({ userId: currentUserId });
|
||||
actions.loadProfilePhotos({ profileId: currentUserId });
|
||||
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 });
|
||||
|
||||
setGlobal(global);
|
||||
|
||||
await callApi('deleteProfilePhotos', [photo]);
|
||||
actions.loadFullUser({ userId: currentUserId, withPhotos: true });
|
||||
});
|
||||
|
||||
addActionHandler('checkUsername', async (global, actions, payload): Promise<void> => {
|
||||
|
||||
@ -26,6 +26,7 @@ import {
|
||||
updateUsers,
|
||||
updateUserSearch,
|
||||
updateUserSearchFetchingStatus,
|
||||
updateUserFullInfo,
|
||||
} from '../../reducers';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import * as langProvider from '../../../util/langProvider';
|
||||
@ -36,7 +37,7 @@ const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
||||
const runThrottledForSearch = throttle((cb) => cb(), 500, false);
|
||||
|
||||
addActionHandler('loadFullUser', async (global, actions, payload): Promise<void> => {
|
||||
const { userId } = payload!;
|
||||
const { userId, withPhotos } = payload!;
|
||||
const user = selectUser(global, userId);
|
||||
if (!user) {
|
||||
return;
|
||||
@ -53,8 +54,15 @@ addActionHandler('loadFullUser', async (global, actions, payload): Promise<void>
|
||||
const hasChangedProfilePhoto = fullInfo?.profilePhoto?.id !== newFullInfo?.profilePhoto?.id;
|
||||
const hasChangedFallbackPhoto = fullInfo?.fallbackPhoto?.id !== newFullInfo?.fallbackPhoto?.id;
|
||||
const hasChangedPersonalPhoto = fullInfo?.personalPhoto?.id !== newFullInfo?.personalPhoto?.id;
|
||||
if ((hasChangedAvatarHash || hasChangedProfilePhoto || hasChangedFallbackPhoto || hasChangedPersonalPhoto)
|
||||
&& user.photos?.length) {
|
||||
const hasChangedPhoto = hasChangedAvatarHash
|
||||
|| hasChangedProfilePhoto
|
||||
|| hasChangedFallbackPhoto
|
||||
|| hasChangedPersonalPhoto;
|
||||
|
||||
global = updateUser(global, userId, result.user);
|
||||
global = updateUserFullInfo(global, userId, result.fullInfo);
|
||||
setGlobal(global);
|
||||
if (withPhotos || (user.photos?.length && hasChangedPhoto)) {
|
||||
actions.loadProfilePhotos({ profileId: userId });
|
||||
}
|
||||
});
|
||||
@ -271,12 +279,14 @@ addActionHandler('loadProfilePhotos', async (global, actions, payload): Promise<
|
||||
|
||||
const userOrChat = user || chat;
|
||||
const { photos, users } = result;
|
||||
photos.sort((a) => (a.id === userOrChat?.avatarHash ? -1 : 1));
|
||||
|
||||
const fallbackPhoto = fullInfo?.fallbackPhoto;
|
||||
const personalPhoto = fullInfo?.personalPhoto;
|
||||
if (fallbackPhoto) photos.push(fallbackPhoto);
|
||||
if (personalPhoto) photos.unshift(personalPhoto);
|
||||
|
||||
photos.sort((a) => (a.id === userOrChat?.avatarHash ? -1 : 1));
|
||||
|
||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||
|
||||
if (isPrivate) {
|
||||
|
||||
@ -1727,6 +1727,7 @@ export interface ActionPayloads {
|
||||
} & WithTabId;
|
||||
loadFullChat: {
|
||||
chatId: string;
|
||||
withPhotos?: boolean;
|
||||
force?: boolean;
|
||||
} & WithTabId;
|
||||
updateChatPhoto: {
|
||||
@ -2095,7 +2096,7 @@ export interface ActionPayloads {
|
||||
setUserSearchQuery: { query?: string } & WithTabId;
|
||||
loadCommonChats: WithTabId | undefined;
|
||||
reportSpam: { chatId: string };
|
||||
loadFullUser: { userId: string };
|
||||
loadFullUser: { userId: string; withPhotos?: boolean };
|
||||
openAddContactDialog: { userId?: string } & WithTabId;
|
||||
openNewContactDialog: WithTabId | undefined;
|
||||
closeNewContactDialog: WithTabId | undefined;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user