diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index e051d4013..92cfdf6aa 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -466,6 +466,7 @@ void GroupInfoBox::prepare() { _photo.create( this, + &_navigation->parentController()->window(), ((_type == Type::Channel) ? tr::lng_create_channel_crop : tr::lng_create_group_crop)(tr::now), diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index b9a2bcd33..35b92a931 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -495,6 +495,7 @@ object_ptr Controller::createPhotoEdit() { _wrap, object_ptr( _wrap, + &_navigation->parentController()->window(), _peer, Ui::UserpicButton::Role::ChangePhoto, st::defaultUserpicButton), diff --git a/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp b/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp index 7ddc06246..598b7b825 100644 --- a/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp @@ -7,12 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "editor/photo_editor_layer_widget.h" +#include "app.h" // readImage +#include "boxes/confirm_box.h" // InformBox #include "editor/photo_editor.h" #include "storage/storage_media_prepare.h" #include "ui/chat/attach/attach_prepare.h" +#include "window/window_controller.h" #include "window/window_session_controller.h" +#include "styles/style_boxes.h" namespace Editor { +namespace { + +constexpr auto kProfilePhotoSize = 640; + +} // namespace void OpenWithPreparedFile( not_null parent, @@ -48,6 +57,80 @@ void OpenWithPreparedFile( Ui::LayerOption::KeepOther); } +void PrepareProfilePhoto( + not_null parent, + not_null controller, + Fn &&doneCallback) { + const auto resizeToMinSize = [=]( + QImage &&image, + Qt::AspectRatioMode mode) { + const auto &minSize = kProfilePhotoSize; + if ((image.width() < minSize) || (image.height() < minSize)) { + return image.scaled( + minSize, + minSize, + mode, + Qt::SmoothTransformation); + } + return std::move(image); + }; + + const auto callback = [=, done = std::move(doneCallback)]( + const FileDialog::OpenResult &result) { + if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { + return; + } + + auto image = result.remoteContent.isEmpty() + ? App::readImage(result.paths.front()) + : App::readImage(result.remoteContent); + if (image.isNull() + || (image.width() > (10 * image.height())) + || (image.height() > (10 * image.width()))) { + controller->show(Box(tr::lng_bad_photo(tr::now))); + return; + } + image = resizeToMinSize( + std::move(image), + Qt::KeepAspectRatioByExpanding); + const auto fileImage = std::make_shared(std::move(image)); + + auto applyModifications = [=, done = std::move(done)]( + const PhotoModifications &mods) { + done(resizeToMinSize( + ImageModified(fileImage->original(), mods), + Qt::KeepAspectRatio)); + }; + + auto crop = [&] { + const auto &i = fileImage; + const auto minSide = std::min(i->width(), i->height()); + return QRect( + (i->width() - minSide) / 2, + (i->height() - minSide) / 2, + minSide, + minSide); + }(); + + controller->showLayer( + std::make_unique( + parent, + controller, + fileImage, + PhotoModifications{ .crop = std::move(crop) }, + std::move(applyModifications), + EditorData{ + .cropType = EditorData::CropType::Ellipse, + .keepAspectRatio = true, }), + Ui::LayerOption::KeepOther); + }; + FileDialog::GetOpenPath( + parent.get(), + tr::lng_choose_image(tr::now), + FileDialog::ImagesOrAllFilter(), + crl::guard(parent, callback)); +} + LayerWidget::LayerWidget( not_null parent, not_null window, diff --git a/Telegram/SourceFiles/editor/photo_editor_layer_widget.h b/Telegram/SourceFiles/editor/photo_editor_layer_widget.h index 8b8041c9f..e1c0b2b89 100644 --- a/Telegram/SourceFiles/editor/photo_editor_layer_widget.h +++ b/Telegram/SourceFiles/editor/photo_editor_layer_widget.h @@ -31,6 +31,11 @@ void OpenWithPreparedFile( int previewWidth, Fn &&doneCallback); +void PrepareProfilePhoto( + not_null parent, + not_null controller, + Fn &&doneCallback); + class PhotoEditor; class LayerWidget : public Ui::LayerWidget { diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 6fb26e7b3..a36245d60 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_changes.h" +#include "editor/photo_editor_layer_widget.h" #include "info/profile/info_profile_values.h" #include "info/info_controller.h" #include "info/info_memento.h" @@ -269,6 +270,13 @@ Cover::Cover( initViewers(std::move(title)); setupChildGeometry(); + + _userpic->uploadPhotoRequests( + ) | rpl::start_with_next([=] { + _peer->session().api().uploadPeerPhoto( + _peer, + _userpic->takeResultImage()); + }, _userpic->lifetime()); } void Cover::setupChildGeometry() { diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index 1822c6353..0bd2eb91c 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_information.h" +#include "editor/photo_editor_layer_widget.h" #include "settings/settings_common.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/padding_wrap.h" @@ -63,38 +64,13 @@ void SetupPhoto( st::settingsInfoPhotoSet); upload->setFullRadius(true); upload->addClickHandler([=] { - const auto filter = FileDialog::ImagesOrAllFilter(); - const auto callback = [=](const FileDialog::OpenResult &result) { - if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { - return; - } - - const auto image = result.remoteContent.isEmpty() - ? App::readImage(result.paths.front()) - : App::readImage(result.remoteContent); - if (image.isNull() - || image.width() > 10 * image.height() - || image.height() > 10 * image.width()) { - controller->show(Box(tr::lng_bad_photo(tr::now))); - return; - } - - auto box = Box( - image, - tr::lng_settings_crop_profile(tr::now)); - box->ready( - ) | rpl::start_with_next([=](QImage &&image) { - self->session().api().uploadPeerPhoto( - self, - std::move(image)); - }, box->lifetime()); - controller->show(std::move(box)); + auto callback = [=](QImage &&image) { + self->session().api().uploadPeerPhoto(self, std::move(image)); }; - FileDialog::GetOpenPath( + Editor::PrepareProfilePhoto( upload, - tr::lng_choose_image(tr::now), - filter, - crl::guard(upload, callback)); + &controller->window(), + std::move(callback)); }); rpl::combine( wrap->widthValue(), diff --git a/Telegram/SourceFiles/ui/special_buttons.cpp b/Telegram/SourceFiles/ui/special_buttons.cpp index eaf659a61..0dc4db565 100644 --- a/Telegram/SourceFiles/ui/special_buttons.cpp +++ b/Telegram/SourceFiles/ui/special_buttons.cpp @@ -29,9 +29,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "boxes/photo_crop_box.h" #include "boxes/confirm_box.h" +#include "editor/photo_editor_layer_widget.h" #include "media/streaming/media_streaming_instance.h" #include "media/streaming/media_streaming_player.h" #include "media/streaming/media_streaming_document.h" +#include "window/window_controller.h" #include "window/window_session_controller.h" #include "lang/lang_keys.h" #include "main/main_session.h" @@ -68,74 +70,6 @@ QPixmap CreateSquarePixmap(int width, Callback &&paintCallback) { return App::pixmapFromImageInPlace(std::move(image)); }; -template -void SuggestPhoto( - const QImage &image, - const QString &title, - Callback &&callback) { - auto badAspect = [](int a, int b) { - return (a >= 10 * b); - }; - if (image.isNull() - || badAspect(image.width(), image.height()) - || badAspect(image.height(), image.width())) { - Ui::show( - Box(tr::lng_bad_photo(tr::now)), - Ui::LayerOption::KeepOther); - return; - } - - const auto box = Ui::show( - Box(image, title), - Ui::LayerOption::KeepOther); - box->ready( - ) | rpl::start_with_next( - std::forward(callback), - box->lifetime()); -} - -template -void SuggestPhotoFile( - const FileDialog::OpenResult &result, - const QString &title, - Callback &&callback) { - if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { - return; - } - - auto image = [&] { - if (!result.remoteContent.isEmpty()) { - return App::readImage(result.remoteContent); - } else if (!result.paths.isEmpty()) { - return App::readImage(result.paths.front()); - } - return QImage(); - }(); - SuggestPhoto( - image, - title, - std::forward(callback)); -} - -template -void ShowChoosePhotoBox( - QPointer parent, - const QString &title, - Callback &&callback) { - auto filter = FileDialog::ImagesOrAllFilter(); - auto handleChosenPhoto = [ - title, - callback = std::forward(callback) - ](auto &&result) mutable { - SuggestPhotoFile(result, title, std::move(callback)); - }; - FileDialog::GetOpenPath( - parent, - tr::lng_choose_image(tr::now), - filter, - std::move(handleChosenPhoto)); -} - } // namespace HistoryDownButton::HistoryDownButton(QWidget *parent, const style::TwoIconButton &st) : RippleButton(parent, st.ripple) @@ -183,11 +117,33 @@ void HistoryDownButton::setUnreadCount(int unreadCount) { UserpicButton::UserpicButton( QWidget *parent, + not_null window, + not_null peer, + Role role, + const style::UserpicButton &st) +: RippleButton(parent, st.changeButton.ripple) +, _st(st) +, _controller(window->sessionController()) +, _window(window) +, _peer(peer) +, _cropTitle(CropTitle(peer)) +, _role(role) { + Expects(_role == Role::ChangePhoto); + + _waiting = false; + prepare(); +} + +UserpicButton::UserpicButton( + QWidget *parent, + not_null window, const QString &cropTitle, Role role, const style::UserpicButton &st) : RippleButton(parent, st.changeButton.ripple) , _st(st) +, _controller(window->sessionController()) +, _window(window) , _cropTitle(cropTitle) , _role(role) { Expects(_role == Role::ChangePhoto); @@ -205,6 +161,7 @@ UserpicButton::UserpicButton( : RippleButton(parent, st.changeButton.ripple) , _st(st) , _controller(controller) +, _window(&controller->window()) , _peer(peer) , _cropTitle(CropTitle(_peer)) , _role(role) { @@ -246,7 +203,7 @@ void UserpicButton::setClickHandlerByRole() { addClickHandler(App::LambdaDelayed( _st.changeButton.ripple.hideDuration, this, - [this] { changePhotoLazy(); })); + [=] { changePhotoLocally(); })); break; case Role::OpenPhoto: @@ -265,18 +222,20 @@ void UserpicButton::setClickHandlerByRole() { } } -void UserpicButton::changePhotoLazy() { - auto callback = crl::guard( +void UserpicButton::changePhotoLocally(bool requestToUpload) { + if (!_window) { + return; + } + auto callback = [=](QImage &&image) { + setImage(std::move(image)); + if (requestToUpload) { + _uploadPhotoRequests.fire({}); + } + }; + Editor::PrepareProfilePhoto( this, - [this](QImage &&image) { setImage(std::move(image)); }); - ShowChoosePhotoBox(this, _cropTitle, std::move(callback)); -} - -void UserpicButton::uploadNewPeerPhoto() { - auto callback = crl::guard(this, [=](QImage &&image) { - _peer->session().api().uploadPeerPhoto(_peer, std::move(image)); - }); - ShowChoosePhotoBox(this, _cropTitle, std::move(callback)); + _window, + std::move(callback)); } void UserpicButton::openPeerPhoto() { @@ -284,7 +243,7 @@ void UserpicButton::openPeerPhoto() { Expects(_controller != nullptr); if (_changeOverlayEnabled && _cursorInChangeOverlay) { - uploadNewPeerPhoto(); + changePhotoLocally(true); return; } @@ -774,6 +733,10 @@ void UserpicButton::prepareUserpicPixmap() { : InMemoryKey(); } +rpl::producer<> UserpicButton::uploadPhotoRequests() const { + return _uploadPhotoRequests.events(); +} + SilentToggle::SilentToggle(QWidget *parent, not_null channel) : RippleButton(parent, st::historySilentToggle.ripple) , _st(st::historySilentToggle) diff --git a/Telegram/SourceFiles/ui/special_buttons.h b/Telegram/SourceFiles/ui/special_buttons.h index b8ba3a2f6..0a681b744 100644 --- a/Telegram/SourceFiles/ui/special_buttons.h +++ b/Telegram/SourceFiles/ui/special_buttons.h @@ -21,6 +21,7 @@ class CloudImageView; } // namespace Data namespace Window { +class Controller; class SessionController; } // namespace Window @@ -68,6 +69,13 @@ public: UserpicButton( QWidget *parent, + not_null<::Window::Controller*> window, + not_null peer, + Role role, + const style::UserpicButton &st); + UserpicButton( + QWidget *parent, + not_null<::Window::Controller*> window, const QString &cropTitle, Role role, const style::UserpicButton &st); @@ -86,6 +94,8 @@ public: void switchChangePhotoOverlay(bool enabled); void showSavedMessagesOnSelf(bool enabled); + rpl::producer<> uploadPhotoRequests() const; + QImage takeResultImage() { return std::move(_result); } @@ -128,11 +138,11 @@ private: void grabOldUserpic(); void setClickHandlerByRole(); void openPeerPhoto(); - void changePhotoLazy(); - void uploadNewPeerPhoto(); + void changePhotoLocally(bool requestToUpload = false); const style::UserpicButton &_st; ::Window::SessionController *_controller = nullptr; + ::Window::Controller *_window = nullptr; PeerData *_peer = nullptr; std::shared_ptr _userpicView; QString _cropTitle; @@ -154,6 +164,8 @@ private: bool _changeOverlayEnabled = false; Ui::Animations::Simple _changeOverlayShown; + rpl::event_stream<> _uploadPhotoRequests; + }; class SilentToggle