mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Add reactions to replies / comments / pinned section.
This commit is contained in:
parent
71d52d26c3
commit
58c9494c03
15 changed files with 144 additions and 32 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue