mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 21:27:07 +02:00
Initial chat-translation feature implementation.
This commit is contained in:
parent
02a0ca7112
commit
719466fcac
40 changed files with 1356 additions and 141 deletions
|
@ -756,6 +756,10 @@ PRIVATE
|
|||
history/view/history_view_sticker_toast.h
|
||||
history/view/history_view_transcribe_button.cpp
|
||||
history/view/history_view_transcribe_button.h
|
||||
history/view/history_view_translate_bar.cpp
|
||||
history/view/history_view_translate_bar.h
|
||||
history/view/history_view_translate_tracker.cpp
|
||||
history/view/history_view_translate_tracker.h
|
||||
history/view/history_view_top_bar_widget.cpp
|
||||
history/view/history_view_top_bar_widget.h
|
||||
history/view/history_view_view_button.cpp
|
||||
|
@ -782,6 +786,8 @@ PRIVATE
|
|||
history/history_inner_widget.h
|
||||
history/history_location_manager.cpp
|
||||
history/history_location_manager.h
|
||||
history/history_translation.cpp
|
||||
history/history_translation.h
|
||||
history/history_unread_things.cpp
|
||||
history/history_unread_things.h
|
||||
history/history_view_highlight_manager.cpp
|
||||
|
|
|
@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "lang/lang_instance.h"
|
||||
#include "lang/lang_cloud_manager.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_info.h"
|
||||
|
@ -43,6 +44,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <QtGui/QClipboard>
|
||||
|
||||
namespace {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] std::vector<QLocale> SkipLocalesFromSettings() {
|
||||
const auto list = Core::App().settings().skipTranslationLanguages();
|
||||
return list
|
||||
| ranges::views::transform(&LanguageId::locale)
|
||||
| ranges::to_vector;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
using Language = Lang::Language;
|
||||
using Languages = Lang::CloudManager::Languages;
|
||||
|
@ -1138,19 +1149,19 @@ void LanguageBox::prepare() {
|
|||
}),
|
||||
st::settingsButtonNoIcon);
|
||||
|
||||
label->fire(Ui::Translate::LocalesFromSettings());
|
||||
label->fire(SkipLocalesFromSettings());
|
||||
translateSkip->setClickedCallback([=] {
|
||||
Ui::BoxShow(this).showBox(
|
||||
Box(Ui::ChooseLanguageBox, [=](std::vector<QLocale> locales) {
|
||||
Box(Ui::ChooseLanguageBox, [=](Locales locales) {
|
||||
label->fire_copy(locales);
|
||||
const auto result = ranges::views::all(
|
||||
using namespace ranges::views;
|
||||
Core::App().settings().setSkipTranslationLanguages(all(
|
||||
locales
|
||||
) | ranges::views::transform([](const QLocale &l) {
|
||||
return int(l.language());
|
||||
}) | ranges::to_vector;
|
||||
Core::App().settings().setSkipTranslationForLanguages(result);
|
||||
) | transform([](const QLocale &l) {
|
||||
return LanguageId{ l.language() };
|
||||
}) | ranges::to_vector);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}, Ui::Translate::LocalesFromSettings()),
|
||||
}, SkipLocalesFromSettings()),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
Settings::AddSkip(topContainer);
|
||||
|
|
|
@ -16,9 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "main/main_session.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "settings/settings_common.h"
|
||||
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
||||
#include "spellcheck/platform/platform_language.h"
|
||||
#endif
|
||||
#include "ui/effects/loading_element.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
|
@ -252,29 +250,6 @@ rpl::producer<Qt::MouseButton> ShowButton::clicks() const {
|
|||
|
||||
} // namespace
|
||||
|
||||
namespace Translate {
|
||||
|
||||
std::vector<QLocale> LocalesFromSettings() {
|
||||
const auto langs = Core::App().settings().skipTranslationForLanguages();
|
||||
if (langs.empty()) {
|
||||
return { QLocale(QLocale::English) };
|
||||
}
|
||||
return ranges::views::all(
|
||||
langs
|
||||
) | ranges::view::transform([](int langId) {
|
||||
const auto lang = QLocale::Language(langId);
|
||||
return (lang == QLocale::English)
|
||||
? QLocale(Lang::LanguageIdOrDefault(Lang::Id()))
|
||||
: (lang == QLocale::C)
|
||||
? QLocale(QLocale::English)
|
||||
: QLocale(lang);
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
} // namespace Translate
|
||||
|
||||
using namespace Translate;
|
||||
|
||||
QString LanguageName(const QLocale &locale) {
|
||||
if (locale.language() == QLocale::English
|
||||
&& (locale.country() == QLocale::UnitedStates
|
||||
|
@ -297,7 +272,7 @@ void TranslateBox(
|
|||
box->setWidth(st::boxWideWidth);
|
||||
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
||||
const auto container = box->verticalLayout();
|
||||
const auto defaultId = LocalesFromSettings().front().name().mid(0, 2);
|
||||
const auto translateTo = Core::App().settings().translateTo().locale();
|
||||
|
||||
const auto api = box->lifetime().make_state<MTP::Sender>(
|
||||
&peer->session().mtp());
|
||||
|
@ -316,7 +291,7 @@ void TranslateBox(
|
|||
msgId = 0;
|
||||
}
|
||||
|
||||
using Flag = MTPmessages_translateText::Flag;
|
||||
using Flag = MTPmessages_TranslateText::Flag;
|
||||
const auto flags = msgId
|
||||
? (Flag::f_peer | Flag::f_id)
|
||||
: !text.text.isEmpty()
|
||||
|
@ -428,7 +403,7 @@ void TranslateBox(
|
|||
: MTP_vector<MTPTextWithEntities>(1, MTP_textWithEntities(
|
||||
MTP_string(text.text),
|
||||
MTP_vector<MTPMessageEntity>()))),
|
||||
MTP_string(toLang)
|
||||
MTP_string(toLang.mid(0, 2))
|
||||
)).done([=](const MTPmessages_TranslatedText &result) {
|
||||
const auto &data = result.data();
|
||||
const auto &list = data.vresult().v;
|
||||
|
@ -439,8 +414,8 @@ void TranslateBox(
|
|||
showText(tr::lng_translate_box_error(tr::now));
|
||||
}).send();
|
||||
};
|
||||
send(defaultId);
|
||||
state->locale.fire(QLocale(defaultId));
|
||||
send(translateTo.name());
|
||||
state->locale.fire_copy(translateTo);
|
||||
|
||||
box->addLeftButton(tr::lng_settings_language(), [=] {
|
||||
if (loading->toggled()) {
|
||||
|
@ -449,10 +424,10 @@ void TranslateBox(
|
|||
Ui::BoxShow(box).showBox(Box(ChooseLanguageBox, [=](
|
||||
std::vector<QLocale> locales) {
|
||||
const auto &locale = locales.front();
|
||||
send(locale.name());
|
||||
state->locale.fire_copy(locale);
|
||||
loading->show(anim::type::instant);
|
||||
translated->hide(anim::type::instant);
|
||||
send(locale.name().mid(0, 2));
|
||||
}, std::vector<QLocale>()));
|
||||
});
|
||||
}
|
||||
|
@ -574,12 +549,9 @@ bool SkipTranslate(TextWithEntities textWithEntities) {
|
|||
}
|
||||
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
||||
const auto result = Platform::Language::Recognize(text);
|
||||
if (result.unknown) {
|
||||
return false;
|
||||
}
|
||||
return ranges::any_of(LocalesFromSettings(), [&](const QLocale &l) {
|
||||
return result.locale.language() == l.language();
|
||||
});
|
||||
const auto skip = Core::App().settings().skipTranslationLanguages();
|
||||
const auto test = (result == result);
|
||||
return result.known() && ranges::contains(skip, result);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
|
|
|
@ -10,9 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
class PeerData;
|
||||
|
||||
namespace Ui {
|
||||
namespace Translate {
|
||||
[[nodiscard]] std::vector<QLocale> LocalesFromSettings();
|
||||
} // namespace Translate
|
||||
|
||||
class GenericBox;
|
||||
|
||||
|
|
|
@ -223,13 +223,13 @@ void EditLinkBox(
|
|||
QObject::connect(text, &Ui::InputField::tabbed, [=] { url->setFocus(); });
|
||||
}
|
||||
|
||||
TextWithEntities StripSupportHashtag(TextWithEntities &&text) {
|
||||
TextWithEntities StripSupportHashtag(TextWithEntities text) {
|
||||
static const auto expression = QRegularExpression(
|
||||
u"\\n?#tsf[a-z0-9_-]*[\\s#a-z0-9_-]*$"_q,
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
const auto match = expression.match(text.text);
|
||||
if (!match.hasMatch()) {
|
||||
return std::move(text);
|
||||
return text;
|
||||
}
|
||||
text.text.chop(match.capturedLength());
|
||||
const auto length = text.text.size();
|
||||
|
@ -246,7 +246,7 @@ TextWithEntities StripSupportHashtag(TextWithEntities &&text) {
|
|||
}
|
||||
++i;
|
||||
}
|
||||
return std::move(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/player/media_player_instance.h"
|
||||
#include "ui/gl/gl_detection.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
@ -91,10 +92,13 @@ Settings::Settings()
|
|||
, _dialogsWidthRatio(DefaultDialogsWidthRatio()) {
|
||||
}
|
||||
|
||||
Settings::~Settings() = default;
|
||||
|
||||
QByteArray Settings::serialize() const {
|
||||
const auto themesAccentColors = _themesAccentColors.serialize();
|
||||
const auto windowPosition = Serialize(_windowPosition);
|
||||
const auto proxy = _proxy.serialize();
|
||||
const auto skipLanguages = _skipTranslationLanguages.current();
|
||||
|
||||
auto recentEmojiPreloadGenerated = std::vector<RecentEmojiPreload>();
|
||||
if (_recentEmojiPreload.empty()) {
|
||||
|
@ -152,7 +156,10 @@ QByteArray Settings::serialize() const {
|
|||
+ Serialize::stringSize(_customDeviceModel.current())
|
||||
+ sizeof(qint32) * 4
|
||||
+ (_accountsOrder.size() * sizeof(quint64))
|
||||
+ sizeof(qint32) * 5;
|
||||
+ sizeof(qint32) * 7
|
||||
+ (skipLanguages.size() * sizeof(quint64))
|
||||
+ sizeof(qint32)
|
||||
+ sizeof(quint64);
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
|
@ -270,13 +277,17 @@ QByteArray Settings::serialize() const {
|
|||
<< qint32(_translateButtonEnabled ? 1 : 0);
|
||||
|
||||
stream
|
||||
<< qint32(_skipTranslationForLanguages.size());
|
||||
for (const auto &lang : _skipTranslationForLanguages) {
|
||||
stream << quint64(lang);
|
||||
<< qint32(skipLanguages.size());
|
||||
for (const auto &id : skipLanguages) {
|
||||
stream << quint64(id.value);
|
||||
}
|
||||
|
||||
stream
|
||||
<< qint32(_rememberedDeleteMessageOnlyForYou ? 1 : 0);
|
||||
|
||||
stream
|
||||
<< qint32(_translateChatEnabled.current() ? 1 : 0)
|
||||
<< quint64(QLocale::Language(_translateToRaw.current()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -371,9 +382,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
qint32 suggestAnimatedEmoji = _suggestAnimatedEmoji ? 1 : 0;
|
||||
qint32 cornerReaction = _cornerReaction.current() ? 1 : 0;
|
||||
qint32 legacySkipTranslationForLanguage = _translateButtonEnabled ? 1 : 0;
|
||||
qint32 skipTranslationForLanguagesCount = 0;
|
||||
std::vector<int> skipTranslationForLanguages;
|
||||
qint32 skipTranslationLanguagesCount = 0;
|
||||
std::vector<LanguageId> skipTranslationLanguages;
|
||||
qint32 rememberedDeleteMessageOnlyForYou = _rememberedDeleteMessageOnlyForYou ? 1 : 0;
|
||||
qint32 translateChatEnabled = _translateChatEnabled.current() ? 1 : 0;
|
||||
quint64 translateToRaw = _translateToRaw.current();
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
|
@ -575,17 +588,24 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
stream >> legacySkipTranslationForLanguage;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> skipTranslationForLanguagesCount;
|
||||
stream >> skipTranslationLanguagesCount;
|
||||
if (stream.status() == QDataStream::Ok) {
|
||||
for (auto i = 0; i != skipTranslationForLanguagesCount; ++i) {
|
||||
for (auto i = 0; i != skipTranslationLanguagesCount; ++i) {
|
||||
quint64 language;
|
||||
stream >> language;
|
||||
skipTranslationForLanguages.emplace_back(language);
|
||||
skipTranslationLanguages.push_back({
|
||||
QLocale::Language(language)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
stream >> rememberedDeleteMessageOnlyForYou;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream
|
||||
>> translateChatEnabled
|
||||
>> translateToRaw;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
|
@ -756,18 +776,21 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
_suggestAnimatedEmoji = (suggestAnimatedEmoji == 1);
|
||||
_cornerReaction = (cornerReaction == 1);
|
||||
{ // Parse the legacy translation setting.
|
||||
_skipTranslationForLanguages = skipTranslationForLanguages;
|
||||
if (legacySkipTranslationForLanguage == 0) {
|
||||
_translateButtonEnabled = false;
|
||||
} else if (legacySkipTranslationForLanguage == 1) {
|
||||
_translateButtonEnabled = true;
|
||||
} else {
|
||||
_translateButtonEnabled = (legacySkipTranslationForLanguage > 0);
|
||||
_skipTranslationForLanguages.push_back(
|
||||
std::abs(legacySkipTranslationForLanguage));
|
||||
skipTranslationLanguages.push_back({
|
||||
QLocale::Language(std::abs(legacySkipTranslationForLanguage))
|
||||
});
|
||||
}
|
||||
_skipTranslationLanguages = std::move(skipTranslationLanguages);
|
||||
}
|
||||
_rememberedDeleteMessageOnlyForYou = (rememberedDeleteMessageOnlyForYou == 1);
|
||||
_translateChatEnabled = (translateChatEnabled == 1);
|
||||
_translateToRaw = int(QLocale::Language(translateToRaw));
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
|
@ -1080,14 +1103,76 @@ float64 Settings::DefaultDialogsWidthRatio() {
|
|||
void Settings::setTranslateButtonEnabled(bool value) {
|
||||
_translateButtonEnabled = value;
|
||||
}
|
||||
|
||||
bool Settings::translateButtonEnabled() const {
|
||||
return _translateButtonEnabled;
|
||||
}
|
||||
void Settings::setSkipTranslationForLanguages(std::vector<int> languages) {
|
||||
_skipTranslationForLanguages = std::move(languages);
|
||||
|
||||
void Settings::setTranslateChatEnabled(bool value) {
|
||||
_translateChatEnabled = value;
|
||||
}
|
||||
std::vector<int> Settings::skipTranslationForLanguages() const {
|
||||
return _skipTranslationForLanguages;
|
||||
|
||||
bool Settings::translateChatEnabled() const {
|
||||
return _translateChatEnabled.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> Settings::translateChatEnabledValue() const {
|
||||
return _translateChatEnabled.value();
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<LanguageId> &DefaultSkipLanguages() {
|
||||
using namespace Platform;
|
||||
|
||||
static auto Result = [&] {
|
||||
auto list = std::vector<LanguageId>();
|
||||
list.push_back({ LanguageId::FromName(Lang::Id()) });
|
||||
const auto systemId = LanguageId::FromName(SystemLanguage());
|
||||
if (list.back() != systemId) {
|
||||
list.push_back(systemId);
|
||||
}
|
||||
|
||||
Ensures(!list.empty());
|
||||
return list;
|
||||
}();
|
||||
return Result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<LanguageId> NonEmptySkipList(
|
||||
std::vector<LanguageId> list) {
|
||||
return list.empty() ? DefaultSkipLanguages() : list;
|
||||
}
|
||||
|
||||
void Settings::setTranslateTo(LanguageId id) {
|
||||
_translateToRaw = int(id.value);
|
||||
}
|
||||
|
||||
LanguageId Settings::translateTo() const {
|
||||
if (const auto raw = _translateToRaw.current()) {
|
||||
return { QLocale::Language(raw) };
|
||||
}
|
||||
return DefaultSkipLanguages().front();
|
||||
}
|
||||
|
||||
rpl::producer<LanguageId> Settings::translateToValue() const {
|
||||
return _translateToRaw.value() | rpl::map([=](int raw) {
|
||||
return raw
|
||||
? LanguageId{ QLocale::Language(raw) }
|
||||
: DefaultSkipLanguages().front();
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
void Settings::setSkipTranslationLanguages(
|
||||
std::vector<LanguageId> languages) {
|
||||
_skipTranslationLanguages = std::move(languages);
|
||||
}
|
||||
|
||||
auto Settings::skipTranslationLanguages() const -> std::vector<LanguageId> {
|
||||
return NonEmptySkipList(_skipTranslationLanguages.current());
|
||||
}
|
||||
|
||||
auto Settings::skipTranslationLanguagesValue() const
|
||||
-> rpl::producer<std::vector<LanguageId>> {
|
||||
return _skipTranslationLanguages.value() | rpl::map(NonEmptySkipList);
|
||||
}
|
||||
|
||||
void Settings::setRememberedDeleteMessageOnlyForYou(bool value) {
|
||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "emoji.h"
|
||||
|
||||
enum class RectPart;
|
||||
struct LanguageId;
|
||||
|
||||
namespace Ui {
|
||||
enum class InputSubmitSettings;
|
||||
|
@ -99,6 +100,7 @@ public:
|
|||
static constexpr auto kDefaultVolume = 0.9;
|
||||
|
||||
Settings();
|
||||
~Settings();
|
||||
|
||||
[[nodiscard]] rpl::producer<> saveDelayedRequests() const {
|
||||
return _saveDelayed.events();
|
||||
|
@ -724,8 +726,16 @@ public:
|
|||
|
||||
void setTranslateButtonEnabled(bool value);
|
||||
[[nodiscard]] bool translateButtonEnabled() const;
|
||||
void setSkipTranslationForLanguages(std::vector<int> languages);
|
||||
[[nodiscard]] std::vector<int> skipTranslationForLanguages() const;
|
||||
void setTranslateChatEnabled(bool value);
|
||||
[[nodiscard]] bool translateChatEnabled() const;
|
||||
[[nodiscard]] rpl::producer<bool> translateChatEnabledValue() const;
|
||||
void setTranslateTo(LanguageId id);
|
||||
[[nodiscard]] LanguageId translateTo() const;
|
||||
[[nodiscard]] rpl::producer<LanguageId> translateToValue() const;
|
||||
void setSkipTranslationLanguages(std::vector<LanguageId> languages);
|
||||
[[nodiscard]] std::vector<LanguageId> skipTranslationLanguages() const;
|
||||
[[nodiscard]] auto skipTranslationLanguagesValue() const
|
||||
-> rpl::producer<std::vector<LanguageId>>;
|
||||
|
||||
void setRememberedDeleteMessageOnlyForYou(bool value);
|
||||
[[nodiscard]] bool rememberedDeleteMessageOnlyForYou() const;
|
||||
|
@ -845,7 +855,10 @@ private:
|
|||
HistoryView::DoubleClickQuickAction _chatQuickAction =
|
||||
HistoryView::DoubleClickQuickAction();
|
||||
bool _translateButtonEnabled = false;
|
||||
std::vector<int> _skipTranslationForLanguages;
|
||||
rpl::variable<bool> _translateChatEnabled = true;
|
||||
rpl::variable<int> _translateToRaw = 0;
|
||||
rpl::variable<std::vector<LanguageId>> _skipTranslationLanguages;
|
||||
rpl::event_stream<> _skipTranslationLanguagesChanges;
|
||||
bool _rememberedDeleteMessageOnlyForYou = false;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
|
|
|
@ -56,52 +56,53 @@ struct PeerUpdate {
|
|||
None = 0,
|
||||
|
||||
// Common flags
|
||||
Name = (1ULL << 0),
|
||||
Username = (1ULL << 1),
|
||||
Photo = (1ULL << 2),
|
||||
About = (1ULL << 3),
|
||||
Notifications = (1ULL << 4),
|
||||
Migration = (1ULL << 5),
|
||||
UnavailableReason = (1ULL << 6),
|
||||
ChatThemeEmoji = (1ULL << 7),
|
||||
IsBlocked = (1ULL << 8),
|
||||
MessagesTTL = (1ULL << 9),
|
||||
FullInfo = (1ULL << 10),
|
||||
Usernames = (1ULL << 11),
|
||||
Name = (1ULL << 0),
|
||||
Username = (1ULL << 1),
|
||||
Photo = (1ULL << 2),
|
||||
About = (1ULL << 3),
|
||||
Notifications = (1ULL << 4),
|
||||
Migration = (1ULL << 5),
|
||||
UnavailableReason = (1ULL << 6),
|
||||
ChatThemeEmoji = (1ULL << 7),
|
||||
IsBlocked = (1ULL << 8),
|
||||
MessagesTTL = (1ULL << 9),
|
||||
FullInfo = (1ULL << 10),
|
||||
Usernames = (1ULL << 11),
|
||||
TranslationDisabled = (1ULL << 12),
|
||||
|
||||
// For users
|
||||
CanShareContact = (1ULL << 12),
|
||||
IsContact = (1ULL << 13),
|
||||
PhoneNumber = (1ULL << 14),
|
||||
OnlineStatus = (1ULL << 15),
|
||||
BotCommands = (1ULL << 16),
|
||||
BotCanBeInvited = (1ULL << 17),
|
||||
BotStartToken = (1ULL << 18),
|
||||
CommonChats = (1ULL << 19),
|
||||
HasCalls = (1ULL << 20),
|
||||
SupportInfo = (1ULL << 21),
|
||||
IsBot = (1ULL << 22),
|
||||
EmojiStatus = (1ULL << 23),
|
||||
CanShareContact = (1ULL << 13),
|
||||
IsContact = (1ULL << 14),
|
||||
PhoneNumber = (1ULL << 15),
|
||||
OnlineStatus = (1ULL << 16),
|
||||
BotCommands = (1ULL << 17),
|
||||
BotCanBeInvited = (1ULL << 18),
|
||||
BotStartToken = (1ULL << 19),
|
||||
CommonChats = (1ULL << 20),
|
||||
HasCalls = (1ULL << 21),
|
||||
SupportInfo = (1ULL << 22),
|
||||
IsBot = (1ULL << 23),
|
||||
EmojiStatus = (1ULL << 24),
|
||||
|
||||
// For chats and channels
|
||||
InviteLinks = (1ULL << 24),
|
||||
Members = (1ULL << 25),
|
||||
Admins = (1ULL << 26),
|
||||
BannedUsers = (1ULL << 27),
|
||||
Rights = (1ULL << 28),
|
||||
PendingRequests = (1ULL << 29),
|
||||
Reactions = (1ULL << 30),
|
||||
InviteLinks = (1ULL << 25),
|
||||
Members = (1ULL << 26),
|
||||
Admins = (1ULL << 27),
|
||||
BannedUsers = (1ULL << 28),
|
||||
Rights = (1ULL << 29),
|
||||
PendingRequests = (1ULL << 30),
|
||||
Reactions = (1ULL << 31),
|
||||
|
||||
// For channels
|
||||
ChannelAmIn = (1ULL << 31),
|
||||
StickersSet = (1ULL << 32),
|
||||
ChannelLinkedChat = (1ULL << 33),
|
||||
ChannelLocation = (1ULL << 34),
|
||||
Slowmode = (1ULL << 35),
|
||||
GroupCall = (1ULL << 36),
|
||||
ChannelAmIn = (1ULL << 32),
|
||||
StickersSet = (1ULL << 33),
|
||||
ChannelLinkedChat = (1ULL << 34),
|
||||
ChannelLocation = (1ULL << 35),
|
||||
Slowmode = (1ULL << 36),
|
||||
GroupCall = (1ULL << 37),
|
||||
|
||||
// For iteration
|
||||
LastUsedBit = (1ULL << 36),
|
||||
LastUsedBit = (1ULL << 37),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
@ -128,8 +129,10 @@ struct HistoryUpdate {
|
|||
OutboxRead = (1U << 10),
|
||||
BotKeyboard = (1U << 11),
|
||||
CloudDraft = (1U << 12),
|
||||
TranslateFrom = (1U << 13),
|
||||
TranslatedTo = (1U << 14),
|
||||
|
||||
LastUsedBit = (1U << 12),
|
||||
LastUsedBit = (1U << 14),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
|
|
@ -1040,6 +1040,7 @@ void ApplyChannelUpdate(
|
|||
}
|
||||
}
|
||||
channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
|
||||
channel->setTranslationDisabled(update.is_translations_disabled());
|
||||
if (const auto allowed = update.vavailable_reactions()) {
|
||||
channel->setAllowedReactions(Data::Parse(*allowed));
|
||||
} else {
|
||||
|
|
|
@ -470,6 +470,7 @@ void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
|
|||
}
|
||||
chat->checkFolder(update.vfolder_id().value_or_empty());
|
||||
chat->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
|
||||
chat->setTranslationDisabled(update.is_translations_disabled());
|
||||
if (const auto allowed = update.vavailable_reactions()) {
|
||||
chat->setAllowedReactions(Data::Parse(*allowed));
|
||||
} else {
|
||||
|
|
|
@ -655,6 +655,8 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
|
|||
const auto type = tr::lng_in_dlg_photo(tr::now);
|
||||
const auto caption = options.hideCaption
|
||||
? TextWithEntities()
|
||||
: options.translated
|
||||
? parent()->translatedText()
|
||||
: parent()->originalText();
|
||||
const auto hasMiniImages = !images.empty();
|
||||
return {
|
||||
|
@ -899,6 +901,8 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
|||
}();
|
||||
const auto caption = options.hideCaption
|
||||
? TextWithEntities()
|
||||
: options.translated
|
||||
? parent()->translatedText()
|
||||
: parent()->originalText();
|
||||
const auto hasMiniImages = !images.empty();
|
||||
return {
|
||||
|
|
|
@ -546,6 +546,32 @@ void PeerData::checkFolder(FolderId folderId) {
|
|||
}
|
||||
}
|
||||
|
||||
void PeerData::setTranslationDisabled(bool disabled) {
|
||||
const auto flag = disabled
|
||||
? TranslationFlag::Disabled
|
||||
: TranslationFlag::Enabled;
|
||||
if (_translationFlag != flag) {
|
||||
_translationFlag = flag;
|
||||
session().changes().peerUpdated(
|
||||
this,
|
||||
UpdateFlag::TranslationDisabled);
|
||||
}
|
||||
}
|
||||
|
||||
PeerData::TranslationFlag PeerData::translationFlag() const {
|
||||
return _translationFlag;
|
||||
}
|
||||
|
||||
void PeerData::saveTranslationDisabled(bool disabled) {
|
||||
setTranslationDisabled(disabled);
|
||||
|
||||
using Flag = MTPmessages_TogglePeerTranslations::Flag;
|
||||
session().api().request(MTPmessages_TogglePeerTranslations(
|
||||
MTP_flags(disabled ? Flag::f_disabled : Flag()),
|
||||
input
|
||||
)).send();
|
||||
}
|
||||
|
||||
void PeerData::setSettings(const MTPPeerSettings &data) {
|
||||
data.match([&](const MTPDpeerSettings &data) {
|
||||
_requestChatTitle = data.vrequest_chat_title().value_or_empty();
|
||||
|
|
|
@ -349,6 +349,15 @@ public:
|
|||
return _requestChatDate;
|
||||
}
|
||||
|
||||
enum class TranslationFlag : uchar {
|
||||
Unknown,
|
||||
Disabled,
|
||||
Enabled,
|
||||
};
|
||||
void setTranslationDisabled(bool disabled);
|
||||
[[nodiscard]] TranslationFlag translationFlag() const;
|
||||
void saveTranslationDisabled(bool disabled);
|
||||
|
||||
void setSettings(const MTPPeerSettings &data);
|
||||
|
||||
enum class BlockStatus : char {
|
||||
|
@ -439,6 +448,7 @@ private:
|
|||
Settings _settings = PeerSettings(PeerSetting::Unknown);
|
||||
BlockStatus _blockStatus = BlockStatus::Unknown;
|
||||
LoadedStatus _loadedStatus = LoadedStatus::Not;
|
||||
TranslationFlag _translationFlag = TranslationFlag::Unknown;
|
||||
bool _userpicHasVideo = false;
|
||||
|
||||
QString _requestChatTitle;
|
||||
|
|
|
@ -288,6 +288,9 @@ enum class MessageFlag : uint64 {
|
|||
|
||||
// Profile photo suggestion, views have special media type.
|
||||
IsUserpicSuggestion = (1ULL << 33),
|
||||
|
||||
OnlyEmojiAndSpaces = (1ULL << 34),
|
||||
OnlyEmojiAndSpacesSet = (1ULL << 35),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
|
|
@ -423,6 +423,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
|||
user->setCommonChatsCount(update.vcommon_chats_count().v);
|
||||
user->checkFolder(update.vfolder_id().value_or_empty());
|
||||
user->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
|
||||
user->setTranslationDisabled(update.is_translations_disabled());
|
||||
|
||||
if (const auto info = user->botInfo.get()) {
|
||||
const auto group = update.vbot_group_admin_rights()
|
||||
|
|
|
@ -9,12 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_item_preview.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "history/history_inner_widget.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/history_inner_widget.h"
|
||||
#include "history/history_translation.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
|
@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "main/main_session.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
|
@ -3456,6 +3458,46 @@ bool History::isTopPromoted() const {
|
|||
return (_flags & Flag::IsTopPromoted);
|
||||
}
|
||||
|
||||
void History::translateOfferFrom(LanguageId id) {
|
||||
if (!id) {
|
||||
if (translatedTo()) {
|
||||
_translation->offerFrom(id);
|
||||
} else if (_translation) {
|
||||
_translation = nullptr;
|
||||
session().changes().historyUpdated(
|
||||
this,
|
||||
UpdateFlag::TranslateFrom);
|
||||
}
|
||||
} else if (!_translation) {
|
||||
_translation = std::make_unique<HistoryTranslation>(this, id);
|
||||
} else {
|
||||
_translation->offerFrom(id);
|
||||
}
|
||||
}
|
||||
|
||||
LanguageId History::translateOfferedFrom() const {
|
||||
return _translation ? _translation->offeredFrom() : LanguageId();
|
||||
}
|
||||
|
||||
void History::translateTo(LanguageId id) {
|
||||
if (!_translation) {
|
||||
return;
|
||||
} else if (!id && !translateOfferedFrom()) {
|
||||
_translation = nullptr;
|
||||
session().changes().historyUpdated(this, UpdateFlag::TranslatedTo);
|
||||
} else {
|
||||
_translation->translateTo(id);
|
||||
}
|
||||
}
|
||||
|
||||
LanguageId History::translatedTo() const {
|
||||
return _translation ? _translation->translatedTo() : LanguageId();
|
||||
}
|
||||
|
||||
HistoryTranslation *History::translation() const {
|
||||
return _translation.get();
|
||||
}
|
||||
|
||||
HistoryBlock::HistoryBlock(not_null<History*> history)
|
||||
: _history(history) {
|
||||
}
|
||||
|
|
|
@ -18,9 +18,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
class History;
|
||||
class HistoryBlock;
|
||||
class HistoryTranslation;
|
||||
class HistoryItem;
|
||||
struct HistoryMessageMarkupData;
|
||||
class HistoryMainElementDelegateMixin;
|
||||
struct LanguageId;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
|
@ -425,6 +427,13 @@ public:
|
|||
|
||||
[[nodiscard]] bool isTopPromoted() const;
|
||||
|
||||
void translateOfferFrom(LanguageId id);
|
||||
[[nodiscard]] LanguageId translateOfferedFrom() const;
|
||||
void translateTo(LanguageId id);
|
||||
[[nodiscard]] LanguageId translatedTo() const;
|
||||
|
||||
[[nodiscard]] HistoryTranslation *translation() const;
|
||||
|
||||
const not_null<PeerData*> peer;
|
||||
|
||||
// Still public data.
|
||||
|
@ -617,6 +626,7 @@ private:
|
|||
HistoryBlock *block = nullptr;
|
||||
};
|
||||
std::unique_ptr<BuildingBlock> _buildingFrontBlock;
|
||||
std::unique_ptr<HistoryTranslation> _translation;
|
||||
|
||||
Data::HistoryDrafts _drafts;
|
||||
base::flat_map<MsgId, TimeId> _acceptCloudDraftsAfter;
|
||||
|
@ -628,6 +638,7 @@ private:
|
|||
|
||||
HistoryView::SendActionPainter _sendActionPainter;
|
||||
|
||||
|
||||
};
|
||||
|
||||
class HistoryBlock {
|
||||
|
|
|
@ -57,6 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
#include "history/history_widget.h"
|
||||
#include "history/view/history_view_translate_tracker.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
|
@ -324,6 +325,7 @@ HistoryInner::HistoryInner(
|
|||
&controller->session(),
|
||||
[=](not_null<const Element*> view) { return itemTop(view); }))
|
||||
, _migrated(history->migrateFrom())
|
||||
, _translateTracker(std::make_unique<HistoryView::TranslateTracker>(history))
|
||||
, _pathGradient(
|
||||
HistoryView::MakePathShiftGradient(
|
||||
controller->chatStyle(),
|
||||
|
@ -340,6 +342,7 @@ HistoryInner::HistoryInner(
|
|||
_history->delegateMixin()->setCurrent(this);
|
||||
if (_migrated) {
|
||||
_migrated->delegateMixin()->setCurrent(this);
|
||||
_migrated->translateTo(_history->translatedTo());
|
||||
}
|
||||
|
||||
Window::ChatThemeValueFromPeer(
|
||||
|
@ -431,7 +434,8 @@ HistoryInner::HistoryInner(
|
|||
|
||||
session().changes().historyUpdates(
|
||||
_history,
|
||||
Data::HistoryUpdate::Flag::OutboxRead
|
||||
(Data::HistoryUpdate::Flag::OutboxRead
|
||||
| Data::HistoryUpdate::Flag::TranslatedTo)
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
}, lifetime());
|
||||
|
@ -910,6 +914,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
|||
|
||||
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
|
||||
&& (!_migrated || _migrated->isDisplayedEmpty());
|
||||
const auto translatedTo = _history->translatedTo();
|
||||
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
|
||||
const auto st = context.st;
|
||||
const auto stm = &st->messageStyle(false, false);
|
||||
|
@ -958,9 +963,11 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
|||
return;
|
||||
}
|
||||
|
||||
_translateTracker->startBunch();
|
||||
auto readTill = (HistoryItem*)nullptr;
|
||||
auto readContents = base::flat_set<not_null<HistoryItem*>>();
|
||||
const auto guard = gsl::finally([&] {
|
||||
_translateTracker->finishBunch();
|
||||
if (readTill && _widget->markingMessagesRead()) {
|
||||
session().data().histories().readInboxTill(readTill);
|
||||
}
|
||||
|
@ -974,6 +981,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
|||
not_null<Element*> view,
|
||||
int top,
|
||||
int height) {
|
||||
_translateTracker->add(view, translatedTo);
|
||||
const auto item = view->data();
|
||||
const auto isSponsored = item->isSponsored();
|
||||
const auto isUnread = !item->out()
|
||||
|
@ -2414,7 +2422,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
[=] { copyContextText(itemId); },
|
||||
&st::menuIconCopy);
|
||||
}
|
||||
if (view->hasVisibleText() || mediaHasTextForCopy) {
|
||||
if ((!item->translation() || !_history->translatedTo())
|
||||
&& (view->hasVisibleText() || mediaHasTextForCopy)) {
|
||||
const auto translate = mediaHasTextForCopy
|
||||
? (HistoryView::TransribedText(item)
|
||||
.append('\n')
|
||||
|
@ -3925,6 +3934,7 @@ void HistoryInner::notifyIsBotChanged() {
|
|||
|
||||
void HistoryInner::notifyMigrateUpdated() {
|
||||
_migrated = _history->migrateFrom();
|
||||
_migrated->translateTo(_history->translatedTo());
|
||||
}
|
||||
|
||||
void HistoryInner::applyDragSelection() {
|
||||
|
|
|
@ -31,6 +31,7 @@ enum class CursorState : char;
|
|||
enum class PointState : char;
|
||||
class EmptyPainter;
|
||||
class Element;
|
||||
class TranslateTracker;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
@ -445,6 +446,7 @@ private:
|
|||
|
||||
std::unique_ptr<BotAbout> _botAbout;
|
||||
std::unique_ptr<HistoryView::EmptyPainter> _emptyPainter;
|
||||
std::unique_ptr<HistoryView::TranslateTracker> _translateTracker;
|
||||
|
||||
mutable History *_curHistory = nullptr;
|
||||
mutable int _curBlock = 0;
|
||||
|
|
|
@ -78,6 +78,25 @@ constexpr auto kPinnedMessageTextLimit = 16;
|
|||
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
|
||||
[[nodiscard]] bool IsOnlyEmojiAndSpaces(const QString &text) {
|
||||
if (text.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
auto emoji = 0;
|
||||
auto start = text.data();
|
||||
const auto end = start + text.size();
|
||||
while (start < end) {
|
||||
if (start->isSpace()) {
|
||||
++start;
|
||||
} else if (Ui::Emoji::Find(start, end, &emoji)) {
|
||||
start += emoji;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
|
||||
|
@ -2006,6 +2025,94 @@ std::optional<QString> HistoryItem::errorTextForForward(
|
|||
return {};
|
||||
}
|
||||
|
||||
const HistoryMessageTranslation *HistoryItem::translation() const {
|
||||
return Get<HistoryMessageTranslation>();
|
||||
}
|
||||
|
||||
bool HistoryItem::translationShowRequiresCheck(LanguageId to) const {
|
||||
// Check if a call to translationShowRequiresRequest(to) is not a no-op.
|
||||
if (!to) {
|
||||
if (const auto translation = Get<HistoryMessageTranslation>()) {
|
||||
return (!translation->failed && translation->text.empty())
|
||||
|| translation->used;
|
||||
}
|
||||
return false;
|
||||
} else if (const auto translation = Get<HistoryMessageTranslation>()) {
|
||||
if (translation->to == to) {
|
||||
return !translation->used && !translation->text.empty();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool HistoryItem::translationShowRequiresRequest(LanguageId to) {
|
||||
// When changing be sure to reflect in translationShowRequiresCheck(to).
|
||||
if (!to) {
|
||||
if (const auto translation = Get<HistoryMessageTranslation>()) {
|
||||
if (!translation->failed && translation->text.empty()) {
|
||||
Assert(!translation->used);
|
||||
RemoveComponents(HistoryMessageTranslation::Bit());
|
||||
} else {
|
||||
translationToggle(translation, false);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (const auto translation = Get<HistoryMessageTranslation>()) {
|
||||
if (translation->to == to) {
|
||||
translationToggle(translation, true);
|
||||
return false;
|
||||
}
|
||||
translationToggle(translation, false);
|
||||
translation->to = to;
|
||||
translation->requested = true;
|
||||
translation->failed = false;
|
||||
translation->text = {};
|
||||
return true;
|
||||
} else {
|
||||
AddComponents(HistoryMessageTranslation::Bit());
|
||||
const auto added = Get<HistoryMessageTranslation>();
|
||||
added->to = to;
|
||||
added->requested = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::translationToggle(
|
||||
not_null<HistoryMessageTranslation*> translation,
|
||||
bool used) {
|
||||
if (translation->used != used && !translation->text.empty()) {
|
||||
translation->used = used;
|
||||
_history->owner().requestItemTextRefresh(this);
|
||||
_history->owner().updateDependentMessages(this);
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::translationDone(LanguageId to, TextWithEntities result) {
|
||||
const auto set = [&](not_null<HistoryMessageTranslation*> translation) {
|
||||
if (result.empty()) {
|
||||
translation->failed = true;
|
||||
} else {
|
||||
translation->text = std::move(result);
|
||||
if (_history->translatedTo() == to) {
|
||||
translationToggle(translation, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (const auto translation = Get<HistoryMessageTranslation>()) {
|
||||
if (translation->to == to && translation->text.empty()) {
|
||||
translation->requested = false;
|
||||
set(translation);
|
||||
}
|
||||
} else {
|
||||
AddComponents(HistoryMessageTranslation::Bit());
|
||||
const auto added = Get<HistoryMessageTranslation>();
|
||||
added->to = to;
|
||||
set(added);
|
||||
}
|
||||
}
|
||||
|
||||
bool HistoryItem::canReact() const {
|
||||
if (!isRegular() || isService()) {
|
||||
return false;
|
||||
|
@ -2180,14 +2287,31 @@ MsgId HistoryItem::idOriginal() const {
|
|||
return id;
|
||||
}
|
||||
|
||||
TextWithEntities HistoryItem::originalText() const {
|
||||
return isService() ? TextWithEntities() : _text;
|
||||
const TextWithEntities &HistoryItem::originalText() const {
|
||||
static const auto kEmpty = TextWithEntities();
|
||||
return isService() ? kEmpty : _text;
|
||||
}
|
||||
|
||||
TextWithEntities HistoryItem::originalTextWithLocalEntities() const {
|
||||
return isService()
|
||||
? TextWithEntities()
|
||||
: withLocalEntities(originalText());
|
||||
const TextWithEntities &HistoryItem::translatedText() const {
|
||||
if (isService()) {
|
||||
static const auto kEmpty = TextWithEntities();
|
||||
return kEmpty;
|
||||
} else if (const auto translation = this->translation()
|
||||
; translation
|
||||
&& translation->used
|
||||
&& (translation->to == history()->translatedTo())) {
|
||||
return translation->text;
|
||||
} else {
|
||||
return originalText();
|
||||
}
|
||||
}
|
||||
|
||||
TextWithEntities HistoryItem::translatedTextWithLocalEntities() const {
|
||||
if (isService()) {
|
||||
return {};
|
||||
} else {
|
||||
return withLocalEntities(translatedText());
|
||||
}
|
||||
}
|
||||
|
||||
TextForMimeData HistoryItem::clipboardText() const {
|
||||
|
@ -2595,6 +2719,7 @@ void HistoryItem::setText(const TextWithEntities &textWithEntities) {
|
|||
void HistoryItem::setTextValue(TextWithEntities text) {
|
||||
const auto had = !_text.empty();
|
||||
_text = std::move(text);
|
||||
RemoveComponents(HistoryMessageTranslation::Bit());
|
||||
if (had) {
|
||||
history()->owner().requestItemTextRefresh(this);
|
||||
}
|
||||
|
@ -2658,7 +2783,7 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
|
|||
if (_media) {
|
||||
return _media->toPreview(options);
|
||||
} else if (!emptyText()) {
|
||||
return { .text = _text };
|
||||
return { .text = options.translated ? translatedText() : _text };
|
||||
}
|
||||
return {};
|
||||
}();
|
||||
|
@ -2705,6 +2830,7 @@ TextWithEntities HistoryItem::inReplyText() const {
|
|||
return toPreview({
|
||||
.hideSender = true,
|
||||
.generateImages = false,
|
||||
.translated = true,
|
||||
}).text;
|
||||
}
|
||||
auto result = notificationText();
|
||||
|
@ -4256,7 +4382,7 @@ PreparedServiceText HistoryItem::preparePinnedText() {
|
|||
result.links.push_back(fromLink());
|
||||
result.links.push_back(pinned->lnk);
|
||||
if (mediaText.isEmpty()) {
|
||||
auto original = pinned->msg->originalText();
|
||||
auto original = pinned->msg->translatedText();
|
||||
auto cutAt = 0;
|
||||
auto limit = kPinnedMessageTextLimit;
|
||||
auto size = original.text.size();
|
||||
|
@ -4544,6 +4670,23 @@ crl::time HistoryItem::getSelfDestructIn(crl::time now) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void HistoryItem::cacheOnlyEmojiAndSpaces(bool only) {
|
||||
_flags |= MessageFlag::OnlyEmojiAndSpacesSet;
|
||||
if (only) {
|
||||
_flags |= MessageFlag::OnlyEmojiAndSpaces;
|
||||
} else {
|
||||
_flags &= ~MessageFlag::OnlyEmojiAndSpaces;
|
||||
}
|
||||
}
|
||||
|
||||
bool HistoryItem::isOnlyEmojiAndSpaces() const {
|
||||
if (!(_flags & MessageFlag::OnlyEmojiAndSpacesSet)) {
|
||||
const_cast<HistoryItem*>(this)->cacheOnlyEmojiAndSpaces(
|
||||
IsOnlyEmojiAndSpaces(_text.text));
|
||||
}
|
||||
return (_flags & MessageFlag::OnlyEmojiAndSpaces);
|
||||
}
|
||||
|
||||
void HistoryItem::setupChatThemeChange() {
|
||||
if (const auto user = history()->peer->asUser()) {
|
||||
auto link = std::make_shared<LambdaClickHandler>([=](
|
||||
|
|
|
@ -21,10 +21,12 @@ struct HistoryMessageReply;
|
|||
struct HistoryMessageViews;
|
||||
struct HistoryMessageMarkupData;
|
||||
struct HistoryMessageReplyMarkup;
|
||||
struct HistoryMessageTranslation;
|
||||
struct HistoryServiceDependentData;
|
||||
enum class HistorySelfDestructType;
|
||||
struct PreparedServiceText;
|
||||
class ReplyKeyboard;
|
||||
struct LanguageId;
|
||||
|
||||
namespace base {
|
||||
template <typename Enum>
|
||||
|
@ -259,6 +261,8 @@ public:
|
|||
[[nodiscard]] bool definesReplyKeyboard() const;
|
||||
[[nodiscard]] ReplyMarkupFlags replyKeyboardFlags() const;
|
||||
|
||||
void cacheOnlyEmojiAndSpaces(bool only);
|
||||
[[nodiscard]] bool isOnlyEmojiAndSpaces() const;
|
||||
[[nodiscard]] bool hasSwitchInlineButton() const {
|
||||
return _flags & MessageFlag::HasSwitchInlineButton;
|
||||
}
|
||||
|
@ -358,8 +362,9 @@ public:
|
|||
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
|
||||
[[nodiscard]] ItemPreview toPreview(ToPreviewOptions options) const;
|
||||
[[nodiscard]] TextWithEntities inReplyText() const;
|
||||
[[nodiscard]] TextWithEntities originalText() const;
|
||||
[[nodiscard]] TextWithEntities originalTextWithLocalEntities() const;
|
||||
[[nodiscard]] const TextWithEntities &originalText() const;
|
||||
[[nodiscard]] const TextWithEntities &translatedText() const;
|
||||
[[nodiscard]] TextWithEntities translatedTextWithLocalEntities() const;
|
||||
[[nodiscard]] const std::vector<ClickHandlerPtr> &customTextLinks() const;
|
||||
[[nodiscard]] TextForMimeData clipboardText() const;
|
||||
|
||||
|
@ -397,6 +402,10 @@ public:
|
|||
[[nodiscard]] bool requiresSendInlineRight() const;
|
||||
[[nodiscard]] std::optional<QString> errorTextForForward(
|
||||
not_null<Data::Thread*> to) const;
|
||||
[[nodiscard]] const HistoryMessageTranslation *translation() const;
|
||||
[[nodiscard]] bool translationShowRequiresCheck(LanguageId to) const;
|
||||
bool translationShowRequiresRequest(LanguageId to);
|
||||
void translationDone(LanguageId to, TextWithEntities result);
|
||||
|
||||
[[nodiscard]] bool canReact() const;
|
||||
enum class ReactionSource {
|
||||
|
@ -542,6 +551,9 @@ private:
|
|||
void setupChatThemeChange();
|
||||
void setupTTLChange();
|
||||
|
||||
void translationToggle(
|
||||
not_null<HistoryMessageTranslation*> translation,
|
||||
bool used);
|
||||
void setSelfDestruct(HistorySelfDestructType type, int ttlSeconds);
|
||||
|
||||
TextWithEntities fromLinkText() const;
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "history/history_item.h"
|
||||
#include "spellcheck/spellcheck_types.h" // LanguageId.
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/chat/message_bubble.h"
|
||||
|
@ -258,6 +259,15 @@ struct HistoryMessageReply
|
|||
|
||||
};
|
||||
|
||||
struct HistoryMessageTranslation
|
||||
: public RuntimeComponent<HistoryMessageTranslation, HistoryItem> {
|
||||
TextWithEntities text;
|
||||
LanguageId to;
|
||||
bool requested = false;
|
||||
bool failed = false;
|
||||
bool used = false;
|
||||
};
|
||||
|
||||
struct HistoryMessageReplyMarkup
|
||||
: public RuntimeComponent<HistoryMessageReplyMarkup, HistoryItem> {
|
||||
using Button = HistoryMessageMarkupButton;
|
||||
|
|
51
Telegram/SourceFiles/history/history_translation.cpp
Normal file
51
Telegram/SourceFiles/history/history_translation.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/history_translation.h"
|
||||
|
||||
#include "data/data_changes.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using UpdateFlag = Data::HistoryUpdate::Flag;
|
||||
|
||||
} // namespace
|
||||
|
||||
HistoryTranslation::HistoryTranslation(
|
||||
not_null<History*> history,
|
||||
const LanguageId &offerFrom)
|
||||
: _history(history) {
|
||||
this->offerFrom(offerFrom);
|
||||
}
|
||||
|
||||
void HistoryTranslation::offerFrom(LanguageId id) {
|
||||
if (_offerFrom == id) {
|
||||
return;
|
||||
}
|
||||
_offerFrom = id;
|
||||
auto &changes = _history->session().changes();
|
||||
changes.historyUpdated(_history, UpdateFlag::TranslateFrom);
|
||||
}
|
||||
|
||||
LanguageId HistoryTranslation::offeredFrom() const {
|
||||
return _offerFrom;
|
||||
}
|
||||
|
||||
void HistoryTranslation::translateTo(LanguageId id) {
|
||||
if (_translatedTo == id) {
|
||||
return;
|
||||
}
|
||||
_translatedTo = id;
|
||||
auto &changes = _history->session().changes();
|
||||
changes.historyUpdated(_history, UpdateFlag::TranslatedTo);
|
||||
}
|
||||
|
||||
LanguageId HistoryTranslation::translatedTo() const {
|
||||
return _translatedTo;
|
||||
}
|
32
Telegram/SourceFiles/history/history_translation.h
Normal file
32
Telegram/SourceFiles/history/history_translation.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
|
||||
class History;
|
||||
|
||||
class HistoryTranslation final {
|
||||
public:
|
||||
HistoryTranslation(
|
||||
not_null<History*> history,
|
||||
const LanguageId &offerFrom);
|
||||
|
||||
void offerFrom(LanguageId id);
|
||||
[[nodiscard]] LanguageId offeredFrom() const;
|
||||
|
||||
void translateTo(LanguageId id);
|
||||
[[nodiscard]] LanguageId translatedTo() const;
|
||||
|
||||
private:
|
||||
const not_null<History*> _history;
|
||||
|
||||
LanguageId _offerFrom;
|
||||
LanguageId _translatedTo;
|
||||
|
||||
};
|
|
@ -102,6 +102,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_item_preview.h"
|
||||
#include "history/view/history_view_requests_bar.h"
|
||||
#include "history/view/history_view_sticker_toast.h"
|
||||
#include "history/view/history_view_translate_bar.h"
|
||||
#include "history/view/media/history_view_media.h"
|
||||
#include "profile/profile_block_group_members.h"
|
||||
#include "info/info_memento.h"
|
||||
|
@ -1488,6 +1489,9 @@ void HistoryWidget::orderWidgets() {
|
|||
if (_pinnedBar) {
|
||||
_pinnedBar->raise();
|
||||
}
|
||||
if (_translateBar) {
|
||||
_translateBar->raise();
|
||||
}
|
||||
if (_requestsBar) {
|
||||
_requestsBar->raise();
|
||||
}
|
||||
|
@ -2093,6 +2097,7 @@ void HistoryWidget::showHistory(
|
|||
_history->showAtMsgId = _showAtMsgId;
|
||||
|
||||
destroyUnreadBarOnClose();
|
||||
_translateBar = nullptr;
|
||||
_pinnedBar = nullptr;
|
||||
_pinnedTracker = nullptr;
|
||||
_groupCallBar = nullptr;
|
||||
|
@ -2238,6 +2243,7 @@ void HistoryWidget::showHistory(
|
|||
|
||||
_updateHistoryItems.cancel();
|
||||
|
||||
setupTranslateBar();
|
||||
setupPinnedTracker();
|
||||
setupGroupCallBar();
|
||||
setupRequestsBar();
|
||||
|
@ -3966,6 +3972,9 @@ void HistoryWidget::showAnimated(
|
|||
show();
|
||||
_topBar->finishAnimating();
|
||||
_cornerButtons.finishAnimations();
|
||||
if (_translateBar) {
|
||||
_translateBar->finishAnimating();
|
||||
}
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->finishAnimating();
|
||||
}
|
||||
|
@ -4029,6 +4038,9 @@ void HistoryWidget::doneShow() {
|
|||
_preserveScrollTop = true;
|
||||
preloadHistoryIfNeeded();
|
||||
updatePinnedViewer();
|
||||
if (_translateBar) {
|
||||
_translateBar->finishAnimating();
|
||||
}
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->finishAnimating();
|
||||
}
|
||||
|
@ -5320,7 +5332,12 @@ void HistoryWidget::updateControlsGeometry() {
|
|||
_requestsBar->move(0, requestsTop);
|
||||
_requestsBar->resizeToWidth(width());
|
||||
}
|
||||
const auto pinnedBarTop = requestsTop + (_requestsBar ? _requestsBar->height() : 0);
|
||||
const auto translateTop = requestsTop + (_requestsBar ? _requestsBar->height() : 0);
|
||||
if (_translateBar) {
|
||||
_translateBar->move(0, translateTop);
|
||||
_translateBar->resizeToWidth(width());
|
||||
}
|
||||
const auto pinnedBarTop = translateTop + (_translateBar ? _translateBar->height() : 0);
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->move(0, pinnedBarTop);
|
||||
_pinnedBar->resizeToWidth(width());
|
||||
|
@ -5499,6 +5516,9 @@ void HistoryWidget::updateHistoryGeometry(
|
|||
}
|
||||
|
||||
auto newScrollHeight = height() - _topBar->height();
|
||||
if (_translateBar) {
|
||||
newScrollHeight -= _translateBar->height();
|
||||
}
|
||||
if (_pinnedBar) {
|
||||
newScrollHeight -= _pinnedBar->height();
|
||||
}
|
||||
|
@ -6229,6 +6249,40 @@ void HistoryWidget::checkLastPinnedClickedIdReset(
|
|||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::setupTranslateBar() {
|
||||
Expects(_history != nullptr);
|
||||
|
||||
_translateBar = std::make_unique<HistoryView::TranslateBar>(
|
||||
this,
|
||||
_history);
|
||||
|
||||
controller()->adaptive().oneColumnValue(
|
||||
) | rpl::start_with_next([=, raw = _translateBar.get()](bool one) {
|
||||
raw->setShadowGeometryPostprocess([=](QRect geometry) {
|
||||
if (!one) {
|
||||
geometry.setLeft(geometry.left() + st::lineWidth);
|
||||
}
|
||||
return geometry;
|
||||
});
|
||||
}, _translateBar->lifetime());
|
||||
|
||||
_translateBarHeight = 0;
|
||||
_translateBar->heightValue(
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
_topDelta = _preserveScrollTop ? 0 : (height - _translateBarHeight);
|
||||
_translateBarHeight = height;
|
||||
updateHistoryGeometry();
|
||||
updateControlsGeometry();
|
||||
_topDelta = 0;
|
||||
}, _translateBar->lifetime());
|
||||
|
||||
orderWidgets();
|
||||
|
||||
if (_showAnimation) {
|
||||
_translateBar->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::setupPinnedTracker() {
|
||||
Expects(_history != nullptr);
|
||||
|
||||
|
|
|
@ -91,6 +91,7 @@ class TopBarWidget;
|
|||
class ContactStatus;
|
||||
class Element;
|
||||
class PinnedTracker;
|
||||
class TranslateBar;
|
||||
class ComposeSearch;
|
||||
namespace Controls {
|
||||
class RecordLock;
|
||||
|
@ -494,6 +495,7 @@ private:
|
|||
void updateReplyEditText(not_null<HistoryItem*> item);
|
||||
|
||||
void updatePinnedViewer();
|
||||
void setupTranslateBar();
|
||||
void setupPinnedTracker();
|
||||
void checkPinnedBarState();
|
||||
void clearHidingPinnedBar();
|
||||
|
@ -638,6 +640,9 @@ private:
|
|||
|
||||
object_ptr<Ui::IconButton> _fieldBarCancel;
|
||||
|
||||
std::unique_ptr<HistoryView::TranslateBar> _translateBar;
|
||||
int _translateBarHeight = 0;
|
||||
|
||||
std::unique_ptr<HistoryView::PinnedTracker> _pinnedTracker;
|
||||
std::unique_ptr<Ui::PinnedBar> _pinnedBar;
|
||||
std::unique_ptr<Ui::PinnedBar> _hidingPinnedBar;
|
||||
|
|
|
@ -66,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "core/application.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
@ -1058,7 +1059,8 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
|||
.append('\n')
|
||||
.append(item->originalText()))
|
||||
: item->originalText();
|
||||
if (!translate.text.isEmpty()
|
||||
if ((!item->translation() || !item->history()->translatedTo())
|
||||
&& !translate.text.isEmpty()
|
||||
&& !Ui::SkipTranslate(translate)) {
|
||||
result->addAction(tr::lng_context_translate(tr::now), [=] {
|
||||
if (const auto item = owner->message(itemId)) {
|
||||
|
|
|
@ -652,6 +652,18 @@ const Ui::Text::String &Element::text() const {
|
|||
return _text;
|
||||
}
|
||||
|
||||
OnlyEmojiAndSpaces Element::isOnlyEmojiAndSpaces() const {
|
||||
if (data()->Has<HistoryMessageTranslation>()) {
|
||||
return OnlyEmojiAndSpaces::No;
|
||||
} else if (!_text.isEmpty() || data()->originalText().empty()) {
|
||||
return _text.isOnlyEmojiAndSpaces()
|
||||
? OnlyEmojiAndSpaces::Yes
|
||||
: OnlyEmojiAndSpaces::No;
|
||||
} else {
|
||||
return OnlyEmojiAndSpaces::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
int Element::textHeightFor(int textWidth) {
|
||||
validateText();
|
||||
if (_textWidth != textWidth) {
|
||||
|
@ -837,7 +849,7 @@ void Element::validateText() {
|
|||
};
|
||||
_text.setMarkedText(
|
||||
st::messageTextStyle,
|
||||
item->originalTextWithLocalEntities(),
|
||||
item->translatedTextWithLocalEntities(),
|
||||
Ui::ItemTextOptions(item),
|
||||
context);
|
||||
if (!text.empty() && _text.isEmpty()) {
|
||||
|
|
|
@ -58,6 +58,12 @@ enum class Context : char {
|
|||
ContactPreview
|
||||
};
|
||||
|
||||
enum class OnlyEmojiAndSpaces : char {
|
||||
Unknown,
|
||||
Yes,
|
||||
No,
|
||||
};
|
||||
|
||||
class Element;
|
||||
class ElementDelegate {
|
||||
public:
|
||||
|
@ -309,6 +315,8 @@ public:
|
|||
[[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const;
|
||||
[[nodiscard]] Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const;
|
||||
|
||||
[[nodiscard]] OnlyEmojiAndSpaces isOnlyEmojiAndSpaces() const;
|
||||
|
||||
// For blocks context this should be called only from recountAttachToPreviousInBlocks().
|
||||
void setAttachToPrevious(bool attachToNext, Element *previous = nullptr);
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ struct ToPreviewOptions {
|
|||
bool generateImages = true;
|
||||
bool ignoreGroup = false;
|
||||
bool ignoreTopic = true;
|
||||
bool translated = false;
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -295,17 +295,6 @@ RepliesWidget::RepliesWidget(
|
|||
searchInTopic();
|
||||
}, _topBar->lifetime());
|
||||
|
||||
if (_rootView) {
|
||||
_rootView->raise();
|
||||
}
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->raise();
|
||||
}
|
||||
if (_topicReopenBar) {
|
||||
_topicReopenBar->bar().raise();
|
||||
}
|
||||
_topBarShadow->raise();
|
||||
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
|
@ -426,6 +415,9 @@ void RepliesWidget::orderWidgets() {
|
|||
if (_pinnedBar) {
|
||||
_pinnedBar->raise();
|
||||
}
|
||||
if (_topicReopenBar) {
|
||||
_topicReopenBar->bar().raise();
|
||||
}
|
||||
_topBarShadow->raise();
|
||||
_composeControls->raisePanels();
|
||||
}
|
||||
|
|
197
Telegram/SourceFiles/history/view/history_view_translate_bar.cpp
Normal file
197
Telegram/SourceFiles/history/view/history_view_translate_bar.cpp
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/view/history_view_translate_bar.h"
|
||||
|
||||
#include "boxes/translate_box.h" // Ui::LanguageName.
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
#include <QtGui/QtEvents>
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
TranslateBar::TranslateBar(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<History*> history)
|
||||
: _wrap(parent, object_ptr<Ui::FlatButton>(
|
||||
parent,
|
||||
QString(),
|
||||
st::historyComposeButton))
|
||||
, _shadow(std::make_unique<Ui::PlainShadow>(_wrap.parentWidget())) {
|
||||
_wrap.hide(anim::type::instant);
|
||||
_shadow->hide();
|
||||
|
||||
setup(history);
|
||||
}
|
||||
|
||||
TranslateBar::~TranslateBar() = default;
|
||||
|
||||
void TranslateBar::updateControlsGeometry(QRect wrapGeometry) {
|
||||
const auto hidden = _wrap.isHidden() || !wrapGeometry.height();
|
||||
if (_shadow->isHidden() != hidden) {
|
||||
_shadow->setVisible(!hidden);
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateBar::setShadowGeometryPostprocess(
|
||||
Fn<QRect(QRect)> postprocess) {
|
||||
_shadowGeometryPostprocess = std::move(postprocess);
|
||||
updateShadowGeometry(_wrap.geometry());
|
||||
}
|
||||
|
||||
void TranslateBar::updateShadowGeometry(QRect wrapGeometry) {
|
||||
const auto regular = QRect(
|
||||
wrapGeometry.x(),
|
||||
wrapGeometry.y() + wrapGeometry.height(),
|
||||
wrapGeometry.width(),
|
||||
st::lineWidth);
|
||||
_shadow->setGeometry(_shadowGeometryPostprocess
|
||||
? _shadowGeometryPostprocess(regular)
|
||||
: regular);
|
||||
}
|
||||
|
||||
void TranslateBar::setup(not_null<History*> history) {
|
||||
_wrap.geometryValue(
|
||||
) | rpl::start_with_next([=](QRect rect) {
|
||||
updateShadowGeometry(rect);
|
||||
updateControlsGeometry(rect);
|
||||
}, _wrap.lifetime());
|
||||
|
||||
const auto button = static_cast<Ui::FlatButton*>(_wrap.entity());
|
||||
button->setClickedCallback([=] {
|
||||
const auto to = history->translatedTo()
|
||||
? LanguageId()
|
||||
: Core::App().settings().translateTo();
|
||||
history->translateTo(to);
|
||||
if (const auto migrated = history->migrateFrom()) {
|
||||
migrated->translateTo(to);
|
||||
}
|
||||
});
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
st::historyTranslateLabel);
|
||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
icon->resize(st::historyTranslateIcon.size());
|
||||
icon->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(icon);
|
||||
st::historyTranslateIcon.paint(p, 0, 0, icon->width());
|
||||
}, icon->lifetime());
|
||||
const auto settings = Ui::CreateChild<Ui::IconButton>(
|
||||
button,
|
||||
st::historyTranslateSettings);
|
||||
const auto updateLabelGeometry = [=] {
|
||||
const auto full = _wrap.width() - icon->width();
|
||||
const auto skip = st::semiboldFont->spacew * 2;
|
||||
const auto natural = label->naturalWidth();
|
||||
const auto top = [&] {
|
||||
return (_wrap.height() - label->height()) / 2;
|
||||
};
|
||||
if (natural <= full - 2 * (settings->width() + skip)) {
|
||||
label->resizeToWidth(natural);
|
||||
label->moveToRight((full - label->width()) / 2, top());
|
||||
} else {
|
||||
const auto available = full - settings->width() - 2 * skip;
|
||||
label->resizeToWidth(std::min(natural, available));
|
||||
label->moveToRight(settings->width() + skip, top());
|
||||
}
|
||||
icon->move(
|
||||
label->x() - icon->width(),
|
||||
(_wrap.height() - icon->height()) / 2);
|
||||
};
|
||||
|
||||
_wrap.sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
settings->moveToRight(0, 0, size.width());
|
||||
updateLabelGeometry();
|
||||
}, lifetime());
|
||||
|
||||
rpl::combine(
|
||||
Core::App().settings().translateToValue(),
|
||||
history->session().changes().historyFlagsValue(
|
||||
history,
|
||||
(Data::HistoryUpdate::Flag::TranslatedTo
|
||||
| Data::HistoryUpdate::Flag::TranslateFrom))
|
||||
) | rpl::map([=](LanguageId to, const auto&) {
|
||||
return history->translatedTo()
|
||||
? u"Show Original"_q
|
||||
: history->translateOfferedFrom()
|
||||
? u"Translate to "_q + Ui::LanguageName(to.locale())
|
||||
: QString();
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](QString phrase) {
|
||||
_shouldBeShown = !phrase.isEmpty();
|
||||
if (_shouldBeShown) {
|
||||
label->setText(phrase);
|
||||
updateLabelGeometry();
|
||||
}
|
||||
if (!_forceHidden) {
|
||||
_wrap.toggle(_shouldBeShown, anim::type::normal);
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void TranslateBar::show() {
|
||||
if (!_forceHidden) {
|
||||
return;
|
||||
}
|
||||
_forceHidden = false;
|
||||
if (_shouldBeShown) {
|
||||
_wrap.show(anim::type::instant);
|
||||
_shadow->show();
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateBar::hide() {
|
||||
if (_forceHidden) {
|
||||
return;
|
||||
}
|
||||
_forceHidden = true;
|
||||
_wrap.hide(anim::type::instant);
|
||||
_shadow->hide();
|
||||
}
|
||||
|
||||
void TranslateBar::raise() {
|
||||
_wrap.raise();
|
||||
_shadow->raise();
|
||||
}
|
||||
|
||||
void TranslateBar::finishAnimating() {
|
||||
_wrap.finishAnimating();
|
||||
}
|
||||
|
||||
void TranslateBar::move(int x, int y) {
|
||||
_wrap.move(x, y);
|
||||
}
|
||||
|
||||
void TranslateBar::resizeToWidth(int width) {
|
||||
_wrap.entity()->resizeToWidth(width);
|
||||
}
|
||||
|
||||
int TranslateBar::height() const {
|
||||
return !_forceHidden
|
||||
? _wrap.height()
|
||||
: _shouldBeShown
|
||||
? st::historyReplyHeight
|
||||
: 0;
|
||||
}
|
||||
|
||||
rpl::producer<int> TranslateBar::heightValue() const {
|
||||
return _wrap.heightValue();
|
||||
}
|
||||
|
||||
} // namespace Ui
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
|
||||
class History;
|
||||
struct LanguageId;
|
||||
|
||||
namespace Ui {
|
||||
class PlainShadow;
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class TranslateBar final {
|
||||
public:
|
||||
TranslateBar(not_null<QWidget*> parent, not_null<History*> history);
|
||||
~TranslateBar();
|
||||
|
||||
void show();
|
||||
void hide();
|
||||
void raise();
|
||||
void finishAnimating();
|
||||
|
||||
void setShadowGeometryPostprocess(Fn<QRect(QRect)> postprocess);
|
||||
|
||||
void move(int x, int y);
|
||||
void resizeToWidth(int width);
|
||||
[[nodiscard]] int height() const;
|
||||
[[nodiscard]] rpl::producer<int> heightValue() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _wrap.lifetime();
|
||||
}
|
||||
|
||||
private:
|
||||
void setup(not_null<History*> history);
|
||||
void updateShadowGeometry(QRect wrapGeometry);
|
||||
void updateControlsGeometry(QRect wrapGeometry);
|
||||
|
||||
Ui::SlideWrap<> _wrap;
|
||||
std::unique_ptr<Ui::PlainShadow> _shadow;
|
||||
Fn<QRect(QRect)> _shadowGeometryPostprocess;
|
||||
bool _shouldBeShown = false;
|
||||
bool _forceHidden = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/view/history_view_translate_tracker.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer_values.h" // Data::AmPremiumValue.
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "main/main_session.h"
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
#include "spellcheck/platform/platform_language.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kEnoughForRecognition = 10;
|
||||
constexpr auto kEnoughForTranslation = 6;
|
||||
constexpr auto kRequestLengthLimit = 24 * 1024;
|
||||
constexpr auto kRequestCountLimit = 20;
|
||||
|
||||
} // namespace
|
||||
|
||||
struct TranslateTracker::ItemForRecognize {
|
||||
uint64 generation = 0;
|
||||
MaybeLanguageId id;
|
||||
};
|
||||
|
||||
TranslateTracker::TranslateTracker(not_null<History*> history)
|
||||
: _history(history)
|
||||
, _limit(kEnoughForRecognition) {
|
||||
setup();
|
||||
}
|
||||
|
||||
TranslateTracker::~TranslateTracker() {
|
||||
cancelToRequest();
|
||||
cancelSentRequest();
|
||||
}
|
||||
|
||||
rpl::producer<bool> TranslateTracker::trackingLanguage() const {
|
||||
return _trackingLanguage.value();
|
||||
}
|
||||
|
||||
void TranslateTracker::setup() {
|
||||
const auto peer = _history->peer;
|
||||
const auto session = &_history->session();
|
||||
peer->updateFull();
|
||||
|
||||
auto translationEnabled = session->changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::TranslationDisabled
|
||||
) | rpl::map([=] {
|
||||
return peer->translationFlag() == PeerData::TranslationFlag::Enabled;
|
||||
}) | rpl::distinct_until_changed();
|
||||
|
||||
_trackingLanguage = Data::AmPremiumValue(&_history->session());
|
||||
|
||||
_trackingLanguage.value(
|
||||
) | rpl::start_with_next([=](bool tracking) {
|
||||
_trackingLifetime.destroy();
|
||||
if (tracking) {
|
||||
recognizeCollected();
|
||||
trackSkipLanguages();
|
||||
} else {
|
||||
checkRecognized({});
|
||||
_history->translateTo({});
|
||||
if (const auto migrated = _history->migrateFrom()) {
|
||||
migrated->translateTo({});
|
||||
}
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
bool TranslateTracker::enoughForRecognition() const {
|
||||
return _itemsForRecognize.size() >= kEnoughForRecognition;
|
||||
}
|
||||
|
||||
void TranslateTracker::startBunch() {
|
||||
_addedInBunch = 0;
|
||||
++_generation;
|
||||
}
|
||||
|
||||
void TranslateTracker::add(
|
||||
not_null<Element*> view,
|
||||
LanguageId translatedTo) {
|
||||
const auto item = view->data();
|
||||
const auto only = view->isOnlyEmojiAndSpaces();
|
||||
if (only != OnlyEmojiAndSpaces::Unknown) {
|
||||
item->cacheOnlyEmojiAndSpaces(only == OnlyEmojiAndSpaces::Yes);
|
||||
}
|
||||
add(item, translatedTo, false);
|
||||
}
|
||||
|
||||
void TranslateTracker::add(
|
||||
not_null<HistoryItem*> item,
|
||||
LanguageId translatedTo) {
|
||||
add(item, translatedTo, false);
|
||||
}
|
||||
|
||||
void TranslateTracker::add(
|
||||
not_null<HistoryItem*> item,
|
||||
LanguageId translatedTo,
|
||||
bool skipDependencies) {
|
||||
Expects(_addedInBunch >= 0);
|
||||
|
||||
if (item->out()
|
||||
|| item->isService()
|
||||
|| !item->isRegular()
|
||||
|| item->isOnlyEmojiAndSpaces()) {
|
||||
return;
|
||||
}
|
||||
if (item->translationShowRequiresCheck(translatedTo)) {
|
||||
_switchTranslations[item] = translatedTo;
|
||||
}
|
||||
if (!skipDependencies) {
|
||||
if (const auto reply = item->Get<HistoryMessageReply>()) {
|
||||
if (const auto to = reply->replyToMsg.get()) {
|
||||
add(to, translatedTo, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto id = item->fullId();
|
||||
const auto i = _itemsForRecognize.find(id);
|
||||
if (i != end(_itemsForRecognize)) {
|
||||
i->second.generation = _generation;
|
||||
return;
|
||||
}
|
||||
const auto &text = item->originalText().text;
|
||||
_itemsForRecognize.emplace(id, ItemForRecognize{
|
||||
.generation = _generation,
|
||||
.id = (_trackingLanguage.current()
|
||||
? Platform::Language::Recognize(text)
|
||||
: MaybeLanguageId{ text }),
|
||||
});
|
||||
++_addedInBunch;
|
||||
}
|
||||
|
||||
void TranslateTracker::switchTranslation(
|
||||
not_null<HistoryItem*> item,
|
||||
LanguageId id) {
|
||||
if (item->translationShowRequiresRequest(id)) {
|
||||
_itemsToRequest.emplace(
|
||||
item->fullId(),
|
||||
ItemToRequest{ item->originalText().text.size() });
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateTracker::finishBunch() {
|
||||
if (_addedInBunch > 0) {
|
||||
accumulate_max(_limit, _addedInBunch + kEnoughForRecognition);
|
||||
_addedInBunch = -1;
|
||||
applyLimit();
|
||||
if (_trackingLanguage.current()) {
|
||||
checkRecognized();
|
||||
}
|
||||
}
|
||||
requestSome();
|
||||
if (!_switchTranslations.empty()) {
|
||||
auto switching = base::take(_switchTranslations);
|
||||
for (const auto &[item, id] : switching) {
|
||||
switchTranslation(item, id);
|
||||
}
|
||||
_switchTranslations = std::move(switching);
|
||||
_switchTranslations.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateTracker::cancelToRequest() {
|
||||
if (!_itemsToRequest.empty()) {
|
||||
const auto owner = &_history->owner();
|
||||
for (const auto &[id, entry] : base::take(_itemsToRequest)) {
|
||||
if (const auto item = owner->message(id)) {
|
||||
item->translationShowRequiresRequest({});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateTracker::cancelSentRequest() {
|
||||
if (_requestId) {
|
||||
const auto owner = &_history->owner();
|
||||
for (const auto &id : base::take(_requested)) {
|
||||
if (const auto item = owner->message(id)) {
|
||||
item->translationShowRequiresRequest({});
|
||||
}
|
||||
}
|
||||
_history->session().api().request(base::take(_requestId)).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateTracker::requestSome() {
|
||||
if (_requestId || _itemsToRequest.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto to = _history->translatedTo();
|
||||
if (!to) {
|
||||
cancelToRequest();
|
||||
return;
|
||||
}
|
||||
_requested.clear();
|
||||
_requested.reserve(_itemsToRequest.size());
|
||||
const auto session = &_history->session();
|
||||
const auto peerId = _itemsToRequest.back().first.peer;
|
||||
auto peer = (peerId == _history->peer->id)
|
||||
? _history->peer
|
||||
: session->data().peer(peerId);
|
||||
auto length = 0;
|
||||
auto list = QVector<MTPint>();
|
||||
list.reserve(_itemsToRequest.size());
|
||||
for (auto i = _itemsToRequest.end(); i != _itemsToRequest.begin();) {
|
||||
if ((--i)->first.peer != peerId) {
|
||||
break;
|
||||
}
|
||||
length += i->second.length;
|
||||
_requested.push_back(i->first);
|
||||
list.push_back(MTP_int(i->first.msg));
|
||||
i = _itemsToRequest.erase(i);
|
||||
if (list.size() >= kRequestCountLimit
|
||||
|| length >= kRequestLengthLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
using Flag = MTPmessages_TranslateText::Flag;
|
||||
_requestId = session->api().request(MTPmessages_TranslateText(
|
||||
MTP_flags(Flag::f_peer | Flag::f_id),
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(list),
|
||||
MTPVector<MTPTextWithEntities>(),
|
||||
MTP_string(to.locale().name().mid(0, 2))
|
||||
)).done([=](const MTPmessages_TranslatedText &result) {
|
||||
requestDone(to, result.data().vresult().v);
|
||||
}).fail([=] {
|
||||
requestDone(to, {});
|
||||
}).send();
|
||||
}
|
||||
|
||||
void TranslateTracker::requestDone(
|
||||
LanguageId to,
|
||||
const QVector<MTPTextWithEntities> &list) {
|
||||
auto index = 0;
|
||||
const auto session = &_history->session();
|
||||
const auto owner = &session->data();
|
||||
for (const auto &id : base::take(_requested)) {
|
||||
if (const auto item = owner->message(id)) {
|
||||
const auto data = (index >= list.size())
|
||||
? nullptr
|
||||
: &list[index].data();
|
||||
auto text = data ? TextWithEntities{
|
||||
qs(data->vtext()),
|
||||
Api::EntitiesFromMTP(session, data->ventities().v)
|
||||
} : TextWithEntities();
|
||||
item->translationDone(to, std::move(text));
|
||||
}
|
||||
++index;
|
||||
}
|
||||
_requestId = 0;
|
||||
requestSome();
|
||||
}
|
||||
|
||||
void TranslateTracker::applyLimit() {
|
||||
const auto generationProjection = [](const auto &pair) {
|
||||
return pair.second.generation;
|
||||
};
|
||||
const auto owner = &_history->owner();
|
||||
|
||||
// Erase starting with oldest generation till items count is not too big.
|
||||
while (_itemsForRecognize.size() > _limit) {
|
||||
const auto oldest = ranges::min_element(
|
||||
_itemsForRecognize,
|
||||
ranges::less(),
|
||||
generationProjection
|
||||
)->second.generation;
|
||||
for (auto i = begin(_itemsForRecognize)
|
||||
; i != end(_itemsForRecognize);) {
|
||||
if (i->second.generation == oldest) {
|
||||
if (const auto j = _itemsToRequest.find(i->first)
|
||||
; j != end(_itemsToRequest)) {
|
||||
if (const auto item = owner->message(i->first)) {
|
||||
item->translationShowRequiresRequest({});
|
||||
}
|
||||
_itemsToRequest.erase(j);
|
||||
}
|
||||
i = _itemsForRecognize.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateTracker::recognizeCollected() {
|
||||
const auto owner = &_history->owner();
|
||||
for (auto &[id, entry] : _itemsForRecognize) {
|
||||
if (const auto text = std::get_if<QString>(&entry.id)) {
|
||||
entry.id = Platform::Language::Recognize(*text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateTracker::trackSkipLanguages() {
|
||||
Core::App().settings().skipTranslationLanguagesValue(
|
||||
) | rpl::start_with_next([=](const std::vector<LanguageId> &skip) {
|
||||
checkRecognized(skip);
|
||||
}, _trackingLifetime);
|
||||
}
|
||||
|
||||
void TranslateTracker::checkRecognized() {
|
||||
checkRecognized(Core::App().settings().skipTranslationLanguages());
|
||||
}
|
||||
|
||||
void TranslateTracker::checkRecognized(const std::vector<LanguageId> &skip) {
|
||||
if (!_trackingLanguage.current()) {
|
||||
_history->translateOfferFrom({});
|
||||
return;
|
||||
}
|
||||
auto languages = base::flat_map<LanguageId, int>();
|
||||
for (const auto &[id, entry] : _itemsForRecognize) {
|
||||
if (const auto id = std::get_if<LanguageId>(&entry.id)) {
|
||||
if (*id && !ranges::contains(skip, *id)) {
|
||||
++languages[*id];
|
||||
}
|
||||
}
|
||||
}
|
||||
using namespace base;
|
||||
const auto count = int(_itemsForRecognize.size());
|
||||
constexpr auto p = &flat_multi_map_pair_type<LanguageId, int>::second;
|
||||
const auto threshold = (count > kEnoughForRecognition)
|
||||
? (count * kEnoughForTranslation / kEnoughForRecognition)
|
||||
: _allLoaded
|
||||
? std::min(count, kEnoughForTranslation)
|
||||
: kEnoughForTranslation;
|
||||
if (ranges::accumulate(languages, 0, ranges::plus(), p) >= threshold) {
|
||||
_history->translateOfferFrom(
|
||||
ranges::max_element(languages, ranges::less(), p)->first);
|
||||
} else {
|
||||
_history->translateOfferFrom({});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class History;
|
||||
class HistoryItem;
|
||||
struct LanguageId;
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class Element;
|
||||
|
||||
class TranslateTracker final {
|
||||
public:
|
||||
explicit TranslateTracker(not_null<History*> history);
|
||||
~TranslateTracker();
|
||||
|
||||
[[nodiscard]] bool enoughForRecognition() const;
|
||||
void startBunch();
|
||||
void add(not_null<Element*> view, LanguageId translatedTo);
|
||||
void add(not_null<HistoryItem*> item, LanguageId translatedTo);
|
||||
void finishBunch();
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> trackingLanguage() const;
|
||||
|
||||
private:
|
||||
using MaybeLanguageId = std::variant<QString, LanguageId>;
|
||||
struct ItemForRecognize;
|
||||
struct ItemToRequest {
|
||||
int length = 0;
|
||||
};
|
||||
|
||||
void setup();
|
||||
void add(
|
||||
not_null<HistoryItem*> item,
|
||||
LanguageId translatedTo,
|
||||
bool skipDependencies);
|
||||
void recognizeCollected();
|
||||
void trackSkipLanguages();
|
||||
void checkRecognized();
|
||||
void checkRecognized(const std::vector<LanguageId> &skip);
|
||||
void applyLimit();
|
||||
void requestSome();
|
||||
void cancelToRequest();
|
||||
void cancelSentRequest();
|
||||
void switchTranslation(not_null<HistoryItem*> item, LanguageId id);
|
||||
|
||||
void requestDone(
|
||||
LanguageId to,
|
||||
const QVector<MTPTextWithEntities> &list);
|
||||
|
||||
const not_null<History*> _history;
|
||||
rpl::variable<bool> _trackingLanguage = false;
|
||||
base::flat_map<FullMsgId, ItemForRecognize> _itemsForRecognize;
|
||||
uint64 _generation = 0;
|
||||
int _limit = 0;
|
||||
int _addedInBunch = -1;
|
||||
bool _allLoaded = false;
|
||||
|
||||
base::flat_map<not_null<HistoryItem*>, LanguageId> _switchTranslations;
|
||||
base::flat_map<FullMsgId, ItemToRequest> _itemsToRequest;
|
||||
std::vector<FullMsgId> _requested;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
rpl::lifetime _trackingLifetime;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
@ -305,7 +305,7 @@ Ui::Text::String Media::createCaption(not_null<HistoryItem*> item) const {
|
|||
};
|
||||
result.setMarkedText(
|
||||
st::messageTextStyle,
|
||||
item->originalTextWithLocalEntities(),
|
||||
item->translatedTextWithLocalEntities(),
|
||||
Ui::ItemTextOptions(item),
|
||||
context);
|
||||
FillTextWithAnimatedSpoilers(_parent, result);
|
||||
|
|
|
@ -2300,7 +2300,7 @@ void OverlayWidget::refreshCaption() {
|
|||
return;
|
||||
}
|
||||
}
|
||||
const auto caption = _message->originalText();
|
||||
const auto caption = _message->translatedText();
|
||||
if (caption.text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1257,3 +1257,19 @@ historyHasCustomEmoji: FlatLabel(defaultFlatLabel) {
|
|||
minWidth: 80px;
|
||||
}
|
||||
historyHasCustomEmojiPosition: point(12px, 4px);
|
||||
|
||||
historyTranslateLabel: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
textFg: windowActiveTextFg;
|
||||
minWidth: 80px;
|
||||
}
|
||||
historyTranslateIcon: icon{{ "menu/translate", windowActiveTextFg }};
|
||||
historyTranslateSettings: IconButton(defaultIconButton) {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
icon: icon{{ "menu/customize", windowActiveTextFg }};
|
||||
iconOver: icon{{ "menu/customize", windowActiveTextFg }};
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 38px;
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit cf59ca87b761ab5bbd80be02a61cd38a70142898
|
||||
Subproject commit e5ac664fe397d5874a244bbbc8a7b266223cb88b
|
|
@ -1 +1 @@
|
|||
Subproject commit 43e9128014c5239a6732ae34bdfe007efb9692c8
|
||||
Subproject commit f2e698f2209a86c133261196275ca98273c7a4dc
|
Loading…
Add table
Reference in a new issue