mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Implement PoC custom reaction selection.
This commit is contained in:
parent
cece9cf09b
commit
09124f6424
24 changed files with 449 additions and 152 deletions
|
@ -1015,7 +1015,7 @@ void Controller::fillManageSection() {
|
||||||
!_peer->isBroadcast(),
|
!_peer->isBroadcast(),
|
||||||
session->data().reactions().list(
|
session->data().reactions().list(
|
||||||
Data::Reactions::Type::Active),
|
Data::Reactions::Type::Active),
|
||||||
*Data::PeerAllowedReactions(_peer),
|
*Data::PeerReactionsFilter(_peer).allowed,
|
||||||
done));
|
done));
|
||||||
},
|
},
|
||||||
{ &st::infoRoundedIconReactions, Settings::kIconRed });
|
{ &st::infoRoundedIconReactions, Settings::kIconRed });
|
||||||
|
|
|
@ -566,45 +566,50 @@ void TabbedSelector::resizeEvent(QResizeEvent *e) {
|
||||||
_tabsSlider->width(),
|
_tabsSlider->width(),
|
||||||
st::lineWidth);
|
st::lineWidth);
|
||||||
}
|
}
|
||||||
|
updateScrollGeometry(e->oldSize());
|
||||||
|
updateRestrictedLabelGeometry();
|
||||||
|
updateFooterGeometry();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabbedSelector::updateScrollGeometry(QSize oldSize) {
|
||||||
auto scrollWidth = width() - st::roundRadiusSmall;
|
auto scrollWidth = width() - st::roundRadiusSmall;
|
||||||
auto scrollHeight = height() - scrollTop() - scrollBottom();
|
auto scrollHeight = height() - scrollTop() - scrollBottom();
|
||||||
auto inner = currentTab()->widget();
|
auto inner = currentTab()->widget();
|
||||||
auto innerWidth = scrollWidth - st::emojiScroll.width;
|
auto innerWidth = scrollWidth - st::emojiScroll.width;
|
||||||
auto updateScrollGeometry = [&] {
|
auto setScrollGeometry = [&] {
|
||||||
_scroll->setGeometryToLeft(
|
_scroll->setGeometryToLeft(
|
||||||
st::roundRadiusSmall,
|
st::roundRadiusSmall,
|
||||||
scrollTop(),
|
scrollTop(),
|
||||||
scrollWidth,
|
scrollWidth,
|
||||||
scrollHeight);
|
scrollHeight);
|
||||||
};
|
};
|
||||||
auto updateInnerGeometry = [&] {
|
auto setInnerGeometry = [&] {
|
||||||
auto scrollTop = _scroll->scrollTop();
|
auto scrollTop = _scroll->scrollTop();
|
||||||
auto scrollBottom = scrollTop + scrollHeight;
|
auto scrollBottom = scrollTop + scrollHeight;
|
||||||
inner->setMinimalHeight(innerWidth, scrollHeight);
|
inner->setMinimalHeight(innerWidth, scrollHeight);
|
||||||
inner->setVisibleTopBottom(scrollTop, scrollBottom);
|
inner->setVisibleTopBottom(scrollTop, scrollBottom);
|
||||||
};
|
};
|
||||||
if (e->oldSize().height() > height()) {
|
if (oldSize.height() > height()) {
|
||||||
updateScrollGeometry();
|
setScrollGeometry();
|
||||||
updateInnerGeometry();
|
setInnerGeometry();
|
||||||
} else {
|
} else {
|
||||||
updateInnerGeometry();
|
setInnerGeometry();
|
||||||
updateScrollGeometry();
|
setScrollGeometry();
|
||||||
}
|
}
|
||||||
_bottomShadow->setGeometry(
|
_bottomShadow->setGeometry(
|
||||||
0,
|
0,
|
||||||
_scroll->y() + _scroll->height() - st::lineWidth,
|
_scroll->y() + _scroll->height() - st::lineWidth,
|
||||||
width(),
|
width(),
|
||||||
st::lineWidth);
|
st::lineWidth);
|
||||||
updateRestrictedLabelGeometry();
|
}
|
||||||
|
|
||||||
|
void TabbedSelector::updateFooterGeometry() {
|
||||||
_footerTop = _dropDown ? 0 : (height() - st::emojiFooterHeight);
|
_footerTop = _dropDown ? 0 : (height() - st::emojiFooterHeight);
|
||||||
for (auto &tab : _tabs) {
|
for (auto &tab : _tabs) {
|
||||||
tab.footer()->resizeToWidth(width());
|
tab.footer()->resizeToWidth(width());
|
||||||
tab.footer()->moveToLeft(0, _footerTop);
|
tab.footer()->moveToLeft(0, _footerTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabbedSelector::updateRestrictedLabelGeometry() {
|
void TabbedSelector::updateRestrictedLabelGeometry() {
|
||||||
|
@ -1139,6 +1144,15 @@ void TabbedSelector::showMenuWithType(SendMenu::Type type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TabbedSelector::setDropDown(bool dropDown) {
|
||||||
|
if (_dropDown == dropDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_dropDown = dropDown;
|
||||||
|
updateFooterGeometry();
|
||||||
|
updateScrollGeometry(size());
|
||||||
|
}
|
||||||
|
|
||||||
rpl::producer<> TabbedSelector::contextMenuRequested() const {
|
rpl::producer<> TabbedSelector::contextMenuRequested() const {
|
||||||
return events(
|
return events(
|
||||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||||
|
|
|
@ -130,9 +130,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void showMenuWithType(SendMenu::Type type);
|
void showMenuWithType(SendMenu::Type type);
|
||||||
void setDropDown(bool dropDown) {
|
void setDropDown(bool dropDown);
|
||||||
_dropDown = dropDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float player interface.
|
// Float player interface.
|
||||||
bool floatPlayerHandleWheelEvent(QEvent *e);
|
bool floatPlayerHandleWheelEvent(QEvent *e);
|
||||||
|
@ -204,6 +202,8 @@ private:
|
||||||
void checkRestrictedPeer();
|
void checkRestrictedPeer();
|
||||||
bool isRestrictedView();
|
bool isRestrictedView();
|
||||||
void updateRestrictedLabelGeometry();
|
void updateRestrictedLabelGeometry();
|
||||||
|
void updateScrollGeometry(QSize oldSize);
|
||||||
|
void updateFooterGeometry();
|
||||||
void handleScroll();
|
void handleScroll();
|
||||||
|
|
||||||
QImage grabForAnimation();
|
QImage grabForAnimation();
|
||||||
|
|
32
Telegram/SourceFiles/data/data_message_reaction_id.cpp
Normal file
32
Telegram/SourceFiles/data/data_message_reaction_id.cpp
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
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 "data/data_message_reaction_id.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
ReactionId ReactionFromMTP(const MTPReaction &reaction) {
|
||||||
|
return reaction.match([](MTPDreactionEmpty) {
|
||||||
|
return ReactionId{ QString() };
|
||||||
|
}, [](const MTPDreactionEmoji &data) {
|
||||||
|
return ReactionId{ qs(data.vemoticon()) };
|
||||||
|
}, [](const MTPDreactionCustomEmoji &data) {
|
||||||
|
return ReactionId{ DocumentId(data.vdocument_id().v) };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MTPReaction ReactionToMTP(ReactionId id) {
|
||||||
|
if (const auto custom = id.custom()) {
|
||||||
|
return MTP_reactionCustomEmoji(MTP_long(custom));
|
||||||
|
}
|
||||||
|
const auto emoji = id.emoji();
|
||||||
|
return emoji.isEmpty()
|
||||||
|
? MTP_reactionEmpty()
|
||||||
|
: MTP_reactionEmoji(MTP_string(emoji));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Data
|
49
Telegram/SourceFiles/data/data_message_reaction_id.h
Normal file
49
Telegram/SourceFiles/data/data_message_reaction_id.h
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
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 Data {
|
||||||
|
|
||||||
|
struct ReactionId {
|
||||||
|
std::variant<QString, DocumentId> data;
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const {
|
||||||
|
const auto emoji = std::get_if<QString>(&data);
|
||||||
|
return emoji && emoji->isEmpty();
|
||||||
|
}
|
||||||
|
[[nodiscard]] QString emoji() const {
|
||||||
|
const auto emoji = std::get_if<QString>(&data);
|
||||||
|
return emoji ? *emoji : QString();
|
||||||
|
}
|
||||||
|
[[nodiscard]] DocumentId custom() const {
|
||||||
|
const auto custom = std::get_if<DocumentId>(&data);
|
||||||
|
return custom ? *custom : DocumentId();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(ReactionId);
|
||||||
|
|
||||||
|
inline bool operator<(const ReactionId &a, const ReactionId &b) {
|
||||||
|
return a.data < b.data;
|
||||||
|
}
|
||||||
|
inline bool operator==(const ReactionId &a, const ReactionId &b) {
|
||||||
|
return a.data == b.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction);
|
||||||
|
[[nodiscard]] MTPReaction ReactionToMTP(ReactionId id);
|
||||||
|
|
||||||
|
struct ReactionsFilter {
|
||||||
|
std::optional<base::flat_set<QString>> allowed;
|
||||||
|
bool customAllowed = false;
|
||||||
|
|
||||||
|
friend inline auto operator<=>(
|
||||||
|
const ReactionsFilter &,
|
||||||
|
const ReactionsFilter &) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Data
|
|
@ -42,26 +42,6 @@ constexpr auto kSizeForDownscale = 64;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ReactionId ReactionFromMTP(const MTPReaction &reaction) {
|
|
||||||
return reaction.match([](MTPDreactionEmpty) {
|
|
||||||
return ReactionId{ QString() };
|
|
||||||
}, [](const MTPDreactionEmoji &data) {
|
|
||||||
return ReactionId{ qs(data.vemoticon()) };
|
|
||||||
}, [](const MTPDreactionCustomEmoji &data) {
|
|
||||||
return ReactionId{ DocumentId(data.vdocument_id().v) };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
MTPReaction ReactionToMTP(ReactionId id) {
|
|
||||||
if (const auto custom = id.custom()) {
|
|
||||||
return MTP_reactionCustomEmoji(MTP_long(custom));
|
|
||||||
}
|
|
||||||
const auto emoji = id.emoji();
|
|
||||||
return emoji.isEmpty()
|
|
||||||
? MTP_reactionEmpty()
|
|
||||||
: MTP_reactionEmoji(MTP_string(emoji));
|
|
||||||
}
|
|
||||||
|
|
||||||
Reactions::Reactions(not_null<Session*> owner)
|
Reactions::Reactions(not_null<Session*> owner)
|
||||||
: _owner(owner)
|
: _owner(owner)
|
||||||
, _repaintTimer([=] { repaintCollected(); }) {
|
, _repaintTimer([=] { repaintCollected(); }) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
#include "data/data_message_reaction_id.h"
|
||||||
|
|
||||||
namespace Lottie {
|
namespace Lottie {
|
||||||
class Icon;
|
class Icon;
|
||||||
|
@ -18,34 +19,6 @@ namespace Data {
|
||||||
class DocumentMedia;
|
class DocumentMedia;
|
||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
struct ReactionId {
|
|
||||||
std::variant<QString, DocumentId> data;
|
|
||||||
|
|
||||||
[[nodiscard]] bool empty() const {
|
|
||||||
const auto emoji = std::get_if<QString>(&data);
|
|
||||||
return emoji && emoji->isEmpty();
|
|
||||||
}
|
|
||||||
[[nodiscard]] QString emoji() const {
|
|
||||||
const auto emoji = std::get_if<QString>(&data);
|
|
||||||
return emoji ? *emoji : QString();
|
|
||||||
}
|
|
||||||
[[nodiscard]] DocumentId custom() const {
|
|
||||||
const auto custom = std::get_if<DocumentId>(&data);
|
|
||||||
return custom ? *custom : DocumentId();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Q_DECLARE_METATYPE(ReactionId);
|
|
||||||
|
|
||||||
inline bool operator<(const ReactionId &a, const ReactionId &b) {
|
|
||||||
return a.data < b.data;
|
|
||||||
}
|
|
||||||
inline bool operator==(const ReactionId &a, const ReactionId &b) {
|
|
||||||
return a.data == b.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction);
|
|
||||||
[[nodiscard]] MTPReaction ReactionToMTP(ReactionId id);
|
|
||||||
|
|
||||||
struct Reaction {
|
struct Reaction {
|
||||||
ReactionId id;
|
ReactionId id;
|
||||||
QString title;
|
QString title;
|
||||||
|
|
|
@ -513,25 +513,23 @@ rpl::producer<QImage> PeerUserpicImageValue(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<base::flat_set<QString>> PeerAllowedReactions(
|
ReactionsFilter PeerReactionsFilter(not_null<PeerData*> peer) {
|
||||||
not_null<PeerData*> peer) {
|
|
||||||
if (const auto chat = peer->asChat()) {
|
if (const auto chat = peer->asChat()) {
|
||||||
return chat->allowedReactions();
|
return { .allowed = chat->allowedReactions() };
|
||||||
} else if (const auto channel = peer->asChannel()) {
|
} else if (const auto channel = peer->asChannel()) {
|
||||||
return channel->allowedReactions();
|
return { .allowed = channel->allowedReactions() };
|
||||||
} else {
|
} else {
|
||||||
return std::nullopt;
|
return { .customAllowed = true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PeerAllowedReactionsValue(
|
rpl::producer<ReactionsFilter> PeerReactionsFilterValue(
|
||||||
not_null<PeerData*> peer)
|
not_null<PeerData*> peer) {
|
||||||
-> rpl::producer<std::optional<base::flat_set<QString>>> {
|
|
||||||
return peer->session().changes().peerFlagsValue(
|
return peer->session().changes().peerFlagsValue(
|
||||||
peer,
|
peer,
|
||||||
Data::PeerUpdate::Flag::Reactions
|
Data::PeerUpdate::Flag::Reactions
|
||||||
) | rpl::map([=]{
|
) | rpl::map([=]{
|
||||||
return PeerAllowedReactions(peer);
|
return PeerReactionsFilter(peer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ class Session;
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
struct Reaction;
|
struct Reaction;
|
||||||
|
struct ReactionsFilter;
|
||||||
|
|
||||||
template <typename ChangeType, typename Error, typename Generator>
|
template <typename ChangeType, typename Error, typename Generator>
|
||||||
inline auto FlagsValueWithMask(
|
inline auto FlagsValueWithMask(
|
||||||
|
@ -133,10 +134,9 @@ inline auto PeerFullFlagValue(
|
||||||
int size,
|
int size,
|
||||||
ImageRoundRadius radius);
|
ImageRoundRadius radius);
|
||||||
|
|
||||||
[[nodiscard]] std::optional<base::flat_set<QString>> PeerAllowedReactions(
|
[[nodiscard]] ReactionsFilter PeerReactionsFilter(not_null<PeerData*> peer);
|
||||||
|
[[nodiscard]] rpl::producer<ReactionsFilter> PeerReactionsFilterValue(
|
||||||
not_null<PeerData*> peer);
|
not_null<PeerData*> peer);
|
||||||
[[nodiscard]] auto PeerAllowedReactionsValue(not_null<PeerData*> peer)
|
|
||||||
-> rpl::producer<std::optional<base::flat_set<QString>>>;
|
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<int> UniqueReactionsLimitValue(
|
[[nodiscard]] rpl::producer<int> UniqueReactionsLimitValue(
|
||||||
not_null<Main::Session*> session);
|
not_null<Main::Session*> session);
|
||||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_context_menu.h"
|
#include "history/view/history_view_context_menu.h"
|
||||||
#include "history/view/history_view_quick_action.h"
|
#include "history/view/history_view_quick_action.h"
|
||||||
#include "history/view/history_view_react_button.h"
|
#include "history/view/history_view_react_button.h"
|
||||||
|
#include "history/view/history_view_react_selector.h"
|
||||||
#include "history/view/history_view_emoji_interactions.h"
|
#include "history/view/history_view_emoji_interactions.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_item_text.h"
|
#include "history/history_item_text.h"
|
||||||
|
@ -345,6 +346,7 @@ HistoryInner::HistoryInner(
|
||||||
Data::UniqueReactionsLimitValue(&controller->session()),
|
Data::UniqueReactionsLimitValue(&controller->session()),
|
||||||
[=](QRect updated) { update(updated); },
|
[=](QRect updated) { update(updated); },
|
||||||
controller->cachedReactionIconFactory().createMethod()))
|
controller->cachedReactionIconFactory().createMethod()))
|
||||||
|
, _reactionsSelector(std::make_unique<HistoryView::Reactions::Selector>())
|
||||||
, _touchSelectTimer([=] { onTouchSelect(); })
|
, _touchSelectTimer([=] { onTouchSelect(); })
|
||||||
, _touchScrollTimer([=] { onTouchScrollTimer(); })
|
, _touchScrollTimer([=] { onTouchScrollTimer(); })
|
||||||
, _scrollDateCheck([this] { scrollDateCheck(); })
|
, _scrollDateCheck([this] { scrollDateCheck(); })
|
||||||
|
@ -393,28 +395,25 @@ HistoryInner::HistoryInner(
|
||||||
_controller->emojiInteractions().playStarted(_peer, std::move(emoji));
|
_controller->emojiInteractions().playStarted(_peer, std::move(emoji));
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
using ChosenReaction = HistoryView::Reactions::Manager::Chosen;
|
rpl::merge(
|
||||||
_reactionsManager->chosen(
|
_reactionsManager->chosen(),
|
||||||
|
_reactionsSelector->chosen()
|
||||||
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
||||||
const auto item = session().data().message(reaction.context);
|
_reactionsManager->updateButton({});
|
||||||
if (!item
|
reactionChosen(reaction);
|
||||||
|| Window::ShowReactPremiumError(
|
}, lifetime());
|
||||||
|
|
||||||
|
_reactionsManager->setExternalSelectorShown(_reactionsSelector->shown());
|
||||||
|
_reactionsManager->expandSelectorRequests(
|
||||||
|
) | rpl::start_with_next([=](ReactionExpandRequest request) {
|
||||||
|
if (request.expanded) {
|
||||||
|
_reactionsSelector->show(
|
||||||
_controller,
|
_controller,
|
||||||
item,
|
this,
|
||||||
reaction.id)) {
|
request.context,
|
||||||
return;
|
request.button);
|
||||||
}
|
} else {
|
||||||
item->toggleReaction(reaction.id);
|
_reactionsSelector->hide();
|
||||||
if (item->chosenReaction() != reaction.id) {
|
|
||||||
return;
|
|
||||||
} else if (const auto view = item->mainView()) {
|
|
||||||
if (const auto top = itemTop(view); top >= 0) {
|
|
||||||
view->animateReaction({
|
|
||||||
.id = reaction.id,
|
|
||||||
.flyIcon = reaction.icon,
|
|
||||||
.flyFrom = reaction.geometry.translated(0, -top),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
@ -463,7 +462,7 @@ HistoryInner::HistoryInner(
|
||||||
HistoryView::Reactions::SetupManagerList(
|
HistoryView::Reactions::SetupManagerList(
|
||||||
_reactionsManager.get(),
|
_reactionsManager.get(),
|
||||||
&session(),
|
&session(),
|
||||||
Data::PeerAllowedReactionsValue(_peer));
|
Data::PeerReactionsFilterValue(_peer));
|
||||||
|
|
||||||
controller->adaptive().chatWideValue(
|
controller->adaptive().chatWideValue(
|
||||||
) | rpl::start_with_next([=](bool wide) {
|
) | rpl::start_with_next([=](bool wide) {
|
||||||
|
@ -478,6 +477,31 @@ HistoryInner::HistoryInner(
|
||||||
setupSharingDisallowed();
|
setupSharingDisallowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryInner::reactionChosen(const ChosenReaction &reaction) {
|
||||||
|
const auto guard = gsl::finally([&] { _reactionsSelector->hide(); });
|
||||||
|
|
||||||
|
const auto item = session().data().message(reaction.context);
|
||||||
|
if (!item
|
||||||
|
|| Window::ShowReactPremiumError(
|
||||||
|
_controller,
|
||||||
|
item,
|
||||||
|
reaction.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item->toggleReaction(reaction.id);
|
||||||
|
if (item->chosenReaction() != reaction.id) {
|
||||||
|
return;
|
||||||
|
} else if (const auto view = item->mainView()) {
|
||||||
|
if (const auto top = itemTop(view); top >= 0) {
|
||||||
|
view->animateReaction({
|
||||||
|
.id = reaction.id,
|
||||||
|
.flyIcon = reaction.icon,
|
||||||
|
.flyFrom = reaction.geometry.translated(0, -top),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Main::Session &HistoryInner::session() const {
|
Main::Session &HistoryInner::session() const {
|
||||||
return _controller->session();
|
return _controller->session();
|
||||||
}
|
}
|
||||||
|
@ -1925,10 +1949,11 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) {
|
||||||
|
|
||||||
void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const {
|
void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const {
|
||||||
const auto favorite = session().data().reactions().favorite();
|
const auto favorite = session().data().reactions().favorite();
|
||||||
const auto allowed = _reactionsManager->allowedSublist();
|
const auto &filter = _reactionsManager->filter();
|
||||||
if (allowed
|
if (favorite.emoji().isEmpty() && !filter.customAllowed) {
|
||||||
&& (favorite.emoji().isEmpty()
|
return;
|
||||||
|| !allowed->contains(favorite.emoji()))) {
|
} else if (filter.allowed
|
||||||
|
&& !filter.allowed->contains(favorite.emoji())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto item = view->data();
|
const auto item = view->data();
|
||||||
|
|
|
@ -36,6 +36,9 @@ class Element;
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
class Manager;
|
class Manager;
|
||||||
|
class Selector;
|
||||||
|
struct ChosenReaction;
|
||||||
|
struct ExpandRequest;
|
||||||
struct ButtonParameters;
|
struct ButtonParameters;
|
||||||
} // namespace HistoryView::Reactions
|
} // namespace HistoryView::Reactions
|
||||||
|
|
||||||
|
@ -225,6 +228,8 @@ private:
|
||||||
void onTouchScrollTimer();
|
void onTouchScrollTimer();
|
||||||
|
|
||||||
class BotAbout;
|
class BotAbout;
|
||||||
|
using ChosenReaction = HistoryView::Reactions::ChosenReaction;
|
||||||
|
using ReactionExpandRequest = HistoryView::Reactions::ExpandRequest;
|
||||||
using VideoUserpic = Dialogs::Ui::VideoUserpic;
|
using VideoUserpic = Dialogs::Ui::VideoUserpic;
|
||||||
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
|
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
|
||||||
enum class MouseAction {
|
enum class MouseAction {
|
||||||
|
@ -396,6 +401,7 @@ private:
|
||||||
const HistoryView::TextState &reactionState) const
|
const HistoryView::TextState &reactionState) const
|
||||||
-> HistoryView::Reactions::ButtonParameters;
|
-> HistoryView::Reactions::ButtonParameters;
|
||||||
void toggleFavoriteReaction(not_null<Element*> view) const;
|
void toggleFavoriteReaction(not_null<Element*> view) const;
|
||||||
|
void reactionChosen(const ChosenReaction &reaction);
|
||||||
|
|
||||||
void setupSharingDisallowed();
|
void setupSharingDisallowed();
|
||||||
[[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const;
|
[[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const;
|
||||||
|
@ -458,6 +464,7 @@ private:
|
||||||
std::unique_ptr<VideoUserpic>> _videoUserpics;
|
std::unique_ptr<VideoUserpic>> _videoUserpics;
|
||||||
|
|
||||||
std::unique_ptr<HistoryView::Reactions::Manager> _reactionsManager;
|
std::unique_ptr<HistoryView::Reactions::Manager> _reactionsManager;
|
||||||
|
std::unique_ptr<HistoryView::Reactions::Selector> _reactionsSelector;
|
||||||
|
|
||||||
MouseAction _mouseAction = MouseAction::None;
|
MouseAction _mouseAction = MouseAction::None;
|
||||||
TextSelectType _mouseSelectType = TextSelectType::Letters;
|
TextSelectType _mouseSelectType = TextSelectType::Letters;
|
||||||
|
|
|
@ -353,9 +353,10 @@ ListWidget::ListWidget(
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
using ChosenReaction = Reactions::Manager::Chosen;
|
|
||||||
_reactionsManager->chosen(
|
_reactionsManager->chosen(
|
||||||
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
||||||
|
_reactionsManager->updateButton({});
|
||||||
|
|
||||||
const auto item = session().data().message(reaction.context);
|
const auto item = session().data().message(reaction.context);
|
||||||
if (!item
|
if (!item
|
||||||
|| Window::ShowReactPremiumError(
|
|| Window::ShowReactPremiumError(
|
||||||
|
@ -2115,10 +2116,11 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) {
|
||||||
|
|
||||||
void ListWidget::toggleFavoriteReaction(not_null<Element*> view) const {
|
void ListWidget::toggleFavoriteReaction(not_null<Element*> view) const {
|
||||||
const auto favorite = session().data().reactions().favorite();
|
const auto favorite = session().data().reactions().favorite();
|
||||||
const auto allowed = _reactionsManager->allowedSublist();
|
const auto &filter = _reactionsManager->filter();
|
||||||
if (allowed
|
if (favorite.emoji().isEmpty() && !filter.customAllowed) {
|
||||||
&& (favorite.emoji().isEmpty()
|
return;
|
||||||
|| !allowed->contains(favorite.emoji()))) {
|
} else if (filter.allowed
|
||||||
|
&& !filter.allowed->contains(favorite.emoji())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto item = view->data();
|
const auto item = view->data();
|
||||||
|
|
|
@ -36,10 +36,12 @@ namespace Data {
|
||||||
struct Group;
|
struct Group;
|
||||||
class CloudImageView;
|
class CloudImageView;
|
||||||
struct Reaction;
|
struct Reaction;
|
||||||
|
struct ReactionsFilter;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
class Manager;
|
class Manager;
|
||||||
|
struct ChosenReaction;
|
||||||
struct ButtonParameters;
|
struct ButtonParameters;
|
||||||
} // namespace HistoryView::Reactions
|
} // namespace HistoryView::Reactions
|
||||||
|
|
||||||
|
@ -118,7 +120,7 @@ public:
|
||||||
}
|
}
|
||||||
virtual CopyRestrictionType listSelectRestrictionType() = 0;
|
virtual CopyRestrictionType listSelectRestrictionType() = 0;
|
||||||
virtual auto listAllowedReactionsValue()
|
virtual auto listAllowedReactionsValue()
|
||||||
-> rpl::producer<std::optional<base::flat_set<QString>>> = 0;
|
-> rpl::producer<Data::ReactionsFilter> = 0;
|
||||||
virtual void listShowPremiumToast(not_null<DocumentData*> document) = 0;
|
virtual void listShowPremiumToast(not_null<DocumentData*> document) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -379,6 +381,7 @@ private:
|
||||||
using ScrollTopState = ListMemento::ScrollTopState;
|
using ScrollTopState = ListMemento::ScrollTopState;
|
||||||
using PointState = HistoryView::PointState;
|
using PointState = HistoryView::PointState;
|
||||||
using CursorState = HistoryView::CursorState;
|
using CursorState = HistoryView::CursorState;
|
||||||
|
using ChosenReaction = HistoryView::Reactions::ChosenReaction;
|
||||||
|
|
||||||
void refreshViewer();
|
void refreshViewer();
|
||||||
void updateAroundPositionFromNearest(int nearestIndex);
|
void updateAroundPositionFromNearest(int nearestIndex);
|
||||||
|
|
|
@ -684,8 +684,8 @@ CopyRestrictionType PinnedWidget::listSelectRestrictionType() {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PinnedWidget::listAllowedReactionsValue()
|
auto PinnedWidget::listAllowedReactionsValue()
|
||||||
-> rpl::producer<std::optional<base::flat_set<QString>>> {
|
-> rpl::producer<Data::ReactionsFilter> {
|
||||||
return Data::PeerAllowedReactionsValue(_history->peer);
|
return Data::PeerReactionsFilterValue(_history->peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PinnedWidget::listShowPremiumToast(not_null<DocumentData*> document) {
|
void PinnedWidget::listShowPremiumToast(not_null<DocumentData*> document) {
|
||||||
|
|
|
@ -106,7 +106,7 @@ public:
|
||||||
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
|
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
|
||||||
CopyRestrictionType listSelectRestrictionType() override;
|
CopyRestrictionType listSelectRestrictionType() override;
|
||||||
auto listAllowedReactionsValue()
|
auto listAllowedReactionsValue()
|
||||||
-> rpl::producer<std::optional<base::flat_set<QString>>> override;
|
-> rpl::producer<Data::ReactionsFilter> override;
|
||||||
void listShowPremiumToast(not_null<DocumentData*> document) override;
|
void listShowPremiumToast(not_null<DocumentData*> document) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -95,18 +95,24 @@ constexpr auto kMaxReactionsScrollAtOnce = 2;
|
||||||
Button::Button(
|
Button::Button(
|
||||||
Fn<void(QRect)> update,
|
Fn<void(QRect)> update,
|
||||||
ButtonParameters parameters,
|
ButtonParameters parameters,
|
||||||
Fn<void()> hideMe)
|
Fn<void(bool expanded)> toggleExpanded,
|
||||||
|
Fn<void()> hide)
|
||||||
: _update(std::move(update))
|
: _update(std::move(update))
|
||||||
|
, _toggleExpanded(std::move(toggleExpanded))
|
||||||
, _finalScale(ScaleForState(_state))
|
, _finalScale(ScaleForState(_state))
|
||||||
, _collapsed(QPoint(), CountOuterSize())
|
, _collapsed(QPoint(), CountOuterSize())
|
||||||
, _finalHeight(_collapsed.height())
|
, _finalHeight(_collapsed.height())
|
||||||
, _expandTimer([=] { applyState(State::Inside, _update); })
|
, _expandTimer([=] { _toggleExpanded(true); })
|
||||||
, _hideTimer(hideMe) {
|
, _hideTimer(hide) {
|
||||||
applyParameters(parameters, nullptr);
|
applyParameters(parameters, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
Button::~Button() = default;
|
Button::~Button() = default;
|
||||||
|
|
||||||
|
void Button::expandWithoutCustom() {
|
||||||
|
applyState(State::Inside, _update);
|
||||||
|
}
|
||||||
|
|
||||||
bool Button::isHidden() const {
|
bool Button::isHidden() const {
|
||||||
return (_state == State::Hidden) && !_opacityAnimation.animating();
|
return (_state == State::Hidden) && !_opacityAnimation.animating();
|
||||||
}
|
}
|
||||||
|
@ -316,6 +322,7 @@ void Button::applyState(State state, Fn<void(QRect)> update) {
|
||||||
_finalScale = finalScale;
|
_finalScale = finalScale;
|
||||||
}
|
}
|
||||||
_state = state;
|
_state = state;
|
||||||
|
_toggleExpanded(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 Button::ScaleForState(State state) {
|
float64 Button::ScaleForState(State state) {
|
||||||
|
@ -410,15 +417,14 @@ Manager::Manager(
|
||||||
_createChooseCallback = [=](ReactionId id) {
|
_createChooseCallback = [=](ReactionId id) {
|
||||||
return [=] {
|
return [=] {
|
||||||
if (auto chosen = lookupChosen(id)) {
|
if (auto chosen = lookupChosen(id)) {
|
||||||
updateButton({});
|
|
||||||
_chosen.fire(std::move(chosen));
|
_chosen.fire(std::move(chosen));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Manager::Chosen Manager::lookupChosen(const ReactionId &id) const {
|
ChosenReaction Manager::lookupChosen(const ReactionId &id) const {
|
||||||
auto result = Chosen{
|
auto result = ChosenReaction{
|
||||||
.context = _buttonContext,
|
.context = _buttonContext,
|
||||||
.id = id,
|
.id = id,
|
||||||
};
|
};
|
||||||
|
@ -461,21 +467,26 @@ Manager::Chosen Manager::lookupChosen(const ReactionId &id) const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::applyListFilters() {
|
bool Manager::applyUniqueLimit() const {
|
||||||
const auto limit = _uniqueLimit.current();
|
const auto limit = _uniqueLimit.current();
|
||||||
const auto applyUniqueLimit = _buttonContext
|
return _buttonContext
|
||||||
&& (limit > 0)
|
&& (limit > 0)
|
||||||
&& (_buttonAlreadyNotMineCount >= limit);
|
&& (_buttonAlreadyNotMineCount >= limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Manager::applyListFilters() {
|
||||||
|
const auto limited = applyUniqueLimit();
|
||||||
auto icons = std::vector<not_null<ReactionIcons*>>();
|
auto icons = std::vector<not_null<ReactionIcons*>>();
|
||||||
icons.reserve(_list.size());
|
icons.reserve(_list.size());
|
||||||
auto showPremiumLock = (ReactionIcons*)nullptr;
|
auto showPremiumLock = (ReactionIcons*)nullptr;
|
||||||
auto favoriteIndex = -1;
|
auto favoriteIndex = -1;
|
||||||
for (auto &icon : _list) {
|
for (auto &icon : _list) {
|
||||||
const auto &id = icon.id;
|
const auto &id = icon.id;
|
||||||
const auto add = applyUniqueLimit
|
const auto add = limited
|
||||||
? _buttonAlreadyList.contains(id)
|
? _buttonAlreadyList.contains(id)
|
||||||
: (!_filter
|
: id.emoji().isEmpty()
|
||||||
|| (!id.emoji().isEmpty() && _filter->contains(id.emoji())));
|
? _filter.customAllowed
|
||||||
|
: (!_filter.allowed || _filter.allowed->contains(id.emoji()));
|
||||||
if (add) {
|
if (add) {
|
||||||
if (icon.premium
|
if (icon.premium
|
||||||
&& !_allowSendingPremium
|
&& !_allowSendingPremium
|
||||||
|
@ -504,6 +515,9 @@ void Manager::applyListFilters() {
|
||||||
const auto first = begin(icons);
|
const auto first = begin(icons);
|
||||||
std::rotate(first, first + favoriteIndex, first + favoriteIndex + 1);
|
std::rotate(first, first + favoriteIndex, first + favoriteIndex + 1);
|
||||||
}
|
}
|
||||||
|
if (!limited && _filter.customAllowed && icons.size() > 1) {
|
||||||
|
icons.erase(begin(icons) + 1, end(icons));
|
||||||
|
}
|
||||||
if (_icons == icons) {
|
if (_icons == icons) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -528,8 +542,13 @@ void Manager::stealWheelEvents(not_null<QWidget*> target) {
|
||||||
Manager::~Manager() = default;
|
Manager::~Manager() = default;
|
||||||
|
|
||||||
void Manager::updateButton(ButtonParameters parameters) {
|
void Manager::updateButton(ButtonParameters parameters) {
|
||||||
if (parameters.cursorLeft && _menu) {
|
if (parameters.cursorLeft) {
|
||||||
return;
|
if (_menu) {
|
||||||
|
return;
|
||||||
|
} else if (_externalSelectorShown) {
|
||||||
|
setSelectedIcon(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const auto contextChanged = (_buttonContext != parameters.context);
|
const auto contextChanged = (_buttonContext != parameters.context);
|
||||||
if (contextChanged) {
|
if (contextChanged) {
|
||||||
|
@ -537,6 +556,7 @@ void Manager::updateButton(ButtonParameters parameters) {
|
||||||
if (_button) {
|
if (_button) {
|
||||||
_button->applyState(ButtonState::Hidden);
|
_button->applyState(ButtonState::Hidden);
|
||||||
_buttonHiding.push_back(std::move(_button));
|
_buttonHiding.push_back(std::move(_button));
|
||||||
|
_expandSelectorRequests.fire({ .expanded = false });
|
||||||
}
|
}
|
||||||
_buttonShowTimer.cancel();
|
_buttonShowTimer.cancel();
|
||||||
_scheduledParameters = std::nullopt;
|
_scheduledParameters = std::nullopt;
|
||||||
|
@ -567,11 +587,32 @@ void Manager::updateButton(ButtonParameters parameters) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Manager::toggleExpanded(bool expanded) {
|
||||||
|
if (!_button || !_buttonContext) {
|
||||||
|
} else if (!expanded || (_filter.customAllowed && !applyUniqueLimit())) {
|
||||||
|
_expandSelectorRequests.fire({
|
||||||
|
.context = _buttonContext,
|
||||||
|
.button = _button->geometry().marginsRemoved(
|
||||||
|
st::reactionCornerShadow),
|
||||||
|
.expanded = expanded,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_button->expandWithoutCustom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Manager::setExternalSelectorShown(rpl::producer<bool> shown) {
|
||||||
|
std::move(shown) | rpl::start_with_next([=](bool shown) {
|
||||||
|
_externalSelectorShown = shown;
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
void Manager::showButtonDelayed() {
|
void Manager::showButtonDelayed() {
|
||||||
clearAppearAnimations();
|
clearAppearAnimations();
|
||||||
_button = std::make_unique<Button>(
|
_button = std::make_unique<Button>(
|
||||||
_buttonUpdate,
|
_buttonUpdate,
|
||||||
*_scheduledParameters,
|
*_scheduledParameters,
|
||||||
|
[=](bool expanded) { toggleExpanded(expanded); },
|
||||||
[=]{ updateButton({}); });
|
[=]{ updateButton({}); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -614,7 +655,7 @@ void Manager::applyList(
|
||||||
setSelectedIcon((selected < _icons.size()) ? selected : -1);
|
setSelectedIcon((selected < _icons.size()) ? selected : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::updateAllowedSublist(AllowedSublist filter) {
|
void Manager::updateFilter(Data::ReactionsFilter filter) {
|
||||||
if (_filter == filter) {
|
if (_filter == filter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -630,7 +671,7 @@ void Manager::updateAllowSendingPremium(bool allow) {
|
||||||
applyListFilters();
|
applyListFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
const Manager::AllowedSublist &Manager::allowedSublist() const {
|
const Data::ReactionsFilter &Manager::filter() const {
|
||||||
return _filter;
|
return _filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1662,7 +1703,7 @@ auto Manager::faveRequests() const -> rpl::producer<ReactionId> {
|
||||||
void SetupManagerList(
|
void SetupManagerList(
|
||||||
not_null<Manager*> manager,
|
not_null<Manager*> manager,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
rpl::producer<Manager::AllowedSublist> filter) {
|
rpl::producer<Data::ReactionsFilter> filter) {
|
||||||
const auto reactions = &session->data().reactions();
|
const auto reactions = &session->data().reactions();
|
||||||
rpl::single(rpl::empty) | rpl::then(
|
rpl::single(rpl::empty) | rpl::then(
|
||||||
reactions->updates()
|
reactions->updates()
|
||||||
|
@ -1675,8 +1716,8 @@ void SetupManagerList(
|
||||||
|
|
||||||
std::move(
|
std::move(
|
||||||
filter
|
filter
|
||||||
) | rpl::start_with_next([=](Manager::AllowedSublist &&list) {
|
) | rpl::start_with_next([=](Data::ReactionsFilter &&list) {
|
||||||
manager->updateAllowedSublist(std::move(list));
|
manager->updateFilter(std::move(list));
|
||||||
}, manager->lifetime());
|
}, manager->lifetime());
|
||||||
|
|
||||||
manager->faveRequests(
|
manager->faveRequests(
|
||||||
|
|
|
@ -73,10 +73,12 @@ public:
|
||||||
Button(
|
Button(
|
||||||
Fn<void(QRect)> update,
|
Fn<void(QRect)> update,
|
||||||
ButtonParameters parameters,
|
ButtonParameters parameters,
|
||||||
Fn<void()> hideMe);
|
Fn<void(bool)> toggleExpanded,
|
||||||
|
Fn<void()> hide);
|
||||||
~Button();
|
~Button();
|
||||||
|
|
||||||
void applyParameters(ButtonParameters parameters);
|
void applyParameters(ButtonParameters parameters);
|
||||||
|
void expandWithoutCustom();
|
||||||
|
|
||||||
using State = ButtonState;
|
using State = ButtonState;
|
||||||
void applyState(State state);
|
void applyState(State state);
|
||||||
|
@ -110,6 +112,8 @@ private:
|
||||||
void updateExpandDirection(const ButtonParameters ¶meters);
|
void updateExpandDirection(const ButtonParameters ¶meters);
|
||||||
|
|
||||||
const Fn<void(QRect)> _update;
|
const Fn<void(QRect)> _update;
|
||||||
|
const Fn<void(bool)> _toggleExpanded;
|
||||||
|
|
||||||
State _state = State::Hidden;
|
State _state = State::Hidden;
|
||||||
float64 _finalScale = 0.;
|
float64 _finalScale = 0.;
|
||||||
Ui::Animations::Simple _scaleAnimation;
|
Ui::Animations::Simple _scaleAnimation;
|
||||||
|
@ -131,6 +135,23 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ChosenReaction {
|
||||||
|
FullMsgId context;
|
||||||
|
Data::ReactionId id;
|
||||||
|
std::shared_ptr<Lottie::Icon> icon;
|
||||||
|
QRect geometry;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return context && !id.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExpandRequest {
|
||||||
|
FullMsgId context;
|
||||||
|
QRect button;
|
||||||
|
bool expanded = false;
|
||||||
|
};
|
||||||
|
|
||||||
using IconFactory = Fn<std::shared_ptr<Lottie::Icon>(
|
using IconFactory = Fn<std::shared_ptr<Lottie::Icon>(
|
||||||
not_null<Data::DocumentMedia*>,
|
not_null<Data::DocumentMedia*>,
|
||||||
int)>;
|
int)>;
|
||||||
|
@ -145,15 +166,14 @@ public:
|
||||||
~Manager();
|
~Manager();
|
||||||
|
|
||||||
using ReactionId = ::Data::ReactionId;
|
using ReactionId = ::Data::ReactionId;
|
||||||
using AllowedSublist = std::optional<base::flat_set<QString>>;
|
|
||||||
|
|
||||||
void applyList(
|
void applyList(
|
||||||
const std::vector<Data::Reaction> &list,
|
const std::vector<Data::Reaction> &list,
|
||||||
const ReactionId &favorite,
|
const ReactionId &favorite,
|
||||||
bool premiumPossible);
|
bool premiumPossible);
|
||||||
void updateAllowedSublist(AllowedSublist filter);
|
void updateFilter(Data::ReactionsFilter filter);
|
||||||
void updateAllowSendingPremium(bool allow);
|
void updateAllowSendingPremium(bool allow);
|
||||||
[[nodiscard]] const AllowedSublist &allowedSublist() const;
|
[[nodiscard]] const Data::ReactionsFilter &filter() const;
|
||||||
void updateUniqueLimit(not_null<HistoryItem*> item);
|
void updateUniqueLimit(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
void updateButton(ButtonParameters parameters);
|
void updateButton(ButtonParameters parameters);
|
||||||
|
@ -163,19 +183,14 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] bool consumeWheelEvent(not_null<QWheelEvent*> e);
|
[[nodiscard]] bool consumeWheelEvent(not_null<QWheelEvent*> e);
|
||||||
|
|
||||||
struct Chosen {
|
[[nodiscard]] rpl::producer<ChosenReaction> chosen() const {
|
||||||
FullMsgId context;
|
|
||||||
ReactionId id;
|
|
||||||
std::shared_ptr<Lottie::Icon> icon;
|
|
||||||
QRect geometry;
|
|
||||||
|
|
||||||
explicit operator bool() const {
|
|
||||||
return context && !id.empty();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
[[nodiscard]] rpl::producer<Chosen> chosen() const {
|
|
||||||
return _chosen.events();
|
return _chosen.events();
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] auto expandSelectorRequests() const
|
||||||
|
-> rpl::producer<ExpandRequest> {
|
||||||
|
return _expandSelectorRequests.events();
|
||||||
|
}
|
||||||
|
void setExternalSelectorShown(rpl::producer<bool> shown);
|
||||||
|
|
||||||
[[nodiscard]] std::optional<QRect> lookupEffectArea(
|
[[nodiscard]] std::optional<QRect> lookupEffectArea(
|
||||||
FullMsgId itemId) const;
|
FullMsgId itemId) const;
|
||||||
|
@ -223,8 +238,10 @@ private:
|
||||||
void showButtonDelayed();
|
void showButtonDelayed();
|
||||||
void stealWheelEvents(not_null<QWidget*> target);
|
void stealWheelEvents(not_null<QWidget*> target);
|
||||||
|
|
||||||
[[nodiscard]] Chosen lookupChosen(const ReactionId &id) const;
|
[[nodiscard]] ChosenReaction lookupChosen(const ReactionId &id) const;
|
||||||
[[nodiscard]] bool overCurrentButton(QPoint position) const;
|
[[nodiscard]] bool overCurrentButton(QPoint position) const;
|
||||||
|
[[nodiscard]] bool applyUniqueLimit() const;
|
||||||
|
void toggleExpanded(bool expanded);
|
||||||
|
|
||||||
void removeStaleButtons();
|
void removeStaleButtons();
|
||||||
void paintButton(
|
void paintButton(
|
||||||
|
@ -314,10 +331,11 @@ private:
|
||||||
void checkIcons();
|
void checkIcons();
|
||||||
|
|
||||||
const IconFactory _iconFactory;
|
const IconFactory _iconFactory;
|
||||||
rpl::event_stream<Chosen> _chosen;
|
rpl::event_stream<ChosenReaction> _chosen;
|
||||||
|
rpl::event_stream<ExpandRequest> _expandSelectorRequests;
|
||||||
std::vector<ReactionIcons> _list;
|
std::vector<ReactionIcons> _list;
|
||||||
ReactionId _favorite;
|
ReactionId _favorite;
|
||||||
AllowedSublist _filter;
|
Data::ReactionsFilter _filter;
|
||||||
QSize _outer;
|
QSize _outer;
|
||||||
QRect _inner;
|
QRect _inner;
|
||||||
QSize _overlayFull;
|
QSize _overlayFull;
|
||||||
|
@ -372,6 +390,7 @@ private:
|
||||||
|
|
||||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
rpl::event_stream<ReactionId> _faveRequests;
|
rpl::event_stream<ReactionId> _faveRequests;
|
||||||
|
bool _externalSelectorShown = false;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
@ -395,7 +414,7 @@ private:
|
||||||
void SetupManagerList(
|
void SetupManagerList(
|
||||||
not_null<Manager*> manager,
|
not_null<Manager*> manager,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
rpl::producer<Manager::AllowedSublist> filter);
|
rpl::producer<Data::ReactionsFilter> filter);
|
||||||
|
|
||||||
[[nodiscard]] std::shared_ptr<Lottie::Icon> DefaultIconFactory(
|
[[nodiscard]] std::shared_ptr<Lottie::Icon> DefaultIconFactory(
|
||||||
not_null<Data::DocumentMedia*> media,
|
not_null<Data::DocumentMedia*> media,
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
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/history_view_react_selector.h"
|
||||||
|
|
||||||
|
#include "history/view/history_view_react_button.h"
|
||||||
|
#include "data/data_document.h"
|
||||||
|
#include "chat_helpers/tabbed_panel.h"
|
||||||
|
#include "chat_helpers/tabbed_selector.h"
|
||||||
|
#include "window/window_session_controller.h"
|
||||||
|
#include "window/window_controller.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
|
namespace HistoryView::Reactions {
|
||||||
|
|
||||||
|
void Selector::show(
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
not_null<QWidget*> widget,
|
||||||
|
FullMsgId contextId,
|
||||||
|
QRect around) {
|
||||||
|
if (!_panel) {
|
||||||
|
create(controller);
|
||||||
|
} else if (_contextId == contextId
|
||||||
|
&& (!_panel->hiding() && !_panel->isHidden())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_contextId = contextId;
|
||||||
|
const auto parent = _panel->parentWidget();
|
||||||
|
const auto global = widget->mapToGlobal(around.topLeft());
|
||||||
|
const auto local = parent->mapFromGlobal(global);
|
||||||
|
const auto availableTop = local.y();
|
||||||
|
const auto availableBottom = parent->height()
|
||||||
|
- local.y()
|
||||||
|
- around.height();
|
||||||
|
if (availableTop >= st::emojiPanMinHeight
|
||||||
|
|| availableTop >= availableBottom) {
|
||||||
|
_panel->setDropDown(false);
|
||||||
|
_panel->moveBottomRight(
|
||||||
|
local.y(),
|
||||||
|
local.x() + around.width() * 3);
|
||||||
|
} else {
|
||||||
|
_panel->setDropDown(true);
|
||||||
|
_panel->moveTopRight(
|
||||||
|
local.y() + around.height(),
|
||||||
|
local.x() + around.width() * 3);
|
||||||
|
}
|
||||||
|
_panel->setDesiredHeightValues(
|
||||||
|
1.,
|
||||||
|
st::emojiPanMinHeight / 2,
|
||||||
|
st::emojiPanMinHeight);
|
||||||
|
_panel->showAnimated();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<ChosenReaction> Selector::chosen() const {
|
||||||
|
return _chosen.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<bool> Selector::shown() const {
|
||||||
|
return _shown.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Selector::create(
|
||||||
|
not_null<Window::SessionController*> controller) {
|
||||||
|
using Selector = ChatHelpers::TabbedSelector;
|
||||||
|
_panel = base::make_unique_q<ChatHelpers::TabbedPanel>(
|
||||||
|
controller->window().widget()->bodyWidget(),
|
||||||
|
controller,
|
||||||
|
object_ptr<Selector>(
|
||||||
|
nullptr,
|
||||||
|
controller,
|
||||||
|
Window::GifPauseReason::Layer,
|
||||||
|
ChatHelpers::TabbedSelector::Mode::EmojiStatus));
|
||||||
|
_panel->shownValue() | rpl::start_to_stream(_shown, _panel->lifetime());
|
||||||
|
_panel->hide();
|
||||||
|
_panel->selector()->setAllowEmojiWithoutPremium(false);
|
||||||
|
|
||||||
|
auto statusChosen = _panel->selector()->customEmojiChosen(
|
||||||
|
) | rpl::map([=](Selector::FileChosen data) {
|
||||||
|
return data.document->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
rpl::merge(
|
||||||
|
std::move(statusChosen),
|
||||||
|
_panel->selector()->emojiChosen() | rpl::map_to(DocumentId())
|
||||||
|
) | rpl::start_with_next([=](DocumentId id) {
|
||||||
|
_chosen.fire(ChosenReaction{ .context = _contextId, .id = { id } });
|
||||||
|
}, _panel->lifetime());
|
||||||
|
|
||||||
|
_panel->selector()->showPromoForPremiumEmoji();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Selector::hide(anim::type animated) {
|
||||||
|
if (!_panel || _panel->isHidden()) {
|
||||||
|
return;
|
||||||
|
} else if (animated == anim::type::instant) {
|
||||||
|
_panel->hideFast();
|
||||||
|
} else {
|
||||||
|
_panel->hideAnimated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HistoryView::Reactions
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
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/unique_qptr.h"
|
||||||
|
#include "ui/effects/animation_value.h"
|
||||||
|
|
||||||
|
namespace ChatHelpers {
|
||||||
|
class TabbedPanel;
|
||||||
|
} // namespace ChatHelpers
|
||||||
|
|
||||||
|
namespace Window {
|
||||||
|
class SessionController;
|
||||||
|
} // namespace Window
|
||||||
|
|
||||||
|
namespace HistoryView::Reactions {
|
||||||
|
|
||||||
|
struct ChosenReaction;
|
||||||
|
|
||||||
|
class Selector final {
|
||||||
|
public:
|
||||||
|
void show(
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
not_null<QWidget*> widget,
|
||||||
|
FullMsgId contextId,
|
||||||
|
QRect around);
|
||||||
|
void hide(anim::type animated = anim::type::normal);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<ChosenReaction> chosen() const;
|
||||||
|
[[nodiscard]] rpl::producer<bool> shown() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void create(not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
|
rpl::event_stream<bool> _shown;
|
||||||
|
base::unique_qptr<ChatHelpers::TabbedPanel> _panel;
|
||||||
|
rpl::event_stream<ChosenReaction> _chosen;
|
||||||
|
FullMsgId _contextId;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace HistoryView::Reactions
|
|
@ -2041,8 +2041,8 @@ CopyRestrictionType RepliesWidget::listSelectRestrictionType() {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto RepliesWidget::listAllowedReactionsValue()
|
auto RepliesWidget::listAllowedReactionsValue()
|
||||||
-> rpl::producer<std::optional<base::flat_set<QString>>> {
|
-> rpl::producer<Data::ReactionsFilter> {
|
||||||
return Data::PeerAllowedReactionsValue(_history->peer);
|
return Data::PeerReactionsFilterValue(_history->peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RepliesWidget::listShowPremiumToast(not_null<DocumentData*> document) {
|
void RepliesWidget::listShowPremiumToast(not_null<DocumentData*> document) {
|
||||||
|
|
|
@ -143,7 +143,7 @@ public:
|
||||||
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
|
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
|
||||||
CopyRestrictionType listSelectRestrictionType() override;
|
CopyRestrictionType listSelectRestrictionType() override;
|
||||||
auto listAllowedReactionsValue()
|
auto listAllowedReactionsValue()
|
||||||
-> rpl::producer<std::optional<base::flat_set<QString>>> override;
|
->rpl::producer<Data::ReactionsFilter> override;
|
||||||
void listShowPremiumToast(not_null<DocumentData*> document) override;
|
void listShowPremiumToast(not_null<DocumentData*> document) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -1355,9 +1355,9 @@ CopyRestrictionType ScheduledWidget::listSelectRestrictionType() {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ScheduledWidget::listAllowedReactionsValue()
|
auto ScheduledWidget::listAllowedReactionsValue()
|
||||||
-> rpl::producer<std::optional<base::flat_set<QString>>> {
|
-> rpl::producer<Data::ReactionsFilter> {
|
||||||
const auto empty = base::flat_set<QString>();
|
const auto empty = base::flat_set<QString>();
|
||||||
return rpl::single(std::optional<base::flat_set<QString>>(empty));
|
return rpl::single(Data::ReactionsFilter{ .allowed = empty });
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScheduledWidget::listShowPremiumToast(
|
void ScheduledWidget::listShowPremiumToast(
|
||||||
|
|
|
@ -128,7 +128,7 @@ public:
|
||||||
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
|
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
|
||||||
CopyRestrictionType listSelectRestrictionType() override;
|
CopyRestrictionType listSelectRestrictionType() override;
|
||||||
auto listAllowedReactionsValue()
|
auto listAllowedReactionsValue()
|
||||||
-> rpl::producer<std::optional<base::flat_set<QString>>> override;
|
-> rpl::producer<Data::ReactionsFilter> override;
|
||||||
void listShowPremiumToast(not_null<DocumentData*> document) override;
|
void listShowPremiumToast(not_null<DocumentData*> document) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -351,7 +351,7 @@ bool ShowSendPremiumError(
|
||||||
const auto type = peer->isBroadcast()
|
const auto type = peer->isBroadcast()
|
||||||
? ReactionDisableType::Channel
|
? ReactionDisableType::Channel
|
||||||
: ReactionDisableType::Group;
|
: ReactionDisableType::Group;
|
||||||
if (const auto allowed = Data::PeerAllowedReactions(peer)) {
|
if (const auto allowed = Data::PeerReactionsFilter(peer).allowed) {
|
||||||
for (const auto &reaction : list) {
|
for (const auto &reaction : list) {
|
||||||
if (reaction.premium
|
if (reaction.premium
|
||||||
&& !allowed->contains(reaction.id.emoji())) {
|
&& !allowed->contains(reaction.id.emoji())) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue