diff --git a/Telegram/Resources/art/bball_idle.tgs b/Telegram/Resources/animations/dice/bball_idle.tgs
similarity index 100%
rename from Telegram/Resources/art/bball_idle.tgs
rename to Telegram/Resources/animations/dice/bball_idle.tgs
diff --git a/Telegram/Resources/art/dart_idle.tgs b/Telegram/Resources/animations/dice/dart_idle.tgs
similarity index 100%
rename from Telegram/Resources/art/dart_idle.tgs
rename to Telegram/Resources/animations/dice/dart_idle.tgs
diff --git a/Telegram/Resources/art/dice_idle.tgs b/Telegram/Resources/animations/dice/dice_idle.tgs
similarity index 100%
rename from Telegram/Resources/art/dice_idle.tgs
rename to Telegram/Resources/animations/dice/dice_idle.tgs
diff --git a/Telegram/Resources/art/fball_idle.tgs b/Telegram/Resources/animations/dice/fball_idle.tgs
similarity index 100%
rename from Telegram/Resources/art/fball_idle.tgs
rename to Telegram/Resources/animations/dice/fball_idle.tgs
diff --git a/Telegram/Resources/art/slot_0_idle.tgs b/Telegram/Resources/animations/dice/slot_0_idle.tgs
similarity index 100%
rename from Telegram/Resources/art/slot_0_idle.tgs
rename to Telegram/Resources/animations/dice/slot_0_idle.tgs
diff --git a/Telegram/Resources/art/slot_1_idle.tgs b/Telegram/Resources/animations/dice/slot_1_idle.tgs
similarity index 100%
rename from Telegram/Resources/art/slot_1_idle.tgs
rename to Telegram/Resources/animations/dice/slot_1_idle.tgs
diff --git a/Telegram/Resources/art/slot_2_idle.tgs b/Telegram/Resources/animations/dice/slot_2_idle.tgs
similarity index 100%
rename from Telegram/Resources/art/slot_2_idle.tgs
rename to Telegram/Resources/animations/dice/slot_2_idle.tgs
diff --git a/Telegram/Resources/art/slot_back.tgs b/Telegram/Resources/animations/dice/slot_back.tgs
similarity index 100%
rename from Telegram/Resources/art/slot_back.tgs
rename to Telegram/Resources/animations/dice/slot_back.tgs
diff --git a/Telegram/Resources/art/slot_pull.tgs b/Telegram/Resources/animations/dice/slot_pull.tgs
similarity index 100%
rename from Telegram/Resources/art/slot_pull.tgs
rename to Telegram/Resources/animations/dice/slot_pull.tgs
diff --git a/Telegram/Resources/art/winners.tgs b/Telegram/Resources/animations/dice/winners.tgs
similarity index 100%
rename from Telegram/Resources/art/winners.tgs
rename to Telegram/Resources/animations/dice/winners.tgs
diff --git a/Telegram/Resources/animations/star_reaction/appear.tgs b/Telegram/Resources/animations/star_reaction/appear.tgs
new file mode 100644
index 000000000..e47d5492e
Binary files /dev/null and b/Telegram/Resources/animations/star_reaction/appear.tgs differ
diff --git a/Telegram/Resources/animations/star_reaction/effect.tgs b/Telegram/Resources/animations/star_reaction/effect.tgs
new file mode 100644
index 000000000..0b87a225a
Binary files /dev/null and b/Telegram/Resources/animations/star_reaction/effect.tgs differ
diff --git a/Telegram/Resources/animations/star_reaction/select.tgs b/Telegram/Resources/animations/star_reaction/select.tgs
new file mode 100644
index 000000000..cf8fc6758
Binary files /dev/null and b/Telegram/Resources/animations/star_reaction/select.tgs differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 1b6ab0ea1..f45253adf 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -1550,6 +1550,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_reactions_max_slider#other" = "{count} reactions per post";
"lng_manage_peer_reactions_max_about" = "Limit the number of different reactions that can be added to a post, including already published ones.";
+"lng_manage_peer_reactions_paid" = "Enable Paid Reactions";
+"lng_manage_peer_reactions_paid_about" = "Switch this on to let your subscribers set paid reactions with Telegram Stars, which you will be able to withdraw later as TON. {link}";
+"lng_manage_peer_reactions_paid_link" = "Learn more >";
+
"lng_manage_peer_antispam" = "Aggressive Anti-Spam";
"lng_manage_peer_antispam_about" = "Telegram will filter more spam but may occasionally affect ordinary messages. You can report False Positives in Recent Actions.";
"lng_manage_peer_antispam_not_enough#one" = "Aggressive filtering can be enabled only in groups with more than **{count} member**.";
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index 0d6663d8f..18a9e0734 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -28,5 +28,20 @@
../../animations/collectible_phone.tgs
../../animations/search.tgs
../../animations/noresults.tgs
+
+ ../../animations/dice/dice_idle.tgs
+ ../../animations/dice/dart_idle.tgs
+ ../../animations/dice/bball_idle.tgs
+ ../../animations/dice/fball_idle.tgs
+ ../../animations/dice/slot_0_idle.tgs
+ ../../animations/dice/slot_1_idle.tgs
+ ../../animations/dice/slot_2_idle.tgs
+ ../../animations/dice/slot_back.tgs
+ ../../animations/dice/slot_pull.tgs
+ ../../animations/dice/winners.tgs
+
+ ../../animations/star_reaction/appear.tgs
+ ../../animations/star_reaction/effect.tgs
+ ../../animations/star_reaction/select.tgs
diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc
index ae6edc957..e83243d33 100644
--- a/Telegram/Resources/qrc/telegram/telegram.qrc
+++ b/Telegram/Resources/qrc/telegram/telegram.qrc
@@ -7,16 +7,6 @@
../../art/logo_256.png
../../art/logo_256_no_margin.png
../../art/themeimage.jpg
- ../../art/dice_idle.tgs
- ../../art/dart_idle.tgs
- ../../art/bball_idle.tgs
- ../../art/fball_idle.tgs
- ../../art/slot_0_idle.tgs
- ../../art/slot_1_idle.tgs
- ../../art/slot_2_idle.tgs
- ../../art/slot_back.tgs
- ../../art/slot_pull.tgs
- ../../art/winners.tgs
../../day-blue.tdesktop-theme
../../night.tdesktop-theme
../../night-green.tdesktop-theme
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp
index 226e30af2..2f1338cfb 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp
@@ -608,6 +608,7 @@ void EditAllowedReactionsBox(
rpl::variable selectorState;
std::vector selected;
rpl::variable customCount;
+ bool paidEnabled = false;
};
const auto allowed = args.allowed;
const auto optionInitial = (allowed.type != AllowedReactionsType::Some)
@@ -617,6 +618,7 @@ void EditAllowedReactionsBox(
: Option::Some;
const auto state = box->lifetime().make_state(State{
.option = optionInitial,
+ .paidEnabled = allowed.paidEnabled,
});
const auto container = box->verticalLayout();
@@ -847,6 +849,29 @@ void EditAllowedReactionsBox(
) | rpl::map(rpl::mappers::_1 == SelectorState::Active)));
Ui::AddDividerText(inner, tr::lng_manage_peer_reactions_max_about());
+
+ Ui::AddSkip(inner);
+ const auto paid = inner->add(object_ptr(
+ inner,
+ tr::lng_manage_peer_reactions_paid(),
+ st::manageGroupNoIconButton.button));
+ paid->toggleOn(rpl::single(allowed.paidEnabled));
+ paid->toggledValue(
+ ) | rpl::start_with_next([=](bool value) {
+ state->paidEnabled = value;
+ }, paid->lifetime());
+ Ui::AddSkip(inner);
+
+ Ui::AddDividerText(
+ inner,
+ tr::lng_manage_peer_reactions_paid_about(
+ lt_link,
+ tr::lng_manage_peer_reactions_paid_link([=](QString text) {
+ return Ui::Text::Link(
+ text,
+ u"https://telegram.org/tos/stars"_q);
+ }),
+ Ui::Text::WithEntities));
}
const auto collect = [=] {
auto result = AllowedReactions();
@@ -856,6 +881,9 @@ void EditAllowedReactionsBox(
: (enabled->toggled())) {
result.some = state->selected;
}
+ if (!isGroup && enabled->toggled()) {
+ result.paidEnabled = state->paidEnabled;
+ }
auto some = result.some;
auto simple = all | ranges::views::transform(
&Data::Reaction::id
@@ -907,16 +935,20 @@ void SaveAllowedReactions(
: allowed.some.empty()
? MTP_chatReactionsNone()
: MTP_chatReactionsSome(MTP_vector(ids));
+ const auto editPaidEnabled = peer->isBroadcast();
+ const auto paidEnabled = editPaidEnabled && allowed.paidEnabled;
+ const auto maxCount = allowed.maxCount;
peer->session().api().request(MTPmessages_SetChatAvailableReactions(
- allowed.maxCount ? MTP_flags(Flag::f_reactions_limit) : MTP_flags(0),
+ MTP_flags(Flag()
+ | (maxCount ? Flag::f_reactions_limit : Flag())
+ | (editPaidEnabled ? Flag::f_paid_enabled : Flag())),
peer->input,
updated,
- MTP_int(allowed.maxCount),
- MTPbool() // paid_enabled
+ MTP_int(maxCount),
+ MTP_bool(paidEnabled)
)).done([=](const MTPUpdates &result) {
peer->session().api().applyUpdates(result);
- auto parsed = Data::Parse(updated);
- parsed.maxCount = allowed.maxCount;
+ auto parsed = Data::Parse(updated, maxCount, paidEnabled);
if (const auto chat = peer->asChat()) {
chat->setAllowedReactions(parsed);
} else if (const auto channel = peer->asChannel()) {
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp
index 69aaddee1..8941f35aa 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp
+++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp
@@ -8,11 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_dice_pack.h"
#include "main/main_session.h"
+#include "chat_helpers/stickers_lottie.h"
#include "data/data_session.h"
#include "data/data_document.h"
-#include "ui/chat/attach/attach_prepare.h"
-#include "ui/image/image_location_factory.h"
-#include "storage/localimageloader.h"
#include "base/unixtime.h"
#include "apiwrap.h"
@@ -104,6 +102,11 @@ void DicePack::tryGenerateLocalZero() {
return;
}
+ const auto generateLocal = [&](int index, const QString &name) {
+ _map.emplace(
+ index,
+ ChatHelpers::GenerateLocalTgsSticker(_session, name));
+ };
if (_emoji == DicePacks::kDiceString) {
generateLocal(0, u"dice_idle"_q);
} else if (_emoji == DicePacks::kDartString) {
@@ -123,32 +126,8 @@ void DicePack::tryGenerateLocalZero() {
}
}
-void DicePack::generateLocal(int index, const QString &name) {
- const auto path = u":/gui/art/"_q + name + u".tgs"_q;
- auto task = FileLoadTask(
- _session,
- path,
- QByteArray(),
- nullptr,
- SendMediaType::File,
- FileLoadTo(0, {}, {}, 0),
- {},
- false);
- task.process({ .generateGoodThumbnail = false });
- const auto result = task.peekResult();
- Assert(result != nullptr);
- const auto document = _session->data().processDocument(
- result->document,
- Images::FromImageInMemory(result->thumb, "WEBP", result->thumbbytes));
- document->setLocation(Core::FileLocation(path));
-
- _map.emplace(index, document);
-
- Ensures(document->sticker());
- Ensures(document->sticker()->isLottie());
-}
-
-DicePacks::DicePacks(not_null session) : _session(session) {
+DicePacks::DicePacks(not_null session)
+: _session(session) {
}
DocumentData *DicePacks::lookup(const QString &emoji, int value) {
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h
index 3a1c49f46..cb5210c35 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h
+++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h
@@ -26,7 +26,6 @@ private:
void load();
void applySet(const MTPDmessages_stickerSet &data);
void tryGenerateLocalZero();
- void generateLocal(int index, const QString &name);
const not_null _session;
QString _emoji;
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp
index 5e2aceb7a..314a99a8a 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp
+++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp
@@ -15,9 +15,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "storage/cache/storage_cache_database.h"
+#include "storage/localimageloader.h"
#include "history/view/media/history_view_media_common.h"
#include "media/clip/media_clip_reader.h"
+#include "ui/chat/attach/attach_prepare.h"
#include "ui/effects/path_shift_gradient.h"
+#include "ui/image/image_location_factory.h"
#include "ui/painter.h"
#include "main/main_session.h"
@@ -312,4 +315,33 @@ QSize ComputeStickerSize(not_null document, QSize box) {
return HistoryView::NonEmptySize(request.size(dimensions, 8) / ratio);
}
+not_null GenerateLocalTgsSticker(
+ not_null session,
+ const QString &name) {
+ const auto path = u":/animations/"_q + name + u".tgs"_q;
+ auto task = FileLoadTask(
+ session,
+ path,
+ QByteArray(),
+ nullptr,
+ SendMediaType::File,
+ FileLoadTo(0, {}, {}, 0),
+ {},
+ false);
+ task.process({ .generateGoodThumbnail = false });
+ const auto result = task.peekResult();
+ Assert(result != nullptr);
+ const auto document = session->data().processDocument(
+ result->document,
+ Images::FromImageInMemory(
+ result->thumb,
+ "WEBP",
+ result->thumbbytes));
+ document->setLocation(Core::FileLocation(path));
+
+ Ensures(document->sticker());
+ Ensures(document->sticker()->isLottie());
+ return document;
+}
+
} // namespace ChatHelpers
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_lottie.h b/Telegram/SourceFiles/chat_helpers/stickers_lottie.h
index 7bd24ac8f..e659329dd 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.h
+++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.h
@@ -130,4 +130,8 @@ bool PaintStickerThumbnailPath(
not_null document,
QSize box);
+[[nodiscard]] not_null GenerateLocalTgsSticker(
+ not_null session,
+ const QString &name);
+
} // namespace ChatHelpers
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index 6162024fd..28c1a1b38 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -1220,11 +1220,16 @@ void ApplyChannelUpdate(
const auto reactionsLimit = update.vreactions_limit().value_or_empty();
if (const auto allowed = update.vavailable_reactions()) {
- auto parsed = Data::Parse(*allowed);
- parsed.maxCount = reactionsLimit;
+ auto parsed = Data::Parse(
+ *allowed,
+ reactionsLimit,
+ update.is_paid_reactions_available());
channel->setAllowedReactions(std::move(parsed));
} else {
- channel->setAllowedReactions({ .maxCount = reactionsLimit });
+ channel->setAllowedReactions({
+ .maxCount = reactionsLimit,
+ .paidEnabled = update.is_paid_reactions_available(),
+ });
}
channel->owner().stories().apply(channel, update.vstories());
channel->fullUpdated();
diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp
index 76aa1f714..b5f5bcf66 100644
--- a/Telegram/SourceFiles/data/data_chat.cpp
+++ b/Telegram/SourceFiles/data/data_chat.cpp
@@ -486,8 +486,8 @@ void ApplyChatUpdate(not_null chat, const MTPDchatFull &update) {
chat->setTranslationDisabled(update.is_translations_disabled());
const auto reactionsLimit = update.vreactions_limit().value_or_empty();
if (const auto allowed = update.vavailable_reactions()) {
- auto parsed = Data::Parse(*allowed);
- parsed.maxCount = reactionsLimit;
+ const auto paidEnabled = false;
+ auto parsed = Data::Parse(*allowed, reactionsLimit, paidEnabled);
chat->setAllowedReactions(std::move(parsed));
} else {
chat->setAllowedReactions({ .maxCount = reactionsLimit });
diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp
index 2b42a740d..c2b92de24 100644
--- a/Telegram/SourceFiles/data/data_message_reactions.cpp
+++ b/Telegram/SourceFiles/data/data_message_reactions.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_message_reactions.h"
+#include "chat_helpers/stickers_lottie.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
@@ -29,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_config.h"
#include "base/timer_rpl.h"
#include "base/call_delayed.h"
+#include "base/unixtime.h"
#include "apiwrap.h"
#include "styles/style_chat.h"
@@ -204,9 +206,13 @@ PossibleItemReactionsRef LookupPossibleReactions(
}
} else {
const auto &allowed = PeerAllowedReactions(peer);
- result.recent.reserve((allowed.type == AllowedReactionsType::Some)
- ? allowed.some.size()
- : full.size());
+ result.recent.reserve((allowed.paidEnabled ? 1 : 0)
+ + ((allowed.type == AllowedReactionsType::Some)
+ ? allowed.some.size()
+ : full.size()));
+ if (allowed.paidEnabled) {
+ result.recent.push_back(reactions->lookupPaid());
+ }
add([&](const Reaction &reaction) {
const auto id = reaction.id;
if (id.custom() && !premiumPossible) {
@@ -569,7 +575,7 @@ rpl::producer<> Reactions::effectsUpdates() const {
}
void Reactions::preloadReactionImageFor(const ReactionId &emoji) {
- if (!emoji.emoji().isEmpty()) {
+ if (emoji.paid() || !emoji.emoji().isEmpty()) {
preloadImageFor(emoji);
}
}
@@ -586,6 +592,10 @@ void Reactions::preloadImageFor(const ReactionId &id) {
}
auto &set = _images.emplace(id).first->second;
set.effect = (id.custom() != 0);
+ if (id.paid()) {
+ loadImage(set, lookupPaid()->selectAnimation, true);
+ return;
+ }
auto &list = set.effect ? _effects : _available;
const auto i = ranges::find(list, id, &Reaction::id);
const auto document = (i == end(list))
@@ -1356,7 +1366,10 @@ void Reactions::send(not_null item, bool addToRecent) {
MTP_flags(flags),
item->history()->peer->input,
MTP_int(id.msg),
- MTP_vector(chosen | ranges::views::transform(
+ MTP_vector(chosen | ranges::views::filter([](
+ const ReactionId &id) {
+ return !id.paid();
+ }) | ranges::views::transform(
ReactionToMTP
) | ranges::to>())
)).done([=](const MTPUpdates &result) {
@@ -1367,6 +1380,21 @@ void Reactions::send(not_null item, bool addToRecent) {
}).send();
}
+void Reactions::sendPaid(not_null item, int count) {
+ const auto id = item->fullId();
+ const auto randomId = base::unixtime::mtproto_msg_id();
+ auto &api = _owner->session().api();
+ api.request(MTPmessages_SendPaidReaction(
+ item->history()->peer->input,
+ MTP_int(id.msg),
+ MTP_int(count),
+ MTP_long(randomId)
+ )).done([=](const MTPUpdates &result) {
+ _owner->session().api().applyUpdates(result);
+ }).fail([=](const MTP::Error &error) {
+ }).send();
+}
+
void Reactions::poll(not_null item, crl::time now) {
// Group them by one second.
const auto last = item->lastReactionsRefreshTime();
@@ -1403,7 +1431,9 @@ void Reactions::clearTemporary() {
}
Reaction *Reactions::lookupTemporary(const ReactionId &id) {
- if (const auto emoji = id.emoji(); !emoji.isEmpty()) {
+ if (id.paid()) {
+ return lookupPaid();
+ } else if (const auto emoji = id.emoji(); !emoji.isEmpty()) {
const auto i = ranges::find(_available, id, &Reaction::id);
return (i != end(_available)) ? &*i : nullptr;
} else if (const auto customId = id.custom()) {
@@ -1424,6 +1454,26 @@ Reaction *Reactions::lookupTemporary(const ReactionId &id) {
return nullptr;
}
+not_null Reactions::lookupPaid() {
+ if (!_paid) {
+ const auto generate = [&](const QString &name) {
+ const auto session = &_owner->session();
+ return ChatHelpers::GenerateLocalTgsSticker(session, name);
+ };
+ const auto select = generate(u"star_reaction_select"_q);
+ _paid.emplace(Reaction{
+ .id = ReactionId::Paid(),
+ .title = u"Telegram Star"_q,
+ .appearAnimation = generate(u"star_reaction_appear"_q),
+ .selectAnimation = select,
+ .centerIcon = select,
+ //.aroundAnimation = generate(u"star_reaction_effect"_q),
+ .active = true,
+ });
+ }
+ return &*_paid;
+}
+
rpl::producer> Reactions::myTagsValue(
SavedSublist *sublist) {
refreshMyTags(sublist);
@@ -1529,6 +1579,25 @@ MessageReactions::MessageReactions(not_null item)
: _item(item) {
}
+void MessageReactions::addPaid(int count) {
+ Expects(_item->history()->peer->isBroadcast());
+
+ const auto id = Data::ReactionId::Paid();
+ const auto history = _item->history();
+ const auto peer = history->peer;
+ const auto i = ranges::find(_list, id, &MessageReaction::id);
+ if (i != end(_list)) {
+ i->my = true;
+ i->count += count;
+ std::rotate(i, i + 1, end(_list));
+ } else {
+ _list.push_back({ .id = id, .count = count, .my = true });
+ }
+ auto &owner = history->owner();
+ owner.reactions().sendPaid(_item, count);
+ owner.notifyItemDataChange(_item);
+}
+
void MessageReactions::add(const ReactionId &id, bool addToRecent) {
Expects(!id.empty());
diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h
index 793fcc198..fff6fcf04 100644
--- a/Telegram/SourceFiles/data/data_message_reactions.h
+++ b/Telegram/SourceFiles/data/data_message_reactions.h
@@ -129,6 +129,7 @@ public:
void preloadAnimationsFor(const ReactionId &emoji);
void send(not_null item, bool addToRecent);
+ void sendPaid(not_null item, int count);
[[nodiscard]] bool sending(not_null item) const;
void poll(not_null item, crl::time now);
@@ -137,6 +138,7 @@ public:
void clearTemporary();
[[nodiscard]] Reaction *lookupTemporary(const ReactionId &id);
+ [[nodiscard]] not_null lookupPaid();
[[nodiscard]] rpl::producer> myTagsValue(
SavedSublist *sublist = nullptr);
@@ -275,6 +277,7 @@ private:
// So we use std::map instead of base::flat_map here.
// Otherwise we could use flat_map>.
std::map _temporary;
+ std::optional _paid;
base::Timer _topRefreshTimer;
mtpRequestId _topRequestId = 0;
@@ -333,6 +336,7 @@ public:
explicit MessageReactions(not_null item);
void add(const ReactionId &id, bool addToRecent);
+ void addPaid(int count);
void remove(const ReactionId &id);
bool change(
const QVector &list,
diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp
index 064f8d91b..265e9f0a9 100644
--- a/Telegram/SourceFiles/data/data_peer.cpp
+++ b/Telegram/SourceFiles/data/data_peer.cpp
@@ -95,28 +95,22 @@ bool ApplyBotMenuButton(
return changed;
}
-bool operator<(
- const AllowedReactions &a,
- const AllowedReactions &b) {
- return (a.type < b.type) || ((a.type == b.type) && (a.some < b.some));
-}
-
-bool operator==(
- const AllowedReactions &a,
- const AllowedReactions &b) {
- return (a.type == b.type)
- && (a.some == b.some)
- && (a.maxCount == b.maxCount);
-}
-
-AllowedReactions Parse(const MTPChatReactions &value) {
+AllowedReactions Parse(
+ const MTPChatReactions &value,
+ int maxCount,
+ bool paidEnabled) {
return value.match([&](const MTPDchatReactionsNone &) {
- return AllowedReactions();
+ return AllowedReactions{
+ .maxCount = maxCount,
+ .paidEnabled = paidEnabled,
+ };
}, [&](const MTPDchatReactionsAll &data) {
return AllowedReactions{
+ .maxCount = maxCount,
.type = (data.is_allow_custom()
? AllowedReactionsType::All
: AllowedReactionsType::Default),
+ .paidEnabled = paidEnabled,
};
}, [&](const MTPDchatReactionsSome &data) {
return AllowedReactions{
@@ -125,7 +119,9 @@ AllowedReactions Parse(const MTPChatReactions &value) {
) | ranges::views::transform(
ReactionFromMTP
) | ranges::to_vector,
+ .maxCount = maxCount,
.type = AllowedReactionsType::Some,
+ .paidEnabled = paidEnabled,
};
});
}
diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h
index 563f5a068..b884f679a 100644
--- a/Telegram/SourceFiles/data/data_peer.h
+++ b/Telegram/SourceFiles/data/data_peer.h
@@ -101,7 +101,7 @@ bool ApplyBotMenuButton(
not_null info,
const MTPBotMenuButton *button);
-enum class AllowedReactionsType {
+enum class AllowedReactionsType : uchar {
All,
Default,
Some,
@@ -109,14 +109,19 @@ enum class AllowedReactionsType {
struct AllowedReactions {
std::vector some;
- AllowedReactionsType type = AllowedReactionsType::Some;
int maxCount = 0;
+ AllowedReactionsType type = AllowedReactionsType::Some;
+ bool paidEnabled = false;
+
+ friend inline bool operator==(
+ const AllowedReactions &,
+ const AllowedReactions &) = default;
};
-bool operator<(const AllowedReactions &a, const AllowedReactions &b);
-bool operator==(const AllowedReactions &a, const AllowedReactions &b);
-
-[[nodiscard]] AllowedReactions Parse(const MTPChatReactions &value);
+[[nodiscard]] AllowedReactions Parse(
+ const MTPChatReactions &value,
+ int maxCount,
+ bool paidEnabled);
[[nodiscard]] PeerData *PeerFromInputMTP(
not_null owner,
const MTPInputPeer &input);
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 7838dda79..5a8e833a2 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/reaction_fly_animation.h"
#include "ui/text/text_options.h"
#include "ui/text/text_isolated_emoji.h"
+#include "ui/boxes/confirm_box.h"
#include "ui/boxes/edit_factcheck_box.h"
#include "ui/boxes/report_box.h"
#include "ui/layers/generic_box.h"
@@ -488,6 +489,15 @@ void HistoryInner::reactionChosen(const ChosenReaction &reaction) {
const auto item = session().data().message(reaction.context);
if (!item) {
return;
+ } else if (reaction.id.paid()) {
+ _controller->show(Ui::MakeConfirmBox({
+ .text = u"Send 10-stars reaction?"_q,
+ .confirmed = [=](Fn close) {
+ item->addPaidReaction(10, HistoryItem::ReactionSource::Selector);
+ close();
+ },
+ }));
+ return;
} else if (Window::ShowReactPremiumError(
_controller,
item,
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index a38b9b33f..e40947033 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -2514,6 +2514,16 @@ bool HistoryItem::canReact() const {
return true;
}
+void HistoryItem::addPaidReaction(int count, ReactionSource source) {
+ Expects(_history->peer->isBroadcast());
+
+ if (!_reactions) {
+ _reactions = std::make_unique(this);
+ }
+ _reactions->addPaid(count);
+ _history->owner().notifyItemDataChange(this);
+}
+
void HistoryItem::toggleReaction(
const Data::ReactionId &reaction,
ReactionSource source) {
@@ -2530,7 +2540,6 @@ void HistoryItem::toggleReaction(
if (_reactions->empty()) {
_reactions = nullptr;
_flags &= ~MessageFlag::CanViewReactions;
- _history->owner().notifyItemDataChange(this);
}
} else {
_reactions->add(reaction, (source == ReactionSource::Selector));
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index e4ed45cf9..9e852c77e 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -443,6 +443,7 @@ public:
void toggleReaction(
const Data::ReactionId &reaction,
ReactionSource source);
+ void addPaidReaction(int count, ReactionSource source);
void updateReactionsUnknown();
[[nodiscard]] auto reactions() const
-> const std::vector &;
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index e1d67952e..14597582d 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -3306,7 +3306,12 @@ void Message::refreshReactions() {
ClickContext context) {
if (const auto strong = weak.get()) {
const auto item = strong->data();
- if (item->reactionsAreTags()) {
+ if (id.paid()) {
+ item->addPaidReaction(
+ 1,
+ HistoryItem::ReactionSource::Existing);
+ return;
+ } else if (item->reactionsAreTags()) {
if (item->history()->session().premium()) {
const auto tag = Data::SearchTagToQuery(id);
HashtagClickHandler(tag).onClick(context);
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
index 3a79d50b7..7dcf5929b 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
@@ -144,6 +144,10 @@ void InlineList::layoutButtons() {
return true;
} else if (acount < bcount) {
return false;
+ } else if (b->id.paid()) {
+ return false;
+ } else if (a->id.paid()) {
+ return true;
}
return ranges::find(list, a->id, &::Data::Reaction::id)
< ranges::find(list, b->id, &::Data::Reaction::id);
diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp
index 395e38fae..e26c8c9d1 100644
--- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp
+++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp
@@ -86,6 +86,11 @@ ReactionFlyAnimation::ReactionFlyAnimation(
_customSize = esize;
_centerSizeMultiplier = _customSize / float64(size);
aroundAnimation = owner->chooseGenericAnimation(document);
+ } else if (args.id.paid()) {
+ const auto fake = owner->lookupPaid();
+ centerIcon = fake->centerIcon;
+ aroundAnimation = fake->aroundAnimation;
+ _centerSizeMultiplier = 1.;// fake->centerIcon ? 1. : 0.5;
} else {
const auto i = ranges::find(list, args.id, &::Data::Reaction::id);
if (i == end(list)/* || !i->centerIcon*/) {