Show "premium required" information in chat.

This commit is contained in:
John Preston 2024-01-11 23:20:36 +04:00
parent c765bee0cd
commit e5cda0e2b1
27 changed files with 566 additions and 200 deletions

View File

@ -752,6 +752,8 @@ PRIVATE
history/view/reactions/history_view_reactions_strip.h
history/view/reactions/history_view_reactions_tabs.cpp
history/view/reactions/history_view_reactions_tabs.h
history/view/history_view_about_view.cpp
history/view/history_view_about_view.h
history/view/history_view_bottom_info.cpp
history/view/history_view_bottom_info.h
history/view/history_view_contact_status.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -3612,6 +3612,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_restricted_send_voice_messages" = "{user} restricted sending of voice messages to them.";
"lng_restricted_send_video_messages" = "{user} restricted sending of video messages to them.";
"lng_restricted_send_non_premium" = "Only Premium users can message {user}.";
"lng_restricted_send_non_premium_more" = "Learn more...";
"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_exceptions_list_title" = "Exceptions";
"lng_removed_list_title" = "Removed users";

View File

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "main/main_session.h"
#include "settings/settings_premium.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
@ -904,3 +905,68 @@ base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
});
return result;
}
base::unique_qptr<Ui::RpWidget> TextErrorSendRestriction(
QWidget *parent,
const QString &text) {
auto result = base::make_unique_q<Ui::RpWidget>(parent);
const auto raw = result.get();
const auto label = CreateChild<Ui::FlatLabel>(
result.get(),
text,
st::historySendPremiumRequired);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
QPainter(raw).fillRect(clip, st::windowBg);
}, raw->lifetime());
raw->sizeValue(
) | rpl::start_with_next([=](QSize size) {
const auto &st = st::historyComposeField;
const auto width = size.width();
const auto margins = (st.textMargins + st.placeholderMargins);
const auto available = width - margins.left() - margins.right();
label->resizeToWidth(available);
label->moveToLeft(
margins.left(),
(size.height() - label->height()) / 2,
width);
}, label->lifetime());
return result;
}
base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
QWidget *parent,
not_null<UserData*> user,
not_null<Window::SessionController*> controller) {
auto result = base::make_unique_q<Ui::RpWidget>(parent);
const auto raw = result.get();
const auto label = CreateChild<Ui::FlatLabel>(
result.get(),
tr::lng_restricted_send_non_premium(
tr::now,
lt_user,
user->shortName()),
st::historySendPremiumRequired);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto link = CreateChild<Ui::LinkButton>(
result.get(),
tr::lng_restricted_send_non_premium_more(tr::now));
raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
QPainter(raw).fillRect(clip, st::windowBg);
}, raw->lifetime());
raw->widthValue(
) | rpl::start_with_next([=](int width) {
const auto &st = st::historyComposeField;
const auto margins = (st.textMargins + st.placeholderMargins);
const auto available = width - margins.left() - margins.right();
label->resizeToWidth(available);
label->moveToLeft(margins.left(), margins.top(), width);
link->move(
(width - link->width()) / 2,
label->y() + label->height());
}, label->lifetime());
link->setClickedCallback([=] {
Settings::ShowPremium(controller, u"require_premium"_q);
});
return result;
}

View File

@ -143,3 +143,10 @@ private:
[[nodiscard]] base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
QWidget *parent,
not_null<PeerData*> peer);
[[nodiscard]] base::unique_qptr<Ui::RpWidget> TextErrorSendRestriction(
QWidget *parent,
const QString &text);
[[nodiscard]] base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
QWidget *parent,
not_null<UserData*> user,
not_null<Window::SessionController*> controller);

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer_values.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/chat/attach/attach_prepare.h"
namespace {
@ -113,6 +114,9 @@ bool CanSendAnyOf(
if (const auto user = peer->asUser()) {
if (user->isInaccessible() || user->isRepliesChat()) {
return false;
} else if (user->meRequiresPremiumToWrite()
&& !user->session().premium()) {
return false;
} else if (rights
& ~(ChatRestriction::SendVoiceMessages
| ChatRestriction::SendVideoMessages
@ -167,6 +171,13 @@ std::optional<QString> RestrictionError(
using Flag = ChatRestriction;
if (const auto restricted = peer->amRestricted(restriction)) {
if (const auto user = peer->asUser()) {
if (user->meRequiresPremiumToWrite()
&& !user->session().premium()) {
return tr::lng_restricted_send_non_premium(
tr::now,
lt_user,
user->shortName());
}
const auto result = (restriction == Flag::SendVoiceMessages)
? tr::lng_restricted_send_voice_messages(
tr::now,

View File

@ -1101,6 +1101,9 @@ Data::RestrictionCheckResult PeerData::amRestricted(
}
};
if (const auto user = asUser()) {
if (user->meRequiresPremiumToWrite() && !user->session().premium()) {
return Result::Explicit();
}
return (right == ChatRestriction::SendVoiceMessages
|| right == ChatRestriction::SendVideoMessages)
? ((user->flags() & UserDataFlag::VoiceMessagesForbidden)

View File

@ -217,13 +217,24 @@ inline auto DefaultRestrictionValue(
using namespace rpl::mappers;
const auto other = rights & ~(ChatRestriction::SendVoiceMessages
| ChatRestriction::SendVideoMessages);
auto allowedAny = PeerFlagsValue(
user,
(UserDataFlag::Deleted | UserDataFlag::MeRequiresPremiumToWrite)
) | rpl::map([=](UserDataFlags flags) {
return (flags & UserDataFlag::Deleted)
? rpl::single(false)
: !(flags & UserDataFlag::MeRequiresPremiumToWrite)
? rpl::single(true)
: AmPremiumValue(&user->session());
}) | rpl::flatten_latest();
if (other) {
return PeerFlagValue(user, UserDataFlag::Deleted)
| rpl::map(!_1);
return std::move(allowedAny);
}
const auto mask = UserDataFlag::Deleted
| UserDataFlag::VoiceMessagesForbidden;
return PeerFlagsValue(user, mask) | rpl::map(!_1);
const auto mask = UserDataFlag::VoiceMessagesForbidden;
return rpl::combine(
std::move(allowedAny),
PeerFlagValue(user, mask),
_1 && !_2);
} else if (const auto chat = peer->asChat()) {
const auto mask = ChatDataFlag()
| ChatDataFlag::Deactivated

View File

@ -520,6 +520,7 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
| Flag::Premium
| Flag::Support
| Flag::SomeRequirePremiumToWrite
| Flag::MeRequiresPremiumToWrite AssertIsDebug()
| Flag::RequirePremiumToWriteKnown
| (!minimal
? Flag::Contact
@ -542,7 +543,7 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
| (data.is_premium() ? Flag::Premium : Flag())
| (data.is_support() ? Flag::Support : Flag())
| (data.is_contact_require_premium()
? (Flag::SomeRequirePremiumToWrite
? ((Flag::SomeRequirePremiumToWrite | Flag::MeRequiresPremiumToWrite) AssertIsDebug()
| (result->someRequirePremiumToWrite()
? (result->requirePremiumToWriteKnown()
? Flag::RequirePremiumToWriteKnown

View File

@ -300,8 +300,8 @@ enum class MessageFlag : uint64 {
OnlyEmojiAndSpaces = (1ULL << 35),
OnlyEmojiAndSpacesSet = (1ULL << 36),
// Fake message with bot cover and information.
FakeBotAbout = (1ULL << 37),
// Fake message with some info, like bot cover and information.
FakeAboutView = (1ULL << 37),
StoryItem = (1ULL << 38),

View File

@ -506,7 +506,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
| Flag::VoiceMessagesForbidden
| Flag::ReadDatesPrivate
| Flag::RequirePremiumToWriteKnown
| Flag::MeRequiresPremiumToWrite;
/*| Flag::MeRequiresPremiumToWrite*/; AssertIsDebug()
user->setFlags((user->flags() & ~mask)
| (update.is_phone_calls_private()
? Flag::PhoneCallsPrivate

View File

@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/file_utilities.h"
#include "core/click_handler_types.h"
#include "history/admin_log/history_admin_log_item.h"
#include "history/history_item_helpers.h"
#include "history/view/controls/history_view_forward_panel.h"
#include "history/view/controls/history_view_draft_options.h"
@ -17,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_web_page.h"
#include "history/view/reactions/history_view_reactions_button.h"
#include "history/view/reactions/history_view_reactions_selector.h"
#include "history/view/history_view_about_view.h"
#include "history/view/history_view_message.h"
#include "history/view/history_view_service_message.h"
#include "history/view/history_view_cursor_state.h"
@ -305,102 +305,6 @@ public:
};
class HistoryInner::BotAbout final : public ClickHandlerHost {
public:
BotAbout(
not_null<History*> history,
not_null<HistoryView::ElementDelegate*> delegate);
[[nodiscard]] not_null<History*> history() const;
[[nodiscard]] HistoryView::Element *view() const;
[[nodiscard]] HistoryItem *item() const;
bool refresh();
int top = 0;
int height = 0;
private:
const not_null<History*> _history;
const not_null<HistoryView::ElementDelegate*> _delegate;
AdminLog::OwnedItem _item;
int _version = 0;
};
HistoryInner::BotAbout::BotAbout(
not_null<History*> history,
not_null<HistoryView::ElementDelegate*> delegate)
: _history(history)
, _delegate(delegate) {
}
not_null<History*> HistoryInner::BotAbout::history() const {
return _history;
}
HistoryView::Element *HistoryInner::BotAbout::view() const {
return _item.get();
}
HistoryItem *HistoryInner::BotAbout::item() const {
if (const auto element = view()) {
return element->data();
}
return nullptr;
}
bool HistoryInner::BotAbout::refresh() {
const auto bot = _history->peer->asUser();
const auto info = bot ? bot->botInfo.get() : nullptr;
if (!info) {
if (_item) {
_item = {};
return true;
}
_version = 0;
return false;
}
const auto version = info->descriptionVersion;
if (_version == version) {
return false;
}
_version = version;
const auto flags = MessageFlag::FakeBotAbout
| MessageFlag::FakeHistoryItem
| MessageFlag::Local;
const auto postAuthor = QString();
const auto date = TimeId(0);
const auto replyTo = FullReplyTo();
const auto viaBotId = UserId(0);
const auto groupedId = uint64(0);
const auto textWithEntities = TextUtilities::ParseEntities(
info->description,
Ui::ItemTextBotNoMonoOptions().flags);
const auto make = [&](auto &&a, auto &&b, auto &&...other) {
return _history->makeMessage(
_history->nextNonHistoryEntryId(),
flags,
replyTo,
viaBotId,
date,
bot->id,
postAuthor,
std::forward<decltype(a)>(a),
std::forward<decltype(b)>(b),
HistoryMessageMarkupData(),
std::forward<decltype(other)>(other)...);
};
const auto item = info->document
? make(info->document, textWithEntities)
: info->photo
? make(info->photo, textWithEntities)
: make(textWithEntities, MTP_messageMediaEmpty(), groupedId);
_item = AdminLog::OwnedItem(_delegate, item);
return true;
}
HistoryInner::HistoryInner(
not_null<HistoryWidget*> historyWidget,
not_null<Ui::ScrollArea*> scroll,
@ -447,7 +351,7 @@ HistoryInner::HistoryInner(
setAttribute(Qt::WA_AcceptTouchEvents);
notifyIsBotChanged();
refreshAboutView();
setMouseTracking(true);
_controller->gifPauseLevelChanged(
@ -1011,10 +915,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
&& (!_migrated || _migrated->isDisplayedEmpty());
if (const auto view = _botAbout ? _botAbout->view() : nullptr) {
if (clip.y() < _botAbout->top + _botAbout->height
&& clip.y() + clip.height() > _botAbout->top) {
const auto top = _botAbout->top;
if (const auto view = _aboutView ? _aboutView->view() : nullptr) {
if (clip.y() < _aboutView->top + _aboutView->height
&& clip.y() + clip.height() > _aboutView->top) {
const auto top = _aboutView->top;
context.translate(0, -top);
context.selection = computeRenderSelection(&_selected, view);
p.translate(0, top);
@ -3066,8 +2970,8 @@ void HistoryInner::recountHistoryGeometry() {
auto oldHistoryPaddingTop = qMax(
visibleHeight - historyHeight() - st::historyPaddingBottom,
0);
if (_botAbout) {
accumulate_max(oldHistoryPaddingTop, _botAbout->height);
if (_aboutView) {
accumulate_max(oldHistoryPaddingTop, _aboutView->height);
}
updateBotInfo(false);
@ -3095,20 +2999,20 @@ void HistoryInner::recountHistoryGeometry() {
}
}
if (const auto view = _botAbout ? _botAbout->view() : nullptr) {
_botAbout->height = view->resizeGetHeight(_contentWidth);
_botAbout->top = qMin(
_historyPaddingTop - _botAbout->height,
qMax(0, (_scroll->height() - _botAbout->height) / 2));
} else if (_botAbout) {
_botAbout->top = _botAbout->height = 0;
if (const auto view = _aboutView ? _aboutView->view() : nullptr) {
_aboutView->height = view->resizeGetHeight(_contentWidth);
_aboutView->top = qMin(
_historyPaddingTop - _aboutView->height,
qMax(0, (_scroll->height() - _aboutView->height) / 2));
} else if (_aboutView) {
_aboutView->top = _aboutView->height = 0;
}
auto newHistoryPaddingTop = qMax(
visibleHeight - historyHeight() - st::historyPaddingBottom,
0);
if (_botAbout) {
accumulate_max(newHistoryPaddingTop, _botAbout->height);
if (_aboutView) {
accumulate_max(newHistoryPaddingTop, _aboutView->height);
}
auto historyPaddingTopDelta = (newHistoryPaddingTop - oldHistoryPaddingTop);
@ -3122,13 +3026,13 @@ void HistoryInner::recountHistoryGeometry() {
}
void HistoryInner::updateBotInfo(bool recount) {
if (!_botAbout) {
if (!_aboutView) {
return;
} else if (_botAbout->refresh() && recount && _contentWidth > 0) {
const auto view = _botAbout->view();
} else if (_aboutView->refresh() && recount && _contentWidth > 0) {
const auto view = _aboutView->view();
const auto now = view ? view->resizeGetHeight(_contentWidth) : 0;
if (_botAbout->height != now) {
_botAbout->height = now;
if (_aboutView->height != now) {
_aboutView->height = now;
updateSize();
}
}
@ -3276,14 +3180,14 @@ void HistoryInner::updateSize() {
const auto visibleHeight = _scroll->height();
const auto itemsHeight = historyHeight() - _revealHeight;
auto newHistoryPaddingTop = qMax(visibleHeight - itemsHeight - st::historyPaddingBottom, 0);
if (_botAbout) {
accumulate_max(newHistoryPaddingTop, _botAbout->height);
if (_aboutView) {
accumulate_max(newHistoryPaddingTop, _aboutView->height);
}
if (_botAbout && _botAbout->height > 0) {
_botAbout->top = qMin(
newHistoryPaddingTop - _botAbout->height,
qMax(0, (_scroll->height() - _botAbout->height) / 2));
if (_aboutView && _aboutView->height > 0) {
_aboutView->top = qMin(
newHistoryPaddingTop - _aboutView->height,
qMax(0, (_scroll->height() - _aboutView->height) / 2));
}
if (_historyPaddingTop != newHistoryPaddingTop) {
@ -3327,7 +3231,7 @@ void HistoryInner::leaveEventHook(QEvent *e) {
}
HistoryInner::~HistoryInner() {
_botAbout = nullptr;
_aboutView = nullptr;
for (const auto &item : _animatedStickersPlayed) {
if (const auto view = item->mainView()) {
if (const auto media = view->media()) {
@ -3641,11 +3545,11 @@ void HistoryInner::mouseActionUpdate() {
const auto reactionView = viewByItem(reactionItem);
const auto view = reactionView
? reactionView
: (_botAbout
&& _botAbout->view()
&& point.y() >= _botAbout->top
&& point.y() < _botAbout->top + _botAbout->view()->height())
? _botAbout->view()
: (_aboutView
&& _aboutView->view()
&& point.y() >= _aboutView->top
&& point.y() < _aboutView->top + _aboutView->view()->height())
? _aboutView->view()
: (_curHistory && !_curHistory->isEmpty())
? _curHistory->blocks[_curBlock]->messages[_curItem].get()
: nullptr;
@ -4004,8 +3908,8 @@ void HistoryInner::clearChooseReportReason() {
auto HistoryInner::viewByItem(const HistoryItem *item) const -> Element* {
return !item
? nullptr
: (_botAbout && _botAbout->item() == item)
? _botAbout->view()
: (_aboutView && _aboutView->item() == item)
? _aboutView->view()
: item->mainView();
}
@ -4017,8 +3921,8 @@ int HistoryInner::itemTop(const HistoryItem *item) const {
int HistoryInner::itemTop(const Element *view) const {
if (!view) {
return -1;
} else if (_botAbout && view == _botAbout->view()) {
return _botAbout->top;
} else if (_aboutView && view == _aboutView->view()) {
return _aboutView->top;
} else if (view->data()->mainView() != view) {
return -1;
}
@ -4056,17 +3960,27 @@ auto HistoryInner::findViewForPinnedTracking(int top) const
return { nullptr, 0 };
}
void HistoryInner::notifyIsBotChanged() {
void HistoryInner::refreshAboutView() {
if (const auto user = _peer->asUser()) {
if (const auto info = user->botInfo.get()) {
if (!_botAbout) {
_botAbout = std::make_unique<BotAbout>(
if (!_aboutView) {
_aboutView = std::make_unique<HistoryView::AboutView>(
_history,
_history->delegateMixin()->delegate());
}
if (!info->inited) {
session().api().requestFullPeer(_peer);
}
} else if (user->meRequiresPremiumToWrite()
&& !user->session().premium()
&& !historyHeight()) {
if (!_aboutView) {
_aboutView = std::make_unique<HistoryView::AboutView>(
_history,
_history->delegateMixin()->delegate());
}
} else {
_aboutView = nullptr;
}
}
}
@ -4313,7 +4227,7 @@ void HistoryInner::applyDragSelection(
if (!toItems->empty() && toItems->cbegin()->second != FullSelection) {
toItems->clear();
}
const auto botAboutView = _botAbout ? _botAbout->view() : nullptr;
const auto botAboutView = _aboutView ? _aboutView->view() : nullptr;
if (_dragSelecting) {
auto fromblock = (_dragSelFrom != botAboutView)
? _dragSelFrom->block()->indexInHistory()

View File

@ -36,6 +36,7 @@ class Element;
class TranslateTracker;
struct PinnedId;
struct SelectedQuote;
class AboutView;
} // namespace HistoryView
namespace HistoryView::Reactions {
@ -192,7 +193,7 @@ public:
[[nodiscard]] std::pair<Element*, int> findViewForPinnedTracking(
int top) const;
void notifyIsBotChanged();
void refreshAboutView();
void notifyMigrateUpdated();
// Ui::AbstractTooltipShower interface.
@ -234,7 +235,6 @@ private:
void onTouchSelect();
void onTouchScrollTimer();
class BotAbout;
using ChosenReaction = HistoryView::Reactions::ChosenReaction;
using VideoUserpic = Dialogs::Ui::VideoUserpic;
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
@ -449,7 +449,7 @@ private:
// the first _history message date (just skip it by height).
int _historySkipHeight = 0;
std::unique_ptr<BotAbout> _botAbout;
std::unique_ptr<HistoryView::AboutView> _aboutView;
std::unique_ptr<HistoryView::EmptyPainter> _emptyPainter;
std::unique_ptr<HistoryView::TranslateTracker> _translateTracker;

View File

@ -314,8 +314,8 @@ public:
[[nodiscard]] bool isLocal() const {
return _flags & MessageFlag::Local;
}
[[nodiscard]] bool isFakeBotAbout() const {
return _flags & MessageFlag::FakeBotAbout;
[[nodiscard]] bool isFakeAboutView() const {
return _flags & MessageFlag::FakeAboutView;
}
[[nodiscard]] bool showSimilarChannels() const {
return _flags & MessageFlag::ShowSimilarChannels;

View File

@ -577,7 +577,7 @@ HistoryWidget::HistoryWidget(
) | rpl::filter([=](not_null<UserData*> user) {
return (_peer == user.get());
}) | rpl::start_with_next([=](not_null<UserData*> user) {
_list->notifyIsBotChanged();
_list->refreshAboutView();
_list->updateBotInfo();
updateControlsVisibility();
updateControlsGeometry();
@ -692,6 +692,17 @@ HistoryWidget::HistoryWidget(
scrollToCurrentVoiceMessage(pair.from.contextId(), pair.to);
}, lifetime());
session().user()->flagsValue(
) | rpl::start_with_next([=](UserData::Flags::Change change) {
if (change.diff & UserData::Flag::Premium) {
if (const auto user = _peer ? _peer->asUser() : nullptr) {
if (user->meRequiresPremiumToWrite()) {
handlePeerUpdate();
}
}
}
}, lifetime());
using PeerUpdateFlag = Data::PeerUpdate::Flag;
session().changes().peerUpdates(
PeerUpdateFlag::Rights
@ -2728,18 +2739,6 @@ bool HistoryWidget::canWriteMessage() const {
return true;
}
std::optional<QString> HistoryWidget::writeRestriction() const {
const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls;
auto result = (_peer && !Data::CanSendAnyOf(_peer, allWithoutPolls))
? Data::RestrictionError(_peer, ChatRestriction::SendOther)
: std::nullopt;
if (result) {
return result;
}
return std::nullopt;
}
void HistoryWidget::updateControlsVisibility() {
auto fieldDisabledRemoved = (_fieldDisabled != nullptr);
const auto guard = gsl::finally([&] {
@ -2861,6 +2860,9 @@ void HistoryWidget::updateControlsVisibility() {
if (_inlineResults) {
_inlineResults->hide();
}
if (_sendRestriction) {
_sendRestriction->hide();
}
hideFieldIfVisible();
} else if (editingMessage() || _canSendMessages) {
checkFieldAutocomplete();
@ -2918,6 +2920,9 @@ void HistoryWidget::updateControlsVisibility() {
if (_botMenuButton) {
_botMenuButton->show();
}
if (_sendRestriction) {
_sendRestriction->hide();
}
{
auto rightButtonsChanged = false;
if (_silent) {
@ -3019,6 +3024,9 @@ void HistoryWidget::updateControlsVisibility() {
if (_inlineResults) {
_inlineResults->hide();
}
if (_sendRestriction) {
_sendRestriction->show();
}
_kbScroll->hide();
hideFieldIfVisible();
}
@ -5131,6 +5139,9 @@ void HistoryWidget::moveFieldControls() {
_joinChannel->setGeometry(fullWidthButtonRect);
_muteUnmute->setGeometry(fullWidthButtonRect);
_reportMessages->setGeometry(fullWidthButtonRect);
if (_sendRestriction) {
_sendRestriction->setGeometry(fullWidthButtonRect);
}
}
void HistoryWidget::updateFieldSize() {
@ -5163,7 +5174,7 @@ void HistoryWidget::updateFieldSize() {
}
if (_fieldDisabled) {
_fieldDisabled->resize(fieldWidth, fieldHeight());
_fieldDisabled->resize(width(), st::historySendSize.height());
}
if (_field->width() != fieldWidth) {
_field->resize(fieldWidth, _field->height());
@ -5849,6 +5860,46 @@ int HistoryWidget::countAutomaticScrollTop() {
return ScrollMax;
}
QString HistoryWidget::computeSendRestriction() const {
if (const auto user = _peer ? _peer->asUser() : nullptr) {
if (user->meRequiresPremiumToWrite()
&& !user->session().premium()) {
return u"premium_required"_q;
}
}
const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls;
const auto error = (_peer && !Data::CanSendAnyOf(_peer, allWithoutPolls))
? Data::RestrictionError(_peer, ChatRestriction::SendOther)
: std::nullopt;
return error ? (u"restriction:"_q + *error) : QString();
}
void HistoryWidget::updateSendRestriction() {
const auto restriction = computeSendRestriction();
if (_sendRestrictionKey == restriction) {
return;
}
_sendRestrictionKey = restriction;
if (restriction.isEmpty()) {
_sendRestriction = nullptr;
} else if (restriction == u"premium_required"_q) {
_sendRestriction = PremiumRequiredSendRestriction(
this,
_peer->asUser(),
controller());
} else if (restriction.startsWith(u"restriction:"_q)) {
const auto error = restriction.mid(12);
_sendRestriction = TextErrorSendRestriction(this, error);
} else {
Unexpected("Restriction type.");
}
if (_sendRestriction) {
_sendRestriction->show();
moveFieldControls();
}
}
void HistoryWidget::updateHistoryGeometry(
bool initial,
bool loadedDown,
@ -5893,8 +5944,8 @@ void HistoryWidget::updateHistoryGeometry(
} else {
if (editingMessage() || _canSendMessages) {
newScrollHeight -= (fieldHeight() + 2 * st::historySendPadding);
} else if (writeRestriction().has_value()) {
newScrollHeight -= _unblock->height();
} else if (_sendRestriction) {
newScrollHeight -= _sendRestriction->height();
}
if (_editMsgId
|| replyTo()
@ -7605,6 +7656,7 @@ void HistoryWidget::fullInfoUpdated() {
void HistoryWidget::handlePeerUpdate() {
bool resize = false;
updateSendRestriction();
updateHistoryGeometry();
if (_peer->isChat() && _peer->asChat()->noParticipantInfo()) {
session().api().requestFullPeer(_peer);
@ -8077,15 +8129,6 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
}
}
void HistoryWidget::drawRestrictedWrite(Painter &p, const QString &error) {
auto rect = myrtlrect(0, height() - _unblock->height(), width(), _unblock->height());
p.fillRect(rect, st::historyReplyBg);
p.setFont(st::normalFont);
p.setPen(st::windowSubTextFg);
p.drawText(rect.marginsRemoved(QMargins(st::historySendPadding, 0, st::historySendPadding, 0)), error, style::al_center);
}
void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int top) const {
if (!rect.intersects(myrtlrect(left, top, width() - left, st::normalFont->height))) {
return;
@ -8165,12 +8208,6 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
|| _kbShown) {
drawField(p, clip);
}
const auto error = restrictionHidden
? std::nullopt
: writeRestriction();
if (error) {
drawRestrictedWrite(p, *error);
}
} else {
const auto w = st::msgServiceFont->width(tr::lng_willbe_history(tr::now))
+ st::msgPadding.left()

View File

@ -474,7 +474,6 @@ private:
[[nodiscard]] MsgId resolveReplyToTopicRootId();
[[nodiscard]] Data::ForumTopic *resolveReplyToTopic();
[[nodiscard]] bool canWriteMessage() const;
std::optional<QString> writeRestriction() const;
void orderWidgets();
[[nodiscard]] InlineBotQuery parseInlineBotQuery() const;
@ -503,11 +502,8 @@ private:
bool editingMessage() const {
return _editMsgId != 0;
}
bool jumpToDialogRow(const Dialogs::RowDescriptor &to);
void setupShortcuts();
bool showNextChat();
bool showPreviousChat();
void handlePeerMigration();
@ -536,7 +532,6 @@ private:
const QRect &rect,
int left,
int top) const;
void drawRestrictedWrite(Painter &p, const QString &error);
bool paintShowAnimationFrame();
void updateMouseTracking();
@ -559,6 +554,8 @@ private:
void addMessagesToFront(not_null<PeerData*> peer, const QVector<MTPMessage> &messages);
void addMessagesToBack(not_null<PeerData*> peer, const QVector<MTPMessage> &messages);
void updateSendRestriction();
[[nodiscard]] QString computeSendRestriction() const;
void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 });
void updateListSize();
void startItemRevealAnimations();
@ -765,6 +762,8 @@ private:
bool _cmdStartShown = false;
object_ptr<Ui::InputField> _field;
base::unique_qptr<Ui::RpWidget> _fieldDisabled;
base::unique_qptr<Ui::RpWidget> _sendRestriction;
QString _sendRestrictionKey;
Ui::Animations::Simple _inPhotoEditOver;
bool _inDetails = false;
bool _inPhotoEdit = false;

View File

@ -0,0 +1,249 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_about_view.h"
#include "core/click_handler_types.h"
#include "data/data_user.h"
#include "history/view/media/history_view_service_box.h"
#include "history/view/media/history_view_sticker_player_abstract.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "history/history_item_reply_markup.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_premium.h"
#include "ui/chat/chat_style.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
class PremiumRequiredBox final : public ServiceBoxContent {
public:
explicit PremiumRequiredBox(not_null<Element*> parent);
~PremiumRequiredBox();
int width() override;
int top() override;
QSize size() override;
QString title() override;
TextWithEntities subtitle() override;
int buttonSkip() override;
rpl::producer<QString> button() override;
void draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) override;
ClickHandlerPtr createViewLink() override;
bool hideServiceText() override {
return true;
}
void stickerClearLoopPlayed() override;
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
private:
const not_null<Element*> _parent;
};
PremiumRequiredBox::PremiumRequiredBox(not_null<Element*> parent)
: _parent(parent) {
}
PremiumRequiredBox::~PremiumRequiredBox() = default;
int PremiumRequiredBox::width() {
return st::premiumRequiredWidth;
}
int PremiumRequiredBox::top() {
return st::msgServiceGiftBoxButtonMargins.top();
}
QSize PremiumRequiredBox::size() {
return { st::msgServicePhotoWidth, st::msgServicePhotoWidth };
}
QString PremiumRequiredBox::title() {
return QString();
}
int PremiumRequiredBox::buttonSkip() {
return st::storyMentionButtonSkip;
}
rpl::producer<QString> PremiumRequiredBox::button() {
return tr::lng_send_non_premium_go();
}
TextWithEntities PremiumRequiredBox::subtitle() {
return _parent->data()->notificationText();
}
ClickHandlerPtr PremiumRequiredBox::createViewLink() {
const auto itemId = _parent->data()->fullId();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
Settings::ShowPremium(controller, u"require_premium"_q);
}
});
}
void PremiumRequiredBox::draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) {
const auto padding = (geometry.width() - st::premiumRequiredCircle) / 2;
const auto size = geometry.width() - 2 * padding;
p.setBrush(context.st->msgServiceBg()); // ?
p.setPen(Qt::NoPen);
p.drawEllipse(geometry);
st::premiumRequiredIcon.paintInCenter(p, geometry);
}
void PremiumRequiredBox::stickerClearLoopPlayed() {
}
std::unique_ptr<StickerPlayer> PremiumRequiredBox::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return nullptr;
}
bool PremiumRequiredBox::hasHeavyPart() {
return false;
}
void PremiumRequiredBox::unloadHeavyPart() {
}
} // namespace
AboutView::AboutView(
not_null<History*> history,
not_null<ElementDelegate*> delegate)
: _history(history)
, _delegate(delegate) {
}
not_null<History*> AboutView::history() const {
return _history;
}
Element *AboutView::view() const {
return _item.get();
}
HistoryItem *AboutView::item() const {
if (const auto element = view()) {
return element->data();
}
return nullptr;
}
bool AboutView::refresh() {
const auto bot = _history->peer->asUser();
const auto info = bot ? bot->botInfo.get() : nullptr;
if (!info) {
if (bot
&& bot->meRequiresPremiumToWrite()
&& !bot->session().premium()
&& _history->isEmpty()) {
if (_item) {
return false;
}
_item = makePremiumRequired();
return true;
}
if (_item) {
_item = {};
return true;
}
_version = 0;
return false;
}
const auto version = info->descriptionVersion;
if (_version == version) {
return false;
}
_version = version;
_item = makeAboutBot(info);
return true;
}
AdminLog::OwnedItem AboutView::makeAboutBot(not_null<BotInfo*> info) {
const auto flags = MessageFlag::FakeAboutView
| MessageFlag::FakeHistoryItem
| MessageFlag::Local;
const auto postAuthor = QString();
const auto date = TimeId(0);
const auto replyTo = FullReplyTo();
const auto viaBotId = UserId(0);
const auto groupedId = uint64(0);
const auto textWithEntities = TextUtilities::ParseEntities(
info->description,
Ui::ItemTextBotNoMonoOptions().flags);
const auto make = [&](auto &&a, auto &&b, auto &&...other) {
return _history->makeMessage(
_history->nextNonHistoryEntryId(),
flags,
replyTo,
viaBotId,
date,
_history->peer->id,
postAuthor,
std::forward<decltype(a)>(a),
std::forward<decltype(b)>(b),
HistoryMessageMarkupData(),
std::forward<decltype(other)>(other)...);
};
const auto item = info->document
? make(info->document, textWithEntities)
: info->photo
? make(info->photo, textWithEntities)
: make(textWithEntities, MTP_messageMediaEmpty(), groupedId);
return AdminLog::OwnedItem(_delegate, item);
}
AdminLog::OwnedItem AboutView::makePremiumRequired() {
const auto flags = MessageFlag::FakeAboutView
| MessageFlag::FakeHistoryItem
| MessageFlag::Local;
const auto date = TimeId(0);
const auto item = _history->makeMessage(
_history->nextNonHistoryEntryId(),
flags,
date,
PreparedServiceText{ tr::lng_send_non_premium_text(
tr::now,
lt_user,
Ui::Text::Bold(_history->peer->shortName()),
Ui::Text::RichLangValue) },
peerToUser(_history->peer->id));
auto result = AdminLog::OwnedItem(_delegate, item);
result->overrideMedia(std::make_unique<ServiceBox>(
result.get(),
std::make_unique<PremiumRequiredBox>(result.get())));
return result;
}
} // namespace HistoryView

View File

@ -0,0 +1,40 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "history/admin_log/history_admin_log_item.h"
namespace HistoryView {
class AboutView final : public ClickHandlerHost {
public:
AboutView(
not_null<History*> history,
not_null<ElementDelegate*> delegate);
[[nodiscard]] not_null<History*> history() const;
[[nodiscard]] Element *view() const;
[[nodiscard]] HistoryItem *item() const;
bool refresh();
int top = 0;
int height = 0;
private:
[[nodiscard]] AdminLog::OwnedItem makeAboutBot(not_null<BotInfo*> info);
[[nodiscard]] AdminLog::OwnedItem makePremiumRequired();
const not_null<History*> _history;
const not_null<ElementDelegate*> _delegate;
AdminLog::OwnedItem _item;
int _version = 0;
};
} // namespace HistoryView

View File

@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum_topic.h"
#include "data/data_sponsored_messages.h"
#include "data/data_message_reactions.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "styles/style_chat.h"
@ -472,8 +473,11 @@ Element::Element(
if (_context == Context::History) {
history()->setHasPendingResizedItems();
}
if (data->isFakeBotAbout() && !data->history()->peer->isRepliesChat()) {
AddComponents(FakeBotAboutTop::Bit());
if (data->isFakeAboutView()) {
const auto user = data->history()->peer->asUser();
if (user && user->isBot() && !user->isRepliesChat()) {
AddComponents(FakeBotAboutTop::Bit());
}
}
}

View File

@ -625,7 +625,7 @@ QSize Message::performCountOptimalSize() {
refreshInfoSkipBlock();
const auto media = this->media();
const auto botTop = item->isFakeBotAbout()
const auto botTop = item->isFakeAboutView()
? Get<FakeBotAboutTop>()
: nullptr;
if (botTop) {
@ -3263,7 +3263,7 @@ bool Message::drawBubble() const {
const auto item = data();
if (isHidden()) {
return false;
} else if (logEntryOriginal() || item->isFakeBotAbout()) {
} else if (logEntryOriginal() || item->isFakeAboutView()) {
return true;
}
const auto media = this->media();
@ -3760,7 +3760,7 @@ QRect Message::innerGeometry() const {
QRect Message::countGeometry() const {
const auto item = data();
const auto centeredView = item->isFakeBotAbout()
const auto centeredView = item->isFakeAboutView()
|| (context() == Context::Replies && item->isDiscussionPost());
const auto media = this->media();
const auto mediaWidth = (media && media->isDisplayed())
@ -3822,7 +3822,7 @@ Ui::BubbleRounding Message::countMessageRounding() const {
const auto skipTail = smallBottom
|| (media && media->skipBubbleTail())
|| (keyboard != nullptr)
|| item->isFakeBotAbout()
|| item->isFakeAboutView()
|| (context() == Context::Replies && item->isDiscussionPost());
const auto right = hasRightLayout();
using Corner = Ui::BubbleCornerRounding;
@ -3870,7 +3870,7 @@ int Message::resizeContentGetHeight(int newWidth) {
}
const auto item = data();
const auto botTop = item->isFakeBotAbout()
const auto botTop = item->isFakeAboutView()
? Get<FakeBotAboutTop>()
: nullptr;
const auto media = this->media();
@ -3878,7 +3878,7 @@ int Message::resizeContentGetHeight(int newWidth) {
const auto bubble = drawBubble();
// This code duplicates countGeometry() but also resizes media.
const auto centeredView = item->isFakeBotAbout()
const auto centeredView = item->isFakeAboutView()
|| (context() == Context::Replies && item->isDiscussionPost());
auto contentWidth = newWidth
- st::msgMargin.left()

View File

@ -2045,7 +2045,7 @@ bool Gif::dataLoaded() const {
}
bool Gif::needInfoDisplay() const {
if (_parent->data()->isFakeBotAbout()) {
if (_parent->data()->isFakeAboutView()) {
return false;
}
return _parent->data()->isSending()

View File

@ -902,7 +902,7 @@ bool Photo::dataLoaded() const {
}
bool Photo::needInfoDisplay() const {
if (_parent->data()->isFakeBotAbout()) {
if (_parent->data()->isFakeAboutView()) {
return false;
}
return _parent->data()->isSending()

View File

@ -20,6 +20,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView {
int ServiceBoxContent::width() {
return st::msgServiceGiftBoxSize.width();
}
ServiceBox::ServiceBox(
not_null<Element*> parent,
std::unique_ptr<ServiceBoxContent> content)
@ -27,7 +31,7 @@ ServiceBox::ServiceBox(
, _parent(parent)
, _content(std::move(content))
, _button({ .link = _content->createViewLink() })
, _maxWidth(st::msgServiceGiftBoxSize.width()
, _maxWidth(_content->width()
- st::msgPadding.left()
- st::msgPadding.right())
, _title(
@ -48,7 +52,7 @@ ServiceBox::ServiceBox(
kMarkupTextOptions,
_maxWidth)
, _size(
st::msgServiceGiftBoxSize.width(),
_content->width(),
(st::msgServiceGiftBoxTopSkip
+ _content->top()
+ _content->size().height()

View File

@ -19,6 +19,7 @@ class ServiceBoxContent {
public:
virtual ~ServiceBoxContent() = default;
[[nodiscard]] virtual int width();
[[nodiscard]] virtual int top() = 0;
[[nodiscard]] virtual QSize size() = 0;
[[nodiscard]] virtual QString title() = 0;

View File

@ -965,6 +965,9 @@ historySendDisabled: FlatLabel(defaultFlatLabel) {
historySendDisabledIcon: icon {{ "emoji/premium_lock", placeholderFgActive }};
historySendDisabledIconSkip: 20px;
historySendDisabledPosition: point(0px, 0px);
historySendPremiumRequired: FlatLabel(historySendDisabled) {
align: align(top);
}
backgroundSwitchToDark: IconButton(defaultIconButton) {
width: 48px;
@ -1031,3 +1034,7 @@ chatSimilarName: TextStyle(defaultTextStyle) {
}
chatSimilarWidthMax: 424px;
chatSimilarSkip: 12px;
premiumRequiredWidth: 186px;
premiumRequiredIcon: icon{{ "chat/large_lockedchat", msgServiceFg }};
premiumRequiredCircle: 60px;