diff --git a/src/components/common/Media.scss b/src/components/common/Media.scss index 84c7ccc4b..e058b6131 100644 --- a/src/components/common/Media.scss +++ b/src/components/common/Media.scss @@ -10,11 +10,11 @@ .video-duration { position: absolute; - top: 0.25rem; - left: 0.25rem; + top: 0.3125rem; + left: 0.3125rem; padding: 0 0.3125rem; - border-radius: 0.1875rem; + border-radius: 0.375rem; font-size: 0.75rem; line-height: 1.125rem; diff --git a/src/components/common/gift/SavedGift.module.scss b/src/components/common/gift/SavedGift.module.scss index ff46b6441..41c13d3ce 100644 --- a/src/components/common/gift/SavedGift.module.scss +++ b/src/components/common/gift/SavedGift.module.scss @@ -13,7 +13,7 @@ padding-top: 0.875rem; border-radius: 0.625rem; - background-color: var(--color-background-secondary); + background-color: var(--color-background); &::before { pointer-events: none; @@ -30,7 +30,7 @@ } &:hover::before { - opacity: 1; + opacity: 0.75; } } diff --git a/src/components/common/profile/ChatExtra.module.scss b/src/components/common/profile/ChatExtra.module.scss index f5ead89df..fc1410288 100644 --- a/src/components/common/profile/ChatExtra.module.scss +++ b/src/components/common/profile/ChatExtra.module.scss @@ -19,7 +19,7 @@ grid-template-rows: auto auto; column-gap: 0.5rem; - margin-bottom: 0.5rem; + margin-bottom: 1rem; @include mixins.with-vt-type('chatExtra'); } @@ -40,6 +40,10 @@ @include mixins.with-vt-type('chatExtra'); } +.switch { + margin-inline-start: auto; +} + .botVerificationSection, .sectionInfo { font-size: 0.875rem; @@ -71,6 +75,7 @@ .personalChannelItem { grid-column: 1 / span 2; grid-row: 2; + margin-top: 0.375rem; :global(.Avatar) { margin-right: 0.9375rem !important; @@ -144,6 +149,10 @@ cursor: var(--custom-cursor, pointer); } +.securityRiskIsland { + margin-bottom: 1rem; +} + .unofficialSecurityRisk { margin-bottom: 0.5rem; padding-inline: 0.5rem; diff --git a/src/components/common/profile/ChatExtra.tsx b/src/components/common/profile/ChatExtra.tsx index 2dcaaf267..2729f1e2b 100644 --- a/src/components/common/profile/ChatExtra.tsx +++ b/src/components/common/profile/ChatExtra.tsx @@ -57,10 +57,11 @@ import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; +import Island from '../../gili/layout/Island'; +import Switch from '../../gili/primitives/Switch'; import Chat from '../../left/main/Chat'; import Button from '../../ui/Button'; import ListItem from '../../ui/ListItem'; -import Switcher from '../../ui/Switcher'; import CompactMapPreview from '../CompactMapPreview'; import CustomEmoji from '../CustomEmoji'; import Icon from '../icons/Icon'; @@ -75,7 +76,9 @@ type OwnProps = { isOwnProfile?: boolean; isSavedDialog?: boolean; isInSettings?: boolean; + withIslands?: boolean; className?: string; + style?: string; }; type StateProps = { @@ -127,7 +130,9 @@ const ChatExtra = ({ botAppPermissions, botVerification, className, + style, isInSettings, + withIslands, canViewSubscribers, }: OwnProps & StateProps) => { const { @@ -387,13 +392,17 @@ const ChatExtra = ({ ); } + const Wrapper = withIslands ? Island : 'div'; + return ( -
+
{user && userFullInfo?.isUnofficialSecurityRisk && ( -
- - {lang('UnofficialSecurityRisk', { peer: getPeerTitle(lang, user) })} -
+ +
+ + {lang('UnofficialSecurityRisk', { peer: getPeerTitle(lang, user) })} +
+
)} {personalChannel && (
@@ -401,244 +410,244 @@ const ChatExtra = ({ {oldLang('Subscribers', personalChannel.membersCount, 'i')} - + + +
)} - {Boolean(formattedNumber?.length) && ( - - {formattedNumber} - {oldLang('Phone')} - - )} - {activeUsernames && renderUsernames(activeUsernames)} - {description && Boolean(description.length) && ( - - - { - renderText(description, [ - 'br', - shouldRenderAllLinks ? 'links' : 'tg_links', - 'emoji', - ]) - } - - {oldLang(userId ? 'UserBio' : 'Info')} - - )} - {activeChatUsernames && !isTopicInfo && renderUsernames(activeChatUsernames, true)} - {((!activeChatUsernames && canInviteUsers) || isTopicInfo) && link && ( - copy(link, oldLang('SetUrlPlaceholder'))} - style={createVtnStyle('link')} - > -
{link}
- {oldLang('SetUrlPlaceholder')} -
- )} - {birthday && ( - - )} - {hasMainMiniApp && ( - - -
- {appTermsInfo} -
-
- )} - {!isOwnProfile && !isInSettings && ( - - {lang('Notifications')} - - - )} - {businessWorkHours && ( - - )} - {businessLocation && ( - -
{businessLocation.address}
- {oldLang('BusinessProfileLocation')} -
- )} - {shouldRenderNote && ( - -
{formattedNumber} + {oldLang('Phone')} + + )} + {activeUsernames && renderUsernames(activeUsernames)} + {description && Boolean(description.length) && ( + - {renderTextWithEntities({ - text: note.text, - entities: note.entities, - })} -
-
- {lang('UserNoteTitle')} + + { + renderText(description, [ + 'br', + shouldRenderAllLinks ? 'links' : 'tg_links', + 'emoji', + ]) + } + + {oldLang(userId ? 'UserBio' : 'Info')} + + )} + {activeChatUsernames && !isTopicInfo && renderUsernames(activeChatUsernames, true)} + {((!activeChatUsernames && canInviteUsers) || isTopicInfo) && link && ( + copy(link, oldLang('SetUrlPlaceholder'))} + style={createVtnStyle('link')} + > +
{link}
+ {oldLang('SetUrlPlaceholder')} +
+ )} + {birthday && ( + + )} + {hasMainMiniApp && ( + + +
+ {appTermsInfo} +
+
+ )} + {!isOwnProfile && !isInSettings && ( + + {lang('Notifications')} + + + )} + {businessWorkHours && ( + + )} + {businessLocation && ( + +
{businessLocation.address}
+ {oldLang('BusinessProfileLocation')} +
+ )} + {shouldRenderNote && ( + +
+ {renderTextWithEntities({ + text: note.text, + entities: note.entities, + })} +
+
+ {lang('UserNoteTitle')} - {lang('UserNoteHint')} - {isNoteCollapsible && ( - - )} + {lang('UserNoteHint')} + {isNoteCollapsible && ( + + )} +
+
+ )} + {hasSavedMessages && !isOwnProfile && !isInSettings && ( + + {oldLang('SavedMessagesTab')} + + )} + {userFullInfo && 'isBotAccessEmojiGranted' in userFullInfo && ( + + {oldLang('BotProfilePermissionEmojiStatus')} + + + )} + {botAppPermissions?.geolocation !== undefined && ( + + {oldLang('BotProfilePermissionLocation')} + + + )} + {canViewSubscribers && ( + +
{lang('ProfileItemSubscribers')}
+ {lang.number(chat?.membersCount || 0)} +
+ )} + {botVerification && ( +
+ + {botVerification.description}
- - )} - {hasSavedMessages && !isOwnProfile && !isInSettings && ( - - {oldLang('SavedMessagesTab')} - - )} - {userFullInfo && 'isBotAccessEmojiGranted' in userFullInfo && ( - - {oldLang('BotProfilePermissionEmojiStatus')} - - - )} - {botAppPermissions?.geolocation !== undefined && ( - - {oldLang('BotProfilePermissionLocation')} - - - )} - {canViewSubscribers && ( - -
{lang('ProfileItemSubscribers')}
- {lang.number(chat?.membersCount || 0)} -
- )} - {botVerification && ( -
- - {botVerification.description} -
- )} + )} +
); }; diff --git a/src/components/common/profile/ProfileInfo.module.scss b/src/components/common/profile/ProfileInfo.module.scss index 1ae7fb033..72a3774a7 100644 --- a/src/components/common/profile/ProfileInfo.module.scss +++ b/src/components/common/profile/ProfileInfo.module.scss @@ -1,6 +1,7 @@ @use "../../../styles/mixins"; .root { + --profile-info-bg: var(--color-background); --rating-outline-color: #000000; --rating-text-color: #000000; @@ -14,7 +15,7 @@ color: var(--color-white); - background-color: var(--color-background); + background-color: var(--profile-info-bg); @include mixins.with-vt-type('profileAvatar'); @@ -33,7 +34,7 @@ color: var(--color-text); .info { - background-image: linear-gradient(0deg, var(--color-background) 50%, transparent); + background-image: linear-gradient(0deg, var(--profile-info-bg) 50%, transparent); } .userRatingNegativeWrapper, @@ -445,11 +446,6 @@ } @include mixins.on-active-vt('profileAvatar') { - &::view-transition-old(.profileInfo), - &::view-transition-new(.profileInfo) { - animation-name: none; - } - &::view-transition-group(.photoDashes) { z-index: 1; } @@ -460,10 +456,22 @@ &::view-transition-group(.profileInfo) { z-index: -2; + overflow: clip; + } + + &::view-transition-old(.profileInfo), + &::view-transition-new(.profileInfo) { + object-fit: none; + object-position: top; + animation-name: none; } } @include mixins.on-active-vt('profileExpand') { + &::view-transition-old(.profileInfo) { + opacity: 0; + } + &::view-transition-old(.avatar) { z-index: -1; animation-name: none; @@ -487,6 +495,10 @@ } @include mixins.on-active-vt('profileCollapse') { + &::view-transition-new(.profileInfo) { + opacity: 0; + } + &::view-transition-new(.avatar) { z-index: -1; animation-name: none; diff --git a/src/components/gili/layout/Surface.module.scss b/src/components/gili/layout/Surface.module.scss index a7a4dd069..693737bbe 100644 --- a/src/components/gili/layout/Surface.module.scss +++ b/src/components/gili/layout/Surface.module.scss @@ -12,4 +12,8 @@ @include mixins.adapt-padding-to-scrollbar(1rem); } + + .noPadding { + padding-inline: 0; + } } diff --git a/src/components/gili/layout/Surface.tsx b/src/components/gili/layout/Surface.tsx index 0e1ad6bc0..bd3c7da50 100644 --- a/src/components/gili/layout/Surface.tsx +++ b/src/components/gili/layout/Surface.tsx @@ -1,14 +1,20 @@ +import type { ElementRef } from '../../../lib/teact/teact'; + import buildClassName from '../../../util/buildClassName'; import styles from './Surface.module.scss'; type OwnProps = React.HTMLAttributes & { + ref?: ElementRef; scrollable?: boolean; + noPadding?: boolean; children: React.ReactNode; }; const Surface = ({ + ref, scrollable, + noPadding, className, children, ...otherProps @@ -17,10 +23,12 @@ const Surface = ({ return (
void; + onChange?: (checked: boolean) => void; }; type Props = OwnProps & Omit; @@ -40,7 +40,7 @@ const Switch = ({ const isDisabled = disabled || interactive?.isDisabled || interactive?.isLoading; const handleChange = useLastCallback((e: React.ChangeEvent) => { - onChange(e.currentTarget.checked); + onChange?.(e.currentTarget.checked); }); if (interactive?.isLoading) return undefined; diff --git a/src/components/left/main/Chat.tsx b/src/components/left/main/Chat.tsx index da5f27c85..277b94c75 100644 --- a/src/components/left/main/Chat.tsx +++ b/src/components/left/main/Chat.tsx @@ -413,7 +413,7 @@ const Chat: FC = ({ ref={ref} className={chatClassName} href={href} - style={`top: ${offsetTop}px`} + style={offsetTop !== undefined ? `top: ${offsetTop}px` : undefined} ripple={!isForum && !isMobile} contextActions={contextActions} withPortalForMenu diff --git a/src/components/left/main/ChatFolderTabList.module.scss b/src/components/left/main/ChatFolderTabList.module.scss new file mode 100644 index 000000000..853fbf217 --- /dev/null +++ b/src/components/left/main/ChatFolderTabList.module.scss @@ -0,0 +1,44 @@ +@layer component.chatFolderTabList { + .root { + --fade-mask-height: 2.625rem; + + position: absolute; + z-index: 4; + top: 0.25rem; + inset-inline: 0.5rem; + + transition: opacity var(--layer-transition); + } + + .hidden { + pointer-events: none; + opacity: 0.25; + } + + .badge { + flex-shrink: 0; + + min-width: 1.25rem; + height: 1.25rem; + margin-inline-start: 0.25rem; + padding: 0 0.3125rem; + border-radius: 0.75rem; + + font-size: 0.75rem; + font-weight: var(--font-weight-medium); + line-height: 1.25rem; + color: white; + text-align: center; + + background: var(--color-gray); + } + + .badgeActive { + background: var(--color-primary); + } + + :global(html.theme-dark) .tabList { + border: 1px solid var(--color-borders); + box-shadow: none; + } +} diff --git a/src/components/left/main/ChatFolderTabList.tsx b/src/components/left/main/ChatFolderTabList.tsx new file mode 100644 index 000000000..6089da06c --- /dev/null +++ b/src/components/left/main/ChatFolderTabList.tsx @@ -0,0 +1,51 @@ +import { memo } from '../../../lib/teact/teact'; + +import type { TabWithProperties } from '../../ui/TabList'; + +import buildClassName from '../../../util/buildClassName'; + +import useLastCallback from '../../../hooks/useLastCallback'; + +import TabList from '../../ui/TabList'; + +import styles from './ChatFolderTabList.module.scss'; + +type OwnProps = { + tabs: readonly TabWithProperties[]; + activeTab: number; + isHidden?: boolean; + className?: string; + onSwitchTab: (index: number) => void; +}; + +const ChatFolderTabList = ({ + tabs, + activeTab, + isHidden, + className, + onSwitchTab, +}: OwnProps) => { + const renderExtra = useLastCallback((tab: TabWithProperties) => { + if (!tab.badgeCount) return undefined; + return ( + + {tab.badgeCount} + + ); + }); + + return ( +
+ +
+ ); +}; + +export default memo(ChatFolderTabList); diff --git a/src/components/left/main/ChatFolders.tsx b/src/components/left/main/ChatFolders.tsx index 071bfc4bd..042695f6b 100644 --- a/src/components/left/main/ChatFolders.tsx +++ b/src/components/left/main/ChatFolders.tsx @@ -26,8 +26,8 @@ import useScrolledState from '../../../hooks/useScrolledState'; import useShowTransition from '../../../hooks/useShowTransition'; import StoryRibbon from '../../story/StoryRibbon'; -import SquareTabList from '../../ui/SquareTabList'; import Transition from '../../ui/Transition'; +import ChatFolderTabList from './ChatFolderTabList'; import ChatList from './ChatList'; type OwnProps = { @@ -87,7 +87,7 @@ const ChatFolders: FC = ({ const lang = useLang(); - const { isAtBeginning: isNotScrolled, handleScroll, updateScrollState } = useScrolledState(); + const { handleScroll, updateScrollState } = useScrolledState(); useEffect(() => { loadChatFolders(); @@ -119,6 +119,7 @@ const ChatFolders: FC = ({ const { displayedFolders, folderTabs } = useFolderTabs({ sidebarMode: false, + noEmoticons: true, orderedFolderIds, chatFoldersById, maxFolders, @@ -252,31 +253,31 @@ const ChatFolders: FC = ({ ref={ref} className={buildClassName( 'ChatFolders', - shouldRenderFolders && shouldHideFolderTabs && 'ChatFolders--tabs-hidden', shouldRenderStoryRibbon && 'with-story-ribbon', isFoldersSidebarShown && 'ChatFolders--tabs-sidebar-shown', )} > {shouldRenderStoryRibbon && } - {shouldRenderFolders ? ( - - ) : shouldRenderPlaceholder ? ( -
- ) : undefined} - - {renderCurrentTab} - +
+ {shouldRenderFolders ? ( + + ) : shouldRenderPlaceholder ? ( +
+ ) : undefined} + + {renderCurrentTab} + +
); }; diff --git a/src/components/left/main/ChatList.tsx b/src/components/left/main/ChatList.tsx index ec2381e03..f6fe33056 100644 --- a/src/components/left/main/ChatList.tsx +++ b/src/components/left/main/ChatList.tsx @@ -48,6 +48,11 @@ type OwnProps = { isFoldersSidebarShown?: boolean; isStoryRibbonShown?: boolean; foldersDispatch?: FolderEditDispatch; + noAbsolutePositioning?: boolean; + noVirtualization?: boolean; + noScrollRestore?: boolean; + noFastList?: boolean; + scrollContainerClosest?: string; onScroll?: (e: React.UIEvent) => void; }; @@ -67,6 +72,11 @@ const ChatList = ({ isFoldersSidebarShown, isStoryRibbonShown, foldersDispatch, + noAbsolutePositioning, + noVirtualization, + noScrollRestore, + noFastList, + scrollContainerClosest, onScroll, }: OwnProps) => { const { @@ -99,7 +109,10 @@ const ChatList = ({ orderDiffById, shiftDiff, getAnimationType, onReorderAnimationEnd: onReorderAnimationEnd, } = useOrderDiff(orderedIds, panesHeight); - const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, CHAT_LIST_SLICE); + const chatListSlice = noVirtualization + ? Math.max(CHAT_LIST_SLICE, orderedIds?.length || 0) + : CHAT_LIST_SLICE; + const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, chatListSlice); // Support + to navigate between chats useHotkeys(useMemo(() => (isActive && orderedIds?.length ? { @@ -194,7 +207,9 @@ const ChatList = ({ return viewportIds!.map((id, i) => { const isPinned = viewportOffset + i < pinnedCount; - const offsetTop = panesHeight + archiveHeight + (viewportOffset + i) * CHAT_HEIGHT_PX; + const offsetTop = noAbsolutePositioning + ? undefined + : panesHeight + archiveHeight + (viewportOffset + i) * CHAT_HEIGHT_PX; return ( diff --git a/src/components/left/main/LeftMain.scss b/src/components/left/main/LeftMain.scss index 22a78e74f..641fc0ada 100644 --- a/src/components/left/main/LeftMain.scss +++ b/src/components/left/main/LeftMain.scss @@ -30,35 +30,27 @@ } } - .SquareTabList { - z-index: 1; - - justify-content: flex-start; - - border-bottom: 1px solid var(--color-borders); - - opacity: 1; - box-shadow: none; - - transition: opacity var(--layer-transition), box-shadow 0.2s, border-color 0.2s; - - &.scrolled { - border-bottom-color: transparent; - box-shadow: 0 2px 2px var(--color-light-shadow); - } - } - - &--tabs-hidden .SquareTabList { - pointer-events: none; - opacity: 0.25; - } - &--tabs-sidebar-shown .chat-list { padding-top: 0; } - .Tab { - flex: 0 0 auto; + &-content { + position: relative; + display: flex; + flex: 1; + flex-direction: column; + + > .Transition { + flex: 1; + } + + &.with-tabs > .Transition { + margin-top: 2.5rem; + } + + &.with-tabs .chat-list { + padding-top: 1.375rem; + } } > .Transition { diff --git a/src/components/middle/composer/AiMessageEditorModal/AiMessageEditorModal.module.scss b/src/components/middle/composer/AiMessageEditorModal/AiMessageEditorModal.module.scss index c93eb2866..1b94f5d13 100644 --- a/src/components/middle/composer/AiMessageEditorModal/AiMessageEditorModal.module.scss +++ b/src/components/middle/composer/AiMessageEditorModal/AiMessageEditorModal.module.scss @@ -27,26 +27,13 @@ padding: 0; } -.tabListWrapper { +.fadeMask { + + --fade-mask-color: var(--color-background-secondary); + position: absolute; z-index: 2; width: 100%; - - &::after { - pointer-events: none; - content: ""; - - position: absolute; - z-index: 0; - top: calc(100% - 2rem); - left: 0; - - width: calc(100% - 2rem); - height: 3.5rem; - margin-inline: 1rem; - - background: linear-gradient(to bottom, var(--color-background-secondary) 0%, transparent 100%); - } } .tabList { diff --git a/src/components/middle/composer/AiMessageEditorModal/AiMessageEditorModal.tsx b/src/components/middle/composer/AiMessageEditorModal/AiMessageEditorModal.tsx index cdad058e1..f316988db 100644 --- a/src/components/middle/composer/AiMessageEditorModal/AiMessageEditorModal.tsx +++ b/src/components/middle/composer/AiMessageEditorModal/AiMessageEditorModal.tsx @@ -280,17 +280,17 @@ const AiMessageEditorModal = ({ )} isSlim > -
- -
+
.profile-info > .ChatInfo { - grid-area: chat_info; - - .status.online { - color: var(--color-primary); - } - } - - > .profile-info > .ChatExtra { - padding: 0.875rem 0.5rem 0.5rem; - - @include mixins.adapt-padding-to-scrollbar(0.5rem); - @include mixins.side-panel-section; - @include mixins.with-vt-type('rightColumn'); - - .narrow { - margin-bottom: 0; - } - - [dir="rtl"] { - .Switcher { - margin-right: auto; - margin-left: 0; - } - } - } - - .hidden { - display: none; - } - - .FloatingActionButton { - z-index: 1; - - &.revealed { - transition-delay: 0.2s; - } - } -} - -.nothing-found-gifts { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - - padding-top: 5rem; - - .description { - unicode-bidi: plaintext; - - margin-block: 1rem; - - font-size: 1rem; - font-weight: var(--font-weight-medium); - color: var(--color-text-secondary); - text-align: center; - } - - .Link { - font-weight: var(--font-weight-medium); - color: var(--color-links); - transition: opacity 0.15s ease-in; - - &:active, - &:hover { - text-decoration: none; - opacity: 0.85; - } - } -} - -.shared-media { - display: flex; - flex: 1; - flex-direction: column-reverse; - - @include mixins.with-vt-type('rightColumn'); - - .SquareTabList { - z-index: 1; - background: var(--color-background); - } - - .info .Transition { - flex-grow: 0; - } - - .Transition { - flex: 1; - } - - .saved-dialogs { - height: 100% !important; - } - - .shared-media-transition { - overflow: hidden; - } - - .content { - transition: transform var(--layer-transition); - &.showContentPanel { - transform: translateY(3rem); - padding-bottom: 3.5rem !important; - } - &.noTransition { - transition: none; - } - - &.empty-list { - display: flex; - align-items: flex-start; - justify-content: center; - - height: 100%; - padding-top: 5rem; - - .Spinner { - --spinner-size: 2.75rem; - } - } - - &.storiesArchive-list, - &.stories-list, - &.media-list, - &.gif-list, - &.previewMedia-list, - &.gifts-list { - display: grid; - grid-auto-rows: 1fr; - grid-template-columns: repeat(3, 1fr); - gap: 0.0625rem; - } - - &.gifts-list { - gap: 0.625rem; - } - - &.documents-list { - padding: 1.25rem; - - & .File + .File { - margin-top: 1.25rem; - } - } - - &.links-list { - padding: 1.25rem; - - .ProgressSpinner, - .message-transfer-progress { - display: none; - } - } - - &.audio-list, - &.voice-list { - padding: 1.25rem; - - & .Audio { - .media-loading { - top: 0; - left: 0; - - display: flex; - align-items: center; - justify-content: center; - } - - & + .Audio { - margin-top: 1.6875rem; - } - } - } - - &.similarChannels-list, - &.similarBots-list, - &.commonChats-list, - &.members-list, - &.gifts-list { - padding: 0.5rem; - - @include mixins.adapt-padding-to-scrollbar(0.5rem); - - @media (max-width: 600px) { - padding: 0.5rem 0; - } - } - - &.similarBots-list, - &.similarChannels-list { - .ListItem.blured { - filter: opacity(0.8); - } - - .show-more-bots, - .show-more-channels { - z-index: 1; - - width: calc(100% - 1rem); - margin: 0 auto; - margin-top: -1.8125rem; - border-radius: var(--border-radius-default-small); - - box-shadow: -1rem 0 1rem 1rem var(--color-background), -1rem 0 1rem 0.3125rem var(--color-background); - } - - .more-similar { - margin-top: 1rem; - font-size: 0.8125rem; - text-align: center; - } - } - } - - .contentCategoriesPanel { - position: absolute; - top: 0; - right: 0; - left: 0; - - transition: transform var(--layer-transition), opacity 0.2s ease; - - &.hiddenPanel { - transform: translateY(-100%); - opacity: 0; - } - - &.noTransition { - transition: none; - } - } - - .saved-gift { - @include mixins.with-vt-type('profileGifts'); - } -} diff --git a/src/components/right/Profile.tsx b/src/components/right/Profile.tsx index 5313ef1fd..a6aee17f9 100644 --- a/src/components/right/Profile.tsx +++ b/src/components/right/Profile.tsx @@ -1,4 +1,5 @@ -import { memo, useCallback, useEffect, useMemo, useRef, useState } from '@teact'; +import type { TeactNode } from '@teact'; +import { memo, useEffect, useMemo, useRef, useState } from '@teact'; import { getActions, getGlobal, withGlobal } from '../../global'; import type { @@ -18,15 +19,13 @@ import type { } from '../../api/types'; import type { ProfileCollectionKey } from '../../global/selectors/payments'; import type { TabState } from '../../global/types'; -import type { AnimationLevel, ProfileState, ProfileTabType, SharedMediaType, ThemeKey, ThreadId } from '../../types'; +import type { AnimationLevel, ProfileState, ProfileTabType, + SharedMediaType, ThemeKey, ThreadId } from '../../types'; import type { RegularLangKey } from '../../types/language'; import { MAIN_THREAD_ID } from '../../api/types'; -import { AudioOrigin, MediaViewerOrigin, NewChatMembersProgress } from '../../types'; +import { AudioOrigin, LoadMoreDirection, MediaViewerOrigin, NewChatMembersProgress } from '../../types'; import { MEMBERS_SLICE, PROFILE_SENSITIVE_AREA, SHARED_MEDIA_SLICE, SLIDE_TRANSITION_DURATION } from '../../config'; -import { selectActiveGiftsCollectionId } from '../../global/selectors/payments'; - -const CONTENT_PANEL_SHOW_DELAY = 300; import { getHasAdminRight, getIsDownloading, @@ -63,6 +62,7 @@ import { } from '../../global/selectors'; import { selectPremiumLimit } from '../../global/selectors/limits'; import { selectMessageDownloadableMedia } from '../../global/selectors/media'; +import { selectActiveGiftsCollectionId } from '../../global/selectors/payments'; import { selectSharedSettings } from '../../global/selectors/sharedState'; import { selectActiveStoriesCollectionId } from '../../global/selectors/stories'; import { @@ -85,7 +85,6 @@ import { useViewTransition } from '../../hooks/animations/useViewTransition'; import { useVtn } from '../../hooks/animations/useVtn.ts'; import usePeerStoriesPolling from '../../hooks/polling/usePeerStoriesPolling'; import useTopOverscroll from '../../hooks/scroll/useTopOverscroll.tsx'; -import useCacheBuster from '../../hooks/useCacheBuster'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import useFlag from '../../hooks/useFlag'; import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; @@ -111,6 +110,8 @@ import PrivateChatInfo from '../common/PrivateChatInfo'; import ChatExtra from '../common/profile/ChatExtra'; import ProfileInfo from '../common/profile/ProfileInfo.tsx'; import WebLink from '../common/WebLink'; +import Island from '../gili/layout/Island'; +import Surface from '../gili/layout/Surface'; import ChatList from '../left/main/ChatList'; import MediaStory from '../story/MediaStory'; import Button from '../ui/Button'; @@ -119,13 +120,13 @@ import InfiniteScroll from '../ui/InfiniteScroll'; import Link from '../ui/Link'; import ListItem, { type MenuItemContextAction } from '../ui/ListItem'; import Spinner from '../ui/Spinner'; -import SquareTabList, { type TabWithProperties } from '../ui/SquareTabList'; +import TabList, { type TabWithProperties } from '../ui/TabList'; import Transition from '../ui/Transition'; import DeleteMemberModal from './DeleteMemberModal'; import StarGiftCollectionList from './gifts/StarGiftCollectionList'; import StoryAlbumList from './stories/StoryAlbumList'; -import './Profile.scss'; +import styles from './Profile.module.scss'; type OwnProps = { chatId: string; @@ -134,6 +135,7 @@ type OwnProps = { isMobile?: boolean; isActive: boolean; onProfileStateChange: (state: ProfileState) => void; + onProfileExpandedChange?: (isExpanded: boolean) => void; }; type StateProps = { @@ -208,6 +210,7 @@ const TABS: LocalTabProps[] = [ { type: 'gif', key: 'ProfileTabGifs' }, ]; +const CONTENT_PANEL_SHOW_DELAY = 300; const HIDDEN_RENDER_DELAY = 1000; const INTERSECTION_THROTTLE = 500; @@ -220,6 +223,26 @@ const VALID_USER_MAIN_TAB_TYPES = new Set>([ const SHARED_MEDIA_TYPES = new Set>([ 'media', 'documents', 'links', 'audio', 'voice', 'gif', ]); +const NON_ISLAND_TABS = new Set([ + 'media', 'gif', 'stories', 'storiesArchive', 'previewMedia', 'gifts', +]); + +const CONTENT_LIST_CLASS: Record = { + media: styles.mediaList, + documents: styles.documentsList, + links: styles.linksList, + audio: styles.audioList, + voice: styles.voiceList, + gif: styles.gifList, + stories: styles.storiesList, + storiesArchive: styles.storiesArchiveList, + previewMedia: styles.previewMediaList, + gifts: styles.giftsList, + members: styles.membersList, + commonChats: styles.commonChatsList, + similarChannels: styles.similarChannelsList, + similarBots: styles.similarBotsList, +}; const Profile = ({ chatId, @@ -279,6 +302,7 @@ const Profile = ({ canUpdateMainTab, canAutoPlayGifs, onProfileStateChange, + onProfileExpandedChange, }: OwnProps & StateProps) => { const { setSharedMediaSearchType, @@ -324,7 +348,9 @@ const Profile = ({ const isGeneralSavedMessages = isSavedMessages && !isSavedDialog; const [isProfileExpanded, expandProfile, collapseProfile] = useFlag(); - const [restoreContentHeightKey, setRestoreContentHeightKey] = useState(0); + useEffect(() => { + onProfileExpandedChange?.(isProfileExpanded); + }, [isProfileExpanded, onProfileExpandedChange]); const isUser = isUserId(chatId); const validMainTabTypes = isUser ? VALID_USER_MAIN_TAB_TYPES : VALID_CHANNEL_MAIN_TAB_TYPES; @@ -438,10 +464,10 @@ const Profile = ({ setActiveTab(peerFullInfo.mainTab); // Only focus when loading full info }, [peerFullInfo]); - const handleSwitchTab = useCallback((index: number) => { + const handleSwitchTab = useLastCallback((index: number) => { startAutoScrollToTabsIfNeeded(); setActiveTab(tabs[index].type); - }, [tabs]); + }); useEffect(() => { if (hasPreviewMediaTab && !botPreviewMedia) { @@ -511,15 +537,15 @@ const Profile = ({ if (!isSynced) return; loadCommonChats({ userId: chatId }); }); - const handleLoadPeerStories = useCallback(({ offsetId }: { offsetId: number }) => { + const handleLoadPeerStories = useLastCallback(({ offsetId }: { offsetId: number }) => { loadPeerProfileStories({ peerId: chatId, offsetId }); - }, [chatId]); - const handleLoadStoriesArchive = useCallback(({ offsetId }: { offsetId: number }) => { + }); + const handleLoadStoriesArchive = useLastCallback(({ offsetId }: { offsetId: number }) => { loadStoriesArchive({ peerId: chatId, offsetId }); - }, [chatId]); - const handleLoadGifts = useCallback(() => { + }); + const handleLoadGifts = useLastCallback(() => { loadPeerSavedGifts({ peerId: chatId }); - }, [chatId]); + }); const handleLoadMoreMembers = useLastCallback(() => { if (!isSynced) return; @@ -574,6 +600,14 @@ const Profile = ({ similarBots, }); + const shouldWrapInIsland = !NON_ISLAND_TABS.has(resultType); + + useEffect(() => { + if (getMore && !viewportIds && isSynced) { + getMore({ direction: LoadMoreDirection.Backwards }); + } + }, [getMore, viewportIds, resultType, isSynced]); + const shouldRenderProfileInfo = !noProfileInfo && !isSavedMessages; const isFirstTab = tabs[0].type === resultType; @@ -589,21 +623,20 @@ const Profile = ({ const shouldShowContentPanel = (isGiftsResult && hasGiftsCollections) || (isStoriesResult && hasStoryAlbums); useEffect(() => { + const timers: ReturnType[] = []; if (hasGiftsCollections) { - setTimeout(() => { - markGiftCollectionsShowed(); - }, CONTENT_PANEL_SHOW_DELAY); + timers.push(setTimeout(markGiftCollectionsShowed, CONTENT_PANEL_SHOW_DELAY)); } else { unmarkGiftCollectionsShowed(); } if (hasStoryAlbums) { - setTimeout(() => { - markStoryAlbumsShowed(); - }, CONTENT_PANEL_SHOW_DELAY); + timers.push(setTimeout(markStoryAlbumsShowed, CONTENT_PANEL_SHOW_DELAY)); } else { unmarkStoryAlbums(); } + + return () => timers.forEach(clearTimeout); }, [hasGiftsCollections, hasStoryAlbums, markGiftCollectionsShowed, markStoryAlbumsShowed]); usePeerStoriesPolling(resultType === 'members' ? viewportIds as string[] : undefined); @@ -638,8 +671,6 @@ const Profile = ({ useTransitionFixes(containerRef); - const [cacheBuster, resetCacheBuster] = useCacheBuster(); - const { observe: observeIntersectionForMedia } = useIntersectionObserver({ rootRef: containerRef, throttleMs: INTERSECTION_THROTTLE, @@ -777,14 +808,14 @@ const Profile = ({ function renderNothingFoundGiftsWithFilter() { return ( -
+
-
+
{lang('GiftSearchEmpty')}
.scroll-item`} + preloadBackwards={canRenderContent + ? (resultType === 'members' ? MEMBERS_SLICE : SHARED_MEDIA_SLICE) : 0} + onLoadMore={getMore} + scrollContainerClosest=".Profile" + sensitiveArea={PROFILE_SENSITIVE_AREA} + noScrollRestore + noFastList + > + {content} + + ) : content; + + return ( +
+ + {inner} + +
+ ); + } + function renderContent() { if (resultType === 'dialogs') { - return ( - + return wrapInIsland( + , + styles.savedDialogsIsland, ); } - const noContent = (!viewportIds && !botPreviewMedia) || !canRenderContent || !messagesById; + const needsMessages = resultType === 'media' || resultType === 'gif' || resultType === 'documents' + || resultType === 'links' || resultType === 'audio' || resultType === 'voice'; + const noContent = (!viewportIds && !botPreviewMedia) || !canRenderContent || (needsMessages && !messagesById); const noSpinner = isFirstTab && !canRenderContent; + if (shouldWrapInIsland) { + return renderSpinnerOrContent(noContent, noSpinner); + } + return ( -
+
{renderCategories()} - {renderSpinnerOrContent(noContent, noSpinner)} + .scroll-item`} + items={canRenderContent ? viewportIds : undefined} + sensitiveArea={PROFILE_SENSITIVE_AREA} + preloadBackwards={canRenderContent ? SHARED_MEDIA_SLICE : 0} + scrollContainerClosest=".Profile" + noScrollRestore + onLoadMore={getMore} + noFastList + > + {renderSpinnerOrContent(noContent, noSpinner)} +
); } @@ -820,9 +908,9 @@ const Profile = ({ return (
@@ -834,9 +922,9 @@ const Profile = ({ return (
@@ -853,7 +941,7 @@ const Profile = ({ return (
{!noSpinner && !forceRenderHiddenMembers && } {forceRenderHiddenMembers && } @@ -903,7 +991,7 @@ const Profile = ({ } return ( -
+
); @@ -916,18 +1004,18 @@ const Profile = ({ const noTransition = resultType === 'gifts' ? isGiftCollectionsShowed : resultType === 'stories' ? isStoryAlbumsShowed : false; - return ( + const contentEl = (
{resultType === 'media' || resultType === 'gif' ? ( - (viewportIds as number[]).map((id) => messagesById[id] && ( + (viewportIds as number[]).filter((id) => Boolean(messagesById[id])).map((id) => ( )) ) : (resultType === 'stories' || resultType === 'storiesArchive') ? ( - (viewportIds as number[]).map((id, i) => storyByIds?.[id] && ( + (viewportIds as number[]).filter((id) => Boolean(storyByIds?.[id])).map((id, i) => ( )) ) : resultType === 'documents' ? ( - (viewportIds as number[]).map((id) => messagesById[id] && ( + (viewportIds as number[]).filter((id) => Boolean(messagesById[id])).map((id) => ( )) ) : resultType === 'links' ? ( - (viewportIds as number[]).map((id) => messagesById[id] && ( + (viewportIds as number[]).filter((id) => Boolean(messagesById[id])).map((id) => ( )) ) : resultType === 'audio' ? ( - (viewportIds as number[]).map((id) => messagesById[id] && ( + (viewportIds as number[]).filter((id) => Boolean(messagesById[id])).map((id) => (