From ba7cd25f21425a828d9fdf5fd7790520c79e199a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Sep 2024 17:39:15 +0400 Subject: [PATCH] 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;