mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Extract FieldAutocomplete code.
This commit is contained in:
parent
969152e949
commit
ba7cd25f21
10 changed files with 497 additions and 481 deletions
|
@ -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
|
||||
|
|
|
@ -713,11 +713,13 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) {
|
|||
}
|
||||
|
||||
SuggestionsController::SuggestionsController(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<QWidget*> outer,
|
||||
not_null<QTextEdit*> field,
|
||||
not_null<Main::Session*> 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()); })
|
||||
|
|
|
@ -37,7 +37,7 @@ class SuggestionsWidget;
|
|||
|
||||
using SuggestionsQuery = std::variant<QString, EmojiPtr>;
|
||||
|
||||
class SuggestionsController {
|
||||
class SuggestionsController final : public QObject {
|
||||
public:
|
||||
struct Options {
|
||||
bool suggestExactFirstWord = true;
|
||||
|
@ -47,6 +47,7 @@ public:
|
|||
};
|
||||
|
||||
SuggestionsController(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<QWidget*> outer,
|
||||
not_null<QTextEdit*> field,
|
||||
not_null<Main::Session*> session,
|
||||
|
|
|
@ -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<Window::SessionController*> controller)
|
||||
: FieldAutocomplete(parent, controller->uiShow()) {
|
||||
}
|
||||
|
||||
FieldAutocomplete::FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<Show> show,
|
||||
|
@ -252,6 +247,22 @@ std::shared_ptr<Show> 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<FieldAutocomplete::MentionChosen> {
|
||||
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<FieldAutocomplete> &autocomplete,
|
||||
FieldAutocompleteDescriptor &&descriptor) {
|
||||
Expects(!autocomplete);
|
||||
|
||||
autocomplete = std::make_unique<FieldAutocomplete>(
|
||||
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<PeerData*> 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
|
||||
|
|
|
@ -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<Window::SessionController*> controller);
|
||||
FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<Show> show,
|
||||
|
@ -74,16 +72,19 @@ public:
|
|||
not_null<PeerData*> 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> mentionChosen() const;
|
||||
rpl::producer<HashtagChosen> hashtagChosen() const;
|
||||
rpl::producer<BotCommandChosen> botCommandChosen() const;
|
||||
rpl::producer<StickerChosen> stickerChosen() const;
|
||||
rpl::producer<Type> choosingProcesses() const;
|
||||
void requestRefresh();
|
||||
[[nodiscard]] rpl::producer<> refreshRequests() const;
|
||||
void requestStickersUpdate();
|
||||
[[nodiscard]] rpl::producer<> stickersUpdateRequests() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<MentionChosen> mentionChosen() const;
|
||||
[[nodiscard]] rpl::producer<HashtagChosen> hashtagChosen() const;
|
||||
[[nodiscard]] rpl::producer<BotCommandChosen> botCommandChosen() const;
|
||||
[[nodiscard]] rpl::producer<StickerChosen> stickerChosen() const;
|
||||
[[nodiscard]] rpl::producer<Type> 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<bool(int)> _moderateKeyActivateCallback;
|
||||
|
||||
};
|
||||
|
||||
struct FieldAutocompleteDescriptor {
|
||||
not_null<QWidget*> parent;
|
||||
std::shared_ptr<Show> show;
|
||||
not_null<Ui::InputField*> field;
|
||||
const style::EmojiPan *stOverride = nullptr;
|
||||
not_null<PeerData*> peer;
|
||||
Fn<ComposeFeatures()> features;
|
||||
Fn<SendMenu::Details()> sendMenuDetails;
|
||||
Fn<void()> stickerChoosing;
|
||||
Fn<void(FileChosen&&)> stickerChosen;
|
||||
Fn<void(TextWithTags)> setText;
|
||||
Fn<void(QString)> sendBotCommand;
|
||||
Fn<void(QString)> processShortcut;
|
||||
Fn<bool(int)> moderateKeyActivateCallback;
|
||||
};
|
||||
void InitFieldAutocomplete(
|
||||
std::unique_ptr<FieldAutocomplete> &autocomplete,
|
||||
FieldAutocompleteDescriptor &&descriptor);
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
|
|
@ -243,7 +243,6 @@ HistoryWidget::HistoryWidget(
|
|||
_scroll.data(),
|
||||
controller->chatStyle(),
|
||||
static_cast<HistoryView::CornerButtonsDelegate*>(this))
|
||||
, _fieldAutocomplete(this, controller->uiShow())
|
||||
, _supportAutocomplete(session().supportMode()
|
||||
? object_ptr<Support::Autocomplete>(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<const QMimeData*> 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<PeerData*> peer,
|
||||
std::optional<bool> 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*> 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()) {
|
||||
|
|
|
@ -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<Window::SessionController*> controller);
|
||||
|
||||
void start();
|
||||
|
||||
void historyLoaded();
|
||||
|
||||
[[nodiscard]] bool preventsClose(Fn<void()> &&continueCallback) const;
|
||||
|
@ -159,7 +160,6 @@ public:
|
|||
|
||||
bool updateReplaceMediaButton();
|
||||
void updateFieldPlaceholder();
|
||||
bool updateStickersByEmoji();
|
||||
|
||||
bool confirmSendingFiles(const QStringList &files);
|
||||
bool confirmSendingFiles(not_null<const QMimeData*> 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<ChatHelpers::FieldAutocomplete> _fieldAutocomplete;
|
||||
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
|
||||
std::unique_ptr<Ui::Emoji::SuggestionsController> _emojiSuggestions;
|
||||
object_ptr<Support::Autocomplete> _supportAutocomplete;
|
||||
|
||||
UserData *_inlineBot = nullptr;
|
||||
|
@ -807,8 +804,6 @@ private:
|
|||
|
||||
DragArea::Areas _attachDragAreas;
|
||||
|
||||
Fn<void()> _raiseEmojiSuggestions;
|
||||
|
||||
bool _nonEmptySelection = false;
|
||||
|
||||
TextUpdateEvents _textUpdateEvents = (TextUpdateEvents() | TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping);
|
||||
|
|
|
@ -859,10 +859,6 @@ ComposeControls::ComposeControls(
|
|||
_wrap.get(),
|
||||
st::historyBotCommandStart)
|
||||
: nullptr)
|
||||
, _autocomplete(std::make_unique<ChatHelpers::FieldAutocomplete>(
|
||||
parent,
|
||||
_show,
|
||||
&_st.tabbed))
|
||||
, _header(std::make_unique<FieldHeader>(
|
||||
_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<DocumentData*> 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<PeerData*> 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<DocumentData*> 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<SendActionUpdate> 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<HistoryItem*> 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 {
|
||||
|
|
|
@ -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<PeerData*> 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<HistoryItem*> item);
|
||||
|
||||
void escape();
|
||||
void fieldChanged();
|
||||
void fieldTabbed();
|
||||
void toggleTabbedSelectorMode();
|
||||
void createTabbedPanel();
|
||||
void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel);
|
||||
|
@ -392,6 +393,7 @@ private:
|
|||
std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;
|
||||
std::unique_ptr<Ui::DropdownMenu> _attachBotsMenu;
|
||||
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
|
||||
std::unique_ptr<Ui::Emoji::SuggestionsController> _emojiSuggestions;
|
||||
|
||||
friend class FieldHeader;
|
||||
const std::unique_ptr<FieldHeader> _header;
|
||||
|
@ -441,8 +443,6 @@ private:
|
|||
|
||||
std::unique_ptr<Controls::WebpageProcessor> _preview;
|
||||
|
||||
Fn<void()> _raiseEmojiSuggestions;
|
||||
|
||||
rpl::lifetime _historyLifetime;
|
||||
rpl::lifetime _uploaderSubscriptions;
|
||||
|
||||
|
|
|
@ -422,7 +422,7 @@ MainWidget::MainWidget(
|
|||
|
||||
cSetOtherOnline(0);
|
||||
|
||||
_history->start();
|
||||
session().data().stickers().notifySavedGifsUpdated();
|
||||
}
|
||||
|
||||
MainWidget::~MainWidget() = default;
|
||||
|
|
Loading…
Add table
Reference in a new issue