From 1ac051a8124ad8f93892400ad5f8061f5315b462 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 1 Nov 2022 08:46:31 +0400 Subject: [PATCH] Implement forwarding to topics. --- Telegram/CMakeLists.txt | 3 + Telegram/SourceFiles/api/api_common.cpp | 22 ++ Telegram/SourceFiles/api/api_common.h | 11 +- Telegram/SourceFiles/apiwrap.cpp | 7 +- Telegram/SourceFiles/boxes/peer_list_box.cpp | 45 ++- Telegram/SourceFiles/boxes/peer_list_box.h | 8 + .../boxes/peer_list_controllers.cpp | 284 ++++++++++++-- .../SourceFiles/boxes/peer_list_controllers.h | 107 ++++-- .../calls/group/calls_group_settings.cpp | 1 + Telegram/SourceFiles/data/data_changes.h | 14 +- Telegram/SourceFiles/data/data_session.cpp | 7 - Telegram/SourceFiles/data/data_session.h | 2 - Telegram/SourceFiles/dialogs/dialogs.style | 15 + .../dialogs/dialogs_inner_widget.cpp | 28 +- .../dialogs/dialogs_inner_widget.h | 3 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 28 +- Telegram/SourceFiles/history/history.cpp | 48 ++- Telegram/SourceFiles/history/history.h | 21 +- .../SourceFiles/history/history_message.cpp | 5 +- .../SourceFiles/history/history_message.h | 3 +- .../SourceFiles/history/history_widget.cpp | 259 ++----------- Telegram/SourceFiles/history/history_widget.h | 9 +- .../history_view_compose_controls.cpp | 177 +++++++-- .../controls/history_view_compose_controls.h | 6 +- .../controls/history_view_forward_panel.cpp | 362 ++++++++++++++++++ .../controls/history_view_forward_panel.h | 64 ++++ .../view/history_view_replies_section.cpp | 2 + .../inline_bots/bot_attach_web_view.cpp | 20 +- Telegram/SourceFiles/mainwidget.cpp | 200 +++++----- Telegram/SourceFiles/mainwidget.h | 23 +- .../touchbar/items/mac_pinned_chats_item.mm | 15 +- .../window/window_history_hider.cpp | 6 +- .../SourceFiles/window/window_history_hider.h | 10 +- .../SourceFiles/window/window_main_menu.cpp | 5 +- .../SourceFiles/window/window_peer_menu.cpp | 124 ++++-- .../SourceFiles/window/window_peer_menu.h | 6 + .../window/window_session_controller.cpp | 18 +- .../window/window_session_controller.h | 6 +- 38 files changed, 1413 insertions(+), 561 deletions(-) create mode 100644 Telegram/SourceFiles/api/api_common.cpp create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 0fb828dc24..168e068526 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -113,6 +113,7 @@ PRIVATE api/api_chat_participants.h api/api_cloud_password.cpp api/api_cloud_password.h + api/api_common.cpp api/api_common.h api/api_confirm_phone.cpp api/api_confirm_phone.h @@ -625,6 +626,8 @@ PRIVATE history/view/controls/history_view_compose_controls.h history/view/controls/history_view_compose_search.cpp history/view/controls/history_view_compose_search.h + history/view/controls/history_view_forward_panel.cpp + history/view/controls/history_view_forward_panel.h history/view/controls/history_view_ttl_button.cpp history/view/controls/history_view_ttl_button.h history/view/controls/history_view_voice_record_bar.cpp diff --git a/Telegram/SourceFiles/api/api_common.cpp b/Telegram/SourceFiles/api/api_common.cpp new file mode 100644 index 0000000000..2a819089d0 --- /dev/null +++ b/Telegram/SourceFiles/api/api_common.cpp @@ -0,0 +1,22 @@ +/* +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 "api/api_common.h" + +#include "data/data_thread.h" + +namespace Api { + +SendAction::SendAction( + not_null thread, + SendOptions options) +: history(thread->owningHistory()) +, options(options) +, topicRootId(thread->topicRootId()) { +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 65b3e7bceb..34631c8a8c 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; +namespace Data { +class Thread; +} // namespace Data + namespace Api { struct SendOptions { @@ -28,11 +32,8 @@ enum class SendType { struct SendAction { explicit SendAction( - not_null history, - SendOptions options = SendOptions()) - : history(history) - , options(options) { - } + not_null thread, + SendOptions options = SendOptions()); not_null history; SendOptions options; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 95e6ca3a1e..a1930194ca 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3049,7 +3049,7 @@ void ApiWrap::sendAction(const SendAction &action) { void ApiWrap::finishForwarding(const SendAction &action) { const auto history = action.history; - auto toForward = history->resolveForwardDraft(); + auto toForward = history->resolveForwardDraft(action.topicRootId); if (!toForward.items.empty()) { const auto error = GetErrorTextForSending( history->peer, @@ -3062,7 +3062,7 @@ void ApiWrap::finishForwarding(const SendAction &action) { } forwardMessages(std::move(toForward), action); - _session->data().cancelForwarding(history); + history->setForwardDraft(action.topicRootId, {}); } _session->data().sendHistoryChangeNotifications(); @@ -3199,7 +3199,8 @@ void ApiWrap::forwardMessages( HistoryItem::NewMessageDate(action.options.scheduled), messageFromId, messagePostAuthor, - item); // #TODO forum forward + item, + action.topicRootId); // #TODO forum forward _session->data().registerMessageRandomId(randomId, newId); if (!localIds) { localIds = std::make_shared>(); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 409f7d6165..089da5e903 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -331,6 +331,14 @@ void PeerListController::peerListSearchAddRow(not_null peer) { } } +void PeerListController::peerListSearchAddRow(PeerListRowId id) { + if (auto row = delegate()->peerListFindRow(id)) { + delegate()->peerListAppendFoundRow(row); + } else if (auto row = createSearchRow(id)) { + delegate()->peerListAppendSearchRow(std::move(row)); + } +} + void PeerListController::peerListSearchRefreshRows() { delegate()->peerListRefreshRows(); } @@ -359,7 +367,8 @@ void PeerListController::setSearchNoResultsText(const QString &text) { if (text.isEmpty()) { setSearchNoResults(nullptr); } else { - setSearchNoResults(object_ptr(nullptr, text, st::membersAbout)); + setSearchNoResults( + object_ptr(nullptr, text, st::membersAbout)); } } @@ -369,6 +378,14 @@ base::unique_qptr PeerListController::rowContextMenu( return nullptr; } +std::unique_ptr PeerListController::createSearchRow( + PeerListRowId id) { + if (const auto peer = session().data().peerLoaded(PeerId(id))) { + return createSearchRow(peer); + } + return nullptr; +} + std::unique_ptr PeerListController::saveState() const { return delegate()->peerListSaveState(); } @@ -648,6 +665,18 @@ PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback() { }; } + +auto PeerListRow::generateNameFirstLetters() const +-> const base::flat_set & { + return peer()->nameFirstLetters(); +} + +auto PeerListRow::generateNameWords() const +-> const base::flat_set & { + return peer()->nameWords(); +} + + void PeerListRow::invalidatePixmapsCache() { if (_checkbox) { _checkbox->invalidateCache(); @@ -983,12 +1012,12 @@ bool PeerListContent::addingToSearchIndex() const { } void PeerListContent::addToSearchIndex(not_null row) { - if (row->isSearchResult() || row->special()) { + if (row->isSearchResult()) { return; } removeFromSearchIndex(row); - row->setNameFirstLetters(row->peer()->nameFirstLetters()); + row->setNameFirstLetters(row->generateNameFirstLetters()); for (auto ch : row->nameFirstLetters()) { _searchIndex[ch].push_back(row); } @@ -1813,9 +1842,9 @@ void PeerListContent::searchQueryChanged(QString query) { } if (minimalList) { auto searchWordInNames = []( - not_null peer, + not_null row, const QString &searchWord) { - for (auto &nameWord : peer->nameWords()) { + for (auto &nameWord : row->generateNameWords()) { if (nameWord.startsWith(searchWord)) { return true; } @@ -1823,9 +1852,9 @@ void PeerListContent::searchQueryChanged(QString query) { return false; }; auto allSearchWordsInNames = [&]( - not_null peer) { + not_null row) { for (const auto &searchWord : searchWordsList) { - if (!searchWordInNames(peer, searchWord)) { + if (!searchWordInNames(row, searchWord)) { return false; } } @@ -1834,7 +1863,7 @@ void PeerListContent::searchQueryChanged(QString query) { _filterResults.reserve(minimalList->size()); for (const auto &row : *minimalList) { - if (!row->special() && allSearchWordsInNames(row->peer())) { + if (allSearchWordsInNames(row)) { _filterResults.push_back(row); } } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 99e64a4166..7da4570c32 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -91,6 +91,11 @@ public: [[nodiscard]] virtual auto generatePaintUserpicCallback() -> PaintRoundImageCallback; + [[nodiscard]] virtual auto generateNameFirstLetters() const + -> const base::flat_set &; + [[nodiscard]] virtual auto generateNameWords() const + -> const base::flat_set &; + void setCustomStatus(const QString &status, bool active = false); void clearCustomStatus(); @@ -360,6 +365,7 @@ private: class PeerListSearchDelegate { public: virtual void peerListSearchAddRow(not_null peer) = 0; + virtual void peerListSearchAddRow(PeerListRowId id) = 0; virtual void peerListSearchRefreshRows() = 0; virtual ~PeerListSearchDelegate() = default; @@ -470,6 +476,7 @@ public: not_null peer) { return nullptr; } + virtual std::unique_ptr createSearchRow(PeerListRowId id); virtual std::unique_ptr createRestoredRow( not_null peer) { return nullptr; @@ -494,6 +501,7 @@ public: void search(const QString &query); void peerListSearchAddRow(not_null peer) override; + void peerListSearchAddRow(PeerListRowId id) override; void peerListSearchRefreshRows() override; [[nodiscard]] virtual bool respectSavedMessagesChat() const { diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 134e1b4a23..9e152f5cba 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -18,55 +18,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" +#include "data/data_forum.h" +#include "data/data_forum_topic.h" #include "data/data_folder.h" #include "data/data_histories.h" #include "data/data_changes.h" +#include "dialogs/ui/dialogs_layout.h" #include "apiwrap.h" #include "mainwidget.h" #include "mainwindow.h" #include "lang/lang_keys.h" #include "history/history.h" +#include "history/history_item.h" #include "dialogs/dialogs_main_list.h" #include "window/window_session_controller.h" // showAddContact() #include "base/unixtime.h" #include "facades.h" #include "styles/style_boxes.h" #include "styles/style_profile.h" +#include "styles/style_dialogs.h" namespace { constexpr auto kSortByOnlineThrottle = 3 * crl::time(1000); +constexpr auto kSearchPerPage = 50; } // namespace -// Not used for now. -// -//MembersAddButton::MembersAddButton(QWidget *parent, const style::TwoIconButton &st) : RippleButton(parent, st.ripple) -//, _st(st) { -// resize(_st.width, _st.height); -// setCursor(style::cur_pointer); -//} -// -//void MembersAddButton::paintEvent(QPaintEvent *e) { -// Painter p(this); -// -// auto ms = crl::now(); -// auto over = isOver(); -// auto down = isDown(); -// -// ((over || down) ? _st.iconBelowOver : _st.iconBelow).paint(p, _st.iconPosition, width()); -// paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms); -// ((over || down) ? _st.iconAboveOver : _st.iconAbove).paint(p, _st.iconPosition, width()); -//} -// -//QImage MembersAddButton::prepareRippleMask() const { -// return Ui::RippleAnimation::EllipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); -//} -// -//QPoint MembersAddButton::prepareRippleStartPosition() const { -// return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; -//} - object_ptr PrepareContactsBox( not_null sessionController) { using Mode = ContactsBoxController::SortMode; @@ -314,7 +292,8 @@ QString ChatsListBoxController::emptyBoxText() const { return tr::lng_contacts_not_found(tr::now); } -std::unique_ptr ChatsListBoxController::createSearchRow(not_null peer) { +std::unique_ptr ChatsListBoxController::createSearchRow( + not_null peer) { return createRow(peer->owner().history(peer)); } @@ -481,8 +460,8 @@ std::unique_ptr ContactsBoxController::createRow( ChooseRecipientBoxController::ChooseRecipientBoxController( not_null session, - FnMut)> callback, - Fn)> filter) + FnMut)> callback, + Fn)> filter) : ChatsListBoxController(session) , _session(session) , _callback(std::move(callback)) @@ -498,10 +477,48 @@ void ChooseRecipientBoxController::prepareViewHook() { } void ChooseRecipientBoxController::rowClicked(not_null row) { - auto weak = base::make_weak(this); + auto guard = base::make_weak(this); + const auto peer = row->peer(); + if (const auto forum = peer->forum()) { + const auto weak = std::make_shared>(); + auto callback = [=](not_null topic) { + const auto exists = guard.get(); + if (!exists) { + if (*weak) { + (*weak)->closeBox(); + } + return; + } + auto onstack = std::move(_callback); + onstack(topic); + if (guard) { + _callback = std::move(onstack); + } else if (*weak) { + (*weak)->closeBox(); + } + }; + auto owned = Box( + std::make_unique( + forum, + std::move(callback)), + [=](not_null box) { + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); + + forum->destroyed( + ) | rpl::start_with_next([=] { + box->closeBox(); + }, box->lifetime()); + }); + *weak = owned.data(); + delegate()->peerListShowBox(std::move(owned)); + return; + } + const auto history = peer->owner().history(peer); auto callback = std::move(_callback); - callback(row->peer()); - if (weak) { + callback(history); + if (guard) { _callback = std::move(callback); } } @@ -510,8 +527,205 @@ auto ChooseRecipientBoxController::createRow( not_null history) -> std::unique_ptr { const auto peer = history->peer; const auto skip = _filter - ? !_filter(peer) + ? !_filter(history) : ((peer->isBroadcast() && !peer->canWrite()) || peer->isRepliesChat()); return skip ? nullptr : std::make_unique(history); } + +ChooseTopicSearchController::ChooseTopicSearchController( + not_null forum) +: _forum(forum) +, _api(&forum->session().mtp()) +, _timer([=] { searchOnServer(); }) { +} + +void ChooseTopicSearchController::searchQuery(const QString &query) { + if (_query != query) { + _query = query; + _api.request(base::take(_requestId)).cancel(); + _offsetDate = 0; + _offsetId = 0; + _offsetTopicId = 0; + _allLoaded = false; + if (!_query.isEmpty()) { + _timer.callOnce(AutoSearchTimeout); + } else { + _timer.cancel(); + } + } +} + +void ChooseTopicSearchController::searchOnServer() { + _requestId = _api.request(MTPchannels_GetForumTopics( + MTP_flags(MTPchannels_GetForumTopics::Flag::f_q), + _forum->channel()->inputChannel, + MTP_string(_query), + MTP_int(_offsetDate), + MTP_int(_offsetId), + MTP_int(_offsetTopicId), + MTP_int(kSearchPerPage) + )).done([=](const MTPmessages_ForumTopics &result) { + _requestId = 0; + const auto savedTopicId = _offsetTopicId; + const auto byCreation = result.data().is_order_by_create_date(); + _forum->applyReceivedTopics(result, [&]( + not_null topic) { + _offsetTopicId = topic->rootId(); + if (byCreation) { + _offsetDate = topic->creationDate(); + if (const auto last = topic->lastServerMessage()) { + _offsetId = last->id; + } + } else if (const auto last = topic->lastServerMessage()) { + _offsetId = last->id; + _offsetDate = last->date(); + } + delegate()->peerListSearchAddRow(topic->rootId().bare); + }); + if (_offsetTopicId != savedTopicId) { + delegate()->peerListSearchRefreshRows(); + } else { + _allLoaded = true; + } + }).fail([=] { + _allLoaded = true; + }).send(); +} + +bool ChooseTopicSearchController::isLoading() { + return _timer.isActive() || _requestId; +} + +bool ChooseTopicSearchController::loadMoreRows() { + if (!isLoading()) { + searchOnServer(); + } + return !_allLoaded; +} + +ChooseTopicBoxController::ChooseTopicBoxController( + not_null forum, + FnMut)> callback, + Fn)> filter) +: PeerListController(std::make_unique(forum)) +, _forum(forum) +, _callback(std::move(callback)) +, _filter(std::move(filter)) { + setStyleOverrides(&st::chooseTopicList); + + _forum->chatsListChanges( + ) | rpl::start_with_next([=] { + refreshRows(); + }, lifetime()); + + _forum->topicDestroyed( + ) | rpl::start_with_next([=](not_null topic) { + const auto id = PeerListRowId(topic->rootId().bare); + if (const auto row = delegate()->peerListFindRow(id)) { + delegate()->peerListRemoveRow(row); + delegate()->peerListRefreshRows(); + } + }, lifetime()); +} + +Main::Session &ChooseTopicBoxController::session() const { + return _forum->session(); +} + +void ChooseTopicBoxController::rowClicked(not_null row) { + const auto weak = base::make_weak(this); + auto onstack = base::take(_callback); + onstack(static_cast(row.get())->topic()); + if (weak) { + _callback = std::move(onstack); + } +} + +void ChooseTopicBoxController::prepare() { + delegate()->peerListSetTitle(tr::lng_forward_choose()); + setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now)); + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); + refreshRows(true); +} + +void ChooseTopicBoxController::refreshRows(bool initial) { + auto added = false; + for (const auto &row : _forum->topicsList()->indexed()->all()) { + if (const auto topic = row->topic()) { + const auto id = topic->rootId().bare; + auto already = delegate()->peerListFindRow(id); + if (initial || !already) { + delegate()->peerListAppendRow(createRow(topic)); + added = true; + } else if (already->isSearchResult()) { + delegate()->peerListAppendFoundRow(already); + added = true; + } + } + } + if (added) { + delegate()->peerListRefreshRows(); + } +} + +void ChooseTopicBoxController::loadMoreRows() { + _forum->requestTopics(); +} + +std::unique_ptr ChooseTopicBoxController::createSearchRow( + PeerListRowId id) { + if (const auto topic = _forum->topicFor(MsgId(id))) { + return std::make_unique(topic); + } + return nullptr; +} + +ChooseTopicBoxController::Row::Row(not_null topic) +: PeerListRow(topic->rootId().bare) +, _topic(topic) { +} + +QString ChooseTopicBoxController::Row::generateName() { + return _topic->title(); +} + +QString ChooseTopicBoxController::Row::generateShortName() { + return _topic->title(); +} + +auto ChooseTopicBoxController::Row::generatePaintUserpicCallback() +-> PaintRoundImageCallback { + return [=]( + Painter &p, + int x, + int y, + int outerWidth, + int size) { + auto view = std::shared_ptr(); + p.translate(x, y); + _topic->paintUserpic(p, view, { + .st = &st::forumTopicRow, + .now = crl::now(), + .width = outerWidth, + .paused = false, + }); + p.translate(-x, -y); + }; +} + +auto ChooseTopicBoxController::Row::generateNameFirstLetters() const +-> const base::flat_set & { + return _topic->chatListFirstLetters(); +} + +auto ChooseTopicBoxController::Row::generateNameWords() const +-> const base::flat_set & { + return _topic->chatListNameWords(); +} + +auto ChooseTopicBoxController::createRow(not_null topic) +-> std::unique_ptr { + const auto skip = _filter ? !_filter(topic) : !topic->canWrite(); + return skip ? nullptr : std::make_unique(topic); +}; diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index f8d5809022..b8c149fc61 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -12,25 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/weak_ptr.h" #include "base/timer.h" -// Not used for now. -// -//class MembersAddButton : public Ui::RippleButton { -//public: -// MembersAddButton(QWidget *parent, const style::TwoIconButton &st); -// -//protected: -// void paintEvent(QPaintEvent *e) override; -// -// QImage prepareRippleMask() const override; -// QPoint prepareRippleStartPosition() const override; -// -//private: -// const style::TwoIconButton &_st; -// -//}; - class History; +namespace Data { +class Thread; +class Forum; +class ForumTopic; +} // namespace Data + namespace Window { class SessionController; } // namespace Window @@ -110,7 +99,8 @@ public: std::unique_ptr searchController); void prepare() override final; - std::unique_ptr createSearchRow(not_null peer) override final; + std::unique_ptr createSearchRow( + not_null peer) override final; protected: virtual std::unique_ptr createRow(not_null history) = 0; @@ -173,8 +163,8 @@ class ChooseRecipientBoxController public: ChooseRecipientBoxController( not_null session, - FnMut)> callback, - Fn)> filter = nullptr); + FnMut)> callback, + Fn)> filter = nullptr); Main::Session &session() const override; void rowClicked(not_null row) override; @@ -189,7 +179,80 @@ protected: private: const not_null _session; - FnMut)> _callback; - Fn)> _filter; + FnMut)> _callback; + Fn)> _filter; + +}; + +class ChooseTopicSearchController : public PeerListSearchController { +public: + explicit ChooseTopicSearchController(not_null forum); + + void searchQuery(const QString &query) override; + bool isLoading() override; + bool loadMoreRows() override; + +private: + void searchOnServer(); + void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId); + + const not_null _forum; + MTP::Sender _api; + base::Timer _timer; + QString _query; + mtpRequestId _requestId = 0; + TimeId _offsetDate = 0; + MsgId _offsetId = 0; + MsgId _offsetTopicId = 0; + bool _allLoaded = false; + +}; + +class ChooseTopicBoxController final + : public PeerListController + , public base::has_weak_ptr { +public: + ChooseTopicBoxController( + not_null forum, + FnMut)> callback, + Fn)> filter = nullptr); + + Main::Session &session() const override; + void rowClicked(not_null row) override; + + void prepare() override; + void loadMoreRows() override; + std::unique_ptr createSearchRow(PeerListRowId id) override; + +private: + class Row final : public PeerListRow { + public: + explicit Row(not_null topic); + + [[nodiscard]] not_null topic() const { + return _topic; + } + + QString generateName() override; + QString generateShortName() override; + PaintRoundImageCallback generatePaintUserpicCallback() override; + + auto generateNameFirstLetters() const + -> const base::flat_set & override; + auto generateNameWords() const + -> const base::flat_set & override; + + private: + const not_null _topic; + + }; + + void refreshRows(bool initial = false); + [[nodiscard]] std::unique_ptr createRow( + not_null topic); + + const not_null _forum; + FnMut)> _callback; + Fn)> _filter; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index c8add875a3..40076019f6 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/share_box.h" #include "history/view/history_view_schedule_box.h" #include "history/history_message.h" // GetErrorTextForSending. +#include "history/history.h" #include "data/data_histories.h" #include "data/data_session.h" #include "base/timer_rpl.h" diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 75e8d8bd55..eae1003c61 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -125,13 +125,11 @@ struct HistoryUpdate { ChatOccupied = (1U << 7), MessageSent = (1U << 8), ScheduledSent = (1U << 9), - ForwardDraft = (1U << 10), - OutboxRead = (1U << 11), - BotKeyboard = (1U << 12), - CloudDraft = (1U << 13), - LocalDraftSet = (1U << 14), + OutboxRead = (1U << 10), + BotKeyboard = (1U << 11), + CloudDraft = (1U << 12), - LastUsedBit = (1U << 14), + LastUsedBit = (1U << 12), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } @@ -196,8 +194,10 @@ struct EntryUpdate { Repaint = (1U << 0), HasPinnedMessages = (1U << 1), + ForwardDraft = (1U << 2), + LocalDraftSet = (1U << 3), - LastUsedBit = (1U << 1), + LastUsedBit = (1U << 3), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index e0bc63e44a..66b197e146 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1151,13 +1151,6 @@ void Session::deleteConversationLocally(not_null peer) { } } -void Session::cancelForwarding(not_null history) { - history->setForwardDraft({}); - session().changes().historyUpdated( - history, - Data::HistoryUpdate::Flag::ForwardDraft); -} - bool Session::chatsListLoaded(Data::Folder *folder) { return chatsList(folder)->loaded(); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 85afbc2a56..0e3cb84cea 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -226,8 +226,6 @@ public: void deleteConversationLocally(not_null peer); - void cancelForwarding(not_null history); - [[nodiscard]] rpl::variable &contactsLoaded() { return _contactsLoaded; } diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 5a2c8d47c5..86bc34b085 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -445,3 +445,18 @@ forumTopicIconPosition: point(2px, 0px); editTopicTitleMargin: margins(70px, 2px, 22px, 18px); editTopicIconPosition: point(24px, 19px); editTopicMaxHeight: 408px; + +chooseTopicListItem: PeerListItem(defaultPeerListItem) { + height: 44px; + photoSize: 28px; + photoPosition: point(16px, 5px); + namePosition: point(71px, 11px); + nameStyle: TextStyle(defaultTextStyle) { + font: font(14px semibold); + linkFont: font(14px semibold); + linkFontOver: font(14px semibold); + } +} +chooseTopicList: PeerList(defaultPeerList) { + item: chooseTopicListItem; +} diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index e0689d62b0..89044d133d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2191,23 +2191,27 @@ void InnerWidget::trackSearchResultsHistory(not_null history) { } } -PeerData *InnerWidget::updateFromParentDrag(QPoint globalPosition) { +Data::Thread *InnerWidget::updateFromParentDrag(QPoint globalPosition) { selectByMouse(globalPosition); - const auto getPeerFromRow = [](Row *row) -> PeerData* { - if (const auto history = row ? row->history() : nullptr) { - return history->peer; - } - return nullptr; + + const auto fromRow = [](Row *row) { + return row ? row->thread() : nullptr; }; if (_state == WidgetState::Default) { - return getPeerFromRow(_selected); + return fromRow(_selected); } else if (_state == WidgetState::Filtered) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) { - return getPeerFromRow(_filterResults[_filteredSelected]); + return fromRow(_filterResults[_filteredSelected]); } else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) { - return _peerSearchResults[_peerSearchSelected]->peer; + return session().data().history( + _peerSearchResults[_peerSearchSelected]->peer); } else if (base::in_range(_searchedSelected, 0, _searchResults.size())) { - return _searchResults[_searchedSelected]->item()->history()->peer; + if (const auto item = _searchResults[_searchedSelected]->item()) { + if (const auto topic = item->topic()) { + return topic; + } + return item->history(); + } } } return nullptr; @@ -3416,8 +3420,8 @@ void InnerWidget::setupShortcuts() { return jumpToDialogRow(last); }); request->check(Command::ChatSelf) && request->handle([=] { - _controller->content()->choosePeer( - session().userPeerId(), + _controller->content()->chooseThread( + session().user(), ShowAtUnreadMsgId); return true; }); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 515b89ff28..e97dbc0926 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -40,6 +40,7 @@ class SessionController; namespace Data { class CloudImageView; +class Thread; class Folder; class Forum; } // namespace Data @@ -133,7 +134,7 @@ public: void onHashtagFilterUpdate(QStringView newFilter); void appendToFiltered(Key key); - PeerData *updateFromParentDrag(QPoint globalPosition); + Data::Thread *updateFromParentDrag(QPoint globalPosition); void setLoadMoreCallback(Fn callback); void setLoadMoreFilteredCallback(Fn callback); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 5e57e7b60a..31111d6582 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -414,10 +414,7 @@ void Widget::chosenRow(const ChosenRow &row) { && row.filteredRow; const auto history = row.key.history(); if (const auto topic = row.key.topic()) { - controller()->showTopic( - topic, - row.message.fullId.msg, - Window::SectionShow::Way::ClearStack); + controller()->content()->chooseThread(topic, row.message.fullId.msg); } else if (history && history->peer->isForum() && !row.message.fullId) { controller()->openForum(history->peer->asChannel()); return; @@ -448,7 +445,7 @@ void Widget::chosenRow(const ChosenRow &row) { toSeparate(); } } else { - controller()->content()->choosePeer(peer->id, showAtMsgId); + controller()->content()->chooseThread(history, showAtMsgId); } } else if (const auto folder = row.key.folder()) { controller()->openFolder(folder); @@ -1783,7 +1780,9 @@ void Widget::dragMoveEvent(QDragMoveEvent *e) { e->setDropAction(Qt::IgnoreAction); } } else { - if (_dragForward) updateDragInScroll(false); + if (_dragForward) { + updateDragInScroll(false); + } _inner->dragLeft(); e->setDropAction(Qt::IgnoreAction); } @@ -1814,10 +1813,11 @@ void Widget::updateDragInScroll(bool inScroll) { void Widget::dropEvent(QDropEvent *e) { _chooseByDragTimer.cancel(); if (_scroll->geometry().contains(e->pos())) { - if (auto peer = _inner->updateFromParentDrag(mapToGlobal(e->pos()))) { + const auto point = mapToGlobal(e->pos()); + if (const auto thread = _inner->updateFromParentDrag(point)) { e->acceptProposedAction(); controller()->content()->onFilesOrForwardDrop( - peer->id, + thread, e->mimeData()); controller()->widget()->raise(); controller()->widget()->activateWindow(); @@ -2339,10 +2339,8 @@ bool Widget::cancelSearch() { cancelSearchRequest(); if (!clearingQuery && _searchInChat) { if (controller()->adaptive().isOneColumn()) { - if (const auto topic = _searchInChat.topic()) { - controller()->showTopic(topic); - } else if (const auto peer = _searchInChat.peer()) { - controller()->showPeerHistory(peer); + if (const auto thread = _searchInChat.thread()) { + controller()->showThread(thread); } else { Unexpected("Empty key in cancelSearch()."); } @@ -2371,10 +2369,8 @@ void Widget::cancelSearchInChat() { if (isOneColumn && !controller()->selectingPeer() && currentSearchQuery().trimmed().isEmpty()) { - if (const auto topic = _searchInChat.topic()) { - controller()->showTopic(topic); - } else if (const auto peer = _searchInChat.peer()) { - controller()->showPeerHistory(peer); + if (const auto thread = _searchInChat.thread()) { + controller()->showThread(thread); } else { Unexpected("Empty key in cancelSearchInPeer()."); } diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 8a845d7694..16b18f87ac 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -355,6 +355,13 @@ void History::draftSavedToCloud(MsgId topicRootId) { session().local().writeDrafts(this); } +const Data::ForwardDraft &History::forwardDraft( + MsgId topicRootId) const { + static const auto kEmpty = Data::ForwardDraft(); + const auto i = _forwardDrafts.find(topicRootId); + return (i != end(_forwardDrafts)) ? i->second : kEmpty; +} + Data::ResolvedForwardDraft History::resolveForwardDraft( const Data::ForwardDraft &draft) const { return Data::ResolvedForwardDraft{ @@ -363,10 +370,12 @@ Data::ResolvedForwardDraft History::resolveForwardDraft( }; } -Data::ResolvedForwardDraft History::resolveForwardDraft() { - auto result = resolveForwardDraft(_forwardDraft); - if (result.items.size() != _forwardDraft.ids.size()) { - setForwardDraft({ +Data::ResolvedForwardDraft History::resolveForwardDraft( + MsgId topicRootId) { + const auto &draft = forwardDraft(topicRootId); + auto result = resolveForwardDraft(draft); + if (result.items.size() != draft.ids.size()) { + setForwardDraft(topicRootId, { .ids = owner().itemsToIds(result.items), .options = result.options, }); @@ -374,8 +383,29 @@ Data::ResolvedForwardDraft History::resolveForwardDraft() { return result; } -void History::setForwardDraft(Data::ForwardDraft &&draft) { - _forwardDraft = std::move(draft); +void History::setForwardDraft( + MsgId topicRootId, + Data::ForwardDraft &&draft) { + auto changed = false; + if (draft.ids.empty()) { + changed = _forwardDrafts.remove(topicRootId); + } else { + auto &now = _forwardDrafts[topicRootId]; + if (now != draft) { + now = std::move(draft); + changed = true; + } + } + if (changed) { + const auto entry = topicRootId + ? peer->forumTopicFor(topicRootId) + : (Dialogs::Entry*)this; + if (entry) { + session().changes().entryUpdated( + entry, + Data::EntryUpdate::Flag::ForwardDraft); + } + } } not_null History::createItem( @@ -624,7 +654,8 @@ not_null History::addNewLocalMessage( TimeId date, PeerId from, const QString &postAuthor, - not_null forwardOriginal) { + not_null forwardOriginal, + MsgId topicRootId) { return addNewItem( makeMessage( id, @@ -632,7 +663,8 @@ not_null History::addNewLocalMessage( date, from, postAuthor, - forwardOriginal), + forwardOriginal, + topicRootId), true); } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 94702744d6..b8f4df785b 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -46,8 +46,14 @@ enum class ForwardOptions { struct ForwardDraft { MessageIdsList ids; ForwardOptions options = ForwardOptions::PreserveInfo; + + friend inline constexpr auto operator<=>( + const ForwardDraft&, + const ForwardDraft&) = default; }; +using ForwardDrafts = base::flat_map; + struct ResolvedForwardDraft { HistoryItemsList items; ForwardOptions options = ForwardOptions::PreserveInfo; @@ -165,7 +171,8 @@ public: TimeId date, PeerId from, const QString &postAuthor, - not_null forwardOriginal); + not_null forwardOriginal, + MsgId topicRootId); not_null addNewLocalMessage( MsgId id, MessageFlags flags, @@ -366,13 +373,13 @@ public: void applyCloudDraft(MsgId topicRootId); void draftSavedToCloud(MsgId topicRootId); - [[nodiscard]] const Data::ForwardDraft &forwardDraft() const { - return _forwardDraft; - } + [[nodiscard]] const Data::ForwardDraft &forwardDraft( + MsgId topicRootId) const; [[nodiscard]] Data::ResolvedForwardDraft resolveForwardDraft( const Data::ForwardDraft &draft) const; - [[nodiscard]] Data::ResolvedForwardDraft resolveForwardDraft(); - void setForwardDraft(Data::ForwardDraft &&draft); + [[nodiscard]] Data::ResolvedForwardDraft resolveForwardDraft( + MsgId topicRootId); + void setForwardDraft(MsgId topicRootId, Data::ForwardDraft &&draft); History *migrateSibling() const; [[nodiscard]] bool useTopPromotion() const; @@ -621,7 +628,7 @@ private: Data::HistoryDrafts _drafts; base::flat_map _acceptCloudDraftsAfter; base::flat_map _savingCloudDraftRequests; - Data::ForwardDraft _forwardDraft; + Data::ForwardDrafts _forwardDrafts; QString _topPromotedMessage; QString _topPromotedType; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 6f18402dd3..4311946198 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -411,7 +411,8 @@ HistoryMessage::HistoryMessage( TimeId date, PeerId from, const QString &postAuthor, - not_null original) + not_null original, + MsgId topicRootId) : HistoryItem( history, id, @@ -424,6 +425,8 @@ HistoryMessage::HistoryMessage( const auto originalMedia = original->media(); const auto dropForwardInfo = original->computeDropForwardedInfo(); + config.replyTo = config.replyToTop = topicRootId; + config.replyIsTopicPost = (topicRootId != 0); if (!dropForwardInfo) { config.originalDate = original->dateOriginal(); if (const auto info = original->hiddenSenderInfo()) { diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index e12778688c..80539aa4b2 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -72,7 +72,8 @@ public: TimeId date, PeerId from, const QString &postAuthor, - not_null original); // local forwarded + not_null original, + MsgId topicRootId); // local forwarded HistoryMessage( not_null history, MsgId id, diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index d0c24a208b..ed41c4d3fc 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -84,6 +84,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "history/history_unread_things.h" #include "history/view/controls/history_view_compose_search.h" +#include "history/view/controls/history_view_forward_panel.h" #include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/controls/history_view_ttl_button.h" #include "history/view/reactions/history_view_reactions_button.h" @@ -255,11 +256,12 @@ HistoryWidget::HistoryWidget( , _botKeyboardShow(this, st::historyBotKeyboardShow) , _botKeyboardHide(this, st::historyBotKeyboardHide) , _botCommandStart(this, st::historyBotCommandStart) -, _voiceRecordBar(std::make_unique( +, _voiceRecordBar(std::make_unique( this, controller, _send, st::historySendSize.height())) +, _forwardPanel(std::make_unique([=] { updateField(); })) , _field( this, st::historyComposeField, @@ -378,6 +380,12 @@ HistoryWidget::HistoryWidget( _scroll->updateBars(); }, lifetime()); + _forwardPanel->itemsUpdated( + ) | rpl::start_with_next([=] { + updateControlsVisibility(); + updateControlsGeometry(); + }, lifetime()); + InitMessageField(controller, _field, [=]( not_null document) { if (_peer && Data::AllowEmojiWithoutPremium(_peer)) { @@ -606,6 +614,7 @@ HistoryWidget::HistoryWidget( using EntryUpdateFlag = Data::EntryUpdate::Flag; session().changes().entryUpdates( EntryUpdateFlag::HasPinnedMessages + | EntryUpdateFlag::ForwardDraft ) | rpl::start_with_next([=](const Data::EntryUpdate &update) { if (_pinnedTracker && (update.flags & EntryUpdateFlag::HasPinnedMessages) @@ -613,12 +622,14 @@ HistoryWidget::HistoryWidget( || (update.entry.get() == _migrated))) { checkPinnedBarState(); } + if (update.flags & EntryUpdateFlag::ForwardDraft) { + updateForwarding(); + } }, lifetime()); using HistoryUpdateFlag = Data::HistoryUpdate::Flag; session().changes().historyUpdates( HistoryUpdateFlag::MessageSent - | HistoryUpdateFlag::ForwardDraft | HistoryUpdateFlag::BotKeyboard | HistoryUpdateFlag::CloudDraft | HistoryUpdateFlag::UnreadMentions @@ -633,9 +644,6 @@ HistoryWidget::HistoryWidget( if (flags & HistoryUpdateFlag::MessageSent) { synteticScrollToY(_scroll->scrollTopMax()); } - if (flags & HistoryUpdateFlag::ForwardDraft) { - updateForwarding(); - } if (flags & HistoryUpdateFlag::BotKeyboard) { updateBotKeyboard(update.history); } @@ -3719,7 +3727,7 @@ void HistoryWidget::send(Api::SendOptions options) { _peer, { .topicRootId = topicRootId, - .forward = &_toForward.items, + .forward = &_forwardPanel->items(), .text = &message.textWithTags, .ignoreSlowmodeCountdown = (options.scheduled != 0), }); @@ -4281,7 +4289,7 @@ QRect HistoryWidget::floatPlayerAvailableRect() { } bool HistoryWidget::readyToForward() const { - return _canSendMessages && !_toForward.items.empty(); + return _canSendMessages && !_forwardPanel->empty(); } bool HistoryWidget::hasSilentToggle() const { @@ -5274,15 +5282,6 @@ void HistoryWidget::itemRemoved(not_null item) { toggleKeyboard(); _kbReplyTo = nullptr; } - auto found = ranges::find(_toForward.items, item); - if (found != _toForward.items.end()) { - _toForward.items.erase(found); - updateForwardingTexts(); - if (_toForward.items.empty()) { - updateControlsVisibility(); - updateControlsGeometry(); - } - } const auto i = _itemRevealAnimations.find(item); if (i != end(_itemRevealAnimations)) { _itemRevealAnimations.erase(i); @@ -5858,81 +5857,7 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { updateField(); } else if (_inReplyEditForward) { if (readyToForward()) { - using Options = Data::ForwardOptions; - const auto now = _toForward.options; - const auto count = _toForward.items.size(); - const auto dropNames = (now != Options::PreserveInfo); - const auto hasCaptions = [&] { - for (const auto item : _toForward.items) { - if (const auto media = item->media()) { - if (!item->originalText().text.isEmpty() - && media->allowsEditCaption()) { - return true; - } - } - } - return false; - }(); - const auto hasOnlyForcedForwardedInfo = [&] { - if (hasCaptions) { - return false; - } - for (const auto item : _toForward.items) { - if (const auto media = item->media()) { - if (!media->forceForwardedInfo()) { - return false; - } - } else { - return false; - } - } - return true; - }(); - const auto dropCaptions = (now == Options::NoNamesAndCaptions); - const auto weak = Ui::MakeWeak(this); - const auto changeRecipient = crl::guard(weak, [=] { - if (_toForward.items.empty()) { - return; - } - const auto draft = std::move(_toForward); - session().data().cancelForwarding(_history); - auto list = session().data().itemsToIds(draft.items); - Window::ShowForwardMessagesBox(controller(), { - .ids = session().data().itemsToIds(draft.items), - .options = draft.options, - }); - }); - if (hasOnlyForcedForwardedInfo) { - changeRecipient(); - return; - } - const auto optionsChanged = crl::guard(weak, [=]( - Ui::ForwardOptions options) { - const auto newOptions = (options.hasCaptions - && options.dropCaptions) - ? Options::NoNamesAndCaptions - : options.dropNames - ? Options::NoSenderNames - : Options::PreserveInfo; - if (_history && _toForward.options != newOptions) { - _toForward.options = newOptions; - _history->setForwardDraft({ - .ids = session().data().itemsToIds(_toForward.items), - .options = newOptions, - }); - updateField(); - } - }); - controller()->show(Box( - Ui::ForwardOptionsBox, - count, - Ui::ForwardOptions{ - .dropNames = dropNames, - .hasCaptions = hasCaptions, - .dropCaptions = dropCaptions, - }, - optionsChanged, - changeRecipient)); + _forwardPanel->editOptions(controller()); } else { controller()->showPeerHistory( _peer, @@ -6725,8 +6650,8 @@ void HistoryWidget::processReply() { .text = tr::lng_reply_cant_forward(), .confirmed = crl::guard(this, [=] { controller()->content()->setForwardDraft( - _peer->id, - {.ids = { 1, itemId } }); + _history, + { .ids = { 1, itemId } }); }), .confirmText = tr::lng_selected_forward(), })); @@ -6757,7 +6682,7 @@ void HistoryWidget::setReplyFieldsFromProcessing() { return; } - session().data().cancelForwarding(_history); + _history->setForwardDraft(MsgId(), {}); if (_composeSearch) { _composeSearch->hideAnimated(); } @@ -7024,7 +6949,7 @@ void HistoryWidget::cancelFieldAreaState() { } else if (_editMsgId) { cancelEdit(); } else if (readyToForward()) { - session().data().cancelForwarding(_history); + _history->setForwardDraft(MsgId(), {}); } else if (_replyToId) { cancelReply(); } else if (_kbReplyTo) { @@ -7437,115 +7362,16 @@ void HistoryWidget::updateReplyEditTexts(bool force) { } void HistoryWidget::updateForwarding() { - if (_history) { - _toForward = _history->resolveForwardDraft(); - updateForwardingTexts(); - } else { - _toForward = {}; + _forwardPanel->update(_history, _history + ? _history->resolveForwardDraft(MsgId()) + : Data::ResolvedForwardDraft()); + if (readyToForward()) { + cancelReply(); } updateControlsVisibility(); updateControlsGeometry(); } -void HistoryWidget::updateForwardingTexts() { - int32 version = 0; - QString from; - TextWithEntities text; - const auto keepNames = (_toForward.options - == Data::ForwardOptions::PreserveInfo); - const auto keepCaptions = (_toForward.options - != Data::ForwardOptions::NoNamesAndCaptions); - if (const auto count = int(_toForward.items.size())) { - auto insertedPeers = base::flat_set>(); - auto insertedNames = base::flat_set(); - auto fullname = QString(); - auto names = std::vector(); - names.reserve(_toForward.items.size()); - for (const auto item : _toForward.items) { - if (const auto from = item->senderOriginal()) { - if (!insertedPeers.contains(from)) { - insertedPeers.emplace(from); - names.push_back(from->shortName()); - fullname = from->name(); - } - version += from->nameVersion(); - } else if (const auto info = item->hiddenSenderInfo()) { - if (!insertedNames.contains(info->name)) { - insertedNames.emplace(info->name); - names.push_back(info->firstName); - fullname = info->name; - } - ++version; - } else { - Unexpected("Corrupt forwarded information in message."); - } - } - if (!keepNames) { - from = tr::lng_forward_sender_names_removed(tr::now); - } else if (names.size() > 2) { - from = tr::lng_forwarding_from(tr::now, lt_count, names.size() - 1, lt_user, names[0]); - } else if (names.size() < 2) { - from = fullname; - } else { - from = tr::lng_forwarding_from_two(tr::now, lt_user, names[0], lt_second_user, names[1]); - } - - if (count < 2) { - const auto item = _toForward.items.front(); - text = item->toPreview({ - .hideSender = true, - .hideCaption = !keepCaptions, - .generateImages = false, - }).text; - - const auto dropCustomEmoji = !session().premium() - && !_peer->isSelf() - && (item->computeDropForwardedInfo() || !keepNames); - if (dropCustomEmoji) { - text = DropCustomEmoji(std::move(text)); - } - } else { - text = Ui::Text::PlainLink( - tr::lng_forward_messages(tr::now, lt_count, count)); - } - } - _toForwardFrom.setText(st::msgNameStyle, from, Ui::NameTextOptions()); - const auto context = Core::MarkedTextContext{ - .session = &session(), - .customEmojiRepaint = [=] { updateField(); }, - }; - _toForwardText.setMarkedText( - st::messageTextStyle, - text, - Ui::DialogTextOptions(), - context); - _toForwardNameVersion = keepNames ? version : keepCaptions ? -1 : -2; -} - -void HistoryWidget::checkForwardingInfo() { - if (!_toForward.items.empty()) { - const auto keepNames = (_toForward.options - == Data::ForwardOptions::PreserveInfo); - const auto keepCaptions = (_toForward.options - != Data::ForwardOptions::NoNamesAndCaptions); - auto version = keepNames ? 0 : keepCaptions ? -1 : -2; - if (keepNames) { - for (const auto item : _toForward.items) { - if (const auto from = item->senderOriginal()) { - version += from->nameVersion(); - } else if (const auto info = item->hiddenSenderInfo()) { - ++version; - } else { - Unexpected("Corrupt forwarded information in message."); - } - } - } - if (version != _toForwardNameVersion) { - updateForwardingTexts(); - } - } -} - void HistoryWidget::updateReplyToName() { if (_editMsgId) { return; @@ -7594,7 +7420,6 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { backy -= st::historyReplyHeight; backh += st::historyReplyHeight; } else if (hasForward) { - checkForwardingInfo(); backy -= st::historyReplyHeight; backh += st::historyReplyHeight; } else if (_previewData && _previewData->pendingTill >= 0) { @@ -7645,36 +7470,14 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { } } } else if (hasForward) { - auto forwardLeft = st::historyReplySkip; st::historyForwardIcon.paint(p, st::historyReplyIconPosition + QPoint(0, backy), width()); if (!drawWebPagePreview) { - const auto firstItem = _toForward.items.front(); - const auto firstMedia = firstItem->media(); - const auto preview = (_toForward.items.size() < 2 && firstMedia && firstMedia->hasReplyPreview()) - ? firstMedia->replyPreview() - : nullptr; - if (preview) { - auto to = QRect(forwardLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); - if (preview->width() == preview->height()) { - p.drawPixmap(to.x(), to.y(), preview->pix()); - } else { - auto from = (preview->width() > preview->height()) ? QRect((preview->width() - preview->height()) / 2, 0, preview->height(), preview->height()) : QRect(0, (preview->height() - preview->width()) / 2, preview->width(), preview->width()); - p.drawPixmap(to, preview->pix(), from); - } - forwardLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); - } - p.setPen(st::historyReplyNameFg); - _toForwardFrom.drawElided(p, forwardLeft, backy + st::msgReplyPadding.top(), width() - forwardLeft - _fieldBarCancel->width() - st::msgReplyPadding.right()); - p.setPen(st::historyComposeAreaFg); - _toForwardText.draw(p, { - .position = QPoint( - forwardLeft, - backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height), - .availableWidth = width() - forwardLeft - _fieldBarCancel->width() - st::msgReplyPadding.right(), - .palette = &st::historyComposeAreaPalette, - .spoiler = Ui::Text::DefaultSpoilerCache(), - .elisionLines = 1, - }); + const auto x = st::historyReplySkip; + const auto available = width() + - x + - _fieldBarCancel->width() + - st::msgReplyPadding.right(); + _forwardPanel->paint(p, x, backy, available, width()); } } if (drawWebPagePreview) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 0b184fd64d..3d3f5aae12 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -94,6 +94,7 @@ class ComposeSearch; namespace Controls { class RecordLock; class VoiceRecordBar; +class ForwardPanel; class TTLButton; } // namespace Controls } // namespace HistoryView @@ -110,6 +111,7 @@ public: using FieldHistoryAction = Ui::InputField::HistoryAction; using RecordLock = HistoryView::Controls::RecordLock; using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar; + using ForwardPanel = HistoryView::Controls::ForwardPanel; HistoryWidget( QWidget *parent, @@ -189,7 +191,6 @@ public: bool cancelReply(bool lastKeyboardUsed = false); void cancelEdit(); void updateForwarding(); - void updateForwardingTexts(); void pushReplyReturn(not_null item); [[nodiscard]] QVector replyReturns() const; @@ -470,7 +471,6 @@ private: int countMembersDropdownHeightMax() const; void updateReplyToName(); - void checkForwardingInfo(); bool editingMessage() const { return _editMsgId != 0; } @@ -617,10 +617,6 @@ private: MsgId _processingReplyId = 0; HistoryItem *_processingReplyItem = nullptr; - Data::ResolvedForwardDraft _toForward; - Ui::Text::String _toForwardFrom, _toForwardText; - int _toForwardNameVersion = 0; - MsgId _editMsgId = 0; HistoryItem *_replyEditMsg = nullptr; @@ -723,6 +719,7 @@ private: object_ptr _scheduled = { nullptr }; std::unique_ptr _ttlInfo; const std::unique_ptr _voiceRecordBar; + const std::unique_ptr _forwardPanel; std::unique_ptr _composeSearch; bool _cmdStartShown = false; object_ptr _field; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 08d2380e3f..32f71fc0d1 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/controls/history_view_ttl_button.h" +#include "history/view/controls/history_view_forward_panel.h" #include "history/view/history_view_webpage_preview.h" #include "inline_bots/bot_attach_web_view.h" #include "inline_bots/inline_results_widget.h" @@ -89,7 +90,8 @@ using MessageToEdit = ComposeControls::MessageToEdit; using VoiceToSend = ComposeControls::VoiceToSend; using SendActionUpdate = ComposeControls::SendActionUpdate; using SetHistoryArgs = ComposeControls::SetHistoryArgs; -using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar; +using VoiceRecordBar = Controls::VoiceRecordBar; +using ForwardPanel = Controls::ForwardPanel; [[nodiscard]] auto ShowWebPagePreview(WebPageData *page) { return page && (page->pendingTill >= 0); @@ -331,13 +333,18 @@ rpl::producer WebpageProcessor::pageDataChanges() const { class FieldHeader final : public Ui::RpWidget { public: - FieldHeader(QWidget *parent, not_null data); + FieldHeader( + QWidget *parent, + not_null controller); void setHistory(const SetHistoryArgs &args); void init(); void editMessage(FullMsgId id); void replyToMessage(FullMsgId id); + void updateForwarding( + Data::Thread *thread, + Data::ResolvedForwardDraft items); void previewRequested( rpl::producer title, rpl::producer description, @@ -345,6 +352,7 @@ public: [[nodiscard]] bool isDisplayed() const; [[nodiscard]] bool isEditingMessage() const; + [[nodiscard]] bool readyToForward() const; [[nodiscard]] FullMsgId replyingToMessage() const; [[nodiscard]] rpl::producer editMsgId() const; [[nodiscard]] rpl::producer scrollToItemRequests() const; @@ -358,6 +366,9 @@ public: [[nodiscard]] rpl::producer<> replyCancelled() const { return _replyCancelled.events(); } + [[nodiscard]] rpl::producer<> forwardCancelled() const { + return _forwardCancelled.events(); + } [[nodiscard]] rpl::producer<> previewCancelled() const { return _previewCancelled.events(); } @@ -374,6 +385,9 @@ private: void paintWebPage(Painter &p, not_null peer); void paintEditOrReplyToMessage(Painter &p); + void paintForwardInfo(Painter &p); + + bool hasPreview() const; struct Preview { WebPageData *data = nullptr; @@ -382,6 +396,7 @@ private: bool cancelled = false; }; + const not_null _controller; History *_history = nullptr; rpl::variable _title; rpl::variable _description; @@ -389,12 +404,13 @@ private: Preview _preview; rpl::event_stream<> _editCancelled; rpl::event_stream<> _replyCancelled; + rpl::event_stream<> _forwardCancelled; rpl::event_stream<> _previewCancelled; - bool hasPreview() const; - rpl::variable _editMsgId; rpl::variable _replyToId; + std::unique_ptr _forwardPanel; + rpl::producer<> _toForwardUpdated; HistoryItem *_shownMessage = nullptr; Ui::Text::String _shownMessageName; @@ -412,9 +428,14 @@ private: }; -FieldHeader::FieldHeader(QWidget *parent, not_null data) +FieldHeader::FieldHeader( + QWidget *parent, + not_null controller) : RpWidget(parent) -, _data(data) +, _controller(controller) +, _forwardPanel( + std::make_unique([=] { customEmojiRepaint(); })) +, _data(&controller->session().data()) , _cancel(Ui::CreateChild(this, st::historyReplyCancel)) { resize(QSize(parent->width(), st::historyReplyHeight)); init(); @@ -430,6 +451,11 @@ void FieldHeader::init() { updateControlsGeometry(size); }, lifetime()); + _forwardPanel->itemsUpdated( + ) | rpl::start_with_next([=] { + updateVisible(); + }, lifetime()); + const auto leftIconPressed = lifetime().make_state(false); paintRequest( ) | rpl::start_with_next([=] { @@ -439,15 +465,19 @@ void FieldHeader::init() { const auto position = st::historyReplyIconPosition; if (isEditingMessage()) { st::historyEditIcon.paint(p, position, width()); + } else if (readyToForward()) { + st::historyForwardIcon.paint(p, position, width()); } else if (replyingToMessage()) { st::historyReplyIcon.paint(p, position, width()); } - (!ShowWebPagePreview(_preview.data) || *leftIconPressed) - ? paintEditOrReplyToMessage(p) - : paintWebPage( + (ShowWebPagePreview(_preview.data) && !*leftIconPressed) + ? paintWebPage( p, - _history ? _history->peer : _data->session().user()); + _history ? _history->peer : _data->session().user()) + : (isEditingMessage() || !readyToForward()) + ? paintEditOrReplyToMessage(p) + : paintForwardInfo(p); }, lifetime()); _editMsgId.value( @@ -487,6 +517,8 @@ void FieldHeader::init() { _previewCancelled.fire({}); } else if (_editMsgId.current()) { _editCancelled.fire({}); + } else if (readyToForward()) { + _forwardCancelled.fire({}); } else if (_replyToId.current()) { _replyCancelled.fire({}); } @@ -515,7 +547,9 @@ void FieldHeader::init() { events( ) | rpl::filter([=](not_null event) { return ranges::contains(kMouseEvents, event->type()) - && (isEditingMessage() || replyingToMessage()); + && (isEditingMessage() + || readyToForward() + || replyingToMessage()); }) | rpl::start_with_next([=](not_null event) { const auto type = event->type(); const auto e = static_cast(event.get()); @@ -538,10 +572,14 @@ void FieldHeader::init() { *leftIconPressed = true; update(); } else if (isLeftButton && inPreviewRect) { - auto id = isEditingMessage() - ? _editMsgId.current() - : replyingToMessage(); - _scrollToItemRequests.fire(std::move(id)); + if (!isEditingMessage() && readyToForward()) { + _forwardPanel->editOptions(_controller); + } else { + auto id = isEditingMessage() + ? _editMsgId.current() + : replyingToMessage(); + _scrollToItemRequests.fire(std::move(id)); + } } } else if (type == QEvent::MouseButtonRelease) { if (isLeftButton && *leftIconPressed) { @@ -761,6 +799,17 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) { }); } +void FieldHeader::paintForwardInfo(Painter &p) { + _repaintScheduled = false; + + const auto replySkip = st::historyReplySkip; + const auto availableWidth = width() + - replySkip + - _cancel->width() + - st::msgReplyPadding.right(); + _forwardPanel->paint(p, replySkip, 0, availableWidth, width()); +} + void FieldHeader::updateVisible() { isDisplayed() ? show() : hide(); _visibleChanged.fire(isVisible()); @@ -771,13 +820,20 @@ rpl::producer FieldHeader::visibleChanged() { } bool FieldHeader::isDisplayed() const { - return isEditingMessage() || replyingToMessage() || hasPreview(); + return isEditingMessage() + || readyToForward() + || replyingToMessage() + || hasPreview(); } bool FieldHeader::isEditingMessage() const { return !!_editMsgId.current(); } +bool FieldHeader::readyToForward() const { + return !_forwardPanel->empty(); +} + FullMsgId FieldHeader::replyingToMessage() const { return _replyToId.current(); } @@ -811,6 +867,15 @@ void FieldHeader::replyToMessage(FullMsgId id) { _replyToId = id; } +void FieldHeader::updateForwarding( + Data::Thread *thread, + Data::ResolvedForwardDraft items) { + _forwardPanel->update(thread, std::move(items)); + if (readyToForward()) { + replyToMessage({}); + } +} + rpl::producer FieldHeader::editMsgId() const { return _editMsgId.value(); } @@ -863,9 +928,7 @@ ComposeControls::ComposeControls( , _autocomplete(std::make_unique( parent, window)) -, _header(std::make_unique( - _wrap.get(), - &_window->session().data())) +, _header(std::make_unique(_wrap.get(), _window)) , _voiceRecordBar(std::make_unique( _wrap.get(), parent, @@ -893,11 +956,10 @@ Main::Session &ComposeControls::session() const { void ComposeControls::setHistory(SetHistoryArgs &&args) { // Right now only single non-null set of history is supported. // Otherwise initWebpageProcess should be updated / rewritten. - Expects(!_history && *args.history); + Expects(!_history && (*args.history)); _showSlowmodeError = std::move(args.showSlowmodeError); _sendActionFactory = std::move(args.sendActionFactory); - _topicRootId = args.topicRootId; _slowmodeSecondsLeft = rpl::single(0) | rpl::then(std::move(args.slowmodeSecondsLeft)); _sendDisabledBySlowmode = rpl::single(false) @@ -905,9 +967,9 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { _writeRestriction = rpl::single(std::optional()) | rpl::then(std::move(args.writeRestriction)); const auto history = *args.history; - //if (_history == history) { - // return; - //} + if (_history == history) { + return; + } unregisterDraftSources(); _history = history; _header->setHistory(args); @@ -915,6 +977,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { _window->tabbedSelector()->setCurrentPeer( history ? history->peer.get() : nullptr); initWebpageProcess(); + initForwardProcess(); updateBotCommandShown(); updateMessagesTTLShown(); updateControlsGeometry(_wrap->size()); @@ -942,7 +1005,10 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { } void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) { + unregisterDraftSources(); _currentDialogsEntryState = state; + updateForwarding(); + registerDraftSource(); if (_inlineResults) { _inlineResults->setCurrentDialogsEntryState(state); } @@ -1329,6 +1395,11 @@ void ComposeControls::init() { cancelReplyMessage(); }, _wrap->lifetime()); + _header->forwardCancelled( + ) | rpl::start_with_next([=] { + cancelForward(); + }, _wrap->lifetime()); + _header->visibleChanged( ) | rpl::start_with_next([=](bool shown) { updateHeight(); @@ -1384,7 +1455,7 @@ bool ComposeControls::showRecordButton() const { && !_voiceRecordBar->isListenState() && !_voiceRecordBar->isRecordingByAnotherBar() && !HasSendText(_field) - //&& !readyToForward() + && !readyToForward() && !isEditingMessage(); } @@ -1855,10 +1926,20 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { _header->replyToMessage({}); } else { _header->replyToMessage({ _history->peer->id, draft->msgId }); + if (_header->replyingToMessage()) { + cancelForward(); + } _header->editMessage({}); } } +void ComposeControls::cancelForward() { + _history->setForwardDraft( + _currentDialogsEntryState.rootId, + {}); + updateForwarding(); +} + void ComposeControls::fieldTabbed() { if (!_autocomplete->isHidden()) { _autocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab); @@ -2391,9 +2472,8 @@ void ComposeControls::toggleTabbedSelectorMode() { && !_window->adaptive().isOneColumn()) { Core::App().settings().setTabbedSelectorSectionEnabled(true); Core::App().saveSettingsDelayed(); - const auto topic = _topicRootId - ? _history->peer->forumTopicFor(_topicRootId) - : nullptr; + const auto topic = _history->peer->forumTopicFor( + _currentDialogsEntryState.rootId); pushTabbedSelectorToThirdSection( (topic ? topic : (Data::Thread*)_history), Window::SectionShow::Way::ClearStack); @@ -2499,6 +2579,9 @@ void ComposeControls::replyToMessage(FullMsgId id) { } } else { _header->replyToMessage(id); + if (_header->replyingToMessage()) { + cancelForward(); + } } _saveDraftText = true; @@ -2528,15 +2611,29 @@ void ComposeControls::cancelReplyMessage() { } } +void ComposeControls::updateForwarding() { + const auto rootId = _currentDialogsEntryState.rootId; + const auto thread = (_history && rootId) + ? _history->peer->forumTopicFor(rootId) + : (Data::Thread*)_history; + _header->updateForwarding(thread, thread + ? _history->resolveForwardDraft(rootId) + : Data::ResolvedForwardDraft()); + updateSendButtonType(); +} + bool ComposeControls::handleCancelRequest() { if (_isInlineBot) { cancelInlineBot(); return true; + } else if (_autocomplete && !_autocomplete->isHidden()) { + _autocomplete->hideAnimated(); + return true; } else if (isEditingMessage()) { cancelEditMessage(); return true; - } else if (_autocomplete && !_autocomplete->isHidden()) { - _autocomplete->hideAnimated(); + } else if (readyToForward()) { + cancelForward(); return true; } else if (replyingToMessage()) { cancelReplyMessage(); @@ -2591,6 +2688,22 @@ void ComposeControls::initWebpageProcess() { _preview->pageDataChanges()); } +void ComposeControls::initForwardProcess() { + using EntryUpdateFlag = Data::EntryUpdate::Flag; + session().changes().entryUpdates( + EntryUpdateFlag::ForwardDraft + ) | rpl::start_with_next([=](const Data::EntryUpdate &update) { + if (const auto topic = update.entry->asTopic()) { + if (topic->history() == _history + && topic->rootId() == _currentDialogsEntryState.rootId) { + updateForwarding(); + } + } + }, _wrap->lifetime()); + + updateForwarding(); +} + WebPageId ComposeControls::webPageId() const { return _header->webPageId(); } @@ -2613,6 +2726,10 @@ FullMsgId ComposeControls::replyingToMessage() const { return _header->replyingToMessage(); } +bool ComposeControls::readyToForward() const { + return _header->readyToForward(); +} + bool ComposeControls::isLockPresent() const { return _voiceRecordBar->isLockPresent(); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index c3b3bcf9bc..421e922b0f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -149,6 +149,7 @@ public: bool returnTabbedSelector(); [[nodiscard]] bool isEditingMessage() const; + [[nodiscard]] bool readyToForward() const; [[nodiscard]] FullMsgId replyingToMessage() const; [[nodiscard]] bool preventsClose(Fn &&continueCallback) const; @@ -164,6 +165,9 @@ public: void replyToMessage(FullMsgId id); void cancelReplyMessage(); + void updateForwarding(); + void cancelForward(); + bool handleCancelRequest(); [[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const; @@ -208,6 +212,7 @@ private: void initSendButton(); void initSendAsButton(); void initWebpageProcess(); + void initForwardProcess(); void initWriteRestriction(); void initVoiceRecordBar(); void initAutocomplete(); @@ -294,7 +299,6 @@ private: rpl::variable _sendDisabledBySlowmode; rpl::variable> _writeRestriction; rpl::variable _hidden; - MsgId _topicRootId = 0; Mode _mode = Mode::Normal; const std::unique_ptr _wrap; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp new file mode 100644 index 0000000000..88c65d6242 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -0,0 +1,362 @@ +/* +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/controls/history_view_forward_panel.h" + +#include "history/history.h" +#include "history/history_message.h" +#include "history/history_item_components.h" +#include "history/view/history_view_item_preview.h" +#include "data/data_session.h" +#include "data/data_media_types.h" +#include "data/data_forum_topic.h" +#include "main/main_session.h" +#include "ui/chat/forward_options_box.h" +#include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" +#include "ui/painter.h" +#include "core/ui_integration.h" +#include "lang/lang_keys.h" +#include "window/window_peer_menu.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" + +namespace HistoryView::Controls { +namespace { + +constexpr auto kUnknownVersion = -1; +constexpr auto kNameWithCaptionsVersion = -2; +constexpr auto kNameNoCaptionsVersion = -3; + +} // namespace + +ForwardPanel::ForwardPanel(Fn repaint) +: _repaint(std::move(repaint)) { +} + +void ForwardPanel::update( + Data::Thread *to, + Data::ResolvedForwardDraft draft) { + if (_to == to + && _data.items == draft.items + && _data.options == draft.options) { + return; + } + _dataLifetime.destroy(); + _data = std::move(draft); + _to = to; + if (!empty()) { + Assert(to != nullptr); + + _data.items.front()->history()->owner().itemRemoved( + ) | rpl::start_with_next([=](not_null item) { + itemRemoved(item); + }, _dataLifetime); + + if (const auto topic = _to->asTopic()) { + topic->destroyed( + ) | rpl::start_with_next([=] { + update(nullptr, {}); + }, _dataLifetime); + } + + updateTexts(); + } + _itemsUpdated.fire({}); +} + +rpl::producer<> ForwardPanel::itemsUpdated() const { + return _itemsUpdated.events(); +} + +void ForwardPanel::checkTexts() { + if (empty()) { + return; + } + const auto keepNames = (_data.options + == Data::ForwardOptions::PreserveInfo); + const auto keepCaptions = (_data.options + != Data::ForwardOptions::NoNamesAndCaptions); + auto version = keepNames + ? 0 + : keepCaptions + ? kNameWithCaptionsVersion + : kNameNoCaptionsVersion; + if (keepNames) { + for (const auto item : _data.items) { + if (const auto from = item->senderOriginal()) { + version += from->nameVersion(); + } else if (const auto info = item->hiddenSenderInfo()) { + ++version; + } else { + Unexpected("Corrupt forwarded information in message."); + } + } + } + if (_nameVersion != version) { + _nameVersion = version; + updateTexts(); + } +} + +void ForwardPanel::updateTexts() { + const auto repainter = gsl::finally([&] { + _repaint(); + }); + if (empty()) { + _from.clear(); + _text.clear(); + return; + } + int32 version = 0; + QString from; + TextWithEntities text; + const auto keepNames = (_data.options + == Data::ForwardOptions::PreserveInfo); + const auto keepCaptions = (_data.options + != Data::ForwardOptions::NoNamesAndCaptions); + if (const auto count = int(_data.items.size())) { + auto insertedPeers = base::flat_set>(); + auto insertedNames = base::flat_set(); + auto fullname = QString(); + auto names = std::vector(); + names.reserve(_data.items.size()); + for (const auto item : _data.items) { + if (const auto from = item->senderOriginal()) { + if (!insertedPeers.contains(from)) { + insertedPeers.emplace(from); + names.push_back(from->shortName()); + fullname = from->name(); + } + } else if (const auto info = item->hiddenSenderInfo()) { + if (!insertedNames.contains(info->name)) { + insertedNames.emplace(info->name); + names.push_back(info->firstName); + fullname = info->name; + } + } else { + Unexpected("Corrupt forwarded information in message."); + } + } + if (!keepNames) { + from = tr::lng_forward_sender_names_removed(tr::now); + } else if (names.size() > 2) { + from = tr::lng_forwarding_from( + tr::now, + lt_count, + names.size() - 1, + lt_user, + names[0]); + } else if (names.size() < 2) { + from = fullname; + } else { + from = tr::lng_forwarding_from_two( + tr::now, + lt_user, + names[0], + lt_second_user, + names[1]); + } + + if (count < 2) { + const auto item = _data.items.front(); + text = item->toPreview({ + .hideSender = true, + .hideCaption = !keepCaptions, + .generateImages = false, + }).text; + const auto history = item->history(); + const auto dropCustomEmoji = !history->session().premium() + && !_to->owningHistory()->peer->isSelf() + && (item->computeDropForwardedInfo() || !keepNames); + if (dropCustomEmoji) { + text = DropCustomEmoji(std::move(text)); + } + } else { + text = Ui::Text::PlainLink( + tr::lng_forward_messages(tr::now, lt_count, count)); + } + } + _from.setText(st::msgNameStyle, from, Ui::NameTextOptions()); + const auto context = Core::MarkedTextContext{ + .session = &_to->session(), + .customEmojiRepaint = _repaint, + }; + _text.setMarkedText( + st::messageTextStyle, + text, + Ui::DialogTextOptions(), + context); +} + +void ForwardPanel::refreshTexts() { + _nameVersion = kUnknownVersion; + checkTexts(); +} + +void ForwardPanel::itemRemoved(not_null item) { + const auto i = ranges::find(_data.items, item); + if (i != end(_data.items)) { + _data.items.erase(i); + refreshTexts(); + _itemsUpdated.fire({}); + } +} + +const HistoryItemsList &ForwardPanel::items() const { + return _data.items; +} + +bool ForwardPanel::empty() const { + return _data.items.empty(); +} + +void ForwardPanel::editOptions( + not_null controller) { + using Options = Data::ForwardOptions; + const auto now = _data.options; + const auto count = _data.items.size(); + const auto dropNames = (now != Options::PreserveInfo); + const auto hasCaptions = [&] { + for (const auto item : _data.items) { + if (const auto media = item->media()) { + if (!item->originalText().text.isEmpty() + && media->allowsEditCaption()) { + return true; + } + } + } + return false; + }(); + const auto hasOnlyForcedForwardedInfo = [&] { + if (hasCaptions) { + return false; + } + for (const auto item : _data.items) { + if (const auto media = item->media()) { + if (!media->forceForwardedInfo()) { + return false; + } + } else { + return false; + } + } + return true; + }(); + const auto dropCaptions = (now == Options::NoNamesAndCaptions); + const auto weak = base::make_weak(this); + const auto changeRecipient = crl::guard(this, [=] { + if (_data.items.empty()) { + return; + } + auto data = base::take(_data); + _to->owningHistory()->setForwardDraft(_to->topicRootId(), {}); + Window::ShowForwardMessagesBox(controller, { + .ids = _to->owner().itemsToIds(data.items), + .options = data.options, + }); + }); + if (hasOnlyForcedForwardedInfo) { + changeRecipient(); + return; + } + const auto optionsChanged = crl::guard(weak, [=]( + Ui::ForwardOptions options) { + if (_data.items.empty()) { + return; + } + const auto newOptions = (options.hasCaptions + && options.dropCaptions) + ? Options::NoNamesAndCaptions + : options.dropNames + ? Options::NoSenderNames + : Options::PreserveInfo; + if (_data.options != newOptions) { + _data.options = newOptions; + _to->owningHistory()->setForwardDraft(_to->topicRootId(), { + .ids = _to->owner().itemsToIds(_data.items), + .options = newOptions, + }); + _repaint(); + } + }); + controller->show(Box( + Ui::ForwardOptionsBox, + count, + Ui::ForwardOptions{ + .dropNames = dropNames, + .hasCaptions = hasCaptions, + .dropCaptions = dropCaptions, + }, + optionsChanged, + changeRecipient)); +} + +void ForwardPanel::paint( + Painter &p, + int x, + int y, + int available, + int outerWidth) const { + if (empty()) { + return; + } + const_cast(this)->checkTexts(); + const auto firstItem = _data.items.front(); + const auto firstMedia = firstItem->media(); + const auto hasPreview = (_data.items.size() < 2) + && firstMedia + && firstMedia->hasReplyPreview(); + const auto preview = hasPreview ? firstMedia->replyPreview() : nullptr; + if (preview) { + auto to = QRect( + x, + y + st::msgReplyPadding.top(), + st::msgReplyBarSize.height(), + st::msgReplyBarSize.height()); + if (preview->width() == preview->height()) { + p.drawPixmap(to.x(), to.y(), preview->pix()); + } else { + auto from = (preview->width() > preview->height()) + ? QRect( + (preview->width() - preview->height()) / 2, + 0, + preview->height(), + preview->height()) + : QRect( + 0, + (preview->height() - preview->width()) / 2, + preview->width(), + preview->width()); + p.drawPixmap(to, preview->pix(), from); + } + const auto skip = st::msgReplyBarSize.height() + + st::msgReplyBarSkip + - st::msgReplyBarSize.width() + - st::msgReplyBarPos.x(); + x += skip; + available -= skip; + } + p.setPen(st::historyReplyNameFg); + _from.drawElided( + p, + x, + y + st::msgReplyPadding.top(), + available); + p.setPen(st::historyComposeAreaFg); + _text.draw(p, { + .position = QPoint( + x, + y + st::msgReplyPadding.top() + st::msgServiceNameFont->height), + .availableWidth = available, + .palette = &st::historyComposeAreaPalette, + .spoiler = Ui::Text::DefaultSpoilerCache(), + .elisionLines = 1, + }); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h new file mode 100644 index 0000000000..41aca59e38 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h @@ -0,0 +1,64 @@ +/* +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 "history/history.h" +#include "ui/text/text.h" +#include "base/weak_ptr.h" + +class Painter; +class HistoryItem; + +namespace Data { +class Thread; +} // namespace Data + +namespace Window { +class SessionController; +} // namespace Window + +namespace HistoryView::Controls { + +class ForwardPanel final : public base::has_weak_ptr { +public: + explicit ForwardPanel(Fn repaint); + + void update(Data::Thread *to, Data::ResolvedForwardDraft draft); + void paint( + Painter &p, + int x, + int y, + int available, + int outerWidth) const; + + [[nodiscard]] rpl::producer<> itemsUpdated() const; + + void editOptions(not_null controller); + + [[nodiscard]] const HistoryItemsList &items() const; + [[nodiscard]] bool empty() const; + +private: + void checkTexts(); + void updateTexts(); + void refreshTexts(); + void itemRemoved(not_null item); + + Fn _repaint; + + Data::Thread *_to = nullptr; + Data::ResolvedForwardDraft _data; + rpl::lifetime _dataLifetime; + + rpl::event_stream<> _itemsUpdated; + Ui::Text::String _from, _text; + int _nameVersion = 0; + +}; + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 8164da1a32..45ace90bf9 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -669,6 +669,7 @@ void RepliesWidget::setupComposeControls() { _composeControls->setHistory({ .history = _history.get(), + .topicRootId = _topic ? _topic->rootId() : MsgId(0), .showSlowmodeError = [=] { return showSlowmodeError(); }, .sendActionFactory = [=] { return prepareSendAction({}); }, .slowmodeSecondsLeft = std::move(slowmodeSecondsLeft), @@ -708,6 +709,7 @@ void RepliesWidget::setupComposeControls() { return; } listSendBotCommand(command, FullMsgId()); + session().api().finishForwarding(prepareSendAction({})); }, lifetime()); const auto saveEditMsgRequestId = lifetime().make_state(0); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index b37640fd4b..233569550e 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -132,16 +132,17 @@ struct ParsedBot { void ShowChooseBox( not_null controller, PeerTypes types, - Fn)> callback) { + Fn)> callback) { const auto weak = std::make_shared>(); - auto done = [=](not_null peer) mutable { + auto done = [=](not_null thread) mutable { if (const auto strong = *weak) { strong->closeBox(); } - callback(peer); + callback(thread); }; - auto filter = [=](not_null peer) -> bool { - if (!peer->canWrite()) { // #TODO forum forward + auto filter = [=](not_null thread) -> bool { + const auto peer = thread->owningHistory()->peer; + if (!thread->canWrite()) { return false; } else if (const auto user = peer->asUser()) { if (user->isBot()) { @@ -577,16 +578,15 @@ void AttachWebView::requestAddToMenu( const auto open = [=](PeerTypes types) { if (const auto useTypes = chooseTypes & types) { if (const auto strong = chooseController.get()) { - const auto callback = [=](not_null peer) { - const auto history = peer->owner().history(peer); - strong->showPeerHistory(history); + const auto done = [=](not_null thread) { + strong->showThread(thread); request( nullptr, - Api::SendAction(history), + Api::SendAction(thread), bot, { .startCommand = startCommand }); }; - ShowChooseBox(strong, useTypes, callback); + ShowChooseBox(strong, useTypes, done); } return true; } else if (!contextAction) { diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index df063ebb15..1fa3c06f4c 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "window/window_history_hider.h" #include "window/window_controller.h" +#include "window/window_peer_menu.h" #include "window/themes/window_theme.h" #include "chat_helpers/tabbed_selector.h" // TabbedSelector::refreshStickers #include "chat_helpers/message_field.h" @@ -307,27 +308,23 @@ MainWidget::MainWidget( session().changes().historyUpdates( Data::HistoryUpdate::Flag::MessageSent - | Data::HistoryUpdate::Flag::LocalDraftSet ) | rpl::start_with_next([=](const Data::HistoryUpdate &update) { const auto history = update.history; - if (update.flags & Data::HistoryUpdate::Flag::MessageSent) { - history->forgetScrollState(); - if (const auto from = history->peer->migrateFrom()) { - auto &owner = history->owner(); - if (const auto migrated = owner.historyLoaded(from)) { - migrated->forgetScrollState(); - } + history->forgetScrollState(); + if (const auto from = history->peer->migrateFrom()) { + auto &owner = history->owner(); + if (const auto migrated = owner.historyLoaded(from)) { + migrated->forgetScrollState(); } } - if (update.flags & Data::HistoryUpdate::Flag::LocalDraftSet) { - const auto opened = (_history->peer() == history->peer.get()); - if (opened) { - _history->applyDraft(); - } else { - Ui::showPeerHistory(history, ShowAtUnreadMsgId); - } - _controller->hideLayer(); - } + }, lifetime()); + + session().changes().entryUpdates( + Data::EntryUpdate::Flag::LocalDraftSet + ) | rpl::start_with_next([=](const Data::EntryUpdate &update) { + controller->showThread(update.entry->asThread(), ShowAtUnreadMsgId); + _history->applyDraft(); // #TODO forum drop + controller->hideLayer(); }, lifetime()); // MSVC BUG + REGRESSION rpl::mappers::tuple :( @@ -515,36 +512,38 @@ void MainWidget::floatPlayerDoubleClickEvent( _controller->showMessage(item); } -bool MainWidget::setForwardDraft(PeerId peerId, Data::ForwardDraft &&draft) { - Expects(peerId != 0); - - const auto peer = session().data().peer(peerId); +bool MainWidget::setForwardDraft( + not_null thread, + Data::ForwardDraft &&draft) { + const auto history = thread->owningHistory(); + const auto peer = history->peer; const auto items = session().data().idsToItems(draft.ids); + const auto topicRootId = thread->topicRootId(); const auto error = GetErrorTextForSending( - peer, // #TODO forum forward - { .forward = &items, .ignoreSlowmodeCountdown = true }); + history->peer, + { + .topicRootId = topicRootId, + .forward = &items, + .ignoreSlowmodeCountdown = true, + }); if (!error.isEmpty()) { Ui::show(Ui::MakeInformBox(error), Ui::LayerOption::KeepOther); return false; } - peer->owner().history(peer)->setForwardDraft(std::move(draft)); - _controller->showPeerHistory( - peer, - SectionShow::Way::Forward, - ShowAtUnreadMsgId); - _history->cancelReply(); + history->setForwardDraft(topicRootId, std::move(draft)); + _controller->showThread( + thread, + ShowAtUnreadMsgId, + SectionShow::Way::Forward); return true; } bool MainWidget::shareUrl( - PeerId peerId, + not_null thread, const QString &url, const QString &text) const { - Expects(peerId != 0); - - const auto peer = session().data().peer(peerId); - if (!peer->canWrite()) { // #TODO forum forward + if (!thread->canWrite()) { _controller->show(Ui::MakeInformBox(tr::lng_share_cant())); return false; } @@ -557,8 +556,8 @@ bool MainWidget::shareUrl( int(url.size()) + 1 + int(text.size()), QFIXED_MAX }; - const auto history = peer->owner().history(peer); - const auto topicRootId = 0; + const auto history = thread->owningHistory(); + const auto topicRootId = thread->topicRootId(); history->setLocalDraft(std::make_unique( textWithTags, 0, // replyTo @@ -566,23 +565,19 @@ bool MainWidget::shareUrl( cursor, Data::PreviewState::Allowed)); history->clearLocalEditDraft(topicRootId); - history->session().changes().historyUpdated( - history, - Data::HistoryUpdate::Flag::LocalDraftSet); + history->session().changes().entryUpdated( + thread, + Data::EntryUpdate::Flag::LocalDraftSet); return true; } bool MainWidget::inlineSwitchChosen( - PeerId peerId, + not_null thread, const QString &botAndQuery) const { - Expects(peerId != 0); - - const auto peer = session().data().peer(peerId); - if (!peer->canWrite()) { // #TODO forum forward + if (!thread->canWrite()) { // #TODO forum forward Ui::show(Ui::MakeInformBox(tr::lng_inline_switch_cant())); return false; } - const auto h = peer->owner().history(peer); const auto textWithTags = TextWithTags{ botAndQuery, TextWithTags::Tags(), @@ -592,61 +587,74 @@ bool MainWidget::inlineSwitchChosen( int(botAndQuery.size()), QFIXED_MAX }; - const auto topicRootId = 0; - h->setLocalDraft(std::make_unique( + const auto history = thread->owningHistory(); + const auto topicRootId = thread->topicRootId(); + history->setLocalDraft(std::make_unique( textWithTags, 0, // replyTo topicRootId, cursor, Data::PreviewState::Allowed)); - h->clearLocalEditDraft(topicRootId); - h->session().changes().historyUpdated( - h, - Data::HistoryUpdate::Flag::LocalDraftSet); + history->clearLocalEditDraft(topicRootId); + thread->session().changes().entryUpdated( + thread, + Data::EntryUpdate::Flag::LocalDraftSet); return true; } -bool MainWidget::sendPaths(PeerId peerId) { - Expects(peerId != 0); - - auto peer = session().data().peer(peerId); - if (!peer->canWrite()) { // #TODO forum forward +bool MainWidget::sendPaths(not_null thread) { + if (!thread->canWrite()) { Ui::show(Ui::MakeInformBox(tr::lng_forward_send_files_cant())); return false; } else if (const auto error = Data::RestrictionError( - peer, + thread->owningHistory()->peer, ChatRestriction::SendMedia)) { Ui::show(Ui::MakeInformBox(*error)); return false; + } else { + controller()->showThread( + thread, + ShowAtTheEndMsgId, + Window::SectionShow::Way::ClearStack); } - Ui::showPeerHistory(peer, ShowAtTheEndMsgId); - return _history->confirmSendingFiles(cSendPaths()); + // #TODO forum drop + return (controller()->activeChatCurrent().thread() == thread) + && _history->confirmSendingFiles(cSendPaths()); } void MainWidget::onFilesOrForwardDrop( - const PeerId &peerId, + not_null thread, const QMimeData *data) { - Expects(peerId != 0); - if (data->hasFormat(qsl("application/x-td-forward"))) { auto draft = Data::ForwardDraft{ .ids = session().data().takeMimeForwardIds(), }; - if (!setForwardDraft(peerId, std::move(draft))) { - // We've already released the mouse button, so the forwarding is cancelled. - if (_hider) { - _hider->startHide(); - clearHider(_hider); - } - } - } else { - auto peer = session().data().peer(peerId); - if (!peer->canWrite()) { // #TODO forum forward - Ui::show(Ui::MakeInformBox(tr::lng_forward_send_files_cant())); + const auto history = thread->asHistory(); + if (const auto forum = history ? history->peer->forum() : nullptr) { + Window::ShowForwardMessagesBox( + _controller, + std::move(draft), + forum); + } else if (setForwardDraft(thread, std::move(draft))) { return; } - Ui::showPeerHistory(peer, ShowAtTheEndMsgId); - _history->confirmSendingFiles(data); + // We've already released the mouse button, + // so the forwarding is cancelled. + if (_hider) { + _hider->startHide(); + clearHider(_hider); + } + } else if (!thread->canWrite()) { + Ui::show(Ui::MakeInformBox(tr::lng_forward_send_files_cant())); + } else { + controller()->showThread( + thread, + ShowAtTheEndMsgId, + Window::SectionShow::Way::ClearStack); + if (thread->asHistory()) { + // #TODO forum drop + _history->confirmSendingFiles(data); + } } } @@ -735,8 +743,9 @@ void MainWidget::hiderLayer(base::unique_qptr hider) { } void MainWidget::showForwardLayer(Data::ForwardDraft &&draft) { - auto callback = [=, draft = std::move(draft)](PeerId peer) mutable { - return setForwardDraft(peer, std::move(draft)); + auto callback = [=, draft = std::move(draft)]( + not_null thread) mutable { + return setForwardDraft(thread, std::move(draft)); }; hiderLayer(base::make_unique_q( this, @@ -749,7 +758,7 @@ void MainWidget::showSendPathsLayer() { hiderLayer(base::make_unique_q( this, tr::lng_forward_choose(tr::now), - [=](PeerId peer) { return sendPaths(peer); }, + [=](not_null thread) { return sendPaths(thread); }, _controller->adaptive().oneColumnValue())); if (_hider) { connect(_hider, &QObject::destroyed, [] { @@ -763,8 +772,8 @@ void MainWidget::shareUrlLayer(const QString &url, const QString &text) { if (url.trimmed().startsWith('@')) { return; } - auto callback = [=](PeerId peer) { - return shareUrl(peer, url, text); + auto callback = [=](not_null thread) { + return shareUrl(thread, url, text); }; hiderLayer(base::make_unique_q( this, @@ -774,8 +783,8 @@ void MainWidget::shareUrlLayer(const QString &url, const QString &text) { } void MainWidget::inlineSwitchLayer(const QString &botAndQuery) { - auto callback = [=](PeerId peer) { - return inlineSwitchChosen(peer, botAndQuery); + auto callback = [=](not_null thread) { + return inlineSwitchChosen(thread, botAndQuery); }; hiderLayer(base::make_unique_q( this, @@ -788,6 +797,14 @@ bool MainWidget::selectingPeer() const { return _hider ? true : false; } +void MainWidget::clearSelectingPeer() { + if (_hider) { + _hider->startHide(); + _hider.release(); + controller()->setSelectingPeer(false); + } +} + void MainWidget::sendBotCommand(Bot::SendCommandRequest request) { const auto type = _mainSection ? _mainSection->sendBotCommand(request) @@ -1241,16 +1258,20 @@ void MainWidget::setInnerFocus() { } } -void MainWidget::choosePeer(PeerId peerId, MsgId showAtMsgId) { +void MainWidget::chooseThread( + not_null thread, + MsgId showAtMsgId) { if (selectingPeer()) { - _hider->offerPeer(peerId); - } else if (peerId) { - Ui::showPeerHistory(session().data().peer(peerId), showAtMsgId); + _hider->offerThread(thread); } else { - Ui::showChatsList(&session()); + controller()->showThread(thread, showAtMsgId); } } +void MainWidget::chooseThread(not_null peer, MsgId showAtMsgId) { + chooseThread(peer->owner().history(peer), showAtMsgId); +} + void MainWidget::clearBotStartToken(PeerData *peer) { if (peer && peer->isUser() && peer->asUser()->isBot()) { peer->asUser()->botInfo->startToken = QString(); @@ -1402,11 +1423,6 @@ void MainWidget::ui_showPeerHistory( if (params.activation != anim::activation::background) { controller()->window().hideSettingsAndLayer(); } - if (_hider) { - _hider->startHide(); - _hider.release(); - controller()->setSelectingPeer(false); - } auto animatedShow = [&] { if (_a_show.animating() diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 94cb3ed776..d2a5676a78 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -42,6 +42,7 @@ class Session; } // namespace Main namespace Data { +class Thread; class WallPaper; struct ForwardDraft; } // namespace Data @@ -174,10 +175,15 @@ public: void shareUrlLayer(const QString &url, const QString &text); void inlineSwitchLayer(const QString &botAndQuery); void hiderLayer(base::unique_qptr h); - bool setForwardDraft(PeerId peer, Data::ForwardDraft &&draft); - bool sendPaths(PeerId peerId); - void onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data); + bool setForwardDraft( + not_null thread, + Data::ForwardDraft &&draft); + bool sendPaths(not_null thread); + void onFilesOrForwardDrop( + not_null thread, + const QMimeData *data); bool selectingPeer() const; + void clearSelectingPeer(); void sendBotCommand(Bot::SendCommandRequest request); void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); @@ -192,8 +198,9 @@ public: void checkChatBackground(); Image *newBackgroundThumb(); - // Does offerPeer or showPeerHistory. - void choosePeer(PeerId peerId, MsgId showAtMsgId); + // Does offerThread or showThread. + void chooseThread(not_null thread, MsgId showAtMsgId); + void chooseThread(not_null peer, MsgId showAtMsgId); void clearBotStartToken(PeerData *peer); void ctrlEnterSubmitUpdated(); @@ -251,10 +258,12 @@ private: -> std::shared_ptr; bool shareUrl( - PeerId peerId, + not_null thread, const QString &url, const QString &text) const; - bool inlineSwitchChosen(PeerId peerId, const QString &botAndQuery) const; + bool inlineSwitchChosen( + not_null thread, + const QString &botAndQuery) const; void setupConnectingWidget(); void createPlayer(); diff --git a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm index 59888a1099..3f7180f44c 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm @@ -778,13 +778,14 @@ TimeId CalculateOnlineTill(not_null peer) { } }; Core::Sandbox::Instance().customEnterFromEventLoop([=] { - (_hasArchive && (index == (_selfUnpinned ? -2 : -1))) - ? openFolder() - : controller->content()->choosePeer( - (_selfUnpinned && index == -1) - ? _session->userPeerId() - : peer->id, - ShowAtUnreadMsgId); + if (_hasArchive && (index == (_selfUnpinned ? -2 : -1))) { + openFolder(); + } else { + const auto chosen = (_selfUnpinned && index == -1) + ? _session->user() + : peer; + controller->content()->chooseThread(chosen, ShowAtUnreadMsgId); + } }); } diff --git a/Telegram/SourceFiles/window/window_history_hider.cpp b/Telegram/SourceFiles/window/window_history_hider.cpp index 4eff8d4f6f..0ab412ca55 100644 --- a/Telegram/SourceFiles/window/window_history_hider.cpp +++ b/Telegram/SourceFiles/window/window_history_hider.cpp @@ -20,7 +20,7 @@ namespace Window { HistoryHider::HistoryHider( QWidget *parent, const QString &text, - Fn confirm, + Fn)> confirm, rpl::producer oneColumnValue) : RpWidget(parent) , _text(text) @@ -122,8 +122,8 @@ void HistoryHider::updateControlsGeometry() { _box = QRect((width() - w) / 2, (height() - h) / 2, w, h); } -void HistoryHider::offerPeer(PeerId peer) { - if (_confirm(peer)) { +void HistoryHider::offerThread(not_null thread) { + if (_confirm(thread)) { startHide(); } } diff --git a/Telegram/SourceFiles/window/window_history_hider.h b/Telegram/SourceFiles/window/window_history_hider.h index 74d37d2163..38c2338033 100644 --- a/Telegram/SourceFiles/window/window_history_hider.h +++ b/Telegram/SourceFiles/window/window_history_hider.h @@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "ui/effects/animations.h" +namespace Data { +class Thread; +} // namespace Data + namespace Ui { class RoundButton; } // namespace Ui @@ -33,10 +37,10 @@ public: HistoryHider( QWidget *parent, const QString &text, - Fn confirm, + Fn)> confirm, rpl::producer oneColumnValue); - void offerPeer(PeerId peer); + void offerThread(not_null thread); void startHide(); void confirm(); @@ -57,7 +61,7 @@ private: void animationCallback(); QString _text; - Fn _confirm; + Fn)> _confirm; Ui::Animations::Simple _a_opacity; QRect _box; diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 165d82131b..f4c964639a 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -691,9 +691,8 @@ void MainMenu::setupMenu() { tr::lng_saved_messages(), { &st::settingsIconSavedMessages, kIconLightBlue } )->setClickedCallback([=] { - controller->content()->choosePeer( - controller->session().userPeerId(), - ShowAtUnreadMsgId); + const auto self = controller->session().user(); + controller->content()->chooseThread(self, ShowAtUnreadMsgId); }); } else { addAction( diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 6e6597ad4f..b92700c455 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -188,6 +188,27 @@ void PeerMenuAddMuteSubmenuAction( } } +void ForwardToSelf( + not_null navigation, + const Data::ForwardDraft &draft) { + const auto content = navigation->parentController()->content(); + const auto session = &navigation->session(); + const auto history = session->data().history(session->user()); + auto resolved = history->resolveForwardDraft(draft); + if (!resolved.items.empty()) { + auto action = Api::SendAction(history); + action.clearDraft = false; + action.generateLocal = false; + const auto weakContent = Ui::MakeWeak(content); + session->api().forwardMessages( + std::move(resolved), + action, + crl::guard(weakContent, [w = weakContent] { + Ui::Toast::Show(w, tr::lng_share_done(tr::now)); + })); + } +} + class Filler { public: Filler( @@ -1227,14 +1248,15 @@ void PeerMenuShareContactBox( not_null user) { // There is no async to make weak from controller. const auto weak = std::make_shared>(); - auto callback = [=](not_null peer) { - if (!peer->canWrite()) { // #TODO forum forward + auto callback = [=](not_null thread) { + const auto peer = thread->owningHistory()->peer; + if (!thread->canWrite()) { navigation->parentController()->show( Ui::MakeInformBox(tr::lng_forward_share_cant()), Ui::LayerOption::KeepOther); return; } else if (peer->isSelf()) { - auto action = Api::SendAction(peer->owner().history(peer)); + auto action = Api::SendAction(thread); action.clearDraft = false; user->session().api().shareContact(user, action); Ui::Toast::Show( @@ -1245,24 +1267,29 @@ void PeerMenuShareContactBox( } return; } + const auto title = thread->asTopic() + ? thread->asTopic()->title() + : peer->name(); auto recipient = peer->isUser() - ? peer->name() - : '\xAB' + peer->name() + '\xBB'; + ? title + : ('\xAB' + title + '\xBB'); + const auto weak = base::make_weak(thread); navigation->parentController()->show( Ui::MakeConfirmBox({ .text = tr::lng_forward_share_contact( tr::now, lt_recipient, recipient), - .confirmed = [peer, user, navigation](Fn &&close) { - const auto history = peer->owner().history(peer); - navigation->showPeerHistory( - history, - Window::SectionShow::Way::ClearStack, - ShowAtTheEndMsgId); - auto action = Api::SendAction(history); - action.clearDraft = false; - user->session().api().shareContact(user, action); + .confirmed = [weak, user, navigation](Fn &&close) { + if (const auto strong = weak.get()) { + navigation->showThread( + strong, + ShowAtTheEndMsgId, + Window::SectionShow::Way::ClearStack); + auto action = Api::SendAction(strong); + action.clearDraft = false; + strong->session().api().shareContact(user, action); + } close(); }, .confirmText = tr::lng_forward_send(), @@ -1470,32 +1497,19 @@ QPointer ShowForwardMessagesBox( Data::ForwardDraft &&draft, FnMut &&successCallback) { const auto weak = std::make_shared>(); - auto callback = [ + auto chosen = [ draft = std::move(draft), callback = std::move(successCallback), weak, navigation - ](not_null peer) mutable { + ](not_null thread) mutable { + const auto peer = thread->owningHistory()->peer; const auto content = navigation->parentController()->content(); if (peer->isSelf() && !draft.ids.empty() && draft.ids.front().peer != peer->id) { - const auto history = peer->owner().history(peer); - auto resolved = history->resolveForwardDraft(draft); - if (!resolved.items.empty()) { - const auto api = &peer->session().api(); - auto action = Api::SendAction(peer->owner().history(peer)); - action.clearDraft = false; - action.generateLocal = false; - const auto weakContent = Ui::MakeWeak(content); - api->forwardMessages( - std::move(resolved), - action, - crl::guard(weakContent, [w = weakContent] { - Ui::Toast::Show(w, tr::lng_share_done(tr::now)); - })); - } - } else if (!content->setForwardDraft(peer->id, std::move(draft))) { + ForwardToSelf(navigation, draft); + } else if (!content->setForwardDraft(thread, std::move(draft))) { return; } if (const auto strong = *weak) { @@ -1513,7 +1527,7 @@ QPointer ShowForwardMessagesBox( *weak = navigation->parentController()->show(Box( std::make_unique( &navigation->session(), - std::move(callback)), + std::move(chosen)), std::move(initBox)), Ui::LayerOption::KeepOther); return weak->data(); } @@ -1528,6 +1542,50 @@ QPointer ShowForwardMessagesBox( std::move(successCallback)); } +QPointer ShowForwardMessagesBox( + not_null navigation, + Data::ForwardDraft &&draft, + not_null forum, + FnMut &&successCallback) { + const auto weak = std::make_shared>(); + auto chosen = [ + draft = std::move(draft), + callback = std::move(successCallback), + weak, + navigation + ](not_null topic) mutable { + const auto content = navigation->parentController()->content(); + if (!content->setForwardDraft(topic, std::move(draft))) { + return; + } else if (const auto strong = *weak) { + strong->closeBox(); + } + if (callback) { + callback(); + } + }; + auto initBox = [](not_null box) { + box->addButton(tr::lng_cancel(), [box] { + box->closeBox(); + }); + }; + *weak = navigation->parentController()->show(Box( + std::make_unique( + forum, + std::move(chosen)), + [=](not_null box) { + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); + + forum->destroyed( + ) | rpl::start_with_next([=] { + box->closeBox(); + }, box->lifetime()); + })); + return weak->data(); +} + QPointer ShowSendNowMessagesBox( not_null navigation, not_null history, diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index e423665e9e..8cdf7a2386 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -21,6 +21,7 @@ class GenericBox; } // namespace Ui namespace Data { +class Forum; class Folder; class Session; struct ForwardDraft; @@ -121,6 +122,11 @@ QPointer ShowForwardMessagesBox( not_null navigation, MessageIdsList &&items, FnMut &&successCallback = nullptr); +QPointer ShowForwardMessagesBox( + not_null navigation, + Data::ForwardDraft &&draft, + not_null forum, + FnMut &&successCallback = nullptr); QPointer ShowSendNowMessagesBox( not_null navigation, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index fef6154c27..a7e817cd31 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -622,15 +622,29 @@ void SessionNavigation::showPeerInfo( void SessionNavigation::showTopic( not_null topic, - MsgId commentId, + MsgId itemId, const SectionShow ¶ms) { return showRepliesForMessage( topic->history(), topic->rootId(), - commentId, + itemId, params); } +void SessionNavigation::showThread( + not_null thread, + MsgId itemId, + const SectionShow ¶ms) { + if (const auto topic = thread->asTopic()) { + showTopic(topic, itemId, params); + } else { + showPeerHistory(thread->asHistory(), params, itemId); + } + if (parentController()->activeChatCurrent().thread() == thread) { + parentController()->content()->clearSelectingPeer(); + } +} + void SessionNavigation::showPeerInfo( not_null peer, const SectionShow ¶ms) { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index e70b5c37f7..f783dfe76e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -216,7 +216,11 @@ public: const SectionShow ¶ms = SectionShow()); void showTopic( not_null topic, - MsgId commentId = 0, + MsgId itemId = 0, + const SectionShow ¶ms = SectionShow()); + void showThread( + not_null thread, + MsgId itemId = 0, const SectionShow ¶ms = SectionShow()); void showPeerInfo(