mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Allow enabling paid reactions.
This commit is contained in:
parent
126fd89bb7
commit
bb3fc17489
33 changed files with 248 additions and 80 deletions
BIN
Telegram/Resources/animations/star_reaction/appear.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/appear.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/star_reaction/effect.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/effect.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/star_reaction/select.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/select.tgs
Normal file
Binary file not shown.
|
@ -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_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_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" = "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_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**.";
|
"lng_manage_peer_antispam_not_enough#one" = "Aggressive filtering can be enabled only in groups with more than **{count} member**.";
|
||||||
|
|
|
@ -28,5 +28,20 @@
|
||||||
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
|
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
|
||||||
<file alias="search.tgs">../../animations/search.tgs</file>
|
<file alias="search.tgs">../../animations/search.tgs</file>
|
||||||
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
|
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
|
||||||
|
|
||||||
|
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
|
||||||
|
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
|
||||||
|
<file alias="bball_idle.tgs">../../animations/dice/bball_idle.tgs</file>
|
||||||
|
<file alias="fball_idle.tgs">../../animations/dice/fball_idle.tgs</file>
|
||||||
|
<file alias="slot_0_idle.tgs">../../animations/dice/slot_0_idle.tgs</file>
|
||||||
|
<file alias="slot_1_idle.tgs">../../animations/dice/slot_1_idle.tgs</file>
|
||||||
|
<file alias="slot_2_idle.tgs">../../animations/dice/slot_2_idle.tgs</file>
|
||||||
|
<file alias="slot_back.tgs">../../animations/dice/slot_back.tgs</file>
|
||||||
|
<file alias="slot_pull.tgs">../../animations/dice/slot_pull.tgs</file>
|
||||||
|
<file alias="winners.tgs">../../animations/dice/winners.tgs</file>
|
||||||
|
|
||||||
|
<file alias="star_reaction_appear.tgs">../../animations/star_reaction/appear.tgs</file>
|
||||||
|
<file alias="star_reaction_effect.tgs">../../animations/star_reaction/effect.tgs</file>
|
||||||
|
<file alias="star_reaction_select.tgs">../../animations/star_reaction/select.tgs</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -7,16 +7,6 @@
|
||||||
<file alias="art/logo_256.png">../../art/logo_256.png</file>
|
<file alias="art/logo_256.png">../../art/logo_256.png</file>
|
||||||
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
|
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
|
||||||
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>
|
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>
|
||||||
<file alias="art/dice_idle.tgs">../../art/dice_idle.tgs</file>
|
|
||||||
<file alias="art/dart_idle.tgs">../../art/dart_idle.tgs</file>
|
|
||||||
<file alias="art/bball_idle.tgs">../../art/bball_idle.tgs</file>
|
|
||||||
<file alias="art/fball_idle.tgs">../../art/fball_idle.tgs</file>
|
|
||||||
<file alias="art/slot_0_idle.tgs">../../art/slot_0_idle.tgs</file>
|
|
||||||
<file alias="art/slot_1_idle.tgs">../../art/slot_1_idle.tgs</file>
|
|
||||||
<file alias="art/slot_2_idle.tgs">../../art/slot_2_idle.tgs</file>
|
|
||||||
<file alias="art/slot_back.tgs">../../art/slot_back.tgs</file>
|
|
||||||
<file alias="art/slot_pull.tgs">../../art/slot_pull.tgs</file>
|
|
||||||
<file alias="art/winners.tgs">../../art/winners.tgs</file>
|
|
||||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||||
|
|
|
@ -608,6 +608,7 @@ void EditAllowedReactionsBox(
|
||||||
rpl::variable<SelectorState> selectorState;
|
rpl::variable<SelectorState> selectorState;
|
||||||
std::vector<Data::ReactionId> selected;
|
std::vector<Data::ReactionId> selected;
|
||||||
rpl::variable<int> customCount;
|
rpl::variable<int> customCount;
|
||||||
|
bool paidEnabled = false;
|
||||||
};
|
};
|
||||||
const auto allowed = args.allowed;
|
const auto allowed = args.allowed;
|
||||||
const auto optionInitial = (allowed.type != AllowedReactionsType::Some)
|
const auto optionInitial = (allowed.type != AllowedReactionsType::Some)
|
||||||
|
@ -617,6 +618,7 @@ void EditAllowedReactionsBox(
|
||||||
: Option::Some;
|
: Option::Some;
|
||||||
const auto state = box->lifetime().make_state<State>(State{
|
const auto state = box->lifetime().make_state<State>(State{
|
||||||
.option = optionInitial,
|
.option = optionInitial,
|
||||||
|
.paidEnabled = allowed.paidEnabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
const auto container = box->verticalLayout();
|
const auto container = box->verticalLayout();
|
||||||
|
@ -847,6 +849,29 @@ void EditAllowedReactionsBox(
|
||||||
) | rpl::map(rpl::mappers::_1 == SelectorState::Active)));
|
) | rpl::map(rpl::mappers::_1 == SelectorState::Active)));
|
||||||
|
|
||||||
Ui::AddDividerText(inner, tr::lng_manage_peer_reactions_max_about());
|
Ui::AddDividerText(inner, tr::lng_manage_peer_reactions_max_about());
|
||||||
|
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
const auto paid = inner->add(object_ptr<Ui::SettingsButton>(
|
||||||
|
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 = [=] {
|
const auto collect = [=] {
|
||||||
auto result = AllowedReactions();
|
auto result = AllowedReactions();
|
||||||
|
@ -856,6 +881,9 @@ void EditAllowedReactionsBox(
|
||||||
: (enabled->toggled())) {
|
: (enabled->toggled())) {
|
||||||
result.some = state->selected;
|
result.some = state->selected;
|
||||||
}
|
}
|
||||||
|
if (!isGroup && enabled->toggled()) {
|
||||||
|
result.paidEnabled = state->paidEnabled;
|
||||||
|
}
|
||||||
auto some = result.some;
|
auto some = result.some;
|
||||||
auto simple = all | ranges::views::transform(
|
auto simple = all | ranges::views::transform(
|
||||||
&Data::Reaction::id
|
&Data::Reaction::id
|
||||||
|
@ -907,16 +935,20 @@ void SaveAllowedReactions(
|
||||||
: allowed.some.empty()
|
: allowed.some.empty()
|
||||||
? MTP_chatReactionsNone()
|
? MTP_chatReactionsNone()
|
||||||
: MTP_chatReactionsSome(MTP_vector<MTPReaction>(ids));
|
: MTP_chatReactionsSome(MTP_vector<MTPReaction>(ids));
|
||||||
|
const auto editPaidEnabled = peer->isBroadcast();
|
||||||
|
const auto paidEnabled = editPaidEnabled && allowed.paidEnabled;
|
||||||
|
const auto maxCount = allowed.maxCount;
|
||||||
peer->session().api().request(MTPmessages_SetChatAvailableReactions(
|
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,
|
peer->input,
|
||||||
updated,
|
updated,
|
||||||
MTP_int(allowed.maxCount),
|
MTP_int(maxCount),
|
||||||
MTPbool() // paid_enabled
|
MTP_bool(paidEnabled)
|
||||||
)).done([=](const MTPUpdates &result) {
|
)).done([=](const MTPUpdates &result) {
|
||||||
peer->session().api().applyUpdates(result);
|
peer->session().api().applyUpdates(result);
|
||||||
auto parsed = Data::Parse(updated);
|
auto parsed = Data::Parse(updated, maxCount, paidEnabled);
|
||||||
parsed.maxCount = allowed.maxCount;
|
|
||||||
if (const auto chat = peer->asChat()) {
|
if (const auto chat = peer->asChat()) {
|
||||||
chat->setAllowedReactions(parsed);
|
chat->setAllowedReactions(parsed);
|
||||||
} else if (const auto channel = peer->asChannel()) {
|
} else if (const auto channel = peer->asChannel()) {
|
||||||
|
|
|
@ -8,11 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "chat_helpers/stickers_dice_pack.h"
|
#include "chat_helpers/stickers_dice_pack.h"
|
||||||
|
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "chat_helpers/stickers_lottie.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_document.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 "base/unixtime.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
|
||||||
|
@ -104,6 +102,11 @@ void DicePack::tryGenerateLocalZero() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto generateLocal = [&](int index, const QString &name) {
|
||||||
|
_map.emplace(
|
||||||
|
index,
|
||||||
|
ChatHelpers::GenerateLocalTgsSticker(_session, name));
|
||||||
|
};
|
||||||
if (_emoji == DicePacks::kDiceString) {
|
if (_emoji == DicePacks::kDiceString) {
|
||||||
generateLocal(0, u"dice_idle"_q);
|
generateLocal(0, u"dice_idle"_q);
|
||||||
} else if (_emoji == DicePacks::kDartString) {
|
} else if (_emoji == DicePacks::kDartString) {
|
||||||
|
@ -123,32 +126,8 @@ void DicePack::tryGenerateLocalZero() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DicePack::generateLocal(int index, const QString &name) {
|
DicePacks::DicePacks(not_null<Main::Session*> session)
|
||||||
const auto path = u":/gui/art/"_q + name + u".tgs"_q;
|
: _session(session) {
|
||||||
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<Main::Session*> session) : _session(session) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentData *DicePacks::lookup(const QString &emoji, int value) {
|
DocumentData *DicePacks::lookup(const QString &emoji, int value) {
|
||||||
|
|
|
@ -26,7 +26,6 @@ private:
|
||||||
void load();
|
void load();
|
||||||
void applySet(const MTPDmessages_stickerSet &data);
|
void applySet(const MTPDmessages_stickerSet &data);
|
||||||
void tryGenerateLocalZero();
|
void tryGenerateLocalZero();
|
||||||
void generateLocal(int index, const QString &name);
|
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
QString _emoji;
|
QString _emoji;
|
||||||
|
|
|
@ -15,9 +15,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
#include "storage/cache/storage_cache_database.h"
|
#include "storage/cache/storage_cache_database.h"
|
||||||
|
#include "storage/localimageloader.h"
|
||||||
#include "history/view/media/history_view_media_common.h"
|
#include "history/view/media/history_view_media_common.h"
|
||||||
#include "media/clip/media_clip_reader.h"
|
#include "media/clip/media_clip_reader.h"
|
||||||
|
#include "ui/chat/attach/attach_prepare.h"
|
||||||
#include "ui/effects/path_shift_gradient.h"
|
#include "ui/effects/path_shift_gradient.h"
|
||||||
|
#include "ui/image/image_location_factory.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
|
||||||
|
@ -312,4 +315,33 @@ QSize ComputeStickerSize(not_null<DocumentData*> document, QSize box) {
|
||||||
return HistoryView::NonEmptySize(request.size(dimensions, 8) / ratio);
|
return HistoryView::NonEmptySize(request.size(dimensions, 8) / ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
not_null<DocumentData*> GenerateLocalTgsSticker(
|
||||||
|
not_null<Main::Session*> 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
|
} // namespace ChatHelpers
|
||||||
|
|
|
@ -130,4 +130,8 @@ bool PaintStickerThumbnailPath(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
QSize box);
|
QSize box);
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<DocumentData*> GenerateLocalTgsSticker(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const QString &name);
|
||||||
|
|
||||||
} // namespace ChatHelpers
|
} // namespace ChatHelpers
|
||||||
|
|
|
@ -1220,11 +1220,16 @@ void ApplyChannelUpdate(
|
||||||
|
|
||||||
const auto reactionsLimit = update.vreactions_limit().value_or_empty();
|
const auto reactionsLimit = update.vreactions_limit().value_or_empty();
|
||||||
if (const auto allowed = update.vavailable_reactions()) {
|
if (const auto allowed = update.vavailable_reactions()) {
|
||||||
auto parsed = Data::Parse(*allowed);
|
auto parsed = Data::Parse(
|
||||||
parsed.maxCount = reactionsLimit;
|
*allowed,
|
||||||
|
reactionsLimit,
|
||||||
|
update.is_paid_reactions_available());
|
||||||
channel->setAllowedReactions(std::move(parsed));
|
channel->setAllowedReactions(std::move(parsed));
|
||||||
} else {
|
} else {
|
||||||
channel->setAllowedReactions({ .maxCount = reactionsLimit });
|
channel->setAllowedReactions({
|
||||||
|
.maxCount = reactionsLimit,
|
||||||
|
.paidEnabled = update.is_paid_reactions_available(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
channel->owner().stories().apply(channel, update.vstories());
|
channel->owner().stories().apply(channel, update.vstories());
|
||||||
channel->fullUpdated();
|
channel->fullUpdated();
|
||||||
|
|
|
@ -486,8 +486,8 @@ void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
|
||||||
chat->setTranslationDisabled(update.is_translations_disabled());
|
chat->setTranslationDisabled(update.is_translations_disabled());
|
||||||
const auto reactionsLimit = update.vreactions_limit().value_or_empty();
|
const auto reactionsLimit = update.vreactions_limit().value_or_empty();
|
||||||
if (const auto allowed = update.vavailable_reactions()) {
|
if (const auto allowed = update.vavailable_reactions()) {
|
||||||
auto parsed = Data::Parse(*allowed);
|
const auto paidEnabled = false;
|
||||||
parsed.maxCount = reactionsLimit;
|
auto parsed = Data::Parse(*allowed, reactionsLimit, paidEnabled);
|
||||||
chat->setAllowedReactions(std::move(parsed));
|
chat->setAllowedReactions(std::move(parsed));
|
||||||
} else {
|
} else {
|
||||||
chat->setAllowedReactions({ .maxCount = reactionsLimit });
|
chat->setAllowedReactions({ .maxCount = reactionsLimit });
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "data/data_message_reactions.h"
|
#include "data/data_message_reactions.h"
|
||||||
|
|
||||||
|
#include "chat_helpers/stickers_lottie.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history_item_components.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 "mtproto/mtproto_config.h"
|
||||||
#include "base/timer_rpl.h"
|
#include "base/timer_rpl.h"
|
||||||
#include "base/call_delayed.h"
|
#include "base/call_delayed.h"
|
||||||
|
#include "base/unixtime.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
|
@ -204,9 +206,13 @@ PossibleItemReactionsRef LookupPossibleReactions(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const auto &allowed = PeerAllowedReactions(peer);
|
const auto &allowed = PeerAllowedReactions(peer);
|
||||||
result.recent.reserve((allowed.type == AllowedReactionsType::Some)
|
result.recent.reserve((allowed.paidEnabled ? 1 : 0)
|
||||||
? allowed.some.size()
|
+ ((allowed.type == AllowedReactionsType::Some)
|
||||||
: full.size());
|
? allowed.some.size()
|
||||||
|
: full.size()));
|
||||||
|
if (allowed.paidEnabled) {
|
||||||
|
result.recent.push_back(reactions->lookupPaid());
|
||||||
|
}
|
||||||
add([&](const Reaction &reaction) {
|
add([&](const Reaction &reaction) {
|
||||||
const auto id = reaction.id;
|
const auto id = reaction.id;
|
||||||
if (id.custom() && !premiumPossible) {
|
if (id.custom() && !premiumPossible) {
|
||||||
|
@ -569,7 +575,7 @@ rpl::producer<> Reactions::effectsUpdates() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reactions::preloadReactionImageFor(const ReactionId &emoji) {
|
void Reactions::preloadReactionImageFor(const ReactionId &emoji) {
|
||||||
if (!emoji.emoji().isEmpty()) {
|
if (emoji.paid() || !emoji.emoji().isEmpty()) {
|
||||||
preloadImageFor(emoji);
|
preloadImageFor(emoji);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -586,6 +592,10 @@ void Reactions::preloadImageFor(const ReactionId &id) {
|
||||||
}
|
}
|
||||||
auto &set = _images.emplace(id).first->second;
|
auto &set = _images.emplace(id).first->second;
|
||||||
set.effect = (id.custom() != 0);
|
set.effect = (id.custom() != 0);
|
||||||
|
if (id.paid()) {
|
||||||
|
loadImage(set, lookupPaid()->selectAnimation, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto &list = set.effect ? _effects : _available;
|
auto &list = set.effect ? _effects : _available;
|
||||||
const auto i = ranges::find(list, id, &Reaction::id);
|
const auto i = ranges::find(list, id, &Reaction::id);
|
||||||
const auto document = (i == end(list))
|
const auto document = (i == end(list))
|
||||||
|
@ -1356,7 +1366,10 @@ void Reactions::send(not_null<HistoryItem*> item, bool addToRecent) {
|
||||||
MTP_flags(flags),
|
MTP_flags(flags),
|
||||||
item->history()->peer->input,
|
item->history()->peer->input,
|
||||||
MTP_int(id.msg),
|
MTP_int(id.msg),
|
||||||
MTP_vector<MTPReaction>(chosen | ranges::views::transform(
|
MTP_vector<MTPReaction>(chosen | ranges::views::filter([](
|
||||||
|
const ReactionId &id) {
|
||||||
|
return !id.paid();
|
||||||
|
}) | ranges::views::transform(
|
||||||
ReactionToMTP
|
ReactionToMTP
|
||||||
) | ranges::to<QVector<MTPReaction>>())
|
) | ranges::to<QVector<MTPReaction>>())
|
||||||
)).done([=](const MTPUpdates &result) {
|
)).done([=](const MTPUpdates &result) {
|
||||||
|
@ -1367,6 +1380,21 @@ void Reactions::send(not_null<HistoryItem*> item, bool addToRecent) {
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Reactions::sendPaid(not_null<HistoryItem*> 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<HistoryItem*> item, crl::time now) {
|
void Reactions::poll(not_null<HistoryItem*> item, crl::time now) {
|
||||||
// Group them by one second.
|
// Group them by one second.
|
||||||
const auto last = item->lastReactionsRefreshTime();
|
const auto last = item->lastReactionsRefreshTime();
|
||||||
|
@ -1403,7 +1431,9 @@ void Reactions::clearTemporary() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Reaction *Reactions::lookupTemporary(const ReactionId &id) {
|
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);
|
const auto i = ranges::find(_available, id, &Reaction::id);
|
||||||
return (i != end(_available)) ? &*i : nullptr;
|
return (i != end(_available)) ? &*i : nullptr;
|
||||||
} else if (const auto customId = id.custom()) {
|
} else if (const auto customId = id.custom()) {
|
||||||
|
@ -1424,6 +1454,26 @@ Reaction *Reactions::lookupTemporary(const ReactionId &id) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
not_null<Reaction*> 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<std::vector<Reaction>> Reactions::myTagsValue(
|
rpl::producer<std::vector<Reaction>> Reactions::myTagsValue(
|
||||||
SavedSublist *sublist) {
|
SavedSublist *sublist) {
|
||||||
refreshMyTags(sublist);
|
refreshMyTags(sublist);
|
||||||
|
@ -1529,6 +1579,25 @@ MessageReactions::MessageReactions(not_null<HistoryItem*> item)
|
||||||
: _item(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) {
|
void MessageReactions::add(const ReactionId &id, bool addToRecent) {
|
||||||
Expects(!id.empty());
|
Expects(!id.empty());
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,7 @@ public:
|
||||||
void preloadAnimationsFor(const ReactionId &emoji);
|
void preloadAnimationsFor(const ReactionId &emoji);
|
||||||
|
|
||||||
void send(not_null<HistoryItem*> item, bool addToRecent);
|
void send(not_null<HistoryItem*> item, bool addToRecent);
|
||||||
|
void sendPaid(not_null<HistoryItem*> item, int count);
|
||||||
[[nodiscard]] bool sending(not_null<HistoryItem*> item) const;
|
[[nodiscard]] bool sending(not_null<HistoryItem*> item) const;
|
||||||
|
|
||||||
void poll(not_null<HistoryItem*> item, crl::time now);
|
void poll(not_null<HistoryItem*> item, crl::time now);
|
||||||
|
@ -137,6 +138,7 @@ public:
|
||||||
|
|
||||||
void clearTemporary();
|
void clearTemporary();
|
||||||
[[nodiscard]] Reaction *lookupTemporary(const ReactionId &id);
|
[[nodiscard]] Reaction *lookupTemporary(const ReactionId &id);
|
||||||
|
[[nodiscard]] not_null<Reaction*> lookupPaid();
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<std::vector<Reaction>> myTagsValue(
|
[[nodiscard]] rpl::producer<std::vector<Reaction>> myTagsValue(
|
||||||
SavedSublist *sublist = nullptr);
|
SavedSublist *sublist = nullptr);
|
||||||
|
@ -275,6 +277,7 @@ private:
|
||||||
// So we use std::map instead of base::flat_map here.
|
// So we use std::map instead of base::flat_map here.
|
||||||
// Otherwise we could use flat_map<DocumentId, unique_ptr<Reaction>>.
|
// Otherwise we could use flat_map<DocumentId, unique_ptr<Reaction>>.
|
||||||
std::map<DocumentId, Reaction> _temporary;
|
std::map<DocumentId, Reaction> _temporary;
|
||||||
|
std::optional<Reaction> _paid;
|
||||||
|
|
||||||
base::Timer _topRefreshTimer;
|
base::Timer _topRefreshTimer;
|
||||||
mtpRequestId _topRequestId = 0;
|
mtpRequestId _topRequestId = 0;
|
||||||
|
@ -333,6 +336,7 @@ public:
|
||||||
explicit MessageReactions(not_null<HistoryItem*> item);
|
explicit MessageReactions(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
void add(const ReactionId &id, bool addToRecent);
|
void add(const ReactionId &id, bool addToRecent);
|
||||||
|
void addPaid(int count);
|
||||||
void remove(const ReactionId &id);
|
void remove(const ReactionId &id);
|
||||||
bool change(
|
bool change(
|
||||||
const QVector<MTPReactionCount> &list,
|
const QVector<MTPReactionCount> &list,
|
||||||
|
|
|
@ -95,28 +95,22 @@ bool ApplyBotMenuButton(
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator<(
|
AllowedReactions Parse(
|
||||||
const AllowedReactions &a,
|
const MTPChatReactions &value,
|
||||||
const AllowedReactions &b) {
|
int maxCount,
|
||||||
return (a.type < b.type) || ((a.type == b.type) && (a.some < b.some));
|
bool paidEnabled) {
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return value.match([&](const MTPDchatReactionsNone &) {
|
return value.match([&](const MTPDchatReactionsNone &) {
|
||||||
return AllowedReactions();
|
return AllowedReactions{
|
||||||
|
.maxCount = maxCount,
|
||||||
|
.paidEnabled = paidEnabled,
|
||||||
|
};
|
||||||
}, [&](const MTPDchatReactionsAll &data) {
|
}, [&](const MTPDchatReactionsAll &data) {
|
||||||
return AllowedReactions{
|
return AllowedReactions{
|
||||||
|
.maxCount = maxCount,
|
||||||
.type = (data.is_allow_custom()
|
.type = (data.is_allow_custom()
|
||||||
? AllowedReactionsType::All
|
? AllowedReactionsType::All
|
||||||
: AllowedReactionsType::Default),
|
: AllowedReactionsType::Default),
|
||||||
|
.paidEnabled = paidEnabled,
|
||||||
};
|
};
|
||||||
}, [&](const MTPDchatReactionsSome &data) {
|
}, [&](const MTPDchatReactionsSome &data) {
|
||||||
return AllowedReactions{
|
return AllowedReactions{
|
||||||
|
@ -125,7 +119,9 @@ AllowedReactions Parse(const MTPChatReactions &value) {
|
||||||
) | ranges::views::transform(
|
) | ranges::views::transform(
|
||||||
ReactionFromMTP
|
ReactionFromMTP
|
||||||
) | ranges::to_vector,
|
) | ranges::to_vector,
|
||||||
|
.maxCount = maxCount,
|
||||||
.type = AllowedReactionsType::Some,
|
.type = AllowedReactionsType::Some,
|
||||||
|
.paidEnabled = paidEnabled,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ bool ApplyBotMenuButton(
|
||||||
not_null<BotInfo*> info,
|
not_null<BotInfo*> info,
|
||||||
const MTPBotMenuButton *button);
|
const MTPBotMenuButton *button);
|
||||||
|
|
||||||
enum class AllowedReactionsType {
|
enum class AllowedReactionsType : uchar {
|
||||||
All,
|
All,
|
||||||
Default,
|
Default,
|
||||||
Some,
|
Some,
|
||||||
|
@ -109,14 +109,19 @@ enum class AllowedReactionsType {
|
||||||
|
|
||||||
struct AllowedReactions {
|
struct AllowedReactions {
|
||||||
std::vector<ReactionId> some;
|
std::vector<ReactionId> some;
|
||||||
AllowedReactionsType type = AllowedReactionsType::Some;
|
|
||||||
int maxCount = 0;
|
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);
|
[[nodiscard]] AllowedReactions Parse(
|
||||||
bool operator==(const AllowedReactions &a, const AllowedReactions &b);
|
const MTPChatReactions &value,
|
||||||
|
int maxCount,
|
||||||
[[nodiscard]] AllowedReactions Parse(const MTPChatReactions &value);
|
bool paidEnabled);
|
||||||
[[nodiscard]] PeerData *PeerFromInputMTP(
|
[[nodiscard]] PeerData *PeerFromInputMTP(
|
||||||
not_null<Session*> owner,
|
not_null<Session*> owner,
|
||||||
const MTPInputPeer &input);
|
const MTPInputPeer &input);
|
||||||
|
|
|
@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/effects/reaction_fly_animation.h"
|
#include "ui/effects/reaction_fly_animation.h"
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
#include "ui/text/text_isolated_emoji.h"
|
#include "ui/text/text_isolated_emoji.h"
|
||||||
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "ui/boxes/edit_factcheck_box.h"
|
#include "ui/boxes/edit_factcheck_box.h"
|
||||||
#include "ui/boxes/report_box.h"
|
#include "ui/boxes/report_box.h"
|
||||||
#include "ui/layers/generic_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);
|
const auto item = session().data().message(reaction.context);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
|
} else if (reaction.id.paid()) {
|
||||||
|
_controller->show(Ui::MakeConfirmBox({
|
||||||
|
.text = u"Send 10-stars reaction?"_q,
|
||||||
|
.confirmed = [=](Fn<void()> close) {
|
||||||
|
item->addPaidReaction(10, HistoryItem::ReactionSource::Selector);
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
return;
|
||||||
} else if (Window::ShowReactPremiumError(
|
} else if (Window::ShowReactPremiumError(
|
||||||
_controller,
|
_controller,
|
||||||
item,
|
item,
|
||||||
|
|
|
@ -2514,6 +2514,16 @@ bool HistoryItem::canReact() const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryItem::addPaidReaction(int count, ReactionSource source) {
|
||||||
|
Expects(_history->peer->isBroadcast());
|
||||||
|
|
||||||
|
if (!_reactions) {
|
||||||
|
_reactions = std::make_unique<Data::MessageReactions>(this);
|
||||||
|
}
|
||||||
|
_reactions->addPaid(count);
|
||||||
|
_history->owner().notifyItemDataChange(this);
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryItem::toggleReaction(
|
void HistoryItem::toggleReaction(
|
||||||
const Data::ReactionId &reaction,
|
const Data::ReactionId &reaction,
|
||||||
ReactionSource source) {
|
ReactionSource source) {
|
||||||
|
@ -2530,7 +2540,6 @@ void HistoryItem::toggleReaction(
|
||||||
if (_reactions->empty()) {
|
if (_reactions->empty()) {
|
||||||
_reactions = nullptr;
|
_reactions = nullptr;
|
||||||
_flags &= ~MessageFlag::CanViewReactions;
|
_flags &= ~MessageFlag::CanViewReactions;
|
||||||
_history->owner().notifyItemDataChange(this);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_reactions->add(reaction, (source == ReactionSource::Selector));
|
_reactions->add(reaction, (source == ReactionSource::Selector));
|
||||||
|
|
|
@ -443,6 +443,7 @@ public:
|
||||||
void toggleReaction(
|
void toggleReaction(
|
||||||
const Data::ReactionId &reaction,
|
const Data::ReactionId &reaction,
|
||||||
ReactionSource source);
|
ReactionSource source);
|
||||||
|
void addPaidReaction(int count, ReactionSource source);
|
||||||
void updateReactionsUnknown();
|
void updateReactionsUnknown();
|
||||||
[[nodiscard]] auto reactions() const
|
[[nodiscard]] auto reactions() const
|
||||||
-> const std::vector<Data::MessageReaction> &;
|
-> const std::vector<Data::MessageReaction> &;
|
||||||
|
|
|
@ -3306,7 +3306,12 @@ void Message::refreshReactions() {
|
||||||
ClickContext context) {
|
ClickContext context) {
|
||||||
if (const auto strong = weak.get()) {
|
if (const auto strong = weak.get()) {
|
||||||
const auto item = strong->data();
|
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()) {
|
if (item->history()->session().premium()) {
|
||||||
const auto tag = Data::SearchTagToQuery(id);
|
const auto tag = Data::SearchTagToQuery(id);
|
||||||
HashtagClickHandler(tag).onClick(context);
|
HashtagClickHandler(tag).onClick(context);
|
||||||
|
|
|
@ -144,6 +144,10 @@ void InlineList::layoutButtons() {
|
||||||
return true;
|
return true;
|
||||||
} else if (acount < bcount) {
|
} else if (acount < bcount) {
|
||||||
return false;
|
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)
|
return ranges::find(list, a->id, &::Data::Reaction::id)
|
||||||
< ranges::find(list, b->id, &::Data::Reaction::id);
|
< ranges::find(list, b->id, &::Data::Reaction::id);
|
||||||
|
|
|
@ -86,6 +86,11 @@ ReactionFlyAnimation::ReactionFlyAnimation(
|
||||||
_customSize = esize;
|
_customSize = esize;
|
||||||
_centerSizeMultiplier = _customSize / float64(size);
|
_centerSizeMultiplier = _customSize / float64(size);
|
||||||
aroundAnimation = owner->chooseGenericAnimation(document);
|
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 {
|
} else {
|
||||||
const auto i = ranges::find(list, args.id, &::Data::Reaction::id);
|
const auto i = ranges::find(list, args.id, &::Data::Reaction::id);
|
||||||
if (i == end(list)/* || !i->centerIcon*/) {
|
if (i == end(list)/* || !i->centerIcon*/) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue