diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index a82493ac2..d25d42ba6 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_changes.h" +#include "data/data_session.h" +#include "data/data_message_reactions.h" #include "main/main_session.h" #include "ui/image/image_prepare.h" #include "base/unixtime.h" @@ -495,4 +497,18 @@ rpl::producer PeerUserpicImageValue( }; } +rpl::producer> PeerAllowedReactionsValue( + not_null peer) { + return rpl::combine( + rpl::single( + rpl::empty_value() + ) | rpl::then(peer->owner().reactions().updates()), + peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::Reactions) + ) | rpl::map([=] { + return peer->owner().reactions().list(peer); + }); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer_values.h b/Telegram/SourceFiles/data/data_peer_values.h index 31cbcb6b3..fa6a8042b 100644 --- a/Telegram/SourceFiles/data/data_peer_values.h +++ b/Telegram/SourceFiles/data/data_peer_values.h @@ -16,6 +16,8 @@ enum class ImageRoundRadius; namespace Data { +struct Reaction; + template inline auto FlagsValueWithMask( rpl::producer &&value, @@ -120,4 +122,7 @@ inline auto PeerFullFlagValue( int size, ImageRoundRadius radius); +[[nodiscard]] auto PeerAllowedReactionsValue(not_null peer) +-> rpl::producer>; + } // namespace Data diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 7d79a55eb..a672aab44 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -974,10 +974,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) { const auto height = view->height(); top += height; - context.viewport.translate(0, -height); - context.clip.translate(0, -height); + context.translate(0, -height); p.translate(0, height); } + context.translate(0, top); p.translate(0, -top); enumerateUserpics([&](not_null view, int userpicTop) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 74228d61d..f0e03a59e 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -276,16 +276,10 @@ HistoryInner::HistoryInner( update(); }, lifetime()); - rpl::combine( - rpl::single( - rpl::empty_value() - ) | rpl::then(session().data().reactions().updates()), - session().changes().peerFlagsValue( - _peer, - Data::PeerUpdate::Flag::Reactions) - ) | rpl::start_with_next([=] { - _reactions = session().data().reactions().list(_peer); - _reactionsManager->applyList(_reactions); + Data::PeerAllowedReactionsValue( + _peer + ) | rpl::start_with_next([=](std::vector &&list) { + _reactionsManager->applyList(std::move(list)); }, lifetime()); controller->adaptive().chatWideValue( @@ -1447,6 +1441,7 @@ std::unique_ptr HistoryInner::prepareDrag() { void HistoryInner::performDrag() { if (auto mimeData = prepareDrag()) { // This call enters event loop and can destroy any QObject. + _reactionsManager->updateButton({}); _controller->widget()->launchDrag( std::move(mimeData), crl::guard(this, [=] { mouseActionUpdate(QCursor::pos()); })); @@ -3019,15 +3014,7 @@ void HistoryInner::mouseActionUpdate() { const auto item = view ? view->data().get() : nullptr; if (view) { const auto was = App::mousedItem(); - if (was != view) { - if (!_reactions.empty()) { - repaintItem(was); - } - App::mousedItem(view); - if (!_reactions.empty()) { - repaintItem(view); - } - } + App::mousedItem(view); m = mapPointToItem(point, view); _reactionsManager->updateButton(reactionButtonParameters( view, diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 7cb32b98e..09a9c9663 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -17,7 +17,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { struct Group; class CloudImageView; -struct Reaction; } // namespace Data namespace HistoryView { @@ -350,10 +349,11 @@ private: void blockSenderAsGroup(FullMsgId itemId); void copySelectedText(); - HistoryView::Reactions::ButtonParameters reactionButtonParameters( + [[nodiscard]] auto reactionButtonParameters( not_null view, QPoint position, - const HistoryView::TextState &reactionState) const; + const HistoryView::TextState &reactionState) const + -> HistoryView::Reactions::ButtonParameters; void setupSharingDisallowed(); [[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const; @@ -407,7 +407,6 @@ private: not_null, std::shared_ptr> _userpics, _userpicsCache; - std::vector _reactions; std::unique_ptr _reactionsManager; MouseAction _mouseAction = MouseAction::None; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index d93bcc168..97f5c5725 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_react_button.h" #include "chat_helpers/message_field.h" #include "mainwindow.h" #include "mainwidget.h" @@ -47,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_channel.h" #include "data/data_file_click_handler.h" +#include "data/data_message_reactions.h" #include "facades.h" #include "styles/style_chat.h" @@ -262,6 +264,10 @@ ListWidget::ListWidget( MakePathShiftGradient( controller->chatStyle(), [=] { update(); })) +, _reactionsManager( + std::make_unique( + this, + [=](QRect updated) { update(updated); })) , _scrollDateCheck([this] { scrollDateCheck(); }) , _applyUpdatedScrollState([this] { applyUpdatedScrollState(); }) , _selectEnabled(_delegate->listAllowsMultiSelect()) @@ -330,6 +336,19 @@ ListWidget::ListWidget( } }); + using ChosenReaction = Reactions::Manager::Chosen; + _reactionsManager->chosen( + ) | rpl::start_with_next([=](ChosenReaction reaction) { + if (const auto item = session().data().message(reaction.context)) { + item->toggleReaction(reaction.emoji); + } + }, lifetime()); + + _delegate->listAllowedReactionsValue( + ) | rpl::start_with_next([=](std::vector &&list) { + _reactionsManager->applyList(std::move(list)); + }, lifetime()); + controller->adaptive().chatWideValue( ) | rpl::start_with_next([=](bool wide) { _isChatWide = wide; @@ -884,6 +903,10 @@ bool ListWidget::hasSelectedItems() const { return !_selected.empty(); } +bool ListWidget::inSelectionMode() const { + return hasSelectedItems() || !_dragSelected.empty(); +} + bool ListWidget::overSelectedItems() const { if (_overState.pointState == PointState::GroupPart) { return _overItemExact @@ -1374,7 +1397,7 @@ crl::time ListWidget::elementHighlightTime( } bool ListWidget::elementInSelectionMode() { - return hasSelectedItems() || !_dragSelected.empty(); + return inSelectionMode(); } bool ListWidget::elementIntersectsRange( @@ -1711,10 +1734,10 @@ void ListWidget::paintEvent(QPaintEvent *e) { view->draw(p, context); const auto height = view->height(); top += height; - context.viewport.translate(0, -height); - context.clip.translate(0, -height); + context.translate(0, -height); p.translate(0, height); } + context.translate(0, top); p.translate(0, -top); enumerateUserpics([&](not_null view, int userpicTop) { @@ -1792,6 +1815,8 @@ void ListWidget::paintEvent(QPaintEvent *e) { } return true; }); + + _reactionsManager->paintButtons(p, context); } } @@ -2113,6 +2138,7 @@ void ListWidget::enterEventHook(QEnterEvent *e) { } void ListWidget::leaveEventHook(QEvent *e) { + _reactionsManager->updateButton({}); if (const auto view = _overElement) { if (_overState.pointState != PointState::Outside) { repaintItem(view); @@ -2391,6 +2417,27 @@ void ListWidget::mouseActionStart( } } +Reactions::ButtonParameters ListWidget::reactionButtonParameters( + not_null view, + QPoint position, + const TextState &reactionState) const { + const auto top = itemTop(view); + if (top < 0 + || !view->data()->canReact() + || _mouseAction == MouseAction::Dragging + || inSelectionMode()) { + return {}; + } + auto result = view->reactionButtonParameters( + position, + reactionState + ).translated({ 0, itemTop(view) }); + result.visibleTop = _visibleTop; + result.visibleBottom = _visibleBottom; + result.globalPointer = _mousePosition; + return result; +} + void ListWidget::mouseActionUpdate(const QPoint &globalPosition) { _mousePosition = globalPosition; mouseActionUpdate(); @@ -2506,7 +2553,12 @@ void ListWidget::mouseActionUpdate() { std::clamp(mousePosition.x(), 0, width()), std::clamp(mousePosition.y(), _visibleTop, _visibleBottom)); - const auto view = strictFindItemByY(point.y()); + const auto reactionState = _reactionsManager->buttonTextState(point); + const auto reactionItem = session().data().message(reactionState.itemId); + const auto reactionView = viewForItem(reactionItem); + const auto view = reactionView + ? reactionView + : strictFindItemByY(point.y()); const auto item = view ? view->data().get() : nullptr; const auto itemPoint = mapPointToItem(point, view); _overState = MouseState( @@ -2519,13 +2571,23 @@ void ListWidget::mouseActionUpdate() { _overElement = view; repaintItem(_overElement); } + _reactionsManager->updateButton(view + ? reactionButtonParameters( + view, + itemPoint, + reactionState) + : Reactions::ButtonParameters()); TextState dragState; ClickHandlerHost *lnkhost = nullptr; auto inTextSelection = (_overState.pointState != PointState::Outside) && (_overState.itemId == _pressState.itemId) && hasSelectedText(); - if (view) { + const auto overReaction = reactionView && reactionState.link; + if (overReaction) { + dragState = reactionState; + lnkhost = reactionView; + } else if (view) { auto cursorDeltaLength = [&] { auto cursorDelta = (_overState.point - _pressState.point); return cursorDelta.manhattanLength(); @@ -2798,6 +2860,7 @@ std::unique_ptr ListWidget::prepareDrag() { void ListWidget::performDrag() { if (auto mimeData = prepareDrag()) { // This call enters event loop and can destroy any QObject. + _reactionsManager->updateButton({}); _controller->widget()->launchDrag( std::move(mimeData), crl::guard(this, [=] { mouseActionUpdate(QCursor::pos()); }));; @@ -2959,6 +3022,7 @@ void ListWidget::itemRemoved(not_null item) { viewReplaced(view, nullptr); _views.erase(i); + _reactionsManager->remove(item->fullId()); updateItemsGeometry(); } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 8fe56d6b9..7b799bf14 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -31,8 +31,14 @@ class SessionController; namespace Data { struct Group; class CloudImageView; +struct Reaction; } // namespace Data +namespace HistoryView::Reactions { +class Manager; +struct ButtonParameters; +} // namespace HistoryView::Reactions + namespace HistoryView { struct TextState; @@ -106,7 +112,8 @@ public: return listCopyRestrictionType(nullptr); } virtual CopyRestrictionType listSelectRestrictionType() = 0; - + virtual auto listAllowedReactionsValue() + -> rpl::producer> = 0; }; struct SelectionData { @@ -236,6 +243,11 @@ public: [[nodiscard]] rpl::producer showMessageRequested() const; void replyNextMessage(FullMsgId fullId, bool next = true); + [[nodiscard]] Reactions::ButtonParameters reactionButtonParameters( + not_null view, + QPoint position, + const TextState &reactionState) const; + // ElementDelegate interface. Context elementContext() override; std::unique_ptr elementCreate( @@ -424,6 +436,7 @@ private: const SelectedMap::const_iterator &i); bool hasSelectedText() const; bool hasSelectedItems() const; + bool inSelectionMode() const; bool overSelectedItems() const; void clearTextSelection(); void clearSelected(); @@ -552,6 +565,8 @@ private: base::unique_qptr _emptyInfo = nullptr; + std::unique_ptr _reactionsManager; + int _minHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 40893f1b4..02a33c746 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -683,6 +683,11 @@ CopyRestrictionType PinnedWidget::listSelectRestrictionType() { return SelectRestrictionTypeFor(_history->peer); } +auto PinnedWidget::listAllowedReactionsValue() +-> rpl::producer> { + return Data::PeerAllowedReactionsValue(_history->peer); +} + void PinnedWidget::confirmDeleteSelected() { ConfirmDeleteSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index 0021fdc24..49725bf54 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -105,6 +105,8 @@ public: not_null listChatTheme() override; CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override; CopyRestrictionType listSelectRestrictionType() override; + auto listAllowedReactionsValue() + -> rpl::producer> override; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.cpp b/Telegram/SourceFiles/history/view/history_view_react_button.cpp index 0f3cacbfb..23bdf7f46 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_button.cpp @@ -220,6 +220,9 @@ void Button::applyState(State state) { } void Button::applyState(State state, Fn update) { + if (state == State::Hidden) { + _expandTimer.cancel(); + } const auto finalHeight = (state == State::Inside) ? _expandedHeight : _collapsed.height(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 249f03b20..83944261f 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_channel.h" #include "data/data_replies_list.h" +#include "data/data_peer_values.h" #include "data/data_changes.h" #include "data/data_send_action.h" #include "storage/storage_media_prepare.h" @@ -1925,6 +1926,11 @@ CopyRestrictionType RepliesWidget::listSelectRestrictionType() { return SelectRestrictionTypeFor(_history->peer); } +auto RepliesWidget::listAllowedReactionsValue() +-> rpl::producer> { + return Data::PeerAllowedReactionsValue(_history->peer); +} + void RepliesWidget::confirmDeleteSelected() { ConfirmDeleteSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index b9c717202..5041548c7 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -141,6 +141,8 @@ public: not_null listChatTheme() override; CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override; CopyRestrictionType listSelectRestrictionType() override; + auto listAllowedReactionsValue() + -> rpl::producer> override; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 7e9ca4800..c330c8b9f 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_scheduled_messages.h" #include "data/data_user.h" +#include "data/data_message_reactions.h" #include "storage/storage_media_prepare.h" #include "storage/storage_account.h" #include "inline_bots/inline_bot_result.h" @@ -1241,6 +1242,11 @@ CopyRestrictionType ScheduledWidget::listSelectRestrictionType() { return CopyRestrictionType::None; } +auto ScheduledWidget::listAllowedReactionsValue() +-> rpl::producer> { + return rpl::single(std::vector()); +} + void ScheduledWidget::confirmSendNowSelected() { ConfirmSendNowSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index ae2e8bb57..e6b93979d 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -122,6 +122,8 @@ public: not_null listChatTheme() override; CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override; CopyRestrictionType listSelectRestrictionType() override; + auto listAllowedReactionsValue() + -> rpl::producer> override; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index aab21eff3..33ed778aa 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -967,7 +967,7 @@ reactionInfoBetween: 3px; reactionCornerSize: size(40px, 32px); reactionCornerRadius: 14px; -reactionCornerCenter: point(-8px, -5px); +reactionCornerCenter: point(-10px, -8px); reactionCornerImage: 24px; reactionCornerShadow: margins(4px, 4px, 4px, 8px); reactionCornerActiveAreaPadding: margins(10px, 10px, 10px, 10px);