Implement forwarding to topics.

This commit is contained in:
John Preston 2022-11-01 08:46:31 +04:00
parent c497e9ca9c
commit 1ac051a812
38 changed files with 1413 additions and 561 deletions

View File

@ -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

View File

@ -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<Data::Thread*> thread,
SendOptions options)
: history(thread->owningHistory())
, options(options)
, topicRootId(thread->topicRootId()) {
}
} // namespace Api

View File

@ -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*> history,
SendOptions options = SendOptions())
: history(history)
, options(options) {
}
not_null<Data::Thread*> thread,
SendOptions options = SendOptions());
not_null<History*> history;
SendOptions options;

View File

@ -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<base::flat_map<uint64, FullMsgId>>();

View File

@ -331,6 +331,14 @@ void PeerListController::peerListSearchAddRow(not_null<PeerData*> 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<Ui::FlatLabel>(nullptr, text, st::membersAbout));
setSearchNoResults(
object_ptr<Ui::FlatLabel>(nullptr, text, st::membersAbout));
}
}
@ -369,6 +378,14 @@ base::unique_qptr<Ui::PopupMenu> PeerListController::rowContextMenu(
return nullptr;
}
std::unique_ptr<PeerListRow> PeerListController::createSearchRow(
PeerListRowId id) {
if (const auto peer = session().data().peerLoaded(PeerId(id))) {
return createSearchRow(peer);
}
return nullptr;
}
std::unique_ptr<PeerListState> PeerListController::saveState() const {
return delegate()->peerListSaveState();
}
@ -648,6 +665,18 @@ PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback() {
};
}
auto PeerListRow::generateNameFirstLetters() const
-> const base::flat_set<QChar> & {
return peer()->nameFirstLetters();
}
auto PeerListRow::generateNameWords() const
-> const base::flat_set<QString> & {
return peer()->nameWords();
}
void PeerListRow::invalidatePixmapsCache() {
if (_checkbox) {
_checkbox->invalidateCache();
@ -983,12 +1012,12 @@ bool PeerListContent::addingToSearchIndex() const {
}
void PeerListContent::addToSearchIndex(not_null<PeerListRow*> 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<PeerData*> peer,
not_null<PeerListRow*> 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<PeerData*> peer) {
not_null<PeerListRow*> 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);
}
}

View File

@ -91,6 +91,11 @@ public:
[[nodiscard]] virtual auto generatePaintUserpicCallback()
-> PaintRoundImageCallback;
[[nodiscard]] virtual auto generateNameFirstLetters() const
-> const base::flat_set<QChar> &;
[[nodiscard]] virtual auto generateNameWords() const
-> const base::flat_set<QString> &;
void setCustomStatus(const QString &status, bool active = false);
void clearCustomStatus();
@ -360,6 +365,7 @@ private:
class PeerListSearchDelegate {
public:
virtual void peerListSearchAddRow(not_null<PeerData*> peer) = 0;
virtual void peerListSearchAddRow(PeerListRowId id) = 0;
virtual void peerListSearchRefreshRows() = 0;
virtual ~PeerListSearchDelegate() = default;
@ -470,6 +476,7 @@ public:
not_null<PeerData*> peer) {
return nullptr;
}
virtual std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id);
virtual std::unique_ptr<PeerListRow> createRestoredRow(
not_null<PeerData*> peer) {
return nullptr;
@ -494,6 +501,7 @@ public:
void search(const QString &query);
void peerListSearchAddRow(not_null<PeerData*> peer) override;
void peerListSearchAddRow(PeerListRowId id) override;
void peerListSearchRefreshRows() override;
[[nodiscard]] virtual bool respectSavedMessagesChat() const {

View File

@ -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<Ui::BoxContent> PrepareContactsBox(
not_null<Window::SessionController*> sessionController) {
using Mode = ContactsBoxController::SortMode;
@ -314,7 +292,8 @@ QString ChatsListBoxController::emptyBoxText() const {
return tr::lng_contacts_not_found(tr::now);
}
std::unique_ptr<PeerListRow> ChatsListBoxController::createSearchRow(not_null<PeerData*> peer) {
std::unique_ptr<PeerListRow> ChatsListBoxController::createSearchRow(
not_null<PeerData*> peer) {
return createRow(peer->owner().history(peer));
}
@ -481,8 +460,8 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
ChooseRecipientBoxController::ChooseRecipientBoxController(
not_null<Main::Session*> session,
FnMut<void(not_null<PeerData*>)> callback,
Fn<bool(not_null<PeerData*>)> filter)
FnMut<void(not_null<Data::Thread*>)> callback,
Fn<bool(not_null<Data::Thread*>)> filter)
: ChatsListBoxController(session)
, _session(session)
, _callback(std::move(callback))
@ -498,10 +477,48 @@ void ChooseRecipientBoxController::prepareViewHook() {
}
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> 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<QPointer<Ui::BoxContent>>();
auto callback = [=](not_null<Data::ForumTopic*> 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<PeerListBox>(
std::make_unique<ChooseTopicBoxController>(
forum,
std::move(callback)),
[=](not_null<PeerListBox*> 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*> history) -> std::unique_ptr<Row> {
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<Row>(history);
}
ChooseTopicSearchController::ChooseTopicSearchController(
not_null<Data::Forum*> 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<Data::ForumTopic*> 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<Data::Forum*> forum,
FnMut<void(not_null<Data::ForumTopic*>)> callback,
Fn<bool(not_null<Data::ForumTopic*>)> filter)
: PeerListController(std::make_unique<ChooseTopicSearchController>(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<Data::ForumTopic*> 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<PeerListRow*> row) {
const auto weak = base::make_weak(this);
auto onstack = base::take(_callback);
onstack(static_cast<Row*>(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<PeerListRow> ChooseTopicBoxController::createSearchRow(
PeerListRowId id) {
if (const auto topic = _forum->topicFor(MsgId(id))) {
return std::make_unique<Row>(topic);
}
return nullptr;
}
ChooseTopicBoxController::Row::Row(not_null<Data::ForumTopic*> 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<Data::CloudImageView>();
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<QChar> & {
return _topic->chatListFirstLetters();
}
auto ChooseTopicBoxController::Row::generateNameWords() const
-> const base::flat_set<QString> & {
return _topic->chatListNameWords();
}
auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
-> std::unique_ptr<Row> {
const auto skip = _filter ? !_filter(topic) : !topic->canWrite();
return skip ? nullptr : std::make_unique<Row>(topic);
};

View File

@ -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<PeerListSearchController> searchController);
void prepare() override final;
std::unique_ptr<PeerListRow> createSearchRow(not_null<PeerData*> peer) override final;
std::unique_ptr<PeerListRow> createSearchRow(
not_null<PeerData*> peer) override final;
protected:
virtual std::unique_ptr<Row> createRow(not_null<History*> history) = 0;
@ -173,8 +163,8 @@ class ChooseRecipientBoxController
public:
ChooseRecipientBoxController(
not_null<Main::Session*> session,
FnMut<void(not_null<PeerData*>)> callback,
Fn<bool(not_null<PeerData*>)> filter = nullptr);
FnMut<void(not_null<Data::Thread*>)> callback,
Fn<bool(not_null<Data::Thread*>)> filter = nullptr);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
@ -189,7 +179,80 @@ protected:
private:
const not_null<Main::Session*> _session;
FnMut<void(not_null<PeerData*>)> _callback;
Fn<bool(not_null<PeerData*>)> _filter;
FnMut<void(not_null<Data::Thread*>)> _callback;
Fn<bool(not_null<Data::Thread*>)> _filter;
};
class ChooseTopicSearchController : public PeerListSearchController {
public:
explicit ChooseTopicSearchController(not_null<Data::Forum*> 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<Data::Forum*> _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<Data::Forum*> forum,
FnMut<void(not_null<Data::ForumTopic*>)> callback,
Fn<bool(not_null<Data::ForumTopic*>)> filter = nullptr);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
void prepare() override;
void loadMoreRows() override;
std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id) override;
private:
class Row final : public PeerListRow {
public:
explicit Row(not_null<Data::ForumTopic*> topic);
[[nodiscard]] not_null<Data::ForumTopic*> topic() const {
return _topic;
}
QString generateName() override;
QString generateShortName() override;
PaintRoundImageCallback generatePaintUserpicCallback() override;
auto generateNameFirstLetters() const
-> const base::flat_set<QChar> & override;
auto generateNameWords() const
-> const base::flat_set<QString> & override;
private:
const not_null<Data::ForumTopic*> _topic;
};
void refreshRows(bool initial = false);
[[nodiscard]] std::unique_ptr<Row> createRow(
not_null<Data::ForumTopic*> topic);
const not_null<Data::Forum*> _forum;
FnMut<void(not_null<Data::ForumTopic*>)> _callback;
Fn<bool(not_null<Data::ForumTopic*>)> _filter;
};

View File

@ -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"

View File

@ -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<Flag>;
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<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@ -1151,13 +1151,6 @@ void Session::deleteConversationLocally(not_null<PeerData*> peer) {
}
}
void Session::cancelForwarding(not_null<History*> history) {
history->setForwardDraft({});
session().changes().historyUpdated(
history,
Data::HistoryUpdate::Flag::ForwardDraft);
}
bool Session::chatsListLoaded(Data::Folder *folder) {
return chatsList(folder)->loaded();
}

View File

@ -226,8 +226,6 @@ public:
void deleteConversationLocally(not_null<PeerData*> peer);
void cancelForwarding(not_null<History*> history);
[[nodiscard]] rpl::variable<bool> &contactsLoaded() {
return _contactsLoaded;
}

View File

@ -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;
}

View File

@ -2191,23 +2191,27 @@ void InnerWidget::trackSearchResultsHistory(not_null<History*> 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;
});

View File

@ -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<void()> callback);
void setLoadMoreFilteredCallback(Fn<void()> callback);

View File

@ -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().");
}

View File

@ -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<HistoryItem*> History::createItem(
@ -624,7 +654,8 @@ not_null<HistoryItem*> History::addNewLocalMessage(
TimeId date,
PeerId from,
const QString &postAuthor,
not_null<HistoryItem*> forwardOriginal) {
not_null<HistoryItem*> forwardOriginal,
MsgId topicRootId) {
return addNewItem(
makeMessage(
id,
@ -632,7 +663,8 @@ not_null<HistoryItem*> History::addNewLocalMessage(
date,
from,
postAuthor,
forwardOriginal),
forwardOriginal,
topicRootId),
true);
}

View File

@ -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<MsgId, ForwardDraft>;
struct ResolvedForwardDraft {
HistoryItemsList items;
ForwardOptions options = ForwardOptions::PreserveInfo;
@ -165,7 +171,8 @@ public:
TimeId date,
PeerId from,
const QString &postAuthor,
not_null<HistoryItem*> forwardOriginal);
not_null<HistoryItem*> forwardOriginal,
MsgId topicRootId);
not_null<HistoryItem*> 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<MsgId, TimeId> _acceptCloudDraftsAfter;
base::flat_map<MsgId, int> _savingCloudDraftRequests;
Data::ForwardDraft _forwardDraft;
Data::ForwardDrafts _forwardDrafts;
QString _topPromotedMessage;
QString _topPromotedType;

View File

@ -411,7 +411,8 @@ HistoryMessage::HistoryMessage(
TimeId date,
PeerId from,
const QString &postAuthor,
not_null<HistoryItem*> original)
not_null<HistoryItem*> 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()) {

View File

@ -72,7 +72,8 @@ public:
TimeId date,
PeerId from,
const QString &postAuthor,
not_null<HistoryItem*> original); // local forwarded
not_null<HistoryItem*> original,
MsgId topicRootId); // local forwarded
HistoryMessage(
not_null<History*> history,
MsgId id,

View File

@ -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<HistoryWidget::VoiceRecordBar>(
, _voiceRecordBar(std::make_unique<VoiceRecordBar>(
this,
controller,
_send,
st::historySendSize.height()))
, _forwardPanel(std::make_unique<ForwardPanel>([=] { 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<DocumentData*> 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<const HistoryItem*> 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<not_null<PeerData*>>();
auto insertedNames = base::flat_set<QString>();
auto fullname = QString();
auto names = std::vector<QString>();
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) {

View File

@ -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<HistoryItem*> item);
[[nodiscard]] QVector<FullMsgId> 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<Ui::IconButton> _scheduled = { nullptr };
std::unique_ptr<HistoryView::Controls::TTLButton> _ttlInfo;
const std::unique_ptr<VoiceRecordBar> _voiceRecordBar;
const std::unique_ptr<ForwardPanel> _forwardPanel;
std::unique_ptr<HistoryView::ComposeSearch> _composeSearch;
bool _cmdStartShown = false;
object_ptr<Ui::InputField> _field;

View File

@ -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<WebPageData*> WebpageProcessor::pageDataChanges() const {
class FieldHeader final : public Ui::RpWidget {
public:
FieldHeader(QWidget *parent, not_null<Data::Session*> data);
FieldHeader(
QWidget *parent,
not_null<Window::SessionController*> 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<QString> title,
rpl::producer<QString> 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<FullMsgId> editMsgId() const;
[[nodiscard]] rpl::producer<FullMsgId> 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<PeerData*> 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<Window::SessionController*> _controller;
History *_history = nullptr;
rpl::variable<QString> _title;
rpl::variable<QString> _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<FullMsgId> _editMsgId;
rpl::variable<FullMsgId> _replyToId;
std::unique_ptr<ForwardPanel> _forwardPanel;
rpl::producer<> _toForwardUpdated;
HistoryItem *_shownMessage = nullptr;
Ui::Text::String _shownMessageName;
@ -412,9 +428,14 @@ private:
};
FieldHeader::FieldHeader(QWidget *parent, not_null<Data::Session*> data)
FieldHeader::FieldHeader(
QWidget *parent,
not_null<Window::SessionController*> controller)
: RpWidget(parent)
, _data(data)
, _controller(controller)
, _forwardPanel(
std::make_unique<ForwardPanel>([=] { customEmojiRepaint(); }))
, _data(&controller->session().data())
, _cancel(Ui::CreateChild<Ui::IconButton>(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<bool>(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<QEvent*> event) {
return ranges::contains(kMouseEvents, event->type())
&& (isEditingMessage() || replyingToMessage());
&& (isEditingMessage()
|| readyToForward()
|| replyingToMessage());
}) | rpl::start_with_next([=](not_null<QEvent*> event) {
const auto type = event->type();
const auto e = static_cast<QMouseEvent*>(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<bool> 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<FullMsgId> FieldHeader::editMsgId() const {
return _editMsgId.value();
}
@ -863,9 +928,7 @@ ComposeControls::ComposeControls(
, _autocomplete(std::make_unique<FieldAutocomplete>(
parent,
window))
, _header(std::make_unique<FieldHeader>(
_wrap.get(),
&_window->session().data()))
, _header(std::make_unique<FieldHeader>(_wrap.get(), _window))
, _voiceRecordBar(std::make_unique<VoiceRecordBar>(
_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<QString>())
| 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();
}

View File

@ -149,6 +149,7 @@ public:
bool returnTabbedSelector();
[[nodiscard]] bool isEditingMessage() const;
[[nodiscard]] bool readyToForward() const;
[[nodiscard]] FullMsgId replyingToMessage() const;
[[nodiscard]] bool preventsClose(Fn<void()> &&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<bool> _sendDisabledBySlowmode;
rpl::variable<std::optional<QString>> _writeRestriction;
rpl::variable<bool> _hidden;
MsgId _topicRootId = 0;
Mode _mode = Mode::Normal;
const std::unique_ptr<Ui::RpWidget> _wrap;

View File

@ -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<void()> 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<const HistoryItem*> 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<not_null<PeerData*>>();
auto insertedNames = base::flat_set<QString>();
auto fullname = QString();
auto names = std::vector<QString>();
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<const HistoryItem*> 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<Window::SessionController*> 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<ForwardPanel*>(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

View File

@ -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<void()> 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<Window::SessionController*> controller);
[[nodiscard]] const HistoryItemsList &items() const;
[[nodiscard]] bool empty() const;
private:
void checkTexts();
void updateTexts();
void refreshTexts();
void itemRemoved(not_null<const HistoryItem*> item);
Fn<void()> _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

View File

@ -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<mtpRequestId>(0);

View File

@ -132,16 +132,17 @@ struct ParsedBot {
void ShowChooseBox(
not_null<Window::SessionController*> controller,
PeerTypes types,
Fn<void(not_null<PeerData*>)> callback) {
Fn<void(not_null<Data::Thread*>)> callback) {
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
auto done = [=](not_null<PeerData*> peer) mutable {
auto done = [=](not_null<Data::Thread*> thread) mutable {
if (const auto strong = *weak) {
strong->closeBox();
}
callback(peer);
callback(thread);
};
auto filter = [=](not_null<PeerData*> peer) -> bool {
if (!peer->canWrite()) { // #TODO forum forward
auto filter = [=](not_null<Data::Thread*> 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<PeerData*> peer) {
const auto history = peer->owner().history(peer);
strong->showPeerHistory(history);
const auto done = [=](not_null<Data::Thread*> 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) {

View File

@ -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<Data::Thread*> 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<Data::Thread*> 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<Data::Draft>(
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<Data::Thread*> 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<Data::Draft>(
const auto history = thread->owningHistory();
const auto topicRootId = thread->topicRootId();
history->setLocalDraft(std::make_unique<Data::Draft>(
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<Data::Thread*> 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<Data::Thread*> 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<Window::HistoryHider> 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<Data::Thread*> thread) mutable {
return setForwardDraft(thread, std::move(draft));
};
hiderLayer(base::make_unique_q<Window::HistoryHider>(
this,
@ -749,7 +758,7 @@ void MainWidget::showSendPathsLayer() {
hiderLayer(base::make_unique_q<Window::HistoryHider>(
this,
tr::lng_forward_choose(tr::now),
[=](PeerId peer) { return sendPaths(peer); },
[=](not_null<Data::Thread*> 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<Data::Thread*> thread) {
return shareUrl(thread, url, text);
};
hiderLayer(base::make_unique_q<Window::HistoryHider>(
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<Data::Thread*> thread) {
return inlineSwitchChosen(thread, botAndQuery);
};
hiderLayer(base::make_unique_q<Window::HistoryHider>(
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<Data::Thread*> 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<PeerData*> 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()

View File

@ -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<Window::HistoryHider> h);
bool setForwardDraft(PeerId peer, Data::ForwardDraft &&draft);
bool sendPaths(PeerId peerId);
void onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data);
bool setForwardDraft(
not_null<Data::Thread*> thread,
Data::ForwardDraft &&draft);
bool sendPaths(not_null<Data::Thread*> thread);
void onFilesOrForwardDrop(
not_null<Data::Thread*> 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<Data::Thread*> thread, MsgId showAtMsgId);
void chooseThread(not_null<PeerData*> peer, MsgId showAtMsgId);
void clearBotStartToken(PeerData *peer);
void ctrlEnterSubmitUpdated();
@ -251,10 +258,12 @@ private:
-> std::shared_ptr<Window::SectionMemento>;
bool shareUrl(
PeerId peerId,
not_null<Data::Thread*> thread,
const QString &url,
const QString &text) const;
bool inlineSwitchChosen(PeerId peerId, const QString &botAndQuery) const;
bool inlineSwitchChosen(
not_null<Data::Thread*> thread,
const QString &botAndQuery) const;
void setupConnectingWidget();
void createPlayer();

View File

@ -778,13 +778,14 @@ TimeId CalculateOnlineTill(not_null<PeerData*> 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);
}
});
}

View File

@ -20,7 +20,7 @@ namespace Window {
HistoryHider::HistoryHider(
QWidget *parent,
const QString &text,
Fn<bool(PeerId)> confirm,
Fn<bool(not_null<Data::Thread*>)> confirm,
rpl::producer<bool> 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<Data::Thread*> thread) {
if (_confirm(thread)) {
startHide();
}
}

View File

@ -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<bool(PeerId)> confirm,
Fn<bool(not_null<Data::Thread*>)> confirm,
rpl::producer<bool> oneColumnValue);
void offerPeer(PeerId peer);
void offerThread(not_null<Data::Thread*> thread);
void startHide();
void confirm();
@ -57,7 +61,7 @@ private:
void animationCallback();
QString _text;
Fn<bool(PeerId)> _confirm;
Fn<bool(not_null<Data::Thread*>)> _confirm;
Ui::Animations::Simple _a_opacity;
QRect _box;

View File

@ -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(

View File

@ -188,6 +188,27 @@ void PeerMenuAddMuteSubmenuAction(
}
}
void ForwardToSelf(
not_null<Window::SessionNavigation*> 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<UserData*> user) {
// There is no async to make weak from controller.
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
auto callback = [=](not_null<PeerData*> peer) {
if (!peer->canWrite()) { // #TODO forum forward
auto callback = [=](not_null<Data::Thread*> 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<void()> &&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<void()> &&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<Ui::BoxContent> ShowForwardMessagesBox(
Data::ForwardDraft &&draft,
FnMut<void()> &&successCallback) {
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
auto callback = [
auto chosen = [
draft = std::move(draft),
callback = std::move(successCallback),
weak,
navigation
](not_null<PeerData*> peer) mutable {
](not_null<Data::Thread*> 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<Ui::BoxContent> ShowForwardMessagesBox(
*weak = navigation->parentController()->show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(
&navigation->session(),
std::move(callback)),
std::move(chosen)),
std::move(initBox)), Ui::LayerOption::KeepOther);
return weak->data();
}
@ -1528,6 +1542,50 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
std::move(successCallback));
}
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
not_null<Window::SessionNavigation*> navigation,
Data::ForwardDraft &&draft,
not_null<Data::Forum*> forum,
FnMut<void()> &&successCallback) {
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
auto chosen = [
draft = std::move(draft),
callback = std::move(successCallback),
weak,
navigation
](not_null<Data::ForumTopic*> 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<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [box] {
box->closeBox();
});
};
*weak = navigation->parentController()->show(Box<PeerListBox>(
std::make_unique<ChooseTopicBoxController>(
forum,
std::move(chosen)),
[=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
forum->destroyed(
) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
}));
return weak->data();
}
QPointer<Ui::BoxContent> ShowSendNowMessagesBox(
not_null<Window::SessionNavigation*> navigation,
not_null<History*> history,

View File

@ -21,6 +21,7 @@ class GenericBox;
} // namespace Ui
namespace Data {
class Forum;
class Folder;
class Session;
struct ForwardDraft;
@ -121,6 +122,11 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
not_null<Window::SessionNavigation*> navigation,
MessageIdsList &&items,
FnMut<void()> &&successCallback = nullptr);
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
not_null<Window::SessionNavigation*> navigation,
Data::ForwardDraft &&draft,
not_null<Data::Forum*> forum,
FnMut<void()> &&successCallback = nullptr);
QPointer<Ui::BoxContent> ShowSendNowMessagesBox(
not_null<Window::SessionNavigation*> navigation,

View File

@ -622,15 +622,29 @@ void SessionNavigation::showPeerInfo(
void SessionNavigation::showTopic(
not_null<Data::ForumTopic*> topic,
MsgId commentId,
MsgId itemId,
const SectionShow &params) {
return showRepliesForMessage(
topic->history(),
topic->rootId(),
commentId,
itemId,
params);
}
void SessionNavigation::showThread(
not_null<Data::Thread*> thread,
MsgId itemId,
const SectionShow &params) {
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<PeerData*> peer,
const SectionShow &params) {

View File

@ -216,7 +216,11 @@ public:
const SectionShow &params = SectionShow());
void showTopic(
not_null<Data::ForumTopic*> topic,
MsgId commentId = 0,
MsgId itemId = 0,
const SectionShow &params = SectionShow());
void showThread(
not_null<Data::Thread*> thread,
MsgId itemId = 0,
const SectionShow &params = SectionShow());
void showPeerInfo(