Message List: Fixes for appearance animation

This commit is contained in:
Alexander Zinchuk 2021-04-24 15:05:01 +03:00
parent 5d0af382ec
commit 76c2ff41ec
3 changed files with 40 additions and 62 deletions

View File

@ -70,6 +70,21 @@
.ActionMessage { .ActionMessage {
position: relative; position: relative;
opacity: 1;
transform: scale(1);
transition: opacity .2s ease, transform .2s ease;
&:not(.open) {
transform: scale(0.8);
opacity: 0;
}
// Restore stacking context
// https://developer.mozilla.org/ru/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
&.open.shown {
transform: none;
}
&::before { &::before {
content: ""; content: "";
position: absolute; position: absolute;

View File

@ -39,7 +39,7 @@ import {
pick, pick,
} from '../../util/iteratees'; } from '../../util/iteratees';
import { import {
fastRaf, debounce, throttleWithTickEnd, fastRaf, debounce, throttleWithTickEnd, onTickEnd,
} from '../../util/schedulers'; } from '../../util/schedulers';
import { formatHumanDate } from '../../util/dateFormat'; import { formatHumanDate } from '../../util/dateFormat';
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps'; import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
@ -143,12 +143,14 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const scrollOffsetRef = useRef<number>(); // We update local cached `scrollOffsetRef` when opening chat.
// Then we update global version every second on scrolling.
const scrollOffsetRef = useRef<number>((type === 'thread' && selectScrollOffset(getGlobal(), chatId, threadId)) || 0);
const anchorIdRef = useRef<string>(); const anchorIdRef = useRef<string>();
const anchorTopRef = useRef<number>(); const anchorTopRef = useRef<number>();
const listItemElementsRef = useRef<HTMLDivElement[]>(); const listItemElementsRef = useRef<HTMLDivElement[]>();
// Updated when opening chat (to preserve divider even after messages are read) // Updated when opening chat (to preserve divider even after messages are read)
const memoUnreadDividerBeforeIdRef = useRef<number>(); const memoUnreadDividerBeforeIdRef = useRef<number | undefined>(firstUnreadId);
// Updated every time (to be used from intersection callback closure) // Updated every time (to be used from intersection callback closure)
const memoFirstUnreadIdRef = useRef<number>(); const memoFirstUnreadIdRef = useRef<number>();
const memoFocusingIdRef = useRef<number>(); const memoFocusingIdRef = useRef<number>();
@ -158,21 +160,15 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
const [containerHeight, setContainerHeight] = useState<number | undefined>(); const [containerHeight, setContainerHeight] = useState<number | undefined>();
const [hasFocusing, setHasFocusing] = useState<boolean>(Boolean(focusingId)); const [hasFocusing, setHasFocusing] = useState<boolean>(Boolean(focusingId));
const areMessagesLoaded = Boolean(messageIds);
useOnChange(() => { useOnChange(() => {
anchorIdRef.current = undefined; // We only need it first time when message list appears
if (areMessagesLoaded) {
memoUnreadDividerBeforeIdRef.current = firstUnreadId; onTickEnd(() => {
shouldAnimateAppearanceRef.current = false;
// We update local cached `scrollOffsetRef` when opening chat. });
// Then we update global version every second on scrolling. }
scrollOffsetRef.current = (type === 'thread' && selectScrollOffset(getGlobal(), chatId, threadId)) || 0; }, [areMessagesLoaded]);
// We need it just first time when message list appears
// TODO Figure out why `onTickEnd`/100ms is not enough
setTimeout(() => {
shouldAnimateAppearanceRef.current = false;
}, 1000);
}, [Boolean(messageIds)]);
useOnChange(() => { useOnChange(() => {
memoFirstUnreadIdRef.current = firstUnreadId; memoFirstUnreadIdRef.current = firstUnreadId;
@ -503,12 +499,13 @@ 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 ? ( ) : messageIds && !messageGroups ? (
// @ts-ignore <div className="empty"><span>{lang('NoMessages')}</span></div>
) : ((messageIds && messageGroups) || lastMessage) ? (
<MessageScroll <MessageScroll
containerRef={containerRef} containerRef={containerRef}
className="messages-container" className="messages-container"
messageIds={messageIds} messageIds={messageIds || [lastMessage!.id]}
containerHeight={containerHeight} containerHeight={containerHeight}
listItemElementsRef={listItemElementsRef} listItemElementsRef={listItemElementsRef}
focusingId={focusingId} focusingId={focusingId}
@ -520,32 +517,9 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
firstUnreadId={firstUnreadId} firstUnreadId={firstUnreadId}
onFabToggle={onFabToggle} onFabToggle={onFabToggle}
> >
{messageGroups && renderMessages(
lang,
messageGroups,
shouldAnimateAppearanceRef.current ? messageIds.length : 0,
observeIntersectionForReading,
observeIntersectionForMedia,
observeIntersectionForAnimatedStickers,
withUsers,
anchorIdRef,
memoUnreadDividerBeforeIdRef,
threadId,
type,
threadTopMessageId,
threadFirstMessageId,
hasLinkedChat,
type === 'scheduled',
)}
</MessageScroll>
) : messageIds ? (
<div className="empty"><span>{lang('NoMessages')}</span></div>
) : lastMessage ? (
<div className="messages-container">
{renderMessages( {renderMessages(
lang, lang,
groupMessages([lastMessage]), messageGroups || groupMessages([lastMessage!]),
0,
observeIntersectionForReading, observeIntersectionForReading,
observeIntersectionForMedia, observeIntersectionForMedia,
observeIntersectionForAnimatedStickers, observeIntersectionForAnimatedStickers,
@ -557,9 +531,10 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
threadTopMessageId, threadTopMessageId,
threadFirstMessageId, threadFirstMessageId,
hasLinkedChat, hasLinkedChat,
false, messageGroups ? type === 'scheduled' : false,
!messageGroups || !shouldAnimateAppearanceRef.current,
)} )}
</div> </MessageScroll>
) : ( ) : (
<Loading color="white" /> <Loading color="white" />
)} )}
@ -570,7 +545,6 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
function renderMessages( function renderMessages(
lang: LangFn, lang: LangFn,
messageGroups: MessageDateGroup[], messageGroups: MessageDateGroup[],
messageCountToAnimate: number,
observeIntersectionForReading: ObserveFn, observeIntersectionForReading: ObserveFn,
observeIntersectionForMedia: ObserveFn, observeIntersectionForMedia: ObserveFn,
observeIntersectionForAnimatedStickers: ObserveFn, observeIntersectionForAnimatedStickers: ObserveFn,
@ -583,6 +557,7 @@ function renderMessages(
threadFirstMessageId?: number, threadFirstMessageId?: number,
hasLinkedChat?: boolean, hasLinkedChat?: boolean,
isSchedule = false, isSchedule = false,
noAppearanceAnimation = false,
) { ) {
const unreadDivider = ( const unreadDivider = (
<div className={buildClassName(UNREAD_DIVIDER_CLASS, 'local-action-message')} key="unread-messages"> <div className={buildClassName(UNREAD_DIVIDER_CLASS, 'local-action-message')} key="unread-messages">
@ -590,6 +565,9 @@ function renderMessages(
</div> </div>
); );
const messageCountToAnimate = noAppearanceAnimation ? 0 : messageGroups.reduce((acc, messageGroup) => {
return acc + flatten(messageGroup.senderGroups).length;
}, 0);
let appearanceIndex = 0; let appearanceIndex = 0;
const dateGroups = messageGroups.map(( const dateGroups = messageGroups.map((

View File

@ -19,21 +19,6 @@
--select-message-scale: 0.9; --select-message-scale: 0.9;
--select-background-color: white; --select-background-color: white;
opacity: 1;
transform: scale(1);
transition: opacity .2s ease, transform .2s ease;
&:not(.open) {
transform: scale(0.8);
opacity: 0;
}
// Restore stacking context
// https://developer.mozilla.org/ru/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
&.open.shown {
transform: none;
}
> .Avatar, > .Avatar,
> .message-content-wrapper { > .message-content-wrapper {
opacity: 1; opacity: 1;