Show toast on forward attempt to premium required.

This commit is contained in:
John Preston 2024-01-17 15:05:44 +04:00
parent 7c468052e6
commit eda7118df9
12 changed files with 428 additions and 64 deletions

View file

@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h"
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "history/history_item.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
@ -334,6 +338,72 @@ const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
return _subscriptionOptions;
}
rpl::producer<> Premium::somePremiumRequiredResolved() const {
return _somePremiumRequiredResolved.events();
}
void Premium::resolvePremiumRequired(not_null<UserData*> user) {
_resolvePremiumRequiredUsers.emplace(user);
if (!_premiumRequiredRequestScheduled
&& _resolvePremiumRequestedUsers.empty()) {
_premiumRequiredRequestScheduled = true;
crl::on_main(_session, [=] {
requestPremiumRequiredSlice();
});
}
}
void Premium::requestPremiumRequiredSlice() {
_premiumRequiredRequestScheduled = false;
if (!_resolvePremiumRequestedUsers.empty()
|| _resolvePremiumRequiredUsers.empty()) {
return;
}
constexpr auto kPerRequest = 100;
auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers
| ranges::views::transform(&UserData::inputUser));
if (users.v.size() > kPerRequest) {
auto shortened = users.v;
shortened.resize(kPerRequest);
users = MTP_vector<MTPInputUser>(std::move(shortened));
const auto from = begin(_resolvePremiumRequiredUsers);
_resolvePremiumRequestedUsers = { from, from + kPerRequest };
_resolvePremiumRequiredUsers.erase(from, from + kPerRequest);
} else {
_resolvePremiumRequestedUsers
= base::take(_resolvePremiumRequiredUsers);
}
const auto finish = [=](const QVector<MTPBool> &list) {
constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite;
constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown;
constexpr auto mask = me | known;
auto index = 0;
for (const auto &user : base::take(_resolvePremiumRequestedUsers)) {
const auto require = (index < list.size())
&& mtpIsTrue(list[index++]);
user->setFlags((user->flags() & ~mask)
| known
| (require ? me : UserDataFlag()));
}
if (!_premiumRequiredRequestScheduled
&& !_resolvePremiumRequiredUsers.empty()) {
_premiumRequiredRequestScheduled = true;
crl::on_main(_session, [=] {
requestPremiumRequiredSlice();
});
}
_somePremiumRequiredResolved.fire({});
};
_session->api().request(
MTPusers_GetIsPremiumRequiredToContact(std::move(users))
).done([=](const MTPVector<MTPBool> &result) {
finish(result.v);
}).fail([=] {
finish({});
}).send();
}
PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null<PeerData*> peer)
: _peer(peer)
, _api(&peer->session().api().instance()) {
@ -494,4 +564,33 @@ bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const {
false);
}
RequirePremiumState ResolveRequiresPremiumToWrite(
not_null<History*> history) {
const auto user = history->peer->asUser();
if (!user
|| !user->someRequirePremiumToWrite()
|| user->session().premium()) {
return RequirePremiumState::No;
} else if (user->requirePremiumToWriteKnown()) {
return user->meRequiresPremiumToWrite()
? RequirePremiumState::Yes
: RequirePremiumState::No;
}
// 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 &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);
return RequirePremiumState::No;
}
}
}
return RequirePremiumState::Unknown;
}
} // namespace Api

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_subscription_option.h"
#include "mtproto/sender.h"
class History;
class ApiWrap;
namespace Main {
@ -103,10 +104,14 @@ public:
[[nodiscard]] auto subscriptionOptions() const
-> const Data::SubscriptionOptions &;
[[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const;
void resolvePremiumRequired(not_null<UserData*> user);
private:
void reloadPromo();
void reloadStickers();
void reloadCloudSet();
void requestPremiumRequiredSlice();
const not_null<Main::Session*> _session;
MTP::Sender _api;
@ -143,6 +148,11 @@ private:
Data::SubscriptionOptions _subscriptionOptions;
rpl::event_stream<> _somePremiumRequiredResolved;
base::flat_set<not_null<UserData*>> _resolvePremiumRequiredUsers;
base::flat_set<not_null<UserData*>> _resolvePremiumRequestedUsers;
bool _premiumRequiredRequestScheduled = false;
};
class PremiumGiftCodeOptions final {
@ -196,4 +206,12 @@ private:
};
enum class RequirePremiumState {
Unknown,
Yes,
No,
};
[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite(
not_null<History*> history);
} // namespace Api

View file

@ -544,6 +544,12 @@ bool PeerListRow::checked() const {
return _checkbox && _checkbox->checked();
}
void PeerListRow::preloadUserpic() {
if (_peer) {
_peer->loadUserpic();
}
}
void PeerListRow::setCustomStatus(const QString &status, bool active) {
setStatusText(status);
_statusType = active ? StatusType::CustomActive : StatusType::Custom;
@ -1914,10 +1920,7 @@ void PeerListContent::loadProfilePhotos() {
if (to > rowsCount) to = rowsCount;
for (auto index = from; index != to; ++index) {
const auto row = getRow(RowIndex(index));
if (!row->special()) {
row->peer()->loadUserpic();
}
getRow(RowIndex(index))->preloadUserpic();
}
}
}

View file

@ -101,6 +101,8 @@ public:
[[nodiscard]] virtual auto generateNameWords() const
-> const base::flat_set<QString> &;
virtual void preloadUserpic();
void setCustomStatus(const QString &status, bool active = false);
void clearCustomStatus();

View file

@ -8,10 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h"
#include "api/api_chat_participants.h"
#include "api/api_premium.h"
#include "base/random.h"
#include "boxes/filters/edit_filter_chats_list.h"
#include "settings/settings_premium.h"
#include "ui/boxes/confirm_box.h"
#include "ui/effects/round_checkbox.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/popup_menu.h"
@ -19,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "main/main_session.h"
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_channel.h"
@ -44,10 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h"
#include "styles/style_profile.h"
#include "styles/style_dialogs.h"
#include "data/data_stories.h"
#include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_stories_list.h"
#include "styles/style_chat_helpers.h"
namespace {
@ -257,9 +258,63 @@ bool PeerListGlobalSearchController::isLoading() {
return _timer.isActive() || _requestId;
}
ChatsListBoxController::Row::Row(not_null<History*> history)
void ChatsListBoxController::RowDelegate::rowPreloadUserpic(
not_null<Row*> row) {
row->PeerListRow::preloadUserpic();
}
ChatsListBoxController::Row::Row(
not_null<History*> history,
RowDelegate *delegate)
: PeerListRow(history->peer)
, _history(history) {
, _history(history)
, _delegate(delegate) {
}
void PaintLock(
Painter &p,
not_null<const style::PeerListItem*> st,
int x,
int y,
int outerWidth,
int size) {
auto hq = PainterHighQualityEnabler(p);
const auto &check = st->checkbox.check;
auto pen = check.border->p;
pen.setWidthF(check.width);
p.setPen(pen);
p.setBrush(st::premiumButtonBg2);
const auto &icon = st::stickersPremiumLock;
const auto width = icon.width();
const auto height = icon.height();
const auto rect = QRect(
QPoint(x + size - width, y + size - height),
icon.size());
p.drawEllipse(rect);
icon.paintInCenter(p, rect);
}
auto ChatsListBoxController::Row::generatePaintUserpicCallback(
bool forceRound)
-> PaintRoundImageCallback {
auto result = PeerListRow::generatePaintUserpicCallback(forceRound);
if (_locked) {
AssertIsDebug();
const auto st = /*_delegate ? _delegate->rowSt() : */&st::defaultPeerListItem;
return [=](Painter &p, int x, int y, int outerWidth, int size) {
result(p, x, y, outerWidth, size);
PaintLock(p, st, x, y, outerWidth, size);
};
}
return result;
}
void ChatsListBoxController::Row::preloadUserpic() {
if (_delegate) {
_delegate->rowPreloadUserpic(this);
} else {
PeerListRow::preloadUserpic();
}
}
ChatsListBoxController::ChatsListBoxController(
@ -629,14 +684,40 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
return std::make_unique<PeerListRow>(user);
}
ChooseRecipientPremiumRequiredError WritePremiumRequiredError(
not_null<UserData*> user) {
return {
.text = tr::lng_send_non_premium_message_toast(
tr::now,
lt_user,
TextWithEntities{ user->shortName() },
lt_link,
Ui::Text::Link(
Ui::Text::Bold(
tr::lng_send_non_premium_message_toast_link(
tr::now))),
Ui::Text::RichLangValue),
};
}
ChooseRecipientBoxController::ChooseRecipientBoxController(
not_null<Main::Session*> session,
FnMut<void(not_null<Data::Thread*>)> callback,
Fn<bool(not_null<Data::Thread*>)> filter)
: ChatsListBoxController(session)
, _session(session)
, _callback(std::move(callback))
, _filter(std::move(filter)) {
: ChooseRecipientBoxController({
.session = session,
.callback = std::move(callback),
.filter = std::move(filter),
}) {
}
ChooseRecipientBoxController::ChooseRecipientBoxController(
ChooseRecipientArgs &&args)
: ChatsListBoxController(args.session)
, _session(args.session)
, _callback(std::move(args.callback))
, _filter(std::move(args.filter))
, _premiumRequiredError(std::move(args.premiumRequiredError)) {
}
Main::Session &ChooseRecipientBoxController::session() const {
@ -645,9 +726,50 @@ Main::Session &ChooseRecipientBoxController::session() const {
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);
}
}
void ChooseRecipientBoxController::refreshLockedRows() {
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count; ++i) {
const auto raw = delegate()->peerListRowAt(i);
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);
}
}
}
}
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);
}
}
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
if (showLockedError(row)) {
return;
}
auto guard = base::make_weak(this);
const auto peer = row->peer();
if (const auto forum = peer->forum()) {
@ -698,6 +820,19 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
}
}
bool ChooseRecipientBoxController::showLockedError(
not_null<PeerListRow*> row) const {
if (!static_cast<Row*>(row.get())->locked()) {
return false;
}
::Settings::ShowPremiumPromoToast(
delegate()->peerListUiShow(),
ChatHelpers::ResolveWindowDefault(),
_premiumRequiredError(row->peer()->asUser()).text,
u"require_premium"_q);
return true;
}
QString ChooseRecipientBoxController::savedMessagesChatStatus() const {
return tr::lng_saved_forward_here(tr::now);
}
@ -708,8 +843,23 @@ auto ChooseRecipientBoxController::createRow(
const auto skip = _filter
? !_filter(history)
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|| (peer->isUser() && !Data::CanSendAnything(peer)));
return skip ? nullptr : std::make_unique<Row>(history);
|| peer->isRepliesChat()
|| (peer->isUser() && (_premiumRequiredError
? peer->asUser()->isInaccessible()
: !Data::CanSendAnything(peer))));
if (skip) {
return nullptr;
}
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);
}
}
return result;
}
ChooseTopicSearchController::ChooseTopicSearchController(

View file

@ -91,16 +91,34 @@ private:
class ChatsListBoxController : public PeerListController {
public:
class Row;
class RowDelegate {
public:
virtual void rowPreloadUserpic(not_null<Row*> row);
};
class Row : public PeerListRow {
public:
Row(not_null<History*> history);
Row(not_null<History*> history, RowDelegate *delegate = nullptr);
not_null<History*> history() const {
[[nodiscard]] not_null<History*> history() const {
return _history;
}
[[nodiscard]] bool locked() const {
return _locked;
}
void setLocked(bool locked) {
_locked = locked;
}
PaintRoundImageCallback generatePaintUserpicCallback(
bool forceRound) override;
void preloadUserpic() override;
private:
not_null<History*> _history;
const not_null<History*> _history;
RowDelegate *_delegate = nullptr;
bool _locked = false;
};
@ -207,14 +225,32 @@ 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;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
};
class ChooseRecipientBoxController
: public ChatsListBoxController
, public base::has_weak_ptr {
, public base::has_weak_ptr
, private ChatsListBoxController::RowDelegate {
public:
ChooseRecipientBoxController(
not_null<Main::Session*> session,
FnMut<void(not_null<Data::Thread*>)> callback,
Fn<bool(not_null<Data::Thread*>)> filter = nullptr);
explicit ChooseRecipientBoxController(ChooseRecipientArgs &&args);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
@ -225,10 +261,19 @@ protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(not_null<History*> history) override;
[[nodiscard]] bool showLockedError(not_null<PeerListRow*> row) const;
private:
void refreshLockedRows();
void rowPreloadUserpic(not_null<Row*> row) override;
const not_null<Main::Session*> _session;
FnMut<void(not_null<Data::Thread*>)> _callback;
Fn<bool(not_null<Data::Thread*>)> _filter;
Fn<ChooseRecipientPremiumRequiredError(
not_null<UserData*>)> _premiumRequiredError;
rpl::lifetime _lifetime;
};

View file

@ -18,29 +18,35 @@ rpl::producer<bool> Show::adjustShadowLeft() const {
return rpl::single(false);
}
Window::SessionController *Show::resolveWindow(WindowUsage usage) const {
const auto session = &this->session();
const auto check = [&](Window::Controller *window) {
if (const auto controller = window->sessionController()) {
if (&controller->session() == session) {
return controller;
ResolveWindow ResolveWindowDefault() {
return [](not_null<Main::Session*> session, WindowUsage usage)
-> Window::SessionController* {
const auto check = [&](Window::Controller *window) {
if (const auto controller = window->sessionController()) {
if (&controller->session() == session) {
return controller;
}
}
return (Window::SessionController*)nullptr;
};
auto &app = Core::App();
if (const auto a = check(app.activeWindow())) {
return a;
} else if (const auto b = check(app.activePrimaryWindow())) {
return b;
} else if (const auto c = check(app.windowFor(&session->account()))) {
return c;
} else if (const auto d = check(
app.ensureSeparateWindowForAccount(
&session->account()))) {
return d;
}
return (Window::SessionController*)nullptr;
return nullptr;
};
auto &app = Core::App();
if (const auto a = check(app.activeWindow())) {
return a;
} else if (const auto b = check(app.activePrimaryWindow())) {
return b;
} else if (const auto c = check(app.windowFor(&session->account()))) {
return c;
} else if (const auto d = check(
app.ensureSeparateWindowForAccount(
&session->account()))) {
return d;
}
return nullptr;
}
Window::SessionController *Show::resolveWindow(WindowUsage usage) const {
return ResolveWindowDefault()(&session(), usage);
}
} // namespace ChatHelpers

View file

@ -44,6 +44,11 @@ enum class WindowUsage {
PremiumPromo,
};
using ResolveWindow = Fn<Window::SessionController*(
not_null<Main::Session*>,
WindowUsage)>;
[[nodiscard]] ResolveWindow ResolveWindowDefault();
class Show : public Main::SessionShow {
public:
virtual void activate() = 0;

View file

@ -892,14 +892,13 @@ void ShowReplyToChatBox(
using Chosen = not_null<Data::Thread*>;
Controller(not_null<Main::Session*> session)
: ChooseRecipientBoxController(
session,
[=](Chosen thread) mutable { _singleChosen.fire_copy(thread); },
nullptr) {
}
void rowClicked(not_null<PeerListRow*> row) override final {
ChooseRecipientBoxController::rowClicked(row);
: ChooseRecipientBoxController({
.session = session,
.callback = [=](Chosen thread) {
_singleChosen.fire_copy(thread);
},
.premiumRequiredError = WritePremiumRequiredError,
}) {
}
[[nodiscard]] rpl::producer<Chosen> singleChosen() const {
@ -912,6 +911,7 @@ void ShowReplyToChatBox(
private:
void prepareViewHook() override {
ChooseRecipientBoxController::prepareViewHook();
delegate()->peerListSetTitle(tr::lng_reply_in_another_title());
}

View file

@ -1316,6 +1316,22 @@ void ShowPremiumPromoToast(
std::shared_ptr<ChatHelpers::Show> show,
TextWithEntities textWithLink,
const QString &ref) {
ShowPremiumPromoToast(show, [=](
not_null<Main::Session*> session,
ChatHelpers::WindowUsage usage) {
Expects(&show->session() == session);
return show->resolveWindow(usage);
}, std::move(textWithLink), ref);
}
void ShowPremiumPromoToast(
std::shared_ptr<Main::SessionShow> show,
Fn<Window::SessionController*(
not_null<Main::Session*>,
ChatHelpers::WindowUsage)> resolveWindow,
TextWithEntities textWithLink,
const QString &ref) {
using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
const auto toast = std::make_shared<WeakToast>();
(*toast) = show->showToast({
@ -1331,7 +1347,8 @@ void ShowPremiumPromoToast(
if (const auto strong = toast->get()) {
strong->hideAnimated();
(*toast) = nullptr;
if (const auto controller = show->resolveWindow(
if (const auto controller = resolveWindow(
&show->session(),
ChatHelpers::WindowUsage::PremiumPromo)) {
Settings::ShowPremium(controller, ref);
}

View file

@ -17,6 +17,7 @@ struct RoundButton;
namespace ChatHelpers {
class Show;
enum class WindowUsage;
} // namespace ChatHelpers
namespace Ui {
@ -28,6 +29,7 @@ class VerticalLayout;
namespace Main {
class Session;
class SessionShow;
} // namespace Main
namespace Window {
@ -61,6 +63,13 @@ void ShowPremiumPromoToast(
std::shared_ptr<ChatHelpers::Show> show,
TextWithEntities textWithLink,
const QString &ref);
void ShowPremiumPromoToast(
std::shared_ptr<::Main::SessionShow> show,
Fn<Window::SessionController*(
not_null<::Main::Session*>,
ChatHelpers::WindowUsage)> resolveWindow,
TextWithEntities textWithLink,
const QString &ref);
struct SubscribeButtonArgs final {
Window::SessionController *controller = nullptr;

View file

@ -1504,8 +1504,11 @@ void PeerMenuShareContactBox(
*weak = navigation->parentController()->show(
Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(
&navigation->session(),
std::move(callback)),
ChooseRecipientArgs{
.session = &navigation->session(),
.callback = std::move(callback),
.premiumRequiredError = WritePremiumRequiredError,
}),
[](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
@ -1742,10 +1745,12 @@ QPointer<Ui::BoxContent> ShowChooseRecipientBox(
}
};
*weak = navigation->parentController()->show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(
&navigation->session(),
std::move(callback),
std::move(filter)),
std::make_unique<ChooseRecipientBoxController>(ChooseRecipientArgs{
.session = &navigation->session(),
.callback = std::move(callback),
.filter = std::move(filter),
.premiumRequiredError = WritePremiumRequiredError,
}),
std::move(initBox)));
return weak->data();
}
@ -1799,15 +1804,18 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
using Chosen = not_null<Data::Thread*>;
Controller(not_null<Main::Session*> session)
: ChooseRecipientBoxController(
session,
[=](Chosen thread) mutable { _singleChosen.fire_copy(thread); },
nullptr) {
: ChooseRecipientBoxController({
.session = session,
.callback = [=](Chosen thread) {
_singleChosen.fire_copy(thread);
},
.premiumRequiredError = WritePremiumRequiredError,
}) {
}
void rowClicked(not_null<PeerListRow*> row) override final {
const auto count = delegate()->peerListSelectedRowsCount();
if (count && row->peer()->isForum()) {
if (showLockedError(row) || (count && row->peer()->isForum())) {
return;
} else if (!count || row->peer()->isForum()) {
ChooseRecipientBoxController::rowClicked(row);
@ -2126,10 +2134,12 @@ QPointer<Ui::BoxContent> ShowShareGameBox(
});
};
*weak = navigation->parentController()->show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(
&navigation->session(),
std::move(chosen),
std::move(filter)),
std::make_unique<ChooseRecipientBoxController>(ChooseRecipientArgs{
.session = &navigation->session(),
.callback = std::move(chosen),
.filter = std::move(filter),
.premiumRequiredError = WritePremiumRequiredError,
}),
std::move(initBox)));
return weak->data();
}