Add reactions to replies / comments / pinned section.

This commit is contained in:
John Preston 2021-12-29 16:06:08 +03:00
parent 71d52d26c3
commit 58c9494c03
15 changed files with 144 additions and 32 deletions

View file

@ -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<QImage> PeerUserpicImageValue(
};
}
rpl::producer<std::vector<Data::Reaction>> PeerAllowedReactionsValue(
not_null<PeerData*> 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

View file

@ -16,6 +16,8 @@ enum class ImageRoundRadius;
namespace Data {
struct Reaction;
template <typename ChangeType, typename Error, typename Generator>
inline auto FlagsValueWithMask(
rpl::producer<ChangeType, Error, Generator> &&value,
@ -120,4 +122,7 @@ inline auto PeerFullFlagValue(
int size,
ImageRoundRadius radius);
[[nodiscard]] auto PeerAllowedReactionsValue(not_null<PeerData*> peer)
-> rpl::producer<std::vector<Data::Reaction>>;
} // namespace Data

View file

@ -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<Element*> view, int userpicTop) {

View file

@ -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<Data::Reaction> &&list) {
_reactionsManager->applyList(std::move(list));
}, lifetime());
controller->adaptive().chatWideValue(
@ -1447,6 +1441,7 @@ std::unique_ptr<QMimeData> 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,

View file

@ -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<const Element*> 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<PeerData*>,
std::shared_ptr<Data::CloudImageView>> _userpics, _userpicsCache;
std::vector<Data::Reaction> _reactions;
std::unique_ptr<HistoryView::Reactions::Manager> _reactionsManager;
MouseAction _mouseAction = MouseAction::None;

View file

@ -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<Reactions::Manager>(
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<Data::Reaction> &&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<Element*> 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<const Element*> 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<QMimeData> 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<const HistoryItem*> item) {
viewReplaced(view, nullptr);
_views.erase(i);
_reactionsManager->remove(item->fullId());
updateItemsGeometry();
}

View file

@ -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<std::vector<Data::Reaction>> = 0;
};
struct SelectionData {
@ -236,6 +243,11 @@ public:
[[nodiscard]] rpl::producer<FullMsgId> showMessageRequested() const;
void replyNextMessage(FullMsgId fullId, bool next = true);
[[nodiscard]] Reactions::ButtonParameters reactionButtonParameters(
not_null<const Element*> view,
QPoint position,
const TextState &reactionState) const;
// ElementDelegate interface.
Context elementContext() override;
std::unique_ptr<Element> 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<Ui::RpWidget> _emptyInfo = nullptr;
std::unique_ptr<HistoryView::Reactions::Manager> _reactionsManager;
int _minHeight = 0;
int _visibleTop = 0;
int _visibleBottom = 0;

View file

@ -683,6 +683,11 @@ CopyRestrictionType PinnedWidget::listSelectRestrictionType() {
return SelectRestrictionTypeFor(_history->peer);
}
auto PinnedWidget::listAllowedReactionsValue()
-> rpl::producer<std::vector<Data::Reaction>> {
return Data::PeerAllowedReactionsValue(_history->peer);
}
void PinnedWidget::confirmDeleteSelected() {
ConfirmDeleteSelectedItems(_inner);
}

View file

@ -105,6 +105,8 @@ public:
not_null<Ui::ChatTheme*> listChatTheme() override;
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
CopyRestrictionType listSelectRestrictionType() override;
auto listAllowedReactionsValue()
-> rpl::producer<std::vector<Data::Reaction>> override;
protected:
void resizeEvent(QResizeEvent *e) override;

View file

@ -220,6 +220,9 @@ void Button::applyState(State state) {
}
void Button::applyState(State state, Fn<void(QRect)> update) {
if (state == State::Hidden) {
_expandTimer.cancel();
}
const auto finalHeight = (state == State::Inside)
? _expandedHeight
: _collapsed.height();

View file

@ -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<std::vector<Data::Reaction>> {
return Data::PeerAllowedReactionsValue(_history->peer);
}
void RepliesWidget::confirmDeleteSelected() {
ConfirmDeleteSelectedItems(_inner);
}

View file

@ -141,6 +141,8 @@ public:
not_null<Ui::ChatTheme*> listChatTheme() override;
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
CopyRestrictionType listSelectRestrictionType() override;
auto listAllowedReactionsValue()
-> rpl::producer<std::vector<Data::Reaction>> override;
protected:
void resizeEvent(QResizeEvent *e) override;

View file

@ -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<std::vector<Data::Reaction>> {
return rpl::single(std::vector<Data::Reaction>());
}
void ScheduledWidget::confirmSendNowSelected() {
ConfirmSendNowSelectedItems(_inner);
}

View file

@ -122,6 +122,8 @@ public:
not_null<Ui::ChatTheme*> listChatTheme() override;
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
CopyRestrictionType listSelectRestrictionType() override;
auto listAllowedReactionsValue()
-> rpl::producer<std::vector<Data::Reaction>> override;
protected:
void resizeEvent(QResizeEvent *e) override;

View file

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