mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Nice bidding information display.
This commit is contained in:
@@ -4036,7 +4036,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_auction_bid_your_title" = "Your bid will be";
|
||||
"lng_auction_bid_your_outbid" = "You've been outbid";
|
||||
"lng_auction_bid_your_winning" = "You're winning";
|
||||
"lng_auction_bid_winners_title" = "Top 3 Winners";
|
||||
"lng_auction_bid_winners_title" = "Top winners";
|
||||
"lng_auction_bid_place" = "Place a {stars} Bid";
|
||||
"lng_auction_bid_increase" = "Add {stars} to Your Bid";
|
||||
"lng_auction_bid_placed_title" = "Your bid has been placed";
|
||||
|
||||
@@ -24,17 +24,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "payments/ui/payments_reaction_box.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/controls/button_labels.h"
|
||||
#include "ui/controls/feature_list.h"
|
||||
#include "ui/controls/table_rows.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_bubble.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/wrap/table_layout.h"
|
||||
#include "ui/color_int_conversion.h"
|
||||
#include "ui/dynamic_thumbnails.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_chat.h"
|
||||
@@ -42,13 +47,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kAuctionAboutShownPref = "gift_auction_about_shown"_cs;
|
||||
constexpr auto kBidPlacedToastDuration = 5 * crl::time(1000);
|
||||
constexpr auto kMaxShownBid = 30'000;
|
||||
constexpr auto kMaxShownBid = 30'000'00; AssertIsDebug()
|
||||
|
||||
enum class BidType {
|
||||
Setting,
|
||||
Winning,
|
||||
Loosing,
|
||||
};
|
||||
struct BidRowData {
|
||||
UserData *user = nullptr;
|
||||
int stars = 0;
|
||||
QString place;
|
||||
BidType type = BidType::Setting;
|
||||
|
||||
friend inline bool operator==(
|
||||
const BidRowData &,
|
||||
const BidRowData &) = default;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<QColor> BidColorOverride(BidType type) {
|
||||
switch (type) {
|
||||
case BidType::Setting: return {};
|
||||
case BidType::Winning: return st::boxTextFgGood->c;
|
||||
case BidType::Loosing: return st::attentionButtonFg->c;
|
||||
}
|
||||
Unexpected("Type in BidType.");
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<int> MinutesLeftTillValue(TimeId endDate) {
|
||||
return [=](auto consumer) {
|
||||
@@ -82,6 +113,131 @@ constexpr auto kMaxShownBid = 30'000;
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<int> SecondsLeftTillValue(TimeId endDate) {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
const auto left = (endDate > now) ? endDate - now : 0;
|
||||
if (!left) {
|
||||
consumer.put_next(0);
|
||||
consumer.put_done();
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
const auto starts = crl::now();
|
||||
const auto ends = starts + left * crl::time(1000);
|
||||
const auto timer = lifetime.make_state<base::Timer>();
|
||||
const auto callback = [=] {
|
||||
const auto now = crl::now();
|
||||
const auto left = (ends > now) ? ends - now : 0;
|
||||
const auto seconds = (left + 999) / 1000;
|
||||
consumer.put_next_copy(seconds);
|
||||
if (seconds) {
|
||||
const auto next = left % 1000;
|
||||
const auto wait = next ? next : 1000;
|
||||
timer->callOnce(wait);
|
||||
} else {
|
||||
consumer.put_done();
|
||||
}
|
||||
};
|
||||
timer->setCallback(callback);
|
||||
callback();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<RpWidget> MakeBidRow(
|
||||
not_null<RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
rpl::producer<BidRowData> data) {
|
||||
auto result = object_ptr<RpWidget>(parent.get());
|
||||
const auto raw = result.data();
|
||||
|
||||
struct State {
|
||||
std::unique_ptr<FlatLabel> place;
|
||||
std::unique_ptr<UserpicButton> userpic;
|
||||
std::unique_ptr<FlatLabel> name;
|
||||
std::unique_ptr<FlatLabel> stars;
|
||||
UserData *user = nullptr;
|
||||
};
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
state->place = std::make_unique<FlatLabel>(
|
||||
raw,
|
||||
rpl::duplicate(data) | rpl::map(&BidRowData::place),
|
||||
st::auctionBidPlace);
|
||||
|
||||
auto name = rpl::duplicate(data) | rpl::map([](const BidRowData &bid) {
|
||||
return bid.user ? bid.user->name() : QString();
|
||||
});
|
||||
state->name = std::make_unique<FlatLabel>(
|
||||
raw,
|
||||
std::move(name),
|
||||
st::auctionBidName);
|
||||
|
||||
auto helper = Text::CustomEmojiHelper(Core::TextContext({
|
||||
.session = &show->session(),
|
||||
}));
|
||||
const auto star = helper.paletteDependent(Ui::Earn::IconCreditsEmoji());
|
||||
auto stars = rpl::duplicate(data) | rpl::map([=](const BidRowData &bid) {
|
||||
return TextWithEntities{ star }.append(' ').append(
|
||||
Lang::FormatCountDecimal(bid.stars));
|
||||
});
|
||||
state->stars = std::make_unique<FlatLabel>(
|
||||
raw,
|
||||
std::move(stars),
|
||||
st::auctionBidStars,
|
||||
st::defaultPopupMenu,
|
||||
helper.context());
|
||||
|
||||
const auto kHuge = u"99999"_q;
|
||||
const auto userpicLeft = st::auctionBidPlace.style.font->width(kHuge);
|
||||
|
||||
std::move(data) | rpl::start_with_next([=](BidRowData bid) {
|
||||
state->place->setTextColorOverride(BidColorOverride(bid.type));
|
||||
if (state->user != bid.user) {
|
||||
state->user = bid.user;
|
||||
if (state->user) {
|
||||
if (auto was = state->userpic.release()) {
|
||||
was->hide();
|
||||
crl::on_main(was, [=] { delete was; });
|
||||
}
|
||||
state->userpic = std::make_unique<UserpicButton>(
|
||||
raw,
|
||||
state->user,
|
||||
st::auctionBidUserpic);
|
||||
state->userpic->show();
|
||||
state->userpic->moveToLeft(userpicLeft, 0);
|
||||
raw->resize(raw->width(), state->userpic->height());
|
||||
} else {
|
||||
raw->resize(raw->width(), 0);
|
||||
}
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
state->stars->widthValue()
|
||||
) | rpl::start_with_next([=](int outer, int stars) {
|
||||
const auto userpicHeight = st::auctionBidUserpic.size.height();
|
||||
const auto top = (userpicHeight - st::normalFont->height) / 2;
|
||||
state->place->moveToLeft(0, top, outer);
|
||||
if (state->userpic) {
|
||||
state->userpic->moveToLeft(userpicLeft, 0, outer);
|
||||
}
|
||||
state->stars->moveToRight(0, top, outer);
|
||||
|
||||
const auto userpicRight = userpicLeft + state->userpic->width();
|
||||
const auto nameLeft = userpicRight + st::auctionBidSkip;
|
||||
const auto nameRight = stars + st::auctionBidSkip;
|
||||
state->name->resizeToWidth(outer - nameLeft - nameRight);
|
||||
state->name->moveToLeft(nameLeft, top, outer);
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct AuctionBidBoxArgs {
|
||||
not_null<PeerData*> peer;
|
||||
std::shared_ptr<ChatHelpers::Show> show;
|
||||
@@ -124,85 +280,318 @@ void PlaceAuctionBid(
|
||||
});
|
||||
}
|
||||
|
||||
object_ptr<RpWidget> MakeAuctionInfoBlocks(
|
||||
not_null<RpWidget*> box,
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<Data::GiftAuctionState> stateValue) {
|
||||
auto helper = Text::CustomEmojiHelper(Core::TextContext({
|
||||
.session = session,
|
||||
}));
|
||||
const auto star = helper.paletteDependent(Ui::Earn::IconCreditsEmoji());
|
||||
|
||||
auto bidTitle = rpl::duplicate(
|
||||
stateValue
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state) {
|
||||
return TextWithEntities{
|
||||
star
|
||||
}.append(' ').append(Lang::FormatCountDecimal(state.minBidAmount));
|
||||
});
|
||||
auto minimal = rpl::duplicate(
|
||||
stateValue
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state) {
|
||||
return state.minBidAmount;
|
||||
}) | tr::to_count();
|
||||
auto untilTitle = rpl::duplicate(
|
||||
stateValue
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state) {
|
||||
return SecondsLeftTillValue(state.nextRoundAt);
|
||||
}) | rpl::flatten_latest() | rpl::map([=](int seconds) {
|
||||
const auto minutes = seconds / 60;
|
||||
const auto hours = minutes / 60;
|
||||
return hours
|
||||
? 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'));
|
||||
}) | Text::ToWithEntities();
|
||||
auto leftTitle = rpl::duplicate(
|
||||
stateValue
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state) {
|
||||
return Data::SingleCustomEmoji(
|
||||
state.gift->document
|
||||
).append(' ').append(Lang::FormatCountDecimal(state.giftsLeft));
|
||||
});
|
||||
auto left = rpl::duplicate(
|
||||
stateValue
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state) {
|
||||
return state.giftsLeft;
|
||||
}) | tr::to_count();
|
||||
return MakeStarSelectInfoBlocks(box, {
|
||||
{
|
||||
.title = std::move(bidTitle),
|
||||
.subtext = tr::lng_auction_bid_minimal(
|
||||
lt_count,
|
||||
std::move(minimal)),
|
||||
},
|
||||
{
|
||||
.title = std::move(untilTitle),
|
||||
.subtext = tr::lng_auction_bid_until(),
|
||||
},
|
||||
{
|
||||
.title = std::move(leftTitle),
|
||||
.subtext = tr::lng_auction_bid_left(lt_count, std::move(left))
|
||||
},
|
||||
}, helper.context());
|
||||
}
|
||||
|
||||
void AddBidPlaces(
|
||||
not_null<GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
rpl::producer<Data::GiftAuctionState> value,
|
||||
rpl::producer<int> chosen) {
|
||||
struct My {
|
||||
BidType type;
|
||||
int position = 0;
|
||||
|
||||
inline bool operator==(const My &) const = default;
|
||||
};
|
||||
struct State {
|
||||
rpl::variable<My> my;
|
||||
rpl::variable<std::vector<BidRowData>> top;
|
||||
std::vector<Ui::PeerUserpicView> cache;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
rpl::duplicate(
|
||||
value
|
||||
) | rpl::start_with_next([=](const Data::GiftAuctionState &value) {
|
||||
auto cache = std::vector<Ui::PeerUserpicView>();
|
||||
cache.reserve(value.topBidders.size());
|
||||
for (const auto &user : value.topBidders) {
|
||||
cache.push_back(user->createUserpicView());
|
||||
}
|
||||
state->cache = std::move(cache);
|
||||
}, box->lifetime());
|
||||
|
||||
state->my = rpl::combine(
|
||||
rpl::duplicate(value),
|
||||
rpl::duplicate(chosen)
|
||||
) | rpl::map([=](const Data::GiftAuctionState &value, int chosen) {
|
||||
const auto my = value.my.bid;
|
||||
const auto &levels = value.bidLevels;
|
||||
|
||||
auto top = std::vector<BidRowData>();
|
||||
top.reserve(3);
|
||||
const auto pushTop = [&](auto i) {
|
||||
const auto index = int(i - begin(levels));
|
||||
if (top.size() < 3 && index < value.topBidders.size()) {
|
||||
top.push_back({ value.topBidders[index], int(i->amount) });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto setting = (chosen > my);
|
||||
const auto finishWith = [&](int position) {
|
||||
state->top = std::move(top);
|
||||
const auto type = setting
|
||||
? BidType::Setting
|
||||
: (position <= value.gift->auctionGiftsPerRound)
|
||||
? BidType::Winning
|
||||
: BidType::Loosing;
|
||||
return My{ type, position };
|
||||
};
|
||||
|
||||
for (auto i = begin(levels), e = end(levels); i != e; ++i) {
|
||||
if (i->amount < chosen
|
||||
|| (!setting
|
||||
&& i->amount == chosen
|
||||
&& i->date > value.my.date)) {
|
||||
top.push_back({ show->session().user(), chosen });
|
||||
for (auto j = i; j != e; ++j) {
|
||||
if (!pushTop(j)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return finishWith(i->position);
|
||||
}
|
||||
pushTop(i);
|
||||
}
|
||||
return finishWith((levels.empty() ? 0 : levels.back().position) + 1);
|
||||
});
|
||||
auto myLabelText = state->my.value() | rpl::map([](My my) {
|
||||
switch (my.type) {
|
||||
case BidType::Setting: return tr::lng_auction_bid_your_title();
|
||||
case BidType::Winning: return tr::lng_auction_bid_your_winning();
|
||||
case BidType::Loosing: return tr::lng_auction_bid_your_outbid();
|
||||
}
|
||||
Unexpected("Type in BidType.");
|
||||
}) | rpl::flatten_latest();
|
||||
const auto myLabel = AddSubsectionTitle(
|
||||
box->verticalLayout(),
|
||||
std::move(myLabelText));
|
||||
state->my.value() | rpl::start_with_next([=](My my) {
|
||||
myLabel->setTextColorOverride(BidColorOverride(my.type));
|
||||
}, myLabel->lifetime());
|
||||
|
||||
auto bid = rpl::combine(
|
||||
state->my.value(),
|
||||
rpl::duplicate(chosen)
|
||||
) | rpl::map([=, user = show->session().user()](My my, int stars) {
|
||||
const auto place = QString::number(my.position);
|
||||
return BidRowData{ user, stars, place, my.type };
|
||||
});
|
||||
box->addRow(MakeBidRow(box, show, std::move(bid)));
|
||||
|
||||
AddSubsectionTitle(
|
||||
box->verticalLayout(),
|
||||
tr::lng_auction_bid_winners_title(),
|
||||
{ 0, st::paidReactTitleSkip / 2, 0, 0 });
|
||||
for (auto i = 0; i != 3; ++i) {
|
||||
auto icon = QString::fromUtf8("\xf0\x9f\xa5\x87");
|
||||
icon.back().unicode() += i;
|
||||
|
||||
auto bid = state->top.value(
|
||||
) | rpl::map([=](const std::vector<BidRowData> &top) {
|
||||
auto result = (i < top.size()) ? top[i] : BidRowData();
|
||||
result.place = icon;
|
||||
return result;
|
||||
});
|
||||
box->addRow(MakeBidRow(box, show, std::move(bid)));
|
||||
}
|
||||
}
|
||||
|
||||
void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
const auto weak = base::make_weak(box);
|
||||
const auto state = box->lifetime().make_state<
|
||||
rpl::variable<Data::GiftAuctionState>
|
||||
>(std::move(args.state));
|
||||
auto submit = [=](rpl::producer<int> amount) {
|
||||
return rpl::combine(
|
||||
state->value(),
|
||||
std::move(amount)
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state, int count) {
|
||||
return !state.my.bid
|
||||
? tr::lng_auction_bid_place(
|
||||
lt_stars,
|
||||
rpl::single(Ui::CreditsEmojiSmall().append(
|
||||
Lang::FormatCountDecimal(count))),
|
||||
tr::marked)
|
||||
: (count <= state.my.bid)
|
||||
? tr::lng_box_ok(tr::marked)
|
||||
: tr::lng_auction_bid_increase(
|
||||
lt_stars,
|
||||
rpl::single(Ui::CreditsEmojiSmall().append(
|
||||
Lang::FormatCountDecimal(count - state.my.bid))),
|
||||
tr::marked);
|
||||
}) | rpl::flatten_latest();
|
||||
|
||||
struct State {
|
||||
State(rpl::producer<Data::GiftAuctionState> value)
|
||||
: value(std::move(value)) {
|
||||
}
|
||||
|
||||
rpl::variable<Data::GiftAuctionState> value;
|
||||
rpl::variable<int> chosen;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>(
|
||||
std::move(args.state));
|
||||
const auto &now = state->value.current();
|
||||
const auto mine = int(now.my.bid);
|
||||
const auto min = std::max(
|
||||
int(mine ? now.my.minBidAmount : now.minBidAmount),
|
||||
1);
|
||||
const auto max = std::max({
|
||||
min + 1,
|
||||
kMaxShownBid,
|
||||
mine,
|
||||
});
|
||||
const auto chosen = mine ? mine : std::clamp(mine, min, max);
|
||||
state->chosen = chosen;
|
||||
|
||||
const auto show = args.show;
|
||||
const auto session = &show->session();
|
||||
auto top = std::vector<PaidReactionTop>{
|
||||
PaidReactionTop{
|
||||
.name = session->user()->shortName(),
|
||||
.photo = MakeUserpicThumbnail(session->user()),
|
||||
.barePeerId = uint64(session->userPeerId().value),
|
||||
.count = int(state->current().my.bid),
|
||||
.my = true,
|
||||
const auto save = [=, peer = args.peer](int amount) {
|
||||
const auto ¤t = state->value.current();
|
||||
if (amount > current.my.bid) {
|
||||
const auto was = (current.my.bid > 0);
|
||||
const auto perRound = current.gift->auctionGiftsPerRound;
|
||||
const auto done = [=](Payments::CheckoutResult result) {
|
||||
if (result == Payments::CheckoutResult::Paid) {
|
||||
show->showToast({
|
||||
.title = (was
|
||||
? tr::lng_auction_bid_increased_title
|
||||
: tr::lng_auction_bid_placed_title)(
|
||||
tr::now),
|
||||
.text = tr::lng_auction_bid_done_text(
|
||||
tr::now,
|
||||
lt_count,
|
||||
perRound,
|
||||
tr::rich),
|
||||
.duration = kBidPlacedToastDuration,
|
||||
});
|
||||
}
|
||||
};
|
||||
PlaceAuctionBid(show, peer, amount, now, done);
|
||||
}
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
const auto save = [=, peer = args.peer](int amount) {
|
||||
const auto &now = state->current();
|
||||
const auto was = (now.my.bid > 0);
|
||||
const auto perRound = now.gift->auctionGiftsPerRound;
|
||||
const auto done = [=](Payments::CheckoutResult result) {
|
||||
if (result == Payments::CheckoutResult::Paid) {
|
||||
show->showToast({
|
||||
.title = (was
|
||||
? tr::lng_auction_bid_increased_title
|
||||
: tr::lng_auction_bid_placed_title)(
|
||||
tr::now),
|
||||
.text = tr::lng_auction_bid_done_text(
|
||||
tr::now,
|
||||
lt_count,
|
||||
perRound,
|
||||
tr::rich),
|
||||
.duration = kBidPlacedToastDuration,
|
||||
});
|
||||
}
|
||||
};
|
||||
PlaceAuctionBid(show, peer, amount, now, done);
|
||||
const auto colorings = show->session().appConfig().groupCallColorings();
|
||||
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setStyle(st::paidReactBox);
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
AddSkip(content, st::boxTitleClose.height + st::paidReactBubbleTop);
|
||||
|
||||
const auto activeFgOverride = [=](int count) {
|
||||
const auto coloring = Calls::Group::Ui::StarsColoringForCount(
|
||||
colorings,
|
||||
count);
|
||||
return ColorFromSerialized(coloring.bgLight);
|
||||
};
|
||||
const auto mine = state->current().my.bid;
|
||||
PaidReactionsBox(box, {
|
||||
.min = int(mine
|
||||
? state->current().my.minBidAmount
|
||||
: state->current().minBidAmount),
|
||||
.explicitlyAllowed = int(mine),
|
||||
.chosen = int(state->current().my.bid),
|
||||
.max = kMaxShownBid,
|
||||
.top = std::move(top),
|
||||
.session = session,
|
||||
.submit = submit,
|
||||
.colorings = session->appConfig().groupCallColorings(),
|
||||
.balanceValue = session->credits().balanceValue(),
|
||||
.send = [=](int count, uint64 barePeerId) {
|
||||
save(count);
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
},
|
||||
.giftAuction = true,
|
||||
AddStarSelectBubble(box, state->chosen.value(), max, activeFgOverride);
|
||||
|
||||
PaidReactionSlider(
|
||||
content,
|
||||
st::paidReactSlider,
|
||||
min,
|
||||
mine,
|
||||
chosen,
|
||||
max,
|
||||
[=](int count) { state->chosen = count; },
|
||||
activeFgOverride);
|
||||
|
||||
box->addTopButton(
|
||||
st::boxTitleClose,
|
||||
[=] { box->closeBox(); });
|
||||
|
||||
const auto skip = st::paidReactTitleSkip;
|
||||
box->addRow(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
tr::lng_auction_bid_title(),
|
||||
st::boostCenteredTitle),
|
||||
st::boxRowPadding + QMargins(0, skip, 0, 0),
|
||||
style::al_top);
|
||||
|
||||
box->addRow(
|
||||
MakeAuctionInfoBlocks(box, &show->session(), state->value.value()),
|
||||
st::boxRowPadding + QMargins(0, skip / 2, 0, skip));
|
||||
|
||||
AddBidPlaces(box, show, state->value.value(), state->chosen.value());
|
||||
|
||||
AddSkip(content);
|
||||
AddSkip(content);
|
||||
|
||||
const auto button = box->addButton(rpl::single(QString()), [=] {
|
||||
save(state->chosen.current());
|
||||
});
|
||||
|
||||
button->setText(rpl::combine(
|
||||
state->value.value(),
|
||||
state->chosen.value()
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state, int count) {
|
||||
return !state.my.bid
|
||||
? tr::lng_auction_bid_place(
|
||||
lt_stars,
|
||||
rpl::single(CreditsEmojiSmall().append(
|
||||
Lang::FormatCountDecimal(count))),
|
||||
tr::marked)
|
||||
: (count <= state.my.bid)
|
||||
? tr::lng_box_ok(tr::marked)
|
||||
: tr::lng_auction_bid_increase(
|
||||
lt_stars,
|
||||
rpl::single(CreditsEmojiSmall().append(
|
||||
Lang::FormatCountDecimal(count - state.my.bid))),
|
||||
tr::marked);
|
||||
}) | rpl::flatten_latest());
|
||||
|
||||
AddStarSelectBalance(
|
||||
box,
|
||||
&show->session(),
|
||||
show->session().credits().balanceValue());
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<BoxContent> MakeAuctionBidBox(
|
||||
@@ -215,8 +604,8 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
std::shared_ptr<TableRowTooltipData> tooltip,
|
||||
const QString &name,
|
||||
int64 amount) {
|
||||
auto helper = Ui::Text::CustomEmojiHelper();
|
||||
const auto price = helper.paletteDependent(Ui::Earn::IconCreditsEmoji(
|
||||
auto helper = Text::CustomEmojiHelper();
|
||||
const auto price = helper.paletteDependent(Earn::IconCreditsEmoji(
|
||||
)).append(' ').append(
|
||||
Lang::FormatCreditsAmountDecimal(CreditsAmount{ amount }));
|
||||
return MakeTableValueWithTooltip(
|
||||
@@ -227,7 +616,7 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
tr::now,
|
||||
lt_amount,
|
||||
tr::bold(
|
||||
Ui::Text::IconEmoji(&st::starIconEmojiInline).append(
|
||||
Text::IconEmoji(&st::starIconEmojiInline).append(
|
||||
Lang::FormatCountDecimal(amount))),
|
||||
lt_gift,
|
||||
tr::bold(name),
|
||||
@@ -237,7 +626,7 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
|
||||
[[nodiscard]] object_ptr<TableLayout> AuctionInfoTable(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<Data::GiftAuctionState> value) {
|
||||
auto result = object_ptr<TableLayout>(parent.get(), st::defaultTable);
|
||||
const auto raw = result.data();
|
||||
@@ -354,14 +743,14 @@ void AuctionAboutBox(
|
||||
st::boxRowPadding + st::confcallLinkHeaderIconPadding);
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
tr::lng_auction_about_title(),
|
||||
st::boxTitle),
|
||||
st::boxRowPadding + st::confcallLinkTitlePadding,
|
||||
style::al_top);
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
tr::lng_auction_about_subtitle(tr::rich),
|
||||
st::confcallLinkCenteredText),
|
||||
@@ -410,7 +799,7 @@ void AuctionAboutBox(
|
||||
},
|
||||
};
|
||||
for (const auto &feature : features) {
|
||||
box->addRow(Ui::MakeFeatureListEntry(box, feature));
|
||||
box->addRow(MakeFeatureListEntry(box, feature));
|
||||
}
|
||||
|
||||
const auto close = Fn<void()>([weak = base::make_weak(box)] {
|
||||
@@ -421,7 +810,7 @@ void AuctionAboutBox(
|
||||
box->addButton(
|
||||
rpl::single(QString()),
|
||||
understood ? [=] { understood(close); } : close
|
||||
)->setText(rpl::single(Ui::Text::IconEmoji(
|
||||
)->setText(rpl::single(Text::IconEmoji(
|
||||
&st::infoStarsUnderstood
|
||||
).append(' ').append(tr::lng_auction_about_understood(tr::now))));
|
||||
}
|
||||
@@ -525,7 +914,7 @@ void AuctionInfoBox(
|
||||
rpl::mappers::_1 > 0
|
||||
) | rpl::take(1) | rpl::start_with_next([=](int count) {
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
tr::lng_auction_bought(
|
||||
lt_count_decimal,
|
||||
@@ -534,7 +923,7 @@ void AuctionInfoBox(
|
||||
rpl::single(Data::SingleCustomEmoji(
|
||||
state->value.current().gift->document)),
|
||||
lt_arrow,
|
||||
rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
|
||||
rpl::single(Text::IconEmoji(&st::textMoreIconEmoji)),
|
||||
tr::link),
|
||||
st::uniqueGiftValueAvailableLink,
|
||||
st::defaultPopupMenu,
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "payments/ui/payments_reaction_box.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/painter.h"
|
||||
@@ -18,57 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace Calls::Group::Ui {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> MakeInfoBlock(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> subtext) {
|
||||
const auto result = CreateChild<Ui::RpWidget>(parent);
|
||||
|
||||
const auto titleHeight = st::videoStreamInfoTitle.style.font->height;
|
||||
const auto subtextHeight = st::videoStreamInfoSubtext.style.font->height;
|
||||
const auto height = titleHeight + subtextHeight;
|
||||
|
||||
result->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(result);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::groupCallMembersBgOver);
|
||||
const auto radius = st::boxRadius;
|
||||
p.drawRoundedRect(result->rect(), radius, radius);
|
||||
}, result->lifetime());
|
||||
|
||||
result->resize(
|
||||
result ->width(),
|
||||
QSize(height, height).grownBy(st::videoStreamInfoPadding).height());
|
||||
|
||||
const auto titleLabel = CreateChild<Ui::FlatLabel>(
|
||||
result,
|
||||
std::move(title),
|
||||
st::videoStreamInfoTitle);
|
||||
const auto subtextLabel = CreateChild<Ui::FlatLabel>(
|
||||
result,
|
||||
std::move(subtext),
|
||||
st::videoStreamInfoSubtext);
|
||||
|
||||
rpl::combine(
|
||||
result->widthValue(),
|
||||
titleLabel->widthValue(),
|
||||
subtextLabel->widthValue()
|
||||
) | rpl::start_with_next([=](int width, int titlew, int subtextw) {
|
||||
const auto padding = st::videoStreamInfoPadding;
|
||||
titleLabel->moveToLeft((width - titlew) / 2, padding.top(), width);
|
||||
subtextLabel->moveToLeft(
|
||||
(width - subtextw) / 2,
|
||||
padding.top() + titleHeight,
|
||||
width);
|
||||
}, result->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StarsColoring StarsColoringForCount(
|
||||
const std::vector<StarsColoring> &colorings,
|
||||
@@ -92,7 +42,7 @@ int StarsRequiredForMessage(
|
||||
auto view = QStringView(text.text);
|
||||
const auto length = int(view.size());
|
||||
while (!view.isEmpty()) {
|
||||
if (Ui::Emoji::Find(view, &outLength)) {
|
||||
if (Emoji::Find(view, &outLength)) {
|
||||
view = view.mid(outLength);
|
||||
++emojis;
|
||||
} else {
|
||||
@@ -107,26 +57,22 @@ int StarsRequiredForMessage(
|
||||
return colorings.back().fromStars + 1;
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> VideoStreamStarsLevel(
|
||||
not_null<Ui::RpWidget*> box,
|
||||
object_ptr<RpWidget> VideoStreamStarsLevel(
|
||||
not_null<RpWidget*> box,
|
||||
const std::vector<StarsColoring> &colorings,
|
||||
rpl::producer<int> starsValue) {
|
||||
auto result = object_ptr<Ui::RpWidget>(box.get());
|
||||
const auto raw = result.data();
|
||||
|
||||
struct State {
|
||||
rpl::variable<int> stars;
|
||||
rpl::variable<StarsColoring> coloring;
|
||||
std::vector<not_null<Ui::RpWidget*>> blocks;
|
||||
};
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->stars = std::move(starsValue);
|
||||
state->coloring = state->stars.value(
|
||||
) | rpl::map([=](int stars) {
|
||||
return StarsColoringForCount(colorings, stars);
|
||||
});
|
||||
|
||||
state->blocks.push_back(MakeInfoBlock(raw, state->coloring.value(
|
||||
auto pinTitle = state->coloring.value(
|
||||
) | rpl::map([=](const StarsColoring &value) {
|
||||
const auto seconds = value.secondsPin;
|
||||
return (seconds >= 3600)
|
||||
@@ -134,46 +80,43 @@ object_ptr<Ui::RpWidget> VideoStreamStarsLevel(
|
||||
: (seconds >= 60)
|
||||
? tr::lng_minutes_tiny(tr::now, lt_count, seconds / 60)
|
||||
: tr::lng_seconds_tiny(tr::now, lt_count, seconds);
|
||||
}), tr::lng_paid_comment_pin_about()));
|
||||
|
||||
state->blocks.push_back(MakeInfoBlock(raw, state->coloring.value(
|
||||
});
|
||||
auto limitTitle = state->coloring.value(
|
||||
) | rpl::map([=](const StarsColoring &value) {
|
||||
return QString::number(value.charactersMax);
|
||||
}), state->coloring.value() | rpl::map([=](const StarsColoring &value) {
|
||||
});
|
||||
auto limitSubtext = state->coloring.value(
|
||||
) | rpl::map([=](const StarsColoring &value) {
|
||||
return tr::lng_paid_comment_limit_about(
|
||||
tr::now,
|
||||
lt_count,
|
||||
value.charactersMax);
|
||||
})));
|
||||
|
||||
state->blocks.push_back(MakeInfoBlock(raw, state->coloring.value(
|
||||
});
|
||||
auto emojiTitle = state->coloring.value(
|
||||
) | rpl::map([=](const StarsColoring &value) {
|
||||
return QString::number(value.emojiLimit);
|
||||
}), state->coloring.value() | rpl::map([=](const StarsColoring &value) {
|
||||
});
|
||||
auto emojiSubtext = state->coloring.value(
|
||||
) | rpl::map([=](const StarsColoring &value) {
|
||||
return tr::lng_paid_comment_emoji_about(
|
||||
tr::now,
|
||||
lt_count,
|
||||
value.emojiLimit);
|
||||
})));
|
||||
|
||||
raw->resize(raw->width(), state->blocks.front()->height());
|
||||
raw->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto count = int(state->blocks.size());
|
||||
const auto skip = (st::boxRowPadding.left() / 2);
|
||||
const auto single = (width - skip * (count - 1)) / float64(count);
|
||||
if (single < 1.) {
|
||||
return;
|
||||
}
|
||||
auto x = 0.;
|
||||
const auto w = int(base::SafeRound(single));
|
||||
for (const auto &block : state->blocks) {
|
||||
block->resizeToWidth(w);
|
||||
block->moveToLeft(int(base::SafeRound(x)), 0);
|
||||
x += single + skip;
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
});
|
||||
return MakeStarSelectInfoBlocks(box, {
|
||||
{
|
||||
.title = std::move(pinTitle) | Text::ToWithEntities(),
|
||||
.subtext = tr::lng_paid_comment_pin_about(),
|
||||
},
|
||||
{
|
||||
.title = std::move(limitTitle) | Text::ToWithEntities(),
|
||||
.subtext = std::move(limitSubtext),
|
||||
},
|
||||
{
|
||||
.title = std::move(emojiTitle) | Text::ToWithEntities(),
|
||||
.subtext = std::move(emojiSubtext),
|
||||
},
|
||||
}, {}, true);
|
||||
}
|
||||
|
||||
} // namespace Calls::Group::Ui
|
||||
|
||||
@@ -65,187 +65,6 @@ struct TopReactorKey {
|
||||
const TopReactorKey &) = default;
|
||||
};
|
||||
|
||||
struct Discreter {
|
||||
Fn<int(float64)> ratioToValue;
|
||||
Fn<float64(int)> valueToRatio;
|
||||
};
|
||||
|
||||
[[nodiscard]] Discreter DiscreterForMax(int max) {
|
||||
Expects(max >= 2);
|
||||
|
||||
// 1/8 of width is 1..10
|
||||
// 1/3 of width is 1..100
|
||||
// 2/3 of width is 1..1000
|
||||
|
||||
auto thresholds = base::flat_map<float64, int>();
|
||||
thresholds.emplace(0., 1);
|
||||
if (max <= 40) {
|
||||
thresholds.emplace(1., max);
|
||||
} else if (max <= 300) {
|
||||
thresholds.emplace(1. / 4, 10);
|
||||
thresholds.emplace(1., max);
|
||||
} else if (max <= 600) {
|
||||
thresholds.emplace(1. / 8, 10);
|
||||
thresholds.emplace(1. / 2, 100);
|
||||
thresholds.emplace(1., max);
|
||||
} else if (max <= 1900) {
|
||||
thresholds.emplace(1. / 8, 10);
|
||||
thresholds.emplace(1. / 3, 100);
|
||||
thresholds.emplace(1., max);
|
||||
} else if (max <= 10000) {
|
||||
thresholds.emplace(1. / 8, 10);
|
||||
thresholds.emplace(1. / 3, 100);
|
||||
thresholds.emplace(2. / 3, 1000);
|
||||
thresholds.emplace(1., max);
|
||||
} else {
|
||||
thresholds.emplace(1. / 10, 10);
|
||||
thresholds.emplace(1. / 6, 100);
|
||||
thresholds.emplace(1. / 3, 1000);
|
||||
thresholds.emplace(1., max);
|
||||
}
|
||||
|
||||
const auto ratioToValue = [=](float64 ratio) {
|
||||
ratio = std::clamp(ratio, 0., 1.);
|
||||
const auto j = thresholds.lower_bound(ratio);
|
||||
if (j == begin(thresholds)) {
|
||||
return 1;
|
||||
}
|
||||
const auto i = j - 1;
|
||||
const auto progress = (ratio - i->first) / (j->first - i->first);
|
||||
const auto value = i->second + (j->second - i->second) * progress;
|
||||
return int(base::SafeRound(value));
|
||||
};
|
||||
const auto valueToRatio = [=](int value) {
|
||||
value = std::clamp(value, 1, max);
|
||||
auto i = begin(thresholds);
|
||||
auto j = i + 1;
|
||||
while (j->second < value) {
|
||||
i = j++;
|
||||
}
|
||||
const auto progress = (value - i->second)
|
||||
/ float64(j->second - i->second);
|
||||
return i->first + (j->first - i->first) * progress;
|
||||
};
|
||||
return {
|
||||
.ratioToValue = ratioToValue,
|
||||
.valueToRatio = valueToRatio,
|
||||
};
|
||||
}
|
||||
|
||||
void PaidReactionSlider(
|
||||
not_null<VerticalLayout*> container,
|
||||
const style::MediaSlider &st,
|
||||
int min,
|
||||
int explicitlyAllowed,
|
||||
int current,
|
||||
int max,
|
||||
Fn<void(int)> changed,
|
||||
Fn<QColor(int)> activeFgOverride = nullptr) {
|
||||
Expects(current >= 1 && current <= max);
|
||||
Expects(explicitlyAllowed <= max);
|
||||
|
||||
if (!explicitlyAllowed) {
|
||||
explicitlyAllowed = min;
|
||||
}
|
||||
const auto slider = container->add(
|
||||
object_ptr<MediaSlider>(container, st),
|
||||
st::boxRowPadding + QMargins(0, st::paidReactSliderTop, 0, 0));
|
||||
slider->resize(slider->width(), st::paidReactSlider.seekSize.height());
|
||||
|
||||
const auto update = [=](int count) {
|
||||
if (activeFgOverride) {
|
||||
const auto color = activeFgOverride(count);
|
||||
slider->setColorOverrides({
|
||||
.activeBg = color,
|
||||
.activeBorder = color,
|
||||
.seekFg = st::groupCallMembersFg->c,
|
||||
.seekBorder = color,
|
||||
.inactiveBorder = Qt::transparent,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const auto discreter = DiscreterForMax(max);
|
||||
slider->setAlwaysDisplayMarker(true);
|
||||
slider->setDirection(ContinuousSlider::Direction::Horizontal);
|
||||
slider->setValue(discreter.valueToRatio(current));
|
||||
const auto ratioToValue = [=](float64 ratio) {
|
||||
const auto value = discreter.ratioToValue(ratio);
|
||||
return (value <= explicitlyAllowed && explicitlyAllowed < min)
|
||||
? explicitlyAllowed
|
||||
: std::max(value, min);
|
||||
};
|
||||
slider->setAdjustCallback([=](float64 ratio) {
|
||||
return discreter.valueToRatio(ratioToValue(ratio));
|
||||
});
|
||||
const auto callback = [=](float64 ratio) {
|
||||
const auto value = ratioToValue(ratio);
|
||||
update(value);
|
||||
changed(value);
|
||||
};
|
||||
slider->setChangeProgressCallback(callback);
|
||||
slider->setChangeFinishedCallback(callback);
|
||||
update(current);
|
||||
|
||||
struct State {
|
||||
StarParticles particles = StarParticles(
|
||||
StarParticles::Type::Right,
|
||||
200,
|
||||
st::lineWidth * 7);
|
||||
Ui::Animations::Basic animation;
|
||||
};
|
||||
const auto state = slider->lifetime().make_state<State>();
|
||||
|
||||
const auto stars = Ui::CreateChild<Ui::RpWidget>(slider->parentWidget());
|
||||
stars->show();
|
||||
stars->raise();
|
||||
slider->geometryValue() | rpl::start_with_next([=](QRect rect) {
|
||||
stars->setGeometry(rect);
|
||||
}, stars->lifetime());
|
||||
|
||||
state->animation.init([=] { stars->update(); });
|
||||
stars->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
const auto seekSize = st::paidReactSlider.seekSize.width();
|
||||
const auto seekRadius = seekSize / 2.;
|
||||
stars->paintRequest() | rpl::start_with_next([=] {
|
||||
if (!state->animation.animating()) {
|
||||
state->animation.start();
|
||||
}
|
||||
auto p = QPainter(stars);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto progress = slider->value();
|
||||
const auto rect = stars->rect();
|
||||
const auto availableWidth = rect.width() - seekSize;
|
||||
const auto seekCenter = seekRadius + availableWidth * progress;
|
||||
|
||||
state->particles.setSpeed(.1 + progress * .3);
|
||||
state->particles.setVisible(.25 + .65 * progress);
|
||||
|
||||
auto fullPath = QPainterPath();
|
||||
fullPath.addRoundedRect(QRectF(rect), seekRadius, seekRadius);
|
||||
auto circlePath = QPainterPath();
|
||||
circlePath.addEllipse(
|
||||
QPointF(seekCenter, rect.height() / 2.),
|
||||
seekRadius,
|
||||
seekRadius);
|
||||
auto rightRect = QPainterPath();
|
||||
rightRect.addRect(
|
||||
QRectF(seekCenter, 0, rect.width() - seekCenter, rect.height()));
|
||||
|
||||
p.setClipPath(fullPath.subtracted(circlePath));
|
||||
state->particles.setColor(Qt::white);
|
||||
state->particles.paint(p, rect, crl::now());
|
||||
p.setClipping(false);
|
||||
|
||||
p.setClipPath(fullPath.intersected(circlePath.united(rightRect)));
|
||||
state->particles.setColor(activeFgOverride
|
||||
? st::groupCallMemberInactiveIcon->c
|
||||
: st::creditsBg3->c);
|
||||
state->particles.paint(p, rect, crl::now());
|
||||
}, stars->lifetime());
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage GenerateBadgeImage(
|
||||
const std::vector<Calls::Group::Ui::StarsColoring> &colorings,
|
||||
int count,
|
||||
@@ -572,6 +391,58 @@ void FillTopReactors(
|
||||
}, wrap->lifetime());
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<RpWidget*> MakeStarSelectInfoBlock(
|
||||
not_null<RpWidget*> parent,
|
||||
rpl::producer<TextWithEntities> title,
|
||||
rpl::producer<QString> subtext,
|
||||
Text::MarkedContext context,
|
||||
bool dark) {
|
||||
const auto result = CreateChild<RpWidget>(parent);
|
||||
|
||||
const auto titleHeight = st::starSelectInfoTitle.style.font->height;
|
||||
const auto subtextHeight = st::starSelectInfoSubtext.style.font->height;
|
||||
const auto height = titleHeight + subtextHeight;
|
||||
|
||||
result->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(result);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(dark ? st::groupCallMembersBgOver : st::windowBgOver);
|
||||
const auto radius = st::boxRadius;
|
||||
p.drawRoundedRect(result->rect(), radius, radius);
|
||||
}, result->lifetime());
|
||||
|
||||
result->resize(
|
||||
result->width(),
|
||||
QSize(height, height).grownBy(st::starSelectInfoPadding).height());
|
||||
|
||||
const auto titleLabel = CreateChild<FlatLabel>(
|
||||
result,
|
||||
std::move(title),
|
||||
dark ? st::videoStreamInfoTitle : st::starSelectInfoTitle,
|
||||
st::defaultPopupMenu,
|
||||
context);
|
||||
const auto subtextLabel = CreateChild<FlatLabel>(
|
||||
result,
|
||||
std::move(subtext),
|
||||
dark ? st::videoStreamInfoSubtext : st::starSelectInfoSubtext);
|
||||
|
||||
rpl::combine(
|
||||
result->widthValue(),
|
||||
titleLabel->widthValue(),
|
||||
subtextLabel->widthValue()
|
||||
) | rpl::start_with_next([=](int width, int titlew, int subtextw) {
|
||||
const auto padding = st::starSelectInfoPadding;
|
||||
titleLabel->moveToLeft((width - titlew) / 2, padding.top(), width);
|
||||
subtextLabel->moveToLeft(
|
||||
(width - subtextw) / 2,
|
||||
padding.top() + titleHeight,
|
||||
width);
|
||||
}, result->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void PaidReactionsBox(
|
||||
@@ -612,7 +483,6 @@ void PaidReactionsBox(
|
||||
const auto colorings = args.colorings;
|
||||
const auto videoStreamChoosing = args.videoStreamChoosing;
|
||||
const auto videoStreamSending = args.videoStreamSending;
|
||||
const auto giftAuction = args.giftAuction;
|
||||
const auto videoStream = videoStreamChoosing || videoStreamSending;
|
||||
const auto initialShownPeer = ranges::find(
|
||||
args.top,
|
||||
@@ -628,41 +498,17 @@ void PaidReactionsBox(
|
||||
const auto content = box->verticalLayout();
|
||||
AddSkip(content, st::boxTitleClose.height + st::paidReactBubbleTop);
|
||||
|
||||
const auto valueToRatio = DiscreterForMax(args.max).valueToRatio;
|
||||
auto bubbleRowState = state->chosen.value() | rpl::map([=](int value) {
|
||||
const auto full = st::boxWideWidth
|
||||
- st::boxRowPadding.left()
|
||||
- st::boxRowPadding.right();
|
||||
const auto marker = st::paidReactSlider.seekSize.width();
|
||||
const auto start = marker / 2;
|
||||
const auto inner = full - marker;
|
||||
const auto correct = start + inner * valueToRatio(value);
|
||||
return Premium::BubbleRowState{
|
||||
.counter = value,
|
||||
.ratio = correct / full,
|
||||
};
|
||||
});
|
||||
const auto activeFgOverride = [=](int count) {
|
||||
const auto coloring = Calls::Group::Ui::StarsColoringForCount(
|
||||
colorings,
|
||||
count);
|
||||
return Ui::ColorFromSerialized(coloring.bgLight);
|
||||
};
|
||||
|
||||
const auto bubble = Premium::AddBubbleRow(
|
||||
content,
|
||||
st::boostBubble,
|
||||
BoxShowFinishes(box),
|
||||
std::move(bubbleRowState),
|
||||
Premium::BubbleType::Credits,
|
||||
nullptr,
|
||||
&st::paidReactBubbleIcon,
|
||||
st::boxRowPadding);
|
||||
if (videoStream || giftAuction) {
|
||||
state->chosen.value() | rpl::start_with_next([=](int count) {
|
||||
bubble->setBrushOverride(activeFgOverride(count));
|
||||
}, bubble->lifetime());
|
||||
}
|
||||
AddStarSelectBubble(
|
||||
box,
|
||||
state->chosen.value(),
|
||||
args.max,
|
||||
videoStream ? activeFgOverride : Fn<QColor(int)>());
|
||||
|
||||
const auto already = ranges::find(
|
||||
args.top,
|
||||
@@ -676,7 +522,7 @@ void PaidReactionsBox(
|
||||
args.chosen,
|
||||
args.max,
|
||||
changed,
|
||||
(videoStream || giftAuction) ? activeFgOverride : Fn<QColor(int)>());
|
||||
videoStream ? activeFgOverride : Fn<QColor(int)>());
|
||||
|
||||
box->addTopButton(
|
||||
dark ? st::darkEditStarsClose : st::boxTitleClose,
|
||||
@@ -700,7 +546,6 @@ void PaidReactionsBox(
|
||||
box->addRow(
|
||||
VideoStreamStarsLevel(box, colorings, state->chosen.value()),
|
||||
st::boxRowPadding + QMargins(0, st::paidReactTitleSkip, 0, 0));
|
||||
} else if (giftAuction) {
|
||||
} else if (videoStreamSending) {
|
||||
addTopReactors();
|
||||
}
|
||||
@@ -712,48 +557,46 @@ void PaidReactionsBox(
|
||||
? tr::lng_paid_comment_title()
|
||||
: videoStreamSending
|
||||
? tr::lng_paid_reaction_title()
|
||||
: giftAuction
|
||||
? tr::lng_auction_bid_title()
|
||||
: tr::lng_paid_react_title()),
|
||||
dark ? st::darkEditStarsCenteredTitle : st::boostCenteredTitle),
|
||||
st::boxRowPadding + QMargins(0, st::paidReactTitleSkip, 0, 0),
|
||||
style::al_top);
|
||||
if (!giftAuction) {
|
||||
const auto labelWrap = box->addRow(
|
||||
object_ptr<RpWidget>(box),
|
||||
(st::boxRowPadding
|
||||
+ QMargins(0, st::lineWidth, 0, st::boostBottomSkip)));
|
||||
const auto label = CreateChild<FlatLabel>(
|
||||
labelWrap,
|
||||
(videoStream
|
||||
? (videoStreamChoosing
|
||||
? tr::lng_paid_comment_about
|
||||
: tr::lng_paid_reaction_about)(
|
||||
lt_name,
|
||||
rpl::single(Text::Bold(args.name)),
|
||||
Text::RichLangValue)
|
||||
: already
|
||||
? tr::lng_paid_react_already(
|
||||
lt_count,
|
||||
rpl::single(already) | tr::to_count(),
|
||||
Text::RichLangValue)
|
||||
: tr::lng_paid_react_about(
|
||||
lt_channel,
|
||||
|
||||
const auto labelWrap = box->addRow(
|
||||
object_ptr<RpWidget>(box),
|
||||
(st::boxRowPadding
|
||||
+ QMargins(0, st::lineWidth, 0, st::boostBottomSkip)));
|
||||
const auto label = CreateChild<FlatLabel>(
|
||||
labelWrap,
|
||||
(videoStream
|
||||
? (videoStreamChoosing
|
||||
? tr::lng_paid_comment_about
|
||||
: tr::lng_paid_reaction_about)(
|
||||
lt_name,
|
||||
rpl::single(Text::Bold(args.name)),
|
||||
Text::RichLangValue)),
|
||||
dark ? st::darkEditStarsText : st::boostText);
|
||||
label->setTryMakeSimilarLines(true);
|
||||
labelWrap->widthValue() | rpl::start_with_next([=](int width) {
|
||||
label->resizeToWidth(width);
|
||||
}, label->lifetime());
|
||||
label->heightValue() | rpl::start_with_next([=](int height) {
|
||||
const auto min = 2 * st::normalFont->height;
|
||||
const auto skip = std::max((min - height) / 2, 0);
|
||||
labelWrap->resize(labelWrap->width(), 2 * skip + height);
|
||||
label->moveToLeft(0, skip);
|
||||
}, label->lifetime());
|
||||
}
|
||||
if (!videoStream && !giftAuction) {
|
||||
Text::RichLangValue)
|
||||
: already
|
||||
? tr::lng_paid_react_already(
|
||||
lt_count,
|
||||
rpl::single(already) | tr::to_count(),
|
||||
Text::RichLangValue)
|
||||
: tr::lng_paid_react_about(
|
||||
lt_channel,
|
||||
rpl::single(Text::Bold(args.name)),
|
||||
Text::RichLangValue)),
|
||||
dark ? st::darkEditStarsText : st::boostText);
|
||||
label->setTryMakeSimilarLines(true);
|
||||
labelWrap->widthValue() | rpl::start_with_next([=](int width) {
|
||||
label->resizeToWidth(width);
|
||||
}, label->lifetime());
|
||||
label->heightValue() | rpl::start_with_next([=](int height) {
|
||||
const auto min = 2 * st::normalFont->height;
|
||||
const auto skip = std::max((min - height) / 2, 0);
|
||||
labelWrap->resize(labelWrap->width(), 2 * skip + height);
|
||||
label->moveToLeft(0, skip);
|
||||
}, label->lifetime());
|
||||
|
||||
if (!videoStream) {
|
||||
addTopReactors();
|
||||
|
||||
const auto skip = st::defaultCheckbox.margin.bottom();
|
||||
@@ -774,21 +617,19 @@ void PaidReactionsBox(
|
||||
AddSkip(content);
|
||||
AddSkip(content);
|
||||
|
||||
if (!giftAuction) {
|
||||
AddDividerText(
|
||||
content,
|
||||
tr::lng_paid_react_agree(
|
||||
lt_link,
|
||||
rpl::combine(
|
||||
tr::lng_paid_react_agree_link(),
|
||||
tr::lng_group_invite_subscription_about_url()
|
||||
) | rpl::map([](const QString &text, const QString &url) {
|
||||
return Ui::Text::Link(text, url);
|
||||
}),
|
||||
Ui::Text::RichLangValue),
|
||||
st::defaultBoxDividerLabelPadding,
|
||||
dark ? st::groupCallDividerLabel : st::defaultDividerLabel);
|
||||
}
|
||||
AddDividerText(
|
||||
content,
|
||||
tr::lng_paid_react_agree(
|
||||
lt_link,
|
||||
rpl::combine(
|
||||
tr::lng_paid_react_agree_link(),
|
||||
tr::lng_group_invite_subscription_about_url()
|
||||
) | rpl::map([](const QString &text, const QString &url) {
|
||||
return Ui::Text::Link(text, url);
|
||||
}),
|
||||
Ui::Text::RichLangValue),
|
||||
st::defaultBoxDividerLabelPadding,
|
||||
dark ? st::groupCallDividerLabel : st::defaultDividerLabel);
|
||||
|
||||
const auto button = box->addButton(rpl::single(QString()), [=] {
|
||||
args.send(state->chosen.current(), state->shownPeer.current());
|
||||
@@ -802,24 +643,11 @@ void PaidReactionsBox(
|
||||
|
||||
button->setText(args.submit(state->chosen.value()));
|
||||
|
||||
{
|
||||
const auto balance = Settings::AddBalanceWidget(
|
||||
content,
|
||||
args.session,
|
||||
std::move(args.balanceValue),
|
||||
false,
|
||||
nullptr,
|
||||
dark);
|
||||
rpl::combine(
|
||||
balance->sizeValue(),
|
||||
box->widthValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
balance->moveToLeft(
|
||||
st::creditsHistoryRightSkip * 2,
|
||||
st::creditsHistoryRightSkip);
|
||||
balance->update();
|
||||
}, balance->lifetime());
|
||||
}
|
||||
AddStarSelectBalance(
|
||||
box,
|
||||
args.session,
|
||||
std::move(args.balanceValue),
|
||||
dark);
|
||||
}
|
||||
|
||||
object_ptr<BoxContent> MakePaidReactionBox(PaidReactionBoxArgs &&args) {
|
||||
@@ -891,4 +719,282 @@ QImage GenerateSmallBadgeImage(
|
||||
return result;
|
||||
}
|
||||
|
||||
StarSelectDiscreter StarSelectDiscreterForMax(int max) {
|
||||
Expects(max >= 2);
|
||||
|
||||
// 1/8 of width is 1..10
|
||||
// 1/3 of width is 1..100
|
||||
// 2/3 of width is 1..1000
|
||||
|
||||
auto thresholds = base::flat_map<float64, int>();
|
||||
thresholds.emplace(0., 1);
|
||||
if (max <= 40) {
|
||||
thresholds.emplace(1., max);
|
||||
} else if (max <= 300) {
|
||||
thresholds.emplace(1. / 4, 10);
|
||||
thresholds.emplace(1., max);
|
||||
} else if (max <= 600) {
|
||||
thresholds.emplace(1. / 8, 10);
|
||||
thresholds.emplace(1. / 2, 100);
|
||||
thresholds.emplace(1., max);
|
||||
} else if (max <= 1900) {
|
||||
thresholds.emplace(1. / 8, 10);
|
||||
thresholds.emplace(1. / 3, 100);
|
||||
thresholds.emplace(1., max);
|
||||
} else if (max <= 10000) {
|
||||
thresholds.emplace(1. / 8, 10);
|
||||
thresholds.emplace(1. / 3, 100);
|
||||
thresholds.emplace(2. / 3, 1000);
|
||||
thresholds.emplace(1., max);
|
||||
} else {
|
||||
thresholds.emplace(1. / 10, 10);
|
||||
thresholds.emplace(1. / 6, 100);
|
||||
thresholds.emplace(1. / 3, 1000);
|
||||
thresholds.emplace(1., max);
|
||||
}
|
||||
|
||||
const auto ratioToValue = [=](float64 ratio) {
|
||||
ratio = std::clamp(ratio, 0., 1.);
|
||||
const auto j = thresholds.lower_bound(ratio);
|
||||
if (j == begin(thresholds)) {
|
||||
return 1;
|
||||
}
|
||||
const auto i = j - 1;
|
||||
const auto progress = (ratio - i->first) / (j->first - i->first);
|
||||
const auto value = i->second + (j->second - i->second) * progress;
|
||||
return int(base::SafeRound(value));
|
||||
};
|
||||
const auto valueToRatio = [=](int value) {
|
||||
value = std::clamp(value, 1, max);
|
||||
auto i = begin(thresholds);
|
||||
auto j = i + 1;
|
||||
while (j->second < value) {
|
||||
i = j++;
|
||||
}
|
||||
const auto progress = (value - i->second)
|
||||
/ float64(j->second - i->second);
|
||||
return i->first + (j->first - i->first) * progress;
|
||||
};
|
||||
return {
|
||||
.ratioToValue = ratioToValue,
|
||||
.valueToRatio = valueToRatio,
|
||||
};
|
||||
}
|
||||
|
||||
void PaidReactionSlider(
|
||||
not_null<VerticalLayout*> container,
|
||||
const style::MediaSlider &st,
|
||||
int min,
|
||||
int explicitlyAllowed,
|
||||
int current,
|
||||
int max,
|
||||
Fn<void(int)> changed,
|
||||
Fn<QColor(int)> activeFgOverride) {
|
||||
Expects(current >= 1 && current <= max);
|
||||
Expects(explicitlyAllowed <= max);
|
||||
|
||||
if (!explicitlyAllowed) {
|
||||
explicitlyAllowed = min;
|
||||
}
|
||||
const auto slider = container->add(
|
||||
object_ptr<MediaSlider>(container, st),
|
||||
st::boxRowPadding + QMargins(0, st::paidReactSliderTop, 0, 0));
|
||||
slider->resize(slider->width(), st::paidReactSlider.seekSize.height());
|
||||
|
||||
const auto update = [=](int count) {
|
||||
if (activeFgOverride) {
|
||||
const auto color = activeFgOverride(count);
|
||||
slider->setColorOverrides({
|
||||
.activeBg = color,
|
||||
.activeBorder = color,
|
||||
.seekFg = st::groupCallMembersFg->c,
|
||||
.seekBorder = color,
|
||||
.inactiveBorder = Qt::transparent,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const auto discreter = StarSelectDiscreterForMax(max);
|
||||
slider->setAlwaysDisplayMarker(true);
|
||||
slider->setDirection(ContinuousSlider::Direction::Horizontal);
|
||||
slider->setValue(discreter.valueToRatio(current));
|
||||
const auto ratioToValue = [=](float64 ratio) {
|
||||
const auto value = discreter.ratioToValue(ratio);
|
||||
return (value <= explicitlyAllowed && explicitlyAllowed < min)
|
||||
? explicitlyAllowed
|
||||
: std::max(value, min);
|
||||
};
|
||||
slider->setAdjustCallback([=](float64 ratio) {
|
||||
return discreter.valueToRatio(ratioToValue(ratio));
|
||||
});
|
||||
const auto callback = [=](float64 ratio) {
|
||||
const auto value = ratioToValue(ratio);
|
||||
update(value);
|
||||
changed(value);
|
||||
};
|
||||
slider->setChangeProgressCallback(callback);
|
||||
slider->setChangeFinishedCallback(callback);
|
||||
update(current);
|
||||
|
||||
struct State {
|
||||
StarParticles particles = StarParticles(
|
||||
StarParticles::Type::Right,
|
||||
200,
|
||||
st::lineWidth * 7);
|
||||
Ui::Animations::Basic animation;
|
||||
};
|
||||
const auto state = slider->lifetime().make_state<State>();
|
||||
|
||||
const auto stars = Ui::CreateChild<Ui::RpWidget>(slider->parentWidget());
|
||||
stars->show();
|
||||
stars->raise();
|
||||
slider->geometryValue() | rpl::start_with_next([=](QRect rect) {
|
||||
stars->setGeometry(rect);
|
||||
}, stars->lifetime());
|
||||
|
||||
state->animation.init([=] { stars->update(); });
|
||||
stars->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
const auto seekSize = st::paidReactSlider.seekSize.width();
|
||||
const auto seekRadius = seekSize / 2.;
|
||||
stars->paintRequest() | rpl::start_with_next([=] {
|
||||
if (!state->animation.animating()) {
|
||||
state->animation.start();
|
||||
}
|
||||
auto p = QPainter(stars);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto progress = slider->value();
|
||||
const auto rect = stars->rect();
|
||||
const auto availableWidth = rect.width() - seekSize;
|
||||
const auto seekCenter = seekRadius + availableWidth * progress;
|
||||
|
||||
state->particles.setSpeed(.1 + progress * .3);
|
||||
state->particles.setVisible(.25 + .65 * progress);
|
||||
|
||||
auto fullPath = QPainterPath();
|
||||
fullPath.addRoundedRect(QRectF(rect), seekRadius, seekRadius);
|
||||
auto circlePath = QPainterPath();
|
||||
circlePath.addEllipse(
|
||||
QPointF(seekCenter, rect.height() / 2.),
|
||||
seekRadius,
|
||||
seekRadius);
|
||||
auto rightRect = QPainterPath();
|
||||
rightRect.addRect(
|
||||
QRectF(seekCenter, 0, rect.width() - seekCenter, rect.height()));
|
||||
|
||||
p.setClipPath(fullPath.subtracted(circlePath));
|
||||
state->particles.setColor(Qt::white);
|
||||
state->particles.paint(p, rect, crl::now());
|
||||
p.setClipping(false);
|
||||
|
||||
p.setClipPath(fullPath.intersected(circlePath.united(rightRect)));
|
||||
state->particles.setColor(activeFgOverride
|
||||
? st::groupCallMemberInactiveIcon->c
|
||||
: st::creditsBg3->c);
|
||||
state->particles.paint(p, rect, crl::now());
|
||||
}, stars->lifetime());
|
||||
}
|
||||
|
||||
void AddStarSelectBalance(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<CreditsAmount> balanceValue,
|
||||
bool dark) {
|
||||
const auto balance = Settings::AddBalanceWidget(
|
||||
box->verticalLayout(),
|
||||
session,
|
||||
std::move(balanceValue),
|
||||
false,
|
||||
nullptr,
|
||||
dark);
|
||||
rpl::combine(
|
||||
balance->sizeValue(),
|
||||
box->widthValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
balance->moveToLeft(
|
||||
st::creditsHistoryRightSkip * 2,
|
||||
st::creditsHistoryRightSkip);
|
||||
balance->update();
|
||||
}, balance->lifetime());
|
||||
}
|
||||
|
||||
void AddStarSelectBubble(
|
||||
not_null<GenericBox*> box,
|
||||
rpl::producer<int> value,
|
||||
int max,
|
||||
Fn<QColor(int)> activeFgOverride) {
|
||||
const auto valueToRatio = StarSelectDiscreterForMax(max).valueToRatio;
|
||||
auto bubbleRowState = rpl::duplicate(value) | rpl::map([=](int value) {
|
||||
const auto full = st::boxWideWidth
|
||||
- st::boxRowPadding.left()
|
||||
- st::boxRowPadding.right();
|
||||
const auto marker = st::paidReactSlider.seekSize.width();
|
||||
const auto start = marker / 2;
|
||||
const auto inner = full - marker;
|
||||
const auto correct = start + inner * valueToRatio(value);
|
||||
return Premium::BubbleRowState{
|
||||
.counter = value,
|
||||
.ratio = correct / full,
|
||||
};
|
||||
});
|
||||
|
||||
const auto bubble = Premium::AddBubbleRow(
|
||||
box->verticalLayout(),
|
||||
st::boostBubble,
|
||||
BoxShowFinishes(box),
|
||||
std::move(bubbleRowState),
|
||||
Premium::BubbleType::Credits,
|
||||
nullptr,
|
||||
&st::paidReactBubbleIcon,
|
||||
st::boxRowPadding);
|
||||
if (activeFgOverride) {
|
||||
std::move(value) | rpl::start_with_next([=](int count) {
|
||||
bubble->setBrushOverride(activeFgOverride(count));
|
||||
}, bubble->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
object_ptr<RpWidget> MakeStarSelectInfoBlocks(
|
||||
not_null<RpWidget*> parent,
|
||||
std::vector<StarSelectInfoBlock> blocks,
|
||||
Text::MarkedContext context,
|
||||
bool dark) {
|
||||
Expects(!blocks.empty());
|
||||
|
||||
auto result = object_ptr<RpWidget>(parent.get());
|
||||
const auto raw = result.data();
|
||||
|
||||
struct State {
|
||||
std::vector<not_null<RpWidget*>> blocks;
|
||||
};
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
|
||||
for (auto &info : blocks) {
|
||||
state->blocks.push_back(MakeStarSelectInfoBlock(
|
||||
raw,
|
||||
std::move(info.title),
|
||||
std::move(info.subtext),
|
||||
context,
|
||||
dark));
|
||||
}
|
||||
raw->resize(raw->width(), state->blocks.front()->height());
|
||||
raw->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto count = int(state->blocks.size());
|
||||
const auto skip = (st::boxRowPadding.left() / 2);
|
||||
const auto single = (width - skip * (count - 1)) / float64(count);
|
||||
if (single < 1.) {
|
||||
return;
|
||||
}
|
||||
auto x = 0.;
|
||||
const auto w = int(base::SafeRound(single));
|
||||
for (const auto &block : state->blocks) {
|
||||
block->resizeToWidth(w);
|
||||
block->moveToLeft(int(base::SafeRound(x)), 0);
|
||||
x += single + skip;
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace style {
|
||||
struct RoundCheckbox;
|
||||
struct MediaSlider;
|
||||
} // namespace style
|
||||
|
||||
namespace Main {
|
||||
@@ -23,6 +24,7 @@ namespace Ui {
|
||||
class BoxContent;
|
||||
class GenericBox;
|
||||
class DynamicImage;
|
||||
class VerticalLayout;
|
||||
|
||||
struct PaidReactionTop {
|
||||
QString name;
|
||||
@@ -49,7 +51,6 @@ struct PaidReactionBoxArgs {
|
||||
Fn<void(int, uint64)> send;
|
||||
bool videoStreamChoosing = false;
|
||||
bool videoStreamSending = false;
|
||||
bool giftAuction = false;
|
||||
bool dark = false;
|
||||
};
|
||||
|
||||
@@ -69,4 +70,43 @@ void PaidReactionsBox(
|
||||
QColor fg,
|
||||
const style::RoundCheckbox *borderSt = nullptr);
|
||||
|
||||
struct StarSelectDiscreter {
|
||||
Fn<int(float64)> ratioToValue;
|
||||
Fn<float64(int)> valueToRatio;
|
||||
};
|
||||
|
||||
[[nodiscard]] StarSelectDiscreter StarSelectDiscreterForMax(int max);
|
||||
|
||||
void PaidReactionSlider(
|
||||
not_null<VerticalLayout*> container,
|
||||
const style::MediaSlider &st,
|
||||
int min,
|
||||
int explicitlyAllowed,
|
||||
int current,
|
||||
int max,
|
||||
Fn<void(int)> changed,
|
||||
Fn<QColor(int)> activeFgOverride = nullptr);
|
||||
|
||||
void AddStarSelectBalance(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<CreditsAmount> balanceValue,
|
||||
bool dark = false);
|
||||
|
||||
void AddStarSelectBubble(
|
||||
not_null<GenericBox*> box,
|
||||
rpl::producer<int> value,
|
||||
int max,
|
||||
Fn<QColor(int)> activeFgOverride = nullptr);
|
||||
|
||||
struct StarSelectInfoBlock {
|
||||
rpl::producer<TextWithEntities> title;
|
||||
rpl::producer<QString> subtext;
|
||||
};
|
||||
[[nodiscard]] object_ptr<RpWidget> MakeStarSelectInfoBlocks(
|
||||
not_null<RpWidget*> parent,
|
||||
std::vector<StarSelectInfoBlock> blocks,
|
||||
Text::MarkedContext context,
|
||||
bool dark = false);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -446,3 +446,20 @@ auctionInfoSubtitleSkip: 8px;
|
||||
auctionInfoTableMargin: margins(0px, 12px, 0px, 12px);
|
||||
auctionAboutLogo: icon {{ "calls/group_call_logo", windowFgActive }};
|
||||
auctionAboutLogoPadding: margins(8px, 8px, 8px, 8px);
|
||||
|
||||
auctionBidPlace: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
textFg: windowActiveTextFg;
|
||||
}
|
||||
auctionBidUserpic: UserpicButton(defaultUserpicButton) {
|
||||
size: size(28px, 36px);
|
||||
photoSize: 28px;
|
||||
photoPosition: point(0px, 4px);
|
||||
}
|
||||
auctionBidName: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
auctionBidStars: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
auctionBidSkip: 10px;
|
||||
|
||||
@@ -560,20 +560,26 @@ starrefPopupMenu: PopupMenu(defaultPopupMenu) {
|
||||
}
|
||||
}
|
||||
|
||||
videoStreamInfoPadding: margins(12px, 8px, 12px, 8px);
|
||||
videoStreamInfoTitle: FlatLabel(defaultFlatLabel) {
|
||||
starSelectInfoPadding: margins(12px, 8px, 12px, 8px);
|
||||
starSelectInfoTitle: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 40px;
|
||||
textFg: groupCallMembersFg;
|
||||
textFg: windowBoldFg;
|
||||
align: align(top);
|
||||
style: TextStyle(boxTextStyle) {
|
||||
font: font(18px semibold);
|
||||
}
|
||||
}
|
||||
videoStreamInfoSubtext: FlatLabel(defaultFlatLabel) {
|
||||
starSelectInfoSubtext: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 40px;
|
||||
textFg: groupCallMembersFg;
|
||||
textFg: windowFg;
|
||||
align: align(top);
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(12px);
|
||||
font: font(10px);
|
||||
}
|
||||
}
|
||||
videoStreamInfoTitle: FlatLabel(starSelectInfoTitle) {
|
||||
textFg: groupCallMembersFg;
|
||||
}
|
||||
videoStreamInfoSubtext: FlatLabel(starSelectInfoSubtext) {
|
||||
textFg: groupCallMembersFg;
|
||||
}
|
||||
|
||||
+1
-1
Submodule Telegram/lib_rpl updated: 9a3ce435f4...f4eb411668
Reference in New Issue
Block a user