From b9b6d4dba15bd91c6cb2ea674dcaad444b69ef42 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 22 Dec 2022 19:02:51 +0400 Subject: [PATCH] Show toast after photo suggestion is accepted. --- Telegram/SourceFiles/api/api_peer_photo.cpp | 50 +++++--- Telegram/SourceFiles/api/api_peer_photo.h | 15 ++- .../SourceFiles/core/local_url_handlers.cpp | 5 +- .../history/view/media/history_view_photo.cpp | 26 ++-- .../history/view/media/history_view_photo.h | 5 + .../media/history_view_userpic_suggestion.cpp | 111 ++++++++++++++++-- Telegram/lib_ui | 2 +- 7 files changed, 173 insertions(+), 41 deletions(-) diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp index 5a627f546..e30b8e4ac 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.cpp +++ b/Telegram/SourceFiles/api/api_peer_photo.cpp @@ -114,17 +114,21 @@ PeerPhoto::PeerPhoto(not_null api) }); } -void PeerPhoto::upload(not_null peer, QImage &&image) { - upload(peer, std::move(image), UploadType::Default); +void PeerPhoto::upload( + not_null peer, + QImage &&image, + Fn done) { + upload(peer, std::move(image), UploadType::Default, std::move(done)); } void PeerPhoto::uploadFallback(not_null peer, QImage &&image) { - upload(peer, std::move(image), UploadType::Fallback); + upload(peer, std::move(image), UploadType::Fallback, nullptr); } void PeerPhoto::updateSelf( not_null photo, - Data::FileOrigin origin) { + Data::FileOrigin origin, + Fn done) { const auto send = [=](auto resend) -> void { const auto usedFileReference = photo->fileReference(); _api.request(MTPphotos_UpdateProfilePhoto( @@ -135,6 +139,9 @@ void PeerPhoto::updateSelf( _session->data().processPhoto(data.vphoto()); _session->data().processUsers(data.vusers()); }); + if (done) { + done(); + } }).fail([=](const MTP::Error &error) { if (error.code() == 400 && error.type().startsWith(u"FILE_REFERENCE_"_q)) { @@ -153,7 +160,8 @@ void PeerPhoto::updateSelf( void PeerPhoto::upload( not_null peer, QImage &&image, - UploadType type) { + UploadType type, + Fn done) { peer = peer->migrateToOrMe(); const auto ready = PreparePeerPhoto( _api.instance().mainDcId(), @@ -169,20 +177,16 @@ void PeerPhoto::upload( [](const auto &pair) { return pair.second.peer; }); if (already != end(_uploads)) { _session->uploader().cancel(already->first); - _suggestions.remove(already->first); _uploads.erase(already); } _uploads.emplace( fakeId, - UploadValue{ peer, type == UploadType::Fallback }); - if (type == UploadType::Suggestion) { - _suggestions.emplace(fakeId); - } + UploadValue{ peer, type, std::move(done) }); _session->uploader().uploadMedia(fakeId, ready); } void PeerPhoto::suggest(not_null peer, QImage &&image) { - upload(peer, std::move(image), UploadType::Suggestion); + upload(peer, std::move(image), UploadType::Suggestion, nullptr); } void PeerPhoto::clear(not_null photo) { @@ -285,20 +289,22 @@ void PeerPhoto::set(not_null peer, not_null photo) { void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) { const auto maybeUploadValue = _uploads.take(msgId); - const auto suggestion = _suggestions.contains(msgId); - _suggestions.remove(msgId); if (!maybeUploadValue) { return; } 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) { _session->updates().applyUpdates(result); + if (done) { + done(); + } }; if (peer->isSelf()) { _api.request(MTPphotos_UploadProfilePhoto( MTP_flags(MTPphotos_UploadProfilePhoto::Flag::f_file - | (fallback + | ((type == UploadType::Fallback) ? MTPphotos_UploadProfilePhoto::Flag::f_fallback : MTPphotos_UploadProfilePhoto::Flags(0))), file, @@ -308,11 +314,14 @@ void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) { const auto photoId = _session->data().processPhoto( result.data().vphoto())->id; _session->data().processUsers(result.data().vusers()); - if (fallback) { + if (type == UploadType::Fallback) { _session->storage().add(Storage::UserPhotosSetBack( peerToUser(peer->id), photoId)); } + if (done) { + done(); + } }).send(); } else if (const auto chat = peer->asChat()) { 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; _api.request(MTPphotos_UploadContactProfilePhoto( MTP_flags(Flag::f_file - | (suggestion ? Flag::f_suggest : Flag::f_save)), + | ((type == UploadType::Suggestion) + ? Flag::f_suggest + : Flag::f_save)), user->inputUser, file, MTPInputFile(), // video @@ -348,9 +359,12 @@ void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) { _session->data().processPhoto(data.vphoto()); _session->data().processUsers(data.vusers()); }); - if (!suggestion) { + if (type != UploadType::Suggestion) { user->updateFullForced(); } + if (done) { + done(); + } }).send(); } } diff --git a/Telegram/SourceFiles/api/api_peer_photo.h b/Telegram/SourceFiles/api/api_peer_photo.h index 3e82b7779..ba6374421 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.h +++ b/Telegram/SourceFiles/api/api_peer_photo.h @@ -28,11 +28,15 @@ public: using UserPhotoId = PhotoId; explicit PeerPhoto(not_null api); - void upload(not_null peer, QImage &&image); + void upload( + not_null peer, + QImage &&image, + Fn done = nullptr); void uploadFallback(not_null peer, QImage &&image); void updateSelf( not_null photo, - Data::FileOrigin origin); + Data::FileOrigin origin, + Fn done = nullptr); void suggest(not_null peer, QImage &&image); void clear(not_null photo); void clearPersonal(not_null user); @@ -59,18 +63,19 @@ private: void upload( not_null peer, QImage &&image, - UploadType type); + UploadType type, + Fn done); const not_null _session; MTP::Sender _api; struct UploadValue { not_null peer; - bool fallback = false; + UploadType type = UploadType::Default; + Fn done; }; base::flat_map _uploads; - base::flat_set _suggestions; base::flat_map, mtpRequestId> _userPhotosRequests; diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index f8ffa6989..0b3aa1872 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_theme_editor_box.h" // GenerateSlug. #include "payments/payments_checkout_process.h" #include "settings/settings_common.h" +#include "settings/settings_information.h" #include "settings/settings_global_ttl.h" #include "settings/settings_folders.h" #include "settings/settings_main.h" @@ -488,6 +489,8 @@ bool ResolveSettings( return ::Settings::ChangePhone::Id(); } else if (section == u"auto_delete"_q) { return ::Settings::GlobalTTLId(); + } else if (section == u"information"_q) { + return ::Settings::Information::Id(); } return ::Settings::Main::Id(); }(); @@ -846,7 +849,7 @@ const std::vector &LocalUrlHandlers() { 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 }, { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index b40c6bb13..0d5ec5d10 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -481,9 +481,10 @@ QImage Photo::prepareImageCacheWithLarge(QSize outer, Image *large) const { void Photo::paintUserpicFrame( Painter &p, - const PaintContext &context, - QPoint photoPosition) const { - const auto autoplay = _data->videoCanBePlayed() && videoAutoplayEnabled(); + QPoint photoPosition, + bool markFrameShown) const { + const auto autoplay = _data->videoCanBePlayed() + && videoAutoplayEnabled(); const auto startPlay = autoplay && !_streamed; if (startPlay) { const_cast(this)->playAnimation(true); @@ -493,8 +494,6 @@ void Photo::paintUserpicFrame( const auto size = QSize(width(), height()); const auto rect = QRect(photoPosition, size); - const auto st = context.st; - const auto sti = context.imageStyle(); const auto forum = _parent->data()->history()->isForum(); if (_streamed @@ -527,7 +526,7 @@ void Photo::paintUserpicFrame( } else { _streamed->frozenFrame = QImage(); p.drawImage(rect, _streamed->instance.frame(request)); - if (!context.paused) { + if (markFrameShown) { _streamed->instance.markFrameShown(); } } @@ -535,10 +534,23 @@ void Photo::paintUserpicFrame( } validateUserpicImageCache(size, forum); p.drawImage(rect, _imageCache); +} + +void Photo::paintUserpicFrame( + Painter &p, + const PaintContext &context, + QPoint photoPosition) const { + paintUserpicFrame(p, photoPosition, !context.paused); if (_data->videoCanBePlayed() && !_streamed) { + const auto st = context.st; + const auto sti = context.imageStyle(); 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); if (context.selected()) { p.setBrush(st->msgDateImgBgSelected()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 6ba555fa1..9440d1421 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -63,6 +63,11 @@ public: } void showPhoto(FullMsgId id); + void paintUserpicFrame( + Painter &p, + QPoint photoPosition, + bool markFrameShown) const; + QSize sizeForGroupingOptimal(int maxWidth) const override; QSize sizeForGrouping(int width) const override; void drawGrouped( diff --git a/Telegram/SourceFiles/history/view/media/history_view_userpic_suggestion.cpp b/Telegram/SourceFiles/history/view/media/history_view_userpic_suggestion.cpp index 4b62605db..ca3fca736 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_userpic_suggestion.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_userpic_suggestion.cpp @@ -24,6 +24,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "window/window_session_controller.h" #include "ui/boxes/confirm_box.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/painter.h" #include "mainwidget.h" #include "apiwrap.h" @@ -34,23 +36,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { +constexpr auto kToastDuration = 5 * crl::time(1000); + void ShowUserpicSuggestion( not_null controller, const std::shared_ptr &media, const FullMsgId itemId, - not_null peer) { + not_null peer, + Fn setDone) { const auto photo = media->owner(); const auto from = peer->asUser(); const auto name = (from && !from->firstName.isEmpty()) ? from->firstName : peer->name(); if (photo->hasVideo()) { - const auto done = [=] { + const auto done = [=](Fn close) { using namespace Settings; const auto session = &photo->session(); auto &peerPhotos = session->api().peerPhoto(); - peerPhotos.updateSelf(photo, itemId); - controller->showSettings(Information::Id()); + peerPhotos.updateSelf(photo, itemId, setDone); + close(); }; controller->show(Ui::MakeConfirmBox({ .text = tr::lng_profile_accept_video_sure( @@ -72,11 +77,10 @@ void ShowUserpicSuggestion( auto &peerPhotos = session->api().peerPhoto(); if (original->size() == image.size() && original->constBits() == image.constBits()) { - peerPhotos.updateSelf(photo, itemId); + peerPhotos.updateSelf(photo, itemId, setDone); } else { - peerPhotos.upload(user, std::move(image)); + peerPhotos.upload(user, std::move(image), setDone); } - controller->showSettings(Information::Id()); }; using namespace Editor; PrepareProfilePhoto( @@ -96,7 +100,86 @@ void ShowUserpicSuggestion( } } +[[nodiscard]] QImage GrabUserpicFrame(base::weak_ptr 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 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(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(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 ShowSetToastCallback( + base::weak_ptr weak, + QImage frame) { + return [weak = std::move(weak), frame = std::move(frame)] { + if (const auto strong = weak.get()) { + ShowSetToast(strong, frame); + } + }; +} + } // namespace + UserpicSuggestion::UserpicSuggestion( not_null parent, not_null chat, @@ -138,10 +221,15 @@ ClickHandlerPtr UserpicSuggestion::createViewLink() { const auto photo = _photo.getPhoto(); const auto itemId = _photo.parent()->data()->fullId(); 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); }); return std::make_shared([=](ClickContext context) { + auto frame = GrabUserpicFrame(weak); + if (frame.isNull()) { + return; + } const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { const auto media = photo->activeMediaView(); @@ -150,7 +238,12 @@ ClickHandlerPtr UserpicSuggestion::createViewLink() { PhotoOpenClickHandler(photo, show, itemId).onClick( context); } else { - ShowUserpicSuggestion(controller, media, itemId, peer); + ShowUserpicSuggestion( + controller, + media, + itemId, + peer, + ShowSetToastCallback(controller, std::move(frame))); } } else if (!photo->loading()) { PhotoSaveClickHandler(photo, itemId).onClick(context); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index fbdc6ed5a..d079108e2 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit fbdc6ed5ac791890d87ca7f22b668ceca47d7684 +Subproject commit d079108e2949db26cd2d83108559a71f70fe52e2