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