Settings: New design for self profile (#1373)

This commit is contained in:
Alexander Zinchuk 2021-08-20 23:46:57 +03:00
parent f039f69331
commit ad36326c83
14 changed files with 151 additions and 89 deletions

View File

@ -12,7 +12,7 @@ import {
import {
getChatDescription, getChatLink, getHasAdminRight, isChatChannel, isChatPrivate, isUserRightBanned, selectIsChatMuted,
} from '../../modules/helpers';
import renderText from '../common/helpers/renderText';
import renderText from './helpers/renderText';
import { pick } from '../../util/iteratees';
import { copyTextToClipboard } from '../../util/clipboard';
import { formatPhoneNumberWithCode } from '../../util/phoneNumber';
@ -118,6 +118,7 @@ const ChatExtra: FC<OwnProps & StateProps & DispatchProps> = ({
<span className="subtitle">{lang('SetUrlPlaceholder')}</span>
</ListItem>
)}
{!forceShowSelf && (
<ListItem icon="unmute" ripple onClick={handleNotificationChange}>
<span>{lang('Notifications')}</span>
<Switcher
@ -127,6 +128,7 @@ const ChatExtra: FC<OwnProps & StateProps & DispatchProps> = ({
inactive
/>
</ListItem>
)}
</div>
);
};

View File

@ -159,4 +159,25 @@
transform: scaleX(-1);
}
}
&.self {
margin: 0 -.5rem .75rem;
overflow: hidden;
&.ghost {
margin: 0;
}
.prev-avatar-media {
z-index: 0;
}
.info {
padding-bottom: .75rem;
}
.status {
line-height: 1rem;
}
}
}

View File

@ -12,13 +12,14 @@ import { selectChat, selectUser } from '../../modules/selectors';
import {
getUserFullName, getUserStatus, isChatChannel, isUserOnline,
} from '../../modules/helpers';
import renderText from '../common/helpers/renderText';
import renderText from './helpers/renderText';
import { pick } from '../../util/iteratees';
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
import buildClassName from '../../util/buildClassName';
import usePhotosPreload from './hooks/usePhotosPreload';
import useLang from '../../hooks/useLang';
import VerifiedIcon from '../common/VerifiedIcon';
import VerifiedIcon from './VerifiedIcon';
import ProfilePhoto from './ProfilePhoto';
import Transition from '../ui/Transition';
@ -35,15 +36,16 @@ type StateProps = {
isSavedMessages?: boolean;
animationLevel: 0 | 1 | 2;
serverTimeOffset: number;
} & Pick<GlobalState, 'lastSyncTime'>;
} & Pick<GlobalState, 'connectionState'>;
type DispatchProps = Pick<GlobalActions, 'loadFullUser' | 'openMediaViewer'>;
const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
const ProfileInfo: FC<OwnProps & StateProps & DispatchProps> = ({
forceShowSelf,
user,
chat,
isSavedMessages,
lastSyncTime,
connectionState,
animationLevel,
loadFullUser,
openMediaViewer,
@ -69,10 +71,10 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
const lang = useLang();
useEffect(() => {
if (lastSyncTime && userId) {
if (connectionState === 'connectionStateReady' && userId && !forceShowSelf) {
loadFullUser({ userId });
}
}, [userId, loadFullUser, lastSyncTime]);
}, [userId, loadFullUser, connectionState, forceShowSelf]);
usePhotosPreload(user || chat, photos, currentPhotoIndex);
@ -80,9 +82,9 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
openMediaViewer({
avatarOwnerId: userId || chatId,
profilePhotoIndex: currentPhotoIndex,
origin: MediaViewerOrigin.ProfileAvatar,
origin: forceShowSelf ? MediaViewerOrigin.SettingsAvatar : MediaViewerOrigin.ProfileAvatar,
});
}, [openMediaViewer, userId, chatId, currentPhotoIndex]);
}, [openMediaViewer, userId, chatId, currentPhotoIndex, forceShowSelf]);
const selectPreviousMedia = useCallback(() => {
if (isFirst) {
@ -174,7 +176,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
const isVerifiedIconShown = (user && user.isVerified) || (chat && chat.isVerified);
return (
<div className="ProfileInfo" dir={lang.isRtl ? 'rtl' : undefined}>
<div className={buildClassName('ProfileInfo', forceShowSelf && 'self')} dir={lang.isRtl ? 'rtl' : undefined}>
<div className="photo-wrapper">
{renderPhotoTabs()}
<Transition activeKey={currentPhotoIndex} name={slideAnimation} className="profile-slide-container">
@ -218,15 +220,15 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
export default memo(withGlobal<OwnProps>(
(global, { userId, forceShowSelf }): StateProps => {
const { lastSyncTime, serverTimeOffset } = global;
const { connectionState, serverTimeOffset } = global;
const user = selectUser(global, userId);
const chat = selectChat(global, userId);
const isSavedMessages = !forceShowSelf && user && user.isSelf;
const { animationLevel } = global.settings.byKey;
return {
lastSyncTime, user, chat, isSavedMessages, animationLevel, serverTimeOffset,
connectionState, user, chat, isSavedMessages, animationLevel, serverTimeOffset,
};
},
(setGlobal, actions): DispatchProps => pick(actions, ['loadFullUser', 'openMediaViewer']),
)(PrivateChatInfo));
)(ProfileInfo));

View File

@ -7,7 +7,7 @@ import {
import {
getChatAvatarHash, isDeletedUser, getUserColorKey, getChatTitle, isChatPrivate, getUserFullName,
} from '../../modules/helpers';
import renderText from '../common/helpers/renderText';
import renderText from './helpers/renderText';
import buildClassName from '../../util/buildClassName';
import { getFirstLetters } from '../../util/textFormat';
import useMedia from '../../hooks/useMedia';
@ -59,11 +59,16 @@ const ProfilePhoto: FC<OwnProps> = ({
}
const imageHash = getMediaHash();
const fullMediaData = useMedia(imageHash, false, ApiMediaFormat.BlobUrl, lastSyncTime);
const fullMediaData = useMedia(
imageHash,
false,
imageHash?.startsWith('avatar') ? ApiMediaFormat.DataUri : ApiMediaFormat.BlobUrl,
lastSyncTime,
);
const avatarThumbnailData = useMedia(
!fullMediaData && isFirstPhoto ? getMediaHash('normal', true) : undefined,
false,
ApiMediaFormat.BlobUrl,
ApiMediaFormat.DataUri,
lastSyncTime,
);
const thumbDataUri = useBlurSync(!fullMediaData && photo && photo.thumbnail && photo.thumbnail.dataUri);

View File

@ -19,6 +19,7 @@
font-size: 1.25rem;
font-weight: 500;
margin-left: 1.375rem;
margin-right: auto;
}
.SearchInput {
@ -33,4 +34,13 @@
@media (max-width: 600px) {
padding: 0.5rem;
}
.Button.smaller {
width: 2.5rem;
height: 2.5rem;
+ .DropdownMenu {
margin-left: .25rem;
}
}
}

View File

@ -6,6 +6,10 @@
flex-direction: column;
overflow: hidden;
}
.left-header {
padding-right: .8125rem;
}
}
.settings-main-header {
@ -30,6 +34,7 @@
background: var(--color-background);
height: calc(100% - var(--header-height));
overflow-y: auto;
overflow-y: overlay;
&.infinite-scroll {
display: flex;
@ -84,36 +89,17 @@
}
.settings-main-menu {
padding: 0 0.5rem 1rem;
}
padding: 0 0.5rem .75rem;
.settings-current-user {
margin-bottom: 1.125rem;
text-align: center;
> .ChatExtra {
padding: 0 .5rem .3125rem;
margin: 0 -.5rem .625rem;
box-shadow: inset 0 -.0625rem 0 0 var(--color-background-secondary-accent);
border-bottom: .625rem solid var(--color-background-secondary);
.Avatar {
margin: 0 auto 1.5rem;
.ListItem.narrow {
margin-bottom: .25rem;
}
.name {
display: flex;
justify-content: center;
align-items: center;
font-weight: 500;
font-size: 1.5rem;
line-height: 2rem;
margin: 0;
.VerifiedIcon {
margin-left: 0.25rem;
margin-top: 0.1rem;
}
}
.phone {
font-size: 0.875rem;
color: #868e96;
margin: 0;
}
}

View File

@ -334,6 +334,7 @@ const Settings: FC<OwnProps> = ({
currentScreen={currentScreen}
onReset={handleReset}
onSaveFilter={handleSaveFilter}
onScreenSelect={onScreenSelect}
editedFolderId={foldersState.folderId}
/>
{renderCurrentSectionContent(isScreenActive, currentKey)}

View File

@ -20,6 +20,7 @@ type OwnProps = {
editedFolderId?: number;
onReset: () => void;
onSaveFilter: () => void;
onScreenSelect: (screen: SettingsScreens) => void;
};
type DispatchProps = Pick<GlobalActions, 'signOut' | 'deleteChatFolder'>;
@ -31,6 +32,7 @@ const SettingsHeader: FC<OwnProps & DispatchProps> = ({
onSaveFilter,
signOut,
deleteChatFolder,
onScreenSelect,
}) => {
const [isSignOutDialogOpen, setIsSignOutDialogOpen] = useState(false);
const [isDeleteFolderDialogOpen, setIsDeleteFolderDialogOpen] = useState(false);
@ -205,6 +207,16 @@ const SettingsHeader: FC<OwnProps & DispatchProps> = ({
<div className="settings-main-header">
<h3>{lang('SETTINGS')}</h3>
<Button
round
ripple={!IS_SINGLE_COLUMN_LAYOUT}
size="smaller"
color="translucent"
onClick={() => onScreenSelect(SettingsScreens.EditProfile)}
ariaLabel={lang('lng_settings_information')}
>
<i className="icon-edit" />
</Button>
<DropdownMenu
className="settings-more-menu"
trigger={SettingsMenuButton}

View File

@ -1,18 +1,18 @@
import React, { FC, memo } from '../../../lib/teact/teact';
import React, { FC, memo, useEffect } from '../../../lib/teact/teact';
import { withGlobal } from '../../../lib/teact/teactn';
import { GlobalActions } from '../../../global/types';
import { SettingsScreens } from '../../../types';
import { ApiUser } from '../../../api/types';
import { selectUser } from '../../../modules/selectors';
import { getUserFullName } from '../../../modules/helpers';
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
import renderText from '../../common/helpers/renderText';
import { pick } from '../../../util/iteratees';
import useLang from '../../../hooks/useLang';
import useHistoryBack from '../../../hooks/useHistoryBack';
import ListItem from '../../ui/ListItem';
import Avatar from '../../common/Avatar';
import ProfileInfo from '../../common/ProfileInfo';
import ChatExtra from '../../common/ChatExtra';
type OwnProps = {
isActive?: boolean;
@ -22,16 +22,27 @@ type OwnProps = {
type StateProps = {
currentUser?: ApiUser;
lastSyncTime?: number;
};
const SettingsMain: FC<OwnProps & StateProps> = ({
type DispatchProps = Pick<GlobalActions, 'loadProfilePhotos'>;
const SettingsMain: FC<OwnProps & StateProps & DispatchProps> = ({
isActive,
onScreenSelect,
onReset,
loadProfilePhotos,
currentUser,
lastSyncTime,
}) => {
const lang = useLang();
const fullName = getUserFullName(currentUser);
const profileId = currentUser ? currentUser.id : undefined;
useEffect(() => {
if (profileId && lastSyncTime) {
loadProfilePhotos({ profileId });
}
}, [lastSyncTime, profileId, loadProfilePhotos]);
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Main);
@ -39,24 +50,17 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
<div className="settings-content custom-scroll">
<div className="settings-main-menu">
{currentUser && (
<div className="settings-current-user">
<Avatar user={currentUser} size="jumbo" />
<p className="name">{fullName && renderText(fullName)}</p>
<p className="phone">{formatPhoneNumberWithCode(currentUser.phoneNumber)}</p>
</div>
<ProfileInfo
userId={currentUser.id}
forceShowSelf
/>
)}
{currentUser && (
<ChatExtra
chatOrUserId={currentUser.id}
forceShowSelf
/>
)}
<ListItem
icon="edit"
onClick={() => onScreenSelect(SettingsScreens.EditProfile)}
>
{lang('lng_settings_information')}
</ListItem>
<ListItem
icon="folder"
onClick={() => onScreenSelect(SettingsScreens.Folders)}
>
{lang('Filters')}
</ListItem>
<ListItem
icon="settings"
onClick={() => onScreenSelect(SettingsScreens.General)}
@ -75,6 +79,12 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
>
{lang('PrivacySettings')}
</ListItem>
<ListItem
icon="folder"
onClick={() => onScreenSelect(SettingsScreens.Folders)}
>
{lang('Filters')}
</ListItem>
<ListItem
icon="language"
onClick={() => onScreenSelect(SettingsScreens.Language)}
@ -88,10 +98,12 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const { currentUserId } = global;
const { currentUserId, lastSyncTime } = global;
return {
currentUser: currentUserId ? selectUser(global, currentUserId) : undefined,
lastSyncTime,
};
},
(setGlobal, actions): DispatchProps => pick(actions, ['loadProfilePhotos']),
)(SettingsMain));

View File

@ -143,7 +143,7 @@ export function animateClosing(origin: MediaViewerOrigin, bestImageData: string,
const existingGhost = document.getElementsByClassName('ghost')[0] as HTMLDivElement;
const ghost = existingGhost || createGhost(bestImageData || toImage, origin === MediaViewerOrigin.ProfileAvatar);
const ghost = existingGhost || createGhost(bestImageData || toImage, origin);
if (!existingGhost) {
applyStyles(ghost, {
top: `${toTop}px`,
@ -203,7 +203,7 @@ export function animateClosing(origin: MediaViewerOrigin, bestImageData: string,
});
}
function createGhost(source: string | HTMLImageElement | HTMLVideoElement, shouldAppendProfileInfo = false) {
function createGhost(source: string | HTMLImageElement | HTMLVideoElement, origin?: MediaViewerOrigin) {
const ghost = document.createElement('div');
ghost.classList.add('ghost');
@ -219,9 +219,16 @@ function createGhost(source: string | HTMLImageElement | HTMLVideoElement, shoul
ghost.appendChild(img);
if (shouldAppendProfileInfo) {
if (origin === MediaViewerOrigin.ProfileAvatar || origin === MediaViewerOrigin.SettingsAvatar) {
ghost.classList.add('ProfileInfo');
const profileInfo = document.querySelector('#RightColumn .ProfileInfo .info');
if (origin === MediaViewerOrigin.SettingsAvatar) {
ghost.classList.add('self');
}
const profileInfo = document.querySelector(
origin === MediaViewerOrigin.ProfileAvatar
? '#RightColumn .ProfileInfo .info'
: '#Settings .ProfileInfo .info',
);
if (profileInfo) {
ghost.appendChild(profileInfo.cloneNode(true));
}
@ -314,6 +321,11 @@ function getNodes(origin: MediaViewerOrigin, message?: ApiMessage) {
mediaSelector = 'img.avatar-media';
break;
case MediaViewerOrigin.SettingsAvatar:
containerSelector = '#Settings .ProfileInfo .active .ProfilePhoto';
mediaSelector = 'img.avatar-media';
break;
case MediaViewerOrigin.ProfileAvatar:
containerSelector = '#RightColumn .ProfileInfo .active .ProfilePhoto';
mediaSelector = 'img.avatar-media';
@ -345,6 +357,7 @@ function applyShape(ghost: HTMLDivElement, origin: MediaViewerOrigin) {
break;
case MediaViewerOrigin.SharedMedia:
case MediaViewerOrigin.SettingsAvatar:
case MediaViewerOrigin.ProfileAvatar:
case MediaViewerOrigin.SearchResult:
(ghost.firstChild as HTMLElement).style.objectFit = 'cover';

View File

@ -46,10 +46,10 @@ import TabList from '../ui/TabList';
import Spinner from '../ui/Spinner';
import ListItem from '../ui/ListItem';
import PrivateChatInfo from '../common/PrivateChatInfo';
import ProfileInfo from './ProfileInfo';
import ProfileInfo from '../common/ProfileInfo';
import Document from '../common/Document';
import Audio from '../common/Audio';
import ChatExtra from './ChatExtra';
import ChatExtra from '../common/ChatExtra';
import Media from '../common/Media';
import WebLink from '../common/WebLink';
import NothingFound from '../common/NothingFound';
@ -405,11 +405,8 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
function renderProfileInfo(chatId: number, resolvedUserId?: number) {
return (
<div className="profile-info">
<ProfileInfo
userId={resolvedUserId || chatId}
forceShowSelf={resolvedUserId !== chatId}
/>
<ChatExtra chatOrUserId={resolvedUserId || chatId} forceShowSelf={resolvedUserId !== chatId} />
<ProfileInfo userId={resolvedUserId || chatId} />
<ChatExtra chatOrUserId={resolvedUserId || chatId} />
</div>
);
}

View File

@ -230,6 +230,7 @@ export enum MediaViewerOrigin {
ScheduledInline,
SharedMedia,
ProfileAvatar,
SettingsAvatar,
MiddleHeaderAvatar,
Album,
ScheduledAlbum,