mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-31 14:38:15 +00:00
Update API scheme on layer 148: Topic icons.
This commit is contained in:
@@ -49,7 +49,7 @@ CloudTheme CloudTheme::Parse(
|
||||
settings.match([&](const MTPDthemeSettings &data) {
|
||||
if (const auto colors = data.vmessage_colors()) {
|
||||
for (const auto &color : colors->v) {
|
||||
result.push_back(ColorFromSerialized(color));
|
||||
result.push_back(Ui::ColorFromSerialized(color));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -57,12 +57,12 @@ CloudTheme CloudTheme::Parse(
|
||||
};
|
||||
const auto accentColor = [&](const MTPThemeSettings &settings) {
|
||||
return settings.match([&](const MTPDthemeSettings &data) {
|
||||
return ColorFromSerialized(data.vaccent_color().v);
|
||||
return Ui::ColorFromSerialized(data.vaccent_color());
|
||||
});
|
||||
};
|
||||
const auto outgoingAccentColor = [&](const MTPThemeSettings &settings) {
|
||||
return settings.match([&](const MTPDthemeSettings &data) {
|
||||
return MaybeColorFromSerialized(data.voutbox_accent_color());
|
||||
return Ui::MaybeColorFromSerialized(data.voutbox_accent_color());
|
||||
});
|
||||
};
|
||||
const auto basedOnDark = [&](const MTPThemeSettings &settings) {
|
||||
|
@@ -26,6 +26,7 @@ namespace {
|
||||
|
||||
constexpr auto kTopicsFirstLoad = 20;
|
||||
constexpr auto kTopicsPerPage = 500;
|
||||
constexpr auto kGeneralColorId = 0xA9A9A9;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -123,6 +124,7 @@ void Forum::applyReceivedTopics(
|
||||
void Forum::applyTopicAdded(
|
||||
MsgId rootId,
|
||||
const QString &title,
|
||||
int32 colorId,
|
||||
DocumentId iconId) {
|
||||
const auto i = _topics.find(rootId);
|
||||
const auto raw = (i != end(_topics))
|
||||
@@ -132,6 +134,7 @@ void Forum::applyTopicAdded(
|
||||
std::make_unique<ForumTopic>(_history, rootId)
|
||||
).first->second.get();
|
||||
raw->applyTitle(title);
|
||||
raw->applyColorId(colorId);
|
||||
raw->applyIconId(iconId);
|
||||
if (!creating(rootId)) {
|
||||
raw->addToChatList(FilterId(), topicsList());
|
||||
@@ -147,10 +150,11 @@ void Forum::applyTopicRemoved(MsgId rootId) {
|
||||
|
||||
MsgId Forum::reserveCreatingId(
|
||||
const QString &title,
|
||||
int32 colorId,
|
||||
DocumentId iconId) {
|
||||
const auto result = _history->owner().nextLocalMessageId();
|
||||
_creatingRootIds.emplace(result);
|
||||
applyTopicAdded(result, title, iconId);
|
||||
applyTopicAdded(result, title, colorId, iconId);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -200,7 +204,7 @@ ForumTopic *Forum::topicFor(MsgId rootId) {
|
||||
}
|
||||
} else {
|
||||
// #TODO forum lang
|
||||
applyTopicAdded(rootId, "General! Created.", DocumentId(0));
|
||||
applyTopicAdded(rootId, "General! Created.", kGeneralColorId, 0);
|
||||
return _topics.find(rootId)->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
|
@@ -34,6 +34,7 @@ public:
|
||||
void applyTopicAdded(
|
||||
MsgId rootId,
|
||||
const QString &title,
|
||||
int32 colorId,
|
||||
DocumentId iconId);
|
||||
void applyTopicRemoved(MsgId rootId);
|
||||
void applyTopicCreated(MsgId rootId, MsgId realId);
|
||||
@@ -44,6 +45,7 @@ public:
|
||||
|
||||
[[nodiscard]] MsgId reserveCreatingId(
|
||||
const QString &title,
|
||||
int32 colorId,
|
||||
DocumentId iconId);
|
||||
void discardCreatingId(MsgId rootId);
|
||||
[[nodiscard]] bool creating(MsgId rootId) const;
|
||||
|
@@ -18,11 +18,110 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/color_int_conversion.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
|
||||
namespace Data {
|
||||
|
||||
const base::flat_map<int32, QString> &ForumTopicIcons() {
|
||||
static const auto Result = base::flat_map<int32, QString>{
|
||||
{ 0x6FB9F0, u"blue"_q },
|
||||
{ 0xFFD67E, u"yellow"_q },
|
||||
{ 0xCB86DB, u"violet"_q },
|
||||
{ 0x8EEE98, u"green"_q },
|
||||
{ 0xFF93B2, u"rose"_q },
|
||||
{ 0xFB6F5F, u"red"_q },
|
||||
};
|
||||
return Result;
|
||||
}
|
||||
|
||||
const std::vector<int32> &ForumTopicColorIds() {
|
||||
static const auto Result = ForumTopicIcons(
|
||||
) | ranges::views::transform([](const auto &pair) {
|
||||
return pair.first;
|
||||
}) | ranges::to_vector;
|
||||
return Result;
|
||||
}
|
||||
|
||||
const QString &ForumTopicDefaultIcon() {
|
||||
static const auto Result = u"gray"_q;
|
||||
return Result;
|
||||
}
|
||||
|
||||
const QString &ForumTopicIcon(int32 colorId) {
|
||||
const auto &icons = ForumTopicIcons();
|
||||
const auto i = icons.find(colorId);
|
||||
return (i != end(icons)) ? i->second : ForumTopicDefaultIcon();
|
||||
}
|
||||
|
||||
QString ForumTopicIconPath(const QString &name) {
|
||||
return u":/gui/topic_icons/%1.svg"_q.arg(name);
|
||||
}
|
||||
|
||||
QImage ForumTopicIconBackground(int32 colorId, int size) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto svg = QSvgRenderer(ForumTopicIconPath(ForumTopicIcon(colorId)));
|
||||
auto result = QImage(
|
||||
QSize(size, size) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
result.fill(Qt::transparent);
|
||||
|
||||
auto p = QPainter(&result);
|
||||
svg.render(&p, QRect(0, 0, size, size));
|
||||
p.end();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString ExtractNonEmojiLetter(const QString &title) {
|
||||
const auto begin = title.data();
|
||||
const auto end = begin + title.size();
|
||||
for (auto ch = begin; ch != end;) {
|
||||
auto length = 0;
|
||||
if (Ui::Emoji::Find(ch, end, &length)) {
|
||||
ch += length;
|
||||
continue;
|
||||
}
|
||||
uint ucs4 = ch->unicode();
|
||||
length = 1;
|
||||
if (QChar::isHighSurrogate(ucs4) && ch + 1 != end) {
|
||||
ushort low = ch[1].unicode();
|
||||
if (QChar::isLowSurrogate(low)) {
|
||||
ucs4 = QChar::surrogateToUcs4(ucs4, low);
|
||||
length = 2;
|
||||
}
|
||||
}
|
||||
if (!QChar::isLetterOrNumber(ucs4)) {
|
||||
ch += length;
|
||||
continue;
|
||||
}
|
||||
return QString(ch, length);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QImage ForumTopicIconFrame(
|
||||
int32 colorId,
|
||||
const QString &title,
|
||||
const style::ForumTopicIcon &st) {
|
||||
auto background = ForumTopicIconBackground(colorId, st.size);
|
||||
|
||||
if (const auto one = ExtractNonEmojiLetter(title); !one.isEmpty()) {
|
||||
auto p = QPainter(&background);
|
||||
p.setPen(Qt::white);
|
||||
p.drawText(
|
||||
QRect(0, st.textTop, st.size, st.font->height * 2),
|
||||
one,
|
||||
style::al_top);
|
||||
}
|
||||
|
||||
return background;
|
||||
}
|
||||
|
||||
ForumTopic::ForumTopic(not_null<History*> history, MsgId rootId)
|
||||
: Entry(&history->owner(), Type::ForumTopic)
|
||||
, _history(history)
|
||||
@@ -62,6 +161,7 @@ void ForumTopic::applyTopic(const MTPForumTopic &topic) {
|
||||
} else {
|
||||
applyIconId(0);
|
||||
}
|
||||
applyColorId(data.vicon_color().v);
|
||||
|
||||
const auto pinned = _list->pinned();
|
||||
#if 0 // #TODO forum pinned
|
||||
@@ -218,19 +318,29 @@ void ForumTopic::paintUserpic(
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
const Dialogs::Ui::PaintContext &context) const {
|
||||
const auto &st = context.st;
|
||||
const auto position = QPoint(st->padding.left(), st->padding.top());
|
||||
if (_icon) {
|
||||
_icon->paint(p, {
|
||||
.preview = st::windowBgOver->c,
|
||||
.now = context.now,
|
||||
.position = QPoint(st->padding.left(), st->padding.top()),
|
||||
.position = position,
|
||||
.paused = context.paused,
|
||||
});
|
||||
} else {
|
||||
// #TODO forum
|
||||
st::stickersPremium.paint(
|
||||
p,
|
||||
QPoint(st->padding.left(), st->padding.top()),
|
||||
st->padding.left() * 2 + st::stickersPremium.width());
|
||||
validateDefaultIcon();
|
||||
const auto size = st::defaultForumTopicIcon.size;
|
||||
const auto esize = st::emojiSize;
|
||||
const auto shift = (esize - size) / 2;
|
||||
p.drawImage(position + QPoint(shift, shift), _defaultIcon);
|
||||
}
|
||||
}
|
||||
|
||||
void ForumTopic::validateDefaultIcon() const {
|
||||
if (_defaultIcon.isNull()) {
|
||||
_defaultIcon = ForumTopicIconFrame(
|
||||
_colorId,
|
||||
_title,
|
||||
st::defaultForumTopicIcon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +401,7 @@ void ForumTopic::applyTitle(const QString &title) {
|
||||
}
|
||||
_title = isGeneral() ? "General! Topic." : title; // #TODO forum lang
|
||||
++_titleVersion;
|
||||
_defaultIcon = QImage();
|
||||
indexTitleParts();
|
||||
updateChatListEntry();
|
||||
}
|
||||
@@ -308,10 +419,21 @@ void ForumTopic::applyIconId(DocumentId iconId) {
|
||||
[=] { updateChatListEntry(); },
|
||||
Data::CustomEmojiManager::SizeTag::Normal)
|
||||
: nullptr;
|
||||
if (iconId) {
|
||||
_defaultIcon = QImage();
|
||||
}
|
||||
}
|
||||
updateChatListEntry();
|
||||
}
|
||||
|
||||
int32 ForumTopic::colorId() const {
|
||||
return _colorId;
|
||||
}
|
||||
|
||||
void ForumTopic::applyColorId(int32 colorId) {
|
||||
_colorId = colorId;
|
||||
}
|
||||
|
||||
void ForumTopic::applyItemAdded(not_null<HistoryItem*> item) {
|
||||
setLastMessage(item);
|
||||
}
|
||||
|
@@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class ChannelData;
|
||||
|
||||
namespace style {
|
||||
struct ForumTopicIcon;
|
||||
} // namespace style
|
||||
|
||||
namespace Dialogs {
|
||||
class MainList;
|
||||
} // namespace Dialogs
|
||||
@@ -25,6 +29,16 @@ namespace Data {
|
||||
class Session;
|
||||
class Forum;
|
||||
|
||||
[[nodiscard]] const base::flat_map<int32, QString> &ForumTopicIcons();
|
||||
[[nodiscard]] const std::vector<int32> &ForumTopicColorIds();
|
||||
[[nodiscard]] const QString &ForumTopicIcon(int32 colorId);
|
||||
[[nodiscard]] QString ForumTopicIconPath(const QString &name);
|
||||
[[nodiscard]] QImage ForumTopicIconBackground(int32 colorId, int size);
|
||||
[[nodiscard]] QImage ForumTopicIconFrame(
|
||||
int32 colorId,
|
||||
const QString &title,
|
||||
const style::ForumTopicIcon &st);
|
||||
|
||||
class ForumTopic final : public Dialogs::Entry {
|
||||
public:
|
||||
static constexpr auto kGeneralId = 1;
|
||||
@@ -72,6 +86,8 @@ public:
|
||||
void applyTitle(const QString &title);
|
||||
[[nodiscard]] DocumentId iconId() const;
|
||||
void applyIconId(DocumentId iconId);
|
||||
[[nodiscard]] int32 colorId() const;
|
||||
void applyColorId(int32 colorId);
|
||||
void applyItemAdded(not_null<HistoryItem*> item);
|
||||
void applyItemRemoved(MsgId id);
|
||||
|
||||
@@ -95,6 +111,7 @@ public:
|
||||
|
||||
private:
|
||||
void indexTitleParts();
|
||||
void validateDefaultIcon() const;
|
||||
void applyTopicTopMessage(MsgId topMessageId);
|
||||
void applyTopicFields(
|
||||
int unreadCount,
|
||||
@@ -119,8 +136,10 @@ private:
|
||||
base::flat_set<QString> _titleWords;
|
||||
base::flat_set<QChar> _titleFirstLetters;
|
||||
int _titleVersion = 0;
|
||||
int32 _colorId = 0;
|
||||
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> _icon;
|
||||
mutable QImage _defaultIcon; // on-demand
|
||||
|
||||
std::optional<MsgId> _inboxReadBefore;
|
||||
std::optional<MsgId> _outboxReadBefore;
|
||||
|
@@ -860,18 +860,15 @@ void Histories::sendCreateTopicRequest(
|
||||
const auto api = &session().api();
|
||||
using Flag = MTPchannels_CreateForumTopic::Flag;
|
||||
api->request(MTPchannels_CreateForumTopic(
|
||||
MTP_flags(topic->iconId() ? Flag::f_icon_emoji_id : Flag(0)),
|
||||
MTP_flags(Flag::f_icon_color
|
||||
| (topic->iconId() ? Flag::f_icon_emoji_id : Flag(0))),
|
||||
history->peer->asChannel()->inputChannel,
|
||||
MTP_string(topic->title()),
|
||||
MTP_int(topic->colorId()),
|
||||
MTP_long(topic->iconId()),
|
||||
MTP_long(randomId),
|
||||
MTPInputPeer() // send_as
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
//AssertIsDebug();
|
||||
//const auto id = result.c_updates().vupdates().v.front().c_updateMessageID().vrandom_id().v;
|
||||
//session().data().registerMessageRandomId(
|
||||
// id,
|
||||
// { history->peer->id, rootId });
|
||||
api->applyUpdates(result, randomId);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
api->sendMessageFail(error, history->peer, randomId);
|
||||
@@ -952,14 +949,14 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realId) {
|
||||
const auto history = _owner->history(rootId.peer);
|
||||
for (auto &entry : scheduled) {
|
||||
_creatingTopicRequests.erase(entry.requestId);
|
||||
//AssertIsDebug();
|
||||
sendPreparedMessage(
|
||||
history,
|
||||
realId,
|
||||
entry.randomId,
|
||||
std::move(entry.message),
|
||||
std::move(entry.done),
|
||||
std::move(entry.fail));
|
||||
AssertIsDebug();
|
||||
//sendPreparedMessage(
|
||||
// history,
|
||||
// realId,
|
||||
// entry.randomId,
|
||||
// std::move(entry.message),
|
||||
// std::move(entry.done),
|
||||
// std::move(entry.fail));
|
||||
}
|
||||
for (const auto &item : history->clientSideMessages()) {
|
||||
const auto replace = [&](MsgId nowId) {
|
||||
|
@@ -260,7 +260,7 @@ void RepliesList::injectRootMessage(not_null<Viewer*> viewer) {
|
||||
return;
|
||||
}
|
||||
const auto root = lookupRoot();
|
||||
if (!root) {
|
||||
if (!root || root->topicRootId() != Data::ForumTopic::kGeneralId) {
|
||||
return;
|
||||
}
|
||||
injectRootDivider(root, slice);
|
||||
|
@@ -307,6 +307,20 @@ void Session::clear() {
|
||||
// Optimization: clear notifications before destroying items.
|
||||
Core::App().notifications().clearFromSession(_session);
|
||||
|
||||
// We must clear all forums before clearing customEmojiManager.
|
||||
// Because in Data::ForumTopic an Ui::Text::CustomEmoji is cached.
|
||||
auto forums = base::flat_set<not_null<ChannelData*>>();
|
||||
for (const auto &[peerId, peer] : _peers) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (channel->isForum()) {
|
||||
forums.emplace(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &channel : forums) {
|
||||
channel->setFlags(channel->flags() & ~ChannelDataFlag::Forum);
|
||||
}
|
||||
|
||||
_sendActionManager->clear();
|
||||
|
||||
_histories->unloadAll();
|
||||
@@ -1244,21 +1258,7 @@ void Session::setupUserIsContactViewer() {
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
Session::~Session() {
|
||||
// We must clear all forums before clearing customEmojiManager.
|
||||
// Because in Data::ForumTopic an Ui::Text::CustomEmoji is cached.
|
||||
auto forums = base::flat_set<not_null<ChannelData*>>();
|
||||
for (const auto &[peerId, peer] : _peers) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (channel->isForum()) {
|
||||
forums.emplace(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &channel : forums) {
|
||||
channel->setFlags(channel->flags() & ~ChannelDataFlag::Forum);
|
||||
}
|
||||
}
|
||||
Session::~Session() = default;
|
||||
|
||||
template <typename Method>
|
||||
void Session::enumerateItemViews(
|
||||
|
@@ -12,9 +12,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "storage/serialize_common.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/color_int_conversion.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
QColor ColorFromSerialized(MTPint serialized) {
|
||||
return ColorFromSerialized(serialized.v);
|
||||
}
|
||||
|
||||
std::optional<QColor> MaybeColorFromSerialized(
|
||||
const tl::conditional<MTPint> &mtp) {
|
||||
return mtp ? ColorFromSerialized(*mtp) : std::optional<QColor>();
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
@@ -38,6 +52,8 @@ constexpr auto kIncorrectDefaultBackground = FromLegacyBackgroundId(105);
|
||||
constexpr auto kVersionTag = qint32(0x7FFFFFFF);
|
||||
constexpr auto kVersion = 1;
|
||||
|
||||
using Ui::MaybeColorFromSerialized;
|
||||
|
||||
[[nodiscard]] quint32 SerializeColor(const QColor &color) {
|
||||
return (quint32(std::clamp(color.red(), 0, 255)) << 16)
|
||||
| (quint32(std::clamp(color.green(), 0, 255)) << 8)
|
||||
@@ -57,7 +73,8 @@ constexpr auto kVersion = 1;
|
||||
}
|
||||
result.reserve(4);
|
||||
result.push_back(*c1);
|
||||
const auto c2 = MaybeColorFromSerialized(data.vsecond_background_color());
|
||||
const auto c2 = MaybeColorFromSerialized(
|
||||
data.vsecond_background_color());
|
||||
if (!c2) {
|
||||
return result;
|
||||
}
|
||||
@@ -67,7 +84,8 @@ constexpr auto kVersion = 1;
|
||||
return result;
|
||||
}
|
||||
result.push_back(*c3);
|
||||
const auto c4 = MaybeColorFromSerialized(data.vfourth_background_color());
|
||||
const auto c4 = MaybeColorFromSerialized(
|
||||
data.vfourth_background_color());
|
||||
if (!c4) {
|
||||
return result;
|
||||
}
|
||||
@@ -712,29 +730,6 @@ QImage GenerateDitheredGradient(const Data::WallPaper &paper) {
|
||||
paper.gradientRotation());
|
||||
}
|
||||
|
||||
QColor ColorFromSerialized(quint32 serialized) {
|
||||
return QColor(
|
||||
int((serialized >> 16) & 0xFFU),
|
||||
int((serialized >> 8) & 0xFFU),
|
||||
int(serialized & 0xFFU));
|
||||
}
|
||||
|
||||
QColor ColorFromSerialized(MTPint serialized) {
|
||||
return ColorFromSerialized(serialized.v);
|
||||
}
|
||||
|
||||
std::optional<QColor> MaybeColorFromSerialized(
|
||||
quint32 serialized) {
|
||||
return (serialized == quint32(-1))
|
||||
? std::nullopt
|
||||
: std::make_optional(ColorFromSerialized(serialized));
|
||||
}
|
||||
|
||||
std::optional<QColor> MaybeColorFromSerialized(
|
||||
const tl::conditional<MTPint> &mtp) {
|
||||
return mtp ? std::make_optional(ColorFromSerialized(*mtp)) : std::nullopt;
|
||||
}
|
||||
|
||||
namespace details {
|
||||
|
||||
WallPaper UninitializedWallPaper() {
|
||||
|
@@ -15,6 +15,14 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
|
||||
[[nodiscard]] QColor ColorFromSerialized(MTPint serialized);
|
||||
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
|
||||
const tl::conditional<MTPint> &mtp);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct FileOrigin;
|
||||
@@ -126,13 +134,6 @@ private:
|
||||
|
||||
[[nodiscard]] QImage GenerateDitheredGradient(const WallPaper &paper);
|
||||
|
||||
[[nodiscard]] QColor ColorFromSerialized(quint32 serialized);
|
||||
[[nodiscard]] QColor ColorFromSerialized(MTPint serialized);
|
||||
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
|
||||
quint32 serialized);
|
||||
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
|
||||
const tl::conditional<MTPint> &mtp);
|
||||
|
||||
namespace details {
|
||||
|
||||
[[nodiscard]] WallPaper UninitializedWallPaper();
|
||||
|
Reference in New Issue
Block a user