mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 23:53:58 +02:00
Add FieldAutocomplete to ComposeControls.
This commit is contained in:
parent
5d2ffae215
commit
ac02e2be9e
9 changed files with 444 additions and 199 deletions
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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([=] {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue