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