Merge tag 'v5.16.3' into dev

This commit is contained in:
AlexeyZavar 2025-07-09 00:01:53 +03:00
commit f450001edb
41 changed files with 655 additions and 99 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -2863,6 +2863,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_gift_converted" = "Converted Gift"; "lng_credits_box_history_entry_gift_converted" = "Converted Gift";
"lng_credits_box_history_entry_gift_transfer" = "Gift Transfer"; "lng_credits_box_history_entry_gift_transfer" = "Gift Transfer";
"lng_credits_box_history_entry_gift_unavailable" = "Unavailable"; "lng_credits_box_history_entry_gift_unavailable" = "Unavailable";
"lng_credits_box_history_entry_gift_released" = "released by {name}";
"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out"; "lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out";
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}"; "lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}"; "lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
@ -3581,12 +3582,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later."; "lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later.";
"lng_gift_channel_title" = "Send a Gift"; "lng_gift_channel_title" = "Send a Gift";
"lng_gift_channel_about" = "Select a gift to show appreciation for {name}."; "lng_gift_channel_about" = "Select a gift to show appreciation for {name}.";
"lng_gift_released_by" = "Released by {name}";
"lng_gift_unique_owner" = "Owner"; "lng_gift_unique_owner" = "Owner";
"lng_gift_unique_address_copied" = "Address copied to clipboard."; "lng_gift_unique_address_copied" = "Address copied to clipboard.";
"lng_gift_unique_status" = "Status"; "lng_gift_unique_status" = "Status";
"lng_gift_unique_status_non" = "Non-Unique"; "lng_gift_unique_status_non" = "Non-Unique";
"lng_gift_unique_status_upgrade" = "upgrade"; "lng_gift_unique_status_upgrade" = "upgrade";
"lng_gift_unique_number" = "Collectible #{index}"; "lng_gift_unique_number" = "Collectible #{index}";
"lng_gift_unique_number_by" = "Collectible #{index} by {name}";
"lng_gift_unique_model" = "Model"; "lng_gift_unique_model" = "Model";
"lng_gift_unique_backdrop" = "Backdrop"; "lng_gift_unique_backdrop" = "Backdrop";
"lng_gift_unique_symbol" = "Symbol"; "lng_gift_unique_symbol" = "Symbol";
@ -4312,6 +4315,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_edit_shortcut" = "Edit Shortcut"; "lng_context_edit_shortcut" = "Edit Shortcut";
"lng_context_delete_shortcut" = "Delete Quick Reply"; "lng_context_delete_shortcut" = "Delete Quick Reply";
"lng_context_gift_send" = "Send Another Gift"; "lng_context_gift_send" = "Send Another Gift";
"lng_context_charge_fee" = "Charge Fee";
"lng_context_remove_fee" = "Remove Fee";
"lng_context_fee_now" = "{name} pays {amount} per message.";
"lng_context_fee_free" = "{name} can send messages for free.";
"lng_add_tag_about" = "Tag this message with an emoji for quick search."; "lng_add_tag_about" = "Tag this message with an emoji for quick search.";
"lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}"; "lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}";
@ -5314,6 +5321,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_payment_bar_button" = "Remove Fee"; "lng_payment_bar_button" = "Remove Fee";
"lng_payment_refund_title" = "Remove Fee"; "lng_payment_refund_title" = "Remove Fee";
"lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?"; "lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?";
"lng_payment_refund_channel" = "Do you want to allow {name} to message the channel for free?";
"lng_payment_refund_also#one" = "Refund already paid {count} Star"; "lng_payment_refund_also#one" = "Refund already paid {count} Star";
"lng_payment_refund_also#other" = "Refund already paid {count} Stars"; "lng_payment_refund_also#other" = "Refund already paid {count} Stars";
"lng_payment_refund_confirm" = "Confirm"; "lng_payment_refund_confirm" = "Confirm";

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.16.2.0" /> Version="5.16.3.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,16,2,0 FILEVERSION 5,16,3,0
PRODUCTVERSION 5,16,2,0 PRODUCTVERSION 5,16,3,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.16.2.0" VALUE "FileVersion", "5.16.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.16.2.0" VALUE "ProductVersion", "5.16.3.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,16,2,0 FILEVERSION 5,16,3,0
PRODUCTVERSION 5,16,2,0 PRODUCTVERSION 5,16,3,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "5.16.2.0" VALUE "FileVersion", "5.16.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.16.2.0" VALUE "ProductVersion", "5.16.3.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -283,7 +283,7 @@ rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
auto lifetime = rpl::lifetime(); auto lifetime = rpl::lifetime();
const auto finish = [=](const QString &url) { const auto finish = [=](const QString &url) {
makeRequest(MTPpayments_GetStarsRevenueStats( api().request(MTPpayments_GetStarsRevenueStats(
MTP_flags(0), MTP_flags(0),
(_isUser ? user()->input : channel()->input) (_isUser ? user()->input : channel()->input)
)).done([=](const MTPpayments_StarsRevenueStats &result) { )).done([=](const MTPpayments_StarsRevenueStats &result) {
@ -313,7 +313,7 @@ rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
}).send(); }).send();
}; };
makeRequest( api().request(
MTPpayments_GetStarsRevenueAdsAccountUrl( MTPpayments_GetStarsRevenueAdsAccountUrl(
(_isUser ? user()->input : channel()->input)) (_isUser ? user()->input : channel()->input))
).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) { ).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) {

View file

@ -619,6 +619,8 @@ auto PremiumGiftCodeOptions::requestStarGifts()
MTP_int(0) MTP_int(0)
)).done([=](const MTPpayments_StarGifts &result) { )).done([=](const MTPpayments_StarGifts &result) {
result.match([&](const MTPDpayments_starGifts &data) { result.match([&](const MTPDpayments_starGifts &data) {
_peer->owner().processUsers(data.vusers());
_peer->owner().processChats(data.vchats());
_giftsHash = data.vhash().v; _giftsHash = data.vhash().v;
const auto &list = data.vgifts().v; const auto &list = data.vgifts().v;
const auto session = &_peer->session(); const auto session = &_peer->session();
@ -805,6 +807,12 @@ std::optional<Data::StarGift> FromTL(
if (!document->sticker()) { if (!document->sticker()) {
return std::optional<Data::StarGift>(); return std::optional<Data::StarGift>();
} }
const auto releasedById = data.vreleased_by()
? peerFromMTP(*data.vreleased_by())
: PeerId();
const auto releasedBy = releasedById
? session->data().peer(releasedById).get()
: nullptr;
return std::optional<Data::StarGift>(Data::StarGift{ return std::optional<Data::StarGift>(Data::StarGift{
.id = uint64(data.vid().v), .id = uint64(data.vid().v),
.stars = int64(data.vstars().v), .stars = int64(data.vstars().v),
@ -812,6 +820,7 @@ std::optional<Data::StarGift> FromTL(
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()), .starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
.starsResellMin = int64(resellPrice), .starsResellMin = int64(resellPrice),
.document = document, .document = document,
.releasedBy = releasedBy,
.resellTitle = qs(data.vtitle().value_or_empty()), .resellTitle = qs(data.vtitle().value_or_empty()),
.resellCount = int(data.vavailability_resale().value_or_empty()), .resellCount = int(data.vavailability_resale().value_or_empty()),
.limitedLeft = remaining.value_or_empty(), .limitedLeft = remaining.value_or_empty(),
@ -841,6 +850,12 @@ std::optional<Data::StarGift> FromTL(
|| !pattern->document->sticker()) { || !pattern->document->sticker()) {
return std::optional<Data::StarGift>(); return std::optional<Data::StarGift>();
} }
const auto releasedById = data.vreleased_by()
? peerFromMTP(*data.vreleased_by())
: PeerId();
const auto releasedBy = releasedById
? session->data().peer(releasedById).get()
: nullptr;
auto result = Data::StarGift{ auto result = Data::StarGift{
.id = uint64(data.vid().v), .id = uint64(data.vid().v),
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{ .unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
@ -852,12 +867,14 @@ std::optional<Data::StarGift> FromTL(
.ownerId = (data.vowner_id() .ownerId = (data.vowner_id()
? peerFromMTP(*data.vowner_id()) ? peerFromMTP(*data.vowner_id())
: PeerId()), : PeerId()),
.releasedBy = releasedBy,
.number = data.vnum().v, .number = data.vnum().v,
.starsForResale = int(data.vresell_stars().value_or_empty()), .starsForResale = int(data.vresell_stars().value_or_empty()),
.model = *model, .model = *model,
.pattern = *pattern, .pattern = *pattern,
}), }),
.document = model->document, .document = model->document,
.releasedBy = releasedBy,
.limitedLeft = (total - data.vavailability_issued().v), .limitedLeft = (total - data.vavailability_issued().v),
.limitedCount = total, .limitedCount = total,
}; };

View file

@ -696,7 +696,7 @@ rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
return [=](auto consumer) { return [=](auto consumer) {
auto lifetime = rpl::lifetime(); auto lifetime = rpl::lifetime();
makeRequest(MTPpayments_GetStarsRevenueStats( api().request(MTPpayments_GetStarsRevenueStats(
MTP_flags(MTPpayments_getStarsRevenueStats::Flag::f_ton), MTP_flags(MTPpayments_getStarsRevenueStats::Flag::f_ton),
(_isUser ? user()->input : channel()->input) (_isUser ? user()->input : channel()->input)
)).done([=](const MTPpayments_StarsRevenueStats &result) { )).done([=](const MTPpayments_StarsRevenueStats &result) {

View file

@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "core/application.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "data/components/promo_suggestions.h" #include "data/components/promo_suggestions.h"
#include "data/data_birthday.h" #include "data/data_birthday.h"
@ -94,6 +95,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/section_widget.h" #include "window/section_widget.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -333,6 +335,69 @@ private:
}; };
class TextBubblePart final : public MediaGenericTextPart {
public:
TextBubblePart(
TextWithEntities text,
QMargins margins,
const style::TextStyle &st = st::defaultTextStyle,
const base::flat_map<uint16, ClickHandlerPtr> &links = {},
const Ui::Text::MarkedContext &context = {},
style::align align = style::al_top);
void draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const override;
private:
void setupPen(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context) const override;
int elisionLines() const override;
};
TextBubblePart::TextBubblePart(
TextWithEntities text,
QMargins margins,
const style::TextStyle &st,
const base::flat_map<uint16, ClickHandlerPtr> &links,
const Ui::Text::MarkedContext &context,
style::align align)
: MediaGenericTextPart(std::move(text), margins, st, links, context, align) {
}
void TextBubblePart::draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const {
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg());
const auto radius = height() / 2.;
const auto left = (outerWidth - width()) / 2;
const auto r = QRect(left, 0, width(), height());
p.drawRoundedRect(r, radius, radius);
MediaGenericTextPart::draw(p, owner, context, outerWidth);
}
void TextBubblePart::setupPen(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context) const {
auto pen = context.st->msgServiceFg()->c;
pen.setAlphaF(pen.alphaF() * 0.65);
p.setPen(pen);
}
int TextBubblePart::elisionLines() const {
return 1;
}
[[nodiscard]] AttributeId FromTL(const MTPStarGiftAttributeId &id) { [[nodiscard]] AttributeId FromTL(const MTPStarGiftAttributeId &id) {
return id.match([&](const MTPDstarGiftAttributeIdBackdrop &data) { return id.match([&](const MTPDstarGiftAttributeIdBackdrop &data) {
return AttributeId{ return AttributeId{
@ -526,6 +591,21 @@ auto GenerateGiftMedia(
st::giftBoxPreviewTitlePadding, st::giftBoxPreviewTitlePadding,
{}, {},
context); context);
if (v::is<GiftTypeStars>(descriptor)) {
const auto &stars = v::get<GiftTypeStars>(descriptor);
if (const auto by = stars.info.releasedBy) {
push(std::make_unique<TextBubblePart>(
tr::lng_gift_released_by(
tr::now,
lt_name,
Ui::Text::Link('@' + by->username()),
Ui::Text::WithEntities),
st::giftBoxReleasedByMargin,
st::defaultTextStyle));
}
}
pushText( pushText(
std::move(description), std::move(description),
st::giftBoxPreviewTextPadding, st::giftBoxPreviewTextPadding,
@ -778,7 +858,7 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
owned.get(), owned.get(),
GenerateGiftMedia(owned.get(), _item.get(), _recipient, details), GenerateGiftMedia(owned.get(), _item.get(), _recipient, details),
MediaGenericDescriptor{ MediaGenericDescriptor{
.maxWidth = st::chatIntroWidth, .maxWidth = st::chatGiftPreviewWidth,
.service = true, .service = true,
})); }));
_item = std::move(owned); _item = std::move(owned);
@ -3820,6 +3900,19 @@ void AddUniqueGiftCover(
Fn<void()> resaleClick) { Fn<void()> resaleClick) {
const auto cover = container->add(object_ptr<RpWidget>(container)); const auto cover = container->add(object_ptr<RpWidget>(container));
struct Released {
Released() : white(QColor(255, 255, 255)) {
}
style::owned_color white;
style::FlatLabel st;
PeerData *by = nullptr;
QColor bg;
};
const auto released = cover->lifetime().make_state<Released>();
released->st = st::uniqueGiftSubtitle;
released->st.palette.linkFg = released->white.color();
if (resalePrice) { if (resalePrice) {
auto background = rpl::duplicate( auto background = rpl::duplicate(
data data
@ -3841,17 +3934,58 @@ void AddUniqueGiftCover(
st::uniqueGiftTitle); st::uniqueGiftTitle);
title->setTextColorOverride(QColor(255, 255, 255)); title->setTextColorOverride(QColor(255, 255, 255));
auto subtitleText = subtitleOverride auto subtitleText = subtitleOverride
? std::move(subtitleOverride) ? std::move(
: rpl::duplicate(data) | rpl::map([](const Data::UniqueGift &gift) { subtitleOverride
return tr::lng_gift_unique_number( ) | Ui::Text::ToWithEntities() | rpl::type_erased()
tr::now, : rpl::duplicate(data) | rpl::map([=](const Data::UniqueGift &gift) {
lt_index, released->by = gift.releasedBy;
QString::number(gift.number)); released->bg = gift.backdrop.patternColor;
return gift.releasedBy
? tr::lng_gift_unique_number_by(
tr::now,
lt_index,
TextWithEntities{ QString::number(gift.number) },
lt_name,
Ui::Text::Link('@' + gift.releasedBy->username()),
Ui::Text::WithEntities)
: tr::lng_gift_unique_number(
tr::now,
lt_index,
TextWithEntities{ QString::number(gift.number) },
Ui::Text::WithEntities);
}); });
const auto subtitle = CreateChild<FlatLabel>( const auto subtitle = CreateChild<FlatLabel>(
cover, cover,
std::move(subtitleText), std::move(subtitleText),
st::uniqueGiftSubtitle); released->st);
if (released->by) {
const auto button = CreateChild<AbstractButton>(cover);
subtitle->raise();
subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
button->setClickedCallback([=] {
GiftReleasedByHandler(released->by);
});
subtitle->geometryValue(
) | rpl::start_with_next([=](QRect geometry) {
button->setGeometry(
geometry.marginsAdded(st::giftBoxReleasedByMargin));
}, button->lifetime());
button->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(button);
auto hq = PainterHighQualityEnabler(p);
const auto use = subtitle->textMaxWidth();
const auto add = button->width() - subtitle->width();
const auto full = use + add;
const auto left = (button->width() - full) / 2;
const auto height = button->height();
const auto radius = height / 2.;
p.setPen(Qt::NoPen);
p.setBrush(released->bg);
p.setOpacity(0.5);
p.drawRoundedRect(left, 0, full, height, radius, radius);
}, button->lifetime());
}
struct GiftView { struct GiftView {
QImage gradient; QImage gradient;
@ -4417,6 +4551,24 @@ void ShowUniqueGiftSellBox(
})); }));
} }
void GiftReleasedByHandler(not_null<PeerData*> peer) {
const auto session = &peer->session();
const auto window = session->tryResolveWindow(peer);
if (window) {
window->showPeerHistory(peer);
return;
}
const auto account = not_null(&session->account());
if (const auto window = Core::App().windowFor(account)) {
window->invokeForSessionController(
&session->account(),
peer,
[=](not_null<Window::SessionController*> window) {
window->showPeerHistory(peer);
});
}
}
struct UpgradeArgs : StarGiftUpgradeArgs { struct UpgradeArgs : StarGiftUpgradeArgs {
std::vector<Data::UniqueGiftModel> models; std::vector<Data::UniqueGiftModel> models;
std::vector<Data::UniqueGiftPattern> patterns; std::vector<Data::UniqueGiftPattern> patterns;

View file

@ -82,6 +82,8 @@ void ShowUniqueGiftSellBox(
Data::SavedStarGiftId savedId, Data::SavedStarGiftId savedId,
Settings::GiftWearBoxStyleOverride st); Settings::GiftWearBoxStyleOverride st);
void GiftReleasedByHandler(not_null<PeerData*> peer);
struct PatternPoint { struct PatternPoint {
QPointF position; QPointF position;
float64 scale = 1.; float64 scale = 1.;

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 5016002; constexpr auto AppVersion = 5016003;
constexpr auto AppVersionStr = "5.16.2"; constexpr auto AppVersionStr = "5.16.3";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -65,6 +65,7 @@ struct CreditsHistoryEntry final {
uint64 bareGiveawayMsgId = 0; uint64 bareGiveawayMsgId = 0;
uint64 bareGiftStickerId = 0; uint64 bareGiftStickerId = 0;
uint64 bareGiftOwnerId = 0; uint64 bareGiftOwnerId = 0;
uint64 bareGiftReleasedById = 0;
uint64 bareGiftResaleRecipientId = 0; uint64 bareGiftResaleRecipientId = 0;
uint64 bareActorId = 0; uint64 bareActorId = 0;
uint64 bareEntryOwnerId = 0; uint64 bareEntryOwnerId = 0;

View file

@ -2569,7 +2569,9 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
message, message,
HistoryView::GenerateUniqueGiftMedia(message, replacing, unique), HistoryView::GenerateUniqueGiftMedia(message, replacing, unique),
HistoryView::MediaGenericDescriptor{ HistoryView::MediaGenericDescriptor{
.maxWidth = st::msgServiceGiftBoxSize.width(), .maxWidth = (_data.stargiftReleasedBy
? st::msgServiceStarGiftByWidth
: st::msgServiceGiftBoxSize.width()),
.paintBg = HistoryView::UniqueGiftBg(message, unique), .paintBg = HistoryView::UniqueGiftBg(message, unique),
.service = true, .service = true,
}); });

View file

@ -144,6 +144,7 @@ struct GiftCode {
QString slug; QString slug;
uint64 stargiftId = 0; uint64 stargiftId = 0;
DocumentData *document = nullptr; DocumentData *document = nullptr;
PeerData *stargiftReleasedBy = nullptr;
std::shared_ptr<UniqueGift> unique; std::shared_ptr<UniqueGift> unique;
TextWithEntities message; TextWithEntities message;
ChannelData *channel = nullptr; ChannelData *channel = nullptr;

View file

@ -655,7 +655,7 @@ bool PeerData::canCreatePolls() const {
} }
bool PeerData::canCreateTodoLists() const { bool PeerData::canCreateTodoLists() const {
if (isMonoforum()) { if (isMonoforum() || isBroadcast()) {
return false; return false;
} }
return session().premium() return session().premium()

View file

@ -731,6 +731,24 @@ void SavedSublist::applyMonoforumDialog(
unreadReactions().setCount(data.vunread_reactions_count().v); unreadReactions().setCount(data.vunread_reactions_count().v);
setUnreadMark(data.is_unread_mark()); setUnreadMark(data.is_unread_mark());
applyMaybeLast(topItem); applyMaybeLast(topItem);
if (data.is_nopaid_messages_exception()) {
_flags |= Flag::FeeRemoved;
} else {
_flags &= ~Flag::FeeRemoved;
}
}
bool SavedSublist::isFeeRemoved() const {
return (_flags & Flag::FeeRemoved);
}
void SavedSublist::toggleFeeRemoved(bool feeRemoved) {
if (feeRemoved) {
_flags |= Flag::FeeRemoved;
} else {
_flags &= ~Flag::FeeRemoved;
}
} }
TimeId SavedSublist::adjustedChatListTimeId() const { TimeId SavedSublist::adjustedChatListTimeId() const {

View file

@ -32,6 +32,8 @@ public:
~SavedSublist(); ~SavedSublist();
[[nodiscard]] bool inMonoforum() const; [[nodiscard]] bool inMonoforum() const;
[[nodiscard]] bool isFeeRemoved() const;
void toggleFeeRemoved(bool feeRemoved);
void apply(const SublistReadTillUpdate &update); void apply(const SublistReadTillUpdate &update);
void apply(const MessageUpdate &update); void apply(const MessageUpdate &update);
@ -125,6 +127,7 @@ private:
enum class Flag : uchar { enum class Flag : uchar {
ResolveChatListMessage = (1 << 0), ResolveChatListMessage = (1 << 0),
InMonoforum = (1 << 1), InMonoforum = (1 << 1),
FeeRemoved = (1 << 2),
}; };
friend inline constexpr bool is_flag_type(Flag) { return true; } friend inline constexpr bool is_flag_type(Flag) { return true; }
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;

View file

@ -44,6 +44,7 @@ struct UniqueGift {
QString ownerAddress; QString ownerAddress;
QString ownerName; QString ownerName;
PeerId ownerId = 0; PeerId ownerId = 0;
PeerData *releasedBy = nullptr;
int number = 0; int number = 0;
int starsForTransfer = -1; int starsForTransfer = -1;
int starsForResale = -1; int starsForResale = -1;
@ -68,6 +69,7 @@ struct StarGift {
int64 starsToUpgrade = 0; int64 starsToUpgrade = 0;
int64 starsResellMin = 0; int64 starsResellMin = 0;
not_null<DocumentData*> document; not_null<DocumentData*> document;
PeerData *releasedBy = nullptr;
QString resellTitle; QString resellTitle;
int resellCount = 0; int resellCount = 0;
int limitedLeft = 0; int limitedLeft = 0;

View file

@ -6531,6 +6531,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
fields.stargiftId = gift->id; fields.stargiftId = gift->id;
fields.starsToUpgrade = gift->starsToUpgrade; fields.starsToUpgrade = gift->starsToUpgrade;
fields.document = gift->document; fields.document = gift->document;
fields.stargiftReleasedBy = gift->releasedBy;
fields.limitedCount = gift->limitedCount; fields.limitedCount = gift->limitedCount;
fields.limitedLeft = gift->limitedLeft; fields.limitedLeft = gift->limitedLeft;
fields.count = gift->stars; fields.count = gift->stars;
@ -6566,6 +6567,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
if (auto gift = Api::FromTL(&history()->session(), data.vgift())) { if (auto gift = Api::FromTL(&history()->session(), data.vgift())) {
fields.stargiftId = gift->id; fields.stargiftId = gift->id;
fields.document = gift->document; fields.document = gift->document;
fields.stargiftReleasedBy = gift->releasedBy;
fields.limitedCount = gift->limitedCount; fields.limitedCount = gift->limitedCount;
fields.limitedLeft = gift->limitedLeft; fields.limitedLeft = gift->limitedLeft;
fields.count = gift->stars; fields.count = gift->stars;

View file

@ -1194,6 +1194,7 @@ PaysStatus::PaysStatus(
not_null<UserData*> user) not_null<UserData*> user)
: _controller(window) : _controller(window)
, _user(user) , _user(user)
, _paidAlready(std::make_shared<rpl::variable<int>>())
, _inner(Ui::CreateChild<Bar>(parent.get(), user)) , _inner(Ui::CreateChild<Bar>(parent.get(), user))
, _bar(parent, object_ptr<Bar>::fromRaw(_inner)) { , _bar(parent, object_ptr<Bar>::fromRaw(_inner)) {
setupState(); setupState();
@ -1220,65 +1221,12 @@ void PaysStatus::setupState() {
void PaysStatus::setupHandlers() { void PaysStatus::setupHandlers() {
_inner->removeClicks( _inner->removeClicks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
const auto user = _user; Window::PeerMenuConfirmToggleFee(
const auto exception = [=](bool refund) { _controller,
using Flag = MTPaccount_ToggleNoPaidMessagesException::Flag; _paidAlready,
const auto api = &user->session().api(); _user->session().user(),
const auto require = false; _user,
api->request(MTPaccount_ToggleNoPaidMessagesException( true);
MTP_flags((refund ? Flag::f_refund_charged : Flag())
| (require ? Flag::f_require_payment : Flag())),
MTPInputPeer(), // parent_peer // #TODO monoforum
user->inputUser
)).done([=] {
user->clearPaysPerMessage();
}).send();
};
_controller->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto refund = std::make_shared<QPointer<Ui::Checkbox>>();
Ui::ConfirmBox(box, {
.text = tr::lng_payment_refund_text(
tr::now,
lt_name,
Ui::Text::Bold(user->shortName()),
Ui::Text::WithEntities),
.confirmed = [=](Fn<void()> close) {
exception(*refund && (*refund)->checked());
close();
},
.confirmText = tr::lng_payment_refund_confirm(tr::now),
.title = tr::lng_payment_refund_title(tr::now),
});
const auto paid = box->lifetime().make_state<
rpl::variable<int>
>();
*paid = _paidAlready.value();
paid->value() | rpl::start_with_next([=](int already) {
if (!already) {
delete base::take(*refund);
} else if (!*refund) {
const auto skip = st::defaultCheckbox.margin.top();
*refund = box->addRow(
object_ptr<Ui::Checkbox>(
box,
tr::lng_payment_refund_also(
lt_count,
paid->value() | tr::to_count()),
false,
st::defaultCheckbox),
st::boxRowPadding + QMargins(0, skip, 0, skip));
}
}, box->lifetime());
user->session().api().request(MTPaccount_GetPaidMessagesRevenue(
MTP_flags(0),
MTPInputPeer(), // parent_peer // #TODO monoforum
user->inputUser
)).done(crl::guard(_inner, [=](
const MTPaccount_PaidMessagesRevenue &result) {
_paidAlready = result.data().vstars_amount().v;
})).send();
}));
}, _bar.lifetime()); }, _bar.lifetime());
} }

View file

@ -208,7 +208,7 @@ private:
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
const not_null<UserData*> _user; const not_null<UserData*> _user;
rpl::variable<int> _paidAlready; std::shared_ptr<rpl::variable<int>> _paidAlready;
State _state; State _state;
QPointer<Bar> _inner; QPointer<Bar> _inner;
SlidingBar _bar; SlidingBar _bar;

View file

@ -1231,6 +1231,11 @@ void TopBarWidget::updateControlsVisibility() {
? (hasPollsMenu || hasTodoListsMenu || hasTopicMenu) ? (hasPollsMenu || hasTodoListsMenu || hasTopicMenu)
: (section == Section::ChatsList) : (section == Section::ChatsList)
? (_activeChat.key.peer() && _activeChat.key.peer()->isForum()) ? (_activeChat.key.peer() && _activeChat.key.peer()->isForum())
: (section == Section::SavedSublist)
? (_activeChat.key.peer()
&& _activeChat.key.peer()->isChannel()
&& _activeChat.key.peer()->owner().commonStarsPerMessage(
_activeChat.key.peer()->asChannel()))
: false); : false);
const auto hasInfo = !_activeChat.key.folder() const auto hasInfo = !_activeChat.key.folder()
&& (section == Section::History && (section == Section::History

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_premium.h" #include "api/api_premium.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/gift_premium_box.h" // ResolveGiftCode #include "boxes/gift_premium_box.h" // ResolveGiftCode
#include "boxes/star_gift_box.h" // GiftReleasedByHandler
#include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_gift_box_pack.h"
#include "core/click_handler_types.h" // ClickHandlerContext #include "core/click_handler_types.h" // ClickHandlerContext
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
@ -54,7 +55,9 @@ int PremiumGift::top() {
} }
int PremiumGift::width() { int PremiumGift::width() {
return st::msgServiceStarGiftBoxWidth; return _data.stargiftReleasedBy
? st::msgServiceStarGiftByWidth
: st::msgServiceStarGiftBoxWidth;
} }
QSize PremiumGift::size() { QSize PremiumGift::size() {
@ -120,6 +123,18 @@ TextWithEntities PremiumGift::title() {
: tr::lng_prize_title(tr::now, WithEntities); : tr::lng_prize_title(tr::now, WithEntities);
} }
TextWithEntities PremiumGift::author() {
using namespace Ui::Text;
if (!_data.stargiftReleasedBy) {
return {};
}
return tr::lng_gift_released_by(
tr::now,
lt_name,
Ui::Text::Link('@' + _data.stargiftReleasedBy->username()),
Ui::Text::WithEntities);
}
TextWithEntities PremiumGift::subtitle() { TextWithEntities PremiumGift::subtitle() {
if (tonGift()) { if (tonGift()) {
return tr::lng_action_gift_got_ton(tr::now, Ui::Text::WithEntities); return tr::lng_action_gift_got_ton(tr::now, Ui::Text::WithEntities);
@ -319,6 +334,18 @@ ClickHandlerPtr PremiumGift::createViewLink() {
}); });
} }
ClickHandlerPtr PremiumGift::authorLink() {
if (const auto by = _data.stargiftReleasedBy) {
if (!_authorLink) {
_authorLink = std::make_shared<LambdaClickHandler>([=] {
Ui::GiftReleasedByHandler(by);
});
}
return _authorLink;
}
return nullptr;
}
int PremiumGift::buttonSkip() { int PremiumGift::buttonSkip() {
return st::msgServiceGiftBoxButtonMargins.top(); return st::msgServiceGiftBoxButtonMargins.top();
} }

View file

@ -29,6 +29,7 @@ public:
int width() override; int width() override;
QSize size() override; QSize size() override;
TextWithEntities title() override; TextWithEntities title() override;
TextWithEntities author() override;
TextWithEntities subtitle() override; TextWithEntities subtitle() override;
rpl::producer<QString> button() override; rpl::producer<QString> button() override;
std::optional<Ui::Premium::MiniStarsType> buttonMinistars() override; std::optional<Ui::Premium::MiniStarsType> buttonMinistars() override;
@ -39,6 +40,7 @@ public:
const PaintContext &context, const PaintContext &context,
const QRect &geometry) override; const QRect &geometry) override;
ClickHandlerPtr createViewLink() override; ClickHandlerPtr createViewLink() override;
ClickHandlerPtr authorLink() override;
bool hideServiceText() override; bool hideServiceText() override;
void stickerClearLoopPlayed() override; void stickerClearLoopPlayed() override;
@ -63,6 +65,7 @@ private:
const not_null<Element*> _parent; const not_null<Element*> _parent;
const not_null<Data::MediaGiftBox*> _gift; const not_null<Data::MediaGiftBox*> _gift;
const Data::GiftCode &_data; const Data::GiftCode &_data;
ClickHandlerPtr _authorLink;
QImage _badgeCache; QImage _badgeCache;
Info::PeerGifts::GiftBadge _badgeKey; Info::PeerGifts::GiftBadge _badgeKey;
mutable std::optional<Sticker> _sticker; mutable std::optional<Sticker> _sticker;

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rect.h" #include "ui/rect.h"
#include "ui/power_saving.h" #include "ui/power_saving.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_credits.h"
#include "styles/style_premium.h" #include "styles/style_premium.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
@ -51,6 +52,11 @@ ServiceBox::ServiceBox(
.session = &parent->history()->session(), .session = &parent->history()->session(),
.repaint = [parent] { parent->customEmojiRepaint(); }, .repaint = [parent] { parent->customEmojiRepaint(); },
})) }))
, _author(
st::defaultTextStyle,
_content->author(),
kMarkupTextOptions,
_maxWidth)
, _subtitle( , _subtitle(
st::premiumPreviewAbout.style, st::premiumPreviewAbout.style,
Ui::Text::Filtered( Ui::Text::Filtered(
@ -79,6 +85,12 @@ ServiceBox::ServiceBox(
? 0 ? 0
: (_title.countHeight(_maxWidth) : (_title.countHeight(_maxWidth)
+ st::msgServiceGiftBoxTitlePadding.bottom())) + st::msgServiceGiftBoxTitlePadding.bottom()))
+ (_author.isEmpty()
? 0
: (st::giftBoxReleasedByMargin.top()
+ st::defaultTextStyle.font->height
+ st::giftBoxReleasedByMargin.bottom()
+ st::msgServiceGiftBoxTitlePadding.bottom()))
+ _subtitle.countHeight(_maxWidth) + _subtitle.countHeight(_maxWidth)
+ (!_content->button() + (!_content->button()
? 0 ? 0
@ -164,6 +176,37 @@ void ServiceBox::draw(Painter &p, const PaintContext &context) const {
}); });
top += _title.countHeight(_maxWidth) + padding.bottom(); top += _title.countHeight(_maxWidth) + padding.bottom();
} }
if (!_author.isEmpty()) {
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg());
const auto use = std::min(_maxWidth, _author.maxWidth())
+ st::giftBoxReleasedByMargin.left()
+ st::giftBoxReleasedByMargin.right();
const auto left = st::msgPadding.left() + (_maxWidth - use) / 2;
const auto height = st::giftBoxReleasedByMargin.top()
+ st::defaultTextStyle.font->height
+ st::giftBoxReleasedByMargin.bottom();
const auto radius = height / 2.;
p.drawRoundedRect(left, top, use, height, radius, radius);
auto fg = context.st->msgServiceFg()->c;
fg.setAlphaF(0.65 * fg.alphaF());
p.setPen(fg);
_author.draw(p, {
.position = QPoint(
left + st::giftBoxReleasedByMargin.left(),
top + st::giftBoxReleasedByMargin.top()),
.availableWidth = (use
- st::giftBoxReleasedByMargin.left()
- st::giftBoxReleasedByMargin.right()),
.palette = &context.st->serviceTextPalette(),
.elisionLines = 1,
});
p.setPen(context.st->msgServiceFg());
top += height + st::msgServiceGiftBoxTitlePadding.bottom();
}
_parent->prepareCustomEmojiPaint(p, context, _subtitle); _parent->prepareCustomEmojiPaint(p, context, _subtitle);
_subtitle.draw(p, { _subtitle.draw(p, {
.position = QPoint(st::msgPadding.left(), top), .position = QPoint(st::msgPadding.left(), top),
@ -231,6 +274,23 @@ TextState ServiceBox::textState(QPoint point, StateRequest request) const {
if (!_title.isEmpty()) { if (!_title.isEmpty()) {
top += _title.countHeight(_maxWidth) + padding.bottom(); top += _title.countHeight(_maxWidth) + padding.bottom();
} }
if (!_author.isEmpty()) {
const auto use = std::min(_maxWidth, _author.maxWidth())
+ st::giftBoxReleasedByMargin.left()
+ st::giftBoxReleasedByMargin.right();
const auto left = st::msgPadding.left() + (_maxWidth - use) / 2;
const auto height = st::giftBoxReleasedByMargin.top()
+ st::defaultTextStyle.font->height
+ st::giftBoxReleasedByMargin.bottom();
if (point.x() >= left
&& point.y() >= top
&& point.x() < left + use
&& point.y() < top + height) {
result.link = _content->authorLink();
}
top += height + st::msgServiceGiftBoxTitlePadding.bottom();
}
auto subtitleRequest = request.forText(); auto subtitleRequest = request.forText();
subtitleRequest.align = style::al_top; subtitleRequest.align = style::al_top;
const auto state = _subtitle.getState( const auto state = _subtitle.getState(

View file

@ -28,6 +28,9 @@ public:
[[nodiscard]] virtual int top() = 0; [[nodiscard]] virtual int top() = 0;
[[nodiscard]] virtual QSize size() = 0; [[nodiscard]] virtual QSize size() = 0;
[[nodiscard]] virtual TextWithEntities title() = 0; [[nodiscard]] virtual TextWithEntities title() = 0;
[[nodiscard]] virtual TextWithEntities author() {
return {};
}
[[nodiscard]] virtual TextWithEntities subtitle() = 0; [[nodiscard]] virtual TextWithEntities subtitle() = 0;
[[nodiscard]] virtual int buttonSkip() { [[nodiscard]] virtual int buttonSkip() {
return top(); return top();
@ -45,6 +48,9 @@ public:
const PaintContext &context, const PaintContext &context,
const QRect &geometry) = 0; const QRect &geometry) = 0;
[[nodiscard]] virtual ClickHandlerPtr createViewLink() = 0; [[nodiscard]] virtual ClickHandlerPtr createViewLink() = 0;
[[nodiscard]] virtual ClickHandlerPtr authorLink() {
return nullptr;
}
[[nodiscard]] virtual bool hideServiceText() = 0; [[nodiscard]] virtual bool hideServiceText() = 0;
@ -123,6 +129,7 @@ private:
const int _maxWidth = 0; const int _maxWidth = 0;
Ui::Text::String _title; Ui::Text::String _title;
Ui::Text::String _author;
Ui::Text::String _subtitle; Ui::Text::String _subtitle;
const QSize _size; const QSize _size;
const QSize _innerSize; const QSize _innerSize;

View file

@ -81,6 +81,95 @@ private:
}; };
class TextBubblePart final : public MediaGenericTextPart {
public:
TextBubblePart(
TextWithEntities text,
QMargins margins,
Data::UniqueGiftBackdrop backdrop,
ClickHandlerPtr link);
void draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
private:
void setupPen(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context) const override;
int elisionLines() const override;
Data::UniqueGiftBackdrop _backdrop;
ClickHandlerPtr _link;
};
TextBubblePart::TextBubblePart(
TextWithEntities text,
QMargins margins,
Data::UniqueGiftBackdrop backdrop,
ClickHandlerPtr link)
: MediaGenericTextPart(
std::move(text),
margins,
st::defaultTextStyle,
{},
{},
style::al_top)
, _backdrop(backdrop)
, _link(std::move(link)) {
}
void TextBubblePart::draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const {
p.setPen(Qt::NoPen);
p.setOpacity(0.5);
p.setBrush(_backdrop.patternColor);
const auto radius = height() / 2.;
const auto left = (outerWidth - width()) / 2;
const auto r = QRect(left, 0, width(), height());
p.drawRoundedRect(r, radius, radius);
p.setOpacity(1.);
MediaGenericTextPart::draw(p, owner, context, outerWidth);
}
TextState TextBubblePart::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
auto result = TextState();
const auto left = (outerWidth - width()) / 2;
if (point.x() >= left
&& point.y() >= 0
&& point.x() < left + width()
&& point.y() < height()) {
result.link = _link;
}
return result;
}
void TextBubblePart::setupPen(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context) const {
p.setPen(_backdrop.textColor);
}
int TextBubblePart::elisionLines() const {
return 1;
}
ButtonPart::ButtonPart( ButtonPart::ButtonPart(
const QString &text, const QString &text,
QMargins margins, QMargins margins,
@ -284,6 +373,21 @@ auto GenerateUniqueGiftMedia(
gift->backdrop.textColor, gift->backdrop.textColor,
st::chatUniqueTextPadding); st::chatUniqueTextPadding);
if (const auto by = gift->releasedBy) {
const auto handler = std::make_shared<LambdaClickHandler>([=] {
Ui::GiftReleasedByHandler(by);
});
push(std::make_unique<TextBubblePart>(
tr::lng_gift_released_by(
tr::now,
lt_name,
Ui::Text::Link('@' + by->username()),
Ui::Text::WithEntities),
st::giftBoxReleasedByMargin,
gift->backdrop,
handler));
}
const auto name = [](const Data::UniqueGiftAttribute &value) { const auto name = [](const Data::UniqueGiftAttribute &value) {
return Ui::Text::Bold(value.name); return Ui::Text::Bold(value.name);
}; };

View file

@ -1890,11 +1890,11 @@ starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true
starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption; starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption;
starGift#c62aca28 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string = StarGift; starGift#7f853c12 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string released_by:flags.6?Peer = StarGift;
starGiftUnique#6411db89 flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector<StarGiftAttribute> availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long = StarGift; starGiftUnique#f63778ae flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector<StarGiftAttribute> availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long released_by:flags.5?Peer = StarGift;
payments.starGiftsNotModified#a388a368 = payments.StarGifts; payments.starGiftsNotModified#a388a368 = payments.StarGifts;
payments.starGifts#901689ea hash:int gifts:Vector<StarGift> = payments.StarGifts; payments.starGifts#2ed82995 hash:int gifts:Vector<StarGift> chats:Vector<Chat> users:Vector<User> = payments.StarGifts;
messageReportOption#7903e3d9 text:string option:bytes = MessageReportOption; messageReportOption#7903e3d9 text:string option:bytes = MessageReportOption;
@ -2720,4 +2720,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool;
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
// LAYER 206 // LAYER 207

View file

@ -1388,9 +1388,10 @@ void SessionPrivate::handleReceived() {
auto sfrom = decryptedInts + 4U; // msg_id + seq_no + length + message auto sfrom = decryptedInts + 4U; // msg_id + seq_no + length + message
MTP_LOG(_shiftedDcId, ("Recv: ") MTP_LOG(_shiftedDcId, ("Recv: ")
+ DumpToText(sfrom, end) + DumpToText(sfrom, end)
+ QString(" (dc:%1,key:%2)" + QString(" (dc:%1,key:%2,session:%3)"
).arg(AbstractConnection::ProtocolDcDebugId(getProtocolDcId()) ).arg(AbstractConnection::ProtocolDcDebugId(getProtocolDcId())
).arg(_encryptionKey->keyId())); ).arg(_encryptionKey->keyId()
).arg(_sessionId));
const auto registered = _receivedMessageIds.registerMsgId( const auto registered = _receivedMessageIds.registerMsgId(
msgId, msgId,
@ -2663,9 +2664,10 @@ bool SessionPrivate::sendSecureRequest(
auto from = request->constData() + 4; auto from = request->constData() + 4;
MTP_LOG(_shiftedDcId, ("Send: ") MTP_LOG(_shiftedDcId, ("Send: ")
+ DumpToText(from, from + messageSize) + DumpToText(from, from + messageSize)
+ QString(" (dc:%1,key:%2)" + QString(" (dc:%1,key:%2,session:%3)"
).arg(AbstractConnection::ProtocolDcDebugId(getProtocolDcId()) ).arg(AbstractConnection::ProtocolDcDebugId(getProtocolDcId())
).arg(_encryptionKey->keyId())); ).arg(_encryptionKey->keyId()
).arg(_sessionId));
uchar encryptedSHA256[32]; uchar encryptedSHA256[32];
MTPint128 &msgKey(*(MTPint128*)(encryptedSHA256 + 8)); MTPint128 &msgKey(*(MTPint128*)(encryptedSHA256 + 8));

View file

@ -1434,7 +1434,23 @@ void GenericCreditsEntryBox(
Ui::AddSkip(content); Ui::AddSkip(content);
} }
if (!isStarGift || creditsHistoryStarGift || e.soldOutInfo) { if (e.bareGiftReleasedById && !e.uniqueGift) {
const auto peer = owner->peer(PeerId(e.bareGiftReleasedById));
const auto released = content->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
content,
tr::lng_credits_box_history_entry_gift_released(
lt_name,
rpl::single(Ui::Text::Link('@' + peer->username())),
Ui::Text::WithEntities),
st::creditsReleasedByLabel)));
released->entity()->setClickHandlerFilter([=](const auto &...) {
Ui::GiftReleasedByHandler(peer);
return false;
});
} else if (!isStarGift || creditsHistoryStarGift || e.soldOutInfo) {
constexpr auto kMinus = QChar(0x2212); constexpr auto kMinus = QChar(0x2212);
auto &lifetime = content->lifetime(); auto &lifetime = content->lifetime();
const auto text = lifetime.make_state<Ui::Text::String>(); const auto text = lifetime.make_state<Ui::Text::String>();
@ -2282,6 +2298,9 @@ void StarGiftViewBox(
.bareGiftOwnerId = (data.unique .bareGiftOwnerId = (data.unique
? data.unique->ownerId.value ? data.unique->ownerId.value
: toId.value), : toId.value),
.bareGiftReleasedById = (data.stargiftReleasedBy
? data.stargiftReleasedBy->id.value
: 0),
.bareActorId = (toChannel ? data.channelFrom->id.value : 0), .bareActorId = (toChannel ? data.channelFrom->id.value : 0),
.bareEntryOwnerId = (toChannel ? data.channel->id.value : 0), .bareEntryOwnerId = (toChannel ? data.channel->id.value : 0),
.giftChannelSavedId = data.channelSavedId, .giftChannelSavedId = data.channelSavedId,

View file

@ -936,6 +936,7 @@ msgServiceGiftBoxTitlePadding: margins(0px, 20px, 0px, 6px);
msgServiceGiftBoxStickerTop: -19px; msgServiceGiftBoxStickerTop: -19px;
msgServiceGiftBoxStickerSize: 140px; msgServiceGiftBoxStickerSize: 140px;
msgServiceStarGiftBoxWidth: 224px; msgServiceStarGiftBoxWidth: 224px;
msgServiceStarGiftByWidth: 272px;
msgServiceStarGiftStickerTop: 24px; msgServiceStarGiftStickerTop: 24px;
msgServiceStarGiftStickerSize: 100px; msgServiceStarGiftStickerSize: 100px;
@ -1105,6 +1106,7 @@ chatIntroWidth: 224px;
chatIntroTitleMargin: margins(11px, 16px, 11px, 4px); chatIntroTitleMargin: margins(11px, 16px, 11px, 4px);
chatIntroMargin: margins(11px, 0px, 11px, 0px); chatIntroMargin: margins(11px, 0px, 11px, 0px);
chatIntroStickerPadding: margins(10px, 8px, 10px, 16px); chatIntroStickerPadding: margins(10px, 8px, 10px, 16px);
chatGiftPreviewWidth: 264px;
liveLocationLongInIcon: icon {{ "chat/live_location_long", msgInServiceFg }}; liveLocationLongInIcon: icon {{ "chat/live_location_long", msgInServiceFg }};
liveLocationLongInIconSelected: icon {{ "chat/live_location_long", msgInServiceFgSelected }}; liveLocationLongInIconSelected: icon {{ "chat/live_location_long", msgInServiceFgSelected }};

View file

@ -66,6 +66,9 @@ creditsBoxAboutDivider: FlatLabel(boxDividerLabel) {
creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) { creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle; style: semiboldTextStyle;
} }
creditsReleasedByLabel: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
starIconEmoji: IconEmoji { starIconEmoji: IconEmoji {
icon: icon{{ "payments/premium_emoji", creditsBg1 }}; icon: icon{{ "payments/premium_emoji", creditsBg1 }};
@ -175,6 +178,7 @@ giftBoxButtonBottomByStars: 18px;
giftBoxButtonPadding: margins(8px, 4px, 8px, 4px); giftBoxButtonPadding: margins(8px, 4px, 8px, 4px);
giftBoxPreviewStickerPadding: margins(10px, 12px, 10px, 16px); giftBoxPreviewStickerPadding: margins(10px, 12px, 10px, 16px);
giftBoxPreviewTitlePadding: margins(12px, 4px, 12px, 4px); giftBoxPreviewTitlePadding: margins(12px, 4px, 12px, 4px);
giftBoxReleasedByMargin: margins(12px, 2px, 12px, 2px);
giftBoxPreviewTextPadding: margins(12px, 4px, 12px, 4px); giftBoxPreviewTextPadding: margins(12px, 4px, 12px, 4px);
giftBoxButtonMargin: margins(12px, 8px, 12px, 12px); giftBoxButtonMargin: margins(12px, 8px, 12px, 12px);
giftBoxStickerTop: 0px; giftBoxStickerTop: 0px;

View file

@ -56,6 +56,7 @@ menuIconDiscussion: icon {{ "menu/discussion", menuIconColor }};
menuIconStats: icon {{ "menu/stats", menuIconColor }}; menuIconStats: icon {{ "menu/stats", menuIconColor }};
menuIconBoosts: icon {{ "menu/boosts", menuIconColor }}; menuIconBoosts: icon {{ "menu/boosts", menuIconColor }};
menuIconEarn: icon {{ "menu/earn", menuIconColor }}; menuIconEarn: icon {{ "menu/earn", menuIconColor }};
menuIconCancelFee: icon {{ "menu/cancel_fee", menuIconColor }};
menuIconCreatePoll: icon {{ "menu/create_poll", menuIconColor }}; menuIconCreatePoll: icon {{ "menu/create_poll", menuIconColor }};
menuIconCreateTodoList: icon {{ "menu/select", menuIconColor }}; menuIconCreateTodoList: icon {{ "menu/select", menuIconColor }};
menuIconQrCode: icon {{ "menu/qr_code", menuIconColor }}; menuIconQrCode: icon {{ "menu/qr_code", menuIconColor }};

View file

@ -316,6 +316,14 @@ windowArchiveToast: Toast(defaultToast) {
maxWidth: boxWideWidth; maxWidth: boxWideWidth;
} }
windowFeeItem: Menu(defaultMenu) {
itemPadding: margins(17px, 3px, 17px, 4px);
itemRightSkip: 0px;
itemStyle: whenReadStyle;
itemFgOver: windowFg;
itemFgDisabled: windowFg;
}
ivWidthMin: 380px; ivWidthMin: 380px;
ivHeightMin: 480px; ivHeightMin: 480px;
ivWidthDefault: 600px; ivWidthDefault: 600px;

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/random.h" #include "base/random.h"
#include "base/options.h" #include "base/options.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "base/unique_qptr.h"
#include "base/qt/qt_key_modifiers.h" #include "base/qt/qt_key_modifiers.h"
#include "boxes/delete_messages_box.h" #include "boxes/delete_messages_box.h"
#include "boxes/max_invite_box.h" #include "boxes/max_invite_box.h"
@ -83,6 +84,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/stories/info_stories_widget.h" #include "info/stories/info_stories_widget.h"
#include "data/components/scheduled_messages.h" #include "data/components/scheduled_messages.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_folder.h" #include "data/data_folder.h"
@ -98,10 +100,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_filters.h" #include "data/data_chat_filters.h"
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "core/application.h" #include "core/application.h"
#include "core/ui_integration.h"
#include "export/export_manager.h" #include "export/export_manager.h"
#include "boxes/peers/edit_peer_info_box.h" #include "boxes/peers/edit_peer_info_box.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_credits.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_window.h" // st::windowMinWidth #include "styles/style_window.h" // st::windowMinWidth
@ -282,6 +286,7 @@ private:
void fillArchiveActions(); void fillArchiveActions();
void fillSavedSublistActions(); void fillSavedSublistActions();
void fillContextMenuActions(); void fillContextMenuActions();
void fillMonoforumPeerActions();
void addHidePromotion(); void addHidePromotion();
void addTogglePin(); void addTogglePin();
@ -326,6 +331,7 @@ private:
void addVideoChat(); void addVideoChat();
void addViewStatistics(); void addViewStatistics();
void addBoostChat(); void addBoostChat();
void addToggleFee();
[[nodiscard]] bool skipCreateActions() const; [[nodiscard]] bool skipCreateActions() const;
@ -1365,6 +1371,7 @@ void Filler::fill() {
case Section::Scheduled: fillScheduledActions(); break; case Section::Scheduled: fillScheduledActions(); break;
case Section::ContextMenu: case Section::ContextMenu:
case Section::SubsectionTabsMenu: fillContextMenuActions(); break; case Section::SubsectionTabsMenu: fillContextMenuActions(); break;
case Section::SavedSublist: fillMonoforumPeerActions(); break;
default: Unexpected("_request.section in Filler::fill."); default: Unexpected("_request.section in Filler::fill.");
} }
} }
@ -1660,6 +1667,68 @@ void Filler::fillSavedSublistActions() {
addTogglePin(); addTogglePin();
} }
void Filler::fillMonoforumPeerActions() {
Expects(_sublist != nullptr);
addToggleFee();
}
void Filler::addToggleFee() {
const auto feeRemoved = _sublist->isFeeRemoved();
const auto text = feeRemoved
? tr::lng_context_charge_fee(tr::now)
: tr::lng_context_remove_fee(tr::now);
const auto navigation = _controller;
const auto parent = _sublist->parentChat();
const auto user = _sublist->sublistPeer()->asUser();
if (!parent || !user) {
return;
}
const auto paidAmount = std::make_shared<rpl::variable<int>>();
_addAction(text, [=] {
const auto removeFee = !feeRemoved;
PeerMenuConfirmToggleFee(
navigation,
paidAmount,
parent,
user,
removeFee);
}, feeRemoved ? &st::menuIconEarn : &st::menuIconCancelFee);
_addAction({ .isSeparator = true });
_addAction({ .make = [=](not_null<Ui::RpWidget*> actionParent) {
const auto text = feeRemoved
? tr::lng_context_fee_free(
tr::now,
lt_name,
TextWithEntities{ user->shortName() },
Ui::Text::WithEntities)
: tr::lng_context_fee_now(
tr::now,
lt_name,
TextWithEntities{ user->shortName() },
lt_amount,
user->owner().customEmojiManager().ministarEmoji(
{ 0, st::giftBoxByStarsStarTop, 0, 0 }
).append(Lang::FormatCountDecimal(
user->owner().commonStarsPerMessage(parent)
)),
Ui::Text::WithEntities);
const auto action = new QAction(actionParent);
action->setDisabled(true);
auto result = base::make_unique_q<Ui::Menu::Action>(
actionParent,
st::windowFeeItem,
action,
nullptr,
nullptr);
result->setMarkedText(
text,
QString(),
Core::TextContext({ .session = &user->session() }));
return result;
} });
}
} // namespace } // namespace
void PeerMenuExportChat( void PeerMenuExportChat(
@ -3752,4 +3821,81 @@ bool CanArchive(History *history, PeerData *peer) {
return true; return true;
} }
void PeerMenuConfirmToggleFee(
not_null<Window::SessionNavigation*> navigation,
std::shared_ptr<rpl::variable<int>> paidAmount,
not_null<PeerData*> peer,
not_null<UserData*> user,
bool removeFee) {
const auto parent = peer->isChannel() ? peer->asChannel() : nullptr;
const auto exception = [=](bool refund) {
using Flag = MTPaccount_ToggleNoPaidMessagesException::Flag;
const auto api = &user->session().api();
api->request(MTPaccount_ToggleNoPaidMessagesException(
MTP_flags((refund ? Flag::f_refund_charged : Flag())
| (removeFee ? Flag() : Flag::f_require_payment)
| (parent ? Flag::f_parent_peer : Flag())),
parent->input,
user->inputUser
)).done([=] {
if (!parent) {
user->clearPaysPerMessage();
} else if (const auto monoforum = peer->monoforum()) {
if (const auto sublist = monoforum->sublistLoaded(user)) {
sublist->toggleFeeRemoved(removeFee);
}
}
}).send();
};
if (!removeFee) {
exception(false);
return;
}
navigation->uiShow()->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto refund = std::make_shared<QPointer<Ui::Checkbox>>();
Ui::ConfirmBox(box, {
.text = tr::lng_payment_refund_text(
tr::now,
lt_name,
Ui::Text::Bold(user->shortName()),
Ui::Text::WithEntities),
.confirmed = [=](Fn<void()> close) {
exception(*refund && (*refund)->checked());
close();
},
.confirmText = tr::lng_payment_refund_confirm(tr::now),
.title = tr::lng_payment_refund_title(tr::now),
});
const auto paid = box->lifetime().make_state<
rpl::variable<int>
>();
*paid = paidAmount->value();
paid->value() | rpl::start_with_next([=](int already) {
if (!already) {
delete base::take(*refund);
} else if (!*refund) {
const auto skip = st::defaultCheckbox.margin.top();
*refund = box->addRow(
object_ptr<Ui::Checkbox>(
box,
tr::lng_payment_refund_also(
lt_count,
paid->value() | tr::to_count()),
false,
st::defaultCheckbox),
st::boxRowPadding + QMargins(0, skip, 0, skip));
}
}, box->lifetime());
using Flag = MTPaccount_GetPaidMessagesRevenue::Flag;
user->session().api().request(MTPaccount_GetPaidMessagesRevenue(
MTP_flags(parent ? Flag::f_parent_peer : Flag()),
parent ? parent->input : MTPInputPeer(),
user->inputUser
)).done([=](const MTPaccount_PaidMessagesRevenue &result) {
*paidAmount = result.data().vstars_amount().v;
}).send();
}));
}
} // namespace Window } // namespace Window

View file

@ -253,4 +253,11 @@ void AddSeparatorAndShiftUp(const PeerMenuCallback &addAction);
[[nodiscard]] bool IsArchived(not_null<History*> history); [[nodiscard]] bool IsArchived(not_null<History*> history);
[[nodiscard]] bool CanArchive(History *history, PeerData *peer); [[nodiscard]] bool CanArchive(History *history, PeerData *peer);
void PeerMenuConfirmToggleFee(
not_null<Window::SessionNavigation*> navigation,
std::shared_ptr<rpl::variable<int>> paidAmount,
not_null<PeerData*> peer,
not_null<UserData*> user,
bool removeFee);
} // namespace Window } // namespace Window

View file

@ -760,7 +760,6 @@ RUN git clone -b v$QT --depth=1 https://github.com/qt/qt5.git \
&& cmake -B build . \ && cmake -B build . \
-DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_INSTALL_PREFIX=/usr/local \
-DBUILD_SHARED_LIBS=OFF \ -DBUILD_SHARED_LIBS=OFF \
-DQT_GENERATE_SBOM=OFF \
-DQT_QPA_PLATFORMS="wayland;xcb" \ -DQT_QPA_PLATFORMS="wayland;xcb" \
-DINPUT_libpng=qt \ -DINPUT_libpng=qt \
-DINPUT_harfbuzz=qt \ -DINPUT_harfbuzz=qt \

View file

@ -1,7 +1,7 @@
AppVersion 5016002 AppVersion 5016003
AppVersionStrMajor 5.16 AppVersionStrMajor 5.16
AppVersionStrSmall 5.16.2 AppVersionStrSmall 5.16.3
AppVersionStr 5.16.2 AppVersionStr 5.16.3
BetaChannel 0 BetaChannel 0
AlphaVersion 0 AlphaVersion 0
AppVersionOriginal 5.16.2 AppVersionOriginal 5.16.3

View file

@ -1,3 +1,9 @@
5.16.3 (08.07.25)
- Allow removing / charging fee in channel direct messages.
- Don't offer creating checklists in channels.
- Support author channel in gifts.
5.16.2 (04.07.25) 5.16.2 (04.07.25)
- Fix crash in some checklists. - Fix crash in some checklists.