Media Viewer: Support selecting main photo for profile and chats (#2162)
This commit is contained in:
parent
be044c2aa1
commit
f5b8b09f37
@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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));
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user