mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Play incoming interactions.
This commit is contained in:
parent
703ea9aacd
commit
cfb43081c7
7 changed files with 293 additions and 54 deletions
|
@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_folder.h"
|
#include "data/data_folder.h"
|
||||||
#include "data/data_scheduled_messages.h"
|
#include "data/data_scheduled_messages.h"
|
||||||
#include "data/data_send_action.h"
|
#include "data/data_send_action.h"
|
||||||
|
#include "chat_helpers/emoji_interactions.h"
|
||||||
#include "lang/lang_cloud_manager.h"
|
#include "lang/lang_cloud_manager.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
@ -984,36 +985,24 @@ void Updates::handleSendActionUpdate(
|
||||||
const auto from = (fromId == session().userPeerId())
|
const auto from = (fromId == session().userPeerId())
|
||||||
? session().user().get()
|
? session().user().get()
|
||||||
: session().data().peerLoaded(fromId);
|
: session().data().peerLoaded(fromId);
|
||||||
const auto isSpeakingInCall = (action.type()
|
if (action.type() == mtpc_speakingInGroupCallAction) {
|
||||||
== mtpc_speakingInGroupCallAction);
|
handleSpeakingInCall(peer, fromId, from);
|
||||||
if (isSpeakingInCall) {
|
|
||||||
if (!peer->isChat() && !peer->isChannel()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto call = peer->groupCall();
|
|
||||||
const auto now = crl::now();
|
|
||||||
if (call) {
|
|
||||||
call->applyActiveUpdate(
|
|
||||||
fromId,
|
|
||||||
Data::LastSpokeTimes{ .anything = now, .voice = now },
|
|
||||||
from);
|
|
||||||
} else {
|
|
||||||
const auto chat = peer->asChat();
|
|
||||||
const auto channel = peer->asChannel();
|
|
||||||
const auto active = chat
|
|
||||||
? (chat->flags() & ChatDataFlag::CallActive)
|
|
||||||
: (channel->flags() & ChannelDataFlag::CallActive);
|
|
||||||
if (active) {
|
|
||||||
_pendingSpeakingCallParticipants.emplace(
|
|
||||||
peer).first->second[fromId] = now;
|
|
||||||
if (peerIsUser(fromId)) {
|
|
||||||
session().api().requestFullPeer(peer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!from || !from->isUser() || from->isSelf()) {
|
if (!from || !from->isUser() || from->isSelf()) {
|
||||||
return;
|
return;
|
||||||
|
} else if (action.type() == mtpc_sendMessageEmojiInteraction) {
|
||||||
|
const auto &data = action.c_sendMessageEmojiInteraction();
|
||||||
|
const auto json = data.vinteraction().match([&](
|
||||||
|
const MTPDdataJSON &data) {
|
||||||
|
return data.vdata().v;
|
||||||
|
});
|
||||||
|
const auto emoticon = qs(data.vemoticon());
|
||||||
|
handleEmojiInteraction(
|
||||||
|
peer,
|
||||||
|
data.vmsg_id().v,
|
||||||
|
qs(data.vemoticon()),
|
||||||
|
ChatHelpers::EmojiInteractions::Parse(json));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const auto when = requestingDifference()
|
const auto when = requestingDifference()
|
||||||
? 0
|
? 0
|
||||||
|
@ -1026,6 +1015,52 @@ void Updates::handleSendActionUpdate(
|
||||||
when);
|
when);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Updates::handleSpeakingInCall(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
PeerId participantPeerId,
|
||||||
|
PeerData *participantPeerLoaded) {
|
||||||
|
if (!peer->isChat() && !peer->isChannel()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto call = peer->groupCall();
|
||||||
|
const auto now = crl::now();
|
||||||
|
if (call) {
|
||||||
|
call->applyActiveUpdate(
|
||||||
|
participantPeerId,
|
||||||
|
Data::LastSpokeTimes{ .anything = now, .voice = now },
|
||||||
|
participantPeerLoaded);
|
||||||
|
} else {
|
||||||
|
const auto chat = peer->asChat();
|
||||||
|
const auto channel = peer->asChannel();
|
||||||
|
const auto active = chat
|
||||||
|
? (chat->flags() & ChatDataFlag::CallActive)
|
||||||
|
: (channel->flags() & ChannelDataFlag::CallActive);
|
||||||
|
if (active) {
|
||||||
|
_pendingSpeakingCallParticipants.emplace(
|
||||||
|
peer).first->second[participantPeerId] = now;
|
||||||
|
if (peerIsUser(participantPeerId)) {
|
||||||
|
session().api().requestFullPeer(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updates::handleEmojiInteraction(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
MsgId messageId,
|
||||||
|
const QString &emoticon,
|
||||||
|
ChatHelpers::EmojiInteractionsBunch bunch) {
|
||||||
|
if (session().windows().empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto window = session().windows().front();
|
||||||
|
window->emojiInteractions().startIncoming(
|
||||||
|
peer,
|
||||||
|
messageId,
|
||||||
|
emoticon,
|
||||||
|
std::move(bunch));
|
||||||
|
}
|
||||||
|
|
||||||
void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||||
switch (updates.type()) {
|
switch (updates.type()) {
|
||||||
case mtpc_updateShortMessage: {
|
case mtpc_updateShortMessage: {
|
||||||
|
|
|
@ -21,6 +21,10 @@ namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace ChatHelpers {
|
||||||
|
struct EmojiInteractionsBunch;
|
||||||
|
} // namespace ChatHelpers
|
||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
|
|
||||||
class Updates final {
|
class Updates final {
|
||||||
|
@ -139,6 +143,15 @@ private:
|
||||||
MsgId rootId,
|
MsgId rootId,
|
||||||
PeerId fromId,
|
PeerId fromId,
|
||||||
const MTPSendMessageAction &action);
|
const MTPSendMessageAction &action);
|
||||||
|
void handleSpeakingInCall(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
PeerId participantPeerId,
|
||||||
|
PeerData *participantPeerLoaded);
|
||||||
|
void handleEmojiInteraction(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
MsgId messageId,
|
||||||
|
const QString &emoticon,
|
||||||
|
ChatHelpers::EmojiInteractionsBunch bunch);
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "history/view/media/history_view_sticker.h"
|
#include "history/view/media/history_view_sticker.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
|
@ -33,7 +34,7 @@ constexpr auto kMinDelay = crl::time(200);
|
||||||
constexpr auto kAccumulateDelay = crl::time(1000);
|
constexpr auto kAccumulateDelay = crl::time(1000);
|
||||||
constexpr auto kMaxDelay = 2 * crl::time(1000);
|
constexpr auto kMaxDelay = 2 * crl::time(1000);
|
||||||
constexpr auto kTimeNever = std::numeric_limits<crl::time>::max();
|
constexpr auto kTimeNever = std::numeric_limits<crl::time>::max();
|
||||||
constexpr auto kVersion = 1;
|
constexpr auto kJsonVersion = 1;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -49,14 +50,32 @@ EmojiInteractions::EmojiInteractions(not_null<Main::Session*> session)
|
||||||
, _checkTimer([=] { check(); }) {
|
, _checkTimer([=] { check(); }) {
|
||||||
_session->changes().messageUpdates(
|
_session->changes().messageUpdates(
|
||||||
Data::MessageUpdate::Flag::Destroyed
|
Data::MessageUpdate::Flag::Destroyed
|
||||||
|
| Data::MessageUpdate::Flag::Edited
|
||||||
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||||
_animations.remove(update.item);
|
if (update.flags & Data::MessageUpdate::Flag::Destroyed) {
|
||||||
|
_outgoing.remove(update.item);
|
||||||
|
_incoming.remove(update.item);
|
||||||
|
} else if (update.flags & Data::MessageUpdate::Flag::Edited) {
|
||||||
|
checkEdition(update.item, _outgoing);
|
||||||
|
checkEdition(update.item, _incoming);
|
||||||
|
}
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
EmojiInteractions::~EmojiInteractions() = default;
|
EmojiInteractions::~EmojiInteractions() = default;
|
||||||
|
|
||||||
void EmojiInteractions::start(not_null<const HistoryView::Element*> view) {
|
void EmojiInteractions::checkEdition(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map) {
|
||||||
|
const auto i = map.find(item);
|
||||||
|
if (i != end(map)
|
||||||
|
&& (i->second.front().emoji
|
||||||
|
!= Ui::Emoji::Find(item->originalText().text))) {
|
||||||
|
map.erase(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiInteractions::startOutgoing(not_null<const HistoryView::Element*> view) {
|
||||||
const auto item = view->data();
|
const auto item = view->data();
|
||||||
if (!IsServerMsgId(item->id) || !item->history()->peer->isUser()) {
|
if (!IsServerMsgId(item->id) || !item->history()->peer->isUser()) {
|
||||||
return;
|
return;
|
||||||
|
@ -70,7 +89,7 @@ void EmojiInteractions::start(not_null<const HistoryView::Element*> view) {
|
||||||
if (list.empty()) {
|
if (list.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto &animations = _animations[item];
|
auto &animations = _outgoing[item];
|
||||||
if (!animations.empty() && animations.front().emoji != emoji) {
|
if (!animations.empty() && animations.front().emoji != emoji) {
|
||||||
// The message was edited, forget the old emoji.
|
// The message was edited, forget the old emoji.
|
||||||
animations.clear();
|
animations.clear();
|
||||||
|
@ -98,10 +117,76 @@ void EmojiInteractions::start(not_null<const HistoryView::Element*> view) {
|
||||||
check(now);
|
check(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmojiInteractions::startIncoming(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
MsgId messageId,
|
||||||
|
const QString &emoticon,
|
||||||
|
EmojiInteractionsBunch &&bunch) {
|
||||||
|
if (!peer->isUser()
|
||||||
|
|| bunch.interactions.empty()
|
||||||
|
|| !IsServerMsgId(messageId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto item = _session->data().message(nullptr, messageId);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto emoji = Ui::Emoji::Find(item->originalText().text);
|
||||||
|
if (!emoji || emoji != Ui::Emoji::Find(emoticon)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto &pack = _session->emojiStickersPack();
|
||||||
|
const auto &list = pack.animationsForEmoji(emoji);
|
||||||
|
if (list.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &animations = _incoming[item];
|
||||||
|
if (!animations.empty() && animations.front().emoji != emoji) {
|
||||||
|
// The message was edited, forget the old emoji.
|
||||||
|
animations.clear();
|
||||||
|
}
|
||||||
|
const auto now = crl::now();
|
||||||
|
for (const auto &single : bunch.interactions) {
|
||||||
|
const auto at = now + crl::time(std::round(single.time * 1000));
|
||||||
|
if (!animations.empty() && animations.back().scheduledAt >= at) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto last = !animations.empty() ? &animations.back() : nullptr;
|
||||||
|
const auto listSize = int(list.size());
|
||||||
|
const auto index = (single.index - 1);
|
||||||
|
if (index < listSize) {
|
||||||
|
const auto document = (begin(list) + index)->second;
|
||||||
|
const auto media = document->createMediaView();
|
||||||
|
media->checkStickerLarge();
|
||||||
|
animations.push_back({
|
||||||
|
.emoji = emoji,
|
||||||
|
.document = document,
|
||||||
|
.media = media,
|
||||||
|
.scheduledAt = at,
|
||||||
|
.index = index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (animations.empty()) {
|
||||||
|
_incoming.remove(item);
|
||||||
|
} else {
|
||||||
|
check(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult {
|
auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult {
|
||||||
|
return Combine(
|
||||||
|
checkAnimations(now, _outgoing),
|
||||||
|
checkAnimations(now, _incoming));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto EmojiInteractions::checkAnimations(
|
||||||
|
crl::time now,
|
||||||
|
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map
|
||||||
|
) -> CheckResult {
|
||||||
auto nearest = kTimeNever;
|
auto nearest = kTimeNever;
|
||||||
auto waitingForDownload = false;
|
auto waitingForDownload = false;
|
||||||
for (auto &[item, animations] : _animations) {
|
for (auto &[item, animations] : map) {
|
||||||
auto lastStartedAt = crl::time();
|
auto lastStartedAt = crl::time();
|
||||||
|
|
||||||
// Erase too old requests.
|
// Erase too old requests.
|
||||||
|
@ -138,7 +223,7 @@ auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmojiInteractions::sendAccumulated(
|
void EmojiInteractions::sendAccumulatedOutgoing(
|
||||||
crl::time now,
|
crl::time now,
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
std::vector<Animation> &animations) {
|
std::vector<Animation> &animations) {
|
||||||
|
@ -153,21 +238,17 @@ void EmojiInteractions::sendAccumulated(
|
||||||
const auto till = ranges::find_if(animations, [&](const auto &animation) {
|
const auto till = ranges::find_if(animations, [&](const auto &animation) {
|
||||||
return !animation.startedAt || (animation.startedAt >= intervalEnd);
|
return !animation.startedAt || (animation.startedAt >= intervalEnd);
|
||||||
});
|
});
|
||||||
auto list = QJsonArray();
|
auto bunch = EmojiInteractionsBunch();
|
||||||
|
bunch.interactions.reserve(till - from);
|
||||||
for (const auto &animation : ranges::make_subrange(from, till)) {
|
for (const auto &animation : ranges::make_subrange(from, till)) {
|
||||||
list.push_back(QJsonObject{
|
bunch.interactions.push_back({
|
||||||
{ "i", (animation.index + 1) },
|
.index = animation.index + 1,
|
||||||
{ "t", (animation.startedAt - firstStartedAt) / 1000. },
|
.time = (animation.startedAt - firstStartedAt) / 1000.,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (list.empty()) {
|
if (bunch.interactions.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto json = QJsonDocument(QJsonObject{
|
|
||||||
{ "v", kVersion },
|
|
||||||
{ "a", std::move(list) },
|
|
||||||
}).toJson(QJsonDocument::Compact);
|
|
||||||
|
|
||||||
_session->api().request(MTPmessages_SetTyping(
|
_session->api().request(MTPmessages_SetTyping(
|
||||||
MTP_flags(0),
|
MTP_flags(0),
|
||||||
item->history()->peer->input,
|
item->history()->peer->input,
|
||||||
|
@ -175,18 +256,31 @@ void EmojiInteractions::sendAccumulated(
|
||||||
MTP_sendMessageEmojiInteraction(
|
MTP_sendMessageEmojiInteraction(
|
||||||
MTP_string(from->emoji->text()),
|
MTP_string(from->emoji->text()),
|
||||||
MTP_int(item->id),
|
MTP_int(item->id),
|
||||||
MTP_dataJSON(MTP_bytes(json)))
|
MTP_dataJSON(MTP_bytes(ToJson(bunch))))
|
||||||
)).send();
|
)).send();
|
||||||
animations.erase(from, till);
|
animations.erase(from, till);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmojiInteractions::clearAccumulatedIncoming(
|
||||||
|
crl::time now,
|
||||||
|
std::vector<Animation> &animations) {
|
||||||
|
Expects(!animations.empty());
|
||||||
|
|
||||||
|
const auto from = begin(animations);
|
||||||
|
const auto till = ranges::find_if(animations, [&](const auto &animation) {
|
||||||
|
return !animation.startedAt
|
||||||
|
|| (animation.startedAt + kMinDelay) > now;
|
||||||
|
});
|
||||||
|
animations.erase(from, till);
|
||||||
|
}
|
||||||
|
|
||||||
auto EmojiInteractions::checkAccumulated(crl::time now) -> CheckResult {
|
auto EmojiInteractions::checkAccumulated(crl::time now) -> CheckResult {
|
||||||
auto nearest = kTimeNever;
|
auto nearest = kTimeNever;
|
||||||
for (auto i = begin(_animations); i != end(_animations);) {
|
for (auto i = begin(_outgoing); i != end(_outgoing);) {
|
||||||
auto &[item, animations] = *i;
|
auto &[item, animations] = *i;
|
||||||
sendAccumulated(now, item, animations);
|
sendAccumulatedOutgoing(now, item, animations);
|
||||||
if (animations.empty()) {
|
if (animations.empty()) {
|
||||||
i = _animations.erase(i);
|
i = _outgoing.erase(i);
|
||||||
continue;
|
continue;
|
||||||
} else if (const auto firstStartedAt = animations.front().startedAt) {
|
} else if (const auto firstStartedAt = animations.front().startedAt) {
|
||||||
nearest = std::min(nearest, firstStartedAt + kAccumulateDelay);
|
nearest = std::min(nearest, firstStartedAt + kAccumulateDelay);
|
||||||
|
@ -194,6 +288,18 @@ auto EmojiInteractions::checkAccumulated(crl::time now) -> CheckResult {
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
for (auto i = begin(_incoming); i != end(_incoming);) {
|
||||||
|
auto &[item, animations] = *i;
|
||||||
|
clearAccumulatedIncoming(now, animations);
|
||||||
|
if (animations.empty()) {
|
||||||
|
i = _incoming.erase(i);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// Doesn't really matter when, just clear them finally.
|
||||||
|
nearest = std::min(nearest, now + kAccumulateDelay);
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
.nextCheckAt = nearest,
|
.nextCheckAt = nearest,
|
||||||
};
|
};
|
||||||
|
@ -229,4 +335,58 @@ void EmojiInteractions::setWaitingForDownload(bool waiting) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EmojiInteractionsBunch EmojiInteractions::Parse(const QByteArray &json) {
|
||||||
|
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||||
|
const auto document = QJsonDocument::fromJson(json, &error);
|
||||||
|
if (error.error != QJsonParseError::NoError || !document.isObject()) {
|
||||||
|
LOG(("API Error: Bad interactions json received."));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto root = document.object();
|
||||||
|
const auto version = root.value("v").toInt();
|
||||||
|
if (version != kJsonVersion) {
|
||||||
|
LOG(("API Error: Bad interactions version: %1").arg(version));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto actions = root.value("a").toArray();
|
||||||
|
if (actions.empty()) {
|
||||||
|
LOG(("API Error: Empty interactions list."));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto result = EmojiInteractionsBunch();
|
||||||
|
for (const auto &interaction : actions) {
|
||||||
|
const auto object = interaction.toObject();
|
||||||
|
const auto index = object.value("i").toInt();
|
||||||
|
if (index < 0 || index > 10) {
|
||||||
|
LOG(("API Error: Bad interaction index: %1").arg(index));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto time = object.value("t").toDouble();
|
||||||
|
if (time < 0.
|
||||||
|
|| time > 1.
|
||||||
|
|| (!result.interactions.empty()
|
||||||
|
&& time <= result.interactions.back().time)) {
|
||||||
|
LOG(("API Error: Bad interaction time: %1").arg(time));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.interactions.push_back({ .index = index, .time = time });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray EmojiInteractions::ToJson(const EmojiInteractionsBunch &bunch) {
|
||||||
|
auto list = QJsonArray();
|
||||||
|
for (const auto &single : bunch.interactions) {
|
||||||
|
list.push_back(QJsonObject{
|
||||||
|
{ "i", single.index },
|
||||||
|
{ "t", single.time },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return QJsonDocument(QJsonObject{
|
||||||
|
{ "v", kJsonVersion },
|
||||||
|
{ "a", std::move(list) },
|
||||||
|
}).toJson(QJsonDocument::Compact);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ChatHelpers
|
} // namespace ChatHelpers
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
|
||||||
|
class PeerData;
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
class DocumentData;
|
class DocumentData;
|
||||||
|
|
||||||
|
@ -32,6 +33,14 @@ struct EmojiInteractionPlayRequest {
|
||||||
crl::time shouldHaveStartedAt = 0;
|
crl::time shouldHaveStartedAt = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct EmojiInteractionsBunch {
|
||||||
|
struct Single {
|
||||||
|
int index = 0;
|
||||||
|
double time = 0;
|
||||||
|
};
|
||||||
|
std::vector<Single> interactions;
|
||||||
|
};
|
||||||
|
|
||||||
class EmojiInteractions final {
|
class EmojiInteractions final {
|
||||||
public:
|
public:
|
||||||
explicit EmojiInteractions(not_null<Main::Session*> session);
|
explicit EmojiInteractions(not_null<Main::Session*> session);
|
||||||
|
@ -39,11 +48,21 @@ public:
|
||||||
|
|
||||||
using PlayRequest = EmojiInteractionPlayRequest;
|
using PlayRequest = EmojiInteractionPlayRequest;
|
||||||
|
|
||||||
void start(not_null<const HistoryView::Element*> view);
|
void startOutgoing(not_null<const HistoryView::Element*> view);
|
||||||
|
void startIncoming(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
MsgId messageId,
|
||||||
|
const QString &emoticon,
|
||||||
|
EmojiInteractionsBunch &&bunch);
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<PlayRequest> playRequests() const {
|
[[nodiscard]] rpl::producer<PlayRequest> playRequests() const {
|
||||||
return _playRequests.events();
|
return _playRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static EmojiInteractionsBunch Parse(const QByteArray &json);
|
||||||
|
[[nodiscard]] static QByteArray ToJson(
|
||||||
|
const EmojiInteractionsBunch &bunch);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Animation {
|
struct Animation {
|
||||||
EmojiPtr emoji;
|
EmojiPtr emoji;
|
||||||
|
@ -61,18 +80,27 @@ private:
|
||||||
|
|
||||||
void check(crl::time now = 0);
|
void check(crl::time now = 0);
|
||||||
[[nodiscard]] CheckResult checkAnimations(crl::time now);
|
[[nodiscard]] CheckResult checkAnimations(crl::time now);
|
||||||
|
[[nodiscard]] CheckResult checkAnimations(
|
||||||
|
crl::time now,
|
||||||
|
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map);
|
||||||
[[nodiscard]] CheckResult checkAccumulated(crl::time now);
|
[[nodiscard]] CheckResult checkAccumulated(crl::time now);
|
||||||
void sendAccumulated(
|
void sendAccumulatedOutgoing(
|
||||||
crl::time now,
|
crl::time now,
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
std::vector<Animation> &animations);
|
std::vector<Animation> &animations);
|
||||||
|
void clearAccumulatedIncoming(
|
||||||
|
crl::time now,
|
||||||
|
std::vector<Animation> &animations);
|
||||||
void setWaitingForDownload(bool waiting);
|
void setWaitingForDownload(bool waiting);
|
||||||
|
|
||||||
|
void checkEdition(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map);
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
|
|
||||||
base::flat_map<
|
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> _outgoing;
|
||||||
not_null<HistoryItem*>,
|
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> _incoming;
|
||||||
std::vector<Animation>> _animations;
|
|
||||||
base::Timer _checkTimer;
|
base::Timer _checkTimer;
|
||||||
rpl::event_stream<PlayRequest> _playRequests;
|
rpl::event_stream<PlayRequest> _playRequests;
|
||||||
|
|
||||||
|
|
|
@ -2720,7 +2720,7 @@ void HistoryInner::elementReplyTo(const FullMsgId &to) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryInner::elementStartInteraction(not_null<const Element*> view) {
|
void HistoryInner::elementStartInteraction(not_null<const Element*> view) {
|
||||||
_controller->emojiInteractions().start(view);
|
_controller->emojiInteractions().startOutgoing(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto HistoryInner::getSelectionState() const
|
auto HistoryInner::getSelectionState() const
|
||||||
|
|
|
@ -100,6 +100,9 @@ void EmojiInteractions::play(
|
||||||
.lottie = std::move(lottie),
|
.lottie = std::move(lottie),
|
||||||
.shift = shift,
|
.shift = shift,
|
||||||
});
|
});
|
||||||
|
if (const auto media = view->media()) {
|
||||||
|
media->stickerClearLoopPlayed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmojiInteractions::visibleAreaUpdated(
|
void EmojiInteractions::visibleAreaUpdated(
|
||||||
|
|
|
@ -119,7 +119,7 @@ bool SendActionPainter::updateNeedsAnimating(
|
||||||
Type::ChooseSticker,
|
Type::ChooseSticker,
|
||||||
kStatusShowClientsideChooseSticker);
|
kStatusShowClientsideChooseSticker);
|
||||||
}, [&](const MTPDsendMessageEmojiInteraction &) {
|
}, [&](const MTPDsendMessageEmojiInteraction &) {
|
||||||
// #TODO interaction
|
Unexpected("EmojiInteraction here.");
|
||||||
}, [&](const MTPDsendMessageEmojiInteractionSeen &) {
|
}, [&](const MTPDsendMessageEmojiInteractionSeen &) {
|
||||||
// #TODO interaction
|
// #TODO interaction
|
||||||
}, [&](const MTPDsendMessageCancelAction &) {
|
}, [&](const MTPDsendMessageCancelAction &) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue