Replaced old photo crop box with photo editor for profile photos.

This commit is contained in:
23rd 2021-02-22 05:10:27 +03:00
parent a996b14291
commit 17465e1082
8 changed files with 162 additions and 113 deletions

View File

@ -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),

View File

@ -495,6 +495,7 @@ object_ptr<Ui::RpWidget> Controller::createPhotoEdit() {
_wrap,
object_ptr<Ui::UserpicButton>(
_wrap,
&_navigation->parentController()->window(),
_peer,
Ui::UserpicButton::Role::ChangePhoto,
st::defaultUserpicButton),

View File

@ -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<Ui::RpWidget*> parent,
@ -48,6 +57,80 @@ void OpenWithPreparedFile(
Ui::LayerOption::KeepOther);
}
void PrepareProfilePhoto(
not_null<Ui::RpWidget*> parent,
not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&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<InformBox>(tr::lng_bad_photo(tr::now)));
return;
}
image = resizeToMinSize(
std::move(image),
Qt::KeepAspectRatioByExpanding);
const auto fileImage = std::make_shared<Image>(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<LayerWidget>(
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<Ui::RpWidget*> parent,
not_null<Window::Controller*> window,

View File

@ -31,6 +31,11 @@ void OpenWithPreparedFile(
int previewWidth,
Fn<void()> &&doneCallback);
void PrepareProfilePhoto(
not_null<Ui::RpWidget*> parent,
not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback);
class PhotoEditor;
class LayerWidget : public Ui::LayerWidget {

View File

@ -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() {

View File

@ -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<InformBox>(tr::lng_bad_photo(tr::now)));
return;
}
auto box = Box<PhotoCropBox>(
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(),

View File

@ -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 <typename Callback>
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<InformBox>(tr::lng_bad_photo(tr::now)),
Ui::LayerOption::KeepOther);
return;
}
const auto box = Ui::show(
Box<PhotoCropBox>(image, title),
Ui::LayerOption::KeepOther);
box->ready(
) | rpl::start_with_next(
std::forward<Callback>(callback),
box->lifetime());
}
template <typename Callback>
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>(callback));
}
template <typename Callback>
void ShowChoosePhotoBox(
QPointer<QWidget> parent,
const QString &title,
Callback &&callback) {
auto filter = FileDialog::ImagesOrAllFilter();
auto handleChosenPhoto = [
title,
callback = std::forward<Callback>(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::Controller*> window,
not_null<PeerData*> 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::Controller*> 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<ChannelData*> channel)
: RippleButton(parent, st::historySilentToggle.ripple)
, _st(st::historySilentToggle)

View File

@ -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<PeerData*> 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<Data::CloudImageView> _userpicView;
QString _cropTitle;
@ -154,6 +164,8 @@ private:
bool _changeOverlayEnabled = false;
Ui::Animations::Simple _changeOverlayShown;
rpl::event_stream<> _uploadPhotoRequests;
};
class SilentToggle