Support personal photo edit in EditContactBox.

This commit is contained in:
John Preston 2022-12-20 17:12:32 +04:00
parent c7c652a277
commit 1dd83f3d34
26 changed files with 484 additions and 182 deletions

View file

@ -208,6 +208,7 @@ void PeerPhoto::clearPersonal(not_null<UserData*> user) {
_session->data().processUsers(data.vusers());
});
}).send();
if (!user->userpicPhotoUnknown() && user->hasPersonalPhoto()) {
_session->storage().remove(Storage::UserPhotosRemoveOne(
peerToUser(user->id),
@ -304,6 +305,9 @@ void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) {
_session->data().processPhoto(data.vphoto());
_session->data().processUsers(data.vusers());
});
if (!suggestion) {
user->updateFullForced();
}
}).send();
}
}

View file

@ -2416,6 +2416,12 @@ void ApiWrap::refreshFileReference(
} else {
fail();
}
}, [&](Data::FileOriginFullUser data) {
if (const auto user = _session->data().user(data.userId)) {
request(MTPusers_GetFullUser(user->inputUser));
} else {
fail();
}
}, [&](Data::FileOriginPeerPhoto data) {
fail();
}, [&](Data::FileOriginStickerSet data) {

View file

@ -465,7 +465,7 @@ void GroupInfoBox::prepare() {
_photo.create(
this,
&_navigation->parentController()->window(),
Ui::UserpicButton::Role::ChangePhoto,
Ui::UserpicButton::Role::ChoosePhoto,
st::defaultUserpicButton);
_title.create(
this,

View file

@ -86,7 +86,6 @@ void GiftBox(
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
top,
user,
Ui::UserpicButton::Role::Custom,
st::defaultUserpicButton);
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
top->widthValue(

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
@ -41,7 +42,8 @@ void SendRequest(
bool sharePhone,
const QString &first,
const QString &last,
const QString &phone) {
const QString &phone,
Fn<void()> done) {
const auto wasContact = user->isContact();
using Flag = MTPcontacts_AddContact::Flag;
user->session().api().request(MTPcontacts_AddContact(
@ -73,6 +75,7 @@ void SendRequest(
}
box->closeBox();
}
done();
}).send();
}
@ -103,6 +106,7 @@ private:
QString _phone;
Fn<void()> _focus;
Fn<void()> _save;
Fn<std::optional<QImage>()> _updatedPersonalPhoto;
};
@ -146,6 +150,7 @@ void Controller::setupCover() {
? tr::lng_contact_mobile_hidden()
: rpl::single(Ui::FormatPhone(_phone)))),
style::margins());
_updatedPersonalPhoto = [=] { return cover->updatedPersonalPhoto(); };
}
void Controller::setupNameFields() {
@ -199,13 +204,29 @@ void Controller::initNameFields(
(inverted ? last : first)->showError();
return;
}
const auto user = _user;
const auto personal = _updatedPersonalPhoto
? _updatedPersonalPhoto()
: std::nullopt;
const auto done = [=] {
if (personal) {
if (personal->isNull()) {
user->session().api().peerPhoto().clearPersonal(user);
} else {
user->session().api().peerPhoto().upload(
user,
base::duplicate(*personal));
}
}
};
SendRequest(
Ui::MakeWeak(_box),
_user,
user,
_sharePhone && _sharePhone->checked(),
firstValue,
lastValue,
_phone);
_phone,
done);
};
const auto submit = [=] {
const auto firstValue = first->getLastText().trimmed();

View file

@ -85,11 +85,7 @@ EditParticipantBox::Inner::Inner(
: RpWidget(parent)
, _peer(peer)
, _user(user)
, _userPhoto(
this,
_user,
Ui::UserpicButton::Role::Custom,
st::rightsPhotoButton)
, _userPhoto(this, _user, st::rightsPhotoButton)
, _hasAdminRights(hasAdminRights)
, _rows(this) {
_rows->heightValue(
@ -97,7 +93,7 @@ EditParticipantBox::Inner::Inner(
resizeToWidth(width());
}, lifetime());
_userPhoto->setPointerCursor(false);
_userPhoto->setAttribute(Qt::WA_TransparentForMouseEvents);
_userName.setText(
st::rightsNameStyle,
_user->name(),

View file

@ -470,9 +470,10 @@ object_ptr<Ui::RpWidget> Controller::createPhotoEdit() {
_wrap,
object_ptr<Ui::UserpicButton>(
_wrap,
&_navigation->parentController()->window(),
_navigation->parentController(),
_peer,
Ui::UserpicButton::Role::ChangePhoto,
Ui::UserpicButton::Source::PeerPhoto,
st::defaultUserpicButton),
st::editPeerPhotoMargins);
_controls.photo = photoWrap->entity();

View file

@ -1244,7 +1244,6 @@ void Panel::refreshTopButton() {
auto joinAsToggle = object_ptr<Ui::UserpicButton>(
widget(),
joinAs,
Ui::UserpicButton::Role::Custom,
st::groupCallJoinAsToggle);
_joinAsToggle.destroy();
_joinAsToggle = std::move(joinAsToggle);

View file

@ -17,6 +17,12 @@ struct FileReferenceAccumulator {
push(item);
}
}
template <typename Type>
void push(const tl::conditional<Type> &data) {
if (data) {
push(*data);
}
}
void push(const MTPPhoto &data) {
data.match([&](const MTPDphoto &data) {
result.data.emplace(
@ -47,52 +53,34 @@ struct FileReferenceAccumulator {
}
void push(const MTPTheme &data) {
data.match([&](const MTPDtheme &data) {
if (const auto document = data.vdocument()) {
push(*document);
}
push(data.vdocument());
});
}
void push(const MTPWebPageAttribute &data) {
data.match([&](const MTPDwebPageAttributeTheme &data) {
if (const auto documents = data.vdocuments()) {
push(*documents);
}
push(data.vdocuments());
});
}
void push(const MTPWebPage &data) {
data.match([&](const MTPDwebPage &data) {
if (const auto document = data.vdocument()) {
push(*document);
}
if (const auto attributes = data.vattributes()) {
push(*attributes);
}
if (const auto photo = data.vphoto()) {
push(*photo);
}
if (const auto page = data.vcached_page()) {
push(*page);
}
push(data.vdocument());
push(data.vattributes());
push(data.vphoto());
push(data.vcached_page());
}, [](const auto &data) {
});
}
void push(const MTPGame &data) {
data.match([&](const MTPDgame &data) {
if (const auto document = data.vdocument()) {
push(*document);
}
push(data.vdocument());
}, [](const auto &data) {
});
}
void push(const MTPMessageMedia &data) {
data.match([&](const MTPDmessageMediaPhoto &data) {
if (const auto photo = data.vphoto()) {
push(*photo);
}
push(data.vphoto());
}, [&](const MTPDmessageMediaDocument &data) {
if (const auto document = data.vdocument()) {
push(*document);
}
push(data.vdocument());
}, [&](const MTPDmessageMediaWebPage &data) {
push(data.vwebpage());
}, [&](const MTPDmessageMediaGame &data) {
@ -102,9 +90,7 @@ struct FileReferenceAccumulator {
}
void push(const MTPMessage &data) {
data.match([&](const MTPDmessage &data) {
if (const auto media = data.vmedia()) {
push(*media);
}
push(data.vmedia());
}, [&](const MTPDmessageService &data) {
data.vaction().match(
[&](const MTPDmessageActionChatEditPhoto &data) {
@ -125,6 +111,11 @@ struct FileReferenceAccumulator {
push(data.vphotos());
});
}
void push(const MTPusers_UserFull &data) {
data.match([&](const auto &data) {
push(data.vfull_user().data().vpersonal_photo());
});
}
void push(const MTPmessages_RecentStickers &data) {
data.match([&](const MTPDmessages_recentStickers &data) {
push(data.vstickers());
@ -181,6 +172,10 @@ UpdatedFileReferences GetFileReferences(const MTPphotos_Photos &data) {
return GetFileReferencesHelper(data);
}
UpdatedFileReferences GetFileReferences(const MTPusers_UserFull &data) {
return GetFileReferencesHelper(data);
}
UpdatedFileReferences GetFileReferences(
const MTPmessages_RecentStickers &data) {
return GetFileReferencesHelper(data);

View file

@ -29,6 +29,18 @@ struct FileOriginUserPhoto {
}
};
struct FileOriginFullUser {
FileOriginFullUser(UserId userId)
: userId(userId) {
}
UserId userId = 0;
inline bool operator<(const FileOriginFullUser &other) const {
return userId < other.userId;
}
};
struct FileOriginPeerPhoto {
explicit FileOriginPeerPhoto(PeerId peerId) : peerId(peerId) {
}
@ -113,6 +125,7 @@ struct FileOrigin {
v::null_t,
FileOriginMessage,
FileOriginUserPhoto,
FileOriginFullUser,
FileOriginPeerPhoto,
FileOriginStickerSet,
FileOriginSavedGifs,
@ -126,6 +139,8 @@ struct FileOrigin {
}
FileOrigin(FileOriginUserPhoto data) : data(data) {
}
FileOrigin(FileOriginFullUser data) : data(data) {
}
FileOrigin(FileOriginPeerPhoto data) : data(data) {
}
FileOrigin(FileOriginStickerSet data) : data(data) {
@ -177,6 +192,7 @@ struct UpdatedFileReferences {
UpdatedFileReferences GetFileReferences(const MTPmessages_Messages &data);
UpdatedFileReferences GetFileReferences(const MTPphotos_Photos &data);
UpdatedFileReferences GetFileReferences(const MTPusers_UserFull &data);
UpdatedFileReferences GetFileReferences(
const MTPmessages_RecentStickers &data);
UpdatedFileReferences GetFileReferences(

View file

@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_emoji_statuses.h"
#include "data/data_user_names.h"
#include "data/notify/data_notify_settings.h"
#include "api/api_peer_photo.h"
#include "apiwrap.h"
#include "ui/text/text_options.h"
#include "lang/lang_keys.h"
#include "styles/style_chat.h"
@ -365,11 +367,18 @@ bool UserData::hasCalls() const {
namespace Data {
void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
if (const auto photo = update.vprofile_photo()) {
user->owner().processPhoto(*photo);
}
if (const auto photo = update.vpersonal_photo()) {
user->owner().processPhoto(*photo);
const auto profilePhoto = update.vprofile_photo()
? user->owner().processPhoto(*update.vprofile_photo()).get()
: nullptr;
const auto personalPhoto = update.vpersonal_photo()
? user->owner().processPhoto(*update.vpersonal_photo()).get()
: nullptr;
if (personalPhoto && profilePhoto) {
user->session().api().peerPhoto().registerNonPersonalPhoto(
user,
profilePhoto);
} else {
user->session().api().peerPhoto().unregisterNonPersonalPhoto(user);
}
user->setSettings(update.vsettings());
user->owner().notifySettings().apply(user, update.vnotify_settings());

View file

@ -429,7 +429,9 @@ QSize Service::performCountCurrentSize(int newWidth) {
}
const auto media = this->media();
if (media && data()->isUserpicSuggestion()) {
newHeight = st::msgServiceMargin.top() + media->resizeGetHeight(newWidth) + st::msgServiceMargin.bottom();
newHeight += st::msgServiceMargin.top()
+ media->resizeGetHeight(newWidth)
+ st::msgServiceMargin.bottom();
} else if (!text().isEmpty()) {
auto contentWidth = newWidth;
if (delegate()->elementIsChatWide()) {

View file

@ -841,9 +841,7 @@ void TopBarWidget::refreshInfoButton() {
} else if (const auto peer = _activeChat.key.peer()) {
auto info = object_ptr<Ui::UserpicButton>(
this,
_controller,
peer,
Ui::UserpicButton::Role::Custom,
st::topBarInfoButton);
info->showSavedMessagesOnSelf(true);
_info.destroy();

View file

@ -329,6 +329,7 @@ infoEditContactCover: InfoProfileCover(infoProfileCover) {
nameTop: 33px;
statusTop: 57px;
}
infoEditContactPersonalLeft: 6px;
infoProfileInaccessibleUserpic: icon {{ "info/inaccessible_userpic", historyPeerUserpicFg }};

View file

@ -308,10 +308,16 @@ Cover::Cover(
this,
controller,
_peer,
(role == Role::Info
? Ui::UserpicButton::Role::OpenPhoto
: Ui::UserpicButton::Role::Custom),
Ui::UserpicButton::Role::OpenPhoto,
Ui::UserpicButton::Source::PeerPhoto,
_st.photo))
, _changePersonal((role == Role::Info
|| topic
|| !_peer->isUser()
|| _peer->isSelf()
|| _peer->asUser()->isBot())
? nullptr
: CreateUploadSubButton(this, _peer->asUser(), controller).get())
, _iconButton(topic
? object_ptr<TopicIconButton>(this, controller, topic)
: nullptr)
@ -363,6 +369,16 @@ void Cover::setupChildGeometry() {
} else {
_iconButton->moveToLeft(_st.photoLeft, _st.photoTop, newWidth);
}
if (_changePersonal) {
_changePersonal->moveToLeft(
(_st.photoLeft
+ _st.photo.photoSize
- _changePersonal->width()
+ st::infoEditContactPersonalLeft),
(_userpic->y()
+ _userpic->height()
- _changePersonal->height()));
}
refreshNameGeometry(newWidth);
refreshStatusGeometry(newWidth);
}, lifetime());
@ -373,6 +389,10 @@ Cover *Cover::setOnlineCount(rpl::producer<int> &&count) {
return this;
}
std::optional<QImage> Cover::updatedPersonalPhoto() const {
return _personalChosen;
}
void Cover::initViewers(rpl::producer<QString> title) {
using Flag = Data::PeerUpdate::Flag;
std::move(
@ -397,10 +417,15 @@ void Cover::initViewers(rpl::producer<QString> title) {
) | rpl::start_with_next([=] {
refreshUploadPhotoOverlay();
}, lifetime());
setupChangePersonal();
}
void Cover::refreshUploadPhotoOverlay() {
if (!_userpic || _role == Role::EditContact) {
if (!_userpic) {
return;
} else if (_role == Role::EditContact) {
_userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
return;
}
@ -421,7 +446,7 @@ void Cover::refreshUploadPhotoOverlay() {
auto &image = chosen.image;
switch (chosen.type) {
case ChosenType::Set:
_userpic->changeTo(base::duplicate(image));
_userpic->showCustom(base::duplicate(image));
_peer->session().api().peerPhoto().upload(
_peer,
std::move(image));
@ -433,6 +458,44 @@ void Cover::refreshUploadPhotoOverlay() {
break;
}
});
if (const auto user = _peer->asUser()) {
_userpic->resetPersonalRequests(
) | rpl::start_with_next([=] {
user->session().api().peerPhoto().clearPersonal(user);
_userpic->showSource(Ui::UserpicButton::Source::PeerPhoto);
}, lifetime());
}
}
void Cover::setupChangePersonal() {
if (!_changePersonal) {
return;
}
_changePersonal->chosenImages(
) | rpl::start_with_next([=](Ui::UserpicButton::ChosenImage &&chosen) {
if (chosen.type == Ui::UserpicButton::ChosenType::Suggest) {
_peer->session().api().peerPhoto().suggest(
_peer,
std::move(chosen.image));
} else {
_personalChosen = std::move(chosen.image);
_userpic->showCustom(base::duplicate(*_personalChosen));
_changePersonal->overrideHasPersonalPhoto(true);
_changePersonal->showSource(
Ui::UserpicButton::Source::NonPersonalIfHasPersonal);
}
}, _changePersonal->lifetime());
_changePersonal->resetPersonalRequests(
) | rpl::start_with_next([=] {
_personalChosen = QImage();
_userpic->showSource(
Ui::UserpicButton::Source::NonPersonalPhoto);
_changePersonal->overrideHasPersonalPhoto(false);
_changePersonal->showCustom(QImage());
}, _changePersonal->lifetime());
}
void Cover::refreshStatusText() {

View file

@ -115,6 +115,7 @@ public:
[[nodiscard]] rpl::producer<Section> showSection() const {
return _showSection.events();
}
[[nodiscard]] std::optional<QImage> updatedPersonalPhoto() const;
private:
Cover(
@ -131,6 +132,7 @@ private:
void refreshNameGeometry(int newWidth);
void refreshStatusGeometry(int newWidth);
void refreshUploadPhotoOverlay();
void setupChangePersonal();
const style::InfoProfileCover &_st;
@ -142,6 +144,8 @@ private:
rpl::variable<int> _onlineCount;
object_ptr<Ui::UserpicButton> _userpic;
Ui::UserpicButton *_changePersonal = nullptr;
std::optional<QImage> _personalChosen;
object_ptr<TopicIconButton> _iconButton;
object_ptr<Ui::FlatLabel> _name = { nullptr };
object_ptr<Ui::FlatLabel> _status = { nullptr };

View file

@ -19,11 +19,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/media/info_media_buttons.h"
#include "boxes/abstract_box.h"
#include "boxes/add_contact_box.h"
#include "data/data_changes.h"
#include "data/data_forum_topic.h"
#include "data/data_photo.h"
#include "data/data_file_origin.h"
#include "ui/boxes/confirm_box.h"
#include "mainwidget.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "window/main_window.h"
#include "window/window_session_controller.h"
#include "storage/storage_shared_media.h"
@ -64,6 +68,20 @@ InnerWidget::InnerWidget(
object_ptr<Ui::RpWidget> InnerWidget::setupContent(
not_null<RpWidget*> parent) {
auto result = object_ptr<Ui::VerticalLayout>(parent);
if (const auto user = _peer->asUser()) {
user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::FullInfo
) | rpl::start_with_next([=] {
auto &photos = user->session().api().peerPhoto();
if (const auto original = photos.nonPersonalPhoto(user)) {
// Preload it for the edit contact box.
_nonPersonalView = original->createMediaView();
const auto id = peerToUser(user->id);
original->load(Data::FileOriginFullUser{ id });
}
}, lifetime());
}
_cover = _topic
? result->add(object_ptr<Cover>(
result,

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
class ForumTopic;
class PhotoMedia;
} // namespace Data
namespace Window {
@ -70,6 +71,8 @@ private:
PeerData * const _migrated = nullptr;
Data::ForumTopic * const _topic = nullptr;
std::shared_ptr<Data::PhotoMedia> _nonPersonalView;
Members *_members = nullptr;
Cover *_cover = nullptr;
Ui::SlideWrap<RpWidget> *_sharedMediaWrap = nullptr;

View file

@ -29,7 +29,7 @@ SignupWidget::SignupWidget(
, _photo(
this,
data->controller,
Ui::UserpicButton::Role::ChangePhoto,
Ui::UserpicButton::Role::ChoosePhoto,
st::defaultUserpicButton)
, _first(this, st::introName, tr::lng_signup_firstname())
, _last(this, st::introName, tr::lng_signup_lastname())

View file

@ -73,7 +73,6 @@ not_null<Ui::RpWidget*> PanelForm::setupContent() {
object_ptr<Ui::UserpicButton>(
userpicWrap,
bot,
Ui::UserpicButton::Role::Custom,
st::passportFormUserpic));
userpicWrap->widthValue(
) | rpl::start_with_next([=](int width) {

View file

@ -31,7 +31,6 @@ PanelAskPassword::PanelAskPassword(
, _userpic(
this,
_controller->bot(),
Ui::UserpicButton::Role::Custom,
st::passportPasswordUserpic)
, _about1(
this,

View file

@ -245,6 +245,7 @@ void SetupPhoto(
controller,
self,
Ui::UserpicButton::Role::OpenPhoto,
Ui::UserpicButton::Source::PeerPhoto,
st::settingsInfoPhoto);
const auto upload = CreateUploadSubButton(wrap, controller);
@ -252,7 +253,7 @@ void SetupPhoto(
) | rpl::start_with_next([=](Ui::UserpicButton::ChosenImage &&chosen) {
auto &image = chosen.image;
UpdatePhotoLocally(self, image);
photo->changeTo(base::duplicate(image));
photo->showCustom(base::duplicate(image));
self->session().api().peerPhoto().upload(self, std::move(image));
}, upload->lifetime());

View file

@ -123,6 +123,7 @@ Cover::Cover(
controller,
_user,
Ui::UserpicButton::Role::OpenPhoto,
Ui::UserpicButton::Source::PeerPhoto,
st::infoProfileCover.photo)
, _name(this, st::infoProfileCover.name)
, _phone(this, st::defaultFlatLabel)
@ -141,7 +142,7 @@ Cover::Cover(
_userpic->switchChangePhotoOverlay(_user->isSelf(), [=](
Ui::UserpicButton::ChosenImage chosen) {
auto &image = chosen.image;
_userpic->changeTo(base::duplicate(image));
_userpic->showCustom(base::duplicate(image));
_user->session().api().peerPhoto().upload(_user, std::move(image));
});

View file

@ -14,8 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_user.h"
#include "data/data_histories.h"
#include "data/data_streaming.h"
#include "data/data_file_origin.h"
#include "data/data_photo_media.h"
#include "history/history.h"
#include "calls/calls_instance.h"
#include "core/application.h"
#include "ui/layers/generic_box.h"
@ -99,26 +102,32 @@ QPixmap CreateSquarePixmap(int width, Callback &&paintCallback) {
return Ui::PixmapFromImage(std::move(image));
};
} // namespace
void SetupSubButtonBackground(
not_null<Ui::UserpicButton*> upload,
not_null<Ui::RpWidget*> background) {
const auto border = st::uploadUserpicButtonBorder;
const auto size = upload->rect().marginsAdded(
{ border, border, border, border }
).size();
UserpicButton::UserpicButton(
QWidget *parent,
not_null<Window::Controller*> window,
not_null<PeerData*> peer,
Role role,
const style::UserpicButton &st)
: RippleButton(parent, st.changeButton.ripple)
, _st(st)
, _controller(window->sessionController())
, _window(window)
, _peer(peer)
, _role(role) {
Expects(_role == Role::ChangePhoto);
background->resize(size);
background->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(background);
auto hq = PainterHighQualityEnabler(p);
p.setBrush(st::boxBg);
p.setPen(Qt::NoPen);
p.drawEllipse(background->rect());
}, background->lifetime());
_waiting = false;
prepare();
upload->positionValue(
) | rpl::start_with_next([=](QPoint position) {
background->move(position - QPoint(border, border));
}, background->lifetime());
}
} // namespace
UserpicButton::UserpicButton(
QWidget *parent,
not_null<Window::Controller*> window,
@ -131,7 +140,6 @@ UserpicButton::UserpicButton(
, _role(role) {
Expects(_role == Role::ChangePhoto || _role == Role::ChoosePhoto);
_waiting = false;
prepare();
}
@ -140,33 +148,39 @@ UserpicButton::UserpicButton(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
Role role,
Source source,
const style::UserpicButton &st)
: RippleButton(parent, st.changeButton.ripple)
, _st(st)
, _controller(controller)
, _window(&controller->window())
, _peer(peer)
, _role(role) {
processPeerPhoto();
, _role(role)
, _source(source) {
if (_source != Source::Custom) {
processPeerPhoto();
setupPeerViewers();
}
prepare();
setupPeerViewers();
}
UserpicButton::UserpicButton(
QWidget *parent,
not_null<PeerData*> peer,
Role role,
const style::UserpicButton &st)
: RippleButton(parent, st.changeButton.ripple)
, _st(st)
, _peer(peer)
, _role(role) {
Expects(_role != Role::OpenProfile && _role != Role::OpenPhoto);
, _role(Role::Custom)
, _source(Source::PeerPhoto) {
Expects(_role != Role::OpenPhoto);
if (_source != Source::Custom) {
processPeerPhoto();
setupPeerViewers();
}
_waiting = false;
processPeerPhoto();
prepare();
setupPeerViewers();
}
UserpicButton::~UserpicButton() = default;
@ -182,12 +196,39 @@ void UserpicButton::prepare() {
if (_role == Role::ChangePhoto) {
chosenImages(
) | rpl::start_with_next([=](ChosenImage &&chosen) {
setImage(std::move(chosen.image));
showCustom(std::move(chosen.image));
}, lifetime());
}
}
void UserpicButton::requestSuggestAvailability() {
if (const auto user = _peer ? _peer->asUser() : nullptr) {
if (!user->isSelf()) {
const auto history = user->owner().history(user);
if (!history->lastServerMessageKnown()) {
// Server allows suggesting photos only in non-empty chats.
user->owner().histories().requestDialogEntry(history);
}
}
}
}
bool UserpicButton::canSuggestPhoto(not_null<UserData*> user) const {
// Server allows suggesting photos only in non-empty chats.
return !user->isSelf()
&& (user->owner().history(user)->lastServerMessage() != nullptr);
}
bool UserpicButton::hasPersonalPhotoLocally() const {
if (const auto user = _peer->asUser()) {
return _overrideHasPersonalPhoto.value_or(user->hasPersonalPhoto());
}
return false;
}
void UserpicButton::setClickHandlerByRole() {
requestSuggestAvailability();
switch (_role) {
case Role::ChoosePhoto:
case Role::ChangePhoto:
@ -197,21 +238,9 @@ void UserpicButton::setClickHandlerByRole() {
case Role::OpenPhoto:
addClickHandler([=] { openPeerPhoto(); });
break;
case Role::OpenProfile:
addClickHandler([this] {
Expects(_controller != nullptr);
_controller->showPeerInfo(_peer);
});
break;
}
}
void UserpicButton::changeTo(QImage &&image) {
setImage(std::move(image));
}
void UserpicButton::choosePhotoLocally() {
if (!_window) {
return;
@ -246,15 +275,16 @@ void UserpicButton::choosePhotoLocally() {
tr::lng_profile_set_photo_for(tr::now),
[=] { chooseFile(); },
&st::menuIconPhotoSet);
_menu->addAction(
tr::lng_profile_suggest_photo(tr::now),
[=] { chooseFile(ChosenType::Suggest); },
&st::menuIconPhotoSuggest);
if (user->hasPersonalPhoto()) {
if (canSuggestPhoto(user)) {
_menu->addAction(
tr::lng_profile_suggest_photo(tr::now),
[=] { chooseFile(ChosenType::Suggest); },
&st::menuIconPhotoSuggest);
}
if (hasPersonalPhotoLocally()) {
_menu->addAction(
tr::lng_profile_photo_reset(tr::now),
[=] { user->session().api().peerPhoto().clearPersonal(
user); _userpicCustom = false; },
[=] { _resetPersonalRequests.fire({}); },
&st::menuIconRemove);
}
} else {
@ -293,23 +323,47 @@ void UserpicButton::openPeerPhoto() {
}
void UserpicButton::setupPeerViewers() {
_peer->session().changes().peerUpdates(
_peer,
Data::PeerUpdate::Flag::Photo
) | rpl::start_with_next([=] {
processNewPeerPhoto();
update();
}, lifetime());
const auto user = _peer->asUser();
if (user
&& (_source == Source::NonPersonalPhoto
|| _source == Source::NonPersonalIfHasPersonal)) {
user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::FullInfo
) | rpl::map([=] {
return std::pair(
user->session().api().peerPhoto().nonPersonalPhoto(user),
user->hasPersonalPhoto());
}) | rpl::distinct_until_changed() | rpl::skip(
1
) | rpl::start_with_next([=] {
processNewPeerPhoto();
update();
}, _sourceLifetime);
}
if (!user
|| _source == Source::PeerPhoto
|| _source == Source::NonPersonalIfHasPersonal) {
_peer->session().changes().peerUpdates(
_peer,
Data::PeerUpdate::Flag::Photo
) | rpl::start_with_next([=] {
processNewPeerPhoto();
update();
}, _sourceLifetime);
}
_peer->session().downloaderTaskFinished(
) | rpl::filter([=] {
return _waiting;
}) | rpl::start_with_next([=] {
if (!Ui::PeerUserpicLoading(_userpicView)) {
const auto loading = _showPeerUserpic
? Ui::PeerUserpicLoading(_userpicView)
: (_nonPersonalView && !_nonPersonalView->loaded());
if (!loading) {
_waiting = false;
startNewPhotoShowing();
}
}, lifetime());
}, _sourceLifetime);
}
void UserpicButton::paintEvent(QPaintEvent *e) {
@ -363,9 +417,9 @@ void UserpicButton::paintEvent(QPaintEvent *e) {
p,
photoLeft,
photoTop,
_userpicHasImage
(_userpicHasImage
? &st::shadowFg->c
: &_st.changeButton.ripple.color->c);
: &_st.changeButton.ripple.color->c));
if (over || !_userpicHasImage) {
auto iconLeft = (_st.changeIconPosition.x() < 0)
? (_st.photoSize - _st.changeIcon.width()) / 2
@ -474,10 +528,36 @@ QPoint UserpicButton::prepareRippleStartPosition() const {
void UserpicButton::processPeerPhoto() {
Expects(_peer != nullptr);
_userpicView = _peer->createUserpicView();
_waiting = Ui::PeerUserpicLoading(_userpicView);
const auto user = _peer->asUser();
const auto nonPersonal = (user && _source != Source::PeerPhoto)
? _peer->session().api().peerPhoto().nonPersonalPhoto(user)
: nullptr;
_showPeerUserpic = (_source == Source::PeerPhoto)
|| (user
&& !user->hasPersonalPhoto()
&& (_source == Source::NonPersonalPhoto
|| (_source == Source::NonPersonalIfHasPersonal
&& hasPersonalPhotoLocally())));
const auto showNonPersonal = _showPeerUserpic ? nullptr : nonPersonal;
_userpicView = _showPeerUserpic
? _peer->createUserpicView()
: PeerUserpicView();
_nonPersonalView = showNonPersonal
? showNonPersonal->createMediaView()
: nullptr;
_waiting = _showPeerUserpic
? Ui::PeerUserpicLoading(_userpicView)
: (_nonPersonalView && !_nonPersonalView->loaded());
if (_waiting) {
_peer->loadUserpic();
if (_showPeerUserpic) {
_peer->loadUserpic();
} else if (_nonPersonalView) {
AssertIsDebug();
showNonPersonal->load(Data::FileOriginFullUser{
peerToUser(user->id),
});
}
}
if (_role == Role::OpenPhoto) {
if (_peer->userpicPhotoUnknown()) {
@ -492,7 +572,7 @@ void UserpicButton::processPeerPhoto() {
void UserpicButton::updateCursor() {
Expects(_role == Role::OpenPhoto);
auto pointer = _canOpenPhoto
const auto pointer = _canOpenPhoto
|| (_changeOverlayEnabled && _cursorInChangeOverlay);
setPointerCursor(pointer);
}
@ -634,7 +714,7 @@ void UserpicButton::setCursorInChangeOverlay(bool inOverlay) {
}
void UserpicButton::processNewPeerPhoto() {
if (_userpicCustom) {
if (_source == Source::Custom) {
return;
}
processPeerPhoto();
@ -653,14 +733,13 @@ void UserpicButton::grabOldUserpic() {
}
void UserpicButton::startNewPhotoShowing() {
auto oldUniqueKey = _userpicUniqueKey;
const auto oldUniqueKey = _userpicUniqueKey;
prepareUserpicPixmap();
update();
if (_notShownYet) {
return;
}
if (oldUniqueKey != _userpicUniqueKey
} else if (oldUniqueKey != _userpicUniqueKey
|| _a_appearance.animating()) {
startAnimation();
}
@ -733,26 +812,67 @@ void UserpicButton::onStateChanged(
}
}
void UserpicButton::setImage(QImage &&image) {
void UserpicButton::showCustom(QImage &&image) {
grabOldUserpic();
auto size = QSize(_st.photoSize, _st.photoSize);
auto small = image.scaled(
size * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
const auto forum = _peer && _peer->isForum();
_userpic = Ui::PixmapFromImage(forum
? Images::Round(std::move(small), Images::Option::RoundLarge)
: Images::Circle(std::move(small)));
clearStreaming();
_sourceLifetime.destroy();
_source = Source::Custom;
_userpicHasImage = !image.isNull();
if (_userpicHasImage) {
auto size = QSize(_st.photoSize, _st.photoSize);
auto small = image.scaled(
size * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
const auto forum = _peer && _peer->isForum();
_userpic = Ui::PixmapFromImage(forum
? Images::Round(std::move(small), Images::Option::RoundLarge)
: Images::Circle(std::move(small)));
} else {
_userpic = CreateSquarePixmap(_st.photoSize, [&](Painter &p) {
fillShape(p, _st.changeButton.textBg);
});
}
_userpic.setDevicePixelRatio(cRetinaFactor());
_userpicCustom = _userpicHasImage = true;
_userpicUniqueKey = {};
_result = std::move(image);
startNewPhotoShowing();
}
void UserpicButton::showSource(Source source) {
Expects(_peer != nullptr);
Expects(source != Source::Custom); // Show this using showCustom().
Expects(source == Source::PeerPhoto || _peer->isUser());
if (_source != source) {
clearStreaming();
}
_sourceLifetime.destroy();
_source = source;
_result = QImage();
processPeerPhoto();
setupPeerViewers();
prepareUserpicPixmap();
update();
}
void UserpicButton::overrideHasPersonalPhoto(bool has) {
Expects(_peer && _peer->isUser());
_overrideHasPersonalPhoto = has;
}
rpl::producer<> UserpicButton::resetPersonalRequests() const {
return _resetPersonalRequests.events();
}
void UserpicButton::fillShape(QPainter &p, const style::color &color) const {
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
@ -767,22 +887,69 @@ void UserpicButton::fillShape(QPainter &p, const style::color &color) const {
}
void UserpicButton::prepareUserpicPixmap() {
if (_userpicCustom) {
if (_source == Source::Custom) {
return;
}
auto size = _st.photoSize;
_userpicHasImage = _peer
&& (_peer->userpicCloudImage(_userpicView)
|| _role != Role::ChangePhoto);
const auto size = _st.photoSize;
_userpicHasImage = _showPeerUserpic
? (_peer
&& (_peer->userpicCloudImage(_userpicView)
|| _role != Role::ChangePhoto))
: (_source == Source::NonPersonalPhoto
|| (_source == Source::NonPersonalIfHasPersonal
&& hasPersonalPhotoLocally()));
_userpic = CreateSquarePixmap(size, [&](Painter &p) {
if (_userpicHasImage) {
_peer->paintUserpic(p, _userpicView, 0, 0, _st.photoSize);
if (_showPeerUserpic) {
_peer->paintUserpic(p, _userpicView, 0, 0, size);
} else if (_nonPersonalView) {
using Size = Data::PhotoSize;
if (const auto full = _nonPersonalView->image(Size::Large)) {
const auto ratio = style::DevicePixelRatio();
auto image = full->original().scaled(
QSize(size, size) * ratio,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
if (_peer->isForum()) {
image = Images::Round(
std::move(image),
Images::CornersMask(size
* Ui::ForumUserpicRadiusMultiplier()));
} else {
image = Images::Circle(std::move(image));
}
p.drawImage(0, 0, image);
}
} else {
const auto user = _peer->asUser();
auto empty = Ui::EmptyUserpic(
Ui::EmptyUserpic::UserpicColor(
Data::PeerColorIndex(_peer->id)),
((user && user->isInaccessible())
? Ui::EmptyUserpic::InaccessibleName()
: _peer->name()));
if (_peer->isForum()) {
empty.paintRounded(
p,
0,
0,
size,
size,
size * Ui::ForumUserpicRadiusMultiplier());
} else {
empty.paintCircle(p, 0, 0, size, size);
}
}
} else {
fillShape(p, _st.changeButton.textBg);
}
});
_userpicUniqueKey = _userpicHasImage
? _peer->userpicUniqueKey(_userpicView)
? (_showPeerUserpic
? _peer->userpicUniqueKey(_userpicView)
: _nonPersonalView
? InMemoryKey{ _nonPersonalView->owner()->id, 0 }
: InMemoryKey{ _peer->id.value, _peer->id.value })
: InMemoryKey();
}
@ -795,27 +962,7 @@ not_null<Ui::UserpicButton*> CreateUploadSubButton(
&controller->window(),
Ui::UserpicButton::Role::ChoosePhoto,
st::uploadUserpicButton);
const auto border = st::uploadUserpicButtonBorder;
const auto size = upload->rect().marginsAdded(
{ border, border, border, border }
).size();
background->resize(size);
background->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(background);
auto hq = PainterHighQualityEnabler(p);
p.setBrush(st::boxBg);
p.setPen(Qt::NoPen);
p.drawEllipse(background->rect());
}, background->lifetime());
upload->positionValue(
) | rpl::start_with_next([=](QPoint position) {
background->move(position - QPoint(border, border));
}, background->lifetime());
SetupSubButtonBackground(upload, background);
return upload;
}
@ -823,8 +970,16 @@ not_null<Ui::UserpicButton*> CreateUploadSubButton(
not_null<Ui::RpWidget*> parent,
not_null<UserData*> contact,
not_null<Window::SessionController*> controller) {
const auto result = CreateUploadSubButton(parent, controller);
return result;
const auto background = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto upload = Ui::CreateChild<Ui::UserpicButton>(
parent.get(),
controller,
contact,
Ui::UserpicButton::Role::ChoosePhoto,
Ui::UserpicButton::Source::NonPersonalIfHasPersonal,
st::uploadUserpicButton);
SetupSubButtonBackground(upload, background);
return upload;
}
} // namespace Ui

View file

@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class PeerData;
namespace Data {
class PhotoMedia;
} // namespace Data
namespace Window {
class Controller;
class SessionController;
@ -40,16 +44,15 @@ public:
ChoosePhoto,
ChangePhoto,
OpenPhoto,
OpenProfile,
Custom,
};
enum class Source {
PeerPhoto,
NonPersonalPhoto,
NonPersonalIfHasPersonal,
Custom,
};
UserpicButton(
QWidget *parent,
not_null<::Window::Controller*> window,
not_null<PeerData*> peer,
Role role,
const style::UserpicButton &st);
UserpicButton(
QWidget *parent,
not_null<::Window::Controller*> window,
@ -60,11 +63,11 @@ public:
not_null<::Window::SessionController*> controller,
not_null<PeerData*> peer,
Role role,
Source source,
const style::UserpicButton &st);
UserpicButton(
QWidget *parent,
not_null<PeerData*> peer,
Role role,
not_null<PeerData*> peer, // Role::Custom, Source::PeerPhoto
const style::UserpicButton &st);
~UserpicButton();
@ -91,8 +94,11 @@ public:
return std::move(_result);
}
// For Role::OpenPhoto as if it is Role::ChangePhoto.
void changeTo(QImage &&image);
void showCustom(QImage &&image);
void showSource(Source source);
void overrideHasPersonalPhoto(bool has);
[[nodiscard]] rpl::producer<> resetPersonalRequests() const;
protected:
void paintEvent(QPaintEvent *e) override;
@ -106,7 +112,6 @@ protected:
private:
void prepare();
void setImage(QImage &&image);
void setupPeerViewers();
void startAnimation();
void processPeerPhoto();
@ -132,20 +137,24 @@ private:
void grabOldUserpic();
void setClickHandlerByRole();
void requestSuggestAvailability();
void openPeerPhoto();
void choosePhotoLocally();
[[nodiscard]] bool canSuggestPhoto(not_null<UserData*> user) const;
[[nodiscard]] bool hasPersonalPhotoLocally() const;
const style::UserpicButton &_st;
::Window::SessionController *_controller = nullptr;
::Window::Controller *_window = nullptr;
PeerData *_peer = nullptr;
PeerUserpicView _userpicView;
std::shared_ptr<Data::PhotoMedia> _nonPersonalView;
Role _role = Role::ChangePhoto;
bool _notShownYet = true;
bool _waiting = false;
QPixmap _userpic, _oldUserpic;
bool _userpicHasImage = false;
bool _userpicCustom = false;
bool _showPeerUserpic = false;
InMemoryKey _userpicUniqueKey;
Animations::Simple _a_appearance;
QImage _result;
@ -164,6 +173,11 @@ private:
rpl::event_stream<ChosenImage> _chosenImages;
Source _source = Source::Custom;
std::optional<bool> _overrideHasPersonalPhoto;
rpl::event_stream<> _resetPersonalRequests;
rpl::lifetime _sourceLifetime;
};
[[nodiscard]] not_null<Ui::UserpicButton*> CreateUploadSubButton(

View file

@ -354,9 +354,7 @@ MainMenu::MainMenu(
, _controller(controller)
, _userpicButton(
this,
_controller,
_controller->session().user(),
Ui::UserpicButton::Role::Custom,
st::mainMenuUserpic)
, _toggleAccounts(this)
, _setEmojiStatus(this, SetStatusLabel(&controller->session()))