From 9bb2bb09b9d1f8f705456a7e8bf7ad17b8f452cd Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Sep 2022 20:07:53 +0400 Subject: [PATCH] Update API scheme on layer 145. Restrict send from channels to premium in line with API restrictions. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/Resources/tl/api.tl | 4 +- Telegram/SourceFiles/boxes/peer_list_box.cpp | 5 +- .../main/session/send_as_peers.cpp | 35 ++-- .../SourceFiles/main/session/send_as_peers.h | 17 +- .../SourceFiles/ui/chat/choose_send_as.cpp | 149 +++++++++++++++--- Telegram/SourceFiles/ui/chat/choose_send_as.h | 8 +- 7 files changed, 176 insertions(+), 44 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4112ee555..a520d1a0d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1935,6 +1935,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_send_anonymous_ph" = "Send anonymously..."; "lng_send_as_title" = "Send message as..."; "lng_send_as_anonymous_admin" = "Anonymous admin"; +"lng_send_as_premium_required" = "Subscribe to {link} to be able to comment on behalf of your channels in group chats."; +"lng_send_as_premium_required_link" = "Telegram Premium"; "lng_record_cancel" = "Release outside this field to cancel"; "lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?"; "lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?"; diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 8e06e4473..06d14a64b 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -1327,7 +1327,7 @@ searchResultPosition#7f648b67 msg_id:int date:int offset:int = SearchResultsPosi messages.searchResultsPositions#53b22baf count:int positions:Vector = messages.SearchResultsPositions; -channels.sendAsPeers#8356cda9 peers:Vector chats:Vector users:Vector = channels.SendAsPeers; +channels.sendAsPeers#f496b0c6 peers:Vector chats:Vector users:Vector = channels.SendAsPeers; users.userFull#3b6d152e full_user:UserFull chats:Vector users:Vector = users.UserFull; @@ -1442,6 +1442,8 @@ account.emailVerifiedLogin#e1bb0d61 email:string sent_code:auth.SentCode = accou premiumSubscriptionOption#b6f11ebe flags:# current:flags.1?true can_purchase_upgrade:flags.2?true months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumSubscriptionOption; +sendAsPeer#b81c7034 flags:# premium_required:flags.0?true peer:Peer = SendAsPeer; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index e9d1eb5e4..8a16ba1a7 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -663,10 +663,7 @@ int PeerListRow::paintNameIconGetWidth( int availableWidth, int outerWidth, bool selected) { - if (special() - || _isSavedMessagesChat - || _isRepliesMessagesChat - || !_peer->isUser()) { + if (special() || _isSavedMessagesChat || _isRepliesMessagesChat) { return 0; } return _bagde.drawGetWidth( diff --git a/Telegram/SourceFiles/main/session/send_as_peers.cpp b/Telegram/SourceFiles/main/session/send_as_peers.cpp index 4d1532318..8f6f1ac26 100644 --- a/Telegram/SourceFiles/main/session/send_as_peers.cpp +++ b/Telegram/SourceFiles/main/session/send_as_peers.cpp @@ -23,7 +23,7 @@ constexpr auto kRequestEach = 30 * crl::time(1000); SendAsPeers::SendAsPeers(not_null session) : _session(session) -, _onlyMe({ session->user() }) { +, _onlyMe({ { .peer = session->user(), .premiumRequired = false } }) { _session->changes().peerUpdates( Data::PeerUpdate::Flag::Rights ) | rpl::map([=](const Data::PeerUpdate &update) { @@ -60,7 +60,7 @@ void SendAsPeers::refresh(not_null peer, bool force) { request(peer); } -const std::vector> &SendAsPeers::list( +const std::vector &SendAsPeers::list( not_null peer) const { const auto i = _lists.find(peer); return (i != end(_lists)) ? i->second : _onlyMe; @@ -108,13 +108,15 @@ not_null SendAsPeers::resolveChosen( not_null SendAsPeers::ResolveChosen( not_null peer, - const std::vector> &list, + const std::vector &list, PeerId chosen) { - const auto i = ranges::find(list, chosen, &PeerData::id); + const auto i = ranges::find(list, chosen, [](const SendAsPeer &as) { + return as.peer->id; + }); return (i != end(list)) - ? (*i) + ? i->peer : !list.empty() - ? list.front() + ? list.front().peer : (peer->isMegagroup() && peer->amAnonymous()) ? peer : peer->session().user(); @@ -124,21 +126,28 @@ void SendAsPeers::request(not_null peer) { peer->session().api().request(MTPchannels_GetSendAs( peer->input )).done([=](const MTPchannels_SendAsPeers &result) { - auto list = std::vector>(); + auto parsed = std::vector(); auto &owner = peer->owner(); result.match([&](const MTPDchannels_sendAsPeers &data) { owner.processUsers(data.vusers()); owner.processChats(data.vchats()); - for (const auto &id : data.vpeers().v) { - if (const auto peer = owner.peerLoaded(peerFromMTP(id))) { - list.push_back(peer); + const auto &list = data.vpeers().v; + parsed.reserve(list.size()); + for (const auto &as : list) { + const auto &data = as.data(); + const auto peerId = peerFromMTP(data.vpeer()); + if (const auto peer = owner.peerLoaded(peerId)) { + parsed.push_back({ + .peer = peer, + .premiumRequired = data.is_premium_required(), + }); } } }); - if (list.size() > 1) { + if (parsed.size() > 1) { auto &now = _lists[peer]; - if (now != list) { - now = std::move(list); + if (now != parsed) { + now = std::move(parsed); _updates.fire_copy(peer); } } else if (const auto i = _lists.find(peer); i != end(_lists)) { diff --git a/Telegram/SourceFiles/main/session/send_as_peers.h b/Telegram/SourceFiles/main/session/send_as_peers.h index 302bdf02d..b047ca3ce 100644 --- a/Telegram/SourceFiles/main/session/send_as_peers.h +++ b/Telegram/SourceFiles/main/session/send_as_peers.h @@ -13,13 +13,20 @@ namespace Main { class Session; +struct SendAsPeer { + not_null peer; + bool premiumRequired = false; + + friend inline auto operator<=>(SendAsPeer, SendAsPeer) = default; +}; + class SendAsPeers final { public: explicit SendAsPeers(not_null session); bool shouldChoose(not_null peer); void refresh(not_null peer, bool force = false); - [[nodiscard]] const std::vector> &list( + [[nodiscard]] const std::vector &list( not_null peer) const; [[nodiscard]] rpl::producer> updated() const; @@ -33,18 +40,16 @@ public: [[nodiscard]] static not_null ResolveChosen( not_null peer, - const std::vector> &list, + const std::vector &list, PeerId chosen); private: void request(not_null peer); const not_null _session; - const std::vector> _onlyMe; + const std::vector _onlyMe; - base::flat_map< - not_null, - std::vector>> _lists; + base::flat_map, std::vector> _lists; base::flat_map, crl::time> _lastRequestTime; base::flat_map, PeerId> _chosen; diff --git a/Telegram/SourceFiles/ui/chat/choose_send_as.cpp b/Telegram/SourceFiles/ui/chat/choose_send_as.cpp index a51803e92..45aa363ff 100644 --- a/Telegram/SourceFiles/ui/chat/choose_send_as.cpp +++ b/Telegram/SourceFiles/ui/chat/choose_send_as.cpp @@ -13,21 +13,45 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer_values.h" #include "history/history.h" #include "ui/controls/send_as_button.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "window/window_session_controller.h" #include "main/main_session.h" #include "main/session/send_as_peers.h" #include "lang/lang_keys.h" +#include "settings/settings_premium.h" #include "styles/style_calls.h" #include "styles/style_boxes.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace Ui { namespace { +class Row final : public PeerListRow { +public: + explicit Row(const Main::SendAsPeer &sendAsPeer); + + int paintNameIconGetWidth( + Painter &p, + Fn repaint, + crl::time now, + int nameLeft, + int nameTop, + int nameWidth, + int availableWidth, + int outerWidth, + bool selected) override; + +private: + bool _premiumRequired = false; + +}; + class ListController final : public PeerListController { public: ListController( - std::vector> list, + std::vector list, not_null selected); Main::Session &session() const override; @@ -37,35 +61,78 @@ public: [[nodiscard]] rpl::producer> clicked() const; private: - std::unique_ptr createRow(not_null peer); + std::unique_ptr createRow(const Main::SendAsPeer &sendAsPeer); - std::vector> _list; + std::vector _list; not_null _selected; rpl::event_stream> _clicked; }; +Row::Row(const Main::SendAsPeer &sendAsPeer) +: PeerListRow(sendAsPeer.peer) +, _premiumRequired(sendAsPeer.premiumRequired) { +} + +int Row::paintNameIconGetWidth( + Painter &p, + Fn repaint, + crl::time now, + int nameLeft, + int nameTop, + int nameWidth, + int availableWidth, + int outerWidth, + bool selected) { + if (_premiumRequired && !peer()->session().premium()) { + const auto &icon = st::emojiPremiumRequired; + availableWidth -= icon.width(); + const auto x = nameLeft + std::min(nameWidth, availableWidth); + icon.paint(p, x, nameTop, outerWidth); + return icon.width(); + } + return PeerListRow::paintNameIconGetWidth( + p, + std::move(repaint), + now, + nameLeft, + nameTop, + nameWidth, + availableWidth, + outerWidth, + selected); +} + ListController::ListController( - std::vector> list, + std::vector list, not_null selected) : PeerListController() , _list(std::move(list)) , _selected(selected) { + Data::AmPremiumValue( + &selected->session() + ) | rpl::skip(1) | rpl::start_with_next([=] { + const auto count = delegate()->peerListFullRowsCount(); + for (auto i = 0; i != count; ++i) { + delegate()->peerListUpdateRow( + delegate()->peerListRowAt(i)); + } + }, lifetime()); } Main::Session &ListController::session() const { return _selected->session(); } -std::unique_ptr ListController::createRow( - not_null peer) { - auto result = std::make_unique(peer); - if (peer->isSelf()) { +std::unique_ptr ListController::createRow( + const Main::SendAsPeer &sendAsPeer) { + auto result = std::make_unique(sendAsPeer); + if (sendAsPeer.peer->isSelf()) { result->setCustomStatus( tr::lng_group_call_join_as_personal(tr::now)); - } else if (peer->isMegagroup()) { + } else if (sendAsPeer.peer->isMegagroup()) { result->setCustomStatus(tr::lng_send_as_anonymous_admin(tr::now)); - } else if (const auto channel = peer->asChannel()) { + } else if (const auto channel = sendAsPeer.peer->asChannel()) { result->setCustomStatus(tr::lng_chat_status_subscribers( tr::now, lt_count, @@ -76,11 +143,11 @@ std::unique_ptr ListController::createRow( void ListController::prepare() { delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled); - for (const auto &peer : _list) { - auto row = createRow(peer); + for (const auto &sendAsPeer : _list) { + auto row = createRow(sendAsPeer); const auto raw = row.get(); delegate()->peerListAppendRow(std::move(row)); - if (peer == _selected) { + if (sendAsPeer.peer == _selected) { delegate()->peerListSetRowChecked(raw, true); raw->finishCheckedAnimation(); } @@ -100,14 +167,50 @@ rpl::producer> ListController::clicked() const { return _clicked.events(); } +void ShowPremiumPromoToast(not_null controller) { + using WeakToast = base::weak_ptr; + const auto toast = std::make_shared(); + + auto link = Ui::Text::Link( + tr::lng_send_as_premium_required_link(tr::now)); + link.entities.push_back( + EntityInText(EntityType::Semibold, 0, link.text.size())); + const auto config = Ui::Toast::Config{ + .text = tr::lng_send_as_premium_required( + tr::now, + lt_link, + link, + Ui::Text::WithEntities), + .st = &st::defaultMultilineToast, + .durationMs = Ui::Toast::kDefaultDuration * 2, + .multiline = true, + .filter = crl::guard(&controller->session(), [=]( + const ClickHandlerPtr &, + Qt::MouseButton button) { + if (button == Qt::LeftButton) { + if (const auto strong = toast->get()) { + strong->hideAnimated(); + (*toast) = nullptr; + Settings::ShowPremium(controller, "send_as"); + return true; + } + } + return false; + }), + }; + (*toast) = Ui::Toast::Show( + Window::Show(controller).toastParent(), + config); +} + } // namespace void ChooseSendAsBox( not_null box, - std::vector> list, + std::vector list, not_null chosen, - Fn)> done) { - Expects(ranges::contains(list, chosen)); + Fn)> done) { + Expects(ranges::contains(list, chosen, &Main::SendAsPeer::peer)); Expects(done != nullptr); box->setWidth(st::groupCallJoinAsWidth); @@ -132,8 +235,7 @@ void ChooseSendAsBox( controller->clicked( ) | rpl::start_with_next([=](not_null peer) { const auto weak = MakeWeak(box); - done(peer); - if (weak) { + if (done(peer) && weak) { box->closeBox(); } }, box->lifetime()); @@ -165,7 +267,18 @@ void SetupSendAsButton( return; } const auto done = [=](not_null sendAs) { + const auto i = ranges::find( + list, + sendAs, + &Main::SendAsPeer::peer); + if (i != end(list) + && i->premiumRequired + && !sendAs->session().premium()) { + ShowPremiumPromoToast(window); + return false; + } session->sendAsPeers().saveChosen(peer, sendAs); + return true; }; window->show(Box( Ui::ChooseSendAsBox, diff --git a/Telegram/SourceFiles/ui/chat/choose_send_as.h b/Telegram/SourceFiles/ui/chat/choose_send_as.h index 1af733e1a..f994b071d 100644 --- a/Telegram/SourceFiles/ui/chat/choose_send_as.h +++ b/Telegram/SourceFiles/ui/chat/choose_send_as.h @@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class PeerData; +namespace Main { +struct SendAsPeer; +} // namespace Main + namespace Window { class SessionController; } // namespace Window @@ -22,9 +26,9 @@ class SendAsButton; void ChooseSendAsBox( not_null box, - std::vector> list, + std::vector list, not_null chosen, - Fn)> done); + Fn)> done); void SetupSendAsButton( not_null button,