Implement multiboost reassign box.
This commit is contained in:
parent
a41bbd27c8
commit
c5d1739e95
|
@ -221,6 +221,8 @@ PRIVATE
|
|||
boxes/peers/peer_short_info_box.h
|
||||
boxes/peers/prepare_short_info_box.cpp
|
||||
boxes/peers/prepare_short_info_box.h
|
||||
boxes/peers/replace_boost_box.cpp
|
||||
boxes/peers/replace_boost_box.h
|
||||
boxes/about_box.cpp
|
||||
boxes/about_box.h
|
||||
boxes/about_sponsored_box.cpp
|
||||
|
|
|
@ -2052,6 +2052,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_boost_channel_post_stories#other" = "post **{count} stories** per day";
|
||||
"lng_boost_error_gifted_title" = "Can't boost with gifted Premium!";
|
||||
"lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels.";
|
||||
"lng_boost_need_more" = "More boosts needed";
|
||||
"lng_boost_need_more_text#one" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts.";
|
||||
"lng_boost_need_more_text#other" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts.";
|
||||
"lng_boost_need_more_again#one" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boost.";
|
||||
"lng_boost_need_more_again#other" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boosts.";
|
||||
"lng_boost_error_already_title" = "Already Boosted!";
|
||||
"lng_boost_error_already_text" = "You are already boosting this channel.";
|
||||
"lng_boost_error_premium_title" = "Premium needed!";
|
||||
|
@ -2062,12 +2067,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
|
||||
"lng_boost_now_replace" = "Replace";
|
||||
"lng_boost_reassign_title" = "Reassign boost";
|
||||
"lng_boost_reassign_text" = "To boost {channel}, reassign a previous boost from another channel.";
|
||||
"lng_boost_reassign_text" = "To boost {channel}, reassign a previous boost or {gift}.";
|
||||
"lng_boost_reassign_gift#one" = "gift **Telegram Premium** to a friend to get **{count}** additional boost";
|
||||
"lng_boost_reassign_gift#other" = "gift **Telegram Premium** to a friend to get **{count}** additional boosts";
|
||||
"lng_boost_remove_title" = "Remove your boost from";
|
||||
"lng_boost_reassign_button" = "Reassign";
|
||||
"lng_boost_available_in" = "available in {duration}";
|
||||
"lng_boost_available_in_toast#one" = "Wait until the boost is available or get **{count}** more boost by gifting a **Telegram Premium** subscription.";
|
||||
"lng_boost_available_in_toast#other" = "Wait until the boost is available or get **{count}** more boosts by gifting a **Telegram Premium** subscription.";
|
||||
"lng_boost_reassign_done#one" = "{count} boost is reassigned from {channels}.";
|
||||
"lng_boost_reassign_done#other" = "{count} boosts are reassigned from {channels}.";
|
||||
"lng_boost_reassign_channels#one" = "{count} channel";
|
||||
"lng_boost_reassign_channels#other" = "{count} channels";
|
||||
|
||||
"lng_boost_channel_title_color" = "Enable colors";
|
||||
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";
|
||||
|
|
|
@ -567,12 +567,10 @@ void PasscodeBox::validateEmail(
|
|||
} else if (error.type() == u"EMAIL_HASH_EXPIRED"_q) {
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
_clearUnconfirmedPassword.fire({});
|
||||
if (weak) {
|
||||
auto box = Ui::MakeInformBox({
|
||||
Lang::Hard::EmailConfirmationExpired()
|
||||
});
|
||||
weak->getDelegate()->show(
|
||||
std::move(box),
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->getDelegate()->show(
|
||||
Ui::MakeInformBox(
|
||||
Lang::Hard::EmailConfirmationExpired()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1682,9 +1682,19 @@ crl::time PeerListContent::paintRow(
|
|||
return refreshStatusIn;
|
||||
}
|
||||
|
||||
const auto opacity = row->opacity();
|
||||
const auto &bg = selected
|
||||
? _st.item.button.textBgOver
|
||||
: _st.item.button.textBg;
|
||||
if (opacity < 1.) {
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (opacity < 1.) {
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
});
|
||||
|
||||
p.fillRect(0, 0, outerWidth, _rowHeight, bg);
|
||||
row->paintRipple(p, 0, 0, outerWidth);
|
||||
row->paintUserpic(
|
||||
|
|
|
@ -141,6 +141,9 @@ public:
|
|||
}
|
||||
virtual void rightActionStopLastRipple() {
|
||||
}
|
||||
[[nodiscard]] virtual float64 opacity() {
|
||||
return 1.;
|
||||
}
|
||||
|
||||
// By default elements code falls back to a simple right action code.
|
||||
virtual int elementsCount() const;
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
|
@ -520,7 +521,7 @@ void Apply(
|
|||
controller->showSection(Info::Boosts::Make(peer));
|
||||
}
|
||||
};
|
||||
auto counters = Window::ParseBoostCounters(result);
|
||||
auto counters = ParseBoostCounters(result);
|
||||
counters.mine = 0; // Don't show current level as just-reached.
|
||||
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
|
||||
.link = qs(data.vboost_url()),
|
||||
|
|
|
@ -1335,7 +1335,7 @@ object_ptr<Ui::BoxContent> ShowInviteLinkBox(
|
|||
auto data = rpl::single(link) | rpl::then(std::move(updates));
|
||||
|
||||
auto initBox = [=, data = rpl::duplicate(data)](
|
||||
not_null<Ui::BoxContent*> box) {
|
||||
not_null<Ui::BoxContent*> box) {
|
||||
rpl::duplicate(
|
||||
data
|
||||
) | rpl::start_with_next([=](const LinkData &link) {
|
||||
|
|
|
@ -0,0 +1,613 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
|
||||
#include "base/event_filter.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/session/session_show.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kWaitingOpacity = 0.5;
|
||||
|
||||
class Row final : public PeerListRow {
|
||||
public:
|
||||
Row(
|
||||
not_null<Main::Session*> session,
|
||||
TakenBoostSlot slot,
|
||||
TimeId unixtimeNow,
|
||||
crl::time preciseNow);
|
||||
|
||||
void updateStatus(TimeId unixtimeNow, crl::time preciseNow);
|
||||
[[nodiscard]] TakenBoostSlot data() const {
|
||||
return _data;
|
||||
}
|
||||
[[nodiscard]] bool waiting() const {
|
||||
return _waiting;
|
||||
}
|
||||
|
||||
QString generateName() override;
|
||||
QString generateShortName() override;
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
float64 opacity() override;
|
||||
|
||||
private:
|
||||
[[nodiscard]]PaintRoundImageCallback peerPaintUserpicCallback();
|
||||
|
||||
TakenBoostSlot _data;
|
||||
PeerData *_peer = nullptr;
|
||||
std::shared_ptr<Ui::EmptyUserpic> _empty;
|
||||
Ui::PeerUserpicView _userpic;
|
||||
crl::time _startPreciseTime = 0;
|
||||
TimeId _startUnixtime = 0;
|
||||
bool _waiting = false;
|
||||
|
||||
};
|
||||
|
||||
class Controller final : public PeerListController {
|
||||
public:
|
||||
Controller(not_null<ChannelData*> to, std::vector<TakenBoostSlot> from);
|
||||
|
||||
[[nodiscard]] rpl::producer<std::vector<int>> selectedValue() const {
|
||||
return _selected.value();
|
||||
}
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
bool trackSelectedList() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void updateWaitingState();
|
||||
|
||||
not_null<ChannelData*> _to;
|
||||
std::vector<TakenBoostSlot> _from;
|
||||
rpl::variable<std::vector<int>> _selected;
|
||||
rpl::variable<std::vector<not_null<PeerData*>>> _selectedPeers;
|
||||
base::Timer _waitingTimer;
|
||||
bool _hasWaitingRows = false;
|
||||
|
||||
};
|
||||
|
||||
Row::Row(
|
||||
not_null<Main::Session*> session,
|
||||
TakenBoostSlot slot,
|
||||
TimeId unixtimeNow,
|
||||
crl::time preciseNow)
|
||||
: PeerListRow(PeerListRowId(slot.id))
|
||||
, _data(slot)
|
||||
, _peer(session->data().peerLoaded(_data.peerId))
|
||||
, _startPreciseTime(preciseNow)
|
||||
, _startUnixtime(unixtimeNow) {
|
||||
updateStatus(unixtimeNow, preciseNow);
|
||||
}
|
||||
|
||||
void Row::updateStatus(TimeId unixtimeNow, crl::time preciseNow) {
|
||||
_waiting = (_data.cooldown > unixtimeNow);
|
||||
if (_waiting) {
|
||||
const auto initial = crl::time(_data.cooldown - _startUnixtime);
|
||||
const auto elapsed = (preciseNow + 500 - _startPreciseTime) / 1000;
|
||||
const auto seconds = initial
|
||||
- std::clamp(elapsed, crl::time(), initial);
|
||||
const auto hours = seconds / 3600;
|
||||
const auto minutes = seconds / 60;
|
||||
const auto duration = (hours > 0)
|
||||
? u"%1:%2:%3"_q.arg(
|
||||
hours
|
||||
).arg(minutes % 60, 2, 10, QChar('0')
|
||||
).arg(seconds % 60, 2, 10, QChar('0'))
|
||||
: u"%1:%2"_q.arg(
|
||||
minutes
|
||||
).arg(seconds % 60, 2, 10, QChar('0'));
|
||||
setCustomStatus(
|
||||
tr::lng_boost_available_in(tr::now, lt_duration, duration));
|
||||
} else {
|
||||
const auto date = base::unixtime::parse(_data.expires);
|
||||
setCustomStatus(tr::lng_boosts_list_status(
|
||||
tr::now,
|
||||
lt_date,
|
||||
langDayOfMonth(date.date())));
|
||||
}
|
||||
}
|
||||
|
||||
QString Row::generateName() {
|
||||
return _peer ? _peer->name() : u" "_q;
|
||||
}
|
||||
|
||||
QString Row::generateShortName() {
|
||||
return _peer ? _peer->shortName() : generateName();
|
||||
}
|
||||
|
||||
PaintRoundImageCallback Row::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
if (_peer) {
|
||||
return (forceRound && _peer->isForum())
|
||||
? ForceRoundUserpicCallback(_peer)
|
||||
: peerPaintUserpicCallback();
|
||||
} else if (!_empty) {
|
||||
const auto colorIndex = _data.id % Ui::kColorIndexCount;
|
||||
_empty = std::make_shared<Ui::EmptyUserpic>(
|
||||
Ui::EmptyUserpic::UserpicColor(colorIndex),
|
||||
u" "_q);
|
||||
}
|
||||
const auto empty = _empty;
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) {
|
||||
empty->paintCircle(p, x, y, outerWidth, size);
|
||||
};
|
||||
}
|
||||
|
||||
float64 Row::opacity() {
|
||||
return _waiting ? kWaitingOpacity : 1.;
|
||||
}
|
||||
|
||||
PaintRoundImageCallback Row::peerPaintUserpicCallback() {
|
||||
const auto peer = _peer;
|
||||
if (!_userpic.cloud && peer->hasUserpic()) {
|
||||
_userpic = peer->createUserpicView();
|
||||
}
|
||||
auto userpic = _userpic;
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
};
|
||||
}
|
||||
|
||||
Controller::Controller(
|
||||
not_null<ChannelData*> to,
|
||||
std::vector<TakenBoostSlot> from)
|
||||
: _to(to)
|
||||
, _from(std::move(from))
|
||||
, _waitingTimer([=] { updateWaitingState(); }) {
|
||||
}
|
||||
|
||||
Main::Session &Controller::session() const {
|
||||
return _to->session();
|
||||
}
|
||||
|
||||
void Controller::prepare() {
|
||||
delegate()->peerListSetTitle(tr::lng_boost_reassign_title());
|
||||
|
||||
const auto session = &_to->session();
|
||||
auto above = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
above->add(
|
||||
CreateBoostReplaceUserpics(
|
||||
above.data(),
|
||||
_selectedPeers.value(),
|
||||
_to),
|
||||
st::boxRowPadding + st::boostReplaceUserpicsPadding);
|
||||
above->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
above.data(),
|
||||
tr::lng_boost_reassign_text(
|
||||
lt_channel,
|
||||
rpl::single(Ui::Text::Bold(_to->name())),
|
||||
lt_gift,
|
||||
tr::lng_boost_reassign_gift(
|
||||
lt_count,
|
||||
rpl::single(1. * BoostsForGift(session)),
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue),
|
||||
st::boostReassignText),
|
||||
st::boxRowPadding);
|
||||
delegate()->peerListSetAboveWidget(std::move(above));
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
const auto precise = crl::now();
|
||||
ranges::stable_sort(_from, ranges::less(), [&](TakenBoostSlot slot) {
|
||||
return (slot.cooldown > now) ? slot.cooldown : -slot.cooldown;
|
||||
});
|
||||
for (const auto &slot : _from) {
|
||||
auto row = std::make_unique<Row>(session, slot, now, precise);
|
||||
if (row->waiting()) {
|
||||
_hasWaitingRows = true;
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
|
||||
if (_hasWaitingRows) {
|
||||
_waitingTimer.callEach(1000);
|
||||
}
|
||||
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void Controller::updateWaitingState() {
|
||||
_hasWaitingRows = false;
|
||||
const auto now = base::unixtime::now();
|
||||
const auto precise = crl::now();
|
||||
const auto count = delegate()->peerListFullRowsCount();
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto bare = delegate()->peerListRowAt(i);
|
||||
const auto row = static_cast<Row*>(bare.get());
|
||||
if (row->waiting()) {
|
||||
row->updateStatus(now, precise);
|
||||
delegate()->peerListUpdateRow(row);
|
||||
if (row->waiting()) {
|
||||
_hasWaitingRows = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_hasWaitingRows) {
|
||||
_waitingTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto slot = static_cast<Row*>(row.get())->data();
|
||||
if (slot.cooldown > base::unixtime::now()) {
|
||||
delegate()->peerListUiShow()->showToast({
|
||||
.text = tr::lng_boost_available_in_toast(
|
||||
tr::now,
|
||||
lt_count,
|
||||
BoostsForGift(&session()),
|
||||
Ui::Text::RichLangValue),
|
||||
.adaptive = true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
auto now = _selected.current();
|
||||
const auto id = slot.id;
|
||||
const auto checked = !row->checked();
|
||||
delegate()->peerListSetRowChecked(row, checked);
|
||||
const auto peer = slot.peerId
|
||||
? _to->owner().peerLoaded(slot.peerId)
|
||||
: nullptr;
|
||||
auto peerRemoved = false;
|
||||
if (checked) {
|
||||
now.push_back(id);
|
||||
} else {
|
||||
now.erase(ranges::remove(now, id), end(now));
|
||||
|
||||
peerRemoved = true;
|
||||
for (const auto left : now) {
|
||||
const auto i = ranges::find(_from, left, &TakenBoostSlot::id);
|
||||
Assert(i != end(_from));
|
||||
if (i->peerId == slot.peerId) {
|
||||
peerRemoved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_selected = std::move(now);
|
||||
|
||||
if (peer) {
|
||||
auto selectedPeers = _selectedPeers.current();
|
||||
const auto i = ranges::find(selectedPeers, not_null(peer));
|
||||
if (peerRemoved) {
|
||||
Assert(i != end(selectedPeers));
|
||||
selectedPeers.erase(i);
|
||||
_selectedPeers = std::move(selectedPeers);
|
||||
} else if (i == end(selectedPeers) && checked) {
|
||||
selectedPeers.insert(begin(selectedPeers), peer);
|
||||
_selectedPeers = std::move(selectedPeers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ReassignBoostFloodBox(int seconds) {
|
||||
const auto days = seconds / 86400;
|
||||
const auto hours = seconds / 3600;
|
||||
const auto minutes = seconds / 60;
|
||||
return Ui::MakeInformBox({
|
||||
.text = tr::lng_boost_error_flood_text(
|
||||
lt_left,
|
||||
rpl::single(Ui::Text::Bold((days > 1)
|
||||
? tr::lng_days(tr::now, lt_count, days)
|
||||
: (hours > 1)
|
||||
? tr::lng_hours(tr::now, lt_count, hours)
|
||||
: (minutes > 1)
|
||||
? tr::lng_minutes(tr::now, lt_count, minutes)
|
||||
: tr::lng_seconds(tr::now, lt_count, seconds))),
|
||||
Ui::Text::RichLangValue),
|
||||
.title = tr::lng_boost_error_flood_title(),
|
||||
});
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ReassignBoostSingleBox(
|
||||
not_null<ChannelData*> to,
|
||||
TakenBoostSlot from,
|
||||
Fn<void(std::vector<int> slots, int sources)> reassign,
|
||||
Fn<void()> cancel) {
|
||||
const auto reassigned = std::make_shared<bool>();
|
||||
const auto slot = from.id;
|
||||
const auto peer = to->owner().peer(from.peerId);
|
||||
const auto confirmed = [=](Fn<void()> close) {
|
||||
*reassigned = true;
|
||||
reassign({ slot }, 1);
|
||||
close();
|
||||
};
|
||||
|
||||
auto result = Box([=](not_null<Ui::GenericBox*> box) {
|
||||
Ui::ConfirmBox(box, {
|
||||
.text = tr::lng_boost_now_instead(
|
||||
lt_channel,
|
||||
rpl::single(Ui::Text::Bold(peer->name())),
|
||||
lt_other,
|
||||
rpl::single(Ui::Text::Bold(to->name())),
|
||||
Ui::Text::WithEntities),
|
||||
.confirmed = confirmed,
|
||||
.confirmText = tr::lng_boost_now_replace(),
|
||||
.labelPadding = st::boxRowPadding,
|
||||
});
|
||||
box->verticalLayout()->insert(
|
||||
0,
|
||||
CreateBoostReplaceUserpics(
|
||||
box,
|
||||
rpl::single(std::vector{ peer }),
|
||||
to),
|
||||
st::boxRowPadding + st::boostReplaceUserpicsPadding);
|
||||
});
|
||||
|
||||
result->boxClosing() | rpl::filter([=] {
|
||||
return !*reassigned;
|
||||
}) | rpl::start_with_next(cancel, result->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ForChannelBoostSlots ParseForChannelBoostSlots(
|
||||
not_null<ChannelData*> channel,
|
||||
const QVector<MTPMyBoost> &boosts) {
|
||||
auto result = ForChannelBoostSlots();
|
||||
const auto now = base::unixtime::now();
|
||||
for (const auto &my : boosts) {
|
||||
const auto &data = my.data();
|
||||
const auto id = data.vslot().v;
|
||||
const auto cooldown = data.vcooldown_until_date().value_or(0);
|
||||
const auto peerId = data.vpeer()
|
||||
? peerFromMTP(*data.vpeer())
|
||||
: PeerId();
|
||||
if (!peerId && cooldown <= now) {
|
||||
result.free.push_back(id);
|
||||
} else if (peerId == channel->id) {
|
||||
result.already.push_back(id);
|
||||
} else {
|
||||
result.other.push_back({
|
||||
.id = id,
|
||||
.expires = data.vexpires().v,
|
||||
.peerId = peerId,
|
||||
.cooldown = cooldown,
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Ui::BoostCounters ParseBoostCounters(
|
||||
const MTPpremium_BoostsStatus &status) {
|
||||
const auto &data = status.data();
|
||||
const auto slots = data.vmy_boost_slots();
|
||||
return {
|
||||
.level = data.vlevel().v,
|
||||
.boosts = data.vboosts().v,
|
||||
.thisLevelBoosts = data.vcurrent_level_boosts().v,
|
||||
.nextLevelBoosts = data.vnext_level_boosts().value_or_empty(),
|
||||
.mine = slots ? slots->v.size() : 0,
|
||||
};
|
||||
}
|
||||
|
||||
int BoostsForGift(not_null<Main::Session*> session) {
|
||||
const auto key = u"boosts_per_sent_gift"_q;
|
||||
return session->account().appConfig().get<int>(key, 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] int SourcesCount(
|
||||
const std::vector<TakenBoostSlot> &from,
|
||||
const std::vector<int> &slots) {
|
||||
auto checked = base::flat_set<PeerId>();
|
||||
checked.reserve(slots.size());
|
||||
for (const auto slot : slots) {
|
||||
const auto i = ranges::find(from, slot, &TakenBoostSlot::id);
|
||||
Assert(i != end(from));
|
||||
checked.emplace(i->peerId);
|
||||
}
|
||||
return checked.size();
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ReassignBoostsBox(
|
||||
not_null<ChannelData*> to,
|
||||
std::vector<TakenBoostSlot> from,
|
||||
Fn<void(std::vector<int> slots, int sources)> reassign,
|
||||
Fn<void()> cancel) {
|
||||
Expects(!from.empty());
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
if (from.size() == 1 && from.front().cooldown > now) {
|
||||
cancel();
|
||||
return ReassignBoostFloodBox(from.front().cooldown - now);
|
||||
} else if (from.size() == 1 && from.front().peerId) {
|
||||
return ReassignBoostSingleBox(to, from.front(), reassign, cancel);
|
||||
}
|
||||
const auto reassigned = std::make_shared<bool>();
|
||||
auto controller = std::make_unique<Controller>(to, from);
|
||||
const auto raw = controller.get();
|
||||
auto initBox = [=](not_null<Ui::BoxContent*> box) {
|
||||
raw->selectedValue(
|
||||
) | rpl::start_with_next([=](std::vector<int> slots) {
|
||||
box->clearButtons();
|
||||
if (!slots.empty()) {
|
||||
const auto sources = SourcesCount(from, slots);
|
||||
box->addButton(tr::lng_boost_reassign_button(), [=] {
|
||||
*reassigned = true;
|
||||
reassign(slots, sources);
|
||||
});
|
||||
}
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}, box->lifetime());
|
||||
|
||||
box->boxClosing() | rpl::filter([=] {
|
||||
return !*reassigned;
|
||||
}) | rpl::start_with_next(cancel, box->lifetime());
|
||||
};
|
||||
return Box<PeerListBox>(std::move(controller), std::move(initBox));
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<std::vector<not_null<PeerData*>>> from,
|
||||
not_null<PeerData*> to) {
|
||||
struct State {
|
||||
std::vector<not_null<PeerData*>> from;
|
||||
std::vector<std::unique_ptr<Ui::UserpicButton>> buttons;
|
||||
QImage layer;
|
||||
rpl::variable<int> count = 0;
|
||||
bool painting = false;
|
||||
};
|
||||
const auto full = st::boostReplaceUserpic.size.height()
|
||||
+ st::boostReplaceIconAdd.y()
|
||||
+ st::lineWidth;
|
||||
auto result = object_ptr<Ui::FixedHeightWidget>(parent, full);
|
||||
const auto raw = result.data();
|
||||
const auto &st = st::boostReplaceUserpic;
|
||||
const auto right = CreateChild<Ui::UserpicButton>(raw, to, st);
|
||||
const auto overlay = CreateChild<Ui::RpWidget>(raw);
|
||||
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
std::move(
|
||||
from
|
||||
) | rpl::start_with_next([=](
|
||||
const std::vector<not_null<PeerData*>> &list) {
|
||||
const auto &st = st::boostReplaceUserpic;
|
||||
auto was = base::take(state->from);
|
||||
auto buttons = base::take(state->buttons);
|
||||
state->from.reserve(list.size());
|
||||
state->buttons.reserve(list.size());
|
||||
for (const auto &peer : list) {
|
||||
state->from.push_back(peer);
|
||||
const auto i = ranges::find(was, peer);
|
||||
if (i != end(was)) {
|
||||
const auto index = int(i - begin(was));
|
||||
Assert(buttons[index] != nullptr);
|
||||
state->buttons.push_back(std::move(buttons[index]));
|
||||
} else {
|
||||
state->buttons.push_back(
|
||||
std::make_unique<Ui::UserpicButton>(raw, peer, st));
|
||||
const auto raw = state->buttons.back().get();
|
||||
base::install_event_filter(raw, [=](not_null<QEvent*> e) {
|
||||
return (e->type() == QEvent::Paint && !state->painting)
|
||||
? base::EventFilterResult::Cancel
|
||||
: base::EventFilterResult::Continue;
|
||||
});
|
||||
}
|
||||
}
|
||||
state->count.force_assign(int(list.size()));
|
||||
overlay->update();
|
||||
}, raw->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
state->count.value()
|
||||
) | rpl::start_with_next([=](int width, int count) {
|
||||
const auto skip = st::boostReplaceUserpicsSkip;
|
||||
const auto left = width - 2 * right->width() - skip;
|
||||
const auto shift = std::min(
|
||||
st::boostReplaceUserpicsShift,
|
||||
(count > 1 ? (left / (count - 1)) : width));
|
||||
const auto total = right->width()
|
||||
+ (count ? (skip + right->width() + (count - 1) * shift) : 0);
|
||||
auto x = (width - total) / 2;
|
||||
for (const auto &single : state->buttons) {
|
||||
single->moveToLeft(x, 0);
|
||||
x += shift;
|
||||
}
|
||||
if (count) {
|
||||
x += right->width() - shift + skip;
|
||||
}
|
||||
right->moveToLeft(x, 0);
|
||||
overlay->setGeometry(QRect(0, 0, width, raw->height()));
|
||||
}, raw->lifetime());
|
||||
|
||||
overlay->paintRequest(
|
||||
) | rpl::filter([=] {
|
||||
return !state->buttons.empty();
|
||||
}) | rpl::start_with_next([=] {
|
||||
const auto outerw = overlay->width();
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (state->layer.size() != QSize(outerw, full) * ratio) {
|
||||
state->layer = QImage(
|
||||
QSize(outerw, full) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
state->layer.setDevicePixelRatio(ratio);
|
||||
}
|
||||
state->layer.fill(Qt::transparent);
|
||||
|
||||
auto q = QPainter(&state->layer);
|
||||
auto hq = PainterHighQualityEnabler(q);
|
||||
const auto stroke = st::boostReplaceIconOutline;
|
||||
const auto half = stroke / 2.;
|
||||
auto pen = st::windowBg->p;
|
||||
pen.setWidthF(stroke * 2.);
|
||||
state->painting = true;
|
||||
for (const auto &button : state->buttons) {
|
||||
q.setPen(pen);
|
||||
q.setBrush(Qt::NoBrush);
|
||||
q.drawEllipse(button->geometry());
|
||||
const auto position = button->pos();
|
||||
button->render(&q, position, QRegion(), QWidget::DrawChildren);
|
||||
}
|
||||
state->painting = false;
|
||||
const auto last = state->buttons.back().get();
|
||||
const auto add = st::boostReplaceIconAdd;
|
||||
const auto skip = st::boostReplaceIconSkip;
|
||||
const auto w = st::boostReplaceIcon.width() + 2 * skip;
|
||||
const auto h = st::boostReplaceIcon.height() + 2 * skip;
|
||||
const auto x = last->x() + last->width() - w + add.x();
|
||||
const auto y = last->y() + last->height() - h + add.y();
|
||||
|
||||
auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
|
||||
brush.setStops(Ui::Premium::ButtonGradientStops());
|
||||
q.setBrush(brush);
|
||||
pen.setWidthF(stroke);
|
||||
q.setPen(pen);
|
||||
q.drawEllipse(x - half, y - half, w + stroke, h + stroke);
|
||||
st::boostReplaceIcon.paint(q, x + skip, y + skip, outerw);
|
||||
|
||||
const auto size = st::boostReplaceArrow.size();
|
||||
st::boostReplaceArrow.paint(
|
||||
q,
|
||||
(last->x()
|
||||
+ last->width()
|
||||
+ (st::boostReplaceUserpicsSkip - size.width()) / 2),
|
||||
(last->height() - size.height()) / 2,
|
||||
outerw);
|
||||
|
||||
q.end();
|
||||
|
||||
auto p = QPainter(overlay);
|
||||
p.drawImage(0, 0, state->layer);
|
||||
}, overlay->lifetime());
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
struct BoostCounters;
|
||||
class BoxContent;
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
struct TakenBoostSlot {
|
||||
int id = 0;
|
||||
TimeId expires = 0;
|
||||
PeerId peerId = 0;
|
||||
TimeId cooldown = 0;
|
||||
};
|
||||
|
||||
struct ForChannelBoostSlots {
|
||||
std::vector<int> free;
|
||||
std::vector<int> already;
|
||||
std::vector<TakenBoostSlot> other;
|
||||
};
|
||||
|
||||
[[nodiscard]] ForChannelBoostSlots ParseForChannelBoostSlots(
|
||||
not_null<ChannelData*> channel,
|
||||
const QVector<MTPMyBoost> &boosts);
|
||||
|
||||
[[nodiscard]] Ui::BoostCounters ParseBoostCounters(
|
||||
const MTPpremium_BoostsStatus &status);
|
||||
|
||||
[[nodiscard]] int BoostsForGift(not_null<Main::Session*> session);
|
||||
|
||||
object_ptr<Ui::BoxContent> ReassignBoostsBox(
|
||||
not_null<ChannelData*> to,
|
||||
std::vector<TakenBoostSlot> from,
|
||||
Fn<void(std::vector<int> slots, int sources)> reassign,
|
||||
Fn<void()> cancel);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<std::vector<not_null<PeerData*>>> from,
|
||||
not_null<PeerData*> to);
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/boxes/boost_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/effects/fireworks_animation.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
|
@ -177,9 +178,10 @@ void BoostBox(
|
|||
(st::boxRowPadding
|
||||
+ QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip)));
|
||||
|
||||
const auto allowMulti = data.allowMulti;
|
||||
auto submit = state->data.value(
|
||||
) | rpl::map([=](BoostCounters counters) {
|
||||
return !counters.nextLevelBoosts
|
||||
return (!counters.nextLevelBoosts || (counters.mine && !allowMulti))
|
||||
? tr::lng_box_ok()
|
||||
: (counters.mine > 0)
|
||||
? tr::lng_boost_again_button()
|
||||
|
@ -189,7 +191,8 @@ void BoostBox(
|
|||
const auto button = box->addButton(rpl::duplicate(submit), [=] {
|
||||
if (state->submitted) {
|
||||
return;
|
||||
} else if (state->data.current().nextLevelBoosts > 0) {
|
||||
} else if (state->data.current().nextLevelBoosts > 0
|
||||
&& (allowMulti || !state->data.current().mine)) {
|
||||
state->submitted = true;
|
||||
const auto was = state->data.current().mine;
|
||||
|
||||
|
@ -346,6 +349,49 @@ object_ptr<Ui::RpWidget> MakeLinkLabel(
|
|||
return result;
|
||||
}
|
||||
|
||||
void BoostBoxAlready(not_null<GenericBox*> box) {
|
||||
ConfirmBox(box, {
|
||||
.text = tr::lng_boost_error_already_text(Text::RichLangValue),
|
||||
.title = tr::lng_boost_error_already_title(),
|
||||
.inform = true,
|
||||
});
|
||||
}
|
||||
|
||||
void GiftForBoostsBox(
|
||||
not_null<GenericBox*> box,
|
||||
QString channel,
|
||||
int receive,
|
||||
bool again) {
|
||||
ConfirmBox(box, {
|
||||
.text = (again
|
||||
? tr::lng_boost_need_more_again
|
||||
: tr::lng_boost_need_more_text)(
|
||||
lt_count,
|
||||
rpl::single(receive) | tr::to_count(),
|
||||
lt_channel,
|
||||
rpl::single(TextWithEntities{ channel }),
|
||||
Text::RichLangValue),
|
||||
.title = tr::lng_boost_need_more(),
|
||||
.inform = true,
|
||||
});
|
||||
}
|
||||
|
||||
void GiftedNoBoostsBox(not_null<GenericBox*> box) {
|
||||
InformBox(box, {
|
||||
.text = tr::lng_boost_error_gifted_text(Text::RichLangValue),
|
||||
.title = tr::lng_boost_error_gifted_title(),
|
||||
});
|
||||
}
|
||||
|
||||
void PremiumForBoostsBox(not_null<GenericBox*> box, Fn<void()> buyPremium) {
|
||||
ConfirmBox(box, {
|
||||
.text = tr::lng_boost_error_premium_text(Text::RichLangValue),
|
||||
.confirmed = buyPremium,
|
||||
.confirmText = tr::lng_boost_error_premium_yes(),
|
||||
.title = tr::lng_boost_error_premium_title(),
|
||||
});
|
||||
}
|
||||
|
||||
void AskBoostBox(
|
||||
not_null<GenericBox*> box,
|
||||
AskBoostBoxData data,
|
||||
|
|
|
@ -33,6 +33,7 @@ struct BoostCounters {
|
|||
struct BoostBoxData {
|
||||
QString name;
|
||||
BoostCounters boost;
|
||||
bool allowMulti = false;
|
||||
};
|
||||
|
||||
void BoostBox(
|
||||
|
@ -40,6 +41,15 @@ void BoostBox(
|
|||
BoostBoxData data,
|
||||
Fn<void(Fn<void(BoostCounters)>)> boost);
|
||||
|
||||
void BoostBoxAlready(not_null<GenericBox*> box);
|
||||
void GiftForBoostsBox(
|
||||
not_null<GenericBox*> box,
|
||||
QString channel,
|
||||
int receive,
|
||||
bool again);
|
||||
void GiftedNoBoostsBox(not_null<GenericBox*> box);
|
||||
void PremiumForBoostsBox(not_null<GenericBox*> box, Fn<void()> buyPremium);
|
||||
|
||||
struct AskBoostBoxData {
|
||||
QString link;
|
||||
BoostCounters boost;
|
||||
|
|
|
@ -100,11 +100,4 @@ object_ptr<Ui::GenericBox> MakeConfirmBox(ConfirmBoxArgs &&args) {
|
|||
return Box(ConfirmBox, std::move(args));
|
||||
}
|
||||
|
||||
object_ptr<Ui::GenericBox> MakeInformBox(v::text::data text) {
|
||||
return MakeConfirmBox({
|
||||
.text = std::move(text),
|
||||
.inform = true,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -40,10 +40,24 @@ struct ConfirmBoxArgs {
|
|||
bool strictCancel = false;
|
||||
};
|
||||
|
||||
void ConfirmBox(not_null<Ui::GenericBox*> box, ConfirmBoxArgs &&args);
|
||||
void ConfirmBox(not_null<GenericBox*> box, ConfirmBoxArgs &&args);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::GenericBox> MakeConfirmBox(
|
||||
ConfirmBoxArgs &&args);
|
||||
[[nodiscard]] object_ptr<Ui::GenericBox> MakeInformBox(v::text::data text);
|
||||
inline void InformBox(not_null<GenericBox*> box, ConfirmBoxArgs &&args) {
|
||||
args.inform = true;
|
||||
ConfirmBox(box, std::move(args));
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<GenericBox> MakeConfirmBox(ConfirmBoxArgs &&args);
|
||||
|
||||
[[nodiscard]] inline object_ptr<GenericBox> MakeInformBox(
|
||||
ConfirmBoxArgs &&args) {
|
||||
args.inform = true;
|
||||
return MakeConfirmBox(std::move(args));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline object_ptr<GenericBox> MakeInformBox(
|
||||
v::text::data text) {
|
||||
return MakeInformBox({ .text = std::move(text) });
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -1108,57 +1108,4 @@ not_null<Ui::UserpicButton*> CreateUploadSubButton(
|
|||
return upload;
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<PeerData*> from,
|
||||
not_null<PeerData*> to) {
|
||||
const auto full = st::boostReplaceUserpic.size.height()
|
||||
+ st::boostReplaceIconAdd.y()
|
||||
+ st::lineWidth;
|
||||
auto result = object_ptr<FixedHeightWidget>(parent, full);
|
||||
const auto raw = result.data();
|
||||
const auto &st = st::boostReplaceUserpic;
|
||||
const auto left = CreateChild<UserpicButton>(raw, from, st);
|
||||
const auto right = CreateChild<UserpicButton>(raw, to, st);
|
||||
const auto overlay = CreateChild<RpWidget>(raw);
|
||||
raw->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto skip = st::boostReplaceUserpicsSkip;
|
||||
const auto total = left->width() + skip + right->width();
|
||||
left->moveToLeft((width - total) / 2, 0);
|
||||
right->moveToLeft(left->x() + left->width() + skip, 0);
|
||||
overlay->setGeometry(QRect(0, 0, width, raw->height()));
|
||||
}, raw->lifetime());
|
||||
overlay->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto outerw = overlay->width();
|
||||
const auto add = st::boostReplaceIconAdd;
|
||||
const auto skip = st::boostReplaceIconSkip;
|
||||
const auto w = st::boostReplaceIcon.width() + 2 * skip;
|
||||
const auto h = st::boostReplaceIcon.height() + 2 * skip;
|
||||
const auto x = left->x() + left->width() - w + add.x();
|
||||
const auto y = left->y() + left->height() - h + add.y();
|
||||
const auto stroke = st::boostReplaceIconOutline;
|
||||
const auto half = stroke / 2.;
|
||||
auto p = QPainter(overlay);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto pen = st::windowBg->p;
|
||||
pen.setWidthF(stroke);
|
||||
p.setPen(pen);
|
||||
auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
|
||||
brush.setStops(Premium::ButtonGradientStops());
|
||||
p.setBrush(brush);
|
||||
p.drawEllipse(x - half, y - half, w + stroke, h + stroke);
|
||||
st::boostReplaceIcon.paint(p, x + skip, y + skip, outerw);
|
||||
|
||||
const auto size = st::boostReplaceArrow.size();
|
||||
st::boostReplaceArrow.paint(
|
||||
p,
|
||||
(outerw - size.width()) / 2,
|
||||
(left->height() - size.height()) / 2,
|
||||
outerw);
|
||||
}, overlay->lifetime());
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -204,9 +204,4 @@ private:
|
|||
not_null<UserData*> contact,
|
||||
not_null<Window::SessionController*> controller);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<PeerData*> from,
|
||||
not_null<PeerData*> to);
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -247,7 +247,6 @@ boostTitleSkip: 32px;
|
|||
boostTitle: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 40px;
|
||||
textFg: windowBoldFg;
|
||||
align: align(top);
|
||||
maxHeight: 24px;
|
||||
style: TextStyle(boxTextStyle) {
|
||||
font: font(17px semibold);
|
||||
|
@ -258,6 +257,10 @@ boostText: FlatLabel(defaultFlatLabel) {
|
|||
minWidth: 40px;
|
||||
align: align(top);
|
||||
}
|
||||
boostReassignText: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 40px;
|
||||
align: align(top);
|
||||
}
|
||||
boostBottomSkip: 6px;
|
||||
boostBox: Box(premiumPreviewDoubledLimitsBox) {
|
||||
buttonPadding: margins(22px, 22px, 22px, 22px);
|
||||
|
@ -271,11 +274,12 @@ boostBox: Box(premiumPreviewDoubledLimitsBox) {
|
|||
|
||||
boostReplaceUserpicsPadding: margins(0px, 18px, 0px, 20px);
|
||||
boostReplaceUserpicsSkip: 42px;
|
||||
boostReplaceUserpicsShift: 24px;
|
||||
boostReplaceUserpic: UserpicButton(defaultUserpicButton) {
|
||||
size: size(60px, 60px);
|
||||
photoSize: 60px;
|
||||
}
|
||||
boostReplaceIcon: icon{{ "stories/boost_mini", windowBg }};
|
||||
boostReplaceIcon: icon{{ "stories/boost_mini", premiumButtonFg }};
|
||||
boostReplaceIconSkip: 3px;
|
||||
boostReplaceIconOutline: 2px;
|
||||
boostReplaceIconAdd: point(4px, 2px);
|
||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/add_contact_box.h"
|
||||
#include "boxes/peers/add_bot_to_chat_box.h"
|
||||
#include "boxes/peers/edit_peer_info_box.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/delete_messages_box.h"
|
||||
#include "window/window_adaptive.h"
|
||||
#include "window/window_controller.h"
|
||||
|
@ -267,19 +268,6 @@ Fn<bool()> PausedIn(
|
|||
return [=] { return IsPaused(controller, level); };
|
||||
}
|
||||
|
||||
Ui::BoostCounters ParseBoostCounters(
|
||||
const MTPpremium_BoostsStatus &status) {
|
||||
const auto &data = status.data();
|
||||
const auto slots = data.vmy_boost_slots();
|
||||
return {
|
||||
.level = data.vlevel().v,
|
||||
.boosts = data.vboosts().v,
|
||||
.thisLevelBoosts = data.vcurrent_level_boosts().v,
|
||||
.nextLevelBoosts = data.vnext_level_boosts().value_or_empty(),
|
||||
.mine = slots ? slots->v.size() : 0,
|
||||
};
|
||||
}
|
||||
|
||||
bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b) {
|
||||
return (a.peer == b.peer) && (a.theme == b.theme);
|
||||
}
|
||||
|
@ -648,6 +636,7 @@ void SessionNavigation::resolveBoostState(not_null<ChannelData*> channel) {
|
|||
uiShow()->show(Box(Ui::BoostBox, Ui::BoostBoxData{
|
||||
.name = channel->name(),
|
||||
.boost = ParseBoostCounters(result),
|
||||
.allowMulti = (BoostsForGift(_session) > 0),
|
||||
}, submit));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_boostStateResolving = nullptr;
|
||||
|
@ -663,86 +652,66 @@ void SessionNavigation::applyBoost(
|
|||
const auto &data = result.data();
|
||||
_session->data().processUsers(data.vusers());
|
||||
_session->data().processChats(data.vchats());
|
||||
const auto &list = data.vmy_boosts().v;
|
||||
if (list.isEmpty()) {
|
||||
if (!_session->premium()) {
|
||||
const auto jumpToPremium = [=] {
|
||||
const auto slots = ParseForChannelBoostSlots(
|
||||
channel,
|
||||
data.vmy_boosts().v);
|
||||
if (!slots.free.empty()) {
|
||||
applyBoostsChecked(channel, { slots.free.front() }, done);
|
||||
} else if (slots.other.empty()) {
|
||||
if (!slots.already.empty()) {
|
||||
if (const auto receive = BoostsForGift(_session)) {
|
||||
const auto again = true;
|
||||
const auto name = channel->name();
|
||||
uiShow()->show(
|
||||
Box(Ui::GiftForBoostsBox, name, receive, again));
|
||||
} else {
|
||||
uiShow()->show(Box(Ui::BoostBoxAlready));
|
||||
}
|
||||
} else if (!_session->premium()) {
|
||||
uiShow()->show(Box(Ui::PremiumForBoostsBox, [=] {
|
||||
const auto id = peerToChannel(channel->id).bare;
|
||||
Settings::ShowPremium(
|
||||
parentController(),
|
||||
"channel_boost__" + QString::number(id));
|
||||
};
|
||||
uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_boost_error_premium_text(
|
||||
Ui::Text::RichLangValue),
|
||||
.confirmed = jumpToPremium,
|
||||
.confirmText = tr::lng_boost_error_premium_yes(),
|
||||
.title = tr::lng_boost_error_premium_title(),
|
||||
}));
|
||||
} else if (const auto receive = BoostsForGift(_session)) {
|
||||
const auto again = false;
|
||||
const auto name = channel->name();
|
||||
uiShow()->show(
|
||||
Box(Ui::GiftForBoostsBox, name, receive, again));
|
||||
} else {
|
||||
uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_boost_error_gifted_text(
|
||||
Ui::Text::RichLangValue),
|
||||
.title = tr::lng_boost_error_gifted_title(),
|
||||
.inform = true,
|
||||
}));
|
||||
uiShow()->show(Box(Ui::GiftedNoBoostsBox));
|
||||
}
|
||||
done({});
|
||||
return;
|
||||
}
|
||||
auto slot = int();
|
||||
auto different = PeerId();
|
||||
auto earliest = TimeId(-1);
|
||||
const auto now = base::unixtime::now();
|
||||
for (const auto &my : list) {
|
||||
const auto &data = my.data();
|
||||
const auto cooldown = data.vcooldown_until_date().value_or(0);
|
||||
const auto peerId = data.vpeer()
|
||||
? peerFromMTP(*data.vpeer())
|
||||
: PeerId();
|
||||
if (!peerId && cooldown <= now) {
|
||||
applyBoostChecked(channel, data.vslot().v, done);
|
||||
return;
|
||||
} else if (peerId != channel->id
|
||||
&& (earliest < 0 || cooldown < earliest)) {
|
||||
slot = data.vslot().v;
|
||||
different = peerId;
|
||||
earliest = cooldown;
|
||||
}
|
||||
}
|
||||
if (different) {
|
||||
if (earliest > now) {
|
||||
const auto seconds = earliest - now;
|
||||
const auto days = seconds / 86400;
|
||||
const auto hours = seconds / 3600;
|
||||
const auto minutes = seconds / 60;
|
||||
uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_boost_error_flood_text(
|
||||
lt_left,
|
||||
rpl::single(Ui::Text::Bold((days > 1)
|
||||
? tr::lng_days(tr::now, lt_count, days)
|
||||
: (hours > 1)
|
||||
? tr::lng_hours(tr::now, lt_count, hours)
|
||||
: (minutes > 1)
|
||||
? tr::lng_minutes(tr::now, lt_count, minutes)
|
||||
: tr::lng_seconds(tr::now, lt_count, seconds))),
|
||||
Ui::Text::RichLangValue),
|
||||
.title = tr::lng_boost_error_flood_title(),
|
||||
.inform = true,
|
||||
}));
|
||||
done({});
|
||||
} else {
|
||||
const auto peer = _session->data().peer(different);
|
||||
replaceBoostConfirm(peer, channel, slot, done);
|
||||
}
|
||||
} else {
|
||||
uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_boost_error_already_text(
|
||||
Ui::Text::RichLangValue),
|
||||
.title = tr::lng_boost_error_already_title(),
|
||||
.inform = true,
|
||||
}));
|
||||
done({});
|
||||
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
const auto reassign = [=](std::vector<int> slots, int sources) {
|
||||
const auto count = int(slots.size());
|
||||
const auto callback = [=](Ui::BoostCounters counters) {
|
||||
if (const auto strong = weak->data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
done(counters);
|
||||
uiShow()->showToast(tr::lng_boost_reassign_done(
|
||||
tr::now,
|
||||
lt_count,
|
||||
count,
|
||||
lt_channels,
|
||||
tr::lng_boost_reassign_channels(
|
||||
tr::now,
|
||||
lt_count,
|
||||
sources)));
|
||||
};
|
||||
applyBoostsChecked(
|
||||
channel,
|
||||
slots,
|
||||
crl::guard(this, callback));
|
||||
};
|
||||
*weak = uiShow()->show(ReassignBoostsBox(
|
||||
channel,
|
||||
slots.other,
|
||||
reassign,
|
||||
[=] { done({}); }));
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto type = error.type();
|
||||
|
@ -751,48 +720,18 @@ void SessionNavigation::applyBoost(
|
|||
}).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
void SessionNavigation::replaceBoostConfirm(
|
||||
not_null<PeerData*> from,
|
||||
void SessionNavigation::applyBoostsChecked(
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
std::vector<int> slots,
|
||||
Fn<void(Ui::BoostCounters)> done) {
|
||||
const auto forwarded = std::make_shared<bool>(false);
|
||||
const auto confirmed = [=](Fn<void()> close) {
|
||||
*forwarded = true;
|
||||
applyBoostChecked(channel, slot, done);
|
||||
close();
|
||||
};
|
||||
const auto box = uiShow()->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
Ui::ConfirmBox(box, {
|
||||
.text = tr::lng_boost_now_instead(
|
||||
lt_channel,
|
||||
rpl::single(Ui::Text::Bold(from->name())),
|
||||
lt_other,
|
||||
rpl::single(Ui::Text::Bold(channel->name())),
|
||||
Ui::Text::WithEntities),
|
||||
.confirmed = confirmed,
|
||||
.confirmText = tr::lng_boost_now_replace(),
|
||||
.labelPadding = st::boxRowPadding,
|
||||
});
|
||||
box->verticalLayout()->insert(
|
||||
0,
|
||||
Ui::CreateBoostReplaceUserpics(box, from, channel),
|
||||
st::boxRowPadding + st::boostReplaceUserpicsPadding);
|
||||
auto mtp = MTP_vector_from_range(ranges::views::all(
|
||||
slots
|
||||
) | ranges::views::transform([](int slot) {
|
||||
return MTP_int(slot);
|
||||
}));
|
||||
box->boxClosing() | rpl::filter([=] {
|
||||
return !*forwarded;
|
||||
}) | rpl::start_with_next([=] {
|
||||
done({});
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
void SessionNavigation::applyBoostChecked(
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
Fn<void(Ui::BoostCounters)> done) {
|
||||
_api.request(MTPpremium_ApplyBoost(
|
||||
MTP_flags(MTPpremium_ApplyBoost::Flag::f_slots),
|
||||
MTP_vector<MTPint>({ MTP_int(slot) }),
|
||||
std::move(mtp),
|
||||
channel->input
|
||||
)).done([=](const MTPpremium_MyBoosts &result) {
|
||||
_api.request(MTPpremium_GetBoostsStatus(
|
||||
|
|
|
@ -318,14 +318,9 @@ private:
|
|||
void applyBoost(
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(Ui::BoostCounters)> done);
|
||||
void replaceBoostConfirm(
|
||||
not_null<PeerData*> from,
|
||||
void applyBoostsChecked(
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
Fn<void(Ui::BoostCounters)> done);
|
||||
void applyBoostChecked(
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
std::vector<int> slots,
|
||||
Fn<void(Ui::BoostCounters)> done);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
@ -755,7 +750,4 @@ void ActivateWindow(not_null<SessionController*> controller);
|
|||
not_null<SessionController*> controller,
|
||||
GifPauseReason level);
|
||||
|
||||
[[nodiscard]] Ui::BoostCounters ParseBoostCounters(
|
||||
const MTPpremium_BoostsStatus &status);
|
||||
|
||||
} // namespace Window
|
||||
|
|
Loading…
Reference in New Issue