diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f644141483..94c2eacd1f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 60016d7a50..06c4790501 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.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; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index d9c5fec6da..ec057993fd 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -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) { Data::HistoryUpdate::Flag::ForwardDraft); } +HistoryView::SendActionPainter *Session::lookupSendActionPainter( + not_null 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, + MsgId rootId, not_null 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, + MsgId rootId) +-> std::shared_ptr { + auto &weak = _sendActionPainters[history][rootId]; + if (auto strong = weak.lock()) { + return strong; + } + auto result = std::make_shared(history); + weak = result; + return result; +} + +void Session::repliesSendActionPainterRemoved( + not_null 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, + not_null 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); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index e66a83c425..c95f20a1ba 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -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, + MsgId rootId, not_null user, const MTPSendMessageAction &action, TimeId when); @@ -377,6 +379,17 @@ public: -> rpl::producer; void updateSendActionAnimation(SendActionAnimationUpdate &&update); + using SendActionPainter = HistoryView::SendActionPainter; + [[nodiscard]] std::shared_ptr repliesSendActionPainter( + not_null history, + MsgId rootId); + void repliesSendActionPainterRemoved( + not_null history, + MsgId rootId); + void repliesSendActionPaintersClear( + not_null history, + not_null 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, + MsgId rootId); void setWallpapers(const QVector &data, int32 hash); @@ -819,7 +835,9 @@ private: std::vector _selfDestructItems; // When typing in this history started. - base::flat_map, crl::time> _sendActions; + base::flat_map< + std::pair, MsgId>, + crl::time> _sendActions; Ui::Animations::Basic _sendActionsAnimation; std::unordered_map< @@ -920,6 +938,11 @@ private: std::unique_ptr _streaming; std::unique_ptr _mediaRotation; std::unique_ptr _histories; + base::flat_map< + not_null, + base::flat_map< + MsgId, + std::weak_ptr>> _sendActionPainters; std::unique_ptr _stickers; MsgId _nonHistoryEntryId = ServerMaxMsgId; diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index 03dcae4d49..2ffb908855 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -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(), diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 769b0a1491..3e00d4b877 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -524,7 +524,8 @@ void Widget::refreshFolderTopBar() { } _folderTopBar->setActiveChat( _openedFolder, - HistoryView::TopBarWidget::Section::History); + HistoryView::TopBarWidget::Section::History, + nullptr); } else { _folderTopBar.destroy(); } diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 2369237378..7b71c06620 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -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 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::max(); @@ -350,219 +337,6 @@ void History::setForwardDraft(MessageIdsList &&items) { _forwardDraft = std::move(items); } -bool History::updateSendActionNeedsAnimating( - not_null 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 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 block, not_null view) { @@ -1266,7 +1023,8 @@ void History::newItemAdded(not_null 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()); } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 5b42c22e01..44718865d7 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -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 user, - const MTPSendMessageAction &action); + [[nodiscard]] auto sendActionPainter() + -> not_null { + return &_sendActionPainter; + } void clearLastKeyboard(); @@ -514,7 +497,6 @@ private: void addEdgesToSharedMedia(); void addItemsToLists(const std::vector> &items); - void clearSendAction(not_null from); bool clearUnreadOnClientSide() const; bool skipUnreadUpdate() const; @@ -583,11 +565,7 @@ private: QString _topPromotedMessage; QString _topPromotedType; - base::flat_map, crl::time> _typing; - base::flat_map, Api::SendProgress> _sendActions; - QString _sendActionString; - Ui::Text::String _sendActionText; - Ui::SendActionAnimation _sendActionAnimation; + HistoryView::SendActionPainter _sendActionPainter; std::deque> _notifications; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index c6480b3c83..622b64ed64 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -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(); diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp index bee9faf545..4bdbdf865b 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp @@ -501,7 +501,8 @@ ComposeControls::ComposeControls( tr::lng_message_ph())) , _header(std::make_unique( _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 ComposeControls::sendActionUpdates() const { + return _sendActionUpdates.events(); +} + void ComposeControls::initTabbedSelector() { if (_window->hasTabbedSelectorOwnership()) { createTabbedPanel(); diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/history_view_compose_controls.h index 9ed8abfde6..45697979ae 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.h @@ -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> keyEvents() const; [[nodiscard]] auto inlineResultChosen() const -> rpl::producer; + [[nodiscard]] rpl::producer sendActionUpdates() const; using MimeDataHook = Fn data, @@ -124,6 +126,13 @@ public: void hidePanelsAnimated(); private: + enum class TextUpdateEvent { + //SaveDraft = (1 << 0), + SendTyping = (1 << 1), + }; + using TextUpdateEvents = base::flags; + 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 panel); @@ -163,8 +173,10 @@ private: rpl::event_stream _fileChosen; rpl::event_stream _photoChosen; rpl::event_stream _inlineResultChosen; + rpl::event_stream _sendActionUpdates; TextWithTags _localSavedText; + TextUpdateEvents _textUpdateEvents; //bool _recording = false; //bool _inField = false; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index f7782b3419..58cc32e690 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -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( @@ -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(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 12b7f92f1e..46265705f1 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -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 _replies; rpl::variable _areComments = false; + std::shared_ptr _sendAction; QPointer _inner; object_ptr _topBar; object_ptr _topBarShadow; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 8a5f341ac5..ac4d01e425 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -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()); diff --git a/Telegram/SourceFiles/history/view/history_view_send_action.cpp b/Telegram/SourceFiles/history/view/history_view_send_action.cpp new file mode 100644 index 0000000000..c77a02156d --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_send_action.cpp @@ -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) +, _sendActionText(st::dialogsTextWidthMin) { +} + +bool SendActionPainter::updateNeedsAnimating( + not_null 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 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 diff --git a/Telegram/SourceFiles/history/view/history_view_send_action.h b/Telegram/SourceFiles/history/view/history_view_send_action.h new file mode 100644 index 0000000000..8f4ebcddb2 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_send_action.h @@ -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); + + 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 user, + const MTPSendMessageAction &action); + void clear(not_null from); + +private: + const not_null _history; + base::flat_map, crl::time> _typing; + base::flat_map, Api::SendProgress> _sendActions; + QString _sendActionString; + Ui::Text::String _sendActionText; + Ui::SendActionAnimation _sendActionAnimation; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index a1a6db939c..ea8ff143ad 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #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(); diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index 33cc263a77..cd77fbf169 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -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 _connecting; + SendActionPainter *_sendAction = nullptr; + base::Timer _onlineUpdater; rpl::event_stream<> _forwardSelection; diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp index 9ae16b8489..837f73a794 100644 --- a/Telegram/SourceFiles/storage/file_upload.cpp +++ b/Telegram/SourceFiles/storage/file_upload.cpp @@ -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 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(); } diff --git a/Telegram/SourceFiles/storage/file_upload.h b/Telegram/SourceFiles/storage/file_upload.h index 5e2d8c1b55..c90edb91b5 100644 --- a/Telegram/SourceFiles/storage/file_upload.h +++ b/Telegram/SourceFiles/storage/file_upload.h @@ -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 _api; + void sendProgressUpdate( + not_null item, + Api::SendProgressType type, + int progress = 0); + + const not_null _api; base::flat_map requestsSent; base::flat_map docRequestsSent; base::flat_map dcMap;