Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
476e66d027 | ||
|
|
fc11d81673 | ||
|
|
629754a353 | ||
|
|
147dbee051 |
BIN
Telegram/Resources/icons/settings/button_auction.png
Normal file
BIN
Telegram/Resources/icons/settings/button_auction.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 522 B |
BIN
Telegram/Resources/icons/settings/button_auction@2x.png
Normal file
BIN
Telegram/Resources/icons/settings/button_auction@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 935 B |
BIN
Telegram/Resources/icons/settings/button_auction@3x.png
Normal file
BIN
Telegram/Resources/icons/settings/button_auction@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/settings/toast_auction.png
Normal file
BIN
Telegram/Resources/icons/settings/toast_auction.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 605 B |
BIN
Telegram/Resources/icons/settings/toast_auction@2x.png
Normal file
BIN
Telegram/Resources/icons/settings/toast_auction@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/settings/toast_auction@3x.png
Normal file
BIN
Telegram/Resources/icons/settings/toast_auction@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="6.3.2.0" />
|
||||
Version="6.3.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,3,2,0
|
||||
PRODUCTVERSION 6,3,2,0
|
||||
FILEVERSION 6,3,3,0
|
||||
PRODUCTVERSION 6,3,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "6.3.2.0"
|
||||
VALUE "FileVersion", "6.3.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.3.2.0"
|
||||
VALUE "ProductVersion", "6.3.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,3,2,0
|
||||
PRODUCTVERSION 6,3,2,0
|
||||
FILEVERSION 6,3,3,0
|
||||
PRODUCTVERSION 6,3,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "6.3.2.0"
|
||||
VALUE "FileVersion", "6.3.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.3.2.0"
|
||||
VALUE "ProductVersion", "6.3.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -8,16 +8,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/star_gift_auction_box.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/send_credits_box.h" // CreditsEmojiSmall
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "core/application.h"
|
||||
#include "core/credits_amount.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/components/gift_auctions.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -180,6 +184,17 @@ struct BidSliderValues {
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] QString NiceCountdownText(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'));
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<RpWidget> MakeBidRow(
|
||||
not_null<RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
@@ -380,17 +395,11 @@ object_ptr<RpWidget> MakeAuctionInfoBlocks(
|
||||
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();
|
||||
return SecondsLeftTillValue(state.nextRoundAt
|
||||
? state.nextRoundAt
|
||||
: state.endDate);
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::map(NiceCountdownText) | rpl::map(tr::marked);
|
||||
auto leftTitle = rpl::duplicate(
|
||||
stateValue
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state) {
|
||||
@@ -653,7 +662,10 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
box->closeBox();
|
||||
|
||||
if (const auto window = show->resolveWindow()) {
|
||||
window->showPeer(update.to, ShowAtTheEndMsgId);
|
||||
window->showPeerHistory(
|
||||
update.to,
|
||||
Window::SectionShow::Way::ClearStack,
|
||||
ShowAtTheEndMsgId);
|
||||
}
|
||||
}
|
||||
}, box->lifetime());
|
||||
@@ -801,6 +813,8 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
lt_count,
|
||||
perRound,
|
||||
tr::rich),
|
||||
.st = &st::auctionBidToast,
|
||||
.attach = RectPart::Top,
|
||||
.duration = kBidPlacedToastDuration,
|
||||
});
|
||||
}
|
||||
@@ -1561,4 +1575,281 @@ void AuctionAboutBox(
|
||||
).append(' ').append(tr::lng_auction_about_understood(tr::now))));
|
||||
}
|
||||
|
||||
TextWithEntities ActiveAuctionsTitle(const Data::ActiveAuctions &auctions) {
|
||||
const auto &list = auctions.list;
|
||||
if (list.size() == 1) {
|
||||
const auto auction = list.front();
|
||||
return Data::SingleCustomEmoji(
|
||||
auction->gift->document
|
||||
).append(' ').append(tr::lng_auction_bar_active(tr::now));
|
||||
}
|
||||
auto result = tr::marked();
|
||||
for (const auto auction : list | ranges::views::take(3)) {
|
||||
result.append(Data::SingleCustomEmoji(auction->gift->document));
|
||||
}
|
||||
return result.append(' ').append(
|
||||
tr::lng_auction_bar_active_many(tr::now, lt_count, list.size()));
|
||||
}
|
||||
|
||||
ManyAuctionsState ActiveAuctionsState(const Data::ActiveAuctions &auctions) {
|
||||
const auto &list = auctions.list;
|
||||
const auto winning = [](not_null<Data::GiftAuctionState*> auction) {
|
||||
const auto position = MyAuctionPosition(*auction);
|
||||
return (position <= auction->gift->auctionGiftsPerRound)
|
||||
? position
|
||||
: 0;
|
||||
};
|
||||
if (list.size() == 1) {
|
||||
const auto auction = list.front();
|
||||
const auto position = winning(auction);
|
||||
auto text = position
|
||||
? tr::lng_auction_bar_winning(
|
||||
tr::now,
|
||||
lt_count,
|
||||
position,
|
||||
tr::marked)
|
||||
: tr::lng_auction_bar_outbid(tr::now, tr::marked);
|
||||
return { std::move(text), !position };
|
||||
}
|
||||
auto outbid = 0;
|
||||
for (const auto auction : list) {
|
||||
if (!winning(auction)) {
|
||||
++outbid;
|
||||
}
|
||||
}
|
||||
auto text = (outbid == list.size())
|
||||
? tr::lng_auction_bar_outbid_all(tr::now, tr::marked)
|
||||
: outbid
|
||||
? tr::lng_auction_bar_outbid_some(
|
||||
tr::now,
|
||||
lt_count,
|
||||
outbid,
|
||||
tr::marked)
|
||||
: tr::lng_auction_bar_winning_all(tr::now, tr::marked);
|
||||
return { std::move(text), outbid != 0 };
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> ActiveAuctionsButton(
|
||||
const Data::ActiveAuctions &auctions) {
|
||||
const auto &list = auctions.list;
|
||||
const auto withIcon = [](const QString &text) {
|
||||
using namespace Ui::Text;
|
||||
return IconEmoji(&st::auctionBidEmoji).append(' ').append(text);
|
||||
};
|
||||
if (list.size() == 1) {
|
||||
const auto auction = auctions.list.front();
|
||||
const auto end = auction->nextRoundAt
|
||||
? auction->nextRoundAt
|
||||
: auction->endDate;
|
||||
return SecondsLeftTillValue(end)
|
||||
| rpl::map(NiceCountdownText)
|
||||
| rpl::map(withIcon);
|
||||
}
|
||||
return tr::lng_auction_bar_view() | rpl::map(withIcon);
|
||||
}
|
||||
|
||||
struct Single {
|
||||
QString slug;
|
||||
not_null<DocumentData*> document;
|
||||
int round = 0;
|
||||
int total = 0;
|
||||
int bid = 0;
|
||||
int position = 0;
|
||||
int winning = 0;
|
||||
TimeId ends = 0;
|
||||
};
|
||||
|
||||
object_ptr<Ui::RpWidget> MakeActiveAuctionRow(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<DocumentData*> document,
|
||||
const QString &slug,
|
||||
rpl::producer<Single> value) {
|
||||
auto result = object_ptr<Ui::VerticalLayout>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
raw->add(object_ptr<Ui::RpWidget>(raw));
|
||||
|
||||
auto title = rpl::duplicate(value) | rpl::map([=](const Single &fields) {
|
||||
return tr::lng_auction_bar_round(
|
||||
tr::now,
|
||||
lt_n,
|
||||
QString::number(fields.round + 1),
|
||||
lt_amount,
|
||||
QString::number(fields.total));
|
||||
});
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(title),
|
||||
st::auctionListTitle),
|
||||
st::auctionListTitlePadding);
|
||||
|
||||
const auto tag = Data::CustomEmojiSizeTag::Isolated;
|
||||
const auto sticker = std::shared_ptr<Ui::Text::CustomEmoji>(
|
||||
document->owner().customEmojiManager().create(
|
||||
document,
|
||||
[=] { raw->update(); },
|
||||
tag));
|
||||
|
||||
raw->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto q = QPainter(raw);
|
||||
sticker->paint(q, {
|
||||
.textColor = st::windowFg->c,
|
||||
.now = crl::now(),
|
||||
.position = QPoint(),
|
||||
});
|
||||
}, raw->lifetime());
|
||||
|
||||
auto helper = Ui::Text::CustomEmojiHelper();
|
||||
const auto star = helper.paletteDependent(Ui::Earn::IconCreditsEmoji());
|
||||
auto text = rpl::duplicate(value) | rpl::map([=](const Single &fields) {
|
||||
const auto stars = tr::marked(star).append(' ').append(
|
||||
Lang::FormatCountDecimal(fields.bid));
|
||||
const auto outbid = (fields.position > fields.winning);
|
||||
return outbid
|
||||
? tr::lng_auction_bar_bid_outbid(
|
||||
tr::now,
|
||||
lt_stars,
|
||||
stars,
|
||||
tr::rich)
|
||||
: tr::lng_auction_bar_bid_ranked(
|
||||
tr::now,
|
||||
lt_stars,
|
||||
stars,
|
||||
lt_n,
|
||||
tr::marked(QString::number(fields.position)),
|
||||
tr::rich);
|
||||
});
|
||||
const auto subtitle = raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(text),
|
||||
st::auctionListText,
|
||||
st::defaultPopupMenu,
|
||||
helper.context()),
|
||||
st::auctionListTextPadding);
|
||||
rpl::duplicate(value) | rpl::start_with_next([=](const Single &fields) {
|
||||
const auto outbid = (fields.position > fields.winning);
|
||||
subtitle->setTextColorOverride(outbid
|
||||
? st::attentionButtonFg->c
|
||||
: std::optional<QColor>());
|
||||
}, subtitle->lifetime());
|
||||
|
||||
const auto button = raw->add(
|
||||
object_ptr<Ui::RoundButton>(
|
||||
raw,
|
||||
rpl::single(QString()),
|
||||
st::auctionListRaise),
|
||||
st::auctionListRaisePadding);
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
|
||||
auto secondsLeft = rpl::duplicate(
|
||||
value
|
||||
) | rpl::map([=](const Single &fields) {
|
||||
return SecondsLeftTillValue(fields.ends);
|
||||
}) | rpl::flatten_latest();
|
||||
button->setText(rpl::combine(
|
||||
std::move(secondsLeft),
|
||||
tr::lng_auction_bar_raise_bid()
|
||||
) | rpl::map([=](int seconds, const QString &text) {
|
||||
return Ui::Text::IconEmoji(
|
||||
&st::auctionBidEmoji
|
||||
).append(' ').append(text).append(' ').append(
|
||||
Ui::Text::Colorized(NiceCountdownText(seconds)));
|
||||
}));
|
||||
button->setClickedCallback([=] {
|
||||
window->showStarGiftAuction(slug);
|
||||
});
|
||||
button->setFullRadius(true);
|
||||
raw->widthValue() | rpl::start_with_next([=](int width) {
|
||||
button->setFullWidth(width);
|
||||
}, button->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Fn<void()> ActiveAuctionsCallback(
|
||||
not_null<Window::SessionController*> window,
|
||||
const Data::ActiveAuctions &auctions) {
|
||||
const auto &list = auctions.list;
|
||||
const auto count = int(list.size());
|
||||
if (count == 1) {
|
||||
const auto slug = list.front()->gift->auctionSlug;
|
||||
return [=] {
|
||||
window->showStarGiftAuction(slug);
|
||||
};
|
||||
}
|
||||
struct Auctions {
|
||||
std::vector<rpl::variable<Single>> list;
|
||||
};
|
||||
const auto state = std::make_shared<Auctions>();
|
||||
const auto singleFrom = [](const Data::GiftAuctionState &state) {
|
||||
return Single{
|
||||
.slug = state.gift->auctionSlug,
|
||||
.document = state.gift->document,
|
||||
.round = state.currentRound,
|
||||
.total = state.totalRounds,
|
||||
.bid = int(state.my.bid),
|
||||
.position = MyAuctionPosition(state),
|
||||
.winning = state.gift->auctionGiftsPerRound,
|
||||
.ends = state.nextRoundAt ? state.nextRoundAt : state.endDate,
|
||||
};
|
||||
};
|
||||
for (const auto auction : list) {
|
||||
state->list.push_back(singleFrom(*auction));
|
||||
}
|
||||
return [=] {
|
||||
window->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
const auto rows = box->lifetime().make_state<
|
||||
rpl::variable<int>
|
||||
>(count);
|
||||
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setTitle(tr::lng_auction_bar_active_many(
|
||||
lt_count,
|
||||
rows->value() | tr::to_count()));
|
||||
|
||||
const auto auctions = &window->session().giftAuctions();
|
||||
for (auto &entry : state->list) {
|
||||
using Data::GiftAuctionState;
|
||||
|
||||
const auto &now = entry.current();
|
||||
entry = auctions->state(
|
||||
now.slug
|
||||
) | rpl::filter([=](const GiftAuctionState &state) {
|
||||
return state.my.bid != 0;
|
||||
}) | rpl::map(singleFrom);
|
||||
|
||||
const auto skip = st::auctionListEntrySkip;
|
||||
const auto row = box->addRow(
|
||||
MakeActiveAuctionRow(
|
||||
box,
|
||||
window,
|
||||
now.document,
|
||||
now.slug,
|
||||
entry.value()),
|
||||
st::boxRowPadding + QMargins(0, skip, 0, skip));
|
||||
|
||||
auctions->state(
|
||||
now.slug
|
||||
) | rpl::start_with_next([=](const GiftAuctionState &state) {
|
||||
if (!state.my.bid) {
|
||||
delete row;
|
||||
if (const auto now = rows->current(); now > 1) {
|
||||
*rows = (now - 1);
|
||||
} else {
|
||||
box->closeBox();
|
||||
}
|
||||
}
|
||||
}, row->lifetime());
|
||||
}
|
||||
|
||||
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
|
||||
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -13,6 +13,7 @@ class Show;
|
||||
|
||||
namespace Data {
|
||||
struct GiftAuctionState;
|
||||
struct ActiveAuctions;
|
||||
} // namespace Data
|
||||
|
||||
namespace Info::PeerGifts {
|
||||
@@ -60,4 +61,18 @@ void AuctionAboutBox(
|
||||
int giftsPerRound,
|
||||
Fn<void(Fn<void()> close)> understood);
|
||||
|
||||
[[nodiscard]] TextWithEntities ActiveAuctionsTitle(
|
||||
const Data::ActiveAuctions &auctions);
|
||||
struct ManyAuctionsState {
|
||||
TextWithEntities text;
|
||||
bool someOutbid = false;
|
||||
};
|
||||
[[nodiscard]] ManyAuctionsState ActiveAuctionsState(
|
||||
const Data::ActiveAuctions &auctions);
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> ActiveAuctionsButton(
|
||||
const Data::ActiveAuctions &auctions);
|
||||
[[nodiscard]] Fn<void()> ActiveAuctionsCallback(
|
||||
not_null<Window::SessionController*> window,
|
||||
const Data::ActiveAuctions &auctions);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 6003002;
|
||||
constexpr auto AppVersionStr = "6.3.2";
|
||||
constexpr auto AppVersion = 6003003;
|
||||
constexpr auto AppVersionStr = "6.3.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/components/gift_auctions.h"
|
||||
|
||||
#include "api/api_hash.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -18,6 +19,16 @@ namespace Data {
|
||||
GiftAuctions::GiftAuctions(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _timer([=] { checkSubscriptions(); }) {
|
||||
crl::on_main(_session, [=] {
|
||||
rpl::merge(
|
||||
_session->data().chatsListChanges(),
|
||||
_session->data().chatsListLoadedEvents()
|
||||
) | rpl::filter(
|
||||
!rpl::mappers::_1
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
requestActive();
|
||||
}, _lifetime);
|
||||
});
|
||||
}
|
||||
|
||||
GiftAuctions::~GiftAuctions() = default;
|
||||
@@ -50,15 +61,27 @@ rpl::producer<GiftAuctionState> GiftAuctions::state(const QString &slug) {
|
||||
|
||||
void GiftAuctions::apply(const MTPDupdateStarGiftAuctionState &data) {
|
||||
if (const auto entry = find(data.vgift_id().v)) {
|
||||
const auto was = myStateKey(entry->state);
|
||||
apply(entry, data.vstate());
|
||||
entry->changes.fire({});
|
||||
if (was != myStateKey(entry->state)) {
|
||||
_activeChanged.fire({});
|
||||
}
|
||||
} else {
|
||||
requestActive();
|
||||
}
|
||||
}
|
||||
|
||||
void GiftAuctions::apply(const MTPDupdateStarGiftAuctionUserState &data) {
|
||||
if (const auto entry = find(data.vgift_id().v)) {
|
||||
const auto was = myStateKey(entry->state);
|
||||
apply(entry, data.vuser_state());
|
||||
entry->changes.fire({});
|
||||
if (was != myStateKey(entry->state)) {
|
||||
_activeChanged.fire({});
|
||||
}
|
||||
} else {
|
||||
requestActive();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +129,37 @@ void GiftAuctions::requestAcquired(
|
||||
}).send();
|
||||
}
|
||||
|
||||
rpl::producer<ActiveAuctions> GiftAuctions::active() const {
|
||||
return _activeChanged.events_starting_with_copy(
|
||||
rpl::empty
|
||||
) | rpl::map([=] {
|
||||
return collectActive();
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<bool> GiftAuctions::hasActiveChanges() const {
|
||||
const auto has = hasActive();
|
||||
return _activeChanged.events(
|
||||
) | rpl::map([=] {
|
||||
return hasActive();
|
||||
}) | rpl::combine_previous(
|
||||
has
|
||||
) | rpl::filter([=](bool previous, bool current) {
|
||||
return previous != current;
|
||||
}) | rpl::map([=](bool previous, bool current) {
|
||||
return current;
|
||||
});
|
||||
}
|
||||
|
||||
bool GiftAuctions::hasActive() const {
|
||||
for (const auto &[slug, entry] : _map) {
|
||||
if (myStateKey(entry->state)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GiftAuctions::checkSubscriptions() {
|
||||
const auto now = crl::now();
|
||||
auto next = crl::time();
|
||||
@@ -128,6 +182,101 @@ void GiftAuctions::checkSubscriptions() {
|
||||
}
|
||||
}
|
||||
|
||||
auto GiftAuctions::myStateKey(const GiftAuctionState &state) const
|
||||
-> MyStateKey {
|
||||
if (!state.my.bid) {
|
||||
return {};
|
||||
}
|
||||
auto min = 0;
|
||||
for (const auto &level : state.bidLevels) {
|
||||
if (level.position > state.gift->auctionGiftsPerRound) {
|
||||
break;
|
||||
} else if (!min || min > level.amount) {
|
||||
min = level.amount;
|
||||
}
|
||||
}
|
||||
return {
|
||||
.bid = int(state.my.bid),
|
||||
.position = MyAuctionPosition(state),
|
||||
.version = state.version,
|
||||
};
|
||||
}
|
||||
|
||||
ActiveAuctions GiftAuctions::collectActive() const {
|
||||
auto result = ActiveAuctions();
|
||||
result.list.reserve(_map.size());
|
||||
for (const auto &[slug, entry] : _map) {
|
||||
const auto raw = &entry->state;
|
||||
if (raw->gift && raw->my.date) {
|
||||
result.list.push_back(raw);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64 GiftAuctions::countActiveHash() const {
|
||||
auto result = Api::HashInit();
|
||||
for (const auto &active : collectActive().list) {
|
||||
Api::HashUpdate(result, active->version);
|
||||
Api::HashUpdate(result, active->my.date);
|
||||
}
|
||||
return Api::HashFinalize(result);
|
||||
}
|
||||
|
||||
void GiftAuctions::requestActive() {
|
||||
if (_activeRequestId) {
|
||||
return;
|
||||
}
|
||||
_activeRequestId = _session->api().request(
|
||||
MTPpayments_GetStarGiftActiveAuctions(MTP_long(countActiveHash()))
|
||||
).done([=](const MTPpayments_StarGiftActiveAuctions &result) {
|
||||
result.match([=](const MTPDpayments_starGiftActiveAuctions &data) {
|
||||
const auto owner = &_session->data();
|
||||
owner->processUsers(data.vusers());
|
||||
|
||||
auto giftsFound = base::flat_set<QString>();
|
||||
const auto &list = data.vauctions().v;
|
||||
giftsFound.reserve(list.size());
|
||||
for (const auto &auction : list) {
|
||||
const auto &data = auction.data();
|
||||
auto gift = Api::FromTL(_session, data.vgift());
|
||||
const auto slug = gift ? gift->auctionSlug : QString();
|
||||
if (slug.isEmpty()) {
|
||||
LOG(("Api Error: Bad auction gift."));
|
||||
continue;
|
||||
}
|
||||
auto &entry = _map[slug];
|
||||
if (!entry) {
|
||||
entry = std::make_unique<Entry>();
|
||||
}
|
||||
const auto raw = entry.get();
|
||||
if (!raw->state.gift) {
|
||||
raw->state.gift = std::move(gift);
|
||||
}
|
||||
apply(raw, data.vstate());
|
||||
apply(raw, data.vuser_state());
|
||||
giftsFound.emplace(slug);
|
||||
}
|
||||
for (const auto &[slug, entry] : _map) {
|
||||
const auto my = &entry->state.my;
|
||||
if (my->date && !giftsFound.contains(slug)) {
|
||||
my->to = nullptr;
|
||||
my->minBidAmount = 0;
|
||||
my->bid = 0;
|
||||
my->date = 0;
|
||||
my->returned = false;
|
||||
giftsFound.emplace(slug);
|
||||
}
|
||||
}
|
||||
for (const auto &slug : giftsFound) {
|
||||
_map[slug]->changes.fire({});
|
||||
}
|
||||
_activeChanged.fire({});
|
||||
}, [](const MTPDpayments_starGiftActiveAuctionsNotModified &) {
|
||||
});
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GiftAuctions::request(const QString &slug) {
|
||||
auto &entry = _map[slug];
|
||||
Assert(entry != nullptr);
|
||||
@@ -144,6 +293,8 @@ void GiftAuctions::request(const QString &slug) {
|
||||
raw->requested = false;
|
||||
const auto &data = result.data();
|
||||
|
||||
_session->data().processUsers(data.vusers());
|
||||
|
||||
raw->state.gift = Api::FromTL(_session, data.vgift());
|
||||
if (!raw->state.gift) {
|
||||
return;
|
||||
@@ -152,8 +303,7 @@ void GiftAuctions::request(const QString &slug) {
|
||||
const auto ms = timeout * crl::time(1000);
|
||||
raw->state.subscribedTill = ms ? (crl::now() + ms) : -1;
|
||||
|
||||
_session->data().processUsers(data.vusers());
|
||||
|
||||
const auto was = myStateKey(raw->state);
|
||||
apply(raw, data.vstate());
|
||||
apply(raw, data.vuser_state());
|
||||
if (raw->changes.has_consumers()) {
|
||||
@@ -162,6 +312,9 @@ void GiftAuctions::request(const QString &slug) {
|
||||
_timer.callOnce(ms);
|
||||
}
|
||||
}
|
||||
if (was != myStateKey(raw->state)) {
|
||||
_activeChanged.fire({});
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -177,49 +330,54 @@ GiftAuctions::Entry *GiftAuctions::find(uint64 giftId) const {
|
||||
void GiftAuctions::apply(
|
||||
not_null<Entry*> entry,
|
||||
const MTPStarGiftAuctionState &state) {
|
||||
Expects(entry->state.gift.has_value());
|
||||
apply(&entry->state, state);
|
||||
}
|
||||
|
||||
void GiftAuctions::apply(
|
||||
not_null<GiftAuctionState*> entry,
|
||||
const MTPStarGiftAuctionState &state) {
|
||||
Expects(entry->gift.has_value());
|
||||
|
||||
const auto raw = &entry->state;
|
||||
state.match([&](const MTPDstarGiftAuctionState &data) {
|
||||
const auto version = data.vversion().v;
|
||||
if (raw->version >= version) {
|
||||
if (entry->version >= version) {
|
||||
return;
|
||||
}
|
||||
const auto owner = &_session->data();
|
||||
raw->startDate = data.vstart_date().v;
|
||||
raw->endDate = data.vend_date().v;
|
||||
raw->minBidAmount = data.vmin_bid_amount().v;
|
||||
entry->startDate = data.vstart_date().v;
|
||||
entry->endDate = data.vend_date().v;
|
||||
entry->minBidAmount = data.vmin_bid_amount().v;
|
||||
const auto &levels = data.vbid_levels().v;
|
||||
raw->bidLevels.clear();
|
||||
raw->bidLevels.reserve(levels.size());
|
||||
entry->bidLevels.clear();
|
||||
entry->bidLevels.reserve(levels.size());
|
||||
for (const auto &level : levels) {
|
||||
auto &entry = raw->bidLevels.emplace_back();
|
||||
auto &bid = entry->bidLevels.emplace_back();
|
||||
const auto &data = level.data();
|
||||
entry.amount = data.vamount().v;
|
||||
entry.position = data.vpos().v;
|
||||
entry.date = data.vdate().v;
|
||||
bid.amount = data.vamount().v;
|
||||
bid.position = data.vpos().v;
|
||||
bid.date = data.vdate().v;
|
||||
}
|
||||
const auto &top = data.vtop_bidders().v;
|
||||
raw->topBidders.clear();
|
||||
raw->topBidders.reserve(top.size());
|
||||
entry->topBidders.clear();
|
||||
entry->topBidders.reserve(top.size());
|
||||
for (const auto &user : top) {
|
||||
raw->topBidders.push_back(owner->user(UserId(user.v)));
|
||||
entry->topBidders.push_back(owner->user(UserId(user.v)));
|
||||
}
|
||||
raw->nextRoundAt = data.vnext_round_at().v;
|
||||
raw->giftsLeft = data.vgifts_left().v;
|
||||
raw->currentRound = data.vcurrent_round().v;
|
||||
raw->totalRounds = data.vtotal_rounds().v;
|
||||
raw->averagePrice = 0;
|
||||
entry->nextRoundAt = data.vnext_round_at().v;
|
||||
entry->giftsLeft = data.vgifts_left().v;
|
||||
entry->currentRound = data.vcurrent_round().v;
|
||||
entry->totalRounds = data.vtotal_rounds().v;
|
||||
entry->averagePrice = 0;
|
||||
}, [&](const MTPDstarGiftAuctionStateFinished &data) {
|
||||
raw->averagePrice = data.vaverage_price().v;
|
||||
raw->startDate = data.vstart_date().v;
|
||||
raw->endDate = data.vend_date().v;
|
||||
raw->minBidAmount = 0;
|
||||
raw->nextRoundAt
|
||||
= raw->currentRound
|
||||
= raw->totalRounds
|
||||
= raw->giftsLeft
|
||||
= raw->version
|
||||
entry->averagePrice = data.vaverage_price().v;
|
||||
entry->startDate = data.vstart_date().v;
|
||||
entry->endDate = data.vend_date().v;
|
||||
entry->minBidAmount = 0;
|
||||
entry->nextRoundAt
|
||||
= entry->currentRound
|
||||
= entry->totalRounds
|
||||
= entry->giftsLeft
|
||||
= entry->version
|
||||
= 0;
|
||||
}, [&](const MTPDstarGiftAuctionStateNotModified &data) {
|
||||
});
|
||||
@@ -228,16 +386,32 @@ void GiftAuctions::apply(
|
||||
void GiftAuctions::apply(
|
||||
not_null<Entry*> entry,
|
||||
const MTPStarGiftAuctionUserState &state) {
|
||||
apply(&entry->state.my, state);
|
||||
}
|
||||
|
||||
void GiftAuctions::apply(
|
||||
not_null<StarGiftAuctionMyState*> entry,
|
||||
const MTPStarGiftAuctionUserState &state) {
|
||||
const auto &data = state.data();
|
||||
const auto raw = &entry->state.my;
|
||||
raw->to = data.vbid_peer()
|
||||
entry->to = data.vbid_peer()
|
||||
? _session->data().peer(peerFromMTP(*data.vbid_peer())).get()
|
||||
: nullptr;
|
||||
raw->minBidAmount = data.vmin_bid_amount().value_or(0);
|
||||
raw->bid = data.vbid_amount().value_or(0);
|
||||
raw->date = data.vbid_date().value_or(0);
|
||||
raw->gotCount = data.vacquired_count().v;
|
||||
raw->returned = data.is_returned();
|
||||
entry->minBidAmount = data.vmin_bid_amount().value_or(0);
|
||||
entry->bid = data.vbid_amount().value_or(0);
|
||||
entry->date = data.vbid_date().value_or(0);
|
||||
entry->gotCount = data.vacquired_count().v;
|
||||
entry->returned = data.is_returned();
|
||||
}
|
||||
|
||||
int MyAuctionPosition(const GiftAuctionState &state) {
|
||||
const auto &levels = state.bidLevels;
|
||||
for (auto i = begin(levels), e = end(levels); i != e; ++i) {
|
||||
if (i->amount < state.my.bid
|
||||
|| (i->amount == state.my.bid && i->date > state.my.date)) {
|
||||
return i->position;
|
||||
}
|
||||
}
|
||||
return (levels.empty() ? 0 : levels.back().position) + 1;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -62,6 +62,10 @@ struct GiftAcquired {
|
||||
bool nameHidden = false;
|
||||
};
|
||||
|
||||
struct ActiveAuctions {
|
||||
std::vector<not_null<GiftAuctionState*>> list;
|
||||
};
|
||||
|
||||
class GiftAuctions final {
|
||||
public:
|
||||
explicit GiftAuctions(not_null<Main::Session*> session);
|
||||
@@ -73,31 +77,63 @@ public:
|
||||
void apply(const MTPDupdateStarGiftAuctionUserState &data);
|
||||
|
||||
void requestAcquired(
|
||||
uint64 giftId,
|
||||
uint64 giftId,
|
||||
Fn<void(std::vector<Data::GiftAcquired>)> done);
|
||||
|
||||
[[nodiscard]] rpl::producer<ActiveAuctions> active() const;
|
||||
[[nodiscard]] rpl::producer<bool> hasActiveChanges() const;
|
||||
[[nodiscard]] bool hasActive() const;
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
GiftAuctionState state;
|
||||
rpl::event_stream<> changes;
|
||||
bool requested = false;
|
||||
};
|
||||
struct MyStateKey {
|
||||
int bid = 0;
|
||||
int position = 0;
|
||||
int version = 0;
|
||||
|
||||
explicit operator bool() const {
|
||||
return bid != 0;
|
||||
}
|
||||
friend inline bool operator==(MyStateKey, MyStateKey) = default;
|
||||
};
|
||||
|
||||
void request(const QString &slug);
|
||||
Entry *find(uint64 giftId) const;
|
||||
void apply(
|
||||
not_null<Entry*> entry,
|
||||
const MTPStarGiftAuctionState &state);
|
||||
void apply(
|
||||
not_null<GiftAuctionState*> entry,
|
||||
const MTPStarGiftAuctionState &state);
|
||||
void apply(
|
||||
not_null<Entry*> entry,
|
||||
const MTPStarGiftAuctionUserState &state);
|
||||
void apply(
|
||||
not_null<StarGiftAuctionMyState*> entry,
|
||||
const MTPStarGiftAuctionUserState &state);
|
||||
void checkSubscriptions();
|
||||
|
||||
[[nodiscard]] MyStateKey myStateKey(const GiftAuctionState &state) const;
|
||||
[[nodiscard]] ActiveAuctions collectActive() const;
|
||||
[[nodiscard]] uint64 countActiveHash() const;
|
||||
void requestActive();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::Timer _timer;
|
||||
base::flat_map<QString, std::unique_ptr<Entry>> _map;
|
||||
|
||||
rpl::event_stream<> _activeChanged;
|
||||
mtpRequestId _activeRequestId = 0;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] int MyAuctionPosition(const GiftAuctionState &state);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -129,6 +129,11 @@ dialogRowOpenBot: DialogRightButton {
|
||||
dialogRowOpenBotRecent: DialogRightButton(dialogRowOpenBot) {
|
||||
margin: margins(0px, 32px, 16px, 0px);
|
||||
}
|
||||
dialogsTopBarRightButton: RoundButton(defaultActiveButton) {
|
||||
width: -16px;
|
||||
height: 22px;
|
||||
textTop: 2px;
|
||||
}
|
||||
|
||||
forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }};
|
||||
forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }};
|
||||
|
||||
@@ -14,9 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
||||
#include "boxes/star_gift_auction_box.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/components/gift_auctions.h"
|
||||
#include "data/components/promo_suggestions.h"
|
||||
#include "data/data_birthday.h"
|
||||
#include "data/data_changes.h"
|
||||
@@ -184,6 +186,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||
rpl::lifetime userpicLifetime;
|
||||
rpl::lifetime giftsLifetime;
|
||||
rpl::lifetime creditsLifetime;
|
||||
rpl::lifetime auctionsLifetime;
|
||||
std::unique_ptr<Api::CreditsHistory> creditsHistory;
|
||||
};
|
||||
|
||||
@@ -193,8 +196,11 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||
rpl::single(st::dialogsTopBarLeftPadding));
|
||||
const auto ensureContent = [=] {
|
||||
if (!state->content) {
|
||||
const auto window = FindSessionController(parent);
|
||||
state->content = Ui::CreateChild<TopBarSuggestionContent>(
|
||||
parent);
|
||||
parent,
|
||||
[=] { return window->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer); });
|
||||
rpl::combine(
|
||||
parent->widthValue(),
|
||||
state->content->desiredHeightValue()
|
||||
@@ -229,6 +235,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||
state->userpicLifetime.destroy();
|
||||
state->giftsLifetime.destroy();
|
||||
state->creditsLifetime.destroy();
|
||||
state->auctionsLifetime.destroy();
|
||||
|
||||
if (!session->api().authorizations().unreviewed().empty()) {
|
||||
state->content = nullptr;
|
||||
@@ -273,7 +280,50 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||
const auto wrap = state->wrap.get();
|
||||
using RightIcon = TopBarSuggestionContent::RightIcon;
|
||||
const auto promo = &session->promoSuggestions();
|
||||
if (const auto custom = promo->custom()) {
|
||||
const auto auctions = &session->giftAuctions();
|
||||
if (auctions->hasActive()) {
|
||||
using namespace Data;
|
||||
struct Button {
|
||||
rpl::variable<TextWithEntities> text;
|
||||
Fn<void()> callback;
|
||||
base::has_weak_ptr guard;
|
||||
};
|
||||
auto &lifetime = state->auctionsLifetime;
|
||||
const auto button = lifetime.template make_state<Button>();
|
||||
const auto window = FindSessionController(parent);
|
||||
auctions->active(
|
||||
) | rpl::start_with_next([=](ActiveAuctions &&active) {
|
||||
const auto empty = active.list.empty();
|
||||
state->desiredWrapToggle.force_assign(
|
||||
Toggle{ !empty, anim::type::normal });
|
||||
if (empty) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto text = Ui::ActiveAuctionsState(active);
|
||||
const auto textColorOverride = text.someOutbid
|
||||
? st::attentionButtonFg->c
|
||||
: std::optional<QColor>();
|
||||
content->setContent(
|
||||
Ui::ActiveAuctionsTitle(active),
|
||||
std::move(text.text),
|
||||
Core::TextContext({ .session = session }),
|
||||
textColorOverride);
|
||||
button->text = Ui::ActiveAuctionsButton(active);
|
||||
button->callback = Ui::ActiveAuctionsCallback(
|
||||
window,
|
||||
active);
|
||||
}, state->auctionsLifetime);
|
||||
const auto callback = crl::guard(&button->guard, [=] {
|
||||
button->callback();
|
||||
});
|
||||
content->setRightButton(button->text.value(), callback);
|
||||
content->setClickedCallback(callback);
|
||||
content->setLeftPadding(state->leftPadding.value());
|
||||
state->desiredWrapToggle.force_assign(
|
||||
Toggle{ true, anim::type::normal });
|
||||
return;
|
||||
} else if (const auto custom = promo->custom()) {
|
||||
content->setRightIcon(RightIcon::Close);
|
||||
content->setLeftPadding(state->leftPadding.value());
|
||||
content->setClickedCallback([=] {
|
||||
@@ -733,12 +783,14 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||
rpl::merge(
|
||||
session->promoSuggestions().value(),
|
||||
session->api().authorizations().unreviewedChanges(),
|
||||
Data::AmPremiumValue(session) | rpl::skip(1) | rpl::to_empty
|
||||
Data::AmPremiumValue(session) | rpl::skip(1) | rpl::to_empty,
|
||||
session->giftAuctions().hasActiveChanges() | rpl::to_empty
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto was = state->wrap.get();
|
||||
const auto weak = base::make_weak(was);
|
||||
processCurrentSuggestion(processCurrentSuggestion);
|
||||
if (was != state->wrap) {
|
||||
consumer.put_next_copy(state->wrap);
|
||||
if (was != state->wrap || (was && !weak)) {
|
||||
consumer.put_next_copy(state->wrap.get());
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
|
||||
@@ -156,15 +156,19 @@ not_null<Ui::SlideWrap<Ui::VerticalLayout>*> CreateUnconfirmedAuthContent(
|
||||
return wrap;
|
||||
}
|
||||
|
||||
TopBarSuggestionContent::TopBarSuggestionContent(not_null<Ui::RpWidget*> p)
|
||||
: Ui::RippleButton(p, st::defaultRippleAnimationBgOver)
|
||||
TopBarSuggestionContent::TopBarSuggestionContent(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
Fn<bool()> emojiPaused)
|
||||
: Ui::RippleButton(parent, st::defaultRippleAnimationBgOver)
|
||||
, _titleSt(st::semiboldTextStyle)
|
||||
, _contentTitleSt(st::dialogsTopBarSuggestionTitleStyle)
|
||||
, _contentTextSt(st::dialogsTopBarSuggestionAboutStyle) {
|
||||
, _contentTextSt(st::dialogsTopBarSuggestionAboutStyle)
|
||||
, _emojiPaused(std::move(emojiPaused)) {
|
||||
setRightIcon(RightIcon::Close);
|
||||
}
|
||||
|
||||
void TopBarSuggestionContent::setRightIcon(RightIcon icon) {
|
||||
_rightButton = nullptr;
|
||||
if (icon == _rightIcon) {
|
||||
return;
|
||||
}
|
||||
@@ -201,6 +205,35 @@ void TopBarSuggestionContent::setRightIcon(RightIcon icon) {
|
||||
}
|
||||
}
|
||||
|
||||
void TopBarSuggestionContent::setRightButton(
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<void()> callback) {
|
||||
_rightHide = nullptr;
|
||||
_rightArrow = nullptr;
|
||||
_rightIcon = RightIcon::None;
|
||||
if (!text) {
|
||||
_rightButton = nullptr;
|
||||
return;
|
||||
}
|
||||
using namespace Ui;
|
||||
_rightButton = base::make_unique_q<RoundButton>(
|
||||
this,
|
||||
rpl::single(QString()),
|
||||
st::dialogsTopBarRightButton);
|
||||
_rightButton->setText(std::move(text));
|
||||
rpl::combine(
|
||||
sizeValue(),
|
||||
_rightButton->sizeValue()
|
||||
) | rpl::start_with_next([=](QSize outer, QSize inner) {
|
||||
const auto top = (outer.height() - inner.height()) / 2;
|
||||
_rightButton->moveToRight(top, top, outer.width());
|
||||
}, _rightButton->lifetime());
|
||||
_rightButton->setFullRadius(true);
|
||||
_rightButton->setTextTransform(RoundButton::TextTransform::NoTransform);
|
||||
_rightButton->setClickedCallback(std::move(callback));
|
||||
_rightButton->show();
|
||||
}
|
||||
|
||||
void TopBarSuggestionContent::draw(QPainter &p) {
|
||||
const auto kLinesForPhoto = 3;
|
||||
|
||||
@@ -226,6 +259,8 @@ void TopBarSuggestionContent::draw(QPainter &p) {
|
||||
- (_rightHide ? _rightHide->width() : 0);
|
||||
const auto titleRight = leftPadding;
|
||||
const auto hasSecondLineTitle = availableWidth < _contentTitle.maxWidth();
|
||||
const auto paused = On(PowerSaving::kEmojiChat)
|
||||
|| (_emojiPaused && _emojiPaused());
|
||||
p.setPen(st::windowActiveTextFg);
|
||||
p.setPen(st::windowFg);
|
||||
{
|
||||
@@ -237,7 +272,7 @@ void TopBarSuggestionContent::draw(QPainter &p) {
|
||||
? availableWidth
|
||||
: (availableWidth - titleRight),
|
||||
.availableWidth = availableWidth,
|
||||
.pausedEmoji = On(PowerSaving::kEmojiChat),
|
||||
.pausedEmoji = paused,
|
||||
.elisionLines = hasSecondLineTitle ? 2 : 1,
|
||||
});
|
||||
}
|
||||
@@ -270,7 +305,7 @@ void TopBarSuggestionContent::draw(QPainter &p) {
|
||||
: availableWidth,
|
||||
};
|
||||
};
|
||||
p.setPen(st::windowSubTextFg);
|
||||
p.setPen(_descriptionColorOverride.value_or(st::windowSubTextFg->c));
|
||||
_contentText.draw(p, {
|
||||
.position = QPoint(left, top),
|
||||
.outerWidth = availableWidth,
|
||||
@@ -278,7 +313,7 @@ void TopBarSuggestionContent::draw(QPainter &p) {
|
||||
.geometry = Ui::Text::GeometryDescriptor{
|
||||
.layout = std::move(lineLayout),
|
||||
},
|
||||
.pausedEmoji = On(PowerSaving::kEmojiChat),
|
||||
.pausedEmoji = paused,
|
||||
});
|
||||
_lastPaintedContentTop = top;
|
||||
_lastPaintedContentLineAmount = lastContentLineAmount;
|
||||
@@ -288,7 +323,9 @@ void TopBarSuggestionContent::draw(QPainter &p) {
|
||||
void TopBarSuggestionContent::setContent(
|
||||
TextWithEntities title,
|
||||
TextWithEntities description,
|
||||
std::optional<Ui::Text::MarkedContext> context) {
|
||||
std::optional<Ui::Text::MarkedContext> context,
|
||||
std::optional<QColor> descriptionColorOverride) {
|
||||
_descriptionColorOverride = descriptionColorOverride;
|
||||
if (context) {
|
||||
context->repaint = [=] { update(); };
|
||||
_contentTitle.setMarkedText(
|
||||
@@ -305,6 +342,7 @@ void TopBarSuggestionContent::setContent(
|
||||
_contentTitle.setMarkedText(_contentTitleSt, std::move(title));
|
||||
_contentText.setMarkedText(_contentTextSt, std::move(description));
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void TopBarSuggestionContent::paintEvent(QPaintEvent *) {
|
||||
|
||||
@@ -40,17 +40,23 @@ public:
|
||||
Arrow,
|
||||
};
|
||||
|
||||
TopBarSuggestionContent(not_null<Ui::RpWidget*>);
|
||||
TopBarSuggestionContent(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
Fn<bool()> emojiPaused = nullptr);
|
||||
|
||||
void setContent(
|
||||
TextWithEntities title,
|
||||
TextWithEntities description,
|
||||
std::optional<Ui::Text::MarkedContext> context = std::nullopt);
|
||||
std::optional<Ui::Text::MarkedContext> context = std::nullopt,
|
||||
std::optional<QColor> descriptionColorOverride = std::nullopt);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
||||
|
||||
void setHideCallback(Fn<void()>);
|
||||
void setRightIcon(RightIcon);
|
||||
void setRightButton(
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<void()> callback);
|
||||
void setLeftPadding(rpl::producer<int>);
|
||||
|
||||
[[nodiscard]] const style::TextStyle &contentTitleSt() const;
|
||||
@@ -69,10 +75,13 @@ private:
|
||||
Ui::Text::String _contentText;
|
||||
rpl::variable<int> _lastPaintedContentLineAmount = 0;
|
||||
rpl::variable<int> _lastPaintedContentTop = 0;
|
||||
std::optional<QColor> _descriptionColorOverride;
|
||||
|
||||
base::unique_qptr<Ui::IconButton> _rightHide;
|
||||
base::unique_qptr<Ui::IconButton> _rightArrow;
|
||||
base::unique_qptr<Ui::RoundButton> _rightButton;
|
||||
Fn<void()> _hideCallback;
|
||||
Fn<bool()> _emojiPaused;
|
||||
|
||||
int _leftPadding = 0;
|
||||
|
||||
|
||||
@@ -444,6 +444,15 @@ videoStreamStarsCover: PremiumCover(creditsLowBalancePremiumCover) {
|
||||
auctionInfoPreviewMargin: margins(0px, 24px, 0px, 8px);
|
||||
auctionInfoSubtitleSkip: 8px;
|
||||
auctionInfoTableMargin: margins(0px, 12px, 0px, 12px);
|
||||
auctionBidEmoji: IconEmoji {
|
||||
icon: icon {{ "settings/button_auction", windowFg }};
|
||||
padding: margins(-4px, -2px, -4px, 0px);
|
||||
}
|
||||
auctionBidToast: Toast(defaultToast) {
|
||||
padding: margins(54px, 13px, 19px, 12px);
|
||||
icon: icon {{ "settings/toast_auction", toastFg }};
|
||||
iconPosition: point(18px, 18px);
|
||||
}
|
||||
auctionAboutLogo: icon {{ "settings/large_auctions", windowFgActive }};
|
||||
auctionAboutLogoPadding: margins(8px, 8px, 8px, 8px);
|
||||
auctionCenteredSubtitle: FlatLabel(defaultFlatLabel) {
|
||||
@@ -470,3 +479,19 @@ auctionBidStars: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
auctionBidSkip: 10px;
|
||||
|
||||
auctionListEntrySkip: 12px;
|
||||
auctionListTitle: FlatLabel(defaultFlatLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(15px semibold);
|
||||
}
|
||||
textFg: windowBoldFg;
|
||||
}
|
||||
auctionListTitlePadding: margins(50px, 0px, 0px, 0px);
|
||||
auctionListText: FlatLabel(defaultFlatLabel) {
|
||||
}
|
||||
auctionListTextPadding: margins(50px, 4px, 0px, 0px);
|
||||
auctionListRaise: RoundButton(defaultActiveButton) {
|
||||
width: 0px;
|
||||
}
|
||||
auctionListRaisePadding: margins(0px, 8px, 0px, 0px);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 6003002
|
||||
AppVersion 6003003
|
||||
AppVersionStrMajor 6.3
|
||||
AppVersionStrSmall 6.3.2
|
||||
AppVersionStr 6.3.2
|
||||
AppVersionStrSmall 6.3.3
|
||||
AppVersionStr 6.3.3
|
||||
BetaChannel 0
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 6.3.2
|
||||
AppVersionOriginal 6.3.3
|
||||
|
||||
Submodule Telegram/lib_ui updated: 9fbbcfd466...be555efdb2
@@ -1,3 +1,7 @@
|
||||
6.3.3 (21.11.25)
|
||||
|
||||
- Some more improvements for gift auctions.
|
||||
|
||||
6.3.2 (20.11.25)
|
||||
|
||||
- Improved support for gift auctions.
|
||||
|
||||
Reference in New Issue
Block a user