Play premium video avatars in chats list.

This commit is contained in:
John Preston 2022-05-16 15:38:35 +04:00
parent 5478a8c014
commit 201edb2e69
19 changed files with 657 additions and 253 deletions

View File

@ -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

View File

@ -92,7 +92,10 @@ ChannelData::ChannelData(not_null<Data::Session*> 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();
});

View File

@ -39,7 +39,10 @@ ChatData::ChatData(not_null<Data::Session*> 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();
});

View File

@ -307,8 +307,12 @@ ClickHandlerPtr PeerData::createOpenLink() {
return std::make_shared<PeerClickHandler>(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();

View File

@ -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<Data::UnavailableReason> &;
void setUserpicChecked(PhotoId photoId, const ImageLocation &location);
void setUserpicChecked(
PhotoId photoId,
const ImageLocation &location,
bool hasVideo);
const not_null<Data::Session*> _owner;
mutable Data::CloudImage _userpic;
PhotoId _userpicPhotoId = kUnknownPhotoId;
bool _userpicHasVideo = false;
mutable std::unique_ptr<Ui::EmptyUserpic> _userpicEmpty;
Ui::Text::String _nameText;

View File

@ -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;

View File

@ -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<Data::UnavailableReason> &&reasons);
int commonChatsCount() const {
return _commonChatsCount;
}
int commonChatsCount() const;
void setCommonChatsCount(int count);
private:

View File

@ -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*> row) {
const auto history = row->history();
return history ? validateVideoUserpic(history) : nullptr;
}
Ui::VideoUserpic *InnerWidget::validateVideoUserpic(
not_null<History*> 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<Ui::VideoUserpic>(
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<PeerData*> 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<const HistoryItem*> item) {

View File

@ -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*> row);
Ui::VideoUserpic *validateVideoUserpic(not_null<History*> 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<PeerData*>,
std::unique_ptr<Ui::VideoUserpic>> _videoUserpics;
Fn<void()> _loadMoreCallback;
rpl::event_stream<> _listBottomReached;
rpl::event_stream<ChosenRow> _chosenRow;

View File

@ -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<PeerData*> peer,
Ui::VideoUserpic *videoUserpic,
std::shared_ptr<Data::CloudImageView> &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<void()> 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<PeerData*> peer,
Fn<void()> 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<CornerBadgeUserpic>();
}
void BasicRow::PaintCornerBadgeFrame(
not_null<CornerBadgeUserpic*> data,
not_null<PeerData*> peer,
std::shared_ptr<Data::CloudImageView> &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<PeerData*> 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<void()> 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<PeerData*> peer,
Fn<void()> 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<CornerBadgeUserpic>();
}
void Row::PaintCornerBadgeFrame(
not_null<CornerBadgeUserpic*> data,
not_null<PeerData*> peer,
Ui::VideoUserpic *videoUserpic,
std::shared_ptr<Data::CloudImageView> &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<PeerData*> 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<HistoryItem*> item)
: _searchInChat(searchInChat)
, _item(item) {

View File

@ -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<PeerData*> peer,
Fn<void()> updateCallback = nullptr) const;
void paintUserpic(
virtual void paintUserpic(
Painter &p,
not_null<PeerData*> 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<void()> updateCallback) const;
void ensureCornerBadgeUserpic() const;
static void PaintCornerBadgeFrame(
not_null<CornerBadgeUserpic*> data,
not_null<PeerData*> peer,
std::shared_ptr<Data::CloudImageView> &view);
mutable std::shared_ptr<Data::CloudImageView> _userpic;
mutable std::unique_ptr<Ui::RippleAnimation> _ripple;
mutable std::unique_ptr<CornerBadgeUserpic> _cornerBadgeUserpic;
mutable bool _cornerBadgeShown = false;
};
@ -94,6 +74,18 @@ public:
}
Row(Key key, int pos);
void updateCornerBadgeShown(
not_null<PeerData*> peer,
Fn<void()> updateCallback = nullptr) const;
void paintUserpic(
Painter &p,
not_null<PeerData*> 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<void()> updateCallback) const;
void ensureCornerBadgeUserpic() const;
static void PaintCornerBadgeFrame(
not_null<CornerBadgeUserpic*> data,
not_null<PeerData*> peer,
Ui::VideoUserpic *videoUserpic,
std::shared_ptr<Data::CloudImageView> &view);
Key _id;
int _pos = 0;
mutable uint32 _listEntryCacheVersion = 0;
mutable Ui::Text::String _listEntryCache;
mutable std::unique_ptr<CornerBadgeUserpic> _cornerBadgeUserpic;
mutable bool _cornerBadgeShown = false;
};

View File

@ -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<const BasicRow*> row,
not_null<Entry*> 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<const Row*> 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,

View File

@ -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<const Row*> row,
VideoUserpic *videoUserpic,
FilterId filterId,
int fullWidth,
bool active,

View File

@ -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<PeerData*> peer, Fn<void()> repaint)
: _peer(peer)
, _repaint(std::move(repaint)) {
}
VideoUserpic::~VideoUserpic() = default;
int VideoUserpic::frameIndex() const {
return -1;
}
void VideoUserpic::paintLeft(
Painter &p,
std::shared_ptr<Data::CloudImageView> &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

View File

@ -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<PeerData*> peer, Fn<void()> repaint);
~VideoUserpic();
[[nodiscard]] int frameIndex() const;
void paintLeft(
Painter &p,
std::shared_ptr<Data::CloudImageView> &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<PeerData*> _peer;
const Fn<void()> _repaint;
Media::Clip::ReaderPointer _video;
int _lastSize = 0;
std::shared_ptr<Data::PhotoMedia> _videoPhotoMedia;
PhotoId _videoPhotoId = 0;
PhotoId _photoIdRequested = 0;
};
} // namespace Dialogs::Ui

View File

@ -1470,7 +1470,7 @@ void InnerWidget::suggestRestrictParticipant(
editRestrictions(false, ChatRestrictionsInfo());
}).send();
}
});
}, &st::menuIconRestrict);
}
void InnerWidget::restrictParticipant(

View File

@ -1362,6 +1362,7 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
const auto payment = Get<HistoryServicePayment>();
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<LambdaClickHandler>([=](
ClickContext context) {

View File

@ -153,10 +153,13 @@ void UploadPhoto(not_null<UserData*> user, QImage image) {
auto bytes = QByteArray();
auto buffer = QBuffer(&bytes);
image.save(&buffer, "JPG", 87);
user->setUserpic(base::RandomValue<PhotoId>(), ImageLocation(
{ .data = InMemoryLocation{ .bytes = bytes } },
image.width(),
image.height()));
user->setUserpic(
base::RandomValue<PhotoId>(),
ImageLocation(
{ .data = InMemoryLocation{ .bytes = bytes } },
image.width(),
image.height()),
false);
user->session().api().peerPhoto().upload(user, std::move(image));
}

View File

@ -146,6 +146,7 @@ uint32 peerSize(not_null<PeerData*> peer) {
void writePeer(QDataStream &stream, not_null<PeerData*> 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;