Update API scheme on layer 145.

This commit is contained in:
John Preston 2022-08-16 14:28:17 +03:00
parent ba8673af5e
commit 60cc232884
40 changed files with 646 additions and 427 deletions

View file

@ -420,7 +420,7 @@ upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes
dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config;
config#232566ac flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction = Config;
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
@ -1330,7 +1330,7 @@ messages.peerSettings#6880b94d settings:PeerSettings chats:Vector<Chat> users:Ve
auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut;
reactionCount#6fb250d1 flags:# chosen:flags.0?true reaction:string count:int = ReactionCount;
reactionCount#2454adf0 flags:# chosen:flags.0?true reaction:Reaction count:int = ReactionCount;
messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector<ReactionCount> recent_reactions:flags.1?Vector<MessagePeerReaction> = MessageReactions;
@ -1344,7 +1344,7 @@ messages.availableReactions#768e3aad hash:int reactions:Vector<AvailableReaction
messages.translateNoResult#67ca4737 = messages.TranslatedText;
messages.translateResultText#a214f7d0 text:string = messages.TranslatedText;
messagePeerReaction#51b67eff flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:string = MessagePeerReaction;
messagePeerReaction#b156fe9c flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:Reaction = MessagePeerReaction;
groupCallStreamChannel#80eb48af channel:int scale:int last_timestamp_ms:long = GroupCallStreamChannel;
@ -1412,6 +1412,10 @@ emojiStatus#929b619d document_id:long = EmojiStatus;
account.emojiStatusesNotModified#d08ce645 = account.EmojiStatuses;
account.emojiStatuses#90c467d1 hash:long statuses:Vector<EmojiStatus> = account.EmojiStatuses;
reactionEmpty#79f5d419 = Reaction;
reactionEmoji#1b2286b8 emoticon:string = Reaction;
reactionCustomEmoji#8935fc73 document_id:long = Reaction;
emailVerifyPurposeLoginSetup#4345be73 phone_number:string phone_code_hash:string = EmailVerifyPurpose;
emailVerifyPurposeLoginChange#527d22eb = EmailVerifyPurpose;
emailVerifyPurposePassport#bbf51685 = EmailVerifyPurpose;
@ -1708,12 +1712,12 @@ messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPe
messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates;
messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates;
messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
messages.sendReaction#25690ce4 flags:# big:flags.1?true peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
messages.sendReaction#5afe99b5 flags:# big:flags.1?true peer:InputPeer msg_id:int reaction:flags.0?Reaction = Updates;
messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector<int> = Updates;
messages.getMessageReactionsList#e0ee6b77 flags:# peer:InputPeer id:int reaction:flags.0?string offset:flags.1?string limit:int = messages.MessageReactionsList;
messages.getMessageReactionsList#461b3f48 flags:# peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = messages.MessageReactionsList;
messages.setChatAvailableReactions#14050ea6 peer:InputPeer available_reactions:Vector<string> = Updates;
messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
messages.setDefaultReaction#d960c4d4 reaction:string = Bool;
messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool;
messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText;
messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_session.h"
#include "data/data_media_types.h"
#include "data/data_message_reactions.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "main/main_account.h"
@ -31,6 +32,8 @@ namespace {
constexpr auto kContextReactionsLimit = 50;
using Data::ReactionId;
struct Peers {
std::vector<PeerId> list;
bool unknown = false;
@ -41,7 +44,7 @@ inline bool operator==(const Peers &a, const Peers &b) noexcept {
struct PeerWithReaction {
PeerId peer = 0;
QString reaction;
ReactionId reaction;
};
inline bool operator==(
const PeerWithReaction &a,
@ -84,7 +87,7 @@ struct Context {
base::flat_map<not_null<HistoryItem*>, CachedRead> cachedRead;
base::flat_map<
not_null<HistoryItem*>,
base::flat_map<QString, CachedReacted>> cachedReacted;
base::flat_map<ReactionId, CachedReacted>> cachedReacted;
base::flat_map<not_null<Main::Session*>, rpl::lifetime> subscriptions;
[[nodiscard]] CachedRead &cacheRead(not_null<HistoryItem*> item) {
@ -97,7 +100,7 @@ struct Context {
[[nodiscard]] CachedReacted &cacheReacted(
not_null<HistoryItem*> item,
const QString &reaction) {
const ReactionId &reaction) {
auto &map = cachedReacted[item];
const auto i = map.find(reaction);
if (i != end(map)) {
@ -249,7 +252,7 @@ struct State {
Peers &&peers) {
auto result = PeersWithReactions{
.list = peers.list | ranges::views::transform([](PeerId peer) {
return PeerWithReaction{.peer = peer };
return PeerWithReaction{ .peer = peer };
}) | ranges::to_vector,
.unknown = peers.unknown,
};
@ -259,7 +262,7 @@ struct State {
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
not_null<HistoryItem*> item,
const QString &reaction,
const ReactionId &reaction,
not_null<QWidget*> context) {
auto weak = QPointer<QWidget>(context.get());
const auto session = &item->history()->session();
@ -273,12 +276,12 @@ struct State {
using Flag = MTPmessages_GetMessageReactionsList::Flag;
entry.requestId = session->api().request(
MTPmessages_GetMessageReactionsList(
MTP_flags(reaction.isEmpty()
MTP_flags(reaction.empty()
? Flag(0)
: Flag::f_reaction),
item->history()->peer->input,
MTP_int(item->id),
MTP_string(reaction),
ReactionToMTP(reaction),
MTPstring(), // offset
MTP_int(kContextReactionsLimit)
)
@ -299,7 +302,8 @@ struct State {
vote.match([&](const auto &data) {
parsed.list.push_back(PeerWithReaction{
.peer = peerFromMTP(data.vpeer_id()),
.reaction = qs(data.vreaction()),
.reaction = Data::ReactionFromMTP(
data.vreaction()),
});
});
}
@ -322,7 +326,7 @@ struct State {
not_null<QWidget*> context)
-> rpl::producer<PeersWithReactions> {
return rpl::combine(
WhoReactedIds(item, QString(), context),
WhoReactedIds(item, {}, context),
WhoReadIds(item, context)
) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
if (reacted.unknown || read.unknown) {
@ -347,7 +351,7 @@ bool UpdateUserpics(
struct ResolvedPeer {
PeerData *peer = nullptr;
QString reaction;
ReactionId reaction;
};
const auto peers = ranges::views::all(
ids
@ -383,7 +387,7 @@ bool UpdateUserpics(
}
now.push_back(Userpic{
.peer = peer,
.reaction = resolved.reaction,
.reaction = resolved.reaction.emoji(), // #TODO reactions
});
auto &userpic = now.back();
userpic.uniqueKey = peer->userpicUniqueKey(userpic.view);
@ -446,7 +450,7 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
const ReactionId &reaction,
not_null<QWidget*> context,
const style::WhoRead &st,
std::shared_ptr<WhoReadList> whoReadIds) {
@ -455,7 +459,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto resolveWhoRead = reaction.isEmpty()
const auto resolveWhoRead = reaction.empty()
&& WhoReadExists(item);
const auto state = lifetime.make_state<State>();
@ -463,7 +467,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
consumer.put_next_copy(state->current);
};
const auto resolveWhoReacted = !reaction.isEmpty()
const auto resolveWhoReacted = !reaction.empty()
|| item->canViewReactions();
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
? WhoReadOrReactedIds(item, context)
@ -475,7 +479,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
: Ui::WhoReadType::Reacted;
if (resolveWhoReacted) {
const auto &list = item->reactions();
state->current.fullReactionsCount = reaction.isEmpty()
state->current.fullReactionsCount = reaction.empty()
? ranges::accumulate(
list,
0,
@ -486,11 +490,11 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
: 0;
// #TODO reactions
state->current.singleReaction = !reaction.isEmpty()
state->current.singleReaction = (!reaction.empty()
? reaction
: (list.size() == 1)
? list.front().first
: QString();
: ReactionId()).emoji();
}
std::move(
idsWithReactions
@ -510,7 +514,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
if (whoReadIds) {
const auto reacted = peers.list.size() - ranges::count(
peers.list,
QString(),
ReactionId(),
&PeerWithReaction::reaction);
whoReadIds->list = (peers.read.size() > reacted)
? std::move(peers.read)
@ -592,12 +596,12 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<QWidget*> context,
const style::WhoRead &st,
std::shared_ptr<WhoReadList> whoReadIds) {
return WhoReacted(item, QString(), context, st, std::move(whoReadIds));
return WhoReacted(item, {}, context, st, std::move(whoReadIds));
}
rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
const Data::ReactionId &reaction,
not_null<QWidget*> context,
const style::WhoRead &st) {
return WhoReacted(item, reaction, context, st, nullptr);

View file

@ -18,6 +18,10 @@ struct WhoReadContent;
enum class WhoReadType;
} // namespace Ui
namespace Data {
struct ReactionId;
} // namespace Data
namespace Api {
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
@ -36,7 +40,7 @@ struct WhoReadList {
std::shared_ptr<WhoReadList> whoReadIds = nullptr);
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
const Data::ReactionId &reaction,
not_null<QWidget*> context, // Cache results for this lifetime.
const style::WhoRead &st);

View file

@ -91,9 +91,12 @@ void EditAllowedReactionsBox(
tr::lng_manage_peer_reactions_available());
const auto active = [&](const Data::Reaction &entry) {
return selected.contains(entry.emoji);
return selected.contains(entry.id.emoji());
};
const auto add = [&](const Data::Reaction &entry) {
if (entry.id.emoji().isEmpty()) {
return;
}
const auto button = Settings::AddButton(
container,
rpl::single(entry.title),
@ -114,7 +117,7 @@ void EditAllowedReactionsBox(
}) | rpl::to_empty,
rpl::never<>(),
&button->lifetime());
state->toggles.emplace(entry.emoji, button);
state->toggles.emplace(entry.id.emoji(), button);
button->toggleOn(rpl::single(
active(entry)
) | rpl::then(

View file

@ -58,10 +58,12 @@ constexpr auto kStarOpacityOff = 0.1;
constexpr auto kStarOpacityOn = 1.;
constexpr auto kStarPeriod = 3 * crl::time(1000);
using Data::ReactionId;
struct Descriptor {
PremiumPreview section = PremiumPreview::Stickers;
DocumentData *requestedSticker = nullptr;
base::flat_map<QString, ReactionDisableType> disabled;
base::flat_map<ReactionId, ReactionDisableType> disabled;
bool fromSettings = false;
Fn<void()> hiddenCallback;
Fn<void(not_null<Ui::BoxContent*>)> shownCallback;
@ -968,7 +970,7 @@ void ReactionPreview::paintEffect(QPainter &p) {
[[nodiscard]] not_null<Ui::RpWidget*> ReactionsPreview(
not_null<Ui::RpWidget*> parent,
not_null<Window::SessionController*> controller,
const base::flat_map<QString, ReactionDisableType> &disabled,
const base::flat_map<ReactionId, ReactionDisableType> &disabled,
Fn<void()> readyCallback) {
struct State {
std::vector<std::unique_ptr<ReactionPreview>> entries;
@ -1015,7 +1017,7 @@ void ReactionPreview::paintEffect(QPainter &p) {
if (!reaction.centerIcon || !reaction.aroundAnimation) {
continue;
}
const auto i = disabled.find(reaction.emoji);
const auto i = disabled.find(reaction.id);
const auto disable = (i != end(disabled))
? i->second
: ReactionDisableType::None;
@ -1623,7 +1625,14 @@ void ShowStickerPreviewBox(
void ShowPremiumPreviewBox(
not_null<Window::SessionController*> controller,
PremiumPreview section,
const base::flat_map<QString, ReactionDisableType> &disabled,
Fn<void(not_null<Ui::BoxContent*>)> shown) {
ShowPremiumPreviewBox(controller, section, {}, std::move(shown));
}
void ShowPremiumPreviewBox(
not_null<Window::SessionController*> controller,
PremiumPreview section,
const base::flat_map<ReactionId, ReactionDisableType> &disabled,
Fn<void(not_null<Ui::BoxContent*>)> shown) {
Show(controller, Descriptor{
.section = section,

View file

@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class DocumentData;
namespace Data {
struct ReactionId;
} // namespace Data
namespace Ui {
class BoxContent;
class GenericBox;
@ -56,7 +60,12 @@ enum class ReactionDisableType {
void ShowPremiumPreviewBox(
not_null<Window::SessionController*> controller,
PremiumPreview section,
const base::flat_map<QString, ReactionDisableType> &disabled = {},
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr);
void ShowPremiumPreviewBox(
not_null<Window::SessionController*> controller,
PremiumPreview section,
const base::flat_map<Data::ReactionId, ReactionDisableType> &disabled,
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr);
void ShowPremiumPreviewToBuy(

View file

@ -222,9 +222,10 @@ void AddMessage(
emojiValue = std::move(emojiValue),
iconSize = st::settingsReactionMessageSize
](const QString &emoji) {
const auto id = Data::ReactionId{ emoji };
const auto &reactions = controller->session().data().reactions();
for (const auto &r : reactions.list(Data::Reactions::Type::Active)) {
if (emoji != r.emoji) {
if (r.id != id) {
continue;
}
const auto index = state->icons.flag ? 1 : 0;
@ -392,7 +393,9 @@ void ReactionsSettingsBox(
const auto &reactions = controller->session().data().reactions();
const auto state = box->lifetime().make_state<State>();
state->selectedEmoji = reactions.favorite();
state->selectedEmoji = v::is<QString>(reactions.favorite().data)
? v::get<QString>(reactions.favorite().data)
: QString();
const auto pinnedToTop = box->setPinnedToTopContent(
object_ptr<Ui::VerticalLayout>(box));
@ -451,7 +454,7 @@ void ReactionsSettingsBox(
rpl::never<>(),
&button->lifetime());
button->setClickedCallback([=, emoji = r.emoji] {
button->setClickedCallback([=, id = r.id] {
if (premium && !controller->session().premium()) {
ShowPremiumPreviewBox(
controller,
@ -459,9 +462,11 @@ void ReactionsSettingsBox(
return;
}
checkButton(button);
state->selectedEmoji = emoji;
state->selectedEmoji = v::is<QString>(id.data)
? v::get<QString>(id.data)
: QString();
});
if (r.emoji == state->selectedEmoji.current()) {
if (r.id == Data::ReactionId{ state->selectedEmoji.current() }) {
firstCheckedButton = button;
}
}
@ -479,9 +484,9 @@ void ReactionsSettingsBox(
box->setWidth(st::boxWideWidth);
box->addButton(tr::lng_settings_save(), [=] {
const auto &data = controller->session().data();
const auto selectedEmoji = state->selectedEmoji.current();
if (data.reactions().favorite() != selectedEmoji) {
data.reactions().setFavorite(selectedEmoji);
const auto selected = state->selectedEmoji.current();
if (data.reactions().favorite() != Data::ReactionId{ selected }) {
data.reactions().setFavorite(Data::ReactionId{ selected });
}
box->closeBox();
});

View file

@ -862,10 +862,7 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) {
void TabbedSelector::showPromoForPremiumEmoji() {
premiumEmojiChosen(
) | rpl::start_with_next([=] {
ShowPremiumPreviewBox(
_controller,
PremiumPreview::AnimatedEmoji,
{});
ShowPremiumPreviewBox(_controller, PremiumPreview::AnimatedEmoji);
}, lifetime());
}

View file

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_icon.h"
#include "storage/localimageloader.h"
#include "ui/image/image_location_factory.h"
#include "mtproto/mtproto_config.h"
#include "base/timer_rpl.h"
#include "apiwrap.h"
#include "styles/style_chat.h"
@ -32,8 +33,35 @@ constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000);
constexpr auto kPollEach = 20 * crl::time(1000);
constexpr auto kSizeForDownscale = 64;
[[nodiscard]] QString ReactionIdToLog(const ReactionId &id) {
if (const auto custom = id.custom()) {
return "custom:" + QString::number(custom);
}
return id.emoji();
}
} // namespace
ReactionId ReactionFromMTP(const MTPReaction &reaction) {
return reaction.match([](MTPDreactionEmpty) {
return ReactionId{ QString() };
}, [](const MTPDreactionEmoji &data) {
return ReactionId{ qs(data.vemoticon()) };
}, [](const MTPDreactionCustomEmoji &data) {
return ReactionId{ DocumentId(data.vdocument_id().v) };
});
}
MTPReaction ReactionToMTP(ReactionId id) {
if (const auto custom = id.custom()) {
return MTP_reactionCustomEmoji(MTP_long(custom));
}
const auto emoji = id.emoji();
return emoji.isEmpty()
? MTP_reactionEmpty()
: MTP_reactionEmoji(MTP_string(emoji));
}
Reactions::Reactions(not_null<Session*> owner)
: _owner(owner)
, _repaintTimer([=] { repaintCollected(); }) {
@ -54,16 +82,18 @@ Reactions::Reactions(not_null<Session*> owner)
_repaintItems.remove(item);
}, _lifetime);
const auto appConfig = &_owner->session().account().appConfig();
appConfig->value(
) | rpl::start_with_next([=] {
const auto favorite = appConfig->get<QString>(
u"reactions_default"_q,
QString::fromUtf8("\xf0\x9f\x91\x8d"));
if (_favorite != favorite && !_saveFaveRequestId) {
_favorite = favorite;
_updated.fire({});
}
rpl::single(rpl::empty) | rpl::then(
_owner->session().mtp().config().updates()
) | rpl::map([=] {
const auto &config = _owner->session().mtp().configValues();
return config.reactionDefaultCustom
? ReactionId{ DocumentId(config.reactionDefaultCustom) }
: ReactionId{ config.reactionDefaultEmoji };
}) | rpl::filter([=](const ReactionId &id) {
return (_favorite != id) && !_saveFaveRequestId;
}) | rpl::start_with_next([=](ReactionId &&id) {
_favorite = std::move(id);
_updated.fire({});
}, _lifetime);
}
@ -81,17 +111,17 @@ const std::vector<Reaction> &Reactions::list(Type type) const {
Unexpected("Type in Reactions::list.");
}
QString Reactions::favorite() const {
ReactionId Reactions::favorite() const {
return _favorite;
}
void Reactions::setFavorite(const QString &emoji) {
void Reactions::setFavorite(const ReactionId &emoji) {
const auto api = &_owner->session().api();
if (_saveFaveRequestId) {
api->request(_saveFaveRequestId).cancel();
}
_saveFaveRequestId = api->request(MTPmessages_SetDefaultReaction(
MTP_string(emoji)
ReactionToMTP(emoji)
)).done([=] {
_saveFaveRequestId = 0;
}).fail([=] {
@ -108,12 +138,12 @@ rpl::producer<> Reactions::updates() const {
return _updated.events();
}
void Reactions::preloadImageFor(const QString &emoji) {
if (_images.contains(emoji)) {
void Reactions::preloadImageFor(const ReactionId &id) {
if (_images.contains(id)) {
return;
}
auto &set = _images.emplace(emoji).first->second;
const auto i = ranges::find(_available, emoji, &Reaction::emoji);
auto &set = _images.emplace(id).first->second;
const auto i = ranges::find(_available, id, &Reaction::id);
const auto document = (i == end(_available))
? nullptr
: i->centerIcon
@ -127,8 +157,8 @@ void Reactions::preloadImageFor(const QString &emoji) {
}
}
void Reactions::preloadAnimationsFor(const QString &emoji) {
const auto i = ranges::find(_available, emoji, &Reaction::emoji);
void Reactions::preloadAnimationsFor(const ReactionId &id) {
const auto i = ranges::find(_available, id, &Reaction::id);
if (i == end(_available)) {
return;
}
@ -146,7 +176,7 @@ void Reactions::preloadAnimationsFor(const QString &emoji) {
}
QImage Reactions::resolveImageFor(
const QString &emoji,
const ReactionId &emoji,
ImageSize size) {
const auto i = _images.find(emoji);
if (i == end(_images)) {
@ -194,11 +224,11 @@ QImage Reactions::resolveImageFor(
}
void Reactions::resolveImages() {
for (auto &[emoji, set] : _images) {
for (auto &[id, set] : _images) {
if (!set.bottomInfo.isNull() || set.icon || set.media) {
continue;
}
const auto i = ranges::find(_available, emoji, &Reaction::emoji);
const auto i = ranges::find(_available, id, &Reaction::id);
const auto document = (i == end(_available))
? nullptr
: i->centerIcon
@ -207,8 +237,8 @@ void Reactions::resolveImages() {
if (document) {
loadImage(set, document, !i->centerIcon);
} else {
LOG(("API Error: Reaction for emoji '%1' not found!"
).arg(emoji));
LOG(("API Error: Reaction '%1' not found!"
).arg(ReactionIdToLog(id)));
}
}
}
@ -338,7 +368,7 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
data.vselect_animation());
return known
? std::make_optional(Reaction{
.emoji = emoji,
.id = ReactionId{ emoji },
.title = qs(data.vtitle()),
.staticIcon = _owner->processDocument(data.vstatic_icon()),
.appearAnimation = _owner->processDocument(
@ -362,7 +392,7 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
});
}
void Reactions::send(not_null<HistoryItem*> item, const QString &chosen) {
void Reactions::send(not_null<HistoryItem*> item, const ReactionId &chosen) {
const auto id = item->fullId();
auto &api = _owner->session().api();
auto i = _sentRequests.find(id);
@ -371,14 +401,14 @@ void Reactions::send(not_null<HistoryItem*> item, const QString &chosen) {
} else {
i = _sentRequests.emplace(id).first;
}
const auto flags = chosen.isEmpty()
const auto flags = chosen.empty()
? MTPmessages_SendReaction::Flag(0)
: MTPmessages_SendReaction::Flag::f_reaction;
i->second = api.request(MTPmessages_SendReaction(
MTP_flags(flags),
item->history()->peer->input,
MTP_int(id.msg),
MTP_string(chosen)
ReactionToMTP(chosen)
)).done([=](const MTPUpdates &result) {
_sentRequests.remove(id);
_owner->session().api().applyUpdates(result);
@ -509,13 +539,13 @@ MessageReactions::MessageReactions(not_null<HistoryItem*> item)
: _item(item) {
}
void MessageReactions::add(const QString &reaction) {
void MessageReactions::add(const ReactionId &reaction) {
if (_chosen == reaction) {
return;
}
const auto history = _item->history();
const auto self = history->session().user();
if (!_chosen.isEmpty()) {
if (!_chosen.empty()) {
const auto i = _list.find(_chosen);
Assert(i != end(_list));
--i->second;
@ -534,7 +564,7 @@ void MessageReactions::add(const QString &reaction) {
}
}
_chosen = reaction;
if (!reaction.isEmpty()) {
if (!reaction.empty()) {
if (_item->canViewReactions()) {
auto &list = _recent[reaction];
list.insert(begin(list), RecentReaction{ self });
@ -547,7 +577,7 @@ void MessageReactions::add(const QString &reaction) {
}
void MessageReactions::remove() {
add(QString());
add(ReactionId());
}
bool MessageReactions::checkIfChanged(
@ -558,10 +588,10 @@ bool MessageReactions::checkIfChanged(
// We'll apply non-stale data from the request response.
return false;
}
auto existing = base::flat_set<QString>();
auto existing = base::flat_set<ReactionId>();
for (const auto &count : list) {
const auto changed = count.match([&](const MTPDreactionCount &data) {
const auto reaction = qs(data.vreaction());
const auto reaction = ReactionFromMTP(data.vreaction());
const auto nowCount = data.vcount().v;
const auto i = _list.find(reaction);
const auto wasCount = (i != end(_list)) ? i->second : 0;
@ -580,10 +610,10 @@ bool MessageReactions::checkIfChanged(
return true;
}
}
auto parsed = base::flat_map<QString, std::vector<RecentReaction>>();
auto parsed = base::flat_map<ReactionId, std::vector<RecentReaction>>();
for (const auto &reaction : recent) {
reaction.match([&](const MTPDmessagePeerReaction &data) {
const auto emoji = qs(data.vreaction());
const auto emoji = ReactionFromMTP(data.vreaction());
if (_list.contains(emoji)) {
parsed[emoji].push_back(RecentReaction{
.peer = owner.peer(peerFromMTP(data.vpeer_id())),
@ -614,16 +644,16 @@ bool MessageReactions::change(
return false;
}
auto changed = false;
auto existing = base::flat_set<QString>();
auto existing = base::flat_set<ReactionId>();
for (const auto &count : list) {
count.match([&](const MTPDreactionCount &data) {
const auto reaction = qs(data.vreaction());
const auto reaction = ReactionFromMTP(data.vreaction());
if (!ignoreChosen) {
if (data.is_chosen() && _chosen != reaction) {
_chosen = reaction;
changed = true;
} else if (!data.is_chosen() && _chosen == reaction) {
_chosen = QString();
_chosen = ReactionId();
changed = true;
}
}
@ -645,14 +675,14 @@ bool MessageReactions::change(
++i;
}
}
if (!_chosen.isEmpty() && !_list.contains(_chosen)) {
_chosen = QString();
if (!_chosen.empty() && !_list.contains(_chosen)) {
_chosen = ReactionId();
}
}
auto parsed = base::flat_map<QString, std::vector<RecentReaction>>();
auto parsed = base::flat_map<ReactionId, std::vector<RecentReaction>>();
for (const auto &reaction : recent) {
reaction.match([&](const MTPDmessagePeerReaction &data) {
const auto emoji = qs(data.vreaction());
const auto emoji = ReactionFromMTP(data.vreaction());
if (_list.contains(emoji)) {
parsed[emoji].push_back(RecentReaction{
.peer = owner.peer(peerFromMTP(data.vpeer_id())),
@ -669,12 +699,12 @@ bool MessageReactions::change(
return changed;
}
const base::flat_map<QString, int> &MessageReactions::list() const {
const base::flat_map<ReactionId, int> &MessageReactions::list() const {
return _list;
}
auto MessageReactions::recent() const
-> const base::flat_map<QString, std::vector<RecentReaction>> & {
-> const base::flat_map<ReactionId, std::vector<RecentReaction>> & {
return _recent;
}
@ -699,7 +729,7 @@ void MessageReactions::markRead() {
}
}
QString MessageReactions::chosen() const {
ReactionId MessageReactions::chosen() const {
return _chosen;
}

View file

@ -18,8 +18,36 @@ namespace Data {
class DocumentMedia;
class Session;
struct ReactionId {
std::variant<QString, DocumentId> data;
[[nodiscard]] bool empty() const {
const auto emoji = std::get_if<QString>(&data);
return emoji && emoji->isEmpty();
}
[[nodiscard]] QString emoji() const {
const auto emoji = std::get_if<QString>(&data);
return emoji ? *emoji : QString();
}
[[nodiscard]] DocumentId custom() const {
const auto custom = std::get_if<DocumentId>(&data);
return custom ? *custom : DocumentId();
}
};
Q_DECLARE_METATYPE(ReactionId);
inline bool operator<(const ReactionId &a, const ReactionId &b) {
return a.data < b.data;
}
inline bool operator==(const ReactionId &a, const ReactionId &b) {
return a.data == b.data;
}
[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction);
[[nodiscard]] MTPReaction ReactionToMTP(ReactionId id);
struct Reaction {
QString emoji;
ReactionId id;
QString title;
not_null<DocumentData*> staticIcon;
not_null<DocumentData*> appearAnimation;
@ -44,8 +72,8 @@ public:
All,
};
[[nodiscard]] const std::vector<Reaction> &list(Type type) const;
[[nodiscard]] QString favorite() const;
void setFavorite(const QString &emoji);
[[nodiscard]] ReactionId favorite() const;
void setFavorite(const ReactionId &emoji);
[[nodiscard]] static base::flat_set<QString> ParseAllowed(
const MTPVector<MTPstring> *list);
@ -56,13 +84,13 @@ public:
BottomInfo,
InlineList,
};
void preloadImageFor(const QString &emoji);
void preloadAnimationsFor(const QString &emoji);
void preloadImageFor(const ReactionId &emoji);
void preloadAnimationsFor(const ReactionId &emoji);
[[nodiscard]] QImage resolveImageFor(
const QString &emoji,
const ReactionId &emoji,
ImageSize size);
void send(not_null<HistoryItem*> item, const QString &chosen);
void send(not_null<HistoryItem*> item, const ReactionId &chosen);
[[nodiscard]] bool sending(not_null<HistoryItem*> item) const;
void poll(not_null<HistoryItem*> item, crl::time now);
@ -104,7 +132,7 @@ private:
std::vector<Reaction> _active;
std::vector<Reaction> _available;
QString _favorite;
ReactionId _favorite;
base::flat_map<
not_null<DocumentData*>,
std::shared_ptr<Data::DocumentMedia>> _iconsCache;
@ -113,7 +141,7 @@ private:
mtpRequestId _requestId = 0;
int32 _hash = 0;
base::flat_map<QString, ImageSet> _images;
base::flat_map<ReactionId, ImageSet> _images;
rpl::lifetime _imagesLoadLifetime;
bool _waitingForList = false;
@ -149,7 +177,7 @@ class MessageReactions final {
public:
explicit MessageReactions(not_null<HistoryItem*> item);
void add(const QString &reaction);
void add(const ReactionId &reaction);
void remove();
bool change(
const QVector<MTPReactionCount> &list,
@ -158,10 +186,10 @@ public:
[[nodiscard]] bool checkIfChanged(
const QVector<MTPReactionCount> &list,
const QVector<MTPMessagePeerReaction> &recent) const;
[[nodiscard]] const base::flat_map<QString, int> &list() const;
[[nodiscard]] const base::flat_map<ReactionId, int> &list() const;
[[nodiscard]] auto recent() const
-> const base::flat_map<QString, std::vector<RecentReaction>> &;
[[nodiscard]] QString chosen() const;
-> const base::flat_map<ReactionId, std::vector<RecentReaction>> &;
[[nodiscard]] ReactionId chosen() const;
[[nodiscard]] bool empty() const;
[[nodiscard]] bool hasUnread() const;
@ -170,9 +198,9 @@ public:
private:
const not_null<HistoryItem*> _item;
QString _chosen;
base::flat_map<QString, int> _list;
base::flat_map<QString, std::vector<RecentReaction>> _recent;
ReactionId _chosen;
base::flat_map<ReactionId, int> _list;
base::flat_map<ReactionId, std::vector<RecentReaction>> _recent;
};

View file

@ -401,16 +401,16 @@ HistoryInner::HistoryInner(
|| Window::ShowReactPremiumError(
_controller,
item,
reaction.emoji)) {
reaction.id)) {
return;
}
item->toggleReaction(reaction.emoji);
if (item->chosenReaction() != reaction.emoji) {
item->toggleReaction(reaction.id);
if (item->chosenReaction() != reaction.id) {
return;
} else if (const auto view = item->mainView()) {
if (const auto top = itemTop(view); top >= 0) {
view->animateReaction({
.emoji = reaction.emoji,
.id = reaction.id,
.flyIcon = reaction.icon,
.flyFrom = reaction.geometry.translated(0, -top),
});
@ -1926,7 +1926,9 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) {
void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const {
const auto favorite = session().data().reactions().favorite();
const auto allowed = _reactionsManager->allowedSublist();
if (allowed && !allowed->contains(favorite)) {
if (allowed
&& (favorite.emoji().isEmpty()
|| !allowed->contains(favorite.emoji()))) {
return;
}
const auto item = view->data();
@ -1934,7 +1936,7 @@ void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const {
return;
} else if (item->chosenReaction() != favorite) {
if (const auto top = itemTop(view); top >= 0) {
view->animateReaction({ .emoji = favorite });
view->animateReaction({ .id = favorite });
}
}
item->toggleReaction(favorite);
@ -1951,7 +1953,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto link = ClickHandler::getActive();
if (link
&& !link->property(kSendReactionEmojiProperty).toString().isEmpty()
&& !link->property(
kSendReactionEmojiProperty).value<Data::ReactionId>().empty()
&& _reactionsManager->showContextMenu(
this,
e,
@ -1995,17 +1998,18 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto hasWhoReactedItem = _dragStateItem
&& Api::WhoReactedExists(_dragStateItem);
const auto clickedEmoji = link
? link->property(kReactionsCountEmojiProperty).toString()
: QString();
const auto clickedReaction = link
? link->property(
kReactionsCountEmojiProperty).value<Data::ReactionId>()
: Data::ReactionId();
_whoReactedMenuLifetime.destroy();
if (hasWhoReactedItem && !clickedEmoji.isEmpty()) {
if (hasWhoReactedItem && !clickedReaction.empty()) {
HistoryView::ShowWhoReactedMenu(
&_menu,
e->globalPos(),
this,
_dragStateItem,
clickedEmoji,
clickedReaction,
_controller,
_whoReactedMenuLifetime);
e->accept();

View file

@ -883,7 +883,7 @@ bool HistoryItem::canReact() const {
return true;
}
void HistoryItem::addReaction(const QString &reaction) {
void HistoryItem::addReaction(const Data::ReactionId &reaction) {
if (!_reactions) {
_reactions = std::make_unique<Data::MessageReactions>(this);
}
@ -891,7 +891,7 @@ void HistoryItem::addReaction(const QString &reaction) {
history()->owner().notifyItemDataChange(this);
}
void HistoryItem::toggleReaction(const QString &reaction) {
void HistoryItem::toggleReaction(const Data::ReactionId &reaction) {
if (!_reactions) {
_reactions = std::make_unique<Data::MessageReactions>(this);
const auto canViewReactions = !isDiscussionPost()
@ -977,15 +977,17 @@ void HistoryItem::updateReactionsUnknown() {
_reactionsLastRefreshed = 1;
}
const base::flat_map<QString, int> &HistoryItem::reactions() const {
static const auto kEmpty = base::flat_map<QString, int>();
const base::flat_map<Data::ReactionId, int> &HistoryItem::reactions() const {
static const auto kEmpty = base::flat_map<Data::ReactionId, int>();
return _reactions ? _reactions->list() : kEmpty;
}
auto HistoryItem::recentReactions() const
-> const base::flat_map<QString, std::vector<Data::RecentReaction>> & {
-> const base::flat_map<
Data::ReactionId,
std::vector<Data::RecentReaction>> & {
static const auto kEmpty = base::flat_map<
QString,
Data::ReactionId,
std::vector<Data::RecentReaction>>();
return _reactions ? _reactions->recent() : kEmpty;
}
@ -996,25 +998,26 @@ bool HistoryItem::canViewReactions() const {
&& !_reactions->list().empty();
}
QString HistoryItem::chosenReaction() const {
return _reactions ? _reactions->chosen() : QString();
Data::ReactionId HistoryItem::chosenReaction() const {
return _reactions ? _reactions->chosen() : Data::ReactionId();
}
QString HistoryItem::lookupUnreadReaction(not_null<UserData*> from) const {
Data::ReactionId HistoryItem::lookupUnreadReaction(
not_null<UserData*> from) const {
if (!_reactions) {
return QString();
return {};
}
const auto recent = _reactions->recent();
for (const auto &[emoji, list] : _reactions->recent()) {
for (const auto &[id, list] : _reactions->recent()) {
const auto i = ranges::find(
list,
from,
&Data::RecentReaction::peer);
if (i != end(list) && i->unread) {
return emoji;
return id;
}
}
return QString();
return {};
}
crl::time HistoryItem::lastReactionsRefreshTime() const {

View file

@ -42,6 +42,7 @@ struct RippleAnimation;
namespace Data {
struct MessagePosition;
struct RecentReaction;
struct ReactionId;
class Media;
class MessageReactions;
} // namespace Data
@ -372,18 +373,19 @@ public:
[[nodiscard]] bool suggestDeleteAllReport() const;
[[nodiscard]] bool canReact() const;
void addReaction(const QString &reaction);
void toggleReaction(const QString &reaction);
void addReaction(const Data::ReactionId &reaction);
void toggleReaction(const Data::ReactionId &reaction);
void updateReactions(const MTPMessageReactions *reactions);
void updateReactionsUnknown();
[[nodiscard]] const base::flat_map<QString, int> &reactions() const;
[[nodiscard]] auto reactions() const
-> const base::flat_map<Data::ReactionId, int> &;
[[nodiscard]] auto recentReactions() const
-> const base::flat_map<
QString,
std::vector<Data::RecentReaction>> &;
-> const base::flat_map<
Data::ReactionId,
std::vector<Data::RecentReaction>> &;
[[nodiscard]] bool canViewReactions() const;
[[nodiscard]] QString chosenReaction() const;
[[nodiscard]] QString lookupUnreadReaction(
[[nodiscard]] Data::ReactionId chosenReaction() const;
[[nodiscard]] Data::ReactionId lookupUnreadReaction(
not_null<UserData*> from) const;
[[nodiscard]] crl::time lastReactionsRefreshTime() const;

View file

@ -29,6 +29,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView {
ReactionAnimationArgs ReactionAnimationArgs::translated(
QPoint point) const {
return {
.id = id,
.flyIcon = flyIcon,
.flyFrom = flyFrom.translated(point),
};
}
BottomInfo::BottomInfo(
not_null<::Data::Reactions*> reactionsOwner,
Data &&data)
@ -150,7 +159,7 @@ ClickHandlerPtr BottomInfo::revokeReactionLink(
auto y = top;
auto widthLeft = available;
for (const auto &reaction : _reactions) {
const auto chosen = (reaction.emoji == _data.chosenReaction);
const auto chosen = (reaction.id == _data.chosenReaction);
const auto add = (reaction.countTextWidth > 0)
? st::reactionInfoDigitSkip
: st::reactionInfoBetween;
@ -192,7 +201,7 @@ ClickHandlerPtr BottomInfo::revokeReactionLink(
auto &owner = controller->session().data();
if (const auto item = owner.message(itemId)) {
const auto chosen = item->chosenReaction();
if (!chosen.isEmpty()) {
if (!chosen.empty()) {
item->toggleReaction(chosen);
}
}
@ -348,7 +357,7 @@ void BottomInfo::paintReactions(
}
if (reaction.image.isNull()) {
reaction.image = _reactionsOwner->resolveImageFor(
reaction.emoji,
reaction.id,
::Data::Reactions::ImageSize::BottomInfo);
}
const auto image = QRect(
@ -476,15 +485,18 @@ void BottomInfo::layoutReactionsText() {
) | ranges::view::transform([](const auto &pair) {
return std::make_pair(pair.first, pair.second);
}) | ranges::to_vector;
ranges::sort(sorted, std::greater<>(), &std::pair<QString, int>::second);
ranges::sort(
sorted,
std::greater<>(),
&std::pair<ReactionId, int>::second);
auto reactions = std::vector<Reaction>();
reactions.reserve(sorted.size());
for (const auto &[emoji, count] : sorted) {
const auto i = ranges::find(_reactions, emoji, &Reaction::emoji);
for (const auto &[id, count] : sorted) {
const auto i = ranges::find(_reactions, id, &Reaction::id);
reactions.push_back((i != end(_reactions))
? std::move(*i)
: prepareReactionWithEmoji(emoji));
: prepareReactionWithId(id));
setReactionCount(reactions.back(), count);
}
_reactions = std::move(reactions);
@ -514,10 +526,10 @@ QSize BottomInfo::countOptimalSize() {
return QSize(width, st::msgDateFont->height);
}
BottomInfo::Reaction BottomInfo::prepareReactionWithEmoji(
const QString &emoji) {
auto result = Reaction{ .emoji = emoji };
_reactionsOwner->preloadImageFor(emoji);
BottomInfo::Reaction BottomInfo::prepareReactionWithId(
const ReactionId &id) {
auto result = Reaction{ .id = id };
_reactionsOwner->preloadImageFor(id);
return result;
}
@ -537,7 +549,7 @@ void BottomInfo::setReactionCount(Reaction &reaction, int count) {
void BottomInfo::animateReaction(
ReactionAnimationArgs &&args,
Fn<void()> repaint) {
const auto i = ranges::find(_reactions, args.emoji, &Reaction::emoji);
const auto i = ranges::find(_reactions, args.id, &Reaction::id);
if (i == end(_reactions)) {
return;
}
@ -549,23 +561,23 @@ void BottomInfo::animateReaction(
}
auto BottomInfo::takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>> {
-> base::flat_map<ReactionId, std::unique_ptr<Reactions::Animation>> {
auto result = base::flat_map<
QString,
ReactionId,
std::unique_ptr<Reactions::Animation>>();
for (auto &reaction : _reactions) {
if (reaction.animation) {
result.emplace(reaction.emoji, std::move(reaction.animation));
result.emplace(reaction.id, std::move(reaction.animation));
}
}
return result;
}
void BottomInfo::continueReactionAnimations(base::flat_map<
QString,
ReactionId,
std::unique_ptr<Reactions::Animation>> animations) {
for (auto &[emoji, animation] : animations) {
const auto i = ranges::find(_reactions, emoji, &Reaction::emoji);
for (auto &[id, animation] : animations) {
const auto i = ranges::find(_reactions, id, &Reaction::id);
if (i != end(_reactions)) {
i->animation = std::move(animation);
}

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "history/view/history_view_object.h"
#include "data/data_message_reactions.h"
#include "ui/text/text.h"
#include "base/flags.h"
@ -15,23 +16,27 @@ namespace Ui {
struct ChatPaintContext;
} // namespace Ui
namespace Data {
class Reactions;
} // namespace Data
namespace HistoryView {
namespace Reactions {
class Animation;
} // namespace Reactions
struct ReactionAnimationArgs {
::Data::ReactionId id;
std::shared_ptr<Lottie::Icon> flyIcon;
QRect flyFrom;
[[nodiscard]] ReactionAnimationArgs translated(QPoint point) const;
};
using PaintContext = Ui::ChatPaintContext;
class Message;
struct TextState;
struct ReactionAnimationArgs;
class BottomInfo final : public Object {
public:
using ReactionId = ::Data::ReactionId;
struct Data {
enum class Flag : uchar {
Edited = 0x01,
@ -49,8 +54,8 @@ public:
QDateTime date;
QString author;
base::flat_map<QString, int> reactions;
QString chosenReaction;
base::flat_map<ReactionId, int> reactions;
ReactionId chosenReaction;
std::optional<int> views;
std::optional<int> replies;
Flags flags;
@ -79,16 +84,16 @@ public:
ReactionAnimationArgs &&args,
Fn<void()> repaint);
[[nodiscard]] auto takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>>;
-> base::flat_map<ReactionId, std::unique_ptr<Reactions::Animation>>;
void continueReactionAnimations(base::flat_map<
QString,
ReactionId,
std::unique_ptr<Reactions::Animation>> animations);
private:
struct Reaction {
mutable std::unique_ptr<Reactions::Animation> animation;
mutable QImage image;
QString emoji;
ReactionId id;
QString countText;
int count = 0;
int countTextWidth = 0;
@ -114,7 +119,8 @@ private:
QSize countCurrentSize(int newWidth) override;
void setReactionCount(Reaction &reaction, int count);
[[nodiscard]] Reaction prepareReactionWithEmoji(const QString &emoji);
[[nodiscard]] Reaction prepareReactionWithId(
const ReactionId &id);
[[nodiscard]] ClickHandlerPtr revokeReactionLink(
not_null<const HistoryItem*> item,
QPoint position) const;

View file

@ -1153,7 +1153,7 @@ void AddWhoReactedAction(
controller->window().show(ReactionsListBox(
controller,
item,
QString(),
{},
whoReadIds));
}
};
@ -1172,7 +1172,7 @@ void ShowWhoReactedMenu(
QPoint position,
not_null<QWidget*> context,
not_null<HistoryItem*> item,
const QString &emoji,
const Data::ReactionId &id,
not_null<Window::SessionController*> controller,
rpl::lifetime &lifetime) {
const auto participantChosen = [=](uint64 id) {
@ -1183,20 +1183,20 @@ void ShowWhoReactedMenu(
controller->window().show(ReactionsListBox(
controller,
item,
emoji));
id));
}
};
const auto reactions = &controller->session().data().reactions();
const auto &list = reactions->list(
Data::Reactions::Type::Active);
const auto activeNonQuick = (emoji != reactions->favorite())
&& ranges::contains(list, emoji, &Data::Reaction::emoji);
const auto activeNonQuick = (id != reactions->favorite())
&& ranges::contains(list, id, &Data::Reaction::id);
const auto filler = lifetime.make_state<Ui::WhoReactedListMenu>(
participantChosen,
showAllChosen);
Api::WhoReacted(
item,
emoji,
id,
context,
st::defaultWhoRead
) | rpl::filter([=](const Ui::WhoReadContent &content) {
@ -1206,7 +1206,7 @@ void ShowWhoReactedMenu(
const auto refill = [=] {
if (activeNonQuick) {
(*menu)->addAction(tr::lng_context_set_as_quick(tr::now), [=] {
reactions->setFavorite(emoji);
reactions->setFavorite(id);
}, &st::menuIconFave);
(*menu)->addSeparator();
}

View file

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unique_qptr.h"
namespace Data {
struct ReactionId;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
@ -75,7 +79,7 @@ void ShowWhoReactedMenu(
QPoint position,
not_null<QWidget*> context,
not_null<HistoryItem*> item,
const QString &emoji,
const Data::ReactionId &id,
not_null<Window::SessionController*> controller,
rpl::lifetime &lifetime);

View file

@ -353,15 +353,6 @@ void DateBadge::paint(
ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide);
}
ReactionAnimationArgs ReactionAnimationArgs::translated(
QPoint point) const {
return {
.emoji = emoji,
.flyIcon = flyIcon,
.flyFrom = flyFrom.translated(point),
};
}
Element::Element(
not_null<ElementDelegate*> delegate,
not_null<HistoryItem*> data,
@ -1115,15 +1106,15 @@ void Element::animateReaction(ReactionAnimationArgs &&args) {
void Element::animateUnreadReactions() {
const auto &recent = data()->recentReactions();
for (const auto &[emoji, list] : recent) {
for (const auto &[id, list] : recent) {
if (ranges::contains(list, true, &Data::RecentReaction::unread)) {
animateReaction({ .emoji = emoji });
animateReaction({ .id = id });
}
}
}
auto Element::takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>> {
-> base::flat_map<Data::ReactionId, std::unique_ptr<Reactions::Animation>> {
return {};
}

View file

@ -20,6 +20,7 @@ struct HistoryMessageReply;
namespace Data {
struct Reaction;
struct ReactionId;
} // namespace Data
namespace Window {
@ -41,6 +42,7 @@ namespace HistoryView {
enum class PointState : char;
enum class InfoDisplayType : char;
struct ReactionAnimationArgs;
struct StateRequest;
struct TextState;
class Media;
@ -238,14 +240,6 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
};
struct ReactionAnimationArgs {
QString emoji;
std::shared_ptr<Lottie::Icon> flyIcon;
QRect flyFrom;
[[nodiscard]] ReactionAnimationArgs translated(QPoint point) const;
};
class Element
: public Object
, public RuntimeComposer<Element>
@ -442,7 +436,9 @@ public:
virtual void animateReaction(ReactionAnimationArgs &&args);
void animateUnreadReactions();
[[nodiscard]] virtual auto takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>>;
-> base::flat_map<
Data::ReactionId,
std::unique_ptr<Reactions::Animation>>;
virtual ~Element();

View file

@ -361,16 +361,16 @@ ListWidget::ListWidget(
|| Window::ShowReactPremiumError(
_controller,
item,
reaction.emoji)) {
reaction.id)) {
return;
}
item->toggleReaction(reaction.emoji);
if (item->chosenReaction() != reaction.emoji) {
item->toggleReaction(reaction.id);
if (item->chosenReaction() != reaction.id) {
return;
} else if (const auto view = viewForItem(item)) {
if (const auto top = itemTop(view); top >= 0) {
view->animateReaction({
.emoji = reaction.emoji,
.id = reaction.id,
.flyIcon = reaction.icon,
.flyFrom = reaction.geometry.translated(0, -top),
});
@ -2116,7 +2116,9 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) {
void ListWidget::toggleFavoriteReaction(not_null<Element*> view) const {
const auto favorite = session().data().reactions().favorite();
const auto allowed = _reactionsManager->allowedSublist();
if (allowed && !allowed->contains(favorite)) {
if (allowed
&& (favorite.emoji().isEmpty()
|| !allowed->contains(favorite.emoji()))) {
return;
}
const auto item = view->data();
@ -2124,7 +2126,7 @@ void ListWidget::toggleFavoriteReaction(not_null<Element*> view) const {
return;
} else if (item->chosenReaction() != favorite) {
if (const auto top = itemTop(view); top >= 0) {
view->animateReaction({ .emoji = favorite });
view->animateReaction({ .id = favorite });
}
}
item->toggleReaction(favorite);
@ -2187,7 +2189,8 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto link = ClickHandler::getActive();
if (link
&& !link->property(kSendReactionEmojiProperty).toString().isEmpty()
&& !link->property(
kSendReactionEmojiProperty).value<Data::ReactionId>().empty()
&& _reactionsManager->showContextMenu(
this,
e,
@ -2201,17 +2204,18 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
: nullptr;
const auto hasWhoReactedItem = overItem
&& Api::WhoReactedExists(overItem);
const auto clickedEmoji = link
? link->property(kReactionsCountEmojiProperty).toString()
: QString();
const auto clickedReaction = link
? link->property(
kReactionsCountEmojiProperty).value<Data::ReactionId>()
: Data::ReactionId();
_whoReactedMenuLifetime.destroy();
if (hasWhoReactedItem && !clickedEmoji.isEmpty()) {
if (hasWhoReactedItem && !clickedReaction.empty()) {
HistoryView::ShowWhoReactedMenu(
&_menu,
e->globalPos(),
this,
overItem,
clickedEmoji,
clickedReaction,
_controller,
_whoReactedMenuLifetime);
e->accept();

View file

@ -260,10 +260,12 @@ Message::Message(
refreshReactions();
auto animations = replacing
? replacing->takeReactionAnimations()
: base::flat_map<QString, std::unique_ptr<Reactions::Animation>>();
: base::flat_map<
Data::ReactionId,
std::unique_ptr<Reactions::Animation>>();
if (!animations.empty()) {
const auto repainter = [=] { repaint(); };
for (const auto &[emoji, animation] : animations) {
for (const auto &[id, animation] : animations) {
animation->setRepaintCallback(repainter);
}
if (_reactions) {
@ -433,7 +435,7 @@ void Message::animateReaction(ReactionAnimationArgs &&args) {
}
auto Message::takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>> {
-> base::flat_map<Data::ReactionId, std::unique_ptr<Reactions::Animation>> {
return _reactions
? _reactions->takeAnimations()
: _bottomInfo.takeReactionAnimations();
@ -2182,15 +2184,15 @@ void Message::refreshReactions() {
using namespace Reactions;
auto reactionsData = InlineListDataFromMessage(this);
if (!_reactions) {
const auto handlerFactory = [=](QString emoji) {
const auto handlerFactory = [=](ReactionId id) {
const auto weak = base::make_weak(this);
return std::make_shared<LambdaClickHandler>([=] {
if (const auto strong = weak.get()) {
strong->data()->toggleReaction(emoji);
strong->data()->toggleReaction(id);
if (const auto now = weak.get()) {
if (now->data()->chosenReaction() == emoji) {
if (now->data()->chosenReaction() == id) {
now->animateReaction({
.emoji = emoji,
.id = id,
});
}
}

View file

@ -16,6 +16,10 @@ class HistoryMessage;
struct HistoryMessageEdited;
struct HistoryMessageForwarded;
namespace Data {
struct ReactionId;
} // namespace Data
namespace HistoryView {
class ViewButton;
@ -136,7 +140,9 @@ public:
void animateReaction(ReactionAnimationArgs &&args) override;
auto takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>> override;
-> base::flat_map<
Data::ReactionId,
std::unique_ptr<Reactions::Animation>> override;
QRect innerGeometry() const override;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_react_animation.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_bottom_info.h"
#include "lottie/lottie_icon.h"
#include "data/data_message_reactions.h"
#include "data/data_document.h"
@ -30,7 +31,7 @@ Animation::Animation(
, _repaint(std::move(repaint))
, _flyFrom(args.flyFrom) {
const auto &list = owner->list(::Data::Reactions::Type::All);
const auto i = ranges::find(list, args.emoji, &::Data::Reaction::emoji);
const auto i = ranges::find(list, args.id, &::Data::Reaction::id);
if (i == end(list) || !i->centerIcon) {
return;
}

View file

@ -407,9 +407,9 @@ Manager::Manager(
applyListFilters();
}, _lifetime);
_createChooseCallback = [=](QString emoji) {
_createChooseCallback = [=](ReactionId id) {
return [=] {
if (auto chosen = lookupChosen(emoji)) {
if (auto chosen = lookupChosen(id)) {
updateButton({});
_chosen.fire(std::move(chosen));
}
@ -417,13 +417,13 @@ Manager::Manager(
};
}
Manager::Chosen Manager::lookupChosen(const QString &emoji) const {
Manager::Chosen Manager::lookupChosen(const ReactionId &id) const {
auto result = Chosen{
.context = _buttonContext,
.emoji = emoji,
.id = id,
};
const auto button = _button.get();
const auto i = ranges::find(_icons, emoji, &ReactionIcons::emoji);
const auto i = ranges::find(_icons, id, &ReactionIcons::id);
if (i == end(_icons) || !button) {
return result;
}
@ -471,14 +471,15 @@ void Manager::applyListFilters() {
auto showPremiumLock = (ReactionIcons*)nullptr;
auto favoriteIndex = -1;
for (auto &icon : _list) {
const auto &emoji = icon.emoji;
const auto &id = icon.id;
const auto add = applyUniqueLimit
? _buttonAlreadyList.contains(emoji)
: (!_filter || _filter->contains(emoji));
? _buttonAlreadyList.contains(id)
: (!_filter
|| (!id.emoji().isEmpty() && _filter->contains(id.emoji())));
if (add) {
if (icon.premium
&& !_allowSendingPremium
&& !_buttonAlreadyList.contains(emoji)) {
&& !_buttonAlreadyList.contains(id)) {
if (_premiumPossible) {
showPremiumLock = &icon;
} else {
@ -486,7 +487,7 @@ void Manager::applyListFilters() {
}
} else {
icon.premiumLock = false;
if (emoji == _favorite) {
if (id == _favorite) {
favoriteIndex = int(icons.size());
}
icons.push_back(&icon);
@ -576,13 +577,13 @@ void Manager::showButtonDelayed() {
void Manager::applyList(
const std::vector<Data::Reaction> &list,
const QString &favorite,
const ReactionId &favorite,
bool premiumPossible) {
const auto possibleChanged = (_premiumPossible != premiumPossible);
_premiumPossible = premiumPossible;
const auto proj = [](const auto &obj) {
return std::tie(
obj.emoji,
obj.id,
obj.appearAnimation,
obj.selectAnimation,
obj.premium);
@ -603,7 +604,7 @@ void Manager::applyList(
_list.clear();
for (const auto &reaction : list) {
_list.push_back({
.emoji = reaction.emoji,
.id = reaction.id,
.appearAnimation = reaction.appearAnimation,
.selectAnimation = reaction.selectAnimation,
.premium = reaction.premium,
@ -639,12 +640,12 @@ void Manager::updateUniqueLimit(not_null<HistoryItem*> item) {
}
const auto &all = item->reactions();
const auto my = item->chosenReaction();
auto list = base::flat_set<QString>();
auto list = base::flat_set<Data::ReactionId>();
list.reserve(all.size());
auto myIsUnique = false;
for (const auto &[emoji, count] : all) {
list.emplace(emoji);
if (count == 1 && emoji == my) {
for (const auto &[id, count] : all) {
list.emplace(id);
if (count == 1 && id == my) {
myIsUnique = true;
}
}
@ -780,7 +781,7 @@ void Manager::loadIcons() {
if (all && !_icons.empty()) {
auto &data = _icons.front()->appearAnimation->owner().reactions();
for (const auto &icon : _icons) {
data.preloadAnimationsFor(icon->emoji);
data.preloadAnimationsFor(icon->id);
}
}
}
@ -886,15 +887,17 @@ void Manager::setSelectedIcon(int index) const {
ClickHandlerPtr Manager::resolveButtonLink(
const ReactionIcons &reaction) const {
const auto emoji = reaction.emoji;
const auto i = _reactionsLinks.find(emoji);
const auto id = reaction.id;
const auto i = _reactionsLinks.find(id);
if (i != end(_reactionsLinks)) {
return i->second;
}
auto handler = std::make_shared<LambdaClickHandler>(
crl::guard(this, _createChooseCallback(emoji)));
handler->setProperty(kSendReactionEmojiProperty, emoji);
return _reactionsLinks.emplace(emoji, std::move(handler)).first->second;
crl::guard(this, _createChooseCallback(id)));
handler->setProperty(
kSendReactionEmojiProperty,
QVariant::fromValue(id));
return _reactionsLinks.emplace(id, std::move(handler)).first->second;
}
TextState Manager::buttonTextState(QPoint position) const {
@ -1625,23 +1628,23 @@ void Manager::recordCurrentReactionEffect(FullMsgId itemId, QPoint origin) {
bool Manager::showContextMenu(
QWidget *parent,
QContextMenuEvent *e,
const QString &favorite) {
const ReactionId &favorite) {
if (_icons.empty() || _selectedIcon < 0) {
return false;
}
const auto lookupSelectedEmoji = [&] {
const auto lookupSelectedId = [&] {
const auto i = ranges::find(_icons, true, &ReactionIcons::selected);
return (i != end(_icons)) ? (*i)->emoji : QString();
return (i != end(_icons)) ? (*i)->id : ReactionId();
};
if (!favorite.isEmpty() && lookupSelectedEmoji() == favorite) {
if (!favorite.empty() && lookupSelectedId() == favorite) {
return true;
}
_menu = base::make_unique_q<Ui::PopupMenu>(
parent,
st::popupMenuWithIcons);
const auto callback = [=] {
if (const auto emoji = lookupSelectedEmoji(); !emoji.isEmpty()) {
_faveRequests.fire_copy(emoji);
if (const auto id = lookupSelectedId(); !id.empty()) {
_faveRequests.fire_copy(id);
}
};
_menu->addAction(
@ -1652,7 +1655,7 @@ bool Manager::showContextMenu(
return true;
}
rpl::producer<QString> Manager::faveRequests() const {
auto Manager::faveRequests() const -> rpl::producer<ReactionId> {
return _faveRequests.events();
}
@ -1677,8 +1680,8 @@ void SetupManagerList(
}, manager->lifetime());
manager->faveRequests(
) | rpl::start_with_next([=](const QString &emoji) {
reactions->setFavorite(emoji);
) | rpl::start_with_next([=](const Data::ReactionId &id) {
reactions->setFavorite(id);
manager->updateButton({});
}, manager->lifetime());

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/widgets/scroll_area.h"
#include "data/data_message_reactions.h"
#include "ui/chat/chat_style.h"
namespace Ui {
@ -143,11 +144,12 @@ public:
IconFactory iconFactory);
~Manager();
using ReactionId = ::Data::ReactionId;
using AllowedSublist = std::optional<base::flat_set<QString>>;
void applyList(
const std::vector<Data::Reaction> &list,
const QString &favorite,
const ReactionId &favorite,
bool premiumPossible);
void updateAllowedSublist(AllowedSublist filter);
void updateAllowSendingPremium(bool allow);
@ -163,12 +165,12 @@ public:
struct Chosen {
FullMsgId context;
QString emoji;
ReactionId id;
std::shared_ptr<Lottie::Icon> icon;
QRect geometry;
explicit operator bool() const {
return context && !emoji.isNull();
return context && !id.empty();
}
};
[[nodiscard]] rpl::producer<Chosen> chosen() const {
@ -185,8 +187,8 @@ public:
bool showContextMenu(
QWidget *parent,
QContextMenuEvent *e,
const QString &favorite);
[[nodiscard]] rpl::producer<QString> faveRequests() const;
const ReactionId &favorite);
[[nodiscard]] rpl::producer<ReactionId> faveRequests() const;
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
@ -198,7 +200,7 @@ private:
std::shared_ptr<Lottie::Icon> icon;
};
struct ReactionIcons {
QString emoji;
ReactionId id;
not_null<DocumentData*> appearAnimation;
not_null<DocumentData*> selectAnimation;
std::shared_ptr<Lottie::Icon> appear;
@ -221,7 +223,7 @@ private:
void showButtonDelayed();
void stealWheelEvents(not_null<QWidget*> target);
[[nodiscard]] Chosen lookupChosen(const QString &emoji) const;
[[nodiscard]] Chosen lookupChosen(const ReactionId &id) const;
[[nodiscard]] bool overCurrentButton(QPoint position) const;
void removeStaleButtons();
@ -314,7 +316,7 @@ private:
const IconFactory _iconFactory;
rpl::event_stream<Chosen> _chosen;
std::vector<ReactionIcons> _list;
QString _favorite;
ReactionId _favorite;
AllowedSublist _filter;
QSize _outer;
QRect _inner;
@ -358,10 +360,10 @@ private:
std::unique_ptr<Button> _button;
std::vector<std::unique_ptr<Button>> _buttonHiding;
FullMsgId _buttonContext;
base::flat_set<QString> _buttonAlreadyList;
base::flat_set<ReactionId> _buttonAlreadyList;
int _buttonAlreadyNotMineCount = 0;
mutable base::flat_map<QString, ClickHandlerPtr> _reactionsLinks;
Fn<Fn<void()>(QString)> _createChooseCallback;
mutable base::flat_map<ReactionId, ClickHandlerPtr> _reactionsLinks;
Fn<Fn<void()>(ReactionId)> _createChooseCallback;
base::flat_map<FullMsgId, QRect> _activeEffectAreas;
@ -369,7 +371,7 @@ private:
base::flat_map<FullMsgId, Ui::ReactionPaintInfo> _collectedEffects;
base::unique_qptr<Ui::PopupMenu> _menu;
rpl::event_stream<QString> _faveRequests;
rpl::event_stream<ReactionId> _faveRequests;
rpl::lifetime _lifetime;

View file

@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_react_animation.h"
#include "history/view/history_view_group_call_bar.h"
#include "core/click_handler_types.h"
#include "data/data_message_reactions.h"
#include "data/data_peer.h"
#include "lang/lang_tag.h"
#include "ui/chat/chat_style.h"
@ -35,9 +34,21 @@ constexpr auto kMaxNicePerRow = 5;
} // namespace
struct InlineList::Button {
QRect geometry;
mutable std::unique_ptr<Animation> animation;
mutable QImage image;
mutable ClickHandlerPtr link;
std::unique_ptr<Userpics> userpics;
ReactionId id;
QString countText;
int count = 0;
int countTextWidth = 0;
};
InlineList::InlineList(
not_null<::Data::Reactions*> owner,
Fn<ClickHandlerPtr(QString)> handlerFactory,
Fn<ClickHandlerPtr(ReactionId)> handlerFactory,
Data &&data)
: _owner(owner)
, _handlerFactory(std::move(handlerFactory))
@ -85,19 +96,19 @@ void InlineList::layoutButtons() {
} else if (p1.second < p2.second) {
return false;
}
return ranges::find(list, p1.first, &::Data::Reaction::emoji)
< ranges::find(list, p2.first, &::Data::Reaction::emoji);
return ranges::find(list, p1.first, &::Data::Reaction::id)
< ranges::find(list, p2.first, &::Data::Reaction::id);
});
auto buttons = std::vector<Button>();
buttons.reserve(sorted.size());
for (const auto &[emoji, count] : sorted) {
const auto i = ranges::find(_buttons, emoji, &Button::emoji);
for (const auto &[id, count] : sorted) {
const auto i = ranges::find(_buttons, id, &Button::id);
buttons.push_back((i != end(_buttons))
? std::move(*i)
: prepareButtonWithEmoji(emoji));
const auto add = (emoji == _data.chosenReaction) ? 1 : 0;
const auto j = _data.recent.find(emoji);
: prepareButtonWithId(id));
const auto add = (id == _data.chosenReaction) ? 1 : 0;
const auto j = _data.recent.find(id);
if (j != end(_data.recent) && !j->second.empty()) {
setButtonUserpics(buttons.back(), j->second);
} else {
@ -107,9 +118,9 @@ void InlineList::layoutButtons() {
_buttons = std::move(buttons);
}
InlineList::Button InlineList::prepareButtonWithEmoji(const QString &emoji) {
auto result = Button{ .emoji = emoji };
_owner->preloadImageFor(emoji);
InlineList::Button InlineList::prepareButtonWithId(const ReactionId &id) {
auto result = Button{ .id = id };
_owner->preloadImageFor(id);
return result;
}
@ -290,7 +301,7 @@ void InlineList::paint(
}
const auto animating = (button.animation != nullptr);
const auto &geometry = button.geometry;
const auto mine = (_data.chosenReaction == button.emoji);
const auto mine = (_data.chosenReaction == button.id);
const auto withoutMine = button.count - (mine ? 1 : 0);
const auto skipImage = animating
&& (withoutMine < 1 || !button.animation->flying());
@ -337,7 +348,7 @@ void InlineList::paint(
}
if (button.image.isNull()) {
button.image = _owner->resolveImageFor(
button.emoji,
button.id,
::Data::Reactions::ImageSize::InlineList);
}
const auto image = QRect(
@ -414,11 +425,11 @@ bool InlineList::getState(
for (const auto &button : _buttons) {
if (button.geometry.contains(point)) {
if (!button.link) {
button.link = _handlerFactory(button.emoji);
button.link = _handlerFactory(button.id);
button.link->setProperty(
kReactionsCountEmojiProperty,
button.emoji);
_owner->preloadAnimationsFor(button.emoji);
QVariant::fromValue(button.id));
_owner->preloadAnimationsFor(button.id);
}
outResult->link = button.link;
return true;
@ -430,7 +441,7 @@ bool InlineList::getState(
void InlineList::animate(
ReactionAnimationArgs &&args,
Fn<void()> repaint) {
const auto i = ranges::find(_buttons, args.emoji, &Button::emoji);
const auto i = ranges::find(_buttons, args.id, &Button::id);
if (i == end(_buttons)) {
return;
}
@ -471,23 +482,23 @@ void InlineList::resolveUserpicsImage(const Button &button) const {
}
auto InlineList::takeAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>> {
-> base::flat_map<ReactionId, std::unique_ptr<Reactions::Animation>> {
auto result = base::flat_map<
QString,
ReactionId,
std::unique_ptr<Reactions::Animation>>();
for (auto &button : _buttons) {
if (button.animation) {
result.emplace(button.emoji, std::move(button.animation));
result.emplace(button.id, std::move(button.animation));
}
}
return result;
}
void InlineList::continueAnimations(base::flat_map<
QString,
ReactionId,
std::unique_ptr<Reactions::Animation>> animations) {
for (auto &[emoji, animation] : animations) {
const auto i = ranges::find(_buttons, emoji, &Button::emoji);
for (auto &[id, animation] : animations) {
const auto i = ranges::find(_buttons, id, &Button::id);
if (i != end(_buttons)) {
i->animation = std::move(animation);
}
@ -519,14 +530,14 @@ InlineListData InlineListDataFromMessage(not_null<Message*> message) {
}();
if (showUserpics) {
result.recent.reserve(recent.size());
for (const auto &[emoji, list] : recent) {
result.recent.emplace(emoji).first->second = list
for (const auto &[id, list] : recent) {
result.recent.emplace(id).first->second = list
| ranges::view::transform(&Data::RecentReaction::peer)
| ranges::to_vector;
}
}
result.chosenReaction = item->chosenReaction();
if (!result.chosenReaction.isEmpty()) {
if (!result.chosenReaction.empty()) {
--result.reactions[result.chosenReaction];
}
result.flags = (message->hasOutLayout() ? Flag::OutLayout : Flag())

View file

@ -8,9 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "history/view/history_view_object.h"
#include "data/data_message_reactions.h"
namespace Data {
class Reactions;
class CloudImageView;
} // namespace Data
@ -22,12 +22,13 @@ namespace HistoryView {
using PaintContext = Ui::ChatPaintContext;
class Message;
struct TextState;
struct ReactionAnimationArgs;
struct UserpicInRow;
struct ReactionAnimationArgs;
} // namespace HistoryView
namespace HistoryView::Reactions {
using ::Data::ReactionId;
class Animation;
struct InlineListData {
@ -39,9 +40,9 @@ struct InlineListData {
friend inline constexpr bool is_flag_type(Flag) { return true; };
using Flags = base::flags<Flag>;
base::flat_map<QString, int> reactions;
base::flat_map<QString, std::vector<not_null<PeerData*>>> recent;
QString chosenReaction;
base::flat_map<ReactionId, int> reactions;
base::flat_map<ReactionId, std::vector<not_null<PeerData*>>> recent;
ReactionId chosenReaction;
Flags flags = {};
};
@ -50,7 +51,7 @@ public:
using Data = InlineListData;
InlineList(
not_null<::Data::Reactions*> owner,
Fn<ClickHandlerPtr(QString)> handlerFactory,
Fn<ClickHandlerPtr(ReactionId)> handlerFactory,
Data &&data);
~InlineList();
@ -76,9 +77,9 @@ public:
ReactionAnimationArgs &&args,
Fn<void()> repaint);
[[nodiscard]] auto takeAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>>;
-> base::flat_map<ReactionId, std::unique_ptr<Reactions::Animation>>;
void continueAnimations(base::flat_map<
QString,
ReactionId,
std::unique_ptr<Reactions::Animation>> animations);
private:
@ -87,17 +88,7 @@ private:
std::vector<UserpicInRow> list;
bool someNotLoaded = false;
};
struct Button {
QRect geometry;
mutable std::unique_ptr<Animation> animation;
mutable QImage image;
mutable ClickHandlerPtr link;
std::unique_ptr<Userpics> userpics;
QString emoji;
QString countText;
int count = 0;
int countTextWidth = 0;
};
struct Button;
void layout();
void layoutButtons();
@ -106,13 +97,13 @@ private:
void setButtonUserpics(
Button &button,
const std::vector<not_null<PeerData*>> &peers);
[[nodiscard]] Button prepareButtonWithEmoji(const QString &emoji);
[[nodiscard]] Button prepareButtonWithId(const ReactionId &id);
void resolveUserpicsImage(const Button &button) const;
QSize countOptimalSize() override;
const not_null<::Data::Reactions*> _owner;
const Fn<ClickHandlerPtr(QString)> _handlerFactory;
const Fn<ClickHandlerPtr(ReactionId)> _handlerFactory;
Data _data;
std::vector<Button> _buttons;
QSize _skipBlock;

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "api/api_who_reacted.h"
#include "ui/controls/who_reacted_context_action.h"
#include "data/data_message_reactions.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_peer.h"
@ -26,9 +27,11 @@ namespace {
constexpr auto kPerPageFirst = 20;
constexpr auto kPerPage = 100;
using ::Data::ReactionId;
class Row final : public PeerListRow {
public:
Row(not_null<PeerData*> peer, const QString &reaction);
Row(not_null<PeerData*> peer, const ReactionId &id);
QSize rightActionSize() const override;
QMargins rightActionMargins() const override;
@ -51,8 +54,8 @@ public:
Controller(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item,
const QString &selected,
rpl::producer<QString> switches,
const ReactionId &selected,
rpl::producer<ReactionId> switches,
std::shared_ptr<Api::WhoReadList> whoReadIds);
Main::Session &session() const override;
@ -61,21 +64,21 @@ public:
void loadMoreRows() override;
private:
using AllEntry = std::pair<not_null<PeerData*>, QString>;
using AllEntry = std::pair<not_null<PeerData*>, Data::ReactionId>;
void fillWhoRead();
void loadMore(const QString &reaction);
bool appendRow(not_null<PeerData*> peer, QString reaction);
void loadMore(const ReactionId &reaction);
bool appendRow(not_null<PeerData*> peer, ReactionId reaction);
std::unique_ptr<PeerListRow> createRow(
not_null<PeerData*> peer,
QString reaction) const;
void showReaction(const QString &reaction);
ReactionId reaction) const;
void showReaction(const ReactionId &reaction);
const not_null<Window::SessionController*> _window;
const not_null<HistoryItem*> _item;
MTP::Sender _api;
QString _shownReaction;
ReactionId _shownReaction;
std::shared_ptr<Api::WhoReadList> _whoReadIds;
std::vector<not_null<PeerData*>> _whoRead;
@ -89,9 +92,9 @@ private:
};
Row::Row(not_null<PeerData*> peer, const QString &reaction)
Row::Row(not_null<PeerData*> peer, const ReactionId &id)
: PeerListRow(peer)
, _emoji(Ui::Emoji::Find(reaction)) {
, _emoji(Ui::Emoji::Find(id.emoji())) { // #TODO reaction
}
QSize Row::rightActionSize() const {
@ -132,8 +135,8 @@ void Row::rightActionPaint(
Controller::Controller(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item,
const QString &selected,
rpl::producer<QString> switches,
const ReactionId &selected,
rpl::producer<ReactionId> switches,
std::shared_ptr<Api::WhoReadList> whoReadIds)
: _window(window)
, _item(item)
@ -142,9 +145,9 @@ Controller::Controller(
, _whoReadIds(whoReadIds) {
std::move(
switches
) | rpl::filter([=](const QString &reaction) {
) | rpl::filter([=](const ReactionId &reaction) {
return (_shownReaction != reaction);
}) | rpl::start_with_next([=](const QString &reaction) {
}) | rpl::start_with_next([=](const ReactionId &reaction) {
showReaction(reaction);
}, lifetime());
}
@ -154,7 +157,7 @@ Main::Session &Controller::session() const {
}
void Controller::prepare() {
if (_shownReaction == u"read"_q) {
if (_shownReaction.emoji() == u"read"_q) {
fillWhoRead();
setDescriptionText(QString());
} else {
@ -164,7 +167,7 @@ void Controller::prepare() {
loadMore(_shownReaction);
}
void Controller::showReaction(const QString &reaction) {
void Controller::showReaction(const ReactionId &reaction) {
if (_shownReaction == reaction) {
return;
}
@ -175,9 +178,9 @@ void Controller::showReaction(const QString &reaction) {
}
_shownReaction = reaction;
if (_shownReaction == u"read"_q) {
if (_shownReaction.emoji() == u"read"_q) {
fillWhoRead();
} else if (_shownReaction.isEmpty()) {
} else if (_shownReaction.empty()) {
_filtered.clear();
for (const auto &[peer, reaction] : _all) {
appendRow(peer, reaction);
@ -210,12 +213,12 @@ void Controller::fillWhoRead() {
}
}
for (const auto &peer : _whoRead) {
appendRow(peer, QString());
appendRow(peer, ReactionId());
}
}
void Controller::loadMoreRows() {
const auto &offset = _shownReaction.isEmpty()
const auto &offset = _shownReaction.empty()
? _allOffset
: _filteredOffset;
if (_loadRequestId || offset.isEmpty()) {
@ -224,33 +227,33 @@ void Controller::loadMoreRows() {
loadMore(_shownReaction);
}
void Controller::loadMore(const QString &reaction) {
if (reaction == u"read"_q) {
loadMore(QString());
void Controller::loadMore(const ReactionId &reaction) {
if (reaction.emoji() == u"read"_q) {
loadMore(ReactionId());
return;
} else if (reaction.isEmpty() && _allOffset.isEmpty() && !_all.empty()) {
} else if (reaction.empty() && _allOffset.isEmpty() && !_all.empty()) {
return;
}
_api.request(_loadRequestId).cancel();
const auto &offset = reaction.isEmpty()
const auto &offset = reaction.empty()
? _allOffset
: _filteredOffset;
using Flag = MTPmessages_GetMessageReactionsList::Flag;
const auto flags = Flag(0)
| (offset.isEmpty() ? Flag(0) : Flag::f_offset)
| (reaction.isEmpty() ? Flag(0) : Flag::f_reaction);
| (reaction.empty() ? Flag(0) : Flag::f_reaction);
_loadRequestId = _api.request(MTPmessages_GetMessageReactionsList(
MTP_flags(flags),
_item->history()->peer->input,
MTP_int(_item->id),
MTP_string(reaction),
Data::ReactionToMTP(reaction),
MTP_string(offset),
MTP_int(offset.isEmpty() ? kPerPageFirst : kPerPage)
)).done([=](const MTPmessages_MessageReactionsList &result) {
_loadRequestId = 0;
const auto filtered = !reaction.isEmpty();
const auto filtered = !reaction.empty();
const auto shown = (reaction == _shownReaction);
result.match([&](const MTPDmessages_messageReactionsList &data) {
const auto sessionData = &session().data();
@ -262,7 +265,8 @@ void Controller::loadMore(const QString &reaction) {
reaction.match([&](const MTPDmessagePeerReaction &data) {
const auto peer = sessionData->peerLoaded(
peerFromMTP(data.vpeer_id()));
const auto reaction = qs(data.vreaction());
const auto reaction = Data::ReactionFromMTP(
data.vreaction());
if (peer && (!shown || appendRow(peer, reaction))) {
if (filtered) {
_filtered.emplace_back(peer);
@ -288,7 +292,7 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
});
}
bool Controller::appendRow(not_null<PeerData*> peer, QString reaction) {
bool Controller::appendRow(not_null<PeerData*> peer, ReactionId reaction) {
if (delegate()->peerListFindRow(peer->id.value)) {
return false;
}
@ -298,7 +302,7 @@ bool Controller::appendRow(not_null<PeerData*> peer, QString reaction) {
std::unique_ptr<PeerListRow> Controller::createRow(
not_null<PeerData*> peer,
QString reaction) const {
ReactionId reaction) const {
return std::make_unique<Row>(peer, reaction);
}
@ -307,23 +311,26 @@ std::unique_ptr<PeerListRow> Controller::createRow(
object_ptr<Ui::BoxContent> ReactionsListBox(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item,
QString selected,
Data::ReactionId selected,
std::shared_ptr<Api::WhoReadList> whoReadIds) {
Expects(IsServerMsgId(item->id));
if (!item->reactions().contains(selected)) {
selected = QString();
selected = {};
}
if (selected.isEmpty() && whoReadIds && !whoReadIds->list.empty()) {
selected = u"read"_q;
if (selected.empty() && whoReadIds && !whoReadIds->list.empty()) {
selected = Data::ReactionId{ u"read"_q };
}
const auto tabRequests = std::make_shared<rpl::event_stream<QString>>();
const auto tabRequests = std::make_shared<
rpl::event_stream<Data::ReactionId>>();
const auto initBox = [=](not_null<PeerListBox*> box) {
box->setNoContentMargin(true);
auto map = item->reactions();
if (whoReadIds && !whoReadIds->list.empty()) {
map.emplace(u"read"_q, int(whoReadIds->list.size()));
map.emplace(
Data::ReactionId{ u"read"_q },
int(whoReadIds->list.size()));
}
const auto selector = CreateReactionSelector(
box,

View file

@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem;
namespace Data {
struct ReactionId;
} // namespace Data
namespace Api {
struct WhoReadList;
} // namespace Api
@ -28,7 +32,7 @@ namespace HistoryView {
object_ptr<Ui::BoxContent> ReactionsListBox(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item,
QString selected,
Data::ReactionId selected,
std::shared_ptr<Api::WhoReadList> whoReadIds = nullptr);
} // namespace HistoryView

View file

@ -10,16 +10,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h"
#include "ui/abstract_button.h"
#include "ui/controls/who_reacted_context_action.h"
#include "data/data_message_reactions.h"
#include "styles/style_widgets.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
using ::Data::ReactionId;
not_null<Ui::AbstractButton*> CreateTab(
not_null<QWidget*> parent,
const style::MultiSelect &st,
const QString &reaction,
const ReactionId &reaction,
Ui::WhoReadType whoReadType,
int count,
rpl::producer<bool> selected) {
@ -67,14 +70,14 @@ not_null<Ui::AbstractButton*> CreateTab(
}
const auto skip = st::reactionsTabIconSkip;
const auto icon = QRect(skip, 0, height, height);
if (const auto emoji = Ui::Emoji::Find(reaction)) {
// #TODO reactions
// #TODO reactions
if (const auto emoji = Ui::Emoji::Find(reaction.emoji())) {
const auto size = Ui::Emoji::GetSizeNormal();
const auto shift = (height - (size / factor)) / 2;
Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift);
} else {
using Type = Ui::WhoReadType;
(reaction.isEmpty()
(reaction.emoji().isEmpty()
? (state->selected
? st::reactionsTabAllSelected
: st::reactionsTabAll)
@ -102,20 +105,20 @@ not_null<Ui::AbstractButton*> CreateTab(
not_null<Selector*> CreateReactionSelector(
not_null<QWidget*> parent,
const base::flat_map<QString, int> &items,
const QString &selected,
const base::flat_map<ReactionId, int> &items,
const ReactionId &selected,
Ui::WhoReadType whoReadType) {
struct State {
rpl::variable<QString> selected;
rpl::variable<ReactionId> selected;
std::vector<not_null<Ui::AbstractButton*>> tabs;
};
const auto result = Ui::CreateChild<Selector>(parent.get());
using Entry = std::pair<int, QString>;
using Entry = std::pair<int, ReactionId>;
auto tabs = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto st = &st::reactionsTabs;
const auto state = tabs->lifetime().make_state<State>();
state->selected = selected;
const auto append = [&](const QString &reaction, int count) {
const auto append = [&](const ReactionId &reaction, int count) {
using namespace rpl::mappers;
const auto tab = CreateTab(
tabs,
@ -131,7 +134,7 @@ not_null<Selector*> CreateReactionSelector(
};
auto sorted = std::vector<Entry>();
for (const auto &[reaction, count] : items) {
if (reaction == u"read"_q) {
if (reaction.emoji() == u"read"_q) {
append(reaction, count);
} else {
sorted.emplace_back(count, reaction);
@ -143,7 +146,7 @@ not_null<Selector*> CreateReactionSelector(
0,
std::plus<>(),
&Entry::first);
append(QString(), count);
append(ReactionId(), count);
for (const auto &[count, reaction] : sorted) {
append(reaction, count);
}

View file

@ -11,19 +11,23 @@ namespace Ui {
enum class WhoReadType;
} // namespace Ui
namespace Data {
struct ReactionId;
} // namespace Data
namespace HistoryView {
struct Selector {
Fn<void(int, int)> move;
Fn<void(int)> resizeToWidth;
Fn<rpl::producer<QString>()> changes;
Fn<rpl::producer<Data::ReactionId>()> changes;
Fn<rpl::producer<int>()> heightValue;
};
not_null<Selector*> CreateReactionSelector(
not_null<QWidget*> parent,
const base::flat_map<QString, int> &items,
const QString &selected,
const base::flat_map<Data::ReactionId, int> &items,
const Data::ReactionId &selected,
Ui::WhoReadType whoReadType);
} // namespace HistoryView

View file

@ -16,7 +16,13 @@ namespace {
constexpr auto kVersion = 1;
} // namespace
QString ConfigDefaultReactionEmoji() {
static const auto result = QString::fromUtf8("\xf0\x9f\x91\x8d");
return result;
}
Config::Config(Environment environment) : _dcOptions(environment) {
_fields.webFileDcId = _dcOptions.isTestMode() ? 2 : 4;
_fields.txtDomainString = _dcOptions.isTestMode()
@ -31,11 +37,15 @@ Config::Config(const Config &other)
QByteArray Config::serialize() const {
auto options = _dcOptions.serialize();
auto size = sizeof(qint32) * 2; // version + environment
size += Serialize::bytearraySize(options);
size += 28 * sizeof(qint32);
size += Serialize::stringSize(_fields.internalLinksDomain);
size += Serialize::stringSize(_fields.txtDomainString);
auto size = sizeof(qint32) * 2 // version + environment
+ Serialize::bytearraySize(options)
+ 19 * sizeof(qint32)
+ Serialize::stringSize(_fields.internalLinksDomain)
+ 6 * sizeof(qint32)
+ Serialize::stringSize(_fields.txtDomainString)
+ 3 * sizeof(qint32)
+ Serialize::stringSize(_fields.reactionDefaultEmoji)
+ sizeof(quint64);
auto result = QByteArray();
result.reserve(size);
@ -77,7 +87,9 @@ QByteArray Config::serialize() const {
<< _fields.txtDomainString
<< qint32(1) // legacy phoneCallsEnabled
<< qint32(_fields.blockedMode ? 1 : 0)
<< qint32(_fields.captionLengthMax);
<< qint32(_fields.captionLengthMax)
<< _fields.reactionDefaultEmoji
<< quint64(_fields.reactionDefaultCustom);
}
return result;
}
@ -117,6 +129,10 @@ std::unique_ptr<Config> Config::FromSerialized(const QByteArray &serialized) {
auto value = qint32();
stream >> value;
field = value;
} else if constexpr (std::is_same_v<Type, uint64>) {
auto value = quint64();
stream >> value;
field = value;
} else if constexpr (std::is_same_v<Type, bool>
|| std::is_same_v<Type, rpl::variable<bool>>) {
auto value = qint32();
@ -161,6 +177,10 @@ std::unique_ptr<Config> Config::FromSerialized(const QByteArray &serialized) {
read(legacyPhoneCallsEnabled);
read(raw->_fields.blockedMode);
read(raw->_fields.captionLengthMax);
if (!stream.atEnd()) {
read(raw->_fields.reactionDefaultEmoji);
read(raw->_fields.reactionDefaultCustom);
}
if (stream.status() != QDataStream::Ok
|| !raw->_dcOptions.constructFromSerialized(dcOptionsSerialized)) {
@ -220,6 +240,16 @@ void Config::apply(const MTPDconfig &data) {
_fields.callPacketTimeoutMs = data.vcall_packet_timeout_ms().v;
_fields.blockedMode = data.is_blocked_mode();
_fields.captionLengthMax = data.vcaption_length_max().v;
_fields.reactionDefaultEmoji = ConfigDefaultReactionEmoji();
_fields.reactionDefaultCustom = 0;
if (const auto reaction = data.vreactions_default()) {
reaction->match([&](const MTPDreactionEmpty &) {
}, [&](const MTPDreactionEmoji &data) {
_fields.reactionDefaultEmoji = qs(data.vemoticon());
}, [&](const MTPDreactionCustomEmoji &data) {
_fields.reactionDefaultCustom = data.vdocument_id().v;
});
}
if (data.vdc_options().v.empty()) {
LOG(("MTP Error: config with empty dc_options received!"));

View file

@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace MTP {
[[nodiscard]] QString ConfigDefaultReactionEmoji();
struct ConfigFields {
int chatSizeMax = 200;
int megagroupSizeMax = 10000;
@ -41,6 +43,8 @@ struct ConfigFields {
QString txtDomainString;
bool blockedMode = false;
int captionLengthMax = 1024;
QString reactionDefaultEmoji = ConfigDefaultReactionEmoji();
uint64 reactionDefaultCustom;
};
class Config final {

View file

@ -905,23 +905,23 @@ void SetupMessages(
state->icons.lifetimes = std::vector<rpl::lifetime>(2);
const auto &reactions = controller->session().data().reactions();
auto emojiValue = rpl::single(
auto idValue = rpl::single(
reactions.favorite()
) | rpl::then(
reactions.updates() | rpl::map([=] {
return controller->session().data().reactions().favorite();
})
) | rpl::filter([](const QString &emoji) {
return !emoji.isEmpty();
) | rpl::filter([](const Data::ReactionId &id) {
return !id.empty();
});
auto selectedEmoji = rpl::duplicate(emojiValue);
auto selected = rpl::duplicate(idValue);
std::move(
selectedEmoji
) | rpl::start_with_next([=, emojiValue = std::move(emojiValue)](
const QString &emoji) {
selected
) | rpl::start_with_next([=, idValue = std::move(idValue)](
const Data::ReactionId &id) {
const auto &reactions = controller->session().data().reactions();
for (const auto &r : reactions.list(Data::Reactions::Type::All)) {
if (emoji != r.emoji) {
if (id != r.id) {
continue;
}
const auto index = state->icons.flag ? 1 : 0;
@ -941,7 +941,7 @@ void SetupMessages(
) | rpl::filter([=](not_null<QEvent*> event) {
return event->type() == QEvent::Enter;
}) | rpl::to_empty,
rpl::duplicate(emojiValue) | rpl::skip(1) | rpl::to_empty,
rpl::duplicate(idValue) | rpl::skip(1) | rpl::to_empty,
&state->icons.lifetimes[index]);
state->icons.flag = !state->icons.flag;
toggleButtonRight(true);

View file

@ -16,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h"
#include "lang/lang_keys.h"
#include "data/notify/data_notify_settings.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_message_reactions.h"
#include "data/data_document_media.h"
#include "data/data_session.h"
#include "data/data_channel.h"
@ -54,6 +56,11 @@ constexpr auto kSystemAlertDuration = crl::time(1000);
constexpr auto kSystemAlertDuration = crl::time(0);
#endif // Q_OS_MAC
[[nodiscard]] QString PlaceholderReactionText() {
static const auto result = QString::fromUtf8("\xf0\x9f\x92\xad");
return result;
}
QString TextWithPermanentSpoiler(const TextWithEntities &textWithEntities) {
auto text = textWithEntities.text;
for (const auto &e : textWithEntities.entities) {
@ -676,13 +683,13 @@ void System::showNext() {
= (notify->type == ItemNotificationType::Reaction);
const auto reaction = reactionNotification
? notify->item->lookupUnreadReaction(notify->reactionSender)
: QString();
if (!reactionNotification || !reaction.isEmpty()) {
: Data::ReactionId();
if (!reactionNotification || !reaction.empty()) {
_manager->showNotification({
.item = notify->item,
.forwardedCount = forwardedCount,
.reactionFrom = notify->reactionSender,
.reactionEmoji = reaction,
.reactionId = reaction,
});
}
}
@ -776,10 +783,34 @@ Manager::DisplayOptions Manager::getNotificationOptions(
TextWithEntities Manager::ComposeReactionNotification(
not_null<HistoryItem*> item,
const QString &reaction,
const Data::ReactionId &reaction,
bool hideContent) {
const auto reactionWithEntities = [&] {
if (const auto emoji = std::get_if<QString>(&reaction.data)) {
return TextWithEntities{ *emoji };
}
const auto id = v::get<DocumentId>(reaction.data);
auto entities = EntitiesInText();
const auto document = item->history()->owner().document(id);
const auto sticker = document->sticker();
const auto text = sticker ? sticker->alt : PlaceholderReactionText();
return TextWithEntities{
text,
{
EntityInText(
EntityType::CustomEmoji,
0,
text.size(),
Data::SerializeCustomEmojiId(Data::CustomEmojiId{ id }))
}
};
}();
const auto simple = [&](const auto &phrase) {
return TextWithEntities{ phrase(tr::now, lt_reaction, reaction) };
return phrase(
tr::now,
lt_reaction,
reactionWithEntities,
Ui::Text::WithEntities);
};
if (hideContent) {
return simple(tr::lng_reaction_notext);
@ -789,7 +820,7 @@ TextWithEntities Manager::ComposeReactionNotification(
return tr::lng_reaction_text(
tr::now,
lt_reaction,
Ui::Text::WithEntities(reaction),
reactionWithEntities,
lt_text,
item->notificationText(),
Ui::Text::WithEntities);
@ -808,14 +839,13 @@ TextWithEntities Manager::ComposeReactionNotification(
} else if (document->isVideoFile()) {
return simple(tr::lng_reaction_video);
} else if (const auto sticker = document->sticker()) {
return {
tr::lng_reaction_sticker(
tr::now,
lt_reaction,
reaction,
lt_emoji,
sticker->alt)
};
return tr::lng_reaction_sticker(
tr::now,
lt_reaction,
reactionWithEntities,
lt_emoji,
Ui::Text::WithEntities(sticker->alt),
Ui::Text::WithEntities);
}
return simple(tr::lng_reaction_document);
} else if (const auto contact = media->sharedContact()) {
@ -829,26 +859,26 @@ TextWithEntities Manager::ComposeReactionNotification(
contact->firstName,
lt_last_name,
contact->lastName);
return {
tr::lng_reaction_contact(
tr::now,
lt_reaction,
reaction,
lt_name,
name)
};
return tr::lng_reaction_contact(
tr::now,
lt_reaction,
reactionWithEntities,
lt_name,
Ui::Text::WithEntities(name),
Ui::Text::WithEntities);
} else if (media->location()) {
return simple(tr::lng_reaction_location);
// lng_reaction_live_location not used right now :(
} else if (const auto poll = media->poll()) {
return {
(poll->quiz() ? tr::lng_reaction_quiz : tr::lng_reaction_poll)(
return (poll->quiz()
? tr::lng_reaction_quiz
: tr::lng_reaction_poll)(
tr::now,
lt_reaction,
reaction,
reactionWithEntities,
lt_title,
poll->question)
};
Ui::Text::WithEntities(poll->question),
Ui::Text::WithEntities);
} else if (media->game()) {
return simple(tr::lng_reaction_game);
} else if (media->invoice()) {
@ -1004,7 +1034,7 @@ void NativeManager::doShowNotification(NotificationFields &&fields) {
const auto text = reactionFrom
? TextWithPermanentSpoiler(ComposeReactionNotification(
item,
fields.reactionEmoji,
fields.reactionId,
options.hideMessageText))
: options.hideMessageText
? tr::lng_notification_preview(tr::now)

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_message_reactions.h"
#include "base/observer.h"
#include "base/timer.h"
@ -224,7 +225,7 @@ public:
not_null<HistoryItem*> item;
int forwardedCount = 0;
PeerData *reactionFrom = nullptr;
QString reactionEmoji;
Data::ReactionId reactionId;
};
explicit Manager(not_null<System*> system) : _system(system) {
@ -268,7 +269,7 @@ public:
ItemNotificationType type) const;
[[nodiscard]] static TextWithEntities ComposeReactionNotification(
not_null<HistoryItem*> item,
const QString &reaction,
const Data::ReactionId &reaction,
bool hideContent);
[[nodiscard]] QString addTargetAccountName(

View file

@ -82,7 +82,7 @@ Manager::Manager(System *system)
Manager::QueuedNotification::QueuedNotification(NotificationFields &&fields)
: history(fields.item->history())
, peer(history->peer)
, reaction(fields.reactionEmoji)
, reaction(fields.reactionId)
, author(!fields.reactionFrom
? fields.item->notificationHeader()
: (fields.reactionFrom != peer)
@ -90,7 +90,7 @@ Manager::QueuedNotification::QueuedNotification(NotificationFields &&fields)
: QString())
, item((fields.forwardedCount < 2) ? fields.item.get() : nullptr)
, forwardedCount(fields.forwardedCount)
, fromScheduled(reaction.isEmpty() && (fields.item->out() || peer->isSelf())
, fromScheduled(reaction.empty() && (fields.item->out() || peer->isSelf())
&& fields.item->isFromScheduled()) {
}
@ -603,7 +603,7 @@ Notification::Notification(
not_null<PeerData*> peer,
const QString &author,
HistoryItem *item,
const QString &reaction,
const Data::ReactionId &reaction,
int forwardedCount,
bool fromScheduled,
QPoint startPosition,
@ -799,7 +799,7 @@ void Notification::updateNotifyDisplay() {
const auto options = manager()->getNotificationOptions(
_item,
(_reaction.isEmpty()
(_reaction.empty()
? ItemNotificationType::Message
: ItemNotificationType::Reaction));
_hideReplyButton = options.hideReplyButton;
@ -852,7 +852,7 @@ void Notification::updateNotifyDisplay() {
}
const auto composeText = !options.hideMessageText
|| (!_reaction.isEmpty() && !options.hideNameAndPhoto);
|| (!_reaction.empty() && !options.hideNameAndPhoto);
if (composeText) {
auto old = base::take(_textCache);
_textCache = Ui::Text::String(itemWidth);
@ -861,7 +861,7 @@ void Notification::updateNotifyDisplay() {
st::notifyItemTop + st::msgNameFont->height,
itemWidth,
2 * st::dialogsTextFont->height);
const auto text = !_reaction.isEmpty()
const auto text = !_reaction.empty()
? (!_author.isEmpty()
? Ui::Text::PlainLink(_author).append(' ')
: TextWithEntities()

View file

@ -113,7 +113,7 @@ private:
not_null<History*> history;
not_null<PeerData*> peer;
QString reaction;
Data::ReactionId reaction;
QString author;
HistoryItem *item = nullptr;
int forwardedCount = 0;
@ -208,7 +208,7 @@ public:
not_null<PeerData*> peer,
const QString &author,
HistoryItem *item,
const QString &reaction,
const Data::ReactionId &reaction,
int forwardedCount,
bool fromScheduled,
QPoint startPosition,
@ -284,7 +284,7 @@ private:
History *_history = nullptr;
std::shared_ptr<Data::CloudImageView> _userpicView;
QString _author;
QString _reaction;
Data::ReactionId _reaction;
HistoryItem *_item = nullptr;
int _forwardedCount = 0;
bool _fromScheduled = false;

View file

@ -346,15 +346,16 @@ bool ShowSendPremiumError(
[[nodiscard]] auto ExtractDisabledReactions(
not_null<PeerData*> peer,
const std::vector<Data::Reaction> &list)
-> base::flat_map<QString, ReactionDisableType> {
auto result = base::flat_map<QString, ReactionDisableType>();
-> base::flat_map<Data::ReactionId, ReactionDisableType> {
auto result = base::flat_map<Data::ReactionId, ReactionDisableType>();
const auto type = peer->isBroadcast()
? ReactionDisableType::Channel
: ReactionDisableType::Group;
if (const auto allowed = Data::PeerAllowedReactions(peer)) {
for (const auto &reaction : list) {
if (reaction.premium && !allowed->contains(reaction.emoji)) {
result.emplace(reaction.emoji, type);
if (reaction.premium
&& !allowed->contains(reaction.id.emoji())) {
result.emplace(reaction.id, type);
}
}
}
@ -364,13 +365,13 @@ bool ShowSendPremiumError(
bool ShowReactPremiumError(
not_null<SessionController*> controller,
not_null<HistoryItem*> item,
const QString &emoji) {
if (item->chosenReaction() == emoji || controller->session().premium()) {
const Data::ReactionId &id) {
if (item->chosenReaction() == id || controller->session().premium()) {
return false;
}
const auto &list = controller->session().data().reactions().list(
Data::Reactions::Type::Active);
const auto i = ranges::find(list, emoji, &Data::Reaction::emoji);
const auto i = ranges::find(list, id, &Data::Reaction::id);
if (i == end(list) || !i->premium) {
return false;
}

View file

@ -16,6 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class PeerData;
namespace Data {
struct ReactionId;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
@ -213,6 +217,6 @@ private:
[[nodiscard]] bool ShowReactPremiumError(
not_null<SessionController*> controller,
not_null<HistoryItem*> item,
const QString &emoji);
const Data::ReactionId &id);
} // namespace Window