mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Show userpics in who read context item.
This commit is contained in:
parent
8f480b52e7
commit
14314df26a
10 changed files with 302 additions and 181 deletions
|
@ -21,17 +21,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "main/main_account.h"
|
#include "main/main_account.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
|
#include "base/weak_ptr.h"
|
||||||
#include "ui/controls/who_read_context_action.h"
|
#include "ui/controls/who_read_context_action.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct Cached {
|
struct Cached {
|
||||||
explicit Cached(UserId unknownFlag)
|
explicit Cached(PeerId unknownFlag)
|
||||||
: list(std::vector<UserId>{ unknownFlag }) {
|
: list(std::vector<PeerId>{ unknownFlag }) {
|
||||||
}
|
}
|
||||||
rpl::variable<std::vector<UserId>> list;
|
rpl::variable<std::vector<PeerId>> list;
|
||||||
mtpRequestId requestId = 0;
|
mtpRequestId requestId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,11 +48,25 @@ struct Context {
|
||||||
}
|
}
|
||||||
return cached.emplace(
|
return cached.emplace(
|
||||||
item,
|
item,
|
||||||
Cached(item->history()->session().userId())
|
Cached(item->history()->session().userPeerId())
|
||||||
).first->second;
|
).first->second;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Userpic {
|
||||||
|
not_null<PeerData*> peer;
|
||||||
|
mutable std::shared_ptr<Data::CloudImageView> view;
|
||||||
|
mutable InMemoryKey uniqueKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
std::vector<Userpic> userpics;
|
||||||
|
Ui::WhoReadContent current;
|
||||||
|
base::has_weak_ptr guard;
|
||||||
|
bool someUserpicsNotLoaded = false;
|
||||||
|
bool scheduled = false;
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] auto Contexts()
|
[[nodiscard]] auto Contexts()
|
||||||
-> base::flat_map<not_null<QWidget*>, std::unique_ptr<Context>> & {
|
-> base::flat_map<not_null<QWidget*>, std::unique_ptr<Context>> & {
|
||||||
static auto result = base::flat_map<
|
static auto result = base::flat_map<
|
||||||
|
@ -82,10 +98,10 @@ struct Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool ListUnknown(
|
[[nodiscard]] bool ListUnknown(
|
||||||
const std::vector<UserId> &list,
|
const std::vector<PeerId> &list,
|
||||||
not_null<HistoryItem*> item) {
|
not_null<HistoryItem*> item) {
|
||||||
return (list.size() == 1)
|
return (list.size() == 1)
|
||||||
&& (list.front() == item->history()->session().userId());
|
&& (list.front() == item->history()->session().userPeerId());
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] Ui::WhoReadType DetectType(not_null<HistoryItem*> item) {
|
[[nodiscard]] Ui::WhoReadType DetectType(not_null<HistoryItem*> item) {
|
||||||
|
@ -103,6 +119,161 @@ struct Context {
|
||||||
return Ui::WhoReadType::Seen;
|
return Ui::WhoReadType::Seen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<std::vector<PeerId>> WhoReadIds(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
not_null<QWidget*> context) {
|
||||||
|
auto weak = QPointer<QWidget>(context.get());
|
||||||
|
const auto fullId = item->fullId();
|
||||||
|
const auto session = &item->history()->session();
|
||||||
|
return [=](auto consumer) {
|
||||||
|
if (!weak) {
|
||||||
|
return rpl::lifetime();
|
||||||
|
}
|
||||||
|
const auto context = ContextAt(weak.data());
|
||||||
|
if (!context->subscriptions.contains(session)) {
|
||||||
|
session->changes().messageUpdates(
|
||||||
|
Data::MessageUpdate::Flag::Destroyed
|
||||||
|
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||||
|
const auto i = context->cached.find(update.item);
|
||||||
|
if (i == end(context->cached)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
session->api().request(i->second.requestId).cancel();
|
||||||
|
context->cached.erase(i);
|
||||||
|
}, context->subscriptions[session]);
|
||||||
|
}
|
||||||
|
auto &entry = context->cache(item);
|
||||||
|
if (!entry.requestId) {
|
||||||
|
entry.requestId = session->api().request(
|
||||||
|
MTPmessages_GetMessageReadParticipants(
|
||||||
|
item->history()->peer->input,
|
||||||
|
MTP_int(item->id)
|
||||||
|
)
|
||||||
|
).done([=](const MTPVector<MTPlong> &result) {
|
||||||
|
auto &entry = context->cache(item);
|
||||||
|
entry.requestId = 0;
|
||||||
|
auto peers = std::vector<PeerId>();
|
||||||
|
peers.reserve(std::max(result.v.size(), 1));
|
||||||
|
for (const auto &id : result.v) {
|
||||||
|
peers.push_back(UserId(id));
|
||||||
|
}
|
||||||
|
entry.list = std::move(peers);
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
auto &entry = context->cache(item);
|
||||||
|
entry.requestId = 0;
|
||||||
|
if (ListUnknown(entry.list.current(), item)) {
|
||||||
|
entry.list = std::vector<PeerId>();
|
||||||
|
}
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
return entry.list.value().start_existing(consumer);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdateUserpics(
|
||||||
|
not_null<State*> state,
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
const std::vector<PeerId> &ids) {
|
||||||
|
auto &owner = item->history()->owner();
|
||||||
|
|
||||||
|
const auto peers = ranges::views::all(
|
||||||
|
ids
|
||||||
|
) | ranges::views::transform([&](PeerId id) {
|
||||||
|
return owner.peerLoaded(id);
|
||||||
|
}) | ranges::views::filter([](PeerData *peer) {
|
||||||
|
return peer != nullptr;
|
||||||
|
}) | ranges::views::transform([](PeerData *peer) {
|
||||||
|
return not_null(peer);
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
|
||||||
|
const auto same = ranges::equal(
|
||||||
|
state->userpics,
|
||||||
|
peers,
|
||||||
|
ranges::less(),
|
||||||
|
&Userpic::peer);
|
||||||
|
if (same) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto &was = state->userpics;
|
||||||
|
auto now = std::vector<Userpic>();
|
||||||
|
for (const auto peer : peers) {
|
||||||
|
if (ranges::contains(now, peer, &Userpic::peer)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto i = ranges::find(was, peer, &Userpic::peer);
|
||||||
|
if (i != end(was)) {
|
||||||
|
now.push_back(std::move(*i));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
now.push_back(Userpic{
|
||||||
|
.peer = peer,
|
||||||
|
});
|
||||||
|
auto &userpic = now.back();
|
||||||
|
userpic.uniqueKey = peer->userpicUniqueKey(userpic.view);
|
||||||
|
peer->loadUserpic();
|
||||||
|
}
|
||||||
|
was = std::move(now);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegenerateUserpics(not_null<State*> state, int small, int large) {
|
||||||
|
Expects(state->userpics.size() == state->current.participants.size());
|
||||||
|
|
||||||
|
state->someUserpicsNotLoaded = false;
|
||||||
|
const auto count = int(state->userpics.size());
|
||||||
|
for (auto i = 0; i != count; ++i) {
|
||||||
|
auto &userpic = state->userpics[i];
|
||||||
|
auto &participant = state->current.participants[i];
|
||||||
|
const auto peer = userpic.peer;
|
||||||
|
const auto key = peer->userpicUniqueKey(userpic.view);
|
||||||
|
if (peer->hasUserpic() && peer->useEmptyUserpic(userpic.view)) {
|
||||||
|
state->someUserpicsNotLoaded = true;
|
||||||
|
}
|
||||||
|
if (userpic.uniqueKey == key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
participant.userpicKey = userpic.uniqueKey = key;
|
||||||
|
participant.userpicLarge = peer->generateUserpicImage(
|
||||||
|
userpic.view,
|
||||||
|
large);
|
||||||
|
if (i < Ui::WhoReadParticipant::kMaxSmallUserpics) {
|
||||||
|
participant.userpicSmall = peer->generateUserpicImage(
|
||||||
|
userpic.view,
|
||||||
|
small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegenerateParticipants(not_null<State*> state, int small, int large) {
|
||||||
|
auto old = base::take(state->current.participants);
|
||||||
|
auto &now = state->current.participants;
|
||||||
|
now.reserve(state->userpics.size());
|
||||||
|
for (auto &userpic : state->userpics) {
|
||||||
|
const auto peer = userpic.peer;
|
||||||
|
const auto id = peer->id.value;
|
||||||
|
const auto was = ranges::find(old, id, &Ui::WhoReadParticipant::id);
|
||||||
|
if (was != end(old)) {
|
||||||
|
was->name = peer->name;
|
||||||
|
now.push_back(std::move(*was));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
now.push_back({
|
||||||
|
.name = peer->name,
|
||||||
|
.userpicLarge = peer->generateUserpicImage(
|
||||||
|
userpic.view,
|
||||||
|
large),
|
||||||
|
.userpicKey = userpic.uniqueKey,
|
||||||
|
.id = id,
|
||||||
|
});
|
||||||
|
if (now.size() <= Ui::WhoReadParticipant::kMaxSmallUserpics) {
|
||||||
|
now.back().userpicSmall = peer->generateUserpicImage(
|
||||||
|
userpic.view,
|
||||||
|
small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RegenerateUserpics(state, small, large);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool WhoReadExists(not_null<HistoryItem*> item) {
|
bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||||
|
@ -144,91 +315,54 @@ bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<std::vector<UserId>> WhoReadIds(
|
rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
not_null<QWidget*> context) {
|
not_null<QWidget*> context,
|
||||||
auto weak = QPointer<QWidget>(context.get());
|
const style::WhoRead &st) {
|
||||||
const auto fullId = item->fullId();
|
const auto small = st.userpics.size;
|
||||||
const auto session = &item->history()->session();
|
const auto large = st.photoSize;
|
||||||
return [=](auto consumer) {
|
return [=](auto consumer) {
|
||||||
if (!weak) {
|
auto lifetime = rpl::lifetime();
|
||||||
return rpl::lifetime();
|
|
||||||
}
|
const auto state = lifetime.make_state<State>();
|
||||||
const auto context = ContextAt(weak.data());
|
const auto pushNext = [=] {
|
||||||
if (!context->subscriptions.contains(session)) {
|
consumer.put_next_copy(state->current);
|
||||||
session->changes().messageUpdates(
|
};
|
||||||
Data::MessageUpdate::Flag::Destroyed
|
|
||||||
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
WhoReadIds(
|
||||||
const auto i = context->cached.find(update.item);
|
item,
|
||||||
if (i == end(context->cached)) {
|
context
|
||||||
|
) | rpl::start_with_next([=](const std::vector<PeerId> &peers) {
|
||||||
|
if (ListUnknown(peers, item)) {
|
||||||
|
state->userpics.clear();
|
||||||
|
consumer.put_next(Ui::WhoReadContent{ .unknown = true });
|
||||||
|
return;
|
||||||
|
} else if (UpdateUserpics(state, item, peers)) {
|
||||||
|
RegenerateParticipants(state, small, large);
|
||||||
|
pushNext();
|
||||||
|
}
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
item->history()->session().downloaderTaskFinished(
|
||||||
|
) | rpl::filter([=] {
|
||||||
|
return state->someUserpicsNotLoaded && !state->scheduled;
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
for (const auto &userpic : state->userpics) {
|
||||||
|
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||||
|
!= userpic.uniqueKey) {
|
||||||
|
state->scheduled = true;
|
||||||
|
crl::on_main(&state->guard, [=] {
|
||||||
|
state->scheduled = false;
|
||||||
|
RegenerateUserpics(state, small, large);
|
||||||
|
pushNext();
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
session->api().request(i->second.requestId).cancel();
|
}
|
||||||
context->cached.erase(i);
|
}, lifetime);
|
||||||
}, context->subscriptions[session]);
|
|
||||||
}
|
return lifetime;
|
||||||
auto &entry = context->cache(item);
|
|
||||||
if (!entry.requestId) {
|
|
||||||
const auto makeEmpty = [=] {
|
|
||||||
// Special value that marks a validated empty list.
|
|
||||||
return std::vector<UserId>{
|
|
||||||
item->history()->session().userId()
|
|
||||||
};
|
|
||||||
};
|
|
||||||
entry.requestId = session->api().request(
|
|
||||||
MTPmessages_GetMessageReadParticipants(
|
|
||||||
item->history()->peer->input,
|
|
||||||
MTP_int(item->id)
|
|
||||||
)
|
|
||||||
).done([=](const MTPVector<MTPlong> &result) {
|
|
||||||
auto &entry = context->cache(item);
|
|
||||||
entry.requestId = 0;
|
|
||||||
auto users = std::vector<UserId>();
|
|
||||||
users.reserve(std::max(result.v.size(), 1));
|
|
||||||
for (const auto &id : result.v) {
|
|
||||||
users.push_back(UserId(id));
|
|
||||||
}
|
|
||||||
entry.list = std::move(users);
|
|
||||||
}).fail([=](const MTP::Error &error) {
|
|
||||||
auto &entry = context->cache(item);
|
|
||||||
entry.requestId = 0;
|
|
||||||
if (ListUnknown(entry.list.current(), item)) {
|
|
||||||
entry.list = std::vector<UserId>();
|
|
||||||
}
|
|
||||||
}).send();
|
|
||||||
}
|
|
||||||
return entry.list.value().start_existing(consumer);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<Ui::WhoReadContent> WhoRead(
|
|
||||||
not_null<HistoryItem*> item,
|
|
||||||
not_null<QWidget*> context) {
|
|
||||||
return WhoReadIds(
|
|
||||||
item,
|
|
||||||
context
|
|
||||||
) | rpl::map([=](const std::vector<UserId> &users) {
|
|
||||||
const auto owner = &item->history()->owner();
|
|
||||||
if (ListUnknown(users, item)) {
|
|
||||||
return Ui::WhoReadContent{ .unknown = true };
|
|
||||||
}
|
|
||||||
auto participants = ranges::views::all(
|
|
||||||
users
|
|
||||||
) | ranges::views::transform([&](UserId id) {
|
|
||||||
return owner->userLoaded(id);
|
|
||||||
}) | ranges::views::filter([](UserData *user) {
|
|
||||||
return user != nullptr;
|
|
||||||
}) | ranges::views::transform([](UserData *user) {
|
|
||||||
return Ui::WhoReadParticipant{
|
|
||||||
.name = user->name,
|
|
||||||
.id = user->id.value,
|
|
||||||
};
|
|
||||||
}) | ranges::to_vector;
|
|
||||||
return Ui::WhoReadContent{
|
|
||||||
.participants = std::move(participants),
|
|
||||||
.type = DetectType(item),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
|
|
||||||
|
namespace style {
|
||||||
|
struct WhoRead;
|
||||||
|
} // namespace style
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
struct WhoReadContent;
|
struct WhoReadContent;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
@ -20,6 +24,7 @@ namespace Api {
|
||||||
// The context must be destroyed before the session holding this item.
|
// The context must be destroyed before the session holding this item.
|
||||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoRead(
|
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
not_null<QWidget*> context); // Cache results for this lifetime.
|
not_null<QWidget*> context,
|
||||||
|
const style::WhoRead &st); // Cache results for this lifetime.
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -462,19 +462,11 @@ void Viewport::RendererGL::validateUserpicFrame(
|
||||||
} else if (!tileData.userpicFrame.isNull()) {
|
} else if (!tileData.userpicFrame.isNull()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tileData.userpicFrame = QImage(
|
const auto size = tile->trackOrUserpicSize();
|
||||||
tile->trackOrUserpicSize(),
|
tileData.userpicFrame = tile->row()->peer()->generateUserpicImage(
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
tile->row()->ensureUserpicView(),
|
||||||
tileData.userpicFrame.fill(Qt::black);
|
size.width(),
|
||||||
{
|
ImageRoundRadius::None);
|
||||||
auto p = Painter(&tileData.userpicFrame);
|
|
||||||
tile->row()->peer()->paintUserpicSquare(
|
|
||||||
p,
|
|
||||||
tile->row()->ensureUserpicView(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
tileData.userpicFrame.width());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::RendererGL::paintTile(
|
void Viewport::RendererGL::paintTile(
|
||||||
|
|
|
@ -71,21 +71,12 @@ void Viewport::RendererSW::validateUserpicFrame(
|
||||||
} else if (!data.userpicFrame.isNull()) {
|
} else if (!data.userpicFrame.isNull()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto userpic = QImage(
|
const auto size = tile->trackOrUserpicSize();
|
||||||
tile->trackOrUserpicSize(),
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
userpic.fill(Qt::black);
|
|
||||||
{
|
|
||||||
auto p = Painter(&userpic);
|
|
||||||
tile->row()->peer()->paintUserpicSquare(
|
|
||||||
p,
|
|
||||||
tile->row()->ensureUserpicView(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
userpic.width());
|
|
||||||
}
|
|
||||||
data.userpicFrame = Images::BlurLargeImage(
|
data.userpicFrame = Images::BlurLargeImage(
|
||||||
std::move(userpic),
|
tile->row()->peer()->generateUserpicImage(
|
||||||
|
tile->row()->ensureUserpicView(),
|
||||||
|
size.width(),
|
||||||
|
ImageRoundRadius::None),
|
||||||
kBlurRadius);
|
kBlurRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -335,32 +335,6 @@ void PeerData::paintUserpic(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(size, size, ImageRoundRadius::Small));
|
|
||||||
} else {
|
|
||||||
ensureEmptyUserpic()->paintRounded(p, x, y, x + size + x, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(size, size));
|
|
||||||
} else {
|
|
||||||
ensureEmptyUserpic()->paintSquare(p, x, y, x + size + x, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PeerData::loadUserpic() {
|
void PeerData::loadUserpic() {
|
||||||
_userpic.load(&session(), userpicOrigin());
|
_userpic.load(&session(), userpicOrigin());
|
||||||
}
|
}
|
||||||
|
@ -398,14 +372,17 @@ void PeerData::saveUserpic(
|
||||||
std::shared_ptr<Data::CloudImageView> &view,
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
const QString &path,
|
const QString &path,
|
||||||
int size) const {
|
int size) const {
|
||||||
genUserpic(view, size).save(path, "PNG");
|
generateUserpicImage(view, size * cIntRetinaFactor()).save(path, "PNG");
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerData::saveUserpicRounded(
|
void PeerData::saveUserpicRounded(
|
||||||
std::shared_ptr<Data::CloudImageView> &view,
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
const QString &path,
|
const QString &path,
|
||||||
int size) const {
|
int size) const {
|
||||||
genUserpicRounded(view, size).save(path, "PNG");
|
generateUserpicImage(
|
||||||
|
view,
|
||||||
|
size * cIntRetinaFactor(),
|
||||||
|
ImageRoundRadius::Small).save(path, "PNG");
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap PeerData::genUserpic(
|
QPixmap PeerData::genUserpic(
|
||||||
|
@ -424,20 +401,43 @@ QPixmap PeerData::genUserpic(
|
||||||
return Ui::PixmapFromImage(std::move(result));
|
return Ui::PixmapFromImage(std::move(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap PeerData::genUserpicRounded(
|
QImage PeerData::generateUserpicImage(
|
||||||
std::shared_ptr<Data::CloudImageView> &view,
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
int size) const {
|
int size) const {
|
||||||
|
return generateUserpicImage(view, size, ImageRoundRadius::Ellipse);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage PeerData::generateUserpicImage(
|
||||||
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
|
int size,
|
||||||
|
ImageRoundRadius radius) const {
|
||||||
if (const auto userpic = currentUserpic(view)) {
|
if (const auto userpic = currentUserpic(view)) {
|
||||||
return userpic->pixRounded(size, size, ImageRoundRadius::Small);
|
const auto options = (radius == ImageRoundRadius::Ellipse)
|
||||||
|
? (Images::Option::RoundedAll | Images::Option::Circled)
|
||||||
|
: (radius == ImageRoundRadius::None)
|
||||||
|
? Images::Options()
|
||||||
|
: (Images::Option::RoundedAll | Images::Option::RoundedSmall);
|
||||||
|
return userpic->pixNoCache(
|
||||||
|
size,
|
||||||
|
size,
|
||||||
|
Images::Option::Smooth | options
|
||||||
|
).toImage();
|
||||||
}
|
}
|
||||||
auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
auto result = QImage(
|
||||||
result.setDevicePixelRatio(cRetinaFactor());
|
QSize(size, size),
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
result.fill(Qt::transparent);
|
result.fill(Qt::transparent);
|
||||||
{
|
{
|
||||||
Painter p(&result);
|
Painter p(&result);
|
||||||
paintUserpicRounded(p, view, 0, 0, size);
|
if (radius == ImageRoundRadius::Ellipse) {
|
||||||
|
ensureEmptyUserpic()->paint(p, 0, 0, size, size);
|
||||||
|
} else if (radius == ImageRoundRadius::None) {
|
||||||
|
ensureEmptyUserpic()->paintSquare(p, 0, 0, size, size);
|
||||||
|
} else {
|
||||||
|
ensureEmptyUserpic()->paintRounded(p, 0, 0, size, size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Ui::PixmapFromImage(std::move(result));
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Data::FileOrigin PeerData::userpicOrigin() const {
|
Data::FileOrigin PeerData::userpicOrigin() const {
|
||||||
|
|
|
@ -340,18 +340,6 @@ public:
|
||||||
int size) const {
|
int size) const {
|
||||||
paintUserpic(p, view, 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();
|
void loadUserpic();
|
||||||
[[nodiscard]] bool hasUserpic() const;
|
[[nodiscard]] bool hasUserpic() const;
|
||||||
[[nodiscard]] std::shared_ptr<Data::CloudImageView> activeUserpicView();
|
[[nodiscard]] std::shared_ptr<Data::CloudImageView> activeUserpicView();
|
||||||
|
@ -371,9 +359,13 @@ public:
|
||||||
[[nodiscard]] QPixmap genUserpic(
|
[[nodiscard]] QPixmap genUserpic(
|
||||||
std::shared_ptr<Data::CloudImageView> &view,
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
int size) const;
|
int size) const;
|
||||||
[[nodiscard]] QPixmap genUserpicRounded(
|
[[nodiscard]] QImage generateUserpicImage(
|
||||||
std::shared_ptr<Data::CloudImageView> &view,
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
int size) const;
|
int size) const;
|
||||||
|
[[nodiscard]] QImage generateUserpicImage(
|
||||||
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
|
int size,
|
||||||
|
ImageRoundRadius radius) const;
|
||||||
[[nodiscard]] ImageLocation userpicLocation() const {
|
[[nodiscard]] ImageLocation userpicLocation() const {
|
||||||
return _userpic.location();
|
return _userpic.location();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1591,8 +1591,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
};
|
};
|
||||||
_menu->addAction(Ui::WhoReadContextAction(
|
_menu->addAction(Ui::WhoReadContextAction(
|
||||||
_menu.get(),
|
_menu.get(),
|
||||||
Api::WhoRead(item, this),
|
Api::WhoRead(item, this, st::defaultWhoRead),
|
||||||
participantChosen));
|
participantChosen));
|
||||||
|
_menu->addSeparator();
|
||||||
}
|
}
|
||||||
if (canSendMessages) {
|
if (canSendMessages) {
|
||||||
_menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {
|
_menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {
|
||||||
|
|
|
@ -863,9 +863,18 @@ ttlDividerLabelPadding: margins(22px, 10px, 22px, 19px);
|
||||||
ttlItemPadding: margins(0px, 4px, 0px, 4px);
|
ttlItemPadding: margins(0px, 4px, 0px, 4px);
|
||||||
ttlItemTimerFont: font(12px);
|
ttlItemTimerFont: font(12px);
|
||||||
|
|
||||||
seenItemUserpics: GroupCallUserpics {
|
WhoRead {
|
||||||
size: 32px;
|
userpics: GroupCallUserpics;
|
||||||
shift: 12px;
|
photoSize: pixels;
|
||||||
stroke: 4px;
|
itemPadding: margins;
|
||||||
align: align(right);
|
}
|
||||||
|
defaultWhoRead: WhoRead {
|
||||||
|
userpics: GroupCallUserpics {
|
||||||
|
size: 22px;
|
||||||
|
shift: 8px;
|
||||||
|
stroke: 4px;
|
||||||
|
align: align(right);
|
||||||
|
}
|
||||||
|
photoSize: 30px;
|
||||||
|
itemPadding: margins(17px, 8px, 17px, 6px);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kMaxUserpics = 3;
|
|
||||||
|
|
||||||
class Action final : public Menu::ItemBase {
|
class Action final : public Menu::ItemBase {
|
||||||
public:
|
public:
|
||||||
Action(
|
Action(
|
||||||
|
@ -77,23 +75,19 @@ Action::Action(
|
||||||
, _dummyAction(new QAction(parentMenu->menu()))
|
, _dummyAction(new QAction(parentMenu->menu()))
|
||||||
, _participantChosen(std::move(participantChosen))
|
, _participantChosen(std::move(participantChosen))
|
||||||
, _userpics(std::make_unique<GroupCallUserpics>(
|
, _userpics(std::make_unique<GroupCallUserpics>(
|
||||||
st::historyGroupCallUserpics,
|
st::defaultWhoRead.userpics,
|
||||||
rpl::never<bool>(),
|
rpl::never<bool>(),
|
||||||
[=] { update(); }))
|
[=] { update(); }))
|
||||||
, _st(parentMenu->menu()->st())
|
, _st(parentMenu->menu()->st())
|
||||||
, _height(_st.itemPadding.top()
|
, _height(st::defaultWhoRead.itemPadding.top()
|
||||||
+ _st.itemStyle.font->height
|
+ _st.itemStyle.font->height
|
||||||
+ _st.itemPadding.bottom()) {
|
+ st::defaultWhoRead.itemPadding.bottom()) {
|
||||||
const auto parent = parentMenu->menu();
|
const auto parent = parentMenu->menu();
|
||||||
|
|
||||||
setAcceptBoth(true);
|
setAcceptBoth(true);
|
||||||
initResizeHook(parent->sizeValue());
|
initResizeHook(parent->sizeValue());
|
||||||
resolveMinWidth();
|
resolveMinWidth();
|
||||||
|
|
||||||
auto copy = std::move(
|
|
||||||
content
|
|
||||||
) | rpl::start_spawning(lifetime());
|
|
||||||
|
|
||||||
_userpics->widthValue(
|
_userpics->widthValue(
|
||||||
) | rpl::start_with_next([=](int width) {
|
) | rpl::start_with_next([=](int width) {
|
||||||
_userpicsWidth = width;
|
_userpicsWidth = width;
|
||||||
|
@ -160,12 +154,12 @@ void Action::updateUserpicsFromContent() {
|
||||||
if (!_content.participants.empty()) {
|
if (!_content.participants.empty()) {
|
||||||
const auto count = std::min(
|
const auto count = std::min(
|
||||||
int(_content.participants.size()),
|
int(_content.participants.size()),
|
||||||
kMaxUserpics);
|
WhoReadParticipant::kMaxSmallUserpics);
|
||||||
users.reserve(count);
|
users.reserve(count);
|
||||||
for (auto i = 0; i != count; ++i) {
|
for (auto i = 0; i != count; ++i) {
|
||||||
const auto &participant = _content.participants[i];
|
const auto &participant = _content.participants[i];
|
||||||
users.push_back({
|
users.push_back({
|
||||||
.userpic = participant.userpic,
|
.userpic = participant.userpicSmall,
|
||||||
.userpicKey = participant.userpicKey,
|
.userpicKey = participant.userpicKey,
|
||||||
.id = participant.id,
|
.id = participant.id,
|
||||||
});
|
});
|
||||||
|
@ -217,8 +211,8 @@ void Action::paint(Painter &p) {
|
||||||
_userpics->paint(
|
_userpics->paint(
|
||||||
p,
|
p,
|
||||||
width() - _st.itemPadding.right(),
|
width() - _st.itemPadding.right(),
|
||||||
_st.itemPadding.top(),
|
(height() - st::defaultWhoRead.userpics.size) / 2,
|
||||||
st::historyGroupCallUserpics.size);
|
st::defaultWhoRead.userpics.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Action::refreshText() {
|
void Action::refreshText() {
|
||||||
|
|
|
@ -18,9 +18,12 @@ class PopupMenu;
|
||||||
|
|
||||||
struct WhoReadParticipant {
|
struct WhoReadParticipant {
|
||||||
QString name;
|
QString name;
|
||||||
QImage userpic;
|
QImage userpicSmall;
|
||||||
|
QImage userpicLarge;
|
||||||
std::pair<uint64, uint64> userpicKey = {};
|
std::pair<uint64, uint64> userpicKey = {};
|
||||||
uint64 id = 0;
|
uint64 id = 0;
|
||||||
|
|
||||||
|
static constexpr auto kMaxSmallUserpics = 3;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool operator==(const WhoReadParticipant &a, const WhoReadParticipant &b);
|
bool operator==(const WhoReadParticipant &a, const WhoReadParticipant &b);
|
||||||
|
|
Loading…
Add table
Reference in a new issue