mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-19 07:37:11 +02:00
Accumulate and send emoji interactions.
This commit is contained in:
parent
d152782115
commit
139b9723d7
9 changed files with 324 additions and 12 deletions
|
@ -285,6 +285,8 @@ PRIVATE
|
|||
chat_helpers/bot_command.h
|
||||
chat_helpers/bot_keyboard.cpp
|
||||
chat_helpers/bot_keyboard.h
|
||||
chat_helpers/emoji_interactions.cpp
|
||||
chat_helpers/emoji_interactions.h
|
||||
chat_helpers/emoji_keywords.cpp
|
||||
chat_helpers/emoji_keywords.h
|
||||
chat_helpers/emoji_list_widget.cpp
|
||||
|
|
223
Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp
Normal file
223
Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
|
||||
#include "chat_helpers/stickers_emoji_pack.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "base/random.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonValue>
|
||||
|
||||
namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMinDelay = crl::time(200);
|
||||
constexpr auto kAccumulateDelay = crl::time(1000);
|
||||
constexpr auto kTimeNever = std::numeric_limits<crl::time>::max();
|
||||
constexpr auto kVersion = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
auto EmojiInteractions::Combine(CheckResult a, CheckResult b) -> CheckResult {
|
||||
return {
|
||||
.nextCheckAt = std::min(a.nextCheckAt, b.nextCheckAt),
|
||||
.waitingForDownload = a.waitingForDownload || b.waitingForDownload,
|
||||
};
|
||||
}
|
||||
|
||||
EmojiInteractions::EmojiInteractions(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _checkTimer([=] { check(); }) {
|
||||
_session->changes().messageUpdates(
|
||||
Data::MessageUpdate::Flag::Destroyed
|
||||
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||
_animations.remove(update.item);
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
EmojiInteractions::~EmojiInteractions() = default;
|
||||
|
||||
void EmojiInteractions::start(not_null<const HistoryView::Element*> view) {
|
||||
const auto item = view->data();
|
||||
if (!IsServerMsgId(item->id) || !item->history()->peer->isUser()) {
|
||||
return;
|
||||
}
|
||||
const auto emoji = Ui::Emoji::Find(item->originalText().text);
|
||||
if (!emoji) {
|
||||
return;
|
||||
}
|
||||
const auto &pack = _session->emojiStickersPack();
|
||||
const auto &list = pack.animationsForEmoji(emoji);
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &animations = _animations[item];
|
||||
if (!animations.empty() && animations.front().emoji != emoji) {
|
||||
// The message was edited, forget the old emoji.
|
||||
animations.clear();
|
||||
}
|
||||
const auto last = !animations.empty() ? &animations.back() : nullptr;
|
||||
const auto listSize = int(list.size());
|
||||
const auto chooseDifferent = (last && listSize > 1);
|
||||
const auto index = chooseDifferent
|
||||
? base::RandomIndex(listSize - 1)
|
||||
: base::RandomIndex(listSize);
|
||||
const auto selected = (begin(list) + index)->second;
|
||||
const auto document = (chooseDifferent && selected == last->document)
|
||||
? (begin(list) + index + 1)->second
|
||||
: selected;
|
||||
const auto media = document->createMediaView();
|
||||
media->checkStickerLarge();
|
||||
const auto now = crl::now();
|
||||
animations.push_back({
|
||||
.emoji = emoji,
|
||||
.document = document,
|
||||
.media = media,
|
||||
.scheduledAt = now,
|
||||
.index = index,
|
||||
});
|
||||
check(now);
|
||||
}
|
||||
|
||||
auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult {
|
||||
auto nearest = kTimeNever;
|
||||
auto waitingForDownload = false;
|
||||
for (auto &[id, animations] : _animations) {
|
||||
auto lastStartedAt = crl::time();
|
||||
for (auto &animation : animations) {
|
||||
if (animation.startedAt) {
|
||||
lastStartedAt = animation.startedAt;
|
||||
} else if (!animation.media->loaded()) {
|
||||
animation.media->checkStickerLarge();
|
||||
waitingForDownload = true;
|
||||
break;
|
||||
} else if (!lastStartedAt || lastStartedAt + kMinDelay <= now) {
|
||||
animation.startedAt = now;
|
||||
|
||||
// #TODO interactions
|
||||
//const auto sticker = std::make_unique<HistoryView::Sticker>(
|
||||
// view,
|
||||
// document);
|
||||
break;
|
||||
} else {
|
||||
nearest = std::min(nearest, lastStartedAt + kMinDelay);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
.nextCheckAt = nearest,
|
||||
.waitingForDownload = waitingForDownload,
|
||||
};
|
||||
}
|
||||
|
||||
void EmojiInteractions::sendAccumulated(
|
||||
crl::time now,
|
||||
not_null<HistoryItem*> item,
|
||||
std::vector<Animation> &animations) {
|
||||
Expects(!animations.empty());
|
||||
|
||||
const auto firstStartedAt = animations.front().startedAt;
|
||||
const auto intervalEnd = firstStartedAt + kAccumulateDelay;
|
||||
if (intervalEnd > now) {
|
||||
return;
|
||||
}
|
||||
const auto from = begin(animations);
|
||||
const auto till = ranges::find_if(animations, [&](const auto &animation) {
|
||||
return !animation.startedAt || (animation.startedAt >= intervalEnd);
|
||||
});
|
||||
auto list = QJsonArray();
|
||||
for (const auto &animation : ranges::make_subrange(from, till)) {
|
||||
list.push_back(QJsonObject{
|
||||
{ "i", (animation.index + 1) },
|
||||
{ "t", (animation.startedAt - firstStartedAt) / 1000. },
|
||||
});
|
||||
}
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto json = QJsonDocument(QJsonObject{
|
||||
{ "v", kVersion },
|
||||
{ "a", std::move(list) },
|
||||
}).toJson(QJsonDocument::Compact);
|
||||
|
||||
_session->api().request(MTPmessages_SetTyping(
|
||||
MTP_flags(0),
|
||||
item->history()->peer->input,
|
||||
MTPint(), // top_msg_id
|
||||
MTP_sendMessageEmojiInteraction(
|
||||
MTP_string(from->emoji->text()),
|
||||
MTP_int(item->id),
|
||||
MTP_dataJSON(MTP_bytes(json)))
|
||||
)).send();
|
||||
animations.erase(from, till);
|
||||
}
|
||||
|
||||
auto EmojiInteractions::checkAccumulated(crl::time now) -> CheckResult {
|
||||
auto nearest = kTimeNever;
|
||||
for (auto i = begin(_animations); i != end(_animations);) {
|
||||
auto &[id, animations] = *i;
|
||||
sendAccumulated(now, id, animations);
|
||||
if (animations.empty()) {
|
||||
i = _animations.erase(i);
|
||||
continue;
|
||||
} else if (const auto firstStartedAt = animations.front().startedAt) {
|
||||
nearest = std::min(nearest, firstStartedAt + kAccumulateDelay);
|
||||
Assert(nearest > now);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return {
|
||||
.nextCheckAt = nearest,
|
||||
};
|
||||
}
|
||||
|
||||
void EmojiInteractions::check(crl::time now) {
|
||||
if (!now) {
|
||||
now = crl::now();
|
||||
}
|
||||
const auto result1 = checkAnimations(now);
|
||||
const auto result2 = checkAccumulated(now);
|
||||
const auto result = Combine(result1, result2);
|
||||
if (result.nextCheckAt < kTimeNever) {
|
||||
Assert(result.nextCheckAt > now);
|
||||
_checkTimer.callOnce(result.nextCheckAt - now);
|
||||
}
|
||||
setWaitingForDownload(result.waitingForDownload);
|
||||
}
|
||||
|
||||
void EmojiInteractions::setWaitingForDownload(bool waiting) {
|
||||
if (_waitingForDownload == waiting) {
|
||||
return;
|
||||
}
|
||||
_waitingForDownload = waiting;
|
||||
if (_waitingForDownload) {
|
||||
_session->downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
check();
|
||||
}, _downloadCheckLifetime);
|
||||
} else {
|
||||
_downloadCheckLifetime.destroy();
|
||||
_downloadCheckLifetime.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
74
Telegram/SourceFiles/chat_helpers/emoji_interactions.h
Normal file
74
Telegram/SourceFiles/chat_helpers/emoji_interactions.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
|
||||
class HistoryItem;
|
||||
class DocumentData;
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace HistoryView {
|
||||
class Element;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
class EmojiInteractions final {
|
||||
public:
|
||||
explicit EmojiInteractions(not_null<Main::Session*> session);
|
||||
~EmojiInteractions();
|
||||
|
||||
void start(not_null<const HistoryView::Element*> view);
|
||||
|
||||
private:
|
||||
struct Animation {
|
||||
EmojiPtr emoji;
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
crl::time scheduledAt = 0;
|
||||
crl::time startedAt = 0;
|
||||
int index = 0;
|
||||
};
|
||||
struct CheckResult {
|
||||
crl::time nextCheckAt = 0;
|
||||
bool waitingForDownload = false;
|
||||
};
|
||||
[[nodiscard]] static CheckResult Combine(CheckResult a, CheckResult b);
|
||||
|
||||
void check(crl::time now = 0);
|
||||
[[nodiscard]] CheckResult checkAnimations(crl::time now);
|
||||
[[nodiscard]] CheckResult checkAccumulated(crl::time now);
|
||||
void sendAccumulated(
|
||||
crl::time now,
|
||||
not_null<HistoryItem*> item,
|
||||
std::vector<Animation> &animations);
|
||||
void setWaitingForDownload(bool waiting);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::flat_map<
|
||||
not_null<HistoryItem*>,
|
||||
std::vector<Animation>> _animations;
|
||||
base::Timer _checkTimer;
|
||||
|
||||
bool _waitingForDownload = false;
|
||||
rpl::lifetime _downloadCheckLifetime;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace ChatHelpers
|
|
@ -32,14 +32,14 @@ constexpr auto kRefreshTimeout = 7200 * crl::time(1000);
|
|||
|
||||
[[nodiscard]] std::optional<int> IndexFromEmoticon(const QString &emoticon) {
|
||||
if (emoticon.size() < 2) {
|
||||
return -1;
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto first = emoticon[0].unicode();
|
||||
return (first >= '1' && first <= '9')
|
||||
? (first - '1')
|
||||
? std::make_optional(first - '1')
|
||||
: (first == 55357 && emoticon[1].unicode() == 56607)
|
||||
? 9
|
||||
: -1;
|
||||
? std::make_optional(9)
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] QSize SingleSize() {
|
||||
|
|
|
@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "history/history_inner_widget.h"
|
||||
|
||||
#include <rpl/merge.h>
|
||||
#include "core/file_utilities.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/click_handler_types.h"
|
||||
|
@ -44,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
#include "history/history_widget.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/unixtime.h"
|
||||
|
@ -2690,6 +2690,7 @@ void HistoryInner::elementReplyTo(const FullMsgId &to) {
|
|||
}
|
||||
|
||||
void HistoryInner::elementStartInteraction(not_null<const Element*> view) {
|
||||
_controller->emojiInteractions().start(view);
|
||||
}
|
||||
|
||||
auto HistoryInner::getSelectionState() const
|
||||
|
|
|
@ -295,14 +295,9 @@ void Sticker::refreshLink() {
|
|||
if (isEmojiSticker()) {
|
||||
const auto weak = base::make_weak(this);
|
||||
_link = std::make_shared<LambdaClickHandler>([weak] {
|
||||
const auto that = weak.get();
|
||||
if (!that || !that->_lottieOncePlayed) {
|
||||
return;
|
||||
if (const auto that = weak.get()) {
|
||||
that->emojiStickerClicked();
|
||||
}
|
||||
that->_parent->delegate()->elementStartInteraction(that->_parent);
|
||||
that->_lottieOncePlayed = false;
|
||||
that->_parent->history()->owner().requestViewRepaint(
|
||||
that->_parent);
|
||||
});
|
||||
} else if (sticker && sticker->set) {
|
||||
_link = std::make_shared<LambdaClickHandler>([document = _data](ClickContext context) {
|
||||
|
@ -328,6 +323,14 @@ void Sticker::refreshLink() {
|
|||
}
|
||||
}
|
||||
|
||||
void Sticker::emojiStickerClicked() {
|
||||
if (_lottie) {
|
||||
_parent->delegate()->elementStartInteraction(_parent);
|
||||
}
|
||||
_lottieOncePlayed = false;
|
||||
_parent->history()->owner().requestViewRepaint(_parent);
|
||||
}
|
||||
|
||||
void Sticker::ensureDataMediaCreated() const {
|
||||
if (_dataMedia) {
|
||||
return;
|
||||
|
|
|
@ -97,6 +97,7 @@ private:
|
|||
void setupLottie();
|
||||
void lottieCreated();
|
||||
void unloadLottie();
|
||||
void emojiStickerClicked();
|
||||
|
||||
const not_null<Element*> _parent;
|
||||
const not_null<DocumentData*> _data;
|
||||
|
|
|
@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_chat_filters.h"
|
||||
#include "passport/passport_form_controller.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
|
@ -518,6 +519,8 @@ SessionController::SessionController(
|
|||
not_null<Controller*> window)
|
||||
: SessionNavigation(session)
|
||||
, _window(window)
|
||||
, _emojiInteractions(
|
||||
std::make_unique<ChatHelpers::EmojiInteractions>(session))
|
||||
, _tabbedSelector(
|
||||
std::make_unique<ChatHelpers::TabbedSelector>(
|
||||
_window->widget(),
|
||||
|
|
|
@ -27,6 +27,7 @@ enum class WindowLayout;
|
|||
|
||||
namespace ChatHelpers {
|
||||
class TabbedSelector;
|
||||
class EmojiInteractions;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Main {
|
||||
|
@ -247,6 +248,9 @@ public:
|
|||
[[nodiscard]] not_null<::MainWindow*> widget() const;
|
||||
[[nodiscard]] not_null<MainWidget*> content() const;
|
||||
[[nodiscard]] Adaptive &adaptive() const;
|
||||
[[nodiscard]] ChatHelpers::EmojiInteractions &emojiInteractions() const {
|
||||
return *_emojiInteractions;
|
||||
}
|
||||
|
||||
// We need access to this from MainWidget::MainWidget, where
|
||||
// we can't call content() yet.
|
||||
|
@ -462,6 +466,7 @@ private:
|
|||
bool generateGradient = true) const;
|
||||
|
||||
const not_null<Controller*> _window;
|
||||
const std::unique_ptr<ChatHelpers::EmojiInteractions> _emojiInteractions;
|
||||
|
||||
std::unique_ptr<Passport::FormController> _passportForm;
|
||||
std::unique_ptr<FiltersMenu> _filters;
|
||||
|
|
Loading…
Add table
Reference in a new issue