Add peer-list-like view for message reactions list.

This commit is contained in:
John Preston 2019-11-12 15:21:40 +03:00
parent b5d80a3c15
commit b151d210bf
25 changed files with 516 additions and 39 deletions

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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<PeerListController> _controller;
Fn<void(PeerListBox*)> _init;
bool _scrollBottomFixed = false;
int _addedTopScrollSkip = 0;
};

View File

@ -669,6 +669,9 @@ void InnerWidget::elementReplyTo(const FullMsgId &to) {
void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
}
void InnerWidget::elementShowReactions(not_null<const Element*> view) {
}
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
memento->setFilter(std::move(_filter));
memento->setAdmins(std::move(_admins));

View File

@ -139,6 +139,8 @@ public:
void elementReplyTo(const FullMsgId &to) override;
void elementStartInteraction(
not_null<const HistoryView::Element*> view) override;
void elementShowReactions(
not_null<const HistoryView::Element*> view) override;
~InnerWidget();

View File

@ -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<const Element*> view) {
_controller->emojiInteractions().startOutgoing(view);
}
void HistoryInner::elementShowReactions(not_null<const Element*> 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<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
Instance->elementStartInteraction(view);
}
}
void elementShowReactions(not_null<const Element*> view) override {
if (Instance) {
Instance->elementShowReactions(view);
}
}
};
static Result result;

View File

@ -116,6 +116,7 @@ public:
not_null<Ui::PathShiftGradient*> elementPathShiftGradient();
void elementReplyTo(const FullMsgId &to);
void elementStartInteraction(not_null<const Element*> view);
void elementShowReactions(not_null<const Element*> view);
void updateBotInfo(bool recount = true);

View File

@ -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<const HistoryItem*> 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*> message) {
return result;
}
BottomInfo::Context BottomInfoContextFromMessage(
not_null<Message*> message) {
auto result = BottomInfo::Context();
result.reactions = std::make_shared<LambdaClickHandler>([=] {
message->delegate()->elementShowReactions(message);
});
return result;
}
} // namespace HistoryView

View File

@ -19,6 +19,7 @@ namespace HistoryView {
using PaintContext = Ui::ChatPaintContext;
class Message;
struct TextState;
class BottomInfo {
public:
@ -40,14 +41,19 @@ public:
std::optional<int> 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<const HistoryItem*> 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*> message);
[[nodiscard]] BottomInfo::Context BottomInfoContextFromMessage(
not_null<Message*> message);
} // namespace HistoryView

View File

@ -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<const Element*> view) {
}
void SimpleElementDelegate::elementShowReactions(
not_null<const Element*> 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(

View File

@ -91,6 +91,7 @@ public:
virtual not_null<Ui::PathShiftGradient*> elementPathShiftGradient() = 0;
virtual void elementReplyTo(const FullMsgId &to) = 0;
virtual void elementStartInteraction(not_null<const Element*> view) = 0;
virtual void elementShowReactions(not_null<const Element*> view) = 0;
virtual ~ElementDelegate() {
}
@ -148,6 +149,7 @@ public:
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullMsgId &to) override;
void elementStartInteraction(not_null<const Element*> view) override;
void elementShowReactions(not_null<const Element*> view) override;
protected:
[[nodiscard]] not_null<Window::SessionController*> 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,

View File

@ -1458,6 +1458,9 @@ void ListWidget::elementReplyTo(const FullMsgId &to) {
void ListWidget::elementStartInteraction(not_null<const Element*> view) {
}
void ListWidget::elementShowReactions(not_null<const Element*> view) {
}
void ListWidget::saveState(not_null<ListMemento*> memento) {
memento->setAroundPosition(_aroundPosition);
auto state = countScrollState();

View File

@ -278,6 +278,7 @@ public:
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullMsgId &to) override;
void elementStartInteraction(not_null<const Element*> view) override;
void elementShowReactions(not_null<const Element*> view) override;
void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w);

View File

@ -242,7 +242,9 @@ Message::Message(
not_null<HistoryMessage*> 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 {

View File

@ -72,7 +72,7 @@ public:
int bottom,
int width,
InfoDisplayType type) const override;
bool pointInTime(
TextState bottomInfoTextState(
int right,
int bottom,
QPoint point,

View File

@ -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()) {

View File

@ -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);

View File

@ -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);

View File

@ -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(

View File

@ -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);

View File

@ -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::SessionController*> window,
not_null<HistoryItem*> item,
rpl::producer<QString> switches);
Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
void loadMoreRows() override;
private:
using AllEntry = std::pair<not_null<UserData*>, QString>;
void loadMore(const QString &offset);
bool appendRow(not_null<UserData*> user, QString reaction = QString());
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user,
QString reaction) const;
void showReaction(const QString &reaction);
const not_null<Window::SessionController*> _window;
const not_null<HistoryItem*> _item;
MTP::Sender _api;
QString _shownReaction;
std::vector<AllEntry> _all;
QString _allOffset;
std::vector<not_null<UserData*>> _filtered;
QString _filteredOffset;
mtpRequestId _loadRequestId = 0;
};
Controller::Controller(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item,
rpl::producer<QString> 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<PeerListRow*> row) {
const auto peerId = row->peer()->id;
const auto window = _window;
crl::on_main(&session(), [=] {
_window->showPeerHistory(peerId);
});
}
bool Controller::appendRow(not_null<UserData*> user, QString reaction) {
if (delegate()->peerListFindRow(user->id.value)) {
return false;
}
delegate()->peerListAppendRow(createRow(user, reaction));
return true;
}
std::unique_ptr<PeerListRow> Controller::createRow(
not_null<UserData*> user,
QString reaction) const {
auto result = std::make_unique<PeerListRow>(user);
if (!reaction.isEmpty()) {
result->setCustomStatus(reaction);
}
return result;
}
} // namespace
object_ptr<Ui::BoxContent> ReactionsListBox(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item) {
Expects(IsServerMsgId(item->id));
const auto tabRequests = std::make_shared<rpl::event_stream<QString>>();
const auto initBox = [=](not_null<PeerListBox*> 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<PeerListBox>(
std::make_unique<Controller>(window, item, tabRequests->events()),
initBox);
}
} // namespace HistoryView

View File

@ -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<Ui::BoxContent> ReactionsListBox(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item);
} // namespace HistoryView

View File

@ -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<Selector*> CreateReactionSelector(
not_null<QWidget*> parent,
const base::flat_map<QString, int> &items) {
const auto sectionsCount = int(items.size() + 1);
const auto result = Ui::CreateChild<Selector>(parent.get());
using Entry = std::pair<int, QString>;
auto sorted = std::vector<Entry>();
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<Ui::SettingsSlider>(
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

View File

@ -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<void(int, int)> move;
Fn<void(int)> resizeToWidth;
Fn<rpl::producer<QString>()> changes;
Fn<int()> height;
};
not_null<Selector*> CreateReactionSelector(
not_null<QWidget*> parent,
const base::flat_map<QString, int> &items);
} // namespace HistoryView

View File

@ -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);