mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-19 18:05:37 +00:00
Add fast chat photo upload to info profile.
This commit is contained in:
parent
8dd3f24285
commit
aecc119bac
Telegram
Resources/icons
SourceFiles
boxes/peers
info/profile
ui
window
BIN
Telegram/Resources/icons/upload_chat_photo.png
Normal file
BIN
Telegram/Resources/icons/upload_chat_photo.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 425 B |
BIN
Telegram/Resources/icons/upload_chat_photo@2x.png
Normal file
BIN
Telegram/Resources/icons/upload_chat_photo@2x.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 873 B |
@ -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);
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user