[Perf] Various fixes for chat opening animation on Android (#1234)
This commit is contained in:
parent
0a594a84e1
commit
fa8e750433
@ -1,5 +1,4 @@
|
|||||||
.MessageOutgoingStatus {
|
.MessageOutgoingStatus {
|
||||||
position: relative;
|
|
||||||
width: 1.19rem;
|
width: 1.19rem;
|
||||||
height: 1.19rem;
|
height: 1.19rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
.Badge-transition {
|
.Badge-transition {
|
||||||
transform: scale(1);
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: transform .3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
transition: transform .3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
position: relative;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,10 +105,6 @@
|
|||||||
margin-inline-end: .25rem;
|
margin-inline-end: .25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-preview {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1.25rem;
|
width: 1.25rem;
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
@ -130,11 +125,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-play {
|
.icon-play {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
position: absolute;
|
|
||||||
top: .1875rem;
|
|
||||||
margin-inline-start: -1.25rem;
|
margin-inline-start: -1.25rem;
|
||||||
|
margin-inline-end: 0.5rem;
|
||||||
|
bottom: 0.0625rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,7 +59,6 @@ type OwnProps = {
|
|||||||
folderId?: number;
|
folderId?: number;
|
||||||
orderDiff: number;
|
orderDiff: number;
|
||||||
animationType: ChatAnimationTypes;
|
animationType: ChatAnimationTypes;
|
||||||
isSelected: boolean;
|
|
||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,6 +74,7 @@ type StateProps = {
|
|||||||
draft?: ApiFormattedText;
|
draft?: ApiFormattedText;
|
||||||
messageListType?: MessageListType;
|
messageListType?: MessageListType;
|
||||||
animationLevel?: number;
|
animationLevel?: number;
|
||||||
|
isSelected?: boolean;
|
||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -88,7 +88,6 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
folderId,
|
folderId,
|
||||||
orderDiff,
|
orderDiff,
|
||||||
animationType,
|
animationType,
|
||||||
isSelected,
|
|
||||||
isPinned,
|
isPinned,
|
||||||
chat,
|
chat,
|
||||||
isMuted,
|
isMuted,
|
||||||
@ -101,6 +100,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
draft,
|
draft,
|
||||||
messageListType,
|
messageListType,
|
||||||
animationLevel,
|
animationLevel,
|
||||||
|
isSelected,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
openChat,
|
openChat,
|
||||||
focusLastMessage,
|
focusLastMessage,
|
||||||
@ -324,7 +324,11 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
: undefined;
|
: undefined;
|
||||||
const { targetUserId: actionTargetUserId, targetChatId: actionTargetChatId } = lastMessageAction || {};
|
const { targetUserId: actionTargetUserId, targetChatId: actionTargetChatId } = lastMessageAction || {};
|
||||||
const privateChatUserId = getPrivateChatUserId(chat);
|
const privateChatUserId = getPrivateChatUserId(chat);
|
||||||
const { type: messageListType } = selectCurrentMessageList(global) || {};
|
const {
|
||||||
|
chatId: currentChatId,
|
||||||
|
threadId: currentThreadId,
|
||||||
|
type: messageListType,
|
||||||
|
} = selectCurrentMessageList(global) || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
@ -338,6 +342,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
draft: selectDraft(global, chatId, MAIN_THREAD_ID),
|
draft: selectDraft(global, chatId, MAIN_THREAD_ID),
|
||||||
messageListType,
|
messageListType,
|
||||||
animationLevel: global.settings.byKey.animationLevel,
|
animationLevel: global.settings.byKey.animationLevel,
|
||||||
|
isSelected: chatId === currentChatId && currentThreadId === MAIN_THREAD_ID,
|
||||||
lastSyncTime: global.lastSyncTime,
|
lastSyncTime: global.lastSyncTime,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { withGlobal } from '../../../lib/teact/teactn';
|
|||||||
|
|
||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
import {
|
import {
|
||||||
ApiChat, ApiChatFolder, ApiUser, MAIN_THREAD_ID,
|
ApiChat, ApiChatFolder, ApiUser,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { NotifyException, NotifySettings } from '../../../types';
|
import { NotifyException, NotifySettings } from '../../../types';
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ import usePrevious from '../../../hooks/usePrevious';
|
|||||||
import { mapValues, pick } from '../../../util/iteratees';
|
import { mapValues, pick } from '../../../util/iteratees';
|
||||||
import { getChatOrder, prepareChatList, prepareFolderListIds } from '../../../modules/helpers';
|
import { getChatOrder, prepareChatList, prepareFolderListIds } from '../../../modules/helpers';
|
||||||
import {
|
import {
|
||||||
selectChatFolder, selectCurrentMessageList, selectNotifyExceptions, selectNotifySettings,
|
selectChatFolder, selectNotifyExceptions, selectNotifySettings,
|
||||||
} from '../../../modules/selectors';
|
} from '../../../modules/selectors';
|
||||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||||
import { useChatAnimationType } from './hooks';
|
import { useChatAnimationType } from './hooks';
|
||||||
@ -36,15 +36,13 @@ type StateProps = {
|
|||||||
usersById: Record<number, ApiUser>;
|
usersById: Record<number, ApiUser>;
|
||||||
chatFolder?: ApiChatFolder;
|
chatFolder?: ApiChatFolder;
|
||||||
listIds?: number[];
|
listIds?: number[];
|
||||||
currentChatId?: number;
|
|
||||||
orderedPinnedIds?: number[];
|
orderedPinnedIds?: number[];
|
||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
isInDiscussionThread?: boolean;
|
|
||||||
notifySettings: NotifySettings;
|
notifySettings: NotifySettings;
|
||||||
notifyExceptions?: Record<number, NotifyException>;
|
notifyExceptions?: Record<number, NotifyException>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, 'loadMoreChats' | 'preloadTopChatMessages' | 'openChat'>;
|
type DispatchProps = Pick<GlobalActions, 'loadMoreChats' | 'preloadTopChatMessages' | 'openChat' | 'openNextChat'>;
|
||||||
|
|
||||||
enum FolderTypeToListType {
|
enum FolderTypeToListType {
|
||||||
'all' = 'active',
|
'all' = 'active',
|
||||||
@ -60,15 +58,14 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
chatsById,
|
chatsById,
|
||||||
usersById,
|
usersById,
|
||||||
listIds,
|
listIds,
|
||||||
currentChatId,
|
|
||||||
orderedPinnedIds,
|
orderedPinnedIds,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
isInDiscussionThread,
|
|
||||||
notifySettings,
|
notifySettings,
|
||||||
notifyExceptions,
|
notifyExceptions,
|
||||||
loadMoreChats,
|
loadMoreChats,
|
||||||
preloadTopChatMessages,
|
preloadTopChatMessages,
|
||||||
openChat,
|
openChat,
|
||||||
|
openNextChat,
|
||||||
}) => {
|
}) => {
|
||||||
const [currentListIds, currentPinnedIds] = useMemo(() => {
|
const [currentListIds, currentPinnedIds] = useMemo(() => {
|
||||||
return folderType === 'folder' && chatFolder
|
return folderType === 'folder' && chatFolder
|
||||||
@ -140,7 +137,6 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
chatId={id}
|
chatId={id}
|
||||||
isPinned
|
isPinned
|
||||||
folderId={folderId}
|
folderId={folderId}
|
||||||
isSelected={id === currentChatId && !isInDiscussionThread}
|
|
||||||
animationType={getAnimationType(id)}
|
animationType={getAnimationType(id)}
|
||||||
orderDiff={orderDiffById[id]}
|
orderDiff={orderDiffById[id]}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -153,7 +149,6 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
teactOrderKey={getChatOrder(chat)}
|
teactOrderKey={getChatOrder(chat)}
|
||||||
chatId={chat.id}
|
chatId={chat.id}
|
||||||
folderId={folderId}
|
folderId={folderId}
|
||||||
isSelected={chat.id === currentChatId && !isInDiscussionThread}
|
|
||||||
animationType={getAnimationType(chat.id)}
|
animationType={getAnimationType(chat.id)}
|
||||||
orderDiff={orderDiffById[chat.id]}
|
orderDiff={orderDiffById[chat.id]}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -181,21 +176,8 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const targetIndexDelta = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : undefined;
|
const targetIndexDelta = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : undefined;
|
||||||
if (!targetIndexDelta) return;
|
if (!targetIndexDelta) return;
|
||||||
|
|
||||||
if (!currentChatId) {
|
|
||||||
e.preventDefault();
|
|
||||||
openChat({ id: orderedIds[0] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const position = orderedIds.indexOf(currentChatId);
|
|
||||||
|
|
||||||
if (position === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const nextId = orderedIds[position + targetIndexDelta];
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
openChat({ id: nextId });
|
openNextChat({ targetIndexDelta, orderedIds });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -238,15 +220,12 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
users: { byId: usersById },
|
users: { byId: usersById },
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
} = global;
|
} = global;
|
||||||
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {};
|
|
||||||
|
|
||||||
const listType = folderType !== 'folder' ? FolderTypeToListType[folderType] : undefined;
|
const listType = folderType !== 'folder' ? FolderTypeToListType[folderType] : undefined;
|
||||||
const chatFolder = folderId ? selectChatFolder(global, folderId) : undefined;
|
const chatFolder = folderId ? selectChatFolder(global, folderId) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chatsById,
|
chatsById,
|
||||||
usersById,
|
usersById,
|
||||||
currentChatId,
|
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
...(listType ? {
|
...(listType ? {
|
||||||
listIds: listIds[listType],
|
listIds: listIds[listType],
|
||||||
@ -254,7 +233,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
} : {
|
} : {
|
||||||
chatFolder,
|
chatFolder,
|
||||||
}),
|
}),
|
||||||
isInDiscussionThread: currentThreadId !== MAIN_THREAD_ID,
|
|
||||||
notifySettings: selectNotifySettings(global),
|
notifySettings: selectNotifySettings(global),
|
||||||
notifyExceptions: selectNotifyExceptions(global),
|
notifyExceptions: selectNotifyExceptions(global),
|
||||||
};
|
};
|
||||||
@ -263,5 +241,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
'loadMoreChats',
|
'loadMoreChats',
|
||||||
'preloadTopChatMessages',
|
'preloadTopChatMessages',
|
||||||
'openChat',
|
'openChat',
|
||||||
|
'openNextChat',
|
||||||
]),
|
]),
|
||||||
)(ChatList));
|
)(ChatList));
|
||||||
|
|||||||
@ -48,10 +48,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-preview {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1.25rem;
|
width: 1.25rem;
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
@ -62,11 +58,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-play {
|
.icon-play {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
position: absolute;
|
|
||||||
top: .1875rem;
|
|
||||||
margin-inline-start: -1.25rem;
|
margin-inline-start: -1.25rem;
|
||||||
|
margin-inline-end: 0.5rem;
|
||||||
|
bottom: 0.0625rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,13 +81,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @optimization
|
|
||||||
#Main.middle-column-open & {
|
|
||||||
.custom-scroll {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Main.history-animation-disabled & {
|
#Main.history-animation-disabled & {
|
||||||
transition: none;
|
transition: none;
|
||||||
|
|
||||||
@ -184,17 +177,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.SymbolMenu {
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
transition: transform var(--layer-transition);
|
|
||||||
|
|
||||||
body.animation-level-0 & {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:not(.is-middle-column-open) & {
|
|
||||||
transform: translate3d(100vw, 0, 0) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -113,11 +113,6 @@ const Main: FC<StateProps & DispatchProps> = ({
|
|||||||
shouldSkipHistoryAnimations && 'history-animation-disabled',
|
shouldSkipHistoryAnimations && 'history-animation-disabled',
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// For animating Symbol Menu on mobile
|
|
||||||
document.body.classList.toggle('is-middle-column-open', className.includes('middle-column-open'));
|
|
||||||
}, [className]);
|
|
||||||
|
|
||||||
// Add `body` classes when toggling right column
|
// Add `body` classes when toggling right column
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (animationLevel > 0) {
|
if (animationLevel > 0) {
|
||||||
|
|||||||
@ -205,11 +205,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.scrolled .sticky-date {
|
&.scrolled:not(.is-animating) .sticky-date {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0.625rem;
|
top: 0.625rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-animating {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-animating .message-select-control {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.has-header-tools & .sticky-date {
|
.has-header-tools & .sticky-date {
|
||||||
top: 3.75rem;
|
top: 3.75rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,6 +69,7 @@ type OwnProps = {
|
|||||||
threadId: number;
|
threadId: number;
|
||||||
type: MessageListType;
|
type: MessageListType;
|
||||||
canPost: boolean;
|
canPost: boolean;
|
||||||
|
isReady: boolean;
|
||||||
onFabToggle: (shouldShow: boolean) => void;
|
onFabToggle: (shouldShow: boolean) => void;
|
||||||
onNotchToggle: (shouldShow: boolean) => void;
|
onNotchToggle: (shouldShow: boolean) => void;
|
||||||
hasTools?: boolean;
|
hasTools?: boolean;
|
||||||
@ -122,6 +123,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
isChatLoaded,
|
isChatLoaded,
|
||||||
isChannelChat,
|
isChannelChat,
|
||||||
canPost,
|
canPost,
|
||||||
|
isReady,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
messageIds,
|
messageIds,
|
||||||
messagesById,
|
messagesById,
|
||||||
@ -331,9 +333,12 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
// Memorize height for scroll animation
|
// Memorize height for scroll animation
|
||||||
const { height: windowHeight } = useWindowSize();
|
const { height: windowHeight } = useWindowSize();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
containerRef.current!.dataset.normalHeight = String(containerRef.current!.offsetHeight);
|
if (isReady) {
|
||||||
}, [windowHeight]);
|
containerRef.current!.dataset.normalHeight = String(containerRef.current!.offsetHeight);
|
||||||
|
}
|
||||||
|
}, [windowHeight, isReady]);
|
||||||
|
|
||||||
// Initial message loading
|
// Initial message loading
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -353,7 +358,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
// Remember scroll position before repositioning it
|
// Remember scroll position before repositioning it
|
||||||
useOnChange(() => {
|
useOnChange(() => {
|
||||||
if (!messageIds || !listItemElementsRef.current) {
|
if (!messageIds || !listItemElementsRef.current || !isReady) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +375,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
anchorIdRef.current = anchor.id;
|
anchorIdRef.current = anchor.id;
|
||||||
anchorTopRef.current = anchor.getBoundingClientRect().top;
|
anchorTopRef.current = anchor.getBoundingClientRect().top;
|
||||||
// This should match deps for `useLayoutEffectWithPrevDeps` below
|
// This should match deps for `useLayoutEffectWithPrevDeps` below
|
||||||
}, [messageIds, isViewportNewest, containerHeight, hasTools]);
|
}, [messageIds, isViewportNewest, containerHeight, hasTools, isReady]);
|
||||||
|
|
||||||
// Handles updated message list, takes care of scroll repositioning
|
// Handles updated message list, takes care of scroll repositioning
|
||||||
useLayoutEffectWithPrevDeps(([
|
useLayoutEffectWithPrevDeps(([
|
||||||
@ -519,6 +524,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
isSelectModeActive && 'select-mode-active',
|
isSelectModeActive && 'select-mode-active',
|
||||||
hasFocusing && 'has-focusing',
|
hasFocusing && 'has-focusing',
|
||||||
isScrolled && 'scrolled',
|
isScrolled && 'scrolled',
|
||||||
|
!isReady && 'is-animating',
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
ANIMATION_END_DELAY,
|
ANIMATION_END_DELAY,
|
||||||
DARK_THEME_BG_COLOR,
|
DARK_THEME_BG_COLOR,
|
||||||
LIGHT_THEME_BG_COLOR,
|
LIGHT_THEME_BG_COLOR,
|
||||||
|
ANIMATION_LEVEL_MIN,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
import {
|
import {
|
||||||
IS_SINGLE_COLUMN_LAYOUT,
|
IS_SINGLE_COLUMN_LAYOUT,
|
||||||
@ -85,8 +86,9 @@ type StateProps = {
|
|||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, 'openChat' | 'unpinAllMessages' | 'loadUser' |
|
type DispatchProps = Pick<GlobalActions, (
|
||||||
'closeLocalTextSearch' | 'exitMessageSelectMode'>;
|
'openChat' | 'unpinAllMessages' | 'loadUser' | 'closeLocalTextSearch' | 'exitMessageSelectMode'
|
||||||
|
)>;
|
||||||
|
|
||||||
const CLOSE_ANIMATION_DURATION = IS_SINGLE_COLUMN_LAYOUT ? 450 + ANIMATION_END_DELAY : undefined;
|
const CLOSE_ANIMATION_DURATION = IS_SINGLE_COLUMN_LAYOUT ? 450 + ANIMATION_END_DELAY : undefined;
|
||||||
|
|
||||||
@ -129,6 +131,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
const [isFabShown, setIsFabShown] = useState<boolean | undefined>();
|
const [isFabShown, setIsFabShown] = useState<boolean | undefined>();
|
||||||
const [isNotchShown, setIsNotchShown] = useState<boolean | undefined>();
|
const [isNotchShown, setIsNotchShown] = useState<boolean | undefined>();
|
||||||
const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false);
|
const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false);
|
||||||
|
const [isReady, setIsReady] = useState(!IS_SINGLE_COLUMN_LAYOUT || animationLevel === ANIMATION_LEVEL_MIN);
|
||||||
|
|
||||||
const hasTools = hasPinnedOrAudioMessage && (
|
const hasTools = hasPinnedOrAudioMessage && (
|
||||||
windowWidth < MOBILE_SCREEN_MAX_WIDTH
|
windowWidth < MOBILE_SCREEN_MAX_WIDTH
|
||||||
@ -162,6 +165,18 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
setIsNotchShown(undefined);
|
setIsNotchShown(undefined);
|
||||||
}, [chatId]);
|
}, [chatId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (animationLevel === ANIMATION_LEVEL_MIN) {
|
||||||
|
setIsReady(true);
|
||||||
|
}
|
||||||
|
}, [animationLevel]);
|
||||||
|
|
||||||
|
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
|
||||||
|
if (e.propertyName === 'transform' && e.target === e.currentTarget) {
|
||||||
|
setIsReady(Boolean(chatId));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isPrivate) {
|
if (isPrivate) {
|
||||||
loadUser({ userId: chatId });
|
loadUser({ userId: chatId });
|
||||||
@ -263,6 +278,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
<div
|
<div
|
||||||
id="MiddleColumn"
|
id="MiddleColumn"
|
||||||
className={className}
|
className={className}
|
||||||
|
onTransitionEnd={handleTransitionEnd}
|
||||||
// @ts-ignore teact-feature
|
// @ts-ignore teact-feature
|
||||||
style={`
|
style={`
|
||||||
--composer-hidden-scale: ${composerHiddenScale};
|
--composer-hidden-scale: ${composerHiddenScale};
|
||||||
@ -290,6 +306,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
chatId={renderingChatId}
|
chatId={renderingChatId}
|
||||||
threadId={renderingThreadId}
|
threadId={renderingThreadId}
|
||||||
messageListType={renderingMessageListType}
|
messageListType={renderingMessageListType}
|
||||||
|
isReady={isReady}
|
||||||
/>
|
/>
|
||||||
<Transition
|
<Transition
|
||||||
name={shouldSkipHistoryAnimations ? 'none' : animationLevel === ANIMATION_LEVEL_MAX ? 'slide' : 'fade'}
|
name={shouldSkipHistoryAnimations ? 'none' : animationLevel === ANIMATION_LEVEL_MAX ? 'slide' : 'fade'}
|
||||||
@ -307,6 +324,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
hasTools={renderingHasTools}
|
hasTools={renderingHasTools}
|
||||||
onFabToggle={setIsFabShown}
|
onFabToggle={setIsFabShown}
|
||||||
onNotchToggle={setIsNotchShown}
|
onNotchToggle={setIsNotchShown}
|
||||||
|
isReady={isReady}
|
||||||
/>
|
/>
|
||||||
<div className={footerClassName}>
|
<div className={footerClassName}>
|
||||||
{renderingCanPost && (
|
{renderingCanPost && (
|
||||||
@ -316,6 +334,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
messageListType={renderingMessageListType}
|
messageListType={renderingMessageListType}
|
||||||
dropAreaState={dropAreaState}
|
dropAreaState={dropAreaState}
|
||||||
onDropHide={handleHideDropArea}
|
onDropHide={handleHideDropArea}
|
||||||
|
isReady={isReady}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isPinnedMessageList && (
|
{isPinnedMessageList && (
|
||||||
|
|||||||
@ -73,6 +73,7 @@ type OwnProps = {
|
|||||||
chatId: number;
|
chatId: number;
|
||||||
threadId: number;
|
threadId: number;
|
||||||
messageListType: MessageListType;
|
messageListType: MessageListType;
|
||||||
|
isReady?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -106,6 +107,7 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
messageListType,
|
messageListType,
|
||||||
|
isReady,
|
||||||
pinnedMessageIds,
|
pinnedMessageIds,
|
||||||
messagesById,
|
messagesById,
|
||||||
canUnpin,
|
canUnpin,
|
||||||
@ -143,10 +145,10 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const topMessageTitle = topMessageSender ? getSenderTitle(lang, topMessageSender) : undefined;
|
const topMessageTitle = topMessageSender ? getSenderTitle(lang, topMessageSender) : undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (threadId === MAIN_THREAD_ID && lastSyncTime) {
|
if (threadId === MAIN_THREAD_ID && lastSyncTime && isReady) {
|
||||||
loadPinnedMessages({ chatId });
|
loadPinnedMessages({ chatId });
|
||||||
}
|
}
|
||||||
}, [chatId, loadPinnedMessages, lastSyncTime, threadId]);
|
}, [chatId, loadPinnedMessages, lastSyncTime, threadId, isReady]);
|
||||||
|
|
||||||
// Reset pinned index when switching chats and pinning/unpinning
|
// Reset pinned index when switching chats and pinning/unpinning
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -244,6 +244,7 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
/>
|
/>
|
||||||
<MessageInput
|
<MessageInput
|
||||||
id="caption-input-text"
|
id="caption-input-text"
|
||||||
|
isAttachmentModalInput
|
||||||
html={caption}
|
html={caption}
|
||||||
editableInputId={EDITABLE_INPUT_MODAL_ID}
|
editableInputId={EDITABLE_INPUT_MODAL_ID}
|
||||||
placeholder={lang('Caption')}
|
placeholder={lang('Caption')}
|
||||||
|
|||||||
@ -94,6 +94,7 @@ type OwnProps = {
|
|||||||
messageListType: MessageListType;
|
messageListType: MessageListType;
|
||||||
dropAreaState: string;
|
dropAreaState: string;
|
||||||
onDropHide: NoneToVoidFunction;
|
onDropHide: NoneToVoidFunction;
|
||||||
|
isReady: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -156,6 +157,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
shouldSchedule,
|
shouldSchedule,
|
||||||
canScheduleUntilOnline,
|
canScheduleUntilOnline,
|
||||||
onDropHide,
|
onDropHide,
|
||||||
|
isReady,
|
||||||
editingMessage,
|
editingMessage,
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
@ -225,15 +227,13 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}, [chatId]);
|
}, [chatId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chatId && lastSyncTime && threadId === MAIN_THREAD_ID) {
|
if (chatId && lastSyncTime && threadId === MAIN_THREAD_ID && isReady) {
|
||||||
loadScheduledHistory();
|
loadScheduledHistory();
|
||||||
}
|
}
|
||||||
}, [chatId, loadScheduledHistory, lastSyncTime, threadId]);
|
}, [isReady, chatId, loadScheduledHistory, lastSyncTime, threadId]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!appendixRef.current) {
|
if (!appendixRef.current) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
appendixRef.current.innerHTML = APPENDIX;
|
appendixRef.current.innerHTML = APPENDIX;
|
||||||
}, []);
|
}, []);
|
||||||
@ -303,6 +303,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
Boolean(shouldSuggestStickers && allowedAttachmentOptions.canSendStickers && !attachments.length),
|
Boolean(shouldSuggestStickers && allowedAttachmentOptions.canSendStickers && !attachments.length),
|
||||||
html,
|
html,
|
||||||
stickersForEmoji,
|
stickersForEmoji,
|
||||||
|
!isReady,
|
||||||
);
|
);
|
||||||
const {
|
const {
|
||||||
isEmojiTooltipOpen, closeEmojiTooltip, filteredEmojis, insertEmoji,
|
isEmojiTooltipOpen, closeEmojiTooltip, filteredEmojis, insertEmoji,
|
||||||
@ -314,6 +315,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
setHtml,
|
setHtml,
|
||||||
baseEmojiKeywords,
|
baseEmojiKeywords,
|
||||||
emojiKeywords,
|
emojiKeywords,
|
||||||
|
!isReady,
|
||||||
);
|
);
|
||||||
|
|
||||||
const insertTextAndUpdateCursor = useCallback((text: string, inputId: string = EDITABLE_INPUT_ID) => {
|
const insertTextAndUpdateCursor = useCallback((text: string, inputId: string = EDITABLE_INPUT_ID) => {
|
||||||
@ -608,6 +610,8 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}, [isRightColumnShown, closeSymbolMenu]);
|
}, [isRightColumnShown, closeSymbolMenu]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isReady) return;
|
||||||
|
|
||||||
if (isSelectModeActive) {
|
if (isSelectModeActive) {
|
||||||
disableHover();
|
disableHover();
|
||||||
} else {
|
} else {
|
||||||
@ -615,7 +619,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
enableHover();
|
enableHover();
|
||||||
}, SELECT_MODE_TRANSITION_MS);
|
}, SELECT_MODE_TRANSITION_MS);
|
||||||
}
|
}
|
||||||
}, [isSelectModeActive, enableHover, disableHover]);
|
}, [isSelectModeActive, enableHover, disableHover, isReady]);
|
||||||
|
|
||||||
const mainButtonHandler = useCallback(() => {
|
const mainButtonHandler = useCallback(() => {
|
||||||
switch (mainButtonState) {
|
switch (mainButtonState) {
|
||||||
@ -687,7 +691,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{allowedAttachmentOptions.canAttachMedia && (
|
{allowedAttachmentOptions.canAttachMedia && isReady && (
|
||||||
<Portal containerId="#middle-column-portals">
|
<Portal containerId="#middle-column-portals">
|
||||||
<DropArea
|
<DropArea
|
||||||
isOpen={dropAreaState !== DropAreaState.None}
|
isOpen={dropAreaState !== DropAreaState.None}
|
||||||
@ -763,7 +767,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
>
|
>
|
||||||
<i className="icon-smile" />
|
<i className="icon-smile" />
|
||||||
<i className="icon-keyboard" />
|
<i className="icon-keyboard" />
|
||||||
<Spinner color="gray" />
|
{!isSymbolMenuLoaded && <Spinner color="gray" />}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<ResponsiveHoverButton
|
<ResponsiveHoverButton
|
||||||
|
|||||||
@ -32,6 +32,7 @@ const TRANSITION_DURATION_FACTOR = 50;
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
id: string;
|
id: string;
|
||||||
|
isAttachmentModalInput?: boolean;
|
||||||
editableInputId?: string;
|
editableInputId?: string;
|
||||||
html: string;
|
html: string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
@ -73,6 +74,7 @@ function clearSelection() {
|
|||||||
|
|
||||||
const MessageInput: FC<OwnProps & StateProps & DispatchProps> = ({
|
const MessageInput: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
id,
|
id,
|
||||||
|
isAttachmentModalInput,
|
||||||
editableInputId,
|
editableInputId,
|
||||||
html,
|
html,
|
||||||
placeholder,
|
placeholder,
|
||||||
@ -101,8 +103,9 @@ const MessageInput: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const [selectedRange, setSelectedRange] = useState<Range>();
|
const [selectedRange, setSelectedRange] = useState<Range>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isAttachmentModalInput) return;
|
||||||
updateInputHeight(false);
|
updateInputHeight(false);
|
||||||
}, []);
|
}, [isAttachmentModalInput]);
|
||||||
|
|
||||||
useLayoutEffectWithPrevDeps(([prevHtml]) => {
|
useLayoutEffectWithPrevDeps(([prevHtml]) => {
|
||||||
if (html !== inputRef.current!.innerHTML) {
|
if (html !== inputRef.current!.innerHTML) {
|
||||||
|
|||||||
@ -19,6 +19,14 @@
|
|||||||
transform: translate3d(0, calc(var(--symbol-menu-height) + var(--symbol-menu-footer-height)), 0);
|
transform: translate3d(0, calc(var(--symbol-menu-height) + var(--symbol-menu-footer-height)), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.animation-level-0 & {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.middle-column-open) {
|
||||||
|
transform: translate3d(100vw, 0, 0) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-main {
|
&-main {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
|
FC, memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
|
import { withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiSticker, ApiVideo } from '../../../api/types';
|
import { ApiSticker, ApiVideo } from '../../../api/types';
|
||||||
|
|
||||||
@ -38,10 +39,14 @@ export type OwnProps = {
|
|||||||
addRecentEmoji: AnyToVoidFunction;
|
addRecentEmoji: AnyToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
isLeftColumnShown: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
let isActivated = false;
|
let isActivated = false;
|
||||||
|
|
||||||
const SymbolMenu: FC<OwnProps> = ({
|
const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||||
isOpen, allowedAttachmentOptions,
|
isOpen, allowedAttachmentOptions, isLeftColumnShown,
|
||||||
onLoad, onClose,
|
onLoad, onClose,
|
||||||
onEmojiSelect, onStickerSelect, onGifSelect,
|
onEmojiSelect, onStickerSelect, onGifSelect,
|
||||||
onRemoveSymbol, onSearchOpen, addRecentEmoji,
|
onRemoveSymbol, onSearchOpen, addRecentEmoji,
|
||||||
@ -188,6 +193,7 @@ const SymbolMenu: FC<OwnProps> = ({
|
|||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
'SymbolMenu mobile-menu',
|
'SymbolMenu mobile-menu',
|
||||||
transitionClassNames,
|
transitionClassNames,
|
||||||
|
!isLeftColumnShown && 'middle-column-open',
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -216,4 +222,10 @@ const SymbolMenu: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(SymbolMenu);
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global): StateProps => {
|
||||||
|
return {
|
||||||
|
isLeftColumnShown: global.isLeftColumnShown,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(SymbolMenu));
|
||||||
|
|||||||
@ -36,6 +36,7 @@ export default function useEmojiTooltip(
|
|||||||
onUpdateHtml: (html: string) => void,
|
onUpdateHtml: (html: string) => void,
|
||||||
baseEmojiKeywords?: Record<string, string[]>,
|
baseEmojiKeywords?: Record<string, string[]>,
|
||||||
emojiKeywords?: Record<string, string[]>,
|
emojiKeywords?: Record<string, string[]>,
|
||||||
|
isDisabled = false,
|
||||||
) {
|
) {
|
||||||
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
|
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ export default function useEmojiTooltip(
|
|||||||
|
|
||||||
// Initialize data on first render.
|
// Initialize data on first render.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isDisabled) return;
|
||||||
const exec = () => {
|
const exec = () => {
|
||||||
setById(emojiData.emojis);
|
setById(emojiData.emojis);
|
||||||
};
|
};
|
||||||
@ -70,10 +72,10 @@ export default function useEmojiTooltip(
|
|||||||
ensureEmojiData()
|
ensureEmojiData()
|
||||||
.then(exec);
|
.then(exec);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [isDisabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!byId) {
|
if (!byId || isDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +109,7 @@ export default function useEmojiTooltip(
|
|||||||
}, {} as Record<string, Emoji[]>);
|
}, {} as Record<string, Emoji[]>);
|
||||||
setByName(emojisByName);
|
setByName(emojisByName);
|
||||||
setNames(Object.keys(emojisByName));
|
setNames(Object.keys(emojisByName));
|
||||||
}, [baseEmojiKeywords, byId, emojiKeywords]);
|
}, [isDisabled, baseEmojiKeywords, byId, emojiKeywords]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAllowed || !html || !byId || !keywords || !keywords.length) {
|
if (!isAllowed || !html || !byId || !keywords || !keywords.length) {
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export default function useStickerTooltip(
|
|||||||
isAllowed: boolean,
|
isAllowed: boolean,
|
||||||
html: string,
|
html: string,
|
||||||
stickers?: ApiSticker[],
|
stickers?: ApiSticker[],
|
||||||
|
isDisabled = false,
|
||||||
) {
|
) {
|
||||||
const { loadStickersForEmoji, clearStickersForEmoji } = getDispatch();
|
const { loadStickersForEmoji, clearStickersForEmoji } = getDispatch();
|
||||||
const isSingleEmoji = (
|
const isSingleEmoji = (
|
||||||
@ -20,6 +21,8 @@ export default function useStickerTooltip(
|
|||||||
const hasStickers = Boolean(stickers) && isSingleEmoji;
|
const hasStickers = Boolean(stickers) && isSingleEmoji;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isDisabled) return;
|
||||||
|
|
||||||
if (isAllowed && isSingleEmoji) {
|
if (isAllowed && isSingleEmoji) {
|
||||||
loadStickersForEmoji({ emoji: html });
|
loadStickersForEmoji({ emoji: html });
|
||||||
} else if (hasStickers || !isSingleEmoji) {
|
} else if (hasStickers || !isSingleEmoji) {
|
||||||
@ -27,7 +30,7 @@ export default function useStickerTooltip(
|
|||||||
}
|
}
|
||||||
// We omit `hasStickers` here to prevent re-fetching after manually closing tooltip (via <Esc>).
|
// We omit `hasStickers` here to prevent re-fetching after manually closing tooltip (via <Esc>).
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [html, isSingleEmoji, clearStickersForEmoji, loadStickersForEmoji, isAllowed]);
|
}, [html, isSingleEmoji, clearStickersForEmoji, loadStickersForEmoji, isAllowed, isDisabled]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isStickerTooltipOpen: hasStickers,
|
isStickerTooltipOpen: hasStickers,
|
||||||
|
|||||||
@ -416,7 +416,7 @@ export type ActionTypes = (
|
|||||||
'joinChannel' | 'leaveChannel' | 'deleteChannel' | 'toggleChatPinned' | 'toggleChatArchived' | 'toggleChatUnread' |
|
'joinChannel' | 'leaveChannel' | 'deleteChannel' | 'toggleChatPinned' | 'toggleChatArchived' | 'toggleChatUnread' |
|
||||||
'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' |
|
'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' |
|
||||||
'updateChat' | 'toggleSignatures' | 'loadGroupsForDiscussion' | 'linkDiscussionGroup' | 'unlinkDiscussionGroup' |
|
'updateChat' | 'toggleSignatures' | 'loadGroupsForDiscussion' | 'linkDiscussionGroup' | 'unlinkDiscussionGroup' |
|
||||||
'loadProfilePhotos' | 'loadMoreMembers' | 'setActiveChatFolder' |
|
'loadProfilePhotos' | 'loadMoreMembers' | 'setActiveChatFolder' | 'openNextChat' |
|
||||||
// messages
|
// messages
|
||||||
'loadViewportMessages' | 'selectMessage' | 'sendMessage' | 'cancelSendingMessage' | 'pinMessage' | 'deleteMessages' |
|
'loadViewportMessages' | 'selectMessage' | 'sendMessage' | 'cancelSendingMessage' | 'pinMessage' | 'deleteMessages' |
|
||||||
'markMessageListRead' | 'markMessagesRead' | 'loadMessage' | 'focusMessage' | 'focusLastMessage' | 'sendPollVote' |
|
'markMessageListRead' | 'markMessagesRead' | 'loadMessage' | 'focusMessage' | 'focusLastMessage' | 'sendPollVote' |
|
||||||
|
|||||||
@ -54,3 +54,23 @@ addReducer('resetChatCreation', (global) => {
|
|||||||
chatCreation: undefined,
|
chatCreation: undefined,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addReducer('openNextChat', (global, actions, payload) => {
|
||||||
|
const { targetIndexDelta, orderedIds } = payload;
|
||||||
|
|
||||||
|
const { chatId } = selectCurrentMessageList(global) || {};
|
||||||
|
|
||||||
|
if (!chatId) {
|
||||||
|
actions.openChat({ id: orderedIds[0] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = orderedIds.indexOf(chatId);
|
||||||
|
|
||||||
|
if (position === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nextId = orderedIds[position + targetIndexDelta];
|
||||||
|
|
||||||
|
actions.openChat({ id: nextId });
|
||||||
|
});
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { getChatTitle } from './chats';
|
|||||||
|
|
||||||
const CONTENT_NOT_SUPPORTED = 'The message is not supported on this version of Telegram';
|
const CONTENT_NOT_SUPPORTED = 'The message is not supported on this version of Telegram';
|
||||||
const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i');
|
const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i');
|
||||||
|
const TRUNCATED_SUMMARY_LENGTH = 80;
|
||||||
|
|
||||||
export function getMessageKey(message: ApiMessage) {
|
export function getMessageKey(message: ApiMessage) {
|
||||||
const { chatId, id } = message;
|
const { chatId, id } = message;
|
||||||
@ -32,16 +33,18 @@ export function getMessageSummaryText(lang: LangFn, message: ApiMessage, noEmoji
|
|||||||
text, photo, video, audio, voice, document, sticker, contact, poll, invoice,
|
text, photo, video, audio, voice, document, sticker, contact, poll, invoice,
|
||||||
} = message.content;
|
} = message.content;
|
||||||
|
|
||||||
|
const truncatedText = text && text.text.substr(0, TRUNCATED_SUMMARY_LENGTH);
|
||||||
|
|
||||||
if (message.groupedId) {
|
if (message.groupedId) {
|
||||||
return `${noEmoji ? '' : '🖼 '}${text ? text.text : lang('lng_in_dlg_album')}`;
|
return `${noEmoji ? '' : '🖼 '}${truncatedText || lang('lng_in_dlg_album')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (photo) {
|
if (photo) {
|
||||||
return `${noEmoji ? '' : '🖼 '}${text ? text.text : lang('AttachPhoto')}`;
|
return `${noEmoji ? '' : '🖼 '}${truncatedText || lang('AttachPhoto')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (video) {
|
if (video) {
|
||||||
return `${noEmoji ? '' : '📹 '}${text ? text.text : lang(video.isGif ? 'AttachGif' : 'AttachVideo')}`;
|
return `${noEmoji ? '' : '📹 '}${truncatedText || lang(video.isGif ? 'AttachGif' : 'AttachVideo')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sticker) {
|
if (sticker) {
|
||||||
@ -53,11 +56,11 @@ export function getMessageSummaryText(lang: LangFn, message: ApiMessage, noEmoji
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (voice) {
|
if (voice) {
|
||||||
return `${noEmoji ? '' : '🎤 '}${text ? text.text : lang('AttachAudio')}`;
|
return `${noEmoji ? '' : '🎤 '}${truncatedText || lang('AttachAudio')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document) {
|
if (document) {
|
||||||
return `${noEmoji ? '' : '📎 '}${text ? text.text : document.fileName}`;
|
return `${noEmoji ? '' : '📎 '}${truncatedText || document.fileName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contact) {
|
if (contact) {
|
||||||
@ -73,7 +76,7 @@ export function getMessageSummaryText(lang: LangFn, message: ApiMessage, noEmoji
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
return text.text;
|
return truncatedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CONTENT_NOT_SUPPORTED;
|
return CONTENT_NOT_SUPPORTED;
|
||||||
|
|||||||
@ -47,9 +47,10 @@ export function replaceChats(global: GlobalState, newById: Record<number, ApiCha
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateChat(
|
// @optimization Don't spread/unspread global for each element, do it in a batch
|
||||||
|
function getUpdatedChat(
|
||||||
global: GlobalState, chatId: number, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
global: GlobalState, chatId: number, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
||||||
): GlobalState {
|
): ApiChat {
|
||||||
const { byId } = global.chats;
|
const { byId } = global.chats;
|
||||||
const chat = byId[chatId];
|
const chat = byId[chatId];
|
||||||
const shouldOmitMinInfo = chatUpdate.isMin && chat && !chat.isMin;
|
const shouldOmitMinInfo = chatUpdate.isMin && chat && !chat.isMin;
|
||||||
@ -60,9 +61,19 @@ export function updateChat(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!updatedChat.id || !updatedChat.type) {
|
if (!updatedChat.id || !updatedChat.type) {
|
||||||
return global;
|
return updatedChat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return updatedChat;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateChat(
|
||||||
|
global: GlobalState, chatId: number, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
||||||
|
): GlobalState {
|
||||||
|
const { byId } = global.chats;
|
||||||
|
|
||||||
|
const updatedChat = getUpdatedChat(global, chatId, chatUpdate, photo);
|
||||||
|
|
||||||
return replaceChats(global, {
|
return replaceChats(global, {
|
||||||
...byId,
|
...byId,
|
||||||
[chatId]: updatedChat,
|
[chatId]: updatedChat,
|
||||||
@ -70,8 +81,17 @@ export function updateChat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateChats(global: GlobalState, updatedById: Record<number, ApiChat>): GlobalState {
|
export function updateChats(global: GlobalState, updatedById: Record<number, ApiChat>): GlobalState {
|
||||||
Object.keys(updatedById).forEach((id) => {
|
const updatedChats = Object.keys(updatedById).map(Number).reduce<Record<number, ApiChat>>((acc, id) => {
|
||||||
global = updateChat(global, Number(id), updatedById[Number(id)]);
|
const updatedChat = getUpdatedChat(global, id, updatedById[id]);
|
||||||
|
if (updatedChat) {
|
||||||
|
acc[id] = updatedChat;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
global = replaceChats(global, {
|
||||||
|
...global.chats.byId,
|
||||||
|
...updatedChats,
|
||||||
});
|
});
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
@ -80,10 +100,19 @@ export function updateChats(global: GlobalState, updatedById: Record<number, Api
|
|||||||
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
||||||
export function addChats(global: GlobalState, addedById: Record<number, ApiChat>): GlobalState {
|
export function addChats(global: GlobalState, addedById: Record<number, ApiChat>): GlobalState {
|
||||||
const { byId } = global.chats;
|
const { byId } = global.chats;
|
||||||
Object.keys(addedById).map(Number).forEach((id) => {
|
const addedChats = Object.keys(addedById).map(Number).reduce<Record<number, ApiChat>>((acc, id) => {
|
||||||
if (!byId[id] || (byId[id].isMin && !addedById[id].isMin)) {
|
if (!byId[id] || (byId[id].isMin && !addedById[id].isMin)) {
|
||||||
global = updateChat(global, id, addedById[id]);
|
const updatedChat = getUpdatedChat(global, id, addedById[id]);
|
||||||
|
if (updatedChat) {
|
||||||
|
acc[id] = updatedChat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
global = replaceChats(global, {
|
||||||
|
...global.chats.byId,
|
||||||
|
...addedChats,
|
||||||
});
|
});
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
|
|||||||
@ -13,29 +13,54 @@ export function replaceUsers(global: GlobalState, newById: Record<number, ApiUse
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function updateUser(global: GlobalState, userId: number, userUpdate: Partial<ApiUser>): GlobalState {
|
|
||||||
|
// @optimization Don't spread/unspread global for each element, do it in a batch
|
||||||
|
function getUpdatedUser(global: GlobalState, userId: number, userUpdate: Partial<ApiUser>): ApiUser {
|
||||||
const { byId } = global.users;
|
const { byId } = global.users;
|
||||||
const { hash, userIds: contactUserIds } = global.contactList || {};
|
|
||||||
const user = byId[userId];
|
const user = byId[userId];
|
||||||
const shouldOmitMinInfo = userUpdate.isMin && user && !user.isMin;
|
const shouldOmitMinInfo = userUpdate.isMin && user && !user.isMin;
|
||||||
|
|
||||||
const updatedUser = {
|
const updatedUser = {
|
||||||
...user,
|
...user,
|
||||||
...(shouldOmitMinInfo ? omit(userUpdate, ['isMin', 'accessHash']) : userUpdate),
|
...(shouldOmitMinInfo ? omit(userUpdate, ['isMin', 'accessHash']) : userUpdate),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!updatedUser.id || !updatedUser.type) {
|
if (!updatedUser.id || !updatedUser.type) {
|
||||||
return global;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedUser.isContact && (contactUserIds && !contactUserIds.includes(userId))) {
|
return updatedUser;
|
||||||
global = {
|
}
|
||||||
...global,
|
|
||||||
contactList: {
|
function updateContactList(global: GlobalState, updatedUsers: ApiUser[]): GlobalState {
|
||||||
hash: hash || 0,
|
const { hash, userIds: contactUserIds } = global.contactList || {};
|
||||||
userIds: [userId, ...contactUserIds],
|
|
||||||
},
|
if (!contactUserIds) return global;
|
||||||
};
|
|
||||||
}
|
const newContactUserIds = updatedUsers
|
||||||
|
.filter((user) => user && user.isContact && !contactUserIds.includes(user.id))
|
||||||
|
.map((user) => user.id);
|
||||||
|
|
||||||
|
if (newContactUserIds.length === 0) return global;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
contactList: {
|
||||||
|
hash: hash || 0,
|
||||||
|
userIds: [
|
||||||
|
...newContactUserIds,
|
||||||
|
...contactUserIds,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateUser(global: GlobalState, userId: number, userUpdate: Partial<ApiUser>): GlobalState {
|
||||||
|
const { byId } = global.users;
|
||||||
|
|
||||||
|
const updatedUser = getUpdatedUser(global, userId, userUpdate);
|
||||||
|
|
||||||
|
global = updateContactList(global, [updatedUser]);
|
||||||
|
|
||||||
return replaceUsers(global, {
|
return replaceUsers(global, {
|
||||||
...byId,
|
...byId,
|
||||||
@ -43,9 +68,21 @@ export function updateUser(global: GlobalState, userId: number, userUpdate: Part
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function updateUsers(global: GlobalState, updatedById: Record<number, ApiUser>): GlobalState {
|
export function updateUsers(global: GlobalState, updatedById: Record<number, ApiUser>): GlobalState {
|
||||||
Object.keys(updatedById).map(Number).forEach((id) => {
|
const updatedUsers = Object.keys(updatedById).map(Number).reduce<Record<number, ApiUser>>((acc, id) => {
|
||||||
global = updateUser(global, id, updatedById[id]);
|
const updatedUser = getUpdatedUser(global, id, updatedById[id]);
|
||||||
|
if (updatedUser) {
|
||||||
|
acc[id] = updatedUser;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
global = updateContactList(global, Object.values(updatedUsers));
|
||||||
|
|
||||||
|
global = replaceUsers(global, {
|
||||||
|
...global.users.byId,
|
||||||
|
...updatedUsers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
@ -54,10 +91,22 @@ export function updateUsers(global: GlobalState, updatedById: Record<number, Api
|
|||||||
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
||||||
export function addUsers(global: GlobalState, addedById: Record<number, ApiUser>): GlobalState {
|
export function addUsers(global: GlobalState, addedById: Record<number, ApiUser>): GlobalState {
|
||||||
const { byId } = global.users;
|
const { byId } = global.users;
|
||||||
Object.keys(addedById).map(Number).forEach((id) => {
|
|
||||||
|
const addedUsers = Object.keys(addedById).map(Number).reduce<Record<number, ApiUser>>((acc, id) => {
|
||||||
if (!byId[id] || (byId[id].isMin && !addedById[id].isMin)) {
|
if (!byId[id] || (byId[id].isMin && !addedById[id].isMin)) {
|
||||||
global = updateUser(global, id, addedById[id]);
|
const updatedUser = getUpdatedUser(global, id, addedById[id]);
|
||||||
|
if (updatedUser) {
|
||||||
|
acc[id] = updatedUser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
global = updateContactList(global, Object.values(addedUsers));
|
||||||
|
|
||||||
|
global = replaceUsers(global, {
|
||||||
|
...global.users.byId,
|
||||||
|
...addedUsers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user