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*/) {