Reactions: Support service messages (#5387)
This commit is contained in:
parent
d0ad84217d
commit
5dbb3ddea9
@ -216,6 +216,7 @@ export function buildApiMessageWithChatId(
|
|||||||
const senderBoosts = mtpMessage.fromBoostsApplied;
|
const senderBoosts = mtpMessage.fromBoostsApplied;
|
||||||
const factCheck = mtpMessage.factcheck && buildApiFactCheck(mtpMessage.factcheck);
|
const factCheck = mtpMessage.factcheck && buildApiFactCheck(mtpMessage.factcheck);
|
||||||
const isVideoProcessingPending = mtpMessage.videoProcessingPending;
|
const isVideoProcessingPending = mtpMessage.videoProcessingPending;
|
||||||
|
const areReactionsPossible = mtpMessage.reactionsArePossible;
|
||||||
|
|
||||||
const isInvertedMedia = mtpMessage.invertMedia;
|
const isInvertedMedia = mtpMessage.invertMedia;
|
||||||
|
|
||||||
@ -242,6 +243,7 @@ export function buildApiMessageWithChatId(
|
|||||||
editDate: mtpMessage.editDate,
|
editDate: mtpMessage.editDate,
|
||||||
isMediaUnread,
|
isMediaUnread,
|
||||||
hasUnreadMention: mtpMessage.mentioned && isMediaUnread,
|
hasUnreadMention: mtpMessage.mentioned && isMediaUnread,
|
||||||
|
areReactionsPossible,
|
||||||
isMentioned: mtpMessage.mentioned,
|
isMentioned: mtpMessage.mentioned,
|
||||||
...(groupedId && {
|
...(groupedId && {
|
||||||
groupedId,
|
groupedId,
|
||||||
@ -439,6 +441,7 @@ function buildAction(
|
|||||||
text = 'Notification.ChangedGroupPhoto';
|
text = 'Notification.ChangedGroupPhoto';
|
||||||
translationValues.push('%action_origin%');
|
translationValues.push('%action_origin%');
|
||||||
}
|
}
|
||||||
|
type = 'updateProfilePhoto';
|
||||||
} else if (action instanceof GramJs.MessageActionChatDeletePhoto) {
|
} else if (action instanceof GramJs.MessageActionChatDeletePhoto) {
|
||||||
if (isChannelPost) {
|
if (isChannelPost) {
|
||||||
text = 'Channel.MessagePhotoRemoved';
|
text = 'Channel.MessagePhotoRemoved';
|
||||||
|
|||||||
@ -478,6 +478,7 @@ export interface ApiAction {
|
|||||||
| 'chatCreate'
|
| 'chatCreate'
|
||||||
| 'topicCreate'
|
| 'topicCreate'
|
||||||
| 'suggestProfilePhoto'
|
| 'suggestProfilePhoto'
|
||||||
|
| 'updateProfilePhoto'
|
||||||
| 'joinedChannel'
|
| 'joinedChannel'
|
||||||
| 'chatBoost'
|
| 'chatBoost'
|
||||||
| 'receipt'
|
| 'receipt'
|
||||||
@ -755,6 +756,7 @@ export interface ApiMessage {
|
|||||||
effectId?: string;
|
effectId?: string;
|
||||||
isInvertedMedia?: true;
|
isInvertedMedia?: true;
|
||||||
isVideoProcessingPending?: true;
|
isVideoProcessingPending?: true;
|
||||||
|
areReactionsPossible?: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiReactions {
|
export interface ApiReactions {
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectGiftStickerForDuration,
|
selectGiftStickerForDuration,
|
||||||
selectGiftStickerForStars,
|
selectGiftStickerForStars,
|
||||||
|
selectIsCurrentUserPremium,
|
||||||
selectIsMessageFocused,
|
selectIsMessageFocused,
|
||||||
selectStarGiftSticker,
|
selectStarGiftSticker,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
@ -47,7 +48,9 @@ import Avatar from '../common/Avatar';
|
|||||||
import GiftRibbon from '../common/gift/GiftRibbon';
|
import GiftRibbon from '../common/gift/GiftRibbon';
|
||||||
import Sparkles from '../common/Sparkles';
|
import Sparkles from '../common/Sparkles';
|
||||||
import ActionMessageSuggestedAvatar from './ActionMessageSuggestedAvatar';
|
import ActionMessageSuggestedAvatar from './ActionMessageSuggestedAvatar';
|
||||||
|
import ActionMessageUpdatedAvatar from './ActionMessageUpdatedAvatar';
|
||||||
import ContextMenuContainer from './message/ContextMenuContainer.async';
|
import ContextMenuContainer from './message/ContextMenuContainer.async';
|
||||||
|
import Reactions from './message/reactions/Reactions';
|
||||||
import SimilarChannels from './message/SimilarChannels';
|
import SimilarChannels from './message/SimilarChannels';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
@ -82,6 +85,7 @@ type StateProps = {
|
|||||||
starsGiftSticker?: ApiSticker;
|
starsGiftSticker?: ApiSticker;
|
||||||
canPlayAnimatedEmojis?: boolean;
|
canPlayAnimatedEmojis?: boolean;
|
||||||
patternColor?: string;
|
patternColor?: string;
|
||||||
|
isCurrentUserPremium?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const APPEARANCE_DELAY = 10;
|
const APPEARANCE_DELAY = 10;
|
||||||
@ -89,6 +93,7 @@ const STAR_GIFT_STICKER_SIZE = 120;
|
|||||||
|
|
||||||
const ActionMessage: FC<OwnProps & StateProps> = ({
|
const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||||
message,
|
message,
|
||||||
|
threadId,
|
||||||
isEmbedded,
|
isEmbedded,
|
||||||
appearanceOrder = 0,
|
appearanceOrder = 0,
|
||||||
isJustAdded,
|
isJustAdded,
|
||||||
@ -114,6 +119,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
observeIntersectionForLoading,
|
observeIntersectionForLoading,
|
||||||
observeIntersectionForPlaying,
|
observeIntersectionForPlaying,
|
||||||
onIntersectPinnedMessage,
|
onIntersectPinnedMessage,
|
||||||
|
isCurrentUserPremium,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openPremiumModal,
|
openPremiumModal,
|
||||||
@ -156,11 +162,14 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
const isPremiumGift = message.content.action?.type === 'giftPremium';
|
const isPremiumGift = message.content.action?.type === 'giftPremium';
|
||||||
const isGiftCode = message.content.action?.type === 'giftCode';
|
const isGiftCode = message.content.action?.type === 'giftCode';
|
||||||
const isSuggestedAvatar = message.content.action?.type === 'suggestProfilePhoto' && message.content.action!.photo;
|
const isSuggestedAvatar = message.content.action?.type === 'suggestProfilePhoto' && message.content.action!.photo;
|
||||||
|
const isUpdatedAvatar = message.content.action?.type === 'updateProfilePhoto' && message.content.action!.photo;
|
||||||
const isJoinedMessage = isJoinedChannelMessage(message);
|
const isJoinedMessage = isJoinedChannelMessage(message);
|
||||||
const isStarsGift = message.content.action?.type === 'giftStars';
|
const isStarsGift = message.content.action?.type === 'giftStars';
|
||||||
const isStarGift = message.content.action?.type === 'starGift';
|
const isStarGift = message.content.action?.type === 'starGift';
|
||||||
const isPrizeStars = message.content.action?.type === 'prizeStars';
|
const isPrizeStars = message.content.action?.type === 'prizeStars';
|
||||||
|
|
||||||
|
const withServiceReactions = Boolean(message.areReactionsPossible && message?.reactions);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (noAppearanceAnimation) {
|
if (noAppearanceAnimation) {
|
||||||
return;
|
return;
|
||||||
@ -548,7 +557,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
'ActionMessage message-list-item',
|
'ActionMessage message-list-item',
|
||||||
isFocused && !noFocusHighlight && 'focused',
|
isFocused && !noFocusHighlight && 'focused',
|
||||||
(isPremiumGift || isSuggestedAvatar) && 'centered-action',
|
(isPremiumGift || isSuggestedAvatar || isUpdatedAvatar) && 'centered-action',
|
||||||
isContextMenuShown && 'has-menu-open',
|
isContextMenuShown && 'has-menu-open',
|
||||||
isLastInList && 'last-in-list',
|
isLastInList && 'last-in-list',
|
||||||
transitionClassNames,
|
transitionClassNames,
|
||||||
@ -564,7 +573,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
{!isSuggestedAvatar && !isGiftCode && !isJoinedMessage && (
|
{!isSuggestedAvatar && !isGiftCode && !isJoinedMessage && !isUpdatedAvatar && (
|
||||||
<span className="action-message-content" onClick={handleClick}>{renderContent()}</span>
|
<span className="action-message-content" onClick={handleClick}>{renderContent()}</span>
|
||||||
)}
|
)}
|
||||||
{isPremiumGift && renderGift()}
|
{isPremiumGift && renderGift()}
|
||||||
@ -575,6 +584,9 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
{isSuggestedAvatar && (
|
{isSuggestedAvatar && (
|
||||||
<ActionMessageSuggestedAvatar message={message} renderContent={renderContent} />
|
<ActionMessageSuggestedAvatar message={message} renderContent={renderContent} />
|
||||||
)}
|
)}
|
||||||
|
{isUpdatedAvatar && (
|
||||||
|
<ActionMessageUpdatedAvatar message={message} renderContent={renderContent} />
|
||||||
|
)}
|
||||||
{isJoinedMessage && <SimilarChannels chatId={targetChatId!} />}
|
{isJoinedMessage && <SimilarChannels chatId={targetChatId!} />}
|
||||||
{contextMenuAnchor && (
|
{contextMenuAnchor && (
|
||||||
<ContextMenuContainer
|
<ContextMenuContainer
|
||||||
@ -586,6 +598,15 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
onCloseAnimationEnd={handleContextMenuHide}
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{withServiceReactions && (
|
||||||
|
<Reactions
|
||||||
|
isOutside
|
||||||
|
message={message!}
|
||||||
|
threadId={threadId}
|
||||||
|
observeIntersection={observeIntersectionForPlaying}
|
||||||
|
isCurrentUserPremium={isCurrentUserPremium}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -646,6 +667,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
focusDirection,
|
focusDirection,
|
||||||
noFocusHighlight,
|
noFocusHighlight,
|
||||||
}),
|
}),
|
||||||
|
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(ActionMessage));
|
)(ActionMessage));
|
||||||
|
|||||||
60
src/components/middle/ActionMessageUpdatedAvatar.tsx
Normal file
60
src/components/middle/ActionMessageUpdatedAvatar.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
|
import type { ApiMessage } from '../../api/types';
|
||||||
|
import type { TextPart } from '../../types';
|
||||||
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
import { MediaViewerOrigin } from '../../types';
|
||||||
|
|
||||||
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Avatar from '../common/Avatar';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
message: ApiMessage;
|
||||||
|
renderContent: () => TextPart | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ActionMessageUpdatedAvatar: FC<OwnProps> = ({
|
||||||
|
message,
|
||||||
|
renderContent,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
openMediaViewer,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const lang = useOldLang();
|
||||||
|
const isVideo = message.content.action!.photo?.isVideo;
|
||||||
|
|
||||||
|
const handleViewUpdatedAvatar = () => {
|
||||||
|
openMediaViewer({
|
||||||
|
chatId: message.chatId,
|
||||||
|
messageId: message.id,
|
||||||
|
threadId: MAIN_THREAD_ID,
|
||||||
|
origin: MediaViewerOrigin.SuggestedAvatar,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span>{renderContent()}</span>
|
||||||
|
<span
|
||||||
|
className="action-message-updated-avatar"
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
onClick={handleViewUpdatedAvatar}
|
||||||
|
aria-label={lang('ViewPhotoAction')}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
photo={message.content.action!.photo}
|
||||||
|
loopIndefinitely
|
||||||
|
withVideo={isVideo}
|
||||||
|
size="jumbo"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ActionMessageUpdatedAvatar);
|
||||||
@ -339,6 +339,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-message-updated-avatar {
|
||||||
|
background: transparent !important;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.action-message-button {
|
.action-message-button {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@ -232,7 +232,8 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
const noReactions = !isPrivate && !enabledReactions;
|
const noReactions = !isPrivate && !enabledReactions;
|
||||||
const withReactions = canShowReactionList && !noReactions;
|
const areReactionsPossible = message.areReactionsPossible;
|
||||||
|
const withReactions = (canShowReactionList && !noReactions) || areReactionsPossible;
|
||||||
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 isPremiumGift = message.content.action?.type === 'giftPremium';
|
||||||
|
|||||||
@ -13,6 +13,12 @@
|
|||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.with-service-reactions {
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 19rem;
|
||||||
|
margin: 0.3rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
.own &.is-outside {
|
.own &.is-outside {
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,6 +64,7 @@ const Reactions: FC<OwnProps> = ({
|
|||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
const { results, areTags, recentReactions } = message.reactions!;
|
const { results, areTags, recentReactions } = message.reactions!;
|
||||||
|
const withServiceReactions = Boolean(message.areReactionsPossible && message.reactions);
|
||||||
|
|
||||||
const totalCount = useMemo(() => (
|
const totalCount = useMemo(() => (
|
||||||
results.reduce((acc, reaction) => acc + reaction.count, 0)
|
results.reduce((acc, reaction) => acc + reaction.count, 0)
|
||||||
@ -178,7 +179,11 @@ const Reactions: FC<OwnProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName('Reactions', isOutside && 'is-outside')}
|
className={buildClassName(
|
||||||
|
'Reactions',
|
||||||
|
isOutside && 'is-outside',
|
||||||
|
withServiceReactions && 'with-service-reactions',
|
||||||
|
)}
|
||||||
style={maxWidth ? `max-width: ${maxWidth}px` : undefined}
|
style={maxWidth ? `max-width: ${maxWidth}px` : undefined}
|
||||||
dir={lang.isRtl ? 'rtl' : 'ltr'}
|
dir={lang.isRtl ? 'rtl' : 'ltr'}
|
||||||
>
|
>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user