| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- /*
- 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/reactions_settings_box.h"
- #include "base/unixtime.h"
- #include "data/data_user.h"
- #include "data/data_document.h"
- #include "data/data_document_media.h"
- #include "data/data_message_reactions.h"
- #include "data/data_session.h"
- #include "history/admin_log/history_admin_log_item.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "history/view/reactions/history_view_reactions_strip.h"
- #include "history/view/history_view_element.h"
- #include "history/view/history_view_fake_items.h"
- #include "lang/lang_keys.h"
- #include "boxes/premium_preview_box.h"
- #include "main/main_session.h"
- #include "settings/settings_premium.h"
- #include "ui/chat/chat_style.h"
- #include "ui/chat/chat_theme.h"
- #include "ui/effects/scroll_content_shadow.h"
- #include "ui/layers/generic_box.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/labels.h"
- #include "ui/widgets/scroll_area.h"
- #include "ui/wrap/vertical_layout.h"
- #include "ui/animated_icon.h"
- #include "ui/painter.h"
- #include "ui/vertical_list.h"
- #include "window/section_widget.h"
- #include "window/window_session_controller.h"
- #include "styles/style_boxes.h"
- #include "styles/style_chat.h"
- #include "styles/style_layers.h"
- #include "styles/style_media_player.h" // mediaPlayerMenuCheck
- #include "styles/style_settings.h"
- namespace {
- void AddMessage(
- not_null<Ui::VerticalLayout*> container,
- not_null<Window::SessionController*> controller,
- rpl::producer<Data::ReactionId> &&idValue,
- int width) {
- const auto widget = container->add(
- object_ptr<Ui::RpWidget>(container),
- style::margins(
- 0,
- st::defaultVerticalListSkip,
- 0,
- st::settingsPrivacySkipTop));
- class Delegate final : public HistoryView::SimpleElementDelegate {
- public:
- using HistoryView::SimpleElementDelegate::SimpleElementDelegate;
- private:
- HistoryView::Context elementContext() override {
- return HistoryView::Context::ContactPreview;
- }
- };
- struct State {
- AdminLog::OwnedItem reply;
- AdminLog::OwnedItem item;
- std::unique_ptr<Delegate> delegate;
- std::unique_ptr<Ui::ChatStyle> style;
- struct {
- std::vector<rpl::lifetime> lifetimes;
- bool flag = false;
- } icons;
- };
- const auto state = container->lifetime().make_state<State>();
- state->delegate = std::make_unique<Delegate>(
- controller,
- crl::guard(widget, [=] { widget->update(); }));
- state->style = std::make_unique<Ui::ChatStyle>(
- controller->session().colorIndicesValue());
- state->style->apply(controller->defaultChatTheme().get());
- state->icons.lifetimes = std::vector<rpl::lifetime>(2);
- const auto history = controller->session().data().history(
- PeerData::kServiceNotificationsId);
- state->reply = HistoryView::GenerateItem(
- state->delegate.get(),
- history,
- HistoryView::GenerateUser(
- history,
- tr::lng_settings_chat_message_reply_from(tr::now)),
- FullMsgId(),
- tr::lng_settings_chat_message_reply(tr::now));
- auto message = HistoryView::GenerateItem(
- state->delegate.get(),
- history,
- history->peer->id,
- state->reply->data()->fullId(),
- tr::lng_settings_chat_message(tr::now));
- const auto view = message.get();
- state->item = std::move(message);
- const auto padding = st::settingsForwardPrivacyPadding;
- const auto updateWidgetSize = [=](int width) {
- const auto height = view->resizeGetHeight(width);
- const auto top = view->marginTop();
- const auto bottom = view->marginBottom();
- const auto full = padding + top + height + bottom + padding;
- widget->resize(width, full);
- };
- widget->widthValue(
- ) | rpl::filter(
- rpl::mappers::_1 >= (st::historyMinimalWidth / 2)
- ) | rpl::start_with_next(updateWidgetSize, widget->lifetime());
- updateWidgetSize(width);
- const auto rightSize = st::settingsReactionCornerSize;
- const auto rightRect = [=] {
- const auto viewInner = view->innerGeometry();
- return QRect(
- viewInner.x() + viewInner.width(),
- padding
- + view->marginTop()
- + view->resizeGetHeight(widget->width())
- - rightSize.height(),
- rightSize.width(),
- rightSize.height()).translated(st::settingsReactionCornerSkip);
- };
- widget->paintRequest(
- ) | rpl::start_with_next([=](const QRect &rect) {
- Window::SectionWidget::PaintBackground(
- controller,
- controller->defaultChatTheme().get(), // #TODO themes
- widget,
- rect);
- Painter p(widget);
- auto hq = PainterHighQualityEnabler(p);
- const auto theme = controller->defaultChatTheme().get();
- auto context = theme->preparePaintContext(
- state->style.get(),
- widget->rect(),
- widget->rect(),
- controller->isGifPausedAtLeastFor(
- Window::GifPauseReason::Layer));
- context.outbg = view->hasOutLayout();
- {
- const auto radius = rightSize.height() / 2;
- const auto r = rightRect();
- const auto &st = context.st->messageStyle(
- context.outbg,
- context.selected());
- p.setPen(Qt::NoPen);
- p.setBrush(st.msgShadow);
- p.drawRoundedRect(r.translated(0, st::msgShadow), radius, radius);
- p.setBrush(st.msgBg);
- p.drawRoundedRect(r, radius, radius);
- }
- p.translate(padding / 2, padding + view->marginBottom());
- view->draw(p, context);
- }, widget->lifetime());
- auto selectedId = rpl::duplicate(idValue);
- std::move(
- selectedId
- ) | rpl::start_with_next([
- =,
- idValue = std::move(idValue),
- iconSize = st::settingsReactionMessageSize
- ](const Data::ReactionId &id) {
- const auto index = state->icons.flag ? 1 : 0;
- state->icons.flag = !state->icons.flag;
- state->icons.lifetimes[index] = rpl::lifetime();
- const auto &reactions = controller->session().data().reactions();
- auto iconPositionValue = widget->geometryValue(
- ) | rpl::map([=](const QRect &r) {
- return widget->pos()
- + rightRect().topLeft()
- + QPoint(
- (rightSize.width() - iconSize) / 2,
- (rightSize.height() - iconSize) / 2);
- });
- auto destroys = rpl::duplicate(
- idValue
- ) | rpl::skip(1) | rpl::to_empty;
- if (const auto customId = id.custom()) {
- AddReactionCustomIcon(
- container,
- std::move(iconPositionValue),
- iconSize,
- controller,
- customId,
- std::move(destroys),
- &state->icons.lifetimes[index]);
- return;
- }
- for (const auto &r : reactions.list(Data::Reactions::Type::Active)) {
- if (r.id != id) {
- continue;
- }
- AddReactionAnimatedIcon(
- container,
- std::move(iconPositionValue),
- iconSize,
- r,
- rpl::never<>(),
- std::move(destroys),
- &state->icons.lifetimes[index]);
- return;
- }
- }, widget->lifetime());
- }
- not_null<Ui::RpWidget*> AddReactionIconWrap(
- not_null<Ui::RpWidget*> parent,
- rpl::producer<QPoint> iconPositionValue,
- int iconSize,
- Fn<void(not_null<QWidget*>, QPainter&)> paintCallback,
- rpl::producer<> &&destroys,
- not_null<rpl::lifetime*> stateLifetime) {
- struct State {
- base::unique_qptr<Ui::RpWidget> widget;
- Ui::Animations::Simple finalAnimation;
- };
- const auto state = stateLifetime->make_state<State>();
- state->widget = base::make_unique_q<Ui::RpWidget>(parent);
- const auto widget = state->widget.get();
- widget->resize(iconSize, iconSize);
- widget->setAttribute(Qt::WA_TransparentForMouseEvents);
- std::move(
- iconPositionValue
- ) | rpl::start_with_next([=](const QPoint &point) {
- widget->moveToLeft(point.x(), point.y());
- }, widget->lifetime());
- const auto update = crl::guard(widget, [=] { widget->update(); });
- widget->paintRequest(
- ) | rpl::start_with_next([=] {
- auto p = QPainter(widget);
- if (state->finalAnimation.animating()) {
- const auto progress = 1. - state->finalAnimation.value(0.);
- const auto size = widget->size();
- const auto scaledSize = size * progress;
- const auto scaledCenter = QPoint(
- (size.width() - scaledSize.width()) / 2.,
- (size.height() - scaledSize.height()) / 2.);
- p.setOpacity(progress);
- p.translate(scaledCenter);
- p.scale(progress, progress);
- }
- paintCallback(widget, p);
- }, widget->lifetime());
- std::move(
- destroys
- ) | rpl::take(1) | rpl::start_with_next([=, from = 0., to = 1.] {
- state->finalAnimation.start(
- [=](float64 value) {
- update();
- if (value == to) {
- stateLifetime->destroy();
- }
- },
- from,
- to,
- st::defaultPopupMenu.showDuration);
- }, widget->lifetime());
- widget->raise();
- widget->show();
- return widget;
- }
- } // namespace
- void AddReactionAnimatedIcon(
- not_null<Ui::RpWidget*> parent,
- rpl::producer<QPoint> iconPositionValue,
- int iconSize,
- const Data::Reaction &reaction,
- rpl::producer<> &&selects,
- rpl::producer<> &&destroys,
- not_null<rpl::lifetime*> stateLifetime) {
- struct State {
- struct Entry {
- std::shared_ptr<Data::DocumentMedia> media;
- std::shared_ptr<Ui::AnimatedIcon> icon;
- };
- Entry appear;
- Entry select;
- bool appearAnimated = false;
- rpl::lifetime loadingLifetime;
- };
- const auto state = stateLifetime->make_state<State>();
- state->appear.media = reaction.appearAnimation->createMediaView();
- state->select.media = reaction.selectAnimation->createMediaView();
- state->appear.media->checkStickerLarge();
- state->select.media->checkStickerLarge();
- rpl::single() | rpl::then(
- reaction.appearAnimation->session().downloaderTaskFinished()
- ) | rpl::start_with_next([=] {
- const auto check = [&](State::Entry &entry) {
- if (!entry.media) {
- return true;
- } else if (!entry.media->loaded()) {
- return false;
- }
- entry.icon = HistoryView::Reactions::DefaultIconFactory(
- entry.media.get(),
- iconSize);
- entry.media = nullptr;
- return true;
- };
- if (check(state->select) && check(state->appear)) {
- state->loadingLifetime.destroy();
- }
- }, state->loadingLifetime);
- const auto paintCallback = [=](not_null<QWidget*> widget, QPainter &p) {
- const auto paintFrame = [&](not_null<Ui::AnimatedIcon*> animation) {
- const auto frame = animation->frame(st::windowFg->c);
- p.drawImage(
- QRect(
- (widget->width() - iconSize) / 2,
- (widget->height() - iconSize) / 2,
- iconSize,
- iconSize),
- frame);
- };
- const auto appear = state->appear.icon.get();
- if (appear && !state->appearAnimated) {
- state->appearAnimated = true;
- appear->animate(crl::guard(widget, [=] { widget->update(); }));
- }
- if (appear && appear->animating()) {
- paintFrame(appear);
- } else if (const auto select = state->select.icon.get()) {
- paintFrame(select);
- }
- };
- const auto widget = AddReactionIconWrap(
- parent,
- std::move(iconPositionValue),
- iconSize,
- paintCallback,
- std::move(destroys),
- stateLifetime);
- std::move(
- selects
- ) | rpl::start_with_next([=] {
- const auto select = state->select.icon.get();
- if (select && !select->animating()) {
- select->animate(crl::guard(widget, [=] { widget->update(); }));
- }
- }, widget->lifetime());
- }
- void AddReactionCustomIcon(
- not_null<Ui::RpWidget*> parent,
- rpl::producer<QPoint> iconPositionValue,
- int iconSize,
- not_null<Window::SessionController*> controller,
- DocumentId customId,
- rpl::producer<> &&destroys,
- not_null<rpl::lifetime*> stateLifetime) {
- struct State {
- std::unique_ptr<Ui::Text::CustomEmoji> custom;
- Fn<void()> repaint;
- };
- const auto state = stateLifetime->make_state<State>();
- static constexpr auto tag = Data::CustomEmojiManager::SizeTag::Normal;
- state->custom = controller->session().data().customEmojiManager().create(
- customId,
- [=] { state->repaint(); },
- tag);
- const auto paintCallback = [=](not_null<QWidget*> widget, QPainter &p) {
- const auto ratio = style::DevicePixelRatio();
- const auto size = Data::FrameSizeFromTag(tag) / ratio;
- state->custom->paint(p, {
- .textColor = st::windowFg->c,
- .now = crl::now(),
- .position = QPoint(
- (widget->width() - size) / 2,
- (widget->height() - size) / 2),
- .paused = controller->isGifPausedAtLeastFor(
- Window::GifPauseReason::Layer),
- });
- };
- const auto widget = AddReactionIconWrap(
- parent,
- std::move(iconPositionValue),
- iconSize,
- paintCallback,
- std::move(destroys),
- stateLifetime);
- state->repaint = crl::guard(widget, [=] { widget->update(); });
- }
- void ReactionsSettingsBox(
- not_null<Ui::GenericBox*> box,
- not_null<Window::SessionController*> controller) {
- struct State {
- rpl::variable<Data::ReactionId> selectedId;
- };
- const auto &reactions = controller->session().data().reactions();
- const auto state = box->lifetime().make_state<State>();
- state->selectedId = reactions.favoriteId();
- const auto pinnedToTop = box->setPinnedToTopContent(
- object_ptr<Ui::VerticalLayout>(box));
- auto idValue = state->selectedId.value();
- AddMessage(pinnedToTop, controller, std::move(idValue), box->width());
- Ui::AddSubsectionTitle(
- pinnedToTop,
- tr::lng_settings_chat_reactions_subtitle());
- const auto container = box->verticalLayout();
- const auto check = Ui::CreateChild<Ui::RpWidget>(container.get());
- check->resize(st::settingsReactionCornerSize);
- check->setAttribute(Qt::WA_TransparentForMouseEvents);
- check->paintRequest(
- ) | rpl::start_with_next([=] {
- Painter p(check);
- st::mediaPlayerMenuCheck.paintInCenter(p, check->rect());
- }, check->lifetime());
- const auto checkButton = [=](not_null<const Ui::RpWidget*> button) {
- check->moveToRight(
- st::settingsButtonRightSkip,
- button->y() + (button->height() - check->height()) / 2);
- };
- auto firstCheckedButton = (Ui::RpWidget*)(nullptr);
- auto list = reactions.list(Data::Reactions::Type::Active);
- if (const auto favorite = reactions.favorite()) {
- if (favorite->id.custom()) {
- list.insert(begin(list), *favorite);
- }
- }
- for (const auto &r : list) {
- const auto button = container->add(object_ptr<Ui::SettingsButton>(
- container,
- rpl::single<QString>(base::duplicate(r.title)),
- st::settingsButton));
- const auto iconSize = st::settingsReactionSize;
- const auto left = button->st().iconLeft;
- auto iconPositionValue = button->sizeValue(
- ) | rpl::map([=](const QSize &s) {
- return QPoint(
- left + st::settingsReactionRightSkip,
- (s.height() - iconSize) / 2);
- });
- if (const auto customId = r.id.custom()) {
- AddReactionCustomIcon(
- button,
- std::move(iconPositionValue),
- iconSize,
- controller,
- customId,
- rpl::never<>(),
- &button->lifetime());
- } else {
- AddReactionAnimatedIcon(
- button,
- std::move(iconPositionValue),
- iconSize,
- r,
- button->events(
- ) | rpl::filter([=](not_null<QEvent*> event) {
- return event->type() == QEvent::Enter;
- }) | rpl::to_empty,
- rpl::never<>(),
- &button->lifetime());
- }
- button->setClickedCallback([=, id = r.id] {
- checkButton(button);
- state->selectedId = id;
- });
- if (r.id == state->selectedId.current()) {
- firstCheckedButton = button;
- }
- }
- if (firstCheckedButton) {
- firstCheckedButton->geometryValue(
- ) | rpl::filter([=](const QRect &r) {
- return r.isValid();
- }) | rpl::take(1) | rpl::start_with_next([=] {
- checkButton(firstCheckedButton);
- }, firstCheckedButton->lifetime());
- }
- check->raise();
- box->setTitle(tr::lng_settings_chat_reactions_title());
- box->setWidth(st::boxWideWidth);
- box->addButton(tr::lng_settings_save(), [=] {
- const auto &data = controller->session().data();
- const auto selectedId = state->selectedId.current();
- if (data.reactions().favoriteId() != selectedId) {
- data.reactions().setFavorite(selectedId);
- }
- box->closeBox();
- });
- box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
- }
|