diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index b1fc1570de..b7ecc7c8a0 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/Resources/icons/chat/large_lockedchat.png b/Telegram/Resources/icons/chat/large_lockedchat.png new file mode 100644 index 0000000000..812b705b30 Binary files /dev/null and b/Telegram/Resources/icons/chat/large_lockedchat.png differ diff --git a/Telegram/Resources/icons/chat/large_lockedchat@2x.png b/Telegram/Resources/icons/chat/large_lockedchat@2x.png new file mode 100644 index 0000000000..30bf73e9b7 Binary files /dev/null and b/Telegram/Resources/icons/chat/large_lockedchat@2x.png differ diff --git a/Telegram/Resources/icons/chat/large_lockedchat@3x.png b/Telegram/Resources/icons/chat/large_lockedchat@3x.png new file mode 100644 index 0000000000..a0174e1295 Binary files /dev/null and b/Telegram/Resources/icons/chat/large_lockedchat@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2e676016e5..5b2f2978df 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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"; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 475e87cb38..ee34183941 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -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 CreateDisabledFieldView( }); return result; } + +base::unique_qptr TextErrorSendRestriction( + QWidget *parent, + const QString &text) { + auto result = base::make_unique_q(parent); + const auto raw = result.get(); + const auto label = CreateChild( + 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 PremiumRequiredSendRestriction( + QWidget *parent, + not_null user, + not_null controller) { + auto result = base::make_unique_q(parent); + const auto raw = result.get(); + const auto label = CreateChild( + 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( + 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; +} diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index a78abd94c2..7bce7facfc 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -143,3 +143,10 @@ private: [[nodiscard]] base::unique_qptr CreateDisabledFieldView( QWidget *parent, not_null peer); +[[nodiscard]] base::unique_qptr TextErrorSendRestriction( + QWidget *parent, + const QString &text); +[[nodiscard]] base::unique_qptr PremiumRequiredSendRestriction( + QWidget *parent, + not_null user, + not_null controller); diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index f875ccdb3a..6644254d52 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -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 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, diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index a6f0339242..1a0abf1345 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -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) diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index c6458fc1d8..7898f414f8 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -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 diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 8d235bc444..9ec603386c 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -520,6 +520,7 @@ not_null 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 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 diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 46f75419ac..3b511cba01 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -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), diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index b455fba662..4c0d4ce5aa 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -506,7 +506,7 @@ void ApplyUserUpdate(not_null 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 diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 17fb3257b7..a50bce0ee5 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -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, - not_null delegate); - - [[nodiscard]] not_null history() const; - [[nodiscard]] HistoryView::Element *view() const; - [[nodiscard]] HistoryItem *item() const; - - bool refresh(); - - int top = 0; - int height = 0; - -private: - const not_null _history; - const not_null _delegate; - AdminLog::OwnedItem _item; - int _version = 0; - -}; - -HistoryInner::BotAbout::BotAbout( - not_null history, - not_null delegate) -: _history(history) -, _delegate(delegate) { -} - -not_null 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(a), - std::forward(b), - HistoryMessageMarkupData(), - std::forward(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, not_null 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( + if (!_aboutView) { + _aboutView = std::make_unique( _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( + _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() diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index dcc8dc4b59..ce88f93d7a 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -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 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>; @@ -449,7 +449,7 @@ private: // the first _history message date (just skip it by height). int _historySkipHeight = 0; - std::unique_ptr _botAbout; + std::unique_ptr _aboutView; std::unique_ptr _emptyPainter; std::unique_ptr _translateTracker; diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 75705eab88..93f1b44d17 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -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; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 1d518a32ec..1832057a9a 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -577,7 +577,7 @@ HistoryWidget::HistoryWidget( ) | rpl::filter([=](not_null user) { return (_peer == user.get()); }) | rpl::start_with_next([=](not_null 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 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() diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 34f101d765..6ec45fba4c 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -474,7 +474,6 @@ private: [[nodiscard]] MsgId resolveReplyToTopicRootId(); [[nodiscard]] Data::ForumTopic *resolveReplyToTopic(); [[nodiscard]] bool canWriteMessage() const; - std::optional 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 peer, const QVector &messages); void addMessagesToBack(not_null peer, const QVector &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 _field; base::unique_qptr _fieldDisabled; + base::unique_qptr _sendRestriction; + QString _sendRestrictionKey; Ui::Animations::Simple _inPhotoEditOver; bool _inDetails = false; bool _inPhotoEdit = false; diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp new file mode 100644 index 0000000000..70fd4a6f5a --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -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 parent); + ~PremiumRequiredBox(); + + int width() override; + int top() override; + QSize size() override; + QString title() override; + TextWithEntities subtitle() override; + int buttonSkip() override; + rpl::producer 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 stickerTakePlayer( + not_null data, + const Lottie::ColorReplacements *replacements) override; + + bool hasHeavyPart() override; + void unloadHeavyPart() override; + +private: + const not_null _parent; + +}; + +PremiumRequiredBox::PremiumRequiredBox(not_null 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 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([=](ClickContext context) { + const auto my = context.other.value(); + 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 PremiumRequiredBox::stickerTakePlayer( + not_null data, + const Lottie::ColorReplacements *replacements) { + return nullptr; +} + +bool PremiumRequiredBox::hasHeavyPart() { + return false; +} + +void PremiumRequiredBox::unloadHeavyPart() { +} + +} // namespace + +AboutView::AboutView( + not_null history, + not_null delegate) +: _history(history) +, _delegate(delegate) { +} + +not_null 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 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(a), + std::forward(b), + HistoryMessageMarkupData(), + std::forward(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( + result.get(), + std::make_unique(result.get()))); + return result; +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.h b/Telegram/SourceFiles/history/view/history_view_about_view.h new file mode 100644 index 0000000000..bf162f4516 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_about_view.h @@ -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, + not_null delegate); + + [[nodiscard]] not_null 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 info); + [[nodiscard]] AdminLog::OwnedItem makePremiumRequired(); + + const not_null _history; + const not_null _delegate; + AdminLog::OwnedItem _item; + int _version = 0; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index d2c56ca8ed..60f0b0fe3a 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -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()); + } } } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 93db9cd129..04c2977e33 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -625,7 +625,7 @@ QSize Message::performCountOptimalSize() { refreshInfoSkipBlock(); const auto media = this->media(); - const auto botTop = item->isFakeBotAbout() + const auto botTop = item->isFakeAboutView() ? Get() : 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() : 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() diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 03164193b3..9b5ff9e127 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -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() diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index fa633ab378..e5996d1bf3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -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() diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp index 6bb085f1a7..571f13a09c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp @@ -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 parent, std::unique_ptr 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() diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_box.h b/Telegram/SourceFiles/history/view/media/history_view_service_box.h index 2b459e027c..d2d247eeda 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_box.h +++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.h @@ -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; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 1b463cf50b..114c6dadfc 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -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;