Story: Block reply for non-premium users if messaging is restricted (#4732)

This commit is contained in:
Alexander Zinchuk 2024-07-15 15:51:50 +02:00
parent ed0c0c8325
commit 890cee4366
3 changed files with 137 additions and 61 deletions

View File

@ -343,6 +343,9 @@
&.with-story-tweaks { &.with-story-tweaks {
border-radius: var(--border-radius-default-small); border-radius: var(--border-radius-default-small);
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
&.is-need-premium {
border-bottom-right-radius: var(--border-radius-default-small);
}
box-shadow: none; box-shadow: none;
} }
@ -754,9 +757,12 @@
.input-scroller { .input-scroller {
min-height: var(--base-height, 3.5rem); min-height: var(--base-height, 3.5rem);
max-height: 10rem; max-height: 10rem;
margin-right: calc((var(--margin-for-scrollbar) + 1rem) * -1); margin-right: calc((var(--margin-for-scrollbar) + 1rem) * -1);
&.is-need-premium {
margin-right: 0.5rem;
}
&:has(.form-control:focus) { &:has(.form-control:focus) {
border-color: var(--color-primary); border-color: var(--color-primary);
} }
@ -764,17 +770,52 @@
.input-scroller-content { .input-scroller-content {
margin-right: calc(var(--margin-for-scrollbar) + 0.5rem); margin-right: calc(var(--margin-for-scrollbar) + 0.5rem);
&.is-need-premium {
margin-right: 0;
}
} }
.placeholder-text { .placeholder-text {
top: auto; top: auto;
bottom: 1.0625rem;
left: 0.875rem; left: 0.875rem;
} }
.unlock-button {
&:hover,
&.active {
background: var(--color-chat-hover);
}
color: var(--color-text);
width: auto;
height: auto;
padding-top: 0.125rem;
padding-bottom: 0.125rem;
padding-right: 0.4375rem;
padding-left: 0.4375rem;
margin-left: 0.4375rem;
text-transform: lowercase;
}
}
#caption-input-text .placeholder-text {
bottom: 1.0625rem;
} }
#story-input-text .placeholder-text { #story-input-text .placeholder-text {
bottom: 0.875rem; top: calc((var(--base-height, 3.5rem) - var(--composer-text-size, 1rem) * 1.3125) / 2);
@media (max-width: 600px) {
top: calc((2.875rem - var(--composer-text-size, 1rem) * 1.3125) / 2);
}
&.is-need-premium {
pointer-events: auto;
}
left: 0.875rem; left: 0.875rem;
} }

View File

@ -257,6 +257,7 @@ type StateProps =
canSendQuickReplies?: boolean; canSendQuickReplies?: boolean;
webPagePreview?: ApiWebPage; webPagePreview?: ApiWebPage;
noWebPage?: boolean; noWebPage?: boolean;
isContactRequirePremium?: boolean;
effect?: ApiAvailableEffect; effect?: ApiAvailableEffect;
effectReactions?: ApiReaction[]; effectReactions?: ApiReaction[];
areEffectsSupported?: boolean; areEffectsSupported?: boolean;
@ -370,6 +371,7 @@ const Composer: FC<OwnProps & StateProps> = ({
onForward, onForward,
webPagePreview, webPagePreview,
noWebPage, noWebPage,
isContactRequirePremium,
effect, effect,
effectReactions, effectReactions,
areEffectsSupported, areEffectsSupported,
@ -490,8 +492,11 @@ const Composer: FC<OwnProps & StateProps> = ({
[chat, chatFullInfo, isChatWithBot, isInStoryViewer], [chat, chatFullInfo, isChatWithBot, isInStoryViewer],
); );
const isNeedPremium = isContactRequirePremium && isInStoryViewer;
const isSendTextBlocked = isNeedPremium || !canSendPlainText;
const hasWebPagePreview = !hasAttachments && canAttachEmbedLinks && !noWebPage && Boolean(webPagePreview); const hasWebPagePreview = !hasAttachments && canAttachEmbedLinks && !noWebPage && Boolean(webPagePreview);
const isComposerBlocked = !canSendPlainText && !editingMessage; const isComposerBlocked = isSendTextBlocked && !editingMessage;
useEffect(() => { useEffect(() => {
if (!hasWebPagePreview) { if (!hasWebPagePreview) {
@ -1601,7 +1606,7 @@ const Composer: FC<OwnProps & StateProps> = ({
editingMessage={editingMessage} editingMessage={editingMessage}
/> />
)} )}
{shouldRenderReactionSelector && ( {shouldRenderReactionSelector && !isNeedPremium && (
<ReactionSelector <ReactionSelector
topReactions={topReactions} topReactions={topReactions}
allAvailableReactions={availableReactions} allAvailableReactions={availableReactions}
@ -1684,30 +1689,35 @@ const Composer: FC<OwnProps & StateProps> = ({
onClick={handleBotCommandSelect} onClick={handleBotCommandSelect}
onClose={closeChatCommandTooltip} onClose={closeChatCommandTooltip}
/> />
<div className={buildClassName('composer-wrapper', isInStoryViewer && 'with-story-tweaks')}> <div className={
<svg className="svg-appendix" width="9" height="20"> buildClassName('composer-wrapper', isInStoryViewer && 'with-story-tweaks', isNeedPremium && 'is-need-premium')
<defs> }
<filter >
x="-50%" {!isNeedPremium && (
y="-14.7%" <svg className="svg-appendix" width="9" height="20">
width="200%" <defs>
height="141.2%" <filter
filterUnits="objectBoundingBox" x="-50%"
id="composerAppendix" y="-14.7%"
> width="200%"
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" /> height="141.2%"
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" /> filterUnits="objectBoundingBox"
<feColorMatrix id="composerAppendix"
values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" >
in="shadowBlurOuter1" <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
/> <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
</filter> <feColorMatrix
</defs> values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0"
<g fill="none" fill-rule="evenodd"> in="shadowBlurOuter1"
<path d="M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z" fill="#000" filter="url(#composerAppendix)" /> />
<path d="M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z" fill="#FFF" className="corner" /> </filter>
</g> </defs>
</svg> <g fill="none" fill-rule="evenodd">
<path d="M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z" fill="#000" filter="url(#composerAppendix)" />
<path d="M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z" fill="#FFF" className="corner" />
</g>
</svg>
)}
{isInMessageList && ( {isInMessageList && (
<> <>
<InlineBotTooltip <InlineBotTooltip
@ -1782,7 +1792,7 @@ const Composer: FC<OwnProps & StateProps> = ({
)} )}
</> </>
)} )}
{(!isComposerBlocked || canSendGifs || canSendStickers) && ( {((!isComposerBlocked || canSendGifs || canSendStickers) && !isNeedPremium) && (
<SymbolMenuButton <SymbolMenuButton
chatId={chatId} chatId={chatId}
threadId={threadId} threadId={threadId}
@ -1825,7 +1835,7 @@ const Composer: FC<OwnProps & StateProps> = ({
? '' ? ''
: (!isComposerBlocked : (!isComposerBlocked
? (botKeyboardPlaceholder || inputPlaceholder || lang(placeholderForForumAsMessages || 'Message')) ? (botKeyboardPlaceholder || inputPlaceholder || lang(placeholderForForumAsMessages || 'Message'))
: lang('Chat.PlaceholderTextNotAllowed')) : isInStoryViewer ? lang('StoryRepliesLocked') : lang('Chat.PlaceholderTextNotAllowed'))
} }
timedPlaceholderDate={timedPlaceholderDate} timedPlaceholderDate={timedPlaceholderDate}
timedPlaceholderLangKey={timedPlaceholderLangKey} timedPlaceholderLangKey={timedPlaceholderLangKey}
@ -1839,6 +1849,7 @@ const Composer: FC<OwnProps & StateProps> = ({
onSuppressedFocus={closeSymbolMenu} onSuppressedFocus={closeSymbolMenu}
onFocus={markInputHasFocus} onFocus={markInputHasFocus}
onBlur={unmarkInputHasFocus} onBlur={unmarkInputHasFocus}
isNeedPremium={isNeedPremium}
/> />
{isInMessageList && ( {isInMessageList && (
<> <>
@ -1875,28 +1886,30 @@ const Composer: FC<OwnProps & StateProps> = ({
{formatVoiceRecordDuration(currentRecordTime - startRecordTimeRef.current!)} {formatVoiceRecordDuration(currentRecordTime - startRecordTimeRef.current!)}
</span> </span>
)} )}
<AttachMenu {!isNeedPremium && (
chatId={chatId} <AttachMenu
threadId={threadId} chatId={chatId}
editingMessage={editingMessage} threadId={threadId}
hasReplaceableMedia={canMediaBeReplaced} editingMessage={editingMessage}
isButtonVisible={!activeVoiceRecording} hasReplaceableMedia={canMediaBeReplaced}
canAttachMedia={canAttachMedia} isButtonVisible={!activeVoiceRecording}
canAttachPolls={canAttachPolls} canAttachMedia={canAttachMedia}
canSendPhotos={canSendPhotos} canAttachPolls={canAttachPolls}
canSendVideos={canSendVideos} canSendPhotos={canSendPhotos}
canSendDocuments={canSendDocuments} canSendVideos={canSendVideos}
canSendAudios={canSendAudios} canSendDocuments={canSendDocuments}
onFileSelect={handleFileSelect} canSendAudios={canSendAudios}
onPollCreate={openPollModal} onFileSelect={handleFileSelect}
isScheduled={isInScheduledList} onPollCreate={openPollModal}
attachBots={isInMessageList ? attachBots : undefined} isScheduled={isInScheduledList}
peerType={attachMenuPeerType} attachBots={isInMessageList ? attachBots : undefined}
shouldCollectDebugLogs={shouldCollectDebugLogs} peerType={attachMenuPeerType}
theme={theme} shouldCollectDebugLogs={shouldCollectDebugLogs}
onMenuOpen={onAttachMenuOpen} theme={theme}
onMenuClose={onAttachMenuClose} onMenuOpen={onAttachMenuOpen}
/> onMenuClose={onAttachMenuClose}
/>
)}
{isInMessageList && Boolean(botKeyboardMessageId) && ( {isInMessageList && Boolean(botKeyboardMessageId) && (
<BotKeyboardMenu <BotKeyboardMenu
messageId={botKeyboardMessageId} messageId={botKeyboardMessageId}
@ -2126,6 +2139,7 @@ export default memo(withGlobal<OwnProps>(
const noWebPage = selectNoWebPage(global, chatId, threadId); const noWebPage = selectNoWebPage(global, chatId, threadId);
const isContactRequirePremium = selectUserFullInfo(global, chatId)?.isContactRequirePremium;
const areEffectsSupported = isChatWithUser && !isChatWithBot const areEffectsSupported = isChatWithUser && !isChatWithBot
&& !isInScheduledList && !isChatWithSelf && type !== 'story' && chatId !== SERVICE_NOTIFICATIONS_USER_ID; && !isInScheduledList && !isChatWithSelf && type !== 'story' && chatId !== SERVICE_NOTIFICATIONS_USER_ID;
const canPlayEffect = selectPerformanceSettingsValue(global, 'stickerEffects'); const canPlayEffect = selectPerformanceSettingsValue(global, 'stickerEffects');
@ -2203,6 +2217,7 @@ export default memo(withGlobal<OwnProps>(
canSendQuickReplies, canSendQuickReplies,
noWebPage, noWebPage,
webPagePreview: selectTabState(global).webPagePreview, webPagePreview: selectTabState(global).webPagePreview,
isContactRequirePremium,
effect, effect,
effectReactions, effectReactions,
areEffectsSupported, areEffectsSupported,

View File

@ -33,6 +33,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import useInputCustomEmojis from './hooks/useInputCustomEmojis'; import useInputCustomEmojis from './hooks/useInputCustomEmojis';
import Button from '../../ui/Button';
import TextTimer from '../../ui/TextTimer'; import TextTimer from '../../ui/TextTimer';
import TextFormatter from './TextFormatter.async'; import TextFormatter from './TextFormatter.async';
@ -72,6 +73,7 @@ type OwnProps = {
captionLimit?: number; captionLimit?: number;
onFocus?: NoneToVoidFunction; onFocus?: NoneToVoidFunction;
onBlur?: NoneToVoidFunction; onBlur?: NoneToVoidFunction;
isNeedPremium?: boolean;
}; };
type StateProps = { type StateProps = {
@ -137,11 +139,13 @@ const MessageInput: FC<OwnProps & StateProps> = ({
onScroll, onScroll,
onFocus, onFocus,
onBlur, onBlur,
isNeedPremium,
}) => { }) => {
const { const {
editLastMessage, editLastMessage,
replyToNextMessage, replyToNextMessage,
showAllowedMessageTypesNotification, showAllowedMessageTypesNotification,
openPremiumModal,
} = getActions(); } = getActions();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -257,7 +261,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
const chatIdRef = useRef(chatId); const chatIdRef = useRef(chatId);
chatIdRef.current = chatId; chatIdRef.current = chatId;
const focusInput = useLastCallback(() => { const focusInput = useLastCallback(() => {
if (!inputRef.current) { if (!inputRef.current || isNeedPremium) {
return; return;
} }
@ -450,10 +454,12 @@ const MessageInput: FC<OwnProps & StateProps> = ({
} }
function handleClick() { function handleClick() {
if (isAttachmentModalInput || canSendPlainText) return; if (isAttachmentModalInput || canSendPlainText || (isStoryInput && isNeedPremium)) return;
showAllowedMessageTypesNotification({ chatId }); showAllowedMessageTypesNotification({ chatId });
} }
const handleOpenPremiumModal = useLastCallback(() => openPremiumModal());
useEffect(() => { useEffect(() => {
if (IS_TOUCH_ENV) { if (IS_TOUCH_ENV) {
return; return;
@ -553,14 +559,16 @@ const MessageInput: FC<OwnProps & StateProps> = ({
shouldSuppressFocus && 'focus-disabled', shouldSuppressFocus && 'focus-disabled',
); );
const inputScrollerContentClass = buildClassName('input-scroller-content', isNeedPremium && 'is-need-premium');
return ( return (
<div id={id} onClick={shouldSuppressFocus ? onSuppressedFocus : undefined} dir={lang.isRtl ? 'rtl' : undefined}> <div id={id} onClick={shouldSuppressFocus ? onSuppressedFocus : undefined} dir={lang.isRtl ? 'rtl' : undefined}>
<div <div
className={buildClassName('custom-scroll', SCROLLER_CLASS)} className={buildClassName('custom-scroll', SCROLLER_CLASS, isNeedPremium && 'is-need-premium')}
onScroll={onScroll} onScroll={onScroll}
onClick={!isAttachmentModalInput && !canSendPlainText ? handleClick : undefined} onClick={!isAttachmentModalInput && !canSendPlainText ? handleClick : undefined}
> >
<div className="input-scroller-content"> <div className={inputScrollerContentClass}>
<div <div
ref={inputRef} ref={inputRef}
id={editableInputId || EDITABLE_INPUT_ID} id={editableInputId || EDITABLE_INPUT_ID}
@ -576,14 +584,15 @@ const MessageInput: FC<OwnProps & StateProps> = ({
onContextMenu={IS_ANDROID ? handleAndroidContextMenu : undefined} onContextMenu={IS_ANDROID ? handleAndroidContextMenu : undefined}
onTouchCancel={IS_ANDROID ? processSelectionWithTimeout : undefined} onTouchCancel={IS_ANDROID ? processSelectionWithTimeout : undefined}
aria-label={placeholder} aria-label={placeholder}
onFocus={onFocus} onFocus={!isNeedPremium ? onFocus : undefined}
onBlur={onBlur} onBlur={!isNeedPremium ? onBlur : undefined}
/> />
{!forcedPlaceholder && ( {!forcedPlaceholder && (
<span <span
className={buildClassName( className={buildClassName(
'placeholder-text', 'placeholder-text',
!isAttachmentModalInput && !canSendPlainText && 'with-icon', !isAttachmentModalInput && !canSendPlainText && 'with-icon',
isNeedPremium && 'is-need-premium',
)} )}
dir="auto" dir="auto"
> >
@ -592,6 +601,11 @@ const MessageInput: FC<OwnProps & StateProps> = ({
{shouldDisplayTimer ? ( {shouldDisplayTimer ? (
<TextTimer langKey={timedPlaceholderLangKey!} endsAt={timedPlaceholderDate!} onEnd={handleTimerEnd} /> <TextTimer langKey={timedPlaceholderLangKey!} endsAt={timedPlaceholderDate!} onEnd={handleTimerEnd} />
) : placeholder} ) : placeholder}
{isStoryInput && isNeedPremium && (
<Button className="unlock-button" size="tiny" color="adaptive" onClick={handleOpenPremiumModal}>
{lang('StoryRepliesLockedButton')}
</Button>
)}
</span> </span>
)} )}
<canvas ref={sharedCanvasRef} className="shared-canvas" /> <canvas ref={sharedCanvasRef} className="shared-canvas" />
@ -599,8 +613,14 @@ const MessageInput: FC<OwnProps & StateProps> = ({
<div ref={absoluteContainerRef} className="absolute-video-container" /> <div ref={absoluteContainerRef} className="absolute-video-container" />
</div> </div>
</div> </div>
<div ref={scrollerCloneRef} className={buildClassName('custom-scroll', SCROLLER_CLASS, 'clone')}> <div
<div className="input-scroller-content"> ref={scrollerCloneRef}
className={buildClassName('custom-scroll',
SCROLLER_CLASS,
'clone',
isNeedPremium && 'is-need-premium')}
>
<div className={inputScrollerContentClass}>
<div ref={cloneRef} className={buildClassName(className, 'clone')} dir="auto" /> <div ref={cloneRef} className={buildClassName(className, 'clone')} dir="auto" />
</div> </div>
</div> </div>