mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-19 06:26:55 +00:00
Show send action animations in Replies thread.
This commit is contained in:
parent
433c147dd0
commit
e8df47c926
@ -596,6 +596,8 @@ PRIVATE
|
||||
history/view/history_view_schedule_box.h
|
||||
history/view/history_view_scheduled_section.cpp
|
||||
history/view/history_view_scheduled_section.h
|
||||
history/view/history_view_send_action.cpp
|
||||
history/view/history_view_send_action.h
|
||||
history/view/history_view_service_message.cpp
|
||||
history/view/history_view_service_message.h
|
||||
history/view/history_view_top_bar_widget.cpp
|
||||
|
@ -1535,7 +1535,12 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
const auto user = session().data().userLoaded(d.vuser_id().v);
|
||||
if (history && user) {
|
||||
const auto when = requestingDifference() ? 0 : base::unixtime::now();
|
||||
session().data().registerSendAction(history, user, d.vaction(), when);
|
||||
session().data().registerSendAction(
|
||||
history,
|
||||
MsgId(),
|
||||
user,
|
||||
d.vaction(),
|
||||
when);
|
||||
}
|
||||
} break;
|
||||
|
||||
@ -1548,7 +1553,12 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
: session().data().userLoaded(d.vuser_id().v);
|
||||
if (history && user) {
|
||||
const auto when = requestingDifference() ? 0 : base::unixtime::now();
|
||||
session().data().registerSendAction(history, user, d.vaction(), when);
|
||||
session().data().registerSendAction(
|
||||
history,
|
||||
MsgId(),
|
||||
user,
|
||||
d.vaction(),
|
||||
when);
|
||||
}
|
||||
} break;
|
||||
|
||||
@ -1559,9 +1569,17 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
const auto user = (d.vuser_id().v == session().userId())
|
||||
? nullptr
|
||||
: session().data().userLoaded(d.vuser_id().v);
|
||||
if (history && user && !d.vtop_msg_id().value_or_empty()) {
|
||||
const auto when = requestingDifference() ? 0 : base::unixtime::now();
|
||||
session().data().registerSendAction(history, user, d.vaction(), when);
|
||||
if (history && user) {
|
||||
const auto when = requestingDifference()
|
||||
? 0
|
||||
: base::unixtime::now();
|
||||
const auto rootId = d.vtop_msg_id().value_or_empty();
|
||||
session().data().registerSendAction(
|
||||
history,
|
||||
rootId,
|
||||
user,
|
||||
d.vaction(),
|
||||
when);
|
||||
}
|
||||
} break;
|
||||
|
||||
|
@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/view/media/history_view_media.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_send_action.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_encrypted_file.h"
|
||||
@ -868,25 +869,97 @@ void Session::cancelForwarding(not_null<History*> history) {
|
||||
Data::HistoryUpdate::Flag::ForwardDraft);
|
||||
}
|
||||
|
||||
HistoryView::SendActionPainter *Session::lookupSendActionPainter(
|
||||
not_null<History*> history,
|
||||
MsgId rootId) {
|
||||
if (!rootId) {
|
||||
return history->sendActionPainter();
|
||||
}
|
||||
const auto i = _sendActionPainters.find(history);
|
||||
if (i == end(_sendActionPainters)) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto j = i->second.find(rootId);
|
||||
return (j == end(i->second)) ? nullptr : j->second.lock().get();
|
||||
}
|
||||
|
||||
void Session::registerSendAction(
|
||||
not_null<History*> history,
|
||||
MsgId rootId,
|
||||
not_null<UserData*> user,
|
||||
const MTPSendMessageAction &action,
|
||||
TimeId when) {
|
||||
if (history->updateSendActionNeedsAnimating(user, action)) {
|
||||
if (history->peer->isSelf()) {
|
||||
return;
|
||||
}
|
||||
const auto sendAction = lookupSendActionPainter(history, rootId);
|
||||
if (!sendAction) {
|
||||
return;
|
||||
}
|
||||
if (sendAction->updateNeedsAnimating(user, action)) {
|
||||
user->madeAction(when);
|
||||
|
||||
const auto i = _sendActions.find(history);
|
||||
if (!_sendActions.contains(history)) {
|
||||
_sendActions.emplace(history, crl::now());
|
||||
const auto i = _sendActions.find(std::pair{ history, rootId });
|
||||
if (!_sendActions.contains(std::pair{ history, rootId })) {
|
||||
_sendActions.emplace(std::pair{ history, rootId }, crl::now());
|
||||
_sendActionsAnimation.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Session::repliesSendActionPainter(
|
||||
not_null<History*> history,
|
||||
MsgId rootId)
|
||||
-> std::shared_ptr<SendActionPainter> {
|
||||
auto &weak = _sendActionPainters[history][rootId];
|
||||
if (auto strong = weak.lock()) {
|
||||
return strong;
|
||||
}
|
||||
auto result = std::make_shared<SendActionPainter>(history);
|
||||
weak = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Session::repliesSendActionPainterRemoved(
|
||||
not_null<History*> history,
|
||||
MsgId rootId) {
|
||||
const auto i = _sendActionPainters.find(history);
|
||||
if (i == end(_sendActionPainters)) {
|
||||
return;
|
||||
}
|
||||
const auto j = i->second.find(rootId);
|
||||
if (j == end(i->second) || j->second.lock()) {
|
||||
return;
|
||||
}
|
||||
i->second.erase(j);
|
||||
if (i->second.empty()) {
|
||||
_sendActionPainters.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void Session::repliesSendActionPaintersClear(
|
||||
not_null<History*> history,
|
||||
not_null<UserData*> user) {
|
||||
auto &map = _sendActionPainters[history];
|
||||
for (auto i = map.begin(); i != map.end();) {
|
||||
if (auto strong = i->second.lock()) {
|
||||
strong->clear(user);
|
||||
++i;
|
||||
} else {
|
||||
i = map.erase(i);
|
||||
}
|
||||
}
|
||||
if (map.empty()) {
|
||||
_sendActionPainters.erase(history);
|
||||
}
|
||||
}
|
||||
|
||||
bool Session::sendActionsAnimationCallback(crl::time now) {
|
||||
for (auto i = begin(_sendActions); i != end(_sendActions);) {
|
||||
if (i->first->updateSendActionNeedsAnimating(now)) {
|
||||
const auto sendAction = lookupSendActionPainter(
|
||||
i->first.first,
|
||||
i->first.second);
|
||||
if (sendAction->updateNeedsAnimating(now)) {
|
||||
++i;
|
||||
} else {
|
||||
i = _sendActions.erase(i);
|
||||
|
@ -31,6 +31,7 @@ namespace HistoryView {
|
||||
struct Group;
|
||||
class Element;
|
||||
class ElementDelegate;
|
||||
class SendActionPainter;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Main {
|
||||
@ -170,6 +171,7 @@ public:
|
||||
|
||||
void registerSendAction(
|
||||
not_null<History*> history,
|
||||
MsgId rootId,
|
||||
not_null<UserData*> user,
|
||||
const MTPSendMessageAction &action,
|
||||
TimeId when);
|
||||
@ -377,6 +379,17 @@ public:
|
||||
-> rpl::producer<SendActionAnimationUpdate>;
|
||||
void updateSendActionAnimation(SendActionAnimationUpdate &&update);
|
||||
|
||||
using SendActionPainter = HistoryView::SendActionPainter;
|
||||
[[nodiscard]] std::shared_ptr<SendActionPainter> repliesSendActionPainter(
|
||||
not_null<History*> history,
|
||||
MsgId rootId);
|
||||
void repliesSendActionPainterRemoved(
|
||||
not_null<History*> history,
|
||||
MsgId rootId);
|
||||
void repliesSendActionPaintersClear(
|
||||
not_null<History*> history,
|
||||
not_null<UserData*> user);
|
||||
|
||||
[[nodiscard]] int unreadBadge() const;
|
||||
[[nodiscard]] bool unreadBadgeMuted() const;
|
||||
[[nodiscard]] int unreadBadgeIgnoreOne(const Dialogs::Key &key) const;
|
||||
@ -759,6 +772,9 @@ private:
|
||||
TimeId date);
|
||||
|
||||
bool sendActionsAnimationCallback(crl::time now);
|
||||
[[nodiscard]] SendActionPainter *lookupSendActionPainter(
|
||||
not_null<History*> history,
|
||||
MsgId rootId);
|
||||
|
||||
void setWallpapers(const QVector<MTPWallPaper> &data, int32 hash);
|
||||
|
||||
@ -819,7 +835,9 @@ private:
|
||||
std::vector<FullMsgId> _selfDestructItems;
|
||||
|
||||
// When typing in this history started.
|
||||
base::flat_map<not_null<History*>, crl::time> _sendActions;
|
||||
base::flat_map<
|
||||
std::pair<not_null<History*>, MsgId>,
|
||||
crl::time> _sendActions;
|
||||
Ui::Animations::Basic _sendActionsAnimation;
|
||||
|
||||
std::unordered_map<
|
||||
@ -920,6 +938,11 @@ private:
|
||||
std::unique_ptr<Streaming> _streaming;
|
||||
std::unique_ptr<MediaRotation> _mediaRotation;
|
||||
std::unique_ptr<Histories> _histories;
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
base::flat_map<
|
||||
MsgId,
|
||||
std::weak_ptr<SendActionPainter>>> _sendActionPainters;
|
||||
std::unique_ptr<Stickers> _stickers;
|
||||
MsgId _nonHistoryEntryId = ServerMaxMsgId;
|
||||
|
||||
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "support/support_helper.h"
|
||||
#include "main/main_session.h"
|
||||
#include "history/view/history_view_send_action.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
@ -362,7 +363,7 @@ void paintRow(
|
||||
|
||||
p.setFont(st::dialogsTextFont);
|
||||
auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService);
|
||||
if (history && !history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) {
|
||||
if (history && !history->sendActionPainter()->paint(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) {
|
||||
if (history->cloudDraftTextCache.isEmpty()) {
|
||||
auto draftWrapped = textcmdLink(1, tr::lng_dialogs_text_from_wrapped(tr::now, lt_from, tr::lng_from_draft(tr::now)));
|
||||
auto draftText = supportMode
|
||||
@ -389,7 +390,7 @@ void paintRow(
|
||||
|
||||
auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService);
|
||||
p.setFont(st::dialogsTextFont);
|
||||
if (history && !history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) {
|
||||
if (history && !history->sendActionPainter()->paint(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) {
|
||||
// Empty history
|
||||
}
|
||||
} else if (!item->isEmpty()) {
|
||||
@ -741,7 +742,7 @@ void RowPainter::paint(
|
||||
texttop,
|
||||
availableWidth,
|
||||
st::dialogsTextFont->height);
|
||||
const auto actionWasPainted = history ? history->paintSendAction(
|
||||
const auto actionWasPainted = history ? history->sendActionPainter()->paint(
|
||||
p,
|
||||
itemRect.x(),
|
||||
itemRect.y(),
|
||||
|
@ -524,7 +524,8 @@ void Widget::refreshFolderTopBar() {
|
||||
}
|
||||
_folderTopBar->setActiveChat(
|
||||
_openedFolder,
|
||||
HistoryView::TopBarWidget::Section::History);
|
||||
HistoryView::TopBarWidget::Section::History,
|
||||
nullptr);
|
||||
} else {
|
||||
_folderTopBar.destroy();
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_types.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
#include "ui/effects/send_action_animations.h"
|
||||
#include "history/view/history_view_send_action.h"
|
||||
#include "base/observer.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/variant.h"
|
||||
@ -23,11 +23,6 @@ class HistoryItem;
|
||||
class HistoryMessage;
|
||||
class HistoryService;
|
||||
|
||||
namespace Api {
|
||||
enum class SendProgressType;
|
||||
struct SendProgress;
|
||||
} // namespace Api
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@ -278,22 +273,10 @@ public:
|
||||
bool hasPendingResizedItems() const;
|
||||
void setHasPendingResizedItems();
|
||||
|
||||
bool paintSendAction(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
style::color color,
|
||||
crl::time now);
|
||||
|
||||
// Interface for Histories
|
||||
bool updateSendActionNeedsAnimating(
|
||||
crl::time now,
|
||||
bool force = false);
|
||||
bool updateSendActionNeedsAnimating(
|
||||
not_null<UserData*> user,
|
||||
const MTPSendMessageAction &action);
|
||||
[[nodiscard]] auto sendActionPainter()
|
||||
-> not_null<HistoryView::SendActionPainter*> {
|
||||
return &_sendActionPainter;
|
||||
}
|
||||
|
||||
void clearLastKeyboard();
|
||||
|
||||
@ -514,7 +497,6 @@ private:
|
||||
void addEdgesToSharedMedia();
|
||||
|
||||
void addItemsToLists(const std::vector<not_null<HistoryItem*>> &items);
|
||||
void clearSendAction(not_null<UserData*> from);
|
||||
bool clearUnreadOnClientSide() const;
|
||||
bool skipUnreadUpdate() const;
|
||||
|
||||
@ -583,11 +565,7 @@ private:
|
||||
QString _topPromotedMessage;
|
||||
QString _topPromotedType;
|
||||
|
||||
base::flat_map<not_null<UserData*>, crl::time> _typing;
|
||||
base::flat_map<not_null<UserData*>, Api::SendProgress> _sendActions;
|
||||
QString _sendActionString;
|
||||
Ui::Text::String _sendActionText;
|
||||
Ui::SendActionAnimation _sendActionAnimation;
|
||||
HistoryView::SendActionPainter _sendActionPainter;
|
||||
|
||||
std::deque<not_null<HistoryItem*>> _notifications;
|
||||
|
||||
|
@ -1792,7 +1792,8 @@ void HistoryWidget::showHistory(
|
||||
|
||||
_topBar->setActiveChat(
|
||||
_history,
|
||||
HistoryView::TopBarWidget::Section::History);
|
||||
HistoryView::TopBarWidget::Section::History,
|
||||
_history->sendActionPainter());
|
||||
updateTopBarSelection();
|
||||
|
||||
if (_channel) {
|
||||
@ -1881,7 +1882,8 @@ void HistoryWidget::showHistory(
|
||||
} else {
|
||||
_topBar->setActiveChat(
|
||||
Dialogs::Key(),
|
||||
HistoryView::TopBarWidget::Section::History);
|
||||
HistoryView::TopBarWidget::Section::History,
|
||||
nullptr);
|
||||
updateTopBarSelection();
|
||||
|
||||
clearFieldText();
|
||||
|
@ -501,7 +501,8 @@ ComposeControls::ComposeControls(
|
||||
tr::lng_message_ph()))
|
||||
, _header(std::make_unique<FieldHeader>(
|
||||
_wrap.get(),
|
||||
&_window->session().data())) {
|
||||
&_window->session().data()))
|
||||
, _textUpdateEvents(TextUpdateEvent::SendTyping) {
|
||||
init();
|
||||
}
|
||||
|
||||
@ -640,13 +641,13 @@ void ComposeControls::clear() {
|
||||
}
|
||||
|
||||
void ComposeControls::setText(const TextWithTags &textWithTags) {
|
||||
//_textUpdateEvents = events;
|
||||
_textUpdateEvents = TextUpdateEvents();
|
||||
_field->setTextWithTags(textWithTags, Ui::InputField::HistoryAction::Clear/*fieldHistoryAction*/);
|
||||
auto cursor = _field->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
_field->setTextCursor(cursor);
|
||||
//_textUpdateEvents = TextUpdateEvent::SaveDraft
|
||||
// | TextUpdateEvent::SendTyping;
|
||||
_textUpdateEvents = /*TextUpdateEvent::SaveDraft
|
||||
| */TextUpdateEvent::SendTyping;
|
||||
|
||||
//previewCancel();
|
||||
//_previewCancelled = false;
|
||||
@ -738,7 +739,7 @@ void ComposeControls::initField() {
|
||||
//Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); });
|
||||
Ui::Connect(_field, &Ui::InputField::resized, [=] { updateHeight(); });
|
||||
//Ui::Connect(_field, &Ui::InputField::focused, [=] { fieldFocused(); });
|
||||
//Ui::Connect(_field, &Ui::InputField::changed, [=] { fieldChanged(); });
|
||||
Ui::Connect(_field, &Ui::InputField::changed, [=] { fieldChanged(); });
|
||||
InitMessageField(_window, _field);
|
||||
const auto suggestions = Ui::Emoji::SuggestionsController::Init(
|
||||
_parent,
|
||||
@ -747,6 +748,21 @@ void ComposeControls::initField() {
|
||||
_raiseEmojiSuggestions = [=] { suggestions->raise(); };
|
||||
}
|
||||
|
||||
void ComposeControls::fieldChanged() {
|
||||
if (/*!_inlineBot
|
||||
&& */!_header->isEditingMessage()
|
||||
&& (_textUpdateEvents & TextUpdateEvent::SendTyping)) {
|
||||
_sendActionUpdates.fire(Api::SendProgress{
|
||||
Api::SendProgressType::Typing,
|
||||
crl::now() + 5 * crl::time(1000),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<Api::SendProgress> ComposeControls::sendActionUpdates() const {
|
||||
return _sendActionUpdates.events();
|
||||
}
|
||||
|
||||
void ComposeControls::initTabbedSelector() {
|
||||
if (_window->hasTabbedSelectorOwnership()) {
|
||||
createTabbedPanel();
|
||||
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "api/api_common.h"
|
||||
#include "api/api_send_progress.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/effects/animations.h"
|
||||
@ -93,6 +94,7 @@ public:
|
||||
[[nodiscard]] rpl::producer<not_null<QKeyEvent*>> keyEvents() const;
|
||||
[[nodiscard]] auto inlineResultChosen() const
|
||||
-> rpl::producer<ChatHelpers::TabbedSelector::InlineChosen>;
|
||||
[[nodiscard]] rpl::producer<Api::SendProgress> sendActionUpdates() const;
|
||||
|
||||
using MimeDataHook = Fn<bool(
|
||||
not_null<const QMimeData*> data,
|
||||
@ -124,6 +126,13 @@ public:
|
||||
void hidePanelsAnimated();
|
||||
|
||||
private:
|
||||
enum class TextUpdateEvent {
|
||||
//SaveDraft = (1 << 0),
|
||||
SendTyping = (1 << 1),
|
||||
};
|
||||
using TextUpdateEvents = base::flags<TextUpdateEvent>;
|
||||
friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; };
|
||||
|
||||
void init();
|
||||
void initField();
|
||||
void initTabbedSelector();
|
||||
@ -136,6 +145,7 @@ private:
|
||||
void paintBackground(QRect clip);
|
||||
|
||||
void escape();
|
||||
void fieldChanged();
|
||||
void toggleTabbedSelectorMode();
|
||||
void createTabbedPanel();
|
||||
void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel);
|
||||
@ -163,8 +173,10 @@ private:
|
||||
rpl::event_stream<FileChosen> _fileChosen;
|
||||
rpl::event_stream<PhotoChosen> _photoChosen;
|
||||
rpl::event_stream<ChatHelpers::TabbedSelector::InlineChosen> _inlineResultChosen;
|
||||
rpl::event_stream<Api::SendProgress> _sendActionUpdates;
|
||||
|
||||
TextWithTags _localSavedText;
|
||||
TextUpdateEvents _textUpdateEvents;
|
||||
|
||||
//bool _recording = false;
|
||||
//bool _inField = false;
|
||||
|
@ -127,6 +127,7 @@ RepliesWidget::RepliesWidget(
|
||||
, _rootId(rootId)
|
||||
, _root(lookupRoot())
|
||||
, _areComments(computeAreComments())
|
||||
, _sendAction(history->owner().repliesSendActionPainter(history, rootId))
|
||||
, _topBar(this, controller)
|
||||
, _topBarShadow(this)
|
||||
, _composeControls(std::make_unique<ComposeControls>(
|
||||
@ -141,7 +142,10 @@ RepliesWidget::RepliesWidget(
|
||||
setupRoot();
|
||||
setupRootView();
|
||||
|
||||
_topBar->setActiveChat(_history, TopBarWidget::Section::Replies);
|
||||
_topBar->setActiveChat(
|
||||
_history,
|
||||
TopBarWidget::Section::Replies,
|
||||
_sendAction.get());
|
||||
|
||||
_topBar->move(0, 0);
|
||||
_topBar->resizeToWidth(width());
|
||||
@ -199,6 +203,14 @@ RepliesWidget::RepliesWidget(
|
||||
_composeControls->replyToMessage(fullId);
|
||||
}, _inner->lifetime());
|
||||
|
||||
_composeControls->sendActionUpdates(
|
||||
) | rpl::start_with_next([=] {
|
||||
session().sendProgressManager().update(
|
||||
_history,
|
||||
_rootId,
|
||||
Api::SendProgressType::Typing);
|
||||
}, lifetime());
|
||||
|
||||
_history->session().changes().messageUpdates(
|
||||
Data::MessageUpdate::Flag::Destroyed
|
||||
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||
@ -226,6 +238,8 @@ RepliesWidget::~RepliesWidget() {
|
||||
if (_readRequestTimer.isActive()) {
|
||||
sendReadTillRequest();
|
||||
}
|
||||
base::take(_sendAction);
|
||||
_history->owner().repliesSendActionPainterRemoved(_history, _rootId);
|
||||
}
|
||||
|
||||
void RepliesWidget::sendReadTillRequest() {
|
||||
@ -827,6 +841,12 @@ void RepliesWidget::send(Api::SendOptions options) {
|
||||
session().api().sendMessage(std::move(message));
|
||||
|
||||
_composeControls->clear();
|
||||
session().sendProgressManager().update(
|
||||
_history,
|
||||
_rootId,
|
||||
Api::SendProgressType::Typing,
|
||||
-1);
|
||||
|
||||
//_saveDraftText = true;
|
||||
//_saveDraftStart = crl::now();
|
||||
//onDraftSave();
|
||||
|
@ -57,6 +57,7 @@ class Element;
|
||||
class TopBarWidget;
|
||||
class RepliesMemento;
|
||||
class ComposeControls;
|
||||
class SendActionPainter;
|
||||
|
||||
class RepliesWidget final
|
||||
: public Window::SectionWidget
|
||||
@ -237,6 +238,7 @@ private:
|
||||
HistoryItem *_root = nullptr;
|
||||
std::shared_ptr<Data::RepliesList> _replies;
|
||||
rpl::variable<bool> _areComments = false;
|
||||
std::shared_ptr<SendActionPainter> _sendAction;
|
||||
QPointer<ListWidget> _inner;
|
||||
object_ptr<TopBarWidget> _topBar;
|
||||
object_ptr<Ui::PlainShadow> _topBarShadow;
|
||||
|
@ -104,7 +104,10 @@ ScheduledWidget::ScheduledWidget(
|
||||
controller,
|
||||
ComposeControls::Mode::Scheduled))
|
||||
, _scrollDown(_scroll, st::historyToDown) {
|
||||
_topBar->setActiveChat(_history, TopBarWidget::Section::Scheduled);
|
||||
_topBar->setActiveChat(
|
||||
_history,
|
||||
TopBarWidget::Section::Scheduled,
|
||||
nullptr);
|
||||
|
||||
_topBar->move(0, 0);
|
||||
_topBar->resizeToWidth(width());
|
||||
|
267
Telegram/SourceFiles/history/view/history_view_send_action.cpp
Normal file
267
Telegram/SourceFiles/history/view/history_view_send_action.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
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 "history/view/history_view_send_action.h"
|
||||
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace HistoryView {
|
||||
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;
|
||||
|
||||
} // namespace
|
||||
|
||||
SendActionPainter::SendActionPainter(not_null<History*> history)
|
||||
: _history(history)
|
||||
, _sendActionText(st::dialogsTextWidthMin) {
|
||||
}
|
||||
|
||||
bool SendActionPainter::updateNeedsAnimating(
|
||||
not_null<UserData*> user,
|
||||
const MTPSendMessageAction &action) {
|
||||
using Type = Api::SendProgressType;
|
||||
if (action.type() == mtpc_sendMessageCancelAction) {
|
||||
clear(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 updateNeedsAnimating(now, true);
|
||||
}
|
||||
|
||||
bool SendActionPainter::paint(
|
||||
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 SendActionPainter::updateNeedsAnimating(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 = _history->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,
|
||||
_history->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 = _history->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())) {
|
||||
_history->peer->owner().updateSendActionAnimation({
|
||||
_history,
|
||||
_sendActionAnimation.width(),
|
||||
st::normalFont->height,
|
||||
changed
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void SendActionPainter::clear(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) {
|
||||
updateNeedsAnimating(updateAtMs, true);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
53
Telegram/SourceFiles/history/view/history_view_send_action.h
Normal file
53
Telegram/SourceFiles/history/view/history_view_send_action.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
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 "ui/effects/send_action_animations.h"
|
||||
#include "api/api_send_progress.h"
|
||||
|
||||
class UserData;
|
||||
|
||||
namespace Api {
|
||||
enum class SendProgressType;
|
||||
struct SendProgress;
|
||||
} // namespace Api
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class SendActionPainter final {
|
||||
public:
|
||||
explicit SendActionPainter(not_null<History*> history);
|
||||
|
||||
bool paint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
style::color color,
|
||||
crl::time now);
|
||||
|
||||
bool updateNeedsAnimating(
|
||||
crl::time now,
|
||||
bool force = false);
|
||||
bool updateNeedsAnimating(
|
||||
not_null<UserData*> user,
|
||||
const MTPSendMessageAction &action);
|
||||
void clear(not_null<UserData*> from);
|
||||
|
||||
private:
|
||||
const not_null<History*> _history;
|
||||
base::flat_map<not_null<UserData*>, crl::time> _typing;
|
||||
base::flat_map<not_null<UserData*>, Api::SendProgress> _sendActions;
|
||||
QString _sendActionString;
|
||||
Ui::Text::String _sendActionText;
|
||||
Ui::SendActionAnimation _sendActionAnimation;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <rpl/combine.h>
|
||||
#include <rpl/combine_previous.h>
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_send_action.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "info/info_memento.h"
|
||||
@ -330,12 +331,9 @@ void TopBarWidget::paintTopBar(Painter &p) {
|
||||
const auto folder = _activeChat.folder();
|
||||
if (folder
|
||||
|| history->peer->sharedMediaInfo()
|
||||
|| (_section == Section::Scheduled)
|
||||
|| !_customTitleText.isEmpty()) {
|
||||
|| (_section == Section::Scheduled)) {
|
||||
// #TODO feed name emoji.
|
||||
auto text = !_customTitleText.isEmpty()
|
||||
? _customTitleText
|
||||
: (_section == Section::Scheduled)
|
||||
auto text = (_section == Section::Scheduled)
|
||||
? ((history && history->peer->isSelf())
|
||||
? tr::lng_reminder_messages(tr::now)
|
||||
: tr::lng_scheduled_messages(tr::now))
|
||||
@ -355,6 +353,28 @@ void TopBarWidget::paintTopBar(Painter &p) {
|
||||
(height() - st::historySavedFont->height) / 2,
|
||||
width(),
|
||||
text);
|
||||
} else if (_section == Section::Replies) {
|
||||
p.setPen(st::dialogsNameFg);
|
||||
p.setFont(st::semiboldFont);
|
||||
p.drawTextLeft(
|
||||
nameleft,
|
||||
nametop,
|
||||
width(),
|
||||
tr::lng_manage_discussion_group(tr::now));
|
||||
|
||||
p.setFont(st::dialogsTextFont);
|
||||
if (!paintConnectingState(p, nameleft, statustop, width())
|
||||
&& !_sendAction->paint(
|
||||
p,
|
||||
nameleft,
|
||||
statustop,
|
||||
availableWidth,
|
||||
width(),
|
||||
st::historyStatusFgTyping,
|
||||
crl::now())) {
|
||||
p.setPen(st::historyStatusFg);
|
||||
p.drawTextLeft(nameleft, statustop, width(), _customTitleText);
|
||||
}
|
||||
} else if (const auto history = _activeChat.history()) {
|
||||
const auto peer = history->peer;
|
||||
const auto &text = peer->topBarNameText();
|
||||
@ -382,18 +402,15 @@ void TopBarWidget::paintTopBar(Painter &p) {
|
||||
namewidth);
|
||||
|
||||
p.setFont(st::dialogsTextFont);
|
||||
if (paintConnectingState(p, nameleft, statustop, width())) {
|
||||
return;
|
||||
} else if (history->paintSendAction(
|
||||
if (!paintConnectingState(p, nameleft, statustop, width())
|
||||
&& !_sendAction->paint(
|
||||
p,
|
||||
nameleft,
|
||||
statustop,
|
||||
availableWidth,
|
||||
availableWidth,
|
||||
width(),
|
||||
st::historyStatusFgTyping,
|
||||
crl::now())) {
|
||||
return;
|
||||
} else {
|
||||
paintStatus(p, nameleft, statustop, availableWidth, width());
|
||||
}
|
||||
}
|
||||
@ -486,12 +503,16 @@ void TopBarWidget::backClicked() {
|
||||
}
|
||||
}
|
||||
|
||||
void TopBarWidget::setActiveChat(Dialogs::Key chat, Section section) {
|
||||
void TopBarWidget::setActiveChat(
|
||||
Dialogs::Key chat,
|
||||
Section section,
|
||||
SendActionPainter *sendAction) {
|
||||
if (_activeChat == chat && _section == section) {
|
||||
return;
|
||||
}
|
||||
_activeChat = chat;
|
||||
_section = section;
|
||||
_sendAction = sendAction;
|
||||
_back->clearState();
|
||||
update();
|
||||
|
||||
|
@ -32,6 +32,8 @@ class SessionController;
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class SendActionPainter;
|
||||
|
||||
class TopBarWidget : public Ui::RpWidget, private base::Subscriber {
|
||||
public:
|
||||
struct SelectedState {
|
||||
@ -62,7 +64,10 @@ public:
|
||||
}
|
||||
void setAnimatingMode(bool enabled);
|
||||
|
||||
void setActiveChat(Dialogs::Key chat, Section section);
|
||||
void setActiveChat(
|
||||
Dialogs::Key chat,
|
||||
Section section,
|
||||
SendActionPainter *sendAction);
|
||||
void setCustomTitle(const QString &title);
|
||||
|
||||
rpl::producer<> forwardSelectionRequest() const {
|
||||
@ -159,6 +164,8 @@ private:
|
||||
bool _animatingMode = false;
|
||||
std::unique_ptr<Ui::InfiniteRadialAnimation> _connecting;
|
||||
|
||||
SendActionPainter *_sendAction = nullptr;
|
||||
|
||||
base::Timer _onlineUpdater;
|
||||
|
||||
rpl::event_stream<> _forwardSelection;
|
||||
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "core/mime_type.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
@ -235,11 +236,7 @@ void Uploader::processPhotoProgress(const FullMsgId &newId) {
|
||||
const auto photo = item->media()
|
||||
? item->media()->photo()
|
||||
: nullptr;
|
||||
session->sendProgressManager().update(
|
||||
item->history(),
|
||||
Api::SendProgressType::UploadPhoto,
|
||||
0);
|
||||
session->data().requestItemRepaint(item);
|
||||
sendProgressUpdate(item, Api::SendProgressType::UploadPhoto);
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,23 +251,14 @@ void Uploader::processDocumentProgress(const FullMsgId &newId) {
|
||||
const auto progress = (document && document->uploading())
|
||||
? document->uploadingData->offset
|
||||
: 0;
|
||||
|
||||
session->sendProgressManager().update(
|
||||
item->history(),
|
||||
sendAction,
|
||||
progress);
|
||||
session->data().requestItemRepaint(item);
|
||||
sendProgressUpdate(item, sendAction, progress);
|
||||
}
|
||||
}
|
||||
|
||||
void Uploader::processPhotoFailed(const FullMsgId &newId) {
|
||||
const auto session = &_api->session();
|
||||
if (const auto item = session->data().message(newId)) {
|
||||
session->sendProgressManager().update(
|
||||
item->history(),
|
||||
Api::SendProgressType::UploadPhoto,
|
||||
-1);
|
||||
session->data().requestItemRepaint(item);
|
||||
sendProgressUpdate(item, Api::SendProgressType::UploadPhoto, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -282,14 +270,25 @@ void Uploader::processDocumentFailed(const FullMsgId &newId) {
|
||||
const auto sendAction = (document && document->isVoiceMessage())
|
||||
? Api::SendProgressType::UploadVoice
|
||||
: Api::SendProgressType::UploadFile;
|
||||
session->sendProgressManager().update(
|
||||
item->history(),
|
||||
sendAction,
|
||||
-1);
|
||||
session->data().requestItemRepaint(item);
|
||||
sendProgressUpdate(item, sendAction, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void Uploader::sendProgressUpdate(
|
||||
not_null<HistoryItem*> item,
|
||||
Api::SendProgressType type,
|
||||
int progress) {
|
||||
const auto history = item->history();
|
||||
auto &manager = _api->session().sendProgressManager();
|
||||
manager.update(history, type, progress);
|
||||
if (const auto replyTo = item->replyToTop()) {
|
||||
if (history->peer->isMegagroup()) {
|
||||
manager.update(history, replyTo, type, progress);
|
||||
}
|
||||
}
|
||||
_api->session().data().requestItemRepaint(item);
|
||||
}
|
||||
|
||||
Uploader::~Uploader() {
|
||||
clear();
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ class ApiWrap;
|
||||
struct FileLoadResult;
|
||||
struct SendMediaReady;
|
||||
|
||||
namespace Api {
|
||||
enum class SendProgressType;
|
||||
} // namespace Api
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@ -128,7 +132,12 @@ private:
|
||||
|
||||
void currentFailed();
|
||||
|
||||
not_null<ApiWrap*> _api;
|
||||
void sendProgressUpdate(
|
||||
not_null<HistoryItem*> item,
|
||||
Api::SendProgressType type,
|
||||
int progress = 0);
|
||||
|
||||
const not_null<ApiWrap*> _api;
|
||||
base::flat_map<mtpRequestId, QByteArray> requestsSent;
|
||||
base::flat_map<mtpRequestId, int32> docRequestsSent;
|
||||
base::flat_map<mtpRequestId, int32> dcMap;
|
||||
|
Loading…
Reference in New Issue
Block a user