diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 4cdb45a56..453056f6a 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -621,6 +621,10 @@ PRIVATE history/view/media/history_view_theme_document.cpp history/view/media/history_view_web_page.h history/view/media/history_view_web_page.cpp + history/view/reactions/message_reactions_list.cpp + history/view/reactions/message_reactions_list.h + history/view/reactions/message_reactions_selector.cpp + history/view/reactions/message_reactions_selector.h history/view/history_view_bottom_info.cpp history/view/history_view_bottom_info.h history/view/history_view_contact_status.cpp diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index bdf951ee4..bf1917776 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -97,8 +97,13 @@ void PeerListBox::createMultiSelect() { _select->moveToLeft(0, 0); } +void PeerListBox::setAddedTopScrollSkip(int skip) { + _addedTopScrollSkip = skip; + updateScrollSkips(); +} + int PeerListBox::getTopScrollSkip() const { - auto result = 0; + auto result = _addedTopScrollSkip; if (_select && !_select->isHidden()) { result += _select->height(); } @@ -109,7 +114,7 @@ void PeerListBox::updateScrollSkips() { // If we show / hide the search field scroll top is fixed. // If we resize search field by bubbles scroll bottom is fixed. setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed); - if (!_select->animating()) { + if (_select && !_select->animating()) { _scrollBottomFixed = true; } } @@ -186,8 +191,15 @@ void PeerListBox::paintEvent(QPaintEvent *e) { const auto &bg = (_controller->listSt() ? *_controller->listSt() : st::peerListBox).bg; + const auto fill = QRect( + 0, + _addedTopScrollSkip, + width(), + height() - _addedTopScrollSkip); for (const auto &rect : e->region()) { - p.fillRect(rect, bg); + if (const auto part = rect.intersected(fill); !part.isEmpty()) { + p.fillRect(part, bg); + } } } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 9591dd3ae..39260c4e3 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -1008,6 +1008,8 @@ public: int peerListSelectedRowsCount() override; void peerListScrollToTop() override; + void setAddedTopScrollSkip(int skip); + protected: void prepare() override; void setInnerFocus() override; @@ -1047,5 +1049,6 @@ private: std::unique_ptr _controller; Fn _init; bool _scrollBottomFixed = false; + int _addedTopScrollSkip = 0; }; 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 37d737b92..7b3f24f16 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -669,6 +669,9 @@ void InnerWidget::elementReplyTo(const FullMsgId &to) { void InnerWidget::elementStartInteraction(not_null view) { } +void InnerWidget::elementShowReactions(not_null view) { +} + void InnerWidget::saveState(not_null memento) { memento->setFilter(std::move(_filter)); memento->setAdmins(std::move(_admins)); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index 65792b3d4..4c4e73efa 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -139,6 +139,8 @@ public: void elementReplyTo(const FullMsgId &to) override; void elementStartInteraction( not_null view) override; + void elementShowReactions( + not_null view) override; ~InnerWidget(); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index aa430e29f..9a664f70a 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -15,13 +15,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_web_page.h" -#include "history/history_item_components.h" -#include "history/history_item_text.h" +#include "history/view/reactions/message_reactions_list.h" #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_context_menu.h" #include "history/view/history_view_emoji_interactions.h" +#include "history/history_item_components.h" +#include "history/history_item_text.h" #include "ui/chat/chat_style.h" #include "ui/widgets/popup_menu.h" #include "ui/image/image.h" @@ -37,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/inactive_press.h" #include "window/window_adaptive.h" #include "window/window_session_controller.h" +#include "window/window_controller.h" #include "window/window_peer_menu.h" #include "window/window_controller.h" #include "window/notifications_manager.h" @@ -2867,6 +2869,12 @@ void HistoryInner::elementStartInteraction(not_null view) { _controller->emojiInteractions().startOutgoing(view); } +void HistoryInner::elementShowReactions(not_null view) { + _controller->window().show(HistoryView::ReactionsListBox( + _controller, + view->data())); +} + auto HistoryInner::getSelectionState() const -> HistoryView::TopBarWidget::SelectedState { auto result = HistoryView::TopBarWidget::SelectedState {}; @@ -3776,6 +3784,12 @@ not_null HistoryInner::ElementDelegate() { Instance->elementStartInteraction(view); } } + void elementShowReactions(not_null view) override { + if (Instance) { + Instance->elementShowReactions(view); + } + } + }; static Result result; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 8e0302223..b6324ef84 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -116,6 +116,7 @@ public: not_null elementPathShiftGradient(); void elementReplyTo(const FullMsgId &to); void elementStartInteraction(not_null view); + void elementShowReactions(not_null view); void updateBotInfo(bool recount = true); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 3179ef7fc..128ce3724 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -14,20 +14,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "history/history_message.h" #include "history/view/history_view_message.h" +#include "history/view/history_view_cursor_state.h" #include "data/data_message_reactions.h" #include "styles/style_chat.h" #include "styles/style_dialogs.h" namespace HistoryView { -BottomInfo::BottomInfo(Data &&data) +BottomInfo::BottomInfo(Data &&data, Context &&context) : _data(std::move(data)) +, _context(std::move(context)) , _reactions(st::msgMinWidth / 2) { layout(); } -void BottomInfo::update(Data &&data, int availableWidth) { +void BottomInfo::update(Data &&data, Context &&context, int availableWidth) { _data = std::move(data); + _context = std::move(context); layout(); if (!_size.isEmpty()) { resizeToWidth(std::min(optimalSize().width(), availableWidth)); @@ -53,13 +56,38 @@ int BottomInfo::firstLineWidth() const { return noReactionsWidth; } -bool BottomInfo::pointInTime(QPoint position) const { - return QRect( +TextState BottomInfo::textState( + not_null item, + QPoint position) const { + auto result = TextState(item); + if (!_reactions.isEmpty()) { + const auto reactionsPosition = [&] { + if (_size.height() == _optimalSize.height()) { + return QPoint(0, 0); + } + const auto available = _size.width(); + const auto use = std::min(available, _reactions.maxWidth()); + return QPoint(_size.width() - use, st::msgDateFont->height); + }(); + const auto state = _reactions.getStateLeft( + position - reactionsPosition, + std::min(_size.width(), _reactions.maxWidth()), + _size.width()); + if (state.uponSymbol) { + result.link = _context.reactions; + return result; + } + } + const auto inTime = QRect( _size.width() - _dateWidth, 0, _dateWidth, st::msgDateFont->height ).contains(position); + if (inTime) { + result.cursor = CursorState::Date; + } + return result; } bool BottomInfo::isSignedAuthorElided() const { @@ -336,4 +364,13 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null message) { return result; } +BottomInfo::Context BottomInfoContextFromMessage( + not_null message) { + auto result = BottomInfo::Context(); + result.reactions = std::make_shared([=] { + message->delegate()->elementShowReactions(message); + }); + return result; +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index a61c1df48..4750aec32 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -19,6 +19,7 @@ namespace HistoryView { using PaintContext = Ui::ChatPaintContext; class Message; +struct TextState; class BottomInfo { public: @@ -40,14 +41,19 @@ public: std::optional replies; Flags flags; }; - explicit BottomInfo(Data &&data); + struct Context { + ClickHandlerPtr reactions; + }; + BottomInfo(Data &&data, Context &&context); - void update(Data &&data, int availableWidth); + void update(Data &&data, Context &&context, int availableWidth); [[nodiscard]] QSize optimalSize() const; [[nodiscard]] QSize size() const; [[nodiscard]] int firstLineWidth() const; - [[nodiscard]] bool pointInTime(QPoint position) const; + [[nodiscard]] TextState textState( + not_null item, + QPoint position) const; [[nodiscard]] bool isSignedAuthorElided() const; void paint( @@ -69,6 +75,7 @@ private: void countOptimalSize(); Data _data; + Context _context; QSize _optimalSize; QSize _size; Ui::Text::String _authorEditedDate; @@ -83,4 +90,7 @@ private: [[nodiscard]] BottomInfo::Data BottomInfoDataFromMessage( not_null message); +[[nodiscard]] BottomInfo::Context BottomInfoContextFromMessage( + not_null message); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 891e38876..144816711 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media_grouped.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_large_emoji.h" +#include "history/view/history_view_cursor_state.h" #include "history/history.h" #include "base/unixtime.h" #include "core/application.h" @@ -179,11 +180,14 @@ auto SimpleElementDelegate::elementPathShiftGradient() void SimpleElementDelegate::elementReplyTo(const FullMsgId &to) { } - void SimpleElementDelegate::elementStartInteraction( not_null view) { } +void SimpleElementDelegate::elementShowReactions( + not_null view) { +} + TextSelection UnshiftItemSelection( TextSelection selection, uint16 byLength) { @@ -956,12 +960,12 @@ void Element::drawInfo( InfoDisplayType type) const { } -bool Element::pointInTime( +TextState Element::bottomInfoTextState( int right, int bottom, QPoint point, InfoDisplayType type) const { - return false; + return TextState(); } TextSelection Element::adjustSelection( diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index bb16805e7..2141c8414 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -91,6 +91,7 @@ public: virtual not_null elementPathShiftGradient() = 0; virtual void elementReplyTo(const FullMsgId &to) = 0; virtual void elementStartInteraction(not_null view) = 0; + virtual void elementShowReactions(not_null view) = 0; virtual ~ElementDelegate() { } @@ -148,6 +149,7 @@ public: not_null elementPathShiftGradient() override; void elementReplyTo(const FullMsgId &to) override; void elementStartInteraction(not_null view) override; + void elementShowReactions(not_null view) override; protected: [[nodiscard]] not_null controller() const { @@ -298,7 +300,7 @@ public: int bottom, int width, InfoDisplayType type) const; - virtual bool pointInTime( + virtual TextState bottomInfoTextState( int right, int bottom, QPoint point, diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 8a969639a..a3d17e04b 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1458,6 +1458,9 @@ void ListWidget::elementReplyTo(const FullMsgId &to) { void ListWidget::elementStartInteraction(not_null view) { } +void ListWidget::elementShowReactions(not_null view) { +} + void ListWidget::saveState(not_null memento) { memento->setAroundPosition(_aroundPosition); auto state = countScrollState(); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index f12a1436c..33a781691 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -278,6 +278,7 @@ public: not_null elementPathShiftGradient() override; void elementReplyTo(const FullMsgId &to) override; void elementStartInteraction(not_null view) override; + void elementShowReactions(not_null view) override; void setEmptyInfoWidget(base::unique_qptr &&w); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index a24503697..2e1ef5d0a 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -242,7 +242,9 @@ Message::Message( not_null data, Element *replacing) : Element(delegate, data, replacing) -, _bottomInfo(BottomInfoDataFromMessage(this)) { +, _bottomInfo( + BottomInfoDataFromMessage(this), + BottomInfoContextFromMessage(this)) { initLogEntryOriginal(); initPsa(); } @@ -1260,17 +1262,18 @@ TextState Message::textState( } } - auto checkForPointInTime = [&] { + auto checkBottomInfoState = [&] { if (mediaOnBottom && (entry || media->customInfoLayout())) { return; } - const auto inDate = pointInTime( + const auto bottomInfoResult = bottomInfoTextState( bubble.left() + bubble.width(), bubble.top() + bubble.height(), point, InfoDisplayType::Default); - if (inDate) { - result.cursor = CursorState::Date; + if (bottomInfoResult.link + || bottomInfoResult.cursor != CursorState::None) { + result = bottomInfoResult; } }; if (inBubble) { @@ -1283,19 +1286,19 @@ TextState Message::textState( result = media->textState(point - QPoint(mediaLeft, mediaTop), request); result.symbol += item->_text.length(); } else if (getStateText(point, trect, &result, request)) { - checkForPointInTime(); + checkBottomInfoState(); return result; } else if (point.y() >= trect.y() + trect.height()) { result.symbol = item->_text.length(); } } else if (getStateText(point, trect, &result, request)) { - checkForPointInTime(); + checkBottomInfoState(); return result; } else if (point.y() >= trect.y() + trect.height()) { result.symbol = item->_text.length(); } } - checkForPointInTime(); + checkBottomInfoState(); if (const auto size = rightActionSize()) { const auto fastShareSkip = std::clamp( (g.height() - size->height()) / 2, @@ -1770,7 +1773,7 @@ void Message::drawInfo( context); } -bool Message::pointInTime( +TextState Message::bottomInfoTextState( int right, int bottom, QPoint point, @@ -1794,7 +1797,9 @@ bool Message::pointInTime( const auto size = _bottomInfo.size(); const auto infoLeft = infoRight - size.width(); const auto infoTop = infoBottom - size.height(); - return _bottomInfo.pointInTime({ infoLeft, infoTop }); + return _bottomInfo.textState( + data(), + point - QPoint{ infoLeft, infoTop }); } int Message::infoWidth() const { @@ -1811,7 +1816,10 @@ bool Message::isSignedAuthorElided() const { void Message::itemDataChanged() { const auto was = _bottomInfo.size(); - _bottomInfo.update(BottomInfoDataFromMessage(this), width()); + _bottomInfo.update( + BottomInfoDataFromMessage(this), + BottomInfoContextFromMessage(this), + width()); if (was != _bottomInfo.size()) { history()->owner().requestViewResize(this); } else { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index ed9db5c1a..0c00cb5dd 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -72,7 +72,7 @@ public: int bottom, int width, InfoDisplayType type) const override; - bool pointInTime( + TextState bottomInfoTextState( int right, int bottom, QPoint point, diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 2be39a618..6b65422e4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -868,8 +868,16 @@ TextState Gif::textState(QPoint point, StateRequest request) const { } } if (!inWebPage) { - if (_parent->pointInTime(fullRight, fullBottom, point, isRound ? InfoDisplayType::Background : InfoDisplayType::Image)) { - result.cursor = CursorState::Date; + const auto bottomInfoResult = _parent->bottomInfoTextState( + fullRight, + fullBottom, + point, + (isRound + ? InfoDisplayType::Background + : InfoDisplayType::Image)); + if (bottomInfoResult.link + || bottomInfoResult.cursor != CursorState::None) { + return bottomInfoResult; } } if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index e3732ae16..cc80266ec 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -287,8 +287,14 @@ TextState Location::textState(QPoint point, StateRequest request) const { if (_parent->media() == this) { auto fullRight = paintx + paintw; auto fullBottom = height(); - if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { - result.cursor = CursorState::Date; + const auto bottomInfoResult = _parent->bottomInfoTextState( + fullRight, + fullBottom, + point, + InfoDisplayType::Image); + if (bottomInfoResult.link + || bottomInfoResult.cursor != CursorState::None) { + return bottomInfoResult; } if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 5d677bef9..f094bd6a7 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -421,8 +421,14 @@ TextState GroupedMedia::textState(QPoint point, StateRequest request) const { } else if (_parent->media() == this) { auto fullRight = width(); auto fullBottom = height(); - if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { - result.cursor = CursorState::Date; + const auto bottomInfoResult = _parent->bottomInfoTextState( + fullRight, + fullBottom, + point, + InfoDisplayType::Image); + if (bottomInfoResult.link + || bottomInfoResult.cursor != CursorState::None) { + return bottomInfoResult; } if (const auto size = _parent->hasBubble() ? std::nullopt : _parent->rightActionSize()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 7d36d2a25..b2980977f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -367,8 +367,14 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { const auto fullRight = calculateFullRight(inner); const auto rightActionSize = _parent->rightActionSize(); auto fullBottom = height(); - if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Background)) { - result.cursor = CursorState::Date; + const auto bottomInfoResult = _parent->bottomInfoTextState( + fullRight, + fullBottom, + point, + InfoDisplayType::Background); + if (bottomInfoResult.link + || bottomInfoResult.cursor != CursorState::None) { + return bottomInfoResult; } if (rightActionSize) { const auto position = calculateFastActionPosition( diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 4e0bf9dd2..0d0b308e5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -464,8 +464,14 @@ TextState Photo::textState(QPoint point, StateRequest request) const { if (_caption.isEmpty() && _parent->media() == this) { auto fullRight = paintx + paintw; auto fullBottom = painty + painth; - if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { - result.cursor = CursorState::Date; + const auto bottomInfoResult = _parent->bottomInfoTextState( + fullRight, + fullBottom, + point, + InfoDisplayType::Image); + if (bottomInfoResult.link + || bottomInfoResult.cursor != CursorState::None) { + return bottomInfoResult; } if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { auto fastShareLeft = (fullRight + st::historyFastShareLeft); diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp new file mode 100644 index 000000000..02019ce3a --- /dev/null +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp @@ -0,0 +1,231 @@ +/* +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 "history/view/reactions/message_reactions_list.h" + +#include "history/view/reactions/message_reactions_selector.h" +#include "boxes/peer_list_box.h" +#include "window/window_session_controller.h" +#include "history/history_item.h" +#include "history/history.h" +#include "main/main_session.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "lang/lang_keys.h" + +namespace HistoryView { +namespace { + +constexpr auto kPerPageFirst = 20; +constexpr auto kPerPage = 200; + +class Controller final : public PeerListController { +public: + Controller( + not_null window, + not_null item, + rpl::producer switches); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + void loadMoreRows() override; + +private: + using AllEntry = std::pair, QString>; + + void loadMore(const QString &offset); + bool appendRow(not_null user, QString reaction = QString()); + std::unique_ptr createRow( + not_null user, + QString reaction) const; + void showReaction(const QString &reaction); + + const not_null _window; + const not_null _item; + MTP::Sender _api; + + QString _shownReaction; + + std::vector _all; + QString _allOffset; + + std::vector> _filtered; + QString _filteredOffset; + + mtpRequestId _loadRequestId = 0; + +}; + +Controller::Controller( + not_null window, + not_null item, + rpl::producer switches) +: _window(window) +, _item(item) +, _api(&window->session().mtp()) { + std::move( + switches + ) | rpl::filter([=](const QString &reaction) { + return (_shownReaction != reaction); + }) | rpl::start_with_next([=](const QString &reaction) { + showReaction(reaction); + }, lifetime()); +} + +Main::Session &Controller::session() const { + return _window->session(); +} + +void Controller::prepare() { + setDescriptionText(tr::lng_contacts_loading(tr::now)); + delegate()->peerListRefreshRows(); + + loadMore(QString()); +} + +void Controller::showReaction(const QString &reaction) { + if (_shownReaction == reaction) { + return; + } + + _api.request(base::take(_loadRequestId)).cancel(); + while (const auto count = delegate()->peerListFullRowsCount()) { + delegate()->peerListRemoveRow(delegate()->peerListRowAt(count - 1)); + } + + _shownReaction = reaction; + if (_shownReaction.isEmpty()) { + _filtered.clear(); + for (const auto &[user, reaction] : _all) { + appendRow(user, reaction); + } + } else { + _filtered = _all | ranges::view::filter([&](const AllEntry &entry) { + return (entry.second == reaction); + }) | ranges::view::transform( + &AllEntry::first + ) | ranges::to_vector; + for (const auto user : _filtered) { + appendRow(user); + } + loadMore(QString()); + } + setDescriptionText(delegate()->peerListFullRowsCount() + ? QString() + : tr::lng_contacts_loading(tr::now)); + delegate()->peerListRefreshRows(); +} + +void Controller::loadMoreRows() { + const auto &offset = _shownReaction.isEmpty() + ? _allOffset + : _filteredOffset; + if (_loadRequestId || offset.isEmpty()) { + return; + } + loadMore(offset); +} + +void Controller::loadMore(const QString &offset) { + _api.request(_loadRequestId).cancel(); + + using Flag = MTPmessages_GetMessageReactionsList::Flag; + const auto flags = Flag(0) + | (offset.isEmpty() ? Flag(0) : Flag::f_offset) + | (_shownReaction.isEmpty() ? Flag(0) : Flag::f_reaction); + _loadRequestId = _api.request(MTPmessages_GetMessageReactionsList( + MTP_flags(flags), + _item->history()->peer->input, + MTP_int(_item->id), + MTP_string(_shownReaction), + MTP_string(offset), + MTP_int(kPerPageFirst) + )).done([=](const MTPmessages_MessageReactionsList &result) { + _loadRequestId = 0; + const auto filtered = !_shownReaction.isEmpty(); + result.match([&](const MTPDmessages_messageReactionsList &data) { + const auto sessionData = &session().data(); + sessionData->processUsers(data.vusers()); + (filtered ? _filteredOffset : _allOffset) + = data.vnext_offset().value_or_empty(); + for (const auto &reaction : data.vreactions().v) { + reaction.match([&](const MTPDmessageUserReaction &data) { + const auto user = sessionData->userLoaded( + data.vuser_id().v); + const auto reaction = filtered ? QString() : qs(data.vreaction()); + if (user && appendRow(user, reaction)) { + if (filtered) { + _filtered.emplace_back(user); + } else { + _all.emplace_back(user, reaction); + } + } + }); + } + }); + setDescriptionText(QString()); + delegate()->peerListRefreshRows(); + }).send(); +} + +void Controller::rowClicked(not_null row) { + const auto peerId = row->peer()->id; + const auto window = _window; + crl::on_main(&session(), [=] { + _window->showPeerHistory(peerId); + }); +} + +bool Controller::appendRow(not_null user, QString reaction) { + if (delegate()->peerListFindRow(user->id.value)) { + return false; + } + delegate()->peerListAppendRow(createRow(user, reaction)); + return true; +} + +std::unique_ptr Controller::createRow( + not_null user, + QString reaction) const { + auto result = std::make_unique(user); + if (!reaction.isEmpty()) { + result->setCustomStatus(reaction); + } + return result; +} + +} // namespace + +object_ptr ReactionsListBox( + not_null window, + not_null item) { + Expects(IsServerMsgId(item->id)); + + const auto tabRequests = std::make_shared>(); + const auto initBox = [=](not_null box) { + box->setNoContentMargin(true); + + const auto selector = CreateReactionSelector(box, item->reactions()); + box->widthValue( + ) | rpl::start_with_next([=](int width) { + selector->resizeToWidth(width); + selector->move(0, 0); + }, box->lifetime()); + selector->changes( + ) | rpl::start_to_stream(*tabRequests, box->lifetime()); + box->setAddedTopScrollSkip(selector->height()); + box->addButton(tr::lng_close(), [=] { + box->closeBox(); + }); + }; + return Box( + std::make_unique(window, item, tabRequests->events()), + initBox); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h new file mode 100644 index 000000000..ad61713ff --- /dev/null +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h @@ -0,0 +1,28 @@ +/* +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 +*/ +#pragma once + +#include "base/object_ptr.h" + +class HistoryItem; + +namespace Window { +class SessionController; +} // namespace Window + +namespace Ui { +class BoxContent; +} // namespace Ui + +namespace HistoryView { + +object_ptr ReactionsListBox( + not_null window, + not_null item); + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp new file mode 100644 index 000000000..1c4bb6a63 --- /dev/null +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp @@ -0,0 +1,59 @@ +/* +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 "history/view/reactions/message_reactions_selector.h" + +#include "ui/widgets/discrete_sliders.h" +#include "styles/style_layers.h" + +namespace HistoryView { + +not_null CreateReactionSelector( + not_null parent, + const base::flat_map &items) { + const auto sectionsCount = int(items.size() + 1); + const auto result = Ui::CreateChild(parent.get()); + using Entry = std::pair; + auto sorted = std::vector(); + for (const auto &[reaction, count] : items) { + sorted.emplace_back(count, reaction); + } + ranges::sort(sorted, std::greater<>(), &Entry::first); + const auto count = ranges::accumulate( + sorted, + 0, + std::plus<>(), + &Entry::first); + auto labels = QStringList() << ("ALL (" + QString::number(count) + ")"); + for (const auto &[count, reaction] : sorted) { + labels.append(reaction + " (" + QString::number(count) + ")"); + } + auto tabs = Ui::CreateChild( + parent.get(), + st::defaultTabsSlider); + tabs->setSections(labels | ranges::to_vector); + tabs->setRippleTopRoundRadius(st::boxRadius); + result->move = [=](int x, int y) { + tabs->moveToLeft(x, y); + }; + result->resizeToWidth = [=](int width) { + tabs->resizeToWidth(std::min( + width, + sectionsCount * st::defaultTabsSlider.height * 2)); + }; + result->height = [=] { + return tabs->height() - st::lineWidth; + }; + result->changes = [=] { + return tabs->sectionActivated() | rpl::map([=](int section) { + return (section > 0) ? sorted[section - 1].second : QString(); + }); + }; + return result; +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h new file mode 100644 index 000000000..8906962d2 --- /dev/null +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h @@ -0,0 +1,23 @@ +/* +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 +*/ +#pragma once + +namespace HistoryView { + +struct Selector { + Fn move; + Fn resizeToWidth; + Fn()> changes; + Fn height; +}; + +not_null CreateReactionSelector( + not_null parent, + const base::flat_map &items); + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp b/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp index b53875dab..d8a35fdc0 100644 --- a/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp +++ b/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp @@ -164,7 +164,7 @@ DiscreteSlider::Section::Section( SettingsSlider::SettingsSlider( QWidget *parent, - const style::SettingsSlider &st) + const style::SettingsSlider &st) : DiscreteSlider(parent) , _st(st) { setSelectOnPress(_st.ripple.showDuration == 0);