diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index b59547e99..72da2a489 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -565,8 +565,9 @@ bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const { } RequirePremiumState ResolveRequiresPremiumToWrite( - not_null history) { - const auto user = history->peer->asUser(); + not_null peer, + History *maybeHistory) { + const auto user = peer->asUser(); if (!user || !user->someRequirePremiumToWrite() || user->session().premium()) { @@ -575,21 +576,36 @@ RequirePremiumState ResolveRequiresPremiumToWrite( return user->meRequiresPremiumToWrite() ? RequirePremiumState::Yes : RequirePremiumState::No; + } else if (user->flags() & UserDataFlag::MutualContact) { + return RequirePremiumState::No; + } else if (!maybeHistory) { + return RequirePremiumState::Unknown; } + + const auto update = [&](bool require) { + using Flag = UserDataFlag; + constexpr auto known = Flag::RequirePremiumToWriteKnown; + constexpr auto me = Flag::MeRequiresPremiumToWrite; + user->setFlags((user->flags() & ~me) + | known + | (require ? me : Flag())); + }; // We allow this potentially-heavy loop because in case we've opened // the chat and have a lot of messages `requires_premium` will be known. - for (const auto &block : history->blocks) { + for (const auto &block : maybeHistory->blocks) { for (const auto &view : block->messages) { const auto item = view->data(); if (!item->out() && !item->isService()) { - using Flag = UserDataFlag; - constexpr auto known = Flag::RequirePremiumToWriteKnown; - constexpr auto me = Flag::MeRequiresPremiumToWrite; - user->setFlags((user->flags() | known) & ~me); + update(false); return RequirePremiumState::No; } } } + if (user->isContact() // Here we know, that we're not in his contacts. + && maybeHistory->loadedAtTop() // And no incoming messages. + && maybeHistory->loadedAtBottom()) { + update(true); + } return RequirePremiumState::Unknown; } diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 19f946691..e1c3a7b41 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -212,6 +212,7 @@ enum class RequirePremiumState { No, }; [[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite( - not_null history); + not_null peer, + History *maybeHistory); } // namespace Api diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 382301725..7063372d9 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -258,27 +258,24 @@ bool PeerListGlobalSearchController::isLoading() { return _timer.isActive() || _requestId; } -void ChatsListBoxController::RowDelegate::rowPreloadUserpic( - not_null row) { - row->PeerListRow::preloadUserpic(); +RecipientRow::RecipientRow( + not_null peer, + const style::PeerListItem *maybeLockedSt, + History *maybeHistory) +: PeerListRow(peer) +, _maybeHistory(maybeHistory) +, _resolvePremiumRequired(maybeLockedSt != nullptr) { + if (maybeLockedSt + && (Api::ResolveRequiresPremiumToWrite(peer, maybeHistory) + == Api::RequirePremiumState::Yes)) { + _lockedSt = maybeLockedSt; + } } -ChatsListBoxController::Row::Row( - not_null history, - RowDelegate *delegate) -: PeerListRow(history->peer) -, _history(history) -, _delegate(delegate) { -} - -auto ChatsListBoxController::Row::generatePaintUserpicCallback( - bool forceRound) --> PaintRoundImageCallback { +PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback( + bool forceRound) { auto result = PeerListRow::generatePaintUserpicCallback(forceRound); - if (_locked) { - const auto st = _delegate - ? _delegate->rowSt().get() - : &st::defaultPeerListItem; + if (const auto st = _lockedSt) { return [=](Painter &p, int x, int y, int outerWidth, int size) { result(p, x, y, outerWidth, size); PaintPremiumRequiredLock(p, st, x, y, outerWidth, size); @@ -287,12 +284,62 @@ auto ChatsListBoxController::Row::generatePaintUserpicCallback( return result; } -void ChatsListBoxController::Row::preloadUserpic() { - if (_delegate) { - _delegate->rowPreloadUserpic(this); - } else { - PeerListRow::preloadUserpic(); +bool RecipientRow::refreshLock( + not_null maybeLockedSt) { + if (const auto user = peer()->asUser()) { + const auto locked = _resolvePremiumRequired + && (Api::ResolveRequiresPremiumToWrite(user, _maybeHistory) + == Api::RequirePremiumState::Yes); + if (this->locked() != locked) { + setLocked(locked ? maybeLockedSt.get() : nullptr); + return true; + } } + return false; +} + +void RecipientRow::preloadUserpic() { + PeerListRow::preloadUserpic(); + + if (!_resolvePremiumRequired) { + return; + } else if (Api::ResolveRequiresPremiumToWrite(peer(), _maybeHistory) + == Api::RequirePremiumState::Unknown) { + const auto user = peer()->asUser(); + user->session().api().premium().resolvePremiumRequired(user); + } +} + +void TrackPremiumRequiredChanges( + not_null controller, + rpl::lifetime &lifetime) { + const auto session = &controller->session(); + rpl::merge( + Data::AmPremiumValue(session) | rpl::to_empty, + session->api().premium().somePremiumRequiredResolved() + ) | rpl::start_with_next([=] { + const auto st = &controller->computeListSt().item; + const auto delegate = controller->delegate(); + const auto process = [&](not_null raw) { + if (static_cast(raw.get())->refreshLock(st)) { + delegate->peerListUpdateRow(raw); + } + }; + auto count = delegate->peerListFullRowsCount(); + for (auto i = 0; i != count; ++i) { + process(delegate->peerListRowAt(i)); + } + count = delegate->peerListSearchRowsCount(); + for (auto i = 0; i != count; ++i) { + process(delegate->peerListSearchRowAt(i)); + } + }, lifetime); +} + +ChatsListBoxController::Row::Row( + not_null history, + const style::PeerListItem *maybeLockedSt) +: RecipientRow(history->peer, maybeLockedSt, history) { } ChatsListBoxController::ChatsListBoxController( @@ -662,7 +709,7 @@ std::unique_ptr ContactsBoxController::createRow( return std::make_unique(user); } -ChooseRecipientPremiumRequiredError WritePremiumRequiredError( +RecipientPremiumRequiredError WritePremiumRequiredError( not_null user) { return { .text = tr::lng_send_non_premium_message_toast( @@ -706,52 +753,13 @@ void ChooseRecipientBoxController::prepareViewHook() { delegate()->peerListSetTitle(tr::lng_forward_choose()); if (_premiumRequiredError) { - rpl::merge( - Data::AmPremiumValue(_session) | rpl::to_empty, - _session->api().premium().somePremiumRequiredResolved() - ) | rpl::start_with_next([=] { - refreshLockedRows(); - }, _lifetime); + TrackPremiumRequiredChanges(this, lifetime()); } } -void ChooseRecipientBoxController::refreshLockedRows() { - const auto process = [&](not_null raw) { - const auto row = static_cast(raw.get()); - if (const auto user = row->peer()->asUser()) { - const auto history = row->history(); - const auto locked = (Api::ResolveRequiresPremiumToWrite(history) - == Api::RequirePremiumState::Yes); - if (row->locked() != locked) { - row->setLocked(locked); - delegate()->peerListUpdateRow(row); - } - } - }; - auto count = delegate()->peerListFullRowsCount(); - for (auto i = 0; i != count; ++i) { - process(delegate()->peerListRowAt(i)); - } - count = delegate()->peerListSearchRowsCount(); - for (auto i = 0; i != count; ++i) { - process(delegate()->peerListSearchRowAt(i)); - } -} - -void ChooseRecipientBoxController::rowPreloadUserpic(not_null row) { - row->PeerListRow::preloadUserpic(); - - if (!_premiumRequiredError) { - return; - } else if (Api::ResolveRequiresPremiumToWrite(row->history()) - == Api::RequirePremiumState::Unknown) { - const auto user = row->peer()->asUser(); - session().api().premium().resolvePremiumRequired(user); - } -} - -not_null ChooseRecipientBoxController::rowSt() { - return &computeListSt().item; +bool ChooseRecipientBoxController::showLockedError( + not_null row) { + return RecipientRow::ShowLockedError(this, row, _premiumRequiredError); } void ChooseRecipientBoxController::rowClicked(not_null row) { @@ -808,15 +816,17 @@ void ChooseRecipientBoxController::rowClicked(not_null row) { } } -bool ChooseRecipientBoxController::showLockedError( - not_null row) const { - if (!static_cast(row.get())->locked()) { +bool RecipientRow::ShowLockedError( + not_null controller, + not_null row, + Fn)> error) { + if (!static_cast(row.get())->locked()) { return false; } ::Settings::ShowPremiumPromoToast( - delegate()->peerListUiShow(), + controller->delegate()->peerListUiShow(), ChatHelpers::ResolveWindowDefault(), - _premiumRequiredError(row->peer()->asUser()).text, + error(row->peer()->asUser()).text, u"require_premium"_q); return true; } @@ -840,13 +850,7 @@ auto ChooseRecipientBoxController::createRow( } auto result = std::make_unique( history, - static_cast(this)); - if (_premiumRequiredError) { - const auto require = Api::ResolveRequiresPremiumToWrite(history); - if (require == Api::RequirePremiumState::Yes) { - result->setLocked(true); - } - } + _premiumRequiredError ? &computeListSt().item : nullptr); return result; } diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index 73522ba4d..20479c2b4 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -93,38 +93,63 @@ private: }; +struct RecipientPremiumRequiredError { + TextWithEntities text; +}; + +[[nodiscard]] RecipientPremiumRequiredError WritePremiumRequiredError( + not_null user); + +class RecipientRow : public PeerListRow { +public: + explicit RecipientRow( + not_null peer, + const style::PeerListItem *maybeLockedSt = nullptr, + History *maybeHistory = nullptr); + + bool refreshLock(not_null maybeLockedSt); + + [[nodiscard]] static bool ShowLockedError( + not_null controller, + not_null row, + Fn)> error); + + [[nodiscard]] History *maybeHistory() const { + return _maybeHistory; + } + [[nodiscard]] bool locked() const { + return _lockedSt != nullptr; + } + void setLocked(const style::PeerListItem *lockedSt) { + _lockedSt = lockedSt; + } + PaintRoundImageCallback generatePaintUserpicCallback( + bool forceRound) override; + + void preloadUserpic() override; + +private: + History *_maybeHistory = nullptr; + const style::PeerListItem *_lockedSt = nullptr; + bool _resolvePremiumRequired = false; + +}; + +void TrackPremiumRequiredChanges( + not_null controller, + rpl::lifetime &lifetime); + class ChatsListBoxController : public PeerListController { public: - class Row; - class RowDelegate { + class Row : public RecipientRow { public: - virtual void rowPreloadUserpic(not_null row); - [[nodiscard]] virtual auto rowSt() - -> not_null = 0; - }; - - class Row : public PeerListRow { - public: - Row(not_null history, RowDelegate *delegate = nullptr); + Row( + not_null history, + const style::PeerListItem *maybeLockedSt = nullptr); [[nodiscard]] not_null history() const { - return _history; + return maybeHistory(); } - [[nodiscard]] bool locked() const { - return _locked; - } - void setLocked(bool locked) { - _locked = locked; - } - PaintRoundImageCallback generatePaintUserpicCallback( - bool forceRound) override; - - void preloadUserpic() override; - - private: - const not_null _history; - RowDelegate *_delegate = nullptr; - bool _locked = false; }; @@ -231,26 +256,18 @@ private: }; -struct ChooseRecipientPremiumRequiredError { - TextWithEntities text; -}; - -[[nodiscard]] ChooseRecipientPremiumRequiredError WritePremiumRequiredError( - not_null user); - struct ChooseRecipientArgs { not_null session; FnMut)> callback; Fn)> filter; - using PremiumRequiredError = ChooseRecipientPremiumRequiredError; + using PremiumRequiredError = RecipientPremiumRequiredError; Fn)> premiumRequiredError; }; class ChooseRecipientBoxController : public ChatsListBoxController - , public base::has_weak_ptr - , private ChatsListBoxController::RowDelegate { + , public base::has_weak_ptr { public: ChooseRecipientBoxController( not_null session, @@ -267,21 +284,15 @@ protected: void prepareViewHook() override; std::unique_ptr createRow(not_null history) override; - [[nodiscard]] bool showLockedError(not_null row) const; + bool showLockedError(not_null row); private: - void refreshLockedRows(); - void rowPreloadUserpic(not_null row) override; - not_null rowSt() override; - const not_null _session; FnMut)> _callback; Fn)> _filter; - Fn)> _premiumRequiredError; - rpl::lifetime _lifetime; - }; class ChooseTopicSearchController : public PeerListSearchController { diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index b27ce349a..d6ddebfa9 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -267,6 +267,10 @@ void AddParticipantsBoxController::subscribeToMigration() { } void AddParticipantsBoxController::rowClicked(not_null row) { + const auto premiumRequiredError = WritePremiumRequiredError; + if (RecipientRow::ShowLockedError(this, row, premiumRequiredError)) { + return; + } const auto &serverConfig = session().serverConfig(); auto count = fullCount(); auto limit = _peer && (_peer->isChat() || _peer->isMegagroup()) @@ -332,8 +336,10 @@ std::unique_ptr AddParticipantsBoxController::createRow( if (user->isSelf()) { return nullptr; } - auto result = std::make_unique(user); - if (isAlreadyIn(user)) { + const auto already = isAlreadyIn(user); + const auto maybeLockedSt = already ? nullptr : &computeListSt().item; + auto result = std::make_unique(user, maybeLockedSt); + if (already) { result->setDisabledState(PeerListRow::State::DisabledChecked); } return result; @@ -707,6 +713,8 @@ void AddSpecialBoxController::prepare() { loadMoreRows(); } delegate()->peerListRefreshRows(); + + TrackPremiumRequiredChanges(this, lifetime()); } void AddSpecialBoxController::prepareChatRows(not_null chat) { diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index b5a549330..db06d0043 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -741,8 +741,10 @@ void ShareBox::Inner::refreshLockedRows() { auto changed = false; for (const auto &[peer, data] : _dataMap) { const auto history = data->history; - const auto locked = (Api::ResolveRequiresPremiumToWrite(history) - == Api::RequirePremiumState::Yes); + const auto locked = (Api::ResolveRequiresPremiumToWrite( + history->peer, + history + ) == Api::RequirePremiumState::Yes); if (data->locked != locked) { data->locked = locked; changed = true; @@ -750,8 +752,10 @@ void ShareBox::Inner::refreshLockedRows() { } for (const auto &data : d_byUsernameFiltered) { const auto history = data->history; - const auto locked = (Api::ResolveRequiresPremiumToWrite(history) - == Api::RequirePremiumState::Yes); + const auto locked = (Api::ResolveRequiresPremiumToWrite( + history->peer, + history + ) == Api::RequirePremiumState::Yes); if (data->locked != locked) { data->locked = locked; changed = true; @@ -821,8 +825,10 @@ void ShareBox::Inner::updateChatName(not_null chat) { void ShareBox::Inner::initChatLocked(not_null chat) { if (_descriptor.premiumRequiredError) { const auto history = chat->history; - const auto require = Api::ResolveRequiresPremiumToWrite(history); - if (require == Api::RequirePremiumState::Yes) { + if (Api::ResolveRequiresPremiumToWrite( + history->peer, + history + ) == Api::RequirePremiumState::Yes) { chat->locked = true; } } @@ -949,8 +955,10 @@ void ShareBox::Inner::preloadUserpic(not_null entry) { const auto history = entry->asHistory(); if (!_descriptor.premiumRequiredError || !history) { return; - } else if (Api::ResolveRequiresPremiumToWrite(history) - == Api::RequirePremiumState::Unknown) { + } else if (Api::ResolveRequiresPremiumToWrite( + history->peer, + history + ) == Api::RequirePremiumState::Unknown) { const auto user = history->peer->asUser(); _descriptor.session->api().premium().resolvePremiumRequired(user); } @@ -1661,7 +1669,7 @@ void FastShareMessage( } auto SharePremiumRequiredError() --> Fn)> { +-> Fn)> { return WritePremiumRequiredError; } diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index 00314f3ad..f0cdc5711 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -69,9 +69,9 @@ void FastShareMessage( not_null controller, not_null item); -struct ChooseRecipientPremiumRequiredError; +struct RecipientPremiumRequiredError; [[nodiscard]] auto SharePremiumRequiredError() --> Fn)>; +-> Fn)>; class ShareBox final : public Ui::BoxContent { public: @@ -106,7 +106,7 @@ public: } forwardOptions; HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle; - using PremiumRequiredError = ChooseRecipientPremiumRequiredError; + using PremiumRequiredError = RecipientPremiumRequiredError; Fn)> premiumRequiredError; }; ShareBox(QWidget*, Descriptor &&descriptor);