From 3d37ac9235ae5e429cd41f20c9413249ea38b303 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 13 Nov 2017 16:02:53 +0400 Subject: [PATCH] Replace NewAvatarButton with UserpicButton. This new control should also replace PeerAvatarButton and Profile::UserpicButton and deliver all the best of those three. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/boxes/abstract_box.h | 8 +- .../SourceFiles/boxes/add_contact_box.cpp | 87 ++--- Telegram/SourceFiles/boxes/add_contact_box.h | 11 +- Telegram/SourceFiles/boxes/boxes.style | 5 - .../boxes/peers/edit_peer_info_box.cpp | 121 ++---- Telegram/SourceFiles/boxes/photo_crop_box.cpp | 19 +- Telegram/SourceFiles/boxes/photo_crop_box.h | 15 +- Telegram/SourceFiles/info/info.style | 2 - Telegram/SourceFiles/intro/intro.style | 2 - Telegram/SourceFiles/intro/introsignup.cpp | 41 +- Telegram/SourceFiles/intro/introsignup.h | 8 +- Telegram/SourceFiles/intro/introwidget.cpp | 6 +- Telegram/SourceFiles/intro/introwidget.h | 2 +- Telegram/SourceFiles/messenger.cpp | 2 +- Telegram/SourceFiles/messenger.h | 2 +- .../SourceFiles/settings/settings_cover.cpp | 9 +- Telegram/SourceFiles/ui/special_buttons.cpp | 350 ++++++++++++++++-- Telegram/SourceFiles/ui/special_buttons.h | 69 +++- Telegram/SourceFiles/ui/widgets/widgets.style | 20 + .../SourceFiles/window/window_peer_menu.cpp | 2 +- 21 files changed, 525 insertions(+), 257 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 862bcf6b5..a4ef8b1c2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -616,6 +616,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_info_channel_title" = "Channel Info"; "lng_profile_enable_notifications" = "Notifications"; "lng_profile_send_message" = "Send Message"; +"lng_profile_edit_group_name" = "Edit group name"; "lng_info_add_as_contact" = "Add as contact"; "lng_profile_shared_media" = "Shared media"; "lng_media_type_photos" = "Photos"; diff --git a/Telegram/SourceFiles/boxes/abstract_box.h b/Telegram/SourceFiles/boxes/abstract_box.h index 5b9454598..deda9a9ec 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.h +++ b/Telegram/SourceFiles/boxes/abstract_box.h @@ -116,6 +116,10 @@ public: finishPrepare(); } + Window::Controller *controller() { + return getDelegate()->controller(); + } + public slots: void onScrollToY(int top, int bottom = -1); @@ -124,10 +128,6 @@ public slots: protected: virtual void prepare() = 0; - Window::Controller *controller() { - return getDelegate()->controller(); - } - void setLayerType(bool layerType) { getDelegate()->setLayerType(layerType); } diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 4d164d013..a4359560c 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -307,14 +307,25 @@ void AddContactBox::updateButtons() { GroupInfoBox::GroupInfoBox(QWidget*, CreatingGroupType creating, bool fromTypeChoose) : _creating(creating) -, _fromTypeChoose(fromTypeChoose) -, _photo(this, st::newGroupPhotoSize, st::newGroupPhotoIconPosition) -, _title(this, st::defaultInputField, langFactory(_creating == CreatingGroupChannel ? lng_dlg_new_channel_name : lng_dlg_new_group_name)) { +, _fromTypeChoose(fromTypeChoose) { } void GroupInfoBox::prepare() { setMouseTracking(true); + _photo.create( + this, + (_creating == CreatingGroupChannel) + ? peerFromChannel(0) + : peerFromChat(0), + Ui::UserpicButton::Role::ChangePhoto, + st::defaultUserpicButton); + _title.create( + this, + st::defaultInputField, + langFactory(_creating == CreatingGroupChannel + ? lng_dlg_new_channel_name + : lng_dlg_new_group_name)); _title->setMaxLength(kMaxGroupChannelTitle); if (_creating == CreatingGroupChannel) { @@ -332,41 +343,9 @@ void GroupInfoBox::prepare() { addButton(langFactory(_creating == CreatingGroupChannel ? lng_create_group_create : lng_create_group_next), [this] { onNext(); }); addButton(langFactory(_fromTypeChoose ? lng_create_group_back : lng_cancel), [this] { closeBox(); }); - setupPhotoButton(); - updateMaxHeight(); } -void GroupInfoBox::setupPhotoButton() { - _photo->setClickedCallback(App::LambdaDelayed(st::defaultActiveButton.ripple.hideDuration, this, [this] { - auto imgExtensions = cImgExtensions(); - auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + FileDialog::AllFilesFilter(); - FileDialog::GetOpenPath(lang(lng_choose_image), filter, base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) { - if (result.remoteContent.isEmpty() && result.paths.isEmpty()) { - return; - } - - QImage img; - if (!result.remoteContent.isEmpty()) { - img = App::readImage(result.remoteContent); - } else { - img = App::readImage(result.paths.front()); - } - if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) { - return; - } - auto box = Ui::show( - Box( - img, - (_creating == CreatingGroupChannel) - ? peerFromChannel(0) - : peerFromChat(0)), - LayerOption::KeepOther); - connect(box, SIGNAL(ready(const QImage&)), this, SLOT(onPhotoReady(const QImage&))); - })); - })); -} - void GroupInfoBox::setInnerFocus() { _title->setFocusFast(); } @@ -376,12 +355,19 @@ void GroupInfoBox::resizeEvent(QResizeEvent *e) { _photo->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::boxPadding.top() + st::newGroupInfoPadding.top()); - auto nameLeft = st::newGroupPhotoSize + st::newGroupNamePosition.x(); + auto nameLeft = st::defaultUserpicButton.size.width() + + st::newGroupNamePosition.x(); _title->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right() - nameLeft, _title->height()); _title->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left() + nameLeft, st::boxPadding.top() + st::newGroupInfoPadding.top() + st::newGroupNamePosition.y()); if (_description) { _description->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _description->height()); - _description->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::boxPadding.top() + st::newGroupInfoPadding.top() + st::newGroupPhotoSize + st::newGroupDescriptionPadding.top()); + auto descriptionLeft = st::boxPadding.left() + + st::newGroupInfoPadding.left(); + auto descriptionTop = st::boxPadding.top() + + st::newGroupInfoPadding.top() + + st::defaultUserpicButton.size.height() + + st::newGroupDescriptionPadding.top(); + _description->moveToLeft(descriptionLeft, descriptionTop); } } @@ -433,8 +419,11 @@ void GroupInfoBox::createGroup(not_null selectUsersBox, const QStr return App::chat(chats->front().c_chat().vid.v); } | [this](not_null chat) { - if (!_photoImage.isNull()) { - Messenger::Instance().uploadProfilePhoto(_photoImage, chat->id); + auto image = _photo->takeResultImage(); + if (!image.isNull()) { + Messenger::Instance().uploadProfilePhoto( + std::move(image), + chat->id); } Ui::showPeerHistory(chat, ShowAtUnreadMsgId); }; @@ -522,9 +511,10 @@ void GroupInfoBox::createChannel(const QString &title, const QString &descriptio return App::channel(chats->front().c_channel().vid.v); } | [this](not_null channel) { - if (!_photoImage.isNull()) { + auto image = _photo->takeResultImage(); + if (!image.isNull()) { Messenger::Instance().uploadProfilePhoto( - _photoImage, + std::move(image), channel->id); } _createdChannel = channel; @@ -562,18 +552,19 @@ void GroupInfoBox::onDescriptionResized() { } void GroupInfoBox::updateMaxHeight() { - auto newHeight = st::boxPadding.top() + st::newGroupInfoPadding.top() + st::newGroupPhotoSize + st::boxPadding.bottom() + st::newGroupInfoPadding.bottom(); + auto newHeight = st::boxPadding.top() + + st::newGroupInfoPadding.top() + + st::defaultUserpicButton.size.height() + + st::boxPadding.bottom() + + st::newGroupInfoPadding.bottom(); if (_description) { - newHeight += st::newGroupDescriptionPadding.top() + _description->height() + st::newGroupDescriptionPadding.bottom(); + newHeight += st::newGroupDescriptionPadding.top() + + _description->height() + + st::newGroupDescriptionPadding.bottom(); } setDimensions(st::boxWideWidth, newHeight); } -void GroupInfoBox::onPhotoReady(const QImage &img) { - _photoImage = img; - _photo->setImage(_photoImage); -} - SetupChannelBox::SetupChannelBox(QWidget*, ChannelData *channel, bool existing) : _channel(channel) , _existing(existing) diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index b4f8f0ca3..2df005450 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -39,7 +39,7 @@ class RadioenumGroup; template class Radioenum; class LinkButton; -class NewAvatarButton; +class UserpicButton; } // namespace Ui enum class PeerFloodType { @@ -105,8 +105,6 @@ protected: void resizeEvent(QResizeEvent *e) override; private slots: - void onPhotoReady(const QImage &img); - void onNext(); void onNameSubmit(); void onDescriptionResized(); @@ -115,7 +113,6 @@ private slots: } private: - void setupPhotoButton(); void createChannel(const QString &title, const QString &description); void createGroup(not_null selectUsersBox, const QString &title, const std::vector> &users); @@ -125,12 +122,10 @@ private: CreatingGroupType _creating; bool _fromTypeChoose = false; - object_ptr _photo; - object_ptr _title; + object_ptr _photo = { nullptr }; + object_ptr _title = { nullptr }; object_ptr _description = { nullptr }; - QImage _photoImage; - // group / channel creation mtpRequestId _creationRequestId = 0; ChannelData *_createdChannel = nullptr; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 6fd7dba7e..189e01344 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -457,11 +457,6 @@ newGroupLinkPadding: margins(4px, 27px, 4px, 21px); newGroupLinkTop: 3px; newGroupLinkFont: font(16px); -newGroupPhotoSize: 76px; -newGroupPhotoIcon: icon {{ "new_chat_photo", activeButtonFg }}; -newGroupPhotoIconPosition: point(23px, 25px); -newGroupPhotoDuration: 150; - newGroupNamePosition: point(27px, 5px); newGroupDescriptionPadding: margins(0px, 13px, 0px, 4px); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index d16266b4f..0e20edf8e 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -36,8 +36,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/add_contact_box.h" #include "mtproto/sender.h" #include "lang/lang_keys.h" -#include "core/file_utilities.h" #include "mainwidget.h" +#include "messenger.h" #include "apiwrap.h" #include "application.h" #include "auth_session.h" @@ -77,7 +77,7 @@ private: struct Controls { Ui::InputField *title = nullptr; Ui::InputArea *description = nullptr; - Ui::NewAvatarButton *photo = nullptr; + Ui::UserpicButton *photo = nullptr; rpl::lifetime initialPhotoImageWaiting; std::shared_ptr> privacy; @@ -118,11 +118,6 @@ private: void submitTitle(); void submitDescription(); void deleteWithConfirmation(); - void choosePhotoDelayed(); - void choosePhoto(); - void suggestPhotoFile( - const FileDialog::OpenResult &result); - void suggestPhoto(const QImage &image); void privacyChanged(Privacy value); void checkUsernameAvailability(); @@ -155,6 +150,7 @@ private: void saveDescription(); void saveInvites(); void saveSignatures(); + void savePhoto(); void pushSaveStage(base::lambda_once &&lambda); void continueSave(); void cancelSave(); @@ -245,7 +241,7 @@ object_ptr Controller::createPhotoAndTitleEdit() { container->widthValue() | rpl::start_with_next([titleEdit](int width) { auto left = st::editPeerPhotoMargins.left() - + st::editPeerPhotoSize; + + st::defaultUserpicButton.size.width(); titleEdit->resizeToWidth(width - left); titleEdit->moveToLeft(left, 0, width); }, titleEdit->lifetime()); @@ -256,16 +252,17 @@ object_ptr Controller::createPhotoAndTitleEdit() { object_ptr Controller::createPhotoEdit() { Expects(_wrap != nullptr); - using PhotoWrap = Ui::PaddingWrap; + using PhotoWrap = Ui::PaddingWrap; auto photoWrap = object_ptr( _wrap, - object_ptr( + object_ptr( _wrap, - st::editPeerPhotoSize, - st::editPeerPhotoIconPosition), + _box->controller(), + _channel, + Ui::UserpicButton::Role::ChangePhoto, + st::defaultUserpicButton), st::editPeerPhotoMargins); _controls.photo = photoWrap->entity(); - _controls.photo->addClickHandler([this] { choosePhotoDelayed(); }); _controls.initialPhotoImageWaiting = base::ObservableViewer( Auth().downloaderTaskFinished()) @@ -278,18 +275,18 @@ object_ptr Controller::createPhotoEdit() { } void Controller::refreshInitialPhotoImage() { - if (auto image = _channel->currentUserpic()) { - image->load(); - if (image->loaded()) { - _controls.photo->setImage(image->pixNoCache( - st::editPeerPhotoSize * cIntRetinaFactor(), - st::editPeerPhotoSize * cIntRetinaFactor(), - Images::Option::Smooth).toImage()); - _controls.initialPhotoImageWaiting.destroy(); - } - } else { - _controls.initialPhotoImageWaiting.destroy(); - } + //if (auto image = _channel->currentUserpic()) { + // image->load(); + // if (image->loaded()) { + // _controls.photo->setImage(image->pixNoCache( + // st::editPeerPhotoSize * cIntRetinaFactor(), + // st::editPeerPhotoSize * cIntRetinaFactor(), + // Images::Option::Smooth).toImage()); + // _controls.initialPhotoImageWaiting.destroy(); + // } + //} else { + // _controls.initialPhotoImageWaiting.destroy(); + //} } object_ptr Controller::createTitleEdit() { @@ -968,7 +965,7 @@ void Controller::save() { pushSaveStage([this] { saveDescription(); }); pushSaveStage([this] { saveInvites(); }); pushSaveStage([this] { saveSignatures(); }); - pushSaveStage([this] { _box->closeBox(); }); + pushSaveStage([this] { savePhoto(); }); continueSave(); } } @@ -1122,6 +1119,18 @@ void Controller::saveSignatures() { }).send(); } +void Controller::savePhoto() { + auto image = _controls.photo + ? _controls.photo->takeResultImage() + : QImage(); + if (!image.isNull()) { + Messenger::Instance().uploadProfilePhoto( + std::move(image), + _channel->id); + } + _box->closeBox(); +} + void Controller::deleteWithConfirmation() { auto text = lang(_isGroup ? lng_sure_delete_group @@ -1144,66 +1153,6 @@ void Controller::deleteWithConfirmation() { std::move(deleteCallback)), LayerOption::KeepOther); } -void Controller::choosePhotoDelayed() { - App::CallDelayed( - st::defaultRippleAnimation.hideDuration, - this, - [this] { choosePhoto(); }); -} - -void Controller::choosePhoto() { - auto handleChosenPhoto = base::lambda_guarded( - _controls.photo, - [this](auto &&result) { suggestPhotoFile(result); }); - - auto imgExtensions = cImgExtensions(); - auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + FileDialog::AllFilesFilter(); - FileDialog::GetOpenPath( - lang(lng_choose_image), - filter, - std::move(handleChosenPhoto)); -} - -void Controller::suggestPhotoFile( - const FileDialog::OpenResult &result) { - 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); -} - -void Controller::suggestPhoto(const QImage &image) { - 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(lang(lng_bad_photo))); - return; - } - - auto callback = [this](const QImage &cropped) { - _controls.photo->setImage(cropped); - }; - auto box = Ui::show( - Box(image, _channel), - LayerOption::KeepOther); - QObject::connect( - box, - &PhotoCropBox::ready, - base::lambda_guarded(_controls.photo, std::move(callback))); -} - } // namespace EditPeerInfoBox::EditPeerInfoBox( diff --git a/Telegram/SourceFiles/boxes/photo_crop_box.cpp b/Telegram/SourceFiles/boxes/photo_crop_box.cpp index 5c4bb2c89..80e220c7d 100644 --- a/Telegram/SourceFiles/boxes/photo_crop_box.cpp +++ b/Telegram/SourceFiles/boxes/photo_crop_box.cpp @@ -21,9 +21,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/photo_crop_box.h" #include "lang/lang_keys.h" -#include "messenger.h" -#include "mainwidget.h" -#include "storage/file_upload.h" #include "ui/widgets/buttons.h" #include "styles/style_boxes.h" @@ -33,7 +30,7 @@ PhotoCropBox::PhotoCropBox(QWidget*, const QImage &img, const PeerId &peer) init(img, nullptr); } -PhotoCropBox::PhotoCropBox(QWidget*, const QImage &img, PeerData *peer) +PhotoCropBox::PhotoCropBox(QWidget*, const QImage &img, not_null peer) : _img(img) , _peerId(peer->id) { init(img, peer); @@ -52,9 +49,6 @@ void PhotoCropBox::init(const QImage &img, PeerData *peer) { void PhotoCropBox::prepare() { addButton(langFactory(lng_settings_save), [this] { sendPhoto(); }); addButton(langFactory(lng_cancel), [this] { closeBox(); }); - if (peerToBareInt(_peerId)) { - connect(this, SIGNAL(ready(const QImage&)), this, SLOT(onReady(const QImage&))); - } int32 s = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); _thumb = App::pixmapFromImageInPlace(_img.scaled(s * cIntRetinaFactor(), s * cIntRetinaFactor(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); @@ -288,10 +282,9 @@ void PhotoCropBox::sendPhoto() { tosend = cropped.copy(); } - emit ready(tosend); - closeBox(); -} - -void PhotoCropBox::onReady(const QImage &tosend) { - Messenger::Instance().uploadProfilePhoto(tosend, _peerId); + auto guard = weak(this); + _readyImages.fire(std::move(tosend)); + if (guard) { + closeBox(); + } } diff --git a/Telegram/SourceFiles/boxes/photo_crop_box.h b/Telegram/SourceFiles/boxes/photo_crop_box.h index 69a36f504..78e8b3e03 100644 --- a/Telegram/SourceFiles/boxes/photo_crop_box.h +++ b/Telegram/SourceFiles/boxes/photo_crop_box.h @@ -23,19 +23,15 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/abstract_box.h" class PhotoCropBox : public BoxContent { - Q_OBJECT - public: PhotoCropBox(QWidget*, const QImage &img, const PeerId &peer); - PhotoCropBox(QWidget*, const QImage &img, PeerData *peer); + PhotoCropBox(QWidget*, const QImage &img, not_null peer); int32 mouseState(QPoint p); -signals: - void ready(const QImage &tosend); - -private slots: - void onReady(const QImage &tosend); + rpl::producer ready() const { + return _readyImages.events(); + } protected: void prepare() override; @@ -58,6 +54,7 @@ private: QImage _img; QPixmap _thumb; QImage _mask, _fade; - PeerId _peerId; + PeerId _peerId = 0; + rpl::event_stream _readyImages; }; diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index f439c4a2c..6268268c2 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -547,8 +547,6 @@ managePeerButtonLabelPosition: point(25px, 10px); editPeerDeleteButtonMargins: margins(23px, 16px, 23px, 16px); editPeerDeleteButton: sessionTerminateAllButton; -editPeerPhotoSize: newGroupPhotoSize; -editPeerPhotoIconPosition: newGroupPhotoIconPosition; editPeerPhotoMargins: margins(23px, 16px, 23px, 8px); editPeerTitle: defaultInputField; editPeerTitleMargins: margins(27px, 21px, 23px, 8px); diff --git a/Telegram/SourceFiles/intro/intro.style b/Telegram/SourceFiles/intro/intro.style index 8fbc2e1b9..cd30310d1 100644 --- a/Telegram/SourceFiles/intro/intro.style +++ b/Telegram/SourceFiles/intro/intro.style @@ -39,8 +39,6 @@ introCoverIconTop: 46px; introSettingsSkip: 10px; -introPhotoSize: 76px; -introPhotoIconPosition: point(23px, 25px); introPhotoTop: 10px; introCoverTitle: FlatLabel(defaultFlatLabel) { diff --git a/Telegram/SourceFiles/intro/introsignup.cpp b/Telegram/SourceFiles/intro/introsignup.cpp index dc3a91b1d..ac64a2a5a 100644 --- a/Telegram/SourceFiles/intro/introsignup.cpp +++ b/Telegram/SourceFiles/intro/introsignup.cpp @@ -35,7 +35,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Intro { SignupWidget::SignupWidget(QWidget *parent, Widget::Data *data) : Step(parent, data) -, _photo(this, st::introPhotoSize, st::introPhotoIconPosition) +, _photo( + this, + peerFromUser(0), + Ui::UserpicButton::Role::ChangePhoto, + st::defaultUserpicButton) , _first(this, st::introName, langFactory(lng_signup_firstname)) , _last(this, st::introName, langFactory(lng_signup_lastname)) , _invertOrder(langFirstNameGoesSecond()) @@ -49,8 +53,6 @@ SignupWidget::SignupWidget(QWidget *parent, Widget::Data *data) : Step(parent, d connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); - setupPhotoButton(); - setErrorCentered(true); setTitleText(langFactory(lng_signup_title)); @@ -68,32 +70,6 @@ void SignupWidget::refreshLang() { updateControlsGeometry(); } -void SignupWidget::setupPhotoButton() { - _photo->setClickedCallback(App::LambdaDelayed(st::defaultActiveButton.ripple.hideDuration, this, [this] { - auto imgExtensions = cImgExtensions(); - auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + FileDialog::AllFilesFilter(); - FileDialog::GetOpenPath(lang(lng_choose_image), filter, base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) { - if (result.remoteContent.isEmpty() && result.paths.isEmpty()) { - return; - } - - QImage img; - if (!result.remoteContent.isEmpty()) { - img = App::readImage(result.remoteContent); - } else { - img = App::readImage(result.paths.front()); - } - - if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) { - showError(langFactory(lng_bad_photo)); - return; - } - auto box = Ui::show(Box(img, PeerId(0))); - connect(box, SIGNAL(ready(const QImage&)), this, SLOT(onPhotoReady(const QImage&))); - })); - })); -} - void SignupWidget::resizeEvent(QResizeEvent *e) { Step::resizeEvent(e); updateControlsGeometry(); @@ -152,11 +128,6 @@ void SignupWidget::onCheckRequest() { } } -void SignupWidget::onPhotoReady(const QImage &img) { - _photoImage = img; - _photo->setImage(_photoImage); -} - void SignupWidget::nameSubmitDone(const MTPauth_Authorization &result) { stopCheck(); auto &d = result.c_auth_authorization(); @@ -164,7 +135,7 @@ void SignupWidget::nameSubmitDone(const MTPauth_Authorization &result) { showError(&Lang::Hard::ServerError); return; } - finish(d.vuser, _photoImage); + finish(d.vuser, _photo->takeResultImage()); } bool SignupWidget::nameSubmitFail(const RPCError &error) { diff --git a/Telegram/SourceFiles/intro/introsignup.h b/Telegram/SourceFiles/intro/introsignup.h index 2cda87e86..22dc0fe08 100644 --- a/Telegram/SourceFiles/intro/introsignup.h +++ b/Telegram/SourceFiles/intro/introsignup.h @@ -25,7 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Ui { class RoundButton; class InputField; -class NewAvatarButton; +class UserpicButton; } // namespace Ui namespace Intro { @@ -48,10 +48,8 @@ protected: private slots: void onInputChange(); void onCheckRequest(); - void onPhotoReady(const QImage &img); private: - void setupPhotoButton(); void refreshLang(); void updateControlsGeometry(); @@ -60,9 +58,7 @@ private: void stopCheck(); - QImage _photoImage; - - object_ptr _photo; + object_ptr _photo; object_ptr _first; object_ptr _last; QString _firstName, _lastName; diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index d55ff1ae6..6b11f4529 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -449,7 +449,7 @@ QString Widget::Step::nextButtonText() const { return lang(lng_intro_next); } -void Widget::Step::finish(const MTPUser &user, QImage photo) { +void Widget::Step::finish(const MTPUser &user, QImage &&photo) { if (user.type() != mtpc_user || !user.c_user().is_self()) { // No idea what to do here. // We could've reset intro and MTP, but this really should not happen. @@ -475,7 +475,9 @@ void Widget::Step::finish(const MTPUser &user, QImage photo) { Auth().api().requestFullPeer(user); } if (!photo.isNull()) { - Messenger::Instance().uploadProfilePhoto(photo, Auth().userId()); + Messenger::Instance().uploadProfilePhoto( + std::move(photo), + Auth().userId()); } } diff --git a/Telegram/SourceFiles/intro/introwidget.h b/Telegram/SourceFiles/intro/introwidget.h index b6eefcc8c..e245968ef 100644 --- a/Telegram/SourceFiles/intro/introwidget.h +++ b/Telegram/SourceFiles/intro/introwidget.h @@ -147,7 +147,7 @@ public: Data *getData() const { return _data; } - void finish(const MTPUser &user, QImage photo = QImage()); + void finish(const MTPUser &user, QImage &&photo = QImage()); void goBack() { if (_goCallback) _goCallback(nullptr, Direction::Back); diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index 60f337cb9..97666041d 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -849,7 +849,7 @@ bool Messenger::openLocalUrl(const QString &url) { return false; } -void Messenger::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) { +void Messenger::uploadProfilePhoto(QImage &&tosend, const PeerId &peerId) { PreparedPhotoThumbs photoThumbs; QVector photoSizes; diff --git a/Telegram/SourceFiles/messenger.h b/Telegram/SourceFiles/messenger.h index d357ebece..c2bd2f097 100644 --- a/Telegram/SourceFiles/messenger.h +++ b/Telegram/SourceFiles/messenger.h @@ -150,7 +150,7 @@ public: void checkStartUrl(); bool openLocalUrl(const QString &url); - void uploadProfilePhoto(const QImage &tosend, const PeerId &peerId); + void uploadProfilePhoto(QImage &&tosend, const PeerId &peerId); void regPhotoUpdate(const PeerId &peer, const FullMsgId &msgId); bool isPhotoUpdating(const PeerId &peer); void cancelPhotoUpdate(const PeerId &peer); diff --git a/Telegram/SourceFiles/settings/settings_cover.cpp b/Telegram/SourceFiles/settings/settings_cover.cpp index 53ac27cbd..97e1e6308 100644 --- a/Telegram/SourceFiles/settings/settings_cover.cpp +++ b/Telegram/SourceFiles/settings/settings_cover.cpp @@ -362,7 +362,14 @@ void CoverWidget::showSetPhotoBox(const QImage &img) { return; } - auto box = Ui::show(Box(img, _self)); + auto peer = _self; + auto box = Ui::show(Box(img, peer)); + box->ready() + | rpl::start_with_next([=](QImage &&image) { + Messenger::Instance().uploadProfilePhoto( + std::move(image), + peer->id); + }, box->lifetime()); subscribe(box->boxClosing, [this] { onPhotoUploadStatusChanged(); }); } diff --git a/Telegram/SourceFiles/ui/special_buttons.cpp b/Telegram/SourceFiles/ui/special_buttons.cpp index 0fae67639..76b99e678 100644 --- a/Telegram/SourceFiles/ui/special_buttons.cpp +++ b/Telegram/SourceFiles/ui/special_buttons.cpp @@ -24,12 +24,34 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_history.h" #include "dialogs/dialogs_layout.h" #include "ui/effects/ripple_animation.h" +#include "data/data_photo.h" +#include "core/file_utilities.h" +#include "boxes/photo_crop_box.h" +#include "boxes/confirm_box.h" +#include "window/window_controller.h" +#include "lang/lang_keys.h" +#include "auth_session.h" +#include "messenger.h" +#include "observer_peer.h" namespace Ui { namespace { constexpr int kWideScale = 5; +template +QPixmap CreateSquarePixmap(int width, Callback &&paintCallback) { + auto size = QSize(width, width) * cIntRetinaFactor(); + auto image = QImage(size, QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(cRetinaFactor()); + image.fill(Qt::transparent); + { + Painter p(&image); + paintCallback(p); + } + return App::pixmapFromImageInPlace(std::move(image)); +}; + } // namespace HistoryDownButton::HistoryDownButton(QWidget *parent, const style::TwoIconButton &st) : RippleButton(parent, st.ripple) @@ -311,41 +333,321 @@ void PeerAvatarButton::paintEvent(QPaintEvent *e) { } } -NewAvatarButton::NewAvatarButton(QWidget *parent, int size, QPoint position) : RippleButton(parent, st::defaultActiveButton.ripple) -, _position(position) { - resize(size, size); +UserpicButton::UserpicButton( + QWidget *parent, + PeerId peerForCrop, + Role role, + const style::UserpicButton &st) +: RippleButton(parent, st.changeButton.ripple) +, _st(st) +, _peerForCrop(peerForCrop) +, _role(role) { + Expects(_role == Role::ChangePhoto); + + _waiting = false; + prepare(); } -void NewAvatarButton::paintEvent(QPaintEvent *e) { - Painter p(this); +UserpicButton::UserpicButton( + QWidget *parent, + not_null controller, + not_null peer, + Role role, + const style::UserpicButton &st) +: RippleButton(parent, st.changeButton.ripple) +, _st(st) +, _controller(controller) +, _peer(peer) +, _peerForCrop(_peer->id) +, _role(role) { + processPeerPhoto(); + prepare(); + setupPeerViewers(); +} - if (!_image.isNull()) { - p.drawPixmap(0, 0, _image); +void UserpicButton::prepare() { + resize(_st.size); + _notShownYet = _waiting; + if (!_waiting) { + prepareUserpicPixmap(); + } + setClickHandlerByRole(); +} + +void UserpicButton::setClickHandlerByRole() { + switch (_role) { + case Role::ChangePhoto: + addClickHandler(App::LambdaDelayed( + _st.changeButton.ripple.hideDuration, + this, + [this] { changePhotoLazy(); })); + break; + + case Role::OpenPhoto: + addClickHandler([this] { + openPeerPhoto(); + }); + break; + + case Role::OpenProfile: + addClickHandler([this] { + Expects(_controller != nullptr); + + _controller->showPeerInfo(_peer); + }); + break; + } +} + +void UserpicButton::changePhotoLazy() { + auto imgExtensions = cImgExtensions(); + auto filter = qsl("Image files (*") + + imgExtensions.join(qsl(" *")) + + qsl(");;") + + FileDialog::AllFilesFilter(); + auto handleChosenPhoto = base::lambda_guarded( + this, + [this](auto &&result) { suggestPhotoFile(result); }); + FileDialog::GetOpenPath( + lang(lng_choose_image), + filter, + std::move(handleChosenPhoto)); +} + +void UserpicButton::suggestPhotoFile( + const FileDialog::OpenResult &result) { + if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { return; } - p.setPen(Qt::NoPen); - p.setBrush(isOver() ? st::defaultActiveButton.textBgOver : st::defaultActiveButton.textBg); - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(rect()); - } - - paintRipple(p, 0, 0, getms()); - - st::newGroupPhotoIcon.paint(p, _position, width()); + 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); } -void NewAvatarButton::setImage(const QImage &image) { - auto small = image.scaled(size() * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - Images::prepareCircle(small); - _image = App::pixmapFromImageInPlace(std::move(small)); - _image.setDevicePixelRatio(cRetinaFactor()); +void UserpicButton::suggestPhoto(const QImage &image) { + 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(lang(lng_bad_photo)), + LayerOption::KeepOther); + return; + } + + auto box = Ui::show( + Box(image, _peerForCrop), + LayerOption::KeepOther); + box->ready() + | rpl::start_with_next([this](QImage &&image) { + setImage(std::move(image)); + }, box->lifetime()); +} + +void UserpicButton::openPeerPhoto() { + Expects(_peer != nullptr); + Expects(_controller != nullptr); + + auto id = _peer->photoId; + if (!id || id == UnknownPeerPhotoId) { + return; + } + auto photo = App::photo(id); + if (photo->date) { + Messenger::Instance().showPhoto(photo, _peer); + } +} + +void UserpicButton::setupPeerViewers() { + Notify::PeerUpdateViewer( + _peer, + Notify::PeerUpdate::Flag::PhotoChanged) + | rpl::start_with_next([this] { + processNewPeerPhoto(); + update(); + }, lifetime()); + base::ObservableViewer(Auth().downloaderTaskFinished()) + | rpl::start_with_next([this] { + if (_waiting && _peer->userpicLoaded()) { + _waiting = false; + startNewPhotoShowing(); + } + }, lifetime()); +} + +void UserpicButton::paintEvent(QPaintEvent *e) { + Painter p(this); + if (!_waiting && _notShownYet) { + _notShownYet = false; + startAnimation(); + } + + auto photoPosition = countPhotoPosition(); + auto photoLeft = photoPosition.x(); + auto photoTop = photoPosition.y(); + + auto ms = getms(); + if (_a_appearance.animating(ms)) { + p.drawPixmapLeft(photoPosition, width(), _oldUserpic); + p.setOpacity(_a_appearance.current()); + } + p.drawPixmapLeft(photoPosition, width(), _userpic); + + if (_role == Role::ChangePhoto) { + auto over = isOver() || isDown(); + if (over) { + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(_userpicHasImage + ? st::msgDateImgBg + : _st.changeButton.textBgOver); + p.drawEllipse( + photoLeft, + photoTop, + _st.photoSize, + _st.photoSize); + } + paintRipple( + p, + photoLeft, + photoTop, + ms, + _userpicHasImage + ? &st::shadowFg->c + : &_st.changeButton.ripple.color->c); + if (over || !_userpicHasImage) { + auto iconLeft = (_st.changeIconPosition.x() < 0) + ? (_st.photoSize - _st.changeIcon.width()) / 2 + : _st.changeIconPosition.x(); + auto iconTop = (_st.changeIconPosition.y() < 0) + ? (_st.photoSize - _st.changeIcon.height()) / 2 + : _st.changeIconPosition.y(); + _st.changeIcon.paint( + p, + photoLeft + iconLeft, + photoTop + iconTop, + width()); + } + } +} + +QPoint UserpicButton::countPhotoPosition() const { + auto photoLeft = (_st.photoPosition.x() < 0) + ? (width() - _st.photoSize) / 2 + : _st.photoPosition.x(); + auto photoTop = (_st.photoPosition.y() < 0) + ? (height() - _st.photoSize) / 2 + : _st.photoPosition.y(); + return { photoLeft, photoTop }; +} + +QImage UserpicButton::prepareRippleMask() const { + return Ui::RippleAnimation::ellipseMask(QSize( + _st.photoSize, + _st.photoSize)); +} + +QPoint UserpicButton::prepareRippleStartPosition() const { + return (_role == Role::ChangePhoto) + ? mapFromGlobal(QCursor::pos()) - countPhotoPosition() + : DisabledRippleStartPosition(); +} + +void UserpicButton::processPeerPhoto() { + Expects(_peer != nullptr); + + bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId); + setCursor(hasPhoto ? style::cur_pointer : style::cur_default); + _waiting = !_peer->userpicLoaded(); + if (_waiting) { + _peer->loadUserpic(true); + } + if (_role == Role::OpenPhoto) { + auto id = _peer->photoId; + if (id == UnknownPeerPhotoId) { + _peer->updateFull(); + } + auto canOpen = (id != 0 && id != UnknownPeerPhotoId); + setCursor(canOpen ? style::cur_pointer : style::cur_default); + } +} + +void UserpicButton::processNewPeerPhoto() { + if (_userpicCustom) { + return; + } + processPeerPhoto(); + if (!_waiting) { + _oldUserpic = myGrab(this); + startNewPhotoShowing(); + } +} + +void UserpicButton::startNewPhotoShowing() { + prepareUserpicPixmap(); + if (_notShownYet) { + return; + } + startAnimation(); update(); } -QImage NewAvatarButton::prepareRippleMask() const { - return Ui::RippleAnimation::ellipseMask(size()); +void UserpicButton::startAnimation() { + _a_appearance.finish(); + _a_appearance.start([this] { update(); }, 0, 1, _st.duration); +} + +void UserpicButton::switchChangePhotoOverlay(bool enabled) { + +} + +void UserpicButton::setImage(QImage &&image) { + _oldUserpic = myGrab(this); + + auto size = QSize(_st.photoSize, _st.photoSize); + auto small = image.scaled( + size * cIntRetinaFactor(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + Images::prepareCircle(small); + _userpic = App::pixmapFromImageInPlace(std::move(small)); + _userpic.setDevicePixelRatio(cRetinaFactor()); + _userpicCustom = _userpicHasImage = true; + _result = std::move(image); + + startNewPhotoShowing(); +} + +void UserpicButton::prepareUserpicPixmap() { + if (_userpicCustom) { + return; + } + auto size = _st.photoSize; + auto paintButton = [&](Painter &p, const style::color &color) { + PainterHighQualityEnabler hq(p); + p.setBrush(color); + p.setPen(Qt::NoPen); + p.drawEllipse(0, 0, size, size); + }; + _userpicHasImage = _peer + ? (_peer->currentUserpic() || _role != Role::ChangePhoto) + : false; + _userpic = CreateSquarePixmap(size, [&](Painter &p) { + if (_userpicHasImage) { + _peer->paintUserpic(p, 0, 0, _st.photoSize); + } else { + paintButton(p, _st.changeButton.textBg); + } + }); } } // namespace Ui diff --git a/Telegram/SourceFiles/ui/special_buttons.h b/Telegram/SourceFiles/ui/special_buttons.h index 26daae1dd..21d37f550 100644 --- a/Telegram/SourceFiles/ui/special_buttons.h +++ b/Telegram/SourceFiles/ui/special_buttons.h @@ -26,6 +26,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org class PeerData; +namespace Window { +class Controller; +} // namespace Window + +namespace FileDialog { +struct OpenResult; +} // namespace FileDialog + namespace Ui { class HistoryDownButton : public RippleButton { @@ -162,24 +170,69 @@ private: }; -class NewAvatarButton : public RippleButton { +class UserpicButton : public RippleButton { public: - NewAvatarButton(QWidget *parent, int size, QPoint position); + enum class Role { + ChangePhoto, + OpenPhoto, + OpenProfile, + Custom, + }; - void setImage(const QImage &image); + UserpicButton( + QWidget *parent, + PeerId peerForCrop, + Role role, + const style::UserpicButton &st); + UserpicButton( + QWidget *parent, + not_null controller, + not_null peer, + Role role, + const style::UserpicButton &st); - int naturalWidth() const override { - return height(); + void switchChangePhotoOverlay(bool enabled); + + QImage takeResultImage() { + return std::move(_result); } protected: - void paintEvent(QPaintEvent *e) override; + void paintEvent(QPaintEvent *e); QImage prepareRippleMask() const override; + QPoint prepareRippleStartPosition() const override; private: - QPixmap _image; - QPoint _position; + void prepare(); + void setImage(QImage &&image); + void setupPeerViewers(); + void startAnimation(); + void processPeerPhoto(); + void processNewPeerPhoto(); + void startNewPhotoShowing(); + void prepareUserpicPixmap(); + QPoint countPhotoPosition() const; + + void setClickHandlerByRole(); + void openPeerPhoto(); + void changePhotoLazy(); + void suggestPhotoFile( + const FileDialog::OpenResult &result); + void suggestPhoto(const QImage &image); + + const style::UserpicButton &_st; + Window::Controller *_controller = nullptr; + PeerData *_peer = nullptr; + PeerId _peerForCrop = 0; + Role _role = Role::ChangePhoto; + bool _notShownYet = true; + bool _waiting = false; + QPixmap _userpic, _oldUserpic; + bool _userpicHasImage = false; + bool _userpicCustom = false; + Animation _a_appearance; + QImage _result; }; diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 5262003a7..caa5201da 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -497,6 +497,16 @@ PeerAvatarButton { photoSize: pixels; } +UserpicButton { + size: size; + photoSize: pixels; + photoPosition: point; + changeButton: RoundButton; + changeIcon: icon; + changeIconPosition: point; + duration: int; +} + InfoProfileButton { textFg: color; textFgOver: color; @@ -975,6 +985,16 @@ defaultImportantTooltipLabel: FlatLabel(defaultFlatLabel) { } } +defaultUserpicButton: UserpicButton { + size: size(76px, 76px); + photoSize: 76px; + photoPosition: point(-1px, -1px); + changeButton: defaultActiveButton; + changeIcon: icon {{ "new_chat_photo", activeButtonFg }}; + changeIconPosition: point(23px, 25px); + duration: 500; +} + historyToDownBelow: icon { { "history_down_shadow", historyToDownShadow }, { "history_down_circle", historyToDownBg, point(4px, 4px) }, diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index bdb94cfef..4691db956 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -350,7 +350,7 @@ void Filler::addChatActions(not_null chat) { if (_source != PeerMenuSource::ChatsList) { if (chat->canEdit()) { _addAction( - lang(lng_profile_edit_contact), + lang(lng_profile_edit_group_name), [chat] { Ui::show(Box(chat)); }); } if (chat->amCreator()