diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp index 4fca854c89..faa83f82ab 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.cpp +++ b/Telegram/SourceFiles/api/api_peer_photo.cpp @@ -208,6 +208,7 @@ void PeerPhoto::clearPersonal(not_null 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(); } } diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index d406e53302..5d5b621188 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -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) { diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 0ca56f9e04..a80c92daa2 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -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, diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 8e38904511..199ee4cad1 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -86,7 +86,6 @@ void GiftBox( const auto userpic = Ui::CreateChild( top, user, - Ui::UserpicButton::Role::Custom, st::defaultUserpicButton); userpic->setAttribute(Qt::WA_TransparentForMouseEvents); top->widthValue( diff --git a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp index 3e33d6b06c..d1a57c91c7 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp @@ -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 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 _focus; Fn _save; + Fn()> _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(); diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index 1bb9093237..fc1b949cbd 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -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(), diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index a5fcbc1195..085780b992 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -470,9 +470,10 @@ object_ptr Controller::createPhotoEdit() { _wrap, object_ptr( _wrap, - &_navigation->parentController()->window(), + _navigation->parentController(), _peer, Ui::UserpicButton::Role::ChangePhoto, + Ui::UserpicButton::Source::PeerPhoto, st::defaultUserpicButton), st::editPeerPhotoMargins); _controls.photo = photoWrap->entity(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index be22e4606e..6291f66676 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -1244,7 +1244,6 @@ void Panel::refreshTopButton() { auto joinAsToggle = object_ptr( widget(), joinAs, - Ui::UserpicButton::Role::Custom, st::groupCallJoinAsToggle); _joinAsToggle.destroy(); _joinAsToggle = std::move(joinAsToggle); diff --git a/Telegram/SourceFiles/data/data_file_origin.cpp b/Telegram/SourceFiles/data/data_file_origin.cpp index 9f0426dfe7..d0e1d446fa 100644 --- a/Telegram/SourceFiles/data/data_file_origin.cpp +++ b/Telegram/SourceFiles/data/data_file_origin.cpp @@ -17,6 +17,12 @@ struct FileReferenceAccumulator { push(item); } } + template + void push(const tl::conditional &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); diff --git a/Telegram/SourceFiles/data/data_file_origin.h b/Telegram/SourceFiles/data/data_file_origin.h index ad258ac192..195ae91884 100644 --- a/Telegram/SourceFiles/data/data_file_origin.h +++ b/Telegram/SourceFiles/data/data_file_origin.h @@ -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( diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index c083faf914..1d2defe70d 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -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 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()); diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index c98141fc02..c955572ef3 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -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()) { diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 09903a9444..c6f98946b2 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -841,9 +841,7 @@ void TopBarWidget::refreshInfoButton() { } else if (const auto peer = _activeChat.key.peer()) { auto info = object_ptr( this, - _controller, peer, - Ui::UserpicButton::Role::Custom, st::topBarInfoButton); info->showSavedMessagesOnSelf(true); _info.destroy(); diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index de3d384ed2..86af787760 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -329,6 +329,7 @@ infoEditContactCover: InfoProfileCover(infoProfileCover) { nameTop: 33px; statusTop: 57px; } +infoEditContactPersonalLeft: 6px; infoProfileInaccessibleUserpic: icon {{ "info/inaccessible_userpic", historyPeerUserpicFg }}; diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 66028290a4..c3b4e05cbe 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -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(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 &&count) { return this; } +std::optional Cover::updatedPersonalPhoto() const { + return _personalChosen; +} + void Cover::initViewers(rpl::producer title) { using Flag = Data::PeerUpdate::Flag; std::move( @@ -397,10 +417,15 @@ void Cover::initViewers(rpl::producer 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() { diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index 469b7962a9..42a40b190b 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -115,6 +115,7 @@ public: [[nodiscard]] rpl::producer
showSection() const { return _showSection.events(); } + [[nodiscard]] std::optional 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 _onlineCount; object_ptr _userpic; + Ui::UserpicButton *_changePersonal = nullptr; + std::optional _personalChosen; object_ptr _iconButton; object_ptr _name = { nullptr }; object_ptr _status = { nullptr }; diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index ab64300476..35f2bd5185 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -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 InnerWidget::setupContent( not_null parent) { auto result = object_ptr(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( result, diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h index 2e50d2135b..4a64959658 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h @@ -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 _nonPersonalView; + Members *_members = nullptr; Cover *_cover = nullptr; Ui::SlideWrap *_sharedMediaWrap = nullptr; diff --git a/Telegram/SourceFiles/intro/intro_signup.cpp b/Telegram/SourceFiles/intro/intro_signup.cpp index e85c7186bc..4b61ba4403 100644 --- a/Telegram/SourceFiles/intro/intro_signup.cpp +++ b/Telegram/SourceFiles/intro/intro_signup.cpp @@ -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()) diff --git a/Telegram/SourceFiles/passport/passport_panel_form.cpp b/Telegram/SourceFiles/passport/passport_panel_form.cpp index 817b76540e..dd3c894aa1 100644 --- a/Telegram/SourceFiles/passport/passport_panel_form.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_form.cpp @@ -73,7 +73,6 @@ not_null PanelForm::setupContent() { object_ptr( userpicWrap, bot, - Ui::UserpicButton::Role::Custom, st::passportFormUserpic)); userpicWrap->widthValue( ) | rpl::start_with_next([=](int width) { diff --git a/Telegram/SourceFiles/passport/passport_panel_password.cpp b/Telegram/SourceFiles/passport/passport_panel_password.cpp index 6dfdd8f7d1..0f8e8e77a8 100644 --- a/Telegram/SourceFiles/passport/passport_panel_password.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_password.cpp @@ -31,7 +31,6 @@ PanelAskPassword::PanelAskPassword( , _userpic( this, _controller->bot(), - Ui::UserpicButton::Role::Custom, st::passportPasswordUserpic) , _about1( this, diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index bbe91eca7f..8adc160917 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -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()); diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 59ad345136..10ea09c5ae 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -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)); }); diff --git a/Telegram/SourceFiles/ui/controls/userpic_button.cpp b/Telegram/SourceFiles/ui/controls/userpic_button.cpp index da4d6d1aff..8291847518 100644 --- a/Telegram/SourceFiles/ui/controls/userpic_button.cpp +++ b/Telegram/SourceFiles/ui/controls/userpic_button.cpp @@ -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 upload, + not_null background) { + const auto border = st::uploadUserpicButtonBorder; + const auto size = upload->rect().marginsAdded( + { border, border, border, border } + ).size(); -UserpicButton::UserpicButton( - QWidget *parent, - not_null window, - not_null 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, @@ -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 controller, not_null 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 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 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 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 CreateUploadSubButton( not_null parent, not_null contact, not_null controller) { - const auto result = CreateUploadSubButton(parent, controller); - return result; + const auto background = Ui::CreateChild(parent.get()); + const auto upload = Ui::CreateChild( + parent.get(), + controller, + contact, + Ui::UserpicButton::Role::ChoosePhoto, + Ui::UserpicButton::Source::NonPersonalIfHasPersonal, + st::uploadUserpicButton); + SetupSubButtonBackground(upload, background); + return upload; } } // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/userpic_button.h b/Telegram/SourceFiles/ui/controls/userpic_button.h index acad63812a..0feb43f442 100644 --- a/Telegram/SourceFiles/ui/controls/userpic_button.h +++ b/Telegram/SourceFiles/ui/controls/userpic_button.h @@ -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 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 peer, Role role, + Source source, const style::UserpicButton &st); UserpicButton( QWidget *parent, - not_null peer, - Role role, + not_null 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 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 _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 _chosenImages; + Source _source = Source::Custom; + std::optional _overrideHasPersonalPhoto; + rpl::event_stream<> _resetPersonalRequests; + rpl::lifetime _sourceLifetime; + }; [[nodiscard]] not_null CreateUploadSubButton( diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index c7def6ab24..915617aae8 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -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()))