Implement drag-n-drop to forum topics.

This commit is contained in:
John Preston 2022-11-01 11:18:56 +04:00
parent 6d215d3729
commit d5ea0149e8
23 changed files with 232 additions and 133 deletions

View file

@ -3200,7 +3200,7 @@ void ApiWrap::forwardMessages(
messageFromId,
messagePostAuthor,
item,
action.topicRootId); // #TODO forum forward
action.topicRootId);
_session->data().registerMessageRandomId(randomId, newId);
if (!localIds) {
localIds = std::make_shared<base::flat_map<uint64, FullMsgId>>();

View file

@ -497,20 +497,24 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
(*weak)->closeBox();
}
};
const auto filter = [=](not_null<Data::ForumTopic*> topic) {
return guard && (!_filter || _filter(topic));
};
auto owned = Box<PeerListBox>(
std::make_unique<ChooseTopicBoxController>(
forum,
std::move(callback)),
std::move(callback),
filter),
[=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
forum->destroyed(
) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
});
forum->destroyed(
) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
});
*weak = owned.data();
delegate()->peerListShowBox(std::move(owned));
return;
@ -604,6 +608,49 @@ bool ChooseTopicSearchController::loadMoreRows() {
return !_allLoaded;
}
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();
}
ChooseTopicBoxController::ChooseTopicBoxController(
not_null<Data::Forum*> forum,
FnMut<void(not_null<Data::ForumTopic*>)> callback,
@ -656,8 +703,10 @@ void ChooseTopicBoxController::refreshRows(bool initial) {
const auto id = topic->rootId().bare;
auto already = delegate()->peerListFindRow(id);
if (initial || !already) {
delegate()->peerListAppendRow(createRow(topic));
added = true;
if (auto created = createRow(topic)) {
delegate()->peerListAppendRow(std::move(created));
added = true;
}
} else if (already->isSearchResult()) {
delegate()->peerListAppendFoundRow(already);
added = true;
@ -681,49 +730,6 @@ std::unique_ptr<PeerListRow> ChooseTopicBoxController::createSearchRow(
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();

View file

@ -1204,7 +1204,7 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
auto object = Box<ShareBox>(ShareBox::Descriptor{
.session = &peer->session(),
.copyCallback = std::move(copyCallback),
.submitCallback = std::move(submitCallback), // #TODO forum forward
.submitCallback = std::move(submitCallback), // #TODO forum share
.filterCallback = [](auto peer) { return peer->canWrite(); },
});
*box = Ui::MakeWeak(object.data());

View file

@ -1292,7 +1292,7 @@ void FastShareMessage(
}
const auto error = [&] {
for (const auto peer : result) { // #TODO forum forward
for (const auto peer : result) { // #TODO forum share
const auto error = GetErrorTextForSending(
peer,
{ .forward = &items, .text = &comment });
@ -1398,7 +1398,7 @@ void FastShareMessage(
}
};
auto filterCallback = [isGame](PeerData *peer) {
if (peer->canWrite()) { // #TODO forum forward
if (peer->canWrite()) { // #TODO forum share
if (auto channel = peer->asChannel()) {
return isGame ? (!channel->isBroadcast()) : true;
}

View file

@ -142,7 +142,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
}
const auto error = [&] {
for (const auto peer : result) { // #TODO forum forward
for (const auto peer : result) { // #TODO forum share
const auto error = GetErrorTextForSending(
peer,
{ .text = &comment });
@ -196,7 +196,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
showToast(tr::lng_share_done(tr::now));
};
auto filterCallback = [](PeerData *peer) {
return peer->canWrite(); // #TODO forum forward
return peer->canWrite(); // #TODO forum share
};
const auto scheduleStyle = [&] {

View file

@ -7,7 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "core/mime_type.h"
#include "core/utils.h"
#include <QtCore/QMimeDatabase>
#include <QtCore/QMimeData>
namespace Core {
@ -152,4 +155,19 @@ bool FileIsImage(const QString &name, const QString &mime) {
return false;
}
std::shared_ptr<QMimeData> ShareMimeMediaData(
not_null<const QMimeData*> original) {
auto result = std::make_shared<QMimeData>();
if (original->hasFormat(u"application/x-td-forward"_q)) {
result->setData(u"application/x-td-forward"_q, "1");
}
if (original->hasImage()) {
result->setImageData(original->imageData());
}
if (auto list = base::GetMimeUrls(original); !list.isEmpty()) {
result->setUrls(std::move(list));
}
return result;
}
} // namespace Core

View file

@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QStringList>
#include <QtCore/QMimeType>
class QMimeData;
namespace Core {
class MimeType {
@ -47,4 +49,7 @@ private:
[[nodiscard]] bool FileIsImage(const QString &name, const QString &mime);
[[nodiscard]] std::shared_ptr<QMimeData> ShareMimeMediaData(
not_null<const QMimeData*> original);
} // namespace Core

View file

@ -449,8 +449,8 @@ editTopicMaxHeight: 408px;
chooseTopicListItem: PeerListItem(defaultPeerListItem) {
height: 44px;
photoSize: 28px;
photoPosition: point(16px, 5px);
namePosition: point(71px, 11px);
photoPosition: point(8px, 5px);
namePosition: point(55px, 11px);
nameStyle: TextStyle(defaultTextStyle) {
font: font(14px semibold);
linkFont: font(14px semibold);

View file

@ -1437,7 +1437,7 @@ ClickHandlerPtr goToMessageClickHandler(
returnToId
};
if (const auto item = peer->owner().message(peer, msgId)) {
controller->showMessage(item);
controller->showMessage(item, params);
} else {
controller->showPeerHistory(peer, params, msgId);
}

View file

@ -150,8 +150,6 @@ public:
bool confirmSendingFiles(const QStringList &files);
bool confirmSendingFiles(not_null<const QMimeData*> data);
void sendFileConfirmed(const std::shared_ptr<FileLoadResult> &file,
const std::optional<FullMsgId> &oldId = std::nullopt);
void updateControlsVisibility();
void updateControlsGeometry();

View file

@ -341,7 +341,8 @@ void CornerButtons::finishAnimations() {
Fn<void(bool found)> CornerButtons::doneJumpFrom(
FullMsgId targetId,
FullMsgId originId) {
FullMsgId originId,
bool ignoreMessageNotFound) {
return [=](bool found) {
skipReplyReturn(targetId);
if (originId) {
@ -351,7 +352,7 @@ Fn<void(bool found)> CornerButtons::doneJumpFrom(
}
}
}
if (!found) {
if (!found && !ignoreMessageNotFound) {
Ui::Toast::Show(
_scroll.get(),
tr::lng_message_not_found(tr::now));

View file

@ -87,7 +87,8 @@ public:
}
[[nodiscard]] Fn<void(bool found)> doneJumpFrom(
FullMsgId targetId,
FullMsgId originId);
FullMsgId originId,
bool ignoreMessageNotFound = false);
private:
bool eventFilter(QObject *o, QEvent *e) override;

View file

@ -687,7 +687,7 @@ bool ListWidget::showAtPositionNow(
}
if (done) {
const auto found = !position.fullId.peer
|| !position.fullId.msg
|| !IsServerMsgId(position.fullId.msg)
|| viewForItem(position.fullId);
done(found);
}

View file

@ -1796,10 +1796,11 @@ void RepliesWidget::showAtPosition(
anim::type animated) {
_lastShownAt = position.fullId;
controller()->setActiveChatEntry(activeChat());
const auto ignore = (position.fullId.msg == _rootId);
_inner->showAtPosition(
position,
animated,
_cornerButtons.doneJumpFrom(position.fullId, originItemId));
_cornerButtons.doneJumpFrom(position.fullId, originItemId, ignore));
}
void RepliesWidget::updateAdaptiveLayout() {
@ -1962,6 +1963,23 @@ Window::SectionActionResult RepliesWidget::sendBotCommand(
return Window::SectionActionResult::Handle;
}
bool RepliesWidget::confirmSendingFiles(const QStringList &files) {
return confirmSendingFiles(files, QString());
}
bool RepliesWidget::confirmSendingFiles(not_null<const QMimeData*> data) {
return confirmSendingFiles(data, std::nullopt);
}
bool RepliesWidget::confirmSendingFiles(
const QStringList &files,
const QString &insertTextOnCancel) {
const auto premium = controller()->session().user()->isPremium();
return confirmSendingFiles(
Storage::PrepareMediaList(files, st::sendMediaPreviewSize, premium),
insertTextOnCancel);
}
void RepliesWidget::replyToMessage(FullMsgId itemId) {
_composeControls->replyToMessage(itemId);
refreshTopBarActiveChat();

View file

@ -104,6 +104,9 @@ public:
Window::SectionActionResult sendBotCommand(
Bot::SendCommandRequest request) override;
bool confirmSendingFiles(const QStringList &files) override;
bool confirmSendingFiles(not_null<const QMimeData*> data) override;
void setInternalState(
const QRect &geometry,
not_null<RepliesMemento*> memento);
@ -261,12 +264,15 @@ private:
QByteArray &&content,
std::optional<bool> overrideSendImagesAsPhotos = std::nullopt,
const QString &insertTextOnCancel = QString());
bool confirmSendingFiles(
const QStringList &files,
const QString &insertTextOnCancel);
bool confirmSendingFiles(
Ui::PreparedList &&list,
const QString &insertTextOnCancel = QString());
bool confirmSendingFiles(
not_null<const QMimeData*> data,
std::optional<bool> overrideSendImagesAsPhotos = std::nullopt,
std::optional<bool> overrideSendImagesAsPhotos,
const QString &insertTextOnCancel = QString());
bool showSendingFilesError(const Ui::PreparedList &list) const;
void sendingFilesConfirmed(

View file

@ -92,6 +92,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/shortcuts.h"
#include "core/application.h"
#include "core/changelogs.h"
#include "core/mime_type.h"
#include "base/unixtime.h"
#include "calls/calls_call.h"
#include "calls/calls_instance.h"
@ -323,7 +324,6 @@ MainWidget::MainWidget(
Data::EntryUpdate::Flag::LocalDraftSet
) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
controller->showThread(update.entry->asThread(), ShowAtUnreadMsgId);
_history->applyDraft(); // #TODO forum drop
controller->hideLayer();
}, lifetime());
@ -574,7 +574,7 @@ bool MainWidget::shareUrl(
bool MainWidget::inlineSwitchChosen(
not_null<Data::Thread*> thread,
const QString &botAndQuery) const {
if (!thread->canWrite()) { // #TODO forum forward
if (!thread->canWrite()) {
Ui::show(Ui::MakeInformBox(tr::lng_inline_switch_cant()));
return false;
}
@ -617,26 +617,33 @@ bool MainWidget::sendPaths(not_null<Data::Thread*> thread) {
ShowAtTheEndMsgId,
Window::SectionShow::Way::ClearStack);
}
// #TODO forum drop
return (controller()->activeChatCurrent().thread() == thread)
&& _history->confirmSendingFiles(cSendPaths());
&& (_mainSection
? _mainSection->confirmSendingFiles(cSendPaths())
: _history->confirmSendingFiles(cSendPaths()));
}
void MainWidget::onFilesOrForwardDrop(
bool MainWidget::onFilesOrForwardDrop(
not_null<Data::Thread*> thread,
const QMimeData *data) {
not_null<const QMimeData*> data) {
const auto history = thread->asHistory();
if (const auto forum = history ? history->peer->forum() : nullptr) {
Window::ShowDropMediaBox(
_controller,
Core::ShareMimeMediaData(data),
forum);
if (_hider) {
_hider->startHide();
clearHider(_hider);
}
return true;
}
if (data->hasFormat(qsl("application/x-td-forward"))) {
auto draft = Data::ForwardDraft{
.ids = session().data().takeMimeForwardIds(),
};
const auto history = thread->asHistory();
if (const auto forum = history ? history->peer->forum() : nullptr) {
Window::ShowForwardMessagesBox(
_controller,
std::move(draft),
forum);
} else if (setForwardDraft(thread, std::move(draft))) {
return;
if (setForwardDraft(thread, std::move(draft))) {
return true;
}
// We've already released the mouse button,
// so the forwarding is cancelled.
@ -644,17 +651,22 @@ void MainWidget::onFilesOrForwardDrop(
_hider->startHide();
clearHider(_hider);
}
return false;
} else if (!thread->canWrite()) {
Ui::show(Ui::MakeInformBox(tr::lng_forward_send_files_cant()));
return false;
} else {
controller()->showThread(
thread,
ShowAtTheEndMsgId,
Window::SectionShow::Way::ClearStack);
if (thread->asHistory()) {
// #TODO forum drop
_history->confirmSendingFiles(data);
if (controller()->activeChatCurrent().thread() != thread) {
return false;
}
(_mainSection
? _mainSection->confirmSendingFiles(data)
: _history->confirmSendingFiles(data));
return true;
}
}
@ -1264,7 +1276,10 @@ void MainWidget::chooseThread(
if (selectingPeer()) {
_hider->offerThread(thread);
} else {
controller()->showThread(thread, showAtMsgId);
controller()->showThread(
thread,
showAtMsgId,
Window::SectionShow::Way::ClearStack);
}
}
@ -1522,6 +1537,31 @@ void MainWidget::ui_showPeerHistory(
floatPlayerCheckVisibility();
}
void MainWidget::showMessage(
not_null<const HistoryItem*> item,
const SectionShow &params) {
const auto peerId = item->history()->peer->id;
const auto itemId = item->id;
if (!v::is_null(params.origin)) {
if (_mainSection) {
if (_mainSection->showMessage(peerId, params, itemId)) {
return;
}
} else if (_history->peer() == item->history()->peer) {
ui_showPeerHistory(peerId, params, itemId);
return;
}
}
if (const auto topic = item->topic()) {
_controller->showTopic(topic, item->id, params);
} else {
_controller->showPeerHistory(
item->history(),
params,
item->id);
}
}
PeerData *MainWidget::peer() const {
return _history->peer();
}
@ -2710,31 +2750,31 @@ bool MainWidget::contentOverlapped(const QRect &globalRect) {
void MainWidget::activate() {
if (_a_show.animating()) {
return;
} else if (!_mainSection) {
if (_hider) {
} else if (!cSendPaths().isEmpty()) {
const auto interpret = qstr("interpret://");
const auto path = cSendPaths()[0];
if (path.startsWith(interpret)) {
cSetSendPaths(QStringList());
const auto error = Support::InterpretSendPath(
_controller,
path.mid(interpret.size()));
if (!error.isEmpty()) {
Ui::show(Ui::MakeInformBox(error));
}
} else {
showSendPathsLayer();
}
} else if (_mainSection) {
_mainSection->setInnerFocus();
} else if (_hider) {
Assert(_dialogs != nullptr);
_dialogs->setInnerFocus();
} else if (!Ui::isLayerShown()) {
if (_history->peer()) {
_history->activate();
} else {
Assert(_dialogs != nullptr);
_dialogs->setInnerFocus();
} else if (!Ui::isLayerShown()) {
if (!cSendPaths().isEmpty()) {
const auto interpret = qstr("interpret://");
const auto path = cSendPaths()[0];
if (path.startsWith(interpret)) {
cSetSendPaths(QStringList());
const auto error = Support::InterpretSendPath(
_controller,
path.mid(interpret.size()));
if (!error.isEmpty()) {
Ui::show(Ui::MakeInformBox(error));
}
} else {
showSendPathsLayer();
}
} else if (_history->peer()) {
_history->activate();
} else {
Assert(_dialogs != nullptr);
_dialogs->setInnerFocus();
}
}
}
_controller->widget()->fixOrder();

View file

@ -179,9 +179,9 @@ public:
not_null<Data::Thread*> thread,
Data::ForwardDraft &&draft);
bool sendPaths(not_null<Data::Thread*> thread);
void onFilesOrForwardDrop(
bool onFilesOrForwardDrop(
not_null<Data::Thread*> thread,
const QMimeData *data);
not_null<const QMimeData*> data);
bool selectingPeer() const;
void clearSelectingPeer();
@ -222,6 +222,9 @@ public:
PeerId peer,
const SectionShow &params,
MsgId msgId);
void showMessage(
not_null<const HistoryItem*> item,
const SectionShow &params);
bool notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo);

View file

@ -151,6 +151,13 @@ public:
return SectionActionResult::Ignore;
}
virtual bool confirmSendingFiles(const QStringList &files) {
return false;
}
virtual bool confirmSendingFiles(not_null<const QMimeData*> data) {
return false;
}
// Create a memento of that section to store it in the history stack.
// This method may modify the section ("take" heavy items).
virtual std::shared_ptr<SectionMemento> createMemento();

View file

@ -1542,20 +1542,20 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
std::move(successCallback));
}
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
QPointer<Ui::BoxContent> ShowDropMediaBox(
not_null<Window::SessionNavigation*> navigation,
Data::ForwardDraft &&draft,
std::shared_ptr<QMimeData> data,
not_null<Data::Forum*> forum,
FnMut<void()> &&successCallback) {
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
auto chosen = [
draft = std::move(draft),
data = std::move(data),
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))) {
if (!content->onFilesOrForwardDrop(topic, data.get())) {
return;
} else if (const auto strong = *weak) {
strong->closeBox();

View file

@ -122,9 +122,9 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
not_null<Window::SessionNavigation*> navigation,
MessageIdsList &&items,
FnMut<void()> &&successCallback = nullptr);
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
QPointer<Ui::BoxContent> ShowDropMediaBox(
not_null<Window::SessionNavigation*> navigation,
Data::ForwardDraft &&draft,
std::shared_ptr<QMimeData> data,
not_null<Data::Forum*> forum,
FnMut<void()> &&successCallback = nullptr);

View file

@ -1670,26 +1670,19 @@ void SessionController::showPeerHistory(
}
void SessionController::showMessage(
not_null<const HistoryItem*> item) {
not_null<const HistoryItem*> item,
const SectionShow &params) {
_window->invokeForSessionController(
&item->history()->peer->session().account(),
&item->history()->session().account(),
item->history()->peer,
[&](not_null<SessionController*> controller) {
if (item->isScheduled()) {
controller->showSection(
std::make_shared<HistoryView::ScheduledMemento>(
item->history()),
SectionShow::Way::ClearStack);
} else if (const auto topic = item->topic()) {
controller->showTopic(
topic,
item->id,
SectionShow::Way::ClearStack);
params);
} else {
controller->showPeerHistory(
item->history()->peer,
SectionShow::Way::ClearStack,
item->id);
controller->content()->showMessage(item, params);
}
});
}

View file

@ -431,7 +431,9 @@ public:
const SectionShow &params = SectionShow::Way::ClearStack,
MsgId msgId = ShowAtUnreadMsgId) override;
void showMessage(not_null<const HistoryItem*> item);
void showMessage(
not_null<const HistoryItem*> item,
const SectionShow &params = SectionShow::Way::ClearStack);
void cancelUploadLayer(not_null<HistoryItem*> item);
void showLayer(

View file

@ -294,4 +294,5 @@ PRIVATE
desktop-app::lib_webview
desktop-app::lib_webrtc
desktop-app::lib_stripe
desktop-app::external_kcoreaddons
)