mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Update API scheme on layer 145.
This commit is contained in:
parent
ba8673af5e
commit
60cc232884
40 changed files with 646 additions and 427 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!"));
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue