Floating Actions Buttons: Fix animations (#3575)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
parent
2688cd768a
commit
11cbab7d39
1
src/@types/global.d.ts
vendored
1
src/@types/global.d.ts
vendored
@ -38,6 +38,7 @@ type AnyLiteral = Record<string, any>;
|
||||
type AnyClass = new (...args: any[]) => any;
|
||||
type AnyFunction = (...args: any[]) => any;
|
||||
type AnyToVoidFunction = (...args: any[]) => void;
|
||||
type BooleanToVoidFunction = (value: boolean) => void;
|
||||
type NoneToVoidFunction = () => void;
|
||||
|
||||
type EmojiCategory = {
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.only-reactions {
|
||||
&.hide-scroll-down {
|
||||
transform: translateY(4rem);
|
||||
|
||||
.unread {
|
||||
|
||||
@ -16,7 +16,7 @@ import ScrollDownButton from './ScrollDownButton';
|
||||
import styles from './FloatingActionButtons.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
isShown: boolean;
|
||||
withScrollDown: boolean;
|
||||
canPost?: boolean;
|
||||
withExtraShift?: boolean;
|
||||
};
|
||||
@ -32,7 +32,7 @@ type StateProps = {
|
||||
const FOCUS_MARGIN = 20;
|
||||
|
||||
const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
||||
isShown,
|
||||
withScrollDown,
|
||||
canPost,
|
||||
messageListType,
|
||||
chatId,
|
||||
@ -64,15 +64,17 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [chatId, fetchUnreadMentions, hasUnreadMentions]);
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
if (!isShown) {
|
||||
const handleScrollDownClick = useLastCallback(() => {
|
||||
if (!withScrollDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageListType === 'thread') {
|
||||
focusNextReply();
|
||||
} else {
|
||||
const messagesContainer = elementRef.current!.parentElement!.querySelector<HTMLDivElement>('.MessageList')!;
|
||||
const messagesContainer = elementRef.current!.parentElement!.querySelector<HTMLDivElement>(
|
||||
'.Transition_slide-active > .MessageList',
|
||||
)!;
|
||||
const messageElements = messagesContainer.querySelectorAll<HTMLDivElement>('.message-list-item');
|
||||
const lastMessageElement = messageElements[messageElements.length - 1];
|
||||
if (!lastMessageElement) {
|
||||
@ -85,8 +87,8 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const fabClassName = buildClassName(
|
||||
styles.root,
|
||||
(isShown || Boolean(reactionsCount) || Boolean(mentionsCount)) && styles.revealed,
|
||||
(Boolean(reactionsCount) || Boolean(mentionsCount)) && !isShown && styles.onlyReactions,
|
||||
(withScrollDown || Boolean(reactionsCount) || Boolean(mentionsCount)) && styles.revealed,
|
||||
(Boolean(reactionsCount) || Boolean(mentionsCount)) && !withScrollDown && styles.hideScrollDown,
|
||||
!canPost && styles.noComposer,
|
||||
!withExtraShift && styles.noExtraShift,
|
||||
);
|
||||
@ -118,7 +120,7 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
||||
<ScrollDownButton
|
||||
icon="arrow-down"
|
||||
ariaLabelLang="AccDescrPageDown"
|
||||
onClick={handleClick}
|
||||
onClick={handleScrollDownClick}
|
||||
unreadCount={unreadCount}
|
||||
className={styles.unread}
|
||||
/>
|
||||
|
||||
@ -90,8 +90,8 @@ type OwnProps = {
|
||||
isComments?: boolean;
|
||||
canPost: boolean;
|
||||
isReady: boolean;
|
||||
onFabToggle: (shouldShow: boolean) => void;
|
||||
onNotchToggle: (shouldShow: boolean) => void;
|
||||
onScrollDownToggle: BooleanToVoidFunction;
|
||||
onNotchToggle: BooleanToVoidFunction;
|
||||
hasTools?: boolean;
|
||||
withBottomShift?: boolean;
|
||||
withDefaultBg: boolean;
|
||||
@ -148,7 +148,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
threadId,
|
||||
type,
|
||||
hasTools,
|
||||
onFabToggle,
|
||||
onScrollDownToggle,
|
||||
onNotchToggle,
|
||||
isCurrentUserPremium,
|
||||
isChatLoaded,
|
||||
@ -597,6 +597,12 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const hasMessages = (messageIds && messageGroups) || lastMessage;
|
||||
|
||||
useEffect(() => {
|
||||
if (hasMessages) return;
|
||||
|
||||
onScrollDownToggle(false);
|
||||
}, [hasMessages, onScrollDownToggle]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
@ -650,7 +656,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
isSchedule={messageGroups ? type === 'scheduled' : false}
|
||||
shouldRenderBotInfo={isBot}
|
||||
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
||||
onFabToggle={onFabToggle}
|
||||
onScrollDownToggle={onScrollDownToggle}
|
||||
onNotchToggle={onNotchToggle}
|
||||
onPinnedIntersectionChange={onPinnedIntersectionChange}
|
||||
/>
|
||||
|
||||
@ -56,7 +56,7 @@ interface OwnProps {
|
||||
shouldRenderBotInfo?: boolean;
|
||||
noAppearanceAnimation: boolean;
|
||||
isSavedDialog?: boolean;
|
||||
onFabToggle: AnyToVoidFunction;
|
||||
onScrollDownToggle: BooleanToVoidFunction;
|
||||
onNotchToggle: AnyToVoidFunction;
|
||||
onPinnedIntersectionChange: PinnedIntersectionChangedCallback;
|
||||
}
|
||||
@ -88,7 +88,7 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
shouldRenderBotInfo,
|
||||
noAppearanceAnimation,
|
||||
isSavedDialog,
|
||||
onFabToggle,
|
||||
onScrollDownToggle,
|
||||
onNotchToggle,
|
||||
onPinnedIntersectionChange,
|
||||
}) => {
|
||||
@ -115,7 +115,7 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
getContainerHeight,
|
||||
isViewportNewest,
|
||||
isUnread,
|
||||
onFabToggle,
|
||||
onScrollDownToggle,
|
||||
onNotchToggle,
|
||||
isReady,
|
||||
);
|
||||
|
||||
@ -238,7 +238,7 @@ function MiddleColumn({
|
||||
|
||||
const lang = useLang();
|
||||
const [dropAreaState, setDropAreaState] = useState(DropAreaState.None);
|
||||
const [isFabShown, setIsFabShown] = useState<boolean | undefined>();
|
||||
const [isScrollDownShown, setIsScrollDownShown] = useState(false);
|
||||
const [isNotchShown, setIsNotchShown] = useState<boolean | undefined>();
|
||||
const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false);
|
||||
|
||||
@ -273,7 +273,9 @@ function MiddleColumn({
|
||||
&& !renderingCanRestartBot && !renderingCanStartBot && !renderingCanSubscribe && !renderingCanUnblock
|
||||
&& chatId !== TMP_CHAT_ID && !isContactRequirePremium;
|
||||
const renderingHasTools = usePrevDuringAnimation(hasTools, closeAnimationDuration);
|
||||
const renderingIsFabShown = usePrevDuringAnimation(isFabShown, closeAnimationDuration) && chatId !== TMP_CHAT_ID;
|
||||
const renderingIsScrollDownShown = usePrevDuringAnimation(
|
||||
isScrollDownShown, closeAnimationDuration,
|
||||
) && chatId !== TMP_CHAT_ID;
|
||||
const renderingIsChannel = usePrevDuringAnimation(isChannel, closeAnimationDuration);
|
||||
const renderingShouldJoinToSend = usePrevDuringAnimation(shouldJoinToSend, closeAnimationDuration);
|
||||
const renderingShouldSendJoinRequest = usePrevDuringAnimation(shouldSendJoinRequest, closeAnimationDuration);
|
||||
@ -489,9 +491,9 @@ function MiddleColumn({
|
||||
);
|
||||
const withMessageListBottomShift = Boolean(
|
||||
renderingCanRestartBot || renderingCanSubscribe || renderingShouldSendJoinRequest || renderingCanStartBot
|
||||
|| isPinnedMessageList || canShowOpenChatButton || renderingCanUnblock,
|
||||
|| (isPinnedMessageList && canUnpin) || canShowOpenChatButton || renderingCanUnblock,
|
||||
);
|
||||
const withExtraShift = Boolean(isMessagingDisabled || isSelectModeActive || isPinnedMessageList);
|
||||
const withExtraShift = Boolean(isMessagingDisabled || isSelectModeActive);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -552,7 +554,7 @@ function MiddleColumn({
|
||||
isComments={isComments}
|
||||
canPost={renderingCanPost!}
|
||||
hasTools={renderingHasTools}
|
||||
onFabToggle={setIsFabShown}
|
||||
onScrollDownToggle={setIsScrollDownShown}
|
||||
onNotchToggle={setIsNotchShown}
|
||||
isReady={isReady}
|
||||
isContactRequirePremium={isContactRequirePremium}
|
||||
@ -693,7 +695,7 @@ function MiddleColumn({
|
||||
</Transition>
|
||||
|
||||
<FloatingActionButtons
|
||||
isShown={renderingIsFabShown!}
|
||||
withScrollDown={renderingIsScrollDownShown}
|
||||
canPost={renderingCanPost}
|
||||
withExtraShift={withExtraShift}
|
||||
/>
|
||||
|
||||
@ -64,7 +64,8 @@
|
||||
top: -0.3125rem;
|
||||
right: -0.3125rem;
|
||||
|
||||
background: var(--color-green);
|
||||
background-color: var(--color-green);
|
||||
border: 1px solid var(--color-background);
|
||||
color: white;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
@ -28,8 +28,8 @@ export default function useScrollHooks(
|
||||
getContainerHeight: Signal<number | undefined>,
|
||||
isViewportNewest: boolean,
|
||||
isUnread: boolean,
|
||||
onFabToggle: AnyToVoidFunction,
|
||||
onNotchToggle: AnyToVoidFunction,
|
||||
onScrollDownToggle: BooleanToVoidFunction,
|
||||
onNotchToggle: BooleanToVoidFunction,
|
||||
isReady: boolean,
|
||||
) {
|
||||
const { loadViewportMessages } = getActions();
|
||||
@ -54,13 +54,13 @@ export default function useScrollHooks(
|
||||
if (!isReady) return;
|
||||
|
||||
if (!messageIds?.length) {
|
||||
onFabToggle(false);
|
||||
onScrollDownToggle(false);
|
||||
onNotchToggle(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isViewportNewest) {
|
||||
onFabToggle(true);
|
||||
onScrollDownToggle(true);
|
||||
onNotchToggle(true);
|
||||
return;
|
||||
}
|
||||
@ -77,7 +77,7 @@ export default function useScrollHooks(
|
||||
|
||||
if (scrollHeight === 0) return;
|
||||
|
||||
onFabToggle(isUnread ? !isAtBottom : !isNearBottom);
|
||||
onScrollDownToggle(isUnread ? !isAtBottom : !isNearBottom);
|
||||
onNotchToggle(!isAtBottom);
|
||||
});
|
||||
|
||||
|
||||
@ -410,6 +410,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
disableContextMenuHint,
|
||||
animateUnreadReaction,
|
||||
focusLastMessage,
|
||||
markMentionsRead,
|
||||
} = getActions();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
@ -795,10 +796,16 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
const bottomMarker = bottomMarkerRef.current;
|
||||
if (hasUnreadReaction && bottomMarker && isElementInViewport(bottomMarker)) {
|
||||
if (!bottomMarker || !isElementInViewport(bottomMarker)) return;
|
||||
|
||||
if (hasUnreadReaction) {
|
||||
animateUnreadReaction({ messageIds: [messageId] });
|
||||
}
|
||||
}, [hasUnreadReaction, messageId, animateUnreadReaction]);
|
||||
|
||||
if (message.hasUnreadMention) {
|
||||
markMentionsRead({ messageIds: [messageId] });
|
||||
}
|
||||
}, [hasUnreadReaction, messageId, animateUnreadReaction, message.hasUnreadMention]);
|
||||
|
||||
const albumLayout = useMemo(() => {
|
||||
return isAlbum
|
||||
|
||||
@ -1550,6 +1550,10 @@ addActionHandler('clickSponsoredMessage', (global, actions, payload): ActionRetu
|
||||
|
||||
addActionHandler('fetchUnreadMentions', async (global, actions, payload): Promise<void> => {
|
||||
const { chatId, offsetId } = payload;
|
||||
await fetchUnreadMentions(global, chatId, offsetId);
|
||||
});
|
||||
|
||||
async function fetchUnreadMentions<T extends GlobalState>(global: T, chatId: string, offsetId?: number) {
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) return;
|
||||
|
||||
@ -1571,7 +1575,7 @@ addActionHandler('fetchUnreadMentions', async (global, actions, payload): Promis
|
||||
});
|
||||
|
||||
setGlobal(global);
|
||||
});
|
||||
}
|
||||
|
||||
addActionHandler('markMentionsRead', (global, actions, payload): ActionReturnType => {
|
||||
const { messageIds, tabId = getCurrentTabId() } = payload;
|
||||
@ -1579,8 +1583,15 @@ addActionHandler('markMentionsRead', (global, actions, payload): ActionReturnTyp
|
||||
const chat = selectCurrentChat(global, tabId);
|
||||
if (!chat) return;
|
||||
|
||||
const unreadMentions = (chat.unreadMentions || []).filter((id) => !messageIds.includes(id));
|
||||
const currentUnreadMentions = chat.unreadMentions || [];
|
||||
|
||||
const unreadMentions = currentUnreadMentions.filter((id) => !messageIds.includes(id));
|
||||
const removedCount = currentUnreadMentions.length - unreadMentions.length;
|
||||
|
||||
global = updateChat(global, chat.id, {
|
||||
...(chat.unreadMentionsCount && {
|
||||
unreadMentionsCount: Math.max(chat.unreadMentionsCount - removedCount, 0) || undefined,
|
||||
}),
|
||||
unreadMentions,
|
||||
});
|
||||
|
||||
@ -1589,12 +1600,20 @@ addActionHandler('markMentionsRead', (global, actions, payload): ActionReturnTyp
|
||||
actions.markMessagesRead({ messageIds, tabId });
|
||||
});
|
||||
|
||||
addActionHandler('focusNextMention', (global, actions, payload): ActionReturnType => {
|
||||
addActionHandler('focusNextMention', async (global, actions, payload): Promise<void> => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
const chat = selectCurrentChat(global, tabId);
|
||||
let chat = selectCurrentChat(global, tabId);
|
||||
|
||||
if (!chat?.unreadMentions) return;
|
||||
if (!chat) return;
|
||||
|
||||
if (!chat.unreadMentions) {
|
||||
await fetchUnreadMentions(global, chat.id);
|
||||
global = getGlobal();
|
||||
const previousChatId = chat.id;
|
||||
chat = selectCurrentChat(global, tabId);
|
||||
if (!chat?.unreadMentions || previousChatId !== chat.id) return;
|
||||
}
|
||||
|
||||
actions.focusMessage({ chatId: chat.id, messageId: chat.unreadMentions[0], tabId });
|
||||
});
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
} from '../../helpers';
|
||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||
import {
|
||||
addChatMessagesById, addChats, addUsers, updateChatMessage,
|
||||
addChatMessagesById, addChats, addUsers, updateChat, updateChatMessage,
|
||||
} from '../../reducers';
|
||||
import { addMessageReaction, subtractXForEmojiInteraction, updateUnreadReactions } from '../../reducers/reactions';
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
@ -437,9 +437,17 @@ addActionHandler('focusNextReaction', (global, actions, payload): ActionReturnTy
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
const chat = selectCurrentChat(global, tabId);
|
||||
|
||||
if (!chat?.unreadReactions) return;
|
||||
if (!chat?.unreadReactions) {
|
||||
if (chat?.unreadReactionsCount) {
|
||||
return updateChat(global, chat.id, {
|
||||
unreadReactionsCount: 0,
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
actions.focusMessage({ chatId: chat.id, messageId: chat.unreadReactions[0], tabId });
|
||||
return undefined;
|
||||
});
|
||||
|
||||
addActionHandler('readAllReactions', (global, actions, payload): ActionReturnType => {
|
||||
|
||||
@ -7,7 +7,7 @@ import { buildCollectionByKey, omit } from '../../../util/iteratees';
|
||||
import { isLocalMessageId } from '../../../util/messageKey';
|
||||
import { closeMessageNotifications, notifyAboutMessage } from '../../../util/notifications';
|
||||
import { buildLocalMessage } from '../../../api/gramjs/apiBuilders/messages';
|
||||
import { isChatChannel } from '../../helpers';
|
||||
import { checkIfHasUnreadReactions, isChatChannel } from '../../helpers';
|
||||
import {
|
||||
addActionHandler, getGlobal, setGlobal,
|
||||
} from '../../index';
|
||||
@ -206,24 +206,22 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
case 'updateCommonBoxMessages':
|
||||
case 'updateChannelMessages': {
|
||||
const { ids, messageUpdate } = update;
|
||||
if (messageUpdate.hasUnreadMention !== false) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
ids.forEach((id) => {
|
||||
const chatId = ('channelId' in update ? update.channelId : selectCommonBoxChatId(global, id))!;
|
||||
const chat = selectChat(global, chatId);
|
||||
|
||||
if (chat?.unreadReactionsCount) {
|
||||
if (messageUpdate.reactions && chat?.unreadReactionsCount
|
||||
&& !checkIfHasUnreadReactions(global, messageUpdate.reactions)) {
|
||||
global = updateUnreadReactions(global, chatId, {
|
||||
unreadReactionsCount: (chat.unreadReactionsCount - 1) || undefined,
|
||||
unreadReactionsCount: Math.max(chat.unreadReactionsCount - 1, 0) || undefined,
|
||||
unreadReactions: chat.unreadReactions?.filter((i) => i !== id),
|
||||
});
|
||||
}
|
||||
|
||||
if (chat?.unreadMentionsCount) {
|
||||
if (!messageUpdate.hasUnreadMention && chat?.unreadMentionsCount) {
|
||||
global = updateChat(global, chatId, {
|
||||
unreadMentionsCount: (chat.unreadMentionsCount - 1) || undefined,
|
||||
unreadMentionsCount: Math.max(chat.unreadMentionsCount - 1, 0) || undefined,
|
||||
unreadMentions: chat.unreadMentions?.filter((i) => i !== id),
|
||||
});
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import useForceUpdate from './useForceUpdate';
|
||||
import usePrevious from './usePrevious';
|
||||
import useSyncEffect from './useSyncEffect';
|
||||
|
||||
export default function usePrevDuringAnimation<T>(current: T, duration?: number) {
|
||||
export default function usePrevDuringAnimation<T>(current: T, duration?: number): T {
|
||||
const prev = usePrevious(current, true);
|
||||
const timeoutRef = useRef<number>();
|
||||
const forceUpdate = useForceUpdate();
|
||||
@ -28,5 +28,5 @@ export default function usePrevDuringAnimation<T>(current: T, duration?: number)
|
||||
}
|
||||
}, [duration, forceUpdate, isCurrentPresent, isPrevPresent]);
|
||||
|
||||
return !timeoutRef.current || !duration || isCurrentPresent ? current : prev;
|
||||
return (!timeoutRef.current || !duration || isCurrentPresent ? current : prev)!;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user