Show correct placeholder in require-premium story reply.

This commit is contained in:
John Preston 2024-01-12 13:58:26 +04:00
parent 3829ed5880
commit 1cfad14437
11 changed files with 300 additions and 51 deletions

View File

@ -3618,10 +3618,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_send_non_premium_text" = "Subscribe to **Premium**\n to message {user}.";
"lng_send_non_premium_go" = "Go Premium";
"lng_send_non_premium_story" = "Replies restricted";
"lng_send_non_premium_unlock" = "Unlock";
"lng_send_non_premium_story_toast" = "You need a **Premium** subscription to reply to **{user}'s** stories.";
"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and **Premium** users.";
"lng_send_non_premium_toast_button" = "View";
"lng_send_non_premium_unlock" = "unlock";
"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers.";
"lng_send_non_premium_message_toast_link" = "Telegram Premium";
"lng_exceptions_list_title" = "Exceptions";
"lng_removed_list_title" = "Removed users";

View File

@ -208,6 +208,8 @@ ComposeControls {
files: ComposeFiles;
premium: PremiumLimits;
boxField: InputField;
restrictionLabel: FlatLabel;
premiumRequired: ComposePremiumRequired;
}
ReportBox {
@ -1218,6 +1220,11 @@ defaultComposeFiles: ComposeFiles {
nameFg: historyFileNameInFg;
statusFg: mediaInFg;
}
defaultRestrictionLabel: FlatLabel(defaultFlatLabel) {
minWidth: 12px;
textFg: placeholderFg;
align: align(top);
}
defaultComposeControls: ComposeControls {
bg: historyComposeAreaBg;
radius: 0px;
@ -1234,6 +1241,7 @@ defaultComposeControls: ComposeControls {
files: defaultComposeFiles;
premium: defaultPremiumLimits;
boxField: defaultInputField;
restrictionLabel: defaultRestrictionLabel;
}
moreChatsBarHeight: 48px;

View File

@ -34,6 +34,31 @@ struct SendActionUpdate {
bool cancel = false;
};
enum class WriteRestrictionType {
None,
Rights,
PremiumRequired,
};
struct WriteRestriction {
using Type = WriteRestrictionType;
QString text;
QString button;
Type type = Type::None;
[[nodiscard]] bool empty() const {
return (type == Type::None);
}
explicit operator bool() const {
return !empty();
}
friend inline bool operator==(
const WriteRestriction &a,
const WriteRestriction &b) = default;
};
struct SetHistoryArgs {
required<History*> history;
MsgId topicRootId = 0;
@ -42,7 +67,7 @@ struct SetHistoryArgs {
rpl::producer<int> slowmodeSecondsLeft;
rpl::producer<bool> sendDisabledBySlowmode;
rpl::producer<bool> liked;
rpl::producer<std::optional<QString>> writeRestriction;
rpl::producer<WriteRestriction> writeRestriction;
};
struct ReplyNextRequest {

View File

@ -62,7 +62,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/session/send_as_peers.h"
#include "media/audio/media_audio_capture.h"
#include "media/audio/media_audio.h"
#include "settings/settings_premium.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/ui_utility.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/dropdown_menu.h"
@ -800,7 +802,6 @@ ComposeControls::ComposeControls(
: not_null(_ownedSelector.get()))
, _mode(descriptor.mode)
, _wrap(std::make_unique<Ui::RpWidget>(parent))
, _writeRestricted(std::make_unique<Ui::RpWidget>(parent))
, _send(std::make_shared<Ui::SendButton>(_wrap.get(), _st.send))
, _like(_features.likes
? Ui::CreateChild<Ui::IconButton>(_wrap.get(), _st.like)
@ -878,7 +879,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
_sendDisabledBySlowmode = rpl::single(false)
| rpl::then(std::move(args.sendDisabledBySlowmode));
_liked = args.liked ? std::move(args.liked) : rpl::single(false);
_writeRestriction = rpl::single(std::optional<QString>())
_writeRestriction = rpl::single(Controls::WriteRestriction())
| rpl::then(std::move(args.writeRestriction));
const auto history = *args.history;
if (_history == history) {
@ -892,6 +893,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
registerDraftSource();
_selector->setCurrentPeer(history ? history->peer.get() : nullptr);
initWebpageProcess();
initWriteRestriction();
initForwardProcess();
updateBotCommandShown();
updateLikeShown();
@ -941,12 +943,16 @@ PeerData *ComposeControls::sendAsPeer() const {
void ComposeControls::move(int x, int y) {
_wrap->move(x, y);
_writeRestricted->move(x, y);
if (_writeRestricted) {
_writeRestricted->move(x, y);
}
}
void ComposeControls::resizeToWidth(int width) {
_wrap->resizeToWidth(width);
_writeRestricted->resizeToWidth(width);
if (_writeRestricted) {
_writeRestricted->resizeToWidth(width);
}
updateHeight();
}
@ -961,12 +967,12 @@ rpl::producer<int> ComposeControls::height() const {
return rpl::conditional(
_writeRestriction.value() | rpl::map(!_1),
_wrap->heightValue(),
_writeRestricted->heightValue());
rpl::single(_st.attach.height));
}
int ComposeControls::heightCurrent() const {
return _writeRestriction.current()
? _writeRestricted->height()
? _st.attach.height
: _wrap->height();
}
@ -1120,7 +1126,9 @@ void ComposeControls::showStarted() {
_autocomplete->hideFast();
}
_wrap->hide();
_writeRestricted->hide();
if (_writeRestricted) {
_writeRestricted->hide();
}
}
void ComposeControls::showFinished() {
@ -1319,7 +1327,8 @@ void ComposeControls::init() {
_wrap->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
paintBackground(clip);
auto p = QPainter(_wrap.get());
paintBackground(p, _wrap->rect(), clip);
}, _wrap->lifetime());
_header->editMsgIdValue(
@ -1466,18 +1475,6 @@ void ComposeControls::clearListenState() {
_voiceRecordBar->clearListenState();
}
void ComposeControls::drawRestrictedWrite(QPainter &p, const QString &error) {
p.fillRect(_writeRestricted->rect(), st::historyReplyBg);
p.setFont(st::normalFont);
p.setPen(st::windowSubTextFg);
p.drawText(
_writeRestricted->rect().marginsRemoved(
QMargins(st::historySendPadding, 0, st::historySendPadding, 0)),
error,
style::al_center);
}
void ComposeControls::initKeyHandler() {
_wrap->events(
) | rpl::filter([=](not_null<QEvent*> event) {
@ -2191,24 +2188,164 @@ void ComposeControls::inlineBotChanged() {
}
}
void SetupRestrictionView(
not_null<Ui::RpWidget*> widget,
not_null<const style::ComposeControls*> st,
std::shared_ptr<ChatHelpers::Show> show,
const QString &name,
rpl::producer<Controls::WriteRestriction> restriction,
Fn<void(QPainter &p, QRect clip)> paintBackground) {
struct State {
std::unique_ptr<Ui::FlatLabel> label;
std::unique_ptr<Ui::AbstractButton> button;
std::unique_ptr<Ui::RoundButton> unlock;
std::unique_ptr<Ui::RpWidget> icon;
Fn<void()> updateGeometries;
};
const auto state = widget->lifetime().make_state<State>();
state->updateGeometries = [=] {
if (!state->label) {
return;
} else if (state->button) {
const auto available = widget->width()
- st->like.width
- st::historySendRight
- state->unlock->width()
- st->premiumRequired.buttonSkip
- st->premiumRequired.position.x();
state->label->resizeToWidth(available);
state->label->moveToLeft(
st->premiumRequired.position.x(),
st->premiumRequired.position.y(),
widget->width());
const auto left = st->premiumRequired.position.x()
+ std::min(available, state->label->textMaxWidth())
+ st->premiumRequired.buttonSkip;
state->unlock->moveToLeft(
left,
st->premiumRequired.buttonTop,
widget->width());
state->button->setGeometry(QRect(
QPoint(),
QSize(left + state->unlock->width(), widget->height())));
state->icon->moveToLeft(0, 0, widget->width());
} else {
const auto left = st::historySendRight;
state->label->resizeToWidth(widget->width() - 2 * left);
state->label->moveToLeft(
left,
(widget->height() - state->label->height()) / 2,
widget->width());
}
};
const auto makeLabel = [=](
const QString &text,
const style::FlatLabel &st) {
auto label = std::make_unique<Ui::FlatLabel>(widget, text, st);
label->show();
label->setAttribute(Qt::WA_TransparentForMouseEvents);
label->heightValue(
) | rpl::start_with_next(
state->updateGeometries,
label->lifetime());
return label;
};
const auto makeUnlock = [=](const QString &text, const QString &name) {
using namespace Ui;
auto unlock = std::make_unique<RoundButton>(
widget,
rpl::single(text),
st->premiumRequired.button);
unlock->show();
unlock->setAttribute(Qt::WA_TransparentForMouseEvents);
unlock->setTextTransform(RoundButton::TextTransform::NoTransform);
unlock->setFullRadius(true);
return unlock;
};
const auto makeIcon = [=] {
auto icon = std::make_unique<Ui::RpWidget>(widget);
icon->resize(st->premiumRequired.icon.size());
icon->show();
icon->paintRequest() | rpl::start_with_next([st, raw = icon.get()] {
auto p = QPainter(raw);
st->premiumRequired.icon.paint(p, {}, raw->width());
}, icon->lifetime());
return icon;
};
std::move(
restriction
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](Controls::WriteRestriction value) {
using Type = Controls::WriteRestriction::Type;
if (value.type == Type::Rights) {
state->icon = nullptr;
state->unlock = nullptr;
state->button = nullptr;
state->label = makeLabel(value.text, st->restrictionLabel);
} else if (value.type == Type::PremiumRequired) {
state->icon = makeIcon();
state->unlock = makeUnlock(value.button, name);
state->button = std::make_unique<Ui::AbstractButton>(widget);
state->button->setClickedCallback([=] {
::Settings::ShowPremiumPromoToast(
show,
tr::lng_send_non_premium_message_toast(
tr::now,
lt_user,
TextWithEntities{ name },
lt_link,
Ui::Text::Link(
Ui::Text::Bold(
tr::lng_send_non_premium_message_toast_link(
tr::now))),
Ui::Text::RichLangValue),
u"require_premium"_q);
});
state->label = makeLabel(value.text, st->premiumRequired.label);
}
}, widget->lifetime());
widget->sizeValue(
) | rpl::start_with_next(state->updateGeometries, widget->lifetime());
widget->paintRequest() | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(widget);
paintBackground(p, clip);
}, widget->lifetime());
}
void ComposeControls::initWriteRestriction() {
if (!_history) {
const auto was = base::take(_writeRestricted);
updateWrappingVisibility();
return;
}
_writeRestricted = std::make_unique<Ui::RpWidget>(_parent);
_writeRestricted->move(_wrap->pos());
_writeRestricted->resizeToWidth(_wrap->widthNoMargins());
_writeRestricted->sizeValue() | rpl::start_with_next([=] {
if (_like && _like->parentWidget() == _writeRestricted.get()) {
updateControlsGeometry(_wrap->size());
}
}, _writeRestricted->lifetime());
_writeRestricted->resize(
_writeRestricted->width(),
st::historyUnblock.height);
_writeRestricted->paintRequest(
) | rpl::start_with_next([=] {
if (const auto error = _writeRestriction.current()) {
auto p = Painter(_writeRestricted.get());
drawRestrictedWrite(p, *error);
}
}, _wrap->lifetime());
const auto background = [=](QPainter &p, QRect clip) {
paintBackground(p, _writeRestricted->rect(), clip);
};
SetupRestrictionView(
_writeRestricted.get(),
&_st,
_show,
_history->peer->shortName(),
_writeRestriction.value(),
background);
_writeRestriction.value(
) | rpl::filter([=] {
return _wrap->isHidden() || _writeRestricted->isHidden();
}) | rpl::start_with_next([=] {
) | rpl::start_with_next([=] {
updateWrappingVisibility();
}, _wrap->lifetime());
}, _writeRestricted->lifetime());
}
void ComposeControls::changeFocusedControl() {
@ -2261,9 +2398,26 @@ void ComposeControls::initVoiceRecordBar() {
void ComposeControls::updateWrappingVisibility() {
const auto hidden = _hidden.current();
const auto restricted = _writeRestriction.current().has_value();
_writeRestricted->setVisible(!hidden && restricted);
const auto &restriction = _writeRestriction.current();
const auto restricted = !restriction.empty() && _writeRestricted;
if (_writeRestricted) {
_writeRestricted->setVisible(!hidden && restricted);
}
_wrap->setVisible(!hidden && !restricted);
using namespace Controls;
if (_like) {
const auto hidden = _like->isHidden();
if (_writeRestricted
&& restriction.type == WriteRestrictionType::PremiumRequired) {
_like->setParent(_writeRestricted.get());
} else {
_like->setParent(_wrap.get());
}
if (!hidden) {
_like->show();
updateControlsGeometry(_wrap->size());
}
}
if (!hidden && !restricted) {
_wrap->raise();
}
@ -2363,9 +2517,14 @@ void ComposeControls::updateControlsGeometry(QSize size) {
_tabbedSelectorToggle->moveToRight(right, buttonsTop);
right += _tabbedSelectorToggle->width();
if (_like) {
_like->moveToRight(right, buttonsTop);
if (_likeShown) {
right += _like->width();
using Type = Controls::WriteRestrictionType;
if (_writeRestriction.current().type == Type::PremiumRequired) {
_like->moveToRight(st::historySendRight, 0);
} else {
_like->moveToRight(right, buttonsTop);
if (_likeShown) {
right += _like->width();
}
}
}
if (_botCommandStart) {
@ -2521,9 +2680,7 @@ void ComposeControls::updateAttachBotsMenu() {
}, _attachBotsMenu->lifetime());
}
void ComposeControls::paintBackground(QRect clip) {
Painter p(_wrap.get());
void ComposeControls::paintBackground(QPainter &p, QRect full, QRect clip) {
if (_backgroundRect) {
//p.setCompositionMode(QPainter::CompositionMode_Source);
//p.fillRect(clip, Qt::transparent);
@ -2532,7 +2689,7 @@ void ComposeControls::paintBackground(QRect clip) {
auto hq = PainterHighQualityEnabler(p);
p.setBrush(_st.bg);
p.setPen(Qt::NoPen);
p.drawRoundedRect(_wrap->rect(), _st.radius, _st.radius);
p.drawRoundedRect(full, _st.radius, _st.radius);
} else {
p.fillRect(clip, _st.bg);
}

View File

@ -272,7 +272,7 @@ private:
void updateControlsGeometry(QSize size);
bool updateReplaceMediaButton();
void updateOuterGeometry(QRect rect);
void paintBackground(QRect clip);
void paintBackground(QPainter &p, QRect full, QRect clip);
[[nodiscard]] auto computeSendButtonType() const;
[[nodiscard]] SendMenu::Type sendMenuType() const;
@ -296,7 +296,6 @@ private:
void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel);
bool showRecordButton() const;
void drawRestrictedWrite(QPainter &p, const QString &error);
bool updateBotCommandShown();
bool updateLikeShown();
@ -353,12 +352,12 @@ private:
rpl::variable<int> _slowmodeSecondsLeft;
rpl::variable<bool> _sendDisabledBySlowmode;
rpl::variable<bool> _liked;
rpl::variable<std::optional<QString>> _writeRestriction;
rpl::variable<Controls::WriteRestriction> _writeRestriction;
rpl::variable<bool> _hidden;
Mode _mode = Mode::Normal;
const std::unique_ptr<Ui::RpWidget> _wrap;
const std::unique_ptr<Ui::RpWidget> _writeRestricted;
std::unique_ptr<Ui::RpWidget> _writeRestricted;
rpl::event_stream<FullReplyTo> _jumpToItemRequests;
std::optional<Ui::RoundRect> _backgroundRect;

View File

@ -699,7 +699,7 @@ void RepliesWidget::setupComposeControls() {
const auto restriction = Data::RestrictionError(
_history->peer,
ChatRestriction::SendOther);
return !canSendAnything
auto text = !canSendAnything
? (restriction
? restriction
: topicRestriction
@ -708,6 +708,10 @@ void RepliesWidget::setupComposeControls() {
: topicRestriction
? std::move(topicRestriction)
: std::optional<QString>();
return text ? Controls::WriteRestriction{
.text = std::move(*text),
.type = Controls::WriteRestrictionType::Rights,
} : Controls::WriteRestriction();
});
_composeControls->setHistory({

View File

@ -198,11 +198,15 @@ void ScheduledWidget::setupComposeControls() {
const auto restriction = Data::RestrictionError(
_history->peer,
ChatRestriction::SendOther);
return !canSendAnything
auto text = !canSendAnything
? (restriction
? restriction
: tr::lng_group_not_accessible(tr::now))
: std::optional<QString>();
return text ? Controls::WriteRestriction{
.text = std::move(*text),
.type = Controls::WriteRestrictionType::Rights,
} : Controls::WriteRestriction();
});
_composeControls->setHistory({
.history = _history.get(),

View File

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_custom_emoji.h"
#include "data/data_document.h"
#include "data/data_message_reaction_id.h"
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/view/controls/compose_controls_common.h"
@ -663,6 +664,22 @@ void ReplyArea::show(
invalidate_weak_ptrs(&_shownPeerGuard);
const auto peer = data.peer;
const auto history = peer ? peer->owner().history(peer).get() : nullptr;
const auto user = peer->asUser();
auto writeRestriction = Data::CanSendAnythingValue(
peer
) | rpl::map([=](bool can) {
using namespace HistoryView::Controls;
return (can
|| !user
|| !user->meRequiresPremiumToWrite()
|| user->session().premium())
? WriteRestriction()
: WriteRestriction{
.text = tr::lng_send_non_premium_story(tr::now),
.button = tr::lng_send_non_premium_unlock(tr::now),
.type = WriteRestrictionType::PremiumRequired,
};
});
_controls->setHistory({
.history = history,
.liked = std::move(
@ -670,6 +687,7 @@ void ReplyArea::show(
) | rpl::map([](const Data::ReactionId &id) {
return !id.empty();
}),
.writeRestriction = std::move(writeRestriction),
});
_controls->clear();
const auto hidden = peer

View File

@ -776,6 +776,32 @@ storiesComposeControls: ComposeControls(defaultComposeControls) {
menu: storiesPopupMenu;
}
restrictionLabel: FlatLabel(defaultFlatLabel) {
minWidth: 12px;
textFg: storiesComposeGrayText;
align: align(top);
}
premiumRequired: ComposePremiumRequired {
label: FlatLabel(defaultFlatLabel) {
minWidth: 12px;
textFg: storiesComposeGrayText;
}
button: RoundButton(defaultActiveButton) {
textFg: storiesComposeWhiteText;
textFgOver: storiesComposeWhiteText;
textBg: groupCallMembersBgRipple;
textBgOver: groupCallMembersBgRipple;
width: -12px;
height: 18px;
textTop: 0px;
font: font(12px);
ripple: storiesComposeRipple;
}
buttonSkip: 6px;
buttonTop: 14px;
position: point(37px, 14px);
icon: icon{{ "emoji/premium_lock", storiesComposeGrayIcon, point(13px, 14px) }};
}
}
storiesViewsMenu: PopupMenu(storiesPopupMenuWithIcons) {
scrollPadding: margins(0px, 6px, 0px, 4px);

View File

@ -1322,6 +1322,7 @@ void ShowPremiumPromoToast(
.text = std::move(textWithLink),
.st = &st::defaultMultilineToast,
.duration = Ui::Toast::kDefaultDuration * 2,
.adaptive = true,
.multiline = true,
.filter = crl::guard(&show->session(), [=](
const ClickHandlerPtr &,

View File

@ -35,6 +35,14 @@ PremiumCover {
about: FlatLabel;
additionalShadowForDarkThemes: bool;
}
ComposePremiumRequired {
label: FlatLabel;
button: RoundButton;
buttonSkip: pixels;
buttonTop: pixels;
position: point;
icon: icon;
}
premiumAboutTextStyle: TextStyle(defaultTextStyle) {
font: font(12px);