mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 15:17:07 +02:00
Correctly apply reaction restrictions.
This commit is contained in:
parent
2733b12cff
commit
963694330d
22 changed files with 313 additions and 170 deletions
Telegram/SourceFiles
|
@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "history/admin_log/history_admin_log_section.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
@ -1060,7 +1061,7 @@ void Controller::fillManageSection() {
|
|||
!_peer->isBroadcast(),
|
||||
session->data().reactions().list(
|
||||
Data::Reactions::Type::Active),
|
||||
session->data().reactions().list(_peer),
|
||||
*Data::PeerAllowedReactions(_peer),
|
||||
done));
|
||||
},
|
||||
st::infoIconReactions);
|
||||
|
|
|
@ -93,7 +93,7 @@ void EditAllowedReactionsBox(
|
|||
not_null<Ui::GenericBox*> box,
|
||||
bool isGroup,
|
||||
const std::vector<Reaction> &list,
|
||||
const std::vector<Reaction> &selected,
|
||||
const base::flat_set<QString> &selected,
|
||||
Fn<void(const std::vector<QString> &)> callback) {
|
||||
box->setTitle(tr::lng_manage_peer_reactions());
|
||||
|
||||
|
@ -146,7 +146,7 @@ void EditAllowedReactionsBox(
|
|||
tr::lng_manage_peer_reactions_available());
|
||||
|
||||
const auto active = [&](const Data::Reaction &entry) {
|
||||
return ranges::contains(selected, entry.emoji, &Reaction::emoji);
|
||||
return selected.contains(entry.emoji);
|
||||
};
|
||||
const auto add = [&](const Data::Reaction &entry) {
|
||||
const auto button = Settings::AddButton(
|
||||
|
@ -198,9 +198,9 @@ void SaveAllowedReactions(
|
|||
)).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().applyUpdates(result);
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->setAllowedReactions(allowed);
|
||||
chat->setAllowedReactions({ begin(allowed), end(allowed) });
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->setAllowedReactions(allowed);
|
||||
channel->setAllowedReactions({ begin(allowed), end(allowed) });
|
||||
} else {
|
||||
Unexpected("Invalid peer type in SaveAllowedReactions.");
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ void EditAllowedReactionsBox(
|
|||
not_null<Ui::GenericBox*> box,
|
||||
bool isGroup,
|
||||
const std::vector<Data::Reaction> &list,
|
||||
const std::vector<Data::Reaction> &selected,
|
||||
const base::flat_set<QString> &selected,
|
||||
Fn<void(const std::vector<QString> &)> callback);
|
||||
|
||||
void SaveAllowedReactions(
|
||||
|
|
|
@ -761,7 +761,7 @@ PeerId ChannelData::groupCallDefaultJoinAs() const {
|
|||
return _callDefaultJoinAs;
|
||||
}
|
||||
|
||||
void ChannelData::setAllowedReactions(std::vector<QString> list) {
|
||||
void ChannelData::setAllowedReactions(base::flat_set<QString> list) {
|
||||
if (_allowedReactions != list) {
|
||||
const auto toggled = (_allowedReactions.empty() != list.empty());
|
||||
_allowedReactions = std::move(list);
|
||||
|
@ -774,7 +774,7 @@ void ChannelData::setAllowedReactions(std::vector<QString> list) {
|
|||
}
|
||||
}
|
||||
|
||||
const std::vector<QString> &ChannelData::allowedReactions() const {
|
||||
const base::flat_set<QString> &ChannelData::allowedReactions() const {
|
||||
return _allowedReactions;
|
||||
}
|
||||
|
||||
|
|
|
@ -410,8 +410,8 @@ public:
|
|||
void setGroupCallDefaultJoinAs(PeerId peerId);
|
||||
[[nodiscard]] PeerId groupCallDefaultJoinAs() const;
|
||||
|
||||
void setAllowedReactions(std::vector<QString> list);
|
||||
[[nodiscard]] const std::vector<QString> &allowedReactions() const;
|
||||
void setAllowedReactions(base::flat_set<QString> list);
|
||||
[[nodiscard]] const base::flat_set<QString> &allowedReactions() const;
|
||||
|
||||
// Still public data members.
|
||||
uint64 access = 0;
|
||||
|
@ -460,7 +460,7 @@ private:
|
|||
QString _inviteLink;
|
||||
std::optional<ChannelData*> _linkedChat;
|
||||
|
||||
std::vector<QString> _allowedReactions;
|
||||
base::flat_set<QString> _allowedReactions;
|
||||
|
||||
std::unique_ptr<Data::GroupCall> _call;
|
||||
PeerId _callDefaultJoinAs = 0;
|
||||
|
|
|
@ -287,7 +287,7 @@ void ChatData::setPendingRequestsCount(
|
|||
}
|
||||
}
|
||||
|
||||
void ChatData::setAllowedReactions(std::vector<QString> list) {
|
||||
void ChatData::setAllowedReactions(base::flat_set<QString> list) {
|
||||
if (_allowedReactions != list) {
|
||||
const auto toggled = (_allowedReactions.empty() != list.empty());
|
||||
_allowedReactions = std::move(list);
|
||||
|
@ -300,7 +300,7 @@ void ChatData::setAllowedReactions(std::vector<QString> list) {
|
|||
}
|
||||
}
|
||||
|
||||
const std::vector<QString> &ChatData::allowedReactions() const {
|
||||
const base::flat_set<QString> &ChatData::allowedReactions() const {
|
||||
return _allowedReactions;
|
||||
}
|
||||
|
||||
|
|
|
@ -175,8 +175,8 @@ public:
|
|||
int count,
|
||||
std::vector<UserId> recentRequesters);
|
||||
|
||||
void setAllowedReactions(std::vector<QString> list);
|
||||
[[nodiscard]] const std::vector<QString> &allowedReactions() const;
|
||||
void setAllowedReactions(base::flat_set<QString> list);
|
||||
[[nodiscard]] const base::flat_set<QString> &allowedReactions() const;
|
||||
|
||||
// Still public data members.
|
||||
const MTPlong inputChat;
|
||||
|
@ -202,7 +202,7 @@ private:
|
|||
int _pendingRequestsCount = 0;
|
||||
std::vector<UserId> _recentRequesters;
|
||||
|
||||
std::vector<QString> _allowedReactions;
|
||||
base::flat_set<QString> _allowedReactions;
|
||||
|
||||
std::unique_ptr<Data::GroupCall> _call;
|
||||
PeerId _callDefaultJoinAs = 0;
|
||||
|
|
|
@ -11,11 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "apiwrap.h"
|
||||
|
@ -65,16 +63,6 @@ const std::vector<Reaction> &Reactions::list(Type type) const {
|
|||
Unexpected("Type in Reactions::list.");
|
||||
}
|
||||
|
||||
std::vector<Reaction> Reactions::list(not_null<PeerData*> peer) const {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
return filtered(chat->allowedReactions());
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
return filtered(channel->allowedReactions());
|
||||
} else {
|
||||
return list(Type::Active);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> Reactions::updates() const {
|
||||
return _updated.events();
|
||||
}
|
||||
|
@ -214,33 +202,17 @@ void Reactions::downloadTaskFinished() {
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<Reaction> Reactions::Filtered(
|
||||
const std::vector<Reaction> &reactions,
|
||||
const std::vector<QString> &emoji) {
|
||||
auto result = std::vector<Reaction>();
|
||||
result.reserve(emoji.size());
|
||||
for (const auto &single : emoji) {
|
||||
const auto i = ranges::find(reactions, single, &Reaction::emoji);
|
||||
if (i != end(reactions)) {
|
||||
result.push_back(*i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Reaction> Reactions::filtered(
|
||||
const std::vector<QString> &emoji) const {
|
||||
return Filtered(list(Type::Active), emoji);
|
||||
}
|
||||
|
||||
std::vector<QString> Reactions::ParseAllowed(
|
||||
base::flat_set<QString> Reactions::ParseAllowed(
|
||||
const MTPVector<MTPstring> *list) {
|
||||
if (!list) {
|
||||
return {};
|
||||
}
|
||||
return list->v | ranges::view::transform([](const MTPstring &string) {
|
||||
const auto parsed = ranges::views::all(
|
||||
list->v
|
||||
) | ranges::views::transform([](const MTPstring &string) {
|
||||
return qs(string);
|
||||
}) | ranges::to_vector;
|
||||
return { begin(parsed), end(parsed) };
|
||||
}
|
||||
|
||||
void Reactions::request() {
|
||||
|
|
|
@ -43,15 +43,8 @@ public:
|
|||
All,
|
||||
};
|
||||
[[nodiscard]] const std::vector<Reaction> &list(Type type) const;
|
||||
[[nodiscard]] std::vector<Reaction> list(not_null<PeerData*> peer) const;
|
||||
|
||||
[[nodiscard]] static std::vector<Reaction> Filtered(
|
||||
const std::vector<Reaction> &reactions,
|
||||
const std::vector<QString> &emoji);
|
||||
[[nodiscard]] std::vector<Reaction> filtered(
|
||||
const std::vector<QString> &emoji) const;
|
||||
|
||||
[[nodiscard]] static std::vector<QString> ParseAllowed(
|
||||
[[nodiscard]] static base::flat_set<QString> ParseAllowed(
|
||||
const MTPVector<MTPstring> *list);
|
||||
|
||||
[[nodiscard]] rpl::producer<> updates() const;
|
||||
|
|
|
@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "base/unixtime.h"
|
||||
|
||||
|
@ -497,18 +499,36 @@ rpl::producer<QImage> PeerUserpicImageValue(
|
|||
};
|
||||
}
|
||||
|
||||
rpl::producer<std::vector<Data::Reaction>> PeerAllowedReactionsValue(
|
||||
std::optional<base::flat_set<QString>> PeerAllowedReactions(
|
||||
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);
|
||||
if (const auto chat = peer->asChat()) {
|
||||
return chat->allowedReactions();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
return channel->allowedReactions();
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto PeerAllowedReactionsValue(
|
||||
not_null<PeerData*> peer)
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>> {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::Reactions
|
||||
) | rpl::map([=]{
|
||||
return PeerAllowedReactions(peer);
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<int> UniqueReactionsLimitValue(
|
||||
not_null<Main::Session*> session) {
|
||||
const auto config = &session->account().appConfig();
|
||||
return config->value(
|
||||
) | rpl::map([=] {
|
||||
return int(base::SafeRound(
|
||||
config->get<double>("reactions_uniq_max", 11)));
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
enum class ImageRoundRadius;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct Reaction;
|
||||
|
@ -122,7 +126,12 @@ inline auto PeerFullFlagValue(
|
|||
int size,
|
||||
ImageRoundRadius radius);
|
||||
|
||||
[[nodiscard]] std::optional<base::flat_set<QString>> PeerAllowedReactions(
|
||||
not_null<PeerData*> peer);
|
||||
[[nodiscard]] auto PeerAllowedReactionsValue(not_null<PeerData*> peer)
|
||||
-> rpl::producer<std::vector<Data::Reaction>>;
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>>;
|
||||
|
||||
[[nodiscard]] rpl::producer<int> UniqueReactionsLimitValue(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -322,6 +322,7 @@ HistoryInner::HistoryInner(
|
|||
, _reactionsManager(
|
||||
std::make_unique<HistoryView::Reactions::Manager>(
|
||||
this,
|
||||
Data::UniqueReactionsLimitValue(&controller->session()),
|
||||
[=](QRect updated) { update(updated); }))
|
||||
, _touchSelectTimer([=] { onTouchSelect(); })
|
||||
, _touchScrollTimer([=] { onTouchScrollTimer(); })
|
||||
|
@ -412,6 +413,7 @@ HistoryInner::HistoryInner(
|
|||
return item->mainView() != nullptr;
|
||||
}) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
|
||||
item->mainView()->itemDataChanged();
|
||||
_reactionsManager->updateUniqueLimit(item);
|
||||
}, lifetime());
|
||||
|
||||
session().changes().historyUpdates(
|
||||
|
@ -421,11 +423,10 @@ HistoryInner::HistoryInner(
|
|||
update();
|
||||
}, lifetime());
|
||||
|
||||
Data::PeerAllowedReactionsValue(
|
||||
_peer
|
||||
) | rpl::start_with_next([=](std::vector<Data::Reaction> &&list) {
|
||||
_reactionsManager->applyList(std::move(list));
|
||||
}, lifetime());
|
||||
HistoryView::Reactions::SetupManagerList(
|
||||
_reactionsManager.get(),
|
||||
&session(),
|
||||
Data::PeerAllowedReactionsValue(_peer));
|
||||
|
||||
controller->adaptive().chatWideValue(
|
||||
) | rpl::start_with_next([=](bool wide) {
|
||||
|
@ -3169,7 +3170,8 @@ void HistoryInner::mouseActionUpdate() {
|
|||
: nullptr;
|
||||
const auto item = view ? view->data().get() : nullptr;
|
||||
if (view) {
|
||||
if (App::mousedItem() != view) {
|
||||
const auto changed = (App::mousedItem() != view);
|
||||
if (changed) {
|
||||
repaintItem(App::mousedItem());
|
||||
App::mousedItem(view);
|
||||
repaintItem(App::mousedItem());
|
||||
|
@ -3179,6 +3181,9 @@ void HistoryInner::mouseActionUpdate() {
|
|||
view,
|
||||
m,
|
||||
reactionState));
|
||||
if (changed) {
|
||||
_reactionsManager->updateUniqueLimit(item);
|
||||
}
|
||||
if (view->pointState(m) != PointState::Outside) {
|
||||
if (App::hoveredItem() != view) {
|
||||
repaintItem(App::hoveredItem());
|
||||
|
|
|
@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_channel.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
|
@ -267,6 +268,7 @@ ListWidget::ListWidget(
|
|||
, _reactionsManager(
|
||||
std::make_unique<Reactions::Manager>(
|
||||
this,
|
||||
Data::UniqueReactionsLimitValue(&controller->session()),
|
||||
[=](QRect updated) { update(updated); }))
|
||||
, _scrollDateCheck([this] { scrollDateCheck(); })
|
||||
, _applyUpdatedScrollState([this] { applyUpdatedScrollState(); })
|
||||
|
@ -344,10 +346,10 @@ ListWidget::ListWidget(
|
|||
}
|
||||
}, lifetime());
|
||||
|
||||
_delegate->listAllowedReactionsValue(
|
||||
) | rpl::start_with_next([=](std::vector<Data::Reaction> &&list) {
|
||||
_reactionsManager->applyList(std::move(list));
|
||||
}, lifetime());
|
||||
Reactions::SetupManagerList(
|
||||
_reactionsManager.get(),
|
||||
&session(),
|
||||
_delegate->listAllowedReactionsValue());
|
||||
|
||||
controller->adaptive().chatWideValue(
|
||||
) | rpl::start_with_next([=](bool wide) {
|
||||
|
@ -2563,7 +2565,8 @@ void ListWidget::mouseActionUpdate() {
|
|||
view ? view->height() : 0,
|
||||
itemPoint,
|
||||
view ? view->pointState(itemPoint) : PointState::Outside);
|
||||
if (_overElement != view) {
|
||||
const auto viewChanged = (_overElement != view);
|
||||
if (viewChanged) {
|
||||
repaintItem(_overElement);
|
||||
_overElement = view;
|
||||
repaintItem(_overElement);
|
||||
|
@ -2574,6 +2577,9 @@ void ListWidget::mouseActionUpdate() {
|
|||
itemPoint,
|
||||
reactionState)
|
||||
: Reactions::ButtonParameters());
|
||||
if (viewChanged && view) {
|
||||
_reactionsManager->updateUniqueLimit(item);
|
||||
}
|
||||
|
||||
TextState dragState;
|
||||
ClickHandlerHost *lnkhost = nullptr;
|
||||
|
|
|
@ -113,7 +113,7 @@ public:
|
|||
}
|
||||
virtual CopyRestrictionType listSelectRestrictionType() = 0;
|
||||
virtual auto listAllowedReactionsValue()
|
||||
-> rpl::producer<std::vector<Data::Reaction>> = 0;
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>> = 0;
|
||||
};
|
||||
|
||||
struct SelectionData {
|
||||
|
|
|
@ -684,7 +684,7 @@ CopyRestrictionType PinnedWidget::listSelectRestrictionType() {
|
|||
}
|
||||
|
||||
auto PinnedWidget::listAllowedReactionsValue()
|
||||
-> rpl::producer<std::vector<Data::Reaction>> {
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>> {
|
||||
return Data::PeerAllowedReactionsValue(_history->peer);
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ public:
|
|||
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
|
||||
CopyRestrictionType listSelectRestrictionType() override;
|
||||
auto listAllowedReactionsValue()
|
||||
-> rpl::producer<std::vector<Data::Reaction>> override;
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>> override;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
|
|
@ -8,9 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_react_button.h"
|
||||
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/history_item.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/message_bubble.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
|
@ -70,7 +72,6 @@ constexpr auto kHoverScale = 1.24;
|
|||
|
||||
[[nodiscard]] std::shared_ptr<Lottie::Icon> CreateIcon(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
int startFrame,
|
||||
int size) {
|
||||
Expects(media->loaded());
|
||||
|
||||
|
@ -78,7 +79,6 @@ constexpr auto kHoverScale = 1.24;
|
|||
.path = media->owner()->filepath(true),
|
||||
.json = media->bytes(),
|
||||
.sizeOverride = QSize(size, size),
|
||||
.frame = startFrame,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -331,6 +331,7 @@ float64 Button::currentOpacity() const {
|
|||
|
||||
Manager::Manager(
|
||||
QWidget *wheelEventsTarget,
|
||||
rpl::producer<int> uniqueLimitValue,
|
||||
Fn<void(QRect)> buttonUpdate)
|
||||
: _outer(CountOuterSize())
|
||||
, _inner(QRect({}, st::reactionCornerSize))
|
||||
|
@ -338,6 +339,7 @@ Manager::Manager(
|
|||
QRect(0, 0, _inner.width(), _inner.width()).marginsAdded(
|
||||
st::reactionCornerShadow
|
||||
).size())
|
||||
, _uniqueLimit(std::move(uniqueLimitValue))
|
||||
, _buttonShowTimer([=] { showButtonDelayed(); })
|
||||
, _buttonUpdate(std::move(buttonUpdate)) {
|
||||
static_assert(!(kFramesCount % kDivider));
|
||||
|
@ -384,6 +386,11 @@ Manager::Manager(
|
|||
stealWheelEvents(wheelEventsTarget);
|
||||
}
|
||||
|
||||
_uniqueLimit.changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
applyListFilters();
|
||||
}, _lifetime);
|
||||
|
||||
_createChooseCallback = [=](QString emoji) {
|
||||
return [=] {
|
||||
if (const auto context = _buttonContext) {
|
||||
|
@ -397,6 +404,33 @@ Manager::Manager(
|
|||
};
|
||||
}
|
||||
|
||||
void Manager::applyListFilters() {
|
||||
const auto limit = _uniqueLimit.current();
|
||||
const auto applyUniqueLimit = _buttonContext
|
||||
&& (limit > 0)
|
||||
&& (_buttonAlreadyNotMineCount >= limit);
|
||||
auto icons = std::vector<not_null<ReactionIcons*>>();
|
||||
icons.reserve(_list.size());
|
||||
for (auto &icon : _list) {
|
||||
const auto add = applyUniqueLimit
|
||||
? _buttonAlreadyList.contains(icon.emoji)
|
||||
: (!_filter || _filter->contains(icon.emoji));
|
||||
if (add) {
|
||||
icons.push_back(&icon);
|
||||
} else {
|
||||
clearStateForHidden(icon);
|
||||
}
|
||||
}
|
||||
if (_icons == icons) {
|
||||
return;
|
||||
}
|
||||
const auto selected = _selectedIcon;
|
||||
setSelectedIcon(-1);
|
||||
_icons = std::move(icons);
|
||||
setSelectedIcon(selected < _icons.size() ? selected : -1);
|
||||
resolveMainReactionIcon();
|
||||
}
|
||||
|
||||
void Manager::stealWheelEvents(not_null<QWidget*> target) {
|
||||
base::install_event_filter(target, [=](not_null<QEvent*> e) {
|
||||
if (e->type() != QEvent::Wheel
|
||||
|
@ -422,8 +456,8 @@ void Manager::updateButton(ButtonParameters parameters) {
|
|||
_scheduledParameters = std::nullopt;
|
||||
}
|
||||
_buttonContext = parameters.context;
|
||||
parameters.reactionsCount = _list.size();
|
||||
if (!_buttonContext || _list.empty()) {
|
||||
parameters.reactionsCount = _icons.size();
|
||||
if (!_buttonContext || _icons.empty()) {
|
||||
return;
|
||||
} else if (_button) {
|
||||
_button->applyParameters(parameters);
|
||||
|
@ -455,7 +489,7 @@ void Manager::showButtonDelayed() {
|
|||
[=]{ updateButton({}); });
|
||||
}
|
||||
|
||||
void Manager::applyList(std::vector<Data::Reaction> list) {
|
||||
void Manager::applyList(const std::vector<Data::Reaction> &list) {
|
||||
constexpr auto predicate = [](
|
||||
const Data::Reaction &a,
|
||||
const Data::Reaction &b) {
|
||||
|
@ -463,32 +497,87 @@ void Manager::applyList(std::vector<Data::Reaction> list) {
|
|||
&& (a.appearAnimation == b.appearAnimation)
|
||||
&& (a.selectAnimation == b.selectAnimation);
|
||||
};
|
||||
if (ranges::equal(_list, list, predicate)) {
|
||||
const auto proj = [](const auto &obj) {
|
||||
return std::tie(obj.emoji, obj.appearAnimation, obj.selectAnimation);
|
||||
};
|
||||
if (ranges::equal(_list, list, ranges::equal_to(), proj, proj)) {
|
||||
return;
|
||||
}
|
||||
_list = std::move(list);
|
||||
_links = std::vector<ClickHandlerPtr>(_list.size());
|
||||
if (_list.empty()) {
|
||||
const auto selected = _selectedIcon;
|
||||
setSelectedIcon(-1);
|
||||
_icons.clear();
|
||||
_list.clear();
|
||||
for (const auto &reaction : list) {
|
||||
_list.push_back({
|
||||
.emoji = reaction.emoji,
|
||||
.appearAnimation = reaction.appearAnimation,
|
||||
.selectAnimation = reaction.selectAnimation,
|
||||
});
|
||||
}
|
||||
applyListFilters();
|
||||
setSelectedIcon(selected < _icons.size() ? selected : -1);
|
||||
}
|
||||
|
||||
void Manager::updateAllowedSublist(
|
||||
std::optional<base::flat_set<QString>> filter) {
|
||||
if (_filter == filter) {
|
||||
return;
|
||||
}
|
||||
_filter = std::move(filter);
|
||||
applyListFilters();
|
||||
}
|
||||
|
||||
void Manager::updateUniqueLimit(not_null<HistoryItem*> item) {
|
||||
if (item->fullId() != _buttonContext) {
|
||||
return;
|
||||
}
|
||||
const auto &all = item->reactions();
|
||||
const auto my = item->chosenReaction();
|
||||
auto list = base::flat_set<QString>();
|
||||
list.reserve(all.size());
|
||||
auto myIsUnique = false;
|
||||
for (const auto &[emoji, count] : all) {
|
||||
list.emplace(emoji);
|
||||
if (count == 1 && emoji == my) {
|
||||
myIsUnique = true;
|
||||
}
|
||||
}
|
||||
const auto notMineCount = int(list.size()) - (myIsUnique ? 1 : 0);
|
||||
|
||||
auto changed = false;
|
||||
if (_buttonAlreadyList != list) {
|
||||
_buttonAlreadyList = std::move(list);
|
||||
changed = true;
|
||||
}
|
||||
if (_buttonAlreadyNotMineCount != notMineCount) {
|
||||
_buttonAlreadyNotMineCount = notMineCount;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
applyListFilters();
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::resolveMainReactionIcon() {
|
||||
if (_icons.empty()) {
|
||||
_mainReactionMedia = nullptr;
|
||||
_mainReactionLifetime.destroy();
|
||||
setSelectedIcon(-1);
|
||||
_icons.clear();
|
||||
return;
|
||||
}
|
||||
const auto main = _list.front().appearAnimation;
|
||||
if (_mainReactionMedia
|
||||
&& _mainReactionMedia->owner() == main) {
|
||||
const auto main = _icons.front()->selectAnimation;
|
||||
_icons.front()->appearAnimated = true;
|
||||
if (_mainReactionMedia && _mainReactionMedia->owner() == main) {
|
||||
if (!_mainReactionLifetime) {
|
||||
loadIcons();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_mainReactionLifetime.destroy();
|
||||
_mainReactionMedia = main->createMediaView();
|
||||
_mainReactionMedia->checkStickerLarge();
|
||||
if (_mainReactionMedia->loaded()) {
|
||||
_mainReactionLifetime.destroy();
|
||||
setMainReactionIcon();
|
||||
} else {
|
||||
} else if (!_mainReactionLifetime) {
|
||||
main->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return _mainReactionMedia->loaded();
|
||||
|
@ -506,16 +595,15 @@ void Manager::setMainReactionIcon() {
|
|||
const auto i = _loadCache.find(_mainReactionMedia->owner());
|
||||
if (i != end(_loadCache) && i->second.icon) {
|
||||
const auto &icon = i->second.icon;
|
||||
if (icon->frameIndex() == icon->framesCount() - 1
|
||||
&& icon->width() == MainReactionSize()) {
|
||||
if (!icon->frameIndex() && icon->width() == MainReactionSize()) {
|
||||
_mainReactionImage = i->second.icon->frame();
|
||||
return;
|
||||
}
|
||||
}
|
||||
_mainReactionImage = CreateIcon(
|
||||
_mainReactionImage = QImage();
|
||||
_mainReactionIcon = CreateIcon(
|
||||
_mainReactionMedia.get(),
|
||||
-1,
|
||||
MainReactionSize())->frame();
|
||||
MainReactionSize());
|
||||
}
|
||||
|
||||
QMargins Manager::innerMargins() const {
|
||||
|
@ -544,7 +632,7 @@ bool Manager::checkIconLoaded(ReactionDocument &entry) const {
|
|||
const auto size = (entry.media == _mainReactionMedia)
|
||||
? MainReactionSize()
|
||||
: CornerImageSize(1.);
|
||||
entry.icon = CreateIcon(entry.media.get(), entry.startFrame, size);
|
||||
entry.icon = CreateIcon(entry.media.get(), size);
|
||||
entry.media = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
@ -556,14 +644,13 @@ void Manager::updateCurrentButton() const {
|
|||
}
|
||||
|
||||
void Manager::loadIcons() {
|
||||
const auto load = [&](not_null<DocumentData*> document, int frame) {
|
||||
const auto load = [&](not_null<DocumentData*> document) {
|
||||
if (const auto i = _loadCache.find(document); i != end(_loadCache)) {
|
||||
return i->second.icon;
|
||||
}
|
||||
auto &entry = _loadCache.emplace(document).first->second;
|
||||
entry.media = document->createMediaView();
|
||||
entry.media->checkStickerLarge();
|
||||
entry.startFrame = frame;
|
||||
if (!checkIconLoaded(entry) && !_loadCacheLifetime) {
|
||||
document->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
|
@ -572,19 +659,14 @@ void Manager::loadIcons() {
|
|||
}
|
||||
return entry.icon;
|
||||
};
|
||||
const auto selected = _selectedIcon;
|
||||
setSelectedIcon(-1);
|
||||
_icons.clear();
|
||||
auto main = true;
|
||||
for (const auto &reaction : _list) {
|
||||
_icons.push_back({
|
||||
.appear = load(reaction.appearAnimation, main ? -1 : 0),
|
||||
.select = load(reaction.selectAnimation, 0),
|
||||
.appearAnimated = main,
|
||||
});
|
||||
main = false;
|
||||
for (const auto &icon : _icons) {
|
||||
if (!icon->appear) {
|
||||
icon->appear = load(icon->appearAnimation);
|
||||
}
|
||||
if (!icon->select) {
|
||||
icon->select = load(icon->selectAnimation);
|
||||
}
|
||||
}
|
||||
setSelectedIcon(selected < _icons.size() ? selected : -1);
|
||||
}
|
||||
|
||||
void Manager::checkIcons() {
|
||||
|
@ -617,7 +699,7 @@ void Manager::paintButtons(Painter &p, const PaintContext &context) {
|
|||
}
|
||||
|
||||
ClickHandlerPtr Manager::computeButtonLink(QPoint position) const {
|
||||
if (_list.empty()) {
|
||||
if (_icons.empty()) {
|
||||
setSelectedIcon(-1);
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -631,10 +713,10 @@ ClickHandlerPtr Manager::computeButtonLink(QPoint position) const {
|
|||
const auto index = std::clamp(
|
||||
int(base::SafeRound(shifted + between / 2.)) / oneHeight,
|
||||
0,
|
||||
int(_list.size() - 1));
|
||||
auto &result = _links[index];
|
||||
int(_icons.size() - 1));
|
||||
auto &result = _icons[index]->link;
|
||||
if (!result) {
|
||||
result = resolveButtonLink(_list[index]);
|
||||
result = resolveButtonLink(*_icons[index]);
|
||||
}
|
||||
setSelectedIcon(index);
|
||||
return result;
|
||||
|
@ -645,25 +727,25 @@ void Manager::setSelectedIcon(int index) const {
|
|||
if (index < 0 || index >= _icons.size()) {
|
||||
return;
|
||||
}
|
||||
auto &icon = _icons[index];
|
||||
if (icon.selected == selected) {
|
||||
const auto &icon = _icons[index];
|
||||
if (icon->selected == selected) {
|
||||
return;
|
||||
}
|
||||
icon.selected = selected;
|
||||
icon.selectedScale.start(
|
||||
icon->selected = selected;
|
||||
icon->selectedScale.start(
|
||||
[=] { updateCurrentButton(); },
|
||||
selected ? 1. : kHoverScale,
|
||||
selected ? kHoverScale : 1.,
|
||||
kHoverScaleDuration,
|
||||
anim::sineInOut);
|
||||
if (selected) {
|
||||
const auto skipAnimation = icon.selectAnimated
|
||||
|| !icon.appearAnimated
|
||||
|| (icon.select && icon.select->animating())
|
||||
|| (icon.appear && icon.appear->animating());
|
||||
const auto select = skipAnimation ? nullptr : icon.select.get();
|
||||
if (select && !icon.selectAnimated) {
|
||||
icon.selectAnimated = true;
|
||||
const auto skipAnimation = icon->selectAnimated
|
||||
|| !icon->appearAnimated
|
||||
|| (icon->select && icon->select->animating())
|
||||
|| (icon->appear && icon->appear->animating());
|
||||
const auto select = skipAnimation ? nullptr : icon->select.get();
|
||||
if (select && !icon->selectAnimated) {
|
||||
icon->selectAnimated = true;
|
||||
select->animate(
|
||||
[=] { updateCurrentButton(); },
|
||||
0,
|
||||
|
@ -679,7 +761,7 @@ void Manager::setSelectedIcon(int index) const {
|
|||
}
|
||||
|
||||
ClickHandlerPtr Manager::resolveButtonLink(
|
||||
const Data::Reaction &reaction) const {
|
||||
const ReactionIcons &reaction) const {
|
||||
const auto emoji = reaction.emoji;
|
||||
const auto i = _reactionsLinks.find(emoji);
|
||||
if (i != end(_reactionsLinks)) {
|
||||
|
@ -1043,12 +1125,12 @@ bool Manager::onlyMainEmojiVisible() const {
|
|||
return true;
|
||||
}
|
||||
const auto &icon = _icons.front();
|
||||
if (icon.selected
|
||||
|| icon.selectedScale.animating()
|
||||
|| (icon.select && icon.select->animating())) {
|
||||
if (icon->selected
|
||||
|| icon->selectedScale.animating()
|
||||
|| (icon->select && icon->select->animating())) {
|
||||
return false;
|
||||
}
|
||||
icon.selectAnimated = false;
|
||||
icon->selectAnimated = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1060,22 +1142,20 @@ void Manager::clearAppearAnimations() {
|
|||
auto main = true;
|
||||
for (auto &icon : _icons) {
|
||||
if (!main) {
|
||||
if (icon.selected) {
|
||||
if (icon->selected) {
|
||||
setSelectedIcon(-1);
|
||||
}
|
||||
icon.selectedScale.stop();
|
||||
if (const auto select = icon.select.get()) {
|
||||
icon->selectedScale.stop();
|
||||
if (const auto select = icon->select.get()) {
|
||||
select->jumpTo(0, nullptr);
|
||||
}
|
||||
icon.selectAnimated = false;
|
||||
icon->selectAnimated = false;
|
||||
}
|
||||
if (icon.appearAnimated != main) {
|
||||
if (const auto appear = icon.appear.get()) {
|
||||
appear->jumpTo(
|
||||
main ? (appear->framesCount() - 1) : 0,
|
||||
nullptr);
|
||||
if (icon->appearAnimated != main) {
|
||||
if (const auto appear = icon->appear.get()) {
|
||||
appear->jumpTo(0, nullptr);
|
||||
}
|
||||
icon.appearAnimated = main;
|
||||
icon->appearAnimated = main;
|
||||
}
|
||||
main = false;
|
||||
}
|
||||
|
@ -1160,8 +1240,8 @@ void Manager::paintAllEmoji(
|
|||
const auto update = [=] {
|
||||
updateCurrentButton();
|
||||
};
|
||||
for (auto &icon : _icons) {
|
||||
const auto target = countTarget(icon).translated(emojiPosition);
|
||||
for (const auto &icon : _icons) {
|
||||
const auto target = countTarget(*icon).translated(emojiPosition);
|
||||
emojiPosition += shift;
|
||||
|
||||
const auto paintFrame = [&](not_null<Lottie::Icon*> animation) {
|
||||
|
@ -1172,21 +1252,25 @@ void Manager::paintAllEmoji(
|
|||
|
||||
if (!target.intersects(clip)) {
|
||||
if (current) {
|
||||
clearStateForHidden(icon);
|
||||
clearStateForHidden(*icon);
|
||||
}
|
||||
} else if (icon.select && icon.select->animating()) {
|
||||
paintFrame(icon.select.get());
|
||||
} else if (const auto appear = icon.appear.get()) {
|
||||
} else {
|
||||
const auto appear = icon->appear.get();
|
||||
if (current
|
||||
&& !icon.appearAnimated
|
||||
&& appear
|
||||
&& !icon->appearAnimated
|
||||
&& target.intersects(animationRect)) {
|
||||
icon.appearAnimated = true;
|
||||
icon->appearAnimated = true;
|
||||
appear->animate(update, 0, appear->framesCount() - 1);
|
||||
}
|
||||
paintFrame(appear);
|
||||
if (appear && appear->animating()) {
|
||||
paintFrame(appear);
|
||||
} else if (const auto select = icon->select.get()) {
|
||||
paintFrame(select);
|
||||
}
|
||||
}
|
||||
if (current) {
|
||||
clearStateForSelectFinished(icon);
|
||||
clearStateForSelectFinished(*icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1288,6 +1372,10 @@ QRect Manager::validateEmoji(int frameIndex, float64 scale) {
|
|||
const auto position = result.topLeft() / ratio;
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.fillRect(QRect(position, result.size() / ratio), Qt::transparent);
|
||||
if (_mainReactionImage.isNull()
|
||||
&& _mainReactionIcon) {
|
||||
_mainReactionImage = base::take(_mainReactionIcon)->frame();
|
||||
}
|
||||
if (!_mainReactionImage.isNull()) {
|
||||
const auto size = CornerImageSize(scale);
|
||||
const auto inner = _inner.translated(position);
|
||||
|
@ -1355,4 +1443,25 @@ QRect Manager::validateFrame(
|
|||
return result;
|
||||
}
|
||||
|
||||
void SetupManagerList(
|
||||
not_null<Manager*> manager,
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<std::optional<base::flat_set<QString>>> filter) {
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
session->data().reactions().updates()
|
||||
) | rpl::start_with_next([=] {
|
||||
manager->applyList(
|
||||
session->data().reactions().list(Data::Reactions::Type::Active));
|
||||
}, manager->lifetime());
|
||||
|
||||
std::move(
|
||||
filter
|
||||
) | rpl::start_with_next([=](
|
||||
std::optional<base::flat_set<QString>> &&list) {
|
||||
manager->updateAllowedSublist(std::move(list));
|
||||
}, manager->lifetime());
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -24,6 +24,10 @@ using PaintContext = Ui::ChatPaintContext;
|
|||
struct TextState;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Lottie {
|
||||
class Icon;
|
||||
} // namespace Lottie
|
||||
|
@ -127,10 +131,13 @@ class Manager final : public base::has_weak_ptr {
|
|||
public:
|
||||
Manager(
|
||||
QWidget *wheelEventsTarget,
|
||||
rpl::producer<int> uniqueLimitValue,
|
||||
Fn<void(QRect)> buttonUpdate);
|
||||
~Manager();
|
||||
|
||||
void applyList(std::vector<Data::Reaction> list);
|
||||
void applyList(const std::vector<Data::Reaction> &list);
|
||||
void updateAllowedSublist(std::optional<base::flat_set<QString>> filter);
|
||||
void updateUniqueLimit(not_null<HistoryItem*> item);
|
||||
|
||||
void updateButton(ButtonParameters parameters);
|
||||
void paintButtons(Painter &p, const PaintContext &context);
|
||||
|
@ -147,15 +154,22 @@ public:
|
|||
return _chosen.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
struct ReactionDocument {
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
std::shared_ptr<Lottie::Icon> icon;
|
||||
int startFrame = 0;
|
||||
};
|
||||
struct ReactionIcons {
|
||||
QString emoji;
|
||||
not_null<DocumentData*> appearAnimation;
|
||||
not_null<DocumentData*> selectAnimation;
|
||||
std::shared_ptr<Lottie::Icon> appear;
|
||||
std::shared_ptr<Lottie::Icon> select;
|
||||
mutable ClickHandlerPtr link;
|
||||
mutable Ui::Animations::Simple selectedScale;
|
||||
bool appearAnimated = false;
|
||||
mutable bool selected = false;
|
||||
|
@ -167,6 +181,7 @@ private:
|
|||
};
|
||||
static constexpr auto kFramesCount = 32;
|
||||
|
||||
void applyListFilters();
|
||||
void showButtonDelayed();
|
||||
void stealWheelEvents(not_null<QWidget*> target);
|
||||
|
||||
|
@ -208,6 +223,7 @@ private:
|
|||
const QImage &image,
|
||||
QRect source);
|
||||
|
||||
void resolveMainReactionIcon();
|
||||
void setMainReactionIcon();
|
||||
void clearAppearAnimations();
|
||||
[[nodiscard]] QRect cacheRect(int frameIndex, int columnIndex) const;
|
||||
|
@ -249,7 +265,7 @@ private:
|
|||
|
||||
[[nodiscard]] ClickHandlerPtr computeButtonLink(QPoint position) const;
|
||||
[[nodiscard]] ClickHandlerPtr resolveButtonLink(
|
||||
const Data::Reaction &reaction) const;
|
||||
const ReactionIcons &reaction) const;
|
||||
|
||||
void updateCurrentButton() const;
|
||||
[[nodiscard]] bool onlyMainEmojiVisible() const;
|
||||
|
@ -258,8 +274,8 @@ private:
|
|||
void checkIcons();
|
||||
|
||||
rpl::event_stream<Chosen> _chosen;
|
||||
std::vector<Data::Reaction> _list;
|
||||
mutable std::vector<ClickHandlerPtr> _links;
|
||||
std::vector<ReactionIcons> _list;
|
||||
std::optional<base::flat_set<QString>> _filter;
|
||||
QSize _outer;
|
||||
QRect _inner;
|
||||
QSize _overlayFull;
|
||||
|
@ -282,11 +298,13 @@ private:
|
|||
QColor _shadow;
|
||||
|
||||
std::shared_ptr<Data::DocumentMedia> _mainReactionMedia;
|
||||
std::shared_ptr<Lottie::Icon> _mainReactionIcon;
|
||||
QImage _mainReactionImage;
|
||||
rpl::lifetime _mainReactionLifetime;
|
||||
|
||||
rpl::variable<int> _uniqueLimit = 0;
|
||||
base::flat_map<not_null<DocumentData*>, ReactionDocument> _loadCache;
|
||||
std::vector<ReactionIcons> _icons;
|
||||
std::vector<not_null<ReactionIcons*>> _icons;
|
||||
rpl::lifetime _loadCacheLifetime;
|
||||
bool _showingAll = false;
|
||||
mutable int _selectedIcon = -1;
|
||||
|
@ -297,9 +315,18 @@ private:
|
|||
std::unique_ptr<Button> _button;
|
||||
std::vector<std::unique_ptr<Button>> _buttonHiding;
|
||||
FullMsgId _buttonContext;
|
||||
base::flat_set<QString> _buttonAlreadyList;
|
||||
int _buttonAlreadyNotMineCount = 0;
|
||||
mutable base::flat_map<QString, ClickHandlerPtr> _reactionsLinks;
|
||||
Fn<Fn<void()>(QString)> _createChooseCallback;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
void SetupManagerList(
|
||||
not_null<Manager*> manager,
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<std::optional<base::flat_set<QString>>> filter);
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -1927,7 +1927,7 @@ CopyRestrictionType RepliesWidget::listSelectRestrictionType() {
|
|||
}
|
||||
|
||||
auto RepliesWidget::listAllowedReactionsValue()
|
||||
-> rpl::producer<std::vector<Data::Reaction>> {
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>> {
|
||||
return Data::PeerAllowedReactionsValue(_history->peer);
|
||||
}
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ public:
|
|||
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
|
||||
CopyRestrictionType listSelectRestrictionType() override;
|
||||
auto listAllowedReactionsValue()
|
||||
-> rpl::producer<std::vector<Data::Reaction>> override;
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>> override;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
|
|
@ -1243,8 +1243,9 @@ CopyRestrictionType ScheduledWidget::listSelectRestrictionType() {
|
|||
}
|
||||
|
||||
auto ScheduledWidget::listAllowedReactionsValue()
|
||||
-> rpl::producer<std::vector<Data::Reaction>> {
|
||||
return rpl::single(std::vector<Data::Reaction>());
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>> {
|
||||
const auto empty = base::flat_set<QString>();
|
||||
return rpl::single(std::optional<base::flat_set<QString>>(empty));
|
||||
}
|
||||
|
||||
void ScheduledWidget::confirmSendNowSelected() {
|
||||
|
|
|
@ -123,7 +123,7 @@ public:
|
|||
CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
|
||||
CopyRestrictionType listSelectRestrictionType() override;
|
||||
auto listAllowedReactionsValue()
|
||||
-> rpl::producer<std::vector<Data::Reaction>> override;
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>> override;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
|
Loading…
Add table
Reference in a new issue