Message Context Menu: Implement Gift Button (#5384)

This commit is contained in:
Alexander Zinchuk 2025-01-03 17:15:28 +01:00
parent 4daf327b38
commit e42b148721
3 changed files with 46 additions and 3 deletions

View File

@ -32,6 +32,8 @@ import {
getIsDownloading, getIsDownloading,
getMessageDownloadableMedia, getMessageDownloadableMedia,
getMessageVideo, getMessageVideo,
getPrivateChatUserId,
getUserFullName,
hasMessageTtl, hasMessageTtl,
isActionMessage, isActionMessage,
isChatChannel, isChatChannel,
@ -46,6 +48,7 @@ import {
selectAllowedMessageActionsSlow, selectAllowedMessageActionsSlow,
selectBot, selectBot,
selectCanForwardMessage, selectCanForwardMessage,
selectCanGift,
selectCanPlayAnimatedEmojis, selectCanPlayAnimatedEmojis,
selectCanScheduleUntilOnline, selectCanScheduleUntilOnline,
selectCanTranslateMessage, selectCanTranslateMessage,
@ -67,6 +70,7 @@ import {
selectStickerSet, selectStickerSet,
selectThreadInfo, selectThreadInfo,
selectTopic, selectTopic,
selectUser,
selectUserStatus, selectUserStatus,
} from '../../../global/selectors'; } from '../../../global/selectors';
import { copyTextToClipboard } from '../../../util/clipboard'; import { copyTextToClipboard } from '../../../util/clipboard';
@ -149,6 +153,8 @@ type StateProps = {
isChannel?: boolean; isChannel?: boolean;
canReplyInChat?: boolean; canReplyInChat?: boolean;
isWithPaidReaction?: boolean; isWithPaidReaction?: boolean;
contactUserFullName?: string;
canGift?: boolean;
}; };
const selection = window.getSelection(); const selection = window.getSelection();
@ -214,6 +220,8 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
isWithPaidReaction, isWithPaidReaction,
onClose, onClose,
onCloseAnimationEnd, onCloseAnimationEnd,
contactUserFullName,
canGift,
}) => { }) => {
const { const {
openThread, openThread,
@ -690,6 +698,8 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
onTranslate={handleTranslate} onTranslate={handleTranslate}
onShowOriginal={handleShowOriginal} onShowOriginal={handleShowOriginal}
onSelectLanguage={handleSelectLanguage} onSelectLanguage={handleSelectLanguage}
contactUserFullName={contactUserFullName}
canGift={canGift}
/> />
<PinMessageModal <PinMessageModal
isOpen={isPinModalOpen} isOpen={isPinModalOpen}
@ -719,6 +729,9 @@ export default memo(withGlobal<OwnProps>(
const chat = selectChat(global, message.chatId); const chat = selectChat(global, message.chatId);
const isPrivate = chat && isUserId(chat.id); const isPrivate = chat && isUserId(chat.id);
const chatFullInfo = !isPrivate ? selectChatFullInfo(global, message.chatId) : undefined; const chatFullInfo = !isPrivate ? selectChatFullInfo(global, message.chatId) : undefined;
const contactUserFullName = chat && isUserId(chat.id)
? getUserFullName(selectUser(global, getPrivateChatUserId(chat)!))
: undefined;
const { const {
seenByExpiresAt, seenByMaxChatMembers, maxUniqueReactions, readDateExpiresAt, seenByExpiresAt, seenByMaxChatMembers, maxUniqueReactions, readDateExpiresAt,
@ -815,6 +828,8 @@ export default memo(withGlobal<OwnProps>(
const storyData = message.content.storyData; const storyData = message.content.storyData;
const story = storyData ? selectPeerStory(global, storyData.peerId, storyData.id) : undefined; const story = storyData ? selectPeerStory(global, storyData.peerId, storyData.id) : undefined;
const canGift = selectCanGift(global, message.chatId);
return { return {
threadId, threadId,
chat, chat,
@ -868,6 +883,8 @@ export default memo(withGlobal<OwnProps>(
isWithPaidReaction: chatFullInfo?.isPaidReactionAvailable, isWithPaidReaction: chatFullInfo?.isPaidReactionAvailable,
poll, poll,
story, story,
contactUserFullName,
canGift,
}; };
}, },
)(ContextMenuContainer)); )(ContextMenuContainer));

View File

@ -19,7 +19,11 @@ import type {
} from '../../../api/types'; } from '../../../api/types';
import type { IAnchorPosition } from '../../../types'; import type { IAnchorPosition } from '../../../types';
import { getUserFullName, groupStatetefulContent, isUserId } from '../../../global/helpers'; import {
getUserFullName,
groupStatetefulContent,
isUserId,
} from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { disableScrolling } from '../../../util/scrollLock'; import { disableScrolling } from '../../../util/scrollLock';
import { REM } from '../../common/helpers/mediaDimensions'; import { REM } from '../../common/helpers/mediaDimensions';
@ -125,6 +129,8 @@ type OwnProps = {
onSendPaidReaction?: NoneToVoidFunction; onSendPaidReaction?: NoneToVoidFunction;
onShowPaidReactionModal?: NoneToVoidFunction; onShowPaidReactionModal?: NoneToVoidFunction;
onReactionPickerOpen?: (position: IAnchorPosition) => void; onReactionPickerOpen?: (position: IAnchorPosition) => void;
contactUserFullName?: string;
canGift?: boolean;
}; };
const SCROLLBAR_WIDTH = 10; const SCROLLBAR_WIDTH = 10;
@ -214,9 +220,11 @@ const MessageContextMenu: FC<OwnProps> = ({
onTranslate, onTranslate,
onShowOriginal, onShowOriginal,
onSelectLanguage, onSelectLanguage,
contactUserFullName,
canGift,
}) => { }) => {
const { const {
showNotification, openStickerSet, openCustomEmojiSets, loadStickers, showNotification, openStickerSet, openCustomEmojiSets, loadStickers, openGiftModal,
} = getActions(); } = getActions();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
@ -227,6 +235,12 @@ const MessageContextMenu: FC<OwnProps> = ({
const withReactions = canShowReactionList && !noReactions; const withReactions = canShowReactionList && !noReactions;
const isEdited = ('isEdited' in message) && message.isEdited; const isEdited = ('isEdited' in message) && message.isEdited;
const seenByDates = message.seenByDates; const seenByDates = message.seenByDates;
const isPremiumGift = message.content.action?.type === 'giftPremium';
const isGiftCode = message.content.action?.type === 'giftCode';
const isStarsGift = message.content.action?.type === 'giftStars';
const isStarGift = message.content.action?.type === 'starGift';
const shouldShowGiftButton = isUserId(message.chatId)
&& canGift && (isPremiumGift || isGiftCode || isStarsGift || isStarGift);
const [areItemsHidden, hideItems] = useFlag(); const [areItemsHidden, hideItems] = useFlag();
const [isReady, markIsReady, unmarkIsReady] = useFlag(); const [isReady, markIsReady, unmarkIsReady] = useFlag();
@ -240,6 +254,11 @@ const MessageContextMenu: FC<OwnProps> = ({
onClose(); onClose();
}); });
const handleGiftClick = useLastCallback(() => {
openGiftModal({ forUserId: message.chatId });
onClose();
});
useEffect(() => { useEffect(() => {
if (isOpen && areItemsHidden && !isReactionPickerOpen) { if (isOpen && areItemsHidden && !isReactionPickerOpen) {
onClose(); onClose();
@ -379,6 +398,13 @@ const MessageContextMenu: FC<OwnProps> = ({
)} )}
dir={lang.isRtl ? 'rtl' : undefined} dir={lang.isRtl ? 'rtl' : undefined}
> >
{shouldShowGiftButton
&& (
<MenuItem icon="gift" onClick={handleGiftClick}>
{message?.isOutgoing ? lang('SendAnotherGift')
: lang('Conversation.ContextMenuSendGiftTo', contactUserFullName)}
</MenuItem>
)}
{canSendNow && <MenuItem icon="send-outline" onClick={onSend}>{lang('MessageScheduleSend')}</MenuItem>} {canSendNow && <MenuItem icon="send-outline" onClick={onSend}>{lang('MessageScheduleSend')}</MenuItem>}
{canReschedule && ( {canReschedule && (
<MenuItem icon="schedule" onClick={onReschedule}>{lang('MessageScheduleEditTime')}</MenuItem> <MenuItem icon="schedule" onClick={onReschedule}>{lang('MessageScheduleEditTime')}</MenuItem>

View File

@ -52,7 +52,7 @@ export const MEDIA_PROGRESSIVE_CACHE_DISABLED = false;
export const MEDIA_PROGRESSIVE_CACHE_NAME = 'tt-media-progressive'; export const MEDIA_PROGRESSIVE_CACHE_NAME = 'tt-media-progressive';
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg'; export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
export const LANG_CACHE_NAME = 'tt-lang-packs-v46'; export const LANG_CACHE_NAME = 'tt-lang-packs-v47';
export const ASSET_CACHE_NAME = 'tt-assets'; export const ASSET_CACHE_NAME = 'tt-assets';
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500]; export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global'; export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';