mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Support custom emoji in the message input field.
This commit is contained in:
parent
cb32c3957b
commit
2499955496
17 changed files with 192 additions and 173 deletions
|
@ -628,6 +628,7 @@ messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity;
|
||||||
messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity;
|
messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity;
|
||||||
messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity;
|
messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity;
|
||||||
messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity;
|
messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity;
|
||||||
|
messageEntityCustomEmoji#d4a00ed5 offset:int length:int stickerset:InputStickerSet document_id:long = MessageEntity;
|
||||||
|
|
||||||
inputChannelEmpty#ee8c1e86 = InputChannel;
|
inputChannelEmpty#ee8c1e86 = InputChannel;
|
||||||
inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel;
|
inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel;
|
||||||
|
@ -742,7 +743,7 @@ draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage;
|
||||||
draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage;
|
draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage;
|
||||||
|
|
||||||
messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers;
|
messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers;
|
||||||
messages.featuredStickers#84c02310 hash:long count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
|
messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
|
||||||
|
|
||||||
messages.recentStickersNotModified#b17f890 = messages.RecentStickers;
|
messages.recentStickersNotModified#b17f890 = messages.RecentStickers;
|
||||||
messages.recentStickers#88d37c56 hash:long packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers;
|
messages.recentStickers#88d37c56 hash:long packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers;
|
||||||
|
@ -1792,9 +1793,8 @@ payments.getSavedInfo#227d824b = payments.SavedInfo;
|
||||||
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
|
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
|
||||||
payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
|
payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
|
||||||
payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice;
|
payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice;
|
||||||
payments.assignAppStoreTransaction#fec13c6 flags:# restore:flags.0?true transaction_id:string receipt:bytes = Updates;
|
payments.assignAppStoreTransaction#d5ccfd0 flags:# restore:flags.0?true receipt:bytes = Updates;
|
||||||
payments.assignPlayMarketTransaction#4faa4aed purchase_token:string = Updates;
|
payments.assignPlayMarketTransaction#4faa4aed purchase_token:string = Updates;
|
||||||
payments.restorePlayMarketReceipt#d164e36a receipt:bytes = Updates;
|
|
||||||
payments.canPurchasePremium#aa6a90c8 = Bool;
|
payments.canPurchasePremium#aa6a90c8 = Bool;
|
||||||
payments.requestRecurringPayment#146e958d user_id:InputUser recurring_init_charge:string invoice_media:InputMedia = Updates;
|
payments.requestRecurringPayment#146e958d user_id:InputUser recurring_init_charge:string invoice_media:InputMedia = Updates;
|
||||||
|
|
||||||
|
@ -1853,4 +1853,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
|
||||||
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||||
|
|
||||||
// LAYER 143
|
// LAYER 144
|
||||||
|
|
|
@ -8,15 +8,61 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_text_entities.h"
|
#include "api/api_text_entities.h"
|
||||||
|
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
|
#include "data/stickers/data_stickers_set.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
|
||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using namespace TextUtilities;
|
using namespace TextUtilities;
|
||||||
|
|
||||||
|
[[nodiscard]] QString CustomEmojiEntityData(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const MTPDmessageEntityCustomEmoji &data) {
|
||||||
|
return Data::SerializeCustomEmojiId({
|
||||||
|
.selfId = session->userId().bare,
|
||||||
|
.id = data.vdocument_id().v,
|
||||||
|
.set = Data::FromInputSet(data.vstickerset()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<MTPMessageEntity> CustomEmojiEntity(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
MTPint offset,
|
||||||
|
MTPint length,
|
||||||
|
const QString &data) {
|
||||||
|
const auto parsed = Data::ParseCustomEmojiData(data);
|
||||||
|
if (!parsed.id || parsed.selfId != session->userId().bare) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return MTP_messageEntityCustomEmoji(
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
Data::InputStickerSet(parsed.set),
|
||||||
|
MTP_long(parsed.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<MTPMessageEntity> MentionNameEntity(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
MTPint offset,
|
||||||
|
MTPint length,
|
||||||
|
const QString &data) {
|
||||||
|
const auto parsed = MentionNameDataToFields(data);
|
||||||
|
if (!parsed.userId || parsed.selfId != session->userId().bare) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return MTP_inputMessageEntityMentionName(
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
(parsed.userId == parsed.selfId
|
||||||
|
? MTP_inputUserSelf()
|
||||||
|
: MTP_inputUser(
|
||||||
|
MTP_long(parsed.userId),
|
||||||
|
MTP_long(parsed.accessHash))));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
EntitiesInText EntitiesFromMTP(
|
EntitiesInText EntitiesFromMTP(
|
||||||
|
@ -34,34 +80,35 @@ EntitiesInText EntitiesFromMTP(
|
||||||
case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back({ EntityType::Cashtag, d.voffset().v, d.vlength().v }); } break;
|
case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back({ EntityType::Cashtag, d.voffset().v, d.vlength().v }); } break;
|
||||||
case mtpc_messageEntityPhone: break; // Skipping phones.
|
case mtpc_messageEntityPhone: break; // Skipping phones.
|
||||||
case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back({ EntityType::Mention, d.voffset().v, d.vlength().v }); } break;
|
case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back({ EntityType::Mention, d.voffset().v, d.vlength().v }); } break;
|
||||||
case mtpc_messageEntityMentionName: {
|
case mtpc_messageEntityMentionName: if (session) {
|
||||||
const auto &d = entity.c_messageEntityMentionName();
|
const auto &d = entity.c_messageEntityMentionName();
|
||||||
const auto userId = UserId(d.vuser_id());
|
const auto userId = UserId(d.vuser_id());
|
||||||
const auto data = [&] {
|
const auto user = session->data().userLoaded(userId);
|
||||||
if (session) {
|
const auto data = MentionNameDataFromFields({
|
||||||
if (const auto user = session->data().userLoaded(userId)) {
|
.selfId = session->userId().bare,
|
||||||
return MentionNameDataFromFields({
|
.userId = userId.bare,
|
||||||
userId.bare,
|
.accessHash = user ? user->accessHash() : 0,
|
||||||
user->accessHash()
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MentionNameDataFromFields(userId.bare);
|
|
||||||
}();
|
|
||||||
result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
|
result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
|
||||||
} break;
|
} break;
|
||||||
case mtpc_inputMessageEntityMentionName: {
|
case mtpc_inputMessageEntityMentionName: if (session) {
|
||||||
const auto &d = entity.c_inputMessageEntityMentionName();
|
const auto &d = entity.c_inputMessageEntityMentionName();
|
||||||
const auto data = [&] {
|
const auto data = d.vuser_id().match([&](
|
||||||
if (session && d.vuser_id().type() == mtpc_inputUserSelf) {
|
const MTPDinputUserSelf &) {
|
||||||
return MentionNameDataFromFields(session->userId().bare);
|
return MentionNameDataFromFields({
|
||||||
} else if (d.vuser_id().type() == mtpc_inputUser) {
|
.selfId = session->userId().bare,
|
||||||
auto &user = d.vuser_id().c_inputUser();
|
.userId = session->userId().bare,
|
||||||
const auto userId = UserId(user.vuser_id());
|
.accessHash = session->user()->accessHash(),
|
||||||
return MentionNameDataFromFields({ userId.bare, user.vaccess_hash().v });
|
});
|
||||||
}
|
}, [&](const MTPDinputUser &data) {
|
||||||
|
return MentionNameDataFromFields({
|
||||||
|
.selfId = session->userId().bare,
|
||||||
|
.userId = UserId(data.vuser_id()).bare,
|
||||||
|
.accessHash = data.vaccess_hash().v,
|
||||||
|
});
|
||||||
|
}, [&](const auto &) {
|
||||||
return QString();
|
return QString();
|
||||||
}();
|
});
|
||||||
if (!data.isEmpty()) {
|
if (!data.isEmpty()) {
|
||||||
result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
|
result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
|
||||||
}
|
}
|
||||||
|
@ -73,9 +120,12 @@ EntitiesInText EntitiesFromMTP(
|
||||||
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
|
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
|
||||||
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
|
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
|
||||||
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break;
|
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break;
|
||||||
case mtpc_messageEntityBankCard: break; // Skipping cards.
|
case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities
|
||||||
case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
|
case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
|
||||||
// #TODO entities
|
case mtpc_messageEntityCustomEmoji: if (session) {
|
||||||
|
const auto &d = entity.c_messageEntityCustomEmoji();
|
||||||
|
result.push_back({ EntityType::CustomEmoji, d.voffset().v, d.vlength().v, CustomEmojiEntityData(session, d) });
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +150,8 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||||
&& entity.type() != EntityType::Pre
|
&& entity.type() != EntityType::Pre
|
||||||
&& entity.type() != EntityType::Spoiler
|
&& entity.type() != EntityType::Spoiler
|
||||||
&& entity.type() != EntityType::MentionName
|
&& entity.type() != EntityType::MentionName
|
||||||
&& entity.type() != EntityType::CustomUrl) {
|
&& entity.type() != EntityType::CustomUrl
|
||||||
|
&& entity.type() != EntityType::CustomEmoji) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,17 +165,8 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||||
case EntityType::Cashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break;
|
case EntityType::Cashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break;
|
||||||
case EntityType::Mention: v.push_back(MTP_messageEntityMention(offset, length)); break;
|
case EntityType::Mention: v.push_back(MTP_messageEntityMention(offset, length)); break;
|
||||||
case EntityType::MentionName: {
|
case EntityType::MentionName: {
|
||||||
auto inputUser = [&](const QString &data) -> MTPInputUser {
|
if (const auto valid = MentionNameEntity(session, offset, length, entity.data())) {
|
||||||
auto fields = MentionNameDataToFields(data);
|
v.push_back(*valid);
|
||||||
if (session && fields.userId == session->userId().bare) {
|
|
||||||
return MTP_inputUserSelf();
|
|
||||||
} else if (fields.userId) {
|
|
||||||
return MTP_inputUser(MTP_long(fields.userId), MTP_long(fields.accessHash));
|
|
||||||
}
|
|
||||||
return MTP_inputUserEmpty();
|
|
||||||
}(entity.data());
|
|
||||||
if (inputUser.type() != mtpc_inputUserEmpty) {
|
|
||||||
v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
|
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case EntityType::BotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
|
case EntityType::BotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
|
||||||
|
@ -135,6 +177,11 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||||
case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
|
case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
|
||||||
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
|
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
|
||||||
case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break;
|
case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break;
|
||||||
|
case EntityType::CustomEmoji: {
|
||||||
|
if (const auto valid = CustomEmojiEntity(session, offset, length, entity.data())) {
|
||||||
|
v.push_back(*valid);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MTP_vector<MTPMessageEntity>(std::move(v));
|
return MTP_vector<MTPMessageEntity>(std::move(v));
|
||||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
@ -48,13 +49,14 @@ using EditLinkSelection = Ui::InputField::EditLinkSelection;
|
||||||
|
|
||||||
constexpr auto kParseLinksTimeout = crl::time(1000);
|
constexpr auto kParseLinksTimeout = crl::time(1000);
|
||||||
|
|
||||||
// For mention tags save and validate userId, ignore tags for different userId.
|
// For mention / custom emoji tags save and validate selfId,
|
||||||
class FieldTagMimeProcessor : public Ui::InputField::TagMimeProcessor {
|
// ignore tags for different users.
|
||||||
|
class FieldTagMimeProcessor final {
|
||||||
public:
|
public:
|
||||||
explicit FieldTagMimeProcessor(
|
explicit FieldTagMimeProcessor(
|
||||||
not_null<Window::SessionController*> controller);
|
not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
QString tagFromMimeTag(const QString &mimeTag) override;
|
QString operator()(QStringView mimeTag);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const not_null<Window::SessionController*> _controller;
|
const not_null<Window::SessionController*> _controller;
|
||||||
|
@ -66,17 +68,23 @@ FieldTagMimeProcessor::FieldTagMimeProcessor(
|
||||||
: _controller(controller) {
|
: _controller(controller) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString FieldTagMimeProcessor::tagFromMimeTag(const QString &mimeTag) {
|
QString FieldTagMimeProcessor::operator()(QStringView mimeTag) {
|
||||||
if (TextUtilities::IsMentionLink(mimeTag)) {
|
const auto id = _controller->session().userId().bare;
|
||||||
const auto userId = _controller->session().userId();
|
auto all = TextUtilities::SplitTags(mimeTag);
|
||||||
auto match = QRegularExpression(":(\\d+)$").match(mimeTag);
|
for (auto i = all.begin(); i != all.end();) {
|
||||||
if (!match.hasMatch()
|
const auto tag = *i;
|
||||||
|| match.capturedView(1).toULongLong() != userId.bare) {
|
if (TextUtilities::IsMentionLink(tag)
|
||||||
return QString();
|
&& TextUtilities::MentionNameDataToFields(tag).selfId != id) {
|
||||||
|
i = all.erase(i);
|
||||||
|
} else if (Ui::InputField::IsCustomEmojiLink(tag)
|
||||||
|
&& Data::ParseCustomEmojiData(
|
||||||
|
Ui::InputField::CustomEmojiEntityData(tag)).selfId != id) {
|
||||||
|
i = all.erase(i);
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
return mimeTag.mid(0, mimeTag.size() - match.capturedLength());
|
|
||||||
}
|
}
|
||||||
return mimeTag;
|
return TextUtilities::JoinTag(all);
|
||||||
}
|
}
|
||||||
|
|
||||||
//bool ValidateUrl(const QString &value) {
|
//bool ValidateUrl(const QString &value) {
|
||||||
|
@ -225,7 +233,9 @@ QString PrepareMentionTag(not_null<UserData*> user) {
|
||||||
return TextUtilities::kMentionTagStart
|
return TextUtilities::kMentionTagStart
|
||||||
+ QString::number(user->id.value)
|
+ QString::number(user->id.value)
|
||||||
+ '.'
|
+ '.'
|
||||||
+ QString::number(user->accessHash());
|
+ QString::number(user->accessHash())
|
||||||
|
+ ':'
|
||||||
|
+ QString::number(user->session().userId().bare);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithTags PrepareEditText(not_null<HistoryItem*> item) {
|
TextWithTags PrepareEditText(not_null<HistoryItem*> item) {
|
||||||
|
@ -279,11 +289,19 @@ Fn<bool(
|
||||||
void InitMessageField(
|
void InitMessageField(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<Ui::InputField*> field) {
|
not_null<Ui::InputField*> field) {
|
||||||
field->setMinHeight(st::historySendSize.height() - 2 * st::historySendPadding);
|
field->setMinHeight(
|
||||||
|
st::historySendSize.height() - 2 * st::historySendPadding);
|
||||||
field->setMaxHeight(st::historyComposeFieldMaxHeight);
|
field->setMaxHeight(st::historyComposeFieldMaxHeight);
|
||||||
|
|
||||||
field->setTagMimeProcessor(
|
field->setTagMimeProcessor(FieldTagMimeProcessor(controller));
|
||||||
std::make_unique<FieldTagMimeProcessor>(controller));
|
field->setCustomEmojiFactory([=](QStringView data, Fn<void()> update) {
|
||||||
|
return controller->session().data().customEmojiManager().create(
|
||||||
|
data,
|
||||||
|
std::move(update));
|
||||||
|
}, [=] {
|
||||||
|
return controller->isGifPausedAtLeastFor(
|
||||||
|
Window::GifPauseReason::Any);
|
||||||
|
});
|
||||||
|
|
||||||
field->document()->setDocumentMargin(4.);
|
field->document()->setDocumentMargin(4.);
|
||||||
field->setAdditionalMargin(style::ConvertScale(4) - 4);
|
field->setAdditionalMargin(style::ConvertScale(4) - 4);
|
||||||
|
|
|
@ -258,15 +258,6 @@ rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
|
||||||
return Core::App().passcodeLockChanges() | rpl::to_empty;
|
return Core::App().passcodeLockChanges() | rpl::to_empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString UiIntegration::convertTagToMimeTag(const QString &tagId) {
|
|
||||||
if (TextUtilities::IsMentionLink(tagId)) {
|
|
||||||
if (const auto session = Core::App().activeAccount().maybeSession()) {
|
|
||||||
return tagId + ':' + QString::number(session->userId().bare);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tagId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Ui::Emoji::One *UiIntegration::defaultEmojiVariant(
|
const Ui::Emoji::One *UiIntegration::defaultEmojiVariant(
|
||||||
const Ui::Emoji::One *emoji) {
|
const Ui::Emoji::One *emoji) {
|
||||||
if (!emoji || !emoji->hasVariants()) {
|
if (!emoji || !emoji->hasVariants()) {
|
||||||
|
|
|
@ -54,7 +54,6 @@ public:
|
||||||
const QString &url,
|
const QString &url,
|
||||||
const QVariant &context) override;
|
const QVariant &context) override;
|
||||||
rpl::producer<> forcePopupMenuHideRequests() override;
|
rpl::producer<> forcePopupMenuHideRequests() override;
|
||||||
QString convertTagToMimeTag(const QString &tagId) override;
|
|
||||||
const Ui::Emoji::One *defaultEmojiVariant(
|
const Ui::Emoji::One *defaultEmojiVariant(
|
||||||
const Ui::Emoji::One *emoji) override;
|
const Ui::Emoji::One *emoji) override;
|
||||||
std::unique_ptr<Ui::Text::CustomEmoji> createCustomEmoji(
|
std::unique_ptr<Ui::Text::CustomEmoji> createCustomEmoji(
|
||||||
|
|
|
@ -22,15 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
#include "data/stickers/data_stickers_set.h"
|
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
struct CustomEmojiId {
|
|
||||||
StickerSetIdentifier set;
|
|
||||||
uint64 id = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using SizeTag = CustomEmojiManager::SizeTag;
|
using SizeTag = CustomEmojiManager::SizeTag;
|
||||||
|
@ -52,42 +45,6 @@ using SizeTag = CustomEmojiManager::SizeTag;
|
||||||
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
|
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id) {
|
|
||||||
return QString::number(id.id)
|
|
||||||
+ '@'
|
|
||||||
+ QString::number(id.set.id)
|
|
||||||
+ ':'
|
|
||||||
+ QString::number(id.set.accessHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] QString SerializeCustomEmojiId(
|
|
||||||
not_null<DocumentData*> document) {
|
|
||||||
const auto sticker = document->sticker();
|
|
||||||
return SerializeCustomEmojiId({
|
|
||||||
sticker ? sticker->set : StickerSetIdentifier(),
|
|
||||||
document->id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] CustomEmojiId ParseCustomEmojiData(QStringView data) {
|
|
||||||
const auto parts = data.split('@');
|
|
||||||
if (parts.size() != 2) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const auto id = parts[0].toULongLong();
|
|
||||||
if (!id) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const auto second = parts[1].split(':');
|
|
||||||
return {
|
|
||||||
.set = {
|
|
||||||
.id = second[0].toULongLong(),
|
|
||||||
.accessHash = second[1].toULongLong(),
|
|
||||||
},
|
|
||||||
.id = id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class CustomEmojiLoader final
|
class CustomEmojiLoader final
|
||||||
|
@ -392,7 +349,7 @@ CustomEmojiManager::CustomEmojiManager(not_null<Session*> owner)
|
||||||
CustomEmojiManager::~CustomEmojiManager() = default;
|
CustomEmojiManager::~CustomEmojiManager() = default;
|
||||||
|
|
||||||
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||||
const QString &data,
|
QStringView data,
|
||||||
Fn<void()> update) {
|
Fn<void()> update) {
|
||||||
const auto parsed = ParseCustomEmojiData(data);
|
const auto parsed = ParseCustomEmojiData(data);
|
||||||
if (!parsed.id || !parsed.set.id) {
|
if (!parsed.id || !parsed.set.id) {
|
||||||
|
@ -547,56 +504,46 @@ Session &CustomEmojiManager::owner() const {
|
||||||
return *_owner;
|
return *_owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FillTestCustomEmoji(
|
QString SerializeCustomEmojiId(const CustomEmojiId &id) {
|
||||||
not_null<Main::Session*> session,
|
return QString::number(id.set.id)
|
||||||
TextWithEntities &text) {
|
+ '.'
|
||||||
auto &sets = session->data().stickers().sets();
|
+ QString::number(id.set.accessHash)
|
||||||
auto recentIt = sets.find(Data::Stickers::CloudRecentSetId);
|
+ ':'
|
||||||
const auto pack = &session->emojiStickersPack();
|
+ QString::number(id.selfId)
|
||||||
const auto begin = text.text.constData(), end = begin + text.text.size();
|
+ '/'
|
||||||
for (auto ch = begin; ch != end;) {
|
+ QString::number(id.id);
|
||||||
auto length = 0;
|
}
|
||||||
if (const auto emoji = Ui::Emoji::Find(ch, end, &length)) {
|
|
||||||
auto replace = (DocumentData*)nullptr;
|
QString SerializeCustomEmojiId(not_null<DocumentData*> document) {
|
||||||
if (recentIt != sets.end()) {
|
const auto sticker = document->sticker();
|
||||||
for (const auto document : recentIt->second->stickers) {
|
return SerializeCustomEmojiId({
|
||||||
if (const auto sticker = document->sticker()) {
|
.selfId = document->session().userId().bare,
|
||||||
if (Ui::Emoji::Find(sticker->alt) == emoji) {
|
.id = document->id,
|
||||||
replace = document;
|
.set = sticker ? sticker->set : StickerSetIdentifier(),
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
CustomEmojiId ParseCustomEmojiData(QStringView data) {
|
||||||
if (const auto found = pack->stickerForEmoji(emoji)) {
|
const auto components = data.split('.');
|
||||||
Assert(found.document->sticker() != nullptr);
|
if (components.size() != 2) {
|
||||||
if (!replace && found.document->sticker()->set.id) {
|
return {};
|
||||||
replace = found.document;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (replace) {
|
|
||||||
text.entities.push_back({
|
|
||||||
EntityType::CustomEmoji,
|
|
||||||
(ch - begin),
|
|
||||||
length,
|
|
||||||
SerializeCustomEmojiId({
|
|
||||||
replace->sticker()->set,
|
|
||||||
replace->id,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ch += length;
|
|
||||||
} else if (ch->isHighSurrogate()
|
|
||||||
&& (ch + 1 != end)
|
|
||||||
&& (ch + 1)->isLowSurrogate()) {
|
|
||||||
ch += 2;
|
|
||||||
} else {
|
|
||||||
++ch;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ranges::stable_sort(
|
const auto parts = components[1].split(':');
|
||||||
text.entities,
|
if (parts.size() != 2) {
|
||||||
ranges::less(),
|
return {};
|
||||||
&EntityInText::offset);
|
}
|
||||||
|
const auto endings = parts[1].split('/');
|
||||||
|
if (endings.size() != 2) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
.selfId = endings[0].toULongLong(),
|
||||||
|
.id = endings[1].toULongLong(),
|
||||||
|
.set = {
|
||||||
|
.id = components[0].toULongLong(),
|
||||||
|
.accessHash = parts[0].toULongLong(),
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "data/stickers/data_stickers_set.h"
|
||||||
#include "ui/text/custom_emoji_instance.h"
|
#include "ui/text/custom_emoji_instance.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
#include "base/weak_ptr.h"
|
#include "base/weak_ptr.h"
|
||||||
|
@ -20,9 +21,14 @@ class Session;
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
class Session;
|
class Session;
|
||||||
struct CustomEmojiId;
|
|
||||||
class CustomEmojiLoader;
|
class CustomEmojiLoader;
|
||||||
|
|
||||||
|
struct CustomEmojiId {
|
||||||
|
uint64 selfId = 0;
|
||||||
|
uint64 id = 0;
|
||||||
|
StickerSetIdentifier set;
|
||||||
|
};
|
||||||
|
|
||||||
class CustomEmojiManager final : public base::has_weak_ptr {
|
class CustomEmojiManager final : public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
enum class SizeTag {
|
enum class SizeTag {
|
||||||
|
@ -34,7 +40,7 @@ public:
|
||||||
~CustomEmojiManager();
|
~CustomEmojiManager();
|
||||||
|
|
||||||
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
|
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
|
||||||
const QString &data,
|
QStringView data,
|
||||||
Fn<void()> update);
|
Fn<void()> update);
|
||||||
|
|
||||||
[[nodiscard]] Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
|
@ -76,8 +82,9 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void FillTestCustomEmoji(
|
[[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id);
|
||||||
not_null<Main::Session*> session,
|
[[nodiscard]] QString SerializeCustomEmojiId(
|
||||||
TextWithEntities &text);
|
not_null<DocumentData*> document);
|
||||||
|
[[nodiscard]] CustomEmojiId ParseCustomEmojiData(QStringView data);
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -155,7 +155,8 @@ std::vector<TextPart> ParseText(
|
||||||
[](const MTPDmessageEntityBlockquote&) {
|
[](const MTPDmessageEntityBlockquote&) {
|
||||||
return Type::Blockquote; },
|
return Type::Blockquote; },
|
||||||
[](const MTPDmessageEntityBankCard&) { return Type::BankCard; },
|
[](const MTPDmessageEntityBankCard&) { return Type::BankCard; },
|
||||||
[](const MTPDmessageEntitySpoiler&) { return Type::Spoiler; });
|
[](const MTPDmessageEntitySpoiler&) { return Type::Spoiler; },
|
||||||
|
[](const MTPDmessageEntityCustomEmoji&) { return Type::CustomEmoji; });
|
||||||
part.text = mid(start, length);
|
part.text = mid(start, length);
|
||||||
part.additional = entity.match(
|
part.additional = entity.match(
|
||||||
[](const MTPDmessageEntityPre &data) {
|
[](const MTPDmessageEntityPre &data) {
|
||||||
|
@ -164,6 +165,8 @@ std::vector<TextPart> ParseText(
|
||||||
return ParseString(data.vurl());
|
return ParseString(data.vurl());
|
||||||
}, [](const MTPDmessageEntityMentionName &data) {
|
}, [](const MTPDmessageEntityMentionName &data) {
|
||||||
return NumberToString(data.vuser_id().v);
|
return NumberToString(data.vuser_id().v);
|
||||||
|
}, [](const MTPDmessageEntityCustomEmoji &data) {
|
||||||
|
return NumberToString(data.vdocument_id().v);
|
||||||
}, [](const auto &) { return Utf8String(); });
|
}, [](const auto &) { return Utf8String(); });
|
||||||
|
|
||||||
result.push_back(std::move(part));
|
result.push_back(std::move(part));
|
||||||
|
|
|
@ -549,6 +549,7 @@ struct TextPart {
|
||||||
Blockquote,
|
Blockquote,
|
||||||
BankCard,
|
BankCard,
|
||||||
Spoiler,
|
Spoiler,
|
||||||
|
CustomEmoji,
|
||||||
};
|
};
|
||||||
Type type = Type::Text;
|
Type type = Type::Text;
|
||||||
Utf8String text;
|
Utf8String text;
|
||||||
|
|
|
@ -274,6 +274,9 @@ QByteArray FormatText(
|
||||||
"onclick=\"ShowSpoiler(this)\">"
|
"onclick=\"ShowSpoiler(this)\">"
|
||||||
"<span aria-hidden=\"true\">"
|
"<span aria-hidden=\"true\">"
|
||||||
+ text + "</span></span>";
|
+ text + "</span></span>";
|
||||||
|
case Type::CustomEmoji: return SerializeString("{custom_emoji}")
|
||||||
|
+ text // TODO custom-emoji
|
||||||
|
+ SerializeString("{/custom_emoji}");
|
||||||
}
|
}
|
||||||
Unexpected("Type in text entities serialization.");
|
Unexpected("Type in text entities serialization.");
|
||||||
}) | ranges::to_vector);
|
}) | ranges::to_vector);
|
||||||
|
|
|
@ -181,17 +181,21 @@ QByteArray SerializeText(
|
||||||
case Type::Blockquote: return "blockquote";
|
case Type::Blockquote: return "blockquote";
|
||||||
case Type::BankCard: return "bank_card";
|
case Type::BankCard: return "bank_card";
|
||||||
case Type::Spoiler: return "spoiler";
|
case Type::Spoiler: return "spoiler";
|
||||||
|
case Type::CustomEmoji: return "custom_emoji";
|
||||||
}
|
}
|
||||||
Unexpected("Type in SerializeText.");
|
Unexpected("Type in SerializeText.");
|
||||||
}();
|
}();
|
||||||
const auto additionalName = (part.type == Type::MentionName)
|
const auto additionalName = (part.type == Type::MentionName)
|
||||||
? "user_id"
|
? "user_id"
|
||||||
|
: (part.type == Type::CustomEmoji)
|
||||||
|
? "document_id"
|
||||||
: (part.type == Type::Pre)
|
: (part.type == Type::Pre)
|
||||||
? "language"
|
? "language"
|
||||||
: (part.type == Type::TextUrl)
|
: (part.type == Type::TextUrl)
|
||||||
? "href"
|
? "href"
|
||||||
: "none";
|
: "none";
|
||||||
const auto additionalValue = (part.type == Type::MentionName)
|
const auto additionalValue = (part.type == Type::MentionName
|
||||||
|
|| part.type == Type::CustomEmoji)
|
||||||
? part.additional
|
? part.additional
|
||||||
: (part.type == Type::Pre || part.type == Type::TextUrl)
|
: (part.type == Type::Pre || part.type == Type::TextUrl)
|
||||||
? SerializeString(part.additional)
|
? SerializeString(part.additional)
|
||||||
|
|
|
@ -337,7 +337,6 @@ HistoryMessage::HistoryMessage(
|
||||||
&history->session(),
|
&history->session(),
|
||||||
data.ventities().value_or_empty())
|
data.ventities().value_or_empty())
|
||||||
};
|
};
|
||||||
Data::FillTestCustomEmoji(&history->session(), textWithEntities);
|
|
||||||
setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities));
|
setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities));
|
||||||
if (const auto groupedId = data.vgrouped_id()) {
|
if (const auto groupedId = data.vgrouped_id()) {
|
||||||
setGroupId(
|
setGroupId(
|
||||||
|
|
|
@ -25,7 +25,7 @@ struct LoadedPart {
|
||||||
|
|
||||||
class Loader {
|
class Loader {
|
||||||
public:
|
public:
|
||||||
static constexpr auto kPartSize = 128 * 1024;
|
static constexpr auto kPartSize = int64(128 * 1024);
|
||||||
|
|
||||||
[[nodiscard]] virtual Storage::Cache::Key baseCacheKey() const = 0;
|
[[nodiscard]] virtual Storage::Cache::Key baseCacheKey() const = 0;
|
||||||
[[nodiscard]] virtual int64 size() const = 0;
|
[[nodiscard]] virtual int64 size() const = 0;
|
||||||
|
|
|
@ -127,7 +127,7 @@ bytes::const_span ParseCachedMap(
|
||||||
if (size > maxSize) {
|
if (size > maxSize) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
for (auto offset = 0; offset < size; offset += kPartSize) {
|
for (auto offset = int64(); offset < size; offset += kPartSize) {
|
||||||
const auto part = data.subspan(
|
const auto part = data.subspan(
|
||||||
offset,
|
offset,
|
||||||
std::min(kPartSize, size - offset));
|
std::min(kPartSize, size - offset));
|
||||||
|
|
|
@ -107,7 +107,7 @@ void StreamedFileDownloader::requestPart() {
|
||||||
++_partsRequested;
|
++_partsRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray StreamedFileDownloader::readLoadedPart(int offset) {
|
QByteArray StreamedFileDownloader::readLoadedPart(int64 offset) {
|
||||||
Expects(offset >= 0 && offset < _fullSize);
|
Expects(offset >= 0 && offset < _fullSize);
|
||||||
Expects(!(offset % kPartSize));
|
Expects(!(offset % kPartSize));
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ public:
|
||||||
uint64 objId() const override;
|
uint64 objId() const override;
|
||||||
Data::FileOrigin fileOrigin() const override;
|
Data::FileOrigin fileOrigin() const override;
|
||||||
|
|
||||||
QByteArray readLoadedPart(int offset);
|
QByteArray readLoadedPart(int64 offset);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startLoading() override;
|
void startLoading() override;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 32cf0968a14f7ef7756834f72500fc58fef50c6d
|
Subproject commit 6bd7518109850d650a174b74e5582367555390da
|
Loading…
Add table
Reference in a new issue