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) {
|
if (action instanceof GramJs.MessageActionChatCreate) {
|
||||||
text = 'Notification.CreatedChatWithTitle';
|
text = 'Notification.CreatedChatWithTitle';
|
||||||
translationValues.push('%action_origin%', action.title);
|
translationValues.push('%action_origin%', action.title);
|
||||||
|
type = 'chatCreate';
|
||||||
} else if (action instanceof GramJs.MessageActionChatEditTitle) {
|
} else if (action instanceof GramJs.MessageActionChatEditTitle) {
|
||||||
if (isChannelPost) {
|
if (isChannelPost) {
|
||||||
text = 'Channel.MessageTitleUpdated';
|
text = 'Channel.MessageTitleUpdated';
|
||||||
@ -656,6 +657,7 @@ function buildAction(
|
|||||||
} else if (action instanceof GramJs.MessageActionContactSignUp) {
|
} else if (action instanceof GramJs.MessageActionContactSignUp) {
|
||||||
text = 'Notification.Joined';
|
text = 'Notification.Joined';
|
||||||
translationValues.push('%action_origin%');
|
translationValues.push('%action_origin%');
|
||||||
|
type = 'contactSignUp';
|
||||||
} else if (action instanceof GramJs.MessageActionPaymentSent) {
|
} else if (action instanceof GramJs.MessageActionPaymentSent) {
|
||||||
const currencySign = getCurrencySign(action.currency);
|
const currencySign = getCurrencySign(action.currency);
|
||||||
const amount = (Number(action.totalAmount) / 100).toFixed(2);
|
const amount = (Number(action.totalAmount) / 100).toFixed(2);
|
||||||
|
|||||||
@ -147,7 +147,7 @@ export interface ApiAction {
|
|||||||
text: string;
|
text: string;
|
||||||
targetUserIds?: number[];
|
targetUserIds?: number[];
|
||||||
targetChatId?: number;
|
targetChatId?: number;
|
||||||
type: 'historyClear' | 'other';
|
type: 'historyClear' | 'contactSignUp' | 'chatCreate' | 'other';
|
||||||
photo?: ApiPhoto;
|
photo?: ApiPhoto;
|
||||||
translationValues: string[];
|
translationValues: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,7 @@ import React, {
|
|||||||
|
|
||||||
import { ApiMediaFormat, ApiSticker } from '../../api/types';
|
import { ApiMediaFormat, ApiSticker } from '../../api/types';
|
||||||
|
|
||||||
import { STICKER_SIZE_TWO_FA } from '../../config';
|
import { LIKE_STICKER_ID } from './helpers/mediaDimensions';
|
||||||
import { getStickerDimensions, LIKE_STICKER_ID } from './helpers/mediaDimensions';
|
|
||||||
import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
import useTransitionForMedia from '../../hooks/useTransitionForMedia';
|
import useTransitionForMedia from '../../hooks/useTransitionForMedia';
|
||||||
@ -18,16 +17,24 @@ import './AnimatedEmoji.scss';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
sticker: ApiSticker;
|
sticker: ApiSticker;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
isInline?: boolean;
|
size?: 'large' | 'medium' | 'small';
|
||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
forceLoadPreview?: boolean;
|
forceLoadPreview?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const QUALITY = 1;
|
const QUALITY = 1;
|
||||||
const RESIZE_FACTOR = 0.5;
|
const WIDTH = {
|
||||||
|
large: 160,
|
||||||
|
medium: 128,
|
||||||
|
small: 104,
|
||||||
|
};
|
||||||
|
|
||||||
const AnimatedEmoji: FC<OwnProps> = ({
|
const AnimatedEmoji: FC<OwnProps> = ({
|
||||||
sticker, isInline = false, observeIntersection, lastSyncTime, forceLoadPreview,
|
sticker,
|
||||||
|
size = 'medium',
|
||||||
|
observeIntersection,
|
||||||
|
lastSyncTime,
|
||||||
|
forceLoadPreview,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@ -54,13 +61,7 @@ const AnimatedEmoji: FC<OwnProps> = ({
|
|||||||
setPlayKey(String(Math.random()));
|
setPlayKey(String(Math.random()));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
let width: number;
|
const width = WIDTH[size];
|
||||||
if (isInline) {
|
|
||||||
width = getStickerDimensions(sticker).width * RESIZE_FACTOR;
|
|
||||||
} else {
|
|
||||||
width = STICKER_SIZE_TWO_FA;
|
|
||||||
}
|
|
||||||
|
|
||||||
const style = `width: ${width}px; height: ${width}px;`;
|
const style = `width: ${width}px; height: ${width}px;`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -20,6 +20,12 @@
|
|||||||
margin: 0 0.5rem;
|
margin: 0 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
width: 10rem;
|
||||||
|
height: 10rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.AnimatedSticker, img {
|
.AnimatedSticker, img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@ -34,7 +34,7 @@ const ArchivedChats: FC<OwnProps> = ({ isActive, onReset, onContentChange }) =>
|
|||||||
</Button>
|
</Button>
|
||||||
<h3>{lang('ArchivedChats')}</h3>
|
<h3>{lang('ArchivedChats')}</h3>
|
||||||
</div>
|
</div>
|
||||||
<ChatList folderType="archived" noChatsText="Archive is empty." isActive={isActive} />
|
<ChatList folderType="archived" isActive={isActive} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { LeftColumnContent, SettingsScreens } from '../../types';
|
|||||||
import { LAYERS_ANIMATION_NAME } from '../../util/environment';
|
import { LAYERS_ANIMATION_NAME } from '../../util/environment';
|
||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
import { pick } from '../../util/iteratees';
|
import { pick } from '../../util/iteratees';
|
||||||
|
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
||||||
|
|
||||||
import Transition from '../ui/Transition';
|
import Transition from '../ui/Transition';
|
||||||
import LeftMain from './main/LeftMain';
|
import LeftMain from './main/LeftMain';
|
||||||
@ -59,6 +60,7 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
const [content, setContent] = useState<LeftColumnContent>(LeftColumnContent.ChatList);
|
const [content, setContent] = useState<LeftColumnContent>(LeftColumnContent.ChatList);
|
||||||
const [settingsScreen, setSettingsScreen] = useState(SettingsScreens.Main);
|
const [settingsScreen, setSettingsScreen] = useState(SettingsScreens.Main);
|
||||||
const [contactsFilter, setContactsFilter] = useState<string>('');
|
const [contactsFilter, setContactsFilter] = useState<string>('');
|
||||||
|
const [foldersState, foldersDispatch] = useFoldersReducer();
|
||||||
|
|
||||||
// Used to reset child components in background.
|
// Used to reset child components in background.
|
||||||
const [lastResetTime, setLastResetTime] = useState<number>(0);
|
const [lastResetTime, setLastResetTime] = useState<number>(0);
|
||||||
@ -193,6 +195,16 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
case SettingsScreens.FoldersEditFolder:
|
case SettingsScreens.FoldersEditFolder:
|
||||||
setSettingsScreen(SettingsScreens.Folders);
|
setSettingsScreen(SettingsScreens.Folders);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case SettingsScreens.FoldersIncludedChatsFromChatList:
|
||||||
|
case SettingsScreens.FoldersExcludedChatsFromChatList:
|
||||||
|
setSettingsScreen(SettingsScreens.FoldersEditFolderFromChatList);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case SettingsScreens.FoldersEditFolderFromChatList:
|
||||||
|
setContent(LeftColumnContent.ChatList);
|
||||||
|
setSettingsScreen(SettingsScreens.Main);
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -274,6 +286,8 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
<Settings
|
<Settings
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
currentScreen={settingsScreen}
|
currentScreen={settingsScreen}
|
||||||
|
foldersState={foldersState}
|
||||||
|
foldersDispatch={foldersDispatch}
|
||||||
onScreenSelect={handleSettingsScreenSelect}
|
onScreenSelect={handleSettingsScreenSelect}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
shouldSkipTransition={shouldSkipHistoryAnimations}
|
shouldSkipTransition={shouldSkipHistoryAnimations}
|
||||||
@ -307,8 +321,10 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
searchDate={searchDate}
|
searchDate={searchDate}
|
||||||
contactsFilter={contactsFilter}
|
contactsFilter={contactsFilter}
|
||||||
|
foldersDispatch={foldersDispatch}
|
||||||
onContentChange={setContent}
|
onContentChange={setContent}
|
||||||
onSearchQuery={handleSearchQuery}
|
onSearchQuery={handleSearchQuery}
|
||||||
|
onScreenSelect={handleSettingsScreenSelect}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
shouldSkipTransition={shouldSkipHistoryAnimations}
|
shouldSkipTransition={shouldSkipHistoryAnimations}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import { withGlobal } from '../../../lib/teact/teactn';
|
|||||||
|
|
||||||
import { ApiChat, ApiChatFolder, ApiUser } from '../../../api/types';
|
import { ApiChat, ApiChatFolder, ApiUser } from '../../../api/types';
|
||||||
import { GlobalActions } from '../../../global/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 { IS_TOUCH_ENV } from '../../../util/environment';
|
||||||
import { buildCollectionByKey, pick } from '../../../util/iteratees';
|
import { buildCollectionByKey, pick } from '../../../util/iteratees';
|
||||||
@ -23,6 +24,11 @@ import Transition from '../../ui/Transition';
|
|||||||
import TabList from '../../ui/TabList';
|
import TabList from '../../ui/TabList';
|
||||||
import ChatList from './ChatList';
|
import ChatList from './ChatList';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
onScreenSelect: (screen: SettingsScreens) => void;
|
||||||
|
foldersDispatch: FolderEditDispatch;
|
||||||
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chatsById: Record<number, ApiChat>;
|
chatsById: Record<number, ApiChat>;
|
||||||
usersById: Record<number, ApiUser>;
|
usersById: Record<number, ApiUser>;
|
||||||
@ -40,7 +46,7 @@ type DispatchProps = Pick<GlobalActions, 'loadChatFolders' | 'setActiveChatFolde
|
|||||||
const INFO_THROTTLE = 3000;
|
const INFO_THROTTLE = 3000;
|
||||||
const SAVED_MESSAGES_HOTKEY = '0';
|
const SAVED_MESSAGES_HOTKEY = '0';
|
||||||
|
|
||||||
const ChatFolders: FC<StateProps & DispatchProps> = ({
|
const ChatFolders: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
chatsById,
|
chatsById,
|
||||||
usersById,
|
usersById,
|
||||||
chatFoldersById,
|
chatFoldersById,
|
||||||
@ -50,6 +56,8 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
|||||||
activeChatFolder,
|
activeChatFolder,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
|
foldersDispatch,
|
||||||
|
onScreenSelect,
|
||||||
loadChatFolders,
|
loadChatFolders,
|
||||||
setActiveChatFolder,
|
setActiveChatFolder,
|
||||||
openChat,
|
openChat,
|
||||||
@ -182,15 +190,23 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
|||||||
.find(({ title }) => title === folderTabs![activeChatFolder].title);
|
.find(({ title }) => title === folderTabs![activeChatFolder].title);
|
||||||
|
|
||||||
if (!activeFolder || activeChatFolder === 0) {
|
if (!activeFolder || activeChatFolder === 0) {
|
||||||
return <ChatList folderType="all" isActive={isActive} />;
|
return (
|
||||||
|
<ChatList
|
||||||
|
folderType="all"
|
||||||
|
isActive={isActive}
|
||||||
|
foldersDispatch={foldersDispatch}
|
||||||
|
onScreenSelect={onScreenSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatList
|
<ChatList
|
||||||
folderType="folder"
|
folderType="folder"
|
||||||
folderId={activeFolder.id}
|
folderId={activeFolder.id}
|
||||||
noChatsText={lang('FilterNoChatsToDisplay')}
|
|
||||||
isActive={isActive}
|
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 => {
|
(global): StateProps => {
|
||||||
const {
|
const {
|
||||||
chats: { byId: chatsById },
|
chats: { byId: chatsById },
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import { GlobalActions } from '../../../global/types';
|
|||||||
import {
|
import {
|
||||||
ApiChat, ApiChatFolder, ApiUser,
|
ApiChat, ApiChatFolder, ApiUser,
|
||||||
} from '../../../api/types';
|
} 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 { ALL_CHATS_PRELOAD_DISABLED, CHAT_HEIGHT_PX, CHAT_LIST_SLICE } from '../../../config';
|
||||||
import { IS_ANDROID, IS_MAC_OS, IS_PWA } from '../../../util/environment';
|
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 InfiniteScroll from '../../ui/InfiniteScroll';
|
||||||
import Loading from '../../ui/Loading';
|
import Loading from '../../ui/Loading';
|
||||||
import Chat from './Chat';
|
import Chat from './Chat';
|
||||||
|
import EmptyFolder from './EmptyFolder';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
folderType: 'all' | 'archived' | 'folder';
|
folderType: 'all' | 'archived' | 'folder';
|
||||||
folderId?: number;
|
folderId?: number;
|
||||||
noChatsText?: string;
|
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
onScreenSelect?: (screen: SettingsScreens) => void;
|
||||||
|
foldersDispatch?: FolderEditDispatch;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -52,7 +55,6 @@ enum FolderTypeToListType {
|
|||||||
const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
folderType,
|
folderType,
|
||||||
folderId,
|
folderId,
|
||||||
noChatsText = 'Chat list is empty.',
|
|
||||||
isActive,
|
isActive,
|
||||||
chatFolder,
|
chatFolder,
|
||||||
chatsById,
|
chatsById,
|
||||||
@ -60,8 +62,10 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
listIds,
|
listIds,
|
||||||
orderedPinnedIds,
|
orderedPinnedIds,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
|
foldersDispatch,
|
||||||
notifySettings,
|
notifySettings,
|
||||||
notifyExceptions,
|
notifyExceptions,
|
||||||
|
onScreenSelect,
|
||||||
loadMoreChats,
|
loadMoreChats,
|
||||||
preloadTopChatMessages,
|
preloadTopChatMessages,
|
||||||
openChat,
|
openChat,
|
||||||
@ -205,7 +209,14 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
{viewportIds && viewportIds.length && chatArrays ? (
|
{viewportIds && viewportIds.length && chatArrays ? (
|
||||||
renderChats()
|
renderChats()
|
||||||
) : viewportIds && !viewportIds.length ? (
|
) : viewportIds && !viewportIds.length ? (
|
||||||
<div className="no-results">{noChatsText}</div>
|
(
|
||||||
|
<EmptyFolder
|
||||||
|
folderId={folderId}
|
||||||
|
folderType={folderType}
|
||||||
|
foldersDispatch={foldersDispatch}
|
||||||
|
onScreenSelect={onScreenSelect}
|
||||||
|
/>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<Loading key="loading" />
|
<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 { withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { GlobalState } from '../../../global/types';
|
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 { IS_TOUCH_ENV } from '../../../util/environment';
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
@ -32,8 +33,10 @@ type OwnProps = {
|
|||||||
searchDate?: number;
|
searchDate?: number;
|
||||||
contactsFilter: string;
|
contactsFilter: string;
|
||||||
shouldSkipTransition?: boolean;
|
shouldSkipTransition?: boolean;
|
||||||
|
foldersDispatch: FolderEditDispatch;
|
||||||
onSearchQuery: (query: string) => void;
|
onSearchQuery: (query: string) => void;
|
||||||
onContentChange: (content: LeftColumnContent) => void;
|
onContentChange: (content: LeftColumnContent) => void;
|
||||||
|
onScreenSelect: (screen: SettingsScreens) => void;
|
||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,8 +54,10 @@ const LeftMain: FC<OwnProps & StateProps> = ({
|
|||||||
searchDate,
|
searchDate,
|
||||||
contactsFilter,
|
contactsFilter,
|
||||||
shouldSkipTransition,
|
shouldSkipTransition,
|
||||||
|
foldersDispatch,
|
||||||
onSearchQuery,
|
onSearchQuery,
|
||||||
onContentChange,
|
onContentChange,
|
||||||
|
onScreenSelect,
|
||||||
onReset,
|
onReset,
|
||||||
connectionState,
|
connectionState,
|
||||||
}) => {
|
}) => {
|
||||||
@ -158,7 +163,7 @@ const LeftMain: FC<OwnProps & StateProps> = ({
|
|||||||
{(isActive) => {
|
{(isActive) => {
|
||||||
switch (content) {
|
switch (content) {
|
||||||
case LeftColumnContent.ChatList:
|
case LeftColumnContent.ChatList:
|
||||||
return <ChatFolders />;
|
return <ChatFolders onScreenSelect={onScreenSelect} foldersDispatch={foldersDispatch} />;
|
||||||
case LeftColumnContent.GlobalSearch:
|
case LeftColumnContent.GlobalSearch:
|
||||||
return (
|
return (
|
||||||
<LeftSearch
|
<LeftSearch
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import React, { FC, memo, useCallback } from '../../../lib/teact/teact';
|
import React, { FC, memo, useCallback } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import { SettingsScreens } from '../../../types';
|
import { SettingsScreens } from '../../../types';
|
||||||
|
import { FolderEditDispatch, FoldersState } from '../../../hooks/reducers/useFoldersReducer';
|
||||||
|
|
||||||
import { LAYERS_ANIMATION_NAME } from '../../../util/environment';
|
import { LAYERS_ANIMATION_NAME } from '../../../util/environment';
|
||||||
import useFoldersReducer from '../../../hooks/reducers/useFoldersReducer';
|
|
||||||
import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer';
|
import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer';
|
||||||
|
|
||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
@ -51,8 +51,11 @@ const FOLDERS_SCREENS = [
|
|||||||
SettingsScreens.Folders,
|
SettingsScreens.Folders,
|
||||||
SettingsScreens.FoldersCreateFolder,
|
SettingsScreens.FoldersCreateFolder,
|
||||||
SettingsScreens.FoldersEditFolder,
|
SettingsScreens.FoldersEditFolder,
|
||||||
|
SettingsScreens.FoldersEditFolderFromChatList,
|
||||||
SettingsScreens.FoldersIncludedChats,
|
SettingsScreens.FoldersIncludedChats,
|
||||||
|
SettingsScreens.FoldersIncludedChatsFromChatList,
|
||||||
SettingsScreens.FoldersExcludedChats,
|
SettingsScreens.FoldersExcludedChats,
|
||||||
|
SettingsScreens.FoldersExcludedChatsFromChatList,
|
||||||
];
|
];
|
||||||
|
|
||||||
const PRIVACY_SCREENS = [
|
const PRIVACY_SCREENS = [
|
||||||
@ -88,6 +91,8 @@ const PRIVACY_GROUP_CHATS_SCREENS = [
|
|||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
currentScreen: SettingsScreens;
|
currentScreen: SettingsScreens;
|
||||||
|
foldersState: FoldersState;
|
||||||
|
foldersDispatch: FolderEditDispatch;
|
||||||
onScreenSelect: (screen: SettingsScreens) => void;
|
onScreenSelect: (screen: SettingsScreens) => void;
|
||||||
shouldSkipTransition?: boolean;
|
shouldSkipTransition?: boolean;
|
||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
@ -96,17 +101,19 @@ export type OwnProps = {
|
|||||||
const Settings: FC<OwnProps> = ({
|
const Settings: FC<OwnProps> = ({
|
||||||
isActive,
|
isActive,
|
||||||
currentScreen,
|
currentScreen,
|
||||||
|
foldersState,
|
||||||
|
foldersDispatch,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
onReset,
|
onReset,
|
||||||
shouldSkipTransition,
|
shouldSkipTransition,
|
||||||
}) => {
|
}) => {
|
||||||
const [foldersState, foldersDispatch] = useFoldersReducer();
|
|
||||||
const [twoFaState, twoFaDispatch] = useTwoFaReducer();
|
const [twoFaState, twoFaDispatch] = useTwoFaReducer();
|
||||||
|
|
||||||
const handleReset = useCallback(() => {
|
const handleReset = useCallback(() => {
|
||||||
if (
|
if (
|
||||||
currentScreen === SettingsScreens.FoldersCreateFolder
|
currentScreen === SettingsScreens.FoldersCreateFolder
|
||||||
|| currentScreen === SettingsScreens.FoldersEditFolder
|
|| currentScreen === SettingsScreens.FoldersEditFolder
|
||||||
|
|| currentScreen === SettingsScreens.FoldersEditFolderFromChatList
|
||||||
) {
|
) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
foldersDispatch({ type: 'reset' });
|
foldersDispatch({ type: 'reset' });
|
||||||
@ -270,8 +277,11 @@ const Settings: FC<OwnProps> = ({
|
|||||||
case SettingsScreens.Folders:
|
case SettingsScreens.Folders:
|
||||||
case SettingsScreens.FoldersCreateFolder:
|
case SettingsScreens.FoldersCreateFolder:
|
||||||
case SettingsScreens.FoldersEditFolder:
|
case SettingsScreens.FoldersEditFolder:
|
||||||
|
case SettingsScreens.FoldersEditFolderFromChatList:
|
||||||
case SettingsScreens.FoldersIncludedChats:
|
case SettingsScreens.FoldersIncludedChats:
|
||||||
|
case SettingsScreens.FoldersIncludedChatsFromChatList:
|
||||||
case SettingsScreens.FoldersExcludedChats:
|
case SettingsScreens.FoldersExcludedChats:
|
||||||
|
case SettingsScreens.FoldersExcludedChatsFromChatList:
|
||||||
return (
|
return (
|
||||||
<SettingsFolders
|
<SettingsFolders
|
||||||
currentScreen={currentScreen}
|
currentScreen={currentScreen}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, useCallback, useMemo, memo, useState,
|
FC, memo, useCallback, useMemo, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../lib/teact/teactn';
|
import { withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
@ -156,6 +156,7 @@ const SettingsHeader: FC<OwnProps & DispatchProps> = ({
|
|||||||
case SettingsScreens.FoldersCreateFolder:
|
case SettingsScreens.FoldersCreateFolder:
|
||||||
return <h3>{lang('FilterNew')}</h3>;
|
return <h3>{lang('FilterNew')}</h3>;
|
||||||
case SettingsScreens.FoldersEditFolder:
|
case SettingsScreens.FoldersEditFolder:
|
||||||
|
case SettingsScreens.FoldersEditFolderFromChatList:
|
||||||
return (
|
return (
|
||||||
<div className="settings-main-header">
|
<div className="settings-main-header">
|
||||||
<h3>{lang('FilterEdit')}</h3>
|
<h3>{lang('FilterEdit')}</h3>
|
||||||
@ -174,14 +175,17 @@ const SettingsHeader: FC<OwnProps & DispatchProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case SettingsScreens.FoldersIncludedChats:
|
case SettingsScreens.FoldersIncludedChats:
|
||||||
|
case SettingsScreens.FoldersIncludedChatsFromChatList:
|
||||||
case SettingsScreens.FoldersExcludedChats:
|
case SettingsScreens.FoldersExcludedChats:
|
||||||
|
case SettingsScreens.FoldersExcludedChatsFromChatList:
|
||||||
return (
|
return (
|
||||||
<div className="settings-main-header">
|
<div className="settings-main-header">
|
||||||
{currentScreen === SettingsScreens.FoldersIncludedChats ? (
|
{(currentScreen === SettingsScreens.FoldersIncludedChats
|
||||||
<h3>{lang('FilterInclude')}</h3>
|
|| currentScreen === SettingsScreens.FoldersIncludedChatsFromChatList) ? (
|
||||||
) : (
|
<h3>{lang('FilterInclude')}</h3>
|
||||||
<h3>{lang('FilterExclude')}</h3>
|
) : (
|
||||||
)}
|
<h3>{lang('FilterExclude')}</h3>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
round
|
round
|
||||||
|
|||||||
@ -36,6 +36,7 @@ const SettingsFolders: FC<OwnProps> = ({
|
|||||||
if (
|
if (
|
||||||
currentScreen === SettingsScreens.FoldersCreateFolder
|
currentScreen === SettingsScreens.FoldersCreateFolder
|
||||||
|| currentScreen === SettingsScreens.FoldersEditFolder
|
|| currentScreen === SettingsScreens.FoldersEditFolder
|
||||||
|
|| currentScreen === SettingsScreens.FoldersEditFolderFromChatList
|
||||||
) {
|
) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
dispatch({ type: 'reset' });
|
dispatch({ type: 'reset' });
|
||||||
@ -72,13 +73,17 @@ const SettingsFolders: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const handleAddIncludedChats = useCallback(() => {
|
const handleAddIncludedChats = useCallback(() => {
|
||||||
dispatch({ type: 'editIncludeFilters' });
|
dispatch({ type: 'editIncludeFilters' });
|
||||||
onScreenSelect(SettingsScreens.FoldersIncludedChats);
|
onScreenSelect(currentScreen === SettingsScreens.FoldersEditFolderFromChatList
|
||||||
}, [dispatch, onScreenSelect]);
|
? SettingsScreens.FoldersIncludedChatsFromChatList
|
||||||
|
: SettingsScreens.FoldersIncludedChats);
|
||||||
|
}, [currentScreen, dispatch, onScreenSelect]);
|
||||||
|
|
||||||
const handleAddExcludedChats = useCallback(() => {
|
const handleAddExcludedChats = useCallback(() => {
|
||||||
dispatch({ type: 'editExcludeFilters' });
|
dispatch({ type: 'editExcludeFilters' });
|
||||||
onScreenSelect(SettingsScreens.FoldersExcludedChats);
|
onScreenSelect(currentScreen === SettingsScreens.FoldersEditFolderFromChatList
|
||||||
}, [dispatch, onScreenSelect]);
|
? SettingsScreens.FoldersExcludedChatsFromChatList
|
||||||
|
: SettingsScreens.FoldersExcludedChats);
|
||||||
|
}, [currentScreen, dispatch, onScreenSelect]);
|
||||||
|
|
||||||
switch (currentScreen) {
|
switch (currentScreen) {
|
||||||
case SettingsScreens.Folders:
|
case SettingsScreens.Folders:
|
||||||
@ -98,6 +103,7 @@ const SettingsFolders: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
case SettingsScreens.FoldersCreateFolder:
|
case SettingsScreens.FoldersCreateFolder:
|
||||||
case SettingsScreens.FoldersEditFolder:
|
case SettingsScreens.FoldersEditFolder:
|
||||||
|
case SettingsScreens.FoldersEditFolderFromChatList:
|
||||||
return (
|
return (
|
||||||
<SettingsFoldersEdit
|
<SettingsFoldersEdit
|
||||||
state={state}
|
state={state}
|
||||||
@ -114,6 +120,7 @@ const SettingsFolders: FC<OwnProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case SettingsScreens.FoldersIncludedChats:
|
case SettingsScreens.FoldersIncludedChats:
|
||||||
|
case SettingsScreens.FoldersIncludedChatsFromChatList:
|
||||||
return (
|
return (
|
||||||
<SettingsFoldersChatFilters
|
<SettingsFoldersChatFilters
|
||||||
mode="included"
|
mode="included"
|
||||||
@ -125,6 +132,7 @@ const SettingsFolders: FC<OwnProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case SettingsScreens.FoldersExcludedChats:
|
case SettingsScreens.FoldersExcludedChats:
|
||||||
|
case SettingsScreens.FoldersExcludedChatsFromChatList:
|
||||||
return (
|
return (
|
||||||
<SettingsFoldersChatFilters
|
<SettingsFoldersChatFilters
|
||||||
mode="excluded"
|
mode="excluded"
|
||||||
|
|||||||
@ -52,7 +52,7 @@ const SUBMIT_TIMEOUT = 500;
|
|||||||
const INITIAL_CHATS_LIMIT = 5;
|
const INITIAL_CHATS_LIMIT = 5;
|
||||||
|
|
||||||
const ERROR_NO_TITLE = 'Please provide a title for this folder.';
|
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> = ({
|
const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
state,
|
state,
|
||||||
@ -265,7 +265,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
<div className="settings-item no-border pt-3">
|
<div className="settings-item no-border pt-3">
|
||||||
{state.error && state.error === ERROR_NO_CHATS && (
|
{state.error && state.error === ERROR_NO_CHATS && (
|
||||||
<p className="settings-item-description color-danger mb-2" dir={lang.isRtl ? 'rtl' : undefined}>
|
<p className="settings-item-description color-danger mb-2" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
{state.error}
|
{lang(state.error)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,7 @@ const SettingsTwoFaCongratulations: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content two-fa custom-scroll">
|
<div className="settings-content two-fa custom-scroll">
|
||||||
<div className="settings-content-header">
|
<div className="settings-content-header">
|
||||||
<AnimatedEmoji sticker={animatedEmoji} />
|
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
||||||
|
|
||||||
<p className="settings-item-description mb-3" dir="auto">
|
<p className="settings-item-description mb-3" dir="auto">
|
||||||
{lang('TwoStepVerificationPasswordSetInfo')}
|
{lang('TwoStepVerificationPasswordSetInfo')}
|
||||||
|
|||||||
@ -80,7 +80,7 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content two-fa custom-scroll">
|
<div className="settings-content two-fa custom-scroll">
|
||||||
<div className="settings-content-header">
|
<div className="settings-content-header">
|
||||||
<AnimatedEmoji sticker={animatedEmoji} />
|
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="settings-item pt-0 no-border">
|
<div className="settings-item pt-0 no-border">
|
||||||
|
|||||||
@ -32,7 +32,7 @@ const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content two-fa custom-scroll">
|
<div className="settings-content two-fa custom-scroll">
|
||||||
<div className="settings-content-header">
|
<div className="settings-content-header">
|
||||||
<AnimatedEmoji sticker={animatedEmoji} />
|
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
||||||
|
|
||||||
<p className="settings-item-description mb-3" dir="auto">
|
<p className="settings-item-description mb-3" dir="auto">
|
||||||
{renderText(lang('EnabledPasswordText'), ['br'])}
|
{renderText(lang('EnabledPasswordText'), ['br'])}
|
||||||
|
|||||||
@ -101,7 +101,7 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content two-fa custom-scroll">
|
<div className="settings-content two-fa custom-scroll">
|
||||||
<div className="settings-content-header">
|
<div className="settings-content-header">
|
||||||
<AnimatedEmoji sticker={animatedEmoji} />
|
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="settings-item pt-0 no-border">
|
<div className="settings-item pt-0 no-border">
|
||||||
|
|||||||
@ -32,7 +32,7 @@ const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content two-fa custom-scroll">
|
<div className="settings-content two-fa custom-scroll">
|
||||||
<div className="settings-content-header">
|
<div className="settings-content-header">
|
||||||
<AnimatedEmoji sticker={animatedEmoji} />
|
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
||||||
|
|
||||||
<p className="settings-item-description mb-3" dir="auto">
|
<p className="settings-item-description mb-3" dir="auto">
|
||||||
{lang('SetAdditionalPasswordInfo')}
|
{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';
|
} from '../../lib/teact/teact';
|
||||||
import { getGlobal, withGlobal } from '../../lib/teact/teactn';
|
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 { GlobalActions, MessageListType } from '../../global/types';
|
||||||
import { LoadMoreDirection } from '../../types';
|
import { LoadMoreDirection } from '../../types';
|
||||||
|
|
||||||
@ -24,13 +26,13 @@ import {
|
|||||||
selectScheduledMessages,
|
selectScheduledMessages,
|
||||||
selectCurrentMessageIds,
|
selectCurrentMessageIds,
|
||||||
} from '../../modules/selectors';
|
} from '../../modules/selectors';
|
||||||
import { isChatChannel, isChatPrivate } from '../../modules/helpers';
|
import { isChatChannel, isChatGroup, isChatPrivate } from '../../modules/helpers';
|
||||||
import { orderBy, pick } from '../../util/iteratees';
|
import { orderBy, pick } from '../../util/iteratees';
|
||||||
import { fastRaf, debounce, onTickEnd } from '../../util/schedulers';
|
import { fastRaf, debounce, onTickEnd } from '../../util/schedulers';
|
||||||
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
|
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
|
||||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { groupMessages } from './helpers/groupMessages';
|
import { groupMessages, MessageDateGroup } from './helpers/groupMessages';
|
||||||
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
||||||
import useOnChange from '../../hooks/useOnChange';
|
import useOnChange from '../../hooks/useOnChange';
|
||||||
import useStickyDates from './hooks/useStickyDates';
|
import useStickyDates from './hooks/useStickyDates';
|
||||||
@ -43,6 +45,8 @@ import useWindowSize from '../../hooks/useWindowSize';
|
|||||||
|
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import MessageListContent from './MessageListContent';
|
import MessageListContent from './MessageListContent';
|
||||||
|
import ContactGreeting from './ContactGreeting';
|
||||||
|
import NoMessages from './NoMessages';
|
||||||
|
|
||||||
import './MessageList.scss';
|
import './MessageList.scss';
|
||||||
|
|
||||||
@ -60,7 +64,10 @@ type OwnProps = {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
isChatLoaded?: boolean;
|
isChatLoaded?: boolean;
|
||||||
isChannelChat?: boolean;
|
isChannelChat?: boolean;
|
||||||
|
isGroupChat?: boolean;
|
||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
|
isCreator?: boolean;
|
||||||
|
isBot?: boolean;
|
||||||
messageIds?: number[];
|
messageIds?: number[];
|
||||||
messagesById?: Record<number, ApiMessage>;
|
messagesById?: Record<number, ApiMessage>;
|
||||||
firstUnreadId?: number;
|
firstUnreadId?: number;
|
||||||
@ -100,9 +107,12 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
onNotchToggle,
|
onNotchToggle,
|
||||||
isChatLoaded,
|
isChatLoaded,
|
||||||
isChannelChat,
|
isChannelChat,
|
||||||
|
isGroupChat,
|
||||||
canPost,
|
canPost,
|
||||||
isReady,
|
isReady,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
|
isCreator,
|
||||||
|
isBot,
|
||||||
messageIds,
|
messageIds,
|
||||||
messagesById,
|
messagesById,
|
||||||
firstUnreadId,
|
firstUnreadId,
|
||||||
@ -424,6 +434,16 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const isPrivate = Boolean(chatId && isChatPrivate(chatId));
|
const isPrivate = Boolean(chatId && isChatPrivate(chatId));
|
||||||
const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf);
|
const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf);
|
||||||
const noAvatars = Boolean(!withUsers || isChannelChat);
|
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(
|
const className = buildClassName(
|
||||||
'MessageList custom-scroll',
|
'MessageList custom-scroll',
|
||||||
@ -451,8 +471,15 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
) : botDescription ? (
|
) : botDescription ? (
|
||||||
<div className="empty rich"><span>{renderText(lang(botDescription), ['br', 'emoji', 'links'])}</span></div>
|
<div className="empty rich"><span>{renderText(lang(botDescription), ['br', 'emoji', 'links'])}</span></div>
|
||||||
) : messageIds && !messageGroups ? (
|
) : shouldRenderGreeting ? (
|
||||||
<div className="empty"><span>{lang('NoMessages')}</span></div>
|
<ContactGreeting userId={chatId} />
|
||||||
|
) : messageIds && (!messageGroups || isGroupChatJustCreated) ? (
|
||||||
|
<NoMessages
|
||||||
|
chatId={chatId}
|
||||||
|
type={type}
|
||||||
|
isChatWithSelf={isChatWithSelf}
|
||||||
|
isGroupChatJustCreated={isGroupChatJustCreated}
|
||||||
|
/>
|
||||||
) : ((messageIds && messageGroups) || lastMessage) ? (
|
) : ((messageIds && messageGroups) || lastMessage) ? (
|
||||||
<MessageListContent
|
<MessageListContent
|
||||||
messageIds={messageIds || [lastMessage!.id]}
|
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>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId, threadId, type }): StateProps => {
|
(global, { chatId, threadId, type }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
@ -510,6 +547,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
&& !messageIds && !chat.unreadCount && !focusingId && lastMessage && !lastMessage.groupedId
|
&& !messageIds && !chat.unreadCount && !focusingId && lastMessage && !lastMessage.groupedId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const bot = selectChatBot(global, chatId);
|
||||||
let botDescription: string | undefined;
|
let botDescription: string | undefined;
|
||||||
if (selectIsChatBotNotStarted(global, chatId)) {
|
if (selectIsChatBotNotStarted(global, chatId)) {
|
||||||
const chatBot = selectChatBot(global, chatId)!;
|
const chatBot = selectChatBot(global, chatId)!;
|
||||||
@ -525,7 +563,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isRestricted,
|
isRestricted,
|
||||||
restrictionReason,
|
restrictionReason,
|
||||||
isChannelChat: isChatChannel(chat),
|
isChannelChat: isChatChannel(chat),
|
||||||
|
isGroupChat: isChatGroup(chat),
|
||||||
|
isCreator: chat.isCreator,
|
||||||
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
||||||
|
isBot: Boolean(bot),
|
||||||
messageIds,
|
messageIds,
|
||||||
messagesById,
|
messagesById,
|
||||||
firstUnreadId: selectFirstUnreadId(global, chatId, threadId),
|
firstUnreadId: selectFirstUnreadId(global, chatId, threadId),
|
||||||
|
|||||||
@ -330,7 +330,7 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
<>
|
<>
|
||||||
{renderBackButton()}
|
{renderBackButton()}
|
||||||
<h3>
|
<h3>
|
||||||
{isChatWithSelf ? lang('Reminders') : lang('messages', messagesCount)}
|
{isChatWithSelf ? lang('Reminders') : lang('messages', messagesCount, 'i')}
|
||||||
</h3>
|
</h3>
|
||||||
</>
|
</>
|
||||||
) : undefined
|
) : 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 && (
|
||||||
<AnimatedEmoji
|
<AnimatedEmoji
|
||||||
isInline
|
size="small"
|
||||||
sticker={animatedEmoji}
|
sticker={animatedEmoji}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForMedia}
|
||||||
lastSyncTime={lastSyncTime}
|
lastSyncTime={lastSyncTime}
|
||||||
|
|||||||
@ -38,7 +38,11 @@ const Tab: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const tab = tabRef.current!;
|
const tab = tabRef.current!;
|
||||||
const indicator = tab.querySelector('i')!;
|
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');
|
currentIndicator.classList.remove('animate');
|
||||||
indicator.classList.remove('animate');
|
indicator.classList.remove('animate');
|
||||||
|
|||||||
@ -62,6 +62,9 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
favorite: {
|
favorite: {
|
||||||
stickers: [],
|
stickers: [],
|
||||||
},
|
},
|
||||||
|
greeting: {
|
||||||
|
stickers: [],
|
||||||
|
},
|
||||||
featured: {
|
featured: {
|
||||||
setIds: [],
|
setIds: [],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -198,6 +198,10 @@ export type GlobalState = {
|
|||||||
hash?: number;
|
hash?: number;
|
||||||
stickers: ApiSticker[];
|
stickers: ApiSticker[];
|
||||||
};
|
};
|
||||||
|
greeting: {
|
||||||
|
hash?: number;
|
||||||
|
stickers: ApiSticker[];
|
||||||
|
};
|
||||||
featured: {
|
featured: {
|
||||||
hash?: number;
|
hash?: number;
|
||||||
setIds?: string[];
|
setIds?: string[];
|
||||||
@ -486,7 +490,7 @@ export type ActionTypes = (
|
|||||||
'loadStickerSets' | 'loadAddedStickers' | 'loadRecentStickers' | 'loadFavoriteStickers' | 'loadFeaturedStickers' |
|
'loadStickerSets' | 'loadAddedStickers' | 'loadRecentStickers' | 'loadFavoriteStickers' | 'loadFeaturedStickers' |
|
||||||
'loadStickers' | 'setStickerSearchQuery' | 'loadSavedGifs' | 'setGifSearchQuery' | 'searchMoreGifs' |
|
'loadStickers' | 'setStickerSearchQuery' | 'loadSavedGifs' | 'setGifSearchQuery' | 'searchMoreGifs' |
|
||||||
'faveSticker' | 'unfaveSticker' | 'toggleStickerSet' | 'loadAnimatedEmojis' |
|
'faveSticker' | 'unfaveSticker' | 'toggleStickerSet' | 'loadAnimatedEmojis' |
|
||||||
'loadStickersForEmoji' | 'clearStickersForEmoji' | 'loadEmojiKeywords' |
|
'loadStickersForEmoji' | 'clearStickersForEmoji' | 'loadEmojiKeywords' | 'loadGreetingStickers' |
|
||||||
// bots
|
// bots
|
||||||
'clickInlineButton' | 'sendBotCommand' | 'loadTopInlineBots' | 'queryInlineBot' | 'sendInlineBotResult' |
|
'clickInlineButton' | 'sendBotCommand' | 'loadTopInlineBots' | 'queryInlineBot' | 'sendInlineBotResult' |
|
||||||
'resetInlineBot' | 'restartBot' |
|
'resetInlineBot' | 'restartBot' |
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
|
|||||||
import { GlobalState } from '../../../global/types';
|
import { GlobalState } from '../../../global/types';
|
||||||
import {
|
import {
|
||||||
ApiPrivacyKey, PrivacyVisibility, ProfileEditProgress, IInputPrivacyRules, IInputPrivacyContact,
|
ApiPrivacyKey, PrivacyVisibility, ProfileEditProgress, IInputPrivacyRules, IInputPrivacyContact,
|
||||||
UPLOADING_WALLPAPER_SLUG, LangCode,
|
UPLOADING_WALLPAPER_SLUG,
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
|
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
|
|||||||
@ -52,6 +52,31 @@ addReducer('loadFavoriteStickers', (global) => {
|
|||||||
void loadFavoriteStickers(hash);
|
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) => {
|
addReducer('loadFeaturedStickers', (global) => {
|
||||||
const { hash } = global.stickers.featured || {};
|
const { hash } = global.stickers.featured || {};
|
||||||
void loadFeaturedStickers(hash);
|
void loadFeaturedStickers(hash);
|
||||||
|
|||||||
@ -163,8 +163,11 @@ export enum SettingsScreens {
|
|||||||
Folders,
|
Folders,
|
||||||
FoldersCreateFolder,
|
FoldersCreateFolder,
|
||||||
FoldersEditFolder,
|
FoldersEditFolder,
|
||||||
|
FoldersEditFolderFromChatList,
|
||||||
FoldersIncludedChats,
|
FoldersIncludedChats,
|
||||||
|
FoldersIncludedChatsFromChatList,
|
||||||
FoldersExcludedChats,
|
FoldersExcludedChats,
|
||||||
|
FoldersExcludedChatsFromChatList,
|
||||||
TwoFaDisabled,
|
TwoFaDisabled,
|
||||||
TwoFaNewPassword,
|
TwoFaNewPassword,
|
||||||
TwoFaNewPasswordConfirm,
|
TwoFaNewPasswordConfirm,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user