From 201edb2e6996e234b800ea42ac32897ed6f8684e Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 May 2022 15:38:35 +0400 Subject: [PATCH] Play premium video avatars in chats list. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/data/data_channel.cpp | 5 +- Telegram/SourceFiles/data/data_chat.cpp | 5 +- Telegram/SourceFiles/data/data_peer.cpp | 36 +- Telegram/SourceFiles/data/data_peer.h | 18 +- Telegram/SourceFiles/data/data_user.cpp | 74 +++- Telegram/SourceFiles/data/data_user.h | 69 +--- .../dialogs/dialogs_inner_widget.cpp | 77 +++- .../dialogs/dialogs_inner_widget.h | 12 + Telegram/SourceFiles/dialogs/dialogs_row.cpp | 332 ++++++++++-------- Telegram/SourceFiles/dialogs/dialogs_row.h | 61 ++-- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 12 +- .../SourceFiles/dialogs/ui/dialogs_layout.h | 3 + .../dialogs/ui/dialogs_video_userpic.cpp | 125 +++++++ .../dialogs/ui/dialogs_video_userpic.h | 54 +++ .../admin_log/history_admin_log_inner.cpp | 2 +- .../SourceFiles/history/history_service.cpp | 1 + .../settings/settings_information.cpp | 11 +- .../SourceFiles/storage/serialize_peer.cpp | 11 +- 19 files changed, 657 insertions(+), 253 deletions(-) create mode 100644 Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.cpp create mode 100644 Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d66ff8495e..cc279de095 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -544,6 +544,8 @@ PRIVATE dialogs/ui/dialogs_layout.h dialogs/ui/dialogs_message_view.cpp dialogs/ui/dialogs_message_view.h + dialogs/ui/dialogs_video_userpic.cpp + dialogs/ui/dialogs_video_userpic.h editor/color_picker.cpp editor/color_picker.h editor/controllers/controllers.h diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index ed04364201..dcea3b92da 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -92,7 +92,10 @@ ChannelData::ChannelData(not_null owner, PeerId id) void ChannelData::setPhoto(const MTPChatPhoto &photo) { photo.match([&](const MTPDchatPhoto & data) { - updateUserpic(data.vphoto_id().v, data.vdc_id().v); + updateUserpic( + data.vphoto_id().v, + data.vdc_id().v, + data.is_has_video()); }, [&](const MTPDchatPhotoEmpty &) { clearUserpic(); }); diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 9ad2968bd7..044231db88 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -39,7 +39,10 @@ ChatData::ChatData(not_null owner, PeerId id) void ChatData::setPhoto(const MTPChatPhoto &photo) { photo.match([&](const MTPDchatPhoto &data) { - updateUserpic(data.vphoto_id().v, data.vdc_id().v); + updateUserpic( + data.vphoto_id().v, + data.vdc_id().v, + data.is_has_video()); }, [&](const MTPDchatPhotoEmpty &) { clearUserpic(); }); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index ea3d7ea2bc..8c9f045d90 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -307,8 +307,12 @@ ClickHandlerPtr PeerData::createOpenLink() { return std::make_shared(this); } -void PeerData::setUserpic(PhotoId photoId, const ImageLocation &location) { +void PeerData::setUserpic( + PhotoId photoId, + const ImageLocation &location, + bool hasVideo) { _userpicPhotoId = photoId; + _userpicHasVideo = hasVideo; _userpic.set(&session(), ImageWithLocation{ .location = location }); } @@ -479,7 +483,10 @@ Data::FileOrigin PeerData::userpicPhotoOrigin() const { : Data::FileOrigin(); } -void PeerData::updateUserpic(PhotoId photoId, MTP::DcId dcId) { +void PeerData::updateUserpic( + PhotoId photoId, + MTP::DcId dcId, + bool hasVideo) { setUserpicChecked( photoId, ImageLocation( @@ -491,19 +498,27 @@ void PeerData::updateUserpic(PhotoId photoId, MTP::DcId dcId) { input, MTP_long(photoId))) }, kUserpicSize, - kUserpicSize)); + kUserpicSize), + hasVideo); } void PeerData::clearUserpic() { - setUserpicChecked(PhotoId(), ImageLocation()); + setUserpicChecked(PhotoId(), ImageLocation(), false); } void PeerData::setUserpicChecked( PhotoId photoId, - const ImageLocation &location) { - if (_userpicPhotoId != photoId || _userpic.location() != location) { - setUserpic(photoId, location); + const ImageLocation &location, + bool hasVideo) { + if (_userpicPhotoId != photoId + || _userpic.location() != location + || _userpicHasVideo != hasVideo) { + const auto known = !userpicPhotoUnknown(); + setUserpic(photoId, location, hasVideo); session().changes().peerUpdated(this, UpdateFlag::Photo); + if (known && isPremium() && userpicPhotoUnknown()) { + updateFull(); + } } } @@ -849,6 +864,13 @@ bool PeerData::isVerified() const { return false; } +bool PeerData::isPremium() const { + if (const auto user = asUser()) { + return user->isPremium(); + } + return false; +} + bool PeerData::isScam() const { if (const auto user = asUser()) { return user->isScam(); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index d42dfc1b12..f6dbff8e40 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -172,6 +172,7 @@ public: } [[nodiscard]] bool isSelf() const; [[nodiscard]] bool isVerified() const; + [[nodiscard]] bool isPremium() const; [[nodiscard]] bool isScam() const; [[nodiscard]] bool isFake() const; [[nodiscard]] bool isMegagroup() const; @@ -266,7 +267,10 @@ public: return _nameFirstLetters; } - void setUserpic(PhotoId photoId, const ImageLocation &location); + void setUserpic( + PhotoId photoId, + const ImageLocation &location, + bool hasVideo); void setUserpicPhoto(const MTPPhoto &data); void paintUserpic( Painter &p, @@ -320,6 +324,9 @@ public: [[nodiscard]] PhotoId userpicPhotoId() const { return userpicPhotoUnknown() ? 0 : _userpicPhotoId; } + [[nodiscard]] bool userpicHasVideo() const { + return _userpicHasVideo; + } [[nodiscard]] Data::FileOrigin userpicOrigin() const; [[nodiscard]] Data::FileOrigin userpicPhotoOrigin() const; @@ -426,7 +433,7 @@ protected: const QString &newName, const QString &newNameOrPhone, const QString &newUsername); - void updateUserpic(PhotoId photoId, MTP::DcId dcId); + void updateUserpic(PhotoId photoId, MTP::DcId dcId, bool hasVideo); void clearUserpic(); private: @@ -435,12 +442,17 @@ private: [[nodiscard]] virtual auto unavailableReasons() const -> const std::vector &; - void setUserpicChecked(PhotoId photoId, const ImageLocation &location); + void setUserpicChecked( + PhotoId photoId, + const ImageLocation &location, + bool hasVideo); const not_null _owner; mutable Data::CloudImage _userpic; PhotoId _userpicPhotoId = kUnknownPhotoId; + bool _userpicHasVideo = false; + mutable std::unique_ptr _userpicEmpty; Ui::Text::String _nameText; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index e52c66cd20..7dfe6a038e 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -47,7 +47,10 @@ void UserData::setIsContact(bool is) { // see Serialize::readPeer as well void UserData::setPhoto(const MTPUserProfilePhoto &photo) { photo.match([&](const MTPDuserProfilePhoto &data) { - updateUserpic(data.vphoto_id().v, data.vdc_id().v); + updateUserpic( + data.vphoto_id().v, + data.vdc_id().v, + data.is_has_video()); }, [&](const MTPDuserProfilePhotoEmpty &) { clearUserpic(); }); @@ -191,6 +194,75 @@ void UserData::removeFlags(UserDataFlags which) { _flags.remove(which & ~UserDataFlag::Self); } +bool UserData::isVerified() const { + return flags() & UserDataFlag::Verified; +} + +bool UserData::isScam() const { + return flags() & UserDataFlag::Scam; +} + +bool UserData::isFake() const { + return flags() & UserDataFlag::Fake; +} + +bool UserData::isPremium() const { + return flags() & UserDataFlag::Premium; +} + +bool UserData::isBotInlineGeo() const { + return flags() & UserDataFlag::BotInlineGeo; +} + +bool UserData::isBot() const { + return botInfo != nullptr; +} + +bool UserData::isSupport() const { + return flags() & UserDataFlag::Support; +} + +bool UserData::isInaccessible() const { + return flags() & UserDataFlag::Deleted; +} + +bool UserData::canWrite() const { + // Duplicated in Data::CanWriteValue(). + return !isInaccessible() && !isRepliesChat(); +} + +bool UserData::applyMinPhoto() const { + return !(flags() & UserDataFlag::DiscardMinPhoto); +} + +bool UserData::canAddContact() const { + return canShareThisContact() && !isContact(); +} + +bool UserData::canShareThisContactFast() const { + return !_phone.isEmpty(); +} + +const QString &UserData::phone() const { + return _phone; +} + +UserData::ContactStatus UserData::contactStatus() const { + return _contactStatus; +} + +bool UserData::isContact() const { + return (contactStatus() == ContactStatus::Contact); +} + +UserData::CallsStatus UserData::callsStatus() const { + return _callsStatus; +} + +int UserData::commonChatsCount() const { + return _commonChatsCount; +} + void UserData::setCallsStatus(CallsStatus callsStatus) { if (callsStatus != _callsStatus) { _callsStatus = callsStatus; diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 817d514685..a16da999a1 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -88,58 +88,31 @@ public: void addFlags(UserDataFlags which); void removeFlags(UserDataFlags which); - [[nodiscard]] bool isVerified() const { - return flags() & UserDataFlag::Verified; - } - [[nodiscard]] bool isScam() const { - return flags() & UserDataFlag::Scam; - } - [[nodiscard]] bool isFake() const { - return flags() & UserDataFlag::Fake; - } - [[nodiscard]] bool isPremium() const { - return flags() & UserDataFlag::Premium; - } - [[nodiscard]] bool isBotInlineGeo() const { - return flags() & UserDataFlag::BotInlineGeo; - } - [[nodiscard]] bool isBot() const { - return botInfo != nullptr; - } - [[nodiscard]] bool isSupport() const { - return flags() & UserDataFlag::Support; - } - [[nodiscard]] bool isInaccessible() const { - return flags() & UserDataFlag::Deleted; - } - [[nodiscard]] bool canWrite() const { - // Duplicated in Data::CanWriteValue(). - return !isInaccessible() && !isRepliesChat(); - } - [[nodiscard]] bool applyMinPhoto() const { - return !(flags() & UserDataFlag::DiscardMinPhoto); - } + [[nodiscard]] bool isVerified() const; + [[nodiscard]] bool isScam() const; + [[nodiscard]] bool isFake() const; + [[nodiscard]] bool isPremium() const; + [[nodiscard]] bool isBotInlineGeo() const; + [[nodiscard]] bool isBot() const; + [[nodiscard]] bool isSupport() const; + [[nodiscard]] bool isInaccessible() const; + [[nodiscard]] bool canWrite() const; + [[nodiscard]] bool applyMinPhoto() const; [[nodiscard]] bool canShareThisContact() const; - [[nodiscard]] bool canAddContact() const { - return canShareThisContact() && !isContact(); - } + [[nodiscard]] bool canAddContact() const; // In Data::Session::processUsers() we check only that. // When actually trying to share contact we perform // a full check by canShareThisContact() call. - [[nodiscard]] bool canShareThisContactFast() const { - return !_phone.isEmpty(); - } + [[nodiscard]] bool canShareThisContactFast() const; MTPInputUser inputUser = MTP_inputUserEmpty(); QString firstName; QString lastName; QString username; - [[nodiscard]] const QString &phone() const { - return _phone; - } + [[nodiscard]] const QString &phone() const; QString nameOrPhone; Ui::Text::String phoneText; TimeId onlineTill = 0; @@ -149,12 +122,8 @@ public: Contact, NotContact, }; - [[nodiscard]] ContactStatus contactStatus() const { - return _contactStatus; - } - [[nodiscard]] bool isContact() const { - return (contactStatus() == ContactStatus::Contact); - } + [[nodiscard]] ContactStatus contactStatus() const; + [[nodiscard]] bool isContact() const; void setIsContact(bool is); enum class CallsStatus : char { @@ -163,9 +132,7 @@ public: Disabled, Private, }; - CallsStatus callsStatus() const { - return _callsStatus; - } + CallsStatus callsStatus() const; bool hasCalls() const; void setCallsStatus(CallsStatus callsStatus); @@ -174,9 +141,7 @@ public: void setUnavailableReasons( std::vector &&reasons); - int commonChatsCount() const { - return _commonChatsCount; - } + int commonChatsCount() const; void setCommonChatsCount(int count); private: diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 692844743d..523b300490 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_indexed_list.h" #include "dialogs/ui/dialogs_layout.h" +#include "dialogs/ui/dialogs_video_userpic.h" #include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_search_from_controllers.h" #include "history/history.h" @@ -218,9 +219,21 @@ InnerWidget::InnerWidget( UpdateFlag::Name | UpdateFlag::Photo | UpdateFlag::IsContact + | UpdateFlag::FullInfo ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { - if (update.flags & (UpdateFlag::Name | UpdateFlag::Photo)) { - this->update(); + if (update.flags + & (UpdateFlag::Name + | UpdateFlag::Photo + | UpdateFlag::FullInfo)) { + const auto peer = update.peer; + const auto history = peer->owner().historyLoaded(peer); + if (_state == WidgetState::Default) { + if (history) { + updateDialogRow({ history, FullMsgId() }); + } + } else { + this->update(); + } _updated.fire({}); } if (update.flags & UpdateFlag::IsContact) { @@ -425,11 +438,13 @@ void InnerWidget::paintEvent(QPaintEvent *e) { if (xadd || yadd) { p.translate(xadd, yadd); } - const auto isActive = (row->key() == active); - const auto isSelected = (row->key() == selected); + const auto key = row->key(); + const auto isActive = (key == active); + const auto isSelected = (key == selected); Ui::RowPainter::paint( p, row, + validateVideoUserpic(row), _filterId, fullWidth, isActive, @@ -548,6 +563,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { Ui::RowPainter::paint( p, _filterResults[from], + validateVideoUserpic(row), _filterId, fullWidth, active, @@ -652,6 +668,34 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } } +Ui::VideoUserpic *InnerWidget::validateVideoUserpic(not_null row) { + const auto history = row->history(); + return history ? validateVideoUserpic(history) : nullptr; +} + +Ui::VideoUserpic *InnerWidget::validateVideoUserpic( + not_null history) { + const auto peer = history->peer; + if (!peer->isPremium() + || peer->userpicPhotoUnknown() + || !peer->userpicHasVideo()) { + _videoUserpics.remove(peer); + return nullptr; + } + const auto i = _videoUserpics.find(peer); + if (i != end(_videoUserpics)) { + return i->second.get(); + } + const auto update = [=] { + updateDialogRow({ history, FullMsgId() }); + updateSearchResult(history->peer); + }; + return _videoUserpics.emplace(peer, std::make_unique( + peer, + update + )).first->second.get(); +} + void InnerWidget::paintCollapsedRows(Painter &p, QRect clip) const { auto index = 0; const auto rowHeight = st::dialogsImportantBarHeight; @@ -1528,15 +1572,18 @@ void InnerWidget::refreshDialogRow(RowDescriptor row) { void InnerWidget::updateSearchResult(not_null peer) { if (_state == WidgetState::Filtered) { - if (!_peerSearchResults.empty()) { - auto index = 0, add = peerSearchOffset(); - for (const auto &result : _peerSearchResults) { - if (result->peer == peer) { - rtlupdate(0, add + index * st::dialogsRowHeight, width(), st::dialogsRowHeight); - break; - } - ++index; - } + const auto i = ranges::find( + _peerSearchResults, + peer, + &PeerSearchResult::peer); + if (i != end(_peerSearchResults)) { + const auto top = peerSearchOffset(); + const auto index = (i - begin(_peerSearchResults)); + rtlupdate( + 0, + top + index * st::dialogsRowHeight, + width(), + st::dialogsRowHeight); } } } @@ -1968,11 +2015,13 @@ void InnerWidget::visibleTopBottomUpdated( _visibleTop = visibleTop; _visibleBottom = visibleBottom; loadPeerPhotos(); - if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) >= height()) { + if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) + >= height()) { if (_loadMoreCallback) { _loadMoreCallback(); } } + } void InnerWidget::itemRemoved(not_null item) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 9cc9e7b860..9fb221eeaa 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -38,6 +38,11 @@ namespace Data { class CloudImageView; } // namespace Data +namespace Dialogs::Ui { +using namespace ::Ui; +class VideoUserpic; +} // namespace Dialogs::Ui + namespace Dialogs { class Row; @@ -312,6 +317,9 @@ private: const Ui::Text::String &text) const; void refreshSearchInChatLabel(); + Ui::VideoUserpic *validateVideoUserpic(not_null row); + Ui::VideoUserpic *validateVideoUserpic(not_null history); + void clearSearchResults(bool clearPeerSearchResults = true); void updateSelectedRow(Key key = Key()); @@ -411,6 +419,10 @@ private: Ui::Text::String _searchFromUserText; RowDescriptor _menuRow; + base::flat_map< + not_null, + std::unique_ptr> _videoUserpics; + Fn _loadMoreCallback; rpl::event_stream<> _listBottomReached; rpl::event_stream _chosenRow; diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index 716c45eec0..e736bd78ac 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" #include "dialogs/dialogs_entry.h" +#include "dialogs/ui/dialogs_video_userpic.h" #include "data/data_folder.h" #include "data/data_peer_values.h" #include "history/history.h" @@ -79,37 +80,27 @@ namespace { : accumulated; } +void PaintUserpic( + Painter &p, + not_null peer, + Ui::VideoUserpic *videoUserpic, + std::shared_ptr &view, + int x, + int y, + int outerWidth, + int size) { + if (videoUserpic) { + videoUserpic->paintLeft(p, view, x, y, outerWidth, size); + } else { + peer->paintUserpicLeft(p, view, x, y, outerWidth, size); + } +} + } // namespace BasicRow::BasicRow() = default; BasicRow::~BasicRow() = default; -void BasicRow::setCornerBadgeShown( - bool shown, - Fn updateCallback) const { - if (_cornerBadgeShown == shown) { - return; - } - _cornerBadgeShown = shown; - if (_cornerBadgeUserpic && _cornerBadgeUserpic->animation.animating()) { - _cornerBadgeUserpic->animation.change( - _cornerBadgeShown ? 1. : 0., - st::dialogsOnlineBadgeDuration); - } else if (updateCallback) { - ensureCornerBadgeUserpic(); - _cornerBadgeUserpic->animation.start( - std::move(updateCallback), - _cornerBadgeShown ? 0. : 1., - _cornerBadgeShown ? 1. : 0., - st::dialogsOnlineBadgeDuration); - } - if (!_cornerBadgeShown - && _cornerBadgeUserpic - && !_cornerBadgeUserpic->animation.animating()) { - _cornerBadgeUserpic = nullptr; - } -} - void BasicRow::addRipple( QPoint origin, QSize size, @@ -144,130 +135,23 @@ void BasicRow::paintRipple( } } -void BasicRow::updateCornerBadgeShown( - not_null peer, - Fn updateCallback) const { - const auto shown = [&] { - if (const auto user = peer->asUser()) { - return Data::IsUserOnline(user); - } else if (const auto channel = peer->asChannel()) { - return Data::ChannelHasActiveCall(channel); - } - return false; - }(); - setCornerBadgeShown(shown, std::move(updateCallback)); -} - -void BasicRow::ensureCornerBadgeUserpic() const { - if (_cornerBadgeUserpic) { - return; - } - _cornerBadgeUserpic = std::make_unique(); -} - -void BasicRow::PaintCornerBadgeFrame( - not_null data, - not_null peer, - std::shared_ptr &view) { - data->frame.fill(Qt::transparent); - - Painter q(&data->frame); - peer->paintUserpic( - q, - view, - 0, - 0, - st::dialogsPhotoSize); - - PainterHighQualityEnabler hq(q); - q.setCompositionMode(QPainter::CompositionMode_Source); - - const auto size = peer->isUser() - ? st::dialogsOnlineBadgeSize - : st::dialogsCallBadgeSize; - const auto stroke = st::dialogsOnlineBadgeStroke; - const auto skip = peer->isUser() - ? st::dialogsOnlineBadgeSkip - : st::dialogsCallBadgeSkip; - const auto shrink = (size / 2) * (1. - data->shown); - - auto pen = QPen(Qt::transparent); - pen.setWidthF(stroke * data->shown); - q.setPen(pen); - q.setBrush(data->active - ? st::dialogsOnlineBadgeFgActive - : st::dialogsOnlineBadgeFg); - q.drawEllipse(QRectF( - st::dialogsPhotoSize - skip.x() - size, - st::dialogsPhotoSize - skip.y() - size, - size, - size - ).marginsRemoved({ shrink, shrink, shrink, shrink })); -} - void BasicRow::paintUserpic( Painter &p, not_null peer, + Ui::VideoUserpic *videoUserpic, History *historyForCornerBadge, crl::time now, bool active, int fullWidth) const { - updateCornerBadgeShown(peer); - - const auto shown = _cornerBadgeUserpic - ? _cornerBadgeUserpic->animation.value(_cornerBadgeShown ? 1. : 0.) - : (_cornerBadgeShown ? 1. : 0.); - if (!historyForCornerBadge || shown == 0.) { - peer->paintUserpicLeft( - p, - _userpic, - st::dialogsPadding.x(), - st::dialogsPadding.y(), - fullWidth, - st::dialogsPhotoSize); - if (!historyForCornerBadge || !_cornerBadgeShown) { - _cornerBadgeUserpic = nullptr; - } - return; - } - ensureCornerBadgeUserpic(); - if (_cornerBadgeUserpic->frame.isNull()) { - _cornerBadgeUserpic->frame = QImage( - st::dialogsPhotoSize * cRetinaFactor(), - st::dialogsPhotoSize * cRetinaFactor(), - QImage::Format_ARGB32_Premultiplied); - _cornerBadgeUserpic->frame.setDevicePixelRatio(cRetinaFactor()); - } - const auto key = peer->userpicUniqueKey(_userpic); - if (_cornerBadgeUserpic->shown != shown - || _cornerBadgeUserpic->key != key - || _cornerBadgeUserpic->active != active) { - _cornerBadgeUserpic->shown = shown; - _cornerBadgeUserpic->key = key; - _cornerBadgeUserpic->active = active; - PaintCornerBadgeFrame(_cornerBadgeUserpic.get(), peer, _userpic); - } - p.drawImage(st::dialogsPadding, _cornerBadgeUserpic->frame); - if (historyForCornerBadge->peer->isUser()) { - return; - } - const auto actionPainter = historyForCornerBadge->sendActionPainter(); - const auto bg = active - ? st::dialogsBgActive - : st::dialogsBg; - const auto size = st::dialogsCallBadgeSize; - const auto skip = st::dialogsCallBadgeSkip; - p.setOpacity(shown); - p.translate(st::dialogsPadding); - actionPainter->paintSpeaking( + PaintUserpic( p, - st::dialogsPhotoSize - skip.x() - size, - st::dialogsPhotoSize - skip.y() - size, + peer, + videoUserpic, + _userpic, + st::dialogsPadding.x(), + st::dialogsPadding.y(), fullWidth, - bg, - now); - p.translate(-st::dialogsPadding); - p.setOpacity(1.); + st::dialogsPhotoSize); } Row::Row(Key key, int pos) : _id(key), _pos(pos) { @@ -297,6 +181,172 @@ void Row::validateListEntryCache() const { Ui::ItemTextDefaultOptions()); } +void Row::setCornerBadgeShown( + bool shown, + Fn updateCallback) const { + if (_cornerBadgeShown == shown) { + return; + } + _cornerBadgeShown = shown; + if (_cornerBadgeUserpic && _cornerBadgeUserpic->animation.animating()) { + _cornerBadgeUserpic->animation.change( + _cornerBadgeShown ? 1. : 0., + st::dialogsOnlineBadgeDuration); + } else if (updateCallback) { + ensureCornerBadgeUserpic(); + _cornerBadgeUserpic->animation.start( + std::move(updateCallback), + _cornerBadgeShown ? 0. : 1., + _cornerBadgeShown ? 1. : 0., + st::dialogsOnlineBadgeDuration); + } + if (!_cornerBadgeShown + && _cornerBadgeUserpic + && !_cornerBadgeUserpic->animation.animating()) { + _cornerBadgeUserpic = nullptr; + } +} + +void Row::updateCornerBadgeShown( + not_null peer, + Fn updateCallback) const { + const auto shown = [&] { + if (const auto user = peer->asUser()) { + return Data::IsUserOnline(user); + } else if (const auto channel = peer->asChannel()) { + return Data::ChannelHasActiveCall(channel); + } + return false; + }(); + setCornerBadgeShown(shown, std::move(updateCallback)); +} + +void Row::ensureCornerBadgeUserpic() const { + if (_cornerBadgeUserpic) { + return; + } + _cornerBadgeUserpic = std::make_unique(); +} + +void Row::PaintCornerBadgeFrame( + not_null data, + not_null peer, + Ui::VideoUserpic *videoUserpic, + std::shared_ptr &view) { + data->frame.fill(Qt::transparent); + + Painter q(&data->frame); + PaintUserpic( + q, + peer, + videoUserpic, + view, + 0, + 0, + data->frame.width() / data->frame.devicePixelRatio(), + st::dialogsPhotoSize); + + PainterHighQualityEnabler hq(q); + q.setCompositionMode(QPainter::CompositionMode_Source); + + const auto size = peer->isUser() + ? st::dialogsOnlineBadgeSize + : st::dialogsCallBadgeSize; + const auto stroke = st::dialogsOnlineBadgeStroke; + const auto skip = peer->isUser() + ? st::dialogsOnlineBadgeSkip + : st::dialogsCallBadgeSkip; + const auto shrink = (size / 2) * (1. - data->shown); + + auto pen = QPen(Qt::transparent); + pen.setWidthF(stroke * data->shown); + q.setPen(pen); + q.setBrush(data->active + ? st::dialogsOnlineBadgeFgActive + : st::dialogsOnlineBadgeFg); + q.drawEllipse(QRectF( + st::dialogsPhotoSize - skip.x() - size, + st::dialogsPhotoSize - skip.y() - size, + size, + size + ).marginsRemoved({ shrink, shrink, shrink, shrink })); +} + +void Row::paintUserpic( + Painter &p, + not_null peer, + Ui::VideoUserpic *videoUserpic, + History *historyForCornerBadge, + crl::time now, + bool active, + int fullWidth) const { + updateCornerBadgeShown(peer); + + const auto shown = _cornerBadgeUserpic + ? _cornerBadgeUserpic->animation.value(_cornerBadgeShown ? 1. : 0.) + : (_cornerBadgeShown ? 1. : 0.); + if (!historyForCornerBadge || shown == 0.) { + BasicRow::paintUserpic( + p, + peer, + videoUserpic, + historyForCornerBadge, + now, + active, + fullWidth); + if (!historyForCornerBadge || !_cornerBadgeShown) { + _cornerBadgeUserpic = nullptr; + } + return; + } + ensureCornerBadgeUserpic(); + if (_cornerBadgeUserpic->frame.isNull()) { + _cornerBadgeUserpic->frame = QImage( + st::dialogsPhotoSize * cRetinaFactor(), + st::dialogsPhotoSize * cRetinaFactor(), + QImage::Format_ARGB32_Premultiplied); + _cornerBadgeUserpic->frame.setDevicePixelRatio(cRetinaFactor()); + } + const auto key = peer->userpicUniqueKey(userpicView()); + const auto frameIndex = videoUserpic ? videoUserpic->frameIndex() : -1; + if (_cornerBadgeUserpic->shown != shown + || _cornerBadgeUserpic->key != key + || _cornerBadgeUserpic->active != active + || _cornerBadgeUserpic->frameIndex != frameIndex + || videoUserpic) { + _cornerBadgeUserpic->shown = shown; + _cornerBadgeUserpic->key = key; + _cornerBadgeUserpic->active = active; + _cornerBadgeUserpic->frameIndex = frameIndex; + PaintCornerBadgeFrame( + _cornerBadgeUserpic.get(), + peer, + videoUserpic, + userpicView()); + } + p.drawImage(st::dialogsPadding, _cornerBadgeUserpic->frame); + if (historyForCornerBadge->peer->isUser()) { + return; + } + const auto actionPainter = historyForCornerBadge->sendActionPainter(); + const auto bg = active + ? st::dialogsBgActive + : st::dialogsBg; + const auto size = st::dialogsCallBadgeSize; + const auto skip = st::dialogsCallBadgeSkip; + p.setOpacity(shown); + p.translate(st::dialogsPadding); + actionPainter->paintSpeaking( + p, + st::dialogsPhotoSize - skip.x() - size, + st::dialogsPhotoSize - skip.y() - size, + fullWidth, + bg, + now); + p.translate(-st::dialogsPadding); + p.setOpacity(1.); +} + FakeRow::FakeRow(Key searchInChat, not_null item) : _searchInChat(searchInChat) , _item(item) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index 21e45a14cc..a9601e658f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -26,6 +26,7 @@ class RippleAnimation; namespace Dialogs::Ui { using namespace ::Ui; class RowPainter; +class VideoUserpic; } // namespace Dialogs::Ui namespace Dialogs { @@ -35,14 +36,12 @@ enum class SortMode; class BasicRow { public: BasicRow(); - ~BasicRow(); + virtual ~BasicRow(); - void updateCornerBadgeShown( - not_null peer, - Fn updateCallback = nullptr) const; - void paintUserpic( + virtual void paintUserpic( Painter &p, not_null peer, + Ui::VideoUserpic *videoUserpic, History *historyForCornerBadge, crl::time now, bool active, @@ -63,27 +62,8 @@ public: } private: - struct CornerBadgeUserpic { - InMemoryKey key; - float64 shown = 0.; - bool active = false; - QImage frame; - Ui::Animations::Simple animation; - }; - - void setCornerBadgeShown( - bool shown, - Fn updateCallback) const; - void ensureCornerBadgeUserpic() const; - static void PaintCornerBadgeFrame( - not_null data, - not_null peer, - std::shared_ptr &view); - mutable std::shared_ptr _userpic; mutable std::unique_ptr _ripple; - mutable std::unique_ptr _cornerBadgeUserpic; - mutable bool _cornerBadgeShown = false; }; @@ -94,6 +74,18 @@ public: } Row(Key key, int pos); + void updateCornerBadgeShown( + not_null peer, + Fn updateCallback = nullptr) const; + void paintUserpic( + Painter &p, + not_null peer, + Ui::VideoUserpic *videoUserpic, + History *historyForCornerBadge, + crl::time now, + bool active, + int fullWidth) const final override; + [[nodiscard]] Key key() const { return _id; } @@ -122,10 +114,31 @@ public: private: friend class List; + struct CornerBadgeUserpic { + InMemoryKey key; + float64 shown = 0.; + int frameIndex = -1; + bool active = false; + QImage frame; + Ui::Animations::Simple animation; + }; + + void setCornerBadgeShown( + bool shown, + Fn updateCallback) const; + void ensureCornerBadgeUserpic() const; + static void PaintCornerBadgeFrame( + not_null data, + not_null peer, + Ui::VideoUserpic *videoUserpic, + std::shared_ptr &view); + Key _id; int _pos = 0; mutable uint32 _listEntryCacheVersion = 0; mutable Ui::Text::String _listEntryCache; + mutable std::unique_ptr _cornerBadgeUserpic; + mutable bool _cornerBadgeShown = false; }; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index b688d56d17..cf9290d0fe 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_drafts.h" #include "data/data_session.h" #include "dialogs/dialogs_list.h" +#include "dialogs/ui/dialogs_video_userpic.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" #include "storage/localstorage.h" @@ -310,6 +311,7 @@ void paintRow( not_null row, not_null entry, Dialogs::Key chat, + VideoUserpic *videoUserpic, FilterId filterId, PeerData *from, const HiddenSenderInfo *hiddenSenderInfo, @@ -360,6 +362,7 @@ void paintRow( row->paintUserpic( p, from, + videoUserpic, (flags & Flag::AllowUserOnline) ? history : nullptr, ms, active, @@ -450,13 +453,13 @@ void paintRow( if (!ShowSendActionInDialogs(history) || !history->sendActionPainter()->paint(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { if (history->cloudDraftTextCache.isEmpty()) { - auto draftWrapped = Ui::Text::PlainLink( + auto draftWrapped = Text::PlainLink( tr::lng_dialogs_text_from_wrapped( tr::now, lt_from, tr::lng_from_draft(tr::now))); auto draftText = supportMode - ? Ui::Text::PlainLink( + ? Text::PlainLink( Support::ChatOccupiedString(history)) : tr::lng_dialogs_text_with_from( tr::now, @@ -464,7 +467,7 @@ void paintRow( draftWrapped, lt_message, { .text = draft->textWithTags.text }, - Ui::Text::WithEntities); + Text::WithEntities); history->cloudDraftTextCache.setMarkedText( st::dialogsTextStyle, draftText, @@ -790,6 +793,7 @@ QRect PaintUnreadBadge( void RowPainter::paint( Painter &p, not_null row, + VideoUserpic *videoUserpic, FilterId filterId, int fullWidth, bool active, @@ -934,6 +938,7 @@ void RowPainter::paint( row, entry, row->key(), + videoUserpic, filterId, from, nullptr, @@ -1067,6 +1072,7 @@ void RowPainter::paint( row, history, history, + nullptr, FilterId(), from, hiddenSenderInfo, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h index 76377c708e..00d973d0c3 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h @@ -18,6 +18,8 @@ class BasicRow; namespace Dialogs::Ui { +class VideoUserpic; + using namespace ::Ui; const style::icon *ChatTypeIcon( @@ -30,6 +32,7 @@ public: static void paint( Painter &p, not_null row, + VideoUserpic *videoUserpic, FilterId filterId, int fullWidth, bool active, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.cpp new file mode 100644 index 0000000000..9d0fa85347 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.cpp @@ -0,0 +1,125 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "dialogs/ui/dialogs_video_userpic.h" + +#include "core/file_location.h" +#include "data/data_peer.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" +#include "data/data_file_origin.h" +#include "data/data_session.h" + +namespace Dialogs::Ui { + +VideoUserpic::VideoUserpic(not_null peer, Fn repaint) +: _peer(peer) +, _repaint(std::move(repaint)) { +} + +VideoUserpic::~VideoUserpic() = default; + +int VideoUserpic::frameIndex() const { + return -1; +} + +void VideoUserpic::paintLeft( + Painter &p, + std::shared_ptr &view, + int x, + int y, + int w, + int size) { + _lastSize = size; + + const auto photoId = _peer->userpicPhotoId(); + if (_videoPhotoId != photoId) { + _videoPhotoId = photoId; + _video = nullptr; + _videoPhotoMedia = nullptr; + const auto photo = _peer->owner().photo(photoId); + if (photo->isNull()) { + _peer->updateFullForced(); + } else { + _videoPhotoMedia = photo->createMediaView(); + _videoPhotoMedia->videoWanted(_peer->userpicPhotoOrigin()); + } + } + if (!_video) { + if (!_videoPhotoMedia) { + const auto photo = _peer->owner().photo(photoId); + if (!photo->isNull()) { + _videoPhotoMedia = photo->createMediaView(); + _videoPhotoMedia->videoWanted(_peer->userpicPhotoOrigin()); + } + } + if (_videoPhotoMedia) { + auto bytes = _videoPhotoMedia->videoContent(); + if (!bytes.isEmpty()) { + auto callback = [=](Media::Clip::Notification notification) { + clipCallback(notification); + }; + _video = Media::Clip::MakeReader( + Core::FileLocation(), + std::move(bytes), + std::move(callback)); + } + } + } + if (rtl()) { + x = w - x - size; + } + if (_video && _video->ready()) { + startReady(); + + const auto now = crl::now(); + p.drawPixmap( + x, + y, + _video->current(request(size), now)); + } else { + _peer->paintUserpicLeft(p, view, x, y, w, size); + } +} + +Media::Clip::FrameRequest VideoUserpic::request(int size) const { + return { + .frame = { size, size }, + .outer = { size, size }, + .factor = cIntRetinaFactor(), + .radius = ImageRoundRadius::Ellipse, + }; +} + +bool VideoUserpic::startReady(int size) { + if (!_video->ready() || _video->started()) { + return false; + } else if (!_lastSize) { + _lastSize = size ? size : _video->width(); + } + _video->start(request(_lastSize)); + _repaint(); + return true; +} + +void VideoUserpic::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; + + switch (notification) { + case Notification::Reinit: { + if (_video->state() == State::Error) { + _video.setBad(); + } else if (startReady()) { + _repaint(); + } + } break; + + case Notification::Repaint: _repaint(); break; + } +} + +} // namespace Dialogs::Ui diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.h b/Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.h new file mode 100644 index 0000000000..eecc89dc9e --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.h @@ -0,0 +1,54 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "media/clip/media_clip_reader.h" + +class Painter; + +namespace Data { +class CloudImageView; +class PhotoMedia; +} // namespace Data + +namespace Dialogs::Ui { + +using namespace ::Ui; + +class VideoUserpic final { +public: + VideoUserpic(not_null peer, Fn repaint); + ~VideoUserpic(); + + [[nodiscard]] int frameIndex() const; + + void paintLeft( + Painter &p, + std::shared_ptr &view, + int x, + int y, + int w, + int size); + +private: + void clipCallback(Media::Clip::Notification notification); + [[nodiscard]] Media::Clip::FrameRequest request(int size) const; + bool startReady(int size = 0); + + const not_null _peer; + const Fn _repaint; + + Media::Clip::ReaderPointer _video; + int _lastSize = 0; + std::shared_ptr _videoPhotoMedia; + PhotoId _videoPhotoId = 0; + PhotoId _photoIdRequested = 0; + +}; + +} // namespace Dialogs::Ui diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index b6df77a809..c72d2f2895 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -1470,7 +1470,7 @@ void InnerWidget::suggestRestrictParticipant( editRestrictions(false, ChatRestrictionsInfo()); }).send(); } - }); + }, &st::menuIconRestrict); } void InnerWidget::restrictParticipant( diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 285bef45a3..47db604560 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -1362,6 +1362,7 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) { const auto payment = Get(); const auto id = fullId(); const auto owner = &history()->owner(); + payment->slug = data.vinvoice_slug().value_or_empty(); payment->amount = Ui::FillAmountAndCurrency(amount, currency); payment->invoiceLink = std::make_shared([=]( ClickContext context) { diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index 0dff2e0dee..a62b895046 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -153,10 +153,13 @@ void UploadPhoto(not_null user, QImage image) { auto bytes = QByteArray(); auto buffer = QBuffer(&bytes); image.save(&buffer, "JPG", 87); - user->setUserpic(base::RandomValue(), ImageLocation( - { .data = InMemoryLocation{ .bytes = bytes } }, - image.width(), - image.height())); + user->setUserpic( + base::RandomValue(), + ImageLocation( + { .data = InMemoryLocation{ .bytes = bytes } }, + image.width(), + image.height()), + false); user->session().api().peerPhoto().upload(user, std::move(image)); } diff --git a/Telegram/SourceFiles/storage/serialize_peer.cpp b/Telegram/SourceFiles/storage/serialize_peer.cpp index 7bde9f5661..2be310ec4a 100644 --- a/Telegram/SourceFiles/storage/serialize_peer.cpp +++ b/Telegram/SourceFiles/storage/serialize_peer.cpp @@ -146,6 +146,7 @@ uint32 peerSize(not_null peer) { void writePeer(QDataStream &stream, not_null peer) { stream << SerializePeerId(peer->id) << quint64(peer->userpicPhotoId()); writeImageLocation(stream, peer->userpicLocation()); + stream << qint32(peer->userpicHasVideo() ? 1 : 0); if (const auto user = peer->asUser()) { const auto botInlinePlaceholder = user->isBot() ? user->botInfo->inlinePlaceholder @@ -190,6 +191,7 @@ PeerData *readPeer( int streamAppVersion, QDataStream &stream) { quint64 peerIdSerialized = 0, photoId = 0; + qint32 photoHasVideo = 0; stream >> peerIdSerialized >> photoId; const auto peerId = DeserializePeerId(peerIdSerialized); if (!peerId) { @@ -201,6 +203,9 @@ PeerData *readPeer( if (!userpic) { return nullptr; } + if (streamAppVersion >= 4000000 || true AssertIsDebug()) { + stream >> photoHasVideo; + } const auto selfId = session->userPeerId(); const auto loaded = (peerId == selfId) @@ -424,7 +429,7 @@ PeerData *readPeer( result->id.value, userpicAccessHash, photoId); - result->setUserpic(photoId, location); + result->setUserpic(photoId, location, (photoHasVideo == 1)); } return result; } @@ -439,6 +444,10 @@ QString peekUserPhone(int streamAppVersion, QDataStream &stream) { || !readImageLocation(streamAppVersion, stream)) { return QString(); } + if (streamAppVersion >= 4000000 || true AssertIsDebug()) { + qint32 photoHasVideo = 0; + stream >> photoHasVideo; + } QString first, last, phone; stream >> first >> last >> phone;