Message List: Show bot info when messages are present (#3353)

This commit is contained in:
Alexander Zinchuk 2023-07-05 13:14:47 +02:00
parent e9f59531ec
commit e616f82639
5 changed files with 168 additions and 111 deletions

View File

@ -217,29 +217,6 @@
text-align: left; text-align: left;
} }
& > .bot-info {
max-width: 80%;
display: flex;
flex-direction: column;
background: var(--pattern-color);
color: white;
font-size: calc(var(--message-text-size, 1rem) - 0.0625rem);
line-height: 1.75;
border-radius: var(--border-radius-messages);
overflow: hidden;
text-align: initial;
& > .bot-info-description {
padding: 0.5rem 1rem;
}
.bot-info-title {
font-weight: 500;
margin-bottom: 0.25rem;
}
}
.text-entity-link { .text-entity-link {
color: inherit !important; color: inherit !important;
text-decoration: underline; text-decoration: underline;

View File

@ -10,7 +10,7 @@ import { requestForcedReflow, forceMeasure, requestMeasure } from '../../lib/fas
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../global'; import { getActions, getGlobal, withGlobal } from '../../global';
import type { import type {
ApiBotInfo, ApiMessage, ApiRestrictionReason, ApiTopic, ApiMessage, ApiRestrictionReason, ApiTopic,
} from '../../api/types'; } from '../../api/types';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
@ -33,7 +33,6 @@ import {
selectIsInSelectMode, selectIsInSelectMode,
selectIsChatWithSelf, selectIsChatWithSelf,
selectBot, selectBot,
selectIsChatBotNotStarted,
selectScrollOffset, selectScrollOffset,
selectThreadTopMessageId, selectThreadTopMessageId,
selectChatScheduledMessages, selectChatScheduledMessages,
@ -42,7 +41,6 @@ import {
selectLastScrollOffset, selectLastScrollOffset,
selectThreadInfo, selectThreadInfo,
selectTabState, selectTabState,
selectUserFullInfo,
selectChatFullInfo, selectChatFullInfo,
selectPerformanceSettingsValue, selectPerformanceSettingsValue,
} from '../../global/selectors'; } from '../../global/selectors';
@ -51,31 +49,23 @@ import {
isUserId, isUserId,
isChatWithRepliesBot, isChatWithRepliesBot,
isChatGroup, isChatGroup,
getBotCoverMediaHash,
getDocumentMediaHash,
getVideoDimensions,
getPhotoFullDimensions,
isLocalMessageId, isLocalMessageId,
} from '../../global/helpers'; } from '../../global/helpers';
import { orderBy } from '../../util/iteratees'; import { orderBy } from '../../util/iteratees';
import { DPR } from '../../util/windowEnvironment';
import { debounce, onTickEnd } from '../../util/schedulers'; import { debounce, onTickEnd } from '../../util/schedulers';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { groupMessages } from './helpers/groupMessages'; import { groupMessages } from './helpers/groupMessages';
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur'; import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
import resetScroll from '../../util/resetScroll'; import resetScroll from '../../util/resetScroll';
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll'; import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
import renderText from '../common/helpers/renderText';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
import { useStateRef } from '../../hooks/useStateRef'; import { useStateRef } from '../../hooks/useStateRef';
import useSyncEffect from '../../hooks/useSyncEffect'; import useSyncEffect from '../../hooks/useSyncEffect';
import useStickyDates from './hooks/useStickyDates'; import useStickyDates from './hooks/useStickyDates';
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import useLang from '../../hooks/useLang';
import useInterval from '../../hooks/useInterval'; import useInterval from '../../hooks/useInterval';
import useNativeCopySelectedMessages from '../../hooks/useNativeCopySelectedMessages'; import useNativeCopySelectedMessages from '../../hooks/useNativeCopySelectedMessages';
import useMedia from '../../hooks/useMedia';
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps'; import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
import useContainerHeight from './hooks/useContainerHeight'; import useContainerHeight from './hooks/useContainerHeight';
@ -85,8 +75,7 @@ import Loading from '../ui/Loading';
import MessageListContent from './MessageListContent'; import MessageListContent from './MessageListContent';
import ContactGreeting from './ContactGreeting'; import ContactGreeting from './ContactGreeting';
import NoMessages from './NoMessages'; import NoMessages from './NoMessages';
import Skeleton from '../ui/Skeleton'; import MessageListBotInfo from './MessageListBotInfo';
import OptimizedVideo from '../ui/OptimizedVideo';
import './MessageList.scss'; import './MessageList.scss';
@ -124,8 +113,6 @@ type StateProps = {
focusingId?: number; focusingId?: number;
isSelectModeActive?: boolean; isSelectModeActive?: boolean;
lastMessage?: ApiMessage; lastMessage?: ApiMessage;
isLoadingBotInfo?: boolean;
botInfo?: ApiBotInfo;
threadTopMessageId?: number; threadTopMessageId?: number;
hasLinkedChat?: boolean; hasLinkedChat?: boolean;
topic?: ApiTopic; topic?: ApiTopic;
@ -173,8 +160,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
focusingId, focusingId,
isSelectModeActive, isSelectModeActive,
lastMessage, lastMessage,
isLoadingBotInfo,
botInfo,
threadTopMessageId, threadTopMessageId,
hasLinkedChat, hasLinkedChat,
withBottomShift, withBottomShift,
@ -210,15 +195,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
const isScrollTopJustUpdatedRef = useRef(false); const isScrollTopJustUpdatedRef = useRef(false);
const shouldAnimateAppearanceRef = useRef(Boolean(lastMessage)); const shouldAnimateAppearanceRef = useRef(Boolean(lastMessage));
const botInfoPhotoUrl = useMedia(botInfo?.photo ? getBotCoverMediaHash(botInfo.photo) : undefined);
const botInfoGifUrl = useMedia(botInfo?.gif ? getDocumentMediaHash(botInfo.gif) : undefined);
const botInfoDimensions = botInfo?.photo ? getPhotoFullDimensions(botInfo.photo) : botInfo?.gif
? getVideoDimensions(botInfo.gif) : undefined;
const botInfoRealDimensions = botInfoDimensions && {
width: botInfoDimensions.width / DPR,
height: botInfoDimensions.height / DPR,
};
const areMessagesLoaded = Boolean(messageIds); const areMessagesLoaded = Boolean(messageIds);
useSyncEffect(() => { useSyncEffect(() => {
@ -533,8 +509,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
} }
}, [isSelectModeActive]); }, [isSelectModeActive]);
const lang = useLang();
const isPrivate = Boolean(chatId && isUserId(chatId)); const isPrivate = Boolean(chatId && isUserId(chatId));
const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf || isRepliesChat); const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf || isRepliesChat);
const noAvatars = Boolean(!withUsers || isChannelChat); const noAvatars = Boolean(!withUsers || isChannelChat);
@ -554,8 +528,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
const isEmptyTopic = messageIds?.length === 1 const isEmptyTopic = messageIds?.length === 1
&& messagesById?.[messageIds[0]]?.content.action?.type === 'topicCreate'; && messagesById?.[messageIds[0]]?.content.action?.type === 'topicCreate';
const isBotInfoEmpty = botInfo && !botInfo.description && !botInfo.gif && !botInfo.photo;
const className = buildClassName( const className = buildClassName(
'MessageList custom-scroll', 'MessageList custom-scroll',
noAvatars && 'no-avatars', noAvatars && 'no-avatars',
@ -568,6 +540,8 @@ const MessageList: FC<OwnProps & StateProps> = ({
!isReady && 'is-animating', !isReady && 'is-animating',
); );
const hasMessages = (messageIds && messageGroups) || lastMessage;
return ( return (
<div <div
ref={containerRef} ref={containerRef}
@ -581,50 +555,8 @@ const MessageList: FC<OwnProps & StateProps> = ({
{restrictionReason ? restrictionReason.text : `This is a private ${isChannelChat ? 'channel' : 'chat'}`} {restrictionReason ? restrictionReason.text : `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
</span> </span>
</div> </div>
) : botInfo ? ( ) : isBot && !hasMessages ? (
<div className="empty"> <MessageListBotInfo chatId={chatId} />
{isLoadingBotInfo && <span>{lang('Loading')}</span>}
{isBotInfoEmpty && !isLoadingBotInfo && <span>{lang('NoMessages')}</span>}
{botInfo && (
<div
className="bot-info"
style={botInfoRealDimensions && (
`width: ${botInfoRealDimensions.width}px`
)}
>
{botInfoPhotoUrl && (
<img
src={botInfoPhotoUrl}
width={botInfoRealDimensions?.width}
height={botInfoRealDimensions?.height}
alt="Bot info"
/>
)}
{botInfoGifUrl && (
<OptimizedVideo
canPlay
src={botInfoGifUrl}
loop
disablePictureInPicture
muted
playsInline
/>
)}
{botInfoDimensions && !botInfoPhotoUrl && !botInfoGifUrl && (
<Skeleton
width={botInfoRealDimensions?.width}
height={botInfoRealDimensions?.height}
/>
)}
{botInfo.description && (
<div className="bot-info-description">
<p className="bot-info-title">{lang('BotInfoTitle')}</p>
{renderText(botInfo.description, ['br', 'emoji', 'links'])}
</div>
)}
</div>
)}
</div>
) : shouldRenderGreeting ? ( ) : shouldRenderGreeting ? (
<ContactGreeting userId={chatId} /> <ContactGreeting userId={chatId} />
) : messageIds && (!messageGroups || isGroupChatJustCreated || isEmptyTopic) ? ( ) : messageIds && (!messageGroups || isGroupChatJustCreated || isEmptyTopic) ? (
@ -635,7 +567,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
isChatWithSelf={isChatWithSelf} isChatWithSelf={isChatWithSelf}
isGroupChatJustCreated={isGroupChatJustCreated} isGroupChatJustCreated={isGroupChatJustCreated}
/> />
) : ((messageIds && messageGroups) || lastMessage) ? ( ) : hasMessages ? (
<MessageListContent <MessageListContent
isCurrentUserPremium={isCurrentUserPremium} isCurrentUserPremium={isCurrentUserPremium}
chatId={chatId} chatId={chatId}
@ -658,6 +590,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
threadTopMessageId={threadTopMessageId} threadTopMessageId={threadTopMessageId}
hasLinkedChat={hasLinkedChat} hasLinkedChat={hasLinkedChat}
isSchedule={messageGroups ? type === 'scheduled' : false} isSchedule={messageGroups ? type === 'scheduled' : false}
shouldRenderBotInfo={isBot}
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current} noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
onFabToggle={onFabToggle} onFabToggle={onFabToggle}
onNotchToggle={onNotchToggle} onNotchToggle={onNotchToggle}
@ -699,17 +632,7 @@ export default memo(withGlobal<OwnProps>(
&& !messageIds && !chat.unreadCount && !focusingId && lastMessage && !lastMessage.groupedId && !messageIds && !chat.unreadCount && !focusingId && lastMessage && !lastMessage.groupedId
); );
const chatBot = selectBot(global, chatId)!; const chatBot = selectBot(global, chatId);
let isLoadingBotInfo = false;
let botInfo;
if (selectIsChatBotNotStarted(global, chatId)) {
const chatBotFullInfo = selectUserFullInfo(global, chatBot.id);
if (chatBotFullInfo) {
botInfo = chatBotFullInfo.botInfo;
} else {
isLoadingBotInfo = true;
}
}
const topic = chat.topics?.[threadId]; const topic = chat.topics?.[threadId];
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined; const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
@ -732,8 +655,6 @@ export default memo(withGlobal<OwnProps>(
isViewportNewest: type !== 'thread' || selectIsViewportNewest(global, chatId, threadId), isViewportNewest: type !== 'thread' || selectIsViewportNewest(global, chatId, threadId),
focusingId, focusingId,
isSelectModeActive: selectIsInSelectMode(global), isSelectModeActive: selectIsInSelectMode(global),
isLoadingBotInfo,
botInfo,
threadTopMessageId, threadTopMessageId,
hasLinkedChat: chatFullInfo ? Boolean(chatFullInfo.linkedChatId) : undefined, hasLinkedChat: chatFullInfo ? Boolean(chatFullInfo.linkedChatId) : undefined,
topic, topic,

View File

@ -0,0 +1,30 @@
.root {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.bot-info {
max-width: 80%;
display: flex;
flex-direction: column;
background: var(--pattern-color);
color: white;
font-size: calc(var(--message-text-size, 1rem) - 0.0625rem);
line-height: 1.75;
border-radius: var(--border-radius-messages);
overflow: hidden;
text-align: initial;
}
.bot-info-description {
padding: 0.5rem 1rem;
}
.bot-info-title {
font-weight: 500;
margin-bottom: 0.25rem;
}

View File

@ -0,0 +1,125 @@
import React, { memo } from '../../lib/teact/teact';
import { withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { ApiBotInfo } from '../../api/types';
import { DPR } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText';
import {
getBotCoverMediaHash,
getDocumentMediaHash,
getPhotoFullDimensions,
getVideoDimensions,
} from '../../global/helpers';
import buildStyle from '../../util/buildStyle';
import buildClassName from '../../util/buildClassName';
import { selectBot, selectUserFullInfo } from '../../global/selectors';
import useMedia from '../../hooks/useMedia';
import useLang from '../../hooks/useLang';
import OptimizedVideo from '../ui/OptimizedVideo';
import Skeleton from '../ui/Skeleton';
import styles from './MessageListBotInfo.module.scss';
type OwnProps = {
chatId: string;
isInMessageList?: boolean;
};
type StateProps = {
botInfo?: ApiBotInfo;
isLoadingBotInfo?: boolean;
};
const MessageListBotInfo: FC<OwnProps & StateProps> = ({
botInfo,
isLoadingBotInfo,
isInMessageList,
}) => {
const lang = useLang();
const botInfoPhotoUrl = useMedia(botInfo?.photo ? getBotCoverMediaHash(botInfo.photo) : undefined);
const botInfoGifUrl = useMedia(botInfo?.gif ? getDocumentMediaHash(botInfo.gif) : undefined);
const botInfoDimensions = botInfo?.photo ? getPhotoFullDimensions(botInfo.photo) : botInfo?.gif
? getVideoDimensions(botInfo.gif) : undefined;
const botInfoRealDimensions = botInfoDimensions && {
width: botInfoDimensions.width / DPR,
height: botInfoDimensions.height / DPR,
};
const isBotInfoEmpty = botInfo && !botInfo.description && !botInfo.gif && !botInfo.photo;
const { width, height } = botInfoRealDimensions || {};
const isEmptyOrLoading = isBotInfoEmpty || isLoadingBotInfo;
if (isEmptyOrLoading && isInMessageList) return undefined;
return (
<div className={buildClassName(styles.root, 'empty')}>
{isLoadingBotInfo && <span>{lang('Loading')}</span>}
{isBotInfoEmpty && !isLoadingBotInfo && <span>{lang('NoMessages')}</span>}
{botInfo && (
<div
className={styles.botInfo}
style={botInfoRealDimensions && (
`width: ${botInfoRealDimensions.width}px`
)}
>
{botInfoPhotoUrl && (
<img
src={botInfoPhotoUrl}
width={botInfoRealDimensions?.width}
height={botInfoRealDimensions?.height}
alt="Bot info"
/>
)}
{botInfoGifUrl && (
<OptimizedVideo
canPlay
src={botInfoGifUrl}
loop
disablePictureInPicture
muted
playsInline
style={buildStyle(Boolean(width) && `width: ${width}px`, Boolean(height) && `height: ${height}px`)}
/>
)}
{botInfoDimensions && !botInfoPhotoUrl && !botInfoGifUrl && (
<Skeleton
width={botInfoRealDimensions?.width}
height={botInfoRealDimensions?.height}
/>
)}
{botInfo.description && (
<div className={styles.botInfoDescription}>
<p className={styles.botInfoTitle}>{lang('BotInfoTitle')}</p>
{renderText(botInfo.description, ['br', 'emoji', 'links'])}
</div>
)}
</div>
)}
</div>
);
};
export default memo(withGlobal<OwnProps>(
(global, { chatId }) => {
const chatBot = selectBot(global, chatId);
let isLoadingBotInfo = false;
let botInfo;
if (chatBot) {
const chatBotFullInfo = selectUserFullInfo(global, chatBot.id);
if (chatBotFullInfo) {
botInfo = chatBotFullInfo.botInfo;
} else {
isLoadingBotInfo = true;
}
}
return {
botInfo,
isLoadingBotInfo,
};
},
)(MessageListBotInfo));

View File

@ -26,6 +26,7 @@ import usePrevious from '../../hooks/usePrevious';
import Message from './message/Message'; import Message from './message/Message';
import SponsoredMessage from './message/SponsoredMessage'; import SponsoredMessage from './message/SponsoredMessage';
import ActionMessage from './ActionMessage'; import ActionMessage from './ActionMessage';
import MessageListBotInfo from './MessageListBotInfo';
interface OwnProps { interface OwnProps {
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
@ -49,6 +50,7 @@ interface OwnProps {
threadTopMessageId: number | undefined; threadTopMessageId: number | undefined;
hasLinkedChat: boolean | undefined; hasLinkedChat: boolean | undefined;
isSchedule: boolean; isSchedule: boolean;
shouldRenderBotInfo?: boolean;
noAppearanceAnimation: boolean; noAppearanceAnimation: boolean;
onFabToggle: AnyToVoidFunction; onFabToggle: AnyToVoidFunction;
onNotchToggle: AnyToVoidFunction; onNotchToggle: AnyToVoidFunction;
@ -79,6 +81,7 @@ const MessageListContent: FC<OwnProps> = ({
threadTopMessageId, threadTopMessageId,
hasLinkedChat, hasLinkedChat,
isSchedule, isSchedule,
shouldRenderBotInfo,
noAppearanceAnimation, noAppearanceAnimation,
onFabToggle, onFabToggle,
onNotchToggle, onNotchToggle,
@ -275,6 +278,7 @@ const MessageListContent: FC<OwnProps> = ({
return ( return (
<div className="messages-container" teactFastList> <div className="messages-container" teactFastList>
{withHistoryTriggers && <div ref={backwardsTriggerRef} key="backwards-trigger" className="backwards-trigger" />} {withHistoryTriggers && <div ref={backwardsTriggerRef} key="backwards-trigger" className="backwards-trigger" />}
{shouldRenderBotInfo && <MessageListBotInfo isInMessageList key={`bot_info_${chatId}`} chatId={chatId} />}
{dateGroups.flat()} {dateGroups.flat()}
{!isCurrentUserPremium && isViewportNewest && ( {!isCurrentUserPremium && isViewportNewest && (
<SponsoredMessage key={chatId} chatId={chatId} containerRef={containerRef} /> <SponsoredMessage key={chatId} chatId={chatId} containerRef={containerRef} />