mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Use Data::CloudImage for userpics.
This commit is contained in:
parent
249f7813c1
commit
f066e0f05a
55 changed files with 748 additions and 284 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
Loading…
Add table
Reference in a new issue