Allow filtering Saved Messages search by tags.

This commit is contained in:
John Preston 2024-01-05 11:25:45 +04:00
parent e667436a98
commit 9c151ca151
10 changed files with 518 additions and 36 deletions

View File

@ -609,6 +609,8 @@ PRIVATE
dialogs/dialogs_row.h
dialogs/dialogs_search_from_controllers.cpp
dialogs/dialogs_search_from_controllers.h
dialogs/dialogs_search_tags.cpp
dialogs/dialogs_search_tags.h
dialogs/dialogs_widget.cpp
dialogs/dialogs_widget.h
dialogs/ui/dialogs_layout.cpp

View File

@ -623,3 +623,6 @@ dialogsStoriesTooltipHide: IconButton(defaultIconButton) {
searchedBarHeight: 32px;
searchedBarFont: normalFont;
searchedBarPosition: point(17px, 7px);
dialogsSearchTagSkip: point(8px, 4px);
dialogsSearchTagBottom: 10px;

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_indexed_list.h"
#include "dialogs/dialogs_widget.h"
#include "dialogs/dialogs_search_from_controllers.h"
#include "dialogs/dialogs_search_tags.h"
#include "history/history.h"
#include "history/history_item.h"
#include "core/shortcuts.h"
@ -40,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_filters.h"
#include "data/data_cloud_file.h"
#include "data/data_changes.h"
#include "data/data_message_reactions.h"
#include "data/data_saved_messages.h"
#include "data/data_stories.h"
#include "data/stickers/data_stickers.h"
@ -477,18 +479,26 @@ int InnerWidget::peerSearchOffset() const {
+ st::searchedBarHeight;
}
int InnerWidget::searchedOffset() const {
auto result = peerSearchOffset();
int InnerWidget::searchInChatOffset() const {
auto result = peerSearchOffset() - st::searchedBarHeight;
if (!_peerSearchResults.empty()) {
result += (_peerSearchResults.size() * st::dialogsRowHeight)
+ st::searchedBarHeight;
}
result += searchInChatSkip();
return result;
}
int InnerWidget::searchedOffset() const {
return searchInChatOffset()
+ searchInChatSkip()
+ st::searchedBarHeight;
}
int InnerWidget::searchInChatSkip() const {
auto result = 0;
if (_searchTags) {
result += _searchTags->height();
}
if (_searchInChat) {
result += st::searchedBarHeight + st::dialogsSearchInHeight;
}
@ -1111,12 +1121,20 @@ void InnerWidget::paintSearchInChat(
auto height = searchInChatSkip();
auto top = 0;
if (_searchTags) {
const auto height = _searchTags->height();
p.fillRect(0, top, width(), height, currentBg());
const auto position = QPoint(_searchTagsLeft, 0);
_searchTags->paint(p, position, context.now, context.paused);
top += height;
}
p.setFont(st::searchedBarFont);
if (_searchInChat) {
top += st::searchedBarHeight;
p.fillRect(0, 0, width(), top, st::searchedBarBg);
const auto bar = st::searchedBarHeight;
p.fillRect(0, top, width(), top + bar, st::searchedBarBg);
p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), tr::lng_dlg_search_in(tr::now));
p.drawTextLeft(st::searchedBarPosition.x(), top + st::searchedBarPosition.y(), width(), tr::lng_dlg_search_in(tr::now));
top += bar;
}
auto fullRect = QRect(0, top, width(), height - top);
p.fillRect(fullRect, currentBg());
@ -1276,6 +1294,21 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
_lastMousePosition = globalPosition;
_lastRowLocalMouseX = local.x();
const auto tagBase = QPoint(_searchTagsLeft, searchInChatOffset());
const auto tagPoint = local - tagBase;
const auto inTags = _searchTags
&& QRect(
tagBase,
QSize(width() - 2 * _searchTagsLeft, _searchTags->height())
).contains(local);
const auto tagLink = inTags
? _searchTags->lookupHandler(tagPoint)
: nullptr;
ClickHandler::setActive(tagLink);
if (inTags) {
setCursor(tagLink ? style::cur_pointer : style::cur_default);
}
const auto w = width();
const auto mouseY = local.y();
clearIrrelevantState();
@ -1370,7 +1403,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
updateSelectedRow();
}
}
if (wasSelected != isSelected()) {
if (!inTags && wasSelected != isSelected()) {
setCursor(wasSelected ? style::cur_default : style::cur_pointer);
}
}
@ -1452,6 +1485,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
QSize(width(), _st->height),
row->repaint());
}
ClickHandler::pressed();
if (anim::Disabled()
&& (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) {
mousePressReleased(e->globalPos(), e->button(), e->modifiers());
@ -1743,6 +1777,9 @@ void InnerWidget::mousePressReleased(
chooseRow(modifiers, pressedTopicRootId);
}
}
if (auto activated = ClickHandler::unpressed()) {
ActivateClickHandler(window(), activated, { button });
}
}
void InnerWidget::setCollapsedPressed(int pressed) {
@ -1825,9 +1862,10 @@ void InnerWidget::moveCancelSearchButtons() {
st::columnMinimalWidthLeft - _narrowWidth);
const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchInChat->width();
const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2;
_cancelSearchInChat->moveToLeft(left, st::searchedBarHeight + top);
const auto skip = _searchInChat ? (st::searchedBarHeight + st::dialogsSearchInHeight + st::lineWidth) : 0;
_cancelSearchFromUser->moveToLeft(left, skip + top);
const auto skip = st::searchedBarHeight + (_searchTags ? _searchTags->height() : 0);
_cancelSearchInChat->moveToLeft(left, skip + top);
const auto next = _searchInChat ? (skip + st::dialogsSearchInHeight + st::lineWidth) : 0;
_cancelSearchFromUser->moveToLeft(left, next + top);
}
void InnerWidget::dialogRowReplaced(
@ -2330,7 +2368,9 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) {
newFilter = words.isEmpty() ? QString() : words.join(' ');
if (newFilter != _filter || force) {
_filter = newFilter;
if (_filter.isEmpty() && !_searchFromPeer) {
if (_filter.isEmpty()
&& !_searchFromPeer
&& _searchTagsSelected.empty()) {
clearFilter();
} else {
setState(WidgetState::Filtered);
@ -2350,7 +2390,9 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) {
top += i->row->height();
}
};
if (!_searchInChat && !_searchFromPeer && !words.isEmpty()) {
if (!_searchInChat
&& !_searchFromPeer
&& !words.isEmpty()) {
if (_savedSublists) {
const auto owner = &session().data();
append(owner->savedMessages().chatsList()->indexed());
@ -2791,6 +2833,11 @@ void InnerWidget::refresh(bool toTop) {
return refreshWithCollapsedRows(toTop);
}
refreshEmptyLabel();
if (_searchTags) {
_searchTagsLeft = st::dialogsFilterSkip
+ st::dialogsFilterPadding.x();
_searchTags->resizeToWidth(width() - 2 * _searchTagsLeft);
}
auto h = 0;
if (_state == WidgetState::Default) {
if (_shownList->empty()) {
@ -2926,6 +2973,43 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
} else if (const auto migrateFrom = peer->migrateFrom()) {
_searchInMigrated = peer->owner().history(migrateFrom);
}
if (peer->isSelf()) {
const auto reactions = &peer->owner().reactions();
const auto list = [=] {
return reactions->list(Data::Reactions::Type::MyTags);
};
_searchTags = std::make_unique<SearchTags>(
&peer->owner(),
rpl::single(
list()
) | rpl::then(
reactions->myTagsUpdates() | rpl::map(list)
));
_searchTags->selectedValue(
) | rpl::start_with_next([=](std::vector<Data::ReactionId> &&list) {
_searchTagsSelected = std::move(list);
}, _searchTags->lifetime());
_searchTags->repaintRequests() | rpl::start_with_next([=] {
const auto height = _searchTags->height();
update(0, searchInChatOffset(), width(), height);
}, _searchTags->lifetime());
_searchTags->heightValue() | rpl::filter(
rpl::mappers::_1 > 0
) | rpl::start_with_next([=] {
refresh();
moveCancelSearchButtons();
}, _searchTags->lifetime());
} else {
_searchTags = nullptr;
_searchTagsSelected.clear();
}
} else {
_searchTags = nullptr;
_searchTagsSelected.clear();
}
_searchInChat = key;
_searchFromPeer = from;
@ -2957,6 +3041,13 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
_searchInChat || !_filter.isEmpty());
}
auto InnerWidget::searchTagsValue() const
-> rpl::producer<std::vector<Data::ReactionId>> {
return _searchTags
? _searchTags->selectedValue()
: rpl::single(std::vector<Data::ReactionId>());
}
void InnerWidget::refreshSearchInChatLabel() {
const auto dialog = [&] {
if (const auto topic = _searchInChat.topic()) {

View File

@ -43,6 +43,7 @@ namespace Data {
class Thread;
class Folder;
class Forum;
struct ReactionId;
} // namespace Data
namespace Dialogs::Ui {
@ -57,6 +58,7 @@ namespace Dialogs {
class Row;
class FakeRow;
class IndexedList;
class SearchTags;
struct ChosenRow {
Key key;
@ -138,6 +140,8 @@ public:
[[nodiscard]] bool hasFilteredResults() const;
void searchInChat(Key key, PeerData *from);
[[nodiscard]] auto searchTagsValue() const
-> rpl::producer<std::vector<Data::ReactionId>>;
void applyFilterUpdate(QString newFilter, bool force = false);
void onHashtagFilterUpdate(QStringView newFilter);
@ -325,6 +329,7 @@ private:
[[nodiscard]] int filteredIndex(int y) const;
[[nodiscard]] int filteredHeight(int till = -1) const;
[[nodiscard]] int peerSearchOffset() const;
[[nodiscard]] int searchInChatOffset() const;
[[nodiscard]] int searchedOffset() const;
[[nodiscard]] int searchInChatSkip() const;
@ -482,6 +487,9 @@ private:
mutable Ui::PeerUserpicView _searchFromUserUserpic;
Ui::Text::String _searchInChatText;
Ui::Text::String _searchFromUserText;
std::unique_ptr<SearchTags> _searchTags;
std::vector<Data::ReactionId> _searchTagsSelected;
int _searchTagsLeft = 0;
RowDescriptor _menuRow;
base::flat_map<

View File

@ -0,0 +1,246 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/dialogs_search_tags.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_document.h"
#include "data/data_message_reactions.h"
#include "data/data_session.h"
#include "history/view/reactions/history_view_reactions.h"
#include "ui/effects/animation_value.h"
#include "ui/power_saving.h"
#include "styles/style_chat.h"
#include "styles/style_dialogs.h"
namespace Dialogs {
struct SearchTags::Tag {
Data::ReactionId id;
std::unique_ptr<Ui::Text::CustomEmoji> custom;
mutable QImage image;
QRect geometry;
ClickHandlerPtr link;
bool selected = false;
};
SearchTags::SearchTags(
not_null<Data::Session*> owner,
rpl::producer<std::vector<Data::Reaction>> tags)
: _owner(owner) {
std::move(
tags
) | rpl::start_with_next([=](const std::vector<Data::Reaction> &list) {
fill(list);
}, _lifetime);
style::PaletteChanged(
) | rpl::start_with_next([=] {
_normalBg = _selectedBg = QImage();
}, _lifetime);
}
SearchTags::~SearchTags() = default;
void SearchTags::fill(const std::vector<Data::Reaction> &list) {
const auto selected = collectSelected();
_tags.clear();
_tags.reserve(list.size());
const auto link = [&](Data::ReactionId id) {
return std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
const auto i = ranges::find(_tags, id, &Tag::id);
if (i != end(_tags)) {
i->selected = !i->selected;
_selectedChanges.fire({});
}
}));
};
for (const auto &reaction : list) {
const auto id = reaction.id;
const auto customId = id.custom();
_tags.push_back({
.id = id,
.custom = (customId
? _owner->customEmojiManager().create(
customId,
[=] { _repaintRequests.fire({}); })
: nullptr),
.link = link(id),
.selected = ranges::contains(selected, id),
});
if (!customId) {
_owner->reactions().preloadImageFor(id);
}
}
if (_width > 0) {
layout();
}
}
void SearchTags::layout() {
Expects(_width > 0);
const auto &bg = validateBg(false);
const auto skip = st::dialogsSearchTagSkip;
const auto size = bg.size() / bg.devicePixelRatio();
const auto xsingle = size.width() + skip.x();
const auto ysingle = size.height() + skip.y();
const auto columns = std::max((_width + skip.x()) / xsingle, 1);
const auto rows = (_tags.size() + columns - 1) / columns;
for (auto row = 0; row != rows; ++row) {
for (auto column = 0; column != columns; ++column) {
const auto index = row * columns + column;
if (index >= _tags.size()) {
break;
}
const auto x = column * xsingle;
const auto y = row * ysingle;
_tags[index].geometry = QRect(QPoint(x, y), size);
}
}
const auto bottom = st::dialogsSearchTagBottom;
_height = rows ? (rows * ysingle - skip.y() + bottom) : 0;
}
void SearchTags::resizeToWidth(int width) {
if (_width == width || width <= 0) {
return;
}
_width = width;
layout();
}
int SearchTags::height() const {
return _height.current();
}
rpl::producer<int> SearchTags::heightValue() const {
return _height.value();
}
rpl::producer<> SearchTags::repaintRequests() const {
return _repaintRequests.events();
}
ClickHandlerPtr SearchTags::lookupHandler(QPoint point) const {
for (const auto &tag : _tags) {
if (tag.geometry.contains(point.x(), point.y())) {
return tag.link;
}
}
return nullptr;
}
auto SearchTags::selectedValue() const
-> rpl::producer<std::vector<Data::ReactionId>> {
return _selectedChanges.events() | rpl::map([=] {
return collectSelected();
});
}
void SearchTags::paintCustomFrame(
QPainter &p,
not_null<Ui::Text::CustomEmoji*> emoji,
QPoint innerTopLeft,
crl::time now,
bool paused,
const QColor &textColor) const {
if (_customCache.isNull()) {
using namespace Ui::Text;
const auto size = st::emojiSize;
const auto factor = style::DevicePixelRatio();
const auto adjusted = AdjustCustomEmojiSize(size);
_customCache = QImage(
QSize(adjusted, adjusted) * factor,
QImage::Format_ARGB32_Premultiplied);
_customCache.setDevicePixelRatio(factor);
_customSkip = (size - adjusted) / 2;
}
_customCache.fill(Qt::transparent);
auto q = QPainter(&_customCache);
emoji->paint(q, {
.textColor = textColor,
.now = now,
.paused = paused || On(PowerSaving::kEmojiChat),
});
q.end();
_customCache = Images::Round(
std::move(_customCache),
(Images::Option::RoundLarge
| Images::Option::RoundSkipTopRight
| Images::Option::RoundSkipBottomRight));
p.drawImage(
innerTopLeft + QPoint(_customSkip, _customSkip),
_customCache);
}
void SearchTags::paint(
QPainter &p,
QPoint position,
crl::time now,
bool paused) const {
const auto size = st::reactionInlineSize;
const auto skip = (size - st::reactionInlineImage) / 2;
const auto padding = st::reactionInlinePadding;
for (const auto &tag : _tags) {
const auto geometry = tag.geometry.translated(position);
p.drawImage(geometry.topLeft(), validateBg(tag.selected));
if (!tag.custom && tag.image.isNull()) {
tag.image = _owner->reactions().resolveImageFor(
tag.id,
::Data::Reactions::ImageSize::InlineList);
}
const auto inner = geometry.marginsRemoved(padding);
const auto image = QRect(
inner.topLeft() + QPoint(skip, skip),
QSize(st::reactionInlineImage, st::reactionInlineImage));
if (const auto custom = tag.custom.get()) {
const auto textFg = tag.selected
? st::dialogsNameFgActive->c
: st::dialogsNameFgOver->c;
paintCustomFrame(
p,
custom,
inner.topLeft(),
now,
paused,
textFg);
} else if (!tag.image.isNull()) {
p.drawImage(image.topLeft(), tag.image);
}
}
}
const QImage &SearchTags::validateBg(bool selected) const {
using namespace HistoryView::Reactions;
auto &image = selected ? _selectedBg : _normalBg;
if (image.isNull()) {
const auto tagBg = selected
? st::dialogsBgActive->c
: st::dialogsBgOver->c;
const auto dotBg = selected
? anim::with_alpha(tagBg, InlineList::TagDotAlpha())
: st::windowSubTextFg->c;
image = InlineList::PrepareTagBg(tagBg, dotBg);
}
return image;
}
std::vector<Data::ReactionId> SearchTags::collectSelected() const {
return _tags | ranges::views::filter(
&Tag::selected
) | ranges::views::transform(
&Tag::id
) | ranges::to_vector;
}
rpl::lifetime &SearchTags::lifetime() {
return _lifetime;
}
} // namespace Dialogs

View File

@ -0,0 +1,78 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/weak_ptr.h"
namespace Data {
class Session;
struct Reaction;
struct ReactionId;
} // namespace Data
namespace Ui::Text {
class CustomEmoji;
} // namespace Ui::Text
namespace Dialogs {
class SearchTags final : public base::has_weak_ptr {
public:
SearchTags(
not_null<Data::Session*> owner,
rpl::producer<std::vector<Data::Reaction>> tags);
~SearchTags();
void resizeToWidth(int width);
[[nodiscard]] int height() const;
[[nodiscard]] rpl::producer<int> heightValue() const;
[[nodiscard]] rpl::producer<> repaintRequests() const;
[[nodiscard]] ClickHandlerPtr lookupHandler(QPoint point) const;
[[nodiscard]] auto selectedValue() const
-> rpl::producer<std::vector<Data::ReactionId>>;
void paint(
QPainter &p,
QPoint position,
crl::time now,
bool paused) const;
[[nodiscard]] rpl::lifetime &lifetime();
private:
struct Tag;
void fill(const std::vector<Data::Reaction> &list);
void paintCustomFrame(
QPainter &p,
not_null<Ui::Text::CustomEmoji*> emoji,
QPoint innerTopLeft,
crl::time now,
bool paused,
const QColor &textColor) const;
void layout();
[[nodiscard]] std::vector<Data::ReactionId> collectSelected() const;
[[nodiscard]] const QImage &validateBg(bool selected) const;
const not_null<Data::Session*> _owner;
std::vector<Tag> _tags;
rpl::event_stream<> _selectedChanges;
rpl::event_stream<> _repaintRequests;
mutable QImage _normalBg;
mutable QImage _selectedBg;
mutable QImage _customCache;
mutable int _customSkip = 0;
rpl::variable<int> _height;
int _width = 0;
rpl::lifetime _lifetime;
};
} // namespace Dialogs

View File

@ -1713,7 +1713,7 @@ void Widget::loadMoreBlockedByDate() {
bool Widget::searchMessages(bool searchCache) {
auto result = false;
auto q = currentSearchQuery().trimmed();
if (q.isEmpty() && !_searchFromAuthor) {
if (q.isEmpty() && !_searchFromAuthor && _searchTags.empty()) {
cancelSearchRequest();
_api.request(base::take(_peerSearchRequest)).cancel();
_api.request(base::take(_topicSearchRequest)).cancel();
@ -1730,6 +1730,7 @@ bool Widget::searchMessages(bool searchCache) {
if (i != _searchCache.end()) {
_searchQuery = q;
_searchQueryFrom = _searchFromAuthor;
_searchQueryTags = _searchTags;
_searchNextRate = 0;
_searchFull = _searchFullMigrated = false;
cancelSearchRequest();
@ -1741,9 +1742,12 @@ bool Widget::searchMessages(bool searchCache) {
0);
result = true;
}
} else if (_searchQuery != q || _searchQueryFrom != _searchFromAuthor) {
} else if (_searchQuery != q
|| _searchQueryFrom != _searchFromAuthor
|| _searchQueryTags != _searchTags) {
_searchQuery = q;
_searchQueryFrom = _searchFromAuthor;
_searchQueryTags = _searchTags;
_searchNextRate = 0;
_searchFull = _searchFullMigrated = false;
cancelSearchRequest();
@ -1757,14 +1761,20 @@ bool Widget::searchMessages(bool searchCache) {
using Flag = MTPmessages_Search::Flag;
_searchRequest = session().api().request(MTPmessages_Search(
MTP_flags((topic ? Flag::f_top_msg_id : Flag())
| (_searchQueryFrom ? Flag::f_from_id : Flag())),
| (_searchQueryFrom ? Flag::f_from_id : Flag())
| (_searchQueryTags.empty()
? Flag()
: Flag::f_saved_reaction)),
peer->input,
MTP_string(_searchQuery),
(_searchQueryFrom
? _searchQueryFrom->input
: MTP_inputPeerEmpty()),
MTPInputPeer(), // saved_peer_id
MTPVector<MTPReaction>(), // saved_reaction
MTP_vector_from_range(
_searchQueryTags | ranges::views::transform(
Data::ReactionToMTP
)),
MTP_int(topic ? topic->rootId() : 0),
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
@ -1868,6 +1878,7 @@ bool Widget::searchMessages(bool searchCache) {
bool Widget::searchForPeersRequired(const QString &query) const {
return !_searchInChat
&& !_searchFromAuthor
&& _searchTags.empty()
&& !_openedForum
&& !query.isEmpty()
&& (query[0] != '#');
@ -1876,6 +1887,7 @@ bool Widget::searchForPeersRequired(const QString &query) const {
bool Widget::searchForTopicsRequired(const QString &query) const {
return !_searchInChat
&& !_searchFromAuthor
&& _searchTags.empty()
&& _openedForum
&& !query.isEmpty()
&& (query[0] != '#')
@ -2000,14 +2012,20 @@ void Widget::searchMore() {
using Flag = MTPmessages_Search::Flag;
_searchRequest = session().api().request(MTPmessages_Search(
MTP_flags((topic ? Flag::f_top_msg_id : Flag())
| (_searchQueryFrom ? Flag::f_from_id : Flag())),
| (_searchQueryFrom ? Flag::f_from_id : Flag())
| (_searchQueryTags.empty()
? Flag()
: Flag::f_saved_reaction)),
peer->input,
MTP_string(_searchQuery),
(_searchQueryFrom
? _searchQueryFrom->input
: MTP_inputPeerEmpty()),
MTPInputPeer(), // saved_peer_id
MTPVector<MTPReaction>(), // saved_reaction
MTP_vector_from_range(
_searchQueryTags | ranges::views::transform(
Data::ReactionToMTP
)),
MTP_int(topic ? topic->rootId() : 0),
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
@ -2401,7 +2419,7 @@ void Widget::applyFilterUpdate(bool force) {
updateStoriesVisibility();
const auto filterText = currentSearchQuery();
_inner->applyFilterUpdate(filterText, force);
if (filterText.isEmpty() && !_searchFromAuthor) {
if (filterText.isEmpty() && !_searchFromAuthor && _searchTags.empty()) {
clearSearchCache();
}
_cancelSearch->toggle(!filterText.isEmpty(), anim::type::normal);
@ -2417,7 +2435,9 @@ void Widget::applyFilterUpdate(bool force) {
_peerSearchQuery = QString();
}
if (_chooseFromUser->toggled() || _searchFromAuthor) {
if (_chooseFromUser->toggled()
|| _searchFromAuthor
|| !_searchTags.empty()) {
auto switchToChooseFrom = HistoryView::SwitchToChooseFromQuery();
if (_lastFilterText != switchToChooseFrom
&& switchToChooseFrom.startsWith(_lastFilterText)
@ -2619,6 +2639,18 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
controller()->closeFolder();
}
_inner->searchInChat(_searchInChat, _searchFromAuthor);
_searchTagsLifetime = _inner->searchTagsValue(
) | rpl::start_with_next([=](std::vector<Data::ReactionId> &&list) {
if (_searchTags != list) {
clearSearchCache();
_searchTags = std::move(list);
if (_searchTags.empty()) {
applyFilterUpdate(true);
} else {
searchMessages();
}
}
});
if (_subsectionTopBar) {
_subsectionTopBar->searchEnableJumpToDate(
_openedForum && _searchInChat);
@ -2639,6 +2671,7 @@ void Widget::clearSearchCache() {
}
_searchQuery = QString();
_searchQueryFrom = nullptr;
_searchQueryTags.clear();
_topicSearchQuery = QString();
_topicSearchOffsetDate = 0;
_topicSearchOffsetId = _topicSearchOffsetTopicId = 0;

View File

@ -22,6 +22,7 @@ class Error;
namespace Data {
class Forum;
enum class StorySourcesList : uchar;
struct ReactionId;
} // namespace Data
namespace Main {
@ -285,6 +286,8 @@ private:
Dialogs::Key _searchInChat;
History *_searchInMigrated = nullptr;
PeerData *_searchFromAuthor = nullptr;
std::vector<Data::ReactionId> _searchTags;
rpl::lifetime _searchTagsLifetime;
QString _lastFilterText;
rpl::event_stream<rpl::producer<Stories::Content>> _storiesContents;
@ -313,6 +316,7 @@ private:
QString _searchQuery;
PeerData *_searchQueryFrom = nullptr;
std::vector<Data::ReactionId> _searchQueryTags;
int32 _searchNextRate = 0;
bool _searchFull = false;
bool _searchFullMigrated = false;

View File

@ -507,12 +507,11 @@ void InlineList::paint(
}
}
void InlineList::validateTagBg(const QColor &color) const {
if (!_tagBg.isNull() && _tagBgColor == color) {
return;
}
_tagBgColor = color;
float64 InlineList::TagDotAlpha() {
return 0.6;
}
QImage InlineList::PrepareTagBg(QColor tagBg, QColor dotBg) {
const auto padding = st::reactionInlinePadding;
const auto size = st::reactionInlineSize;
const auto width = padding.left()
@ -522,20 +521,19 @@ void InlineList::validateTagBg(const QColor &color) const {
const auto height = padding.top() + size + padding.bottom();
const auto ratio = style::DevicePixelRatio();
auto mask = QImage(
auto result = QImage(
QSize(width, height) * ratio,
QImage::Format_ARGB32_Premultiplied);
mask.setDevicePixelRatio(ratio);
result.setDevicePixelRatio(ratio);
mask.fill(Qt::transparent);
auto p = QPainter(&mask);
result.fill(Qt::transparent);
auto p = QPainter(&result);
auto path = QPainterPath();
const auto arrow = st::reactionInlineTagArrow;
const auto rradius = st::reactionInlineTagRightRadius * 1.;
const auto radius = st::reactionInlineTagLeftRadius - rradius;
const auto fg = QColor(255, 255, 255);
auto pen = QPen(fg);
auto pen = QPen(tagBg);
pen.setWidthF(rradius * 2.);
pen.setJoinStyle(Qt::RoundJoin);
const auto rect = QRectF(0, 0, width, height).marginsRemoved(
@ -548,9 +546,15 @@ void InlineList::validateTagBg(const QColor &color) const {
path.lineTo(right, rect.y() + rect.height() / 2);
path.lineTo(right - arrow, bottom);
path.lineTo(rect.x() + radius, bottom);
path.arcTo(QRectF(rect.x(), bottom - radius * 2, radius * 2, radius * 2), 270, -90);
path.arcTo(
QRectF(rect.x(), bottom - radius * 2, radius * 2, radius * 2),
270,
-90);
path.lineTo(rect.x(), rect.y() + radius);
path.arcTo(QRectF(rect.x(), rect.y(), radius * 2, radius * 2), 180, -90);
path.arcTo(
QRectF(rect.x(), rect.y(), radius * 2, radius * 2),
180,
-90);
path.closeSubpath();
const auto dsize = st::reactionInlineTagDot;
@ -563,16 +567,26 @@ void InlineList::validateTagBg(const QColor &color) const {
auto hq = PainterHighQualityEnabler(p);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setPen(pen);
p.setBrush(fg);
p.setBrush(tagBg);
p.drawPath(path);
p.setPen(Qt::NoPen);
p.setBrush(QColor(255, 255, 255, 255 * 0.6));
p.setBrush(dotBg);
p.drawEllipse(dot);
p.end();
_tagBg = style::colorizeImage(mask, color);
return result;
}
void InlineList::validateTagBg(const QColor &color) const {
if (!_tagBg.isNull() && _tagBgColor == color) {
return;
}
_tagBgColor = color;
auto dot = color;
dot.setAlphaF(dot.alphaF() * TagDotAlpha());
_tagBg = PrepareTagBg(color, anim::with_alpha(color, TagDotAlpha()));
}
void InlineList::paintSingleBg(

View File

@ -93,6 +93,9 @@ public:
ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>> animations);
[[nodiscard]] static float64 TagDotAlpha();
[[nodiscard]] static QImage PrepareTagBg(QColor tagBg, QColor dotBg);
private:
struct Userpics {
QImage image;