[poll-create] Added poll answer link entry and preview.

This commit is contained in:
23rd
2026-05-22 17:14:00 +03:00
committed by John Preston
parent 4887aa6537
commit b6ccc35ad7
4 changed files with 185 additions and 9 deletions
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/create_poll_box.h"
#include "poll/poll_link_thumbnail.h"
#include "poll/poll_media_upload.h"
#include "base/call_delayed.h"
#include "base/qt/qt_key_modifiers.h"
@@ -36,7 +37,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_web_page.h"
#include "data/stickers/data_custom_emoji.h"
#include "history/view/controls/history_view_webpage_processor.h"
#include "history/view/media/menu/history_view_poll_menu.h"
#include "history/view/history_view_schedule_box.h"
#include "info/channel_statistics/boosts/giveaway/select_countries_box.h"
@@ -75,6 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/labels.h"
#include "ui/boxes/choose_date_time.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
@@ -340,6 +344,51 @@ void FocusAtEnd(not_null<Ui::InputField*> field) {
field->ensureCursorVisible();
}
void AddPollOptionLinkBox(
not_null<Ui::GenericBox*> box,
const QString &initial,
Fn<void(QString)> callback) {
box->setTitle(tr::lng_polls_create_option_link_title());
const auto content = box->verticalLayout();
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_polls_create_option_link_description(),
st::defaultFlatLabel),
st::boxRowPadding);
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto field = content->add(
object_ptr<Ui::InputField>(
content,
st::createPollLinkUrlField,
Ui::InputField::Mode::SingleLine,
tr::lng_polls_create_option_link_placeholder(),
initial),
st::boxRowPadding);
const auto submit = [=] {
const auto url = field->getLastText().trimmed();
if (url.isEmpty()) {
field->showError();
return;
}
const auto weak = base::make_weak(box);
callback(url);
if (weak) {
box->closeBox();
}
};
field->submits(
) | rpl::on_next(submit, field->lifetime());
box->addButton(tr::lng_box_done(), submit);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
box->setFocusCallback([=] { field->setFocusFast(); });
}
[[nodiscard]] QStringList ParsePastedList(const QString &text) {
auto list = QStringView(text).split('\n');
for (auto i = list.begin(); i != list.end();) {
@@ -1570,6 +1619,9 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
= std::make_shared<PollMediaState>();
std::shared_ptr<PollMediaState> solutionMedia
= std::make_shared<PollMediaState>();
std::shared_ptr<HistoryView::Controls::WebpageResolver>
webpageResolver;
base::flat_map<PollMediaState*, rpl::lifetime> webPageLifetimes;
std::weak_ptr<PollMediaState> stickerTarget;
base::flat_map<FullMsgId, UploadContext> uploads;
base::unique_qptr<Ui::PopupMenu> mediaMenu;
@@ -1582,6 +1634,8 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
};
const auto state = lifetime().make_state<State>();
state->prepareQueue = std::make_unique<TaskQueue>();
state->webpageResolver = std::make_shared<
HistoryView::Controls::WebpageResolver>(&_controller->session());
auto result = object_ptr<Ui::VerticalLayout>(this);
const auto container = result.data();
@@ -2461,6 +2515,89 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
FileDialog::AllFilesFilter(),
callback);
};
const auto applyResolvedWebPage = [=](
std::shared_ptr<PollMediaState> media,
not_null<WebPageData*> page) {
auto pollMedia = PollMedia();
pollMedia.webpage = page;
pollMedia.url = page->url.isEmpty() ? media->media.url : page->url;
auto thumbnail = page->photo
? Ui::MakePhotoThumbnailCenterCrop(page->photo, FullMsgId())
: Poll::MakeLinkThumbnail();
const auto rounded = (page->photo != nullptr);
setMedia(media, pollMedia, std::move(thumbnail), rounded);
};
const auto subscribeToWebPageUpdates = [=](
std::shared_ptr<PollMediaState> media,
not_null<WebPageData*> page) {
const auto raw = media.get();
const auto weak = std::weak_ptr<PollMediaState>(media);
_controller->session().data().webPageUpdates(
) | rpl::filter([=](not_null<WebPageData*> updated) {
const auto locked = weak.lock();
return locked
&& (updated == page)
&& (locked->media.webpage == page);
}) | rpl::on_next([=] {
if (const auto locked = weak.lock()) {
applyResolvedWebPage(locked, page);
}
}, state->webPageLifetimes[raw]);
};
const auto resolveLink = [=](
std::shared_ptr<PollMediaState> media,
QString url) {
const auto raw = media.get();
const auto weak = std::weak_ptr<PollMediaState>(media);
state->webPageLifetimes[raw].destroy();
const auto token = media->token;
const auto apply = [=](const QString &resolvedUrl) {
const auto locked = weak.lock();
if (!locked || locked->token != token || resolvedUrl != url) {
return;
}
const auto cached = state->webpageResolver->lookup(url);
if (!cached || !*cached) {
return;
}
const auto page = *cached;
applyResolvedWebPage(locked, page);
subscribeToWebPageUpdates(locked, page);
};
if (const auto cached = state->webpageResolver->lookup(url)) {
if (*cached) {
applyResolvedWebPage(media, *cached);
subscribeToWebPageUpdates(media, *cached);
}
return;
}
state->webPageLifetimes[raw]
= state->webpageResolver->resolved(
) | rpl::filter([=](const QString &resolvedUrl) {
const auto locked = weak.lock();
return locked
&& (resolvedUrl == url)
&& (locked->token == token);
}) | rpl::take(1) | rpl::on_next(apply);
state->webpageResolver->request(url);
};
const auto chooseLink = [=](std::shared_ptr<PollMediaState> media) {
const auto initial = media->media.url;
const auto callback = crl::guard(this, [=](QString url) {
auto pollMedia = PollMedia();
pollMedia.url = url;
setMedia(
media,
pollMedia,
Poll::MakeLinkThumbnail(),
false);
resolveLink(media, url);
});
_controller->show(Box(
AddPollOptionLinkBox,
initial,
callback));
};
const auto clearMedia = [=](std::shared_ptr<PollMediaState> media) {
auto toCancel = std::vector<FullMsgId>();
for (auto i = state->uploads.begin(); i != state->uploads.end();) {
@@ -2474,6 +2611,7 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
for (const auto &id : toCancel) {
_controller->session().uploader().cancel(id);
}
state->webPageLifetimes.remove(media.get());
setMedia(media, PollMedia(), nullptr, false);
};
const auto chooseLocation = [=](
@@ -2580,6 +2718,10 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
[=] { showStickerPanel(button, media); },
&st::menuIconStickers);
}
state->mediaMenu->addAction(
tr::lng_polls_create_option_link(tr::now),
[=] { chooseLink(media); },
&st::menuIconLink);
if (media->media || media->uploading) {
state->mediaMenu->addAction(
tr::lng_box_remove(tr::now),