Show emoji interaction seen status.

This commit is contained in:
John Preston 2021-09-17 00:14:53 +03:00
parent 4b7f594b0e
commit 34c0d97c54
7 changed files with 215 additions and 20 deletions

View File

@ -1618,6 +1618,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_user_action_upload_file" = "{user} is sending a file";
"lng_send_action_choose_sticker" = "choosing a sticker";
"lng_user_action_choose_sticker" = "{user} is choosing a sticker";
"lng_user_action_watching_animations" = "watching {emoji} animations";
"lng_unread_bar#one" = "{count} unread message";
"lng_unread_bar#other" = "{count} unread messages";
"lng_unread_bar_some" = "Unread messages";

View File

@ -991,17 +991,11 @@ void Updates::handleSendActionUpdate(
if (!from || !from->isUser() || from->isSelf()) {
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));
handleEmojiInteraction(peer, action.c_sendMessageEmojiInteraction());
return;
} else if (action.type() == mtpc_sendMessageEmojiInteractionSeen) {
const auto &data = action.c_sendMessageEmojiInteractionSeen();
handleEmojiInteraction(peer, qs(data.vemoticon()));
return;
}
const auto when = requestingDifference()
@ -1015,6 +1009,20 @@ void Updates::handleSendActionUpdate(
when);
}
void Updates::handleEmojiInteraction(
not_null<PeerData*> peer,
const MTPDsendMessageEmojiInteraction &data) {
const auto json = data.vinteraction().match([&](
const MTPDdataJSON &data) {
return data.vdata().v;
});
handleEmojiInteraction(
peer,
data.vmsg_id().v,
qs(data.vemoticon()),
ChatHelpers::EmojiInteractions::Parse(json));
}
void Updates::handleSpeakingInCall(
not_null<PeerData*> peer,
PeerId participantPeerId,
@ -1061,6 +1069,16 @@ void Updates::handleEmojiInteraction(
std::move(bunch));
}
void Updates::handleEmojiInteraction(
not_null<PeerData*> peer,
const QString &emoticon) {
if (session().windows().empty()) {
return;
}
const auto window = session().windows().front();
window->emojiInteractions().seenOutgoing(peer, emoticon);
}
void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
switch (updates.type()) {
case mtpc_updateShortMessage: {

View File

@ -143,6 +143,9 @@ private:
MsgId rootId,
PeerId fromId,
const MTPSendMessageAction &action);
void handleEmojiInteraction(
not_null<PeerData*> peer,
const MTPDsendMessageEmojiInteraction &data);
void handleSpeakingInCall(
not_null<PeerData*> peer,
PeerId participantPeerId,
@ -152,6 +155,9 @@ private:
MsgId messageId,
const QString &emoticon,
ChatHelpers::EmojiInteractionsBunch bunch);
void handleEmojiInteraction(
not_null<PeerData*> peer,
const QString &emoticon);
const not_null<Main::Session*> _session;

View File

@ -33,6 +33,7 @@ namespace {
constexpr auto kMinDelay = crl::time(200);
constexpr auto kAccumulateDelay = crl::time(1000);
constexpr auto kAccumulateSeenRequests = kAccumulateDelay;
constexpr auto kAcceptSeenSinceRequest = 3 * crl::time(1000);
constexpr auto kMaxDelay = 2 * crl::time(1000);
constexpr auto kTimeNever = std::numeric_limits<crl::time>::max();
constexpr auto kJsonVersion = 1;
@ -177,6 +178,21 @@ void EmojiInteractions::startIncoming(
}
}
void EmojiInteractions::seenOutgoing(
not_null<PeerData*> peer,
const QString &emoticon) {
if (const auto i = _playsSent.find(peer); i != end(_playsSent)) {
if (const auto emoji = Ui::Emoji::Find(emoticon)) {
if (const auto j = i->second.find(emoji); j != end(i->second)) {
const auto last = j->second.lastDoneReceivedAt;
if (!last || last + kAcceptSeenSinceRequest > crl::now()) {
_seen.fire({ peer, emoji });
}
}
}
}
}
auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult {
return Combine(
checkAnimations(now, _outgoing),
@ -254,15 +270,26 @@ void EmojiInteractions::sendAccumulatedOutgoing(
if (bunch.interactions.empty()) {
return;
}
_session->api().request(MTPmessages_SetTyping(
const auto peer = item->history()->peer;
const auto emoji = from->emoji;
const auto requestId = _session->api().request(MTPmessages_SetTyping(
MTP_flags(0),
item->history()->peer->input,
peer->input,
MTPint(), // top_msg_id
MTP_sendMessageEmojiInteraction(
MTP_string(from->emoji->text()),
MTP_string(emoji->text()),
MTP_int(item->id),
MTP_dataJSON(MTP_bytes(ToJson(bunch))))
)).send();
)).done([=](const MTPBool &result, mtpRequestId requestId) {
auto &sent = _playsSent[peer][emoji];
if (sent.lastRequestId == requestId) {
sent.lastDoneReceivedAt = crl::now();
if (!_checkTimer.isActive()) {
_checkTimer.callOnce(kAcceptSeenSinceRequest);
}
}
}).send();
_playsSent[peer][emoji] = PlaySent{ .lastRequestId = requestId };
animations.erase(from, till);
}
@ -315,6 +342,7 @@ void EmojiInteractions::check(crl::time now) {
now = crl::now();
}
checkSeenRequests(now);
checkSentRequests(now);
const auto result1 = checkAnimations(now);
const auto result2 = checkAccumulated(now);
const auto result = Combine(result1, result2);
@ -323,6 +351,8 @@ void EmojiInteractions::check(crl::time now) {
_checkTimer.callOnce(result.nextCheckAt - now);
} else if (!_playStarted.empty()) {
_checkTimer.callOnce(kAccumulateSeenRequests);
} else if (!_playsSent.empty()) {
_checkTimer.callOnce(kAcceptSeenSinceRequest);
}
setWaitingForDownload(result.waitingForDownload);
}
@ -344,6 +374,24 @@ void EmojiInteractions::checkSeenRequests(crl::time now) {
}
}
void EmojiInteractions::checkSentRequests(crl::time now) {
for (auto i = begin(_playsSent); i != end(_playsSent);) {
for (auto j = begin(i->second); j != end(i->second);) {
const auto last = j->second.lastDoneReceivedAt;
if (last && last + kAcceptSeenSinceRequest <= now) {
j = i->second.erase(j);
} else {
++j;
}
}
if (i->second.empty()) {
i = _playsSent.erase(i);
} else {
++i;
}
}
}
void EmojiInteractions::setWaitingForDownload(bool waiting) {
if (_waitingForDownload == waiting) {
return;

View File

@ -43,6 +43,11 @@ struct EmojiInteractionsBunch {
std::vector<Single> interactions;
};
struct EmojiInteractionSeen {
not_null<PeerData*> peer;
not_null<EmojiPtr> emoji;
};
class EmojiInteractions final {
public:
explicit EmojiInteractions(not_null<Main::Session*> session);
@ -57,6 +62,11 @@ public:
const QString &emoticon,
EmojiInteractionsBunch &&bunch);
void seenOutgoing(not_null<PeerData*> peer, const QString &emoticon);
[[nodiscard]] rpl::producer<EmojiInteractionSeen> seen() const {
return _seen.events();
}
[[nodiscard]] rpl::producer<PlayRequest> playRequests() const {
return _playRequests.events();
}
@ -68,7 +78,7 @@ public:
private:
struct Animation {
EmojiPtr emoji;
not_null<EmojiPtr> emoji;
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> media;
crl::time scheduledAt = 0;
@ -76,6 +86,10 @@ private:
bool incoming = false;
int index = 0;
};
struct PlaySent {
mtpRequestId lastRequestId = 0;
crl::time lastDoneReceivedAt = 0;
};
struct CheckResult {
crl::time nextCheckAt = 0;
bool waitingForDownload = false;
@ -98,6 +112,7 @@ private:
void setWaitingForDownload(bool waiting);
void checkSeenRequests(crl::time now);
void checkSentRequests(crl::time now);
void checkEdition(
not_null<HistoryItem*> item,
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map);
@ -111,6 +126,10 @@ private:
base::flat_map<
not_null<PeerData*>,
base::flat_map<QString, crl::time>> _playStarted;
base::flat_map<
not_null<PeerData*>,
base::flat_map<not_null<EmojiPtr>, PlaySent>> _playsSent;
rpl::event_stream<EmojiInteractionSeen> _seen;
bool _waitingForDownload = false;
rpl::lifetime _downloadCheckLifetime;

View File

@ -29,6 +29,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/radial_animation.h"
#include "ui/toasts/common_toasts.h"
#include "ui/boxes/report_box.h" // Ui::ReportReason
#include "ui/text/text.h"
#include "ui/text/text_options.h"
#include "ui/special_buttons.h"
#include "ui/unread_badge.h"
#include "ui/ui_utility.h"
@ -45,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_changes.h"
#include "data/data_send_action.h"
#include "chat_helpers/emoji_interactions.h"
#include "base/unixtime.h"
#include "support/support_helper.h"
#include "apiwrap.h"
@ -54,6 +57,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_info.h"
namespace HistoryView {
namespace {
constexpr auto kEmojiInteractionSeenDuration = 3 * crl::time(1000);
} // namespace
struct TopBarWidget::EmojiInteractionSeenAnimation {
Ui::SendActionAnimation animation;
Ui::Animations::Basic scheduler;
Ui::Text::String text = { st::dialogsTextWidthMin };
crl::time till = 0;
};
TopBarWidget::TopBarWidget(
QWidget *parent,
@ -402,6 +417,7 @@ void TopBarWidget::paintTopBar(Painter &p) {
return;
}
const auto now = crl::now();
const auto history = _activeChat.key.history();
const auto folder = _activeChat.key.folder();
if (folder
@ -442,14 +458,14 @@ void TopBarWidget::paintTopBar(Painter &p) {
p.setFont(st::dialogsTextFont);
if (!paintConnectingState(p, nameleft, statustop, width())
&& !_sendAction->paint(
&& !paintSendAction(
p,
nameleft,
statustop,
availableWidth,
width(),
st::historyStatusFgTyping,
crl::now())) {
now)) {
p.setPen(st::historyStatusFg);
p.drawTextLeft(nameleft, statustop, width(), _customTitleText);
}
@ -481,19 +497,48 @@ void TopBarWidget::paintTopBar(Painter &p) {
p.setFont(st::dialogsTextFont);
if (!paintConnectingState(p, nameleft, statustop, width())
&& !_sendAction->paint(
&& !paintSendAction(
p,
nameleft,
statustop,
availableWidth,
width(),
st::historyStatusFgTyping,
crl::now())) {
now)) {
paintStatus(p, nameleft, statustop, availableWidth, width());
}
}
}
bool TopBarWidget::paintSendAction(
Painter &p,
int x,
int y,
int availableWidth,
int outerWidth,
style::color fg,
crl::time now) {
const auto seen = _emojiInteractionSeen.get();
if (!seen || seen->till <= now) {
return _sendAction->paint(p, x, y, availableWidth, outerWidth, fg, now);
}
const auto animationWidth = seen->animation.width();
const auto extraAnimationWidth = animationWidth * 2;
seen->animation.paint(
p,
fg,
x,
y + st::normalFont->ascent,
outerWidth,
now);
x += animationWidth;
availableWidth -= extraAnimationWidth;
p.setPen(fg);
seen->text.drawElided(p, x, y, availableWidth);
return true;
}
bool TopBarWidget::paintConnectingState(
Painter &p,
int left,
@ -597,6 +642,7 @@ void TopBarWidget::setActiveChat(
update();
if (peerChanged) {
_emojiInteractionSeen = nullptr;
_activeChatLifetime.destroy();
if (const auto history = _activeChat.key.history()) {
session().changes().peerFlagsValue(
@ -615,6 +661,14 @@ void TopBarWidget::setActiveChat(
updateControlsVisibility();
updateControlsGeometry();
}, _activeChatLifetime);
using InteractionSeen = ChatHelpers::EmojiInteractionSeen;
_controller->emojiInteractions().seen(
) | rpl::filter([=](const InteractionSeen &seen) {
return (seen.peer == history->peer);
}) | rpl::start_with_next([=](const InteractionSeen &seen) {
handleEmojiInteractionSeen(seen.emoji->text());
}, lifetime());
}
}
updateUnreadBadge();
@ -628,6 +682,42 @@ void TopBarWidget::setActiveChat(
refreshUnreadBadge();
}
void TopBarWidget::handleEmojiInteractionSeen(const QString &emoticon) {
auto seen = _emojiInteractionSeen.get();
if (!seen) {
_emojiInteractionSeen
= std::make_unique<EmojiInteractionSeenAnimation>();
seen = _emojiInteractionSeen.get();
seen->animation.start(Ui::SendActionAnimation::Type::ChooseSticker);
seen->scheduler.init([=] {
if (seen->till <= crl::now()) {
crl::on_main(this, [=] {
if (_emojiInteractionSeen
&& _emojiInteractionSeen->till <= crl::now()) {
_emojiInteractionSeen = nullptr;
update();
}
});
} else {
const auto animationWidth = seen->animation.width();
const auto skip = st::topBarArrowPadding.bottom();
update(
_leftTaken,
st::topBarHeight - skip - st::dialogsTextFont->height,
seen->animation.width(),
st::dialogsTextFont->height);
}
});
seen->scheduler.start();
}
seen->till = crl::now() + kEmojiInteractionSeenDuration;
seen->text.setText(
st::dialogsTextStyle,
tr::lng_user_action_watching_animations(tr::now, lt_emoji, emoticon),
Ui::NameTextOptions());
update();
}
void TopBarWidget::setCustomTitle(const QString &title) {
if (_customTitleText != title) {
_customTitleText = title;

View File

@ -95,6 +95,8 @@ protected:
int resizeGetHeight(int newWidth) override;
private:
struct EmojiInteractionSeenAnimation;
void refreshInfoButton();
void refreshLang();
void updateSearchVisibility();
@ -109,6 +111,16 @@ private:
void showMenu();
void toggleInfoSection();
void handleEmojiInteractionSeen(const QString &emoticon);
bool paintSendAction(
Painter &p,
int x,
int y,
int availableWidth,
int outerWidth,
style::color fg,
crl::time now);
void updateConnectingState();
void updateAdaptiveLayout();
int countSelectedButtonsTop(float64 selectedShown);
@ -140,6 +152,7 @@ private:
const not_null<Window::SessionController*> _controller;
ActiveChat _activeChat;
QString _customTitleText;
std::unique_ptr<EmojiInteractionSeenAnimation> _emojiInteractionSeen;
rpl::lifetime _activeChatLifetime;
int _selectedCount = 0;