Add FieldAutocomplete to ComposeControls.

This commit is contained in:
John Preston 2020-11-10 17:10:08 +03:00
parent 5d2ffae215
commit ac02e2be9e
9 changed files with 444 additions and 199 deletions

View file

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu #include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "chat_helpers/message_field.h" // PrepareMentionTag.
#include "mainwindow.h" #include "mainwindow.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -27,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_single_player.h" #include "lottie/lottie_single_player.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/input_fields.h"
#include "ui/image/image.h" #include "ui/image/image.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "ui/cached_round_corners.h" #include "ui/cached_round_corners.h"
@ -39,15 +41,105 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
class FieldAutocomplete::Inner final
: public Ui::RpWidget
, private base::Subscriber {
public:
struct ScrollTo {
int top;
int bottom;
};
Inner(
not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
not_null<HashtagRows*> hrows,
not_null<BotCommandRows*> brows,
not_null<StickerRows*> srows);
void clearSel(bool hidden = false);
bool moveSel(int key);
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
bool chooseAtIndex(
FieldAutocomplete::ChooseMethod method,
int index,
Api::SendOptions options = Api::SendOptions()) const;
void setRecentInlineBotsInRows(int32 bots);
void rowsUpdated();
rpl::producer<FieldAutocomplete::MentionChosen> mentionChosen() const;
rpl::producer<FieldAutocomplete::HashtagChosen> hashtagChosen() const;
rpl::producer<FieldAutocomplete::BotCommandChosen>
botCommandChosen() const;
rpl::producer<FieldAutocomplete::StickerChosen> stickerChosen() const;
rpl::producer<ScrollTo> scrollToRequested() const;
void onParentGeometryChanged();
private:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void enterEventHook(QEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void updateSelectedRow();
void setSel(int sel, bool scroll = false);
void showPreview();
void selectByMouse(QPoint global);
QSize stickerBoundingBox() const;
void setupLottie(StickerSuggestion &suggestion);
void repaintSticker(not_null<DocumentData*> document);
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
const not_null<Window::SessionController*> _controller;
const not_null<FieldAutocomplete*> _parent;
const not_null<MentionRows*> _mrows;
const not_null<HashtagRows*> _hrows;
const not_null<BotCommandRows*> _brows;
const not_null<StickerRows*> _srows;
rpl::lifetime _stickersLifetime;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
base::unique_qptr<Ui::PopupMenu> _menu;
int _stickersPerRow = 1;
int _recentInlineBotsInRows = 0;
int _sel = -1;
int _down = -1;
std::optional<QPoint> _lastMousePosition;
bool _mouseSelection = false;
bool _overDelete = false;
bool _previewShown = false;
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
rpl::event_stream<FieldAutocomplete::HashtagChosen> _hashtagChosen;
rpl::event_stream<FieldAutocomplete::BotCommandChosen> _botCommandChosen;
rpl::event_stream<FieldAutocomplete::StickerChosen> _stickerChosen;
rpl::event_stream<ScrollTo> _scrollToRequested;
base::Timer _previewTimer;
};
FieldAutocomplete::FieldAutocomplete( FieldAutocomplete::FieldAutocomplete(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller) not_null<Window::SessionController*> controller)
: RpWidget(parent) : RpWidget(parent)
, _controller(controller) , _controller(controller)
, _scroll(this, st::mentionScroll) { , _scroll(this, st::mentionScroll) {
_scroll->setGeometry(rect()); hide();
using Inner = internal::FieldAutocompleteInner; _scroll->setGeometry(rect());
_inner = _scroll->setOwnedWidget( _inner = _scroll->setOwnedWidget(
object_ptr<Inner>( object_ptr<Inner>(
@ -76,6 +168,10 @@ FieldAutocomplete::FieldAutocomplete(
&Inner::onParentGeometryChanged); &Inner::onParentGeometryChanged);
} }
not_null<Window::SessionController*> FieldAutocomplete::controller() const {
return _controller;
}
auto FieldAutocomplete::mentionChosen() const auto FieldAutocomplete::mentionChosen() const
-> rpl::producer<FieldAutocomplete::MentionChosen> { -> rpl::producer<FieldAutocomplete::MentionChosen> {
return _inner->mentionChosen(); return _inner->mentionChosen();
@ -125,9 +221,9 @@ void FieldAutocomplete::showFiltered(
if (query.isEmpty()) { if (query.isEmpty()) {
_type = Type::Mentions; _type = Type::Mentions;
rowsUpdated( rowsUpdated(
internal::MentionRows(), MentionRows(),
internal::HashtagRows(), HashtagRows(),
internal::BotCommandRows(), BotCommandRows(),
base::take(_srows), base::take(_srows),
false); false);
return; return;
@ -171,7 +267,7 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
base::take(_mrows), base::take(_mrows),
base::take(_hrows), base::take(_hrows),
base::take(_brows), base::take(_brows),
internal::StickerRows(), StickerRows(),
false); false);
return; return;
} }
@ -203,7 +299,7 @@ inline int indexOfInFirstN(const T &v, const U &elem, int last) {
} }
} }
internal::StickerRows FieldAutocomplete::getStickerSuggestions() { FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
const auto list = _controller->session().data().stickers().getListByEmoji( const auto list = _controller->session().data().stickers().getListByEmoji(
_emoji, _emoji,
_stickersSeed _stickersSeed
@ -211,7 +307,7 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
auto result = ranges::view::all( auto result = ranges::view::all(
list list
) | ranges::view::transform([](not_null<DocumentData*> sticker) { ) | ranges::view::transform([](not_null<DocumentData*> sticker) {
return internal::StickerSuggestion{ return StickerSuggestion{
sticker, sticker,
sticker->createMediaView() sticker->createMediaView()
}; };
@ -223,7 +319,7 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
const auto i = ranges::find( const auto i = ranges::find(
result, result,
suggestion.document, suggestion.document,
&internal::StickerSuggestion::document); &StickerSuggestion::document);
if (i != end(result)) { if (i != end(result)) {
i->animated = std::move(suggestion.animated); i->animated = std::move(suggestion.animated);
} }
@ -233,10 +329,10 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
void FieldAutocomplete::updateFiltered(bool resetScroll) { void FieldAutocomplete::updateFiltered(bool resetScroll) {
int32 now = base::unixtime::now(), recentInlineBots = 0; int32 now = base::unixtime::now(), recentInlineBots = 0;
internal::MentionRows mrows; MentionRows mrows;
internal::HashtagRows hrows; HashtagRows hrows;
internal::BotCommandRows brows; BotCommandRows brows;
internal::StickerRows srows; StickerRows srows;
if (_emoji) { if (_emoji) {
srows = getStickerSuggestions(); srows = getStickerSuggestions();
} else if (_type == Type::Mentions) { } else if (_type == Type::Mentions) {
@ -435,10 +531,10 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
} }
void FieldAutocomplete::rowsUpdated( void FieldAutocomplete::rowsUpdated(
internal::MentionRows &&mrows, MentionRows &&mrows,
internal::HashtagRows &&hrows, HashtagRows &&hrows,
internal::BotCommandRows &&brows, BotCommandRows &&brows,
internal::StickerRows &&srows, StickerRows &&srows,
bool resetScroll) { bool resetScroll) {
if (mrows.empty() && hrows.empty() && brows.empty() && srows.empty()) { if (mrows.empty() && hrows.empty() && brows.empty() && srows.empty()) {
if (!isHidden()) { if (!isHidden()) {
@ -620,9 +716,7 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
return QWidget::eventFilter(obj, e); return QWidget::eventFilter(obj, e);
} }
namespace internal { FieldAutocomplete::Inner::Inner(
FieldAutocompleteInner::FieldAutocompleteInner(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent, not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows, not_null<MentionRows*> mrows,
@ -642,7 +736,7 @@ FieldAutocompleteInner::FieldAutocompleteInner(
}, lifetime()); }, lifetime());
} }
void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
QRect r(e->rect()); QRect r(e->rect());
@ -841,11 +935,11 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg); p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg);
} }
void FieldAutocompleteInner::resizeEvent(QResizeEvent *e) { void FieldAutocomplete::Inner::resizeEvent(QResizeEvent *e) {
_stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width())); _stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
} }
void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) { void FieldAutocomplete::Inner::mouseMoveEvent(QMouseEvent *e) {
const auto globalPosition = e->globalPos(); const auto globalPosition = e->globalPos();
if (!_lastMousePosition) { if (!_lastMousePosition) {
_lastMousePosition = globalPosition; _lastMousePosition = globalPosition;
@ -857,7 +951,7 @@ void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) {
selectByMouse(globalPosition); selectByMouse(globalPosition);
} }
void FieldAutocompleteInner::clearSel(bool hidden) { void FieldAutocomplete::Inner::clearSel(bool hidden) {
_overDelete = false; _overDelete = false;
_mouseSelection = false; _mouseSelection = false;
_lastMousePosition = std::nullopt; _lastMousePosition = std::nullopt;
@ -868,7 +962,7 @@ void FieldAutocompleteInner::clearSel(bool hidden) {
} }
} }
bool FieldAutocompleteInner::moveSel(int key) { bool FieldAutocomplete::Inner::moveSel(int key) {
_mouseSelection = false; _mouseSelection = false;
_lastMousePosition = std::nullopt; _lastMousePosition = std::nullopt;
@ -903,12 +997,12 @@ bool FieldAutocompleteInner::moveSel(int key) {
return true; return true;
} }
bool FieldAutocompleteInner::chooseSelected( bool FieldAutocomplete::Inner::chooseSelected(
FieldAutocomplete::ChooseMethod method) const { FieldAutocomplete::ChooseMethod method) const {
return chooseAtIndex(method, _sel); return chooseAtIndex(method, _sel);
} }
bool FieldAutocompleteInner::chooseAtIndex( bool FieldAutocomplete::Inner::chooseAtIndex(
FieldAutocomplete::ChooseMethod method, FieldAutocomplete::ChooseMethod method,
int index, int index,
Api::SendOptions options) const { Api::SendOptions options) const {
@ -955,11 +1049,11 @@ bool FieldAutocompleteInner::chooseAtIndex(
return false; return false;
} }
void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) { void FieldAutocomplete::Inner::setRecentInlineBotsInRows(int32 bots) {
_recentInlineBotsInRows = bots; _recentInlineBotsInRows = bots;
} }
void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) { void FieldAutocomplete::Inner::mousePressEvent(QMouseEvent *e) {
selectByMouse(e->globalPos()); selectByMouse(e->globalPos());
if (e->button() == Qt::LeftButton) { if (e->button() == Qt::LeftButton) {
if (_overDelete && _sel >= 0 && _sel < (_mrows->empty() ? _hrows->size() : _recentInlineBotsInRows)) { if (_overDelete && _sel >= 0 && _sel < (_mrows->empty() ? _hrows->size() : _recentInlineBotsInRows)) {
@ -999,7 +1093,7 @@ void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
} }
} }
void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) { void FieldAutocomplete::Inner::mouseReleaseEvent(QMouseEvent *e) {
_previewTimer.cancel(); _previewTimer.cancel();
int32 pressed = _down; int32 pressed = _down;
@ -1017,7 +1111,7 @@ void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick); chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
} }
void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) { void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) {
if (_sel < 0 || _srows->empty() || _down >= 0) { if (_sel < 0 || _srows->empty() || _down >= 0) {
return; return;
} }
@ -1040,11 +1134,11 @@ void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) {
} }
} }
void FieldAutocompleteInner::enterEventHook(QEvent *e) { void FieldAutocomplete::Inner::enterEventHook(QEvent *e) {
setMouseTracking(true); setMouseTracking(true);
} }
void FieldAutocompleteInner::leaveEventHook(QEvent *e) { void FieldAutocomplete::Inner::leaveEventHook(QEvent *e) {
setMouseTracking(false); setMouseTracking(false);
if (_mouseSelection) { if (_mouseSelection) {
setSel(-1); setSel(-1);
@ -1053,7 +1147,7 @@ void FieldAutocompleteInner::leaveEventHook(QEvent *e) {
} }
} }
void FieldAutocompleteInner::updateSelectedRow() { void FieldAutocomplete::Inner::updateSelectedRow() {
if (_sel >= 0) { if (_sel >= 0) {
if (_srows->empty()) { if (_srows->empty()) {
update(0, _sel * st::mentionHeight, width(), st::mentionHeight); update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
@ -1064,7 +1158,7 @@ void FieldAutocompleteInner::updateSelectedRow() {
} }
} }
void FieldAutocompleteInner::setSel(int sel, bool scroll) { void FieldAutocomplete::Inner::setSel(int sel, bool scroll) {
updateSelectedRow(); updateSelectedRow();
_sel = sel; _sel = sel;
updateSelectedRow(); updateSelectedRow();
@ -1084,13 +1178,13 @@ void FieldAutocompleteInner::setSel(int sel, bool scroll) {
} }
} }
void FieldAutocompleteInner::rowsUpdated() { void FieldAutocomplete::Inner::rowsUpdated() {
if (_srows->empty()) { if (_srows->empty()) {
_stickersLifetime.destroy(); _stickersLifetime.destroy();
} }
} }
auto FieldAutocompleteInner::getLottieRenderer() auto FieldAutocomplete::Inner::getLottieRenderer()
-> std::shared_ptr<Lottie::FrameRenderer> { -> std::shared_ptr<Lottie::FrameRenderer> {
if (auto result = _lottieRenderer.lock()) { if (auto result = _lottieRenderer.lock()) {
return result; return result;
@ -1100,7 +1194,7 @@ auto FieldAutocompleteInner::getLottieRenderer()
return result; return result;
} }
void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) { void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) {
const auto document = suggestion.document; const auto document = suggestion.document;
suggestion.animated = ChatHelpers::LottiePlayerFromDocument( suggestion.animated = ChatHelpers::LottiePlayerFromDocument(
suggestion.documentMedia.get(), suggestion.documentMedia.get(),
@ -1115,13 +1209,13 @@ void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
}, _stickersLifetime); }, _stickersLifetime);
} }
QSize FieldAutocompleteInner::stickerBoundingBox() const { QSize FieldAutocomplete::Inner::stickerBoundingBox() const {
return QSize( return QSize(
st::stickerPanSize.width() - st::buttonRadius * 2, st::stickerPanSize.width() - st::buttonRadius * 2,
st::stickerPanSize.height() - st::buttonRadius * 2); st::stickerPanSize.height() - st::buttonRadius * 2);
} }
void FieldAutocompleteInner::repaintSticker( void FieldAutocomplete::Inner::repaintSticker(
not_null<DocumentData*> document) { not_null<DocumentData*> document) {
const auto i = ranges::find( const auto i = ranges::find(
*_srows, *_srows,
@ -1140,7 +1234,7 @@ void FieldAutocompleteInner::repaintSticker(
st::stickerPanSize.height()); st::stickerPanSize.height());
} }
void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) { void FieldAutocomplete::Inner::selectByMouse(QPoint globalPosition) {
_mouseSelection = true; _mouseSelection = true;
_lastMousePosition = globalPosition; _lastMousePosition = globalPosition;
const auto mouse = mapFromGlobal(globalPosition); const auto mouse = mapFromGlobal(globalPosition);
@ -1186,7 +1280,7 @@ void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
} }
} }
void FieldAutocompleteInner::onParentGeometryChanged() { void FieldAutocomplete::Inner::onParentGeometryChanged() {
const auto globalPosition = QCursor::pos(); const auto globalPosition = QCursor::pos();
if (rect().contains(mapFromGlobal(globalPosition))) { if (rect().contains(mapFromGlobal(globalPosition))) {
setMouseTracking(true); setMouseTracking(true);
@ -1196,7 +1290,7 @@ void FieldAutocompleteInner::onParentGeometryChanged() {
} }
} }
void FieldAutocompleteInner::showPreview() { void FieldAutocomplete::Inner::showPreview() {
if (_down >= 0 && _down < _srows->size()) { if (_down >= 0 && _down < _srows->size()) {
if (const auto w = App::wnd()) { if (const auto w = App::wnd()) {
w->showMediaPreview( w->showMediaPreview(
@ -1207,29 +1301,27 @@ void FieldAutocompleteInner::showPreview() {
} }
} }
auto FieldAutocompleteInner::mentionChosen() const auto FieldAutocomplete::Inner::mentionChosen() const
-> rpl::producer<FieldAutocomplete::MentionChosen> { -> rpl::producer<FieldAutocomplete::MentionChosen> {
return _mentionChosen.events(); return _mentionChosen.events();
} }
auto FieldAutocompleteInner::hashtagChosen() const auto FieldAutocomplete::Inner::hashtagChosen() const
-> rpl::producer<FieldAutocomplete::HashtagChosen> { -> rpl::producer<FieldAutocomplete::HashtagChosen> {
return _hashtagChosen.events(); return _hashtagChosen.events();
} }
auto FieldAutocompleteInner::botCommandChosen() const auto FieldAutocomplete::Inner::botCommandChosen() const
-> rpl::producer<FieldAutocomplete::BotCommandChosen> { -> rpl::producer<FieldAutocomplete::BotCommandChosen> {
return _botCommandChosen.events(); return _botCommandChosen.events();
} }
auto FieldAutocompleteInner::stickerChosen() const auto FieldAutocomplete::Inner::stickerChosen() const
-> rpl::producer<FieldAutocomplete::StickerChosen> { -> rpl::producer<FieldAutocomplete::StickerChosen> {
return _stickerChosen.events(); return _stickerChosen.events();
} }
auto FieldAutocompleteInner::scrollToRequested() const auto FieldAutocomplete::Inner::scrollToRequested() const
-> rpl::producer<ScrollTo> { -> rpl::producer<ScrollTo> {
return _scrollToRequested.events(); return _scrollToRequested.events();
} }
} // namespace internal

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui { namespace Ui {
class PopupMenu; class PopupMenu;
class ScrollArea; class ScrollArea;
class InputField;
} // namespace Ui } // namespace Ui
namespace Lottie { namespace Lottie {
@ -32,42 +33,15 @@ class DocumentMedia;
class CloudImageView; class CloudImageView;
} // namespace Data } // namespace Data
namespace internal {
struct StickerSuggestion {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
std::unique_ptr<Lottie::SinglePlayer> animated;
};
struct MentionRow {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> userpic;
};
struct BotCommandRow {
not_null<UserData*> user;
not_null<const BotCommand*> command;
std::shared_ptr<Data::CloudImageView> userpic;
};
using HashtagRows = std::vector<QString>;
using BotCommandRows = std::vector<BotCommandRow>;
using StickerRows = std::vector<StickerSuggestion>;
using MentionRows = std::vector<MentionRow>;
class FieldAutocompleteInner;
} // namespace internal
class FieldAutocomplete final : public Ui::RpWidget { class FieldAutocomplete final : public Ui::RpWidget {
public: public:
FieldAutocomplete( FieldAutocomplete(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller); not_null<Window::SessionController*> controller);
~FieldAutocomplete(); ~FieldAutocomplete();
[[nodiscard]] not_null<Window::SessionController*> controller() const;
bool clearFilteredBotCommands(); bool clearFilteredBotCommands();
void showFiltered( void showFiltered(
not_null<PeerData*> peer, not_null<PeerData*> peer,
@ -140,29 +114,54 @@ protected:
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
private: private:
class Inner;
friend class Inner;
struct StickerSuggestion {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
std::unique_ptr<Lottie::SinglePlayer> animated;
};
struct MentionRow {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> userpic;
};
struct BotCommandRow {
not_null<UserData*> user;
not_null<const BotCommand*> command;
std::shared_ptr<Data::CloudImageView> userpic;
};
using HashtagRows = std::vector<QString>;
using BotCommandRows = std::vector<BotCommandRow>;
using StickerRows = std::vector<StickerSuggestion>;
using MentionRows = std::vector<MentionRow>;
void animationCallback(); void animationCallback();
void hideFinish(); void hideFinish();
void updateFiltered(bool resetScroll = false); void updateFiltered(bool resetScroll = false);
void recount(bool resetScroll = false); void recount(bool resetScroll = false);
internal::StickerRows getStickerSuggestions(); StickerRows getStickerSuggestions();
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
QPixmap _cache; QPixmap _cache;
internal::MentionRows _mrows; MentionRows _mrows;
internal::HashtagRows _hrows; HashtagRows _hrows;
internal::BotCommandRows _brows; BotCommandRows _brows;
internal::StickerRows _srows; StickerRows _srows;
void rowsUpdated( void rowsUpdated(
internal::MentionRows &&mrows, MentionRows &&mrows,
internal::HashtagRows &&hrows, HashtagRows &&hrows,
internal::BotCommandRows &&brows, BotCommandRows &&brows,
internal::StickerRows &&srows, StickerRows &&srows,
bool resetScroll); bool resetScroll);
object_ptr<Ui::ScrollArea> _scroll; object_ptr<Ui::ScrollArea> _scroll;
QPointer<internal::FieldAutocompleteInner> _inner; QPointer<Inner> _inner;
ChatData *_chat = nullptr; ChatData *_chat = nullptr;
UserData *_user = nullptr; UserData *_user = nullptr;
@ -186,100 +185,4 @@ private:
Fn<bool(int)> _moderateKeyActivateCallback; Fn<bool(int)> _moderateKeyActivateCallback;
friend class internal::FieldAutocompleteInner;
}; };
namespace internal {
class FieldAutocompleteInner final
: public Ui::RpWidget
, private base::Subscriber {
public:
struct ScrollTo {
int top;
int bottom;
};
FieldAutocompleteInner(
not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
not_null<HashtagRows*> hrows,
not_null<BotCommandRows*> brows,
not_null<StickerRows*> srows);
void clearSel(bool hidden = false);
bool moveSel(int key);
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
bool chooseAtIndex(
FieldAutocomplete::ChooseMethod method,
int index,
Api::SendOptions options = Api::SendOptions()) const;
void setRecentInlineBotsInRows(int32 bots);
void rowsUpdated();
rpl::producer<FieldAutocomplete::MentionChosen> mentionChosen() const;
rpl::producer<FieldAutocomplete::HashtagChosen> hashtagChosen() const;
rpl::producer<FieldAutocomplete::BotCommandChosen>
botCommandChosen() const;
rpl::producer<FieldAutocomplete::StickerChosen> stickerChosen() const;
rpl::producer<ScrollTo> scrollToRequested() const;
void onParentGeometryChanged();
private:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void enterEventHook(QEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void updateSelectedRow();
void setSel(int sel, bool scroll = false);
void showPreview();
void selectByMouse(QPoint global);
QSize stickerBoundingBox() const;
void setupLottie(StickerSuggestion &suggestion);
void repaintSticker(not_null<DocumentData*> document);
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
const not_null<Window::SessionController*> _controller;
const not_null<FieldAutocomplete*> _parent;
const not_null<MentionRows*> _mrows;
const not_null<HashtagRows*> _hrows;
const not_null<BotCommandRows*> _brows;
const not_null<StickerRows*> _srows;
rpl::lifetime _stickersLifetime;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
base::unique_qptr<Ui::PopupMenu> _menu;
int _stickersPerRow = 1;
int _recentInlineBotsInRows = 0;
int _sel = -1;
int _down = -1;
std::optional<QPoint> _lastMousePosition;
bool _mouseSelection = false;
bool _overDelete = false;
bool _previewShown = false;
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
rpl::event_stream<FieldAutocomplete::HashtagChosen> _hashtagChosen;
rpl::event_stream<FieldAutocomplete::BotCommandChosen> _botCommandChosen;
rpl::event_stream<FieldAutocomplete::StickerChosen> _stickerChosen;
rpl::event_stream<ScrollTo> _scrollToRequested;
base::Timer _previewTimer;
};
} // namespace internal

View file

@ -286,7 +286,6 @@ HistoryWidget::HistoryWidget(
_unreadMentions->installEventFilter(this); _unreadMentions->installEventFilter(this);
InitMessageField(controller, _field); InitMessageField(controller, _field);
_fieldAutocomplete->hide();
_fieldAutocomplete->mentionChosen( _fieldAutocomplete->mentionChosen(
) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) { ) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) {
@ -1216,6 +1215,9 @@ void HistoryWidget::orderWidgets() {
_pinnedBar->raise(); _pinnedBar->raise();
} }
_topShadow->raise(); _topShadow->raise();
if (_fieldAutocomplete) {
_fieldAutocomplete->raise();
}
if (_membersDropdown) { if (_membersDropdown) {
_membersDropdown->raise(); _membersDropdown->raise();
} }

View file

@ -15,12 +15,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_section.h" #include "chat_helpers/tabbed_section.h"
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "chat_helpers/field_autocomplete.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_messages.h" #include "data/data_messages.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_stickers.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
#include "storage/storage_account.h"
#include "facades.h" #include "facades.h"
#include "boxes/confirm_box.h" #include "boxes/confirm_box.h"
#include "history/history.h" #include "history/history.h"
@ -515,6 +519,9 @@ ComposeControls::ComposeControls(
st::historyComposeField, st::historyComposeField,
Ui::InputField::Mode::MultiLine, Ui::InputField::Mode::MultiLine,
tr::lng_message_ph())) tr::lng_message_ph()))
, _autocomplete(std::make_unique<FieldAutocomplete>(
parent,
window))
, _header(std::make_unique<FieldHeader>( , _header(std::make_unique<FieldHeader>(
_wrap.get(), _wrap.get(),
&_window->session().data())) &_window->session().data()))
@ -565,6 +572,12 @@ void ComposeControls::resizeToWidth(int width) {
updateHeight(); updateHeight();
} }
void ComposeControls::setAutocompleteBoundingRect(QRect rect) {
if (_autocomplete) {
_autocomplete->setBoundings(rect);
}
}
rpl::producer<int> ComposeControls::height() const { rpl::producer<int> ComposeControls::height() const {
using namespace rpl::mappers; using namespace rpl::mappers;
return rpl::conditional( return rpl::conditional(
@ -619,6 +632,10 @@ rpl::producer<VoiceToSend> ComposeControls::sendVoiceRequests() const {
return _voiceRecordBar->sendVoiceRequests(); return _voiceRecordBar->sendVoiceRequests();
} }
rpl::producer<QString> ComposeControls::sendCommandRequests() const {
return _sendCommandRequests.events();
}
rpl::producer<MessageToEdit> ComposeControls::editRequests() const { rpl::producer<MessageToEdit> ComposeControls::editRequests() const {
auto toValue = rpl::map([=] { return _header->queryToEdit(); }); auto toValue = rpl::map([=] { return _header->queryToEdit(); });
auto filter = rpl::filter([=] { auto filter = rpl::filter([=] {
@ -681,6 +698,21 @@ void ComposeControls::showFinished() {
_voiceRecordBar->orderControls(); _voiceRecordBar->orderControls();
} }
void ComposeControls::raisePanels() {
if (_autocomplete) {
_autocomplete->raise();
}
if (_inlineResults) {
_inlineResults->raise();
}
if (_tabbedPanel) {
_tabbedPanel->raise();
}
if (_raiseEmojiSuggestions) {
_raiseEmojiSuggestions();
}
}
void ComposeControls::showForGrab() { void ComposeControls::showForGrab() {
showFinished(); showFinished();
} }
@ -708,7 +740,9 @@ void ComposeControls::setText(const TextWithTags &textWithTags) {
} }
void ComposeControls::hidePanelsAnimated() { void ComposeControls::hidePanelsAnimated() {
//_fieldAutocomplete->hideAnimated(); if (_autocomplete) {
_autocomplete->hideAnimated();
}
if (_tabbedPanel) { if (_tabbedPanel) {
_tabbedPanel->hideAnimated(); _tabbedPanel->hideAnimated();
} }
@ -717,6 +751,36 @@ void ComposeControls::hidePanelsAnimated() {
} }
} }
void ComposeControls::checkAutocomplete() {
if (!_history) {
return;
}
const auto peer = _history->peer;
const auto isInlineBot = false;// _inlineBot && !_inlineLookingUpBot;
const auto autocomplete = isInlineBot
? AutocompleteQuery()
: ParseMentionHashtagBotCommandQuery(_field);
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::init() { void ComposeControls::init() {
initField(); initField();
initTabbedSelector(); initTabbedSelector();
@ -817,11 +881,12 @@ void ComposeControls::initField() {
_field->setSubmitSettings(Core::App().settings().sendSubmitWay()); _field->setSubmitSettings(Core::App().settings().sendSubmitWay());
//Ui::Connect(_field, &Ui::InputField::submitted, [=] { send(); }); //Ui::Connect(_field, &Ui::InputField::submitted, [=] { send(); });
Ui::Connect(_field, &Ui::InputField::cancelled, [=] { escape(); }); Ui::Connect(_field, &Ui::InputField::cancelled, [=] { escape(); });
//Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); }); Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); });
Ui::Connect(_field, &Ui::InputField::resized, [=] { updateHeight(); }); Ui::Connect(_field, &Ui::InputField::resized, [=] { updateHeight(); });
//Ui::Connect(_field, &Ui::InputField::focused, [=] { fieldFocused(); }); //Ui::Connect(_field, &Ui::InputField::focused, [=] { fieldFocused(); });
Ui::Connect(_field, &Ui::InputField::changed, [=] { fieldChanged(); }); Ui::Connect(_field, &Ui::InputField::changed, [=] { fieldChanged(); });
InitMessageField(_window, _field); InitMessageField(_window, _field);
initAutocomplete();
const auto suggestions = Ui::Emoji::SuggestionsController::Init( const auto suggestions = Ui::Emoji::SuggestionsController::Init(
_parent, _parent,
_field, _field,
@ -830,6 +895,112 @@ void ComposeControls::initField() {
InitSpellchecker(_window, _field); InitSpellchecker(_window, _field);
} }
void ComposeControls::initAutocomplete() {
const auto insertHashtagOrBotCommand = [=](
const QString &string,
FieldAutocomplete::ChooseMethod method) {
// Send bot command at once, if it was not inserted by pressing Tab.
if (string.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) {
_sendCommandRequests.fire_copy(string);
setText(
_field->getTextWithTagsPart(_field->textCursor().position()));
} else {
_field->insertTag(string);
}
};
const auto insertMention = [=](not_null<UserData*> user) {
auto replacement = QString();
auto entityTag = QString();
if (user->username.isEmpty()) {
_field->insertTag(
user->firstName.isEmpty() ? user->name : user->firstName,
PrepareMentionTag(user));
} else {
_field->insertTag('@' + user->username);
}
};
_autocomplete->mentionChosen(
) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) {
insertMention(data.user);
}, _autocomplete->lifetime());
_autocomplete->hashtagChosen(
) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) {
insertHashtagOrBotCommand(data.hashtag, data.method);
}, _autocomplete->lifetime());
_autocomplete->botCommandChosen(
) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) {
insertHashtagOrBotCommand(data.command, data.method);
}, _autocomplete->lifetime());
_autocomplete->stickerChosen(
) | rpl::start_with_next([=](FieldAutocomplete::StickerChosen data) {
setText({});
//_saveDraftText = true;
//_saveDraftStart = crl::now();
//onDraftSave();
//onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft
_fileChosen.fire(FileChosen{
.document = data.sticker,
.options = data.options,
});
}, _autocomplete->lifetime());
//_autocomplete->setModerateKeyActivateCallback([=](int key) {
// return _keyboard->isHidden()
// ? false
// : _keyboard->moderateKeyActivate(key);
//});
_field->rawTextEdit()->installEventFilter(_autocomplete.get());
_window->session().data().botCommandsChanges(
) | rpl::filter([=](not_null<UserData*> user) {
const auto peer = _history ? _history->peer.get() : nullptr;
return peer && (peer == user || !peer->isUser());
}) | rpl::start_with_next([=](not_null<UserData*> user) {
if (_autocomplete->clearFilteredBotCommands()) {
checkAutocomplete();
}
}, _autocomplete->lifetime());
_window->session().data().stickers().updated(
) | rpl::start_with_next([=] {
updateStickersByEmoji();
}, _autocomplete->lifetime());
QObject::connect(
_field->rawTextEdit(),
&QTextEdit::cursorPositionChanged,
_autocomplete.get(),
[=] { checkAutocomplete(); },
Qt::QueuedConnection);
}
void ComposeControls::updateStickersByEmoji() {
if (!_history) {
return;
}
const auto emoji = [&] {
const auto errorForStickers = Data::RestrictionError(
_history->peer,
ChatRestriction::f_send_stickers);
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;
}
}
}
return EmojiPtr(nullptr);
}();
_autocomplete->showStickers(emoji);
}
void ComposeControls::fieldChanged() { void ComposeControls::fieldChanged() {
if (/*!_inlineBot if (/*!_inlineBot
&& */!_header->isEditingMessage() && */!_header->isEditingMessage()
@ -840,6 +1011,15 @@ void ComposeControls::fieldChanged() {
if (showRecordButton()) { if (showRecordButton()) {
//_previewCancelled = false; //_previewCancelled = false;
} }
InvokeQueued(_autocomplete.get(), [=] {
updateStickersByEmoji();
});
}
void ComposeControls::fieldTabbed() {
if (!_autocomplete->isHidden()) {
_autocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
}
} }
rpl::producer<SendActionUpdate> ComposeControls::sendActionUpdates() const { rpl::producer<SendActionUpdate> ComposeControls::sendActionUpdates() const {
@ -1136,10 +1316,16 @@ void ComposeControls::updateHeight() {
void ComposeControls::editMessage(FullMsgId id) { void ComposeControls::editMessage(FullMsgId id) {
cancelEditMessage(); cancelEditMessage();
_header->editMessage(id); _header->editMessage(id);
if (_autocomplete) {
InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); });
}
} }
void ComposeControls::cancelEditMessage() { void ComposeControls::cancelEditMessage() {
_header->editMessage({}); _header->editMessage({});
if (_autocomplete) {
InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); });
}
} }
void ComposeControls::replyToMessage(FullMsgId id) { void ComposeControls::replyToMessage(FullMsgId id) {
@ -1151,6 +1337,20 @@ void ComposeControls::cancelReplyMessage() {
_header->replyToMessage({}); _header->replyToMessage({});
} }
bool ComposeControls::handleCancelRequest() {
if (isEditingMessage()) {
cancelEditMessage();
return true;
} else if (_autocomplete && !_autocomplete->isHidden()) {
_autocomplete->hideAnimated();
return true;
} else if (replyingToMessage()) {
cancelReplyMessage();
return true;
}
return false;
}
void ComposeControls::initWebpageProcess() { void ComposeControls::initWebpageProcess() {
Expects(_history); Expects(_history);
const auto peer = _history->peer; const auto peer = _history->peer;
@ -1287,7 +1487,10 @@ void ComposeControls::initWebpageProcess() {
Data::PeerUpdate::Flag::Rights Data::PeerUpdate::Flag::Rights
) | rpl::filter([=](const Data::PeerUpdate &update) { ) | rpl::filter([=](const Data::PeerUpdate &update) {
return (update.peer.get() == peer); return (update.peer.get() == peer);
}) | rpl::start_with_next(checkPreview, lifetime); }) | rpl::start_with_next([=] {
checkPreview();
updateStickersByEmoji();
}, lifetime);
_window->session().downloaderTaskFinished( _window->session().downloaderTaskFinished(
) | rpl::filter([=] { ) | rpl::filter([=] {

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
class History; class History;
class FieldAutocomplete;
namespace ChatHelpers { namespace ChatHelpers {
class TabbedPanel; class TabbedPanel;
@ -89,6 +90,7 @@ public:
void move(int x, int y); void move(int x, int y);
void resizeToWidth(int width); void resizeToWidth(int width);
void setAutocompleteBoundingRect(QRect rect);
[[nodiscard]] rpl::producer<int> height() const; [[nodiscard]] rpl::producer<int> height() const;
[[nodiscard]] int heightCurrent() const; [[nodiscard]] int heightCurrent() const;
@ -96,6 +98,7 @@ public:
[[nodiscard]] rpl::producer<> cancelRequests() const; [[nodiscard]] rpl::producer<> cancelRequests() const;
[[nodiscard]] rpl::producer<> sendRequests() const; [[nodiscard]] rpl::producer<> sendRequests() const;
[[nodiscard]] rpl::producer<VoiceToSend> sendVoiceRequests() const; [[nodiscard]] rpl::producer<VoiceToSend> sendVoiceRequests() const;
[[nodiscard]] rpl::producer<QString> sendCommandRequests() const;
[[nodiscard]] rpl::producer<MessageToEdit> editRequests() const; [[nodiscard]] rpl::producer<MessageToEdit> editRequests() const;
[[nodiscard]] rpl::producer<> attachRequests() const; [[nodiscard]] rpl::producer<> attachRequests() const;
[[nodiscard]] rpl::producer<FileChosen> fileChosen() const; [[nodiscard]] rpl::producer<FileChosen> fileChosen() const;
@ -122,6 +125,7 @@ public:
void showForGrab(); void showForGrab();
void showStarted(); void showStarted();
void showFinished(); void showFinished();
void raisePanels();
void editMessage(FullMsgId id); void editMessage(FullMsgId id);
void cancelEditMessage(); void cancelEditMessage();
@ -129,6 +133,8 @@ public:
void replyToMessage(FullMsgId id); void replyToMessage(FullMsgId id);
void cancelReplyMessage(); void cancelReplyMessage();
bool handleCancelRequest();
[[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const; [[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const;
[[nodiscard]] WebPageId webPageId() const; [[nodiscard]] WebPageId webPageId() const;
void setText(const TextWithTags &text); void setText(const TextWithTags &text);
@ -154,6 +160,7 @@ private:
void initWebpageProcess(); void initWebpageProcess();
void initWriteRestriction(); void initWriteRestriction();
void initVoiceRecordBar(); void initVoiceRecordBar();
void initAutocomplete();
void updateSendButtonType(); void updateSendButtonType();
void updateHeight(); void updateHeight();
void updateWrappingVisibility(); void updateWrappingVisibility();
@ -163,9 +170,12 @@ private:
void paintBackground(QRect clip); void paintBackground(QRect clip);
void orderControls(); void orderControls();
void checkAutocomplete();
void updateStickersByEmoji();
void escape(); void escape();
void fieldChanged(); void fieldChanged();
void fieldTabbed();
void toggleTabbedSelectorMode(); void toggleTabbedSelectorMode();
void createTabbedPanel(); void createTabbedPanel();
void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel); void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel);
@ -194,6 +204,7 @@ private:
const not_null<Ui::InputField*> _field; const not_null<Ui::InputField*> _field;
std::unique_ptr<InlineBots::Layout::Widget> _inlineResults; std::unique_ptr<InlineBots::Layout::Widget> _inlineResults;
std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel; std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;
std::unique_ptr<FieldAutocomplete> _autocomplete;
friend class FieldHeader; friend class FieldHeader;
const std::unique_ptr<FieldHeader> _header; const std::unique_ptr<FieldHeader> _header;
@ -204,6 +215,7 @@ private:
rpl::event_stream<PhotoChosen> _photoChosen; rpl::event_stream<PhotoChosen> _photoChosen;
rpl::event_stream<ChatHelpers::TabbedSelector::InlineChosen> _inlineResultChosen; rpl::event_stream<ChatHelpers::TabbedSelector::InlineChosen> _inlineResultChosen;
rpl::event_stream<SendActionUpdate> _sendActionUpdates; rpl::event_stream<SendActionUpdate> _sendActionUpdates;
rpl::event_stream<QString> _sendCommandRequests;
TextWithTags _localSavedText; TextWithTags _localSavedText;
TextUpdateEvents _textUpdateEvents; TextUpdateEvents _textUpdateEvents;

View file

@ -253,6 +253,7 @@ RepliesWidget::RepliesWidget(
setupScrollDownButton(); setupScrollDownButton();
setupComposeControls(); setupComposeControls();
orderWidgets();
} }
RepliesWidget::~RepliesWidget() { RepliesWidget::~RepliesWidget() {
@ -263,6 +264,17 @@ RepliesWidget::~RepliesWidget() {
_history->owner().repliesSendActionPainterRemoved(_history, _rootId); _history->owner().repliesSendActionPainterRemoved(_history, _rootId);
} }
void RepliesWidget::orderWidgets() {
if (_topBar) {
_topBar->raise();
}
if (_rootView) {
_rootView->raise();
}
_topBarShadow->raise();
_composeControls->raisePanels();
}
void RepliesWidget::sendReadTillRequest() { void RepliesWidget::sendReadTillRequest() {
if (!_root) { if (!_root) {
_readRequestPending = true; _readRequestPending = true;
@ -428,6 +440,18 @@ void RepliesWidget::setupComposeControls() {
sendVoice(data.bytes, data.waveform, data.duration); sendVoice(data.bytes, data.waveform, data.duration);
}, lifetime()); }, lifetime());
_composeControls->sendCommandRequests(
) | rpl::start_with_next([=](const QString &command) {
if (showSlowmodeError()) {
return;
}
auto message = ApiWrap::MessageToSend(_history);
message.textWithTags = { command };
message.action.replyTo = replyToId();
session().api().sendMessage(std::move(message));
finishSending();
}, lifetime());
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0); const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
_composeControls->editRequests( _composeControls->editRequests(
) | rpl::start_with_next([=](auto data) { ) | rpl::start_with_next([=](auto data) {
@ -1474,6 +1498,7 @@ void RepliesWidget::updateControlsGeometry() {
updateInnerVisibleArea(); updateInnerVisibleArea();
} }
_composeControls->move(0, bottom - controlsHeight); _composeControls->move(0, bottom - controlsHeight);
_composeControls->setAutocompleteBoundingRect(_scroll->geometry());
updateScrollDownPosition(); updateScrollDownPosition();
} }
@ -1598,12 +1623,7 @@ void RepliesWidget::listCancelRequest() {
if (_inner && !_inner->getSelectedItems().empty()) { if (_inner && !_inner->getSelectedItems().empty()) {
clearSelected(); clearSelected();
return; return;
} } else if (_composeControls->handleCancelRequest()) {
if (_composeControls->isEditingMessage()) {
_composeControls->cancelEditMessage();
return;
} else if (_composeControls->replyingToMessage()) {
_composeControls->cancelReplyMessage();
return; return;
} }
controller()->showBackFromStack(); controller()->showBackFromStack();

View file

@ -186,6 +186,7 @@ private:
[[nodiscard]] MsgId replyToId() const; [[nodiscard]] MsgId replyToId() const;
[[nodiscard]] HistoryItem *lookupRoot() const; [[nodiscard]] HistoryItem *lookupRoot() const;
[[nodiscard]] bool computeAreComments() const; [[nodiscard]] bool computeAreComments() const;
void orderWidgets();
void pushReplyReturn(not_null<HistoryItem*> item); void pushReplyReturn(not_null<HistoryItem*> item);
void computeCurrentReplyReturn(); void computeCurrentReplyReturn();

View file

@ -180,6 +180,19 @@ void ScheduledWidget::setupComposeControls() {
sendVoice(data.bytes, data.waveform, data.duration); sendVoice(data.bytes, data.waveform, data.duration);
}, lifetime()); }, lifetime());
_composeControls->sendCommandRequests(
) | rpl::start_with_next([=](const QString &command) {
const auto callback = [=](Api::SendOptions options) {
auto message = ApiWrap::MessageToSend(_history);
message.textWithTags = { command };
message.action.options = options;
session().api().sendMessage(std::move(message));
};
Ui::show(
PrepareScheduleBox(this, sendMenuType(), callback),
Ui::LayerOption::KeepOther);
}, lifetime());
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0); const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
_composeControls->editRequests( _composeControls->editRequests(
) | rpl::start_with_next([=](auto data) { ) | rpl::start_with_next([=](auto data) {
@ -979,6 +992,7 @@ void ScheduledWidget::updateControlsGeometry() {
updateInnerVisibleArea(); updateInnerVisibleArea();
} }
_composeControls->move(0, bottom - controlsHeight); _composeControls->move(0, bottom - controlsHeight);
_composeControls->setAutocompleteBoundingRect(_scroll->geometry());
updateScrollDownPosition(); updateScrollDownPosition();
} }
@ -1057,9 +1071,7 @@ void ScheduledWidget::listCancelRequest() {
if (_inner && !_inner->getSelectedItems().empty()) { if (_inner && !_inner->getSelectedItems().empty()) {
clearSelected(); clearSelected();
return; return;
} } else if (_composeControls->handleCancelRequest()) {
if (_composeControls->isEditingMessage()) {
_composeControls->cancelEditMessage();
return; return;
} }
controller()->showBackFromStack(); controller()->showBackFromStack();

View file

@ -35,7 +35,7 @@ struct Contact {
QString lastName; QString lastName;
}; };
class Autocomplete : public Ui::RpWidget { class Autocomplete final : public Ui::RpWidget {
public: public:
Autocomplete(QWidget *parent, not_null<Main::Session*> session); Autocomplete(QWidget *parent, not_null<Main::Session*> session);