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(
not_null<History*> history) {
const auto user = history->peer->asUser();
not_null<PeerData*> 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;
}

View File

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

View File

@ -258,27 +258,24 @@ bool PeerListGlobalSearchController::isLoading() {
return _timer.isActive() || _requestId;
}
void ChatsListBoxController::RowDelegate::rowPreloadUserpic(
not_null<Row*> row) {
row->PeerListRow::preloadUserpic();
RecipientRow::RecipientRow(
not_null<PeerData*> 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*> 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<const style::PeerListItem*> 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<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(
@ -662,7 +709,7 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
return std::make_unique<PeerListRow>(user);
}
ChooseRecipientPremiumRequiredError WritePremiumRequiredError(
RecipientPremiumRequiredError WritePremiumRequiredError(
not_null<UserData*> 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<PeerListRow*> raw) {
const auto row = static_cast<Row*>(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) {
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;
bool ChooseRecipientBoxController::showLockedError(
not_null<PeerListRow*> row) {
return RecipientRow::ShowLockedError(this, row, _premiumRequiredError);
}
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
@ -808,15 +816,17 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
}
}
bool ChooseRecipientBoxController::showLockedError(
not_null<PeerListRow*> row) const {
if (!static_cast<Row*>(row.get())->locked()) {
bool RecipientRow::ShowLockedError(
not_null<PeerListController*> controller,
not_null<PeerListRow*> row,
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error) {
if (!static_cast<RecipientRow*>(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<Row>(
history,
static_cast<RowDelegate*>(this));
if (_premiumRequiredError) {
const auto require = Api::ResolveRequiresPremiumToWrite(history);
if (require == Api::RequirePremiumState::Yes) {
result->setLocked(true);
}
}
_premiumRequiredError ? &computeListSt().item : nullptr);
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 {
public:
class Row;
class RowDelegate {
class Row : public RecipientRow {
public:
virtual void rowPreloadUserpic(not_null<Row*> row);
[[nodiscard]] virtual auto rowSt()
-> not_null<const style::PeerListItem*> = 0;
};
class Row : public PeerListRow {
public:
Row(not_null<History*> history, RowDelegate *delegate = nullptr);
Row(
not_null<History*> history,
const style::PeerListItem *maybeLockedSt = nullptr);
[[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 {
not_null<Main::Session*> session;
FnMut<void(not_null<Data::Thread*>)> callback;
Fn<bool(not_null<Data::Thread*>)> filter;
using PremiumRequiredError = ChooseRecipientPremiumRequiredError;
using PremiumRequiredError = RecipientPremiumRequiredError;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
};
class ChooseRecipientBoxController
: public ChatsListBoxController
, public base::has_weak_ptr
, private ChatsListBoxController::RowDelegate {
, public base::has_weak_ptr {
public:
ChooseRecipientBoxController(
not_null<Main::Session*> session,
@ -267,21 +284,15 @@ protected:
void prepareViewHook() 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:
void refreshLockedRows();
void rowPreloadUserpic(not_null<Row*> row) override;
not_null<const style::PeerListItem*> rowSt() override;
const not_null<Main::Session*> _session;
FnMut<void(not_null<Data::Thread*>)> _callback;
Fn<bool(not_null<Data::Thread*>)> _filter;
Fn<ChooseRecipientPremiumRequiredError(
Fn<RecipientPremiumRequiredError(
not_null<UserData*>)> _premiumRequiredError;
rpl::lifetime _lifetime;
};
class ChooseTopicSearchController : public PeerListSearchController {

View File

@ -267,6 +267,10 @@ void AddParticipantsBoxController::subscribeToMigration() {
}
void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> 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<PeerListRow> AddParticipantsBoxController::createRow(
if (user->isSelf()) {
return nullptr;
}
auto result = std::make_unique<PeerListRow>(user);
if (isAlreadyIn(user)) {
const auto already = isAlreadyIn(user);
const auto maybeLockedSt = already ? nullptr : &computeListSt().item;
auto result = std::make_unique<RecipientRow>(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<ChatData*> chat) {

View File

@ -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*> chat) {
void ShareBox::Inner::initChatLocked(not_null<Chat*> 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<Dialogs::Entry*> 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<ChooseRecipientPremiumRequiredError(not_null<UserData*>)> {
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
return WritePremiumRequiredError;
}

View File

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