From f5be551ff8bb16f92638d4ddb5097a4e64507851 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 30 Jan 2023 14:10:42 +0400 Subject: [PATCH] Add translation bar dropdown menu. --- Telegram/CMakeLists.txt | 5 +- Telegram/SourceFiles/boxes/language_box.cpp | 38 +- Telegram/SourceFiles/boxes/language_box.h | 2 + .../SourceFiles/boxes/premium_preview_box.cpp | 5 + .../SourceFiles/boxes/premium_preview_box.h | 1 + Telegram/SourceFiles/boxes/translate_box.cpp | 327 ++---------------- Telegram/SourceFiles/boxes/translate_box.h | 14 +- .../chat_helpers/stickers_list_footer.cpp | 5 +- Telegram/SourceFiles/history/history_item.cpp | 10 +- .../SourceFiles/history/history_widget.cpp | 1 + .../history/view/history_view_element.cpp | 10 +- .../view/history_view_translate_bar.cpp | 220 +++++++++++- .../history/view/history_view_translate_bar.h | 24 +- .../view/history_view_translate_tracker.cpp | 16 +- .../history/view/media/history_view_media.cpp | 4 +- Telegram/SourceFiles/lang/lang_instance.cpp | 9 - Telegram/SourceFiles/lang/lang_instance.h | 4 +- Telegram/SourceFiles/lang/lang_keys.cpp | 15 + Telegram/SourceFiles/lang/lang_keys.h | 2 + Telegram/SourceFiles/settings/settings.style | 1 + .../SourceFiles/settings/settings_premium.cpp | 12 + .../ui/boxes/choose_language_box.cpp | 321 +++++++++++++++++ .../ui/boxes/choose_language_box.h | 26 ++ Telegram/SourceFiles/ui/chat/chat.style | 1 + Telegram/cmake/td_ui.cmake | 3 + Telegram/cmake/telegram_options.cmake | 6 - Telegram/lib_ui | 2 +- 27 files changed, 700 insertions(+), 384 deletions(-) create mode 100644 Telegram/SourceFiles/ui/boxes/choose_language_box.cpp create mode 100644 Telegram/SourceFiles/ui/boxes/choose_language_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index e794043f2..923385559 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -12,9 +12,7 @@ add_subdirectory(lib_crl) add_subdirectory(lib_base) add_subdirectory(lib_ui) add_subdirectory(lib_tl) -if (NOT DESKTOP_APP_DISABLE_SPELLCHECK) - add_subdirectory(lib_spellcheck) -endif() +add_subdirectory(lib_spellcheck) add_subdirectory(lib_storage) add_subdirectory(lib_lottie) add_subdirectory(lib_qr) @@ -72,6 +70,7 @@ PRIVATE desktop-app::lib_crl desktop-app::lib_ui desktop-app::lib_tl + desktop-app::lib_spellcheck desktop-app::lib_storage desktop-app::lib_lottie desktop-app::lib_qr diff --git a/Telegram/SourceFiles/boxes/language_box.cpp b/Telegram/SourceFiles/boxes/language_box.cpp index 98384e5f4..2471022ac 100644 --- a/Telegram/SourceFiles/boxes/language_box.cpp +++ b/Telegram/SourceFiles/boxes/language_box.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/language_box.h" #include "lang/lang_keys.h" +#include "ui/boxes/choose_language_box.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" @@ -44,16 +45,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace { -namespace { - -[[nodiscard]] std::vector 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; @@ -1128,8 +1119,7 @@ void LanguageBox::prepare() { Core::App().saveSettingsDelayed(); }, translateEnabled->lifetime()); - using Locales = std::vector; - const auto label = lifetime().make_state>(); + using Languages = std::vector; const auto translateSkipWrap = topContainer->add( object_ptr>( topContainer, @@ -1141,28 +1131,16 @@ void LanguageBox::prepare() { const auto translateSkip = Settings::AddButtonWithLabel( translateSkipWrap->entity(), tr::lng_translate_settings_choose(), - label->events( - ) | rpl::map([](const Locales &locales) { - return (locales.size() > 1) - ? tr::lng_languages_count(tr::now, lt_count, locales.size()) - : Ui::LanguageName(locales.front()); + Core::App().settings().skipTranslationLanguagesValue( + ) | rpl::map([](const Languages &list) { + return (list.size() > 1) + ? tr::lng_languages_count(tr::now, lt_count, list.size()) + : Ui::LanguageName(list.front()); }), st::settingsButtonNoIcon); - label->fire(SkipLocalesFromSettings()); translateSkip->setClickedCallback([=] { - Ui::BoxShow(this).showBox( - Box(Ui::ChooseLanguageBox, [=](Locales locales) { - label->fire_copy(locales); - using namespace ranges::views; - Core::App().settings().setSkipTranslationLanguages(all( - locales - ) | transform([](const QLocale &l) { - return LanguageId{ l.language() }; - }) | ranges::to_vector); - Core::App().saveSettingsDelayed(); - }, SkipLocalesFromSettings()), - Ui::LayerOption::KeepOther); + Ui::BoxShow(this).showBox(Ui::EditSkipTranslationLanguages()); }); Settings::AddSkip(topContainer); Settings::AddDividerText( diff --git a/Telegram/SourceFiles/boxes/language_box.h b/Telegram/SourceFiles/boxes/language_box.h index 9f75099f8..0727ad58a 100644 --- a/Telegram/SourceFiles/boxes/language_box.h +++ b/Telegram/SourceFiles/boxes/language_box.h @@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" #include "base/binary_guard.h" +struct LanguageId; + namespace Ui { class MultiSelect; struct ScrollToRequest; diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 3f73ad166..ffc5bc709 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -114,6 +114,8 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_subtitle_profile_badge(); case PremiumPreview::AnimatedUserpics: return tr::lng_premium_summary_subtitle_animated_userpics(); + case PremiumPreview::RealTimeTranslation: + return tr::lng_premium_summary_subtitle_translation(); } Unexpected("PremiumPreview in SectionTitle."); } @@ -142,6 +144,8 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_about_profile_badge(); case PremiumPreview::AnimatedUserpics: return tr::lng_premium_summary_about_animated_userpics(); + case PremiumPreview::RealTimeTranslation: + return tr::lng_premium_summary_about_translation(); } Unexpected("PremiumPreview in SectionTitle."); } @@ -453,6 +457,7 @@ struct VideoPreviewDocument { case PremiumPreview::InfiniteReactions: return "infinite_reactions"; case PremiumPreview::ProfileBadge: return "profile_badge"; case PremiumPreview::AnimatedUserpics: return "animated_userpics"; + case PremiumPreview::RealTimeTranslation: return "translations"; } return ""; }(); diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index bca679670..3d16349b6 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -49,6 +49,7 @@ enum class PremiumPreview { AdvancedChatManagement, ProfileBadge, AnimatedUserpics, + RealTimeTranslation, kCount, }; diff --git a/Telegram/SourceFiles/boxes/translate_box.cpp b/Telegram/SourceFiles/boxes/translate_box.cpp index 7e8f3258c..116263e28 100644 --- a/Telegram/SourceFiles/boxes/translate_box.cpp +++ b/Telegram/SourceFiles/boxes/translate_box.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/sender.h" #include "settings/settings_common.h" #include "spellcheck/platform/platform_language.h" +#include "ui/boxes/choose_language_box.h" #include "ui/effects/loading_element.h" #include "ui/layers/generic_box.h" #include "ui/painter.h" @@ -36,171 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { namespace { -[[nodiscard]] std::vector Languages() { - return std::vector{ - QLocale::English, - QLocale::Afrikaans, - QLocale::Albanian, - QLocale::Amharic, - QLocale::Arabic, - QLocale::Armenian, - QLocale::Azerbaijani, - QLocale::Basque, - QLocale::Belarusian, - QLocale::Bosnian, - QLocale::Bulgarian, - QLocale::Burmese, - QLocale::Catalan, - QLocale::Chinese, - QLocale::Croatian, - QLocale::Czech, - QLocale::Danish, - QLocale::Dutch, - QLocale::Esperanto, - QLocale::Estonian, - QLocale::Finnish, - QLocale::French, - QLocale::Gaelic, - QLocale::Galician, - QLocale::Georgian, - QLocale::German, - QLocale::Greek, - QLocale::Gusii, - QLocale::Hausa, - QLocale::Hebrew, - QLocale::Hungarian, - QLocale::Icelandic, - QLocale::Igbo, - QLocale::Indonesian, - QLocale::Irish, - QLocale::Italian, - QLocale::Japanese, - QLocale::Kazakh, - QLocale::Kinyarwanda, - QLocale::Korean, - QLocale::Kurdish, - QLocale::Lao, - QLocale::Latvian, - QLocale::Lithuanian, - QLocale::Luxembourgish, - QLocale::Macedonian, - QLocale::Malagasy, - QLocale::Malay, - QLocale::Maltese, - QLocale::Maori, - QLocale::Mongolian, - QLocale::Nepali, - QLocale::Pashto, - QLocale::Persian, - QLocale::Polish, - QLocale::Portuguese, - QLocale::Romanian, - QLocale::Russian, - QLocale::Serbian, - QLocale::Shona, - QLocale::Sindhi, - QLocale::Sinhala, - QLocale::Slovak, - QLocale::Slovenian, - QLocale::Somali, - QLocale::Spanish, - QLocale::Sundanese, - QLocale::Swahili, - QLocale::Swedish, - QLocale::Tajik, - QLocale::Tatar, - QLocale::Teso, - QLocale::Thai, - QLocale::Turkish, - QLocale::Turkmen, - QLocale::Ukrainian, - QLocale::Urdu, - QLocale::Uzbek, - QLocale::Vietnamese, - QLocale::Welsh, - QLocale::WesternFrisian, - QLocale::Xhosa, - QLocale::Yiddish, - }; -} - -class Row final : public Ui::SettingsButton { -public: - Row(not_null parent, const QLocale &locale); - - [[nodiscard]] bool filtered(const QString &query) const; - [[nodiscard]] QLocale locale() const; - - int resizeGetHeight(int newWidth) override; - -protected: - void paintEvent(QPaintEvent *e) override; - -private: - const style::PeerListItem &_st; - const QLocale _locale; - const QString _status; - const QString _titleText; - Ui::Text::String _title; - -}; - -Row::Row(not_null parent, const QLocale &locale) -: SettingsButton(parent, rpl::never()) -, _st(st::inviteLinkListItem) -, _locale(locale) -, _status(QLocale::languageToString(locale.language())) -, _titleText(LanguageName(locale)) -, _title(_st.nameStyle, _titleText) { -} - -QLocale Row::locale() const { - return _locale; -} - -bool Row::filtered(const QString &query) const { - return _status.startsWith(query, Qt::CaseInsensitive) - || _titleText.startsWith(query, Qt::CaseInsensitive); -} - -int Row::resizeGetHeight(int newWidth) { - return _st.height; -} - -void Row::paintEvent(QPaintEvent *e) { - auto p = Painter(this); - - const auto paintOver = (isOver() || isDown()) && !isDisabled(); - Ui::SettingsButton::paintBg(p, e->rect(), paintOver); - Ui::SettingsButton::paintRipple(p, 0, 0); - Ui::SettingsButton::paintToggle(p, width()); - - const auto &color = st::windowSubTextFg; - p.setPen(Qt::NoPen); - p.setBrush(color); - - const auto left = st::settingsSubsectionTitlePadding.left(); - const auto toggleRect = Ui::SettingsButton::maybeToggleRect(); - const auto right = left - + (toggleRect.isEmpty() ? 0 : (width() - toggleRect.x())); - - p.setPen(_st.nameFg); - _title.drawLeft( - p, - left, - _st.namePosition.y(), - width() - left - right, - width() - left - right); - - p.setPen(paintOver ? _st.statusFgOver : _st.statusFg); - p.setFont(st::contactsStatusFont); - p.drawTextLeft( - left, - _st.statusPosition.y(), - width() - left - right, - _status); -} - class ShowButton final : public RpWidget { public: ShowButton(not_null parent); @@ -250,19 +86,6 @@ rpl::producer ShowButton::clicks() const { } // namespace -QString LanguageName(const QLocale &locale) { - if (locale.language() == QLocale::English - && (locale.country() == QLocale::UnitedStates - || locale.country() == QLocale::AnyCountry)) { - return u"English"_q; - } else if (locale.language() == QLocale::Spanish) { - return QString::fromUtf8("\x45\x73\x70\x61\xc3\xb1\x6f\x6c"); - } else { - const auto name = locale.nativeLanguageName(); - return name.left(1).toUpper() + name.mid(1); - } -} - void TranslateBox( not_null box, not_null peer, @@ -272,14 +95,10 @@ void TranslateBox( box->setWidth(st::boxWideWidth); box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); const auto container = box->verticalLayout(); - const auto translateTo = Core::App().settings().translateTo().locale(); + auto id = Core::App().settings().translateToValue(); const auto api = box->lifetime().make_state( &peer->session().mtp()); - struct State { - rpl::event_stream locale; - }; - const auto state = box->lifetime().make_state(); text.entities = ranges::views::all( text.entities @@ -355,11 +174,10 @@ void TranslateBox( const auto padding = st::settingsSubsectionTitlePadding; const auto subtitle = Settings::AddSubsectionTitle( container, - state->locale.events() | rpl::map(LanguageName)); + rpl::duplicate(id) | rpl::map(LanguageName)); // Workaround. - state->locale.events( - ) | rpl::start_with_next([=] { + rpl::duplicate(id) | rpl::start_with_next([=] { subtitle->resizeToWidth(container->width() - padding.left() - padding.right()); @@ -370,7 +188,6 @@ void TranslateBox( box, object_ptr(box, stLabel))); translated->entity()->setSelectable(!hasCopyRestriction); - translated->hide(anim::type::instant); constexpr auto kMaxLines = 3; container->resizeToWidth(box->width()); @@ -380,10 +197,9 @@ void TranslateBox( box, st::aboutLabel, std::min(original->entity()->height() / lineHeight, kMaxLines), - state->locale.events() | rpl::map([=](const QLocale &locale) { - return locale.textDirection() == Qt::RightToLeft; + rpl::duplicate(id) | rpl::map([=](LanguageId id) { + return id.locale().textDirection() == Qt::RightToLeft; })))); - loading->show(anim::type::instant); const auto showText = [=](const QString &text) { translated->entity()->setText(text); @@ -391,7 +207,9 @@ void TranslateBox( loading->hide(anim::type::instant); }; - const auto send = [=](const QString &toLang) { + const auto send = [=](LanguageId to) { + loading->show(anim::type::instant); + translated->hide(anim::type::instant); api->request(MTPmessages_TranslateText( MTP_flags(flags), msgId ? peer->input : MTP_inputPeerEmpty(), @@ -403,7 +221,7 @@ void TranslateBox( : MTP_vector(1, MTP_textWithEntities( MTP_string(text.text), MTP_vector()))), - MTP_string(toLang.mid(0, 2)) + MTP_string(to.locale().name().mid(0, 2)) )).done([=](const MTPmessages_TranslatedText &result) { const auto &data = result.data(); const auto &list = data.vresult().v; @@ -414,117 +232,16 @@ void TranslateBox( showText(tr::lng_translate_box_error(tr::now)); }).send(); }; - send(translateTo.name()); - state->locale.fire_copy(translateTo); + std::move(id) | rpl::start_with_next(send, box->lifetime()); box->addLeftButton(tr::lng_settings_language(), [=] { if (loading->toggled()) { return; } - Ui::BoxShow(box).showBox(Box(ChooseLanguageBox, [=]( - std::vector locales) { - const auto &locale = locales.front(); - send(locale.name()); - state->locale.fire_copy(locale); - loading->show(anim::type::instant); - translated->hide(anim::type::instant); - }, std::vector())); + Ui::BoxShow(box).showBox(ChooseTranslateToBox()); }); } -void ChooseLanguageBox( - not_null box, - Fn)> callback, - std::vector toggled) { - box->setMinHeight(st::boxWidth); - box->setMaxHeight(st::boxWidth); - box->setTitle(tr::lng_languages()); - - const auto hasToggled = !toggled.empty(); - - const auto multiSelect = box->setPinnedToTopContent( - object_ptr( - box, - st::defaultMultiSelect, - tr::lng_participant_filter())); - box->setFocusCallback([=] { multiSelect->setInnerFocus(); }); - - const auto container = box->verticalLayout(); - const auto langs = [&] { - auto langs = Languages(); - const auto current = QLocale( - Lang::LanguageIdOrDefault(Lang::Id())).language(); - if (const auto it = ranges::find(langs, current); it != end(langs)) { - base::reorder(langs, std::distance(begin(langs), it), 0); - } - return langs; - }(); - auto rows = std::vector*>>(); - rows.reserve(langs.size()); - for (const auto &lang : langs) { - const auto locale = QLocale(lang); - const auto button = container->add( - object_ptr>( - container, - object_ptr(container, locale))); - if (hasToggled) { - button->entity()->toggleOn( - rpl::single(ranges::contains(toggled, locale)), - false); - } else { - button->entity()->setClickedCallback([=] { - callback({ locale }); - box->closeBox(); - }); - } - rows.push_back(button); - } - - multiSelect->setQueryChangedCallback([=](const QString &query) { - for (const auto &row : rows) { - const auto toggled = row->entity()->filtered(query); - if (toggled != row->toggled()) { - row->toggle(toggled, anim::type::instant); - } - } - }); - - { - const auto label = Ui::CreateChild( - box.get(), - tr::lng_languages_none(), - st::membersAbout); - box->verticalLayout()->geometryValue( - ) | rpl::start_with_next([=](const QRect &geometry) { - const auto shown = (geometry.height() <= 0); - label->setVisible(shown); - if (shown) { - label->moveToLeft( - (geometry.width() - label->width()) / 2, - geometry.y() + st::membersAbout.style.font->height * 4); - label->stackUnder(box->verticalLayout()); - } - }, label->lifetime()); - } - - if (hasToggled) { - box->addButton(tr::lng_settings_save(), [=] { - auto result = ranges::views::all( - rows - ) | ranges::views::filter([](const auto &row) { - return row->entity()->toggled(); - }) | ranges::views::transform([](const auto &row) { - return row->entity()->locale(); - }) | ranges::to_vector; - if (!result.empty()) { - callback(std::move(result)); - } - box->closeBox(); - }); - } - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); -} - bool SkipTranslate(TextWithEntities textWithEntities) { const auto &text = textWithEntities.text; if (text.isEmpty()) { @@ -557,4 +274,24 @@ bool SkipTranslate(TextWithEntities textWithEntities) { #endif } +object_ptr EditSkipTranslationLanguages() { + auto title = tr::lng_translate_settings_choose(); + return Box(ChooseLanguageBox, std::move(title), [=]( + std::vector &&list) { + Core::App().settings().setSkipTranslationLanguages( + std::move(list)); + Core::App().saveSettingsDelayed(); + }, Core::App().settings().skipTranslationLanguages()); +} + +object_ptr ChooseTranslateToBox() { + return Box(ChooseLanguageBox, tr::lng_languages(), [=]( + const std::vector &ids) { + Expects(!ids.empty()); + + Core::App().settings().setTranslateTo(ids.front()); + Core::App().saveSettingsDelayed(); + }, std::nullopt); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/translate_box.h b/Telegram/SourceFiles/boxes/translate_box.h index 760e0c8d5..04df4b1f1 100644 --- a/Telegram/SourceFiles/boxes/translate_box.h +++ b/Telegram/SourceFiles/boxes/translate_box.h @@ -7,16 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/object_ptr.h" + class PeerData; +struct LanguageId; namespace Ui { +class BoxContent; class GenericBox; -[[nodiscard]] QString LanguageName(const QLocale &locale); - void TranslateBox( - not_null box, + not_null box, not_null peer, MsgId msgId, TextWithEntities text, @@ -24,9 +26,7 @@ void TranslateBox( [[nodiscard]] bool SkipTranslate(TextWithEntities textWithEntities); -void ChooseLanguageBox( - not_null box, - Fn)> callback, - std::vector toggled); +[[nodiscard]] object_ptr EditSkipTranslationLanguages(); +[[nodiscard]] object_ptr ChooseTranslateToBox(); } // namespace Ui diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 604a15652..26646b1e0 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -1447,6 +1447,7 @@ void StickersListFooter::paintSetIconToCache( p.restore(); } else { paintOne(0, [&] { + const auto selected = (info.index == _iconState.selected); if (icon.setId == Data::Stickers::FeaturedSetId) { const auto &stickers = _session->data().stickers(); return stickers.featuredSetsUnreadCount() @@ -1457,9 +1458,9 @@ void StickersListFooter::paintSetIconToCache( } else if (icon.setId == AllEmojiSectionSetId()) { return &st::emojiPeople; } else if (const auto section = SetIdEmojiSection(icon.setId)) { - return sectionIcon(*section, false); + return sectionIcon(*section, selected); } - return &st::emojiRecent; + return sectionIcon(Section::Recent, selected); }()); } } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index f44aba75f..e9ebb96cf 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -78,9 +78,9 @@ constexpr auto kPinnedMessageTextLimit = 16; using ItemPreview = HistoryView::ItemPreview; -[[nodiscard]] bool IsOnlyEmojiAndSpaces(const QString &text) { +[[nodiscard]] bool HasNotEmojiAndSpaces(const QString &text) { if (text.isEmpty()) { - return true; + return false; } auto emoji = 0; auto start = text.data(); @@ -91,10 +91,10 @@ using ItemPreview = HistoryView::ItemPreview; } else if (Ui::Emoji::Find(start, end, &emoji)) { start += emoji; } else { - return false; + return true; } } - return true; + return false; } } // namespace @@ -4682,7 +4682,7 @@ void HistoryItem::cacheOnlyEmojiAndSpaces(bool only) { bool HistoryItem::isOnlyEmojiAndSpaces() const { if (!(_flags & MessageFlag::OnlyEmojiAndSpacesSet)) { const_cast(this)->cacheOnlyEmojiAndSpaces( - IsOnlyEmojiAndSpaces(_text.text)); + !HasNotEmojiAndSpaces(_text.text)); } return (_flags & MessageFlag::OnlyEmojiAndSpaces); } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index b231f74ab..226a72380 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -6254,6 +6254,7 @@ void HistoryWidget::setupTranslateBar() { _translateBar = std::make_unique( this, + controller(), _history); controller()->adaptive().oneColumnValue( diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 8477dcfd5..0bab7aee2 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -655,10 +655,12 @@ const Ui::Text::String &Element::text() const { OnlyEmojiAndSpaces Element::isOnlyEmojiAndSpaces() const { if (data()->Has()) { return OnlyEmojiAndSpaces::No; - } else if (!_text.isEmpty() || data()->originalText().empty()) { - return _text.isOnlyEmojiAndSpaces() - ? OnlyEmojiAndSpaces::Yes - : OnlyEmojiAndSpaces::No; + } else if (!_text.isEmpty()) { + return _text.hasNotEmojiAndSpaces() + ? OnlyEmojiAndSpaces::No + : OnlyEmojiAndSpaces::Yes; + } else if (data()->originalText().empty()) { + return OnlyEmojiAndSpaces::Yes; } else { return OnlyEmojiAndSpaces::Unknown; } diff --git a/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp b/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp index cdf6e4de6..9741443cc 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp @@ -7,26 +7,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_translate_bar.h" -#include "boxes/translate_box.h" // Ui::LanguageName. +#include "boxes/translate_box.h" #include "core/application.h" #include "core/core_settings.h" #include "data/data_changes.h" #include "history/history.h" +#include "lang/lang_keys.h" #include "main/main_session.h" #include "spellcheck/spellcheck_types.h" +#include "ui/boxes/choose_language_box.h" // EditSkipTranslationLanguages. +#include "ui/layers/box_content.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/widgets/popup_menu.h" #include "ui/widgets/shadow.h" +#include "window/window_session_controller.h" #include "styles/style_chat.h" +#include "styles/style_menu_icons.h" #include namespace HistoryView { +namespace { + +constexpr auto kToastDuration = 4 * crl::time(1000); + +} // namespace TranslateBar::TranslateBar( not_null parent, + not_null controller, not_null history) -: _wrap(parent, object_ptr( +: _controller(controller) +, _history(history) +, _wrap(parent, object_ptr( parent, QString(), st::historyComposeButton)) @@ -95,6 +111,8 @@ void TranslateBar::setup(not_null history) { const auto settings = Ui::CreateChild( button, st::historyTranslateSettings); + settings->setClickedCallback([=] { showMenu(createMenu(settings)); }); + const auto updateLabelGeometry = [=] { const auto full = _wrap.width() - icon->width(); const auto skip = st::semiboldFont->spacew * 2; @@ -125,14 +143,23 @@ void TranslateBar::setup(not_null history) { 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 + | Data::HistoryUpdate::Flag::TranslateFrom)), + history->session().changes().peerFlagsValue( + history->peer, + Data::PeerUpdate::Flag::TranslationDisabled) + ) | rpl::map([=](LanguageId to, const auto&, const auto&) { + using Flag = PeerData::TranslationFlag; + return (history->peer->translationFlag() != Flag::Enabled) + ? rpl::single(QString()) + : history->translatedTo() + ? tr::lng_translate_show_original() : history->translateOfferedFrom() - ? u"Translate to "_q + Ui::LanguageName(to.locale()) - : QString(); - }) | rpl::distinct_until_changed( + ? tr::lng_translate_bar_to( + lt_name, + rpl::single(Ui::LanguageName(to))) + : rpl::single(QString()); + }) | rpl::flatten_latest( + ) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](QString phrase) { _shouldBeShown = !phrase.isEmpty(); if (_shouldBeShown) { @@ -145,6 +172,181 @@ void TranslateBar::setup(not_null history) { }, lifetime()); } +base::unique_qptr TranslateBar::createMenu( + not_null button) { + if (_menu) { + return nullptr; + } + auto result = base::make_unique_q( + &_wrap, + st::popupMenuExpandedSeparator); + result->setDestroyedCallback([ + this, + weak = Ui::MakeWeak(&_wrap), + weakButton = Ui::MakeWeak(button), + menu = result.get() + ] { + if (weak && _menu == menu) { + if (weakButton) { + weakButton->setForceRippled(false); + } + } + }); + button->setForceRippled(true); + return result; +} + +void TranslateBar::showMenu(base::unique_qptr menu) { + if (!menu) { + return; + } + _menu = std::move(menu); + _menu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight); + + const auto weak = base::make_weak(_controller); + _menu->addAction(tr::lng_translate_menu_to(tr::now), [=] { + if (const auto strong = weak.get()) { + strong->show(Ui::ChooseTranslateToBox()); + } + }, &st::menuIconTranslate); + _menu->addSeparator(); + const auto history = _history; + if (const auto translateOfferedFrom = _history->translateOfferedFrom()) { + const auto name = Ui::LanguageName(translateOfferedFrom); + const auto addToIgnoreList = [=] { + showSettingsToast(history->peer, translateOfferedFrom); + + history->peer->saveTranslationDisabled(true); + + auto &settings = Core::App().settings(); + auto skip = settings.skipTranslationLanguages(); + if (!ranges::contains(skip, translateOfferedFrom)) { + skip.push_back(translateOfferedFrom); + } + settings.setSkipTranslationLanguages(std::move(skip)); + Core::App().saveSettingsDelayed(); + }; + _menu->addAction( + tr::lng_translate_menu_dont(tr::now, lt_name, name), + addToIgnoreList, + &st::menuIconBlock); + } + const auto hideBar = [=] { + showHiddenToast(history->peer); + + history->peer->saveTranslationDisabled(true); + }; + _menu->addAction( + tr::lng_translate_menu_hide(tr::now), + hideBar, + &st::menuIconCancel); + _menu->popup(_wrap.mapToGlobal( + QPoint(_wrap.width(), 0) + st::historyTranslateMenuPosition)); +} + +void TranslateBar::showSettingsToast( + not_null peer, + LanguageId ignored) { + const auto weak = base::make_weak(_controller); + const auto text = tr::lng_translate_dont_added( + tr::now, + lt_name, + Ui::Text::Bold(Ui::LanguageName(ignored)), + Ui::Text::WithEntities); + showToast(text, tr::lng_translate_settings(tr::now), [=] { + if (const auto strong = weak.get()) { + const auto box = strong->show( + Ui::EditSkipTranslationLanguages()); + if (box) { + box->boxClosing() | rpl::start_with_next([=] { + const auto in = ranges::contains( + Core::App().settings().skipTranslationLanguages(), + ignored); + if (!in && weak) { + peer->saveTranslationDisabled(false); + } + }, box->lifetime()); + } + } + }); +} + +void TranslateBar::showHiddenToast(not_null peer) { + const auto &phrase = peer->isUser() + ? tr::lng_translate_hidden_user + : peer->isBroadcast() + ? tr::lng_translate_hidden_channel + : tr::lng_translate_hidden_group; + const auto proj = Ui::Text::WithEntities; + showToast(phrase(tr::now, proj), tr::lng_translate_undo(tr::now), [=] { + peer->saveTranslationDisabled(false); + }); +} + +void TranslateBar::showToast( + TextWithEntities text, + const QString &buttonText, + Fn buttonCallback) { + const auto st = std::make_shared(st::historyPremiumToast); + const auto skip = st->padding.top(); + st->padding.setRight(st::historyPremiumViewSet.font->width(buttonText) + - st::historyPremiumViewSet.width); + + const auto weak = Ui::Toast::Show(_wrap.window(), Ui::Toast::Config{ + .text = std::move(text), + .st = st.get(), + .durationMs = kToastDuration, + .multiline = true, + .dark = true, + .slideSide = RectPart::Bottom, + }); + const auto strong = weak.get(); + if (!strong) { + return; + } + strong->setInputUsed(true); + const auto widget = strong->widget(); + widget->lifetime().add([st] {}); + const auto hideToast = [weak] { + if (const auto strong = weak.get()) { + strong->hideAnimated(); + } + }; + + const auto clickableBackground = Ui::CreateChild( + widget.get()); + clickableBackground->setPointerCursor(false); + clickableBackground->setAcceptBoth(); + clickableBackground->show(); + clickableBackground->addClickHandler([=](Qt::MouseButton button) { + if (button == Qt::RightButton) { + hideToast(); + } + }); + + const auto button = Ui::CreateChild( + widget.get(), + rpl::single(buttonText), + st::historyPremiumViewSet); + button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + button->show(); + rpl::combine( + widget->sizeValue(), + button->sizeValue() + ) | rpl::start_with_next([=](QSize outer, QSize inner) { + button->moveToRight( + 0, + (outer.height() - inner.height()) / 2, + outer.width()); + clickableBackground->resize(outer); + }, widget->lifetime()); + + button->setClickedCallback([=] { + buttonCallback(); + hideToast(); + }); +} + void TranslateBar::show() { if (!_forceHidden) { return; diff --git a/Telegram/SourceFiles/history/view/history_view_translate_bar.h b/Telegram/SourceFiles/history/view/history_view_translate_bar.h index 4f580f463..7d5b08690 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_bar.h +++ b/Telegram/SourceFiles/history/view/history_view_translate_bar.h @@ -14,13 +14,22 @@ struct LanguageId; namespace Ui { class PlainShadow; +class PopupMenu; +class IconButton; } // namespace Ui +namespace Window { +class SessionController; +} // namespace Window + namespace HistoryView { class TranslateBar final { public: - TranslateBar(not_null parent, not_null history); + TranslateBar( + not_null parent, + not_null controller, + not_null history); ~TranslateBar(); void show(); @@ -43,10 +52,23 @@ private: void setup(not_null history); void updateShadowGeometry(QRect wrapGeometry); void updateControlsGeometry(QRect wrapGeometry); + [[nodiscard]] base::unique_qptr createMenu( + not_null button); + void showMenu(base::unique_qptr menu); + void showHiddenToast(not_null peer); + void showSettingsToast(not_null peer, LanguageId ignored); + void showToast( + TextWithEntities text, + const QString &buttonText, + Fn buttonCallback); + + const not_null _controller; + const not_null _history; Ui::SlideWrap<> _wrap; std::unique_ptr _shadow; Fn _shadowGeometryPostprocess; + base::unique_qptr _menu; bool _shouldBeShown = false; bool _forceHidden = false; diff --git a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp index 01a2f5252..716a11dfa 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp @@ -57,13 +57,6 @@ void TranslateTracker::setup() { 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( @@ -340,7 +333,14 @@ void TranslateTracker::checkRecognized(const std::vector &skip) { : _allLoaded ? std::min(count, kEnoughForTranslation) : kEnoughForTranslation; - if (ranges::accumulate(languages, 0, ranges::plus(), p) >= threshold) { + const auto translatable = ranges::accumulate( + languages, + 0, + ranges::plus(), + p); + if (count < kEnoughForTranslation) { + // Don't change offer by small amount of messages. + } else if (translatable >= threshold) { _history->translateOfferFrom( ranges::max_element(languages, ranges::less(), p)->first); } else { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 353aa24fc..2d252eb59 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -121,7 +121,9 @@ TextWithEntities AddTimestampLinks( return text; } static const auto expression = QRegularExpression( - "(? #include "lang_auto.h" +#include "base/const_string.h" #include "base/weak_ptr.h" namespace Lang { @@ -31,8 +31,6 @@ inline bool operator!=(const Language &a, const Language &b) { return !(a == b); } -QString DefaultLanguageId(); -QString LanguageIdOrDefault(const QString &id); QString CloudLangPackName(); QString CustomLanguageId(); Language DefaultLanguage(); diff --git a/Telegram/SourceFiles/lang/lang_keys.cpp b/Telegram/SourceFiles/lang/lang_keys.cpp index 4da803e42..51f581a04 100644 --- a/Telegram/SourceFiles/lang/lang_keys.cpp +++ b/Telegram/SourceFiles/lang/lang_keys.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "lang/lang_keys.h" +#include "base/const_string.h" #include "lang/lang_file_parser.h" #include "ui/integration.h" @@ -14,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { +constexpr auto kDefaultLanguage = "en"_cs; + template inline QString langDateMaybeWithYear( QDate date, @@ -231,3 +234,15 @@ QString langDateTimeFull(const QDateTime &date) { lt_time, QLocale().toString(date.time(), QLocale::ShortFormat)); } + +namespace Lang { + +QString DefaultLanguageId() { + return kDefaultLanguage.utf16(); +} + +QString LanguageIdOrDefault(const QString &id) { + return !id.isEmpty() ? id : DefaultLanguageId(); +} + +} // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_keys.h b/Telegram/SourceFiles/lang/lang_keys.h index d4254cd49..87934fcce 100644 --- a/Telegram/SourceFiles/lang/lang_keys.h +++ b/Telegram/SourceFiles/lang/lang_keys.h @@ -34,5 +34,7 @@ namespace Lang { [[nodiscard]] QString Id(); [[nodiscard]] rpl::producer<> Updated(); [[nodiscard]] QString GetNonDefaultValue(const QByteArray &key); +[[nodiscard]] QString DefaultLanguageId(); +[[nodiscard]] QString LanguageIdOrDefault(const QString &id); } // namespace Lang diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 1b322bd9d..ebf77cdb6 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -119,6 +119,7 @@ settingsPremiumIconSpeed: icon {{ "settings/premium/speed", settingsIconFg }}; settingsPremiumIconStar: icon {{ "settings/premium/star", settingsIconFg }}; settingsPremiumIconVoice: icon {{ "settings/premium/voice", settingsIconFg }}; settingsPremiumIconFiles: icon {{ "settings/premium/files", settingsIconFg }}; +settingsPremiumIconTranslations: icon {{ "settings/premium/translations", settingsIconFg }}; settingsTTLChatsOff: icon {{ "settings/ttl/autodelete_off", windowSubTextFg }}; settingsTTLChatsOn: icon {{ "settings/ttl/autodelete_on", windowActiveTextFg }}; diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 1d94c60e9..8f7eca2ec 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -221,6 +221,7 @@ using Order = std::vector; u"advanced_chat_management"_q, u"profile_badge"_q, u"animated_userpics"_q, + u"translations"_q, }; } @@ -333,6 +334,15 @@ using Order = std::vector; PremiumPreview::AnimatedUserpics, }, }, + { + u"translations"_q, + Entry{ + &st::settingsPremiumIconTranslations, + tr::lng_premium_summary_subtitle_translation(), + tr::lng_premium_summary_about_translation(), + PremiumPreview::RealTimeTranslation, + }, + }, }; } @@ -1887,6 +1897,8 @@ not_null CreateSubscribeButton( return PremiumPreview::ProfileBadge; } else if (s == u"animated_userpics"_q) { return PremiumPreview::AnimatedUserpics; + } else if (s == u"translations"_q) { + return PremiumPreview::RealTimeTranslation; } return PremiumPreview::kCount; }) | ranges::views::filter([](PremiumPreview type) { diff --git a/Telegram/SourceFiles/ui/boxes/choose_language_box.cpp b/Telegram/SourceFiles/ui/boxes/choose_language_box.cpp new file mode 100644 index 000000000..35726e786 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/choose_language_box.cpp @@ -0,0 +1,321 @@ +/* +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 "ui/boxes/choose_language_box.h" + +#include "lang/lang_keys.h" +#include "spellcheck/spellcheck_types.h" +#include "ui/layers/generic_box.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/multi_select.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/painter.h" +#include "styles/style_info.h" +#include "styles/style_layers.h" +#include "styles/style_settings.h" + +namespace Ui { +namespace { + +const auto kLanguageNamePrefix = "cloud_lng_passport_in_"; + +[[nodiscard]] std::vector TranslationLanguagesList() { + return { + { QLocale::English }, + { QLocale::Afrikaans }, + { QLocale::Albanian }, + { QLocale::Amharic }, + { QLocale::Arabic }, + { QLocale::Armenian }, + { QLocale::Azerbaijani }, + { QLocale::Basque }, + { QLocale::Belarusian }, + { QLocale::Bosnian }, + { QLocale::Bulgarian }, + { QLocale::Burmese }, + { QLocale::Catalan }, + { QLocale::Chinese }, + { QLocale::Croatian }, + { QLocale::Czech }, + { QLocale::Danish }, + { QLocale::Dutch }, + { QLocale::Esperanto }, + { QLocale::Estonian }, + { QLocale::Finnish }, + { QLocale::French }, + { QLocale::Gaelic }, + { QLocale::Galician }, + { QLocale::Georgian }, + { QLocale::German }, + { QLocale::Greek }, + { QLocale::Gusii }, + { QLocale::Hausa }, + { QLocale::Hebrew }, + { QLocale::Hungarian }, + { QLocale::Icelandic }, + { QLocale::Igbo }, + { QLocale::Indonesian }, + { QLocale::Irish }, + { QLocale::Italian }, + { QLocale::Japanese }, + { QLocale::Kazakh }, + { QLocale::Kinyarwanda }, + { QLocale::Korean }, + { QLocale::Kurdish }, + { QLocale::Lao }, + { QLocale::Latvian }, + { QLocale::Lithuanian }, + { QLocale::Luxembourgish }, + { QLocale::Macedonian }, + { QLocale::Malagasy }, + { QLocale::Malay }, + { QLocale::Maltese }, + { QLocale::Maori }, + { QLocale::Mongolian }, + { QLocale::Nepali }, + { QLocale::Pashto }, + { QLocale::Persian }, + { QLocale::Polish }, + { QLocale::Portuguese }, + { QLocale::Romanian }, + { QLocale::Russian }, + { QLocale::Serbian }, + { QLocale::Shona }, + { QLocale::Sindhi }, + { QLocale::Sinhala }, + { QLocale::Slovak }, + { QLocale::Slovenian }, + { QLocale::Somali }, + { QLocale::Spanish }, + { QLocale::Sundanese }, + { QLocale::Swahili }, + { QLocale::Swedish }, + { QLocale::Tajik }, + { QLocale::Tatar }, + { QLocale::Teso }, + { QLocale::Thai }, + { QLocale::Turkish }, + { QLocale::Turkmen }, + { QLocale::Ukrainian }, + { QLocale::Urdu }, + { QLocale::Uzbek }, + { QLocale::Vietnamese }, + { QLocale::Welsh }, + { QLocale::WesternFrisian }, + { QLocale::Xhosa }, + { QLocale::Yiddish }, + }; +} + +class Row final : public SettingsButton { +public: + Row(not_null parent, LanguageId id); + + [[nodiscard]] bool filtered(const QString &query) const; + [[nodiscard]] LanguageId id() const; + + int resizeGetHeight(int newWidth) override; + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + const style::PeerListItem &_st; + const LanguageId _id; + const QString _status; + const QString _titleText; + Text::String _title; + +}; + +Row::Row(not_null parent, LanguageId id) +: SettingsButton(parent, rpl::never()) +, _st(st::inviteLinkListItem) +, _id(id) +, _status(LanguageName(id)) +, _titleText(LanguageNameNative(id)) +, _title(_st.nameStyle, _titleText) { +} + +LanguageId Row::id() const { + return _id; +} + +bool Row::filtered(const QString &query) const { + return _status.startsWith(query, Qt::CaseInsensitive) + || _titleText.startsWith(query, Qt::CaseInsensitive); +} + +int Row::resizeGetHeight(int newWidth) { + return _st.height; +} + +void Row::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + + const auto paintOver = (isOver() || isDown()) && !isDisabled(); + SettingsButton::paintBg(p, e->rect(), paintOver); + SettingsButton::paintRipple(p, 0, 0); + SettingsButton::paintToggle(p, width()); + + const auto &color = st::windowSubTextFg; + p.setPen(Qt::NoPen); + p.setBrush(color); + + const auto left = st::settingsSubsectionTitlePadding.left(); + const auto toggleRect = SettingsButton::maybeToggleRect(); + const auto right = left + + (toggleRect.isEmpty() ? 0 : (width() - toggleRect.x())); + + const auto availableWidth = std::min( + _title.maxWidth(), + width() - left - right); + p.setPen(_st.nameFg); + _title.drawLeft( + p, + left, + _st.namePosition.y(), + availableWidth, + width() - left - right); + + p.setPen(paintOver ? _st.statusFgOver : _st.statusFg); + p.setFont(st::contactsStatusFont); + p.drawTextLeft( + left, + _st.statusPosition.y(), + width() - left - right, + _status); +} + +} // namespace + +QString LanguageNameTranslated(const QString &twoLetterCode) { + return Lang::GetNonDefaultValue( + kLanguageNamePrefix + twoLetterCode.toUtf8()); +} + +QString LanguageName(LanguageId id) { + const auto code = id.locale().name().toLower().mid(0, 2); + const auto translated = LanguageNameTranslated(code); + return translated.isEmpty() + ? QLocale::languageToString(id.locale().language()) + : translated; +} + +QString LanguageNameNative(LanguageId id) { + const auto locale = id.locale(); + if (locale.language() == QLocale::English + && (locale.country() == QLocale::UnitedStates + || locale.country() == QLocale::AnyCountry)) { + return u"English"_q; + } else if (locale.language() == QLocale::Spanish) { + return QString::fromUtf8("\x45\x73\x70\x61\xc3\xb1\x6f\x6c"); + } else { + const auto name = locale.nativeLanguageName(); + return name.left(1).toUpper() + name.mid(1); + } +} + +void ChooseLanguageBox( + not_null box, + rpl::producer title, + Fn)> callback, + std::optional> toggled) { + box->setMinHeight(st::boxWidth); + box->setMaxHeight(st::boxWidth); + box->setTitle(std::move(title)); + + const auto hasToggles = toggled.has_value(); + + const auto multiSelect = box->setPinnedToTopContent( + object_ptr( + box, + st::defaultMultiSelect, + tr::lng_participant_filter())); + box->setFocusCallback([=] { multiSelect->setInnerFocus(); }); + + const auto container = box->verticalLayout(); + const auto langs = [&] { + auto list = TranslationLanguagesList(); + const auto current = LanguageId{ QLocale( + Lang::LanguageIdOrDefault(Lang::Id())).language() }; + if (const auto i = ranges::find(list, current); i != end(list)) { + base::reorder(list, std::distance(begin(list), i), 0); + } + if (hasToggles) { + ranges::stable_partition(list, [&](LanguageId id) { + return ranges::contains(*toggled, id); + }); + } + return list; + }(); + auto rows = std::vector*>>(); + rows.reserve(langs.size()); + for (const auto &id : langs) { + const auto button = container->add( + object_ptr>( + container, + object_ptr(container, id))); + if (hasToggles) { + button->entity()->toggleOn( + rpl::single(ranges::contains(*toggled, id)), + false); + } else { + button->entity()->setClickedCallback([=] { + callback({ id }); + box->closeBox(); + }); + } + rows.push_back(button); + } + + multiSelect->setQueryChangedCallback([=](const QString &query) { + for (const auto &row : rows) { + const auto toggled = row->entity()->filtered(query); + if (toggled != row->toggled()) { + row->toggle(toggled, anim::type::instant); + } + } + }); + + { + const auto label = CreateChild( + box.get(), + tr::lng_languages_none(), + st::membersAbout); + box->verticalLayout()->geometryValue( + ) | rpl::start_with_next([=](const QRect &geometry) { + const auto shown = (geometry.height() <= 0); + label->setVisible(shown); + if (shown) { + label->moveToLeft( + (geometry.width() - label->width()) / 2, + geometry.y() + st::membersAbout.style.font->height * 4); + label->stackUnder(box->verticalLayout()); + } + }, label->lifetime()); + } + + if (hasToggles) { + box->addButton(tr::lng_settings_save(), [=] { + auto result = ranges::views::all( + rows + ) | ranges::views::filter([](const auto &row) { + return row->entity()->toggled(); + }) | ranges::views::transform([](const auto &row) { + return row->entity()->id(); + }) | ranges::to_vector; + if (!result.empty()) { + callback(std::move(result)); + } + box->closeBox(); + }); + } + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/choose_language_box.h b/Telegram/SourceFiles/ui/boxes/choose_language_box.h new file mode 100644 index 000000000..50c201127 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/choose_language_box.h @@ -0,0 +1,26 @@ +/* +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 + +struct LanguageId; + +namespace Ui { + +class GenericBox; + +[[nodiscard]] QString LanguageNameTranslated(const QString &twoLetterCode); +[[nodiscard]] QString LanguageName(LanguageId id); +[[nodiscard]] QString LanguageNameNative(LanguageId id); + +void ChooseLanguageBox( + not_null box, + rpl::producer title, + Fn)> callback, + std::optional> toggled); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 7ffd941c7..8d101abb2 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1273,3 +1273,4 @@ historyTranslateSettings: IconButton(defaultIconButton) { rippleAreaSize: 38px; ripple: defaultRippleAnimation; } +historyTranslateMenuPosition: point(-6px, 40px); \ No newline at end of file diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index f4627965e..e015d73d9 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -156,6 +156,8 @@ PRIVATE ui/boxes/calendar_box.h ui/boxes/choose_date_time.cpp ui/boxes/choose_date_time.h + ui/boxes/choose_language_box.cpp + ui/boxes/choose_language_box.h ui/boxes/choose_time.cpp ui/boxes/choose_time.h ui/boxes/confirm_box.cpp @@ -326,6 +328,7 @@ PRIVATE desktop-app::lib_ffmpeg desktop-app::lib_webview desktop-app::lib_webrtc + desktop-app::lib_spellcheck desktop-app::lib_stripe desktop-app::external_kcoreaddons ) diff --git a/Telegram/cmake/telegram_options.cmake b/Telegram/cmake/telegram_options.cmake index ff6bd398a..a5a6d9405 100644 --- a/Telegram/cmake/telegram_options.cmake +++ b/Telegram/cmake/telegram_options.cmake @@ -36,12 +36,6 @@ if (TDESKTOP_API_ID STREQUAL "0" OR TDESKTOP_API_HASH STREQUAL "") " ") endif() -if (DESKTOP_APP_DISABLE_SPELLCHECK) - target_compile_definitions(Telegram PRIVATE TDESKTOP_DISABLE_SPELLCHECK) -else() - target_link_libraries(Telegram PRIVATE desktop-app::lib_spellcheck) -endif() - if (DESKTOP_APP_DISABLE_AUTOUPDATE) target_compile_definitions(Telegram PRIVATE TDESKTOP_DISABLE_AUTOUPDATE) endif() diff --git a/Telegram/lib_ui b/Telegram/lib_ui index f2e698f22..7bcca23bf 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit f2e698f2209a86c133261196275ca98273c7a4dc +Subproject commit 7bcca23bfe9f95bf9fd10a2e803734baccc517b4