From 44c31ff6a0d8cdeea2f97a48b01e28db4c6cf48b Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 4 Sep 2023 04:05:23 +0200 Subject: [PATCH] Premium Modal: Display user emoji status (#3776) --- src/components/common/PrivateChatInfo.tsx | 4 + src/components/common/ProfileInfo.scss | 2 +- src/components/common/ProfileInfo.tsx | 8 +- src/components/common/StickerSetModal.scss | 12 +-- .../premium/PremiumFeatureItem.module.scss | 1 + .../main/premium/PremiumMainModal.module.scss | 36 +++++++- .../main/premium/PremiumMainModal.tsx | 88 ++++++++++++++++--- src/components/middle/MiddleHeader.tsx | 13 ++- 8 files changed, 139 insertions(+), 25 deletions(-) diff --git a/src/components/common/PrivateChatInfo.tsx b/src/components/common/PrivateChatInfo.tsx index b85b7baf5..41c123dbc 100644 --- a/src/components/common/PrivateChatInfo.tsx +++ b/src/components/common/PrivateChatInfo.tsx @@ -44,6 +44,7 @@ type OwnProps = { noStatusOrTyping?: boolean; noRtl?: boolean; adminMember?: ApiChatMember; + onEmojiStatusClick?: NoneToVoidFunction; }; type StateProps = @@ -75,6 +76,7 @@ const PrivateChatInfo: FC = ({ areMessagesLoaded, adminMember, ripple, + onEmojiStatusClick, }) => { const { loadFullUser, @@ -159,6 +161,7 @@ const PrivateChatInfo: FC = ({ withEmojiStatus={!noEmojiStatus} emojiStatusSize={emojiStatusSize} isSavedMessages={isSavedMessages} + onEmojiStatusClick={onEmojiStatusClick} /> {customTitle && {customTitle}} @@ -171,6 +174,7 @@ const PrivateChatInfo: FC = ({ withEmojiStatus={!noEmojiStatus} emojiStatusSize={emojiStatusSize} isSavedMessages={isSavedMessages} + onEmojiStatusClick={onEmojiStatusClick} /> ); } diff --git a/src/components/common/ProfileInfo.scss b/src/components/common/ProfileInfo.scss index c5f62f036..33139e38f 100644 --- a/src/components/common/ProfileInfo.scss +++ b/src/components/common/ProfileInfo.scss @@ -44,7 +44,7 @@ .custom-emoji { --custom-emoji-size: 1.5rem; - color: var(--color-white); + color: var(--color-white) !important; pointer-events: auto; cursor: var(--custom-cursor, pointer); } diff --git a/src/components/common/ProfileInfo.tsx b/src/components/common/ProfileInfo.tsx index 9623443ac..882d6c4e0 100644 --- a/src/components/common/ProfileInfo.tsx +++ b/src/components/common/ProfileInfo.tsx @@ -138,10 +138,10 @@ const ProfileInfo: FC = ({ }); }); - const handleClickPremium = useLastCallback(() => { - if (!user) return; + const handleStatusClick = useLastCallback(() => { + if (!userId) return; - openPremiumModal({ fromUserId: user.id }); + openPremiumModal({ fromUserId: userId }); }); const selectPreviousMedia = useLastCallback(() => { @@ -334,7 +334,7 @@ const ProfileInfo: FC = ({ withEmojiStatus emojiStatusSize={EMOJI_STATUS_SIZE} isSavedMessages={isSavedMessages} - onEmojiStatusClick={handleClickPremium} + onEmojiStatusClick={handleStatusClick} noLoopLimit canCopyTitle /> diff --git a/src/components/common/StickerSetModal.scss b/src/components/common/StickerSetModal.scss index d2387c225..49212bd44 100644 --- a/src/components/common/StickerSetModal.scss +++ b/src/components/common/StickerSetModal.scss @@ -11,7 +11,7 @@ } .modal-header { - padding: 0.5rem 1rem; + padding: 0.5rem; &.with-top-border { /* stylelint-disable-next-line plugin/whole-pixel */ @@ -24,13 +24,13 @@ text-align: center; padding: 0 !important; + } - &.custom-emoji { - --emoji-size: 2.25rem; + &.custom-emoji .modal-content { + --emoji-size: 2.25rem; - .stickers { - padding: 0 0.5rem; - } + .stickers { + padding: 0 0.5rem; } } diff --git a/src/components/main/premium/PremiumFeatureItem.module.scss b/src/components/main/premium/PremiumFeatureItem.module.scss index cf2a7e49d..6fcfb029d 100644 --- a/src/components/main/premium/PremiumFeatureItem.module.scss +++ b/src/components/main/premium/PremiumFeatureItem.module.scss @@ -27,4 +27,5 @@ align-self: center; border-radius: 0.625rem; background: var(--item-color, #000); + margin-left: 1rem; } diff --git a/src/components/main/premium/PremiumMainModal.module.scss b/src/components/main/premium/PremiumMainModal.module.scss index 900e4981a..fa6aa70ce 100644 --- a/src/components/main/premium/PremiumMainModal.module.scss +++ b/src/components/main/premium/PremiumMainModal.module.scss @@ -1,3 +1,5 @@ +@import '../../../styles/mixins'; + .root { --premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%); --premium-feature-background: linear-gradient(65.85deg, #6C93FF -0.24%, #976FFF 53.99%, #DF69D1 110.53%); @@ -30,12 +32,14 @@ } .main { - padding: 1rem; + padding: 1rem 0.5rem; height: 100%; - overflow: auto; + overflow: scroll; display: flex; flex-direction: column; align-items: center; + + @include adapt-padding-to-scrollbar(0.5rem); } .logo { @@ -45,14 +49,23 @@ min-height: 6.25rem; } +.status-emoji { + --custom-emoji-size: 8rem; + + margin: 1rem; + cursor: var(--custom-cursor, pointer); +} + .header-text { font-size: 1.5rem; font-weight: 500; text-align: center; + margin-inline: 0.5rem; } .description { text-align: center; + margin-inline: 0.5rem; margin-bottom: 2rem; } @@ -119,6 +132,25 @@ padding: 1rem; } +.stickerSetText { + font-size: 1.25rem; +} + +.stickerSetLink { + --custom-emoji-size: 1.5rem; + + color: var(--color-links); + cursor: var(--custom-cursor, pointer); + + &:hover { + text-decoration: underline; + } +} + +.stickerSetLinkIcon { + vertical-align: middle; + margin-inline-end: 0.25rem; +} @media (max-width: 600px) { .root :global(.modal-dialog) { diff --git a/src/components/main/premium/PremiumMainModal.tsx b/src/components/main/premium/PremiumMainModal.tsx index abbc23a3c..46431fdc7 100644 --- a/src/components/main/premium/PremiumMainModal.tsx +++ b/src/components/main/premium/PremiumMainModal.tsx @@ -4,7 +4,9 @@ import React, { import { getActions, withGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; -import type { ApiPremiumPromo, ApiUser } from '../../../api/types'; +import type { + ApiPremiumPromo, ApiSticker, ApiStickerSet, ApiUser, +} from '../../../api/types'; import type { GlobalState } from '../../../global/types'; import PremiumFeatureModal, { @@ -15,19 +17,24 @@ import PremiumFeatureModal, { import { TME_LINK_PREFIX } from '../../../config'; import { formatCurrency } from '../../../util/formatCurrency'; import buildClassName from '../../../util/buildClassName'; -import { selectTabState, selectIsCurrentUserPremium, selectUser } from '../../../global/selectors'; +import { + selectTabState, selectIsCurrentUserPremium, selectUser, selectStickerSet, +} from '../../../global/selectors'; import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities'; import { selectPremiumLimit } from '../../../global/selectors/limits'; import renderText from '../../common/helpers/renderText'; import { getUserFullName } from '../../../global/helpers'; +import { REM } from '../../common/helpers/mediaDimensions'; import useLang from '../../../hooks/useLang'; import useSyncEffect from '../../../hooks/useSyncEffect'; +import useLastCallback from '../../../hooks/useLastCallback'; import Modal from '../../ui/Modal'; import Button from '../../ui/Button'; import PremiumFeatureItem from './PremiumFeatureItem'; import Transition from '../../ui/Transition'; +import CustomEmoji from '../../common/CustomEmoji'; import PremiumLogo from '../../../assets/premium/PremiumLogo.svg'; import PremiumLimits from '../../../assets/premium/PremiumLimits.svg'; @@ -47,6 +54,7 @@ import PremiumTranslate from '../../../assets/premium/PremiumTranslate.svg'; import styles from './PremiumMainModal.module.scss'; const LIMIT_ACCOUNTS = 4; +const STATUS_EMOJI_SIZE = 8 * REM; const PREMIUM_FEATURE_COLOR_ICONS: Record = { double_limits: PremiumLimits, @@ -73,6 +81,8 @@ type StateProps = { promo?: ApiPremiumPromo; isClosing?: boolean; fromUser?: ApiUser; + fromUserStatusEmoji?: ApiSticker; + fromUserStatusSet?: ApiStickerSet; toUser?: ApiUser; initialSection?: string; isPremium?: boolean; @@ -93,6 +103,8 @@ const PremiumMainModal: FC = ({ isOpen, currentUserId, fromUser, + fromUserStatusEmoji, + fromUserStatusSet, promo, initialSection, isPremium, @@ -113,7 +125,7 @@ const PremiumMainModal: FC = ({ // eslint-disable-next-line no-null/no-null const dialogRef = useRef(null); const { - closePremiumModal, openInvoice, requestConfetti, openTelegramLink, + closePremiumModal, openInvoice, requestConfetti, openTelegramLink, loadStickers, openStickerSet, } = getActions(); const lang = useLang(); @@ -148,9 +160,9 @@ const PremiumMainModal: FC = ({ } } - function handleClick() { + const handleClick = useLastCallback(() => { handleClickWithStartParam(); - } + }); const showConfetti = useCallback(() => { const dialog = dialogRef.current; @@ -185,7 +197,39 @@ const PremiumMainModal: FC = ({ return premiumPromoOrder.filter((section) => PREMIUM_FEATURE_SECTIONS.includes(section)); }, [premiumPromoOrder]); - if (!promo) return undefined; + useEffect(() => { + if (!fromUserStatusEmoji || fromUserStatusSet) return; + loadStickers({ + stickerSetInfo: fromUserStatusEmoji.stickerSetInfo, + }); + }, [loadStickers, fromUserStatusEmoji, fromUserStatusSet]); + + const handleOpenStatusSet = useLastCallback(() => { + if (!fromUserStatusSet) return; + + openStickerSet({ + stickerSetInfo: fromUserStatusSet, + }); + }); + + const stickerSetTitle = useMemo(() => { + if (!fromUserStatusSet || !fromUser) return undefined; + + const template = lang('lng_premium_emoji_status_title').replace('{user}', getUserFullName(fromUser)!); + const [first, second] = template.split('{link}'); + + const emoji = fromUserStatusSet.thumbCustomEmojiId ? ( + + ) : undefined; + const link = ( + + {emoji}{renderText(fromUserStatusSet.title)} + + ); + return [renderText(first), link, renderText(second)]; + }, [fromUser, fromUserStatusSet, lang]); + + if (!promo || (fromUserStatusEmoji && !fromUserStatusSet)) return undefined; // TODO Support all subscription options const month = promo.options.find((option) => option.months === 1)!; @@ -209,6 +253,10 @@ const PremiumMainModal: FC = ({ : lang('TelegramPremiumUserGiftedPremiumDialogSubtitle'); } + if (fromUserStatusSet) { + return lang('TelegramPremiumUserStatusDialogSubtitle'); + } + return fromUser ? lang('TelegramPremiumUserDialogSubtitle') : lang(isPremium ? 'TelegramPremiumSubscribedSubtitle' : 'TelegramPremiumSubtitle'); @@ -252,9 +300,19 @@ const PremiumMainModal: FC = ({ > - -

- {renderText(getHeaderText(), ['simple_markdown', 'emoji'])} + {fromUserStatusEmoji ? ( + + ) : ( + + )} +

+ {fromUserStatusSet ? stickerSetTitle : renderText(getHeaderText(), ['simple_markdown', 'emoji'])}

{renderText(getHeaderDescription(), ['simple_markdown', 'emoji'])} @@ -297,7 +355,6 @@ const PremiumMainModal: FC = ({
{!isPremium && (
- {/* eslint-disable-next-line react/jsx-no-bind */} @@ -324,6 +381,13 @@ export default memo(withGlobal((global): StateProps => { const { premiumModal, } = selectTabState(global); + + const fromUser = premiumModal?.fromUserId ? selectUser(global, premiumModal.fromUserId) : undefined; + const fromUserStatusEmoji = fromUser?.emojiStatus ? global.customEmojis.byId[fromUser.emojiStatus.documentId] + : undefined; + const fromUserStatusSet = fromUserStatusEmoji ? selectStickerSet(global, fromUserStatusEmoji.stickerSetInfo) + : undefined; + return { currentUserId: global.currentUserId, promo: premiumModal?.promo, @@ -331,7 +395,9 @@ export default memo(withGlobal((global): StateProps => { isSuccess: premiumModal?.isSuccess, isGift: premiumModal?.isGift, monthsAmount: premiumModal?.monthsAmount, - fromUser: premiumModal?.fromUserId ? selectUser(global, premiumModal.fromUserId) : undefined, + fromUser, + fromUserStatusEmoji, + fromUserStatusSet, toUser: premiumModal?.toUserId ? selectUser(global, premiumModal.toUserId) : undefined, initialSection: premiumModal?.initialSection, isPremium: selectIsCurrentUserPremium(global), diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index 07b83497d..40bd53488 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -149,6 +149,7 @@ const MiddleHeader: FC = ({ loadPinnedMessages, toggleLeftColumn, exitMessageSelectMode, + openPremiumModal, } = getActions(); const lang = useLang(); @@ -184,7 +185,12 @@ const MiddleHeader: FC = ({ const componentRef = useRef(null); const shouldAnimateTools = useRef(true); - const { handleClick: handleHeaderClick, handleMouseDown: handleHeaderMouseDown } = useFastClick(() => { + const { + handleClick: handleHeaderClick, + handleMouseDown: handleHeaderMouseDown, + } = useFastClick((e: React.MouseEvent) => { + if (e.type === 'mousedown' && (e.target as Element).closest('.title > .custom-emoji')) return; + openChatWithInfo({ id: chatId, threadId }); }); @@ -214,6 +220,10 @@ const MiddleHeader: FC = ({ }, BACK_BUTTON_INACTIVE_TIME); }); + const handleStatusClick = useLastCallback(() => { + openPremiumModal({ fromUserId: chatId }); + }); + const handleBackClick = useLastCallback((e: React.MouseEvent) => { if (!isBackButtonActive.current) return; @@ -371,6 +381,7 @@ const MiddleHeader: FC = ({ withUpdatingStatus emojiStatusSize={EMOJI_STATUS_SIZE} noRtl + onEmojiStatusClick={handleStatusClick} /> ) : (