diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 51aaf3060..d2f8f03c9 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -7,6 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/window_peer_menu.h" +#include "menu/menu_check_item.h" +#include "boxes/share_box.h" +#include "chat_helpers/message_field.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/widgets/input_fields.h" #include "api/api_chat_participants.h" #include "lang/lang_keys.h" #include "ui/boxes/confirm_box.h" @@ -1604,30 +1609,299 @@ QPointer ShowChooseRecipientBox( QPointer ShowForwardMessagesBox( not_null navigation, Data::ForwardDraft &&draft, - FnMut &&successCallback) { - auto chosen = [navigation, draft = std::move(draft)]( - not_null thread) mutable { - const auto content = navigation->parentController()->content(); - const auto peer = thread->peer(); - if (peer->isSelf() - && !draft.ids.empty() - && draft.ids.front().peer != peer->id) { - ForwardToSelf(navigation, draft); - return true; + Fn &&successCallback) { + const auto msgIds = draft.ids; + + class ListBox final : public PeerListBox { + public: + using PeerListBox::PeerListBox; + + void setBottomSkip(int bottomSkip) { + PeerListBox::setInnerBottomSkip(bottomSkip); } - return content->setForwardDraft(thread, std::move(draft)); + + [[nodiscard]] rpl::producer<> focusRequests() const { + return _focusRequests.events(); + } + + [[nodiscard]] Data::ForwardOptions forwardOptionsData() const { + return (_forwardOptions.hasCaptions + && _forwardOptions.dropCaptions) + ? Data::ForwardOptions::NoNamesAndCaptions + : _forwardOptions.dropNames + ? Data::ForwardOptions::NoSenderNames + : Data::ForwardOptions::PreserveInfo; + } + [[nodiscard]] Ui::ForwardOptions forwardOptions() const { + return _forwardOptions; + } + void setForwardOptions(Ui::ForwardOptions forwardOptions) { + _forwardOptions = forwardOptions; + } + + private: + rpl::event_stream<> _focusRequests; + Ui::ForwardOptions _forwardOptions; + }; - return ShowChooseRecipientBox( - navigation, - std::move(chosen), - nullptr, - std::move(successCallback)); + + class Controller final : public ChooseRecipientBoxController { + public: + using Chosen = not_null; + + Controller(not_null session) + : ChooseRecipientBoxController( + session, + [=](Chosen thread) mutable { _singleChosen.fire_copy(thread); }, + nullptr) { + } + + void rowClicked(not_null row) override final { + const auto count = delegate()->peerListSelectedRowsCount(); + if (count && row->peer()->isForum()) { + return; + } else if (!count || row->peer()->isForum()) { + ChooseRecipientBoxController::rowClicked(row); + } else if (count) { + delegate()->peerListSetRowChecked(row, !row->checked()); + _hasSelectedChanges.fire( + delegate()->peerListSelectedRowsCount() > 0); + } + } + + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override final { + const auto count = delegate()->peerListSelectedRowsCount(); + if (!count && !row->peer()->isForum()) { + auto menu = base::make_unique_q( + parent, + st::popupMenuWithIcons); + menu->addAction(tr::lng_bot_choose_chat(tr::now), [=] { + delegate()->peerListSetRowChecked(row, !row->checked()); + _hasSelectedChanges.fire( + delegate()->peerListSelectedRowsCount() > 0); + }, &st::menuIconSelect); + return menu; + } + return nullptr; + } + + [[nodiscard]] rpl::producer hasSelectedChanges() const{ + return _hasSelectedChanges.events_starting_with(false); + } + + [[nodiscard]] rpl::producer singleChosen() const{ + return _singleChosen.events(); + } + + private: + rpl::event_stream _singleChosen; + rpl::event_stream _hasSelectedChanges; + + }; + + const auto session = &navigation->session(); + struct State { + not_null box; + not_null controller; + base::unique_qptr menu; + }; + const auto state = [&] { + auto controller = std::make_unique(session); + const auto controllerRaw = controller.get(); + auto box = Box(std::move(controller), nullptr); + const auto boxRaw = box.data(); + navigation->parentController()->show( + std::move(box), + Ui::LayerOption::KeepOther); + auto state = State{ boxRaw, controllerRaw }; + return boxRaw->lifetime().make_state(std::move(state)); + }(); + + { // Chosen a single. + auto chosen = [navigation, draft = std::move(draft)]( + not_null thread) mutable { + const auto content = navigation->parentController()->content(); + const auto peer = thread->peer(); + if (peer->isSelf() + && !draft.ids.empty() + && draft.ids.front().peer != peer->id) { + ForwardToSelf(navigation, draft); + return true; + } + return content->setForwardDraft(thread, std::move(draft)); + }; + auto callback = [=, chosen = std::move(chosen)]( + Controller::Chosen thread) mutable { + const auto weak = Ui::MakeWeak(state->box); + if (!chosen(thread)) { + return; + } else if (const auto strong = weak.get()) { + strong->closeBox(); + } + if (successCallback) { + successCallback(); + } + }; + state->controller->singleChosen( + ) | rpl::start_with_next(std::move(callback), state->box->lifetime()); + } + + const auto comment = Ui::CreateChild>( + state->box.get(), + object_ptr( + state->box, + st::shareComment, + Ui::InputField::Mode::MultiLine, + tr::lng_photos_comment()), + st::shareCommentPadding); + + const auto send = ShareBox::DefaultForwardCallback( + std::make_shared(navigation), + session->data().message(msgIds.front())->history(), + msgIds); + + const auto submit = [=](Api::SendOptions options) { + const auto peers = state->box->collectSelectedRows(); + send( + ranges::views::all( + peers + ) | ranges::views::transform([&]( + not_null peer) -> Controller::Chosen { + return peer->owner().history(peer); + }) | ranges::to_vector, + comment->entity()->getTextWithAppliedMarkdown(), + options, + state->box->forwardOptionsData()); + if (successCallback) { + successCallback(); + } + }; + + const auto sendMenuType = [=] { + const auto selected = state->box->collectSelectedRows(); + return ranges::all_of(selected, HistoryView::CanScheduleUntilOnline) + ? SendMenu::Type::ScheduledToUser + : ((selected.size() == 1) && selected.front()->isSelf()) + ? SendMenu::Type::Reminder + : SendMenu::Type::Scheduled; + }; + + const auto showForwardOptions = true; + const auto showMenu = [=](not_null parent) { + if (state->menu) { + state->menu = nullptr; + return; + } + state->menu.emplace(parent, st::popupMenuWithIcons); + + if (showForwardOptions) { + auto createView = [&]( + rpl::producer &&text, + bool checked) { + auto item = base::make_unique_q( + state->menu->menu(), + st::popupMenuWithIcons.menu, + Ui::CreateChild(state->menu->menu().get()), + nullptr, + nullptr); + std::move( + text + ) | rpl::start_with_next([action = item->action()]( + QString text) { + action->setText(text); + }, item->lifetime()); + item->init(checked); + const auto view = item->checkView(); + state->menu->addAction(std::move(item)); + return view; + }; + Ui::FillForwardOptions( + std::move(createView), + msgIds.size(), + state->box->forwardOptions(), + [=](Ui::ForwardOptions o) { + state->box->setForwardOptions(o); + }, + state->menu->lifetime()); + + state->menu->addSeparator(); + } + const auto type = sendMenuType(); + const auto result = SendMenu::FillSendMenu( + state->menu.get(), + type, + SendMenu::DefaultSilentCallback(submit), + SendMenu::DefaultScheduleCallback(state->box, type, submit)); + const auto success = (result == SendMenu::FillMenuResult::Success); + if (showForwardOptions || success) { + state->menu->setForcedVerticalOrigin( + Ui::PopupMenu::VerticalOrigin::Bottom); + state->menu->popup(QCursor::pos()); + } + }; + + comment->hide(anim::type::instant); + comment->toggleOn(state->controller->hasSelectedChanges()); + + rpl::combine( + state->box->sizeValue(), + comment->heightValue() + ) | rpl::start_with_next([=](const QSize &size, int commentHeight) { + comment->moveToLeft(0, size.height() - commentHeight); + comment->resizeToWidth(size.width()); + + state->box->setBottomSkip(comment->isHidden() ? 0 : commentHeight); + }, comment->lifetime()); + + const auto field = comment->entity(); + + QObject::connect(field, &Ui::InputField::submitted, [=] { + submit({}); + }); + const auto show = std::make_shared(state->box); + if (show->valid()) { + InitMessageFieldHandlers(session, show, field, nullptr); + } + field->setSubmitSettings(Core::App().settings().sendSubmitWay()); + + Ui::SendPendingMoveResizeEvents(comment); + + state->box->focusRequests( + ) | rpl::start_with_next([=] { + if (!comment->isHidden()) { + comment->entity()->setFocusFast(); + } + }, comment->lifetime()); + + state->controller->hasSelectedChanges( + ) | rpl::start_with_next([=](bool shown) { + state->box->clearButtons(); + if (shown) { + const auto send = state->box->addButton( + tr::lng_send_button(), + [=] { submit({}); }); + send->setAcceptBoth(); + send->clicks( + ) | rpl::start_with_next([=](Qt::MouseButton button) { + if (button == Qt::RightButton) { + showMenu(send); + } + }, send->lifetime()); + } + state->box->addButton(tr::lng_cancel(), [=] { + state->box->closeBox(); + }); + }, state->box->lifetime()); + + return QPointer(state->box); } QPointer ShowForwardMessagesBox( not_null navigation, MessageIdsList &&items, - FnMut &&successCallback) { + Fn &&successCallback) { return ShowForwardMessagesBox( navigation, Data::ForwardDraft{ .ids = std::move(items) }, diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index 1fa4b0fcb..63af72e5f 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -122,11 +122,11 @@ QPointer ShowChooseRecipientBox( QPointer ShowForwardMessagesBox( not_null navigation, Data::ForwardDraft &&draft, - FnMut &&successCallback = nullptr); + Fn &&successCallback = nullptr); QPointer ShowForwardMessagesBox( not_null navigation, MessageIdsList &&items, - FnMut &&successCallback = nullptr); + Fn &&successCallback = nullptr); QPointer ShowShareUrlBox( not_null navigation, const QString &url,