Add a semi-nice looking box with reactions overview.

This commit is contained in:
John Preston 2021-12-29 19:55:53 +03:00
parent 3aacae2cb2
commit c875f367e6
6 changed files with 228 additions and 36 deletions

View file

@ -1085,7 +1085,10 @@ void AddWhoReactedAction(
strong->hideMenu();
}
if (const auto item = controller->session().data().message(itemId)) {
controller->window().show(ReactionsListBox(controller, item));
controller->window().show(ReactionsListBox(
controller,
item,
QString()));
}
};
if (!menu->empty()) {

View file

@ -28,6 +28,7 @@ public:
Controller(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item,
const QString &selected,
rpl::producer<QString> switches);
Main::Session &session() const override;
@ -64,10 +65,12 @@ private:
Controller::Controller(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item,
const QString &selected,
rpl::producer<QString> switches)
: _window(window)
, _item(item)
, _api(&window->session().mtp()) {
, _api(&window->session().mtp())
, _shownReaction(selected) {
std::move(
switches
) | rpl::filter([=](const QString &reaction) {
@ -189,42 +192,113 @@ bool Controller::appendRow(not_null<UserData*> user, QString reaction) {
return true;
}
class Row final : public PeerListRow {
public:
Row(not_null<PeerData*> peer, const QString &reaction);
QSize rightActionSize() const override;
QMargins rightActionMargins() const override;
bool rightActionDisabled() const override;
void rightActionPaint(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) override;
private:
EmojiPtr _emoji = nullptr;
};
Row::Row(not_null<PeerData*> peer, const QString &reaction)
: PeerListRow(peer)
, _emoji(Ui::Emoji::Find(reaction)) {
}
QSize Row::rightActionSize() const {
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
return _emoji ? QSize(size, size) : QSize();
}
QMargins Row::rightActionMargins() const {
if (!_emoji) {
return QMargins();
}
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
return QMargins(
size / 2,
(st::defaultPeerList.item.height - size) / 2,
(size * 3) / 2,
0);
}
bool Row::rightActionDisabled() const {
return true;
}
void Row::rightActionPaint(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) {
if (!_emoji) {
return;
}
// #TODO reactions
Ui::Emoji::Draw(p, _emoji, Ui::Emoji::GetSizeNormal(), x, y);
}
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;
return std::make_unique<Row>(user, reaction);
}
} // namespace
object_ptr<Ui::BoxContent> ReactionsListBox(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item) {
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item,
QString selected) {
Expects(IsServerMsgId(item->id));
if (!item->reactions().contains(selected)) {
selected = QString();
}
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());
const auto selector = CreateReactionSelector(
box,
item->reactions(),
selected);
selector->changes(
) | rpl::start_to_stream(*tabRequests, box->lifetime());
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());
selector->heightValue(
) | rpl::start_with_next([=](int height) {
box->setAddedTopScrollSkip(height);
}, box->lifetime());
box->addButton(tr::lng_close(), [=] {
box->closeBox();
});
};
return Box<PeerListBox>(
std::make_unique<Controller>(window, item, tabRequests->events()),
std::make_unique<Controller>(
window,
item,
selected,
tabRequests->events()),
initBox);
}

View file

@ -23,6 +23,7 @@ namespace HistoryView {
object_ptr<Ui::BoxContent> ReactionsListBox(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item);
not_null<HistoryItem*> item,
QString selected);
} // namespace HistoryView

View file

@ -7,15 +7,95 @@ 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"
#include "ui/rp_widget.h"
#include "ui/abstract_button.h"
#include "styles/style_widgets.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
not_null<Ui::AbstractButton*> CreateTab(
not_null<QWidget*> parent,
const style::MultiSelect &st,
const QString &reaction,
int count,
rpl::producer<bool> selected) {
struct State {
bool selected = false;
QImage cache;
};
const auto stm = &st.item;
const auto text = QString("%L1").arg(count);
const auto font = st::semiboldFont;
const auto textWidth = font->width(text);
const auto result = Ui::CreateChild<Ui::AbstractButton>(parent.get());
const auto width = stm->height
+ stm->padding.left()
+ textWidth
+ stm->padding.right();
result->resize(width, stm->height);
const auto state = result->lifetime().make_state<State>();
std::move(
selected
) | rpl::start_with_next([=](bool selected) {
state->selected = selected;
state->cache = QImage();
result->update();
}, result->lifetime());
result->paintRequest(
) | rpl::start_with_next([=] {
if (state->cache.isNull()) {
const auto factor = style::DevicePixelRatio();
state->cache = QImage(
result->size() * factor,
QImage::Format_ARGB32_Premultiplied);
state->cache.setDevicePixelRatio(factor);
state->cache.fill(Qt::transparent);
auto p = QPainter(&state->cache);
const auto height = stm->height;
const auto radius = height / 2;
p.setPen(Qt::NoPen);
p.setBrush(state->selected ? stm->textActiveBg : stm->textBg);
{
PainterHighQualityEnabler hq(p);
p.drawRoundedRect(result->rect(), radius, radius);
}
const auto skip = st::reactionsTabIconSkip;
const auto icon = QRect(skip, 0, height, height);
if (const auto emoji = Ui::Emoji::Find(reaction)) {
// #TODO reactions
const auto size = Ui::Emoji::GetSizeNormal();
const auto shift = (height - (size / factor)) / 2;
Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift);
} else {
(state->selected
? st::reactionsTabAllSelected
: st::reactionsTabAll).paintInCenter(p, icon);
}
const auto textLeft = height + stm->padding.left();
p.setPen(state->selected ? stm->textActiveFg : stm->textFg);
p.setFont(font);
p.drawText(textLeft, stm->padding.top() + font->ascent, text);
}
QPainter(result).drawImage(0, 0, state->cache);
}, result->lifetime());
return result;
}
} // namespace
not_null<Selector*> CreateReactionSelector(
not_null<QWidget*> parent,
const base::flat_map<QString, int> &items) {
const auto sectionsCount = int(items.size() + 1);
const base::flat_map<QString, int> &items,
const QString &selected) {
struct State {
rpl::variable<QString> selected;
std::vector<not_null<Ui::AbstractButton*>> tabs;
};
const auto result = Ui::CreateChild<Selector>(parent.get());
using Entry = std::pair<int, QString>;
auto sorted = std::vector<Entry>();
@ -28,30 +108,57 @@ not_null<Selector*> CreateReactionSelector(
0,
std::plus<>(),
&Entry::first);
auto labels = QStringList() << ("ALL (" + QString::number(count) + ")");
auto tabs = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto st = &st::reactionsTabs;
const auto state = tabs->lifetime().make_state<State>();
state->selected = selected;
const auto append = [&](const QString &reaction, int count) {
using namespace rpl::mappers;
const auto tab = CreateTab(
tabs,
*st,
reaction,
count,
state->selected.value() | rpl::map(_1 == reaction));
tab->setClickedCallback([=] {
state->selected = reaction;
});
state->tabs.push_back(tab);
};
append(QString(), count);
for (const auto &[count, reaction] : sorted) {
labels.append(reaction + " (" + QString::number(count) + ")");
append(reaction, 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));
const auto available = width
- st->padding.left()
- st->padding.right();
if (available <= 0) {
return;
}
auto left = available;
auto height = st->padding.top();
for (const auto &tab : state->tabs) {
if (left > 0 && available - left < tab->width()) {
left = 0;
height += tab->height() + st->itemSkip;
}
tab->move(
st->padding.left() + left,
height - tab->height() - st->itemSkip);
left += tab->width() + st->itemSkip;
}
tabs->resize(width, height - st->itemSkip + st->padding.bottom());
};
result->height = [=] {
return tabs->height() - st::lineWidth;
result->heightValue = [=] {
using namespace rpl::mappers;
return tabs->heightValue() | rpl::map(_1 - st::lineWidth);
};
result->changes = [=] {
return tabs->sectionActivated() | rpl::map([=](int section) {
return (section > 0) ? sorted[section - 1].second : QString();
});
return state->selected.changes();
};
return result;
}

View file

@ -13,11 +13,12 @@ struct Selector {
Fn<void(int, int)> move;
Fn<void(int)> resizeToWidth;
Fn<rpl::producer<QString>()> changes;
Fn<int()> height;
Fn<rpl::producer<int>()> heightValue;
};
not_null<Selector*> CreateReactionSelector(
not_null<QWidget*> parent,
const base::flat_map<QString, int> &items);
const base::flat_map<QString, int> &items,
const QString &selected);
} // namespace HistoryView

View file

@ -921,6 +921,12 @@ whoReadPlayed: icon{{ "menu/read_audio", menuSubmenuArrowFg }};
whoReadPlayedOver: icon{{ "menu/read_audio", menuSubmenuArrowFg }};
whoReadPlayedDisabled: icon {{ "menu/read_audio", menuFgDisabled }};
reactionsTabAll: icon {{ "menu/read_reactions", windowFg }};
reactionsTabAllSelected: icon {{ "menu/read_reactions", activeButtonFg }};
reactionsTabs: MultiSelect(defaultMultiSelect) {
padding: margins(12px, 10px, 12px, 10px);
}
reactionsTabIconSkip: 3px;
historyRequestsUserpics: GroupCallUserpics {
size: 22px;
shift: 8px;