Add fast chat photo upload to info profile.

This commit is contained in:
John Preston 2017-11-13 21:12:36 +04:00
parent 8dd3f24285
commit aecc119bac
10 changed files with 257 additions and 115 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

View file

@ -114,7 +114,6 @@ private:
object_ptr<Ui::RpWidget> createInvitesEdit();
object_ptr<Ui::RpWidget> createDeleteButton();
void refreshInitialPhotoImage();
void submitTitle();
void submitDescription();
void deleteWithConfirmation();
@ -264,31 +263,9 @@ object_ptr<Ui::RpWidget> Controller::createPhotoEdit() {
st::editPeerPhotoMargins);
_controls.photo = photoWrap->entity();
_controls.initialPhotoImageWaiting = base::ObservableViewer(
Auth().downloaderTaskFinished())
| rpl::start_with_next([=] {
refreshInitialPhotoImage();
});
refreshInitialPhotoImage();
return photoWrap;
}
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();
//}
}
object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
Expects(_wrap != nullptr);

View file

@ -243,7 +243,6 @@ Cover::Cover(
_name->setContextCopyText(lang(lng_profile_copy_fullname));
_status->setAttribute(Qt::WA_TransparentForMouseEvents);
initUserpicButton();
initViewers();
setupChildGeometry();
}
@ -275,10 +274,6 @@ Cover *Cover::setOnlineCount(rpl::producer<int> &&count) {
void Cover::initViewers() {
using Flag = Notify::PeerUpdate::Flag;
Notify::PeerUpdateValue(_peer, Flag::PhotoChanged)
| rpl::start_with_next(
[this] { refreshUserpicLink(); },
lifetime());
Notify::PeerUpdateValue(_peer, Flag::NameChanged)
| rpl::start_with_next(
[this] { refreshNameText(); },
@ -288,12 +283,30 @@ void Cover::initViewers() {
| rpl::start_with_next(
[this] { refreshStatusText(); },
lifetime());
if (!_peer->isUser()) {
Notify::PeerUpdateValue(_peer,
Flag::ChannelRightsChanged | Flag::ChatCanEdit)
| rpl::start_with_next(
[this] { refreshUploadPhotoOverlay(); },
lifetime());
}
VerifiedValue(_peer)
| rpl::start_with_next(
[this](bool verified) { setVerified(verified); },
lifetime());
}
void Cover::refreshUploadPhotoOverlay() {
_userpic->switchChangePhotoOverlay([&] {
if (auto chat = _peer->asChat()) {
return chat->canEdit();
} else if (auto channel = _peer->asChannel()) {
return channel->canEditInformation();
}
return false;
}());
}
void Cover::setVerified(bool verified) {
if ((_verifiedCheck != nullptr) == verified) {
return;
@ -313,29 +326,6 @@ void Cover::setVerified(bool verified) {
refreshNameGeometry(width());
}
void Cover::initUserpicButton() {
_userpic->setClickedCallback([this] {
auto hasPhoto = (_peer->photoId != 0);
auto knownPhoto = (_peer->photoId != UnknownPeerPhotoId);
if (hasPhoto && knownPhoto) {
if (auto photo = App::photo(_peer->photoId)) {
if (photo->date) {
Messenger::Instance().showPhoto(photo, _peer);
}
}
}
});
}
void Cover::refreshUserpicLink() {
auto hasPhoto = (_peer->photoId != 0);
auto knownPhoto = (_peer->photoId != UnknownPeerPhotoId);
_userpic->setPointerCursor(hasPhoto && knownPhoto);
if (!knownPhoto) {
Auth().api().requestFullPeer(_peer);
}
}
void Cover::refreshNameText() {
_name->setText(App::peerName(_peer));
refreshNameGeometry(width());

View file

@ -79,12 +79,11 @@ public:
private:
void setupChildGeometry();
void initViewers();
void initUserpicButton();
void refreshUserpicLink();
void refreshNameText();
void refreshStatusText();
void refreshNameGeometry(int newWidth);
void refreshStatusGeometry(int newWidth);
void refreshUploadPhotoOverlay();
void setVerified(bool verified);
not_null<PeerData*> _peer;

View file

@ -83,6 +83,7 @@ protected:
Down = (1 << 1),
Disabled = (1 << 2),
};
friend constexpr bool is_flag_type(StateFlag) { return true; };
using State = base::flags<StateFlag>;
State state() const {

View file

@ -52,6 +52,74 @@ QPixmap CreateSquarePixmap(int width, Callback &&paintCallback) {
return App::pixmapFromImageInPlace(std::move(image));
};
template <typename Callback>
void SuggestPhoto(
const QImage &image,
PeerId peerForCrop,
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>(lang(lng_bad_photo)),
LayerOption::KeepOther);
return;
}
auto box = Ui::show(
Box<PhotoCropBox>(image, peerForCrop),
LayerOption::KeepOther);
box->ready()
| rpl::start_with_next(
std::forward<Callback>(callback),
box->lifetime());
}
template <typename Callback>
void SuggestPhotoFile(
const FileDialog::OpenResult &result,
PeerId peerForCrop,
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,
peerForCrop,
std::forward<Callback>(callback));
}
template <typename Callback>
void ShowChoosePhotoBox(PeerId peerForCrop, Callback &&callback) {
auto imgExtensions = cImgExtensions();
auto filter = qsl("Image files (*")
+ imgExtensions.join(qsl(" *"))
+ qsl(");;")
+ FileDialog::AllFilesFilter();
auto handleChosenPhoto = [
peerForCrop,
callback = std::forward<Callback>(callback)
](auto &&result) mutable {
SuggestPhotoFile(result, peerForCrop, std::move(callback));
};
FileDialog::GetOpenPath(
lang(lng_choose_image),
filter,
std::move(handleChosenPhoto));
}
} // namespace
HistoryDownButton::HistoryDownButton(QWidget *parent, const style::TwoIconButton &st) : RippleButton(parent, st.ripple)
@ -387,63 +455,33 @@ void UserpicButton::setClickHandlerByRole() {
}
void UserpicButton::changePhotoLazy() {
auto imgExtensions = cImgExtensions();
auto filter = qsl("Image files (*")
+ imgExtensions.join(qsl(" *"))
+ qsl(");;")
+ FileDialog::AllFilesFilter();
auto handleChosenPhoto = base::lambda_guarded(
auto callback = base::lambda_guarded(
this,
[this](auto &&result) { suggestPhotoFile(result); });
FileDialog::GetOpenPath(
lang(lng_choose_image),
filter,
std::move(handleChosenPhoto));
[this](QImage &&image) { setImage(std::move(image)); });
ShowChoosePhotoBox(_peerForCrop, std::move(callback));
}
void UserpicButton::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 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::uploadNewPeerPhoto() {
auto callback = base::lambda_guarded(
this,
[this](QImage &&image) {
Messenger::Instance().uploadProfilePhoto(
std::move(image),
_peer->id
);
});
ShowChoosePhotoBox(_peerForCrop, std::move(callback));
}
void UserpicButton::openPeerPhoto() {
Expects(_peer != nullptr);
Expects(_controller != nullptr);
if (_changeOverlayEnabled && _cursorInChangeOverlay) {
uploadNewPeerPhoto();
return;
}
auto id = _peer->photoId;
if (!id || id == UnknownPeerPhotoId) {
return;
@ -524,6 +562,47 @@ void UserpicButton::paintEvent(QPaintEvent *e) {
photoTop + iconTop,
width());
}
} else if (_changeOverlayEnabled) {
auto current = _changeOverlayShown.current(
ms,
(isOver() || isDown()) ? 1. : 0.);
auto barHeight = anim::interpolate(
0,
_st.uploadHeight,
current);
if (barHeight > 0) {
auto barLeft = photoLeft;
auto barTop = photoTop + _st.photoSize - barHeight;
auto rect = QRect(
barLeft,
barTop,
_st.photoSize,
barHeight);
p.setClipRect(rect);
{
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(_st.uploadBg);
p.drawEllipse(
photoLeft,
photoTop,
_st.photoSize,
_st.photoSize);
}
auto iconLeft = (_st.uploadIconPosition.x() < 0)
? (_st.photoSize - _st.uploadIcon.width()) / 2
: _st.uploadIconPosition.x();
auto iconTop = (_st.uploadIconPosition.y() < 0)
? (_st.uploadHeight - _st.uploadIcon.height()) / 2
: _st.uploadIconPosition.y();
if (iconTop < barHeight) {
_st.uploadIcon.paint(
p,
barLeft + iconLeft,
barTop + iconTop,
width());
}
}
}
}
@ -552,8 +631,8 @@ QPoint UserpicButton::prepareRippleStartPosition() const {
void UserpicButton::processPeerPhoto() {
Expects(_peer != nullptr);
bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId);
setCursor(hasPhoto ? style::cur_pointer : style::cur_default);
auto hasPhoto = (_peer->photoId
&& _peer->photoId != UnknownPeerPhotoId);
_waiting = !_peer->userpicLoaded();
if (_waiting) {
_peer->loadUserpic(true);
@ -561,10 +640,52 @@ void UserpicButton::processPeerPhoto() {
if (_role == Role::OpenPhoto) {
auto id = _peer->photoId;
if (id == UnknownPeerPhotoId) {
_peer->updateFull();
_peer->updateFullForced();
}
auto canOpen = (id != 0 && id != UnknownPeerPhotoId);
setCursor(canOpen ? style::cur_pointer : style::cur_default);
_canOpenPhoto = (id != 0 && id != UnknownPeerPhotoId);
updateCursor();
}
}
void UserpicButton::updateCursor() {
Expects(_role == Role::OpenPhoto);
auto pointer = _canOpenPhoto
|| (_changeOverlayEnabled && _cursorInChangeOverlay);
setPointerCursor(pointer);
}
void UserpicButton::mouseMoveEvent(QMouseEvent *e) {
RippleButton::mouseMoveEvent(e);
if (_role == Role::OpenPhoto) {
updateCursorInChangeOverlay(e->pos());
}
}
void UserpicButton::updateCursorInChangeOverlay(QPoint localPos) {
auto photoPosition = countPhotoPosition();
auto overlayRect = QRect(
photoPosition.x(),
photoPosition.y() + _st.photoSize - _st.uploadHeight,
_st.photoSize,
_st.uploadHeight);
auto inOverlay = overlayRect.contains(localPos);
setCursorInChangeOverlay(inOverlay);
}
void UserpicButton::leaveEventHook(QEvent *e) {
if (_role == Role::OpenPhoto) {
setCursorInChangeOverlay(false);
}
return RippleButton::leaveEventHook(e);
}
void UserpicButton::setCursorInChangeOverlay(bool inOverlay) {
Expects(_role == Role::OpenPhoto);
if (_cursorInChangeOverlay != inOverlay) {
_cursorInChangeOverlay = inOverlay;
updateCursor();
}
}
@ -606,7 +727,45 @@ void UserpicButton::startAnimation() {
}
void UserpicButton::switchChangePhotoOverlay(bool enabled) {
Expects(_role == Role::OpenPhoto);
if (_changeOverlayEnabled != enabled) {
_changeOverlayEnabled = enabled;
if (enabled) {
if (isOver()) {
startChangeOverlayAnimation();
}
updateCursorInChangeOverlay(
mapFromGlobal(QCursor::pos()));
} else {
_changeOverlayShown.finish();
update();
}
}
}
void UserpicButton::startChangeOverlayAnimation() {
auto over = isOver() || isDown();
_changeOverlayShown.start(
[this] { update(); },
over ? 0. : 1.,
over ? 1. : 0.,
st::slideWrapDuration);
update();
}
void UserpicButton::onStateChanged(
State was,
StateChangeSource source) {
RippleButton::onStateChanged(was, source);
if (_changeOverlayEnabled) {
auto mask = (StateFlag::Over | StateFlag::Down);
auto wasOver = (was & mask) != 0;
auto nowOver = (state() & mask) != 0;
if (wasOver != nowOver) {
startChangeOverlayAnimation();
}
}
}
void UserpicButton::setImage(QImage &&image) {

View file

@ -30,10 +30,6 @@ namespace Window {
class Controller;
} // namespace Window
namespace FileDialog {
struct OpenResult;
} // namespace FileDialog
namespace Ui {
class HistoryDownButton : public RippleButton {
@ -180,7 +176,11 @@ public:
}
protected:
void paintEvent(QPaintEvent *e);
void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override;
void onStateChanged(State was, StateChangeSource source) override;
QImage prepareRippleMask() const override;
QPoint prepareRippleStartPosition() const override;
@ -195,14 +195,16 @@ private:
void startNewPhotoShowing();
void prepareUserpicPixmap();
QPoint countPhotoPosition() const;
void startChangeOverlayAnimation();
void updateCursorInChangeOverlay(QPoint localPos);
void setCursorInChangeOverlay(bool inOverlay);
void updateCursor();
void grabOldUserpic();
void setClickHandlerByRole();
void openPeerPhoto();
void changePhotoLazy();
void suggestPhotoFile(
const FileDialog::OpenResult &result);
void suggestPhoto(const QImage &image);
void uploadNewPeerPhoto();
const style::UserpicButton &_st;
Window::Controller *_controller = nullptr;
@ -218,6 +220,11 @@ private:
Animation _a_appearance;
QImage _result;
bool _canOpenPhoto = false;
bool _cursorInChangeOverlay = false;
bool _changeOverlayEnabled = false;
Animation _changeOverlayShown;
};
} // namespace Ui

View file

@ -500,6 +500,10 @@ UserpicButton {
changeIcon: icon;
changeIconPosition: point;
duration: int;
uploadHeight: pixels;
uploadBg: color;
uploadIcon: icon;
uploadIconPosition: point;
}
InfoProfileButton {
@ -981,6 +985,7 @@ defaultImportantTooltipLabel: FlatLabel(defaultFlatLabel) {
}
defaultChangeUserpicIcon: icon {{ "new_chat_photo", activeButtonFg }};
defaultUploadUserpicIcon: icon {{ "upload_chat_photo", msgDateImgFg }};
defaultUserpicButton: UserpicButton {
size: size(76px, 76px);
photoSize: 76px;
@ -989,6 +994,10 @@ defaultUserpicButton: UserpicButton {
changeIcon: defaultChangeUserpicIcon;
changeIconPosition: point(23px, 25px);
duration: 500;
uploadHeight: 24px;
uploadBg: msgDateImgBgOver;
uploadIcon: defaultUploadUserpicIcon;
uploadIconPosition: point(-1px, 1px);
}
historyToDownBelow: icon {

View file

@ -199,7 +199,7 @@ void Controller::resizeForThirdSection() {
auto extendBy = qMax(
minimalThreeColumnWidth() - layout.bodyWidth,
st::columnMinimalWidthThird);
countThirdColumnWidthFromRatio(layout.bodyWidth));
auto newBodyWidth = layout.bodyWidth + extendBy;
auto currentRatio = Auth().data().dialogsWidthRatio();
Auth().data().setDialogsWidthRatio(