mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
feat: search in deleted messages
This commit is contained in:
@@ -296,14 +296,38 @@ void addDeletedMessage(const DeletedMessage &message) {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DeletedMessage> getDeletedMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit) {
|
||||
std::vector<DeletedMessage> getDeletedMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit, const std::string &searchQuery) {
|
||||
if (searchQuery.empty()) {
|
||||
return storage.get_all<DeletedMessage>(
|
||||
where(
|
||||
column<DeletedMessage>(&DeletedMessage::userId) == userId and
|
||||
column<DeletedMessage>(&DeletedMessage::dialogId) == dialogId and
|
||||
(column<DeletedMessage>(&DeletedMessage::topicId) == topicId or topicId == 0) and
|
||||
(column<DeletedMessage>(&DeletedMessage::messageId) > minId or minId == 0) and
|
||||
(column<DeletedMessage>(&DeletedMessage::messageId) < maxId or maxId == 0)
|
||||
),
|
||||
order_by(column<DeletedMessage>(&DeletedMessage::messageId)).desc(),
|
||||
limit(totalLimit)
|
||||
);
|
||||
}
|
||||
|
||||
std::string escaped;
|
||||
escaped.reserve(searchQuery.size());
|
||||
for (const auto c : searchQuery) {
|
||||
if (c == '%' || c == '_' || c == '\\') {
|
||||
escaped += '\\';
|
||||
}
|
||||
escaped += c;
|
||||
}
|
||||
const auto pattern = "%" + escaped + "%";
|
||||
return storage.get_all<DeletedMessage>(
|
||||
where(
|
||||
column<DeletedMessage>(&DeletedMessage::userId) == userId and
|
||||
column<DeletedMessage>(&DeletedMessage::dialogId) == dialogId and
|
||||
(column<DeletedMessage>(&DeletedMessage::topicId) == topicId or topicId == 0) and
|
||||
(column<DeletedMessage>(&DeletedMessage::messageId) > minId or minId == 0) and
|
||||
(column<DeletedMessage>(&DeletedMessage::messageId) < maxId or maxId == 0)
|
||||
(column<DeletedMessage>(&DeletedMessage::messageId) < maxId or maxId == 0) and
|
||||
like(column<DeletedMessage>(&DeletedMessage::text), pattern, "\\")
|
||||
),
|
||||
order_by(column<DeletedMessage>(&DeletedMessage::messageId)).desc(),
|
||||
limit(totalLimit)
|
||||
|
||||
@@ -26,7 +26,7 @@ std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageI
|
||||
bool hasRevisions(ID userId, ID dialogId, ID messageId);
|
||||
|
||||
void addDeletedMessage(const DeletedMessage &message);
|
||||
std::vector<DeletedMessage> getDeletedMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit);
|
||||
std::vector<DeletedMessage> getDeletedMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit, const std::string &searchQuery = "");
|
||||
bool hasDeletedMessages(ID userId, ID dialogId, ID topicId);
|
||||
|
||||
std::vector<RegexFilter> getAllRegexFilters();
|
||||
|
||||
@@ -127,10 +127,10 @@ void addDeletedMessage(not_null<HistoryItem*> item) {
|
||||
}
|
||||
|
||||
std::vector<AyuMessageBase>
|
||||
getDeletedMessages(not_null<PeerData*> peer, ID topicId, ID minId, ID maxId, int totalLimit) {
|
||||
getDeletedMessages(not_null<PeerData*> peer, ID topicId, ID minId, ID maxId, int totalLimit, const QString &searchQuery) {
|
||||
const ID userId = peer->session().userId().bare & PeerId::kChatTypeMask;
|
||||
return convertToBase(
|
||||
AyuDatabase::getDeletedMessages(userId, getDialogIdFromPeer(peer), topicId, minId, maxId, totalLimit));
|
||||
AyuDatabase::getDeletedMessages(userId, getDialogIdFromPeer(peer), topicId, minId, maxId, totalLimit, searchQuery.toStdString()));
|
||||
}
|
||||
|
||||
bool hasDeletedMessages(not_null<PeerData*> peer, ID topicId) {
|
||||
|
||||
@@ -17,7 +17,7 @@ std::vector<AyuMessageBase> getEditedMessages(not_null<HistoryItem*> item, ID mi
|
||||
bool hasRevisions(not_null<HistoryItem*> item);
|
||||
|
||||
void addDeletedMessage(not_null<HistoryItem*> item);
|
||||
std::vector<AyuMessageBase> getDeletedMessages(not_null<PeerData*> peer, ID topicId, ID minId, ID maxId, int totalLimit);
|
||||
std::vector<AyuMessageBase> getDeletedMessages(not_null<PeerData*> peer, ID topicId, ID minId, ID maxId, int totalLimit, const QString &searchQuery = QString());
|
||||
bool hasDeletedMessages(not_null<PeerData*> peer, ID topicId);
|
||||
|
||||
}
|
||||
|
||||
@@ -472,6 +472,9 @@ void InnerWidget::checkPreloadMore() {
|
||||
}
|
||||
|
||||
void InnerWidget::updateEmptyText() {
|
||||
// auto text = !_searchQuery.isEmpty()
|
||||
// ? tr::lng_admin_log_no_results_title(tr::now)
|
||||
// : tr::lng_search_messages_none(tr::now);
|
||||
_emptyText.setMarkedText(st::defaultTextStyle, Ui::Text::Semibold(tr::lng_search_messages_none(tr::now)));
|
||||
}
|
||||
|
||||
@@ -639,6 +642,7 @@ void InnerWidget::saveState(not_null<SectionMemento*> memento) {
|
||||
base::take(_messageIds),
|
||||
_upLoaded,
|
||||
_downLoaded);
|
||||
memento->setSearchQuery(base::take(_searchQuery));
|
||||
base::take(_itemsByData);
|
||||
_upLoaded = _downLoaded = true; // Don't load or handle anything anymore.
|
||||
}
|
||||
@@ -652,10 +656,30 @@ void InnerWidget::restoreState(not_null<SectionMemento*> memento) {
|
||||
_messageIds = memento->takeMessageIds();
|
||||
_upLoaded = memento->upLoaded();
|
||||
_downLoaded = memento->downLoaded();
|
||||
_searchQuery = memento->takeSearchQuery();
|
||||
updateMinMaxIds();
|
||||
updateSize();
|
||||
}
|
||||
|
||||
void InnerWidget::applySearch(const QString &query) {
|
||||
if (_item) {
|
||||
return;
|
||||
}
|
||||
if (_searchQuery != query) {
|
||||
_searchQuery = query;
|
||||
_upLoaded = false;
|
||||
_downLoaded = true;
|
||||
_items.clear();
|
||||
_messageIds.clear();
|
||||
_itemsByData.clear();
|
||||
_itemDates.clear();
|
||||
updateMinMaxIds();
|
||||
updateEmptyText();
|
||||
updateSize();
|
||||
preloadMore(Direction::Up);
|
||||
}
|
||||
}
|
||||
|
||||
void InnerWidget::preloadMore(Direction direction) {
|
||||
auto &loadedFlag = (direction == Direction::Up) ? _upLoaded : _downLoaded;
|
||||
if (loadedFlag) {
|
||||
@@ -672,7 +696,7 @@ void InnerWidget::preloadMore(Direction direction) {
|
||||
messages = AyuMessages::getEditedMessages(_item, minId, maxId, perPage);
|
||||
} else {
|
||||
// viewing deleted messages
|
||||
messages = AyuMessages::getDeletedMessages(_peer, _topicId, minId, maxId, perPage);
|
||||
messages = AyuMessages::getDeletedMessages(_peer, _topicId, minId, maxId, perPage, _searchQuery);
|
||||
}
|
||||
|
||||
crl::on_main([=]
|
||||
|
||||
@@ -78,6 +78,8 @@ public:
|
||||
void saveState(not_null<SectionMemento*> memento);
|
||||
void restoreState(not_null<SectionMemento*> memento);
|
||||
|
||||
void applySearch(const QString &query);
|
||||
|
||||
// Ui::AbstractTooltipShower interface.
|
||||
QString tooltipText() const override;
|
||||
QPoint tooltipPos() const override;
|
||||
@@ -292,6 +294,7 @@ private:
|
||||
bool _upLoaded = true;
|
||||
bool _downLoaded = true;
|
||||
Ui::Text::String _emptyText;
|
||||
QString _searchQuery;
|
||||
|
||||
MouseAction _mouseAction = MouseAction::None;
|
||||
TextSelectType _mouseSelectType = TextSelectType::Letters;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "apiwrap.h"
|
||||
#include "ayu/ui/message_history/history_inner.h"
|
||||
#include "base/timer.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
@@ -17,10 +18,12 @@
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "window/window_adaptive.h"
|
||||
@@ -35,13 +38,25 @@ public:
|
||||
FixedBar(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer);
|
||||
not_null<PeerData*> peer,
|
||||
bool searchEnabled);
|
||||
|
||||
[[nodiscard]] rpl::producer<> searchCancelRequests() const;
|
||||
[[nodiscard]] rpl::producer<QString> searchRequests() const;
|
||||
|
||||
// When animating mode is enabled the content is hidden and the
|
||||
// whole fixed bar acts like a back button.
|
||||
void setAnimatingMode(bool enabled);
|
||||
|
||||
void goBack();
|
||||
void showSearch();
|
||||
bool setSearchFocus() {
|
||||
if (_searchShown) {
|
||||
_field->setFocus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
@@ -49,12 +64,27 @@ protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
|
||||
private:
|
||||
void toggleSearch();
|
||||
void cancelSearch();
|
||||
void searchUpdated();
|
||||
void applySearch();
|
||||
void searchAnimationCallback();
|
||||
|
||||
not_null<Window::SessionController*> _controller;
|
||||
not_null<PeerData*> _peer;
|
||||
object_ptr<Ui::InputField> _field;
|
||||
object_ptr<Profile::BackButton> _backButton;
|
||||
object_ptr<Ui::IconButton> _search;
|
||||
object_ptr<Ui::CrossButton> _cancel;
|
||||
|
||||
Ui::Animations::Simple _searchShownAnimation;
|
||||
bool _searchShown = false;
|
||||
bool _searchEnabled = true;
|
||||
bool _animatingMode = false;
|
||||
base::Timer _searchTimer;
|
||||
|
||||
rpl::event_stream<> _searchCancelRequests;
|
||||
rpl::event_stream<QString> _searchRequests;
|
||||
};
|
||||
|
||||
object_ptr<Window::SectionWidget> SectionMemento::createWidget(
|
||||
@@ -73,18 +103,34 @@ object_ptr<Window::SectionWidget> SectionMemento::createWidget(
|
||||
FixedBar::FixedBar(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer) : Ui::RpWidget(parent)
|
||||
not_null<PeerData*> peer,
|
||||
bool searchEnabled) : Ui::RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _peer(peer)
|
||||
, _field(this, st::defaultMultiSelectSearchField, tr::lng_dlg_filter())
|
||||
, _backButton(this)
|
||||
, _cancel(this, st::historyAdminLogCancelSearch) {
|
||||
, _search(this, st::topBarSearch)
|
||||
, _cancel(this, st::historyAdminLogCancelSearch)
|
||||
, _searchEnabled(searchEnabled) {
|
||||
_backButton->moveToLeft(0, 0);
|
||||
_backButton->setClickedCallback([=]
|
||||
{
|
||||
goBack();
|
||||
});
|
||||
_backButton->setClickedCallback([=] { goBack(); });
|
||||
_search->setClickedCallback([=] { showSearch(); });
|
||||
_cancel->setClickedCallback([=] { cancelSearch(); });
|
||||
_field->hide();
|
||||
_field->cancelled() | rpl::on_next([=] {
|
||||
cancelSearch();
|
||||
}, _field->lifetime());
|
||||
_field->changes() | rpl::on_next([=] {
|
||||
searchUpdated();
|
||||
}, _field->lifetime());
|
||||
_field->submits(
|
||||
) | rpl::on_next([=] { applySearch(); }, _field->lifetime());
|
||||
_searchTimer.setCallback([=] { applySearch(); });
|
||||
|
||||
_cancel->hide(anim::type::instant);
|
||||
if (!_searchEnabled) {
|
||||
_search->hide();
|
||||
}
|
||||
|
||||
Info::Profile::NameValue(peer) | rpl::on_next([=](QString name) {
|
||||
_backButton->setText(name);
|
||||
@@ -99,19 +145,106 @@ void FixedBar::goBack() {
|
||||
_controller->showBackFromStack();
|
||||
}
|
||||
|
||||
void FixedBar::showSearch() {
|
||||
if (_searchEnabled && !_searchShown) {
|
||||
toggleSearch();
|
||||
}
|
||||
}
|
||||
|
||||
void FixedBar::toggleSearch() {
|
||||
_searchShown = !_searchShown;
|
||||
_cancel->toggle(_searchShown, anim::type::normal);
|
||||
_searchShownAnimation.start(
|
||||
[=] { searchAnimationCallback(); },
|
||||
_searchShown ? 0. : 1.,
|
||||
_searchShown ? 1. : 0.,
|
||||
st::historyAdminLogSearchSlideDuration);
|
||||
_search->setDisabled(_searchShown);
|
||||
if (_searchShown) {
|
||||
_field->show();
|
||||
_field->setFocus();
|
||||
} else {
|
||||
_searchCancelRequests.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
void FixedBar::searchAnimationCallback() {
|
||||
if (!_searchShownAnimation.animating()) {
|
||||
_field->setVisible(_searchShown);
|
||||
_search->setIconOverride(
|
||||
_searchShown ? &st::topBarSearch.icon : nullptr,
|
||||
_searchShown ? &st::topBarSearch.icon : nullptr);
|
||||
_search->setRippleColorOverride(
|
||||
_searchShown ? &st::topBarBg : nullptr);
|
||||
_search->setCursor(
|
||||
_searchShown ? style::cur_default : style::cur_pointer);
|
||||
_backButton->setOpacity(1.);
|
||||
}
|
||||
resizeToWidth(width());
|
||||
}
|
||||
|
||||
void FixedBar::cancelSearch() {
|
||||
if (_searchShown) {
|
||||
if (!_field->getLastText().isEmpty()) {
|
||||
_field->clear();
|
||||
_field->setFocus();
|
||||
applySearch();
|
||||
} else {
|
||||
toggleSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FixedBar::searchUpdated() {
|
||||
if (_field->getLastText().isEmpty()) {
|
||||
applySearch();
|
||||
} else {
|
||||
_searchTimer.callOnce(AutoSearchTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void FixedBar::applySearch() {
|
||||
_searchRequests.fire_copy(_field->getLastText());
|
||||
}
|
||||
|
||||
rpl::producer<> FixedBar::searchCancelRequests() const {
|
||||
return _searchCancelRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<QString> FixedBar::searchRequests() const {
|
||||
return _searchRequests.events();
|
||||
}
|
||||
|
||||
int FixedBar::resizeGetHeight(int newWidth) {
|
||||
auto filterLeft = newWidth;
|
||||
|
||||
auto cancelLeft = filterLeft - _cancel->width();
|
||||
_cancel->moveToLeft(cancelLeft, 0);
|
||||
|
||||
auto searchShownLeft = st::topBarArrowPadding.left();
|
||||
auto searchHiddenLeft = filterLeft - 0;
|
||||
auto searchCurrentLeft = anim::interpolate(searchHiddenLeft, searchShownLeft, 0.0);
|
||||
const auto offset = st::historySendRight + st::lineWidth;
|
||||
const auto searchShownLeft = st::topBarArrowPadding.left();
|
||||
const auto searchHiddenLeft = _searchEnabled
|
||||
? newWidth - _search->width() - offset
|
||||
: newWidth;
|
||||
const auto searchShown = _searchShownAnimation.value(_searchShown
|
||||
? 1.
|
||||
: 0.);
|
||||
const auto searchCurrentLeft = anim::interpolate(
|
||||
searchHiddenLeft,
|
||||
searchShownLeft,
|
||||
searchShown);
|
||||
if (_searchEnabled) {
|
||||
_search->moveToLeft(searchCurrentLeft, 0);
|
||||
}
|
||||
_backButton->setOpacity(1. - searchShown);
|
||||
_backButton->resizeToWidth(searchCurrentLeft);
|
||||
_backButton->moveToLeft(0, 0);
|
||||
|
||||
auto newHeight = _backButton->height();
|
||||
const auto cancelLeft = newWidth - _cancel->width() - offset;
|
||||
_cancel->moveToLeft(cancelLeft, 0);
|
||||
|
||||
const auto newHeight = _backButton->height();
|
||||
const auto fieldLeft = searchShownLeft + _search->width();
|
||||
_field->setGeometryToLeft(
|
||||
fieldLeft,
|
||||
st::historyAdminLogSearchTop,
|
||||
cancelLeft - fieldLeft,
|
||||
_field->height());
|
||||
|
||||
return newHeight;
|
||||
}
|
||||
@@ -126,7 +259,11 @@ void FixedBar::setAnimatingMode(bool enabled) {
|
||||
} else {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
showChildren();
|
||||
_field->hide();
|
||||
_cancel->setVisible(false);
|
||||
if (!_searchEnabled) {
|
||||
_search->hide();
|
||||
}
|
||||
}
|
||||
show();
|
||||
}
|
||||
@@ -155,12 +292,20 @@ Widget::Widget(
|
||||
ID topicId)
|
||||
: Window::SectionWidget(parent, controller, rpl::single<PeerData*>(peer)),
|
||||
_scroll(this, st::historyScroll, false),
|
||||
_fixedBar(this, controller, peer),
|
||||
_fixedBar(this, controller, peer, !item),
|
||||
_fixedBarShadow(this),
|
||||
_item(item),
|
||||
_topicId(topicId) {
|
||||
_fixedBar->move(0, 0);
|
||||
_fixedBar->resizeToWidth(width());
|
||||
_fixedBar->searchCancelRequests(
|
||||
) | rpl::on_next([=] {
|
||||
setInnerFocus();
|
||||
}, lifetime());
|
||||
_fixedBar->searchRequests(
|
||||
) | rpl::on_next([=](const QString &query) {
|
||||
_inner->applySearch(query);
|
||||
}, lifetime());
|
||||
_fixedBar->show();
|
||||
|
||||
_fixedBarShadow->raise();
|
||||
@@ -219,7 +364,9 @@ QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) {
|
||||
}
|
||||
|
||||
void Widget::doSetInnerFocus() {
|
||||
_inner->setFocus();
|
||||
if (!_fixedBar->setSearchFocus()) {
|
||||
_inner->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
bool Widget::showInternal(
|
||||
@@ -241,7 +388,19 @@ void Widget::setInternalState(const QRect &geometry, not_null<SectionMemento*> m
|
||||
}
|
||||
|
||||
void Widget::setupShortcuts() {
|
||||
// todo: smth
|
||||
Shortcuts::Requests(
|
||||
) | rpl::filter([=] {
|
||||
return Ui::AppInFocus()
|
||||
&& Ui::InFocusChain(this)
|
||||
&& !controller()->isLayerShown()
|
||||
&& isActiveWindow();
|
||||
}) | rpl::on_next([=](not_null<Shortcuts::Request*> request) {
|
||||
using Command = Shortcuts::Command;
|
||||
request->check(Command::Search, 2) && request->handle([=] {
|
||||
_fixedBar->showSearch();
|
||||
return true;
|
||||
});
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
std::shared_ptr<Window::SectionMemento> Widget::createMemento() {
|
||||
|
||||
@@ -139,6 +139,14 @@ public:
|
||||
return _downLoaded;
|
||||
}
|
||||
|
||||
void setSearchQuery(QString &&query) {
|
||||
_searchQuery = std::move(query);
|
||||
}
|
||||
|
||||
QString takeSearchQuery() {
|
||||
return std::move(_searchQuery);
|
||||
}
|
||||
|
||||
private:
|
||||
not_null<PeerData*> _peer;
|
||||
HistoryItem *_item;
|
||||
@@ -148,6 +156,7 @@ private:
|
||||
std::set<uint64> _messageIds;
|
||||
bool _upLoaded = false;
|
||||
bool _downLoaded = true;
|
||||
QString _searchQuery;
|
||||
};
|
||||
|
||||
} // namespace MessageHistory
|
||||
|
||||
Reference in New Issue
Block a user