Chat: Add mini app button (#5263)

This commit is contained in:
zubiden 2024-12-06 19:43:53 +04:00 committed by Alexander Zinchuk
parent 7fd8062f1a
commit 81386ba911
5 changed files with 87 additions and 9 deletions

View File

@ -21,7 +21,6 @@ import { StoryViewerOrigin } from '../../../types';
import {
getMessageAction,
getPrivateChatUserId,
groupStatetefulContent,
isUserId,
isUserOnline,
@ -367,6 +366,7 @@ const Chat: FC<OwnProps & StateProps> = ({
shouldShowOnlyMostImportant
forceHidden={getIsForumPanelClosed}
topics={topics}
isSelected={isSelected}
/>
</div>
{chat.isCallActive && chat.isCallNotEmpty && (
@ -400,7 +400,9 @@ const Chat: FC<OwnProps & StateProps> = ({
isPinned={isPinned}
isMuted={isMuted}
isSavedDialog={isSavedDialog}
hasMiniApp={user?.hasMainMiniApp}
topics={topics}
isSelected={isSelected}
/>
)}
</div>
@ -439,6 +441,7 @@ export default memo(withGlobal<OwnProps>(
chatId, isSavedDialog, isPreview, previewMessageId,
}): StateProps => {
const chat = selectChat(global, chatId);
const user = selectUser(global, chatId);
if (!chat) {
return {
currentUserId: global.currentUserId!,
@ -458,7 +461,6 @@ export default memo(withGlobal<OwnProps>(
? selectChatMessage(global, chat.id, replyToMessageId)
: undefined;
const { targetUserIds: actionTargetUserIds, targetChatId: actionTargetChatId } = lastMessageAction || {};
const privateChatUserId = getPrivateChatUserId(chat);
const {
chatId: currentChatId,
@ -470,8 +472,7 @@ export default memo(withGlobal<OwnProps>(
const isSelectedForum = (chat.isForum && chatId === currentChatId)
|| chatId === selectTabState(global).forumPanelChatId;
const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
const userStatus = privateChatUserId ? selectUserStatus(global, privateChatUserId) : undefined;
const userStatus = selectUserStatus(global, chatId);
const lastMessageTopic = lastMessage && selectTopicFromMessage(global, lastMessage);
const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus');

View File

@ -97,4 +97,8 @@
}
}
}
&.miniapp {
z-index: calc(var(--z-chat-ripple) + 1);
}
}

View File

@ -1,5 +1,6 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useMemo } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { ApiChat, ApiTopic } from '../../../api/types';
import type { Signal } from '../../../util/signals';
@ -7,10 +8,14 @@ import type { Signal } from '../../../util/signals';
import buildClassName from '../../../util/buildClassName';
import { isSignal } from '../../../util/signals';
import { formatIntegerCompact } from '../../../util/textFormat';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
import useDerivedState from '../../../hooks/useDerivedState';
import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang';
import AnimatedCounter from '../../common/AnimatedCounter';
import Button from '../../ui/Button';
import ShowTransition from '../../ui/ShowTransition';
import './ChatBadge.scss';
@ -23,8 +28,10 @@ type OwnProps = {
isMuted?: boolean;
isSavedDialog?: boolean;
shouldShowOnlyMostImportant?: boolean;
hasMiniApp?: boolean;
forceHidden?: boolean | Signal<boolean>;
topics?: Record<number, ApiTopic>;
isSelected?: boolean;
};
const ChatBadge: FC<OwnProps> = ({
@ -37,7 +44,13 @@ const ChatBadge: FC<OwnProps> = ({
wasTopicOpened,
forceHidden,
isSavedDialog,
hasMiniApp,
isSelected,
}) => {
const { requestMainWebView } = getActions();
const oldLang = useOldLang();
const {
unreadMentionsCount = 0, unreadReactionsCount = 0,
} = !chat.isForum ? chat : {}; // TODO[forums] Unread mentions and reactions temporarily disabled for forums
@ -71,7 +84,7 @@ const ChatBadge: FC<OwnProps> = ({
);
const isShown = !resolvedForceHidden && Boolean(
unreadCount || unreadMentionsCount || hasUnreadMark || isPinned || unreadReactionsCount
|| isTopicUnopened,
|| isTopicUnopened || hasMiniApp,
);
const isUnread = Boolean((unreadCount || hasUnreadMark) && !isSavedDialog);
@ -82,6 +95,18 @@ const ChatBadge: FC<OwnProps> = ({
isUnread && 'unread',
);
const handleOpenApp = useLastCallback((e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
const theme = extractCurrentThemeParams();
requestMainWebView({
botId: chat.id,
peerId: chat.id,
theme,
shouldMarkBotTrusted: true,
});
});
function renderContent() {
const unreadReactionsElement = unreadReactionsCount && (
<div className={buildClassName('ChatBadge reaction', shouldBeMuted && 'muted')}>
@ -111,6 +136,18 @@ const ChatBadge: FC<OwnProps> = ({
</div>
);
const miniAppButton = hasMiniApp && (
<Button
color={isSelected ? 'secondary' : 'primary'}
className="ChatBadge miniapp"
pill
size="tiny"
onClick={handleOpenApp}
>
{oldLang('BotOpen')}
</Button>
);
const visiblePinnedElement = !unreadCountElement && !unreadMentionsElement && !unreadReactionsElement
&& pinnedElement;
@ -120,6 +157,9 @@ const ChatBadge: FC<OwnProps> = ({
if (isSavedDialog) return pinnedElement;
// Show only if empty or have pinned icon
if (hasMiniApp && (elements.length === 0 || visiblePinnedElement)) return miniAppButton;
if (elements.length === 0) return undefined;
if (elements.length === 1) return elements[0];

View File

@ -1,6 +1,6 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback } from '../../../lib/teact/teact';
import { withGlobal } from '../../../global';
import { getActions, withGlobal } from '../../../global';
import type { ApiChat, ApiUser } from '../../../api/types';
import { StoryViewerOrigin } from '../../../types';
@ -10,13 +10,17 @@ import {
selectChat, selectIsChatPinned, selectNotifyExceptions,
selectNotifySettings, selectUser,
} from '../../../global/selectors';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
import useChatContextActions from '../../../hooks/useChatContextActions';
import useFlag from '../../../hooks/useFlag';
import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang';
import useSelectWithEnter from '../../../hooks/useSelectWithEnter';
import GroupChatInfo from '../../common/GroupChatInfo';
import PrivateChatInfo from '../../common/PrivateChatInfo';
import Button from '../../ui/Button';
import ListItem from '../../ui/ListItem';
import ChatFolderModal from '../ChatFolderModal.async';
import MuteChatModal from '../MuteChatModal.async';
@ -24,6 +28,7 @@ import MuteChatModal from '../MuteChatModal.async';
type OwnProps = {
chatId: string;
withUsername?: boolean;
isRecent?: boolean;
onClick: (id: string) => void;
};
@ -38,13 +43,17 @@ type StateProps = {
const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
chatId,
withUsername,
onClick,
chat,
user,
isPinned,
isMuted,
canChangeFolder,
isRecent,
onClick,
}) => {
const { requestMainWebView } = getActions();
const oldLang = useOldLang();
const [isMuteModalOpen, openMuteModal, closeMuteModal] = useFlag();
const [isChatFolderModalOpen, openChatFolderModal, closeChatFolderModal] = useFlag();
const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag();
@ -70,9 +79,21 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
handleChatFolderChange,
}, true);
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
onClick(chatId);
}, [chatId, onClick]);
});
const handleOpenApp = useLastCallback((e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
const theme = extractCurrentThemeParams();
requestMainWebView({
botId: chatId,
peerId: chatId,
theme,
shouldMarkBotTrusted: true,
});
});
const buttonRef = useSelectWithEnter(handleClick);
@ -100,6 +121,17 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
storyViewerOrigin={StoryViewerOrigin.SearchResult}
/>
)}
{isRecent && user?.hasMainMiniApp && (
<Button
className="ChatBadge miniapp"
pill
fluid
size="tiny"
onClick={handleOpenApp}
>
{oldLang('BotOpen')}
</Button>
)}
{shouldRenderMuteModal && (
<MuteChatModal
isOpen={isMuteModalOpen}

View File

@ -119,6 +119,7 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
{recentlyFoundChatIds.map((id) => (
<LeftSearchResultChat
chatId={id}
isRecent
onClick={handleClick}
/>
))}