mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +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_chat_participants.h
|
||||||
api/api_cloud_password.cpp
|
api/api_cloud_password.cpp
|
||||||
api/api_cloud_password.h
|
api/api_cloud_password.h
|
||||||
|
api/api_common.cpp
|
||||||
api/api_common.h
|
api/api_common.h
|
||||||
api/api_confirm_phone.cpp
|
api/api_confirm_phone.cpp
|
||||||
api/api_confirm_phone.h
|
api/api_confirm_phone.h
|
||||||
|
@ -625,6 +626,8 @@ PRIVATE
|
||||||
history/view/controls/history_view_compose_controls.h
|
history/view/controls/history_view_compose_controls.h
|
||||||
history/view/controls/history_view_compose_search.cpp
|
history/view/controls/history_view_compose_search.cpp
|
||||||
history/view/controls/history_view_compose_search.h
|
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.cpp
|
||||||
history/view/controls/history_view_ttl_button.h
|
history/view/controls/history_view_ttl_button.h
|
||||||
history/view/controls/history_view_voice_record_bar.cpp
|
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;
|
class History;
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
class Thread;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
|
|
||||||
struct SendOptions {
|
struct SendOptions {
|
||||||
|
@ -28,11 +32,8 @@ enum class SendType {
|
||||||
|
|
||||||
struct SendAction {
|
struct SendAction {
|
||||||
explicit SendAction(
|
explicit SendAction(
|
||||||
not_null<History*> history,
|
not_null<Data::Thread*> thread,
|
||||||
SendOptions options = SendOptions())
|
SendOptions options = SendOptions());
|
||||||
: history(history)
|
|
||||||
, options(options) {
|
|
||||||
}
|
|
||||||
|
|
||||||
not_null<History*> history;
|
not_null<History*> history;
|
||||||
SendOptions options;
|
SendOptions options;
|
||||||
|
|
|
@ -3049,7 +3049,7 @@ void ApiWrap::sendAction(const SendAction &action) {
|
||||||
|
|
||||||
void ApiWrap::finishForwarding(const SendAction &action) {
|
void ApiWrap::finishForwarding(const SendAction &action) {
|
||||||
const auto history = action.history;
|
const auto history = action.history;
|
||||||
auto toForward = history->resolveForwardDraft();
|
auto toForward = history->resolveForwardDraft(action.topicRootId);
|
||||||
if (!toForward.items.empty()) {
|
if (!toForward.items.empty()) {
|
||||||
const auto error = GetErrorTextForSending(
|
const auto error = GetErrorTextForSending(
|
||||||
history->peer,
|
history->peer,
|
||||||
|
@ -3062,7 +3062,7 @@ void ApiWrap::finishForwarding(const SendAction &action) {
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardMessages(std::move(toForward), action);
|
forwardMessages(std::move(toForward), action);
|
||||||
_session->data().cancelForwarding(history);
|
history->setForwardDraft(action.topicRootId, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
_session->data().sendHistoryChangeNotifications();
|
_session->data().sendHistoryChangeNotifications();
|
||||||
|
@ -3199,7 +3199,8 @@ void ApiWrap::forwardMessages(
|
||||||
HistoryItem::NewMessageDate(action.options.scheduled),
|
HistoryItem::NewMessageDate(action.options.scheduled),
|
||||||
messageFromId,
|
messageFromId,
|
||||||
messagePostAuthor,
|
messagePostAuthor,
|
||||||
item); // #TODO forum forward
|
item,
|
||||||
|
action.topicRootId); // #TODO forum forward
|
||||||
_session->data().registerMessageRandomId(randomId, newId);
|
_session->data().registerMessageRandomId(randomId, newId);
|
||||||
if (!localIds) {
|
if (!localIds) {
|
||||||
localIds = std::make_shared<base::flat_map<uint64, FullMsgId>>();
|
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() {
|
void PeerListController::peerListSearchRefreshRows() {
|
||||||
delegate()->peerListRefreshRows();
|
delegate()->peerListRefreshRows();
|
||||||
}
|
}
|
||||||
|
@ -359,7 +367,8 @@ void PeerListController::setSearchNoResultsText(const QString &text) {
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
setSearchNoResults(nullptr);
|
setSearchNoResults(nullptr);
|
||||||
} else {
|
} 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;
|
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 {
|
std::unique_ptr<PeerListState> PeerListController::saveState() const {
|
||||||
return delegate()->peerListSaveState();
|
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() {
|
void PeerListRow::invalidatePixmapsCache() {
|
||||||
if (_checkbox) {
|
if (_checkbox) {
|
||||||
_checkbox->invalidateCache();
|
_checkbox->invalidateCache();
|
||||||
|
@ -983,12 +1012,12 @@ bool PeerListContent::addingToSearchIndex() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListContent::addToSearchIndex(not_null<PeerListRow*> row) {
|
void PeerListContent::addToSearchIndex(not_null<PeerListRow*> row) {
|
||||||
if (row->isSearchResult() || row->special()) {
|
if (row->isSearchResult()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromSearchIndex(row);
|
removeFromSearchIndex(row);
|
||||||
row->setNameFirstLetters(row->peer()->nameFirstLetters());
|
row->setNameFirstLetters(row->generateNameFirstLetters());
|
||||||
for (auto ch : row->nameFirstLetters()) {
|
for (auto ch : row->nameFirstLetters()) {
|
||||||
_searchIndex[ch].push_back(row);
|
_searchIndex[ch].push_back(row);
|
||||||
}
|
}
|
||||||
|
@ -1813,9 +1842,9 @@ void PeerListContent::searchQueryChanged(QString query) {
|
||||||
}
|
}
|
||||||
if (minimalList) {
|
if (minimalList) {
|
||||||
auto searchWordInNames = [](
|
auto searchWordInNames = [](
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerListRow*> row,
|
||||||
const QString &searchWord) {
|
const QString &searchWord) {
|
||||||
for (auto &nameWord : peer->nameWords()) {
|
for (auto &nameWord : row->generateNameWords()) {
|
||||||
if (nameWord.startsWith(searchWord)) {
|
if (nameWord.startsWith(searchWord)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1823,9 +1852,9 @@ void PeerListContent::searchQueryChanged(QString query) {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
auto allSearchWordsInNames = [&](
|
auto allSearchWordsInNames = [&](
|
||||||
not_null<PeerData*> peer) {
|
not_null<PeerListRow*> row) {
|
||||||
for (const auto &searchWord : searchWordsList) {
|
for (const auto &searchWord : searchWordsList) {
|
||||||
if (!searchWordInNames(peer, searchWord)) {
|
if (!searchWordInNames(row, searchWord)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1834,7 +1863,7 @@ void PeerListContent::searchQueryChanged(QString query) {
|
||||||
|
|
||||||
_filterResults.reserve(minimalList->size());
|
_filterResults.reserve(minimalList->size());
|
||||||
for (const auto &row : *minimalList) {
|
for (const auto &row : *minimalList) {
|
||||||
if (!row->special() && allSearchWordsInNames(row->peer())) {
|
if (allSearchWordsInNames(row)) {
|
||||||
_filterResults.push_back(row);
|
_filterResults.push_back(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,11 @@ public:
|
||||||
[[nodiscard]] virtual auto generatePaintUserpicCallback()
|
[[nodiscard]] virtual auto generatePaintUserpicCallback()
|
||||||
-> PaintRoundImageCallback;
|
-> 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 setCustomStatus(const QString &status, bool active = false);
|
||||||
void clearCustomStatus();
|
void clearCustomStatus();
|
||||||
|
|
||||||
|
@ -360,6 +365,7 @@ private:
|
||||||
class PeerListSearchDelegate {
|
class PeerListSearchDelegate {
|
||||||
public:
|
public:
|
||||||
virtual void peerListSearchAddRow(not_null<PeerData*> peer) = 0;
|
virtual void peerListSearchAddRow(not_null<PeerData*> peer) = 0;
|
||||||
|
virtual void peerListSearchAddRow(PeerListRowId id) = 0;
|
||||||
virtual void peerListSearchRefreshRows() = 0;
|
virtual void peerListSearchRefreshRows() = 0;
|
||||||
virtual ~PeerListSearchDelegate() = default;
|
virtual ~PeerListSearchDelegate() = default;
|
||||||
|
|
||||||
|
@ -470,6 +476,7 @@ public:
|
||||||
not_null<PeerData*> peer) {
|
not_null<PeerData*> peer) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
virtual std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id);
|
||||||
virtual std::unique_ptr<PeerListRow> createRestoredRow(
|
virtual std::unique_ptr<PeerListRow> createRestoredRow(
|
||||||
not_null<PeerData*> peer) {
|
not_null<PeerData*> peer) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -494,6 +501,7 @@ public:
|
||||||
void search(const QString &query);
|
void search(const QString &query);
|
||||||
|
|
||||||
void peerListSearchAddRow(not_null<PeerData*> peer) override;
|
void peerListSearchAddRow(not_null<PeerData*> peer) override;
|
||||||
|
void peerListSearchAddRow(PeerListRowId id) override;
|
||||||
void peerListSearchRefreshRows() override;
|
void peerListSearchRefreshRows() override;
|
||||||
|
|
||||||
[[nodiscard]] virtual bool respectSavedMessagesChat() const {
|
[[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_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.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_folder.h"
|
||||||
#include "data/data_histories.h"
|
#include "data/data_histories.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
#include "dialogs/ui/dialogs_layout.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
#include "dialogs/dialogs_main_list.h"
|
#include "dialogs/dialogs_main_list.h"
|
||||||
#include "window/window_session_controller.h" // showAddContact()
|
#include "window/window_session_controller.h" // showAddContact()
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "facades.h"
|
#include "facades.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_profile.h"
|
#include "styles/style_profile.h"
|
||||||
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kSortByOnlineThrottle = 3 * crl::time(1000);
|
constexpr auto kSortByOnlineThrottle = 3 * crl::time(1000);
|
||||||
|
constexpr auto kSearchPerPage = 50;
|
||||||
|
|
||||||
} // namespace
|
} // 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(
|
object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||||
not_null<Window::SessionController*> sessionController) {
|
not_null<Window::SessionController*> sessionController) {
|
||||||
using Mode = ContactsBoxController::SortMode;
|
using Mode = ContactsBoxController::SortMode;
|
||||||
|
@ -314,7 +292,8 @@ QString ChatsListBoxController::emptyBoxText() const {
|
||||||
return tr::lng_contacts_not_found(tr::now);
|
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));
|
return createRow(peer->owner().history(peer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,8 +460,8 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
|
||||||
|
|
||||||
ChooseRecipientBoxController::ChooseRecipientBoxController(
|
ChooseRecipientBoxController::ChooseRecipientBoxController(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
FnMut<void(not_null<PeerData*>)> callback,
|
FnMut<void(not_null<Data::Thread*>)> callback,
|
||||||
Fn<bool(not_null<PeerData*>)> filter)
|
Fn<bool(not_null<Data::Thread*>)> filter)
|
||||||
: ChatsListBoxController(session)
|
: ChatsListBoxController(session)
|
||||||
, _session(session)
|
, _session(session)
|
||||||
, _callback(std::move(callback))
|
, _callback(std::move(callback))
|
||||||
|
@ -498,10 +477,48 @@ void ChooseRecipientBoxController::prepareViewHook() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
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);
|
auto callback = std::move(_callback);
|
||||||
callback(row->peer());
|
callback(history);
|
||||||
if (weak) {
|
if (guard) {
|
||||||
_callback = std::move(callback);
|
_callback = std::move(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -510,8 +527,205 @@ auto ChooseRecipientBoxController::createRow(
|
||||||
not_null<History*> history) -> std::unique_ptr<Row> {
|
not_null<History*> history) -> std::unique_ptr<Row> {
|
||||||
const auto peer = history->peer;
|
const auto peer = history->peer;
|
||||||
const auto skip = _filter
|
const auto skip = _filter
|
||||||
? !_filter(peer)
|
? !_filter(history)
|
||||||
: ((peer->isBroadcast() && !peer->canWrite())
|
: ((peer->isBroadcast() && !peer->canWrite())
|
||||||
|| peer->isRepliesChat());
|
|| peer->isRepliesChat());
|
||||||
return skip ? nullptr : std::make_unique<Row>(history);
|
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/weak_ptr.h"
|
||||||
#include "base/timer.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;
|
class History;
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
class Thread;
|
||||||
|
class Forum;
|
||||||
|
class ForumTopic;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
@ -110,7 +99,8 @@ public:
|
||||||
std::unique_ptr<PeerListSearchController> searchController);
|
std::unique_ptr<PeerListSearchController> searchController);
|
||||||
|
|
||||||
void prepare() override final;
|
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:
|
protected:
|
||||||
virtual std::unique_ptr<Row> createRow(not_null<History*> history) = 0;
|
virtual std::unique_ptr<Row> createRow(not_null<History*> history) = 0;
|
||||||
|
@ -173,8 +163,8 @@ class ChooseRecipientBoxController
|
||||||
public:
|
public:
|
||||||
ChooseRecipientBoxController(
|
ChooseRecipientBoxController(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
FnMut<void(not_null<PeerData*>)> callback,
|
FnMut<void(not_null<Data::Thread*>)> callback,
|
||||||
Fn<bool(not_null<PeerData*>)> filter = nullptr);
|
Fn<bool(not_null<Data::Thread*>)> filter = nullptr);
|
||||||
|
|
||||||
Main::Session &session() const override;
|
Main::Session &session() const override;
|
||||||
void rowClicked(not_null<PeerListRow*> row) override;
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
@ -189,7 +179,80 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
FnMut<void(not_null<PeerData*>)> _callback;
|
FnMut<void(not_null<Data::Thread*>)> _callback;
|
||||||
Fn<bool(not_null<PeerData*>)> _filter;
|
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 "boxes/share_box.h"
|
||||||
#include "history/view/history_view_schedule_box.h"
|
#include "history/view/history_view_schedule_box.h"
|
||||||
#include "history/history_message.h" // GetErrorTextForSending.
|
#include "history/history_message.h" // GetErrorTextForSending.
|
||||||
|
#include "history/history.h"
|
||||||
#include "data/data_histories.h"
|
#include "data/data_histories.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "base/timer_rpl.h"
|
#include "base/timer_rpl.h"
|
||||||
|
|
|
@ -125,13 +125,11 @@ struct HistoryUpdate {
|
||||||
ChatOccupied = (1U << 7),
|
ChatOccupied = (1U << 7),
|
||||||
MessageSent = (1U << 8),
|
MessageSent = (1U << 8),
|
||||||
ScheduledSent = (1U << 9),
|
ScheduledSent = (1U << 9),
|
||||||
ForwardDraft = (1U << 10),
|
OutboxRead = (1U << 10),
|
||||||
OutboxRead = (1U << 11),
|
BotKeyboard = (1U << 11),
|
||||||
BotKeyboard = (1U << 12),
|
CloudDraft = (1U << 12),
|
||||||
CloudDraft = (1U << 13),
|
|
||||||
LocalDraftSet = (1U << 14),
|
|
||||||
|
|
||||||
LastUsedBit = (1U << 14),
|
LastUsedBit = (1U << 12),
|
||||||
};
|
};
|
||||||
using Flags = base::flags<Flag>;
|
using Flags = base::flags<Flag>;
|
||||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||||
|
@ -196,8 +194,10 @@ struct EntryUpdate {
|
||||||
|
|
||||||
Repaint = (1U << 0),
|
Repaint = (1U << 0),
|
||||||
HasPinnedMessages = (1U << 1),
|
HasPinnedMessages = (1U << 1),
|
||||||
|
ForwardDraft = (1U << 2),
|
||||||
|
LocalDraftSet = (1U << 3),
|
||||||
|
|
||||||
LastUsedBit = (1U << 1),
|
LastUsedBit = (1U << 3),
|
||||||
};
|
};
|
||||||
using Flags = base::flags<Flag>;
|
using Flags = base::flags<Flag>;
|
||||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
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) {
|
bool Session::chatsListLoaded(Data::Folder *folder) {
|
||||||
return chatsList(folder)->loaded();
|
return chatsList(folder)->loaded();
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,8 +226,6 @@ public:
|
||||||
|
|
||||||
void deleteConversationLocally(not_null<PeerData*> peer);
|
void deleteConversationLocally(not_null<PeerData*> peer);
|
||||||
|
|
||||||
void cancelForwarding(not_null<History*> history);
|
|
||||||
|
|
||||||
[[nodiscard]] rpl::variable<bool> &contactsLoaded() {
|
[[nodiscard]] rpl::variable<bool> &contactsLoaded() {
|
||||||
return _contactsLoaded;
|
return _contactsLoaded;
|
||||||
}
|
}
|
||||||
|
|
|
@ -445,3 +445,18 @@ forumTopicIconPosition: point(2px, 0px);
|
||||||
editTopicTitleMargin: margins(70px, 2px, 22px, 18px);
|
editTopicTitleMargin: margins(70px, 2px, 22px, 18px);
|
||||||
editTopicIconPosition: point(24px, 19px);
|
editTopicIconPosition: point(24px, 19px);
|
||||||
editTopicMaxHeight: 408px;
|
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);
|
selectByMouse(globalPosition);
|
||||||
const auto getPeerFromRow = [](Row *row) -> PeerData* {
|
|
||||||
if (const auto history = row ? row->history() : nullptr) {
|
const auto fromRow = [](Row *row) {
|
||||||
return history->peer;
|
return row ? row->thread() : nullptr;
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
};
|
};
|
||||||
if (_state == WidgetState::Default) {
|
if (_state == WidgetState::Default) {
|
||||||
return getPeerFromRow(_selected);
|
return fromRow(_selected);
|
||||||
} else if (_state == WidgetState::Filtered) {
|
} else if (_state == WidgetState::Filtered) {
|
||||||
if (base::in_range(_filteredSelected, 0, _filterResults.size())) {
|
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())) {
|
} 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())) {
|
} 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;
|
return nullptr;
|
||||||
|
@ -3416,8 +3420,8 @@ void InnerWidget::setupShortcuts() {
|
||||||
return jumpToDialogRow(last);
|
return jumpToDialogRow(last);
|
||||||
});
|
});
|
||||||
request->check(Command::ChatSelf) && request->handle([=] {
|
request->check(Command::ChatSelf) && request->handle([=] {
|
||||||
_controller->content()->choosePeer(
|
_controller->content()->chooseThread(
|
||||||
session().userPeerId(),
|
session().user(),
|
||||||
ShowAtUnreadMsgId);
|
ShowAtUnreadMsgId);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,6 +40,7 @@ class SessionController;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class CloudImageView;
|
class CloudImageView;
|
||||||
|
class Thread;
|
||||||
class Folder;
|
class Folder;
|
||||||
class Forum;
|
class Forum;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
@ -133,7 +134,7 @@ public:
|
||||||
void onHashtagFilterUpdate(QStringView newFilter);
|
void onHashtagFilterUpdate(QStringView newFilter);
|
||||||
void appendToFiltered(Key key);
|
void appendToFiltered(Key key);
|
||||||
|
|
||||||
PeerData *updateFromParentDrag(QPoint globalPosition);
|
Data::Thread *updateFromParentDrag(QPoint globalPosition);
|
||||||
|
|
||||||
void setLoadMoreCallback(Fn<void()> callback);
|
void setLoadMoreCallback(Fn<void()> callback);
|
||||||
void setLoadMoreFilteredCallback(Fn<void()> callback);
|
void setLoadMoreFilteredCallback(Fn<void()> callback);
|
||||||
|
|
|
@ -414,10 +414,7 @@ void Widget::chosenRow(const ChosenRow &row) {
|
||||||
&& row.filteredRow;
|
&& row.filteredRow;
|
||||||
const auto history = row.key.history();
|
const auto history = row.key.history();
|
||||||
if (const auto topic = row.key.topic()) {
|
if (const auto topic = row.key.topic()) {
|
||||||
controller()->showTopic(
|
controller()->content()->chooseThread(topic, row.message.fullId.msg);
|
||||||
topic,
|
|
||||||
row.message.fullId.msg,
|
|
||||||
Window::SectionShow::Way::ClearStack);
|
|
||||||
} else if (history && history->peer->isForum() && !row.message.fullId) {
|
} else if (history && history->peer->isForum() && !row.message.fullId) {
|
||||||
controller()->openForum(history->peer->asChannel());
|
controller()->openForum(history->peer->asChannel());
|
||||||
return;
|
return;
|
||||||
|
@ -448,7 +445,7 @@ void Widget::chosenRow(const ChosenRow &row) {
|
||||||
toSeparate();
|
toSeparate();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
controller()->content()->choosePeer(peer->id, showAtMsgId);
|
controller()->content()->chooseThread(history, showAtMsgId);
|
||||||
}
|
}
|
||||||
} else if (const auto folder = row.key.folder()) {
|
} else if (const auto folder = row.key.folder()) {
|
||||||
controller()->openFolder(folder);
|
controller()->openFolder(folder);
|
||||||
|
@ -1783,7 +1780,9 @@ void Widget::dragMoveEvent(QDragMoveEvent *e) {
|
||||||
e->setDropAction(Qt::IgnoreAction);
|
e->setDropAction(Qt::IgnoreAction);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_dragForward) updateDragInScroll(false);
|
if (_dragForward) {
|
||||||
|
updateDragInScroll(false);
|
||||||
|
}
|
||||||
_inner->dragLeft();
|
_inner->dragLeft();
|
||||||
e->setDropAction(Qt::IgnoreAction);
|
e->setDropAction(Qt::IgnoreAction);
|
||||||
}
|
}
|
||||||
|
@ -1814,10 +1813,11 @@ void Widget::updateDragInScroll(bool inScroll) {
|
||||||
void Widget::dropEvent(QDropEvent *e) {
|
void Widget::dropEvent(QDropEvent *e) {
|
||||||
_chooseByDragTimer.cancel();
|
_chooseByDragTimer.cancel();
|
||||||
if (_scroll->geometry().contains(e->pos())) {
|
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();
|
e->acceptProposedAction();
|
||||||
controller()->content()->onFilesOrForwardDrop(
|
controller()->content()->onFilesOrForwardDrop(
|
||||||
peer->id,
|
thread,
|
||||||
e->mimeData());
|
e->mimeData());
|
||||||
controller()->widget()->raise();
|
controller()->widget()->raise();
|
||||||
controller()->widget()->activateWindow();
|
controller()->widget()->activateWindow();
|
||||||
|
@ -2339,10 +2339,8 @@ bool Widget::cancelSearch() {
|
||||||
cancelSearchRequest();
|
cancelSearchRequest();
|
||||||
if (!clearingQuery && _searchInChat) {
|
if (!clearingQuery && _searchInChat) {
|
||||||
if (controller()->adaptive().isOneColumn()) {
|
if (controller()->adaptive().isOneColumn()) {
|
||||||
if (const auto topic = _searchInChat.topic()) {
|
if (const auto thread = _searchInChat.thread()) {
|
||||||
controller()->showTopic(topic);
|
controller()->showThread(thread);
|
||||||
} else if (const auto peer = _searchInChat.peer()) {
|
|
||||||
controller()->showPeerHistory(peer);
|
|
||||||
} else {
|
} else {
|
||||||
Unexpected("Empty key in cancelSearch().");
|
Unexpected("Empty key in cancelSearch().");
|
||||||
}
|
}
|
||||||
|
@ -2371,10 +2369,8 @@ void Widget::cancelSearchInChat() {
|
||||||
if (isOneColumn
|
if (isOneColumn
|
||||||
&& !controller()->selectingPeer()
|
&& !controller()->selectingPeer()
|
||||||
&& currentSearchQuery().trimmed().isEmpty()) {
|
&& currentSearchQuery().trimmed().isEmpty()) {
|
||||||
if (const auto topic = _searchInChat.topic()) {
|
if (const auto thread = _searchInChat.thread()) {
|
||||||
controller()->showTopic(topic);
|
controller()->showThread(thread);
|
||||||
} else if (const auto peer = _searchInChat.peer()) {
|
|
||||||
controller()->showPeerHistory(peer);
|
|
||||||
} else {
|
} else {
|
||||||
Unexpected("Empty key in cancelSearchInPeer().");
|
Unexpected("Empty key in cancelSearchInPeer().");
|
||||||
}
|
}
|
||||||
|
|
|
@ -355,6 +355,13 @@ void History::draftSavedToCloud(MsgId topicRootId) {
|
||||||
session().local().writeDrafts(this);
|
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(
|
Data::ResolvedForwardDraft History::resolveForwardDraft(
|
||||||
const Data::ForwardDraft &draft) const {
|
const Data::ForwardDraft &draft) const {
|
||||||
return Data::ResolvedForwardDraft{
|
return Data::ResolvedForwardDraft{
|
||||||
|
@ -363,10 +370,12 @@ Data::ResolvedForwardDraft History::resolveForwardDraft(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Data::ResolvedForwardDraft History::resolveForwardDraft() {
|
Data::ResolvedForwardDraft History::resolveForwardDraft(
|
||||||
auto result = resolveForwardDraft(_forwardDraft);
|
MsgId topicRootId) {
|
||||||
if (result.items.size() != _forwardDraft.ids.size()) {
|
const auto &draft = forwardDraft(topicRootId);
|
||||||
setForwardDraft({
|
auto result = resolveForwardDraft(draft);
|
||||||
|
if (result.items.size() != draft.ids.size()) {
|
||||||
|
setForwardDraft(topicRootId, {
|
||||||
.ids = owner().itemsToIds(result.items),
|
.ids = owner().itemsToIds(result.items),
|
||||||
.options = result.options,
|
.options = result.options,
|
||||||
});
|
});
|
||||||
|
@ -374,8 +383,29 @@ Data::ResolvedForwardDraft History::resolveForwardDraft() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void History::setForwardDraft(Data::ForwardDraft &&draft) {
|
void History::setForwardDraft(
|
||||||
_forwardDraft = std::move(draft);
|
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(
|
not_null<HistoryItem*> History::createItem(
|
||||||
|
@ -624,7 +654,8 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||||
TimeId date,
|
TimeId date,
|
||||||
PeerId from,
|
PeerId from,
|
||||||
const QString &postAuthor,
|
const QString &postAuthor,
|
||||||
not_null<HistoryItem*> forwardOriginal) {
|
not_null<HistoryItem*> forwardOriginal,
|
||||||
|
MsgId topicRootId) {
|
||||||
return addNewItem(
|
return addNewItem(
|
||||||
makeMessage(
|
makeMessage(
|
||||||
id,
|
id,
|
||||||
|
@ -632,7 +663,8 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||||
date,
|
date,
|
||||||
from,
|
from,
|
||||||
postAuthor,
|
postAuthor,
|
||||||
forwardOriginal),
|
forwardOriginal,
|
||||||
|
topicRootId),
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,14 @@ enum class ForwardOptions {
|
||||||
struct ForwardDraft {
|
struct ForwardDraft {
|
||||||
MessageIdsList ids;
|
MessageIdsList ids;
|
||||||
ForwardOptions options = ForwardOptions::PreserveInfo;
|
ForwardOptions options = ForwardOptions::PreserveInfo;
|
||||||
|
|
||||||
|
friend inline constexpr auto operator<=>(
|
||||||
|
const ForwardDraft&,
|
||||||
|
const ForwardDraft&) = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using ForwardDrafts = base::flat_map<MsgId, ForwardDraft>;
|
||||||
|
|
||||||
struct ResolvedForwardDraft {
|
struct ResolvedForwardDraft {
|
||||||
HistoryItemsList items;
|
HistoryItemsList items;
|
||||||
ForwardOptions options = ForwardOptions::PreserveInfo;
|
ForwardOptions options = ForwardOptions::PreserveInfo;
|
||||||
|
@ -165,7 +171,8 @@ public:
|
||||||
TimeId date,
|
TimeId date,
|
||||||
PeerId from,
|
PeerId from,
|
||||||
const QString &postAuthor,
|
const QString &postAuthor,
|
||||||
not_null<HistoryItem*> forwardOriginal);
|
not_null<HistoryItem*> forwardOriginal,
|
||||||
|
MsgId topicRootId);
|
||||||
not_null<HistoryItem*> addNewLocalMessage(
|
not_null<HistoryItem*> addNewLocalMessage(
|
||||||
MsgId id,
|
MsgId id,
|
||||||
MessageFlags flags,
|
MessageFlags flags,
|
||||||
|
@ -366,13 +373,13 @@ public:
|
||||||
void applyCloudDraft(MsgId topicRootId);
|
void applyCloudDraft(MsgId topicRootId);
|
||||||
void draftSavedToCloud(MsgId topicRootId);
|
void draftSavedToCloud(MsgId topicRootId);
|
||||||
|
|
||||||
[[nodiscard]] const Data::ForwardDraft &forwardDraft() const {
|
[[nodiscard]] const Data::ForwardDraft &forwardDraft(
|
||||||
return _forwardDraft;
|
MsgId topicRootId) const;
|
||||||
}
|
|
||||||
[[nodiscard]] Data::ResolvedForwardDraft resolveForwardDraft(
|
[[nodiscard]] Data::ResolvedForwardDraft resolveForwardDraft(
|
||||||
const Data::ForwardDraft &draft) const;
|
const Data::ForwardDraft &draft) const;
|
||||||
[[nodiscard]] Data::ResolvedForwardDraft resolveForwardDraft();
|
[[nodiscard]] Data::ResolvedForwardDraft resolveForwardDraft(
|
||||||
void setForwardDraft(Data::ForwardDraft &&draft);
|
MsgId topicRootId);
|
||||||
|
void setForwardDraft(MsgId topicRootId, Data::ForwardDraft &&draft);
|
||||||
|
|
||||||
History *migrateSibling() const;
|
History *migrateSibling() const;
|
||||||
[[nodiscard]] bool useTopPromotion() const;
|
[[nodiscard]] bool useTopPromotion() const;
|
||||||
|
@ -621,7 +628,7 @@ private:
|
||||||
Data::HistoryDrafts _drafts;
|
Data::HistoryDrafts _drafts;
|
||||||
base::flat_map<MsgId, TimeId> _acceptCloudDraftsAfter;
|
base::flat_map<MsgId, TimeId> _acceptCloudDraftsAfter;
|
||||||
base::flat_map<MsgId, int> _savingCloudDraftRequests;
|
base::flat_map<MsgId, int> _savingCloudDraftRequests;
|
||||||
Data::ForwardDraft _forwardDraft;
|
Data::ForwardDrafts _forwardDrafts;
|
||||||
|
|
||||||
QString _topPromotedMessage;
|
QString _topPromotedMessage;
|
||||||
QString _topPromotedType;
|
QString _topPromotedType;
|
||||||
|
|
|
@ -411,7 +411,8 @@ HistoryMessage::HistoryMessage(
|
||||||
TimeId date,
|
TimeId date,
|
||||||
PeerId from,
|
PeerId from,
|
||||||
const QString &postAuthor,
|
const QString &postAuthor,
|
||||||
not_null<HistoryItem*> original)
|
not_null<HistoryItem*> original,
|
||||||
|
MsgId topicRootId)
|
||||||
: HistoryItem(
|
: HistoryItem(
|
||||||
history,
|
history,
|
||||||
id,
|
id,
|
||||||
|
@ -424,6 +425,8 @@ HistoryMessage::HistoryMessage(
|
||||||
|
|
||||||
const auto originalMedia = original->media();
|
const auto originalMedia = original->media();
|
||||||
const auto dropForwardInfo = original->computeDropForwardedInfo();
|
const auto dropForwardInfo = original->computeDropForwardedInfo();
|
||||||
|
config.replyTo = config.replyToTop = topicRootId;
|
||||||
|
config.replyIsTopicPost = (topicRootId != 0);
|
||||||
if (!dropForwardInfo) {
|
if (!dropForwardInfo) {
|
||||||
config.originalDate = original->dateOriginal();
|
config.originalDate = original->dateOriginal();
|
||||||
if (const auto info = original->hiddenSenderInfo()) {
|
if (const auto info = original->hiddenSenderInfo()) {
|
||||||
|
|
|
@ -72,7 +72,8 @@ public:
|
||||||
TimeId date,
|
TimeId date,
|
||||||
PeerId from,
|
PeerId from,
|
||||||
const QString &postAuthor,
|
const QString &postAuthor,
|
||||||
not_null<HistoryItem*> original); // local forwarded
|
not_null<HistoryItem*> original,
|
||||||
|
MsgId topicRootId); // local forwarded
|
||||||
HistoryMessage(
|
HistoryMessage(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
MsgId id,
|
MsgId id,
|
||||||
|
|
|
@ -84,6 +84,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_unread_things.h"
|
#include "history/history_unread_things.h"
|
||||||
#include "history/view/controls/history_view_compose_search.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_voice_record_bar.h"
|
||||||
#include "history/view/controls/history_view_ttl_button.h"
|
#include "history/view/controls/history_view_ttl_button.h"
|
||||||
#include "history/view/reactions/history_view_reactions_button.h"
|
#include "history/view/reactions/history_view_reactions_button.h"
|
||||||
|
@ -255,11 +256,12 @@ HistoryWidget::HistoryWidget(
|
||||||
, _botKeyboardShow(this, st::historyBotKeyboardShow)
|
, _botKeyboardShow(this, st::historyBotKeyboardShow)
|
||||||
, _botKeyboardHide(this, st::historyBotKeyboardHide)
|
, _botKeyboardHide(this, st::historyBotKeyboardHide)
|
||||||
, _botCommandStart(this, st::historyBotCommandStart)
|
, _botCommandStart(this, st::historyBotCommandStart)
|
||||||
, _voiceRecordBar(std::make_unique<HistoryWidget::VoiceRecordBar>(
|
, _voiceRecordBar(std::make_unique<VoiceRecordBar>(
|
||||||
this,
|
this,
|
||||||
controller,
|
controller,
|
||||||
_send,
|
_send,
|
||||||
st::historySendSize.height()))
|
st::historySendSize.height()))
|
||||||
|
, _forwardPanel(std::make_unique<ForwardPanel>([=] { updateField(); }))
|
||||||
, _field(
|
, _field(
|
||||||
this,
|
this,
|
||||||
st::historyComposeField,
|
st::historyComposeField,
|
||||||
|
@ -378,6 +380,12 @@ HistoryWidget::HistoryWidget(
|
||||||
_scroll->updateBars();
|
_scroll->updateBars();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
_forwardPanel->itemsUpdated(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
updateControlsVisibility();
|
||||||
|
updateControlsGeometry();
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
InitMessageField(controller, _field, [=](
|
InitMessageField(controller, _field, [=](
|
||||||
not_null<DocumentData*> document) {
|
not_null<DocumentData*> document) {
|
||||||
if (_peer && Data::AllowEmojiWithoutPremium(_peer)) {
|
if (_peer && Data::AllowEmojiWithoutPremium(_peer)) {
|
||||||
|
@ -606,6 +614,7 @@ HistoryWidget::HistoryWidget(
|
||||||
using EntryUpdateFlag = Data::EntryUpdate::Flag;
|
using EntryUpdateFlag = Data::EntryUpdate::Flag;
|
||||||
session().changes().entryUpdates(
|
session().changes().entryUpdates(
|
||||||
EntryUpdateFlag::HasPinnedMessages
|
EntryUpdateFlag::HasPinnedMessages
|
||||||
|
| EntryUpdateFlag::ForwardDraft
|
||||||
) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
|
) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
|
||||||
if (_pinnedTracker
|
if (_pinnedTracker
|
||||||
&& (update.flags & EntryUpdateFlag::HasPinnedMessages)
|
&& (update.flags & EntryUpdateFlag::HasPinnedMessages)
|
||||||
|
@ -613,12 +622,14 @@ HistoryWidget::HistoryWidget(
|
||||||
|| (update.entry.get() == _migrated))) {
|
|| (update.entry.get() == _migrated))) {
|
||||||
checkPinnedBarState();
|
checkPinnedBarState();
|
||||||
}
|
}
|
||||||
|
if (update.flags & EntryUpdateFlag::ForwardDraft) {
|
||||||
|
updateForwarding();
|
||||||
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
using HistoryUpdateFlag = Data::HistoryUpdate::Flag;
|
using HistoryUpdateFlag = Data::HistoryUpdate::Flag;
|
||||||
session().changes().historyUpdates(
|
session().changes().historyUpdates(
|
||||||
HistoryUpdateFlag::MessageSent
|
HistoryUpdateFlag::MessageSent
|
||||||
| HistoryUpdateFlag::ForwardDraft
|
|
||||||
| HistoryUpdateFlag::BotKeyboard
|
| HistoryUpdateFlag::BotKeyboard
|
||||||
| HistoryUpdateFlag::CloudDraft
|
| HistoryUpdateFlag::CloudDraft
|
||||||
| HistoryUpdateFlag::UnreadMentions
|
| HistoryUpdateFlag::UnreadMentions
|
||||||
|
@ -633,9 +644,6 @@ HistoryWidget::HistoryWidget(
|
||||||
if (flags & HistoryUpdateFlag::MessageSent) {
|
if (flags & HistoryUpdateFlag::MessageSent) {
|
||||||
synteticScrollToY(_scroll->scrollTopMax());
|
synteticScrollToY(_scroll->scrollTopMax());
|
||||||
}
|
}
|
||||||
if (flags & HistoryUpdateFlag::ForwardDraft) {
|
|
||||||
updateForwarding();
|
|
||||||
}
|
|
||||||
if (flags & HistoryUpdateFlag::BotKeyboard) {
|
if (flags & HistoryUpdateFlag::BotKeyboard) {
|
||||||
updateBotKeyboard(update.history);
|
updateBotKeyboard(update.history);
|
||||||
}
|
}
|
||||||
|
@ -3719,7 +3727,7 @@ void HistoryWidget::send(Api::SendOptions options) {
|
||||||
_peer,
|
_peer,
|
||||||
{
|
{
|
||||||
.topicRootId = topicRootId,
|
.topicRootId = topicRootId,
|
||||||
.forward = &_toForward.items,
|
.forward = &_forwardPanel->items(),
|
||||||
.text = &message.textWithTags,
|
.text = &message.textWithTags,
|
||||||
.ignoreSlowmodeCountdown = (options.scheduled != 0),
|
.ignoreSlowmodeCountdown = (options.scheduled != 0),
|
||||||
});
|
});
|
||||||
|
@ -4281,7 +4289,7 @@ QRect HistoryWidget::floatPlayerAvailableRect() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryWidget::readyToForward() const {
|
bool HistoryWidget::readyToForward() const {
|
||||||
return _canSendMessages && !_toForward.items.empty();
|
return _canSendMessages && !_forwardPanel->empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryWidget::hasSilentToggle() const {
|
bool HistoryWidget::hasSilentToggle() const {
|
||||||
|
@ -5274,15 +5282,6 @@ void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) {
|
||||||
toggleKeyboard();
|
toggleKeyboard();
|
||||||
_kbReplyTo = nullptr;
|
_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);
|
const auto i = _itemRevealAnimations.find(item);
|
||||||
if (i != end(_itemRevealAnimations)) {
|
if (i != end(_itemRevealAnimations)) {
|
||||||
_itemRevealAnimations.erase(i);
|
_itemRevealAnimations.erase(i);
|
||||||
|
@ -5858,81 +5857,7 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
|
||||||
updateField();
|
updateField();
|
||||||
} else if (_inReplyEditForward) {
|
} else if (_inReplyEditForward) {
|
||||||
if (readyToForward()) {
|
if (readyToForward()) {
|
||||||
using Options = Data::ForwardOptions;
|
_forwardPanel->editOptions(controller());
|
||||||
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));
|
|
||||||
} else {
|
} else {
|
||||||
controller()->showPeerHistory(
|
controller()->showPeerHistory(
|
||||||
_peer,
|
_peer,
|
||||||
|
@ -6725,8 +6650,8 @@ void HistoryWidget::processReply() {
|
||||||
.text = tr::lng_reply_cant_forward(),
|
.text = tr::lng_reply_cant_forward(),
|
||||||
.confirmed = crl::guard(this, [=] {
|
.confirmed = crl::guard(this, [=] {
|
||||||
controller()->content()->setForwardDraft(
|
controller()->content()->setForwardDraft(
|
||||||
_peer->id,
|
_history,
|
||||||
{.ids = { 1, itemId } });
|
{ .ids = { 1, itemId } });
|
||||||
}),
|
}),
|
||||||
.confirmText = tr::lng_selected_forward(),
|
.confirmText = tr::lng_selected_forward(),
|
||||||
}));
|
}));
|
||||||
|
@ -6757,7 +6682,7 @@ void HistoryWidget::setReplyFieldsFromProcessing() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
session().data().cancelForwarding(_history);
|
_history->setForwardDraft(MsgId(), {});
|
||||||
if (_composeSearch) {
|
if (_composeSearch) {
|
||||||
_composeSearch->hideAnimated();
|
_composeSearch->hideAnimated();
|
||||||
}
|
}
|
||||||
|
@ -7024,7 +6949,7 @@ void HistoryWidget::cancelFieldAreaState() {
|
||||||
} else if (_editMsgId) {
|
} else if (_editMsgId) {
|
||||||
cancelEdit();
|
cancelEdit();
|
||||||
} else if (readyToForward()) {
|
} else if (readyToForward()) {
|
||||||
session().data().cancelForwarding(_history);
|
_history->setForwardDraft(MsgId(), {});
|
||||||
} else if (_replyToId) {
|
} else if (_replyToId) {
|
||||||
cancelReply();
|
cancelReply();
|
||||||
} else if (_kbReplyTo) {
|
} else if (_kbReplyTo) {
|
||||||
|
@ -7437,115 +7362,16 @@ void HistoryWidget::updateReplyEditTexts(bool force) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::updateForwarding() {
|
void HistoryWidget::updateForwarding() {
|
||||||
if (_history) {
|
_forwardPanel->update(_history, _history
|
||||||
_toForward = _history->resolveForwardDraft();
|
? _history->resolveForwardDraft(MsgId())
|
||||||
updateForwardingTexts();
|
: Data::ResolvedForwardDraft());
|
||||||
} else {
|
if (readyToForward()) {
|
||||||
_toForward = {};
|
cancelReply();
|
||||||
}
|
}
|
||||||
updateControlsVisibility();
|
updateControlsVisibility();
|
||||||
updateControlsGeometry();
|
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() {
|
void HistoryWidget::updateReplyToName() {
|
||||||
if (_editMsgId) {
|
if (_editMsgId) {
|
||||||
return;
|
return;
|
||||||
|
@ -7594,7 +7420,6 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
|
||||||
backy -= st::historyReplyHeight;
|
backy -= st::historyReplyHeight;
|
||||||
backh += st::historyReplyHeight;
|
backh += st::historyReplyHeight;
|
||||||
} else if (hasForward) {
|
} else if (hasForward) {
|
||||||
checkForwardingInfo();
|
|
||||||
backy -= st::historyReplyHeight;
|
backy -= st::historyReplyHeight;
|
||||||
backh += st::historyReplyHeight;
|
backh += st::historyReplyHeight;
|
||||||
} else if (_previewData && _previewData->pendingTill >= 0) {
|
} else if (_previewData && _previewData->pendingTill >= 0) {
|
||||||
|
@ -7645,36 +7470,14 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (hasForward) {
|
} else if (hasForward) {
|
||||||
auto forwardLeft = st::historyReplySkip;
|
|
||||||
st::historyForwardIcon.paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
|
st::historyForwardIcon.paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
|
||||||
if (!drawWebPagePreview) {
|
if (!drawWebPagePreview) {
|
||||||
const auto firstItem = _toForward.items.front();
|
const auto x = st::historyReplySkip;
|
||||||
const auto firstMedia = firstItem->media();
|
const auto available = width()
|
||||||
const auto preview = (_toForward.items.size() < 2 && firstMedia && firstMedia->hasReplyPreview())
|
- x
|
||||||
? firstMedia->replyPreview()
|
- _fieldBarCancel->width()
|
||||||
: nullptr;
|
- st::msgReplyPadding.right();
|
||||||
if (preview) {
|
_forwardPanel->paint(p, x, backy, available, width());
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (drawWebPagePreview) {
|
if (drawWebPagePreview) {
|
||||||
|
|
|
@ -94,6 +94,7 @@ class ComposeSearch;
|
||||||
namespace Controls {
|
namespace Controls {
|
||||||
class RecordLock;
|
class RecordLock;
|
||||||
class VoiceRecordBar;
|
class VoiceRecordBar;
|
||||||
|
class ForwardPanel;
|
||||||
class TTLButton;
|
class TTLButton;
|
||||||
} // namespace Controls
|
} // namespace Controls
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
@ -110,6 +111,7 @@ public:
|
||||||
using FieldHistoryAction = Ui::InputField::HistoryAction;
|
using FieldHistoryAction = Ui::InputField::HistoryAction;
|
||||||
using RecordLock = HistoryView::Controls::RecordLock;
|
using RecordLock = HistoryView::Controls::RecordLock;
|
||||||
using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar;
|
using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar;
|
||||||
|
using ForwardPanel = HistoryView::Controls::ForwardPanel;
|
||||||
|
|
||||||
HistoryWidget(
|
HistoryWidget(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
|
@ -189,7 +191,6 @@ public:
|
||||||
bool cancelReply(bool lastKeyboardUsed = false);
|
bool cancelReply(bool lastKeyboardUsed = false);
|
||||||
void cancelEdit();
|
void cancelEdit();
|
||||||
void updateForwarding();
|
void updateForwarding();
|
||||||
void updateForwardingTexts();
|
|
||||||
|
|
||||||
void pushReplyReturn(not_null<HistoryItem*> item);
|
void pushReplyReturn(not_null<HistoryItem*> item);
|
||||||
[[nodiscard]] QVector<FullMsgId> replyReturns() const;
|
[[nodiscard]] QVector<FullMsgId> replyReturns() const;
|
||||||
|
@ -470,7 +471,6 @@ private:
|
||||||
int countMembersDropdownHeightMax() const;
|
int countMembersDropdownHeightMax() const;
|
||||||
|
|
||||||
void updateReplyToName();
|
void updateReplyToName();
|
||||||
void checkForwardingInfo();
|
|
||||||
bool editingMessage() const {
|
bool editingMessage() const {
|
||||||
return _editMsgId != 0;
|
return _editMsgId != 0;
|
||||||
}
|
}
|
||||||
|
@ -617,10 +617,6 @@ private:
|
||||||
MsgId _processingReplyId = 0;
|
MsgId _processingReplyId = 0;
|
||||||
HistoryItem *_processingReplyItem = nullptr;
|
HistoryItem *_processingReplyItem = nullptr;
|
||||||
|
|
||||||
Data::ResolvedForwardDraft _toForward;
|
|
||||||
Ui::Text::String _toForwardFrom, _toForwardText;
|
|
||||||
int _toForwardNameVersion = 0;
|
|
||||||
|
|
||||||
MsgId _editMsgId = 0;
|
MsgId _editMsgId = 0;
|
||||||
|
|
||||||
HistoryItem *_replyEditMsg = nullptr;
|
HistoryItem *_replyEditMsg = nullptr;
|
||||||
|
@ -723,6 +719,7 @@ private:
|
||||||
object_ptr<Ui::IconButton> _scheduled = { nullptr };
|
object_ptr<Ui::IconButton> _scheduled = { nullptr };
|
||||||
std::unique_ptr<HistoryView::Controls::TTLButton> _ttlInfo;
|
std::unique_ptr<HistoryView::Controls::TTLButton> _ttlInfo;
|
||||||
const std::unique_ptr<VoiceRecordBar> _voiceRecordBar;
|
const std::unique_ptr<VoiceRecordBar> _voiceRecordBar;
|
||||||
|
const std::unique_ptr<ForwardPanel> _forwardPanel;
|
||||||
std::unique_ptr<HistoryView::ComposeSearch> _composeSearch;
|
std::unique_ptr<HistoryView::ComposeSearch> _composeSearch;
|
||||||
bool _cmdStartShown = false;
|
bool _cmdStartShown = false;
|
||||||
object_ptr<Ui::InputField> _field;
|
object_ptr<Ui::InputField> _field;
|
||||||
|
|
|
@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/view/controls/history_view_voice_record_bar.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_ttl_button.h"
|
||||||
|
#include "history/view/controls/history_view_forward_panel.h"
|
||||||
#include "history/view/history_view_webpage_preview.h"
|
#include "history/view/history_view_webpage_preview.h"
|
||||||
#include "inline_bots/bot_attach_web_view.h"
|
#include "inline_bots/bot_attach_web_view.h"
|
||||||
#include "inline_bots/inline_results_widget.h"
|
#include "inline_bots/inline_results_widget.h"
|
||||||
|
@ -89,7 +90,8 @@ using MessageToEdit = ComposeControls::MessageToEdit;
|
||||||
using VoiceToSend = ComposeControls::VoiceToSend;
|
using VoiceToSend = ComposeControls::VoiceToSend;
|
||||||
using SendActionUpdate = ComposeControls::SendActionUpdate;
|
using SendActionUpdate = ComposeControls::SendActionUpdate;
|
||||||
using SetHistoryArgs = ComposeControls::SetHistoryArgs;
|
using SetHistoryArgs = ComposeControls::SetHistoryArgs;
|
||||||
using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar;
|
using VoiceRecordBar = Controls::VoiceRecordBar;
|
||||||
|
using ForwardPanel = Controls::ForwardPanel;
|
||||||
|
|
||||||
[[nodiscard]] auto ShowWebPagePreview(WebPageData *page) {
|
[[nodiscard]] auto ShowWebPagePreview(WebPageData *page) {
|
||||||
return page && (page->pendingTill >= 0);
|
return page && (page->pendingTill >= 0);
|
||||||
|
@ -331,13 +333,18 @@ rpl::producer<WebPageData*> WebpageProcessor::pageDataChanges() const {
|
||||||
|
|
||||||
class FieldHeader final : public Ui::RpWidget {
|
class FieldHeader final : public Ui::RpWidget {
|
||||||
public:
|
public:
|
||||||
FieldHeader(QWidget *parent, not_null<Data::Session*> data);
|
FieldHeader(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
void setHistory(const SetHistoryArgs &args);
|
void setHistory(const SetHistoryArgs &args);
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void editMessage(FullMsgId id);
|
void editMessage(FullMsgId id);
|
||||||
void replyToMessage(FullMsgId id);
|
void replyToMessage(FullMsgId id);
|
||||||
|
void updateForwarding(
|
||||||
|
Data::Thread *thread,
|
||||||
|
Data::ResolvedForwardDraft items);
|
||||||
void previewRequested(
|
void previewRequested(
|
||||||
rpl::producer<QString> title,
|
rpl::producer<QString> title,
|
||||||
rpl::producer<QString> description,
|
rpl::producer<QString> description,
|
||||||
|
@ -345,6 +352,7 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] bool isDisplayed() const;
|
[[nodiscard]] bool isDisplayed() const;
|
||||||
[[nodiscard]] bool isEditingMessage() const;
|
[[nodiscard]] bool isEditingMessage() const;
|
||||||
|
[[nodiscard]] bool readyToForward() const;
|
||||||
[[nodiscard]] FullMsgId replyingToMessage() const;
|
[[nodiscard]] FullMsgId replyingToMessage() const;
|
||||||
[[nodiscard]] rpl::producer<FullMsgId> editMsgId() const;
|
[[nodiscard]] rpl::producer<FullMsgId> editMsgId() const;
|
||||||
[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
|
[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
|
||||||
|
@ -358,6 +366,9 @@ public:
|
||||||
[[nodiscard]] rpl::producer<> replyCancelled() const {
|
[[nodiscard]] rpl::producer<> replyCancelled() const {
|
||||||
return _replyCancelled.events();
|
return _replyCancelled.events();
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] rpl::producer<> forwardCancelled() const {
|
||||||
|
return _forwardCancelled.events();
|
||||||
|
}
|
||||||
[[nodiscard]] rpl::producer<> previewCancelled() const {
|
[[nodiscard]] rpl::producer<> previewCancelled() const {
|
||||||
return _previewCancelled.events();
|
return _previewCancelled.events();
|
||||||
}
|
}
|
||||||
|
@ -374,6 +385,9 @@ private:
|
||||||
|
|
||||||
void paintWebPage(Painter &p, not_null<PeerData*> peer);
|
void paintWebPage(Painter &p, not_null<PeerData*> peer);
|
||||||
void paintEditOrReplyToMessage(Painter &p);
|
void paintEditOrReplyToMessage(Painter &p);
|
||||||
|
void paintForwardInfo(Painter &p);
|
||||||
|
|
||||||
|
bool hasPreview() const;
|
||||||
|
|
||||||
struct Preview {
|
struct Preview {
|
||||||
WebPageData *data = nullptr;
|
WebPageData *data = nullptr;
|
||||||
|
@ -382,6 +396,7 @@ private:
|
||||||
bool cancelled = false;
|
bool cancelled = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const not_null<Window::SessionController*> _controller;
|
||||||
History *_history = nullptr;
|
History *_history = nullptr;
|
||||||
rpl::variable<QString> _title;
|
rpl::variable<QString> _title;
|
||||||
rpl::variable<QString> _description;
|
rpl::variable<QString> _description;
|
||||||
|
@ -389,12 +404,13 @@ private:
|
||||||
Preview _preview;
|
Preview _preview;
|
||||||
rpl::event_stream<> _editCancelled;
|
rpl::event_stream<> _editCancelled;
|
||||||
rpl::event_stream<> _replyCancelled;
|
rpl::event_stream<> _replyCancelled;
|
||||||
|
rpl::event_stream<> _forwardCancelled;
|
||||||
rpl::event_stream<> _previewCancelled;
|
rpl::event_stream<> _previewCancelled;
|
||||||
|
|
||||||
bool hasPreview() const;
|
|
||||||
|
|
||||||
rpl::variable<FullMsgId> _editMsgId;
|
rpl::variable<FullMsgId> _editMsgId;
|
||||||
rpl::variable<FullMsgId> _replyToId;
|
rpl::variable<FullMsgId> _replyToId;
|
||||||
|
std::unique_ptr<ForwardPanel> _forwardPanel;
|
||||||
|
rpl::producer<> _toForwardUpdated;
|
||||||
|
|
||||||
HistoryItem *_shownMessage = nullptr;
|
HistoryItem *_shownMessage = nullptr;
|
||||||
Ui::Text::String _shownMessageName;
|
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)
|
: RpWidget(parent)
|
||||||
, _data(data)
|
, _controller(controller)
|
||||||
|
, _forwardPanel(
|
||||||
|
std::make_unique<ForwardPanel>([=] { customEmojiRepaint(); }))
|
||||||
|
, _data(&controller->session().data())
|
||||||
, _cancel(Ui::CreateChild<Ui::IconButton>(this, st::historyReplyCancel)) {
|
, _cancel(Ui::CreateChild<Ui::IconButton>(this, st::historyReplyCancel)) {
|
||||||
resize(QSize(parent->width(), st::historyReplyHeight));
|
resize(QSize(parent->width(), st::historyReplyHeight));
|
||||||
init();
|
init();
|
||||||
|
@ -430,6 +451,11 @@ void FieldHeader::init() {
|
||||||
updateControlsGeometry(size);
|
updateControlsGeometry(size);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
_forwardPanel->itemsUpdated(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
updateVisible();
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
const auto leftIconPressed = lifetime().make_state<bool>(false);
|
const auto leftIconPressed = lifetime().make_state<bool>(false);
|
||||||
paintRequest(
|
paintRequest(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
|
@ -439,15 +465,19 @@ void FieldHeader::init() {
|
||||||
const auto position = st::historyReplyIconPosition;
|
const auto position = st::historyReplyIconPosition;
|
||||||
if (isEditingMessage()) {
|
if (isEditingMessage()) {
|
||||||
st::historyEditIcon.paint(p, position, width());
|
st::historyEditIcon.paint(p, position, width());
|
||||||
|
} else if (readyToForward()) {
|
||||||
|
st::historyForwardIcon.paint(p, position, width());
|
||||||
} else if (replyingToMessage()) {
|
} else if (replyingToMessage()) {
|
||||||
st::historyReplyIcon.paint(p, position, width());
|
st::historyReplyIcon.paint(p, position, width());
|
||||||
}
|
}
|
||||||
|
|
||||||
(!ShowWebPagePreview(_preview.data) || *leftIconPressed)
|
(ShowWebPagePreview(_preview.data) && !*leftIconPressed)
|
||||||
? paintEditOrReplyToMessage(p)
|
? paintWebPage(
|
||||||
: paintWebPage(
|
|
||||||
p,
|
p,
|
||||||
_history ? _history->peer : _data->session().user());
|
_history ? _history->peer : _data->session().user())
|
||||||
|
: (isEditingMessage() || !readyToForward())
|
||||||
|
? paintEditOrReplyToMessage(p)
|
||||||
|
: paintForwardInfo(p);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_editMsgId.value(
|
_editMsgId.value(
|
||||||
|
@ -487,6 +517,8 @@ void FieldHeader::init() {
|
||||||
_previewCancelled.fire({});
|
_previewCancelled.fire({});
|
||||||
} else if (_editMsgId.current()) {
|
} else if (_editMsgId.current()) {
|
||||||
_editCancelled.fire({});
|
_editCancelled.fire({});
|
||||||
|
} else if (readyToForward()) {
|
||||||
|
_forwardCancelled.fire({});
|
||||||
} else if (_replyToId.current()) {
|
} else if (_replyToId.current()) {
|
||||||
_replyCancelled.fire({});
|
_replyCancelled.fire({});
|
||||||
}
|
}
|
||||||
|
@ -515,7 +547,9 @@ void FieldHeader::init() {
|
||||||
events(
|
events(
|
||||||
) | rpl::filter([=](not_null<QEvent*> event) {
|
) | rpl::filter([=](not_null<QEvent*> event) {
|
||||||
return ranges::contains(kMouseEvents, event->type())
|
return ranges::contains(kMouseEvents, event->type())
|
||||||
&& (isEditingMessage() || replyingToMessage());
|
&& (isEditingMessage()
|
||||||
|
|| readyToForward()
|
||||||
|
|| replyingToMessage());
|
||||||
}) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
}) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
||||||
const auto type = event->type();
|
const auto type = event->type();
|
||||||
const auto e = static_cast<QMouseEvent*>(event.get());
|
const auto e = static_cast<QMouseEvent*>(event.get());
|
||||||
|
@ -538,10 +572,14 @@ void FieldHeader::init() {
|
||||||
*leftIconPressed = true;
|
*leftIconPressed = true;
|
||||||
update();
|
update();
|
||||||
} else if (isLeftButton && inPreviewRect) {
|
} else if (isLeftButton && inPreviewRect) {
|
||||||
auto id = isEditingMessage()
|
if (!isEditingMessage() && readyToForward()) {
|
||||||
? _editMsgId.current()
|
_forwardPanel->editOptions(_controller);
|
||||||
: replyingToMessage();
|
} else {
|
||||||
_scrollToItemRequests.fire(std::move(id));
|
auto id = isEditingMessage()
|
||||||
|
? _editMsgId.current()
|
||||||
|
: replyingToMessage();
|
||||||
|
_scrollToItemRequests.fire(std::move(id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (type == QEvent::MouseButtonRelease) {
|
} else if (type == QEvent::MouseButtonRelease) {
|
||||||
if (isLeftButton && *leftIconPressed) {
|
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() {
|
void FieldHeader::updateVisible() {
|
||||||
isDisplayed() ? show() : hide();
|
isDisplayed() ? show() : hide();
|
||||||
_visibleChanged.fire(isVisible());
|
_visibleChanged.fire(isVisible());
|
||||||
|
@ -771,13 +820,20 @@ rpl::producer<bool> FieldHeader::visibleChanged() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FieldHeader::isDisplayed() const {
|
bool FieldHeader::isDisplayed() const {
|
||||||
return isEditingMessage() || replyingToMessage() || hasPreview();
|
return isEditingMessage()
|
||||||
|
|| readyToForward()
|
||||||
|
|| replyingToMessage()
|
||||||
|
|| hasPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FieldHeader::isEditingMessage() const {
|
bool FieldHeader::isEditingMessage() const {
|
||||||
return !!_editMsgId.current();
|
return !!_editMsgId.current();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FieldHeader::readyToForward() const {
|
||||||
|
return !_forwardPanel->empty();
|
||||||
|
}
|
||||||
|
|
||||||
FullMsgId FieldHeader::replyingToMessage() const {
|
FullMsgId FieldHeader::replyingToMessage() const {
|
||||||
return _replyToId.current();
|
return _replyToId.current();
|
||||||
}
|
}
|
||||||
|
@ -811,6 +867,15 @@ void FieldHeader::replyToMessage(FullMsgId id) {
|
||||||
_replyToId = 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 {
|
rpl::producer<FullMsgId> FieldHeader::editMsgId() const {
|
||||||
return _editMsgId.value();
|
return _editMsgId.value();
|
||||||
}
|
}
|
||||||
|
@ -863,9 +928,7 @@ ComposeControls::ComposeControls(
|
||||||
, _autocomplete(std::make_unique<FieldAutocomplete>(
|
, _autocomplete(std::make_unique<FieldAutocomplete>(
|
||||||
parent,
|
parent,
|
||||||
window))
|
window))
|
||||||
, _header(std::make_unique<FieldHeader>(
|
, _header(std::make_unique<FieldHeader>(_wrap.get(), _window))
|
||||||
_wrap.get(),
|
|
||||||
&_window->session().data()))
|
|
||||||
, _voiceRecordBar(std::make_unique<VoiceRecordBar>(
|
, _voiceRecordBar(std::make_unique<VoiceRecordBar>(
|
||||||
_wrap.get(),
|
_wrap.get(),
|
||||||
parent,
|
parent,
|
||||||
|
@ -893,11 +956,10 @@ Main::Session &ComposeControls::session() const {
|
||||||
void ComposeControls::setHistory(SetHistoryArgs &&args) {
|
void ComposeControls::setHistory(SetHistoryArgs &&args) {
|
||||||
// Right now only single non-null set of history is supported.
|
// Right now only single non-null set of history is supported.
|
||||||
// Otherwise initWebpageProcess should be updated / rewritten.
|
// Otherwise initWebpageProcess should be updated / rewritten.
|
||||||
Expects(!_history && *args.history);
|
Expects(!_history && (*args.history));
|
||||||
|
|
||||||
_showSlowmodeError = std::move(args.showSlowmodeError);
|
_showSlowmodeError = std::move(args.showSlowmodeError);
|
||||||
_sendActionFactory = std::move(args.sendActionFactory);
|
_sendActionFactory = std::move(args.sendActionFactory);
|
||||||
_topicRootId = args.topicRootId;
|
|
||||||
_slowmodeSecondsLeft = rpl::single(0)
|
_slowmodeSecondsLeft = rpl::single(0)
|
||||||
| rpl::then(std::move(args.slowmodeSecondsLeft));
|
| rpl::then(std::move(args.slowmodeSecondsLeft));
|
||||||
_sendDisabledBySlowmode = rpl::single(false)
|
_sendDisabledBySlowmode = rpl::single(false)
|
||||||
|
@ -905,9 +967,9 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
|
||||||
_writeRestriction = rpl::single(std::optional<QString>())
|
_writeRestriction = rpl::single(std::optional<QString>())
|
||||||
| rpl::then(std::move(args.writeRestriction));
|
| rpl::then(std::move(args.writeRestriction));
|
||||||
const auto history = *args.history;
|
const auto history = *args.history;
|
||||||
//if (_history == history) {
|
if (_history == history) {
|
||||||
// return;
|
return;
|
||||||
//}
|
}
|
||||||
unregisterDraftSources();
|
unregisterDraftSources();
|
||||||
_history = history;
|
_history = history;
|
||||||
_header->setHistory(args);
|
_header->setHistory(args);
|
||||||
|
@ -915,6 +977,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
|
||||||
_window->tabbedSelector()->setCurrentPeer(
|
_window->tabbedSelector()->setCurrentPeer(
|
||||||
history ? history->peer.get() : nullptr);
|
history ? history->peer.get() : nullptr);
|
||||||
initWebpageProcess();
|
initWebpageProcess();
|
||||||
|
initForwardProcess();
|
||||||
updateBotCommandShown();
|
updateBotCommandShown();
|
||||||
updateMessagesTTLShown();
|
updateMessagesTTLShown();
|
||||||
updateControlsGeometry(_wrap->size());
|
updateControlsGeometry(_wrap->size());
|
||||||
|
@ -942,7 +1005,10 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) {
|
void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) {
|
||||||
|
unregisterDraftSources();
|
||||||
_currentDialogsEntryState = state;
|
_currentDialogsEntryState = state;
|
||||||
|
updateForwarding();
|
||||||
|
registerDraftSource();
|
||||||
if (_inlineResults) {
|
if (_inlineResults) {
|
||||||
_inlineResults->setCurrentDialogsEntryState(state);
|
_inlineResults->setCurrentDialogsEntryState(state);
|
||||||
}
|
}
|
||||||
|
@ -1329,6 +1395,11 @@ void ComposeControls::init() {
|
||||||
cancelReplyMessage();
|
cancelReplyMessage();
|
||||||
}, _wrap->lifetime());
|
}, _wrap->lifetime());
|
||||||
|
|
||||||
|
_header->forwardCancelled(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
cancelForward();
|
||||||
|
}, _wrap->lifetime());
|
||||||
|
|
||||||
_header->visibleChanged(
|
_header->visibleChanged(
|
||||||
) | rpl::start_with_next([=](bool shown) {
|
) | rpl::start_with_next([=](bool shown) {
|
||||||
updateHeight();
|
updateHeight();
|
||||||
|
@ -1384,7 +1455,7 @@ bool ComposeControls::showRecordButton() const {
|
||||||
&& !_voiceRecordBar->isListenState()
|
&& !_voiceRecordBar->isListenState()
|
||||||
&& !_voiceRecordBar->isRecordingByAnotherBar()
|
&& !_voiceRecordBar->isRecordingByAnotherBar()
|
||||||
&& !HasSendText(_field)
|
&& !HasSendText(_field)
|
||||||
//&& !readyToForward()
|
&& !readyToForward()
|
||||||
&& !isEditingMessage();
|
&& !isEditingMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1855,10 +1926,20 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||||
_header->replyToMessage({});
|
_header->replyToMessage({});
|
||||||
} else {
|
} else {
|
||||||
_header->replyToMessage({ _history->peer->id, draft->msgId });
|
_header->replyToMessage({ _history->peer->id, draft->msgId });
|
||||||
|
if (_header->replyingToMessage()) {
|
||||||
|
cancelForward();
|
||||||
|
}
|
||||||
_header->editMessage({});
|
_header->editMessage({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ComposeControls::cancelForward() {
|
||||||
|
_history->setForwardDraft(
|
||||||
|
_currentDialogsEntryState.rootId,
|
||||||
|
{});
|
||||||
|
updateForwarding();
|
||||||
|
}
|
||||||
|
|
||||||
void ComposeControls::fieldTabbed() {
|
void ComposeControls::fieldTabbed() {
|
||||||
if (!_autocomplete->isHidden()) {
|
if (!_autocomplete->isHidden()) {
|
||||||
_autocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
|
_autocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
|
||||||
|
@ -2391,9 +2472,8 @@ void ComposeControls::toggleTabbedSelectorMode() {
|
||||||
&& !_window->adaptive().isOneColumn()) {
|
&& !_window->adaptive().isOneColumn()) {
|
||||||
Core::App().settings().setTabbedSelectorSectionEnabled(true);
|
Core::App().settings().setTabbedSelectorSectionEnabled(true);
|
||||||
Core::App().saveSettingsDelayed();
|
Core::App().saveSettingsDelayed();
|
||||||
const auto topic = _topicRootId
|
const auto topic = _history->peer->forumTopicFor(
|
||||||
? _history->peer->forumTopicFor(_topicRootId)
|
_currentDialogsEntryState.rootId);
|
||||||
: nullptr;
|
|
||||||
pushTabbedSelectorToThirdSection(
|
pushTabbedSelectorToThirdSection(
|
||||||
(topic ? topic : (Data::Thread*)_history),
|
(topic ? topic : (Data::Thread*)_history),
|
||||||
Window::SectionShow::Way::ClearStack);
|
Window::SectionShow::Way::ClearStack);
|
||||||
|
@ -2499,6 +2579,9 @@ void ComposeControls::replyToMessage(FullMsgId id) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_header->replyToMessage(id);
|
_header->replyToMessage(id);
|
||||||
|
if (_header->replyingToMessage()) {
|
||||||
|
cancelForward();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveDraftText = true;
|
_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() {
|
bool ComposeControls::handleCancelRequest() {
|
||||||
if (_isInlineBot) {
|
if (_isInlineBot) {
|
||||||
cancelInlineBot();
|
cancelInlineBot();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (_autocomplete && !_autocomplete->isHidden()) {
|
||||||
|
_autocomplete->hideAnimated();
|
||||||
|
return true;
|
||||||
} else if (isEditingMessage()) {
|
} else if (isEditingMessage()) {
|
||||||
cancelEditMessage();
|
cancelEditMessage();
|
||||||
return true;
|
return true;
|
||||||
} else if (_autocomplete && !_autocomplete->isHidden()) {
|
} else if (readyToForward()) {
|
||||||
_autocomplete->hideAnimated();
|
cancelForward();
|
||||||
return true;
|
return true;
|
||||||
} else if (replyingToMessage()) {
|
} else if (replyingToMessage()) {
|
||||||
cancelReplyMessage();
|
cancelReplyMessage();
|
||||||
|
@ -2591,6 +2688,22 @@ void ComposeControls::initWebpageProcess() {
|
||||||
_preview->pageDataChanges());
|
_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 {
|
WebPageId ComposeControls::webPageId() const {
|
||||||
return _header->webPageId();
|
return _header->webPageId();
|
||||||
}
|
}
|
||||||
|
@ -2613,6 +2726,10 @@ FullMsgId ComposeControls::replyingToMessage() const {
|
||||||
return _header->replyingToMessage();
|
return _header->replyingToMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ComposeControls::readyToForward() const {
|
||||||
|
return _header->readyToForward();
|
||||||
|
}
|
||||||
|
|
||||||
bool ComposeControls::isLockPresent() const {
|
bool ComposeControls::isLockPresent() const {
|
||||||
return _voiceRecordBar->isLockPresent();
|
return _voiceRecordBar->isLockPresent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,7 @@ public:
|
||||||
bool returnTabbedSelector();
|
bool returnTabbedSelector();
|
||||||
|
|
||||||
[[nodiscard]] bool isEditingMessage() const;
|
[[nodiscard]] bool isEditingMessage() const;
|
||||||
|
[[nodiscard]] bool readyToForward() const;
|
||||||
[[nodiscard]] FullMsgId replyingToMessage() const;
|
[[nodiscard]] FullMsgId replyingToMessage() const;
|
||||||
|
|
||||||
[[nodiscard]] bool preventsClose(Fn<void()> &&continueCallback) const;
|
[[nodiscard]] bool preventsClose(Fn<void()> &&continueCallback) const;
|
||||||
|
@ -164,6 +165,9 @@ public:
|
||||||
void replyToMessage(FullMsgId id);
|
void replyToMessage(FullMsgId id);
|
||||||
void cancelReplyMessage();
|
void cancelReplyMessage();
|
||||||
|
|
||||||
|
void updateForwarding();
|
||||||
|
void cancelForward();
|
||||||
|
|
||||||
bool handleCancelRequest();
|
bool handleCancelRequest();
|
||||||
|
|
||||||
[[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const;
|
[[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const;
|
||||||
|
@ -208,6 +212,7 @@ private:
|
||||||
void initSendButton();
|
void initSendButton();
|
||||||
void initSendAsButton();
|
void initSendAsButton();
|
||||||
void initWebpageProcess();
|
void initWebpageProcess();
|
||||||
|
void initForwardProcess();
|
||||||
void initWriteRestriction();
|
void initWriteRestriction();
|
||||||
void initVoiceRecordBar();
|
void initVoiceRecordBar();
|
||||||
void initAutocomplete();
|
void initAutocomplete();
|
||||||
|
@ -294,7 +299,6 @@ private:
|
||||||
rpl::variable<bool> _sendDisabledBySlowmode;
|
rpl::variable<bool> _sendDisabledBySlowmode;
|
||||||
rpl::variable<std::optional<QString>> _writeRestriction;
|
rpl::variable<std::optional<QString>> _writeRestriction;
|
||||||
rpl::variable<bool> _hidden;
|
rpl::variable<bool> _hidden;
|
||||||
MsgId _topicRootId = 0;
|
|
||||||
Mode _mode = Mode::Normal;
|
Mode _mode = Mode::Normal;
|
||||||
|
|
||||||
const std::unique_ptr<Ui::RpWidget> _wrap;
|
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({
|
_composeControls->setHistory({
|
||||||
.history = _history.get(),
|
.history = _history.get(),
|
||||||
|
.topicRootId = _topic ? _topic->rootId() : MsgId(0),
|
||||||
.showSlowmodeError = [=] { return showSlowmodeError(); },
|
.showSlowmodeError = [=] { return showSlowmodeError(); },
|
||||||
.sendActionFactory = [=] { return prepareSendAction({}); },
|
.sendActionFactory = [=] { return prepareSendAction({}); },
|
||||||
.slowmodeSecondsLeft = std::move(slowmodeSecondsLeft),
|
.slowmodeSecondsLeft = std::move(slowmodeSecondsLeft),
|
||||||
|
@ -708,6 +709,7 @@ void RepliesWidget::setupComposeControls() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listSendBotCommand(command, FullMsgId());
|
listSendBotCommand(command, FullMsgId());
|
||||||
|
session().api().finishForwarding(prepareSendAction({}));
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
|
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
|
||||||
|
|
|
@ -132,16 +132,17 @@ struct ParsedBot {
|
||||||
void ShowChooseBox(
|
void ShowChooseBox(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
PeerTypes types,
|
PeerTypes types,
|
||||||
Fn<void(not_null<PeerData*>)> callback) {
|
Fn<void(not_null<Data::Thread*>)> callback) {
|
||||||
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
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) {
|
if (const auto strong = *weak) {
|
||||||
strong->closeBox();
|
strong->closeBox();
|
||||||
}
|
}
|
||||||
callback(peer);
|
callback(thread);
|
||||||
};
|
};
|
||||||
auto filter = [=](not_null<PeerData*> peer) -> bool {
|
auto filter = [=](not_null<Data::Thread*> thread) -> bool {
|
||||||
if (!peer->canWrite()) { // #TODO forum forward
|
const auto peer = thread->owningHistory()->peer;
|
||||||
|
if (!thread->canWrite()) {
|
||||||
return false;
|
return false;
|
||||||
} else if (const auto user = peer->asUser()) {
|
} else if (const auto user = peer->asUser()) {
|
||||||
if (user->isBot()) {
|
if (user->isBot()) {
|
||||||
|
@ -577,16 +578,15 @@ void AttachWebView::requestAddToMenu(
|
||||||
const auto open = [=](PeerTypes types) {
|
const auto open = [=](PeerTypes types) {
|
||||||
if (const auto useTypes = chooseTypes & types) {
|
if (const auto useTypes = chooseTypes & types) {
|
||||||
if (const auto strong = chooseController.get()) {
|
if (const auto strong = chooseController.get()) {
|
||||||
const auto callback = [=](not_null<PeerData*> peer) {
|
const auto done = [=](not_null<Data::Thread*> thread) {
|
||||||
const auto history = peer->owner().history(peer);
|
strong->showThread(thread);
|
||||||
strong->showPeerHistory(history);
|
|
||||||
request(
|
request(
|
||||||
nullptr,
|
nullptr,
|
||||||
Api::SendAction(history),
|
Api::SendAction(thread),
|
||||||
bot,
|
bot,
|
||||||
{ .startCommand = startCommand });
|
{ .startCommand = startCommand });
|
||||||
};
|
};
|
||||||
ShowChooseBox(strong, useTypes, callback);
|
ShowChooseBox(strong, useTypes, done);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (!contextAction) {
|
} else if (!contextAction) {
|
||||||
|
|
|
@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "window/window_history_hider.h"
|
#include "window/window_history_hider.h"
|
||||||
#include "window/window_controller.h"
|
#include "window/window_controller.h"
|
||||||
|
#include "window/window_peer_menu.h"
|
||||||
#include "window/themes/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "chat_helpers/tabbed_selector.h" // TabbedSelector::refreshStickers
|
#include "chat_helpers/tabbed_selector.h" // TabbedSelector::refreshStickers
|
||||||
#include "chat_helpers/message_field.h"
|
#include "chat_helpers/message_field.h"
|
||||||
|
@ -307,27 +308,23 @@ MainWidget::MainWidget(
|
||||||
|
|
||||||
session().changes().historyUpdates(
|
session().changes().historyUpdates(
|
||||||
Data::HistoryUpdate::Flag::MessageSent
|
Data::HistoryUpdate::Flag::MessageSent
|
||||||
| Data::HistoryUpdate::Flag::LocalDraftSet
|
|
||||||
) | rpl::start_with_next([=](const Data::HistoryUpdate &update) {
|
) | rpl::start_with_next([=](const Data::HistoryUpdate &update) {
|
||||||
const auto history = update.history;
|
const auto history = update.history;
|
||||||
if (update.flags & Data::HistoryUpdate::Flag::MessageSent) {
|
history->forgetScrollState();
|
||||||
history->forgetScrollState();
|
if (const auto from = history->peer->migrateFrom()) {
|
||||||
if (const auto from = history->peer->migrateFrom()) {
|
auto &owner = history->owner();
|
||||||
auto &owner = history->owner();
|
if (const auto migrated = owner.historyLoaded(from)) {
|
||||||
if (const auto migrated = owner.historyLoaded(from)) {
|
migrated->forgetScrollState();
|
||||||
migrated->forgetScrollState();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (update.flags & Data::HistoryUpdate::Flag::LocalDraftSet) {
|
}, lifetime());
|
||||||
const auto opened = (_history->peer() == history->peer.get());
|
|
||||||
if (opened) {
|
session().changes().entryUpdates(
|
||||||
_history->applyDraft();
|
Data::EntryUpdate::Flag::LocalDraftSet
|
||||||
} else {
|
) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
|
||||||
Ui::showPeerHistory(history, ShowAtUnreadMsgId);
|
controller->showThread(update.entry->asThread(), ShowAtUnreadMsgId);
|
||||||
}
|
_history->applyDraft(); // #TODO forum drop
|
||||||
_controller->hideLayer();
|
controller->hideLayer();
|
||||||
}
|
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
// MSVC BUG + REGRESSION rpl::mappers::tuple :(
|
// MSVC BUG + REGRESSION rpl::mappers::tuple :(
|
||||||
|
@ -515,36 +512,38 @@ void MainWidget::floatPlayerDoubleClickEvent(
|
||||||
_controller->showMessage(item);
|
_controller->showMessage(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWidget::setForwardDraft(PeerId peerId, Data::ForwardDraft &&draft) {
|
bool MainWidget::setForwardDraft(
|
||||||
Expects(peerId != 0);
|
not_null<Data::Thread*> thread,
|
||||||
|
Data::ForwardDraft &&draft) {
|
||||||
const auto peer = session().data().peer(peerId);
|
const auto history = thread->owningHistory();
|
||||||
|
const auto peer = history->peer;
|
||||||
const auto items = session().data().idsToItems(draft.ids);
|
const auto items = session().data().idsToItems(draft.ids);
|
||||||
|
const auto topicRootId = thread->topicRootId();
|
||||||
const auto error = GetErrorTextForSending(
|
const auto error = GetErrorTextForSending(
|
||||||
peer, // #TODO forum forward
|
history->peer,
|
||||||
{ .forward = &items, .ignoreSlowmodeCountdown = true });
|
{
|
||||||
|
.topicRootId = topicRootId,
|
||||||
|
.forward = &items,
|
||||||
|
.ignoreSlowmodeCountdown = true,
|
||||||
|
});
|
||||||
if (!error.isEmpty()) {
|
if (!error.isEmpty()) {
|
||||||
Ui::show(Ui::MakeInformBox(error), Ui::LayerOption::KeepOther);
|
Ui::show(Ui::MakeInformBox(error), Ui::LayerOption::KeepOther);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
peer->owner().history(peer)->setForwardDraft(std::move(draft));
|
history->setForwardDraft(topicRootId, std::move(draft));
|
||||||
_controller->showPeerHistory(
|
_controller->showThread(
|
||||||
peer,
|
thread,
|
||||||
SectionShow::Way::Forward,
|
ShowAtUnreadMsgId,
|
||||||
ShowAtUnreadMsgId);
|
SectionShow::Way::Forward);
|
||||||
_history->cancelReply();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWidget::shareUrl(
|
bool MainWidget::shareUrl(
|
||||||
PeerId peerId,
|
not_null<Data::Thread*> thread,
|
||||||
const QString &url,
|
const QString &url,
|
||||||
const QString &text) const {
|
const QString &text) const {
|
||||||
Expects(peerId != 0);
|
if (!thread->canWrite()) {
|
||||||
|
|
||||||
const auto peer = session().data().peer(peerId);
|
|
||||||
if (!peer->canWrite()) { // #TODO forum forward
|
|
||||||
_controller->show(Ui::MakeInformBox(tr::lng_share_cant()));
|
_controller->show(Ui::MakeInformBox(tr::lng_share_cant()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -557,8 +556,8 @@ bool MainWidget::shareUrl(
|
||||||
int(url.size()) + 1 + int(text.size()),
|
int(url.size()) + 1 + int(text.size()),
|
||||||
QFIXED_MAX
|
QFIXED_MAX
|
||||||
};
|
};
|
||||||
const auto history = peer->owner().history(peer);
|
const auto history = thread->owningHistory();
|
||||||
const auto topicRootId = 0;
|
const auto topicRootId = thread->topicRootId();
|
||||||
history->setLocalDraft(std::make_unique<Data::Draft>(
|
history->setLocalDraft(std::make_unique<Data::Draft>(
|
||||||
textWithTags,
|
textWithTags,
|
||||||
0, // replyTo
|
0, // replyTo
|
||||||
|
@ -566,23 +565,19 @@ bool MainWidget::shareUrl(
|
||||||
cursor,
|
cursor,
|
||||||
Data::PreviewState::Allowed));
|
Data::PreviewState::Allowed));
|
||||||
history->clearLocalEditDraft(topicRootId);
|
history->clearLocalEditDraft(topicRootId);
|
||||||
history->session().changes().historyUpdated(
|
history->session().changes().entryUpdated(
|
||||||
history,
|
thread,
|
||||||
Data::HistoryUpdate::Flag::LocalDraftSet);
|
Data::EntryUpdate::Flag::LocalDraftSet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWidget::inlineSwitchChosen(
|
bool MainWidget::inlineSwitchChosen(
|
||||||
PeerId peerId,
|
not_null<Data::Thread*> thread,
|
||||||
const QString &botAndQuery) const {
|
const QString &botAndQuery) const {
|
||||||
Expects(peerId != 0);
|
if (!thread->canWrite()) { // #TODO forum forward
|
||||||
|
|
||||||
const auto peer = session().data().peer(peerId);
|
|
||||||
if (!peer->canWrite()) { // #TODO forum forward
|
|
||||||
Ui::show(Ui::MakeInformBox(tr::lng_inline_switch_cant()));
|
Ui::show(Ui::MakeInformBox(tr::lng_inline_switch_cant()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto h = peer->owner().history(peer);
|
|
||||||
const auto textWithTags = TextWithTags{
|
const auto textWithTags = TextWithTags{
|
||||||
botAndQuery,
|
botAndQuery,
|
||||||
TextWithTags::Tags(),
|
TextWithTags::Tags(),
|
||||||
|
@ -592,61 +587,74 @@ bool MainWidget::inlineSwitchChosen(
|
||||||
int(botAndQuery.size()),
|
int(botAndQuery.size()),
|
||||||
QFIXED_MAX
|
QFIXED_MAX
|
||||||
};
|
};
|
||||||
const auto topicRootId = 0;
|
const auto history = thread->owningHistory();
|
||||||
h->setLocalDraft(std::make_unique<Data::Draft>(
|
const auto topicRootId = thread->topicRootId();
|
||||||
|
history->setLocalDraft(std::make_unique<Data::Draft>(
|
||||||
textWithTags,
|
textWithTags,
|
||||||
0, // replyTo
|
0, // replyTo
|
||||||
topicRootId,
|
topicRootId,
|
||||||
cursor,
|
cursor,
|
||||||
Data::PreviewState::Allowed));
|
Data::PreviewState::Allowed));
|
||||||
h->clearLocalEditDraft(topicRootId);
|
history->clearLocalEditDraft(topicRootId);
|
||||||
h->session().changes().historyUpdated(
|
thread->session().changes().entryUpdated(
|
||||||
h,
|
thread,
|
||||||
Data::HistoryUpdate::Flag::LocalDraftSet);
|
Data::EntryUpdate::Flag::LocalDraftSet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWidget::sendPaths(PeerId peerId) {
|
bool MainWidget::sendPaths(not_null<Data::Thread*> thread) {
|
||||||
Expects(peerId != 0);
|
if (!thread->canWrite()) {
|
||||||
|
|
||||||
auto peer = session().data().peer(peerId);
|
|
||||||
if (!peer->canWrite()) { // #TODO forum forward
|
|
||||||
Ui::show(Ui::MakeInformBox(tr::lng_forward_send_files_cant()));
|
Ui::show(Ui::MakeInformBox(tr::lng_forward_send_files_cant()));
|
||||||
return false;
|
return false;
|
||||||
} else if (const auto error = Data::RestrictionError(
|
} else if (const auto error = Data::RestrictionError(
|
||||||
peer,
|
thread->owningHistory()->peer,
|
||||||
ChatRestriction::SendMedia)) {
|
ChatRestriction::SendMedia)) {
|
||||||
Ui::show(Ui::MakeInformBox(*error));
|
Ui::show(Ui::MakeInformBox(*error));
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
controller()->showThread(
|
||||||
|
thread,
|
||||||
|
ShowAtTheEndMsgId,
|
||||||
|
Window::SectionShow::Way::ClearStack);
|
||||||
}
|
}
|
||||||
Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
|
// #TODO forum drop
|
||||||
return _history->confirmSendingFiles(cSendPaths());
|
return (controller()->activeChatCurrent().thread() == thread)
|
||||||
|
&& _history->confirmSendingFiles(cSendPaths());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWidget::onFilesOrForwardDrop(
|
void MainWidget::onFilesOrForwardDrop(
|
||||||
const PeerId &peerId,
|
not_null<Data::Thread*> thread,
|
||||||
const QMimeData *data) {
|
const QMimeData *data) {
|
||||||
Expects(peerId != 0);
|
|
||||||
|
|
||||||
if (data->hasFormat(qsl("application/x-td-forward"))) {
|
if (data->hasFormat(qsl("application/x-td-forward"))) {
|
||||||
auto draft = Data::ForwardDraft{
|
auto draft = Data::ForwardDraft{
|
||||||
.ids = session().data().takeMimeForwardIds(),
|
.ids = session().data().takeMimeForwardIds(),
|
||||||
};
|
};
|
||||||
if (!setForwardDraft(peerId, std::move(draft))) {
|
const auto history = thread->asHistory();
|
||||||
// We've already released the mouse button, so the forwarding is cancelled.
|
if (const auto forum = history ? history->peer->forum() : nullptr) {
|
||||||
if (_hider) {
|
Window::ShowForwardMessagesBox(
|
||||||
_hider->startHide();
|
_controller,
|
||||||
clearHider(_hider);
|
std::move(draft),
|
||||||
}
|
forum);
|
||||||
}
|
} else if (setForwardDraft(thread, std::move(draft))) {
|
||||||
} else {
|
|
||||||
auto peer = session().data().peer(peerId);
|
|
||||||
if (!peer->canWrite()) { // #TODO forum forward
|
|
||||||
Ui::show(Ui::MakeInformBox(tr::lng_forward_send_files_cant()));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
|
// We've already released the mouse button,
|
||||||
_history->confirmSendingFiles(data);
|
// 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) {
|
void MainWidget::showForwardLayer(Data::ForwardDraft &&draft) {
|
||||||
auto callback = [=, draft = std::move(draft)](PeerId peer) mutable {
|
auto callback = [=, draft = std::move(draft)](
|
||||||
return setForwardDraft(peer, std::move(draft));
|
not_null<Data::Thread*> thread) mutable {
|
||||||
|
return setForwardDraft(thread, std::move(draft));
|
||||||
};
|
};
|
||||||
hiderLayer(base::make_unique_q<Window::HistoryHider>(
|
hiderLayer(base::make_unique_q<Window::HistoryHider>(
|
||||||
this,
|
this,
|
||||||
|
@ -749,7 +758,7 @@ void MainWidget::showSendPathsLayer() {
|
||||||
hiderLayer(base::make_unique_q<Window::HistoryHider>(
|
hiderLayer(base::make_unique_q<Window::HistoryHider>(
|
||||||
this,
|
this,
|
||||||
tr::lng_forward_choose(tr::now),
|
tr::lng_forward_choose(tr::now),
|
||||||
[=](PeerId peer) { return sendPaths(peer); },
|
[=](not_null<Data::Thread*> thread) { return sendPaths(thread); },
|
||||||
_controller->adaptive().oneColumnValue()));
|
_controller->adaptive().oneColumnValue()));
|
||||||
if (_hider) {
|
if (_hider) {
|
||||||
connect(_hider, &QObject::destroyed, [] {
|
connect(_hider, &QObject::destroyed, [] {
|
||||||
|
@ -763,8 +772,8 @@ void MainWidget::shareUrlLayer(const QString &url, const QString &text) {
|
||||||
if (url.trimmed().startsWith('@')) {
|
if (url.trimmed().startsWith('@')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto callback = [=](PeerId peer) {
|
auto callback = [=](not_null<Data::Thread*> thread) {
|
||||||
return shareUrl(peer, url, text);
|
return shareUrl(thread, url, text);
|
||||||
};
|
};
|
||||||
hiderLayer(base::make_unique_q<Window::HistoryHider>(
|
hiderLayer(base::make_unique_q<Window::HistoryHider>(
|
||||||
this,
|
this,
|
||||||
|
@ -774,8 +783,8 @@ void MainWidget::shareUrlLayer(const QString &url, const QString &text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWidget::inlineSwitchLayer(const QString &botAndQuery) {
|
void MainWidget::inlineSwitchLayer(const QString &botAndQuery) {
|
||||||
auto callback = [=](PeerId peer) {
|
auto callback = [=](not_null<Data::Thread*> thread) {
|
||||||
return inlineSwitchChosen(peer, botAndQuery);
|
return inlineSwitchChosen(thread, botAndQuery);
|
||||||
};
|
};
|
||||||
hiderLayer(base::make_unique_q<Window::HistoryHider>(
|
hiderLayer(base::make_unique_q<Window::HistoryHider>(
|
||||||
this,
|
this,
|
||||||
|
@ -788,6 +797,14 @@ bool MainWidget::selectingPeer() const {
|
||||||
return _hider ? true : false;
|
return _hider ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWidget::clearSelectingPeer() {
|
||||||
|
if (_hider) {
|
||||||
|
_hider->startHide();
|
||||||
|
_hider.release();
|
||||||
|
controller()->setSelectingPeer(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWidget::sendBotCommand(Bot::SendCommandRequest request) {
|
void MainWidget::sendBotCommand(Bot::SendCommandRequest request) {
|
||||||
const auto type = _mainSection
|
const auto type = _mainSection
|
||||||
? _mainSection->sendBotCommand(request)
|
? _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()) {
|
if (selectingPeer()) {
|
||||||
_hider->offerPeer(peerId);
|
_hider->offerThread(thread);
|
||||||
} else if (peerId) {
|
|
||||||
Ui::showPeerHistory(session().data().peer(peerId), showAtMsgId);
|
|
||||||
} else {
|
} 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) {
|
void MainWidget::clearBotStartToken(PeerData *peer) {
|
||||||
if (peer && peer->isUser() && peer->asUser()->isBot()) {
|
if (peer && peer->isUser() && peer->asUser()->isBot()) {
|
||||||
peer->asUser()->botInfo->startToken = QString();
|
peer->asUser()->botInfo->startToken = QString();
|
||||||
|
@ -1402,11 +1423,6 @@ void MainWidget::ui_showPeerHistory(
|
||||||
if (params.activation != anim::activation::background) {
|
if (params.activation != anim::activation::background) {
|
||||||
controller()->window().hideSettingsAndLayer();
|
controller()->window().hideSettingsAndLayer();
|
||||||
}
|
}
|
||||||
if (_hider) {
|
|
||||||
_hider->startHide();
|
|
||||||
_hider.release();
|
|
||||||
controller()->setSelectingPeer(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto animatedShow = [&] {
|
auto animatedShow = [&] {
|
||||||
if (_a_show.animating()
|
if (_a_show.animating()
|
||||||
|
|
|
@ -42,6 +42,7 @@ class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
class Thread;
|
||||||
class WallPaper;
|
class WallPaper;
|
||||||
struct ForwardDraft;
|
struct ForwardDraft;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
@ -174,10 +175,15 @@ public:
|
||||||
void shareUrlLayer(const QString &url, const QString &text);
|
void shareUrlLayer(const QString &url, const QString &text);
|
||||||
void inlineSwitchLayer(const QString &botAndQuery);
|
void inlineSwitchLayer(const QString &botAndQuery);
|
||||||
void hiderLayer(base::unique_qptr<Window::HistoryHider> h);
|
void hiderLayer(base::unique_qptr<Window::HistoryHider> h);
|
||||||
bool setForwardDraft(PeerId peer, Data::ForwardDraft &&draft);
|
bool setForwardDraft(
|
||||||
bool sendPaths(PeerId peerId);
|
not_null<Data::Thread*> thread,
|
||||||
void onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data);
|
Data::ForwardDraft &&draft);
|
||||||
|
bool sendPaths(not_null<Data::Thread*> thread);
|
||||||
|
void onFilesOrForwardDrop(
|
||||||
|
not_null<Data::Thread*> thread,
|
||||||
|
const QMimeData *data);
|
||||||
bool selectingPeer() const;
|
bool selectingPeer() const;
|
||||||
|
void clearSelectingPeer();
|
||||||
|
|
||||||
void sendBotCommand(Bot::SendCommandRequest request);
|
void sendBotCommand(Bot::SendCommandRequest request);
|
||||||
void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo);
|
void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo);
|
||||||
|
@ -192,8 +198,9 @@ public:
|
||||||
void checkChatBackground();
|
void checkChatBackground();
|
||||||
Image *newBackgroundThumb();
|
Image *newBackgroundThumb();
|
||||||
|
|
||||||
// Does offerPeer or showPeerHistory.
|
// Does offerThread or showThread.
|
||||||
void choosePeer(PeerId peerId, MsgId showAtMsgId);
|
void chooseThread(not_null<Data::Thread*> thread, MsgId showAtMsgId);
|
||||||
|
void chooseThread(not_null<PeerData*> peer, MsgId showAtMsgId);
|
||||||
void clearBotStartToken(PeerData *peer);
|
void clearBotStartToken(PeerData *peer);
|
||||||
|
|
||||||
void ctrlEnterSubmitUpdated();
|
void ctrlEnterSubmitUpdated();
|
||||||
|
@ -251,10 +258,12 @@ private:
|
||||||
-> std::shared_ptr<Window::SectionMemento>;
|
-> std::shared_ptr<Window::SectionMemento>;
|
||||||
|
|
||||||
bool shareUrl(
|
bool shareUrl(
|
||||||
PeerId peerId,
|
not_null<Data::Thread*> thread,
|
||||||
const QString &url,
|
const QString &url,
|
||||||
const QString &text) const;
|
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 setupConnectingWidget();
|
||||||
void createPlayer();
|
void createPlayer();
|
||||||
|
|
|
@ -778,13 +778,14 @@ TimeId CalculateOnlineTill(not_null<PeerData*> peer) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Core::Sandbox::Instance().customEnterFromEventLoop([=] {
|
Core::Sandbox::Instance().customEnterFromEventLoop([=] {
|
||||||
(_hasArchive && (index == (_selfUnpinned ? -2 : -1)))
|
if (_hasArchive && (index == (_selfUnpinned ? -2 : -1))) {
|
||||||
? openFolder()
|
openFolder();
|
||||||
: controller->content()->choosePeer(
|
} else {
|
||||||
(_selfUnpinned && index == -1)
|
const auto chosen = (_selfUnpinned && index == -1)
|
||||||
? _session->userPeerId()
|
? _session->user()
|
||||||
: peer->id,
|
: peer;
|
||||||
ShowAtUnreadMsgId);
|
controller->content()->chooseThread(chosen, ShowAtUnreadMsgId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace Window {
|
||||||
HistoryHider::HistoryHider(
|
HistoryHider::HistoryHider(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
const QString &text,
|
const QString &text,
|
||||||
Fn<bool(PeerId)> confirm,
|
Fn<bool(not_null<Data::Thread*>)> confirm,
|
||||||
rpl::producer<bool> oneColumnValue)
|
rpl::producer<bool> oneColumnValue)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _text(text)
|
, _text(text)
|
||||||
|
@ -122,8 +122,8 @@ void HistoryHider::updateControlsGeometry() {
|
||||||
_box = QRect((width() - w) / 2, (height() - h) / 2, w, h);
|
_box = QRect((width() - w) / 2, (height() - h) / 2, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryHider::offerPeer(PeerId peer) {
|
void HistoryHider::offerThread(not_null<Data::Thread*> thread) {
|
||||||
if (_confirm(peer)) {
|
if (_confirm(thread)) {
|
||||||
startHide();
|
startHide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
class Thread;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class RoundButton;
|
class RoundButton;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
@ -33,10 +37,10 @@ public:
|
||||||
HistoryHider(
|
HistoryHider(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
const QString &text,
|
const QString &text,
|
||||||
Fn<bool(PeerId)> confirm,
|
Fn<bool(not_null<Data::Thread*>)> confirm,
|
||||||
rpl::producer<bool> oneColumnValue);
|
rpl::producer<bool> oneColumnValue);
|
||||||
|
|
||||||
void offerPeer(PeerId peer);
|
void offerThread(not_null<Data::Thread*> thread);
|
||||||
|
|
||||||
void startHide();
|
void startHide();
|
||||||
void confirm();
|
void confirm();
|
||||||
|
@ -57,7 +61,7 @@ private:
|
||||||
void animationCallback();
|
void animationCallback();
|
||||||
|
|
||||||
QString _text;
|
QString _text;
|
||||||
Fn<bool(PeerId)> _confirm;
|
Fn<bool(not_null<Data::Thread*>)> _confirm;
|
||||||
Ui::Animations::Simple _a_opacity;
|
Ui::Animations::Simple _a_opacity;
|
||||||
|
|
||||||
QRect _box;
|
QRect _box;
|
||||||
|
|
|
@ -691,9 +691,8 @@ void MainMenu::setupMenu() {
|
||||||
tr::lng_saved_messages(),
|
tr::lng_saved_messages(),
|
||||||
{ &st::settingsIconSavedMessages, kIconLightBlue }
|
{ &st::settingsIconSavedMessages, kIconLightBlue }
|
||||||
)->setClickedCallback([=] {
|
)->setClickedCallback([=] {
|
||||||
controller->content()->choosePeer(
|
const auto self = controller->session().user();
|
||||||
controller->session().userPeerId(),
|
controller->content()->chooseThread(self, ShowAtUnreadMsgId);
|
||||||
ShowAtUnreadMsgId);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
addAction(
|
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 {
|
class Filler {
|
||||||
public:
|
public:
|
||||||
Filler(
|
Filler(
|
||||||
|
@ -1227,14 +1248,15 @@ void PeerMenuShareContactBox(
|
||||||
not_null<UserData*> user) {
|
not_null<UserData*> user) {
|
||||||
// There is no async to make weak from controller.
|
// There is no async to make weak from controller.
|
||||||
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||||
auto callback = [=](not_null<PeerData*> peer) {
|
auto callback = [=](not_null<Data::Thread*> thread) {
|
||||||
if (!peer->canWrite()) { // #TODO forum forward
|
const auto peer = thread->owningHistory()->peer;
|
||||||
|
if (!thread->canWrite()) {
|
||||||
navigation->parentController()->show(
|
navigation->parentController()->show(
|
||||||
Ui::MakeInformBox(tr::lng_forward_share_cant()),
|
Ui::MakeInformBox(tr::lng_forward_share_cant()),
|
||||||
Ui::LayerOption::KeepOther);
|
Ui::LayerOption::KeepOther);
|
||||||
return;
|
return;
|
||||||
} else if (peer->isSelf()) {
|
} else if (peer->isSelf()) {
|
||||||
auto action = Api::SendAction(peer->owner().history(peer));
|
auto action = Api::SendAction(thread);
|
||||||
action.clearDraft = false;
|
action.clearDraft = false;
|
||||||
user->session().api().shareContact(user, action);
|
user->session().api().shareContact(user, action);
|
||||||
Ui::Toast::Show(
|
Ui::Toast::Show(
|
||||||
|
@ -1245,24 +1267,29 @@ void PeerMenuShareContactBox(
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const auto title = thread->asTopic()
|
||||||
|
? thread->asTopic()->title()
|
||||||
|
: peer->name();
|
||||||
auto recipient = peer->isUser()
|
auto recipient = peer->isUser()
|
||||||
? peer->name()
|
? title
|
||||||
: '\xAB' + peer->name() + '\xBB';
|
: ('\xAB' + title + '\xBB');
|
||||||
|
const auto weak = base::make_weak(thread);
|
||||||
navigation->parentController()->show(
|
navigation->parentController()->show(
|
||||||
Ui::MakeConfirmBox({
|
Ui::MakeConfirmBox({
|
||||||
.text = tr::lng_forward_share_contact(
|
.text = tr::lng_forward_share_contact(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_recipient,
|
lt_recipient,
|
||||||
recipient),
|
recipient),
|
||||||
.confirmed = [peer, user, navigation](Fn<void()> &&close) {
|
.confirmed = [weak, user, navigation](Fn<void()> &&close) {
|
||||||
const auto history = peer->owner().history(peer);
|
if (const auto strong = weak.get()) {
|
||||||
navigation->showPeerHistory(
|
navigation->showThread(
|
||||||
history,
|
strong,
|
||||||
Window::SectionShow::Way::ClearStack,
|
ShowAtTheEndMsgId,
|
||||||
ShowAtTheEndMsgId);
|
Window::SectionShow::Way::ClearStack);
|
||||||
auto action = Api::SendAction(history);
|
auto action = Api::SendAction(strong);
|
||||||
action.clearDraft = false;
|
action.clearDraft = false;
|
||||||
user->session().api().shareContact(user, action);
|
strong->session().api().shareContact(user, action);
|
||||||
|
}
|
||||||
close();
|
close();
|
||||||
},
|
},
|
||||||
.confirmText = tr::lng_forward_send(),
|
.confirmText = tr::lng_forward_send(),
|
||||||
|
@ -1470,32 +1497,19 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
|
||||||
Data::ForwardDraft &&draft,
|
Data::ForwardDraft &&draft,
|
||||||
FnMut<void()> &&successCallback) {
|
FnMut<void()> &&successCallback) {
|
||||||
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||||
auto callback = [
|
auto chosen = [
|
||||||
draft = std::move(draft),
|
draft = std::move(draft),
|
||||||
callback = std::move(successCallback),
|
callback = std::move(successCallback),
|
||||||
weak,
|
weak,
|
||||||
navigation
|
navigation
|
||||||
](not_null<PeerData*> peer) mutable {
|
](not_null<Data::Thread*> thread) mutable {
|
||||||
|
const auto peer = thread->owningHistory()->peer;
|
||||||
const auto content = navigation->parentController()->content();
|
const auto content = navigation->parentController()->content();
|
||||||
if (peer->isSelf()
|
if (peer->isSelf()
|
||||||
&& !draft.ids.empty()
|
&& !draft.ids.empty()
|
||||||
&& draft.ids.front().peer != peer->id) {
|
&& draft.ids.front().peer != peer->id) {
|
||||||
const auto history = peer->owner().history(peer);
|
ForwardToSelf(navigation, draft);
|
||||||
auto resolved = history->resolveForwardDraft(draft);
|
} else if (!content->setForwardDraft(thread, std::move(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))) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (const auto strong = *weak) {
|
if (const auto strong = *weak) {
|
||||||
|
@ -1513,7 +1527,7 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
|
||||||
*weak = navigation->parentController()->show(Box<PeerListBox>(
|
*weak = navigation->parentController()->show(Box<PeerListBox>(
|
||||||
std::make_unique<ChooseRecipientBoxController>(
|
std::make_unique<ChooseRecipientBoxController>(
|
||||||
&navigation->session(),
|
&navigation->session(),
|
||||||
std::move(callback)),
|
std::move(chosen)),
|
||||||
std::move(initBox)), Ui::LayerOption::KeepOther);
|
std::move(initBox)), Ui::LayerOption::KeepOther);
|
||||||
return weak->data();
|
return weak->data();
|
||||||
}
|
}
|
||||||
|
@ -1528,6 +1542,50 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
|
||||||
std::move(successCallback));
|
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(
|
QPointer<Ui::BoxContent> ShowSendNowMessagesBox(
|
||||||
not_null<Window::SessionNavigation*> navigation,
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
|
|
|
@ -21,6 +21,7 @@ class GenericBox;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
class Forum;
|
||||||
class Folder;
|
class Folder;
|
||||||
class Session;
|
class Session;
|
||||||
struct ForwardDraft;
|
struct ForwardDraft;
|
||||||
|
@ -121,6 +122,11 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
|
||||||
not_null<Window::SessionNavigation*> navigation,
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
MessageIdsList &&items,
|
MessageIdsList &&items,
|
||||||
FnMut<void()> &&successCallback = nullptr);
|
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(
|
QPointer<Ui::BoxContent> ShowSendNowMessagesBox(
|
||||||
not_null<Window::SessionNavigation*> navigation,
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
|
|
|
@ -622,15 +622,29 @@ void SessionNavigation::showPeerInfo(
|
||||||
|
|
||||||
void SessionNavigation::showTopic(
|
void SessionNavigation::showTopic(
|
||||||
not_null<Data::ForumTopic*> topic,
|
not_null<Data::ForumTopic*> topic,
|
||||||
MsgId commentId,
|
MsgId itemId,
|
||||||
const SectionShow ¶ms) {
|
const SectionShow ¶ms) {
|
||||||
return showRepliesForMessage(
|
return showRepliesForMessage(
|
||||||
topic->history(),
|
topic->history(),
|
||||||
topic->rootId(),
|
topic->rootId(),
|
||||||
commentId,
|
itemId,
|
||||||
params);
|
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(
|
void SessionNavigation::showPeerInfo(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const SectionShow ¶ms) {
|
const SectionShow ¶ms) {
|
||||||
|
|
|
@ -216,7 +216,11 @@ public:
|
||||||
const SectionShow ¶ms = SectionShow());
|
const SectionShow ¶ms = SectionShow());
|
||||||
void showTopic(
|
void showTopic(
|
||||||
not_null<Data::ForumTopic*> topic,
|
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());
|
const SectionShow ¶ms = SectionShow());
|
||||||
|
|
||||||
void showPeerInfo(
|
void showPeerInfo(
|
||||||
|
|
Loading…
Add table
Reference in a new issue