mirror of
https://github.com/danog/telegram-tt.git
synced 2024-11-26 20:34:44 +01:00
Chat List, Message List: New designs for empty screens (#1342)
This commit is contained in:
parent
2ff25c9113
commit
1038c2ffcf
@ -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);
|
||||
|
@ -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[];
|
||||
}
|
||||
|
@ -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<OwnProps> = ({
|
||||
sticker, isInline = false, observeIntersection, lastSyncTime, forceLoadPreview,
|
||||
sticker,
|
||||
size = 'medium',
|
||||
observeIntersection,
|
||||
lastSyncTime,
|
||||
forceLoadPreview,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@ -54,13 +61,7 @@ const AnimatedEmoji: FC<OwnProps> = ({
|
||||
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 (
|
||||
|
@ -20,6 +20,12 @@
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
&.large {
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.AnimatedSticker, img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -34,7 +34,7 @@ const ArchivedChats: FC<OwnProps> = ({ isActive, onReset, onContentChange }) =>
|
||||
</Button>
|
||||
<h3>{lang('ArchivedChats')}</h3>
|
||||
</div>
|
||||
<ChatList folderType="archived" noChatsText="Archive is empty." isActive={isActive} />
|
||||
<ChatList folderType="archived" isActive={isActive} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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<StateProps & DispatchProps> = ({
|
||||
const [content, setContent] = useState<LeftColumnContent>(LeftColumnContent.ChatList);
|
||||
const [settingsScreen, setSettingsScreen] = useState(SettingsScreens.Main);
|
||||
const [contactsFilter, setContactsFilter] = useState<string>('');
|
||||
const [foldersState, foldersDispatch] = useFoldersReducer();
|
||||
|
||||
// Used to reset child components in background.
|
||||
const [lastResetTime, setLastResetTime] = useState<number>(0);
|
||||
@ -193,6 +195,16 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
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<StateProps & DispatchProps> = ({
|
||||
<Settings
|
||||
isActive={isActive}
|
||||
currentScreen={settingsScreen}
|
||||
foldersState={foldersState}
|
||||
foldersDispatch={foldersDispatch}
|
||||
onScreenSelect={handleSettingsScreenSelect}
|
||||
onReset={handleReset}
|
||||
shouldSkipTransition={shouldSkipHistoryAnimations}
|
||||
@ -307,8 +321,10 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
searchQuery={searchQuery}
|
||||
searchDate={searchDate}
|
||||
contactsFilter={contactsFilter}
|
||||
foldersDispatch={foldersDispatch}
|
||||
onContentChange={setContent}
|
||||
onSearchQuery={handleSearchQuery}
|
||||
onScreenSelect={handleSettingsScreenSelect}
|
||||
onReset={handleReset}
|
||||
shouldSkipTransition={shouldSkipHistoryAnimations}
|
||||
/>
|
||||
|
@ -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<number, ApiChat>;
|
||||
usersById: Record<number, ApiUser>;
|
||||
@ -40,7 +46,7 @@ type DispatchProps = Pick<GlobalActions, 'loadChatFolders' | 'setActiveChatFolde
|
||||
const INFO_THROTTLE = 3000;
|
||||
const SAVED_MESSAGES_HOTKEY = '0';
|
||||
|
||||
const ChatFolders: FC<StateProps & DispatchProps> = ({
|
||||
const ChatFolders: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
chatsById,
|
||||
usersById,
|
||||
chatFoldersById,
|
||||
@ -50,6 +56,8 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
||||
activeChatFolder,
|
||||
currentUserId,
|
||||
lastSyncTime,
|
||||
foldersDispatch,
|
||||
onScreenSelect,
|
||||
loadChatFolders,
|
||||
setActiveChatFolder,
|
||||
openChat,
|
||||
@ -182,15 +190,23 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
||||
.find(({ title }) => title === folderTabs![activeChatFolder].title);
|
||||
|
||||
if (!activeFolder || activeChatFolder === 0) {
|
||||
return <ChatList folderType="all" isActive={isActive} />;
|
||||
return (
|
||||
<ChatList
|
||||
folderType="all"
|
||||
isActive={isActive}
|
||||
foldersDispatch={foldersDispatch}
|
||||
onScreenSelect={onScreenSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatList
|
||||
folderType="folder"
|
||||
folderId={activeFolder.id}
|
||||
noChatsText={lang('FilterNoChatsToDisplay')}
|
||||
isActive={isActive}
|
||||
onScreenSelect={onScreenSelect}
|
||||
foldersDispatch={foldersDispatch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -214,7 +230,7 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal(
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const {
|
||||
chats: { byId: chatsById },
|
||||
|
@ -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<OwnProps & StateProps & DispatchProps> = ({
|
||||
folderType,
|
||||
folderId,
|
||||
noChatsText = 'Chat list is empty.',
|
||||
isActive,
|
||||
chatFolder,
|
||||
chatsById,
|
||||
@ -60,8 +62,10 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
listIds,
|
||||
orderedPinnedIds,
|
||||
lastSyncTime,
|
||||
foldersDispatch,
|
||||
notifySettings,
|
||||
notifyExceptions,
|
||||
onScreenSelect,
|
||||
loadMoreChats,
|
||||
preloadTopChatMessages,
|
||||
openChat,
|
||||
@ -205,7 +209,14 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
{viewportIds && viewportIds.length && chatArrays ? (
|
||||
renderChats()
|
||||
) : viewportIds && !viewportIds.length ? (
|
||||
<div className="no-results">{noChatsText}</div>
|
||||
(
|
||||
<EmptyFolder
|
||||
folderId={folderId}
|
||||
folderType={folderType}
|
||||
foldersDispatch={foldersDispatch}
|
||||
onScreenSelect={onScreenSelect}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<Loading key="loading" />
|
||||
)}
|
||||
|
44
src/components/left/main/EmptyFolder.scss
Normal file
44
src/components/left/main/EmptyFolder.scss
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
70
src/components/left/main/EmptyFolder.tsx
Normal file
70
src/components/left/main/EmptyFolder.tsx
Normal file
@ -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<OwnProps & StateProps> = ({
|
||||
chatFolder, animatedEmoji, foldersDispatch, onScreenSelect,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
const handleEditFolder = useCallback(() => {
|
||||
foldersDispatch!({ type: 'editFolder', payload: chatFolder });
|
||||
onScreenSelect!(SettingsScreens.FoldersEditFolderFromChatList);
|
||||
}, [chatFolder, foldersDispatch, onScreenSelect]);
|
||||
|
||||
return (
|
||||
<div className="EmptyFolder">
|
||||
<div className="sticker">{animatedEmoji && <AnimatedEmoji sticker={animatedEmoji} />}</div>
|
||||
<h3 className="title" dir="auto">{lang('FilterNoChatsToDisplay')}</h3>
|
||||
<p className="description" dir="auto">
|
||||
{lang(chatFolder ? 'ChatList.EmptyChatListFilterText' : 'Chat.EmptyChat')}
|
||||
</p>
|
||||
{chatFolder && foldersDispatch && onScreenSelect && (
|
||||
<Button
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
fluid
|
||||
pill
|
||||
onClick={handleEditFolder}
|
||||
size="smaller"
|
||||
isRtl={lang.isRtl}
|
||||
>
|
||||
<i className="icon-settings" />
|
||||
{lang('ChatList.EmptyChatListEditFilter')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global, { folderId, folderType }): StateProps => {
|
||||
const chatFolder = folderId && folderType === 'folder' ? selectChatFolder(global, folderId) : undefined;
|
||||
|
||||
return {
|
||||
chatFolder,
|
||||
animatedEmoji: selectAnimatedEmoji(global, '📂'),
|
||||
};
|
||||
})(EmptyFolder));
|
@ -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<OwnProps & StateProps> = ({
|
||||
searchDate,
|
||||
contactsFilter,
|
||||
shouldSkipTransition,
|
||||
foldersDispatch,
|
||||
onSearchQuery,
|
||||
onContentChange,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
connectionState,
|
||||
}) => {
|
||||
@ -158,7 +163,7 @@ const LeftMain: FC<OwnProps & StateProps> = ({
|
||||
{(isActive) => {
|
||||
switch (content) {
|
||||
case LeftColumnContent.ChatList:
|
||||
return <ChatFolders />;
|
||||
return <ChatFolders onScreenSelect={onScreenSelect} foldersDispatch={foldersDispatch} />;
|
||||
case LeftColumnContent.GlobalSearch:
|
||||
return (
|
||||
<LeftSearch
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { FC, memo, useCallback } from '../../../lib/teact/teact';
|
||||
|
||||
import { SettingsScreens } from '../../../types';
|
||||
import { FolderEditDispatch, FoldersState } from '../../../hooks/reducers/useFoldersReducer';
|
||||
|
||||
import { LAYERS_ANIMATION_NAME } from '../../../util/environment';
|
||||
import useFoldersReducer from '../../../hooks/reducers/useFoldersReducer';
|
||||
import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer';
|
||||
|
||||
import Transition from '../../ui/Transition';
|
||||
@ -51,8 +51,11 @@ const FOLDERS_SCREENS = [
|
||||
SettingsScreens.Folders,
|
||||
SettingsScreens.FoldersCreateFolder,
|
||||
SettingsScreens.FoldersEditFolder,
|
||||
SettingsScreens.FoldersEditFolderFromChatList,
|
||||
SettingsScreens.FoldersIncludedChats,
|
||||
SettingsScreens.FoldersIncludedChatsFromChatList,
|
||||
SettingsScreens.FoldersExcludedChats,
|
||||
SettingsScreens.FoldersExcludedChatsFromChatList,
|
||||
];
|
||||
|
||||
const PRIVACY_SCREENS = [
|
||||
@ -88,6 +91,8 @@ const PRIVACY_GROUP_CHATS_SCREENS = [
|
||||
export type OwnProps = {
|
||||
isActive: boolean;
|
||||
currentScreen: SettingsScreens;
|
||||
foldersState: FoldersState;
|
||||
foldersDispatch: FolderEditDispatch;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
shouldSkipTransition?: boolean;
|
||||
onReset: () => void;
|
||||
@ -96,17 +101,19 @@ export type OwnProps = {
|
||||
const Settings: FC<OwnProps> = ({
|
||||
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<OwnProps> = ({
|
||||
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 (
|
||||
<SettingsFolders
|
||||
currentScreen={currentScreen}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, {
|
||||
FC, useCallback, useMemo, memo, useState,
|
||||
FC, memo, useCallback, useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
@ -156,6 +156,7 @@ const SettingsHeader: FC<OwnProps & DispatchProps> = ({
|
||||
case SettingsScreens.FoldersCreateFolder:
|
||||
return <h3>{lang('FilterNew')}</h3>;
|
||||
case SettingsScreens.FoldersEditFolder:
|
||||
case SettingsScreens.FoldersEditFolderFromChatList:
|
||||
return (
|
||||
<div className="settings-main-header">
|
||||
<h3>{lang('FilterEdit')}</h3>
|
||||
@ -174,14 +175,17 @@ const SettingsHeader: FC<OwnProps & DispatchProps> = ({
|
||||
</div>
|
||||
);
|
||||
case SettingsScreens.FoldersIncludedChats:
|
||||
case SettingsScreens.FoldersIncludedChatsFromChatList:
|
||||
case SettingsScreens.FoldersExcludedChats:
|
||||
case SettingsScreens.FoldersExcludedChatsFromChatList:
|
||||
return (
|
||||
<div className="settings-main-header">
|
||||
{currentScreen === SettingsScreens.FoldersIncludedChats ? (
|
||||
<h3>{lang('FilterInclude')}</h3>
|
||||
) : (
|
||||
<h3>{lang('FilterExclude')}</h3>
|
||||
)}
|
||||
{(currentScreen === SettingsScreens.FoldersIncludedChats
|
||||
|| currentScreen === SettingsScreens.FoldersIncludedChatsFromChatList) ? (
|
||||
<h3>{lang('FilterInclude')}</h3>
|
||||
) : (
|
||||
<h3>{lang('FilterExclude')}</h3>
|
||||
)}
|
||||
|
||||
<Button
|
||||
round
|
||||
|
@ -36,6 +36,7 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
if (
|
||||
currentScreen === SettingsScreens.FoldersCreateFolder
|
||||
|| currentScreen === SettingsScreens.FoldersEditFolder
|
||||
|| currentScreen === SettingsScreens.FoldersEditFolderFromChatList
|
||||
) {
|
||||
setTimeout(() => {
|
||||
dispatch({ type: 'reset' });
|
||||
@ -72,13 +73,17 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
|
||||
const handleAddIncludedChats = useCallback(() => {
|
||||
dispatch({ type: 'editIncludeFilters' });
|
||||
onScreenSelect(SettingsScreens.FoldersIncludedChats);
|
||||
}, [dispatch, onScreenSelect]);
|
||||
onScreenSelect(currentScreen === SettingsScreens.FoldersEditFolderFromChatList
|
||||
? SettingsScreens.FoldersIncludedChatsFromChatList
|
||||
: SettingsScreens.FoldersIncludedChats);
|
||||
}, [currentScreen, dispatch, onScreenSelect]);
|
||||
|
||||
const handleAddExcludedChats = useCallback(() => {
|
||||
dispatch({ type: 'editExcludeFilters' });
|
||||
onScreenSelect(SettingsScreens.FoldersExcludedChats);
|
||||
}, [dispatch, onScreenSelect]);
|
||||
onScreenSelect(currentScreen === SettingsScreens.FoldersEditFolderFromChatList
|
||||
? SettingsScreens.FoldersExcludedChatsFromChatList
|
||||
: SettingsScreens.FoldersExcludedChats);
|
||||
}, [currentScreen, dispatch, onScreenSelect]);
|
||||
|
||||
switch (currentScreen) {
|
||||
case SettingsScreens.Folders:
|
||||
@ -98,6 +103,7 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
);
|
||||
case SettingsScreens.FoldersCreateFolder:
|
||||
case SettingsScreens.FoldersEditFolder:
|
||||
case SettingsScreens.FoldersEditFolderFromChatList:
|
||||
return (
|
||||
<SettingsFoldersEdit
|
||||
state={state}
|
||||
@ -114,6 +120,7 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.FoldersIncludedChats:
|
||||
case SettingsScreens.FoldersIncludedChatsFromChatList:
|
||||
return (
|
||||
<SettingsFoldersChatFilters
|
||||
mode="included"
|
||||
@ -125,6 +132,7 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.FoldersExcludedChats:
|
||||
case SettingsScreens.FoldersExcludedChatsFromChatList:
|
||||
return (
|
||||
<SettingsFoldersChatFilters
|
||||
mode="excluded"
|
||||
|
@ -52,7 +52,7 @@ const SUBMIT_TIMEOUT = 500;
|
||||
const INITIAL_CHATS_LIMIT = 5;
|
||||
|
||||
const ERROR_NO_TITLE = 'Please provide a title for this folder.';
|
||||
const ERROR_NO_CHATS = 'Please select at least one chat for this folder.';
|
||||
const ERROR_NO_CHATS = 'ChatList.Filter.Error.Empty';
|
||||
|
||||
const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
state,
|
||||
@ -265,7 +265,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<div className="settings-item no-border pt-3">
|
||||
{state.error && state.error === ERROR_NO_CHATS && (
|
||||
<p className="settings-item-description color-danger mb-2" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{state.error}
|
||||
{lang(state.error)}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
@ -35,7 +35,7 @@ const SettingsTwoFaCongratulations: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<div className="settings-content two-fa custom-scroll">
|
||||
<div className="settings-content-header">
|
||||
<AnimatedEmoji sticker={animatedEmoji} />
|
||||
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
||||
|
||||
<p className="settings-item-description mb-3" dir="auto">
|
||||
{lang('TwoStepVerificationPasswordSetInfo')}
|
||||
|
@ -80,7 +80,7 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<div className="settings-content two-fa custom-scroll">
|
||||
<div className="settings-content-header">
|
||||
<AnimatedEmoji sticker={animatedEmoji} />
|
||||
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
||||
</div>
|
||||
|
||||
<div className="settings-item pt-0 no-border">
|
||||
|
@ -32,7 +32,7 @@ const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<div className="settings-content two-fa custom-scroll">
|
||||
<div className="settings-content-header">
|
||||
<AnimatedEmoji sticker={animatedEmoji} />
|
||||
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
||||
|
||||
<p className="settings-item-description mb-3" dir="auto">
|
||||
{renderText(lang('EnabledPasswordText'), ['br'])}
|
||||
|
@ -101,7 +101,7 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<div className="settings-content two-fa custom-scroll">
|
||||
<div className="settings-content-header">
|
||||
<AnimatedEmoji sticker={animatedEmoji} />
|
||||
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
||||
</div>
|
||||
|
||||
<div className="settings-item pt-0 no-border">
|
||||
|
@ -32,7 +32,7 @@ const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<div className="settings-content two-fa custom-scroll">
|
||||
<div className="settings-content-header">
|
||||
<AnimatedEmoji sticker={animatedEmoji} />
|
||||
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
||||
|
||||
<p className="settings-item-description mb-3" dir="auto">
|
||||
{lang('SetAdditionalPasswordInfo')}
|
||||
|
41
src/components/middle/ContactGreeting.scss
Normal file
41
src/components/middle/ContactGreeting.scss
Normal file
@ -0,0 +1,41 @@
|
||||
.ContactGreeting {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
.wrapper {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: var(--pattern-color);
|
||||
width: 14.5rem;
|
||||
padding: .75rem 1rem;
|
||||
border-radius: 1.5rem;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: .9375rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.sticker {
|
||||
margin: 2rem 0 1rem;
|
||||
height: 10rem;
|
||||
width: 10rem;
|
||||
cursor: pointer;
|
||||
|
||||
.thumbnail {
|
||||
height: 10rem;
|
||||
width: 10rem;
|
||||
}
|
||||
}
|
||||
}
|
116
src/components/middle/ContactGreeting.tsx
Normal file
116
src/components/middle/ContactGreeting.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import React, {
|
||||
FC, memo, useCallback, useEffect, useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../global/types';
|
||||
import { ApiSticker, ApiUpdateConnectionStateType } from '../../api/types';
|
||||
|
||||
import { pick } from '../../util/iteratees';
|
||||
import { selectChat } from '../../modules/selectors';
|
||||
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import StickerButton from '../common/StickerButton';
|
||||
|
||||
import './ContactGreeting.scss';
|
||||
|
||||
type OwnProps = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
sticker?: ApiSticker;
|
||||
lastUnreadMessageId?: number;
|
||||
connectionState?: ApiUpdateConnectionStateType;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'loadGreetingStickers' | 'sendMessage' | 'markMessageListRead'>;
|
||||
|
||||
const INTERSECTION_DEBOUNCE_MS = 200;
|
||||
|
||||
const ContactGreeting: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
sticker,
|
||||
connectionState,
|
||||
lastUnreadMessageId,
|
||||
loadGreetingStickers,
|
||||
sendMessage,
|
||||
markMessageListRead,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const {
|
||||
observe: observeIntersection,
|
||||
} = useIntersectionObserver({
|
||||
rootRef: containerRef,
|
||||
debounceMs: INTERSECTION_DEBOUNCE_MS,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (sticker || connectionState !== 'connectionStateReady') {
|
||||
return;
|
||||
}
|
||||
|
||||
loadGreetingStickers();
|
||||
}, [connectionState, loadGreetingStickers, sticker]);
|
||||
|
||||
useEffect(() => {
|
||||
if (connectionState === 'connectionStateReady' && lastUnreadMessageId) {
|
||||
markMessageListRead({ maxId: lastUnreadMessageId });
|
||||
}
|
||||
}, [connectionState, markMessageListRead, lastUnreadMessageId]);
|
||||
|
||||
const handleStickerSelect = useCallback((selectedSticker: ApiSticker) => {
|
||||
selectedSticker = {
|
||||
...selectedSticker,
|
||||
isPreloadedGlobally: true,
|
||||
};
|
||||
sendMessage({ sticker: selectedSticker });
|
||||
}, [sendMessage]);
|
||||
|
||||
return (
|
||||
<div className="ContactGreeting" ref={containerRef}>
|
||||
<div className="wrapper">
|
||||
<p className="title" dir="auto">{lang('Conversation.EmptyPlaceholder')}</p>
|
||||
<p className="description" dir="auto">{lang('Conversation.GreetingText')}</p>
|
||||
|
||||
<div className="sticker">
|
||||
{sticker && (
|
||||
<StickerButton
|
||||
sticker={sticker}
|
||||
onClick={handleStickerSelect}
|
||||
clickArg={sticker}
|
||||
observeIntersection={observeIntersection}
|
||||
size={160}
|
||||
className="large"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { userId }): StateProps => {
|
||||
const { stickers } = global.stickers.greeting;
|
||||
const sticker = stickers && stickers.length ? stickers[userId % stickers.length] : undefined;
|
||||
const chat = selectChat(global, userId);
|
||||
if (!chat) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
sticker,
|
||||
lastUnreadMessageId: chat.lastMessage && chat.lastMessage.id !== chat.lastReadInboxMessageId
|
||||
? chat.lastMessage.id
|
||||
: undefined,
|
||||
connectionState: global.connectionState,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
'loadGreetingStickers', 'sendMessage', 'markMessageListRead',
|
||||
]),
|
||||
|
||||
)(ContactGreeting));
|
@ -3,7 +3,9 @@ import React, {
|
||||
} from '../../lib/teact/teact';
|
||||
import { getGlobal, withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { ApiMessage, ApiRestrictionReason, MAIN_THREAD_ID } from '../../api/types';
|
||||
import {
|
||||
ApiAction, ApiMessage, ApiRestrictionReason, MAIN_THREAD_ID,
|
||||
} from '../../api/types';
|
||||
import { GlobalActions, MessageListType } from '../../global/types';
|
||||
import { LoadMoreDirection } from '../../types';
|
||||
|
||||
@ -24,13 +26,13 @@ import {
|
||||
selectScheduledMessages,
|
||||
selectCurrentMessageIds,
|
||||
} from '../../modules/selectors';
|
||||
import { isChatChannel, isChatPrivate } from '../../modules/helpers';
|
||||
import { isChatChannel, isChatGroup, isChatPrivate } from '../../modules/helpers';
|
||||
import { orderBy, pick } from '../../util/iteratees';
|
||||
import { fastRaf, debounce, onTickEnd } from '../../util/schedulers';
|
||||
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
|
||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { groupMessages } from './helpers/groupMessages';
|
||||
import { groupMessages, MessageDateGroup } from './helpers/groupMessages';
|
||||
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
||||
import useOnChange from '../../hooks/useOnChange';
|
||||
import useStickyDates from './hooks/useStickyDates';
|
||||
@ -43,6 +45,8 @@ import useWindowSize from '../../hooks/useWindowSize';
|
||||
|
||||
import Loading from '../ui/Loading';
|
||||
import MessageListContent from './MessageListContent';
|
||||
import ContactGreeting from './ContactGreeting';
|
||||
import NoMessages from './NoMessages';
|
||||
|
||||
import './MessageList.scss';
|
||||
|
||||
@ -60,7 +64,10 @@ type OwnProps = {
|
||||
type StateProps = {
|
||||
isChatLoaded?: boolean;
|
||||
isChannelChat?: boolean;
|
||||
isGroupChat?: boolean;
|
||||
isChatWithSelf?: boolean;
|
||||
isCreator?: boolean;
|
||||
isBot?: boolean;
|
||||
messageIds?: number[];
|
||||
messagesById?: Record<number, ApiMessage>;
|
||||
firstUnreadId?: number;
|
||||
@ -100,9 +107,12 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
onNotchToggle,
|
||||
isChatLoaded,
|
||||
isChannelChat,
|
||||
isGroupChat,
|
||||
canPost,
|
||||
isReady,
|
||||
isChatWithSelf,
|
||||
isCreator,
|
||||
isBot,
|
||||
messageIds,
|
||||
messagesById,
|
||||
firstUnreadId,
|
||||
@ -424,6 +434,16 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const isPrivate = Boolean(chatId && isChatPrivate(chatId));
|
||||
const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf);
|
||||
const noAvatars = Boolean(!withUsers || isChannelChat);
|
||||
const shouldRenderGreeting = isChatPrivate(chatId) && !isChatWithSelf && !isBot
|
||||
&& ((
|
||||
!messageGroups && !lastMessage && messageIds
|
||||
// Used to avoid flickering when deleting a greeting that has just been sent
|
||||
&& (!listItemElementsRef.current || listItemElementsRef.current.length === 0))
|
||||
|| checkSingleMessageActionByType('contactSignUp', messageGroups)
|
||||
|| (lastMessage && lastMessage.content.action && lastMessage.content.action.type === 'contactSignUp')
|
||||
);
|
||||
const isGroupChatJustCreated = isGroupChat && isCreator
|
||||
&& checkSingleMessageActionByType('chatCreate', messageGroups);
|
||||
|
||||
const className = buildClassName(
|
||||
'MessageList custom-scroll',
|
||||
@ -451,8 +471,15 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
</div>
|
||||
) : botDescription ? (
|
||||
<div className="empty rich"><span>{renderText(lang(botDescription), ['br', 'emoji', 'links'])}</span></div>
|
||||
) : messageIds && !messageGroups ? (
|
||||
<div className="empty"><span>{lang('NoMessages')}</span></div>
|
||||
) : shouldRenderGreeting ? (
|
||||
<ContactGreeting userId={chatId} />
|
||||
) : messageIds && (!messageGroups || isGroupChatJustCreated) ? (
|
||||
<NoMessages
|
||||
chatId={chatId}
|
||||
type={type}
|
||||
isChatWithSelf={isChatWithSelf}
|
||||
isGroupChatJustCreated={isGroupChatJustCreated}
|
||||
/>
|
||||
) : ((messageIds && messageGroups) || lastMessage) ? (
|
||||
<MessageListContent
|
||||
messageIds={messageIds || [lastMessage!.id]}
|
||||
@ -481,6 +508,16 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
function checkSingleMessageActionByType(type: ApiAction['type'], messageGroups?: MessageDateGroup[]) {
|
||||
return messageGroups
|
||||
&& messageGroups.length === 1
|
||||
&& messageGroups[0].senderGroups.length === 1
|
||||
&& messageGroups[0].senderGroups[0].length === 1
|
||||
&& 'content' in messageGroups[0].senderGroups[0][0]
|
||||
&& messageGroups[0].senderGroups[0][0].content.action
|
||||
&& messageGroups[0].senderGroups[0][0].content.action.type === type;
|
||||
}
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId, threadId, type }): StateProps => {
|
||||
const chat = selectChat(global, chatId);
|
||||
@ -510,6 +547,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
&& !messageIds && !chat.unreadCount && !focusingId && lastMessage && !lastMessage.groupedId
|
||||
);
|
||||
|
||||
const bot = selectChatBot(global, chatId);
|
||||
let botDescription: string | undefined;
|
||||
if (selectIsChatBotNotStarted(global, chatId)) {
|
||||
const chatBot = selectChatBot(global, chatId)!;
|
||||
@ -525,7 +563,10 @@ export default memo(withGlobal<OwnProps>(
|
||||
isRestricted,
|
||||
restrictionReason,
|
||||
isChannelChat: isChatChannel(chat),
|
||||
isGroupChat: isChatGroup(chat),
|
||||
isCreator: chat.isCreator,
|
||||
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
||||
isBot: Boolean(bot),
|
||||
messageIds,
|
||||
messagesById,
|
||||
firstUnreadId: selectFirstUnreadId(global, chatId, threadId),
|
||||
|
@ -330,7 +330,7 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<>
|
||||
{renderBackButton()}
|
||||
<h3>
|
||||
{isChatWithSelf ? lang('Reminders') : lang('messages', messagesCount)}
|
||||
{isChatWithSelf ? lang('Reminders') : lang('messages', messagesCount, 'i')}
|
||||
</h3>
|
||||
</>
|
||||
) : undefined
|
||||
|
56
src/components/middle/NoMessages.scss
Normal file
56
src/components/middle/NoMessages.scss
Normal file
@ -0,0 +1,56 @@
|
||||
.NoMessages {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon {
|
||||
font-size: 5rem;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
background: var(--pattern-color);
|
||||
max-width: 20rem;
|
||||
padding: .75rem 1rem;
|
||||
border-radius: 1.5rem;
|
||||
color: #fff;
|
||||
|
||||
&[dir=rtl] {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
margin-bottom: .25rem;
|
||||
text-align: center;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: .9375rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.list-checkmarks {
|
||||
font-size: .9375rem;
|
||||
margin: .25rem 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
unicode-bidi: plaintext;
|
||||
line-height: 1.8;
|
||||
|
||||
li::before {
|
||||
content: '✓';
|
||||
margin-inline-end: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
79
src/components/middle/NoMessages.tsx
Normal file
79
src/components/middle/NoMessages.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import React, { FC, memo } from '../../lib/teact/teact';
|
||||
|
||||
import { MessageListType } from '../../global/types';
|
||||
|
||||
import useLang, { LangFn } from '../../hooks/useLang';
|
||||
|
||||
import './NoMessages.scss';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: number;
|
||||
isChatWithSelf?: boolean;
|
||||
type: MessageListType;
|
||||
isGroupChatJustCreated?: boolean;
|
||||
};
|
||||
|
||||
const NoMessages: FC<OwnProps> = ({
|
||||
isChatWithSelf, type, isGroupChatJustCreated,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
if (type === 'scheduled') {
|
||||
return renderScheduled(lang);
|
||||
}
|
||||
|
||||
if (isChatWithSelf) {
|
||||
return renderSavedMessages(lang);
|
||||
}
|
||||
|
||||
if (isGroupChatJustCreated) {
|
||||
return renderGroup(lang);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="empty"><span>{lang('NoMessages')}</span></div>
|
||||
);
|
||||
};
|
||||
|
||||
function renderScheduled(lang: LangFn) {
|
||||
return (
|
||||
<div className="empty"><span>{lang('ScheduledMessages.EmptyPlaceholder')}</span></div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSavedMessages(lang: LangFn) {
|
||||
return (
|
||||
<div className="NoMessages">
|
||||
<div className="wrapper">
|
||||
<i className="icon icon-cloud-download" />
|
||||
<h3 className="title">{lang('Conversation.CloudStorageInfo.Title')}</h3>
|
||||
<ul className="description">
|
||||
<li>{lang('Conversation.ClousStorageInfo.Description1')}</li>
|
||||
<li>{lang('Conversation.ClousStorageInfo.Description2')}</li>
|
||||
<li>{lang('Conversation.ClousStorageInfo.Description3')}</li>
|
||||
<li>{lang('Conversation.ClousStorageInfo.Description4')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderGroup(lang: LangFn) {
|
||||
return (
|
||||
<div className="NoMessages">
|
||||
<div className="wrapper" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<h3 className="title">{lang('EmptyGroupInfo.Title')}</h3>
|
||||
<p className="description">{lang('EmptyGroupInfo.Subtitle')}</p>
|
||||
<ul className="list-checkmarks">
|
||||
<li>{lang('EmptyGroupInfo.Line1')}</li>
|
||||
<li>{lang('EmptyGroupInfo.Line2')}</li>
|
||||
<li>{lang('EmptyGroupInfo.Line3')}</li>
|
||||
<li>{lang('EmptyGroupInfo.Line4')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default memo(NoMessages);
|
@ -609,7 +609,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
)}
|
||||
{animatedEmoji && (
|
||||
<AnimatedEmoji
|
||||
isInline
|
||||
size="small"
|
||||
sticker={animatedEmoji}
|
||||
observeIntersection={observeIntersectionForMedia}
|
||||
lastSyncTime={lastSyncTime}
|
||||
|
@ -38,7 +38,11 @@ const Tab: FC<OwnProps> = ({
|
||||
|
||||
const tab = tabRef.current!;
|
||||
const indicator = tab.querySelector('i')!;
|
||||
const currentIndicator = tab.parentElement!.children[previousActiveTab].querySelector('i')!;
|
||||
const prevTab = tab.parentElement!.children[previousActiveTab];
|
||||
if (!prevTab) {
|
||||
return;
|
||||
}
|
||||
const currentIndicator = prevTab.querySelector('i')!;
|
||||
|
||||
currentIndicator.classList.remove('animate');
|
||||
indicator.classList.remove('animate');
|
||||
|
@ -62,6 +62,9 @@ export const INITIAL_STATE: GlobalState = {
|
||||
favorite: {
|
||||
stickers: [],
|
||||
},
|
||||
greeting: {
|
||||
stickers: [],
|
||||
},
|
||||
featured: {
|
||||
setIds: [],
|
||||
},
|
||||
|
@ -198,6 +198,10 @@ export type GlobalState = {
|
||||
hash?: number;
|
||||
stickers: ApiSticker[];
|
||||
};
|
||||
greeting: {
|
||||
hash?: number;
|
||||
stickers: ApiSticker[];
|
||||
};
|
||||
featured: {
|
||||
hash?: number;
|
||||
setIds?: string[];
|
||||
@ -486,7 +490,7 @@ export type ActionTypes = (
|
||||
'loadStickerSets' | 'loadAddedStickers' | 'loadRecentStickers' | 'loadFavoriteStickers' | 'loadFeaturedStickers' |
|
||||
'loadStickers' | 'setStickerSearchQuery' | 'loadSavedGifs' | 'setGifSearchQuery' | 'searchMoreGifs' |
|
||||
'faveSticker' | 'unfaveSticker' | 'toggleStickerSet' | 'loadAnimatedEmojis' |
|
||||
'loadStickersForEmoji' | 'clearStickersForEmoji' | 'loadEmojiKeywords' |
|
||||
'loadStickersForEmoji' | 'clearStickersForEmoji' | 'loadEmojiKeywords' | 'loadGreetingStickers' |
|
||||
// bots
|
||||
'clickInlineButton' | 'sendBotCommand' | 'loadTopInlineBots' | 'queryInlineBot' | 'sendInlineBotResult' |
|
||||
'resetInlineBot' | 'restartBot' |
|
||||
|
@ -3,7 +3,7 @@ import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
|
||||
import { GlobalState } from '../../../global/types';
|
||||
import {
|
||||
ApiPrivacyKey, PrivacyVisibility, ProfileEditProgress, IInputPrivacyRules, IInputPrivacyContact,
|
||||
UPLOADING_WALLPAPER_SLUG, LangCode,
|
||||
UPLOADING_WALLPAPER_SLUG,
|
||||
} from '../../../types';
|
||||
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
|
@ -52,6 +52,31 @@ addReducer('loadFavoriteStickers', (global) => {
|
||||
void loadFavoriteStickers(hash);
|
||||
});
|
||||
|
||||
addReducer('loadGreetingStickers', (global) => {
|
||||
const { hash } = global.stickers.greeting || {};
|
||||
|
||||
(async () => {
|
||||
const greeting = await callApi('fetchStickersForEmoji', { emoji: '👋⭐️', hash });
|
||||
|
||||
if (!greeting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newGlobal = getGlobal();
|
||||
|
||||
setGlobal({
|
||||
...newGlobal,
|
||||
stickers: {
|
||||
...newGlobal.stickers,
|
||||
greeting: {
|
||||
hash: greeting.hash,
|
||||
stickers: greeting.stickers.filter((sticker) => sticker.emoji === '👋'),
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
||||
});
|
||||
|
||||
addReducer('loadFeaturedStickers', (global) => {
|
||||
const { hash } = global.stickers.featured || {};
|
||||
void loadFeaturedStickers(hash);
|
||||
|
@ -163,8 +163,11 @@ export enum SettingsScreens {
|
||||
Folders,
|
||||
FoldersCreateFolder,
|
||||
FoldersEditFolder,
|
||||
FoldersEditFolderFromChatList,
|
||||
FoldersIncludedChats,
|
||||
FoldersIncludedChatsFromChatList,
|
||||
FoldersExcludedChats,
|
||||
FoldersExcludedChatsFromChatList,
|
||||
TwoFaDisabled,
|
||||
TwoFaNewPassword,
|
||||
TwoFaNewPasswordConfirm,
|
||||
|
Loading…
Reference in New Issue
Block a user