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 {
border-radius: var(--border-radius-default-small);
border-bottom-right-radius: 0;
&.is-need-premium {
border-bottom-right-radius: var(--border-radius-default-small);
}
box-shadow: none;
}
@ -754,9 +757,12 @@
.input-scroller {
min-height: var(--base-height, 3.5rem);
max-height: 10rem;
margin-right: calc((var(--margin-for-scrollbar) + 1rem) * -1);
&.is-need-premium {
margin-right: 0.5rem;
}
&:has(.form-control:focus) {
border-color: var(--color-primary);
}
@ -764,17 +770,52 @@
.input-scroller-content {
margin-right: calc(var(--margin-for-scrollbar) + 0.5rem);
&.is-need-premium {
margin-right: 0;
}
}
.placeholder-text {
top: auto;
bottom: 1.0625rem;
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 {
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;
}

View File

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

View File

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