From 1038c2ffcf55dbdf044752f31ecc01ed5e2984c2 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 4 Aug 2021 23:54:13 +0300 Subject: [PATCH] Chat List, Message List: New designs for empty screens (#1342) --- src/api/gramjs/apiBuilders/messages.ts | 2 + src/api/types/messages.ts | 2 +- src/components/common/AnimatedEmoji.tsx | 25 ++-- src/components/common/StickerButton.scss | 6 + src/components/left/ArchivedChats.tsx | 2 +- src/components/left/LeftColumn.tsx | 16 +++ src/components/left/main/ChatFolders.tsx | 26 +++- src/components/left/main/ChatList.tsx | 19 ++- src/components/left/main/EmptyFolder.scss | 44 +++++++ src/components/left/main/EmptyFolder.tsx | 70 +++++++++++ src/components/left/main/LeftMain.tsx | 9 +- src/components/left/settings/Settings.tsx | 14 ++- .../left/settings/SettingsHeader.tsx | 16 ++- .../left/settings/folders/SettingsFolders.tsx | 16 ++- .../settings/folders/SettingsFoldersEdit.tsx | 4 +- .../twoFa/SettingsTwoFaCongratulations.tsx | 2 +- .../settings/twoFa/SettingsTwoFaEmailCode.tsx | 2 +- .../settings/twoFa/SettingsTwoFaEnabled.tsx | 2 +- .../twoFa/SettingsTwoFaSkippableForm.tsx | 2 +- .../settings/twoFa/SettingsTwoFaStart.tsx | 2 +- src/components/middle/ContactGreeting.scss | 41 +++++++ src/components/middle/ContactGreeting.tsx | 116 ++++++++++++++++++ src/components/middle/MessageList.tsx | 51 +++++++- src/components/middle/MiddleHeader.tsx | 2 +- src/components/middle/NoMessages.scss | 56 +++++++++ src/components/middle/NoMessages.tsx | 79 ++++++++++++ src/components/middle/message/Message.tsx | 2 +- src/components/ui/Tab.tsx | 6 +- src/global/initial.ts | 3 + src/global/types.ts | 6 +- src/modules/actions/api/settings.ts | 2 +- src/modules/actions/api/symbols.ts | 25 ++++ src/types/index.ts | 3 + 33 files changed, 619 insertions(+), 54 deletions(-) create mode 100644 src/components/left/main/EmptyFolder.scss create mode 100644 src/components/left/main/EmptyFolder.tsx create mode 100644 src/components/middle/ContactGreeting.scss create mode 100644 src/components/middle/ContactGreeting.tsx create mode 100644 src/components/middle/NoMessages.scss create mode 100644 src/components/middle/NoMessages.tsx diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index 4b23fb93d..2e6766e40 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -585,6 +585,7 @@ function buildAction( if (action instanceof GramJs.MessageActionChatCreate) { text = 'Notification.CreatedChatWithTitle'; translationValues.push('%action_origin%', action.title); + type = 'chatCreate'; } else if (action instanceof GramJs.MessageActionChatEditTitle) { if (isChannelPost) { text = 'Channel.MessageTitleUpdated'; @@ -656,6 +657,7 @@ function buildAction( } else if (action instanceof GramJs.MessageActionContactSignUp) { text = 'Notification.Joined'; translationValues.push('%action_origin%'); + type = 'contactSignUp'; } else if (action instanceof GramJs.MessageActionPaymentSent) { const currencySign = getCurrencySign(action.currency); const amount = (Number(action.totalAmount) / 100).toFixed(2); diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index 72e20196c..8e52cf1c8 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -147,7 +147,7 @@ export interface ApiAction { text: string; targetUserIds?: number[]; targetChatId?: number; - type: 'historyClear' | 'other'; + type: 'historyClear' | 'contactSignUp' | 'chatCreate' | 'other'; photo?: ApiPhoto; translationValues: string[]; } diff --git a/src/components/common/AnimatedEmoji.tsx b/src/components/common/AnimatedEmoji.tsx index e96820700..f282ed1c0 100644 --- a/src/components/common/AnimatedEmoji.tsx +++ b/src/components/common/AnimatedEmoji.tsx @@ -4,8 +4,7 @@ import React, { import { ApiMediaFormat, ApiSticker } from '../../api/types'; -import { STICKER_SIZE_TWO_FA } from '../../config'; -import { getStickerDimensions, LIKE_STICKER_ID } from './helpers/mediaDimensions'; +import { LIKE_STICKER_ID } from './helpers/mediaDimensions'; import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver'; import useMedia from '../../hooks/useMedia'; import useTransitionForMedia from '../../hooks/useTransitionForMedia'; @@ -18,16 +17,24 @@ import './AnimatedEmoji.scss'; type OwnProps = { sticker: ApiSticker; observeIntersection?: ObserveFn; - isInline?: boolean; + size?: 'large' | 'medium' | 'small'; lastSyncTime?: number; forceLoadPreview?: boolean; }; const QUALITY = 1; -const RESIZE_FACTOR = 0.5; +const WIDTH = { + large: 160, + medium: 128, + small: 104, +}; const AnimatedEmoji: FC = ({ - sticker, isInline = false, observeIntersection, lastSyncTime, forceLoadPreview, + sticker, + size = 'medium', + observeIntersection, + lastSyncTime, + forceLoadPreview, }) => { // eslint-disable-next-line no-null/no-null const ref = useRef(null); @@ -54,13 +61,7 @@ const AnimatedEmoji: FC = ({ setPlayKey(String(Math.random())); }, []); - let width: number; - if (isInline) { - width = getStickerDimensions(sticker).width * RESIZE_FACTOR; - } else { - width = STICKER_SIZE_TWO_FA; - } - + const width = WIDTH[size]; const style = `width: ${width}px; height: ${width}px;`; return ( diff --git a/src/components/common/StickerButton.scss b/src/components/common/StickerButton.scss index 6c21014df..7e231f053 100644 --- a/src/components/common/StickerButton.scss +++ b/src/components/common/StickerButton.scss @@ -20,6 +20,12 @@ margin: 0 0.5rem; } + &.large { + width: 10rem; + height: 10rem; + margin: 0; + } + .AnimatedSticker, img { position: absolute; top: 0; diff --git a/src/components/left/ArchivedChats.tsx b/src/components/left/ArchivedChats.tsx index 9f2e0cba8..30bdd876b 100644 --- a/src/components/left/ArchivedChats.tsx +++ b/src/components/left/ArchivedChats.tsx @@ -34,7 +34,7 @@ const ArchivedChats: FC = ({ isActive, onReset, onContentChange }) =>

{lang('ArchivedChats')}

- + ); }; diff --git a/src/components/left/LeftColumn.tsx b/src/components/left/LeftColumn.tsx index fae950c6e..c989160e3 100644 --- a/src/components/left/LeftColumn.tsx +++ b/src/components/left/LeftColumn.tsx @@ -9,6 +9,7 @@ import { LeftColumnContent, SettingsScreens } from '../../types'; import { LAYERS_ANIMATION_NAME } from '../../util/environment'; import captureEscKeyListener from '../../util/captureEscKeyListener'; import { pick } from '../../util/iteratees'; +import useFoldersReducer from '../../hooks/reducers/useFoldersReducer'; import Transition from '../ui/Transition'; import LeftMain from './main/LeftMain'; @@ -59,6 +60,7 @@ const LeftColumn: FC = ({ const [content, setContent] = useState(LeftColumnContent.ChatList); const [settingsScreen, setSettingsScreen] = useState(SettingsScreens.Main); const [contactsFilter, setContactsFilter] = useState(''); + const [foldersState, foldersDispatch] = useFoldersReducer(); // Used to reset child components in background. const [lastResetTime, setLastResetTime] = useState(0); @@ -193,6 +195,16 @@ const LeftColumn: FC = ({ case SettingsScreens.FoldersEditFolder: setSettingsScreen(SettingsScreens.Folders); return; + + case SettingsScreens.FoldersIncludedChatsFromChatList: + case SettingsScreens.FoldersExcludedChatsFromChatList: + setSettingsScreen(SettingsScreens.FoldersEditFolderFromChatList); + return; + + case SettingsScreens.FoldersEditFolderFromChatList: + setContent(LeftColumnContent.ChatList); + setSettingsScreen(SettingsScreens.Main); + return; default: break; } @@ -274,6 +286,8 @@ const LeftColumn: FC = ({ = ({ searchQuery={searchQuery} searchDate={searchDate} contactsFilter={contactsFilter} + foldersDispatch={foldersDispatch} onContentChange={setContent} onSearchQuery={handleSearchQuery} + onScreenSelect={handleSettingsScreenSelect} onReset={handleReset} shouldSkipTransition={shouldSkipHistoryAnimations} /> diff --git a/src/components/left/main/ChatFolders.tsx b/src/components/left/main/ChatFolders.tsx index 641c84277..d2f6e641a 100644 --- a/src/components/left/main/ChatFolders.tsx +++ b/src/components/left/main/ChatFolders.tsx @@ -5,7 +5,8 @@ import { withGlobal } from '../../../lib/teact/teactn'; import { ApiChat, ApiChatFolder, ApiUser } from '../../../api/types'; import { GlobalActions } from '../../../global/types'; -import { NotifyException, NotifySettings } from '../../../types'; +import { NotifyException, NotifySettings, SettingsScreens } from '../../../types'; +import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer'; import { IS_TOUCH_ENV } from '../../../util/environment'; import { buildCollectionByKey, pick } from '../../../util/iteratees'; @@ -23,6 +24,11 @@ import Transition from '../../ui/Transition'; import TabList from '../../ui/TabList'; import ChatList from './ChatList'; +type OwnProps = { + onScreenSelect: (screen: SettingsScreens) => void; + foldersDispatch: FolderEditDispatch; +}; + type StateProps = { chatsById: Record; usersById: Record; @@ -40,7 +46,7 @@ type DispatchProps = Pick = ({ +const ChatFolders: FC = ({ chatsById, usersById, chatFoldersById, @@ -50,6 +56,8 @@ const ChatFolders: FC = ({ activeChatFolder, currentUserId, lastSyncTime, + foldersDispatch, + onScreenSelect, loadChatFolders, setActiveChatFolder, openChat, @@ -182,15 +190,23 @@ const ChatFolders: FC = ({ .find(({ title }) => title === folderTabs![activeChatFolder].title); if (!activeFolder || activeChatFolder === 0) { - return ; + return ( + + ); } return ( ); } @@ -214,7 +230,7 @@ const ChatFolders: FC = ({ ); }; -export default memo(withGlobal( +export default memo(withGlobal( (global): StateProps => { const { chats: { byId: chatsById }, diff --git a/src/components/left/main/ChatList.tsx b/src/components/left/main/ChatList.tsx index 4d231285d..b441541fc 100644 --- a/src/components/left/main/ChatList.tsx +++ b/src/components/left/main/ChatList.tsx @@ -7,7 +7,8 @@ import { GlobalActions } from '../../../global/types'; import { ApiChat, ApiChatFolder, ApiUser, } from '../../../api/types'; -import { NotifyException, NotifySettings } from '../../../types'; +import { NotifyException, NotifySettings, SettingsScreens } from '../../../types'; +import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer'; import { ALL_CHATS_PRELOAD_DISABLED, CHAT_HEIGHT_PX, CHAT_LIST_SLICE } from '../../../config'; import { IS_ANDROID, IS_MAC_OS, IS_PWA } from '../../../util/environment'; @@ -23,12 +24,14 @@ import { useChatAnimationType } from './hooks'; import InfiniteScroll from '../../ui/InfiniteScroll'; import Loading from '../../ui/Loading'; import Chat from './Chat'; +import EmptyFolder from './EmptyFolder'; type OwnProps = { folderType: 'all' | 'archived' | 'folder'; folderId?: number; - noChatsText?: string; isActive: boolean; + onScreenSelect?: (screen: SettingsScreens) => void; + foldersDispatch?: FolderEditDispatch; }; type StateProps = { @@ -52,7 +55,6 @@ enum FolderTypeToListType { const ChatList: FC = ({ folderType, folderId, - noChatsText = 'Chat list is empty.', isActive, chatFolder, chatsById, @@ -60,8 +62,10 @@ const ChatList: FC = ({ listIds, orderedPinnedIds, lastSyncTime, + foldersDispatch, notifySettings, notifyExceptions, + onScreenSelect, loadMoreChats, preloadTopChatMessages, openChat, @@ -205,7 +209,14 @@ const ChatList: FC = ({ {viewportIds && viewportIds.length && chatArrays ? ( renderChats() ) : viewportIds && !viewportIds.length ? ( -
{noChatsText}
+ ( + + ) ) : ( )} diff --git a/src/components/left/main/EmptyFolder.scss b/src/components/left/main/EmptyFolder.scss new file mode 100644 index 000000000..46b29f411 --- /dev/null +++ b/src/components/left/main/EmptyFolder.scss @@ -0,0 +1,44 @@ +.EmptyFolder { + width: 100%; + height: 80%; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + + @media (max-height: 480px) { + height: 100%; + } + + .sticker { + height: 8rem; + margin-bottom: 1.875rem; + } + + .title { + font-size: 1.25rem; + margin-bottom: .125rem; + } + + .description { + font-size: .875rem; + color: var(--color-text-secondary); + + body.is-ios &, + body.is-macos & { + color: var(--color-text-secondary-apple); + } + } + + .Button.pill { + margin-top: .625rem; + font-weight: 500; + padding-inline-start: .75rem; + unicode-bidi: plaintext; + + i { + margin-inline-end: .625rem; + font-size: 1.5rem; + } + } +} diff --git a/src/components/left/main/EmptyFolder.tsx b/src/components/left/main/EmptyFolder.tsx new file mode 100644 index 000000000..cb7e230ac --- /dev/null +++ b/src/components/left/main/EmptyFolder.tsx @@ -0,0 +1,70 @@ +import React, { FC, memo, useCallback } from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; + +import { ApiChatFolder, ApiSticker } from '../../../api/types'; +import { SettingsScreens } from '../../../types'; +import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer'; + +import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment'; +import { selectAnimatedEmoji, selectChatFolder } from '../../../modules/selectors'; +import useLang from '../../../hooks/useLang'; + +import Button from '../../ui/Button'; +import AnimatedEmoji from '../../common/AnimatedEmoji'; + +import './EmptyFolder.scss'; + +type OwnProps = { + folderId?: number; + folderType: 'all' | 'archived' | 'folder'; + foldersDispatch?: FolderEditDispatch; + onScreenSelect?: (screen: SettingsScreens) => void; +}; + +type StateProps = { + chatFolder?: ApiChatFolder; + animatedEmoji?: ApiSticker; +}; + +const EmptyFolder: FC = ({ + chatFolder, animatedEmoji, foldersDispatch, onScreenSelect, +}) => { + const lang = useLang(); + + const handleEditFolder = useCallback(() => { + foldersDispatch!({ type: 'editFolder', payload: chatFolder }); + onScreenSelect!(SettingsScreens.FoldersEditFolderFromChatList); + }, [chatFolder, foldersDispatch, onScreenSelect]); + + return ( +
+
{animatedEmoji && }
+

{lang('FilterNoChatsToDisplay')}

+

+ {lang(chatFolder ? 'ChatList.EmptyChatListFilterText' : 'Chat.EmptyChat')} +

+ {chatFolder && foldersDispatch && onScreenSelect && ( + + )} +
+ ); +}; + +export default memo(withGlobal((global, { folderId, folderType }): StateProps => { + const chatFolder = folderId && folderType === 'folder' ? selectChatFolder(global, folderId) : undefined; + + return { + chatFolder, + animatedEmoji: selectAnimatedEmoji(global, '📂'), + }; +})(EmptyFolder)); diff --git a/src/components/left/main/LeftMain.tsx b/src/components/left/main/LeftMain.tsx index 76022a825..9b05dd5c0 100644 --- a/src/components/left/main/LeftMain.tsx +++ b/src/components/left/main/LeftMain.tsx @@ -4,7 +4,8 @@ import React, { import { withGlobal } from '../../../lib/teact/teactn'; import { GlobalState } from '../../../global/types'; -import { LeftColumnContent } from '../../../types'; +import { LeftColumnContent, SettingsScreens } from '../../../types'; +import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer'; import { IS_TOUCH_ENV } from '../../../util/environment'; import { pick } from '../../../util/iteratees'; @@ -32,8 +33,10 @@ type OwnProps = { searchDate?: number; contactsFilter: string; shouldSkipTransition?: boolean; + foldersDispatch: FolderEditDispatch; onSearchQuery: (query: string) => void; onContentChange: (content: LeftColumnContent) => void; + onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -51,8 +54,10 @@ const LeftMain: FC = ({ searchDate, contactsFilter, shouldSkipTransition, + foldersDispatch, onSearchQuery, onContentChange, + onScreenSelect, onReset, connectionState, }) => { @@ -158,7 +163,7 @@ const LeftMain: FC = ({ {(isActive) => { switch (content) { case LeftColumnContent.ChatList: - return ; + return ; case LeftColumnContent.GlobalSearch: return ( void; shouldSkipTransition?: boolean; onReset: () => void; @@ -96,17 +101,19 @@ export type OwnProps = { const Settings: FC = ({ isActive, currentScreen, + foldersState, + foldersDispatch, onScreenSelect, onReset, shouldSkipTransition, }) => { - const [foldersState, foldersDispatch] = useFoldersReducer(); const [twoFaState, twoFaDispatch] = useTwoFaReducer(); const handleReset = useCallback(() => { if ( currentScreen === SettingsScreens.FoldersCreateFolder || currentScreen === SettingsScreens.FoldersEditFolder + || currentScreen === SettingsScreens.FoldersEditFolderFromChatList ) { setTimeout(() => { foldersDispatch({ type: 'reset' }); @@ -270,8 +277,11 @@ const Settings: FC = ({ case SettingsScreens.Folders: case SettingsScreens.FoldersCreateFolder: case SettingsScreens.FoldersEditFolder: + case SettingsScreens.FoldersEditFolderFromChatList: case SettingsScreens.FoldersIncludedChats: + case SettingsScreens.FoldersIncludedChatsFromChatList: case SettingsScreens.FoldersExcludedChats: + case SettingsScreens.FoldersExcludedChatsFromChatList: return ( = ({ case SettingsScreens.FoldersCreateFolder: return

{lang('FilterNew')}

; case SettingsScreens.FoldersEditFolder: + case SettingsScreens.FoldersEditFolderFromChatList: return (

{lang('FilterEdit')}

@@ -174,14 +175,17 @@ const SettingsHeader: FC = ({
); case SettingsScreens.FoldersIncludedChats: + case SettingsScreens.FoldersIncludedChatsFromChatList: case SettingsScreens.FoldersExcludedChats: + case SettingsScreens.FoldersExcludedChatsFromChatList: return (
- {currentScreen === SettingsScreens.FoldersIncludedChats ? ( -

{lang('FilterInclude')}

- ) : ( -

{lang('FilterExclude')}

- )} + {(currentScreen === SettingsScreens.FoldersIncludedChats + || currentScreen === SettingsScreens.FoldersIncludedChatsFromChatList) ? ( +

{lang('FilterInclude')}

+ ) : ( +

{lang('FilterExclude')}

+ )}