/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/edit_peer_reactions.h" #include "data/data_message_reactions.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_peer.h" #include "data/data_chat.h" #include "data/data_channel.h" #include "data/data_session.h" #include "main/main_session.h" #include "apiwrap.h" #include "lottie/lottie_icon.h" #include "lang/lang_keys.h" #include "ui/widgets/buttons.h" #include "info/profile/info_profile_icon.h" #include "settings/settings_common.h" #include "styles/style_settings.h" #include "styles/style_info.h" namespace { using Data::Reaction; void AddReactionIcon( not_null button, not_null document) { struct State { std::shared_ptr media; std::unique_ptr icon; QImage image; }; const auto size = st::editPeerReactionsPreview; const auto state = button->lifetime().make_state(State{ .media = document->createMediaView(), }); const auto icon = Ui::CreateChild(button.get()); icon->setAttribute(Qt::WA_TransparentForMouseEvents); icon->resize(size, size); button->sizeValue( ) | rpl::start_with_next([=](QSize size) { icon->moveToLeft( st::editPeerReactionsIconLeft, (size.height() - icon->height()) / 2, size.width()); }, icon->lifetime()); const auto initLottie = [=] { state->icon = std::make_unique(Lottie::IconDescriptor{ .path = state->media->owner()->filepath(true), .json = state->media->bytes(), .sizeOverride = QSize(size, size), .frame = -1, }); state->media = nullptr; }; state->media->checkStickerLarge(); if (state->media->loaded()) { initLottie(); } else { document->session().downloaderTaskFinished( ) | rpl::filter([=] { return state->media->loaded(); }) | rpl::take(1) | rpl::start_with_next([=] { initLottie(); icon->update(); }, icon->lifetime()); } icon->paintRequest( ) | rpl::start_with_next([=] { QPainter p(icon); if (state->image.isNull() && state->icon) { state->image = state->icon->frame(); crl::async([icon = std::move(state->icon)]{}); } if (!state->image.isNull()) { p.drawImage(QRect(0, 0, size, size), state->image); } }, icon->lifetime()); } } // namespace void EditAllowedReactionsBox( not_null box, bool isGroup, const std::vector &list, const base::flat_set &selected, Fn &)> callback) { box->setTitle(tr::lng_manage_peer_reactions()); struct State { base::flat_map> toggles; rpl::variable anyToggled; rpl::event_stream forceToggleAll; }; const auto state = box->lifetime().make_state(State{ .anyToggled = !selected.empty(), }); const auto collect = [=] { auto result = std::vector(); result.reserve(state->toggles.size()); for (const auto &[emoji, button] : state->toggles) { if (button->toggled()) { result.push_back(emoji); } } return result; }; const auto container = box->verticalLayout(); const auto enabled = Settings::AddButton( container, tr::lng_manage_peer_reactions_enable(), st::manageGroupButton.button); Ui::CreateChild( enabled.get(), st::infoIconReactions, st::manageGroupButton.iconPosition); enabled->toggleOn(state->anyToggled.value()); enabled->toggledChanges( ) | rpl::filter([=](bool value) { return (value != state->anyToggled.current()); }) | rpl::start_to_stream(state->forceToggleAll, enabled->lifetime()); Settings::AddSkip(container); Settings::AddDividerText( container, (isGroup ? tr::lng_manage_peer_reactions_about : tr::lng_manage_peer_reactions_about_channel)()); Settings::AddSkip(container); Settings::AddSubsectionTitle( container, tr::lng_manage_peer_reactions_available()); const auto active = [&](const Data::Reaction &entry) { return selected.contains(entry.emoji); }; const auto add = [&](const Data::Reaction &entry) { const auto button = Settings::AddButton( container, rpl::single(entry.title), st::manageGroupButton.button); AddReactionIcon(button, entry.centerIcon ? entry.centerIcon : entry.appearAnimation.get()); state->toggles.emplace(entry.emoji, button); button->toggleOn(rpl::single( active(entry) ) | rpl::then( state->forceToggleAll.events() )); button->toggledChanges( ) | rpl::start_with_next([=](bool toggled) { if (toggled) { state->anyToggled = true; } else if (collect().empty()) { state->anyToggled = false; } }, button->lifetime()); }; for (const auto &entry : list) { add(entry); } box->addButton(tr::lng_settings_save(), [=] { const auto ids = collect(); box->closeBox(); callback(ids); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } void SaveAllowedReactions( not_null peer, const std::vector &allowed) { auto ids = allowed | ranges::views::transform([=](QString value) { return MTP_string(value); }) | ranges::to; peer->session().api().request(MTPmessages_SetChatAvailableReactions( peer->input, MTP_vector(ids) )).done([=](const MTPUpdates &result) { peer->session().api().applyUpdates(result); if (const auto chat = peer->asChat()) { chat->setAllowedReactions({ begin(allowed), end(allowed) }); } else if (const auto channel = peer->asChannel()) { channel->setAllowedReactions({ begin(allowed), end(allowed) }); } else { Unexpected("Invalid peer type in SaveAllowedReactions."); } }).fail([=](const MTP::Error &error) { if (error.type() == qstr("REACTION_INVALID")) { peer->updateFullForced(); peer->owner().reactions().refresh(); } }).send(); }