diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0488dd517..6a9dea937 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1802,6 +1802,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_seen_reacted#other" = "{count} Reacted"; "lng_context_seen_reacted_none" = "Nobody Reacted"; "lng_context_seen_reacted_all" = "Show All Reactions"; +"lng_context_set_as_quick" = "Set As Quick"; "lng_send_image_empty" = "Could not send an empty file: {name}"; "lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}"; diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index dc39ec65c..27a280de1 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL constexpr auto kPeerLinkPeerIdProperty = 0x01; constexpr auto kPhotoLinkMediaIdProperty = 0x02; constexpr auto kDocumentLinkMediaIdProperty = 0x03; +constexpr auto kSendReactionEmojiProperty = 0x04; +constexpr auto kReactionsCountEmojiProperty = 0x05; namespace Main { class Session; diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 7b2e84efe..12718cd28 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -56,7 +56,7 @@ Reactions::Reactions(not_null owner) const auto favorite = appConfig->get( u"reactions_default"_q, QString::fromUtf8("\xf0\x9f\x91\x8d")); - if (_favorite != favorite) { + if (_favorite != favorite && !_saveFaveRequestId) { _favorite = favorite; _updated.fire({}); } @@ -81,6 +81,25 @@ QString Reactions::favorite() const { return _favorite; } +void Reactions::setFavorite(const QString &emoji) { + const auto api = &_owner->session().api(); + if (_saveFaveRequestId) { + api->request(_saveFaveRequestId).cancel(); + } + _saveFaveRequestId = api->request(MTPmessages_SetDefaultReaction( + MTP_string(emoji) + )).done([=] { + _saveFaveRequestId = 0; + }).fail([=] { + _saveFaveRequestId = 0; + }).send(); + + if (_favorite != emoji) { + _favorite = emoji; + _updated.fire({}); + } +} + rpl::producer<> Reactions::updates() const { return _updated.events(); } diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 2138e2290..add18375f 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -44,6 +44,7 @@ public: }; [[nodiscard]] const std::vector &list(Type type) const; [[nodiscard]] QString favorite() const; + void setFavorite(const QString &emoji); [[nodiscard]] static base::flat_set ParseAllowed( const MTPVector *list); @@ -117,6 +118,8 @@ private: base::flat_set> _pollingItems; mtpRequestId _pollRequestId = 0; + mtpRequestId _saveFaveRequestId = 0; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 757038433..0690e0480 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1849,6 +1849,12 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { mouseActionUpdate(e->globalPos()); } + const auto link = ClickHandler::getActive(); + if (link + && !link->property(kSendReactionEmojiProperty).toString().isEmpty() + && _reactionsManager->showContextMenu(this, e)) { + return; + } auto selectedState = getSelectionState(); auto canSendMessages = _peer->canWrite(); @@ -2044,7 +2050,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } }; - const auto link = ClickHandler::getActive(); const auto lnkPhotoId = PhotoId(link ? link->property(kPhotoLinkMediaIdProperty).toULongLong() : 0); @@ -2870,7 +2875,7 @@ void HistoryInner::enterEventHook(QEnterEvent *e) { } void HistoryInner::leaveEventHook(QEvent *e) { - _reactionsManager->updateButton({}); + _reactionsManager->updateButton({ .cursorLeft = true }); if (auto item = App::hoveredItem()) { repaintItem(item); App::hoveredItem(nullptr); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index c96fe4857..612d12405 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2092,9 +2092,16 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { mouseActionUpdate(e->globalPos()); } + const auto link = ClickHandler::getActive(); + if (link + && !link->property(kSendReactionEmojiProperty).toString().isEmpty() + && _reactionsManager->showContextMenu(this, e)) { + return; + } + auto request = ContextMenuRequest(_controller); - request.link = ClickHandler::getActive(); + request.link = link; request.view = _overElement; request.item = _overItemExact ? _overItemExact @@ -2158,7 +2165,7 @@ void ListWidget::enterEventHook(QEnterEvent *e) { } void ListWidget::leaveEventHook(QEvent *e) { - _reactionsManager->updateButton({}); + _reactionsManager->updateButton({ .cursorLeft = true }); if (const auto view = _overElement) { if (_overState.pointState != PointState::Outside) { repaintItem(view); diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.cpp b/Telegram/SourceFiles/history/view/history_view_react_button.cpp index b7e899791..3280863a0 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_button.cpp @@ -11,14 +11,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "ui/chat/chat_style.h" #include "ui/chat/message_bubble.h" +#include "ui/widgets/popup_menu.h" #include "data/data_message_reactions.h" #include "data/data_session.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "lang/lang_keys.h" +#include "core/click_handler_types.h" #include "lottie/lottie_icon.h" #include "main/main_session.h" #include "base/event_filter.h" #include "styles/style_chat.h" +#include "styles/style_menu_icons.h" namespace HistoryView::Reactions { namespace { @@ -499,6 +503,9 @@ void Manager::stealWheelEvents(not_null target) { Manager::~Manager() = default; void Manager::updateButton(ButtonParameters parameters) { + if (parameters.cursorLeft && _menu) { + return; + } const auto contextChanged = (_buttonContext != parameters.context); if (contextChanged) { setSelectedIcon(-1); @@ -844,11 +851,10 @@ ClickHandlerPtr Manager::resolveButtonLink( if (i != end(_reactionsLinks)) { return i->second; } - return _reactionsLinks.emplace( - emoji, - std::make_shared( - crl::guard(this, _createChooseCallback(emoji))) - ).first->second; + auto handler = std::make_shared( + crl::guard(this, _createChooseCallback(emoji))); + handler->setProperty(kSendReactionEmojiProperty, emoji); + return _reactionsLinks.emplace(emoji, std::move(handler)).first->second; } TextState Manager::buttonTextState(QPoint position) const { @@ -1547,6 +1553,33 @@ void Manager::recordCurrentReactionEffect(FullMsgId itemId, QPoint origin) { } } +bool Manager::showContextMenu(QWidget *parent, QContextMenuEvent *e) { + if (_icons.empty() || _selectedIcon < 0) { + return false; + } + _menu = base::make_unique_q( + parent, + st::popupMenuWithIcons); + const auto callback = [=] { + for (const auto &icon : _icons) { + if (icon->selected) { + _faveRequests.fire_copy(icon->emoji); + return; + } + } + }; + _menu->addAction( + tr::lng_context_set_as_quick(tr::now), + callback, + &st::menuIconFave); + _menu->popup(e->globalPos()); + return true; +} + +rpl::producer Manager::faveRequests() const { + return _faveRequests.events(); +} + void SetupManagerList( not_null manager, not_null session, @@ -1568,6 +1601,12 @@ void SetupManagerList( std::optional> &&list) { manager->updateAllowedSublist(std::move(list)); }, manager->lifetime()); + + manager->faveRequests( + ) | rpl::start_with_next([=](const QString &emoji) { + reactions->setFavorite(emoji); + manager->updateButton({}); + }, manager->lifetime()); } IconFactory CachedIconFactory::createMethod() { diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.h b/Telegram/SourceFiles/history/view/history_view_react_button.h index 22ab6ab56..dbe80fb6e 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.h +++ b/Telegram/SourceFiles/history/view/history_view_react_button.h @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { struct ChatPaintContext; +class PopupMenu; } // namespace Ui namespace Data { @@ -56,6 +57,7 @@ struct ButtonParameters { int visibleTop = 0; int visibleBottom = 0; bool outside = false; + bool cursorLeft = false; }; enum class ButtonState { @@ -175,6 +177,9 @@ public: -> not_null; void recordCurrentReactionEffect(FullMsgId itemId, QPoint origin); + bool showContextMenu(QWidget *parent, QContextMenuEvent *e); + [[nodiscard]] rpl::producer faveRequests() const; + [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; } @@ -349,6 +354,9 @@ private: Ui::ReactionEffectPainter _currentEffect; base::flat_map _collectedEffects; + base::unique_qptr _menu; + rpl::event_stream _faveRequests; + rpl::lifetime _lifetime; };