From 22a3093815ad6a4428647d87d75e9224eb154fc8 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 18 Sep 2024 04:08:16 +0400 Subject: [PATCH 001/177] Check updater exit status on Linux --- Telegram/SourceFiles/platform/linux/launcher_linux.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp index 008910300..3c853c7cd 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp @@ -109,6 +109,7 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { Logs::closeMain(); CrashReports::Finish(); + int waitStatus = 0; if (justRelaunch) { return GLib::spawn_async( initialWorkingDir().toStdString(), @@ -129,8 +130,8 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { nullptr, nullptr, nullptr, - nullptr, - nullptr)) { + &waitStatus, + nullptr) || !g_spawn_check_exit_status(waitStatus, nullptr)) { return false; } return launchUpdater(UpdaterLaunch::JustRelaunch); From 4288ba24493f5a7be63b4c034c06468eda2e6085 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 18 Sep 2024 16:35:11 +0300 Subject: [PATCH 002/177] Improved UX in box for link creation when user has link in clipboard. --- .../chat_helpers/message_field.cpp | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index e38c134af..378175969 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -220,6 +220,9 @@ void EditLinkBox( if (startText.isEmpty()) { text->setFocusFast(); } else { + if (!url->empty()) { + url->selectAll(); + } url->setFocusFast(); } }); @@ -227,12 +230,31 @@ void EditLinkBox( url->customTab(true); text->customTab(true); + const auto clearFullSelection = [=](not_null input) { + if (input->empty()) { + return; + } + auto cursor = input->rawTextEdit()->textCursor(); + const auto hasFull = (!cursor.selectionStart() + && (cursor.selectionEnd() + == (input->rawTextEdit()->document()->characterCount() - 1))); + if (hasFull) { + cursor.clearSelection(); + input->setTextCursor(cursor); + } + }; + url->tabbed( ) | rpl::start_with_next([=] { + clearFullSelection(url); text->setFocus(); }, url->lifetime()); text->tabbed( ) | rpl::start_with_next([=] { + if (!url->empty()) { + url->selectAll(); + } + clearFullSelection(text); url->setFocus(); }, text->lifetime()); } From b78443cf2dc03c9d0061a4f80c1d3c98af6d0292 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 16 Sep 2024 09:51:52 +0300 Subject: [PATCH 003/177] Added mini icon to drafts with reply. --- Telegram/SourceFiles/dialogs/dialogs.style | 2 ++ Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 1e2421764..f34114397 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -496,6 +496,8 @@ dialogsMiniForward: DialogsMiniIcon { skipMedia: 2px; } +dialogsMiniReplyIcon: icon {{ "mini_forward-flip_horizontal", attentionButtonFg, point(0px, 2px) }}; + dialogsMiniReplyStory: DialogsMiniIcon { icon: ThreeStateIcon { icon: icon {{ "mini_reply_story", dialogsTextFg, point(0px, 1px) }}; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 2347dd224..f66cc4e6c 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum_topic.h" #include "data/data_saved_sublist.h" #include "data/data_session.h" +#include "data/stickers/data_custom_emoji.h" #include "dialogs/dialogs_list.h" #include "dialogs/dialogs_three_state_icon.h" #include "dialogs/ui/dialogs_video_userpic.h" @@ -468,7 +469,7 @@ void PaintRow( : tr::lng_dialogs_text_with_from( tr::now, lt_from_part, - draftWrapped, + std::move(draftWrapped), lt_message, DialogsPreviewText({ .text = draft->textWithTags.text, @@ -476,13 +477,22 @@ void PaintRow( draft->textWithTags.tags), }), Text::WithEntities); + if (draft && draft->reply) { + auto &data = thread->owner().customEmojiManager(); + const auto internal = data.registerInternalEmoji( + st::dialogsMiniReplyIcon, + {}, + false); + draftText = Ui::Text::SingleCustomEmoji( + std::move(internal)).append(std::move(draftText)); + } const auto context = Core::MarkedTextContext{ .session = &thread->session(), .customEmojiRepaint = customEmojiRepaint, }; cache.setMarkedText( st::dialogsTextStyle, - draftText, + std::move(draftText), DialogTextOptions(), context); } From 969152e949788dc70c3dace43004ade6fe37ae7b Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Sep 2024 13:13:28 +0400 Subject: [PATCH 004/177] Move FieldAutocomplete to ChatHelpers. --- .../chat_helpers/field_autocomplete.cpp | 51 ++++++++++--------- .../chat_helpers/field_autocomplete.h | 24 +++++---- .../SourceFiles/history/history_widget.cpp | 24 +++++---- Telegram/SourceFiles/history/history_widget.h | 7 +-- .../history_view_compose_controls.cpp | 26 ++++++---- .../controls/history_view_compose_controls.h | 4 +- 6 files changed, 78 insertions(+), 58 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index bece81b64..76db78597 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include +namespace ChatHelpers { namespace { [[nodiscard]] QString PrimaryUsername(not_null user) { @@ -60,6 +61,18 @@ namespace { return usernames.empty() ? user->username() : usernames.front(); } +template +inline int indexOfInFirstN(const T &v, const U &elem, int last) { + for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last) + ; i != e + ; ++i) { + if (i->user == elem) { + return (i - b); + } + } + return -1; +} + } // namespace class FieldAutocomplete::Inner final : public Ui::RpWidget { @@ -70,7 +83,7 @@ public: }; Inner( - std::shared_ptr show, + std::shared_ptr show, const style::EmojiPan &st, not_null parent, not_null mrows, @@ -127,7 +140,7 @@ private: Media::Clip::Notification notification, not_null document); - const std::shared_ptr _show; + const std::shared_ptr _show; const not_null _session; const style::EmojiPan &_st; const not_null _parent; @@ -197,7 +210,7 @@ FieldAutocomplete::FieldAutocomplete( FieldAutocomplete::FieldAutocomplete( QWidget *parent, - std::shared_ptr show, + std::shared_ptr show, const style::EmojiPan *stOverride) : RpWidget(parent) , _show(std::move(show)) @@ -235,7 +248,7 @@ FieldAutocomplete::FieldAutocomplete( }), lifetime()); } -std::shared_ptr FieldAutocomplete::uiShow() const { +std::shared_ptr FieldAutocomplete::uiShow() const { return _show; } @@ -373,18 +386,6 @@ bool FieldAutocomplete::clearFilteredBotCommands() { return true; } -namespace { -template -inline int indexOfInFirstN(const T &v, const U &elem, int last) { - for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last); i != e; ++i) { - if (i->user == elem) { - return (i - b); - } - } - return -1; -} -} - FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() { const auto data = &_session->data().stickers(); const auto list = data->getListByEmoji({ _emoji }, _stickersSeed); @@ -871,7 +872,7 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) { } FieldAutocomplete::Inner::Inner( - std::shared_ptr show, + std::shared_ptr show, const style::EmojiPan &st, not_null parent, not_null mrows, @@ -963,8 +964,8 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) { media->checkStickerSmall(); const auto paused = _show->paused( - ChatHelpers::PauseReason::TabbedPanel); - const auto size = ChatHelpers::ComputeStickerSize( + PauseReason::TabbedPanel); + const auto size = ComputeStickerSize( document, stickerBoundingBox()); const auto ppos = pos + QPoint( @@ -989,7 +990,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) { } else if (const auto image = media->getStickerSmall()) { p.drawPixmapLeft(ppos, width(), image->pix(size)); } else { - ChatHelpers::PaintStickerThumbnailPath( + PaintStickerThumbnailPath( p, media.get(), QRect(ppos, size), @@ -1250,7 +1251,7 @@ bool FieldAutocomplete::Inner::chooseAtIndex( const auto bounding = selectedRect(index); auto contentRect = QRect( QPoint(), - ChatHelpers::ComputeStickerSize( + ComputeStickerSize( document, stickerBoundingBox())); contentRect.moveCenter(bounding.center()); @@ -1464,9 +1465,9 @@ auto FieldAutocomplete::Inner::getLottieRenderer() void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) { const auto document = suggestion.document; - suggestion.lottie = ChatHelpers::LottiePlayerFromDocument( + suggestion.lottie = LottiePlayerFromDocument( suggestion.documentMedia.get(), - ChatHelpers::StickerLottieSize::InlineResults, + StickerLottieSize::InlineResults, stickerBoundingBox() * style::DevicePixelRatio(), Lottie::Quality::Default, getLottieRenderer()); @@ -1534,7 +1535,7 @@ void FieldAutocomplete::Inner::clipCallback( } else if (i->webm->state() == State::Error) { i->webm.setBad(); } else if (i->webm->ready() && !i->webm->started()) { - const auto size = ChatHelpers::ComputeStickerSize( + const auto size = ComputeStickerSize( i->document, stickerBoundingBox()); i->webm->start({ .frame = size, .keepAlpha = true }); @@ -1632,3 +1633,5 @@ auto FieldAutocomplete::Inner::scrollToRequested() const -> rpl::producer { return _scrollToRequested.events(); } + +} // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.h b/Telegram/SourceFiles/chat_helpers/field_autocomplete.h index d95b7e394..b13659709 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.h +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.h @@ -46,9 +46,15 @@ struct Details; } // namespace SendMenu namespace ChatHelpers { + struct FileChosen; class Show; -} // namespace ChatHelpers + +enum class FieldAutocompleteChooseMethod { + ByEnter, + ByTab, + ByClick, +}; class FieldAutocomplete final : public Ui::RpWidget { public: @@ -57,11 +63,11 @@ public: not_null controller); FieldAutocomplete( QWidget *parent, - std::shared_ptr show, + std::shared_ptr show, const style::EmojiPan *stOverride = nullptr); ~FieldAutocomplete(); - [[nodiscard]] std::shared_ptr uiShow() const; + [[nodiscard]] std::shared_ptr uiShow() const; bool clearFilteredBotCommands(); void showFiltered( @@ -81,11 +87,7 @@ public: bool eventFilter(QObject *obj, QEvent *e) override; - enum class ChooseMethod { - ByEnter, - ByTab, - ByClick, - }; + using ChooseMethod = FieldAutocompleteChooseMethod; struct MentionChosen { not_null user; QString mention; @@ -100,7 +102,7 @@ public: QString command; ChooseMethod method = ChooseMethod::ByEnter; }; - using StickerChosen = ChatHelpers::FileChosen; + using StickerChosen = FileChosen; enum class Type { Mentions, Hashtags, @@ -157,7 +159,7 @@ private: void recount(bool resetScroll = false); StickerRows getStickerSuggestions(); - const std::shared_ptr _show; + const std::shared_ptr _show; const not_null _session; const style::EmojiPan &_st; QPixmap _cache; @@ -193,3 +195,5 @@ private: Fn _moderateKeyActivateCallback; }; + +} // namespace ChatHelpers diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index d857f252d..90c0fb7df 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -118,6 +118,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" #include "profile/profile_block_group_members.h" #include "core/click_handler_types.h" +#include "chat_helpers/field_autocomplete.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_section.h" @@ -473,7 +474,8 @@ HistoryWidget::HistoryWidget( }, lifetime()); _fieldAutocomplete->mentionChosen( - ) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) { + ) | rpl::start_with_next([=]( + ChatHelpers::FieldAutocomplete::MentionChosen data) { auto replacement = QString(); auto entityTag = QString(); if (data.mention.isEmpty()) { @@ -489,13 +491,15 @@ HistoryWidget::HistoryWidget( }, lifetime()); _fieldAutocomplete->hashtagChosen( - ) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) { + ) | rpl::start_with_next([=]( + ChatHelpers::FieldAutocomplete::HashtagChosen data) { insertHashtagOrBotCommand(data.hashtag, data.method); }, lifetime()); _fieldAutocomplete->botCommandChosen( - ) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) { - using Method = FieldAutocomplete::ChooseMethod; + ) | rpl::start_with_next([=]( + ChatHelpers::FieldAutocomplete::BotCommandChosen data) { + using Method = ChatHelpers::FieldAutocompleteChooseMethod; const auto messages = &data.user->owner().shortcutMessages(); const auto shortcut = data.user->isSelf(); const auto command = data.command.mid(1); @@ -528,11 +532,11 @@ HistoryWidget::HistoryWidget( }); _fieldAutocomplete->choosingProcesses( - ) | rpl::start_with_next([=](FieldAutocomplete::Type type) { + ) | rpl::start_with_next([=](ChatHelpers::FieldAutocomplete::Type type) { if (!_history) { return; } - if (type == FieldAutocomplete::Type::Stickers) { + if (type == ChatHelpers::FieldAutocomplete::Type::Stickers) { session().sendProgressManager().update( _history, Api::SendProgressType::ChooseSticker); @@ -1488,13 +1492,14 @@ void HistoryWidget::start() { void HistoryWidget::insertHashtagOrBotCommand( QString str, - FieldAutocomplete::ChooseMethod method) { + ChatHelpers::FieldAutocompleteChooseMethod method) { if (!_peer) { return; } // Send bot command at once, if it was not inserted by pressing Tab. - if (str.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) { + using Method = ChatHelpers::FieldAutocompleteChooseMethod; + if (str.at(0) == '/' && method != Method::ByTab) { sendBotCommand({ _peer, str, FullMsgId(), replyTo() }); session().api().finishForwarding(prepareSendAction({})); setFieldText(_field->getTextWithTagsPart(_field->textCursor().position())); @@ -6935,7 +6940,8 @@ void HistoryWidget::fieldTabbed() { if (_supportAutocomplete) { _supportAutocomplete->activate(_field.data()); } else if (!_fieldAutocomplete->isHidden()) { - _fieldAutocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab); + _fieldAutocomplete->chooseSelected( + ChatHelpers::FieldAutocomplete::ChooseMethod::ByTab); } } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 362dccb79..1f0851431 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_view_highlight_manager.h" #include "history/history_view_top_toast.h" #include "history/history.h" -#include "chat_helpers/field_autocomplete.h" #include "chat_helpers/field_characters_count_manager.h" #include "window/section_widget.h" #include "ui/widgets/fields/input_field.h" @@ -84,6 +83,8 @@ class SessionController; namespace ChatHelpers { class TabbedPanel; class TabbedSelector; +class FieldAutocomplete; +enum class FieldAutocompleteChooseMethod; } // namespace ChatHelpers namespace HistoryView { @@ -368,7 +369,7 @@ private: void insertHashtagOrBotCommand( QString str, - FieldAutocomplete::ChooseMethod method); + ChatHelpers::FieldAutocompleteChooseMethod method); void cancelInlineBot(); void saveDraft(bool delayed = false); void saveCloudDraft(); @@ -739,7 +740,7 @@ private: HistoryView::CornerButtons _cornerButtons; - const object_ptr _fieldAutocomplete; + const object_ptr _fieldAutocomplete; object_ptr _supportAutocomplete; UserData *_inlineBot = nullptr; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 11ab5d49e..b0d17ff9c 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -859,7 +859,7 @@ ComposeControls::ComposeControls( _wrap.get(), st::historyBotCommandStart) : nullptr) -, _autocomplete(std::make_unique( +, _autocomplete(std::make_unique( parent, _show, &_st.tabbed)) @@ -1701,9 +1701,10 @@ void ComposeControls::updateSubmitSettings() { void ComposeControls::initAutocomplete() { const auto insertHashtagOrBotCommand = [=]( const QString &string, - FieldAutocomplete::ChooseMethod method) { + ChatHelpers::FieldAutocompleteChooseMethod method) { // Send bot command at once, if it was not inserted by pressing Tab. - if (string.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) { + using Method = ChatHelpers::FieldAutocompleteChooseMethod; + if (string.at(0) == '/' && method != Method::ByTab) { _sendCommandRequests.fire_copy(string); setText( _field->getTextWithTagsPart(_field->textCursor().position())); @@ -1713,7 +1714,8 @@ void ComposeControls::initAutocomplete() { }; _autocomplete->mentionChosen( - ) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) { + ) | rpl::start_with_next([=]( + ChatHelpers::FieldAutocomplete::MentionChosen data) { const auto user = data.user; if (data.mention.isEmpty()) { _field->insertTag( @@ -1725,17 +1727,20 @@ void ComposeControls::initAutocomplete() { }, _autocomplete->lifetime()); _autocomplete->hashtagChosen( - ) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) { + ) | rpl::start_with_next([=]( + ChatHelpers::FieldAutocomplete::HashtagChosen data) { insertHashtagOrBotCommand(data.hashtag, data.method); }, _autocomplete->lifetime()); _autocomplete->botCommandChosen( - ) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) { + ) | rpl::start_with_next([=]( + ChatHelpers::FieldAutocomplete::BotCommandChosen data) { insertHashtagOrBotCommand(data.command, data.method); }, _autocomplete->lifetime()); _autocomplete->stickerChosen( - ) | rpl::start_with_next([=](FieldAutocomplete::StickerChosen data) { + ) | rpl::start_with_next([=]( + ChatHelpers::FieldAutocomplete::StickerChosen data) { if (!_showSlowmodeError || !_showSlowmodeError()) { setText({}); } @@ -1747,8 +1752,8 @@ void ComposeControls::initAutocomplete() { }, _autocomplete->lifetime()); _autocomplete->choosingProcesses( - ) | rpl::start_with_next([=](FieldAutocomplete::Type type) { - if (type == FieldAutocomplete::Type::Stickers) { + ) | rpl::start_with_next([=](ChatHelpers::FieldAutocomplete::Type type) { + if (type == ChatHelpers::FieldAutocomplete::Type::Stickers) { _sendActionUpdates.fire({ .type = Api::SendProgressType::ChooseSticker, }); @@ -2125,7 +2130,8 @@ void ComposeControls::cancelForward() { void ComposeControls::fieldTabbed() { if (!_autocomplete->isHidden()) { - _autocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab); + _autocomplete->chooseSelected( + ChatHelpers::FieldAutocomplete::ChooseMethod::ByTab); } } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index fb373a0f7..6950111cc 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; class DocumentData; -class FieldAutocomplete; class Image; namespace style { @@ -39,6 +38,7 @@ struct FileChosen; struct PhotoChosen; class Show; enum class PauseReason; +class FieldAutocomplete; } // namespace ChatHelpers namespace Data { @@ -391,7 +391,7 @@ private: std::unique_ptr _inlineResults; std::unique_ptr _tabbedPanel; std::unique_ptr _attachBotsMenu; - std::unique_ptr _autocomplete; + std::unique_ptr _autocomplete; friend class FieldHeader; const std::unique_ptr _header; From ba7cd25f21425a828d9fdf5fd7790520c79e199a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Sep 2024 17:39:15 +0400 Subject: [PATCH 005/177] Extract FieldAutocomplete code. --- .../chat_helpers/compose/compose_features.h | 29 +- .../chat_helpers/emoji_suggestions_widget.cpp | 4 +- .../chat_helpers/emoji_suggestions_widget.h | 3 +- .../chat_helpers/field_autocomplete.cpp | 188 ++++++++- .../chat_helpers/field_autocomplete.h | 64 ++- .../SourceFiles/history/history_widget.cpp | 390 +++++++----------- Telegram/SourceFiles/history/history_widget.h | 23 +- .../history_view_compose_controls.cpp | 263 ++++-------- .../controls/history_view_compose_controls.h | 12 +- Telegram/SourceFiles/mainwidget.cpp | 2 +- 10 files changed, 497 insertions(+), 481 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_features.h b/Telegram/SourceFiles/chat_helpers/compose/compose_features.h index 5466b34e9..2f33d8163 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_features.h +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_features.h @@ -10,20 +10,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace ChatHelpers { struct ComposeFeatures { - bool likes = false; - bool sendAs = true; - bool ttlInfo = true; - bool botCommandSend = true; - bool silentBroadcastToggle = true; - bool attachBotsMenu = true; - bool inlineBots = true; - bool megagroupSet = true; - bool stickersSettings = true; - bool openStickerSets = true; - bool autocompleteHashtags = true; - bool autocompleteMentions = true; - bool autocompleteCommands = true; - bool commonTabbedPanel = true; + bool likes : 1 = false; + bool sendAs : 1 = true; + bool ttlInfo : 1 = true; + bool botCommandSend : 1 = true; + bool silentBroadcastToggle : 1 = true; + bool attachBotsMenu : 1 = true; + bool inlineBots : 1 = true; + bool megagroupSet : 1 = true; + bool stickersSettings : 1 = true; + bool openStickerSets : 1 = true; + bool autocompleteHashtags : 1 = true; + bool autocompleteMentions : 1 = true; + bool autocompleteCommands : 1 = true; + bool suggestStickersByEmoji : 1 = true; + bool commonTabbedPanel : 1 = true; }; } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index 223870044..87fd38886 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -713,11 +713,13 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) { } SuggestionsController::SuggestionsController( + not_null parent, not_null outer, not_null field, not_null session, const Options &options) -: _st(options.st ? *options.st : st::defaultEmojiSuggestions) +: QObject(parent) +, _st(options.st ? *options.st : st::defaultEmojiSuggestions) , _field(field) , _session(session) , _showExactTimer([=] { showWithQuery(getEmojiQuery()); }) diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h index b89de0ad9..bea497dc2 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h @@ -37,7 +37,7 @@ class SuggestionsWidget; using SuggestionsQuery = std::variant; -class SuggestionsController { +class SuggestionsController final : public QObject { public: struct Options { bool suggestExactFirstWord = true; @@ -47,6 +47,7 @@ public: }; SuggestionsController( + not_null parent, not_null outer, not_null field, not_null session, diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 76db78597..d956acfa6 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/business/data_shortcut_messages.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" @@ -202,12 +203,6 @@ struct FieldAutocomplete::BotCommandRow { Ui::Text::String descriptionText; }; -FieldAutocomplete::FieldAutocomplete( - QWidget *parent, - not_null controller) -: FieldAutocomplete(parent, controller->uiShow()) { -} - FieldAutocomplete::FieldAutocomplete( QWidget *parent, std::shared_ptr show, @@ -252,6 +247,22 @@ std::shared_ptr FieldAutocomplete::uiShow() const { return _show; } +void FieldAutocomplete::requestRefresh() { + _refreshRequests.fire({}); +} + +rpl::producer<> FieldAutocomplete::refreshRequests() const { + return _refreshRequests.events(); +} + +void FieldAutocomplete::requestStickersUpdate() { + _stickersUpdateRequests.fire({}); +} + +rpl::producer<> FieldAutocomplete::stickersUpdateRequests() const { + return _stickersUpdateRequests.events(); +} + auto FieldAutocomplete::mentionChosen() const -> rpl::producer { return _inner->mentionChosen(); @@ -378,6 +389,10 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) { updateFiltered(resetScroll); } +EmojiPtr FieldAutocomplete::stickersEmoji() const { + return _emoji; +} + bool FieldAutocomplete::clearFilteredBotCommands() { if (_brows.empty()) { return false; @@ -1634,4 +1649,165 @@ auto FieldAutocomplete::Inner::scrollToRequested() const return _scrollToRequested.events(); } +void InitFieldAutocomplete( + std::unique_ptr &autocomplete, + FieldAutocompleteDescriptor &&descriptor) { + Expects(!autocomplete); + + autocomplete = std::make_unique( + descriptor.parent, + descriptor.show, + descriptor.stOverride); + const auto raw = autocomplete.get(); + const auto field = descriptor.field; + + field->rawTextEdit()->installEventFilter(raw); + + raw->mentionChosen( + ) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) { + const auto user = data.user; + if (data.mention.isEmpty()) { + field->insertTag( + user->firstName.isEmpty() ? user->name() : user->firstName, + PrepareMentionTag(user)); + } else { + field->insertTag('@' + data.mention); + } + }, raw->lifetime()); + + const auto sendCommand = descriptor.sendBotCommand; + const auto setText = descriptor.setText; + + raw->hashtagChosen( + ) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) { + field->insertTag(data.hashtag); + }, raw->lifetime()); + + const auto peer = descriptor.peer; + const auto processShortcut = descriptor.processShortcut; + const auto shortcutMessages = (processShortcut != nullptr) + ? &peer->owner().shortcutMessages() + : nullptr; + raw->botCommandChosen( + ) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) { + using Method = FieldAutocompleteChooseMethod; + const auto byTab = (data.method == Method::ByTab); + const auto shortcut = data.user->isSelf(); + + // Send bot command at once, if it was not inserted by pressing Tab. + if (byTab && data.command.size() > 1) { + field->insertTag(data.command); + } else if (!shortcut) { + sendCommand(data.command); + setText( + field->getTextWithTagsPart(field->textCursor().position())); + } else if (processShortcut) { + processShortcut(data.command.mid(1)); + } + }, raw->lifetime()); + + raw->setModerateKeyActivateCallback(std::move(descriptor.moderateKeyActivateCallback)); + + const auto stickerChoosing = descriptor.stickerChoosing; + raw->choosingProcesses( + ) | rpl::start_with_next([=](FieldAutocomplete::Type type) { + if (type == FieldAutocomplete::Type::Stickers) { + stickerChoosing(); + } + }, raw->lifetime()); + if (auto chosen = descriptor.stickerChosen) { + raw->stickerChosen( + ) | rpl::start_with_next(std::move(chosen), raw->lifetime()); + } + + field->tabbed( + ) | rpl::start_with_next([=] { + if (!raw->isHidden()) { + raw->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab); + } + }, raw->lifetime()); + + const auto features = descriptor.features; + const auto check = [=] { + auto parsed = ParseMentionHashtagBotCommandQuery(field, features()); + if (parsed.query.isEmpty()) { + } else if (parsed.query[0] == '#' + && cRecentWriteHashtags().isEmpty() + && cRecentSearchHashtags().isEmpty()) { + peer->session().local().readRecentHashtagsAndBots(); + } else if (parsed.query[0] == '@' + && cRecentInlineBots().isEmpty()) { + peer->session().local().readRecentHashtagsAndBots(); + } else if (parsed.query[0] == '/' + && peer->isUser() + && !peer->asUser()->isBot() + && (!shortcutMessages + || shortcutMessages->shortcuts().list.empty())) { + parsed = {}; + } + raw->showFiltered(peer, parsed.query, parsed.fromStart); + }; + + const auto updateStickersByEmoji = [=] { + const auto errorForStickers = Data::RestrictionError( + peer, + ChatRestriction::SendStickers); + if (features().suggestStickersByEmoji && !errorForStickers) { + const auto &text = field->getTextWithTags().text; + auto length = 0; + if (const auto emoji = Ui::Emoji::Find(text, &length)) { + if (text.size() <= length) { + raw->showStickers(emoji); + return; + } + } + } + raw->showStickers(nullptr); + }; + + raw->refreshRequests( + ) | rpl::start_with_next(check, raw->lifetime()); + + raw->stickersUpdateRequests( + ) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime()); + + peer->owner().botCommandsChanges( + ) | rpl::filter([=](not_null changed) { + return (peer == changed); + }) | rpl::start_with_next([=] { + if (raw->clearFilteredBotCommands()) { + check(); + } + }, raw->lifetime()); + + peer->owner().stickers().updated( + Data::StickersType::Stickers + ) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime()); + + QObject::connect( + field->rawTextEdit(), + &QTextEdit::cursorPositionChanged, + raw, + check, + Qt::QueuedConnection); + + field->changes() | rpl::start_with_next( + updateStickersByEmoji, + raw->lifetime()); + + peer->session().changes().peerUpdates( + Data::PeerUpdate::Flag::Rights + ) | rpl::filter([=](const Data::PeerUpdate &update) { + return (update.peer == peer); + }) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime()); + + if (shortcutMessages) { + shortcutMessages->shortcutsChanged( + ) | rpl::start_with_next(check, raw->lifetime()); + } + + raw->setSendMenuDetails(std::move(descriptor.sendMenuDetails)); + raw->hideFast(); +} + } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.h b/Telegram/SourceFiles/chat_helpers/field_autocomplete.h index b13659709..89184bc03 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.h +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.h @@ -47,6 +47,7 @@ struct Details; namespace ChatHelpers { +struct ComposeFeatures; struct FileChosen; class Show; @@ -58,9 +59,6 @@ enum class FieldAutocompleteChooseMethod { class FieldAutocomplete final : public Ui::RpWidget { public: - FieldAutocomplete( - QWidget *parent, - not_null controller); FieldAutocomplete( QWidget *parent, std::shared_ptr show, @@ -74,16 +72,19 @@ public: not_null peer, QString query, bool addInlineBots); + void showStickers(EmojiPtr emoji); + [[nodiscard]] EmojiPtr stickersEmoji() const; + void setBoundings(QRect boundings); - const QString &filter() const; - ChatData *chat() const; - ChannelData *channel() const; - UserData *user() const; + [[nodiscard]] const QString &filter() const; + [[nodiscard]] ChatData *chat() const; + [[nodiscard]] ChannelData *channel() const; + [[nodiscard]] UserData *user() const; - int32 innerTop(); - int32 innerBottom(); + [[nodiscard]] int32 innerTop(); + [[nodiscard]] int32 innerBottom(); bool eventFilter(QObject *obj, QEvent *e) override; @@ -112,13 +113,14 @@ public: bool chooseSelected(ChooseMethod method) const; - bool stickersShown() const { + [[nodiscard]] bool stickersShown() const { return !_srows.empty(); } - bool overlaps(const QRect &globalRect) { - if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false; - + [[nodiscard]] bool overlaps(const QRect &globalRect) { + if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) { + return false; + } return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } @@ -131,11 +133,16 @@ public: void showAnimated(); void hideAnimated(); - rpl::producer mentionChosen() const; - rpl::producer hashtagChosen() const; - rpl::producer botCommandChosen() const; - rpl::producer stickerChosen() const; - rpl::producer choosingProcesses() const; + void requestRefresh(); + [[nodiscard]] rpl::producer<> refreshRequests() const; + void requestStickersUpdate(); + [[nodiscard]] rpl::producer<> stickersUpdateRequests() const; + + [[nodiscard]] rpl::producer mentionChosen() const; + [[nodiscard]] rpl::producer hashtagChosen() const; + [[nodiscard]] rpl::producer botCommandChosen() const; + [[nodiscard]] rpl::producer stickerChosen() const; + [[nodiscard]] rpl::producer choosingProcesses() const; protected: void paintEvent(QPaintEvent *e) override; @@ -191,9 +198,30 @@ private: bool _hiding = false; Ui::Animations::Simple _a_opacity; + rpl::event_stream<> _refreshRequests; + rpl::event_stream<> _stickersUpdateRequests; Fn _moderateKeyActivateCallback; }; +struct FieldAutocompleteDescriptor { + not_null parent; + std::shared_ptr show; + not_null field; + const style::EmojiPan *stOverride = nullptr; + not_null peer; + Fn features; + Fn sendMenuDetails; + Fn stickerChoosing; + Fn stickerChosen; + Fn setText; + Fn sendBotCommand; + Fn processShortcut; + Fn moderateKeyActivateCallback; +}; +void InitFieldAutocomplete( + std::unique_ptr &autocomplete, + FieldAutocompleteDescriptor &&descriptor); + } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 90c0fb7df..e236b9603 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -243,7 +243,6 @@ HistoryWidget::HistoryWidget( _scroll.data(), controller->chatStyle(), static_cast(this)) -, _fieldAutocomplete(this, controller->uiShow()) , _supportAutocomplete(session().supportMode() ? object_ptr(this, &session()) : nullptr) @@ -416,15 +415,6 @@ HistoryWidget::HistoryWidget( saveDraftDelayed(); }, _field->lifetime()); - connect(rawTextEdit, &QTextEdit::cursorPositionChanged, this, [=] { - checkFieldAutocomplete(); - }, Qt::QueuedConnection); - - controller->session().data().shortcutMessages().shortcutsChanged( - ) | rpl::start_with_next([=] { - checkFieldAutocomplete(); - }, lifetime()); - _fieldBarCancel->hide(); _topBar->hide(); @@ -473,85 +463,10 @@ HistoryWidget::HistoryWidget( sendBotCommand(r); }, lifetime()); - _fieldAutocomplete->mentionChosen( - ) | rpl::start_with_next([=]( - ChatHelpers::FieldAutocomplete::MentionChosen data) { - auto replacement = QString(); - auto entityTag = QString(); - if (data.mention.isEmpty()) { - replacement = data.user->firstName; - if (replacement.isEmpty()) { - replacement = data.user->name(); - } - entityTag = PrepareMentionTag(data.user); - } else { - replacement = '@' + data.mention; - } - _field->insertTag(replacement, entityTag); - }, lifetime()); - - _fieldAutocomplete->hashtagChosen( - ) | rpl::start_with_next([=]( - ChatHelpers::FieldAutocomplete::HashtagChosen data) { - insertHashtagOrBotCommand(data.hashtag, data.method); - }, lifetime()); - - _fieldAutocomplete->botCommandChosen( - ) | rpl::start_with_next([=]( - ChatHelpers::FieldAutocomplete::BotCommandChosen data) { - using Method = ChatHelpers::FieldAutocompleteChooseMethod; - const auto messages = &data.user->owner().shortcutMessages(); - const auto shortcut = data.user->isSelf(); - const auto command = data.command.mid(1); - const auto byTab = (data.method == Method::ByTab); - const auto shortcutId = (_peer && shortcut && !byTab) - ? messages->lookupShortcutId(command) - : BusinessShortcutId(); - if (shortcut && command.isEmpty()) { - controller->showSettings(Settings::QuickRepliesId()); - } else if (!shortcutId) { - insertHashtagOrBotCommand(data.command, data.method); - } else if (!_peer->session().premium()) { - ShowPremiumPreviewToBuy( - controller, - PremiumFeature::QuickReplies); - } else { - session().api().sendShortcutMessages(_peer, shortcutId); - session().api().finishForwarding(prepareSendAction({})); - setFieldText(_field->getTextWithTagsPart(_field->textCursor().position())); - } - }, lifetime()); - - _fieldAutocomplete->setModerateKeyActivateCallback([=](int key) { - const auto context = [=](FullMsgId itemId) { - return _list->prepareClickContext(Qt::LeftButton, itemId); - }; - return !_keyboard->isHidden() && _keyboard->moderateKeyActivate( - key, - context); - }); - - _fieldAutocomplete->choosingProcesses( - ) | rpl::start_with_next([=](ChatHelpers::FieldAutocomplete::Type type) { - if (!_history) { - return; - } - if (type == ChatHelpers::FieldAutocomplete::Type::Stickers) { - session().sendProgressManager().update( - _history, - Api::SendProgressType::ChooseSticker); - } - }, lifetime()); - - _fieldAutocomplete->setSendMenuDetails([=] { - return sendMenuDetails(); - }); - if (_supportAutocomplete) { supportInitAutocomplete(); } _field->rawTextEdit()->installEventFilter(this); - _field->rawTextEdit()->installEventFilter(_fieldAutocomplete); _field->setMimeDataHook([=]( not_null data, Ui::InputField::MimeAction action) { @@ -566,15 +481,6 @@ HistoryWidget::HistoryWidget( Unexpected("action in MimeData hook."); }); - const auto allow = [=](const auto&) { - return _peer && _peer->isSelf(); - }; - const auto suggestions = Ui::Emoji::SuggestionsController::Init( - this, - _field, - &controller->session(), - { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow }); - _raiseEmojiSuggestions = [=] { suggestions->raise(); }; updateFieldSubmitSettings(); _field->hide(); @@ -699,9 +605,6 @@ HistoryWidget::HistoryWidget( updateControlsVisibility(); updateControlsGeometry(); } - if (_fieldAutocomplete->clearFilteredBotCommands()) { - checkFieldAutocomplete(); - } }, lifetime()); using EntryUpdateFlag = Data::EntryUpdate::Flag; @@ -834,7 +737,6 @@ HistoryWidget::HistoryWidget( return update.flags; }) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) { if (flags & PeerUpdateFlag::Rights) { - updateStickersByEmoji(); updateFieldPlaceholder(); _preview->checkNow(false); @@ -1191,35 +1093,10 @@ void HistoryWidget::initTabbedSelector() { rpl::merge( selector->fileChosen() | filter, - _fieldAutocomplete->stickerChosen(), selector->customEmojiChosen() | filter, controller()->stickerOrEmojiChosen() | filter - ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) { - controller()->hideLayer(anim::type::normal); - if (const auto info = data.document->sticker() - ; info && info->setType == Data::StickersType::Emoji) { - if (data.document->isPremiumEmoji() - && !session().premium() - && (!_peer - || !Data::AllowEmojiWithoutPremium( - _peer, - data.document))) { - showPremiumToast(data.document); - } else if (!_field->isHidden()) { - Data::InsertCustomEmoji(_field.data(), data.document); - } - } else { - controller()->sendingAnimation().appendSending( - data.messageSendingFrom); - const auto localId = data.messageSendingFrom.localId; - auto messageToSend = Api::MessageToSend( - prepareSendAction(data.options)); - messageToSend.textWithTags = base::take(data.caption); - sendExistingDocument( - data.document, - std::move(messageToSend), - localId); - } + ) | rpl::start_with_next([=](ChatHelpers::FileChosen &&data) { + fileChosen(std::move(data)); }, lifetime()); selector->photoChosen( @@ -1481,66 +1358,95 @@ int HistoryWidget::itemTopForHighlight( return itemTop; } -void HistoryWidget::start() { - session().data().stickers().updated( - Data::StickersType::Stickers - ) | rpl::start_with_next([=] { - updateStickersByEmoji(); - }, lifetime()); - session().data().stickers().notifySavedGifsUpdated(); -} - -void HistoryWidget::insertHashtagOrBotCommand( - QString str, - ChatHelpers::FieldAutocompleteChooseMethod method) { +void HistoryWidget::initFieldAutocomplete() { + _emojiSuggestions = nullptr; + _autocomplete = nullptr; if (!_peer) { return; } - - // Send bot command at once, if it was not inserted by pressing Tab. - using Method = ChatHelpers::FieldAutocompleteChooseMethod; - if (str.at(0) == '/' && method != Method::ByTab) { - sendBotCommand({ _peer, str, FullMsgId(), replyTo() }); - session().api().finishForwarding(prepareSendAction({})); - setFieldText(_field->getTextWithTagsPart(_field->textCursor().position())); - } else { - _field->insertTag(str); - } + const auto processShortcut = [=](QString shortcut) { + if (!_peer) { + return; + } + const auto messages = &_peer->owner().shortcutMessages(); + const auto shortcutId = messages->lookupShortcutId(shortcut); + if (shortcut.isEmpty()) { + controller()->showSettings(Settings::QuickRepliesId()); + } else if (!_peer->session().premium()) { + ShowPremiumPreviewToBuy( + controller(), + PremiumFeature::QuickReplies); + } else if (shortcutId) { + session().api().sendShortcutMessages(_peer, shortcutId); + session().api().finishForwarding(prepareSendAction({})); + setFieldText(_field->getTextWithTagsPart( + _field->textCursor().position())); + } + }; + ChatHelpers::InitFieldAutocomplete(_autocomplete, { + .parent = this, + .show = controller()->uiShow(), + .field = _field.data(), + .peer = _peer, + .features = [=] { + auto result = ChatHelpers::ComposeFeatures(); + if (_showAnimation + || isChoosingTheme() + || (_inlineBot && !_inlineLookingUpBot)) { + result.autocompleteMentions = false; + result.autocompleteHashtags = false; + result.autocompleteCommands = false; + } + if (_editMsgId) { + result.autocompleteCommands = false; + result.suggestStickersByEmoji = false; + } + return result; + }, + .sendMenuDetails = [=] { return sendMenuDetails(); }, + .stickerChoosing = [=] { + if (_history) { + session().sendProgressManager().update( + _history, + Api::SendProgressType::ChooseSticker); + } + }, + .stickerChosen = [=](ChatHelpers::FileChosen &&data) { + fileChosen(std::move(data)); + }, + .setText = [=](TextWithTags text) { if (_peer) setFieldText(text); }, + .sendBotCommand = [=](QString command) { + if (_peer) { + sendBotCommand({ _peer, command, FullMsgId(), replyTo() }); + session().api().finishForwarding(prepareSendAction({})); + } + }, + .processShortcut = processShortcut, + .moderateKeyActivateCallback = [=](int key) { + const auto context = [=](FullMsgId itemId) { + return _list->prepareClickContext(Qt::LeftButton, itemId); + }; + return !_keyboard->isHidden() && _keyboard->moderateKeyActivate( + key, + context); + }, + }); + const auto allow = [=](const auto&) { + return _peer->isSelf(); + }; + _emojiSuggestions.reset(Ui::Emoji::SuggestionsController::Init( + this, + _field, + &controller()->session(), + { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow })); } - InlineBotQuery HistoryWidget::parseInlineBotQuery() const { return (isChoosingTheme() || _editMsgId) ? InlineBotQuery() : ParseInlineBotQuery(&session(), _field); } -AutocompleteQuery HistoryWidget::parseMentionHashtagBotCommandQuery() const { - const auto result = (isChoosingTheme() - || (_inlineBot && !_inlineLookingUpBot)) - ? AutocompleteQuery() - : ParseMentionHashtagBotCommandQuery(_field, {}); - if (result.query.isEmpty()) { - return result; - } else if (result.query[0] == '#' - && cRecentWriteHashtags().isEmpty() - && cRecentSearchHashtags().isEmpty()) { - session().local().readRecentHashtagsAndBots(); - } else if (result.query[0] == '@' - && cRecentInlineBots().isEmpty()) { - session().local().readRecentHashtagsAndBots(); - } else if (result.query[0] == '/') { - if (_editMsgId) { - return {}; - } else if (_peer->isUser() - && !_peer->asUser()->isBot() - && _peer->owner().shortcutMessages().shortcuts().list.empty()) { - return {}; - } - } - return result; -} - void HistoryWidget::updateInlineBotQuery() { if (!_history) { return; @@ -1632,8 +1538,8 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { orderWidgets(); } _inlineResults->queryInlineBot(_inlineBot, _peer, query); - if (!_fieldAutocomplete->isHidden()) { - _fieldAutocomplete->hideAnimated(); + if (_autocomplete) { + _autocomplete->hideAnimated(); } } else { clearInlineBot(); @@ -1665,7 +1571,9 @@ void HistoryWidget::orderWidgets() { _chooseTheme->raise(); } _topShadow->raise(); - _fieldAutocomplete->raise(); + if (_autocomplete) { + _autocomplete->raise(); + } if (_membersDropdown) { _membersDropdown->raise(); } @@ -1675,34 +1583,13 @@ void HistoryWidget::orderWidgets() { if (_tabbedPanel) { _tabbedPanel->raise(); } - _raiseEmojiSuggestions(); + if (_emojiSuggestions) { + _emojiSuggestions->raise(); + } _attachDragAreas.document->raise(); _attachDragAreas.photo->raise(); } -bool HistoryWidget::updateStickersByEmoji() { - if (!_peer) { - return false; - } - const auto emoji = [&] { - const auto errorForStickers = Data::RestrictionError( - _peer, - ChatRestriction::SendStickers); - if (!_editMsgId && !errorForStickers) { - const auto &text = _field->getTextWithTags().text; - auto length = 0; - if (const auto emoji = Ui::Emoji::Find(text, &length)) { - if (text.size() <= length) { - return emoji; - } - } - } - return EmojiPtr(nullptr); - }(); - _fieldAutocomplete->showStickers(emoji); - return (emoji != nullptr); -} - void HistoryWidget::toggleChooseChatTheme( not_null peer, std::optional show) { @@ -1745,11 +1632,10 @@ void HistoryWidget::fieldChanged() { InvokeQueued(this, [=] { updateInlineBotQuery(); - const auto choosingSticker = updateStickersByEmoji(); if (_history && !_inlineBot && !_editMsgId - && !choosingSticker + && (!_autocomplete || !_autocomplete->stickersEmoji()) && updateTyping) { session().sendProgressManager().update( _history, @@ -1839,6 +1725,34 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() { } } +void HistoryWidget::fileChosen(ChatHelpers::FileChosen &&data) { + controller()->hideLayer(anim::type::normal); + if (const auto info = data.document->sticker() + ; info && info->setType == Data::StickersType::Emoji) { + if (data.document->isPremiumEmoji() + && !session().premium() + && (!_peer + || !Data::AllowEmojiWithoutPremium( + _peer, + data.document))) { + showPremiumToast(data.document); + } else if (!_field->isHidden()) { + Data::InsertCustomEmoji(_field.data(), data.document); + } + } else { + controller()->sendingAnimation().appendSending( + data.messageSendingFrom); + const auto localId = data.messageSendingFrom.localId; + auto messageToSend = Api::MessageToSend( + prepareSendAction(data.options)); + messageToSend.textWithTags = base::take(data.caption); + sendExistingDocument( + data.document, + std::move(messageToSend), + localId); + } +} + void HistoryWidget::saveCloudDraft() { controller()->session().api().saveCurrentDraftToCloud(); } @@ -2043,7 +1957,11 @@ void HistoryWidget::fastShowAtEnd(not_null history) { } bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { - InvokeQueued(this, [=] { updateStickersByEmoji(); }); + InvokeQueued(this, [=] { + if (_autocomplete) { + _autocomplete->requestStickersUpdate(); + } + }); const auto editDraft = _history ? _history->localEditDraft({}) : nullptr; const auto draft = editDraft @@ -2380,6 +2298,7 @@ void HistoryWidget::showHistory( controller()->tabbedSelector()->setCurrentPeer(_peer); } refreshTabbedPanel(); + initFieldAutocomplete(); if (_peer) { _unblock->setText(((_peer->isUser() @@ -2901,7 +2820,7 @@ void HistoryWidget::refreshSendAsToggle() { bool HistoryWidget::contentOverlapped(const QRect &globalRect) { return (_attachDragAreas.document->overlaps(globalRect) || _attachDragAreas.photo->overlaps(globalRect) - || _fieldAutocomplete->overlaps(globalRect) + || (_autocomplete && _autocomplete->overlaps(globalRect)) || (_tabbedPanel && _tabbedPanel->overlaps(globalRect)) || (_inlineResults && _inlineResults->overlaps(globalRect))); } @@ -3005,7 +2924,9 @@ void HistoryWidget::updateControlsVisibility() { toggle(_botStart); } _kbShown = false; - _fieldAutocomplete->hide(); + if (_autocomplete) { + _autocomplete->hide(); + } if (_supportAutocomplete) { _supportAutocomplete->hide(); } @@ -3049,7 +2970,9 @@ void HistoryWidget::updateControlsVisibility() { } hideFieldIfVisible(); } else if (editingMessage() || _canSendMessages) { - checkFieldAutocomplete(); + if (_autocomplete) { + _autocomplete->requestRefresh(); + } _unblock->hide(); _botStart->hide(); _joinChannel->hide(); @@ -3156,7 +3079,9 @@ void HistoryWidget::updateControlsVisibility() { _fieldBarCancel->hide(); } } else { - _fieldAutocomplete->hide(); + if (_autocomplete) { + _autocomplete->hide(); + } if (_supportAutocomplete) { _supportAutocomplete->hide(); } @@ -4157,7 +4082,9 @@ void HistoryWidget::hideChildWidgets() { } void HistoryWidget::hideSelectorControlsAnimated() { - _fieldAutocomplete->hideAnimated(); + if (_autocomplete) { + _autocomplete->hideAnimated(); + } if (_supportAutocomplete) { _supportAutocomplete->hide(); } @@ -4992,10 +4919,10 @@ bool HistoryWidget::updateCmdStartShown() { }, .source = InlineBots::WebViewSourceBotMenu(), }); - } else if (!_fieldAutocomplete->isHidden()) { - _fieldAutocomplete->hideAnimated(); - } else { - _fieldAutocomplete->showFiltered(_peer, "/", true); + } else if (_autocomplete && !_autocomplete->isHidden()) { + _autocomplete->hideAnimated(); + } else if (_autocomplete) { + _autocomplete->showFiltered(_peer, "/", true); } }); _botMenu.button->widthValue( @@ -5458,7 +5385,9 @@ void HistoryWidget::clearInlineBot() { if (_inlineResults) { _inlineResults->clearInlineBot(); } - checkFieldAutocomplete(); + if (_autocomplete) { + _autocomplete->requestRefresh(); + } } void HistoryWidget::inlineBotChanged() { @@ -5483,18 +5412,6 @@ void HistoryWidget::fieldFocused() { } } -void HistoryWidget::checkFieldAutocomplete() { - if (!_history || _showAnimation) { - return; - } - - const auto autocomplete = parseMentionHashtagBotCommandQuery(); - _fieldAutocomplete->showFiltered( - _peer, - autocomplete.query, - autocomplete.fromStart); -} - void HistoryWidget::updateFieldPlaceholder() { if (!_editMsgId && _inlineBot && !_inlineLookingUpBot) { _field->setPlaceholder( @@ -5968,7 +5885,9 @@ void HistoryWidget::updateControlsGeometry() { const auto scrollAreaTop = businessBotTop + (_businessBotStatus ? _businessBotStatus->bar().height() : 0); if (_scroll->y() != scrollAreaTop) { _scroll->moveToLeft(0, scrollAreaTop); - _fieldAutocomplete->setBoundings(_scroll->geometry()); + if (_autocomplete) { + _autocomplete->setBoundings(_scroll->geometry()); + } if (_supportAutocomplete) { _supportAutocomplete->setBoundings(_scroll->geometry()); } @@ -6245,7 +6164,9 @@ void HistoryWidget::updateHistoryGeometry( visibleAreaUpdated(); } - _fieldAutocomplete->setBoundings(_scroll->geometry()); + if (_autocomplete) { + _autocomplete->setBoundings(_scroll->geometry()); + } if (_supportAutocomplete) { _supportAutocomplete->setBoundings(_scroll->geometry()); } @@ -6939,9 +6860,6 @@ bool HistoryWidget::showSlowmodeError() { void HistoryWidget::fieldTabbed() { if (_supportAutocomplete) { _supportAutocomplete->activate(_field.data()); - } else if (!_fieldAutocomplete->isHidden()) { - _fieldAutocomplete->chooseSelected( - ChatHelpers::FieldAutocomplete::ChooseMethod::ByTab); } } @@ -7500,7 +7418,7 @@ bool HistoryWidget::sendExistingDocument( document, localId); - if (_fieldAutocomplete->stickersShown()) { + if (_autocomplete && _autocomplete->stickersShown()) { clearFieldText(); //_saveDraftText = true; //_saveDraftStart = crl::now(); @@ -7986,7 +7904,9 @@ void HistoryWidget::fullInfoUpdated() { if (updateCanSendMessage()) { refresh = true; } - checkFieldAutocomplete(); + if (_autocomplete) { + _autocomplete->requestRefresh(); + } _list->refreshAboutView(); _list->updateBotInfo(); @@ -8129,8 +8049,8 @@ void HistoryWidget::escape() { } else { cancelEdit(); } - } else if (!_fieldAutocomplete->isHidden()) { - _fieldAutocomplete->hideAnimated(); + } else if (_autocomplete && !_autocomplete->isHidden()) { + _autocomplete->hideAnimated(); } else if (_replyTo && _field->getTextWithTags().text.isEmpty()) { cancelReply(); } else if (auto &voice = _voiceRecordBar; voice->isActive()) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 1f0851431..d0d73b9ca 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL enum class SendMediaType; class MessageLinksParser; struct InlineBotQuery; -struct AutocompleteQuery; namespace MTP { class Error; @@ -76,6 +75,10 @@ class ContinuousScroll; struct ChatPaintHighlight; } // namespace Ui +namespace Ui::Emoji { +class SuggestionsController; +} // namespace Ui::Emoji + namespace Window { class SessionController; } // namespace Window @@ -84,7 +87,7 @@ namespace ChatHelpers { class TabbedPanel; class TabbedSelector; class FieldAutocomplete; -enum class FieldAutocompleteChooseMethod; +struct FileChosen; } // namespace ChatHelpers namespace HistoryView { @@ -126,8 +129,6 @@ public: QWidget *parent, not_null controller); - void start(); - void historyLoaded(); [[nodiscard]] bool preventsClose(Fn &&continueCallback) const; @@ -159,7 +160,6 @@ public: bool updateReplaceMediaButton(); void updateFieldPlaceholder(); - bool updateStickersByEmoji(); bool confirmSendingFiles(const QStringList &files); bool confirmSendingFiles(not_null data); @@ -367,17 +367,15 @@ private: void fieldFocused(); void fieldResized(); - void insertHashtagOrBotCommand( - QString str, - ChatHelpers::FieldAutocompleteChooseMethod method); + void initFieldAutocomplete(); void cancelInlineBot(); void saveDraft(bool delayed = false); void saveCloudDraft(); void saveDraftDelayed(); - void checkFieldAutocomplete(); void showMembersDropdown(); void windowIsVisibleChanged(); void saveFieldToHistoryLocalDraft(); + void fileChosen(ChatHelpers::FileChosen &&data); void updateFieldSubmitSettings(); @@ -485,8 +483,6 @@ private: void orderWidgets(); [[nodiscard]] InlineBotQuery parseInlineBotQuery() const; - [[nodiscard]] auto parseMentionHashtagBotCommandQuery() const - -> AutocompleteQuery; void clearInlineBot(); void inlineBotChanged(); @@ -740,7 +736,8 @@ private: HistoryView::CornerButtons _cornerButtons; - const object_ptr _fieldAutocomplete; + std::unique_ptr _autocomplete; + std::unique_ptr _emojiSuggestions; object_ptr _supportAutocomplete; UserData *_inlineBot = nullptr; @@ -807,8 +804,6 @@ private: DragArea::Areas _attachDragAreas; - Fn _raiseEmojiSuggestions; - bool _nonEmptySelection = false; TextUpdateEvents _textUpdateEvents = (TextUpdateEvents() | TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index b0d17ff9c..933e55c92 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -859,10 +859,6 @@ ComposeControls::ComposeControls( _wrap.get(), st::historyBotCommandStart) : nullptr) -, _autocomplete(std::make_unique( - parent, - _show, - &_st.tabbed)) , _header(std::make_unique( _wrap.get(), _show, @@ -963,6 +959,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { _header->setHistory(args); registerDraftSource(); _selector->setCurrentPeer(history ? history->peer.get() : nullptr); + initFieldAutocomplete(); initWebpageProcess(); initWriteRestriction(); initForwardProcess(); @@ -1238,8 +1235,8 @@ void ComposeControls::raisePanels() { if (_attachBotsMenu) { _attachBotsMenu->raise(); } - if (_raiseEmojiSuggestions) { - _raiseEmojiSuggestions(); + if (_emojiSuggestions) { + _emojiSuggestions->raise(); } } @@ -1323,35 +1320,6 @@ void ComposeControls::hidePanelsAnimated() { } } -void ComposeControls::checkAutocomplete() { - if (!_history) { - return; - } - - const auto peer = _history->peer; - const auto autocomplete = _isInlineBot - ? AutocompleteQuery() - : ParseMentionHashtagBotCommandQuery(_field, _features); - if (!autocomplete.query.isEmpty()) { - if (autocomplete.query[0] == '#' - && cRecentWriteHashtags().isEmpty() - && cRecentSearchHashtags().isEmpty()) { - peer->session().local().readRecentHashtagsAndBots(); - } else if (autocomplete.query[0] == '@' - && cRecentInlineBots().isEmpty()) { - peer->session().local().readRecentHashtagsAndBots(); - } else if (autocomplete.query[0] == '/' - && peer->isUser() - && !peer->asUser()->isBot()) { - return; - } - } - _autocomplete->showFiltered( - peer, - autocomplete.query, - autocomplete.fromStart); -} - void ComposeControls::hide() { showStarted(); _hidden = true; @@ -1361,7 +1329,9 @@ void ComposeControls::show() { if (_hidden.current()) { _hidden = false; showFinished(); - checkAutocomplete(); + if (_autocomplete) { + _autocomplete->requestRefresh(); + } } } @@ -1630,10 +1600,6 @@ void ComposeControls::initField() { ) | rpl::start_with_next([=] { escape(); }, _field->lifetime()); - _field->tabbed( - ) | rpl::start_with_next([=] { - fieldTabbed(); - }, _field->lifetime()); _field->heightChanges( ) | rpl::start_with_next([=] { updateHeight(); @@ -1664,21 +1630,6 @@ void ComposeControls::initField() { _field->setEditLinkCallback( DefaultEditLinkCallback(_show, _field, &_st.boxField)); _field->setEditLanguageCallback(DefaultEditLanguageCallback(_show)); - initAutocomplete(); - const auto allow = [=](not_null emoji) { - return _history - && Data::AllowEmojiWithoutPremium(_history->peer, emoji); - }; - const auto suggestions = Ui::Emoji::SuggestionsController::Init( - _panelsParent, - _field, - _session, - { - .suggestCustomEmoji = true, - .allowCustomWithoutPremium = allow, - .st = &_st.suggestions, - }); - _raiseEmojiSuggestions = [=] { suggestions->raise(); }; const auto rawTextEdit = _field->rawTextEdit().get(); rpl::merge( @@ -1698,124 +1649,65 @@ void ComposeControls::updateSubmitSettings() { _field->setSubmitSettings(settings); } -void ComposeControls::initAutocomplete() { - const auto insertHashtagOrBotCommand = [=]( - const QString &string, - ChatHelpers::FieldAutocompleteChooseMethod method) { - // Send bot command at once, if it was not inserted by pressing Tab. - using Method = ChatHelpers::FieldAutocompleteChooseMethod; - if (string.at(0) == '/' && method != Method::ByTab) { - _sendCommandRequests.fire_copy(string); - setText( - _field->getTextWithTagsPart(_field->textCursor().position())); - } else { - _field->insertTag(string); - } - }; - - _autocomplete->mentionChosen( - ) | rpl::start_with_next([=]( - ChatHelpers::FieldAutocomplete::MentionChosen data) { - const auto user = data.user; - if (data.mention.isEmpty()) { - _field->insertTag( - user->firstName.isEmpty() ? user->name() : user->firstName, - PrepareMentionTag(user)); - } else { - _field->insertTag('@' + data.mention); - } - }, _autocomplete->lifetime()); - - _autocomplete->hashtagChosen( - ) | rpl::start_with_next([=]( - ChatHelpers::FieldAutocomplete::HashtagChosen data) { - insertHashtagOrBotCommand(data.hashtag, data.method); - }, _autocomplete->lifetime()); - - _autocomplete->botCommandChosen( - ) | rpl::start_with_next([=]( - ChatHelpers::FieldAutocomplete::BotCommandChosen data) { - insertHashtagOrBotCommand(data.command, data.method); - }, _autocomplete->lifetime()); - - _autocomplete->stickerChosen( - ) | rpl::start_with_next([=]( - ChatHelpers::FieldAutocomplete::StickerChosen data) { - if (!_showSlowmodeError || !_showSlowmodeError()) { - setText({}); - } - //_saveDraftText = true; - //_saveDraftStart = crl::now(); - //saveDraft(); - //saveCloudDraft(); // won't be needed if SendInlineBotResult will clear the cloud draft - _fileChosen.fire(std::move(data)); - }, _autocomplete->lifetime()); - - _autocomplete->choosingProcesses( - ) | rpl::start_with_next([=](ChatHelpers::FieldAutocomplete::Type type) { - if (type == ChatHelpers::FieldAutocomplete::Type::Stickers) { +void ComposeControls::initFieldAutocomplete() { + _emojiSuggestions = nullptr; + _autocomplete = nullptr; + if (!_history) { + return; + } + ChatHelpers::InitFieldAutocomplete(_autocomplete, { + .parent = _parent, + .show = _show, + .field = _field.get(), + .stOverride = &_st.tabbed, + .peer = _history->peer, + .features = [=] { + auto result = _features; + if (_inlineBot && !_inlineLookingUpBot) { + result.autocompleteMentions = false; + result.autocompleteHashtags = false; + result.autocompleteCommands = false; + } + if (isEditingMessage()) { + result.autocompleteCommands = false; + result.suggestStickersByEmoji = false; + } + return result; + }, + .sendMenuDetails = [=] { return sendMenuDetails(); }, + .stickerChoosing = [=] { _sendActionUpdates.fire({ .type = Api::SendProgressType::ChooseSticker, }); - } - }, _autocomplete->lifetime()); - - _autocomplete->setSendMenuDetails([=] { return sendMenuDetails(); }); - - //_autocomplete->setModerateKeyActivateCallback([=](int key) { - // return _keyboard->isHidden() - // ? false - // : _keyboard->moderateKeyActivate(key); - //}); - - _field->rawTextEdit()->installEventFilter(_autocomplete.get()); - - _session->data().botCommandsChanges( - ) | rpl::filter([=](not_null peer) { - return _history && (_history->peer == peer); - }) | rpl::start_with_next([=] { - if (_autocomplete->clearFilteredBotCommands()) { - checkAutocomplete(); - } - }, _autocomplete->lifetime()); - - _session->data().stickers().updated( - Data::StickersType::Stickers - ) | rpl::start_with_next([=] { - updateStickersByEmoji(); - }, _autocomplete->lifetime()); - - QObject::connect( - _field->rawTextEdit(), - &QTextEdit::cursorPositionChanged, - _autocomplete.get(), - [=] { checkAutocomplete(); }, - Qt::QueuedConnection); - - _autocomplete->hideFast(); -} - -bool ComposeControls::updateStickersByEmoji() { - if (!_history) { - return false; - } - const auto emoji = [&] { - const auto errorForStickers = Data::RestrictionError( - _history->peer, - ChatRestriction::SendStickers); - if (!isEditingMessage() && !errorForStickers) { - const auto &text = _field->getTextWithTags().text; - auto length = 0; - if (const auto emoji = Ui::Emoji::Find(text, &length)) { - if (text.size() <= length) { - return emoji; - } + }, + .stickerChosen = [=](ChatHelpers::FileChosen &&data) { + if (!_showSlowmodeError || !_showSlowmodeError()) { + setText({}); } - } - return EmojiPtr(nullptr); - }(); - _autocomplete->showStickers(emoji); - return (emoji != nullptr); + //_saveDraftText = true; + //_saveDraftStart = crl::now(); + //saveDraft(); + // Won't be needed if SendInlineBotResult clears the cloud draft. + //saveCloudDraft(); + _fileChosen.fire(std::move(data)); + }, + .setText = [=](TextWithTags text) { setText(text); }, + .sendBotCommand = [=](QString command) { + _sendCommandRequests.fire_copy(command); + }, + }); + const auto allow = [=](not_null emoji) { + return Data::AllowEmojiWithoutPremium(_history->peer, emoji); + }; + _emojiSuggestions.reset(Ui::Emoji::SuggestionsController::Init( + _panelsParent, + _field, + _session, + { + .suggestCustomEmoji = true, + .allowCustomWithoutPremium = allow, + .st = &_st.suggestions, + })); } void ComposeControls::updateFieldPlaceholder() { @@ -1872,10 +1764,9 @@ void ComposeControls::fieldChanged() { updateControlsVisibility(); updateControlsGeometry(_wrap->size()); } - InvokeQueued(_autocomplete.get(), [=] { + InvokeQueued(_field.get(), [=] { updateInlineBotQuery(); - const auto choosingSticker = updateStickersByEmoji(); - if (!choosingSticker && typing) { + if ((!_autocomplete || !_autocomplete->stickersEmoji()) && typing) { _sendActionUpdates.fire({ Api::SendProgressType::Typing }); } }); @@ -2026,7 +1917,11 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { ? draft->reply.messageId : FullMsgId(); - InvokeQueued(_autocomplete.get(), [=] { updateStickersByEmoji(); }); + InvokeQueued(_autocomplete.get(), [=] { + if (_autocomplete) { + _autocomplete->requestStickersUpdate(); + } + }); const auto guard = gsl::finally([&] { updateSendButtonType(); updateReplaceMediaButton(); @@ -2128,13 +2023,6 @@ void ComposeControls::cancelForward() { updateForwarding(); } -void ComposeControls::fieldTabbed() { - if (!_autocomplete->isHidden()) { - _autocomplete->chooseSelected( - ChatHelpers::FieldAutocomplete::ChooseMethod::ByTab); - } -} - rpl::producer ComposeControls::sendActionUpdates() const { return rpl::merge( _sendActionUpdates.events(), @@ -2301,7 +2189,9 @@ void ComposeControls::clearInlineBot() { if (_inlineResults) { _inlineResults->clearInlineBot(); } - checkAutocomplete(); + if (_autocomplete) { + _autocomplete->requestRefresh(); + } } void ComposeControls::inlineBotChanged() { @@ -2310,7 +2200,9 @@ void ComposeControls::inlineBotChanged() { _isInlineBot = isInlineBot; updateFieldPlaceholder(); updateSubmitSettings(); - checkAutocomplete(); + if (_autocomplete) { + _autocomplete->requestRefresh(); + } } } @@ -2983,7 +2875,9 @@ void ComposeControls::editMessage(not_null item) { } if (_autocomplete) { - InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); }); + InvokeQueued(_autocomplete.get(), [=] { + _autocomplete->requestRefresh(); + }); } } @@ -3169,7 +3063,6 @@ void ComposeControls::initWebpageProcess() { }) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) { if (flags & Data::PeerUpdate::Flag::Rights) { _preview->checkNow(false); - updateStickersByEmoji(); updateFieldPlaceholder(); } if (flags & Data::PeerUpdate::Flag::Notifications) { @@ -3401,7 +3294,7 @@ void ComposeControls::applyInlineBotQuery( updateOuterGeometry(_wrap->geometry()); } _inlineResults->queryInlineBot(_inlineBot, _history->peer, query); - if (!_autocomplete->isHidden()) { + if (_autocomplete) { _autocomplete->hideAnimated(); } } else { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 6950111cc..fc26079e7 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -68,6 +68,10 @@ class DropdownMenu; struct PreparedList; } // namespace Ui +namespace Ui::Emoji { +class SuggestionsController; +} // namespace Ui::Emoji + namespace Main { class Session; } // namespace Main @@ -259,6 +263,7 @@ private: void init(); void initField(); + void initFieldAutocomplete(); void initTabbedSelector(); void initSendButton(); void initSendAsButton(not_null peer); @@ -266,7 +271,6 @@ private: void initForwardProcess(); void initWriteRestriction(); void initVoiceRecordBar(); - void initAutocomplete(); void initKeyHandler(); void updateSubmitSettings(); void updateSendButtonType(); @@ -290,15 +294,12 @@ private: SendRequestType requestType = SendRequestType::Text) const; void orderControls(); - void checkAutocomplete(); - bool updateStickersByEmoji(); void updateFieldPlaceholder(); void updateSilentBroadcast(); void editMessage(not_null item); void escape(); void fieldChanged(); - void fieldTabbed(); void toggleTabbedSelectorMode(); void createTabbedPanel(); void setTabbedPanel(std::unique_ptr panel); @@ -392,6 +393,7 @@ private: std::unique_ptr _tabbedPanel; std::unique_ptr _attachBotsMenu; std::unique_ptr _autocomplete; + std::unique_ptr _emojiSuggestions; friend class FieldHeader; const std::unique_ptr _header; @@ -441,8 +443,6 @@ private: std::unique_ptr _preview; - Fn _raiseEmojiSuggestions; - rpl::lifetime _historyLifetime; rpl::lifetime _uploaderSubscriptions; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index f0daa4e7c..b6f69d4ee 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -422,7 +422,7 @@ MainWidget::MainWidget( cSetOtherOnline(0); - _history->start(); + session().data().stickers().notifySavedGifsUpdated(); } MainWidget::~MainWidget() = default; From 3eec43cacd65be84b01f450a0f5203b0685c9ecc Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Sep 2024 20:04:47 +0400 Subject: [PATCH 006/177] Add FieldAutocomplete to SendFilesBox. --- Telegram/SourceFiles/boxes/send_files_box.cpp | 64 +++++++++++++++++++ Telegram/SourceFiles/boxes/send_files_box.h | 6 ++ .../chat_helpers/field_autocomplete.cpp | 24 ++++--- 3 files changed, 84 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index d1757da15..a6e853eca 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/message_field.h" #include "menu/menu_send.h" #include "chat_helpers/emoji_suggestions_widget.h" +#include "chat_helpers/field_autocomplete.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "editor/photo_editor_layer_widget.h" @@ -1273,6 +1274,7 @@ void SendFilesBox::setupCaption() { [=] { return show->paused(Window::GifPauseReason::Layer); }, allow, &_st.files.caption); + setupCaptionAutocomplete(); Ui::Emoji::SuggestionsController::Init( getDelegate()->outerContainer(), _caption, @@ -1332,6 +1334,60 @@ void SendFilesBox::setupCaption() { }, _caption->lifetime()); } +void SendFilesBox::setupCaptionAutocomplete() { + if (!_captionToPeer || !_caption) { + return; + } + const auto parent = getDelegate()->outerContainer(); + ChatHelpers::InitFieldAutocomplete(_autocomplete, { + .parent = parent, + .show = _show, + .field = _caption.data(), + .peer = _captionToPeer, + .features = [=] { + auto result = ChatHelpers::ComposeFeatures(); + result.autocompleteCommands = false; + result.suggestStickersByEmoji = false; + return result; + }, + .sendMenuDetails = _sendMenuDetails, + }); + const auto raw = _autocomplete.get(); + const auto scheduled = std::make_shared(); + const auto recountPostponed = [=] { + if (*scheduled) { + return; + } + *scheduled = true; + Ui::PostponeCall(raw, [=] { + *scheduled = false; + + auto full = parent->rect(); + auto field = Ui::MapFrom(parent, this, _caption->geometry()); + _autocomplete->setBoundings(QRect( + field.x() - _caption->x(), + st::defaultBox.margin.top(), + width(), + (field.y() + + _st.files.caption.textMargins.top() + + _st.files.caption.placeholderShift + + _st.files.caption.placeholderFont->height + - st::defaultBox.margin.top()))); + }); + }; + for (auto w = (QWidget*)_caption.data(); w; w = w->parentWidget()) { + base::install_event_filter(raw, w, [=](not_null e) { + if (e->type() == QEvent::Move || e->type() == QEvent::Resize) { + recountPostponed(); + } + return base::EventFilterResult::Continue; + }); + if (w == parent) { + break; + } + } +} + void SendFilesBox::checkCharsLimitation() { const auto limits = Data::PremiumLimits(&_show->session()); const auto caption = (_caption && !_caption->isHidden()) @@ -1647,6 +1703,14 @@ void SendFilesBox::updateControlsGeometry() { _scroll->move(0, _titleHeight.current()); } +void SendFilesBox::showFinished() { + if (const auto raw = _autocomplete.get()) { + InvokeQueued(raw, [=] { + raw->raise(); + }); + } +} + void SendFilesBox::setInnerFocus() { if (_caption && !_caption->isHidden()) { _caption->setFocusFast(); diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 8f3432ebd..6646ec107 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -28,6 +28,7 @@ enum class SendType; namespace ChatHelpers { class TabbedPanel; class Show; +class FieldAutocomplete; } // namespace ChatHelpers namespace Ui { @@ -126,6 +127,8 @@ public: _cancelledCallback = std::move(callback); } + void showFinished() override; + ~SendFilesBox(); protected: @@ -206,6 +209,7 @@ private: void refreshControls(bool initial = false); void setupSendWayControls(); void setupCaption(); + void setupCaptionAutocomplete(); void setupEmojiPanel(); void updateSendWayControls(); @@ -257,6 +261,7 @@ private: SendFilesLimits _limits = {}; Fn _sendMenuDetails; Fn _sendMenuCallback; + PeerData *_captionToPeer = nullptr; SendFilesCheck _check; SendFilesConfirmed _confirmedCallback; @@ -268,6 +273,7 @@ private: bool _invertCaption = false; object_ptr _caption = { nullptr }; + std::unique_ptr _autocomplete; TextWithTags _prefilledCaptionText; object_ptr _emojiToggle = { nullptr }; base::unique_qptr _emojiPanel; diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index d956acfa6..b3a9250d8 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -1684,12 +1684,16 @@ void InitFieldAutocomplete( }, raw->lifetime()); const auto peer = descriptor.peer; + const auto features = descriptor.features; const auto processShortcut = descriptor.processShortcut; const auto shortcutMessages = (processShortcut != nullptr) ? &peer->owner().shortcutMessages() : nullptr; raw->botCommandChosen( ) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) { + if (!features().autocompleteCommands) { + return; + } using Method = FieldAutocompleteChooseMethod; const auto byTab = (data.method == Method::ByTab); const auto shortcut = data.user->isSelf(); @@ -1708,16 +1712,17 @@ void InitFieldAutocomplete( raw->setModerateKeyActivateCallback(std::move(descriptor.moderateKeyActivateCallback)); - const auto stickerChoosing = descriptor.stickerChoosing; - raw->choosingProcesses( - ) | rpl::start_with_next([=](FieldAutocomplete::Type type) { - if (type == FieldAutocomplete::Type::Stickers) { - stickerChoosing(); - } - }, raw->lifetime()); - if (auto chosen = descriptor.stickerChosen) { + if (const auto stickerChoosing = descriptor.stickerChoosing) { + raw->choosingProcesses( + ) | rpl::start_with_next([=](FieldAutocomplete::Type type) { + if (type == FieldAutocomplete::Type::Stickers) { + stickerChoosing(); + } + }, raw->lifetime()); + } + if (const auto chosen = descriptor.stickerChosen) { raw->stickerChosen( - ) | rpl::start_with_next(std::move(chosen), raw->lifetime()); + ) | rpl::start_with_next(chosen, raw->lifetime()); } field->tabbed( @@ -1727,7 +1732,6 @@ void InitFieldAutocomplete( } }, raw->lifetime()); - const auto features = descriptor.features; const auto check = [=] { auto parsed = ParseMentionHashtagBotCommandQuery(field, features()); if (parsed.query.isEmpty()) { From 2e2d8d2af3d4082866598403458dbaec835a7f97 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Sep 2024 20:04:59 +0400 Subject: [PATCH 007/177] Add FieldAutocomplete to EditCaptionBox. --- .../SourceFiles/boxes/edit_caption_box.cpp | 60 +++++++++++++++++++ Telegram/SourceFiles/boxes/edit_caption_box.h | 6 ++ 2 files changed, 66 insertions(+) diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index bd089a9b5..8377ff531 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/premium_limits_box.h" #include "boxes/premium_preview_box.h" #include "chat_helpers/emoji_suggestions_widget.h" +#include "chat_helpers/field_autocomplete.h" #include "chat_helpers/message_field.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" @@ -371,6 +372,14 @@ void EditCaptionBox::StartPhotoEdit( }); } +void EditCaptionBox::showFinished() { + if (const auto raw = _autocomplete.get()) { + InvokeQueued(raw, [=] { + raw->raise(); + }); + } +} + void EditCaptionBox::prepare() { const auto button = addButton(tr::lng_settings_save(), [=] { save(); }); addButton(tr::lng_cancel(), [=] { closeBox(); }); @@ -525,6 +534,7 @@ void EditCaptionBox::setupField() { _field.get(), Window::GifPauseReason::Layer, allow); + setupFieldAutocomplete(); Ui::Emoji::SuggestionsController::Init( getDelegate()->outerContainer(), _field, @@ -562,6 +572,56 @@ void EditCaptionBox::setupField() { }); } +void EditCaptionBox::setupFieldAutocomplete() { + const auto parent = getDelegate()->outerContainer(); + ChatHelpers::InitFieldAutocomplete(_autocomplete, { + .parent = parent, + .show = _controller->uiShow(), + .field = _field.get(), + .peer = _historyItem->history()->peer, + .features = [=] { + auto result = ChatHelpers::ComposeFeatures(); + result.autocompleteCommands = false; + result.suggestStickersByEmoji = false; + return result; + }, + }); + const auto raw = _autocomplete.get(); + const auto scheduled = std::make_shared(); + const auto recountPostponed = [=] { + if (*scheduled) { + return; + } + *scheduled = true; + Ui::PostponeCall(raw, [=] { + *scheduled = false; + + auto full = parent->rect(); + auto field = Ui::MapFrom(parent, this, _field->geometry()); + _autocomplete->setBoundings(QRect( + field.x() - _field->x(), + st::defaultBox.margin.top(), + width(), + (field.y() + + st::defaultComposeFiles.caption.textMargins.top() + + st::defaultComposeFiles.caption.placeholderShift + + st::defaultComposeFiles.caption.placeholderFont->height + - st::defaultBox.margin.top()))); + }); + }; + for (auto w = (QWidget*)_field.get(); w; w = w->parentWidget()) { + base::install_event_filter(raw, w, [=](not_null e) { + if (e->type() == QEvent::Move || e->type() == QEvent::Resize) { + recountPostponed(); + } + return base::EventFilterResult::Continue; + }); + if (w == parent) { + break; + } + } +} + void EditCaptionBox::setInitialText() { _field->setTextWithTags( _initialText, diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index af72a250f..745350616 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace ChatHelpers { class TabbedPanel; +class FieldAutocomplete; } // namespace ChatHelpers namespace Window { @@ -68,6 +69,8 @@ public: bool invertCaption, Fn saved); + void showFinished() override; + protected: void prepare() override; void setInnerFocus() override; @@ -81,6 +84,7 @@ private: void setupEditEventHandler(); void setupPhotoEditorEventHandler(); void setupField(); + void setupFieldAutocomplete(); void setupControls(); void setInitialText(); @@ -115,6 +119,8 @@ private: const base::unique_qptr _field; const base::unique_qptr _emojiToggle; + std::unique_ptr _autocomplete; + base::unique_qptr _content; base::unique_qptr _emojiPanel; base::unique_qptr _emojiFilter; From 8b86f12c23e19f360e663109079859c616f56adf Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Sep 2024 11:25:04 +0400 Subject: [PATCH 008/177] Fix quote-reply to album captions. --- Telegram/SourceFiles/history/view/history_view_message.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 35f7647bc..09f3cd381 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -3054,7 +3054,8 @@ TextForMimeData Message::selectedText(TextSelection selection) const { } SelectedQuote Message::selectedQuote(TextSelection selection) const { - const auto item = data(); + const auto textItem = this->textItem(); + const auto item = textItem ? textItem : data().get(); const auto &translated = item->translatedText(); const auto &original = item->originalText(); if (&translated != &original @@ -3068,7 +3069,7 @@ SelectedQuote Message::selectedQuote(TextSelection selection) const { const auto textSelection = mediaBefore ? media->skipSelection(selection) : selection; - return FindSelectedQuote(text(), textSelection, data()); + return FindSelectedQuote(text(), textSelection, item); } else if (const auto media = this->media()) { if (media->isDisplayed() || isHiddenByGroup()) { return media->selectedQuote(selection); From 592d46c8f2dfeaa460c3813944f9470cfdbc9850 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Sep 2024 11:25:25 +0400 Subject: [PATCH 009/177] Fix emoji packs from album captions. --- Telegram/SourceFiles/history/history_inner_widget.cpp | 4 +++- .../SourceFiles/history/view/history_view_context_menu.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 5544f1912..8c47894c8 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2916,9 +2916,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } if (_dragStateItem) { + const auto view = viewByItem(_dragStateItem); + const auto textItem = view ? view->textItem() : _dragStateItem; HistoryView::AddEmojiPacksAction( _menu, - _dragStateItem, + textItem ? textItem : _dragStateItem, HistoryView::EmojiPacksSource::Message, _controller); } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 3cddfc986..307eeb8f7 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1266,10 +1266,10 @@ base::unique_qptr FillContextMenu( } AddMessageActions(result, request, list); - if (item) { + if (const auto textItem = view ? view->textItem() : item) { AddEmojiPacksAction( result, - item, + textItem, HistoryView::EmojiPacksSource::Message, list->controller()); } From 3658b32db59fb0a4c4024d76a152f158745c2abf Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Sep 2024 13:02:38 +0400 Subject: [PATCH 010/177] Correctly count 'other accounts unread' in menu. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 1 + .../window/window_filters_menu.cpp | 1 + .../SourceFiles/window/window_main_menu.cpp | 27 ++++++++++++------- .../SourceFiles/window/window_main_menu.h | 6 +++-- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 44f558c10..5e734ce1c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -891,6 +891,7 @@ void Widget::setupMainMenuToggle() { }, lifetime()); Window::OtherAccountsUnreadState( + &controller()->session().account() ) | rpl::start_with_next([=](const Window::OthersUnreadState &state) { const auto icon = !state.count ? nullptr diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index fe8e6ec86..8578ef3b2 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -160,6 +160,7 @@ void FiltersMenu::setup() { void FiltersMenu::setupMainMenuIcon() { OtherAccountsUnreadState( + &_session->session().account() ) | rpl::start_with_next([=](const OthersUnreadState &state) { const auto icon = !state.count ? nullptr diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 0d222894a..2f97454dd 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -194,7 +194,7 @@ void ShowCallsBox(not_null window) { class MainMenu::ToggleAccountsButton final : public Ui::AbstractButton { public: - explicit ToggleAccountsButton(QWidget *parent); + ToggleAccountsButton(QWidget *parent, not_null current); [[nodiscard]] int rightSkip() const { return _rightSkip.current(); @@ -210,6 +210,7 @@ private: void validateUnreadBadge(); [[nodiscard]] QString computeUnreadBadge() const; + const not_null _current; rpl::variable _rightSkip = 0; Ui::Animations::Simple _toggledAnimation; bool _toggled = false; @@ -230,8 +231,11 @@ protected: }; -MainMenu::ToggleAccountsButton::ToggleAccountsButton(QWidget *parent) -: AbstractButton(parent) { +MainMenu::ToggleAccountsButton::ToggleAccountsButton( + QWidget *parent, + not_null current) +: AbstractButton(parent) +, _current(current) { rpl::single(rpl::empty) | rpl::then( Core::App().unreadBadgeChanges() ) | rpl::start_with_next([=] { @@ -320,7 +324,7 @@ void MainMenu::ToggleAccountsButton::validateUnreadBadge() { } QString MainMenu::ToggleAccountsButton::computeUnreadBadge() const { - const auto state = OtherAccountsUnreadStateCurrent(); + const auto state = OtherAccountsUnreadStateCurrent(_current); return state.allMuted ? QString() : (state.count > 0) @@ -381,7 +385,7 @@ MainMenu::MainMenu( this, _controller->session().user(), st::mainMenuUserpic) -, _toggleAccounts(this) +, _toggleAccounts(this, &controller->session().account()) , _setEmojiStatus(this, SetStatusLabel(&controller->session())) , _emojiStatusPanel(std::make_unique()) , _badge(std::make_unique( @@ -975,13 +979,13 @@ void MainMenu::initResetScaleButton() { }, lifetime()); } -OthersUnreadState OtherAccountsUnreadStateCurrent() { +OthersUnreadState OtherAccountsUnreadStateCurrent( + not_null current) { auto &domain = Core::App().domain(); - const auto active = &domain.active(); auto counter = 0; auto allMuted = true; for (const auto &[index, account] : domain.accounts()) { - if (account.get() == active) { + if (account.get() == current) { continue; } else if (const auto session = account->maybeSession()) { counter += session->data().unreadBadge(); @@ -996,10 +1000,13 @@ OthersUnreadState OtherAccountsUnreadStateCurrent() { }; } -rpl::producer OtherAccountsUnreadState() { +rpl::producer OtherAccountsUnreadState( + not_null current) { return rpl::single(rpl::empty) | rpl::then( Core::App().unreadBadgeChanges() - ) | rpl::map(OtherAccountsUnreadStateCurrent); + ) | rpl::map([=] { + return OtherAccountsUnreadStateCurrent(current); + }); } } // namespace Window diff --git a/Telegram/SourceFiles/window/window_main_menu.h b/Telegram/SourceFiles/window/window_main_menu.h index 6356f4103..3089151e4 100644 --- a/Telegram/SourceFiles/window/window_main_menu.h +++ b/Telegram/SourceFiles/window/window_main_menu.h @@ -108,7 +108,9 @@ struct OthersUnreadState { bool allMuted = false; }; -[[nodiscard]] OthersUnreadState OtherAccountsUnreadStateCurrent(); -[[nodiscard]] rpl::producer OtherAccountsUnreadState(); +[[nodiscard]] OthersUnreadState OtherAccountsUnreadStateCurrent( + not_null current); +[[nodiscard]] rpl::producer OtherAccountsUnreadState( + not_null current); } // namespace Window From 3218c309832430ed4fbf5647d14c215d64c747fb Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Sep 2024 13:27:39 +0400 Subject: [PATCH 011/177] Add "Include muted chats in folder counters". --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/core/core_settings.cpp | 14 +++++++--- Telegram/SourceFiles/core/core_settings.h | 7 +++++ .../settings/settings_notifications.cpp | 22 +++++++++++++++ .../window/window_filters_menu.cpp | 28 ++++++++++++++----- .../SourceFiles/window/window_filters_menu.h | 1 + 6 files changed, 62 insertions(+), 11 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 42cafa6f9..e42ed7485 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -508,6 +508,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_alert_linux" = "Draw attention to the window"; "lng_settings_badge_title" = "Badge counter"; "lng_settings_include_muted" = "Include muted chats in unread count"; +"lng_settings_include_muted_folders" = "Include muted chats in folder counters"; "lng_settings_count_unread" = "Count unread messages"; "lng_settings_events_title" = "Events"; "lng_settings_events_joined" = "Contact joined Telegram"; diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index f876ac3c4..5f304356d 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -221,7 +221,8 @@ QByteArray Settings::serialize() const { + Serialize::stringSize(noWarningExtensions) + Serialize::stringSize(_customFontFamily) + sizeof(qint32) * 3 - + Serialize::bytearraySize(_tonsiteStorageToken); + + Serialize::bytearraySize(_tonsiteStorageToken) + + sizeof(qint32); auto result = QByteArray(); result.reserve(size); @@ -375,7 +376,8 @@ QByteArray Settings::serialize() const { 1000000)) << qint32(_systemUnlockEnabled ? 1 : 0) << qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2) - << _tonsiteStorageToken; + << _tonsiteStorageToken + << qint32(_includeMutedCounterFolders ? 1 : 0); } Ensures(result.size() == size); @@ -423,6 +425,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 sendFilesWay = _sendFilesWay.serialize(); qint32 sendSubmitWay = static_cast(_sendSubmitWay.current()); qint32 includeMutedCounter = _includeMutedCounter ? 1 : 0; + qint32 includeMutedCounterFolders = _includeMutedCounterFolders ? 1 : 0; qint32 countUnreadMessages = _countUnreadMessages ? 1 : 0; std::optional noWarningExtensions; qint32 legacyExeLaunchWarning = 1; @@ -804,6 +807,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> tonsiteStorageToken; } + if (!stream.atEnd()) { + stream >> includeMutedCounterFolders; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -844,6 +850,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { case ScreenCorner::BottomLeft: _notificationsCorner = uncheckedNotificationsCorner; break; } _includeMutedCounter = (includeMutedCounter == 1); + _includeMutedCounterFolders = (includeMutedCounterFolders == 1); _countUnreadMessages = (countUnreadMessages == 1); _notifyAboutPinned = (notifyAboutPinned == 1); _autoLock = autoLock; @@ -864,8 +871,6 @@ void Settings::addFromSerialized(const QByteArray &serialized) { case Ui::InputSubmitSettings::Enter: case Ui::InputSubmitSettings::CtrlEnter: _sendSubmitWay = uncheckedSendSubmitWay; break; } - _includeMutedCounter = (includeMutedCounter == 1); - _countUnreadMessages = (countUnreadMessages == 1); if (noWarningExtensions) { const auto list = noWarningExtensions->mid(0, 10240) .split(' ', Qt::SkipEmptyParts) @@ -1345,6 +1350,7 @@ void Settings::resetOnLastLogout() { //_notificationsCount = 3; //_notificationsCorner = ScreenCorner::BottomRight; _includeMutedCounter = true; + _includeMutedCounterFolders = true; _countUnreadMessages = true; _notifyAboutPinned = true; //_autoLock = 3600; diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 06f7475e5..ab5f999a6 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -238,6 +238,12 @@ public: void setIncludeMutedCounter(bool value) { _includeMutedCounter = value; } + [[nodiscard]] bool includeMutedCounterFolders() const { + return _includeMutedCounterFolders; + } + void setIncludeMutedCounterFolders(bool value) { + _includeMutedCounterFolders = value; + } [[nodiscard]] bool countUnreadMessages() const { return _countUnreadMessages; } @@ -951,6 +957,7 @@ private: int _notificationsCount = 3; ScreenCorner _notificationsCorner = ScreenCorner::BottomRight; bool _includeMutedCounter = true; + bool _includeMutedCounterFolders = true; bool _countUnreadMessages = true; rpl::variable _notifyAboutPinned = true; int _autoLock = 3600; diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp index 1a04419be..cf3c759af 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_domain.h" #include "api/api_authorizations.h" #include "api/api_ringtones.h" +#include "data/data_chat_filters.h" #include "data/data_session.h" #include "data/data_document.h" #include "data/notify/data_notify_settings.h" @@ -1085,6 +1086,16 @@ void SetupNotificationsContent( tr::lng_settings_include_muted(), st::settingsButtonNoIcon)); muted->toggleOn(rpl::single(settings.includeMutedCounter())); + const auto mutedFolders = session->data().chatsFilters().has() + ? container->add(object_ptr