mirror of
https://github.com/danog/telegram-tt.git
synced 2024-12-12 00:59:52 +01:00
Settings: New design for self profile (#1373)
This commit is contained in:
parent
f039f69331
commit
ad36326c83
@ -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>
|
||||
);
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
@ -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);
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,6 +334,7 @@ const Settings: FC<OwnProps> = ({
|
||||
currentScreen={currentScreen}
|
||||
onReset={handleReset}
|
||||
onSaveFilter={handleSaveFilter}
|
||||
onScreenSelect={onScreenSelect}
|
||||
editedFolderId={foldersState.folderId}
|
||||
/>
|
||||
{renderCurrentSectionContent(isScreenActive, currentKey)}
|
||||
|
@ -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}
|
||||
|
@ -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));
|
||||
|
@ -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';
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -230,6 +230,7 @@ export enum MediaViewerOrigin {
|
||||
ScheduledInline,
|
||||
SharedMedia,
|
||||
ProfileAvatar,
|
||||
SettingsAvatar,
|
||||
MiddleHeaderAvatar,
|
||||
Album,
|
||||
ScheduledAlbum,
|
||||
|
Loading…
Reference in New Issue
Block a user