diff --git a/Telegram/Resources/icons/menu/cancel_fee.png b/Telegram/Resources/icons/menu/cancel_fee.png
new file mode 100644
index 0000000000..467c369cec
Binary files /dev/null and b/Telegram/Resources/icons/menu/cancel_fee.png differ
diff --git a/Telegram/Resources/icons/menu/cancel_fee@2x.png b/Telegram/Resources/icons/menu/cancel_fee@2x.png
new file mode 100644
index 0000000000..75c0a6c941
Binary files /dev/null and b/Telegram/Resources/icons/menu/cancel_fee@2x.png differ
diff --git a/Telegram/Resources/icons/menu/cancel_fee@3x.png b/Telegram/Resources/icons/menu/cancel_fee@3x.png
new file mode 100644
index 0000000000..73fee9f98a
Binary files /dev/null and b/Telegram/Resources/icons/menu/cancel_fee@3x.png differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index eda9942456..caac333013 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -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_transfer" = "Gift Transfer";
"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_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}";
@@ -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_channel_title" = "Send a Gift";
"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_address_copied" = "Address copied to clipboard.";
"lng_gift_unique_status" = "Status";
"lng_gift_unique_status_non" = "Non-Unique";
"lng_gift_unique_status_upgrade" = "upgrade";
"lng_gift_unique_number" = "Collectible #{index}";
+"lng_gift_unique_number_by" = "Collectible #{index} by {name}";
"lng_gift_unique_model" = "Model";
"lng_gift_unique_backdrop" = "Backdrop";
"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_delete_shortcut" = "Delete Quick Reply";
"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_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_refund_title" = "Remove Fee";
"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#other" = "Refund already paid {count} Stars";
"lng_payment_refund_confirm" = "Confirm";
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 135f939788..d83d286216 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="5.16.3.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index f573d05ff7..b96320c234 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,16,2,0
- PRODUCTVERSION 5,16,2,0
+ FILEVERSION 5,16,3,0
+ PRODUCTVERSION 5,16,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "5.16.2.0"
+ VALUE "FileVersion", "5.16.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.16.2.0"
+ VALUE "ProductVersion", "5.16.3.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index e59ae58110..e1293d48f7 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,16,2,0
- PRODUCTVERSION 5,16,2,0
+ FILEVERSION 5,16,3,0
+ PRODUCTVERSION 5,16,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "5.16.2.0"
+ VALUE "FileVersion", "5.16.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.16.2.0"
+ VALUE "ProductVersion", "5.16.3.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp
index d274d424d2..0670972d68 100644
--- a/Telegram/SourceFiles/api/api_credits.cpp
+++ b/Telegram/SourceFiles/api/api_credits.cpp
@@ -283,7 +283,7 @@ rpl::producer CreditsEarnStatistics::request() {
auto lifetime = rpl::lifetime();
const auto finish = [=](const QString &url) {
- makeRequest(MTPpayments_GetStarsRevenueStats(
+ api().request(MTPpayments_GetStarsRevenueStats(
MTP_flags(0),
(_isUser ? user()->input : channel()->input)
)).done([=](const MTPpayments_StarsRevenueStats &result) {
@@ -313,7 +313,7 @@ rpl::producer CreditsEarnStatistics::request() {
}).send();
};
- makeRequest(
+ api().request(
MTPpayments_GetStarsRevenueAdsAccountUrl(
(_isUser ? user()->input : channel()->input))
).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) {
diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp
index 5119f3908c..5065541e27 100644
--- a/Telegram/SourceFiles/api/api_premium.cpp
+++ b/Telegram/SourceFiles/api/api_premium.cpp
@@ -619,6 +619,8 @@ auto PremiumGiftCodeOptions::requestStarGifts()
MTP_int(0)
)).done([=](const MTPpayments_StarGifts &result) {
result.match([&](const MTPDpayments_starGifts &data) {
+ _peer->owner().processUsers(data.vusers());
+ _peer->owner().processChats(data.vchats());
_giftsHash = data.vhash().v;
const auto &list = data.vgifts().v;
const auto session = &_peer->session();
@@ -805,6 +807,12 @@ std::optional FromTL(
if (!document->sticker()) {
return std::optional();
}
+ 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{
.id = uint64(data.vid().v),
.stars = int64(data.vstars().v),
@@ -812,6 +820,7 @@ std::optional FromTL(
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
.starsResellMin = int64(resellPrice),
.document = document,
+ .releasedBy = releasedBy,
.resellTitle = qs(data.vtitle().value_or_empty()),
.resellCount = int(data.vavailability_resale().value_or_empty()),
.limitedLeft = remaining.value_or_empty(),
@@ -841,6 +850,12 @@ std::optional FromTL(
|| !pattern->document->sticker()) {
return std::optional();
}
+ 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{
.id = uint64(data.vid().v),
.unique = std::make_shared(Data::UniqueGift{
@@ -852,12 +867,14 @@ std::optional FromTL(
.ownerId = (data.vowner_id()
? peerFromMTP(*data.vowner_id())
: PeerId()),
+ .releasedBy = releasedBy,
.number = data.vnum().v,
.starsForResale = int(data.vresell_stars().value_or_empty()),
.model = *model,
.pattern = *pattern,
}),
.document = model->document,
+ .releasedBy = releasedBy,
.limitedLeft = (total - data.vavailability_issued().v),
.limitedCount = total,
};
diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp
index 430af6f158..04f471e923 100644
--- a/Telegram/SourceFiles/api/api_statistics.cpp
+++ b/Telegram/SourceFiles/api/api_statistics.cpp
@@ -696,7 +696,7 @@ rpl::producer EarnStatistics::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
- makeRequest(MTPpayments_GetStarsRevenueStats(
+ api().request(MTPpayments_GetStarsRevenueStats(
MTP_flags(MTPpayments_getStarsRevenueStats::Flag::f_ton),
(_isUser ? user()->input : channel()->input)
)).done([=](const MTPpayments_StarsRevenueStats &result) {
diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp
index 6e4608a47d..64ce31f976 100644
--- a/Telegram/SourceFiles/boxes/star_gift_box.cpp
+++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_lottie.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
+#include "core/application.h"
#include "core/ui_integration.h"
#include "data/components/promo_suggestions.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 "window/themes/window_theme.h"
#include "window/section_widget.h"
+#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.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 &links = {},
+ const Ui::Text::MarkedContext &context = {},
+ style::align align = style::al_top);
+
+ void draw(
+ Painter &p,
+ not_null owner,
+ const PaintContext &context,
+ int outerWidth) const override;
+
+private:
+ void setupPen(
+ Painter &p,
+ not_null owner,
+ const PaintContext &context) const override;
+ int elisionLines() const override;
+
+};
+
+TextBubblePart::TextBubblePart(
+ TextWithEntities text,
+ QMargins margins,
+ const style::TextStyle &st,
+ const base::flat_map &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 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 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) {
return id.match([&](const MTPDstarGiftAttributeIdBackdrop &data) {
return AttributeId{
@@ -526,6 +591,21 @@ auto GenerateGiftMedia(
st::giftBoxPreviewTitlePadding,
{},
context);
+
+ if (v::is(descriptor)) {
+ const auto &stars = v::get(descriptor);
+ if (const auto by = stars.info.releasedBy) {
+ push(std::make_unique(
+ tr::lng_gift_released_by(
+ tr::now,
+ lt_name,
+ Ui::Text::Link('@' + by->username()),
+ Ui::Text::WithEntities),
+ st::giftBoxReleasedByMargin,
+ st::defaultTextStyle));
+ }
+ }
+
pushText(
std::move(description),
st::giftBoxPreviewTextPadding,
@@ -778,7 +858,7 @@ void PreviewWrap::prepare(rpl::producer details) {
owned.get(),
GenerateGiftMedia(owned.get(), _item.get(), _recipient, details),
MediaGenericDescriptor{
- .maxWidth = st::chatIntroWidth,
+ .maxWidth = st::chatGiftPreviewWidth,
.service = true,
}));
_item = std::move(owned);
@@ -3820,6 +3900,19 @@ void AddUniqueGiftCover(
Fn resaleClick) {
const auto cover = container->add(object_ptr(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->st = st::uniqueGiftSubtitle;
+ released->st.palette.linkFg = released->white.color();
+
if (resalePrice) {
auto background = rpl::duplicate(
data
@@ -3841,17 +3934,58 @@ void AddUniqueGiftCover(
st::uniqueGiftTitle);
title->setTextColorOverride(QColor(255, 255, 255));
auto subtitleText = subtitleOverride
- ? std::move(subtitleOverride)
- : rpl::duplicate(data) | rpl::map([](const Data::UniqueGift &gift) {
- return tr::lng_gift_unique_number(
- tr::now,
- lt_index,
- QString::number(gift.number));
+ ? std::move(
+ subtitleOverride
+ ) | Ui::Text::ToWithEntities() | rpl::type_erased()
+ : rpl::duplicate(data) | rpl::map([=](const Data::UniqueGift &gift) {
+ released->by = gift.releasedBy;
+ 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(
cover,
std::move(subtitleText),
- st::uniqueGiftSubtitle);
+ released->st);
+ if (released->by) {
+ const auto button = CreateChild(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 {
QImage gradient;
@@ -4417,6 +4551,24 @@ void ShowUniqueGiftSellBox(
}));
}
+void GiftReleasedByHandler(not_null 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) {
+ window->showPeerHistory(peer);
+ });
+ }
+}
+
struct UpgradeArgs : StarGiftUpgradeArgs {
std::vector models;
std::vector patterns;
diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h
index d26270d1a4..78ee14a028 100644
--- a/Telegram/SourceFiles/boxes/star_gift_box.h
+++ b/Telegram/SourceFiles/boxes/star_gift_box.h
@@ -82,6 +82,8 @@ void ShowUniqueGiftSellBox(
Data::SavedStarGiftId savedId,
Settings::GiftWearBoxStyleOverride st);
+void GiftReleasedByHandler(not_null peer);
+
struct PatternPoint {
QPointF position;
float64 scale = 1.;
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index c6bac14728..424c724925 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
-constexpr auto AppVersion = 5016002;
-constexpr auto AppVersionStr = "5.16.2";
+constexpr auto AppVersion = 5016003;
+constexpr auto AppVersionStr = "5.16.3";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h
index 21f2427a4e..0b0b9382ed 100644
--- a/Telegram/SourceFiles/data/data_credits.h
+++ b/Telegram/SourceFiles/data/data_credits.h
@@ -65,6 +65,7 @@ struct CreditsHistoryEntry final {
uint64 bareGiveawayMsgId = 0;
uint64 bareGiftStickerId = 0;
uint64 bareGiftOwnerId = 0;
+ uint64 bareGiftReleasedById = 0;
uint64 bareGiftResaleRecipientId = 0;
uint64 bareActorId = 0;
uint64 bareEntryOwnerId = 0;
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index 30b3929be7..c8a1d35d07 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -2569,7 +2569,9 @@ std::unique_ptr MediaGiftBox::createView(
message,
HistoryView::GenerateUniqueGiftMedia(message, replacing, unique),
HistoryView::MediaGenericDescriptor{
- .maxWidth = st::msgServiceGiftBoxSize.width(),
+ .maxWidth = (_data.stargiftReleasedBy
+ ? st::msgServiceStarGiftByWidth
+ : st::msgServiceGiftBoxSize.width()),
.paintBg = HistoryView::UniqueGiftBg(message, unique),
.service = true,
});
diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h
index dab3e57b2e..bf5d003830 100644
--- a/Telegram/SourceFiles/data/data_media_types.h
+++ b/Telegram/SourceFiles/data/data_media_types.h
@@ -144,6 +144,7 @@ struct GiftCode {
QString slug;
uint64 stargiftId = 0;
DocumentData *document = nullptr;
+ PeerData *stargiftReleasedBy = nullptr;
std::shared_ptr unique;
TextWithEntities message;
ChannelData *channel = nullptr;
diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp
index 56f6e0e497..97683de736 100644
--- a/Telegram/SourceFiles/data/data_peer.cpp
+++ b/Telegram/SourceFiles/data/data_peer.cpp
@@ -655,7 +655,7 @@ bool PeerData::canCreatePolls() const {
}
bool PeerData::canCreateTodoLists() const {
- if (isMonoforum()) {
+ if (isMonoforum() || isBroadcast()) {
return false;
}
return session().premium()
diff --git a/Telegram/SourceFiles/data/data_saved_sublist.cpp b/Telegram/SourceFiles/data/data_saved_sublist.cpp
index 488eab3a1a..64ef0886df 100644
--- a/Telegram/SourceFiles/data/data_saved_sublist.cpp
+++ b/Telegram/SourceFiles/data/data_saved_sublist.cpp
@@ -731,6 +731,24 @@ void SavedSublist::applyMonoforumDialog(
unreadReactions().setCount(data.vunread_reactions_count().v);
setUnreadMark(data.is_unread_mark());
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 {
diff --git a/Telegram/SourceFiles/data/data_saved_sublist.h b/Telegram/SourceFiles/data/data_saved_sublist.h
index 1361f207fb..cdcc2e28ee 100644
--- a/Telegram/SourceFiles/data/data_saved_sublist.h
+++ b/Telegram/SourceFiles/data/data_saved_sublist.h
@@ -32,6 +32,8 @@ public:
~SavedSublist();
[[nodiscard]] bool inMonoforum() const;
+ [[nodiscard]] bool isFeeRemoved() const;
+ void toggleFeeRemoved(bool feeRemoved);
void apply(const SublistReadTillUpdate &update);
void apply(const MessageUpdate &update);
@@ -125,6 +127,7 @@ private:
enum class Flag : uchar {
ResolveChatListMessage = (1 << 0),
InMonoforum = (1 << 1),
+ FeeRemoved = (1 << 2),
};
friend inline constexpr bool is_flag_type(Flag) { return true; }
using Flags = base::flags;
diff --git a/Telegram/SourceFiles/data/data_star_gift.h b/Telegram/SourceFiles/data/data_star_gift.h
index 3de6c85531..df0aacc953 100644
--- a/Telegram/SourceFiles/data/data_star_gift.h
+++ b/Telegram/SourceFiles/data/data_star_gift.h
@@ -44,6 +44,7 @@ struct UniqueGift {
QString ownerAddress;
QString ownerName;
PeerId ownerId = 0;
+ PeerData *releasedBy = nullptr;
int number = 0;
int starsForTransfer = -1;
int starsForResale = -1;
@@ -68,6 +69,7 @@ struct StarGift {
int64 starsToUpgrade = 0;
int64 starsResellMin = 0;
not_null document;
+ PeerData *releasedBy = nullptr;
QString resellTitle;
int resellCount = 0;
int limitedLeft = 0;
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index e4c570d014..6d96224f21 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -6531,6 +6531,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
fields.stargiftId = gift->id;
fields.starsToUpgrade = gift->starsToUpgrade;
fields.document = gift->document;
+ fields.stargiftReleasedBy = gift->releasedBy;
fields.limitedCount = gift->limitedCount;
fields.limitedLeft = gift->limitedLeft;
fields.count = gift->stars;
@@ -6566,6 +6567,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
if (auto gift = Api::FromTL(&history()->session(), data.vgift())) {
fields.stargiftId = gift->id;
fields.document = gift->document;
+ fields.stargiftReleasedBy = gift->releasedBy;
fields.limitedCount = gift->limitedCount;
fields.limitedLeft = gift->limitedLeft;
fields.count = gift->stars;
diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp
index f5710124bb..99c135f6f3 100644
--- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp
@@ -1194,6 +1194,7 @@ PaysStatus::PaysStatus(
not_null user)
: _controller(window)
, _user(user)
+, _paidAlready(std::make_shared>())
, _inner(Ui::CreateChild(parent.get(), user))
, _bar(parent, object_ptr::fromRaw(_inner)) {
setupState();
@@ -1220,65 +1221,12 @@ void PaysStatus::setupState() {
void PaysStatus::setupHandlers() {
_inner->removeClicks(
) | rpl::start_with_next([=] {
- const auto user = _user;
- const auto exception = [=](bool refund) {
- using Flag = MTPaccount_ToggleNoPaidMessagesException::Flag;
- const auto api = &user->session().api();
- const auto require = false;
- api->request(MTPaccount_ToggleNoPaidMessagesException(
- 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 box) {
- const auto refund = std::make_shared>();
- Ui::ConfirmBox(box, {
- .text = tr::lng_payment_refund_text(
- tr::now,
- lt_name,
- Ui::Text::Bold(user->shortName()),
- Ui::Text::WithEntities),
- .confirmed = [=](Fn 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
- >();
- *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(
- 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();
- }));
+ Window::PeerMenuConfirmToggleFee(
+ _controller,
+ _paidAlready,
+ _user->session().user(),
+ _user,
+ true);
}, _bar.lifetime());
}
diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.h b/Telegram/SourceFiles/history/view/history_view_contact_status.h
index 0c540a36c4..47aa1232ce 100644
--- a/Telegram/SourceFiles/history/view/history_view_contact_status.h
+++ b/Telegram/SourceFiles/history/view/history_view_contact_status.h
@@ -208,7 +208,7 @@ private:
const not_null _controller;
const not_null _user;
- rpl::variable _paidAlready;
+ std::shared_ptr> _paidAlready;
State _state;
QPointer _inner;
SlidingBar _bar;
diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
index 11fb594b0e..243ac722f1 100644
--- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
@@ -1231,6 +1231,11 @@ void TopBarWidget::updateControlsVisibility() {
? (hasPollsMenu || hasTodoListsMenu || hasTopicMenu)
: (section == Section::ChatsList)
? (_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);
const auto hasInfo = !_activeChat.key.folder()
&& (section == Section::History
diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp
index d3ef5e26a1..bcbcaf70e2 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_premium.h"
#include "base/unixtime.h"
#include "boxes/gift_premium_box.h" // ResolveGiftCode
+#include "boxes/star_gift_box.h" // GiftReleasedByHandler
#include "chat_helpers/stickers_gift_box_pack.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "data/stickers/data_custom_emoji.h"
@@ -54,7 +55,9 @@ int PremiumGift::top() {
}
int PremiumGift::width() {
- return st::msgServiceStarGiftBoxWidth;
+ return _data.stargiftReleasedBy
+ ? st::msgServiceStarGiftByWidth
+ : st::msgServiceStarGiftBoxWidth;
}
QSize PremiumGift::size() {
@@ -120,6 +123,18 @@ TextWithEntities PremiumGift::title() {
: 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() {
if (tonGift()) {
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([=] {
+ Ui::GiftReleasedByHandler(by);
+ });
+ }
+ return _authorLink;
+ }
+ return nullptr;
+}
+
int PremiumGift::buttonSkip() {
return st::msgServiceGiftBoxButtonMargins.top();
}
diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h
index ef0bf67612..10be8b9bcb 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h
@@ -29,6 +29,7 @@ public:
int width() override;
QSize size() override;
TextWithEntities title() override;
+ TextWithEntities author() override;
TextWithEntities subtitle() override;
rpl::producer button() override;
std::optional buttonMinistars() override;
@@ -39,6 +40,7 @@ public:
const PaintContext &context,
const QRect &geometry) override;
ClickHandlerPtr createViewLink() override;
+ ClickHandlerPtr authorLink() override;
bool hideServiceText() override;
void stickerClearLoopPlayed() override;
@@ -63,6 +65,7 @@ private:
const not_null _parent;
const not_null _gift;
const Data::GiftCode &_data;
+ ClickHandlerPtr _authorLink;
QImage _badgeCache;
Info::PeerGifts::GiftBadge _badgeKey;
mutable std::optional _sticker;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp
index 5e078f7d19..4e5e11d65f 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rect.h"
#include "ui/power_saving.h"
#include "styles/style_chat.h"
+#include "styles/style_credits.h"
#include "styles/style_premium.h"
#include "styles/style_layers.h"
@@ -51,6 +52,11 @@ ServiceBox::ServiceBox(
.session = &parent->history()->session(),
.repaint = [parent] { parent->customEmojiRepaint(); },
}))
+, _author(
+ st::defaultTextStyle,
+ _content->author(),
+ kMarkupTextOptions,
+ _maxWidth)
, _subtitle(
st::premiumPreviewAbout.style,
Ui::Text::Filtered(
@@ -79,6 +85,12 @@ ServiceBox::ServiceBox(
? 0
: (_title.countHeight(_maxWidth)
+ st::msgServiceGiftBoxTitlePadding.bottom()))
+ + (_author.isEmpty()
+ ? 0
+ : (st::giftBoxReleasedByMargin.top()
+ + st::defaultTextStyle.font->height
+ + st::giftBoxReleasedByMargin.bottom()
+ + st::msgServiceGiftBoxTitlePadding.bottom()))
+ _subtitle.countHeight(_maxWidth)
+ (!_content->button()
? 0
@@ -164,6 +176,37 @@ void ServiceBox::draw(Painter &p, const PaintContext &context) const {
});
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);
_subtitle.draw(p, {
.position = QPoint(st::msgPadding.left(), top),
@@ -231,6 +274,23 @@ TextState ServiceBox::textState(QPoint point, StateRequest request) const {
if (!_title.isEmpty()) {
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();
subtitleRequest.align = style::al_top;
const auto state = _subtitle.getState(
diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_box.h b/Telegram/SourceFiles/history/view/media/history_view_service_box.h
index fe7018e849..b15ded3306 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_service_box.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.h
@@ -28,6 +28,9 @@ public:
[[nodiscard]] virtual int top() = 0;
[[nodiscard]] virtual QSize size() = 0;
[[nodiscard]] virtual TextWithEntities title() = 0;
+ [[nodiscard]] virtual TextWithEntities author() {
+ return {};
+ }
[[nodiscard]] virtual TextWithEntities subtitle() = 0;
[[nodiscard]] virtual int buttonSkip() {
return top();
@@ -45,6 +48,9 @@ public:
const PaintContext &context,
const QRect &geometry) = 0;
[[nodiscard]] virtual ClickHandlerPtr createViewLink() = 0;
+ [[nodiscard]] virtual ClickHandlerPtr authorLink() {
+ return nullptr;
+ }
[[nodiscard]] virtual bool hideServiceText() = 0;
@@ -123,6 +129,7 @@ private:
const int _maxWidth = 0;
Ui::Text::String _title;
+ Ui::Text::String _author;
Ui::Text::String _subtitle;
const QSize _size;
const QSize _innerSize;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp
index b44d10f000..350b7846c5 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp
@@ -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 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 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 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 owner,
+ const PaintContext &context) const {
+ p.setPen(_backdrop.textColor);
+}
+
+int TextBubblePart::elisionLines() const {
+ return 1;
+}
+
ButtonPart::ButtonPart(
const QString &text,
QMargins margins,
@@ -284,6 +373,21 @@ auto GenerateUniqueGiftMedia(
gift->backdrop.textColor,
st::chatUniqueTextPadding);
+ if (const auto by = gift->releasedBy) {
+ const auto handler = std::make_shared([=] {
+ Ui::GiftReleasedByHandler(by);
+ });
+ push(std::make_unique(
+ 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) {
return Ui::Text::Bold(value.name);
};
diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl
index 61aff7c1f2..e33dd8366b 100644
--- a/Telegram/SourceFiles/mtproto/scheme/api.tl
+++ b/Telegram/SourceFiles/mtproto/scheme/api.tl
@@ -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;
-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;
-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 availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long = 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#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 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.starGifts#901689ea hash:int gifts:Vector = payments.StarGifts;
+payments.starGifts#2ed82995 hash:int gifts:Vector chats:Vector users:Vector = payments.StarGifts;
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;
-// LAYER 206
+// LAYER 207
diff --git a/Telegram/SourceFiles/mtproto/session_private.cpp b/Telegram/SourceFiles/mtproto/session_private.cpp
index 0786eb68ea..4458537725 100644
--- a/Telegram/SourceFiles/mtproto/session_private.cpp
+++ b/Telegram/SourceFiles/mtproto/session_private.cpp
@@ -1388,9 +1388,10 @@ void SessionPrivate::handleReceived() {
auto sfrom = decryptedInts + 4U; // msg_id + seq_no + length + message
MTP_LOG(_shiftedDcId, ("Recv: ")
+ DumpToText(sfrom, end)
- + QString(" (dc:%1,key:%2)"
+ + QString(" (dc:%1,key:%2,session:%3)"
).arg(AbstractConnection::ProtocolDcDebugId(getProtocolDcId())
- ).arg(_encryptionKey->keyId()));
+ ).arg(_encryptionKey->keyId()
+ ).arg(_sessionId));
const auto registered = _receivedMessageIds.registerMsgId(
msgId,
@@ -2663,9 +2664,10 @@ bool SessionPrivate::sendSecureRequest(
auto from = request->constData() + 4;
MTP_LOG(_shiftedDcId, ("Send: ")
+ DumpToText(from, from + messageSize)
- + QString(" (dc:%1,key:%2)"
+ + QString(" (dc:%1,key:%2,session:%3)"
).arg(AbstractConnection::ProtocolDcDebugId(getProtocolDcId())
- ).arg(_encryptionKey->keyId()));
+ ).arg(_encryptionKey->keyId()
+ ).arg(_sessionId));
uchar encryptedSHA256[32];
MTPint128 &msgKey(*(MTPint128*)(encryptedSHA256 + 8));
diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp
index 15d889fea8..afb9b9c64b 100644
--- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp
+++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp
@@ -1434,7 +1434,23 @@ void GenericCreditsEntryBox(
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>(
+ box,
+ object_ptr(
+ 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);
auto &lifetime = content->lifetime();
const auto text = lifetime.make_state();
@@ -2282,6 +2298,9 @@ void StarGiftViewBox(
.bareGiftOwnerId = (data.unique
? data.unique->ownerId.value
: toId.value),
+ .bareGiftReleasedById = (data.stargiftReleasedBy
+ ? data.stargiftReleasedBy->id.value
+ : 0),
.bareActorId = (toChannel ? data.channelFrom->id.value : 0),
.bareEntryOwnerId = (toChannel ? data.channel->id.value : 0),
.giftChannelSavedId = data.channelSavedId,
diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style
index 604c931019..c45b937ab7 100644
--- a/Telegram/SourceFiles/ui/chat/chat.style
+++ b/Telegram/SourceFiles/ui/chat/chat.style
@@ -936,6 +936,7 @@ msgServiceGiftBoxTitlePadding: margins(0px, 20px, 0px, 6px);
msgServiceGiftBoxStickerTop: -19px;
msgServiceGiftBoxStickerSize: 140px;
msgServiceStarGiftBoxWidth: 224px;
+msgServiceStarGiftByWidth: 272px;
msgServiceStarGiftStickerTop: 24px;
msgServiceStarGiftStickerSize: 100px;
@@ -1105,6 +1106,7 @@ chatIntroWidth: 224px;
chatIntroTitleMargin: margins(11px, 16px, 11px, 4px);
chatIntroMargin: margins(11px, 0px, 11px, 0px);
chatIntroStickerPadding: margins(10px, 8px, 10px, 16px);
+chatGiftPreviewWidth: 264px;
liveLocationLongInIcon: icon {{ "chat/live_location_long", msgInServiceFg }};
liveLocationLongInIconSelected: icon {{ "chat/live_location_long", msgInServiceFgSelected }};
diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style
index 3c865fead6..b9c69f00e5 100644
--- a/Telegram/SourceFiles/ui/effects/credits.style
+++ b/Telegram/SourceFiles/ui/effects/credits.style
@@ -66,6 +66,9 @@ creditsBoxAboutDivider: FlatLabel(boxDividerLabel) {
creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
}
+creditsReleasedByLabel: FlatLabel(defaultFlatLabel) {
+ textFg: windowSubTextFg;
+}
starIconEmoji: IconEmoji {
icon: icon{{ "payments/premium_emoji", creditsBg1 }};
@@ -175,6 +178,7 @@ giftBoxButtonBottomByStars: 18px;
giftBoxButtonPadding: margins(8px, 4px, 8px, 4px);
giftBoxPreviewStickerPadding: margins(10px, 12px, 10px, 16px);
giftBoxPreviewTitlePadding: margins(12px, 4px, 12px, 4px);
+giftBoxReleasedByMargin: margins(12px, 2px, 12px, 2px);
giftBoxPreviewTextPadding: margins(12px, 4px, 12px, 4px);
giftBoxButtonMargin: margins(12px, 8px, 12px, 12px);
giftBoxStickerTop: 0px;
diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style
index 9c3233f0e2..fcd6d2bbb8 100644
--- a/Telegram/SourceFiles/ui/menu_icons.style
+++ b/Telegram/SourceFiles/ui/menu_icons.style
@@ -56,6 +56,7 @@ menuIconDiscussion: icon {{ "menu/discussion", menuIconColor }};
menuIconStats: icon {{ "menu/stats", menuIconColor }};
menuIconBoosts: icon {{ "menu/boosts", menuIconColor }};
menuIconEarn: icon {{ "menu/earn", menuIconColor }};
+menuIconCancelFee: icon {{ "menu/cancel_fee", menuIconColor }};
menuIconCreatePoll: icon {{ "menu/create_poll", menuIconColor }};
menuIconCreateTodoList: icon {{ "menu/select", menuIconColor }};
menuIconQrCode: icon {{ "menu/qr_code", menuIconColor }};
diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style
index 32681d4fd5..168d225ff7 100644
--- a/Telegram/SourceFiles/window/window.style
+++ b/Telegram/SourceFiles/window/window.style
@@ -316,6 +316,14 @@ windowArchiveToast: Toast(defaultToast) {
maxWidth: boxWideWidth;
}
+windowFeeItem: Menu(defaultMenu) {
+ itemPadding: margins(17px, 3px, 17px, 4px);
+ itemRightSkip: 0px;
+ itemStyle: whenReadStyle;
+ itemFgOver: windowFg;
+ itemFgDisabled: windowFg;
+}
+
ivWidthMin: 380px;
ivHeightMin: 480px;
ivWidthDefault: 600px;
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index 8dbfca0fb7..b380a7e63e 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/random.h"
#include "base/options.h"
#include "base/unixtime.h"
+#include "base/unique_qptr.h"
#include "base/qt/qt_key_modifiers.h"
#include "boxes/delete_messages_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 "data/components/scheduled_messages.h"
#include "data/notify/data_notify_settings.h"
+#include "data/stickers/data_custom_emoji.h"
#include "data/data_changes.h"
#include "data/data_session.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 "dialogs/dialogs_key.h"
#include "core/application.h"
+#include "core/ui_integration.h"
#include "export/export_manager.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "boxes/premium_preview_box.h"
#include "styles/style_chat.h"
+#include "styles/style_credits.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_window.h" // st::windowMinWidth
@@ -282,6 +286,7 @@ private:
void fillArchiveActions();
void fillSavedSublistActions();
void fillContextMenuActions();
+ void fillMonoforumPeerActions();
void addHidePromotion();
void addTogglePin();
@@ -326,6 +331,7 @@ private:
void addVideoChat();
void addViewStatistics();
void addBoostChat();
+ void addToggleFee();
[[nodiscard]] bool skipCreateActions() const;
@@ -1365,6 +1371,7 @@ void Filler::fill() {
case Section::Scheduled: fillScheduledActions(); break;
case Section::ContextMenu:
case Section::SubsectionTabsMenu: fillContextMenuActions(); break;
+ case Section::SavedSublist: fillMonoforumPeerActions(); break;
default: Unexpected("_request.section in Filler::fill.");
}
}
@@ -1660,6 +1667,68 @@ void Filler::fillSavedSublistActions() {
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>();
+ _addAction(text, [=] {
+ const auto removeFee = !feeRemoved;
+ PeerMenuConfirmToggleFee(
+ navigation,
+ paidAmount,
+ parent,
+ user,
+ removeFee);
+ }, feeRemoved ? &st::menuIconEarn : &st::menuIconCancelFee);
+ _addAction({ .isSeparator = true });
+ _addAction({ .make = [=](not_null 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(
+ actionParent,
+ st::windowFeeItem,
+ action,
+ nullptr,
+ nullptr);
+ result->setMarkedText(
+ text,
+ QString(),
+ Core::TextContext({ .session = &user->session() }));
+ return result;
+ } });
+}
+
} // namespace
void PeerMenuExportChat(
@@ -3752,4 +3821,81 @@ bool CanArchive(History *history, PeerData *peer) {
return true;
}
+void PeerMenuConfirmToggleFee(
+ not_null navigation,
+ std::shared_ptr> paidAmount,
+ not_null peer,
+ not_null 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 box) {
+ const auto refund = std::make_shared>();
+ Ui::ConfirmBox(box, {
+ .text = tr::lng_payment_refund_text(
+ tr::now,
+ lt_name,
+ Ui::Text::Bold(user->shortName()),
+ Ui::Text::WithEntities),
+ .confirmed = [=](Fn 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
+ >();
+ *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(
+ 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
diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h
index f01801423f..56c4a6baec 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.h
+++ b/Telegram/SourceFiles/window/window_peer_menu.h
@@ -253,4 +253,11 @@ void AddSeparatorAndShiftUp(const PeerMenuCallback &addAction);
[[nodiscard]] bool IsArchived(not_null history);
[[nodiscard]] bool CanArchive(History *history, PeerData *peer);
+void PeerMenuConfirmToggleFee(
+ not_null navigation,
+ std::shared_ptr> paidAmount,
+ not_null peer,
+ not_null user,
+ bool removeFee);
+
} // namespace Window
diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile
index 99fb373035..4df1a8508b 100644
--- a/Telegram/build/docker/centos_env/Dockerfile
+++ b/Telegram/build/docker/centos_env/Dockerfile
@@ -760,7 +760,6 @@ RUN git clone -b v$QT --depth=1 https://github.com/qt/qt5.git \
&& cmake -B build . \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DBUILD_SHARED_LIBS=OFF \
- -DQT_GENERATE_SBOM=OFF \
-DQT_QPA_PLATFORMS="wayland;xcb" \
-DINPUT_libpng=qt \
-DINPUT_harfbuzz=qt \
diff --git a/Telegram/build/version b/Telegram/build/version
index ecab5bf25c..4d8e1cfc6a 100644
--- a/Telegram/build/version
+++ b/Telegram/build/version
@@ -1,7 +1,7 @@
-AppVersion 5016002
+AppVersion 5016003
AppVersionStrMajor 5.16
-AppVersionStrSmall 5.16.2
-AppVersionStr 5.16.2
+AppVersionStrSmall 5.16.3
+AppVersionStr 5.16.3
BetaChannel 0
AlphaVersion 0
-AppVersionOriginal 5.16.2
+AppVersionOriginal 5.16.3
diff --git a/changelog.txt b/changelog.txt
index 91d27aedda..3474436d1e 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -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)
- Fix crash in some checklists.