Use Data::CloudImage for userpics.

This commit is contained in:
John Preston 2020-05-28 18:32:10 +04:00
parent 249f7813c1
commit f066e0f05a
55 changed files with 748 additions and 284 deletions

View file

@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "data/data_cloud_file.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "apiwrap.h"
@ -222,6 +223,7 @@ private:
}
not_null<PeerData*> peer;
mutable std::shared_ptr<Data::CloudImageView> userpic;
Ui::Text::String name, status;
};
void paintChat(Painter &p, const ChatRow &row, bool selected) const;
@ -1438,7 +1440,7 @@ void RevokePublicLinkBox::resizeEvent(QResizeEvent *e) {
void RevokePublicLinkBox::Inner::paintChat(Painter &p, const ChatRow &row, bool selected) const {
auto peer = row.peer;
peer->paintUserpicLeft(p, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize);
peer->paintUserpicLeft(p, row.userpic, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize);
p.setPen(st::contactsNameFg);

View file

@ -909,19 +909,20 @@ ConfirmInviteBox::ConfirmInviteBox(
}
}
std::vector<not_null<UserData*>> ConfirmInviteBox::GetParticipants(
not_null<Main::Session*> session,
const MTPDchatInvite &data) {
auto ConfirmInviteBox::GetParticipants(
not_null<Main::Session*> session,
const MTPDchatInvite &data)
-> std::vector<Participant> {
const auto participants = data.vparticipants();
if (!participants) {
return {};
}
const auto &v = participants->v;
auto result = std::vector<not_null<UserData*>>();
auto result = std::vector<Participant>();
result.reserve(v.size());
for (const auto &participant : v) {
if (const auto user = session->data().processUser(participant)) {
result.push_back(user);
result.push_back(Participant{ user });
}
}
return result;
@ -946,12 +947,12 @@ void ConfirmInviteBox::prepare() {
_userWidth = (st::confirmInviteUserPhotoSize + 2 * padding);
int sumWidth = _participants.size() * _userWidth;
int left = (st::boxWideWidth - sumWidth) / 2;
for (const auto user : _participants) {
for (const auto &participant : _participants) {
auto name = new Ui::FlatLabel(this, st::confirmInviteUserName);
name->resizeToWidth(st::confirmInviteUserPhotoSize + padding);
name->setText(user->firstName.isEmpty()
? user->name
: user->firstName);
name->setText(participant.user->firstName.isEmpty()
? participant.user->name
: participant.user->firstName);
name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop);
left += _userWidth;
}
@ -993,9 +994,10 @@ void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
int sumWidth = _participants.size() * _userWidth;
int left = (width() - sumWidth) / 2;
for_const (auto user, _participants) {
user->paintUserpicLeft(
for (auto &participant : _participants) {
participant.user->paintUserpicLeft(
p,
participant.userpic,
left + (_userWidth - st::confirmInviteUserPhotoSize) / 2,
st::confirmInviteUserPhotoTop,
width(),

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
class PhotoMedia;
class CloudImageView;
} // namespace Data
namespace Main {
@ -225,7 +226,11 @@ protected:
void paintEvent(QPaintEvent *e) override;
private:
static std::vector<not_null<UserData*>> GetParticipants(
struct Participant {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> userpic;
};
static std::vector<Participant> GetParticipants(
not_null<Main::Session*> session,
const MTPDchatInvite &data);
@ -236,7 +241,7 @@ private:
object_ptr<Ui::FlatLabel> _status;
std::shared_ptr<Data::PhotoMedia> _photo;
std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
std::vector<not_null<UserData*>> _participants;
std::vector<Participant> _participants;
bool _isChannel = false;
int _userWidth = 0;

View file

@ -78,6 +78,7 @@ private:
};
struct PeerButton {
not_null<History*> history;
std::shared_ptr<Data::CloudImageView> userpic;
Button button;
};
@ -185,8 +186,9 @@ void FilterChatsPreview::updateData(
}
for (const auto history : peers) {
_removePeer.push_back({
history,
makeButton([=] { removePeer(history); }) });
.history = history,
.button = makeButton([=] { removePeer(history); })
});
}
refresh();
}
@ -203,7 +205,7 @@ int FilterChatsPreview::resizeGetHeight(int newWidth) {
for (const auto &[flag, button] : _removeFlag) {
moveNextButton(button.get());
}
for (const auto &[history, button] : _removePeer) {
for (const auto &[history, userpic, button] : _removePeer) {
moveNextButton(button.get());
}
return top;
@ -235,7 +237,7 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
FilterChatsTypeName(flag));
top += st.height;
}
for (const auto &[history, button] : _removePeer) {
for (auto &[history, userpic, button] : _removePeer) {
const auto savedMessages = history->peer->isSelf();
if (savedMessages) {
Ui::EmptyUserpic::PaintSavedMessages(
@ -253,6 +255,7 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
} else {
history->peer->paintUserpicLeft(
p,
userpic,
iconLeft,
top + iconTop,
width(),

View file

@ -180,11 +180,12 @@ QString ExceptionRow::generateShortName() {
PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback() {
const auto peer = this->peer();
const auto saved = peer->isSelf();
return [=](Painter &p, int x, int y, int outerWidth, int size) {
auto userpic = saved ? nullptr : ensureUserpicView();
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
if (saved) {
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
} else {
peer->paintUserpicLeft(p, x, y, outerWidth, size);
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
}
};
}

View file

@ -41,8 +41,9 @@ PaintRoundImageCallback PaintUserpicCallback(
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
};
}
return [=](Painter &p, int x, int y, int outerWidth, int size) {
peer->paintUserpicLeft(p, x, y, outerWidth, size);
auto userpic = std::shared_ptr<Data::CloudImageView>();
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
};
}
@ -483,14 +484,22 @@ QString PeerListRow::generateShortName() {
: peer()->shortName();
}
std::shared_ptr<Data::CloudImageView> PeerListRow::ensureUserpicView() {
if (!_userpic) {
_userpic = peer()->createUserpicView();
}
return _userpic;
}
PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback() {
const auto saved = _isSavedMessagesChat;
const auto peer = this->peer();
return [=](Painter &p, int x, int y, int outerWidth, int size) {
auto userpic = saved ? nullptr : ensureUserpicView();
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
if (saved) {
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
} else {
peer->paintUserpicLeft(p, x, y, outerWidth, size);
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
}
};
}
@ -595,7 +604,7 @@ void PeerListRow::paintDisabledCheckUserpic(
if (_isSavedMessagesChat) {
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
} else {
peer()->paintUserpicLeft(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
}
{

View file

@ -7,11 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <rpl/event_stream.h>
#include "ui/rp_widget.h"
#include "ui/empty_userpic.h"
#include "boxes/abstract_box.h"
#include "mtproto/sender.h"
#include "data/data_cloud_file.h"
#include "base/timer.h"
namespace style {
@ -85,6 +85,8 @@ public:
return _id;
}
[[nodiscard]] std::shared_ptr<Data::CloudImageView> ensureUserpicView();
[[nodiscard]] virtual QString generateName();
[[nodiscard]] virtual QString generateShortName();
[[nodiscard]] virtual auto generatePaintUserpicCallback()
@ -223,6 +225,7 @@ private:
PeerListRowId _id = 0;
PeerData *_peer = nullptr;
mutable std::shared_ptr<Data::CloudImageView> _userpic;
std::unique_ptr<Ui::RippleAnimation> _ripple;
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
Ui::Text::String _name;

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "data/data_photo_media.h"
#include "data/data_cloud_file.h"
#include "calls/calls_emoji_fingerprint.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
@ -490,6 +491,7 @@ void Panel::finishAnimating() {
void Panel::showControls() {
Expects(_call != nullptr);
showChildren();
_decline->setVisible(_decline->toggled());
_cancel->setVisible(_cancel->toggled());
@ -511,9 +513,8 @@ void Panel::hideAndDestroy() {
}
void Panel::processUserPhoto() {
if (!_user->userpicLoaded()) {
_user->loadUserpic();
}
_userpic = _user->createUserpicView();
_user->loadUserpic();
const auto photo = _user->userpicPhotoId()
? _user->owner().photo(_user->userpicPhotoId()).get()
: nullptr;
@ -542,9 +543,8 @@ void Panel::refreshUserPhoto() {
_photo->image(Data::PhotoSize::Large),
_user->userpicPhotoOrigin());
} else if (_userPhoto.isNull()) {
const auto userpic = _user->currentUserpic();
createUserpicCache(
userpic ? userpic.get() : nullptr,
_userpic ? _userpic->image() : nullptr,
_user->userpicOrigin());
}
}

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
class PhotoMedia;
class CloudImageView;
} // namespace Data
namespace Ui {
@ -116,6 +117,7 @@ private:
Call *_call = nullptr;
not_null<UserData*> _user;
std::shared_ptr<Data::CloudImageView> _userpic;
std::shared_ptr<Data::PhotoMedia> _photo;
bool _useTransparency = true;

View file

@ -50,9 +50,9 @@ FieldAutocomplete::FieldAutocomplete(
&_srows));
_inner->setGeometry(rect());
connect(_inner, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(hashtagChosen(QString, FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString, FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(botCommandChosen(QString, FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString, FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(mentionChosen(not_null<UserData*>,FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(not_null<UserData*>,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(stickerChosen(not_null<DocumentData*>,FieldAutocomplete::ChooseMethod)), this, SIGNAL(stickerChosen(not_null<DocumentData*>,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(mustScrollTo(int, int)), _scroll, SLOT(scrollToY(int, int)));
@ -152,7 +152,9 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
}
bool FieldAutocomplete::clearFilteredBotCommands() {
if (_brows.isEmpty()) return false;
if (_brows.empty()) {
return false;
}
_brows.clear();
return true;
}
@ -160,8 +162,8 @@ bool FieldAutocomplete::clearFilteredBotCommands() {
namespace {
template <typename T, typename U>
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
for (auto b = v.cbegin(), i = b, e = b + qMax(v.size(), last); i != e; ++i) {
if (*i == elem) {
for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last); i != e; ++i) {
if (i->user == elem) {
return (i - b);
}
}
@ -241,12 +243,12 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
for_const (auto user, cRecentInlineBots()) {
if (user->isInaccessible()) continue;
if (!listAllSuggestions && filterNotPassedByUsername(user)) continue;
mrows.push_back(user);
mrows.push_back({ user });
++recentInlineBots;
}
}
if (_chat) {
auto ordered = QMultiMap<TimeId, not_null<UserData*>>();
auto sorted = base::flat_multi_map<TimeId, not_null<UserData*>>();
const auto byOnline = [&](not_null<UserData*> user) {
return Data::SortByOnlineValue(user, now);
};
@ -258,23 +260,19 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
if (user->isInaccessible()) continue;
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
ordered.insertMulti(byOnline(user), user);
sorted.emplace(byOnline(user), user);
}
}
for (const auto user : _chat->lastAuthors) {
if (user->isInaccessible()) continue;
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
mrows.push_back(user);
if (!ordered.isEmpty()) {
ordered.remove(byOnline(user), user);
}
mrows.push_back({ user });
sorted.remove(byOnline(user), user);
}
if (!ordered.isEmpty()) {
for (auto i = ordered.cend(), b = ordered.cbegin(); i != b;) {
--i;
mrows.push_back(i.value());
}
for (auto i = sorted.cend(), b = sorted.cbegin(); i != b;) {
--i;
mrows.push_back({ i->second });
}
} else if (_channel && _channel->isMegagroup()) {
QMultiMap<int32, UserData*> ordered;
@ -286,7 +284,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
if (user->isInaccessible()) continue;
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
mrows.push_back(user);
mrows.push_back({ user });
}
}
}
@ -378,7 +376,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
continue;
}
}
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
brows.push_back({ user, &user->botInfo->commands.at(j) });
}
}
}
@ -390,7 +388,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) continue;
}
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
brows.push_back({ user, &user->botInfo->commands.at(j) });
}
}
}
@ -411,7 +409,7 @@ void FieldAutocomplete::rowsUpdated(
internal::BotCommandRows &&brows,
internal::StickerRows &&srows,
bool resetScroll) {
if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.empty()) {
if (mrows.empty() && hrows.empty() && brows.empty() && srows.empty()) {
if (!isHidden()) {
hideAnimated();
}
@ -452,11 +450,11 @@ void FieldAutocomplete::recount(bool resetScroll) {
int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
int32 rows = rowscount(_srows.size(), stickersPerRow);
h = st::stickerPanPadding + rows * st::stickerPanSize.height();
} else if (!_mrows.isEmpty()) {
} else if (!_mrows.empty()) {
h = _mrows.size() * st::mentionHeight;
} else if (!_hrows.isEmpty()) {
} else if (!_hrows.empty()) {
h = _hrows.size() * st::mentionHeight;
} else if (!_brows.isEmpty()) {
} else if (!_brows.empty()) {
h = _brows.size() * st::mentionHeight;
}
@ -693,7 +691,11 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
}
} else {
int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1;
int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
int32 last = !_mrows->empty()
? _mrows->size()
: !_hrows->empty()
? _hrows->size()
: _brows->size();
auto filter = _parent->filter();
bool hasUsername = filter.indexOf('@') > 0;
int filterSize = filter.size();
@ -705,12 +707,13 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
if (selected) {
p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver);
int skip = (st::mentionHeight - st::smallCloseIconOver.height()) / 2;
if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) {
if (!_hrows->empty() || (!_mrows->empty() && i < _recentInlineBotsInRows)) {
st::smallCloseIconOver.paint(p, QPoint(width() - st::smallCloseIconOver.width() - skip, i * st::mentionHeight + skip), width());
}
}
if (!_mrows->isEmpty()) {
const auto user = _mrows->at(i);
if (!_mrows->empty()) {
auto &row = _mrows->at(i);
const auto user = row.user;
auto first = (!filterIsEmpty && user->username.startsWith(filter, Qt::CaseInsensitive)) ? ('@' + user->username.mid(0, filterSize)) : QString();
auto second = first.isEmpty() ? (user->username.isEmpty() ? QString() : ('@' + user->username)) : user->username.mid(filterSize);
auto firstwidth = st::mentionFont->width(first);
@ -732,7 +735,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
}
}
user->loadUserpic();
user->paintUserpicLeft(p, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
user->paintUserpicLeft(p, row.userpic, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
p.setPen(selected ? st::mentionNameFgOver : st::mentionNameFg);
user->nameText().drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth);
@ -744,7 +747,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
p.setPen(selected ? st::mentionFgOver : st::mentionFg);
p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
} else if (!_hrows->isEmpty()) {
} else if (!_hrows->empty()) {
QString hrow = _hrows->at(i);
QString first = filterIsEmpty ? QString() : ('#' + hrow.mid(0, filterSize));
QString second = filterIsEmpty ? ('#' + hrow) : hrow.mid(filterSize);
@ -768,16 +771,17 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
} else {
UserData *user = _brows->at(i).first;
auto &row = _brows->at(i);
const auto user = row.user;
const BotCommand *command = _brows->at(i).second;
QString toHighlight = command->command;
const auto command = row.command;
auto toHighlight = command->command;
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
if (hasUsername || botStatus == 0 || botStatus == 2) {
toHighlight += '@' + user->username;
}
user->loadUserpic();
user->paintUserpicLeft(p, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
user->paintUserpicLeft(p, row.userpic, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
auto commandText = '/' + toHighlight;
@ -820,7 +824,7 @@ void FieldAutocompleteInner::clearSel(bool hidden) {
_overDelete = false;
_mouseSelection = false;
_lastMousePosition = std::nullopt;
setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0);
setSel((_mrows->empty() && _brows->empty() && _hrows->empty()) ? -1 : 0);
if (hidden) {
_down = -1;
_previewShown = false;
@ -831,7 +835,13 @@ bool FieldAutocompleteInner::moveSel(int key) {
_mouseSelection = false;
_lastMousePosition = std::nullopt;
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size());
int32 maxSel = !_mrows->empty()
? _mrows->size()
: !_hrows->empty()
? _hrows->size()
: !_brows->empty()
? _brows->size()
: _srows->size();
int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0);
if (!_srows->empty()) {
if (key == Qt::Key_Left) {
@ -862,20 +872,20 @@ bool FieldAutocompleteInner::chooseSelected(FieldAutocomplete::ChooseMethod meth
emit stickerChosen((*_srows)[_sel].document, method);
return true;
}
} else if (!_mrows->isEmpty()) {
} else if (!_mrows->empty()) {
if (_sel >= 0 && _sel < _mrows->size()) {
emit mentionChosen(_mrows->at(_sel), method);
emit mentionChosen(_mrows->at(_sel).user, method);
return true;
}
} else if (!_hrows->isEmpty()) {
} else if (!_hrows->empty()) {
if (_sel >= 0 && _sel < _hrows->size()) {
emit hashtagChosen('#' + _hrows->at(_sel), method);
return true;
}
} else if (!_brows->isEmpty()) {
} else if (!_brows->empty()) {
if (_sel >= 0 && _sel < _brows->size()) {
UserData *user = _brows->at(_sel).first;
const BotCommand *command(_brows->at(_sel).second);
const auto user = _brows->at(_sel).user;
const auto command = _brows->at(_sel).command;
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
if (botStatus == 0 || botStatus == 2 || _parent->filter().indexOf('@') > 0) {
emit botCommandChosen('/' + command->command + '@' + user->username, method);
@ -895,9 +905,9 @@ void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) {
void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
selectByMouse(e->globalPos());
if (e->button() == Qt::LeftButton) {
if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) {
if (_overDelete && _sel >= 0 && _sel < (_mrows->empty() ? _hrows->size() : _recentInlineBotsInRows)) {
bool removed = false;
if (_mrows->isEmpty()) {
if (_mrows->empty()) {
QString toRemove = _hrows->at(_sel);
RecentHashtagPack &recent(cRefRecentWriteHashtags());
for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) {
@ -909,7 +919,7 @@ void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
}
}
} else {
UserData *toRemove = _mrows->at(_sel);
UserData *toRemove = _mrows->at(_sel).user;
RecentInlineBots &recent(cRefRecentInlineBots());
int32 index = recent.indexOf(toRemove);
if (index >= 0) {
@ -1066,8 +1076,12 @@ void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
_overDelete = false;
} else {
sel = mouse.y() / int32(st::mentionHeight);
maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
_overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
maxSel = !_mrows->empty()
? _mrows->size()
: !_hrows->empty()
? _hrows->size()
: _brows->size();
_overDelete = (!_hrows->empty() || (!_mrows->empty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
}
if (sel < 0 || sel >= maxSel) {
sel = -1;

View file

@ -28,6 +28,7 @@ class SessionController;
namespace Data {
class DocumentMedia;
class CloudImageView;
} // namespace Data
namespace internal {
@ -38,10 +39,21 @@ struct StickerSuggestion {
std::unique_ptr<Lottie::SinglePlayer> animated;
};
using MentionRows = QList<UserData*>;
using HashtagRows = QList<QString>;
using BotCommandRows = QList<QPair<UserData*, const BotCommand*>>;
struct MentionRow {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> userpic;
};
struct BotCommandRow {
not_null<UserData*> user;
not_null<const BotCommand*> command;
std::shared_ptr<Data::CloudImageView> userpic;
};
using HashtagRows = std::vector<QString>;
using BotCommandRows = std::vector<BotCommandRow>;
using StickerRows = std::vector<StickerSuggestion>;
using MentionRows = std::vector<MentionRow>;
class FieldAutocompleteInner;
@ -94,7 +106,7 @@ public:
void hideFast();
signals:
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
void mentionChosen(not_null<UserData*> user, FieldAutocomplete::ChooseMethod method) const;
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const;
void stickerChosen(not_null<DocumentData*> sticker, FieldAutocomplete::ChooseMethod method) const;
@ -182,7 +194,7 @@ public:
void rowsUpdated();
signals:
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
void mentionChosen(not_null<UserData*> user, FieldAutocomplete::ChooseMethod method) const;
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const;
void stickerChosen(not_null<DocumentData*> sticker, FieldAutocomplete::ChooseMethod method) const;

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_file_origin.h"
#include "data/data_cloud_file.h"
#include "ui/widgets/buttons.h"
#include "ui/effects/animations.h"
#include "ui/effects/ripple_animation.h"
@ -89,9 +90,10 @@ struct StickerIcon {
mutable std::unique_ptr<Lottie::SinglePlayer> lottie;
mutable QPixmap savedFrame;
DocumentData *sticker = nullptr;
ChannelData *megagroup = nullptr;
mutable std::shared_ptr<Stickers::SetThumbnailView> thumbnailMedia;
mutable std::shared_ptr<Data::DocumentMedia> stickerMedia;
ChannelData *megagroup = nullptr;
mutable std::shared_ptr<Data::CloudImageView> megagroupUserpic;
int pixw = 0;
int pixh = 0;
mutable rpl::lifetime lifetime;
@ -820,7 +822,7 @@ void StickersListWidget::Footer::paintSetIcon(
}
}
} else if (icon.megagroup) {
icon.megagroup->paintUserpicLeft(p, x + (st::stickerIconWidth - st::stickerGroupCategorySize) / 2, _iconsTop + (st::emojiFooterHeight - st::stickerGroupCategorySize) / 2, width(), st::stickerGroupCategorySize);
icon.megagroup->paintUserpicLeft(p, icon.megagroupUserpic, x + (st::stickerIconWidth - st::stickerGroupCategorySize) / 2, _iconsTop + (st::emojiFooterHeight - st::stickerGroupCategorySize) / 2, width(), st::stickerGroupCategorySize);
} else {
const auto paintedIcon = [&] {
if (icon.setId == Stickers::FeaturedSetId) {

View file

@ -38,6 +38,36 @@ Image *CloudImageView::image() const {
return _image.get();
}
void CloudImage::set(
not_null<Main::Session*> session,
const ImageWithLocation &data) {
const auto &was = _file.location.file().data;
const auto &now = data.location.file().data;
if (!data.location.valid()) {
_file.flags |= CloudFile::Flag::Cancelled;
_file.loader = nullptr;
_file.location = ImageLocation();
_file.byteSize = 0;
_file.flags = CloudFile::Flag();
_view = std::weak_ptr<CloudImageView>();
} else if (was != now
&& (!was.is<InMemoryLocation>() || now.is<InMemoryLocation>())) {
_file.location = ImageLocation();
_view = std::weak_ptr<CloudImageView>();
}
UpdateCloudFile(
_file,
data,
session->data().cache(),
kImageCacheTag,
[=](FileOrigin origin) { load(session, origin); },
[=](QImage preloaded) {
if (const auto view = activeView()) {
view->set(session, data.preloaded);
}
});
}
void CloudImage::update(
not_null<Main::Session*> session,
const ImageWithLocation &data) {
@ -103,6 +133,14 @@ std::shared_ptr<CloudImageView> CloudImage::activeView() {
return _view.lock();
}
bool CloudImage::isCurrentView(
const std::shared_ptr<CloudImageView> &view) const {
if (!view) {
return empty();
}
return !view.owner_before(_view) && !_view.owner_before(view);
}
void UpdateCloudFile(
CloudFile &file,
const ImageWithLocation &data,

View file

@ -57,6 +57,11 @@ public:
not_null<Main::Session*> session,
const ImageWithLocation &data);
// This method will replace the location and zero the _view pointer.
void set(
not_null<Main::Session*> session,
const ImageWithLocation &data);
void update(
not_null<Main::Session*> session,
const ImageWithLocation &data);
@ -70,6 +75,8 @@ public:
[[nodiscard]] std::shared_ptr<CloudImageView> createView();
[[nodiscard]] std::shared_ptr<CloudImageView> activeView();
[[nodiscard]] bool isCurrentView(
const std::shared_ptr<CloudImageView> &view) const;
private:
CloudFile _file;

View file

@ -248,6 +248,7 @@ void Folder::loadUserpic() {
void Folder::paintUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const {

View file

@ -64,6 +64,7 @@ public:
void loadUserpic() override;
void paintUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const override;

View file

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/view/history_view_element.h"
#include "history/history_item.h"
#include "storage/file_download.h"
#include "facades.h"
#include "app.h"
@ -108,8 +109,7 @@ void PeerClickHandler::onClick(ClickContext context) const {
PeerData::PeerData(not_null<Data::Session*> owner, PeerId id)
: id(id)
, _owner(owner)
, _userpicEmpty(createEmptyUserpic()) {
, _owner(owner) {
_nameText.setText(st::msgNameStyle, QString(), Ui::NameTextOptions());
}
@ -145,7 +145,8 @@ void PeerData::updateNameDelayed(
}
name = newName;
_nameText.setText(st::msgNameStyle, name, Ui::NameTextOptions());
refreshEmptyUserpic();
_userpicEmpty = nullptr;
Notify::PeerUpdate update(this);
if (nameVersion++ > 1) {
update.flags |= UpdateFlag::NameChanged;
@ -175,28 +176,22 @@ void PeerData::updateNameDelayed(
}
}
std::unique_ptr<Ui::EmptyUserpic> PeerData::createEmptyUserpic() const {
return std::make_unique<Ui::EmptyUserpic>(
Data::PeerUserpicColor(id),
name);
}
void PeerData::refreshEmptyUserpic() const {
_userpicEmpty = useEmptyUserpic() ? createEmptyUserpic() : nullptr;
not_null<Ui::EmptyUserpic*> PeerData::ensureEmptyUserpic() const {
if (!_userpicEmpty) {
_userpicEmpty = std::make_unique<Ui::EmptyUserpic>(
Data::PeerUserpicColor(id),
name);
}
return _userpicEmpty.get();
}
ClickHandlerPtr PeerData::createOpenLink() {
return std::make_shared<PeerClickHandler>(this);
}
void PeerData::setUserpic(
PhotoId photoId,
const StorageImageLocation &location,
ImagePtr userpic) {
void PeerData::setUserpic(PhotoId photoId, const ImageLocation &location) {
_userpicPhotoId = photoId;
_userpic = userpic;
_userpicLocation = location;
refreshEmptyUserpic();
_userpic.set(&session(), ImageWithLocation{ .location = location });
}
void PeerData::setUserpicPhoto(const MTPPhoto &data) {
@ -213,80 +208,109 @@ void PeerData::setUserpicPhoto(const MTPPhoto &data) {
}
}
ImagePtr PeerData::currentUserpic() const {
if (_userpic) {
_userpic->load(userpicOrigin());
if (_userpic->loaded()) {
if (!useEmptyUserpic()) {
_userpicEmpty = nullptr;
}
return _userpic;
}
Image *PeerData::currentUserpic(
std::shared_ptr<Data::CloudImageView> &view) const {
if (!_userpic.isCurrentView(view)) {
view = _userpic.createView();
_userpic.load(&session(), userpicOrigin());
}
if (!_userpicEmpty) {
refreshEmptyUserpic();
const auto image = view ? view->image() : nullptr;
if (image) {
_userpicEmpty = nullptr;
}
return ImagePtr();
return image;
}
void PeerData::paintUserpic(Painter &p, int x, int y, int size) const {
if (auto userpic = currentUserpic()) {
void PeerData::paintUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const {
if (const auto userpic = currentUserpic(view)) {
p.drawPixmap(x, y, userpic->pixCircled(userpicOrigin(), size, size));
} else {
_userpicEmpty->paint(p, x, y, x + size + x, size);
ensureEmptyUserpic()->paint(p, x, y, x + size + x, size);
}
}
void PeerData::paintUserpicRounded(Painter &p, int x, int y, int size) const {
if (auto userpic = currentUserpic()) {
void PeerData::paintUserpicRounded(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const {
if (const auto userpic = currentUserpic(view)) {
p.drawPixmap(x, y, userpic->pixRounded(userpicOrigin(), size, size, ImageRoundRadius::Small));
} else {
_userpicEmpty->paintRounded(p, x, y, x + size + x, size);
ensureEmptyUserpic()->paintRounded(p, x, y, x + size + x, size);
}
}
void PeerData::paintUserpicSquare(Painter &p, int x, int y, int size) const {
if (auto userpic = currentUserpic()) {
void PeerData::paintUserpicSquare(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const {
if (const auto userpic = currentUserpic(view)) {
p.drawPixmap(x, y, userpic->pix(userpicOrigin(), size, size));
} else {
_userpicEmpty->paintSquare(p, x, y, x + size + x, size);
ensureEmptyUserpic()->paintSquare(p, x, y, x + size + x, size);
}
}
void PeerData::loadUserpic() {
_userpic->load(userpicOrigin());
_userpic.load(&session(), userpicOrigin());
}
bool PeerData::userpicLoaded() const {
return _userpic->loaded();
bool PeerData::hasUserpic() const {
return !_userpic.empty();
}
bool PeerData::useEmptyUserpic() const {
return !_userpicLocation.valid()
|| !_userpic
|| !_userpic->loaded();
std::shared_ptr<Data::CloudImageView> PeerData::activeUserpicView() {
return _userpic.empty() ? nullptr : _userpic.activeView();
}
InMemoryKey PeerData::userpicUniqueKey() const {
if (useEmptyUserpic()) {
if (!_userpicEmpty) {
refreshEmptyUserpic();
}
return _userpicEmpty->uniqueKey();
std::shared_ptr<Data::CloudImageView> PeerData::createUserpicView() {
if (_userpic.empty()) {
return nullptr;
}
return inMemoryKey(_userpicLocation);
auto result = _userpic.createView();
_userpic.load(&session(), userpicPhotoOrigin());
return result;
}
void PeerData::saveUserpic(const QString &path, int size) const {
genUserpic(size).save(path, "PNG");
bool PeerData::useEmptyUserpic(
std::shared_ptr<Data::CloudImageView> &view) const {
return !currentUserpic(view);
}
void PeerData::saveUserpicRounded(const QString &path, int size) const {
genUserpicRounded(size).save(path, "PNG");
InMemoryKey PeerData::userpicUniqueKey(
std::shared_ptr<Data::CloudImageView> &view) const {
return useEmptyUserpic(view)
? ensureEmptyUserpic()->uniqueKey()
: inMemoryKey(_userpic.location());
}
QPixmap PeerData::genUserpic(int size) const {
if (auto userpic = currentUserpic()) {
void PeerData::saveUserpic(
std::shared_ptr<Data::CloudImageView> &view,
const QString &path,
int size) const {
genUserpic(view, size).save(path, "PNG");
}
void PeerData::saveUserpicRounded(
std::shared_ptr<Data::CloudImageView> &view,
const QString &path,
int size) const {
genUserpicRounded(view, size).save(path, "PNG");
}
QPixmap PeerData::genUserpic(
std::shared_ptr<Data::CloudImageView> &view,
int size) const {
if (const auto userpic = currentUserpic(view)) {
return userpic->pixCircled(userpicOrigin(), size, size);
}
auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
@ -294,13 +318,15 @@ QPixmap PeerData::genUserpic(int size) const {
result.fill(Qt::transparent);
{
Painter p(&result);
paintUserpic(p, 0, 0, size);
paintUserpic(p, view, 0, 0, size);
}
return App::pixmapFromImageInPlace(std::move(result));
}
QPixmap PeerData::genUserpicRounded(int size) const {
if (auto userpic = currentUserpic()) {
QPixmap PeerData::genUserpicRounded(
std::shared_ptr<Data::CloudImageView> &view,
int size) const {
if (auto userpic = currentUserpic(view)) {
return userpic->pixRounded(userpicOrigin(), size, size, ImageRoundRadius::Small);
}
auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
@ -308,7 +334,7 @@ QPixmap PeerData::genUserpicRounded(int size) const {
result.fill(Qt::transparent);
{
Painter p(&result);
paintUserpicRounded(p, 0, 0, size);
paintUserpicRounded(p, view, 0, 0, size);
}
return App::pixmapFromImageInPlace(std::move(result));
}
@ -327,49 +353,42 @@ void PeerData::updateUserpic(
PhotoId photoId,
MTP::DcId dcId,
const MTPFileLocation &location) {
const auto size = kUserpicSize;
const auto loc = location.match([&](
setUserpicChecked(photoId, location.match([&](
const MTPDfileLocationToBeDeprecated &deprecated) {
return StorageImageLocation(
StorageFileLocation(
return ImageLocation(
{ StorageFileLocation(
dcId,
isSelf() ? peerToUser(id) : 0,
MTP_inputPeerPhotoFileLocation(
MTP_flags(0),
input,
deprecated.vvolume_id(),
deprecated.vlocal_id())),
size,
size);
});
setUserpicChecked(photoId, loc, Images::Create(loc));
deprecated.vlocal_id())) },
kUserpicSize,
kUserpicSize);
}));
}
void PeerData::clearUserpic() {
const auto photoId = PhotoId(0);
const auto loc = StorageImageLocation();
const auto photo = [&] {
if (isNotificationsUser()) {
auto image = Core::App().logoNoMargin().scaledToWidth(
kUserpicSize,
Qt::SmoothTransformation);
return _userpic
? _userpic
: Images::Create(std::move(image), "PNG");
}
return ImagePtr();
}();
setUserpicChecked(photoId, loc, photo);
//const auto photo = [&] { // #TODO optimize
// if (isNotificationsUser()) {
// auto image = Core::App().logoNoMargin().scaledToWidth(
// kUserpicSize,
// Qt::SmoothTransformation);
// return _userpic
// ? _userpic
// : Images::Create(std::move(image), "PNG");
// }
// return ImagePtr();
//}();
setUserpicChecked(PhotoId(), ImageLocation());
}
void PeerData::setUserpicChecked(
PhotoId photoId,
const StorageImageLocation &location,
ImagePtr userpic) {
if (_userpicPhotoId != photoId
|| _userpic.get() != userpic.get()
|| _userpicLocation != location) {
setUserpic(photoId, location, userpic);
const ImageLocation &location) {
if (_userpicPhotoId != photoId || _userpic.location() != location) {
setUserpic(photoId, location);
Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged);
//if (const auto channel = asChannel()) { // #feed
// if (const auto feed = channel->feed()) {

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_types.h"
#include "data/data_flags.h"
#include "data/data_notify_settings.h"
#include "data/data_cloud_file.h"
class PeerData;
class UserData;
@ -43,6 +44,8 @@ using ChatRestrictions = MTPDchatBannedRights::Flags;
namespace Data {
class CloudImageView;
class RestrictionCheckResult {
public:
[[nodiscard]] static RestrictionCheckResult Allowed() {
@ -234,44 +237,59 @@ public:
return _nameFirstLetters;
}
void setUserpic(
PhotoId photoId,
const StorageImageLocation &location,
ImagePtr userpic);
void setUserpic(PhotoId photoId, const ImageLocation &location);
void setUserpicPhoto(const MTPPhoto &data);
void paintUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const;
void paintUserpicLeft(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int w,
int size) const {
paintUserpic(p, rtl() ? (w - x - size) : x, y, size);
paintUserpic(p, view, rtl() ? (w - x - size) : x, y, size);
}
void paintUserpicRounded(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const;
void paintUserpicSquare(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const;
void loadUserpic();
[[nodiscard]] bool userpicLoaded() const;
[[nodiscard]] bool useEmptyUserpic() const;
[[nodiscard]] InMemoryKey userpicUniqueKey() const;
void saveUserpic(const QString &path, int size) const;
void saveUserpicRounded(const QString &path, int size) const;
[[nodiscard]] QPixmap genUserpic(int size) const;
[[nodiscard]] QPixmap genUserpicRounded(int size) const;
[[nodiscard]] StorageImageLocation userpicLocation() const {
return _userpicLocation;
[[nodiscard]] bool hasUserpic() const;
[[nodiscard]] std::shared_ptr<Data::CloudImageView> activeUserpicView();
[[nodiscard]] std::shared_ptr<Data::CloudImageView> createUserpicView();
[[nodiscard]] bool useEmptyUserpic(
std::shared_ptr<Data::CloudImageView> &view) const;
[[nodiscard]] InMemoryKey userpicUniqueKey(
std::shared_ptr<Data::CloudImageView> &view) const;
void saveUserpic(
std::shared_ptr<Data::CloudImageView> &view,
const QString &path,
int size) const;
void saveUserpicRounded(
std::shared_ptr<Data::CloudImageView> &view,
const QString &path,
int size) const;
[[nodiscard]] QPixmap genUserpic(
std::shared_ptr<Data::CloudImageView> &view,
int size) const;
[[nodiscard]] QPixmap genUserpicRounded(
std::shared_ptr<Data::CloudImageView> &view,
int size) const;
[[nodiscard]] ImageLocation userpicLocation() const {
return _userpic.location();
}
[[nodiscard]] bool userpicPhotoUnknown() const {
return (_userpicPhotoId == kUnknownPhotoId);
@ -294,7 +312,8 @@ public:
return _openLink;
}
[[nodiscard]] ImagePtr currentUserpic() const;
[[nodiscard]] Image *currentUserpic(
std::shared_ptr<Data::CloudImageView> &view) const;
[[nodiscard]] bool canPinMessages() const;
[[nodiscard]] bool canEditMessagesIndefinitely() const;
@ -356,24 +375,19 @@ protected:
private:
void fillNames();
std::unique_ptr<Ui::EmptyUserpic> createEmptyUserpic() const;
void refreshEmptyUserpic() const;
[[nodiscard]] not_null<Ui::EmptyUserpic*> ensureEmptyUserpic() const;
[[nodiscard]] virtual auto unavailableReasons() const
-> const std::vector<Data::UnavailableReason> &;
void setUserpicChecked(
PhotoId photoId,
const StorageImageLocation &location,
ImagePtr userpic);
void setUserpicChecked(PhotoId photoId, const ImageLocation &location);
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
const not_null<Data::Session*> _owner;
ImagePtr _userpic;
mutable Data::CloudImage _userpic;
PhotoId _userpicPhotoId = kUnknownPhotoId;
mutable std::unique_ptr<Ui::EmptyUserpic> _userpicEmpty;
StorageImageLocation _userpicLocation;
Ui::Text::String _nameText;
Data::NotifySettings _notify;

View file

@ -18,6 +18,7 @@ class Session;
namespace Data {
class Session;
class Folder;
class CloudImageView;
} // namespace Data
namespace Dialogs {
@ -158,16 +159,18 @@ public:
virtual void loadUserpic() = 0;
virtual void paintUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const = 0;
void paintUserpicLeft(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int w,
int size) const {
paintUserpic(p, rtl() ? (w - x - size) : x, y, size);
paintUserpic(p, view, rtl() ? (w - x - size) : x, y, size);
}
TimeId chatListTimeId() const {

View file

@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer_values.h"
#include "data/data_histories.h"
#include "data/data_chat_filters.h"
#include "data/data_cloud_file.h"
#include "base/unixtime.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
@ -734,7 +735,7 @@ void InnerWidget::paintPeerSearchResult(
auto peer = result->peer;
auto userpicPeer = (peer->migrateTo() ? peer->migrateTo() : peer);
userpicPeer->paintUserpicLeft(p, st::dialogsPadding.x(), st::dialogsPadding.y(), width(), st::dialogsPhotoSize);
userpicPeer->paintUserpicLeft(p, result->row.userpicView(), st::dialogsPadding.x(), st::dialogsPadding.y(), width(), st::dialogsPhotoSize);
auto nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding;
auto namewidth = fullWidth - nameleft - st::dialogsPadding.x();
@ -810,7 +811,7 @@ void InnerWidget::paintSearchInChat(Painter &p) const {
if (peer->isSelf()) {
paintSearchInSaved(p, top, _searchInChatText);
} else {
paintSearchInPeer(p, peer, top, _searchInChatText);
paintSearchInPeer(p, peer, _searchInChatUserpic, top, _searchInChatText);
}
//} else if (const auto feed = _searchInChat.feed()) { // #feed
// paintSearchInFeed(p, feed, top, fullWidth, _searchInChatText);
@ -821,7 +822,7 @@ void InnerWidget::paintSearchInChat(Painter &p) const {
top += st::dialogsSearchInHeight + st::lineWidth;
p.setPen(st::dialogsTextFg);
p.setTextPalette(st::dialogsSearchFromPalette);
paintSearchInPeer(p, from, top, _searchFromUserText);
paintSearchInPeer(p, from, _searchFromUserUserpic, top, _searchFromUserText);
p.restoreTextPalette();
}
}
@ -866,10 +867,11 @@ void InnerWidget::paintSearchInFilter(
void InnerWidget::paintSearchInPeer(
Painter &p,
not_null<PeerData*> peer,
std::shared_ptr<Data::CloudImageView> &userpic,
int top,
const Ui::Text::String &text) const {
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
peer->paintUserpicLeft(p, x, y, width(), size);
peer->paintUserpicLeft(p, userpic, x, y, width(), size);
};
const auto icon = Layout::ChatTypeIcon(peer, false, false);
paintSearchInFilter(p, paintUserpic, top, icon, text);
@ -2334,9 +2336,18 @@ void InnerWidget::searchInChat(Key key, UserData *from) {
}
if (_searchFromUser) {
_cancelSearchFromUser->show();
_searchFromUserUserpic = _searchFromUser->createUserpicView();
} else {
_cancelSearchFromUser->hide();
_searchFromUserUserpic = nullptr;
}
if (const auto peer = _searchInChat.peer()) {
_searchInChatUserpic = peer->createUserpicView();
} else {
_searchInChatUserpic = nullptr;
}
_controller->dialogsListDisplayForced().set(
_searchInChat || !_filter.isEmpty(),
true);

View file

@ -33,6 +33,10 @@ namespace Notify {
struct PeerUpdate;
} // namespace Notify
namespace Data {
class CloudImageView;
} // namespace Data
namespace Dialogs {
class Row;
@ -283,6 +287,7 @@ private:
void paintSearchInPeer(
Painter &p,
not_null<PeerData*> peer,
std::shared_ptr<Data::CloudImageView> &userpic,
int top,
const Ui::Text::String &text) const;
void paintSearchInSaved(
@ -394,6 +399,8 @@ private:
Key _searchInChat;
History *_searchInMigrated = nullptr;
UserData *_searchFromUser = nullptr;
mutable std::shared_ptr<Data::CloudImageView> _searchInChatUserpic;
mutable std::shared_ptr<Data::CloudImageView> _searchFromUserUserpic;
Ui::Text::String _searchInChatText;
Ui::Text::String _searchFromUserText;
RowDescriptor _menuRow;

View file

@ -273,6 +273,7 @@ void paintRow(
} else {
entry->paintUserpicLeft(
p,
row->userpicView(),
st::dialogsPadding.x(),
st::dialogsPadding.y(),
fullWidth,
@ -946,6 +947,7 @@ void PaintCollapsedRow(
} else {
folder->paintUserpicLeft(
p,
row.userpicView(),
(fullWidth - st::dialogsUnreadHeight) / 2,
unreadTop,
fullWidth,

View file

@ -138,12 +138,14 @@ void BasicRow::ensureOnlineUserpic() const {
void BasicRow::PaintOnlineFrame(
not_null<OnlineUserpic*> data,
not_null<PeerData*> peer) {
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);
@ -185,6 +187,7 @@ void BasicRow::paintUserpic(
if (!allowOnline || online == 0.) {
peer->paintUserpicLeft(
p,
_userpic,
st::dialogsPadding.x(),
st::dialogsPadding.y(),
fullWidth,
@ -202,14 +205,14 @@ void BasicRow::paintUserpic(
QImage::Format_ARGB32_Premultiplied);
_onlineUserpic->frame.setDevicePixelRatio(cRetinaFactor());
}
const auto key = peer->userpicUniqueKey();
const auto key = peer->userpicUniqueKey(_userpic);
if (_onlineUserpic->online != online
|| _onlineUserpic->key != key
|| _onlineUserpic->active != active) {
_onlineUserpic->online = online;
_onlineUserpic->key = key;
_onlineUserpic->active = active;
PaintOnlineFrame(_onlineUserpic.get(), peer);
PaintOnlineFrame(_onlineUserpic.get(), peer, _userpic);
}
p.drawImage(st::dialogsPadding, _onlineUserpic->frame);
}

View file

@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class History;
class HistoryItem;
namespace Data {
class CloudImageView;
} // namespace Data
namespace Ui {
class RippleAnimation;
} // namespace Ui
@ -48,6 +52,10 @@ public:
int outerWidth,
const QColor *colorOverride = nullptr) const;
std::shared_ptr<Data::CloudImageView> &userpicView() const {
return _userpic;
}
private:
struct OnlineUserpic {
InMemoryKey key;
@ -60,8 +68,10 @@ private:
void ensureOnlineUserpic() const;
static void PaintOnlineFrame(
not_null<OnlineUserpic*> data,
not_null<PeerData*> peer);
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<OnlineUserpic> _onlineUserpic;
mutable bool _online = false;

View file

@ -17,6 +17,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
namespace Data {
class CloudImageView;
} // namespace Data
namespace AdminLog {
namespace {
@ -59,7 +63,8 @@ private:
QRect _checkRect;
not_null<UserData*> _user;
const not_null<UserData*> _user;
std::shared_ptr<Data::CloudImageView> _userpic;
QString _statusText;
bool _statusOnline = false;
@ -113,7 +118,7 @@ void UserCheckbox::paintEvent(QPaintEvent *e) {
auto userpicLeft = _checkRect.x() + _checkRect.width() + st::adminLogFilterUserpicLeft;
auto userpicTop = 0;
_user->paintUserpicLeft(p, userpicLeft, userpicTop, width(), st::contactsPhotoSize);
_user->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, width(), st::contactsPhotoSize);
auto nameLeft = userpicLeft + st::contactsPhotoSize + st::contactsPadding.left();
auto nameTop = userpicTop + st::contactsNameTop;

View file

@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_media_types.h"
#include "data/data_file_origin.h"
#include "data/data_cloud_file.h"
#include "data/data_channel.h"
#include "data/data_user.h"
#include "facades.h"
@ -59,6 +60,7 @@ constexpr auto kMaxChannelAdmins = 200;
constexpr auto kScrollDateHideTimeout = 1000;
constexpr auto kEventsFirstPage = 20;
constexpr auto kEventsPerPage = 50;
constexpr auto kClearUserpicsAfter = 50;
} // namespace
@ -311,6 +313,11 @@ void InnerWidget::visibleTopBottomUpdated(
_visibleTop = visibleTop;
_visibleBottom = visibleBottom;
// Unload userpics.
if (_userpics.size() > kClearUserpicsAfter) {
_userpicsCache = std::move(_userpics);
}
updateVisibleTopItem();
checkPreloadMore();
if (scrolledUp) {
@ -813,6 +820,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
return;
}
const auto guard = gsl::finally([&] {
_userpicsCache.clear();
});
Painter p(this);
auto ms = crl::now();
@ -855,7 +866,14 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
const auto message = view->data()->toHistoryMessage();
Assert(message != nullptr);
message->from()->paintUserpicLeft(p, st::historyPhotoLeft, userpicTop, view->width(), st::msgPhotoSize);
const auto from = message->from();
from->paintUserpicLeft(
p,
_userpics[from],
st::historyPhotoLeft,
userpicTop,
view->width(),
st::msgPhotoSize);
}
return true;
});

View file

@ -16,6 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/sender.h"
#include "base/timer.h"
namespace Data {
class CloudImageView;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
@ -230,6 +234,9 @@ private:
std::set<uint64> _eventIds;
std::map<not_null<const HistoryItem*>, not_null<Element*>> _itemsByData;
base::flat_set<FullMsgId> _animatedStickersPlayed;
base::flat_map<
not_null<PeerData*>,
std::shared_ptr<Data::CloudImageView>> _userpics, _userpicsCache;
int _itemsTop = 0;
int _itemsWidth = 0;
int _itemsHeight = 0;

View file

@ -2218,10 +2218,11 @@ void History::loadUserpic() {
void History::paintUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const {
peer->paintUserpic(p, x, y, size);
peer->paintUserpic(p, view, x, y, size);
}
void History::startBuildingFrontBlock(int expectedItemsCount) {

View file

@ -356,6 +356,7 @@ public:
void loadUserpic() override;
void paintUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const override;

View file

@ -68,6 +68,7 @@ namespace {
constexpr auto kScrollDateHideTimeout = 1000;
constexpr auto kUnloadHeavyPartsPages = 2;
constexpr auto kClearUserpicsAfter = 50;
// Helper binary search for an item in a list that is not completely
// above the given top of the visible area or below the given bottom of the visible area
@ -566,6 +567,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
return;
}
const auto guard = gsl::finally([&] {
_userpicsCache.clear();
});
Painter p(this);
auto clip = e->rect();
auto ms = crl::now();
@ -731,6 +736,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
if (const auto from = message->displayFrom()) {
from->paintUserpicLeft(
p,
_userpics[from],
st::historyPhotoLeft,
userpicTop,
width(),
@ -2234,6 +2240,11 @@ void HistoryInner::visibleAreaUpdated(int top, int bottom) {
scrollDateHideByTimer();
}
// Unload userpics.
if (_userpics.size() > kClearUserpicsAfter) {
_userpicsCache = std::move(_userpics);
}
// Unload lottie animations.
const auto pages = kUnloadHeavyPartsPages;
const auto from = _visibleAreaTop - pages * visibleAreaHeight;

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
struct Group;
class CloudImageView;
} // namespace Data
namespace HistoryView {
@ -345,6 +346,9 @@ private:
SelectedItems _selected;
base::flat_set<not_null<const HistoryItem*>> _animatedStickersPlayed;
base::flat_map<
not_null<PeerData*>,
std::shared_ptr<Data::CloudImageView>> _userpics, _userpicsCache;
MouseAction _mouseAction = MouseAction::None;
TextSelectType _mouseSelectType = TextSelectType::Letters;

View file

@ -380,7 +380,9 @@ HistoryWidget::HistoryWidget(
InitMessageField(controller, _field);
_fieldAutocomplete->hide();
connect(_fieldAutocomplete, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onMentionInsert(UserData*)));
connect(_fieldAutocomplete, &FieldAutocomplete::mentionChosen, this, [=](not_null<UserData*> user) {
onMentionInsert(user);
});
connect(_fieldAutocomplete, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
connect(_fieldAutocomplete, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
connect(_fieldAutocomplete, &FieldAutocomplete::stickerChosen, this, [=](not_null<DocumentData*> document) {

View file

@ -50,6 +50,7 @@ constexpr auto kPreloadedScreensCount = 4;
constexpr auto kPreloadIfLessThanScreens = 2;
constexpr auto kPreloadedScreensCountFull
= kPreloadedScreensCount + 1 + kPreloadedScreensCount;
constexpr auto kClearUserpicsAfter = 50;
} // namespace
@ -583,6 +584,11 @@ void ListWidget::visibleTopBottomUpdated(
_visibleTop = visibleTop;
_visibleBottom = visibleBottom;
// Unload userpics.
if (_userpics.size() > kClearUserpicsAfter) {
_userpicsCache = std::move(_userpics);
}
if (initializing) {
checkUnreadBarCreation();
}
@ -1305,6 +1311,10 @@ void ListWidget::paintEvent(QPaintEvent *e) {
return;
}
const auto guard = gsl::finally([&] {
_userpicsCache.clear();
});
Painter p(this);
auto ms = crl::now();
@ -1346,6 +1356,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
if (const auto from = message->displayFrom()) {
from->paintUserpicLeft(
p,
_userpics[from],
st::historyPhotoLeft,
userpicTop,
view->width(),

View file

@ -29,6 +29,7 @@ class SessionController;
namespace Data {
struct Group;
class CloudImageView;
} // namespace Data
namespace HistoryView {
@ -454,6 +455,9 @@ private:
int _itemsHeight = 0;
int _itemAverageHeight = 0;
base::flat_set<FullMsgId> _animatedStickersPlayed;
base::flat_map<
not_null<PeerData*>,
std::shared_ptr<Data::CloudImageView>> _userpics, _userpicsCache;
int _minHeight = 0;
int _visibleTop = 0;

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_media_types.h"
#include "data/data_cloud_file.h"
#include "main/main_session.h"
#include "app.h"
#include "styles/style_history.h"
@ -165,7 +166,11 @@ void Contact::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim
QRect rthumb(style::rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, paintw));
if (_contact) {
_contact->paintUserpic(p, rthumb.x(), rthumb.y(), st::msgFileThumbSize);
const auto was = (_userpic != nullptr);
_contact->paintUserpic(p, _userpic, rthumb.x(), rthumb.y(), st::msgFileThumbSize);
if (!was && _userpic) {
history()->owner().registerHeavyViewPart(_parent);
}
} else {
_photoEmpty->paint(p, st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, paintw, st::msgFileThumbSize);
}

View file

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media.h"
namespace Data {
class CloudImageView;
} // namespace Data
namespace Ui {
class EmptyUserpic;
} // namespace Ui
@ -55,6 +59,14 @@ public:
// Should be called only by Data::Session.
void updateSharedContactUserId(UserId userId) override;
void unloadHeavyPart() override {
_userpic = nullptr;
}
bool hasHeavyPart() const override {
return (_userpic != nullptr);
}
private:
QSize countOptimalSize() override;
@ -65,6 +77,7 @@ private:
QString _fname, _lname, _phone;
Ui::Text::String _name;
std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
mutable std::shared_ptr<Data::CloudImageView> _userpic;
ClickHandlerPtr _linkl;
int _linkw = 0;

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_cursor_state.h"
#include "ui/image/image.h"
#include "ui/text_options.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/data_cloud_file.h"
#include "app.h"
@ -56,6 +57,7 @@ void Location::ensureMediaCreated() const {
}
_media = _data->createView();
_data->load(&history()->session(), _parent->data()->fullId());
history()->owner().registerHeavyViewPart(_parent);
}
QSize Location::countOptimalSize() {

View file

@ -479,9 +479,17 @@ void Poll::updateRecentVoters() {
auto &&sliced = ranges::view::all(
_poll->recentVoters
) | ranges::view::take(kShowRecentVotersCount);
const auto changed = !ranges::equal(_recentVoters, sliced);
const auto changed = !ranges::equal(
_recentVoters,
sliced,
ranges::equal_to(),
&RecentVoter::user);
if (changed) {
_recentVoters = sliced | ranges::to_vector;
_recentVoters = ranges::view::all(
sliced
) | ranges::views::transform([](not_null<UserData*> user) {
return RecentVoter{ user };
}) | ranges::to_vector;
}
}
@ -858,14 +866,23 @@ void Poll::paintRecentVoters(
? (outbg ? st::msgOutBgSelected : st::msgInBgSelected)
: (outbg ? st::msgOutBg : st::msgInBg))->p;
pen.setWidth(st::lineWidth);
for (const auto &recent : _recentVoters) {
recent->paintUserpic(p, x, y, size);
auto created = false;
for (auto &recent : _recentVoters) {
const auto was = (recent.userpic != nullptr);
recent.user->paintUserpic(p, recent.userpic, x, y, size);
if (!was && recent.userpic) {
created = true;
}
p.setPen(pen);
p.setBrush(Qt::NoBrush);
PainterHighQualityEnabler hq(p);
p.drawEllipse(x, y, size, size);
x -= st::historyPollRecentVoterSkip;
}
if (created) {
history()->owner().registerHeavyViewPart(_parent);
}
}
void Poll::paintCloseByTimer(
@ -1401,6 +1418,21 @@ void Poll::clickHandlerPressedChanged(
}
}
void Poll::unloadHeavyPart() {
for (auto &recent : _recentVoters) {
recent.userpic = nullptr;
}
}
bool Poll::hasHeavyPart() const {
for (auto &recent : _recentVoters) {
if (recent.userpic) {
return true;
}
}
return false;
}
void Poll::toggleRipple(Answer &answer, bool pressed) {
if (pressed) {
const auto outerWidth = width();

View file

@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_poll.h"
#include "base/weak_ptr.h"
namespace Data {
class CloudImageView;
} // namespace Data
namespace Ui {
class RippleAnimation;
class FireworksAnimation;
@ -54,6 +58,9 @@ public:
const ClickHandlerPtr &handler,
bool pressed) override;
void unloadHeavyPart() override;
bool hasHeavyPart() const override;
private:
struct AnswerAnimation;
struct AnswersAnimation;
@ -61,6 +68,11 @@ private:
struct Answer;
struct CloseInformation;
struct RecentVoter {
not_null<UserData*> user;
mutable std::shared_ptr<Data::CloudImageView> userpic;
};
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
@ -186,7 +198,7 @@ private:
Ui::Text::String _question;
Ui::Text::String _subtitle;
std::vector<not_null<UserData*>> _recentVoters;
std::vector<RecentVoter> _recentVoters;
QImage _recentVotersImage;
std::vector<Answer> _answers;

View file

@ -2743,13 +2743,12 @@ void OverlayWidget::validatePhotoCurrentImage() {
validatePhotoImage(_photoMedia->image(Data::PhotoSize::Small), true);
validatePhotoImage(_photoMedia->thumbnailInline(), true);
if (_staticContent.isNull()
&& _peer
&& !_msgid
&& _peer->userpicLoaded()
&& _peer->userpicLocation().file().valid()) {
validatePhotoImage(
Images::Create(_peer->userpicLocation()).get(),
true);
&& _peer
&& _peer->hasUserpic()) {
if (const auto view = _peer->activeUserpicView()) {
validatePhotoImage(view->image(), true);
}
}
if (_staticContent.isNull()) {
_photoMedia->wanted(Data::PhotoSize::Small, fileOrigin());

View file

@ -461,7 +461,9 @@ bool Manager::Private::showNotification(
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) {
if (!_notificationManager || !_notifier || !_notificationFactory) return false;
if (!_notificationManager || !_notifier || !_notificationFactory) {
return false;
}
ComPtr<IXmlDocument> toastXml;
bool withSubtitle = !subtitle.isEmpty();
@ -476,9 +478,10 @@ bool Manager::Private::showNotification(
hr = SetAudioSilent(toastXml.Get());
if (!SUCCEEDED(hr)) return false;
auto view = std::shared_ptr<Data::CloudImageView>(); // #TODO optimize
const auto key = hideNameAndPhoto
? InMemoryKey()
: peer->userpicUniqueKey();
: peer->userpicUniqueKey(view);
const auto userpicPath = _cachedUserpics.get(key, peer);
const auto userpicPathWide = QDir::toNativeSeparators(userpicPath).toStdWString();

View file

@ -32,11 +32,11 @@ using UpdateFlag = Notify::PeerUpdate::Flag;
} // namespace
GroupMembersWidget::Member::Member(UserData *user) : Item(user) {
GroupMembersWidget::Member::Member(not_null<UserData*> user) : Item(user) {
}
UserData *GroupMembersWidget::Member::user() const {
return static_cast<UserData*>(peer);
not_null<UserData*> GroupMembersWidget::Member::user() const {
return static_cast<UserData*>(peer.get());
}
GroupMembersWidget::GroupMembersWidget(

View file

@ -55,8 +55,8 @@ private:
void refreshUserOnline(UserData *user);
struct Member : public Item {
explicit Member(UserData *user);
UserData *user() const;
explicit Member(not_null<UserData*> user);
not_null<UserData*> user() const;
TimeId onlineTextTill = 0;
TimeId onlineTill = 0;

View file

@ -10,13 +10,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/ripple_animation.h"
#include "ui/text_options.h"
#include "data/data_peer.h"
#include "data/data_cloud_file.h"
#include "main/main_session.h"
#include "styles/style_profile.h"
#include "styles/style_widgets.h"
namespace Profile {
PeerListWidget::Item::Item(PeerData *peer) : peer(peer) {
PeerListWidget::Item::Item(not_null<PeerData*> peer) : peer(peer) {
}
PeerListWidget::Item::~Item() = default;
@ -84,7 +85,7 @@ void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool select
}
int skip = _st.photoPosition.x();
item->peer->paintUserpicLeft(p, x + _st.photoPosition.x(), y + _st.photoPosition.y(), width(), _st.photoSize);
item->peer->paintUserpicLeft(p, item->userpic, x + _st.photoPosition.x(), y + _st.photoPosition.y(), width(), _st.photoSize);
if (item->name.isEmpty()) {
item->name.setText(

View file

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "profile/profile_block_widget.h"
namespace Data {
class CloudImageView;
} // namespace Data
namespace Ui {
class RippleAnimation;
class PopupMenu;
@ -29,10 +33,11 @@ public:
PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const style::PeerListItem &st, const QString &removeText);
struct Item {
explicit Item(PeerData *peer);
explicit Item(not_null<PeerData*> peer);
~Item();
PeerData * const peer;
const not_null<PeerData*> peer;
std::shared_ptr<Data::CloudImageView> userpic;
Ui::Text::String name;
QString statusText;
bool statusHasOnlineColor = false;

View file

@ -127,7 +127,7 @@ std::optional<ImageLocation> readImageLocation(
uint32 peerSize(not_null<PeerData*> peer) {
uint32 result = sizeof(quint64)
+ sizeof(quint64)
+ storageImageLocationSize(peer->userpicLocation());
+ imageLocationSize(peer->userpicLocation());
if (peer->isUser()) {
UserData *user = peer->asUser();
@ -157,7 +157,7 @@ uint32 peerSize(not_null<PeerData*> peer) {
void writePeer(QDataStream &stream, PeerData *peer) {
stream << quint64(peer->id) << quint64(peer->userpicPhotoId());
writeStorageImageLocation(stream, peer->userpicLocation());
writeImageLocation(stream, peer->userpicLocation());
if (const auto user = peer->asUser()) {
stream
<< user->firstName
@ -207,7 +207,7 @@ PeerData *readPeer(int streamAppVersion, QDataStream &stream) {
return nullptr;
}
const auto userpic = readStorageImageLocation(streamAppVersion, stream);
const auto userpic = readImageLocation(streamAppVersion, stream);
auto userpicAccessHash = uint64(0);
if (!userpic) {
return nullptr;
@ -324,14 +324,13 @@ PeerData *readPeer(int streamAppVersion, QDataStream &stream) {
}
if (!loaded) {
using LocationType = StorageFileLocation::Type;
const auto location = (userpic->valid()
&& userpic->type() == LocationType::Legacy)
const auto location = (userpic->valid() && userpic->isLegacy())
? userpic->convertToModern(
LocationType::PeerPhoto,
result->id,
userpicAccessHash)
: *userpic;
result->setUserpic(photoId, location, Images::Create(location));
result->setUserpic(photoId, location);
}
return result;
}

View file

@ -590,6 +590,49 @@ InMemoryKey inMemoryKey(const StorageFileLocation &location) {
return { key.high, key.low };
}
InMemoryKey inMemoryKey(const WebFileLocation &location) {
auto result = InMemoryKey();
const auto &url = location.url();
const auto sha = hashSha1(url.data(), url.size());
bytes::copy(
bytes::object_as_span(&result),
bytes::make_span(sha).subspan(0, sizeof(result)));
return result;
}
InMemoryKey inMemoryKey(const GeoPointLocation &location) {
return InMemoryKey(
(uint64(std::round(std::abs(location.lat + 360.) * 1000000)) << 32)
| uint64(std::round(std::abs(location.lon + 360.) * 1000000)),
(uint64(location.width) << 32) | uint64(location.height));
}
InMemoryKey inMemoryKey(const PlainUrlLocation &location) {
auto result = InMemoryKey();
const auto &url = location.url;
const auto sha = hashSha1(url.data(), url.size() * sizeof(QChar));
bytes::copy(
bytes::object_as_span(&result),
bytes::make_span(sha).subspan(0, sizeof(result)));
return result;
}
InMemoryKey inMemoryKey(const InMemoryLocation &location) {
auto result = InMemoryKey();
const auto &data = location.bytes;
const auto sha = hashSha1(data.data(), data.size());
bytes::copy(
bytes::object_as_span(&result),
bytes::make_span(sha).subspan(0, sizeof(result)));
return result;
}
InMemoryKey inMemoryKey(const DownloadLocation &location) {
return location.data.match([](const auto &data) {
return inMemoryKey(data);
});
}
StorageImageLocation::StorageImageLocation(
const StorageFileLocation &file,
int width,

View file

@ -96,7 +96,6 @@ public:
[[nodiscard]] static const StorageFileLocation &Invalid();
private:
friend bool operator==(
const StorageFileLocation &a,
const StorageFileLocation &b);
@ -104,6 +103,7 @@ private:
const StorageFileLocation &a,
const StorageFileLocation &b);
private:
uint16 _dcId = 0;
Type _type = Type::Legacy;
uint8 _sizeLetter = 0;
@ -203,7 +203,6 @@ public:
return result;
}
private:
friend inline bool operator==(
const StorageImageLocation &a,
const StorageImageLocation &b) {
@ -215,6 +214,7 @@ private:
return (a._file < b._file);
}
private:
StorageFileLocation _file;
int _width = 0;
int _height = 0;
@ -381,6 +381,30 @@ struct PlainUrlLocation {
}
};
inline bool operator!=(
const PlainUrlLocation &a,
const PlainUrlLocation &b) {
return !(a == b);
}
inline bool operator>(
const PlainUrlLocation &a,
const PlainUrlLocation &b) {
return (b < a);
}
inline bool operator<=(
const PlainUrlLocation &a,
const PlainUrlLocation &b) {
return !(b < a);
}
inline bool operator>=(
const PlainUrlLocation &a,
const PlainUrlLocation &b) {
return !(a < b);
}
struct InMemoryLocation {
QByteArray bytes;
@ -396,6 +420,30 @@ struct InMemoryLocation {
}
};
inline bool operator!=(
const InMemoryLocation &a,
const InMemoryLocation &b) {
return !(a == b);
}
inline bool operator>(
const InMemoryLocation &a,
const InMemoryLocation &b) {
return (b < a);
}
inline bool operator<=(
const InMemoryLocation &a,
const InMemoryLocation &b) {
return !(b < a);
}
inline bool operator>=(
const InMemoryLocation &a,
const InMemoryLocation &b) {
return !(a < b);
}
class DownloadLocation {
public:
base::variant<
@ -423,7 +471,6 @@ public:
bool refreshFileReference(const QByteArray &data);
bool refreshFileReference(const Data::UpdatedFileReferences &updates);
private:
friend inline bool operator==(
const DownloadLocation &a,
const DownloadLocation &b) {
@ -437,6 +484,30 @@ private:
};
inline bool operator!=(
const DownloadLocation &a,
const DownloadLocation &b) {
return !(a == b);
}
inline bool operator>(
const DownloadLocation &a,
const DownloadLocation &b) {
return (b < a);
}
inline bool operator<=(
const DownloadLocation &a,
const DownloadLocation &b) {
return !(b < a);
}
inline bool operator>=(
const DownloadLocation &a,
const DownloadLocation &b) {
return !(a < b);
}
class ImageLocation {
public:
ImageLocation() = default;
@ -496,7 +567,6 @@ public:
return result;
}
private:
friend inline bool operator==(
const ImageLocation &a,
const ImageLocation &b) {
@ -508,12 +578,37 @@ private:
return (a._file < b._file);
}
private:
DownloadLocation _file;
int _width = 0;
int _height = 0;
};
inline bool operator!=(
const ImageLocation &a,
const ImageLocation &b) {
return !(a == b);
}
inline bool operator>(
const ImageLocation &a,
const ImageLocation &b) {
return (b < a);
}
inline bool operator<=(
const ImageLocation &a,
const ImageLocation &b) {
return !(b < a);
}
inline bool operator>=(
const ImageLocation &a,
const ImageLocation &b) {
return !(a < b);
}
struct ImageWithLocation {
ImageLocation location;
int bytesCount = 0;
@ -543,21 +638,14 @@ inline InMemoryKey inMemoryKey(const StorageImageLocation &location) {
return inMemoryKey(location.file());
}
inline InMemoryKey inMemoryKey(const WebFileLocation &location) {
auto result = InMemoryKey();
const auto &url = location.url();
const auto sha = hashSha1(url.data(), url.size());
bytes::copy(
bytes::object_as_span(&result),
bytes::make_span(sha).subspan(0, sizeof(result)));
return result;
}
InMemoryKey inMemoryKey(const WebFileLocation &location);
InMemoryKey inMemoryKey(const GeoPointLocation &location);
InMemoryKey inMemoryKey(const PlainUrlLocation &location);
InMemoryKey inMemoryKey(const InMemoryLocation &location);
InMemoryKey inMemoryKey(const DownloadLocation &location);
inline InMemoryKey inMemoryKey(const GeoPointLocation &location) {
return InMemoryKey(
(uint64(std::round(std::abs(location.lat + 360.) * 1000000)) << 32)
| uint64(std::round(std::abs(location.lon + 360.) * 1000000)),
(uint64(location.width) << 32) | uint64(location.height));
inline InMemoryKey inMemoryKey(const ImageLocation &location) {
return inMemoryKey(location.file());
}
inline QSize shrinkToKeepAspect(int32 width, int32 height, int32 towidth, int32 toheight) {

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_folder.h"
#include "data/data_channel.h"
#include "data/data_cloud_file.h"
#include "history/history.h"
#include "core/file_utilities.h"
#include "core/application.h"
@ -633,15 +634,17 @@ void UserpicButton::setupPeerViewers() {
Notify::PeerUpdateViewer(
_peer,
Notify::PeerUpdate::Flag::PhotoChanged
) | rpl::start_with_next([this] {
) | rpl::start_with_next([=] {
processNewPeerPhoto();
update();
}, lifetime());
base::ObservableViewer(
_peer->session().downloaderTaskFinished()
) | rpl::start_with_next([this] {
if (_waiting && _peer->userpicLoaded()) {
) | rpl::filter([=] {
return _waiting;
}) | rpl::start_with_next([=] {
if (!_userpicView || _userpicView->image()) {
_waiting = false;
startNewPhotoShowing();
}
@ -776,7 +779,8 @@ QPoint UserpicButton::prepareRippleStartPosition() const {
void UserpicButton::processPeerPhoto() {
Expects(_peer != nullptr);
_waiting = !_peer->userpicLoaded();
_userpicView = _peer->createUserpicView();
_waiting = _userpicView && !_userpicView->image();
if (_waiting) {
_peer->loadUserpic();
}
@ -951,17 +955,17 @@ void UserpicButton::prepareUserpicPixmap() {
p.drawEllipse(0, 0, size, size);
};
_userpicHasImage = _peer
? (_peer->currentUserpic() || _role != Role::ChangePhoto)
? (_peer->currentUserpic(_userpicView) || _role != Role::ChangePhoto)
: false;
_userpic = CreateSquarePixmap(size, [&](Painter &p) {
if (_userpicHasImage) {
_peer->paintUserpic(p, 0, 0, _st.photoSize);
_peer->paintUserpic(p, _userpicView, 0, 0, _st.photoSize);
} else {
paintButton(p, _st.changeButton.textBg);
}
});
_userpicUniqueKey = _userpicHasImage
? _peer->userpicUniqueKey()
? _peer->userpicUniqueKey(_userpicView)
: InMemoryKey();
}
// // #feed

View file

@ -15,9 +15,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class PeerData;
namespace Ui {
class InfiniteRadialAnimation;
} // namespace Ui
namespace Data {
class CloudImageView;
} // namespace Data
namespace Window {
class SessionController;
@ -25,6 +25,8 @@ class SessionController;
namespace Ui {
class InfiniteRadialAnimation;
class HistoryDownButton : public RippleButton {
public:
HistoryDownButton(QWidget *parent, const style::TwoIconButton &st);
@ -220,6 +222,7 @@ private:
const style::UserpicButton &_st;
Window::SessionController *_controller = nullptr;
PeerData *_peer = nullptr;
std::shared_ptr<Data::CloudImageView> _userpicView;
QString _cropTitle;
Role _role = Role::ChangePhoto;
bool _notShownYet = true;

View file

@ -536,9 +536,10 @@ Notification::Notification(
int shift,
Direction shiftDirection)
: Widget(manager, startPosition, shift, shiftDirection)
, _peer(peer)
, _started(crl::now())
, _history(history)
, _peer(peer)
, _userpicView(_peer->createUserpicView())
, _author(author)
, _item(item)
, _forwardedCount(forwardedCount)
@ -550,7 +551,7 @@ Notification::Notification(
auto position = computePosition(st::notifyMinHeight);
updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyMinHeight);
_userpicLoaded = _peer ? _peer->userpicLoaded() : true;
_userpicLoaded = (_userpicView->image() != nullptr);
updateNotifyDisplay();
_hideTimer.setSingleShot(true);
@ -674,7 +675,7 @@ void Notification::actionsOpacityCallback() {
}
void Notification::updateNotifyDisplay() {
if (!_history || !_peer || (!_item && _forwardedCount < 2)) return;
if (!_history || (!_item && _forwardedCount < 2)) return;
const auto options = Manager::getNotificationOptions(_item);
_hideReplyButton = options.hideReplyButton;
@ -696,8 +697,9 @@ void Notification::updateNotifyDisplay() {
Ui::EmptyUserpic::PaintSavedMessages(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
_userpicLoaded = true;
} else {
_userpicView = _history->peer->createUserpicView();
_history->peer->loadUserpic();
_history->peer->paintUserpicLeft(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
_history->peer->paintUserpicLeft(p, _userpicView, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
}
} else {
p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), manager()->hiddenUserpicPlaceholder());
@ -792,7 +794,11 @@ void Notification::updateNotifyDisplay() {
}
void Notification::updatePeerPhoto() {
if (_userpicLoaded || !_peer || !_peer->userpicLoaded()) {
if (_userpicLoaded) {
return;
}
_userpicView = _peer->createUserpicView();
if (_userpicView && !_userpicView->image()) {
return;
}
_userpicLoaded = true;
@ -800,9 +806,16 @@ void Notification::updatePeerPhoto() {
auto img = _cache.toImage();
{
Painter p(&img);
_peer->paintUserpicLeft(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
_peer->paintUserpicLeft(
p,
_userpicView,
st::notifyPhotoPos.x(),
st::notifyPhotoPos.y(),
width(),
st::notifyPhotoSize);
}
_cache = App::pixmapFromImageInPlace(std::move(img));
_userpicView = nullptr;
update();
}

View file

@ -16,6 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QTimer>
namespace Data {
class CloudImageView;
} // namespace Data
namespace Ui {
class IconButton;
class RoundButton;
@ -232,6 +236,8 @@ private:
void updateGeometry(int x, int y, int width, int height) override;
void actionsOpacityCallback();
const not_null<PeerData*> _peer;
QPixmap _cache;
bool _hideReplyButton = false;
@ -242,7 +248,7 @@ private:
crl::time _started;
History *_history = nullptr;
PeerData *_peer = nullptr;
std::shared_ptr<Data::CloudImageView> _userpicView;
QString _author;
HistoryItem *_item = nullptr;
int _forwardedCount = 0;

View file

@ -46,15 +46,16 @@ QString CachedUserpics::get(const InMemoryKey &key, PeerData *peer) {
}
v.path = cWorkingDir() + qsl("tdata/temp/") + QString::number(rand_value<uint64>(), 16) + qsl(".png");
if (key.first || key.second) {
auto userpic = std::shared_ptr<Data::CloudImageView>(); // #TODO optimize userpic
if (peer->isSelf()) {
const auto method = _type == Type::Rounded
const auto method = (_type == Type::Rounded)
? Ui::EmptyUserpic::GenerateSavedMessagesRounded
: Ui::EmptyUserpic::GenerateSavedMessages;
method(st::notifyMacPhotoSize).save(v.path, "PNG");
} else if (_type == Type::Rounded) {
peer->saveUserpicRounded(v.path, st::notifyMacPhotoSize);
peer->saveUserpicRounded(userpic, v.path, st::notifyMacPhotoSize);
} else {
peer->saveUserpic(v.path, st::notifyMacPhotoSize);
peer->saveUserpic(userpic, v.path, st::notifyMacPhotoSize);
}
} else {
Core::App().logoNoMargin().save(v.path, "PNG");

View file

@ -36,7 +36,7 @@ private:
Type _type = Type::Rounded;
struct Image {
crl::time until;
crl::time until = 0;
QString path;
};
using Images = QMap<InMemoryKey, Image>;

@ -1 +1 @@
Subproject commit dd96d4d482afae4dfd16d0d0f91ca814b9a652a5
Subproject commit 3fc4f2699e53ec225bb5052542255302e14b5ce4