Initial suggested reaction implementation.

This commit is contained in:
John Preston 2023-08-29 21:34:21 +04:00
parent d5b429e910
commit f3e65181cd
9 changed files with 479 additions and 130 deletions

View File

@ -106,6 +106,99 @@ std::unique_ptr<Ui::PathShiftGradient> MakePathShiftGradient(
st->paletteChanged());
}
bool DefaultElementDelegate::elementUnderCursor(
not_null<const Element*> view) {
return false;
}
float64 DefaultElementDelegate::elementHighlightOpacity(
not_null<const HistoryItem*> item) const {
return 0.;
}
bool DefaultElementDelegate::elementInSelectionMode() {
return false;
}
bool DefaultElementDelegate::elementIntersectsRange(
not_null<const Element*> view,
int from,
int till) {
return true;
}
void DefaultElementDelegate::elementStartStickerLoop(
not_null<const Element*> view) {
}
void DefaultElementDelegate::elementShowPollResults(
not_null<PollData*> poll,
FullMsgId context) {
}
void DefaultElementDelegate::elementOpenPhoto(
not_null<PhotoData*> photo,
FullMsgId context) {
}
void DefaultElementDelegate::elementOpenDocument(
not_null<DocumentData*> document,
FullMsgId context,
bool showInMediaView) {
}
void DefaultElementDelegate::elementCancelUpload(const FullMsgId &context) {
}
void DefaultElementDelegate::elementShowTooltip(
const TextWithEntities &text,
Fn<void()> hiddenCallback) {
}
bool DefaultElementDelegate::elementHideReply(
not_null<const Element*> view) {
return false;
}
bool DefaultElementDelegate::elementShownUnread(
not_null<const Element*> view) {
return view->data()->unread(view->data()->history());
}
void DefaultElementDelegate::elementSendBotCommand(
const QString &command,
const FullMsgId &context) {
}
void DefaultElementDelegate::elementHandleViaClick(
not_null<UserData*> bot) {
}
bool DefaultElementDelegate::elementIsChatWide() {
return false;
}
void DefaultElementDelegate::elementReplyTo(const FullMsgId &to) {
}
void DefaultElementDelegate::elementStartInteraction(
not_null<const Element*> view) {
}
void DefaultElementDelegate::elementStartPremium(
not_null<const Element*> view,
Element *replacing) {
}
void DefaultElementDelegate::elementCancelPremium(
not_null<const Element*> view) {
}
QString DefaultElementDelegate::elementAuthorRank(
not_null<const Element*> view) {
return {};
}
SimpleElementDelegate::SimpleElementDelegate(
not_null<Window::SessionController*> controller,
Fn<void()> update)
@ -118,106 +211,15 @@ SimpleElementDelegate::SimpleElementDelegate(
SimpleElementDelegate::~SimpleElementDelegate() = default;
bool SimpleElementDelegate::elementUnderCursor(
not_null<const Element*> view) {
return false;
}
float64 SimpleElementDelegate::elementHighlightOpacity(
not_null<const HistoryItem*> item) const {
return 0.;
}
bool SimpleElementDelegate::elementInSelectionMode() {
return false;
}
bool SimpleElementDelegate::elementIntersectsRange(
not_null<const Element*> view,
int from,
int till) {
return true;
}
void SimpleElementDelegate::elementStartStickerLoop(
not_null<const Element*> view) {
}
void SimpleElementDelegate::elementShowPollResults(
not_null<PollData*> poll,
FullMsgId context) {
}
void SimpleElementDelegate::elementOpenPhoto(
not_null<PhotoData*> photo,
FullMsgId context) {
}
void SimpleElementDelegate::elementOpenDocument(
not_null<DocumentData*> document,
FullMsgId context,
bool showInMediaView) {
}
void SimpleElementDelegate::elementCancelUpload(const FullMsgId &context) {
}
void SimpleElementDelegate::elementShowTooltip(
const TextWithEntities &text,
Fn<void()> hiddenCallback) {
}
bool SimpleElementDelegate::elementAnimationsPaused() {
return _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
}
bool SimpleElementDelegate::elementHideReply(not_null<const Element*> view) {
return false;
}
bool SimpleElementDelegate::elementShownUnread(
not_null<const Element*> view) {
return view->data()->unread(view->data()->history());
}
void SimpleElementDelegate::elementSendBotCommand(
const QString &command,
const FullMsgId &context) {
}
void SimpleElementDelegate::elementHandleViaClick(not_null<UserData*> bot) {
}
bool SimpleElementDelegate::elementIsChatWide() {
return false;
}
auto SimpleElementDelegate::elementPathShiftGradient()
-> not_null<Ui::PathShiftGradient*> {
return _pathGradient.get();
}
void SimpleElementDelegate::elementReplyTo(const FullMsgId &to) {
}
void SimpleElementDelegate::elementStartInteraction(
not_null<const Element*> view) {
}
void SimpleElementDelegate::elementStartPremium(
not_null<const Element*> view,
Element *replacing) {
}
void SimpleElementDelegate::elementCancelPremium(
not_null<const Element*> view) {
}
QString SimpleElementDelegate::elementAuthorRank(
not_null<const Element*> view) {
return {};
}
TextSelection UnshiftItemSelection(
TextSelection selection,
uint16 byLength) {
@ -611,7 +613,24 @@ bool Element::isHidden() const {
return isHiddenByGroup();
}
void Element::overrideMedia(std::unique_ptr<Media> media) {
Expects(!history()->owner().groups().find(data()));
_text = Ui::Text::String(st::msgMinWidth);
_textWidth = -1;
_textHeight = 0;
_media = std::move(media);
if (!pendingResize()) {
history()->owner().requestViewResize(this);
}
_flags |= Flag::MediaOverriden;
}
void Element::refreshMedia(Element *replacing) {
if (_flags & Flag::MediaOverriden) {
return;
}
_flags &= ~Flag::HiddenByGroup;
const auto item = data();

View File

@ -117,13 +117,8 @@ public:
not_null<const Ui::ChatStyle*> st,
Fn<void()> update);
class SimpleElementDelegate : public ElementDelegate {
class DefaultElementDelegate : public ElementDelegate {
public:
SimpleElementDelegate(
not_null<Window::SessionController*> controller,
Fn<void()> update);
~SimpleElementDelegate();
bool elementUnderCursor(not_null<const Element*> view) override;
[[nodiscard]] float64 elementHighlightOpacity(
not_null<const HistoryItem*> item) const override;
@ -147,7 +142,6 @@ public:
void elementShowTooltip(
const TextWithEntities &text,
Fn<void()> hiddenCallback) override;
bool elementAnimationsPaused() override;
bool elementHideReply(not_null<const Element*> view) override;
bool elementShownUnread(not_null<const Element*> view) override;
void elementSendBotCommand(
@ -155,7 +149,6 @@ public:
const FullMsgId &context) override;
void elementHandleViaClick(not_null<UserData*> bot) override;
bool elementIsChatWide() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullMsgId &to) override;
void elementStartInteraction(not_null<const Element*> view) override;
void elementStartPremium(
@ -164,6 +157,17 @@ public:
void elementCancelPremium(not_null<const Element*> view) override;
QString elementAuthorRank(not_null<const Element*> view) override;
};
class SimpleElementDelegate : public DefaultElementDelegate {
public:
SimpleElementDelegate(
not_null<Window::SessionController*> controller,
Fn<void()> update);
~SimpleElementDelegate();
bool elementAnimationsPaused() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
protected:
[[nodiscard]] not_null<Window::SessionController*> controller() const {
@ -264,6 +268,7 @@ public:
CustomEmojiRepainting = 0x0100,
ScheduledUntilOnline = 0x0200,
TopicRootReply = 0x0400,
MediaOverriden = 0x0800,
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
@ -480,6 +485,8 @@ public:
Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>>;
void overrideMedia(std::unique_ptr<Media> media);
virtual ~Element();
static void Hovered(Element *view);

View File

@ -509,27 +509,27 @@ void Controller::initLayout() {
.nameBoundingRect = nameBoundingRect(right, false),
.nameFontSize = nameFontSize,
};
if (!_locationAreas.empty()) {
rebuildLocationAreas(layout);
if (!_areas.empty()) {
rebuildActiveAreas(layout);
}
return layout;
});
}
void Controller::rebuildLocationAreas(const Layout &layout) const {
Expects(_locations.size() == _locationAreas.size());
void Controller::rebuildActiveAreas(const Layout &layout) const {
const auto origin = layout.content.topLeft();
const auto scale = layout.content.size();
for (auto i = 0, count = int(_locations.size()); i != count; ++i) {
auto &area = _locationAreas[i];
const auto &general = _locations[i].area.geometry;
for (auto &area : _areas) {
const auto &general = area.original;
area.geometry = QRect(
int(base::SafeRound(general.x() * scale.width())),
int(base::SafeRound(general.y() * scale.height())),
int(base::SafeRound(general.width() * scale.width())),
int(base::SafeRound(general.height() * scale.height()))
).translated(origin);
if (const auto reaction = area.reaction.get()) {
reaction->setAreaGeometry(area.geometry);
}
}
}
@ -914,9 +914,16 @@ bool Controller::changeShown(Data::Story *story) {
const auto &locations = story
? story->locations()
: std::vector<Data::StoryLocation>();
const auto &suggestedReactions = story
? story->suggestedReactions()
: std::vector<Data::SuggestedReaction>();
if (_locations != locations) {
_locations = locations;
_locationAreas.clear();
_areas.clear();
}
if (_suggestedReactions != suggestedReactions) {
_suggestedReactions = suggestedReactions;
_areas.clear();
}
return true;
@ -1082,26 +1089,47 @@ void Controller::updatePlayback(const Player::TrackState &state) {
}
}
ClickHandlerPtr Controller::lookupLocationHandler(QPoint point) const {
ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
const auto &layout = _layout.current();
if (_locations.empty() || !layout) {
if ((_locations.empty() && _suggestedReactions.empty()) || !layout) {
return nullptr;
} else if (_locationAreas.empty()) {
_locationAreas = _locations | ranges::views::transform([](
const Data::StoryLocation &location) {
return LocationArea{
} else if (_areas.empty()) {
_areas.reserve(_locations.size() + _suggestedReactions.size());
for (const auto &location : _locations) {
_areas.push_back({
.original = location.area.geometry,
.rotation = location.area.rotation,
.handler = std::make_shared<LocationClickHandler>(
location.point),
};
}) | ranges::to_vector;
rebuildLocationAreas(*layout);
});
}
for (const auto &suggestedReaction : _suggestedReactions) {
const auto id = suggestedReaction.reaction;
_areas.push_back({
.original = suggestedReaction.area.geometry,
.rotation = suggestedReaction.area.rotation,
.handler = std::make_shared<LambdaClickHandler>([=] {
_reactions->applyLike(id);
}),
.reaction = _reactions->makeSuggestedReactionWidget(
suggestedReaction),
});
}
rebuildActiveAreas(*layout);
}
for (const auto &area : _locationAreas) {
const auto circleContains = [&](QRect circle) {
const auto radius = std::min(circle.width(), circle.height()) / 2;
const auto delta = circle.center() - point;
return QPoint::dotProduct(delta, delta) < (radius * radius);
};
for (const auto &area : _areas) {
const auto center = area.geometry.center();
const auto angle = -area.rotation;
if (area.geometry.contains(Rotated(point, center, angle))) {
const auto contains = area.reaction
? circleContains(area.geometry)
: area.geometry.contains(Rotated(point, center, angle));
if (contains) {
return area.handler;
}
}

View File

@ -68,6 +68,7 @@ enum class SiblingType;
struct ContentLayout;
class CaptionFullView;
enum class ReactionsMode;
class SuggestedReactionView;
enum class HeaderLayout {
Normal,
@ -135,7 +136,7 @@ public:
void ready();
void updateVideoPlayback(const Player::TrackState &state);
[[nodiscard]] ClickHandlerPtr lookupLocationHandler(QPoint point) const;
[[nodiscard]] ClickHandlerPtr lookupAreaHandler(QPoint point) const;
[[nodiscard]] bool subjumpAvailable(int delta) const;
[[nodiscard]] bool subjumpFor(int delta);
@ -197,10 +198,12 @@ private:
return peerId != 0;
}
};
struct LocationArea {
struct ActiveArea {
QRectF original;
QRect geometry;
float64 rotation = 0.;
ClickHandlerPtr handler;
std::unique_ptr<SuggestedReactionView> reaction;
};
void initLayout();
@ -215,7 +218,7 @@ private:
void updateContentFaded();
void updatePlayingAllowed();
void setPlayingAllowed(bool allowed);
void rebuildLocationAreas(const Layout &layout) const;
void rebuildActiveAreas(const Layout &layout) const;
void hideSiblings();
void showSiblings(not_null<Main::Session*> session);
@ -284,7 +287,8 @@ private:
bool _viewed = false;
std::vector<Data::StoryLocation> _locations;
mutable std::vector<LocationArea> _locationAreas;
std::vector<Data::SuggestedReaction> _suggestedReactions;
mutable std::vector<ActiveArea> _areas;
std::vector<CachedSource> _cachedSourcesList;
int _cachedSourceIndex = -1;

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/stories/media_stories_reactions.h"
#include "base/event_filter.h"
#include "base/unixtime.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h"
@ -16,17 +17,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_message_reactions.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "history/admin_log/history_admin_log_item.h"
#include "history/view/media/history_view_custom_emoji.h"
#include "history/view/media/history_view_media_unwrapped.h"
#include "history/view/reactions/history_view_reactions_selector.h"
#include "history/view/history_view_element.h"
#include "history/history_item_reply_markup.h"
#include "history/history_item.h"
#include "history/history.h"
#include "main/main_session.h"
#include "media/stories/media_stories_controller.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/emoji_fly_animation.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/effects/reaction_fly_animation.h"
#include "ui/text/text_isolated_emoji.h"
#include "ui/widgets/popup_menu.h"
#include "ui/animated_icon.h"
#include "ui/painter.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_chat.h"
#include "styles/style_media_view.h"
#include "styles/style_widgets.h"
#include "styles/style_window.h"
namespace Media::Stories {
namespace {
@ -34,6 +47,241 @@ namespace {
constexpr auto kReactionScaleOutTarget = 0.7;
constexpr auto kReactionScaleOutDuration = crl::time(1000);
constexpr auto kMessageReactionScaleOutDuration = crl::time(400);
constexpr auto kSuggestedBubbleSize = 1.0;
constexpr auto kSuggestedTailBigSize = 0.264;
constexpr auto kSuggestedTailBigOffset = 0.464;
constexpr auto kSuggestedTailSmallSize = 0.110;
constexpr auto kSuggestedTailSmallOffset = 0.697;
constexpr auto kSuggestedTailBigRotation = -42.29;
constexpr auto kSuggestedTailSmallRotation = -40.87;
constexpr auto kSuggestedReactionSize = 0.7;
class ReactionView final
: public Ui::RpWidget
, public SuggestedReactionView
, public HistoryView::DefaultElementDelegate {
public:
ReactionView(
QWidget *parent,
not_null<Main::Session*> session,
const Data::SuggestedReaction &reaction);
void setAreaGeometry(QRect geometry) override;
private:
using Element = HistoryView::Element;
not_null<HistoryView::ElementDelegate*> delegate();
HistoryView::Context elementContext() override;
bool elementAnimationsPaused() override;
bool elementShownUnread(not_null<const Element*> view) override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void paintEvent(QPaintEvent *e) override;
void cacheBackground();
Data::SuggestedReaction _data;
std::unique_ptr<Ui::ChatStyle> _chatStyle;
std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
AdminLog::OwnedItem _fake;
QImage _background;
QRectF _bubbleGeometry;
int _size = 0;
int _mediaLeft = 0;
int _mediaTop = 0;
int _mediaWidth = 0;
int _mediaHeight = 0;
float64 _bubble = 0;
float64 _bigOffset = 0;
float64 _bigSize = 0;
float64 _smallOffset = 0;
float64 _smallSize = 0;
};
[[nodiscard]] AdminLog::OwnedItem GenerateFakeItem(
not_null<HistoryView::ElementDelegate*> delegate,
not_null<History*> history) {
Expects(history->peer->isUser());
const auto flags = MessageFlag::FakeHistoryItem
| MessageFlag::HasFromId;
const auto replyTo = FullReplyTo();
const auto viaBotId = UserId();
const auto groupedId = uint64();
const auto item = history->makeMessage(
history->nextNonHistoryEntryId(),
flags,
replyTo,
viaBotId,
base::unixtime::now(),
peerToUser(history->peer->id),
QString(),
TextWithEntities(),
MTP_messageMediaEmpty(),
HistoryMessageMarkupData(),
groupedId);
return AdminLog::OwnedItem(delegate, item);
}
ReactionView::ReactionView(
QWidget *parent,
not_null<Main::Session*> session,
const Data::SuggestedReaction &reaction)
: RpWidget(parent)
, _data(reaction)
, _chatStyle(std::make_unique<Ui::ChatStyle>())
, _pathGradient(
std::make_unique<Ui::PathShiftGradient>(
st::shadowFg,
st::shadowFg,
[=] { update(); }))
, _fake(
GenerateFakeItem(
delegate(),
session->data().history(PeerData::kServiceNotificationsId))) {
style::PaletteChanged() | rpl::start_with_next([=] {
_background = QImage();
}, lifetime());
const auto view = _fake.get();
const auto item = view->data();
const auto entityData = [&] {
const auto &id = _data.reaction;
const auto reactions = &session->data().reactions();
reactions->preloadAnimationsFor(id);
if (const auto customId = id.custom()) {
return Data::SerializeCustomEmojiId(customId);
}
const auto type = Data::Reactions::Type::All;
const auto &list = reactions->list(type);
const auto i = ranges::find(list, id, &Data::Reaction::id);
return (i != end(list))
? Data::SerializeCustomEmojiId(i->selectAnimation->id)
: QString();
}();
const auto emoji = Ui::Text::OnlyCustomEmoji{
{ { { entityData } } }
};
view->overrideMedia(std::make_unique<HistoryView::UnwrappedMedia>(
view,
std::make_unique<HistoryView::CustomEmoji>(view, emoji)));
view->initDimensions();
_mediaLeft = st::msgMargin.left();
_mediaTop = st::msgMargin.top();
_mediaWidth = _mediaHeight = view->resizeGetHeight(st::windowMinWidth)
- _mediaTop
- st::msgMargin.bottom();
session->data().viewRepaintRequest(
) | rpl::start_with_next([=](not_null<const Element*> element) {
if (element == view) {
update();
}
}, lifetime());
setAttribute(Qt::WA_TransparentForMouseEvents);
show();
}
void ReactionView::setAreaGeometry(QRect geometry) {
_size = std::min(geometry.width(), geometry.height());
const auto scaled = [&](float64 scale) {
return int(base::SafeRound(scale * _size));
};
_bubble = _size * kSuggestedBubbleSize;
_bigOffset = _bubble * kSuggestedTailBigOffset;
_bigSize = _bubble * kSuggestedTailBigSize;
_smallOffset = _bubble * kSuggestedTailSmallOffset;
_smallSize = _bubble * kSuggestedTailSmallSize;
const auto add = int(base::SafeRound(_smallOffset + _smallSize))
- (_size / 2);
setGeometry(geometry.marginsAdded({ add, add, add, add }));
}
not_null<HistoryView::ElementDelegate*> ReactionView::delegate() {
return static_cast<HistoryView::ElementDelegate*>(this);
}
void ReactionView::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
if (!_size) {
return;
} else if (_background.size() != size() * style::DevicePixelRatio()) {
cacheBackground();
}
p.drawImage(0, 0, _background);
auto hq = PainterHighQualityEnabler(p);
p.translate(_bubbleGeometry.center());
p.scale(
kSuggestedReactionSize * _bubbleGeometry.width() / _mediaWidth,
kSuggestedReactionSize * _bubbleGeometry.height() / _mediaHeight);
p.rotate(_data.area.rotation);
p.translate(
-(_mediaLeft + (_mediaWidth / 2)),
-(_mediaTop + (_mediaHeight / 2)));
auto context = Ui::ChatPaintContext{
.st = _chatStyle.get(),
.viewport = rect(),
.clip = rect(),
.now = crl::now(),
};
_fake->draw(p, context);
}
void ReactionView::cacheBackground() {
const auto ratio = style::DevicePixelRatio();
_background = QImage(
size() * ratio,
QImage::Format_ARGB32_Premultiplied);
_background.setDevicePixelRatio(ratio);
_background.fill(Qt::transparent);
auto p = QPainter(&_background);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(_data.dark ? QColor(0, 0, 0, 128) : QColor(255, 255, 255));
p.setCompositionMode(QPainter::CompositionMode_Source);
_bubbleGeometry = QRectF(
(width() - _bubble) / 2.,
(height() - _bubble) / 2.,
_bubble,
_bubble);
p.drawEllipse(_bubbleGeometry);
const auto center = QPointF(width() / 2., height() / 2.);
p.translate(center);
auto previous = 0.;
const auto rotate = [&](float64 initial) {
if (_data.flipped) {
initial = 180 - initial;
}
auto rotation = _data.area.rotation - initial;
while (rotation < 0) {
rotation += 360;
}
while (rotation >= 360) {
rotation -= 360;
}
const auto delta = rotation - previous;
previous = rotation;
p.rotate(delta);
};
const auto paintTailPart = [&](float64 offset, float64 size) {
p.drawEllipse(QRectF(offset - size / 2., -size / 2., size, size));
};
rotate(kSuggestedTailBigRotation);
paintTailPart(_bigOffset, _bigSize);
rotate(kSuggestedTailSmallRotation);
paintTailPart(_smallOffset, _smallSize);
}
[[nodiscard]] Data::ReactionId HeartReactionId() {
return { QString() + QChar(10084) };
@ -67,6 +315,24 @@ constexpr auto kMessageReactionScaleOutDuration = crl::time(400);
return result;
}
HistoryView::Context ReactionView::elementContext() {
return HistoryView::Context::ContactPreview;
}
bool ReactionView::elementAnimationsPaused() {
return false;
}
bool ReactionView::elementShownUnread(
not_null<const Element*> view) {
return false;
}
auto ReactionView::elementPathShiftGradient()
-> not_null<Ui::PathShiftGradient*> {
return _pathGradient.get();
}
} // namespace
class Reactions::Panel final {
@ -358,6 +624,15 @@ auto Reactions::chosen() const -> rpl::producer<Chosen> {
return _chosen.events();
}
auto Reactions::makeSuggestedReactionWidget(
const Data::SuggestedReaction &reaction)
-> std::unique_ptr<SuggestedReactionView> {
return std::make_unique<ReactionView>(
_controller->wrap(),
&_controller->uiShow()->session(),
reaction);
}
void Reactions::setReplyFieldState(
rpl::producer<bool> focused,
rpl::producer<bool> hasSendText) {
@ -458,9 +733,12 @@ void Reactions::outsidePressed() {
void Reactions::toggleLiked() {
const auto liked = !_liked.current().empty();
const auto now = liked ? Data::ReactionId() : HeartReactionId();
if (_liked.current() != now) {
animateAndProcess({ { .id = now }, ReactionsMode::Reaction });
applyLike(liked ? Data::ReactionId() : HeartReactionId());
}
void Reactions::applyLike(Data::ReactionId id) {
if (_liked.current() != id) {
animateAndProcess({ { .id = id }, ReactionsMode::Reaction });
}
}

View File

@ -15,6 +15,7 @@ class DocumentMedia;
struct ReactionId;
class Session;
class Story;
struct SuggestedReaction;
} // namespace Data
namespace HistoryView::Reactions {
@ -40,6 +41,13 @@ enum class ReactionsMode {
Reaction,
};
class SuggestedReactionView {
public:
virtual ~SuggestedReactionView() = default;
virtual void setAreaGeometry(QRect geometry) = 0;
};
class Reactions final {
public:
explicit Reactions(not_null<Controller*> controller);
@ -64,8 +72,13 @@ public:
void hide();
void outsidePressed();
void toggleLiked();
void applyLike(Data::ReactionId id);
void ready();
[[nodiscard]] auto makeSuggestedReactionWidget(
const Data::SuggestedReaction &reaction)
-> std::unique_ptr<SuggestedReactionView>;
void setReplyFieldState(
rpl::producer<bool> focused,
rpl::producer<bool> hasSendText);

View File

@ -59,8 +59,8 @@ void View::updatePlayback(const Player::TrackState &state) {
_controller->updateVideoPlayback(state);
}
ClickHandlerPtr View::lookupLocationHandler(QPoint point) const {
return _controller->lookupLocationHandler(point);
ClickHandlerPtr View::lookupAreaHandler(QPoint point) const {
return _controller->lookupAreaHandler(point);
}
bool View::subjumpAvailable(int delta) const {

View File

@ -81,7 +81,7 @@ public:
void showFullCaption();
void updatePlayback(const Player::TrackState &state);
[[nodiscard]] ClickHandlerPtr lookupLocationHandler(QPoint point) const;
[[nodiscard]] ClickHandlerPtr lookupAreaHandler(QPoint point) const;
[[nodiscard]] bool subjumpAvailable(int delta) const;
[[nodiscard]] bool subjumpFor(int delta) const;

View File

@ -5685,7 +5685,7 @@ void OverlayWidget::updateOver(QPoint pos) {
lnk = _groupThumbs->getState(point);
lnkhost = this;
} else if (_stories) {
lnk = _stories->lookupLocationHandler(pos);
lnk = _stories->lookupAreaHandler(pos);
lnkhost = this;
}