mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Added simple radial upload progress overlay to userpic widgets.
This commit is contained in:
@@ -150,12 +150,37 @@ PeerPhoto::PeerPhoto(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
crl::on_main(_session, [=] {
|
||||
auto &uploader = _session->uploader();
|
||||
|
||||
// You can't use _session->lifetime() in the constructor,
|
||||
// only queued, because it is not constructed yet.
|
||||
_session->uploader().photoReady(
|
||||
uploader.photoReady(
|
||||
) | rpl::on_next([=](const Storage::UploadedMedia &data) {
|
||||
ready(data.fullId, data.info.file, std::nullopt);
|
||||
}, _session->lifetime());
|
||||
|
||||
uploader.photoProgress(
|
||||
) | rpl::on_next([=](const FullMsgId &id) {
|
||||
const auto i = _uploads.find(id);
|
||||
if (i == end(_uploads) || !i->second.photoId) {
|
||||
return;
|
||||
}
|
||||
const auto peer = i->second.peer;
|
||||
const auto photo = _session->data().photo(
|
||||
i->second.photoId);
|
||||
_uploadProgress.fire({ peer, photo->progress() });
|
||||
}, _session->lifetime());
|
||||
|
||||
uploader.photoFailed(
|
||||
) | rpl::on_next([=](const FullMsgId &id) {
|
||||
const auto i = _uploads.find(id);
|
||||
if (i == end(_uploads)) {
|
||||
return;
|
||||
}
|
||||
const auto peer = i->second.peer;
|
||||
_uploads.erase(i);
|
||||
_uploadFailed.fire_copy(peer);
|
||||
}, _session->lifetime());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,17 +247,18 @@ void PeerPhoto::upload(
|
||||
_session->uploader().cancel(already->first);
|
||||
_uploads.erase(already);
|
||||
}
|
||||
_uploads.emplace(
|
||||
const auto &[it, ok] = _uploads.emplace(
|
||||
fakeId,
|
||||
UploadValue{ peer, type, std::move(done) });
|
||||
UploadValue{ peer, type, std::move(done), PhotoId(0) });
|
||||
if (mtpMarkup) {
|
||||
ready(fakeId, std::nullopt, mtpMarkup);
|
||||
} else {
|
||||
const auto ready = PreparePeerPhoto(
|
||||
const auto prepared = PreparePeerPhoto(
|
||||
_api.instance().mainDcId(),
|
||||
peer->id,
|
||||
base::take(photo.image));
|
||||
_session->uploader().upload(fakeId, ready);
|
||||
it->second.photoId = prepared->thumbId;
|
||||
_session->uploader().upload(fakeId, prepared);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +266,68 @@ void PeerPhoto::suggest(not_null<PeerData*> peer, UserPhoto &&photo) {
|
||||
upload(peer, std::move(photo), UploadType::Suggestion, nullptr);
|
||||
}
|
||||
|
||||
void PeerPhoto::subscribeToUpload(
|
||||
not_null<PeerData*> peer,
|
||||
rpl::lifetime &lifetime,
|
||||
UploadCallbacks callbacks) {
|
||||
uploadProgress(
|
||||
) | rpl::filter([=](const UploadProgress &data) {
|
||||
return (data.peer == peer);
|
||||
}) | rpl::on_next([cb = callbacks.progress](const UploadProgress &data) {
|
||||
if (cb) {
|
||||
cb(data.progress);
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
uploadDone(
|
||||
) | rpl::filter([=](not_null<PeerData*> p) {
|
||||
return (p == peer);
|
||||
}) | rpl::on_next([cb = callbacks.done](not_null<PeerData*>) {
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
uploadFailed(
|
||||
) | rpl::filter([=](not_null<PeerData*> p) {
|
||||
return (p == peer);
|
||||
}) | rpl::on_next([cb = callbacks.failed](not_null<PeerData*>) {
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
}, lifetime);
|
||||
}
|
||||
|
||||
auto PeerPhoto::uploadProgress() const
|
||||
-> rpl::producer<UploadProgress> {
|
||||
return _uploadProgress.events();
|
||||
}
|
||||
|
||||
auto PeerPhoto::uploadDone() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _uploadDone.events();
|
||||
}
|
||||
|
||||
auto PeerPhoto::uploadFailed() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _uploadFailed.events();
|
||||
}
|
||||
|
||||
void PeerPhoto::cancelUpload(not_null<PeerData*> peer) {
|
||||
peer = peer->migrateToOrMe();
|
||||
const auto i = ranges::find(
|
||||
_uploads,
|
||||
peer,
|
||||
[](const auto &pair) { return pair.second.peer; });
|
||||
if (i == end(_uploads)) {
|
||||
return;
|
||||
}
|
||||
const auto fakeId = i->first;
|
||||
_uploads.erase(i);
|
||||
_session->uploader().cancel(fakeId);
|
||||
_uploadFailed.fire_copy(peer);
|
||||
}
|
||||
|
||||
void PeerPhoto::clear(not_null<PhotoData*> photo) {
|
||||
const auto self = _session->user();
|
||||
if (self->userpicPhotoId() == photo->id) {
|
||||
@@ -362,12 +450,19 @@ void PeerPhoto::ready(
|
||||
const auto peer = maybeUploadValue->peer;
|
||||
const auto type = maybeUploadValue->type;
|
||||
const auto done = maybeUploadValue->done;
|
||||
const auto applier = [=](const MTPUpdates &result) {
|
||||
_session->updates().applyUpdates(result);
|
||||
const auto finish = [=] {
|
||||
_uploadDone.fire_copy(peer);
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
_uploadFailed.fire_copy(peer);
|
||||
};
|
||||
const auto applier = [=](const MTPUpdates &result) {
|
||||
_session->updates().applyUpdates(result);
|
||||
finish();
|
||||
};
|
||||
const auto botUserInput = [&] {
|
||||
const auto user = peer->asUser();
|
||||
return (user && user->botInfo && user->botInfo->canEditInformation)
|
||||
@@ -400,10 +495,8 @@ void PeerPhoto::ready(
|
||||
peerToUser(peer->id),
|
||||
photoId));
|
||||
}
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}).send();
|
||||
finish();
|
||||
}).fail(fail).send();
|
||||
} else if (const auto chat = peer->asChat()) {
|
||||
const auto history = _session->data().history(chat);
|
||||
using Flag = MTPDinputChatUploadedPhoto::Flag;
|
||||
@@ -417,7 +510,7 @@ void PeerPhoto::ready(
|
||||
MTPInputFile(), // video
|
||||
MTPdouble(), // video_start_ts
|
||||
videoSize ? (*videoSize) : MTPVideoSize()) // video_emoji_markup
|
||||
)).done(applier).afterRequest(history->sendRequestId).send();
|
||||
)).done(applier).fail(fail).afterRequest(history->sendRequestId).send();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
using Flag = MTPDinputChatUploadedPhoto::Flag;
|
||||
const auto none = MTPDinputChatUploadedPhoto::Flags(0);
|
||||
@@ -431,7 +524,7 @@ void PeerPhoto::ready(
|
||||
MTPInputFile(), // video
|
||||
MTPdouble(), // video_start_ts
|
||||
videoSize ? (*videoSize) : MTPVideoSize()) // video_emoji_markup
|
||||
)).done(applier).afterRequest(history->sendRequestId).send();
|
||||
)).done(applier).fail(fail).afterRequest(history->sendRequestId).send();
|
||||
} else if (const auto user = peer->asUser()) {
|
||||
using Flag = MTPphotos_UploadContactProfilePhoto::Flag;
|
||||
const auto none = MTPphotos_UploadContactProfilePhoto::Flags(0);
|
||||
@@ -454,10 +547,8 @@ void PeerPhoto::ready(
|
||||
if (type != UploadType::Suggestion) {
|
||||
user->updateFullForced();
|
||||
}
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}).send();
|
||||
finish();
|
||||
}).fail(fail).send();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@ public:
|
||||
std::vector<QColor> markupColors;
|
||||
};
|
||||
|
||||
struct UploadProgress {
|
||||
not_null<PeerData*> peer;
|
||||
float64 progress = 0.;
|
||||
};
|
||||
|
||||
void upload(
|
||||
not_null<PeerData*> peer,
|
||||
UserPhoto &&photo,
|
||||
@@ -55,6 +60,24 @@ public:
|
||||
void clearPersonal(not_null<UserData*> user);
|
||||
void set(not_null<PeerData*> peer, not_null<PhotoData*> photo);
|
||||
|
||||
struct UploadCallbacks {
|
||||
Fn<void(float64)> progress;
|
||||
Fn<void()> done;
|
||||
Fn<void()> failed;
|
||||
};
|
||||
void subscribeToUpload(
|
||||
not_null<PeerData*> peer,
|
||||
rpl::lifetime &lifetime,
|
||||
UploadCallbacks callbacks);
|
||||
|
||||
[[nodiscard]] auto uploadProgress() const
|
||||
-> rpl::producer<UploadProgress>;
|
||||
[[nodiscard]] auto uploadDone() const
|
||||
-> rpl::producer<not_null<PeerData*>>;
|
||||
[[nodiscard]] auto uploadFailed() const
|
||||
-> rpl::producer<not_null<PeerData*>>;
|
||||
void cancelUpload(not_null<PeerData*> peer);
|
||||
|
||||
void requestUserPhotos(not_null<UserData*> user, UserPhotoId afterId);
|
||||
|
||||
void requestEmojiList(EmojiListType type);
|
||||
@@ -100,9 +123,13 @@ private:
|
||||
not_null<PeerData*> peer;
|
||||
UploadType type = UploadType::Default;
|
||||
Fn<void()> done;
|
||||
PhotoId photoId = 0;
|
||||
};
|
||||
|
||||
base::flat_map<FullMsgId, UploadValue> _uploads;
|
||||
rpl::event_stream<UploadProgress> _uploadProgress;
|
||||
rpl::event_stream<not_null<PeerData*>> _uploadDone;
|
||||
rpl::event_stream<not_null<PeerData*>> _uploadFailed;
|
||||
|
||||
base::flat_map<not_null<UserData*>, mtpRequestId> _userPhotosRequests;
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ UserpicButton {
|
||||
uploadBg: color;
|
||||
uploadIcon: icon;
|
||||
uploadIconPosition: point;
|
||||
uploadProgressLine: pixels;
|
||||
uploadProgressMargin: pixels;
|
||||
}
|
||||
UserpicsRow {
|
||||
button: UserpicButton;
|
||||
@@ -72,6 +74,8 @@ defaultUserpicButton: UserpicButton {
|
||||
uploadBg: msgDateImgBgOver;
|
||||
uploadIcon: defaultUploadUserpicIcon;
|
||||
uploadIconPosition: point(-1px, 1px);
|
||||
uploadProgressLine: 3px;
|
||||
uploadProgressMargin: 8px;
|
||||
}
|
||||
uploadUserpicSize: 32px;
|
||||
uploadUserpicButton: UserpicButton(defaultUserpicButton) {
|
||||
@@ -81,6 +85,7 @@ uploadUserpicButton: UserpicButton(defaultUserpicButton) {
|
||||
changeIconPosition: point(4px, 4px);
|
||||
}
|
||||
uploadUserpicButtonBorder: 2px;
|
||||
userpicUploadCancel: icon {{ "history_file_cancel", historyFileThumbIconFg }};
|
||||
restoreUserpicIcon: UserpicButton(defaultUserpicButton) {
|
||||
size: size(22px, 22px);
|
||||
photoSize: 22px;
|
||||
|
||||
@@ -70,6 +70,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/controls/stars_rating.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/upload_progress_overlay.h"
|
||||
#include "ui/effects/outline_segments.h"
|
||||
#include "ui/effects/round_checkbox.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
@@ -1120,6 +1121,7 @@ void TopBar::setupUserpicButton(
|
||||
_peer->session().api().peerPhoto().upload(
|
||||
_peer,
|
||||
std::move(result));
|
||||
startUploadOverlay();
|
||||
break;
|
||||
case ChosenType::Suggest:
|
||||
_peer->session().api().peerPhoto().suggest(
|
||||
@@ -1280,7 +1282,9 @@ void TopBar::setupUserpicButton(
|
||||
(*menu)->popup(QCursor::pos());
|
||||
}
|
||||
} else if (button == Qt::LeftButton) {
|
||||
if (_topicIconView && _topic && _topic->iconId()) {
|
||||
if (_uploadOverlay && _uploadOverlay->uploading()) {
|
||||
_peer->session().api().peerPhoto().cancelUpload(_peer);
|
||||
} else if (_topicIconView && _topic && _topic->iconId()) {
|
||||
const auto document = _peer->owner().document(
|
||||
_topic->iconId());
|
||||
if (const auto sticker = document->sticker()) {
|
||||
@@ -1321,6 +1325,46 @@ void TopBar::setupUserpicButton(
|
||||
}, _userpicButton->lifetime());
|
||||
}
|
||||
|
||||
void TopBar::startUploadOverlay() {
|
||||
if (_uploadOverlay && _uploadOverlay->uploading()) {
|
||||
return;
|
||||
}
|
||||
_waitingUserpicCloudLoad = true;
|
||||
_uploadOverlay = std::make_unique<Ui::UploadProgressOverlay>(
|
||||
this,
|
||||
[=] { update(); });
|
||||
_uploadOverlay->start();
|
||||
|
||||
_userpicButton->events(
|
||||
) | rpl::filter([](not_null<QEvent*> e) {
|
||||
return e->type() == QEvent::Enter
|
||||
|| e->type() == QEvent::Leave;
|
||||
}) | rpl::on_next([=](not_null<QEvent*> e) {
|
||||
if (_uploadOverlay) {
|
||||
_uploadOverlay->setOver(e->type() == QEvent::Enter);
|
||||
}
|
||||
}, _uploadLifetime);
|
||||
|
||||
const auto cleanup = [=] {
|
||||
_uploadLifetime.destroy();
|
||||
_uploadOverlay = nullptr;
|
||||
};
|
||||
_peer->session().api().peerPhoto().subscribeToUpload(
|
||||
_peer,
|
||||
_uploadLifetime,
|
||||
{
|
||||
.progress = [=](float64 value) {
|
||||
_uploadOverlay->setProgress(value);
|
||||
},
|
||||
.done = [=] {
|
||||
_uploadOverlay->stop(cleanup);
|
||||
},
|
||||
.failed = [=] {
|
||||
_uploadOverlay->fail(cleanup);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
void TopBar::setupUniqueBadgeTooltip() {
|
||||
if (!_badge || _source == Source::Preview) {
|
||||
return;
|
||||
@@ -1738,7 +1782,11 @@ void TopBar::paintUserpic(QPainter &p, const QRect &geometry) {
|
||||
}
|
||||
}
|
||||
const auto key = _peer->userpicUniqueKey(_userpicView);
|
||||
if (_userpicUniqueKey != key) {
|
||||
const auto overlayActive = _uploadOverlay && _uploadOverlay->shown();
|
||||
const auto awaitingCloud = _waitingUserpicCloudLoad
|
||||
&& !_peer->userpicCloudImage(_userpicView);
|
||||
if (!overlayActive && !awaitingCloud && _userpicUniqueKey != key) {
|
||||
_waitingUserpicCloudLoad = false;
|
||||
_userpicUniqueKey = key;
|
||||
const auto fullSize = st::infoProfileTopBarPhotoSize;
|
||||
const auto scaled = fullSize * style::DevicePixelRatio();
|
||||
@@ -1773,6 +1821,15 @@ void TopBar::paintUserpic(QPainter &p, const QRect &geometry) {
|
||||
_cachedUserpic.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
}
|
||||
p.drawImage(geometry, _cachedUserpic);
|
||||
if (_uploadOverlay && _uploadOverlay->shown()) {
|
||||
_uploadOverlay->paint(p, geometry, {
|
||||
.lineWidth = st::defaultUserpicButton.uploadProgressLine,
|
||||
.margin = st::defaultUserpicButton.uploadProgressMargin,
|
||||
.progressFg = st::historyFileThumbRadialFg,
|
||||
.overlayFg = st::songCoverOverlayFg,
|
||||
.cancelIcon = &st::userpicUploadCancel,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void TopBar::paintEvent(QPaintEvent *e) {
|
||||
|
||||
@@ -32,6 +32,7 @@ class MultiPlayer;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Ui {
|
||||
class UploadProgressOverlay;
|
||||
class VideoUserpicPlayer;
|
||||
struct OutlineSegment;
|
||||
namespace Text {
|
||||
@@ -151,6 +152,7 @@ private:
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Ui::Menu::MenuCallback &addAction);
|
||||
void setupUserpicButton(not_null<Window::SessionController*> controller);
|
||||
void startUploadOverlay();
|
||||
void setupActions(not_null<Window::SessionController*> controller);
|
||||
void setupButtons(
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -252,6 +254,9 @@ private:
|
||||
QImage _monoforumMask;
|
||||
std::unique_ptr<Ui::VideoUserpicPlayer> _videoUserpicPlayer;
|
||||
std::unique_ptr<TopicIconView> _topicIconView;
|
||||
std::unique_ptr<Ui::UploadProgressOverlay> _uploadOverlay;
|
||||
rpl::lifetime _uploadLifetime;
|
||||
bool _waitingUserpicCloudLoad = false;
|
||||
rpl::lifetime _userpicLoadingLifetime;
|
||||
|
||||
base::unique_qptr<Ui::IconButton> _close;
|
||||
|
||||
@@ -283,6 +283,7 @@ void SetupPhoto(
|
||||
auto &image = chosen.image;
|
||||
UpdatePhotoLocally(self, image);
|
||||
photo->showCustom(base::duplicate(image));
|
||||
const auto isMarkup = (chosen.markup.documentId != 0);
|
||||
self->session().api().peerPhoto().upload(
|
||||
self,
|
||||
{
|
||||
@@ -290,6 +291,9 @@ void SetupPhoto(
|
||||
chosen.markup.documentId,
|
||||
chosen.markup.colors,
|
||||
});
|
||||
if (!isMarkup) {
|
||||
photo->showUploadProgress();
|
||||
}
|
||||
}, upload->lifetime());
|
||||
|
||||
const auto name = Ui::CreateChild<Ui::FlatLabel>(
|
||||
|
||||
@@ -188,6 +188,7 @@ Cover::Cover(
|
||||
Ui::UserpicButton::ChosenImage chosen) {
|
||||
auto &image = chosen.image;
|
||||
_userpic->showCustom(base::duplicate(image));
|
||||
const auto isMarkup = (chosen.markup.documentId != 0);
|
||||
_user->session().api().peerPhoto().upload(
|
||||
_user,
|
||||
{
|
||||
@@ -195,6 +196,9 @@ Cover::Cover(
|
||||
chosen.markup.documentId,
|
||||
chosen.markup.colors,
|
||||
});
|
||||
if (!isMarkup) {
|
||||
_userpic->showUploadProgress();
|
||||
}
|
||||
});
|
||||
|
||||
_badge.setPremiumClickCallback([=] {
|
||||
|
||||
@@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/controls/userpic_button.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "ui/effects/upload_progress_overlay.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "boxes/edit_privacy_box.h"
|
||||
@@ -30,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "editor/photo_editor_common.h"
|
||||
#include "editor/photo_editor_layer_widget.h"
|
||||
@@ -294,6 +297,10 @@ void UserpicButton::setClickHandlerByRole() {
|
||||
}
|
||||
|
||||
void UserpicButton::choosePhotoLocally() {
|
||||
if (_uploadOverlay && _uploadOverlay->uploading()) {
|
||||
_peer->session().api().peerPhoto().cancelUpload(_peer);
|
||||
return;
|
||||
}
|
||||
if (!_window) {
|
||||
return;
|
||||
} else if (const auto controller = _window->sessionController()) {
|
||||
@@ -499,6 +506,10 @@ void UserpicButton::openPeerPhoto() {
|
||||
Expects(_peer != nullptr);
|
||||
Expects(_controller != nullptr);
|
||||
|
||||
if (_uploadOverlay && _uploadOverlay->uploading()) {
|
||||
_peer->session().api().peerPhoto().cancelUpload(_peer);
|
||||
return;
|
||||
}
|
||||
if (_changeOverlayEnabled && _cursorInChangeOverlay) {
|
||||
choosePhotoLocally();
|
||||
return;
|
||||
@@ -612,7 +623,10 @@ void UserpicButton::paintEvent(QPaintEvent *e) {
|
||||
p.translate(-photoLeft, -photoTop);
|
||||
};
|
||||
|
||||
if (_role == Role::ChangePhoto || _role == Role::ChoosePhoto) {
|
||||
const auto uploadShown = _uploadOverlay
|
||||
&& _uploadOverlay->shown();
|
||||
if (!uploadShown
|
||||
&& (_role == Role::ChangePhoto || _role == Role::ChoosePhoto)) {
|
||||
auto over = isOver() || isDown();
|
||||
if (over) {
|
||||
fillTranslatedShape(_userpicHasImage
|
||||
@@ -633,7 +647,7 @@ void UserpicButton::paintEvent(QPaintEvent *e) {
|
||||
photoTop + iconTop,
|
||||
width());
|
||||
}
|
||||
} else if (_changeOverlayEnabled) {
|
||||
} else if (!uploadShown && _changeOverlayEnabled) {
|
||||
auto current = _changeOverlayShown.value(
|
||||
(isOver() || isDown()) ? 1. : 0.);
|
||||
auto barHeight = anim::interpolate(
|
||||
@@ -665,6 +679,18 @@ void UserpicButton::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uploadShown) {
|
||||
_uploadOverlay->paint(p, QRect(photoPosition, Size(_st.photoSize)), {
|
||||
.lineWidth = _st.uploadProgressLine,
|
||||
.margin = _st.uploadProgressMargin,
|
||||
.progressFg = st::historyFileThumbRadialFg,
|
||||
.overlayFg = st::songCoverOverlayFg,
|
||||
.cancelIcon = &st::userpicUploadCancel,
|
||||
.roundRadius = useForumShape()
|
||||
? (_st.photoSize * ForumUserpicRadiusMultiplier())
|
||||
: 0.,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void UserpicButton::paintUserpicFrame(Painter &p, QPoint photoPosition) {
|
||||
@@ -1055,6 +1081,14 @@ void UserpicButton::onStateChanged(
|
||||
startChangeOverlayAnimation();
|
||||
}
|
||||
}
|
||||
if (_uploadOverlay && _uploadOverlay->uploading()) {
|
||||
const auto over = isOver() || isDown();
|
||||
const auto wasOver = (was & StateFlag::Over)
|
||||
|| (was & StateFlag::Down);
|
||||
if (over != wasOver) {
|
||||
_uploadOverlay->setOver(over);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UserpicButton::showCustom(QImage &&image) {
|
||||
@@ -1122,6 +1156,40 @@ rpl::producer<> UserpicButton::resetPersonalRequests() const {
|
||||
return _resetPersonalRequests.events();
|
||||
}
|
||||
|
||||
void UserpicButton::showUploadProgress() {
|
||||
if (_uploadOverlay && _uploadOverlay->uploading()) {
|
||||
return;
|
||||
}
|
||||
Expects(_peer != nullptr);
|
||||
|
||||
_uploadOverlay = std::make_unique<UploadProgressOverlay>(
|
||||
this,
|
||||
[=] { update(); });
|
||||
_uploadOverlay->start();
|
||||
|
||||
_peer->session().api().peerPhoto().subscribeToUpload(
|
||||
_peer,
|
||||
_uploadLifetime,
|
||||
{
|
||||
.progress = [=](float64 value) {
|
||||
_uploadOverlay->setProgress(value);
|
||||
},
|
||||
.done = [=] {
|
||||
_uploadOverlay->stop([=] {
|
||||
_uploadLifetime.destroy();
|
||||
_uploadOverlay = nullptr;
|
||||
});
|
||||
},
|
||||
.failed = [=] {
|
||||
_uploadOverlay->fail([=] {
|
||||
_uploadLifetime.destroy();
|
||||
_uploadOverlay = nullptr;
|
||||
showSource(Source::PeerPhoto);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
void UserpicButton::fillShape(QPainter &p, QBrush brush) const {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
@@ -40,6 +40,8 @@ class ItemBase;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class UploadProgressOverlay;
|
||||
|
||||
class PopupMenu;
|
||||
|
||||
class UserpicButton final : public RippleButton {
|
||||
@@ -111,6 +113,8 @@ public:
|
||||
void showSource(Source source);
|
||||
void showCustomOnChosen();
|
||||
|
||||
void showUploadProgress();
|
||||
|
||||
[[nodiscard]] PopupMenu *showChangePhotoMenu();
|
||||
|
||||
void overrideHasPersonalPhoto(bool has);
|
||||
@@ -202,6 +206,9 @@ private:
|
||||
rpl::event_stream<> _resetPersonalRequests;
|
||||
rpl::lifetime _sourceLifetime;
|
||||
|
||||
std::unique_ptr<UploadProgressOverlay> _uploadOverlay;
|
||||
rpl::lifetime _uploadLifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] not_null<Ui::UserpicButton*> CreateUploadSubButton(
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
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 "ui/effects/upload_progress_overlay.h"
|
||||
|
||||
#include "base/call_delayed.h"
|
||||
#include "ui/effects/animation_value.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_basic.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
UploadProgressOverlay::UploadProgressOverlay(
|
||||
not_null<QWidget*> parent,
|
||||
Fn<void()> update)
|
||||
: _parent(parent)
|
||||
, _update(std::move(update))
|
||||
, _radial([=](crl::time now) { radialAnimationCallback(now); }) {
|
||||
}
|
||||
|
||||
void UploadProgressOverlay::start() {
|
||||
_uploading = true;
|
||||
_stopping = false;
|
||||
_startedAt = crl::now();
|
||||
_progress = 0.;
|
||||
_hiding.stop();
|
||||
_hidingDone = nullptr;
|
||||
_radial.start(_progress);
|
||||
_update();
|
||||
}
|
||||
|
||||
void UploadProgressOverlay::stop(Fn<void()> done) {
|
||||
if (!_uploading) {
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_hidingDone = std::move(done);
|
||||
_stopping = true;
|
||||
_progress = 1.;
|
||||
_update();
|
||||
|
||||
const auto catchUp = st::radialDuration * 2;
|
||||
base::call_delayed(catchUp, _parent, [=] {
|
||||
if (!_stopping) {
|
||||
return;
|
||||
}
|
||||
proceedWithStop();
|
||||
});
|
||||
}
|
||||
|
||||
void UploadProgressOverlay::fail(Fn<void()> done) {
|
||||
if (!_uploading) {
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_hidingDone = std::move(done);
|
||||
_stopping = true;
|
||||
_progress = 0.;
|
||||
_update();
|
||||
|
||||
const auto catchUp = st::radialDuration * 2;
|
||||
base::call_delayed(catchUp, _parent, [=] {
|
||||
if (!_stopping) {
|
||||
return;
|
||||
}
|
||||
proceedWithStop();
|
||||
});
|
||||
}
|
||||
|
||||
void UploadProgressOverlay::proceedWithStop() {
|
||||
_stopping = false;
|
||||
_uploading = false;
|
||||
|
||||
const auto elapsed = crl::now() - _startedAt;
|
||||
const auto minDuration = st::radialDuration * 3;
|
||||
const auto delay = std::max(minDuration - elapsed, crl::time(0));
|
||||
base::call_delayed(delay, _parent, [=] {
|
||||
if (_uploading) {
|
||||
return;
|
||||
}
|
||||
_hiding.start(
|
||||
_update,
|
||||
1.,
|
||||
0.,
|
||||
st::radialDuration);
|
||||
_hiding.setFinishedCallback([=] {
|
||||
_radial.stop();
|
||||
const auto callback = base::take(_hidingDone);
|
||||
_update();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void UploadProgressOverlay::finish(Fn<void()> done) {
|
||||
_uploading = false;
|
||||
_update();
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
void UploadProgressOverlay::setProgress(float64 progress) {
|
||||
_progress = progress;
|
||||
_update();
|
||||
}
|
||||
|
||||
void UploadProgressOverlay::setOver(bool over) {
|
||||
_over = over;
|
||||
if (!_uploading) {
|
||||
return;
|
||||
}
|
||||
_cancelShown.start(
|
||||
_update,
|
||||
over ? 0. : 1.,
|
||||
over ? 1. : 0.,
|
||||
st::universalDuration);
|
||||
}
|
||||
|
||||
bool UploadProgressOverlay::shown() const {
|
||||
return _uploading
|
||||
|| _radial.animating()
|
||||
|| _hiding.animating()
|
||||
|| _hidingDone;
|
||||
}
|
||||
|
||||
bool UploadProgressOverlay::uploading() const {
|
||||
return _uploading && !_stopping;
|
||||
}
|
||||
|
||||
void UploadProgressOverlay::paint(
|
||||
QPainter &p,
|
||||
QRect rect,
|
||||
const PaintArgs &args) {
|
||||
const auto hideOpacity = _hiding.animating()
|
||||
? _hiding.value(0.)
|
||||
: (_uploading || _hidingDone) ? 1. : 0.;
|
||||
if (hideOpacity <= 0.) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto o = p.opacity();
|
||||
if (hideOpacity < 1.) {
|
||||
p.setOpacity(o * hideOpacity);
|
||||
}
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(args.overlayFg);
|
||||
if (args.roundRadius > 0.) {
|
||||
p.drawRoundedRect(rect, args.roundRadius, args.roundRadius);
|
||||
} else {
|
||||
p.drawEllipse(rect);
|
||||
}
|
||||
|
||||
if (_uploading) {
|
||||
const auto cancelOpacity = _cancelShown.value(
|
||||
_over ? 1. : 0.);
|
||||
if (cancelOpacity > 0. && args.cancelIcon) {
|
||||
p.setOpacity(o * hideOpacity * cancelOpacity);
|
||||
args.cancelIcon->paintInCenter(p, rect);
|
||||
}
|
||||
}
|
||||
|
||||
p.setOpacity(o * hideOpacity);
|
||||
const auto line = float64(args.lineWidth);
|
||||
const auto margin = float64(args.margin);
|
||||
const auto arc = QRectF(rect) - QMarginsF(
|
||||
margin,
|
||||
margin,
|
||||
margin,
|
||||
margin);
|
||||
_radial.draw(p, arc, line, args.progressFg);
|
||||
|
||||
p.setOpacity(o);
|
||||
}
|
||||
|
||||
void UploadProgressOverlay::radialAnimationCallback(crl::time now) {
|
||||
const auto updated = _radial.update(
|
||||
_progress,
|
||||
!_uploading,
|
||||
now);
|
||||
if (!anim::Disabled() || updated || _radial.animating()) {
|
||||
_update();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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 "ui/effects/radial_animation.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class UploadProgressOverlay final {
|
||||
public:
|
||||
struct PaintArgs {
|
||||
int lineWidth = 3;
|
||||
int margin = 8;
|
||||
style::color progressFg;
|
||||
style::color overlayFg;
|
||||
const style::icon *cancelIcon = nullptr;
|
||||
float64 roundRadius = 0.;
|
||||
};
|
||||
|
||||
UploadProgressOverlay(
|
||||
not_null<QWidget*> parent,
|
||||
Fn<void()> update);
|
||||
|
||||
void start();
|
||||
void stop(Fn<void()> done = nullptr);
|
||||
void fail(Fn<void()> done = nullptr);
|
||||
void setProgress(float64 progress);
|
||||
void setOver(bool over);
|
||||
|
||||
[[nodiscard]] bool shown() const;
|
||||
[[nodiscard]] bool uploading() const;
|
||||
|
||||
void paint(QPainter &p, QRect rect, const PaintArgs &args);
|
||||
|
||||
private:
|
||||
void radialAnimationCallback(crl::time now);
|
||||
void finish(Fn<void()> done);
|
||||
void proceedWithStop();
|
||||
|
||||
const not_null<QWidget*> _parent;
|
||||
const Fn<void()> _update;
|
||||
RadialAnimation _radial;
|
||||
Animations::Simple _cancelShown;
|
||||
Animations::Simple _hiding;
|
||||
Fn<void()> _hidingDone;
|
||||
bool _uploading = false;
|
||||
bool _stopping = false;
|
||||
bool _over = false;
|
||||
float64 _progress = 0.;
|
||||
crl::time _startedAt = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
@@ -494,6 +494,8 @@ PRIVATE
|
||||
ui/effects/snowflakes.h
|
||||
ui/effects/toggle_arrow.cpp
|
||||
ui/effects/toggle_arrow.h
|
||||
ui/effects/upload_progress_overlay.cpp
|
||||
ui/effects/upload_progress_overlay.h
|
||||
ui/effects/ttl_icon.cpp
|
||||
ui/effects/ttl_icon.h
|
||||
ui/search_field_controller.cpp
|
||||
|
||||
Reference in New Issue
Block a user