Chat List: Introduce Archive item (#2566)

This commit is contained in:
Alexander Zinchuk 2023-02-28 18:43:21 +01:00
parent 3cc27156cb
commit 5055f70f77
28 changed files with 859 additions and 254 deletions

Binary file not shown.

Binary file not shown.

View File

@ -235,7 +235,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
<ListItem
key={id}
className="chat-item-clickable force-rounded-corners small-icon"
style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
style={`height: ${CHAT_HEIGHT_PX}px; top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
onClick={handleClick}
clickArg={id}
>

View File

@ -28,4 +28,8 @@
transition: none;
}
}
.archived-chats-more-menu {
margin-left: auto !important;
}
}

View File

@ -1,6 +1,10 @@
import React, { memo } from '../../lib/teact/teact';
import React, { memo, useCallback } from '../../lib/teact/teact';
import { getActions } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { LeftColumnContent, SettingsScreens } from '../../types';
import type { FolderEditDispatch } from '../../hooks/reducers/useFoldersReducer';
import type { GlobalState } from '../../global/types';
import buildClassName from '../../util/buildClassName';
import useLang from '../../hooks/useLang';
@ -12,22 +16,33 @@ import useForumPanelRender from '../../hooks/useForumPanelRender';
import Button from '../ui/Button';
import ChatList from './main/ChatList';
import ForumPanel from './main/ForumPanel';
import DropdownMenu from '../ui/DropdownMenu';
import MenuItem from '../ui/MenuItem';
import './ArchivedChats.scss';
export type OwnProps = {
isActive: boolean;
isForumPanelOpen?: boolean;
archiveSettings: GlobalState['archiveSettings'];
onReset: () => void;
onTopicSearch: NoneToVoidFunction;
isForumPanelOpen?: boolean;
onSettingsScreenSelect: (screen: SettingsScreens) => void;
foldersDispatch: FolderEditDispatch;
onLeftColumnContentChange: (content: LeftColumnContent) => void;
};
const ArchivedChats: FC<OwnProps> = ({
isActive,
isForumPanelOpen,
archiveSettings,
onReset,
onTopicSearch,
onSettingsScreenSelect,
onLeftColumnContentChange,
foldersDispatch,
}) => {
const { updateArchiveSettings } = getActions();
const lang = useLang();
useHistoryBack({
@ -35,6 +50,10 @@ const ArchivedChats: FC<OwnProps> = ({
onBack: onReset,
});
const handleDisplayArchiveInChats = useCallback(() => {
updateArchiveSettings({ isHidden: false });
}, [updateArchiveSettings]);
const {
shouldDisableDropdownMenuTransitionRef,
handleDropdownMenuTransitionEnd,
@ -67,8 +86,27 @@ const ArchivedChats: FC<OwnProps> = ({
<i className="icon-arrow-left" />
</Button>
{shouldRenderTitle && <h3 className={titleClassNames}>{lang('ArchivedChats')}</h3>}
{archiveSettings.isHidden && (
<DropdownMenu
className="archived-chats-more-menu"
positionX="right"
onTransitionEnd={lang.isRtl ? handleDropdownMenuTransitionEnd : undefined}
>
<MenuItem icon="archive-from-main" onClick={handleDisplayArchiveInChats}>
{lang('lng_context_archive_to_list')}
</MenuItem>
</DropdownMenu>
)}
</div>
<ChatList folderType="archived" isActive={isActive} isForumPanelOpen={isForumPanelOpen} />
<ChatList
folderType="archived"
isActive={isActive}
isForumPanelOpen={isForumPanelOpen}
onSettingsScreenSelect={onSettingsScreenSelect}
onLeftColumnContentChange={onLeftColumnContentChange}
foldersDispatch={foldersDispatch}
archiveSettings={archiveSettings}
/>
{shouldRenderForumPanel && (
<ForumPanel
isOpen={isForumPanelOpen}

View File

@ -1,9 +1,10 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useRef, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { GlobalState } from '../../global/types';
import { LeftColumnContent, SettingsScreens } from '../../types';
import { IS_MAC_OS, IS_PWA, LAYERS_ANIMATION_NAME } from '../../util/environment';
@ -36,6 +37,7 @@ type StateProps = {
isForumPanelOpen?: boolean;
forumPanelChatId?: string;
isClosingSearch?: boolean;
archiveSettings: GlobalState['archiveSettings'];
};
enum ContentType {
@ -66,6 +68,7 @@ const LeftColumn: FC<StateProps> = ({
isForumPanelOpen,
forumPanelChatId,
isClosingSearch,
archiveSettings,
}) => {
const {
setGlobalSearchQuery,
@ -352,6 +355,11 @@ const LeftColumn: FC<StateProps> = ({
openChat({ id: currentUserId, shouldReplaceHistory: true });
}, [currentUserId, openChat]);
const handleArchivedChats = useCallback((e: KeyboardEvent) => {
e.preventDefault();
setContent(LeftColumnContent.Archived);
}, []);
const handleHotkeySettings = useCallback((e: KeyboardEvent) => {
e.preventDefault();
setContent(LeftColumnContent.Settings);
@ -360,7 +368,10 @@ const LeftColumn: FC<StateProps> = ({
useHotkeys({
'Mod+Shift+F': handleHotkeySearch,
'Mod+Shift+S': handleHotkeySavedMessages,
'Mod+0': handleHotkeySavedMessages,
...(IS_PWA && {
'Mod+0': handleHotkeySavedMessages,
'Mod+9': handleArchivedChats,
}),
...(IS_MAC_OS && IS_PWA && { 'Mod+,': handleHotkeySettings }),
});
@ -411,7 +422,11 @@ const LeftColumn: FC<StateProps> = ({
isActive={isActive}
onReset={handleReset}
onTopicSearch={handleTopicSearch}
foldersDispatch={foldersDispatch}
onSettingsScreenSelect={handleSettingsScreenSelect}
onLeftColumnContentChange={setContent}
isForumPanelOpen={isForumPanelOpen}
archiveSettings={archiveSettings}
/>
);
case ContentType.Settings:
@ -458,7 +473,7 @@ const LeftColumn: FC<StateProps> = ({
foldersDispatch={foldersDispatch}
onContentChange={setContent}
onSearchQuery={handleSearchQuery}
onScreenSelect={handleSettingsScreenSelect}
onSettingsScreenSelect={handleSettingsScreenSelect}
onReset={handleReset}
shouldSkipTransition={shouldSkipHistoryAnimations}
isUpdateAvailable={isUpdateAvailable}
@ -498,6 +513,7 @@ export default memo(withGlobal(
hasPasscode,
},
isUpdateAvailable,
archiveSettings,
} = global;
const currentChat = selectCurrentChat(global);
@ -519,6 +535,7 @@ export default memo(withGlobal(
isForumPanelOpen,
forumPanelChatId,
isClosingSearch: tabState.globalSearch.isClosing,
archiveSettings,
};
},
)(LeftColumn));

View File

@ -0,0 +1,90 @@
.root {
--background-color: var(--color-background);
}
.minimized {
background-color: var(--color-background-secondary);
margin: -0.5rem -0.5rem 0 -0.5rem !important;
&:hover {
opacity: 0.85;
}
.button {
border-radius: 0;
padding: 0.375rem !important;
background-color: transparent;
}
:global(body.is-ios) &,
:global(body.is-android) & {
.button {
padding-bottom: 0.3125rem !important;
}
}
.title {
justify-content: center !important;
color: var(--color-text-secondary);
}
.unread-count {
position: absolute;
right: 0.75rem;
background-color: transparent;
color: var(--color-text-secondary);
font-size: 0.8125rem;
}
.info {
transform: none !important;
}
.name {
line-height: 1.25rem !important;
font-size: 0.8125rem !important;
}
&::after {
opacity: 0;
}
}
.info {
transition: opacity 0.3s ease, transform var(--layer-transition);
}
.icon {
margin-inline-end: 0.5rem;
}
.name {
display: flex;
align-items: center;
}
.avatarWrapper {
flex-shrink: 0;
background-color: var(--background-color);
z-index: 1;
}
.avatar {
font-size: 1.625rem;
background: linear-gradient(var(--color-white) -125%, var(--color-archive)) !important;
}
.title {
flex-grow: 1;
}
.chatsPreview {
color: var(--color-text-secondary);
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.unread {
color: var(--color-text);
}

View File

@ -0,0 +1,166 @@
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
import { getActions, getGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { GlobalState } from '../../../global/types';
import { ARCHIVED_FOLDER_ID } from '../../../config';
import buildClassName from '../../../util/buildClassName';
import { compact } from '../../../util/iteratees';
import { formatIntegerCompact } from '../../../util/textFormat';
import renderText from '../../common/helpers/renderText';
import useLang from '../../../hooks/useLang';
import { useFolderManagerForOrderedIds, useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
import ListItem from '../../ui/ListItem';
import AnimatedCounter from '../../common/AnimatedCounter';
import styles from './Archive.module.scss';
type OwnProps = {
archiveSettings: GlobalState['archiveSettings'];
onDragEnter?: NoneToVoidFunction;
onClick?: NoneToVoidFunction;
};
const PREVIEW_SLICE = 5;
const Archive: FC<OwnProps> = ({
archiveSettings,
onDragEnter,
onClick,
}) => {
const { updateArchiveSettings } = getActions();
const lang = useLang();
const orderedChatIds = useFolderManagerForOrderedIds(ARCHIVED_FOLDER_ID);
const unreadCounters = useFolderManagerForUnreadCounters();
const archiveUnreadCount = unreadCounters[ARCHIVED_FOLDER_ID]?.chatsCount;
const previewItems = useMemo(() => {
if (!orderedChatIds?.length) return lang('Loading');
const chatsById = getGlobal().chats.byId;
return orderedChatIds.slice(0, PREVIEW_SLICE).map((chatId, i, arr) => {
const isLast = i === arr.length - 1;
const chat = chatsById[chatId];
if (!chat) {
return undefined;
}
return (
<>
<span className={buildClassName(styles.chat, archiveUnreadCount && chat.unreadCount && styles.unread)}>
{renderText(chat.title)}
</span>
{isLast ? '' : ', '}
</>
);
});
}, [orderedChatIds, lang, archiveUnreadCount]);
const contextActions = useMemo(() => {
const actionMinimize = !archiveSettings.isMinimized && {
title: lang('lng_context_archive_collapse'),
icon: 'collapse',
handler: () => {
updateArchiveSettings({ isMinimized: true });
},
};
const actionExpand = archiveSettings.isMinimized && {
title: lang('lng_context_archive_expand'),
icon: 'expand',
handler: () => {
updateArchiveSettings({ isMinimized: false });
},
};
const actionHide = {
title: lang('lng_context_archive_to_menu'),
icon: 'archive-to-main',
handler: () => {
updateArchiveSettings({ isHidden: true });
},
};
return compact([actionMinimize, actionExpand, actionHide]);
}, [archiveSettings.isMinimized, lang, updateArchiveSettings]);
const handleDragEnter = useCallback((e) => {
e.preventDefault();
onDragEnter?.();
}, [onDragEnter]);
function renderCollapsed() {
return (
<div className={buildClassName(styles.info, 'info')}>
<div className="info-row">
<div className={buildClassName('title', styles.title)}>
<h3 dir="auto" className={buildClassName(styles.name, 'fullName')}>
<i className={buildClassName(styles.icon, 'icon-archive-filled')} />
{lang('ArchivedChats')}
</h3>
</div>
{Boolean(archiveUnreadCount) && (
<div className={styles.unreadCount}>
<AnimatedCounter text={formatIntegerCompact(archiveUnreadCount)} />
</div>
)}
</div>
</div>
);
}
function renderRegular() {
return (
<>
<div className={buildClassName('status', styles.avatarWrapper)}>
<div className={buildClassName('Avatar', styles.avatar)}>
<i className="icon-archive-filled" />
</div>
</div>
<div className={buildClassName(styles.info, 'info')}>
<div className="info-row">
<div className={buildClassName('title', styles.title)}>
<h3 dir="auto" className={buildClassName(styles.name, 'fullName')}>{lang('ArchivedChats')}</h3>
</div>
</div>
<div className="subtitle">
<div className={buildClassName('status', styles.chatsPreview)}>
{previewItems}
</div>
{Boolean(archiveUnreadCount) && (
<div className="Badge">
<AnimatedCounter text={formatIntegerCompact(archiveUnreadCount)} />
</div>
)}
</div>
</div>
</>
);
}
return (
<ListItem
onClick={onClick}
onDragEnter={handleDragEnter}
className={buildClassName(
styles.root,
archiveSettings.isMinimized && styles.minimized,
'chat-item-clickable',
'chat-item-archive',
)}
buttonClassName={styles.button}
offsetCollapseDelta={0}
contextActions={contextActions}
withPortalForMenu
>
{archiveSettings.isMinimized ? renderCollapsed() : renderRegular()}
</ListItem>
);
};
export default memo(Archive);

View File

@ -5,8 +5,9 @@ import React, {
import { getActions, withGlobal } from '../../../global';
import type { ApiChatFolder } from '../../../api/types';
import type { SettingsScreens } from '../../../types';
import type { LeftColumnContent, SettingsScreens } from '../../../types';
import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
import type { GlobalState } from '../../../global/types';
import { ALL_FOLDER_ID } from '../../../config';
import { IS_TOUCH_ENV } from '../../../util/environment';
@ -25,8 +26,9 @@ import TabList from '../../ui/TabList';
import ChatList from './ChatList';
type OwnProps = {
onScreenSelect: (screen: SettingsScreens) => void;
onSettingsScreenSelect: (screen: SettingsScreens) => void;
foldersDispatch: FolderEditDispatch;
onLeftColumnContentChange: (content: LeftColumnContent) => void;
shouldHideFolderTabs?: boolean;
};
@ -39,6 +41,8 @@ type StateProps = {
lastSyncTime?: number;
shouldSkipHistoryAnimations?: boolean;
maxFolders: number;
hasArchivedChats?: boolean;
archiveSettings: GlobalState['archiveSettings'];
};
const SAVED_MESSAGES_HOTKEY = '0';
@ -46,7 +50,8 @@ const FIRST_FOLDER_INDEX = 0;
const ChatFolders: FC<OwnProps & StateProps> = ({
foldersDispatch,
onScreenSelect,
onSettingsScreenSelect,
onLeftColumnContentChange,
chatFoldersById,
orderedFolderIds,
activeChatFolder,
@ -56,6 +61,8 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
shouldSkipHistoryAnimations,
maxFolders,
shouldHideFolderTabs,
hasArchivedChats,
archiveSettings,
}) => {
const {
loadChatFolders,
@ -122,7 +129,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
// Prevent `activeTab` pointing at non-existing folder after update
useEffect(() => {
if (!folderTabs || !folderTabs.length) {
if (!folderTabs?.length) {
return;
}
@ -211,7 +218,10 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
isForumPanelOpen={isForumPanelOpen}
lastSyncTime={lastSyncTime}
foldersDispatch={foldersDispatch}
onScreenSelect={onScreenSelect}
onSettingsScreenSelect={onSettingsScreenSelect}
onLeftColumnContentChange={onLeftColumnContentChange}
canDisplayArchive={hasArchivedChats && !archiveSettings.isHidden}
archiveSettings={archiveSettings}
/>
);
}
@ -249,8 +259,14 @@ export default memo(withGlobal<OwnProps>(
byId: chatFoldersById,
orderedIds: orderedFolderIds,
},
chats: {
listIds: {
archived,
},
},
currentUserId,
lastSyncTime,
archiveSettings,
} = global;
const { shouldSkipHistoryAnimations, activeChatFolder } = selectTabState(global);
@ -262,7 +278,9 @@ export default memo(withGlobal<OwnProps>(
isForumPanelOpen: selectIsForumPanelOpen(global),
lastSyncTime,
shouldSkipHistoryAnimations,
hasArchivedChats: Boolean(archived?.length),
maxFolders: selectCurrentLimit(global, 'dialogFilters'),
archiveSettings,
};
},
)(ChatFolders));

View File

@ -4,12 +4,14 @@ import React, {
import { getActions, getGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { SettingsScreens } from '../../../types';
import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
import { LeftColumnContent } from '../../../types';
import type { SettingsScreens } from '../../../types';
import type { GlobalState } from '../../../global/types';
import {
ALL_FOLDER_ID,
ARCHIVED_FOLDER_ID, CHAT_HEIGHT_FORUM_PX,
ARCHIVED_FOLDER_ID, ARCHIVE_MINIMIZED_HEIGHT, CHAT_HEIGHT_FORUM_PX,
CHAT_HEIGHT_PX,
CHAT_LIST_SLICE,
} from '../../../config';
@ -24,35 +26,43 @@ import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'
import { useHotkeys } from '../../../hooks/useHotkeys';
import useDebouncedCallback from '../../../hooks/useDebouncedCallback';
import useChatOrderDiff from './hooks/useChatOrderDiff';
import useCollapseWithForumPanel from './hooks/useCollapseWithForumPanel';
import InfiniteScroll from '../../ui/InfiniteScroll';
import Loading from '../../ui/Loading';
import Chat from './Chat';
import EmptyFolder from './EmptyFolder';
import useCollapseWithForumPanel from './hooks/useCollapseWithForumPanel';
import Archive from './Archive';
type OwnProps = {
folderType: 'all' | 'archived' | 'folder';
folderId?: number;
isActive: boolean;
canDisplayArchive?: boolean;
archiveSettings: GlobalState['archiveSettings'];
isForumPanelOpen?: boolean;
lastSyncTime?: number;
foldersDispatch?: FolderEditDispatch;
onScreenSelect?: (screen: SettingsScreens) => void;
foldersDispatch: FolderEditDispatch;
onSettingsScreenSelect: (screen: SettingsScreens) => void;
onLeftColumnContentChange: (content: LeftColumnContent) => void;
};
const INTERSECTION_THROTTLE = 200;
const DRAG_ENTER_DEBOUNCE = 500;
const RESERVED_HOTKEYS = new Set(['9', '0']);
const ChatList: FC<OwnProps> = ({
folderType,
folderId,
isActive,
isForumPanelOpen,
canDisplayArchive,
archiveSettings,
foldersDispatch,
onScreenSelect,
onSettingsScreenSelect,
onLeftColumnContentChange,
}) => {
const { openChat, openNextChat } = getActions();
const { openChat, openNextChat, closeForumPanel } = getActions();
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null);
const shouldIgnoreDragRef = useRef(false);
@ -61,8 +71,14 @@ const ChatList: FC<OwnProps> = ({
folderType === 'all' ? ALL_FOLDER_ID : folderType === 'archived' ? ARCHIVED_FOLDER_ID : folderId!
);
const shouldDisplayArchive = folderType === 'all' && canDisplayArchive;
const orderedIds = useFolderManagerForOrderedIds(resolvedFolderId);
const chatsHeight = (orderedIds?.length || 0) * CHAT_HEIGHT_PX;
const archiveHeight = shouldDisplayArchive
? archiveSettings.isMinimized ? ARCHIVE_MINIMIZED_HEIGHT : CHAT_HEIGHT_PX : 0;
const { orderDiffById, getAnimationType } = useChatOrderDiff(orderedIds);
const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, CHAT_LIST_SLICE);
@ -88,10 +104,19 @@ const ChatList: FC<OwnProps> = ({
function handleKeyDown(e: KeyboardEvent) {
if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.code.startsWith('Digit')) {
const [, digit] = e.code.match(/Digit(\d)/) || [];
if (!digit) return;
if (!digit || RESERVED_HOTKEYS.has(digit)) return;
const position = Number(digit) - 1;
if (position > orderedIds!.length - 1 || position < 0) return;
const isArchiveInList = shouldDisplayArchive && !archiveSettings.isMinimized;
const shift = isArchiveInList ? -1 : 0;
const position = Number(digit) + shift - 1;
if (isArchiveInList && position === -1) {
onLeftColumnContentChange(LeftColumnContent.Archived);
return;
}
if (position > orderedIds!.length - 1) return;
openChat({ id: orderedIds![position], shouldReplaceHistory: true });
}
@ -102,7 +127,7 @@ const ChatList: FC<OwnProps> = ({
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isActive, openChat, openNextChat, orderedIds]);
}, [archiveSettings, isActive, onLeftColumnContentChange, openChat, openNextChat, orderedIds, shouldDisplayArchive]);
const { observe } = useIntersectionObserver({
rootRef: containerRef,
@ -111,6 +136,19 @@ const ChatList: FC<OwnProps> = ({
useCollapseWithForumPanel(containerRef, isForumPanelOpen);
const handleArchivedClick = useCallback(() => {
onLeftColumnContentChange(LeftColumnContent.Archived);
closeForumPanel();
}, [closeForumPanel, onLeftColumnContentChange]);
const handleArchivedDragEnter = useCallback(() => {
if (shouldIgnoreDragRef.current) {
shouldIgnoreDragRef.current = false;
return;
}
handleArchivedClick();
}, [handleArchivedClick]);
const handleDragEnter = useDebouncedCallback((chatId: string) => {
if (shouldIgnoreDragRef.current) {
shouldIgnoreDragRef.current = false;
@ -131,13 +169,13 @@ const ChatList: FC<OwnProps> = ({
if (!viewportIds?.length) return 0;
const global = getGlobal();
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
return orderedIds!.reduce((acc, id, i) => {
return archiveHeight + orderedIds!.reduce((acc, id, i) => {
if (i >= viewportOffset) {
return acc;
}
return acc + (selectChat(global, id)!.isForum ? CHAT_HEIGHT_FORUM_PX : CHAT_HEIGHT_PX);
}, 0);
}, [orderedIds, viewportIds]);
}, [archiveHeight, orderedIds, viewportIds]);
function renderChats() {
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
@ -150,7 +188,7 @@ const ChatList: FC<OwnProps> = ({
return viewportIds!.map((id, i) => {
const isPinned = viewportOffset + i < pinnedCount;
const expendedOffsetTop = currentChatListHeight;
const collapsedOffsetTop = (viewportOffset + i) * CHAT_HEIGHT_PX;
const collapsedOffsetTop = archiveHeight + (viewportOffset + i) * CHAT_HEIGHT_PX;
currentChatListHeight += (selectChat(global, id)?.isForum ? CHAT_HEIGHT_FORUM_PX : CHAT_HEIGHT_PX);
@ -177,12 +215,21 @@ const ChatList: FC<OwnProps> = ({
className={buildClassName('chat-list custom-scroll', isForumPanelOpen && 'forum-panel-open')}
ref={containerRef}
items={viewportIds}
itemSelector=".ListItem:not(.chat-item-archive)"
preloadBackwards={CHAT_LIST_SLICE}
withAbsolutePositioning
maxHeight={(orderedIds?.length || 0) * CHAT_HEIGHT_PX}
maxHeight={chatsHeight + archiveHeight}
onLoadMore={getMore}
onDragLeave={handleDragLeave}
>
{shouldDisplayArchive && (
<Archive
key="archive"
archiveSettings={archiveSettings}
onClick={handleArchivedClick}
onDragEnter={handleArchivedDragEnter}
/>
)}
{viewportIds?.length ? (
renderChats()
) : viewportIds && !viewportIds.length ? (
@ -191,7 +238,7 @@ const ChatList: FC<OwnProps> = ({
folderId={folderId}
folderType={folderType}
foldersDispatch={foldersDispatch}
onScreenSelect={onScreenSelect}
onSettingsScreenSelect={onSettingsScreenSelect}
/>
)
) : (

View File

@ -18,8 +18,8 @@ import styles from './EmptyFolder.module.scss';
type OwnProps = {
folderId?: number;
folderType: 'all' | 'archived' | 'folder';
foldersDispatch?: FolderEditDispatch;
onScreenSelect?: (screen: SettingsScreens) => void;
foldersDispatch: FolderEditDispatch;
onSettingsScreenSelect: (screen: SettingsScreens) => void;
};
type StateProps = {
@ -30,15 +30,15 @@ type StateProps = {
const ICON_SIZE = 96;
const EmptyFolder: FC<OwnProps & StateProps> = ({
chatFolder, animatedEmoji, foldersDispatch, onScreenSelect,
chatFolder, animatedEmoji, foldersDispatch, onSettingsScreenSelect,
}) => {
const lang = useLang();
const { isMobile } = useAppLayout();
const handleEditFolder = useCallback(() => {
foldersDispatch!({ type: 'editFolder', payload: chatFolder });
onScreenSelect!(SettingsScreens.FoldersEditFolderFromChatList);
}, [chatFolder, foldersDispatch, onScreenSelect]);
foldersDispatch({ type: 'editFolder', payload: chatFolder });
onSettingsScreenSelect(SettingsScreens.FoldersEditFolderFromChatList);
}, [chatFolder, foldersDispatch, onSettingsScreenSelect]);
return (
<div className={styles.root}>
@ -49,7 +49,7 @@ const EmptyFolder: FC<OwnProps & StateProps> = ({
<p className={styles.description} dir="auto">
{lang(chatFolder ? 'ChatList.EmptyChatListFilterText' : 'Chat.EmptyChat')}
</p>
{chatFolder && foldersDispatch && onScreenSelect && (
{chatFolder && (
<Button
ripple={!isMobile}
fluid

View File

@ -23,6 +23,7 @@ import Button from '../../ui/Button';
import ForumPanel from './ForumPanel';
import './LeftMain.scss';
import { getActions } from '../../../global';
type OwnProps = {
content: LeftColumnContent;
@ -36,7 +37,7 @@ type OwnProps = {
isClosingSearch?: boolean;
onSearchQuery: (query: string) => void;
onContentChange: (content: LeftColumnContent) => void;
onScreenSelect: (screen: SettingsScreens) => void;
onSettingsScreenSelect: (screen: SettingsScreens) => void;
onTopicSearch: NoneToVoidFunction;
onReset: () => void;
};
@ -58,10 +59,11 @@ const LeftMain: FC<OwnProps> = ({
isForumPanelOpen,
onSearchQuery,
onContentChange,
onScreenSelect,
onSettingsScreenSelect,
onReset,
onTopicSearch,
}) => {
const { closeForumPanel } = getActions();
const [isNewChatButtonShown, setIsNewChatButtonShown] = useState(IS_TOUCH_ENV);
const { shouldRenderForumPanel, handleForumPanelAnimationEnd } = useForumPanelRender(isForumPanelOpen);
@ -107,7 +109,8 @@ const LeftMain: FC<OwnProps> = ({
const handleSelectArchived = useCallback(() => {
onContentChange(LeftColumnContent.Archived);
}, [onContentChange]);
closeForumPanel();
}, [closeForumPanel, onContentChange]);
const handleUpdateClick = useCallback(() => {
window.location.reload();
@ -172,7 +175,8 @@ const LeftMain: FC<OwnProps> = ({
return (
<ChatFolders
shouldHideFolderTabs={isForumPanelVisible}
onScreenSelect={onScreenSelect}
onSettingsScreenSelect={onSettingsScreenSelect}
onLeftColumnContentChange={onContentChange}
foldersDispatch={foldersDispatch}
/>
);

View File

@ -78,7 +78,7 @@ type StateProps =
areChatsLoaded?: boolean;
hasPasscode?: boolean;
}
& Pick<GlobalState, 'connectionState' | 'isSyncing'> & Pick<TabState, 'canInstall'>;
& Pick<GlobalState, 'connectionState' | 'isSyncing' | 'archiveSettings'> & Pick<TabState, 'canInstall'>;
const ANIMATION_LEVEL_OPTIONS = [0, 1, 2];
const WEBK_VERSION_URL = 'https://web.telegram.org/k/';
@ -110,6 +110,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
areChatsLoaded,
hasPasscode,
canInstall,
archiveSettings,
}) => {
const {
openChat,
@ -276,15 +277,17 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
>
{lang('SavedMessages')}
</MenuItem>
<MenuItem
icon="archive"
onClick={onSelectArchived}
>
<span className="menu-item-name">{lang('ArchivedChats')}</span>
{archivedUnreadChatsCount > 0 && (
<div className="right-badge">{archivedUnreadChatsCount}</div>
)}
</MenuItem>
{archiveSettings.isHidden && (
<MenuItem
icon="archive"
onClick={onSelectArchived}
>
<span className="menu-item-name">{lang('ArchivedChats')}</span>
{archivedUnreadChatsCount > 0 && (
<div className="right-badge">{archivedUnreadChatsCount}</div>
)}
</MenuItem>
)}
<MenuItem
icon="user"
onClick={onSelectContacts}
@ -359,9 +362,9 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
)}
</>
), [
animationLevel, archivedUnreadChatsCount, canInstall, handleAnimationLevelChange, handleBugReportClick,
handleChangelogClick, handleDarkModeToggle, handleOpenTipsChat, handleSelectSaved, handleSwitchToWebK, lang,
onSelectArchived, onSelectContacts, onSelectSettings, theme, withOtherVersions,
animationLevel, archiveSettings.isHidden, archivedUnreadChatsCount, canInstall, handleAnimationLevelChange,
handleBugReportClick, handleChangelogClick, handleDarkModeToggle, handleOpenTipsChat, handleSelectSaved,
handleSwitchToWebK, lang, onSelectArchived, onSelectContacts, onSelectSettings, theme, withOtherVersions,
]);
return (
@ -457,7 +460,9 @@ export default memo(withGlobal<OwnProps>(
const {
query: searchQuery, fetchingStatus, chatId, date,
} = tabState.globalSearch;
const { currentUserId, connectionState, isSyncing } = global;
const {
currentUserId, connectionState, isSyncing, archiveSettings,
} = global;
const { byId: chatsById } = global.chats;
const { isConnectionStatusMinimized, animationLevel } = global.settings.byKey;
@ -478,6 +483,7 @@ export default memo(withGlobal<OwnProps>(
areChatsLoaded: Boolean(global.chats.listIds.active),
hasPasscode: Boolean(global.passcode.hasPasscode),
canInstall: Boolean(tabState.canInstall),
archiveSettings,
};
},
)(LeftMainHeader));

View File

@ -1,5 +1,5 @@
import { getActions } from '../../../../global';
import { useMemo } from '../../../../lib/teact/teact';
import { getActions } from '../../../../global';
import type { ApiChat, ApiTopic } from '../../../../api/types';
import type { MenuItemContextAction } from '../../../ui/ListItem';

View File

@ -18,6 +18,8 @@
.info {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.title {

View File

@ -128,10 +128,11 @@
&.commonChats-list,
&.members-list {
padding: 0.5rem 1rem;
padding: 0.5rem;
@media (max-width: 600px) {
padding: 0.5rem 0;
.ListItem.chat-item-clickable {
margin: 0;
}

View File

@ -1,13 +1,17 @@
import React, {
useState, useRef, useCallback, useMemo,
} from '../../lib/teact/teact';
import type { FC } from '../../lib/teact/teact';
import React, { useState, useRef, useCallback } from '../../lib/teact/teact';
import Menu from './Menu';
import Button from './Button';
import './DropdownMenu.scss';
type OwnProps = {
className?: string;
trigger: FC<{ onTrigger: () => void; isOpen?: boolean }>;
trigger?: FC<{ onTrigger: () => void; isOpen?: boolean }>;
positionX?: 'left' | 'right';
positionY?: 'top' | 'bottom';
footer?: string;
@ -69,6 +73,23 @@ const DropdownMenu: FC<OwnProps> = ({
onClose?.();
}, [onClose]);
const triggerComponent: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
if (trigger) return trigger;
return ({ onTrigger, isOpen: isMenuOpen }) => (
<Button
round
size="smaller"
color="translucent"
className={isMenuOpen ? 'active' : ''}
onClick={onTrigger}
ariaLabel="More actions"
>
<i className="icon-more" />
</Button>
);
}, [trigger]);
return (
<div
ref={dropdownRef}
@ -76,7 +97,7 @@ const DropdownMenu: FC<OwnProps> = ({
onKeyDown={handleKeyDown}
onTransitionEnd={onTransitionEnd}
>
{trigger({ onTrigger: toggleIsOpen, isOpen })}
{triggerComponent({ onTrigger: toggleIsOpen, isOpen })}
<Menu
ref={menuRef}

View File

@ -32,6 +32,7 @@
.ListItem-button {
width: 100%;
height: 100%;
background-color: var(--background-color);
border: none !important;
box-shadow: none !important;

View File

@ -61,8 +61,9 @@ export const MIN_PASSWORD_LENGTH = 1;
export const MESSAGE_LIST_SLICE = isBigScreen ? 60 : 40;
export const MESSAGE_LIST_VIEWPORT_LIMIT = MESSAGE_LIST_SLICE * 2;
export const ARCHIVE_MINIMIZED_HEIGHT = 36;
export const CHAT_HEIGHT_PX = 72;
export const CHAT_HEIGHT_FORUM_PX = 96;
export const CHAT_HEIGHT_FORUM_PX = 94;
export const TOPIC_HEIGHT_PX = 65;
export const CHAT_LIST_SLICE = isBigScreen ? 30 : 25;
export const CHAT_LIST_LOAD_SLICE = 100;

View File

@ -561,6 +561,19 @@ addActionHandler('closeEditTopicPanel', (global, actions, payload): ActionReturn
}, tabId);
});
addActionHandler('updateArchiveSettings', (global, actions, payload): ActionReturnType => {
const { archiveSettings } = global;
const { isHidden = archiveSettings.isHidden, isMinimized = archiveSettings.isMinimized } = payload;
return {
...global,
archiveSettings: {
isHidden,
isMinimized,
},
};
});
addActionHandler('checkAppVersion', (global): ActionReturnType => {
const APP_VERSION_REGEX = /^\d+\.\d+(\.\d+)?$/;

View File

@ -195,10 +195,6 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
cached.serviceNotifications = [];
}
if (!cached.groupCalls) {
cached.groupCalls = initialState.groupCalls;
}
if (!cached.users.statusesById) {
cached.users.statusesById = {};
}
@ -336,10 +332,6 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
cached.availableReactions = cached.availableReactions
.map((r) => ({ ...r, reaction: { emoticon: r.reaction as unknown as string } }));
}
if (!cached.attachmentSettings) {
cached.attachmentSettings = initialState.attachmentSettings;
}
}
function updateCache() {
@ -390,6 +382,7 @@ export function serializeGlobal<T extends GlobalState>(global: T) {
'attachmentSettings',
'leftColumnWidth',
'lastIsChatInfoShown',
'archiveSettings',
'mediaViewer',
'audioPlayer',
]),

View File

@ -186,6 +186,11 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
transcriptions: {},
byTabId: {},
archiveSettings: {
isMinimized: false,
isHidden: false,
},
};
export const INITIAL_TAB_STATE: TabState = {

View File

@ -781,6 +781,11 @@ export type GlobalState = {
serviceNotifications: ServiceNotification[];
byTabId: Record<number, TabState>;
archiveSettings: {
isMinimized: boolean;
isHidden: boolean;
};
};
export type CallSound = (
@ -2088,6 +2093,11 @@ export interface ActionPayloads {
shouldSendGrouped?: boolean;
};
updateArchiveSettings: {
isMinimized?: boolean;
isHidden?: boolean;
};
openUrl: {
url: string;
shouldSkipModal?: boolean;

View File

@ -332,6 +332,7 @@ export function typify<
if (DEBUG) {
(window as any).getGlobal = getGlobal;
(window as any).setGlobal = setGlobal;
document.addEventListener('dblclick', () => {
// eslint-disable-next-line no-console

File diff suppressed because it is too large Load Diff

View File

@ -81,24 +81,22 @@
.chat-list {
background: var(--color-background);
height: 100%;
padding: 0.5rem 0.125rem 0.5rem 0.4375rem;
padding: 0.5rem 0.4375rem 0.5rem 0.4375rem;
overflow-y: auto;
body.is-android & {
@include overflow-y-overlay();
}
@include overflow-y-overlay();
&.forum-panel-open {
.info {
transform: translateX(-25%);
opacity: 0;
transform: translateX(-25%);
}
.Chat[dir="rtl"] .info {
transform: translateX(25%);
}
.ListItem-button {
.group .ListItem-button {
height: 4.5rem;
}
}
@ -159,6 +157,6 @@
}
&.deleted-account {
--color-user: #9eaab5;
--color-user: var(--color-deleted-account);
}
}

View File

@ -180,6 +180,9 @@ $color-message-reaction-own-hover: #b5e0a4;
--color-forum-hover-unread-topic: #e9e9e9;
--color-forum-hover-unread-topic-hover: #dcdcdc;
--color-deleted-account: #9eaab5;
--color-archive: #9eaab5;
--vh: 1vh;
--border-radius-default: 0.75rem;

View File

@ -51,6 +51,21 @@
.icon-volume-3:before {
content: "\e991";
}
.icon-archive-filled:before {
content: "\e9ba";
}
.icon-archive-from-main:before {
content: "\e9bb";
}
.icon-archive-to-main:before {
content: "\e9bc";
}
.icon-collapse:before {
content: "\e9bd";
}
.icon-expand:before {
content: "\e9be";
}
.icon-replies:before {
content: "\e9b9";
}