Show toast after photo suggestion is accepted.

This commit is contained in:
John Preston 2022-12-22 19:02:51 +04:00
parent 349fbeeb23
commit b9b6d4dba1
7 changed files with 173 additions and 41 deletions

View file

@ -114,17 +114,21 @@ PeerPhoto::PeerPhoto(not_null<ApiWrap*> api)
}); });
} }
void PeerPhoto::upload(not_null<PeerData*> peer, QImage &&image) { void PeerPhoto::upload(
upload(peer, std::move(image), UploadType::Default); not_null<PeerData*> peer,
QImage &&image,
Fn<void()> done) {
upload(peer, std::move(image), UploadType::Default, std::move(done));
} }
void PeerPhoto::uploadFallback(not_null<PeerData*> peer, QImage &&image) { void PeerPhoto::uploadFallback(not_null<PeerData*> peer, QImage &&image) {
upload(peer, std::move(image), UploadType::Fallback); upload(peer, std::move(image), UploadType::Fallback, nullptr);
} }
void PeerPhoto::updateSelf( void PeerPhoto::updateSelf(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
Data::FileOrigin origin) { Data::FileOrigin origin,
Fn<void()> done) {
const auto send = [=](auto resend) -> void { const auto send = [=](auto resend) -> void {
const auto usedFileReference = photo->fileReference(); const auto usedFileReference = photo->fileReference();
_api.request(MTPphotos_UpdateProfilePhoto( _api.request(MTPphotos_UpdateProfilePhoto(
@ -135,6 +139,9 @@ void PeerPhoto::updateSelf(
_session->data().processPhoto(data.vphoto()); _session->data().processPhoto(data.vphoto());
_session->data().processUsers(data.vusers()); _session->data().processUsers(data.vusers());
}); });
if (done) {
done();
}
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
if (error.code() == 400 if (error.code() == 400
&& error.type().startsWith(u"FILE_REFERENCE_"_q)) { && error.type().startsWith(u"FILE_REFERENCE_"_q)) {
@ -153,7 +160,8 @@ void PeerPhoto::updateSelf(
void PeerPhoto::upload( void PeerPhoto::upload(
not_null<PeerData*> peer, not_null<PeerData*> peer,
QImage &&image, QImage &&image,
UploadType type) { UploadType type,
Fn<void()> done) {
peer = peer->migrateToOrMe(); peer = peer->migrateToOrMe();
const auto ready = PreparePeerPhoto( const auto ready = PreparePeerPhoto(
_api.instance().mainDcId(), _api.instance().mainDcId(),
@ -169,20 +177,16 @@ void PeerPhoto::upload(
[](const auto &pair) { return pair.second.peer; }); [](const auto &pair) { return pair.second.peer; });
if (already != end(_uploads)) { if (already != end(_uploads)) {
_session->uploader().cancel(already->first); _session->uploader().cancel(already->first);
_suggestions.remove(already->first);
_uploads.erase(already); _uploads.erase(already);
} }
_uploads.emplace( _uploads.emplace(
fakeId, fakeId,
UploadValue{ peer, type == UploadType::Fallback }); UploadValue{ peer, type, std::move(done) });
if (type == UploadType::Suggestion) {
_suggestions.emplace(fakeId);
}
_session->uploader().uploadMedia(fakeId, ready); _session->uploader().uploadMedia(fakeId, ready);
} }
void PeerPhoto::suggest(not_null<PeerData*> peer, QImage &&image) { void PeerPhoto::suggest(not_null<PeerData*> peer, QImage &&image) {
upload(peer, std::move(image), UploadType::Suggestion); upload(peer, std::move(image), UploadType::Suggestion, nullptr);
} }
void PeerPhoto::clear(not_null<PhotoData*> photo) { void PeerPhoto::clear(not_null<PhotoData*> photo) {
@ -285,20 +289,22 @@ void PeerPhoto::set(not_null<PeerData*> peer, not_null<PhotoData*> photo) {
void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) { void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) {
const auto maybeUploadValue = _uploads.take(msgId); const auto maybeUploadValue = _uploads.take(msgId);
const auto suggestion = _suggestions.contains(msgId);
_suggestions.remove(msgId);
if (!maybeUploadValue) { if (!maybeUploadValue) {
return; return;
} }
const auto peer = maybeUploadValue->peer; const auto peer = maybeUploadValue->peer;
const auto fallback = maybeUploadValue->fallback; const auto type = maybeUploadValue->type;
const auto done = maybeUploadValue->done;
const auto applier = [=](const MTPUpdates &result) { const auto applier = [=](const MTPUpdates &result) {
_session->updates().applyUpdates(result); _session->updates().applyUpdates(result);
if (done) {
done();
}
}; };
if (peer->isSelf()) { if (peer->isSelf()) {
_api.request(MTPphotos_UploadProfilePhoto( _api.request(MTPphotos_UploadProfilePhoto(
MTP_flags(MTPphotos_UploadProfilePhoto::Flag::f_file MTP_flags(MTPphotos_UploadProfilePhoto::Flag::f_file
| (fallback | ((type == UploadType::Fallback)
? MTPphotos_UploadProfilePhoto::Flag::f_fallback ? MTPphotos_UploadProfilePhoto::Flag::f_fallback
: MTPphotos_UploadProfilePhoto::Flags(0))), : MTPphotos_UploadProfilePhoto::Flags(0))),
file, file,
@ -308,11 +314,14 @@ void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) {
const auto photoId = _session->data().processPhoto( const auto photoId = _session->data().processPhoto(
result.data().vphoto())->id; result.data().vphoto())->id;
_session->data().processUsers(result.data().vusers()); _session->data().processUsers(result.data().vusers());
if (fallback) { if (type == UploadType::Fallback) {
_session->storage().add(Storage::UserPhotosSetBack( _session->storage().add(Storage::UserPhotosSetBack(
peerToUser(peer->id), peerToUser(peer->id),
photoId)); photoId));
} }
if (done) {
done();
}
}).send(); }).send();
} else if (const auto chat = peer->asChat()) { } else if (const auto chat = peer->asChat()) {
const auto history = _session->data().history(chat); const auto history = _session->data().history(chat);
@ -338,7 +347,9 @@ void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) {
using Flag = MTPphotos_UploadContactProfilePhoto::Flag; using Flag = MTPphotos_UploadContactProfilePhoto::Flag;
_api.request(MTPphotos_UploadContactProfilePhoto( _api.request(MTPphotos_UploadContactProfilePhoto(
MTP_flags(Flag::f_file MTP_flags(Flag::f_file
| (suggestion ? Flag::f_suggest : Flag::f_save)), | ((type == UploadType::Suggestion)
? Flag::f_suggest
: Flag::f_save)),
user->inputUser, user->inputUser,
file, file,
MTPInputFile(), // video MTPInputFile(), // video
@ -348,9 +359,12 @@ void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) {
_session->data().processPhoto(data.vphoto()); _session->data().processPhoto(data.vphoto());
_session->data().processUsers(data.vusers()); _session->data().processUsers(data.vusers());
}); });
if (!suggestion) { if (type != UploadType::Suggestion) {
user->updateFullForced(); user->updateFullForced();
} }
if (done) {
done();
}
}).send(); }).send();
} }
} }

View file

@ -28,11 +28,15 @@ public:
using UserPhotoId = PhotoId; using UserPhotoId = PhotoId;
explicit PeerPhoto(not_null<ApiWrap*> api); explicit PeerPhoto(not_null<ApiWrap*> api);
void upload(not_null<PeerData*> peer, QImage &&image); void upload(
not_null<PeerData*> peer,
QImage &&image,
Fn<void()> done = nullptr);
void uploadFallback(not_null<PeerData*> peer, QImage &&image); void uploadFallback(not_null<PeerData*> peer, QImage &&image);
void updateSelf( void updateSelf(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
Data::FileOrigin origin); Data::FileOrigin origin,
Fn<void()> done = nullptr);
void suggest(not_null<PeerData*> peer, QImage &&image); void suggest(not_null<PeerData*> peer, QImage &&image);
void clear(not_null<PhotoData*> photo); void clear(not_null<PhotoData*> photo);
void clearPersonal(not_null<UserData*> user); void clearPersonal(not_null<UserData*> user);
@ -59,18 +63,19 @@ private:
void upload( void upload(
not_null<PeerData*> peer, not_null<PeerData*> peer,
QImage &&image, QImage &&image,
UploadType type); UploadType type,
Fn<void()> done);
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
MTP::Sender _api; MTP::Sender _api;
struct UploadValue { struct UploadValue {
not_null<PeerData*> peer; not_null<PeerData*> peer;
bool fallback = false; UploadType type = UploadType::Default;
Fn<void()> done;
}; };
base::flat_map<FullMsgId, UploadValue> _uploads; base::flat_map<FullMsgId, UploadValue> _uploads;
base::flat_set<FullMsgId> _suggestions;
base::flat_map<not_null<UserData*>, mtpRequestId> _userPhotosRequests; base::flat_map<not_null<UserData*>, mtpRequestId> _userPhotosRequests;

View file

@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/themes/window_theme_editor_box.h" // GenerateSlug. #include "window/themes/window_theme_editor_box.h" // GenerateSlug.
#include "payments/payments_checkout_process.h" #include "payments/payments_checkout_process.h"
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "settings/settings_information.h"
#include "settings/settings_global_ttl.h" #include "settings/settings_global_ttl.h"
#include "settings/settings_folders.h" #include "settings/settings_folders.h"
#include "settings/settings_main.h" #include "settings/settings_main.h"
@ -488,6 +489,8 @@ bool ResolveSettings(
return ::Settings::ChangePhone::Id(); return ::Settings::ChangePhone::Id();
} else if (section == u"auto_delete"_q) { } else if (section == u"auto_delete"_q) {
return ::Settings::GlobalTTLId(); return ::Settings::GlobalTTLId();
} else if (section == u"information"_q) {
return ::Settings::Information::Id();
} }
return ::Settings::Main::Id(); return ::Settings::Main::Id();
}(); }();
@ -846,7 +849,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
ResolvePrivatePost ResolvePrivatePost
}, },
{ {
u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete)?$"_q, u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information)?$"_q,
ResolveSettings ResolveSettings
}, },
{ {

View file

@ -481,9 +481,10 @@ QImage Photo::prepareImageCacheWithLarge(QSize outer, Image *large) const {
void Photo::paintUserpicFrame( void Photo::paintUserpicFrame(
Painter &p, Painter &p,
const PaintContext &context, QPoint photoPosition,
QPoint photoPosition) const { bool markFrameShown) const {
const auto autoplay = _data->videoCanBePlayed() && videoAutoplayEnabled(); const auto autoplay = _data->videoCanBePlayed()
&& videoAutoplayEnabled();
const auto startPlay = autoplay && !_streamed; const auto startPlay = autoplay && !_streamed;
if (startPlay) { if (startPlay) {
const_cast<Photo*>(this)->playAnimation(true); const_cast<Photo*>(this)->playAnimation(true);
@ -493,8 +494,6 @@ void Photo::paintUserpicFrame(
const auto size = QSize(width(), height()); const auto size = QSize(width(), height());
const auto rect = QRect(photoPosition, size); const auto rect = QRect(photoPosition, size);
const auto st = context.st;
const auto sti = context.imageStyle();
const auto forum = _parent->data()->history()->isForum(); const auto forum = _parent->data()->history()->isForum();
if (_streamed if (_streamed
@ -527,7 +526,7 @@ void Photo::paintUserpicFrame(
} else { } else {
_streamed->frozenFrame = QImage(); _streamed->frozenFrame = QImage();
p.drawImage(rect, _streamed->instance.frame(request)); p.drawImage(rect, _streamed->instance.frame(request));
if (!context.paused) { if (markFrameShown) {
_streamed->instance.markFrameShown(); _streamed->instance.markFrameShown();
} }
} }
@ -535,10 +534,23 @@ void Photo::paintUserpicFrame(
} }
validateUserpicImageCache(size, forum); validateUserpicImageCache(size, forum);
p.drawImage(rect, _imageCache); p.drawImage(rect, _imageCache);
}
void Photo::paintUserpicFrame(
Painter &p,
const PaintContext &context,
QPoint photoPosition) const {
paintUserpicFrame(p, photoPosition, !context.paused);
if (_data->videoCanBePlayed() && !_streamed) { if (_data->videoCanBePlayed() && !_streamed) {
const auto st = context.st;
const auto sti = context.imageStyle();
const auto innerSize = st::msgFileLayout.thumbSize; const auto innerSize = st::msgFileLayout.thumbSize;
auto inner = QRect(rect.x() + (rect.width() - innerSize) / 2, rect.y() + (rect.height() - innerSize) / 2, innerSize, innerSize); auto inner = QRect(
photoPosition.x() + (width() - innerSize) / 2,
photoPosition.y() + (height() - innerSize) / 2,
innerSize,
innerSize);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
if (context.selected()) { if (context.selected()) {
p.setBrush(st->msgDateImgBgSelected()); p.setBrush(st->msgDateImgBgSelected());

View file

@ -63,6 +63,11 @@ public:
} }
void showPhoto(FullMsgId id); void showPhoto(FullMsgId id);
void paintUserpicFrame(
Painter &p,
QPoint photoPosition,
bool markFrameShown) const;
QSize sizeForGroupingOptimal(int maxWidth) const override; QSize sizeForGroupingOptimal(int maxWidth) const override;
QSize sizeForGrouping(int width) const override; QSize sizeForGrouping(int width) const override;
void drawGrouped( void drawGrouped(

View file

@ -24,6 +24,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "apiwrap.h" #include "apiwrap.h"
@ -34,23 +36,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView { namespace HistoryView {
namespace { namespace {
constexpr auto kToastDuration = 5 * crl::time(1000);
void ShowUserpicSuggestion( void ShowUserpicSuggestion(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
const std::shared_ptr<Data::PhotoMedia> &media, const std::shared_ptr<Data::PhotoMedia> &media,
const FullMsgId itemId, const FullMsgId itemId,
not_null<PeerData*> peer) { not_null<PeerData*> peer,
Fn<void()> setDone) {
const auto photo = media->owner(); const auto photo = media->owner();
const auto from = peer->asUser(); const auto from = peer->asUser();
const auto name = (from && !from->firstName.isEmpty()) const auto name = (from && !from->firstName.isEmpty())
? from->firstName ? from->firstName
: peer->name(); : peer->name();
if (photo->hasVideo()) { if (photo->hasVideo()) {
const auto done = [=] { const auto done = [=](Fn<void()> close) {
using namespace Settings; using namespace Settings;
const auto session = &photo->session(); const auto session = &photo->session();
auto &peerPhotos = session->api().peerPhoto(); auto &peerPhotos = session->api().peerPhoto();
peerPhotos.updateSelf(photo, itemId); peerPhotos.updateSelf(photo, itemId, setDone);
controller->showSettings(Information::Id()); close();
}; };
controller->show(Ui::MakeConfirmBox({ controller->show(Ui::MakeConfirmBox({
.text = tr::lng_profile_accept_video_sure( .text = tr::lng_profile_accept_video_sure(
@ -72,11 +77,10 @@ void ShowUserpicSuggestion(
auto &peerPhotos = session->api().peerPhoto(); auto &peerPhotos = session->api().peerPhoto();
if (original->size() == image.size() if (original->size() == image.size()
&& original->constBits() == image.constBits()) { && original->constBits() == image.constBits()) {
peerPhotos.updateSelf(photo, itemId); peerPhotos.updateSelf(photo, itemId, setDone);
} else { } else {
peerPhotos.upload(user, std::move(image)); peerPhotos.upload(user, std::move(image), setDone);
} }
controller->showSettings(Information::Id());
}; };
using namespace Editor; using namespace Editor;
PrepareProfilePhoto( PrepareProfilePhoto(
@ -96,7 +100,86 @@ void ShowUserpicSuggestion(
} }
} }
[[nodiscard]] QImage GrabUserpicFrame(base::weak_ptr<Photo> photo) {
const auto strong = photo.get();
if (!strong || !strong->width() || !strong->height()) {
return {};
}
const auto ratio = style::DevicePixelRatio();
auto frame = QImage(
QSize(strong->width(), strong->height()) * ratio,
QImage::Format_ARGB32_Premultiplied);
frame.fill(Qt::transparent);
frame.setDevicePixelRatio(ratio);
auto p = Painter(&frame);
strong->paintUserpicFrame(p, QPoint(0, 0), false);
p.end();
return frame;
}
void ShowSetToast(
not_null<Window::SessionController*> controller,
const QImage &frame) {
const auto text = Ui::Text::Bold(
tr::lng_profile_changed_photo_title(tr::now)
).append('\n').append(
tr::lng_profile_changed_photo_about(
tr::now,
lt_link,
Ui::Text::Link(
tr::lng_profile_changed_photo_link(tr::now),
u"tg://settings/information"_q),
Ui::Text::WithEntities)
);
auto st = std::make_shared<style::Toast>(st::historyPremiumToast);
const auto skip = st->padding.top();
const auto size = st->style.font->height * 2;
const auto ratio = style::DevicePixelRatio();
auto copy = frame.scaled(
QSize(size, size) * ratio,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
copy.setDevicePixelRatio(ratio);
st->padding.setLeft(skip + size + skip);
st->palette.linkFg = st->palette.selectLinkFg = st::mediaviewTextLinkFg;
const auto parent = Window::Show(controller).toastParent();
const auto weak = Ui::Toast::Show(parent, {
.text = text,
.st = st.get(),
.durationMs = kToastDuration,
.multiline = true,
.dark = true,
.slideSide = RectPart::Bottom,
});
if (const auto strong = weak.get()) {
const auto widget = strong->widget();
widget->lifetime().add([st = std::move(st)] {});
const auto preview = Ui::CreateChild<Ui::RpWidget>(widget.get());
preview->moveToLeft(skip, skip);
preview->resize(size, size);
preview->show();
preview->setAttribute(Qt::WA_TransparentForMouseEvents);
preview->paintRequest(
) | rpl::start_with_next([=] {
QPainter(preview).drawImage(0, 0, copy);
}, preview->lifetime());
}
}
[[nodiscard]] Fn<void()> ShowSetToastCallback(
base::weak_ptr<Window::SessionController> weak,
QImage frame) {
return [weak = std::move(weak), frame = std::move(frame)] {
if (const auto strong = weak.get()) {
ShowSetToast(strong, frame);
}
};
}
} // namespace } // namespace
UserpicSuggestion::UserpicSuggestion( UserpicSuggestion::UserpicSuggestion(
not_null<Element*> parent, not_null<Element*> parent,
not_null<PeerData*> chat, not_null<PeerData*> chat,
@ -138,10 +221,15 @@ ClickHandlerPtr UserpicSuggestion::createViewLink() {
const auto photo = _photo.getPhoto(); const auto photo = _photo.getPhoto();
const auto itemId = _photo.parent()->data()->fullId(); const auto itemId = _photo.parent()->data()->fullId();
const auto peer = _photo.parent()->data()->history()->peer; const auto peer = _photo.parent()->data()->history()->peer;
const auto show = crl::guard(&_photo, [=](FullMsgId id) { const auto weak = base::make_weak(&_photo);
const auto show = crl::guard(weak, [=](FullMsgId id) {
_photo.showPhoto(id); _photo.showPhoto(id);
}); });
return std::make_shared<LambdaClickHandler>([=](ClickContext context) { return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
auto frame = GrabUserpicFrame(weak);
if (frame.isNull()) {
return;
}
const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) { if (const auto controller = my.sessionWindow.get()) {
const auto media = photo->activeMediaView(); const auto media = photo->activeMediaView();
@ -150,7 +238,12 @@ ClickHandlerPtr UserpicSuggestion::createViewLink() {
PhotoOpenClickHandler(photo, show, itemId).onClick( PhotoOpenClickHandler(photo, show, itemId).onClick(
context); context);
} else { } else {
ShowUserpicSuggestion(controller, media, itemId, peer); ShowUserpicSuggestion(
controller,
media,
itemId,
peer,
ShowSetToastCallback(controller, std::move(frame)));
} }
} else if (!photo->loading()) { } else if (!photo->loading()) {
PhotoSaveClickHandler(photo, itemId).onClick(context); PhotoSaveClickHandler(photo, itemId).onClick(context);

@ -1 +1 @@
Subproject commit fbdc6ed5ac791890d87ca7f22b668ceca47d7684 Subproject commit d079108e2949db26cd2d83108559a71f70fe52e2