mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Implement forwarding to topics.
This commit is contained in:
parent
c497e9ca9c
commit
1ac051a812
38 changed files with 1413 additions and 561 deletions
|
@ -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
|
||||
|
|
22
Telegram/SourceFiles/api/api_common.cpp
Normal file
22
Telegram/SourceFiles/api/api_common.cpp
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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>>();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -226,8 +226,6 @@ public:
|
|||
|
||||
void deleteConversationLocally(not_null<PeerData*> peer);
|
||||
|
||||
void cancelForwarding(not_null<History*> history);
|
||||
|
||||
[[nodiscard]] rpl::variable<bool> &contactsLoaded() {
|
||||
return _contactsLoaded;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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().");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -622,15 +622,29 @@ void SessionNavigation::showPeerInfo(
|
|||
|
||||
void SessionNavigation::showTopic(
|
||||
not_null<Data::ForumTopic*> topic,
|
||||
MsgId commentId,
|
||||
MsgId itemId,
|
||||
const SectionShow ¶ms) {
|
||||
return showRepliesForMessage(
|
||||
topic->history(),
|
||||
topic->rootId(),
|
||||
commentId,
|
||||
itemId,
|
||||
params);
|
||||
}
|
||||
|
||||
void SessionNavigation::showThread(
|
||||
not_null<Data::Thread*> thread,
|
||||
MsgId itemId,
|
||||
const SectionShow ¶ms) {
|
||||
if (const auto topic = thread->asTopic()) {
|
||||
showTopic(topic, itemId, params);
|
||||
} else {
|
||||
showPeerHistory(thread->asHistory(), params, itemId);
|
||||
}
|
||||
if (parentController()->activeChatCurrent().thread() == thread) {
|
||||
parentController()->content()->clearSelectingPeer();
|
||||
}
|
||||
}
|
||||
|
||||
void SessionNavigation::showPeerInfo(
|
||||
not_null<PeerData*> peer,
|
||||
const SectionShow ¶ms) {
|
||||
|
|
|
@ -216,7 +216,11 @@ public:
|
|||
const SectionShow ¶ms = SectionShow());
|
||||
void showTopic(
|
||||
not_null<Data::ForumTopic*> topic,
|
||||
MsgId commentId = 0,
|
||||
MsgId itemId = 0,
|
||||
const SectionShow ¶ms = SectionShow());
|
||||
void showThread(
|
||||
not_null<Data::Thread*> thread,
|
||||
MsgId itemId = 0,
|
||||
const SectionShow ¶ms = SectionShow());
|
||||
|
||||
void showPeerInfo(
|
||||
|
|
Loading…
Add table
Reference in a new issue