Replace NewAvatarButton with UserpicButton.

This new control should also replace PeerAvatarButton and
Profile::UserpicButton and deliver all the best of those three.
This commit is contained in:
John Preston 2017-11-13 16:02:53 +04:00
parent 3deea14559
commit 3d37ac9235
21 changed files with 525 additions and 257 deletions

View File

@ -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";

View File

@ -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);
}

View File

@ -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<PhotoCropBox>(
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<PeerListBox*> selectUsersBox, const QStr
return App::chat(chats->front().c_chat().vid.v);
}
| [this](not_null<ChatData*> 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<ChannelData*> 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)

View File

@ -39,7 +39,7 @@ class RadioenumGroup;
template <typename Enum>
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<PeerListBox*> selectUsersBox, const QString &title, const std::vector<not_null<PeerData*>> &users);
@ -125,12 +122,10 @@ private:
CreatingGroupType _creating;
bool _fromTypeChoose = false;
object_ptr<Ui::NewAvatarButton> _photo;
object_ptr<Ui::InputField> _title;
object_ptr<Ui::UserpicButton> _photo = { nullptr };
object_ptr<Ui::InputField> _title = { nullptr };
object_ptr<Ui::InputArea> _description = { nullptr };
QImage _photoImage;
// group / channel creation
mtpRequestId _creationRequestId = 0;
ChannelData *_createdChannel = nullptr;

View File

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

View File

@ -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<Ui::RadioenumGroup<Privacy>> 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<void()> &&lambda);
void continueSave();
void cancelSave();
@ -245,7 +241,7 @@ object_ptr<Ui::RpWidget> 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<Ui::RpWidget> Controller::createPhotoAndTitleEdit() {
object_ptr<Ui::RpWidget> Controller::createPhotoEdit() {
Expects(_wrap != nullptr);
using PhotoWrap = Ui::PaddingWrap<Ui::NewAvatarButton>;
using PhotoWrap = Ui::PaddingWrap<Ui::UserpicButton>;
auto photoWrap = object_ptr<PhotoWrap>(
_wrap,
object_ptr<Ui::NewAvatarButton>(
object_ptr<Ui::UserpicButton>(
_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<Ui::RpWidget> 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<Ui::RpWidget> 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<InformBox>(lang(lng_bad_photo)));
return;
}
auto callback = [this](const QImage &cropped) {
_controls.photo->setImage(cropped);
};
auto box = Ui::show(
Box<PhotoCropBox>(image, _channel),
LayerOption::KeepOther);
QObject::connect(
box,
&PhotoCropBox::ready,
base::lambda_guarded(_controls.photo, std::move(callback)));
}
} // namespace
EditPeerInfoBox::EditPeerInfoBox(

View File

@ -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<PeerData*> 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();
}
}

View File

@ -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<PeerData*> peer);
int32 mouseState(QPoint p);
signals:
void ready(const QImage &tosend);
private slots:
void onReady(const QImage &tosend);
rpl::producer<QImage> 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<QImage> _readyImages;
};

View File

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

View File

@ -39,8 +39,6 @@ introCoverIconTop: 46px;
introSettingsSkip: 10px;
introPhotoSize: 76px;
introPhotoIconPosition: point(23px, 25px);
introPhotoTop: 10px;
introCoverTitle: FlatLabel(defaultFlatLabel) {

View File

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

View File

@ -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<Ui::NewAvatarButton> _photo;
object_ptr<Ui::UserpicButton> _photo;
object_ptr<Ui::InputField> _first;
object_ptr<Ui::InputField> _last;
QString _firstName, _lastName;

View File

@ -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());
}
}

View File

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

View File

@ -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<MTPPhotoSize> photoSizes;

View File

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

View File

@ -362,7 +362,14 @@ void CoverWidget::showSetPhotoBox(const QImage &img) {
return;
}
auto box = Ui::show(Box<PhotoCropBox>(img, _self));
auto peer = _self;
auto box = Ui::show(Box<PhotoCropBox>(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(); });
}

View File

@ -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 <typename Callback>
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<Window::Controller*> controller,
not_null<PeerData*> 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<InformBox>(lang(lng_bad_photo)),
LayerOption::KeepOther);
return;
}
auto box = Ui::show(
Box<PhotoCropBox>(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

View File

@ -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<Window::Controller*> controller,
not_null<PeerData*> 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;
};

View File

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

View File

@ -350,7 +350,7 @@ void Filler::addChatActions(not_null<ChatData*> chat) {
if (_source != PeerMenuSource::ChatsList) {
if (chat->canEdit()) {
_addAction(
lang(lng_profile_edit_contact),
lang(lng_profile_edit_group_name),
[chat] { Ui::show(Box<EditNameTitleBox>(chat)); });
}
if (chat->amCreator()