Settings / Privacy: Public Profile Pictures (#2309)
This commit is contained in:
parent
3dab7609e3
commit
c1f3685217
@ -15,6 +15,7 @@ export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
fullUser: {
|
||||
about, commonChatsCount, pinnedMsgId, botInfo, blocked,
|
||||
profilePhoto, voiceMessagesForbidden, premiumGifts,
|
||||
fallbackPhoto,
|
||||
},
|
||||
users,
|
||||
} = mtpUserFull;
|
||||
@ -25,6 +26,7 @@ export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
...user,
|
||||
fullInfo: {
|
||||
...(profilePhoto instanceof GramJs.Photo && { profilePhoto: buildApiPhoto(profilePhoto) }),
|
||||
...(fallbackPhoto instanceof GramJs.Photo && { fallbackPhoto: buildApiPhoto(fallbackPhoto) }),
|
||||
bio: about,
|
||||
commonChatsCount,
|
||||
pinnedMessageId: pinnedMsgId,
|
||||
|
||||
@ -82,10 +82,11 @@ export function updateUsername(username: string) {
|
||||
return invokeRequest(new GramJs.account.UpdateUsername({ username }), true);
|
||||
}
|
||||
|
||||
export async function updateProfilePhoto(photo?: ApiPhoto) {
|
||||
export async function updateProfilePhoto(photo?: ApiPhoto, isFallback?: boolean) {
|
||||
const photoId = photo ? buildInputPhoto(photo) : new GramJs.InputPhotoEmpty();
|
||||
const result = await invokeRequest(new GramJs.photos.UpdateProfilePhoto({
|
||||
id: photoId,
|
||||
...(isFallback ? { fallback: true } : undefined),
|
||||
}));
|
||||
if (!result) return undefined;
|
||||
|
||||
@ -100,10 +101,11 @@ export async function updateProfilePhoto(photo?: ApiPhoto) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function uploadProfilePhoto(file: File) {
|
||||
export async function uploadProfilePhoto(file: File, isFallback?: boolean) {
|
||||
const inputFile = await uploadFile(file);
|
||||
const result = await invokeRequest(new GramJs.photos.UploadProfilePhoto({
|
||||
file: inputFile,
|
||||
...(isFallback ? { fallback: true } : undefined),
|
||||
}));
|
||||
|
||||
if (!result) return undefined;
|
||||
|
||||
@ -45,10 +45,16 @@ export async function fetchFullUser({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
updateLocalDb(fullInfo);
|
||||
|
||||
if (fullInfo.fullUser.profilePhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[fullInfo.fullUser.profilePhoto.id.toString()] = fullInfo.fullUser.profilePhoto;
|
||||
}
|
||||
|
||||
if (fullInfo.fullUser.fallbackPhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[fullInfo.fullUser.fallbackPhoto.id.toString()] = fullInfo.fullUser.fallbackPhoto;
|
||||
}
|
||||
|
||||
const botInfo = fullInfo.fullUser.botInfo;
|
||||
if (botInfo?.descriptionPhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[botInfo.descriptionPhoto.id.toString()] = botInfo.descriptionPhoto;
|
||||
@ -58,11 +64,14 @@ export async function fetchFullUser({
|
||||
}
|
||||
|
||||
const userWithFullInfo = buildApiUserFromFull(fullInfo);
|
||||
const user = buildApiUser(fullInfo.users[0]);
|
||||
|
||||
onUpdate({
|
||||
'@type': 'updateUser',
|
||||
id,
|
||||
user: {
|
||||
...user,
|
||||
avatarHash: user?.avatarHash || undefined,
|
||||
fullInfo: userWithFullInfo.fullInfo,
|
||||
},
|
||||
});
|
||||
|
||||
@ -41,6 +41,7 @@ export interface ApiUserFullInfo {
|
||||
pinnedMessageId?: number;
|
||||
botInfo?: ApiBotInfo;
|
||||
profilePhoto?: ApiPhoto;
|
||||
fallbackPhoto?: ApiPhoto;
|
||||
noVoiceMessages?: boolean;
|
||||
premiumGifts?: ApiPremiumGiftOption[];
|
||||
}
|
||||
|
||||
@ -52,6 +52,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.size-mini {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
font-size: 0.75rem;
|
||||
|
||||
.emoji {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.size-small {
|
||||
width: 2.125rem;
|
||||
height: 2.125rem;
|
||||
|
||||
@ -45,7 +45,7 @@ cn.icon = cn('icon');
|
||||
|
||||
type OwnProps = {
|
||||
className?: string;
|
||||
size?: 'micro' | 'tiny' | 'small' | 'medium' | 'large' | 'jumbo';
|
||||
size?: 'micro' | 'tiny' | 'mini' | 'small' | 'medium' | 'large' | 'jumbo';
|
||||
chat?: ApiChat;
|
||||
user?: ApiUser;
|
||||
photo?: ApiPhoto;
|
||||
|
||||
@ -15,6 +15,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
.fallbackPhoto {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
transition: 0.25s ease-in-out opacity;
|
||||
}
|
||||
|
||||
.fallbackPhotoContents {
|
||||
display: flex;
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-white);
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
align-items: center;
|
||||
height: 1.5rem;
|
||||
pointer-events: none;
|
||||
|
||||
transition: 0.25s ease-in-out opacity;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.fallbackPhotoVisible {
|
||||
opacity: 1;
|
||||
|
||||
.fallbackPhotoContents {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
.fallbackPhotoAvatar {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.photoWrapper {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
@ -33,7 +78,7 @@
|
||||
width: 100%;
|
||||
height: 0.125rem;
|
||||
padding: 0 0.375rem;
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
|
||||
display: flex;
|
||||
top: 0.5rem;
|
||||
|
||||
@ -29,6 +29,7 @@ import FullNameTitle from './FullNameTitle';
|
||||
import ProfilePhoto from './ProfilePhoto';
|
||||
import Transition from '../ui/Transition';
|
||||
import TopicIcon from './TopicIcon';
|
||||
import Avatar from './Avatar';
|
||||
|
||||
import './ProfileInfo.scss';
|
||||
import styles from './ProfileInfo.module.scss';
|
||||
@ -145,6 +146,12 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
setCurrentPhotoIndex(currentPhotoIndex + 1);
|
||||
}, [currentPhotoIndex, isLast]);
|
||||
|
||||
function handleSelectFallbackPhoto() {
|
||||
if (!isFirst) return;
|
||||
setHasSlideAnimation(true);
|
||||
setCurrentPhotoIndex(photos.length - 1);
|
||||
}
|
||||
|
||||
// Swipe gestures
|
||||
useEffect(() => {
|
||||
const element = document.querySelector<HTMLDivElement>(`.${styles.photoWrapper}`);
|
||||
@ -251,6 +258,24 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
<div className={styles.photoWrapper}>
|
||||
{renderPhotoTabs()}
|
||||
{forceShowSelf && user?.fullInfo?.fallbackPhoto && (
|
||||
<div className={buildClassName(
|
||||
styles.fallbackPhoto,
|
||||
(isFirst || isLast) && styles.fallbackPhotoVisible,
|
||||
)}
|
||||
>
|
||||
<div className={styles.fallbackPhotoContents} onClick={handleSelectFallbackPhoto}>
|
||||
{!isLast && (
|
||||
<Avatar
|
||||
photo={user.fullInfo.fallbackPhoto}
|
||||
className={styles.fallbackPhotoAvatar}
|
||||
size="mini"
|
||||
/>
|
||||
)}
|
||||
{lang(user.fullInfo.fallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Transition activeKey={currentPhotoIndex} name={slideAnimation}>
|
||||
{renderPhoto}
|
||||
</Transition>
|
||||
|
||||
@ -56,7 +56,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
const isDeleted = user && isDeletedUser(user);
|
||||
const isRepliesChat = chat && isChatWithRepliesBot(chat.id);
|
||||
const userOrChat = user || chat;
|
||||
const currentPhoto = photo || userOrChat?.fullInfo?.profilePhoto;
|
||||
const currentPhoto = photo || userOrChat?.fullInfo?.profilePhoto || user?.fullInfo?.fallbackPhoto;
|
||||
const canHaveMedia = userOrChat && !isSavedMessages && !isDeleted && !isRepliesChat;
|
||||
const { isVideo } = currentPhoto || {};
|
||||
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
.fallback-photo {
|
||||
margin-right: 2rem;
|
||||
transform: scale(1.25);
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useRef,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { ApiUser } from '../../../api/types';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import SelectAvatar from '../../ui/SelectAvatar';
|
||||
import Avatar from '../../common/Avatar';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
|
||||
import styles from './SettingsPrivacyPublicPhoto.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
currentUser: ApiUser;
|
||||
};
|
||||
|
||||
const SettingsPrivacyPublicProfilePhoto: FC<OwnProps> = ({
|
||||
currentUser,
|
||||
}) => {
|
||||
const {
|
||||
loadFullUser, uploadProfilePhoto, deleteProfilePhoto, showNotification,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const fallbackPhoto = currentUser.fullInfo?.fallbackPhoto;
|
||||
const [isDeleteFallbackPhotoModalOpen, openDeleteFallbackPhotoModal, closeDeleteFallbackPhotoModal] = useFlag(false);
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentUser.fullInfo) {
|
||||
loadFullUser({ userId: currentUser.id });
|
||||
}
|
||||
}, [currentUser.fullInfo, currentUser.id, loadFullUser]);
|
||||
|
||||
const handleSelectFile = useCallback((file: File) => {
|
||||
uploadProfilePhoto({
|
||||
file,
|
||||
isFallback: true,
|
||||
});
|
||||
showNotification({
|
||||
message: lang('Privacy.ProfilePhoto.PublicPhotoSuccess'),
|
||||
});
|
||||
}, [lang, showNotification, uploadProfilePhoto]);
|
||||
|
||||
const handleConfirmDelete = useCallback(() => {
|
||||
closeDeleteFallbackPhotoModal();
|
||||
deleteProfilePhoto({ photo: fallbackPhoto! });
|
||||
}, [closeDeleteFallbackPhotoModal, deleteProfilePhoto, fallbackPhoto]);
|
||||
|
||||
const handleOpenFileSelector = useCallback(() => {
|
||||
inputRef.current?.click();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="settings-item">
|
||||
<ListItem
|
||||
icon="camera-add"
|
||||
onClick={handleOpenFileSelector}
|
||||
>
|
||||
<SelectAvatar
|
||||
onChange={handleSelectFile}
|
||||
inputRef={inputRef}
|
||||
/>
|
||||
{lang(fallbackPhoto ? 'Privacy.ProfilePhoto.UpdatePublicPhoto' : 'Privacy.ProfilePhoto.SetPublicPhoto')}
|
||||
</ListItem>
|
||||
{fallbackPhoto && (
|
||||
<ListItem
|
||||
leftElement={<Avatar photo={fallbackPhoto} size="mini" className={styles.fallbackPhoto} />}
|
||||
onClick={openDeleteFallbackPhotoModal}
|
||||
destructive
|
||||
>
|
||||
{lang(fallbackPhoto.isVideo
|
||||
? 'Privacy.ProfilePhoto.RemovePublicVideo'
|
||||
: 'Privacy.ProfilePhoto.RemovePublicPhoto')}
|
||||
<ConfirmDialog
|
||||
isOpen={isDeleteFallbackPhotoModalOpen}
|
||||
onClose={closeDeleteFallbackPhotoModal}
|
||||
text={lang('Privacy.ResetPhoto.Confirm')}
|
||||
confirmLabel={lang('Delete')}
|
||||
confirmHandler={handleConfirmDelete}
|
||||
confirmIsDestructive
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{lang('Privacy.ProfilePhoto.PublicPhotoInfo')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SettingsPrivacyPublicProfilePhoto);
|
||||
@ -6,12 +6,14 @@ import type { ApiChat, ApiUser } from '../../../api/types';
|
||||
import type { ApiPrivacySettings } from '../../../types';
|
||||
import { SettingsScreens } from '../../../types';
|
||||
|
||||
import { getPrivacyKey } from './helpers/privacy';
|
||||
import { selectUser } from '../../../global/selectors';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import RadioGroup from '../../ui/RadioGroup';
|
||||
import { getPrivacyKey } from './helpers/privacy';
|
||||
import SettingsPrivacyPublicProfilePhoto from './SettingsPrivacyPublicProfilePhoto';
|
||||
|
||||
type OwnProps = {
|
||||
screen: SettingsScreens;
|
||||
@ -24,6 +26,7 @@ type StateProps =
|
||||
Partial<ApiPrivacySettings> & {
|
||||
chatsById?: Record<string, ApiChat>;
|
||||
usersById?: Record<string, ApiUser>;
|
||||
currentUser: ApiUser;
|
||||
};
|
||||
|
||||
const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
|
||||
@ -37,6 +40,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
|
||||
blockUserIds,
|
||||
blockChatIds,
|
||||
chatsById,
|
||||
currentUser,
|
||||
}) => {
|
||||
const { setPrivacyVisibility } = getActions();
|
||||
|
||||
@ -44,7 +48,6 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const visibilityOptions = useMemo(() => {
|
||||
switch (screen) {
|
||||
case SettingsScreens.PrivacyProfilePhoto:
|
||||
case SettingsScreens.PrivacyGroupChats:
|
||||
return [
|
||||
{ value: 'everybody', label: lang('P2PEverybody') },
|
||||
@ -226,6 +229,10 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
|
||||
</ListItem>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{screen === SettingsScreens.PrivacyProfilePhoto && exceptionLists.shouldShowAllowed && (
|
||||
<SettingsPrivacyPublicProfilePhoto currentUser={currentUser} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -239,6 +246,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
settings: { privacy },
|
||||
} = global;
|
||||
|
||||
const currentUser = selectUser(global, global.currentUserId!)!;
|
||||
|
||||
switch (screen) {
|
||||
case SettingsScreens.PrivacyPhoneNumber:
|
||||
privacySettings = privacy.phoneNumber;
|
||||
@ -274,12 +283,15 @@ export default memo(withGlobal<OwnProps>(
|
||||
}
|
||||
|
||||
if (!privacySettings) {
|
||||
return {};
|
||||
return {
|
||||
currentUser,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...privacySettings,
|
||||
chatsById,
|
||||
currentUser,
|
||||
};
|
||||
},
|
||||
)(SettingsPrivacyVisibility));
|
||||
|
||||
@ -11,7 +11,7 @@ import { MediaViewerOrigin } from '../../types';
|
||||
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
import {
|
||||
getChatMediaMessageIds, isChatAdmin,
|
||||
getChatMediaMessageIds, isChatAdmin, isUserId,
|
||||
} from '../../global/helpers';
|
||||
import {
|
||||
selectChat,
|
||||
@ -288,6 +288,8 @@ const MediaViewer: FC<StateProps> = ({
|
||||
key={mediaId}
|
||||
chatId={avatarOwner.id}
|
||||
isAvatar
|
||||
isFallbackAvatar={isUserId(avatarOwner.id)
|
||||
&& (avatarOwner as ApiUser).photos?.[mediaId!].id === (avatarOwner as ApiUser).fullInfo?.fallbackPhoto?.id}
|
||||
/>
|
||||
) : (
|
||||
<SenderInfo
|
||||
|
||||
@ -25,6 +25,7 @@ type OwnProps = {
|
||||
chatId?: string;
|
||||
messageId?: number;
|
||||
isAvatar?: boolean;
|
||||
isFallbackAvatar?: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -39,6 +40,7 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
messageId,
|
||||
sender,
|
||||
isFallbackAvatar,
|
||||
isAvatar,
|
||||
message,
|
||||
animationLevel,
|
||||
@ -85,7 +87,7 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
<div className="date" dir="auto">
|
||||
{isAvatar
|
||||
? lang('lng_mediaview_profile_photo')
|
||||
? lang(isFallbackAvatar ? 'lng_mediaview_profile_public_photo' : 'lng_mediaview_profile_photo')
|
||||
: formatMediaDateTime(lang, message!.date * 1000, true)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -35,7 +35,7 @@ const AvatarEditable: FC<OwnProps> = ({
|
||||
function handleSelectFile(event: ChangeEvent<HTMLInputElement>) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
|
||||
if (!target || !target.files || !target.files[0]) {
|
||||
if (!target?.files?.[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
3
src/components/ui/SelectAvatar.module.scss
Normal file
3
src/components/ui/SelectAvatar.module.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.input {
|
||||
display: none;
|
||||
}
|
||||
55
src/components/ui/SelectAvatar.tsx
Normal file
55
src/components/ui/SelectAvatar.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import type { ChangeEvent, RefObject } from 'react';
|
||||
import React, { memo, useCallback, useState } from '../../lib/teact/teact';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
|
||||
import CropModal from './CropModal';
|
||||
|
||||
import styles from './SelectAvatar.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
onChange: (file: File) => void;
|
||||
inputRef: RefObject<HTMLInputElement>;
|
||||
};
|
||||
|
||||
const SelectAvatar: FC<OwnProps> = ({
|
||||
onChange,
|
||||
inputRef,
|
||||
}) => {
|
||||
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);
|
||||
}, [onChange]);
|
||||
|
||||
const handleModalClose = useCallback(() => {
|
||||
setSelectedFile(undefined);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="file"
|
||||
onChange={handleSelectFile}
|
||||
accept="image/png, image/jpeg"
|
||||
ref={inputRef}
|
||||
className={styles.input}
|
||||
/>
|
||||
<CropModal file={selectedFile} onClose={handleModalClose} onChange={handleAvatarCrop} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SelectAvatar);
|
||||
@ -92,14 +92,16 @@ addActionHandler('setAuthPassword', (global, actions, payload) => {
|
||||
});
|
||||
|
||||
addActionHandler('uploadProfilePhoto', async (global, actions, payload) => {
|
||||
const { file } = payload!;
|
||||
const { file, isFallback } = payload!;
|
||||
|
||||
const result = await callApi('uploadProfilePhoto', file);
|
||||
const result = await callApi('uploadProfilePhoto', file, isFallback);
|
||||
if (!result) return;
|
||||
|
||||
global = getGlobal();
|
||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||
setGlobal(global);
|
||||
|
||||
actions.loadFullUser({ userId: global.currentUserId! });
|
||||
});
|
||||
|
||||
addActionHandler('signUp', (global, actions, payload) => {
|
||||
|
||||
@ -96,7 +96,7 @@ addActionHandler('updateProfile', async (global, actions, payload) => {
|
||||
});
|
||||
|
||||
addActionHandler('updateProfilePhoto', async (global, actions, payload) => {
|
||||
const { photo } = payload;
|
||||
const { photo, isFallback } = payload;
|
||||
const { currentUserId } = global;
|
||||
if (!currentUserId) return;
|
||||
const currentUser = selectChat(global, currentUserId);
|
||||
@ -108,7 +108,7 @@ addActionHandler('updateProfilePhoto', async (global, actions, payload) => {
|
||||
profilePhoto: undefined,
|
||||
},
|
||||
}));
|
||||
const result = await callApi('updateProfilePhoto', photo);
|
||||
const result = await callApi('updateProfilePhoto', photo, isFallback);
|
||||
if (!result) return;
|
||||
|
||||
const { photo: newPhoto, users } = result;
|
||||
|
||||
@ -29,7 +29,7 @@ import * as langProvider from '../../../util/langProvider';
|
||||
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
||||
const runThrottledForSearch = throttle((cb) => cb(), 500, false);
|
||||
|
||||
addActionHandler('loadFullUser', (global, actions, payload) => {
|
||||
addActionHandler('loadFullUser', async (global, actions, payload) => {
|
||||
const { userId } = payload!;
|
||||
const user = selectUser(global, userId);
|
||||
if (!user) {
|
||||
@ -37,7 +37,12 @@ addActionHandler('loadFullUser', (global, actions, payload) => {
|
||||
}
|
||||
|
||||
const { id, accessHash } = user;
|
||||
callApi('fetchFullUser', { id, accessHash });
|
||||
const newUser = await callApi('fetchFullUser', { id, accessHash });
|
||||
if (!newUser) return;
|
||||
|
||||
if (user.avatarHash !== newUser.avatarHash && user.photos?.length) {
|
||||
actions.loadProfilePhotos({ profileId: userId });
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('loadUser', async (global, actions, payload) => {
|
||||
@ -238,12 +243,18 @@ addActionHandler('loadProfilePhotos', async (global, actions, payload) => {
|
||||
const { profileId } = payload!;
|
||||
const isPrivate = isUserId(profileId);
|
||||
|
||||
const user = isPrivate ? selectUser(global, profileId) : undefined;
|
||||
let user = isPrivate ? selectUser(global, profileId) : undefined;
|
||||
const chat = !isPrivate ? selectChat(global, profileId) : undefined;
|
||||
if (!user && !chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user && !user?.fullInfo) {
|
||||
const { id, accessHash } = user;
|
||||
user = await callApi('fetchFullUser', { id, accessHash });
|
||||
if (!user) return;
|
||||
}
|
||||
|
||||
const result = await callApi('fetchProfilePhotos', user, chat);
|
||||
if (!result || !result.photos) {
|
||||
return;
|
||||
@ -254,6 +265,8 @@ addActionHandler('loadProfilePhotos', async (global, actions, payload) => {
|
||||
const userOrChat = user || chat;
|
||||
const { photos, users } = result;
|
||||
photos.sort((a) => (a.id === userOrChat?.avatarHash ? -1 : 1));
|
||||
const fallbackPhoto = user?.fullInfo?.fallbackPhoto;
|
||||
if (fallbackPhoto) photos.push(fallbackPhoto);
|
||||
|
||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import type {
|
||||
ApiUser,
|
||||
ApiChatBannedRights,
|
||||
ApiChatAdminRights,
|
||||
ApiChatFolder, ApiTopic,
|
||||
ApiChatFolder, ApiTopic, ApiUserFullInfo,
|
||||
} from '../../api/types';
|
||||
import {
|
||||
MAIN_THREAD_ID,
|
||||
@ -124,8 +124,9 @@ export function getChatAvatarHash(
|
||||
owner: ApiChat | ApiUser,
|
||||
size: 'normal' | 'big' = 'normal',
|
||||
type: 'photo' | 'video' = 'photo',
|
||||
avatarHash = owner.avatarHash,
|
||||
) {
|
||||
if (!owner.avatarHash) {
|
||||
if (!avatarHash) {
|
||||
return undefined;
|
||||
}
|
||||
const { fullInfo } = owner;
|
||||
@ -134,14 +135,18 @@ export function getChatAvatarHash(
|
||||
if (fullInfo?.profilePhoto?.isVideo) {
|
||||
return getVideoAvatarMediaHash(fullInfo.profilePhoto);
|
||||
}
|
||||
const userFullInfo = isUserId(owner.id) ? fullInfo as ApiUserFullInfo : undefined;
|
||||
if (userFullInfo?.fallbackPhoto?.isVideo) {
|
||||
return getVideoAvatarMediaHash(userFullInfo.fallbackPhoto);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
case 'big':
|
||||
return `profile${owner.id}?${owner.avatarHash}`;
|
||||
return `profile${owner.id}?${avatarHash}`;
|
||||
default:
|
||||
return `avatar${owner.id}?${owner.avatarHash}`;
|
||||
return `avatar${owner.id}?${avatarHash}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -984,6 +984,7 @@ export interface ActionPayloads {
|
||||
};
|
||||
updateProfilePhoto: {
|
||||
photo: ApiPhoto;
|
||||
isFallback?: boolean;
|
||||
};
|
||||
|
||||
// Forwards
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user