Show toast on invite attempt to premium required.

This commit is contained in:
John Preston 2024-01-18 12:01:03 +04:00
parent f3f660a180
commit f6a95df550
7 changed files with 193 additions and 145 deletions

View file

@ -565,8 +565,9 @@ bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const {
} }
RequirePremiumState ResolveRequiresPremiumToWrite( RequirePremiumState ResolveRequiresPremiumToWrite(
not_null<History*> history) { not_null<PeerData*> peer,
const auto user = history->peer->asUser(); History *maybeHistory) {
const auto user = peer->asUser();
if (!user if (!user
|| !user->someRequirePremiumToWrite() || !user->someRequirePremiumToWrite()
|| user->session().premium()) { || user->session().premium()) {
@ -575,21 +576,36 @@ RequirePremiumState ResolveRequiresPremiumToWrite(
return user->meRequiresPremiumToWrite() return user->meRequiresPremiumToWrite()
? RequirePremiumState::Yes ? RequirePremiumState::Yes
: RequirePremiumState::No; : 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 // 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. // 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) { for (const auto &view : block->messages) {
const auto item = view->data(); const auto item = view->data();
if (!item->out() && !item->isService()) { if (!item->out() && !item->isService()) {
using Flag = UserDataFlag; update(false);
constexpr auto known = Flag::RequirePremiumToWriteKnown;
constexpr auto me = Flag::MeRequiresPremiumToWrite;
user->setFlags((user->flags() | known) & ~me);
return RequirePremiumState::No; 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; return RequirePremiumState::Unknown;
} }

View file

@ -212,6 +212,7 @@ enum class RequirePremiumState {
No, No,
}; };
[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite( [[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite(
not_null<History*> history); not_null<PeerData*> peer,
History *maybeHistory);
} // namespace Api } // namespace Api

View file

@ -258,27 +258,24 @@ bool PeerListGlobalSearchController::isLoading() {
return _timer.isActive() || _requestId; return _timer.isActive() || _requestId;
} }
void ChatsListBoxController::RowDelegate::rowPreloadUserpic( RecipientRow::RecipientRow(
not_null<Row*> row) { not_null<PeerData*> peer,
row->PeerListRow::preloadUserpic(); 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( PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback(
not_null<History*> history, bool forceRound) {
RowDelegate *delegate)
: PeerListRow(history->peer)
, _history(history)
, _delegate(delegate) {
}
auto ChatsListBoxController::Row::generatePaintUserpicCallback(
bool forceRound)
-> PaintRoundImageCallback {
auto result = PeerListRow::generatePaintUserpicCallback(forceRound); auto result = PeerListRow::generatePaintUserpicCallback(forceRound);
if (_locked) { if (const auto st = _lockedSt) {
const auto st = _delegate
? _delegate->rowSt().get()
: &st::defaultPeerListItem;
return [=](Painter &p, int x, int y, int outerWidth, int size) { return [=](Painter &p, int x, int y, int outerWidth, int size) {
result(p, x, y, outerWidth, size); result(p, x, y, outerWidth, size);
PaintPremiumRequiredLock(p, st, x, y, outerWidth, size); PaintPremiumRequiredLock(p, st, x, y, outerWidth, size);
@ -287,12 +284,62 @@ auto ChatsListBoxController::Row::generatePaintUserpicCallback(
return result; return result;
} }
void ChatsListBoxController::Row::preloadUserpic() { bool RecipientRow::refreshLock(
if (_delegate) { not_null<const style::PeerListItem*> maybeLockedSt) {
_delegate->rowPreloadUserpic(this); if (const auto user = peer()->asUser()) {
} else { const auto locked = _resolvePremiumRequired
PeerListRow::preloadUserpic(); && (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<PeerListController*> 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<PeerListRow*> raw) {
if (static_cast<RecipientRow*>(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*> history,
const style::PeerListItem *maybeLockedSt)
: RecipientRow(history->peer, maybeLockedSt, history) {
} }
ChatsListBoxController::ChatsListBoxController( ChatsListBoxController::ChatsListBoxController(
@ -662,7 +709,7 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
return std::make_unique<PeerListRow>(user); return std::make_unique<PeerListRow>(user);
} }
ChooseRecipientPremiumRequiredError WritePremiumRequiredError( RecipientPremiumRequiredError WritePremiumRequiredError(
not_null<UserData*> user) { not_null<UserData*> user) {
return { return {
.text = tr::lng_send_non_premium_message_toast( .text = tr::lng_send_non_premium_message_toast(
@ -706,52 +753,13 @@ void ChooseRecipientBoxController::prepareViewHook() {
delegate()->peerListSetTitle(tr::lng_forward_choose()); delegate()->peerListSetTitle(tr::lng_forward_choose());
if (_premiumRequiredError) { if (_premiumRequiredError) {
rpl::merge( TrackPremiumRequiredChanges(this, lifetime());
Data::AmPremiumValue(_session) | rpl::to_empty,
_session->api().premium().somePremiumRequiredResolved()
) | rpl::start_with_next([=] {
refreshLockedRows();
}, _lifetime);
} }
} }
void ChooseRecipientBoxController::refreshLockedRows() { bool ChooseRecipientBoxController::showLockedError(
const auto process = [&](not_null<PeerListRow*> raw) { not_null<PeerListRow*> row) {
const auto row = static_cast<Row*>(raw.get()); return RecipientRow::ShowLockedError(this, row, _premiumRequiredError);
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) {
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<const style::PeerListItem*> ChooseRecipientBoxController::rowSt() {
return &computeListSt().item;
} }
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) { void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
@ -808,15 +816,17 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
} }
} }
bool ChooseRecipientBoxController::showLockedError( bool RecipientRow::ShowLockedError(
not_null<PeerListRow*> row) const { not_null<PeerListController*> controller,
if (!static_cast<Row*>(row.get())->locked()) { not_null<PeerListRow*> row,
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error) {
if (!static_cast<RecipientRow*>(row.get())->locked()) {
return false; return false;
} }
::Settings::ShowPremiumPromoToast( ::Settings::ShowPremiumPromoToast(
delegate()->peerListUiShow(), controller->delegate()->peerListUiShow(),
ChatHelpers::ResolveWindowDefault(), ChatHelpers::ResolveWindowDefault(),
_premiumRequiredError(row->peer()->asUser()).text, error(row->peer()->asUser()).text,
u"require_premium"_q); u"require_premium"_q);
return true; return true;
} }
@ -840,13 +850,7 @@ auto ChooseRecipientBoxController::createRow(
} }
auto result = std::make_unique<Row>( auto result = std::make_unique<Row>(
history, history,
static_cast<RowDelegate*>(this)); _premiumRequiredError ? &computeListSt().item : nullptr);
if (_premiumRequiredError) {
const auto require = Api::ResolveRequiresPremiumToWrite(history);
if (require == Api::RequirePremiumState::Yes) {
result->setLocked(true);
}
}
return result; return result;
} }

View file

@ -93,38 +93,63 @@ private:
}; };
struct RecipientPremiumRequiredError {
TextWithEntities text;
};
[[nodiscard]] RecipientPremiumRequiredError WritePremiumRequiredError(
not_null<UserData*> user);
class RecipientRow : public PeerListRow {
public:
explicit RecipientRow(
not_null<PeerData*> peer,
const style::PeerListItem *maybeLockedSt = nullptr,
History *maybeHistory = nullptr);
bool refreshLock(not_null<const style::PeerListItem*> maybeLockedSt);
[[nodiscard]] static bool ShowLockedError(
not_null<PeerListController*> controller,
not_null<PeerListRow*> row,
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> 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<PeerListController*> controller,
rpl::lifetime &lifetime);
class ChatsListBoxController : public PeerListController { class ChatsListBoxController : public PeerListController {
public: public:
class Row; class Row : public RecipientRow {
class RowDelegate {
public: public:
virtual void rowPreloadUserpic(not_null<Row*> row); Row(
[[nodiscard]] virtual auto rowSt() not_null<History*> history,
-> not_null<const style::PeerListItem*> = 0; const style::PeerListItem *maybeLockedSt = nullptr);
};
class Row : public PeerListRow {
public:
Row(not_null<History*> history, RowDelegate *delegate = nullptr);
[[nodiscard]] not_null<History*> history() const { [[nodiscard]] not_null<History*> 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*> _history;
RowDelegate *_delegate = nullptr;
bool _locked = false;
}; };
@ -231,26 +256,18 @@ private:
}; };
struct ChooseRecipientPremiumRequiredError {
TextWithEntities text;
};
[[nodiscard]] ChooseRecipientPremiumRequiredError WritePremiumRequiredError(
not_null<UserData*> user);
struct ChooseRecipientArgs { struct ChooseRecipientArgs {
not_null<Main::Session*> session; not_null<Main::Session*> session;
FnMut<void(not_null<Data::Thread*>)> callback; FnMut<void(not_null<Data::Thread*>)> callback;
Fn<bool(not_null<Data::Thread*>)> filter; Fn<bool(not_null<Data::Thread*>)> filter;
using PremiumRequiredError = ChooseRecipientPremiumRequiredError; using PremiumRequiredError = RecipientPremiumRequiredError;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError; Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
}; };
class ChooseRecipientBoxController class ChooseRecipientBoxController
: public ChatsListBoxController : public ChatsListBoxController
, public base::has_weak_ptr , public base::has_weak_ptr {
, private ChatsListBoxController::RowDelegate {
public: public:
ChooseRecipientBoxController( ChooseRecipientBoxController(
not_null<Main::Session*> session, not_null<Main::Session*> session,
@ -267,21 +284,15 @@ protected:
void prepareViewHook() override; void prepareViewHook() override;
std::unique_ptr<Row> createRow(not_null<History*> history) override; std::unique_ptr<Row> createRow(not_null<History*> history) override;
[[nodiscard]] bool showLockedError(not_null<PeerListRow*> row) const; bool showLockedError(not_null<PeerListRow*> row);
private: private:
void refreshLockedRows();
void rowPreloadUserpic(not_null<Row*> row) override;
not_null<const style::PeerListItem*> rowSt() override;
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
FnMut<void(not_null<Data::Thread*>)> _callback; FnMut<void(not_null<Data::Thread*>)> _callback;
Fn<bool(not_null<Data::Thread*>)> _filter; Fn<bool(not_null<Data::Thread*>)> _filter;
Fn<ChooseRecipientPremiumRequiredError( Fn<RecipientPremiumRequiredError(
not_null<UserData*>)> _premiumRequiredError; not_null<UserData*>)> _premiumRequiredError;
rpl::lifetime _lifetime;
}; };
class ChooseTopicSearchController : public PeerListSearchController { class ChooseTopicSearchController : public PeerListSearchController {

View file

@ -267,6 +267,10 @@ void AddParticipantsBoxController::subscribeToMigration() {
} }
void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) { void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto premiumRequiredError = WritePremiumRequiredError;
if (RecipientRow::ShowLockedError(this, row, premiumRequiredError)) {
return;
}
const auto &serverConfig = session().serverConfig(); const auto &serverConfig = session().serverConfig();
auto count = fullCount(); auto count = fullCount();
auto limit = _peer && (_peer->isChat() || _peer->isMegagroup()) auto limit = _peer && (_peer->isChat() || _peer->isMegagroup())
@ -332,8 +336,10 @@ std::unique_ptr<PeerListRow> AddParticipantsBoxController::createRow(
if (user->isSelf()) { if (user->isSelf()) {
return nullptr; return nullptr;
} }
auto result = std::make_unique<PeerListRow>(user); const auto already = isAlreadyIn(user);
if (isAlreadyIn(user)) { const auto maybeLockedSt = already ? nullptr : &computeListSt().item;
auto result = std::make_unique<RecipientRow>(user, maybeLockedSt);
if (already) {
result->setDisabledState(PeerListRow::State::DisabledChecked); result->setDisabledState(PeerListRow::State::DisabledChecked);
} }
return result; return result;
@ -707,6 +713,8 @@ void AddSpecialBoxController::prepare() {
loadMoreRows(); loadMoreRows();
} }
delegate()->peerListRefreshRows(); delegate()->peerListRefreshRows();
TrackPremiumRequiredChanges(this, lifetime());
} }
void AddSpecialBoxController::prepareChatRows(not_null<ChatData*> chat) { void AddSpecialBoxController::prepareChatRows(not_null<ChatData*> chat) {

View file

@ -741,8 +741,10 @@ void ShareBox::Inner::refreshLockedRows() {
auto changed = false; auto changed = false;
for (const auto &[peer, data] : _dataMap) { for (const auto &[peer, data] : _dataMap) {
const auto history = data->history; const auto history = data->history;
const auto locked = (Api::ResolveRequiresPremiumToWrite(history) const auto locked = (Api::ResolveRequiresPremiumToWrite(
== Api::RequirePremiumState::Yes); history->peer,
history
) == Api::RequirePremiumState::Yes);
if (data->locked != locked) { if (data->locked != locked) {
data->locked = locked; data->locked = locked;
changed = true; changed = true;
@ -750,8 +752,10 @@ void ShareBox::Inner::refreshLockedRows() {
} }
for (const auto &data : d_byUsernameFiltered) { for (const auto &data : d_byUsernameFiltered) {
const auto history = data->history; const auto history = data->history;
const auto locked = (Api::ResolveRequiresPremiumToWrite(history) const auto locked = (Api::ResolveRequiresPremiumToWrite(
== Api::RequirePremiumState::Yes); history->peer,
history
) == Api::RequirePremiumState::Yes);
if (data->locked != locked) { if (data->locked != locked) {
data->locked = locked; data->locked = locked;
changed = true; changed = true;
@ -821,8 +825,10 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
void ShareBox::Inner::initChatLocked(not_null<Chat*> chat) { void ShareBox::Inner::initChatLocked(not_null<Chat*> chat) {
if (_descriptor.premiumRequiredError) { if (_descriptor.premiumRequiredError) {
const auto history = chat->history; const auto history = chat->history;
const auto require = Api::ResolveRequiresPremiumToWrite(history); if (Api::ResolveRequiresPremiumToWrite(
if (require == Api::RequirePremiumState::Yes) { history->peer,
history
) == Api::RequirePremiumState::Yes) {
chat->locked = true; chat->locked = true;
} }
} }
@ -949,8 +955,10 @@ void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) {
const auto history = entry->asHistory(); const auto history = entry->asHistory();
if (!_descriptor.premiumRequiredError || !history) { if (!_descriptor.premiumRequiredError || !history) {
return; return;
} else if (Api::ResolveRequiresPremiumToWrite(history) } else if (Api::ResolveRequiresPremiumToWrite(
== Api::RequirePremiumState::Unknown) { history->peer,
history
) == Api::RequirePremiumState::Unknown) {
const auto user = history->peer->asUser(); const auto user = history->peer->asUser();
_descriptor.session->api().premium().resolvePremiumRequired(user); _descriptor.session->api().premium().resolvePremiumRequired(user);
} }
@ -1661,7 +1669,7 @@ void FastShareMessage(
} }
auto SharePremiumRequiredError() auto SharePremiumRequiredError()
-> Fn<ChooseRecipientPremiumRequiredError(not_null<UserData*>)> { -> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
return WritePremiumRequiredError; return WritePremiumRequiredError;
} }

View file

@ -69,9 +69,9 @@ void FastShareMessage(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item); not_null<HistoryItem*> item);
struct ChooseRecipientPremiumRequiredError; struct RecipientPremiumRequiredError;
[[nodiscard]] auto SharePremiumRequiredError() [[nodiscard]] auto SharePremiumRequiredError()
-> Fn<ChooseRecipientPremiumRequiredError(not_null<UserData*>)>; -> Fn<RecipientPremiumRequiredError(not_null<UserData*>)>;
class ShareBox final : public Ui::BoxContent { class ShareBox final : public Ui::BoxContent {
public: public:
@ -106,7 +106,7 @@ public:
} forwardOptions; } forwardOptions;
HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle; HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle;
using PremiumRequiredError = ChooseRecipientPremiumRequiredError; using PremiumRequiredError = RecipientPremiumRequiredError;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError; Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
}; };
ShareBox(QWidget*, Descriptor &&descriptor); ShareBox(QWidget*, Descriptor &&descriptor);