Replaced old upload pipeline in create_poll_box with extracted pipeline.

This commit is contained in:
23rd
2026-03-23 23:16:40 +03:00
parent 0d93cb580d
commit edbdbef2eb
4 changed files with 20 additions and 382 deletions
+7 -2
View File
@@ -188,6 +188,7 @@ void Polls::sendVotes(
void Polls::addAnswer(
FullMsgId itemId,
const TextWithEntities &text,
const PollMedia &media,
Fn<void()> done,
Fn<void(QString)> fail) {
if (_pollAddAnswerRequestIds.contains(itemId)) {
@@ -201,12 +202,16 @@ void Polls::addAnswer(
_session,
text.entities,
Api::ConvertOption::SkipLocal);
using Flag = MTPDinputPollAnswer::Flag;
const auto flags = media
? Flag::f_media
: Flag();
const auto answer = MTP_inputPollAnswer(
MTP_flags(MTPDinputPollAnswer::Flags(0)),
MTP_flags(flags),
MTP_textWithEntities(
MTP_string(text.text),
sentEntities),
MTPInputMedia());
media ? PollMediaToMTP(media) : MTPInputMedia());
const auto requestId = _api.request(MTPmessages_AddPollAnswer(
item->history()->peer->input(),
MTP_int(item->id),
+2
View File
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class ApiWrap;
class HistoryItem;
struct PollData;
struct PollMedia;
namespace Main {
class Session;
@@ -38,6 +39,7 @@ public:
void addAnswer(
FullMsgId itemId,
const TextWithEntities &text,
const PollMedia &media,
Fn<void()> done,
Fn<void(QString)> fail);
void close(not_null<HistoryItem*> item);
+10 -380
View File
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/create_poll_box.h"
#include "poll/poll_media_upload.h"
#include "base/call_delayed.h"
#include "base/unixtime.h"
#include "boxes/premium_limits_box.h"
@@ -95,387 +96,16 @@ constexpr auto kSolutionLimit = 200;
constexpr auto kWarnSolutionLimit = 60;
constexpr auto kErrorLimit = 99;
[[nodiscard]] bool ValidateFileDragData(not_null<const QMimeData*> data) {
if (data->hasImage()) {
return true;
}
const auto urls = Core::ReadMimeUrls(data);
return (urls.size() == 1) && urls.front().isLocalFile();
}
using PollMediaState = PollMediaUpload::PollMediaState;
using PollMediaButton = PollMediaUpload::PollMediaButton;
using PollMediaUploader = PollMediaUpload::PollMediaUploader;
[[nodiscard]] Ui::PreparedList FileListFromMimeData(
not_null<const QMimeData*> data,
bool premium) {
using Error = Ui::PreparedList::Error;
const auto urls = Core::ReadMimeUrls(data);
if (!urls.isEmpty()) {
return Storage::PrepareMediaList(
urls.mid(0, 1),
st::sendMediaPreviewSize,
premium);
} else if (auto read = Core::ReadMimeImage(data)) {
return Storage::PrepareMediaFromImage(
std::move(read.image),
std::move(read.content),
st::sendMediaPreviewSize);
}
return Ui::PreparedList(Error::EmptyFile, QString());
}
struct PollMediaState {
PollMedia media;
std::shared_ptr<Ui::DynamicImage> thumbnail;
bool rounded = false;
bool uploading = false;
float64 progress = 0.;
uint64 uploadDataId = 0;
uint64 token = 0;
Fn<void()> update;
};
class LocalImageThumbnail final : public Ui::DynamicImage {
public:
explicit LocalImageThumbnail(QImage original)
: _original(std::move(original)) {
}
std::shared_ptr<Ui::DynamicImage> clone() override {
return std::make_shared<LocalImageThumbnail>(_original);
}
QImage image(int size) override {
return _original;
}
void subscribeToUpdates(Fn<void()> callback) override {
}
private:
QImage _original;
};
[[nodiscard]] QImage GenerateDocumentFilePreview(
const QString &filename,
int size) {
const auto preview = Layout::DocumentGenericPreview::Create(filename);
const auto &color = preview.color;
const auto &ext = preview.ext;
const auto ratio = style::DevicePixelRatio();
auto result = QImage(
QSize(size, size) * ratio,
QImage::Format_ARGB32_Premultiplied);
result.fill(Qt::transparent);
result.setDevicePixelRatio(ratio);
auto p = QPainter(&result);
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(color);
p.drawRoundedRect(
QRect(0, 0, size, size),
st::roundRadiusSmall,
st::roundRadiusSmall);
if (!ext.isEmpty()) {
const auto refSize = st::overviewFileLayout.fileThumbSize;
const auto fontSize = std::max(
size * st::overviewFileExtFont->f.pixelSize() / refSize,
8);
const auto font = style::font(
fontSize,
st::overviewFileExtFont->flags(),
st::overviewFileExtFont->family());
const auto padding = size * st::overviewFileExtPadding / refSize;
const auto maxw = size - padding * 2;
auto extStr = ext;
auto extw = font->width(extStr);
if (extw > maxw) {
extStr = font->elided(extStr, maxw, Qt::ElideMiddle);
extw = font->width(extStr);
}
p.setFont(font);
p.setPen(st::overviewFileExtFg);
p.drawText(
(size - extw) / 2,
(size - font->height) / 2 + font->ascent,
extStr);
}
p.end();
return result;
}
class PollMediaButton final : public Ui::RippleButton {
public:
PollMediaButton(
not_null<QWidget*> parent,
const style::IconButton &st,
std::shared_ptr<PollMediaState> state)
: Ui::RippleButton(parent, st.ripple)
, _st(st)
, _state(std::move(state))
, _attach(Ui::MakeIconThumbnail(_st.icon))
, _attachOver(_st.iconOver.empty()
? _attach
: Ui::MakeIconThumbnail(_st.iconOver))
, _radial([=](crl::time now) { radialAnimationCallback(now); }) {
const auto weak = QPointer<PollMediaButton>(this);
_state->update = [=] {
if (weak) {
weak->updateMediaSubscription();
weak->update();
}
};
resize(_st.width, _st.height);
setPointerCursor(true);
updateMediaSubscription();
}
~PollMediaButton() override {
if (_subscribed) {
_subscribed->subscribeToUpdates(nullptr);
}
}
protected:
void paintEvent(QPaintEvent *e) override {
auto p = Painter(this);
paintRipple(p, _st.rippleAreaPosition);
if (const auto image = _state->thumbnail
? _state->thumbnail
: currentAttachThumbnail()) {
const auto target = _state->thumbnail
? rippleRect()
: iconRect();
if (_state->thumbnail) {
paintCover(
p,
target,
image->image(std::max(target.width(), target.height())),
_state->rounded);
} else {
p.drawImage(
target,
image->image(std::max(target.width(), target.height())));
}
}
if (_state->thumbnail && !_state->uploading) {
const auto viewOpacity = _viewShown.value(
(isOver() || isDown()) ? 1. : 0.);
if (viewOpacity > 0.) {
p.save();
p.setOpacity(viewOpacity);
auto path = QPainterPath();
path.addRoundedRect(
rippleRect(),
st::roundRadiusSmall,
st::roundRadiusSmall);
p.setClipPath(path);
p.fillRect(rippleRect(), st::songCoverOverlayFg);
st::pollAttachView.paintInCenter(p, rippleRect());
p.restore();
}
}
if (_state->uploading && !_radial.animating()) {
_radial.start(_state->progress);
}
if (_state->uploading || _radial.animating()) {
if (_state->thumbnail) {
p.save();
auto path = QPainterPath();
path.addRoundedRect(
rippleRect(),
st::roundRadiusSmall,
st::roundRadiusSmall);
p.setClipPath(path);
p.fillRect(rippleRect(), st::songCoverOverlayFg);
p.restore();
}
const auto cancelOpacity = _state->uploading
? _cancelShown.value(
(isOver() || isDown()) ? 1. : 0.)
: 0.;
const auto line = float64(st::lineWidth * 2);
const auto margin = float64(st::pollAttachProgressMargin);
const auto arc = QRectF(rippleRect()) - Margins(margin);
if (cancelOpacity > 0.) {
p.setOpacity(cancelOpacity);
st::pollAttachCancel.paintInCenter(p, rippleRect());
p.setOpacity(1.);
}
_radial.draw(p, arc, line, st::historyFileThumbRadialFg);
}
}
void onStateChanged(State was, StateChangeSource source) override {
RippleButton::onStateChanged(was, source);
if (!_state->thumbnail) {
update();
}
const auto over = isOver() || isDown();
const auto wasOver = (was & StateFlag::Over)
|| (was & StateFlag::Down);
if (over != wasOver) {
if (_state->uploading) {
_cancelShown.start(
[=] { update(); },
over ? 0. : 1.,
over ? 1. : 0.,
st::universalDuration);
}
if (_state->thumbnail && !_state->uploading) {
_viewShown.start(
[=] { update(); },
over ? 0. : 1.,
over ? 1. : 0.,
st::universalDuration);
}
}
}
QImage prepareRippleMask() const override {
return Ui::RippleAnimation::EllipseMask(QSize(
_st.rippleAreaSize,
_st.rippleAreaSize));
}
QPoint prepareRippleStartPosition() const override {
auto result = mapFromGlobal(QCursor::pos())
- _st.rippleAreaPosition;
const auto rect = QRect(
QPoint(),
QSize(_st.rippleAreaSize, _st.rippleAreaSize));
return rect.contains(result)
? result
: DisabledRippleStartPosition();
}
private:
void paintCover(
Painter &p,
QRect target,
QImage image,
bool rounded) const {
if (image.isNull() || target.isEmpty()) {
return;
}
const auto source = QRectF(
0,
0,
image.width(),
image.height());
const auto kx = target.width() / source.width();
const auto ky = target.height() / source.height();
const auto scale = std::max(kx, ky);
const auto size = QSizeF(
source.width() * scale,
source.height() * scale);
const auto geometry = QRectF(
target.x() + (target.width() - size.width()) / 2.,
target.y() + (target.height() - size.height()) / 2.,
size.width(),
size.height());
p.save();
if (rounded) {
auto path = QPainterPath();
path.addRoundedRect(
target,
st::roundRadiusSmall,
st::roundRadiusSmall);
p.setClipPath(path);
}
p.drawImage(geometry, image, source);
p.restore();
}
void radialAnimationCallback(crl::time now) {
const auto updated = _radial.update(
_state->progress,
!_state->uploading,
now);
if (!anim::Disabled() || updated || _radial.animating()) {
update(rippleRect());
}
}
[[nodiscard]] QRect rippleRect() const {
return QRect(
_st.rippleAreaPosition,
QSize(_st.rippleAreaSize, _st.rippleAreaSize));
}
[[nodiscard]] QRect iconRect() const {
const auto over = isOver() || isDown();
const auto &icon = over && !_st.iconOver.empty()
? _st.iconOver
: _st.icon;
auto position = _st.iconPosition;
if (position.x() < 0) {
position.setX((width() - icon.width()) / 2);
}
if (position.y() < 0) {
position.setY((height() - icon.height()) / 2);
}
return QRect(position, QSize(icon.width(), icon.height()));
}
std::shared_ptr<Ui::DynamicImage> currentAttachThumbnail() const {
return (isOver() || isDown()) ? _attachOver : _attach;
}
void updateMediaSubscription() {
if (_subscribed == _state->thumbnail) {
return;
}
if (_subscribed) {
_subscribed->subscribeToUpdates(nullptr);
}
_subscribed = _state->thumbnail;
if (!_subscribed) {
return;
}
const auto weak = QPointer<PollMediaButton>(this);
_subscribed->subscribeToUpdates([=] {
if (weak) {
weak->update();
}
});
}
const style::IconButton &_st;
const std::shared_ptr<PollMediaState> _state;
const std::shared_ptr<Ui::DynamicImage> _attach;
const std::shared_ptr<Ui::DynamicImage> _attachOver;
std::shared_ptr<Ui::DynamicImage> _subscribed;
Ui::RadialAnimation _radial;
Ui::Animations::Simple _cancelShown;
Ui::Animations::Simple _viewShown;
};
class PreparePollMediaTask final : public Task {
public:
PreparePollMediaTask(
FileLoadTask::Args &&args,
Fn<void(std::shared_ptr<FilePrepareResult>)> done)
: _task(std::move(args))
, _done(std::move(done)) {
}
void process() override {
_task.process({ .generateGoodThumbnail = false });
}
void finish() override {
_done(_task.peekResult());
}
private:
FileLoadTask _task;
Fn<void(std::shared_ptr<FilePrepareResult>)> _done;
};
using PollMediaUpload::FileListFromMimeData;
using PollMediaUpload::GenerateDocumentFilePreview;
using PollMediaUpload::LocalImageThumbnail;
using PollMediaUpload::PreparePollMediaTask;
using PollMediaUpload::UploadContext;
using PollMediaUpload::ValidateFileDragData;
class Options {
public:
@@ -122,6 +122,7 @@ void AddPollOptionWidget::triggerSubmit() {
_session->api().polls().addAnswer(
_itemId,
{ text },
PollMedia(),
[=] { _submittedEvents.fire({}); },
[=](QString error) {
_field->setEnabled(true);