mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-31 14:38:15 +00:00
Show send action animations in Replies thread.
This commit is contained in:
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/history.h"
|
||||
|
||||
#include "api/api_send_progress.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history_message.h"
|
||||
#include "history/history_service.h"
|
||||
@@ -50,18 +49,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kStatusShowClientsideTyping = 6000;
|
||||
constexpr auto kStatusShowClientsideRecordVideo = 6000;
|
||||
constexpr auto kStatusShowClientsideUploadVideo = 6000;
|
||||
constexpr auto kStatusShowClientsideRecordVoice = 6000;
|
||||
constexpr auto kStatusShowClientsideUploadVoice = 6000;
|
||||
constexpr auto kStatusShowClientsideRecordRound = 6000;
|
||||
constexpr auto kStatusShowClientsideUploadRound = 6000;
|
||||
constexpr auto kStatusShowClientsideUploadPhoto = 6000;
|
||||
constexpr auto kStatusShowClientsideUploadFile = 6000;
|
||||
constexpr auto kStatusShowClientsideChooseLocation = 6000;
|
||||
constexpr auto kStatusShowClientsideChooseContact = 6000;
|
||||
constexpr auto kStatusShowClientsidePlayGame = 10000;
|
||||
constexpr auto kNewBlockEachMessage = 50;
|
||||
constexpr auto kSkipCloudDraftsFor = TimeId(3);
|
||||
|
||||
@@ -74,7 +61,7 @@ History::History(not_null<Data::Session*> owner, PeerId peerId)
|
||||
, peer(owner->peer(peerId))
|
||||
, cloudDraftTextCache(st::dialogsTextWidthMin)
|
||||
, _mute(owner->notifyIsMuted(peer))
|
||||
, _sendActionText(st::dialogsTextWidthMin) {
|
||||
, _sendActionPainter(this) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (user->isBot()) {
|
||||
_outboxReadBefore = std::numeric_limits<MsgId>::max();
|
||||
@@ -350,219 +337,6 @@ void History::setForwardDraft(MessageIdsList &&items) {
|
||||
_forwardDraft = std::move(items);
|
||||
}
|
||||
|
||||
bool History::updateSendActionNeedsAnimating(
|
||||
not_null<UserData*> user,
|
||||
const MTPSendMessageAction &action) {
|
||||
if (peer->isSelf()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using Type = Api::SendProgressType;
|
||||
if (action.type() == mtpc_sendMessageCancelAction) {
|
||||
clearSendAction(user);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto now = crl::now();
|
||||
const auto emplaceAction = [&](
|
||||
Type type,
|
||||
crl::time duration,
|
||||
int progress = 0) {
|
||||
_sendActions.emplace_or_assign(user, type, now + duration, progress);
|
||||
};
|
||||
action.match([&](const MTPDsendMessageTypingAction &) {
|
||||
_typing.emplace_or_assign(user, now + kStatusShowClientsideTyping);
|
||||
}, [&](const MTPDsendMessageRecordVideoAction &) {
|
||||
emplaceAction(Type::RecordVideo, kStatusShowClientsideRecordVideo);
|
||||
}, [&](const MTPDsendMessageRecordAudioAction &) {
|
||||
emplaceAction(Type::RecordVoice, kStatusShowClientsideRecordVoice);
|
||||
}, [&](const MTPDsendMessageRecordRoundAction &) {
|
||||
emplaceAction(Type::RecordRound, kStatusShowClientsideRecordRound);
|
||||
}, [&](const MTPDsendMessageGeoLocationAction &) {
|
||||
emplaceAction(Type::ChooseLocation, kStatusShowClientsideChooseLocation);
|
||||
}, [&](const MTPDsendMessageChooseContactAction &) {
|
||||
emplaceAction(Type::ChooseContact, kStatusShowClientsideChooseContact);
|
||||
}, [&](const MTPDsendMessageUploadVideoAction &data) {
|
||||
emplaceAction(
|
||||
Type::UploadVideo,
|
||||
kStatusShowClientsideUploadVideo,
|
||||
data.vprogress().v);
|
||||
}, [&](const MTPDsendMessageUploadAudioAction &data) {
|
||||
emplaceAction(
|
||||
Type::UploadVoice,
|
||||
kStatusShowClientsideUploadVoice,
|
||||
data.vprogress().v);
|
||||
}, [&](const MTPDsendMessageUploadRoundAction &data) {
|
||||
emplaceAction(
|
||||
Type::UploadRound,
|
||||
kStatusShowClientsideUploadRound,
|
||||
data.vprogress().v);
|
||||
}, [&](const MTPDsendMessageUploadPhotoAction &data) {
|
||||
emplaceAction(
|
||||
Type::UploadPhoto,
|
||||
kStatusShowClientsideUploadPhoto,
|
||||
data.vprogress().v);
|
||||
}, [&](const MTPDsendMessageUploadDocumentAction &data) {
|
||||
emplaceAction(
|
||||
Type::UploadFile,
|
||||
kStatusShowClientsideUploadFile,
|
||||
data.vprogress().v);
|
||||
}, [&](const MTPDsendMessageGamePlayAction &) {
|
||||
const auto i = _sendActions.find(user);
|
||||
if ((i == end(_sendActions))
|
||||
|| (i->second.type == Type::PlayGame)
|
||||
|| (i->second.until <= now)) {
|
||||
emplaceAction(Type::PlayGame, kStatusShowClientsidePlayGame);
|
||||
}
|
||||
}, [&](const MTPDsendMessageCancelAction &) {
|
||||
Unexpected("CancelAction here.");
|
||||
});
|
||||
return updateSendActionNeedsAnimating(now, true);
|
||||
}
|
||||
|
||||
bool History::paintSendAction(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
style::color color,
|
||||
crl::time ms) {
|
||||
if (_sendActionAnimation) {
|
||||
_sendActionAnimation.paint(
|
||||
p,
|
||||
color,
|
||||
x,
|
||||
y + st::normalFont->ascent,
|
||||
outerWidth,
|
||||
ms);
|
||||
auto animationWidth = _sendActionAnimation.width();
|
||||
x += animationWidth;
|
||||
availableWidth -= animationWidth;
|
||||
p.setPen(color);
|
||||
_sendActionText.drawElided(p, x, y, availableWidth);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool History::updateSendActionNeedsAnimating(crl::time now, bool force) {
|
||||
auto changed = force;
|
||||
for (auto i = begin(_typing); i != end(_typing);) {
|
||||
if (now >= i->second) {
|
||||
i = _typing.erase(i);
|
||||
changed = true;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
for (auto i = begin(_sendActions); i != end(_sendActions);) {
|
||||
if (now >= i->second.until) {
|
||||
i = _sendActions.erase(i);
|
||||
changed = true;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
QString newTypingString;
|
||||
auto typingCount = _typing.size();
|
||||
if (typingCount > 2) {
|
||||
newTypingString = tr::lng_many_typing(tr::now, lt_count, typingCount);
|
||||
} else if (typingCount > 1) {
|
||||
newTypingString = tr::lng_users_typing(
|
||||
tr::now,
|
||||
lt_user,
|
||||
begin(_typing)->first->firstName,
|
||||
lt_second_user,
|
||||
(end(_typing) - 1)->first->firstName);
|
||||
} else if (typingCount) {
|
||||
newTypingString = peer->isUser()
|
||||
? tr::lng_typing(tr::now)
|
||||
: tr::lng_user_typing(
|
||||
tr::now,
|
||||
lt_user,
|
||||
begin(_typing)->first->firstName);
|
||||
} else if (!_sendActions.empty()) {
|
||||
// Handles all actions except game playing.
|
||||
using Type = Api::SendProgressType;
|
||||
auto sendActionString = [](Type type, const QString &name) -> QString {
|
||||
switch (type) {
|
||||
case Type::RecordVideo: return name.isEmpty() ? tr::lng_send_action_record_video(tr::now) : tr::lng_user_action_record_video(tr::now, lt_user, name);
|
||||
case Type::UploadVideo: return name.isEmpty() ? tr::lng_send_action_upload_video(tr::now) : tr::lng_user_action_upload_video(tr::now, lt_user, name);
|
||||
case Type::RecordVoice: return name.isEmpty() ? tr::lng_send_action_record_audio(tr::now) : tr::lng_user_action_record_audio(tr::now, lt_user, name);
|
||||
case Type::UploadVoice: return name.isEmpty() ? tr::lng_send_action_upload_audio(tr::now) : tr::lng_user_action_upload_audio(tr::now, lt_user, name);
|
||||
case Type::RecordRound: return name.isEmpty() ? tr::lng_send_action_record_round(tr::now) : tr::lng_user_action_record_round(tr::now, lt_user, name);
|
||||
case Type::UploadRound: return name.isEmpty() ? tr::lng_send_action_upload_round(tr::now) : tr::lng_user_action_upload_round(tr::now, lt_user, name);
|
||||
case Type::UploadPhoto: return name.isEmpty() ? tr::lng_send_action_upload_photo(tr::now) : tr::lng_user_action_upload_photo(tr::now, lt_user, name);
|
||||
case Type::UploadFile: return name.isEmpty() ? tr::lng_send_action_upload_file(tr::now) : tr::lng_user_action_upload_file(tr::now, lt_user, name);
|
||||
case Type::ChooseLocation:
|
||||
case Type::ChooseContact: return name.isEmpty() ? tr::lng_typing(tr::now) : tr::lng_user_typing(tr::now, lt_user, name);
|
||||
default: break;
|
||||
};
|
||||
return QString();
|
||||
};
|
||||
for (const auto [user, action] : _sendActions) {
|
||||
newTypingString = sendActionString(
|
||||
action.type,
|
||||
peer->isUser() ? QString() : user->firstName);
|
||||
if (!newTypingString.isEmpty()) {
|
||||
_sendActionAnimation.start(action.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Everyone in sendActions are playing a game.
|
||||
if (newTypingString.isEmpty()) {
|
||||
int playingCount = _sendActions.size();
|
||||
if (playingCount > 2) {
|
||||
newTypingString = tr::lng_many_playing_game(
|
||||
tr::now,
|
||||
lt_count,
|
||||
playingCount);
|
||||
} else if (playingCount > 1) {
|
||||
newTypingString = tr::lng_users_playing_game(
|
||||
tr::now,
|
||||
lt_user,
|
||||
begin(_sendActions)->first->firstName,
|
||||
lt_second_user,
|
||||
(end(_sendActions) - 1)->first->firstName);
|
||||
} else {
|
||||
newTypingString = peer->isUser()
|
||||
? tr::lng_playing_game(tr::now)
|
||||
: tr::lng_user_playing_game(
|
||||
tr::now,
|
||||
lt_user,
|
||||
begin(_sendActions)->first->firstName);
|
||||
}
|
||||
_sendActionAnimation.start(Type::PlayGame);
|
||||
}
|
||||
}
|
||||
if (typingCount > 0) {
|
||||
_sendActionAnimation.start(Api::SendProgressType::Typing);
|
||||
} else if (newTypingString.isEmpty()) {
|
||||
_sendActionAnimation.stop();
|
||||
}
|
||||
if (_sendActionString != newTypingString) {
|
||||
_sendActionString = newTypingString;
|
||||
_sendActionText.setText(
|
||||
st::dialogsTextStyle,
|
||||
_sendActionString,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
}
|
||||
const auto result = (!_typing.empty() || !_sendActions.empty());
|
||||
if (changed || (result && !anim::Disabled())) {
|
||||
owner().updateSendActionAnimation({
|
||||
this,
|
||||
_sendActionAnimation.width(),
|
||||
st::normalFont->height,
|
||||
changed
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HistoryItem *History::createItem(
|
||||
const MTPMessage &message,
|
||||
MTPDmessage_ClientFlags clientFlags,
|
||||
@@ -1229,23 +1003,6 @@ void History::applyServiceChanges(
|
||||
}
|
||||
}
|
||||
|
||||
void History::clearSendAction(not_null<UserData*> from) {
|
||||
auto updateAtMs = crl::time(0);
|
||||
auto i = _typing.find(from);
|
||||
if (i != _typing.cend()) {
|
||||
updateAtMs = crl::now();
|
||||
i->second = updateAtMs;
|
||||
}
|
||||
auto j = _sendActions.find(from);
|
||||
if (j != _sendActions.cend()) {
|
||||
if (!updateAtMs) updateAtMs = crl::now();
|
||||
j->second.until = updateAtMs;
|
||||
}
|
||||
if (updateAtMs) {
|
||||
updateSendActionNeedsAnimating(updateAtMs, true);
|
||||
}
|
||||
}
|
||||
|
||||
void History::mainViewRemoved(
|
||||
not_null<HistoryBlock*> block,
|
||||
not_null<HistoryView::Element*> view) {
|
||||
@@ -1266,7 +1023,8 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
|
||||
item->indexAsNewItem();
|
||||
if (const auto from = item->from() ? item->from()->asUser() : nullptr) {
|
||||
if (from == item->author()) {
|
||||
clearSendAction(from);
|
||||
_sendActionPainter.clear(from);
|
||||
owner().repliesSendActionPaintersClear(this, from);
|
||||
}
|
||||
from->madeAction(item->date());
|
||||
}
|
||||
|
Reference in New Issue
Block a user