mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Redesign search scope selection.
This commit is contained in:
parent
fe6f65b3ab
commit
72c667b153
22 changed files with 1069 additions and 640 deletions
BIN
Telegram/Resources/icons/menu/chats.png
Normal file
BIN
Telegram/Resources/icons/menu/chats.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 766 B |
BIN
Telegram/Resources/icons/menu/chats@2x.png
Normal file
BIN
Telegram/Resources/icons/menu/chats@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/menu/chats@3x.png
Normal file
BIN
Telegram/Resources/icons/menu/chats@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
using "ui/basic.style";
|
using "ui/basic.style";
|
||||||
|
|
||||||
|
using "ui/layers/layers.style"; // boxRoundShadow
|
||||||
using "ui/widgets/widgets.style";
|
using "ui/widgets/widgets.style";
|
||||||
|
|
||||||
DialogRow {
|
DialogRow {
|
||||||
|
@ -446,10 +447,25 @@ dialogsLoadMoreLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation)
|
||||||
size: size(12px, 12px);
|
size: size(12px, 12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogsSearchInHeight: 52px;
|
dialogsSearchInHeight: 38px;
|
||||||
dialogsSearchInPhotoSize: 36px;
|
dialogsSearchInPhotoSize: 26px;
|
||||||
dialogsSearchInPhotoPadding: 10px;
|
dialogsSearchInPhotoPadding: 12px;
|
||||||
dialogsSearchInSkip: 7px;
|
dialogsSearchInSkip: 10px;
|
||||||
|
dialogsSearchInNameTop: 9px;
|
||||||
|
dialogsSearchInDownTop: 15px;
|
||||||
|
dialogsSearchInDown: icon {{ "intro_country_dropdown", windowBoldFg }};
|
||||||
|
dialogsSearchInDownSkip: 4px;
|
||||||
|
dialogsSearchInMenu: PopupMenu(defaultPopupMenu) {
|
||||||
|
shadow: boxRoundShadow;
|
||||||
|
animation: PanelAnimation(defaultPanelAnimation) {
|
||||||
|
shadow: boxRoundShadow;
|
||||||
|
}
|
||||||
|
scrollPadding: margins(0px, 0px, 0px, 0px);
|
||||||
|
radius: 8px;
|
||||||
|
menu: menuWithIcons;
|
||||||
|
}
|
||||||
|
dialogsSearchInCheck: icon {{ "player/player_check", mediaPlayerActiveFg }};
|
||||||
|
dialogsSearchInCheckSkip: 8px;
|
||||||
dialogsSearchFromStyle: defaultTextStyle;
|
dialogsSearchFromStyle: defaultTextStyle;
|
||||||
dialogsSearchFromPalette: TextPalette(defaultTextPalette) {
|
dialogsSearchFromPalette: TextPalette(defaultTextPalette) {
|
||||||
linkFg: dialogsNameFg;
|
linkFg: dialogsNameFg;
|
||||||
|
@ -694,17 +710,17 @@ dialogsStoriesTooltipHide: IconButton(defaultIconButton) {
|
||||||
ripple: emptyRippleAnimation;
|
ripple: emptyRippleAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchedBarHeight: 32px;
|
searchedBarHeight: 28px;
|
||||||
searchedBarFont: normalFont;
|
searchedBarFont: normalFont;
|
||||||
searchedBarPosition: point(17px, 7px);
|
searchedBarPosition: point(14px, 5px);
|
||||||
searchedBarLabel: FlatLabel(defaultFlatLabel) {
|
searchedBarLabel: FlatLabel(defaultFlatLabel) {
|
||||||
textFg: searchedBarFg;
|
textFg: searchedBarFg;
|
||||||
margin: margins(17px, 7px, 17px, 7px);
|
margin: margins(14px, 5px, 14px, 5px);
|
||||||
}
|
}
|
||||||
searchedBarLink: LinkButton(defaultLinkButton) {
|
searchedBarLink: LinkButton(defaultLinkButton) {
|
||||||
color: searchedBarFg;
|
color: searchedBarFg;
|
||||||
overColor: searchedBarFg;
|
overColor: searchedBarFg;
|
||||||
padding: margins(17px, 7px, 17px, 7px);
|
padding: margins(14px, 5px, 14px, 5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogsSearchTagSkip: point(8px, 4px);
|
dialogsSearchTagSkip: point(8px, 4px);
|
||||||
|
|
|
@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "dialogs/dialogs_three_state_icon.h"
|
#include "dialogs/dialogs_three_state_icon.h"
|
||||||
#include "dialogs/ui/chat_search_empty.h"
|
#include "dialogs/ui/chat_search_empty.h"
|
||||||
#include "dialogs/ui/chat_search_tabs.h"
|
#include "dialogs/ui/chat_search_in.h"
|
||||||
#include "dialogs/ui/dialogs_layout.h"
|
#include "dialogs/ui/dialogs_layout.h"
|
||||||
#include "dialogs/ui/dialogs_stories_content.h"
|
#include "dialogs/ui/dialogs_stories_content.h"
|
||||||
#include "dialogs/ui/dialogs_video_userpic.h"
|
#include "dialogs/ui/dialogs_video_userpic.h"
|
||||||
|
@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
|
#include "ui/dynamic_thumbnails.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "data/data_drafts.h"
|
#include "data/data_drafts.h"
|
||||||
|
@ -137,9 +138,7 @@ constexpr auto kStartReorderThreshold = 30;
|
||||||
if (hashtag) {
|
if (hashtag) {
|
||||||
text.append(tr::lng_search_tab_by_hashtag(tr::now));
|
text.append(tr::lng_search_tab_by_hashtag(tr::now));
|
||||||
} else {
|
} else {
|
||||||
text.append(
|
text.append(tr::lng_dlg_search_for_messages(tr::now));
|
||||||
tr::lng_dlg_search_for_messages(tr::now)
|
|
||||||
).append('\n').append(Ui::Text::Link(tr::lng_cancel(tr::now)));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
text.append(tr::lng_search_tab_no_results(
|
text.append(tr::lng_search_tab_no_results(
|
||||||
|
@ -214,12 +213,9 @@ InnerWidget::InnerWidget(
|
||||||
, _narrowWidth(st::defaultDialogRow.padding.left()
|
, _narrowWidth(st::defaultDialogRow.padding.left()
|
||||||
+ st::defaultDialogRow.photoSize
|
+ st::defaultDialogRow.photoSize
|
||||||
+ st::defaultDialogRow.padding.left())
|
+ st::defaultDialogRow.padding.left())
|
||||||
, _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer)
|
|
||||||
, _childListShown(std::move(childListShown)) {
|
, _childListShown(std::move(childListShown)) {
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent, true);
|
setAttribute(Qt::WA_OpaquePaintEvent, true);
|
||||||
|
|
||||||
_cancelSearchFromUser->hide();
|
|
||||||
|
|
||||||
style::PaletteChanged(
|
style::PaletteChanged(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
_topicJumpCache = nullptr;
|
_topicJumpCache = nullptr;
|
||||||
|
@ -553,11 +549,7 @@ int InnerWidget::searchTagsOffset() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
int InnerWidget::searchInChatOffset() const {
|
int InnerWidget::searchInChatOffset() const {
|
||||||
auto result = searchTagsOffset();
|
return searchTagsOffset() + (_searchTags ? _searchTags->height() : 0);
|
||||||
if (_searchTags) {
|
|
||||||
result += _searchTags->height();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int InnerWidget::searchedOffset() const {
|
int InnerWidget::searchedOffset() const {
|
||||||
|
@ -567,11 +559,7 @@ int InnerWidget::searchedOffset() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
int InnerWidget::searchInChatSkip() const {
|
int InnerWidget::searchInChatSkip() const {
|
||||||
auto result = 0;
|
return _searchIn ? _searchIn->height() : 0;
|
||||||
if (_searchFromShown) {
|
|
||||||
result += st::dialogsSearchInHeight;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
|
void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
|
||||||
|
@ -928,14 +916,14 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||||
p.translate(0, _searchTags->height());
|
p.translate(0, _searchTags->height());
|
||||||
top += _searchTags->height();
|
top += _searchTags->height();
|
||||||
}
|
}
|
||||||
if (_searchFromShown) {
|
if (_searchIn) {
|
||||||
paintSearchInChat(p, {
|
//paintSearchInChat(p, {
|
||||||
.st = &st::forumTopicRow,
|
// .st = &st::forumTopicRow,
|
||||||
.currentBg = currentBg(),
|
// .currentBg = currentBg(),
|
||||||
.now = ms,
|
// .now = ms,
|
||||||
.width = fullWidth,
|
// .width = fullWidth,
|
||||||
.paused = videoPaused,
|
// .paused = videoPaused,
|
||||||
});
|
//});
|
||||||
p.translate(0, searchInChatSkip());
|
p.translate(0, searchInChatSkip());
|
||||||
top += searchInChatSkip();
|
top += searchInChatSkip();
|
||||||
if (_searchResults.empty()) {
|
if (_searchResults.empty()) {
|
||||||
|
@ -1211,111 +1199,112 @@ void InnerWidget::paintSearchTags(
|
||||||
const auto position = QPoint(_searchTagsLeft, top);
|
const auto position = QPoint(_searchTagsLeft, top);
|
||||||
_searchTags->paint(p, position, context.now, context.paused);
|
_searchTags->paint(p, position, context.now, context.paused);
|
||||||
}
|
}
|
||||||
|
//
|
||||||
void InnerWidget::paintSearchInChat(
|
//void InnerWidget::paintSearchInChat(
|
||||||
Painter &p,
|
// Painter &p,
|
||||||
const Ui::PaintContext &context) const {
|
// const Ui::PaintContext &context) const {
|
||||||
auto height = searchInChatSkip();
|
// auto height = searchInChatSkip();
|
||||||
|
//
|
||||||
auto top = 0;
|
// auto top = 0;
|
||||||
p.setFont(st::searchedBarFont);
|
// p.setFont(st::searchedBarFont);
|
||||||
auto fullRect = QRect(0, top, width(), height - top);
|
// auto fullRect = QRect(0, top, width(), height - top);
|
||||||
p.fillRect(fullRect, currentBg());
|
// p.fillRect(fullRect, currentBg());
|
||||||
if (_searchFromShown) {
|
// if (_searchFromShown) {
|
||||||
p.setPen(st::dialogsTextFg);
|
// p.setPen(st::dialogsTextFg);
|
||||||
p.setTextPalette(st::dialogsSearchFromPalette);
|
// p.setTextPalette(st::dialogsSearchFromPalette);
|
||||||
paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText);
|
// paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText);
|
||||||
p.restoreTextPalette();
|
// p.restoreTextPalette();
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
template <typename PaintUserpic>
|
//
|
||||||
void InnerWidget::paintSearchInFilter(
|
//template <typename PaintUserpic>
|
||||||
Painter &p,
|
//void InnerWidget::paintSearchInFilter(
|
||||||
PaintUserpic paintUserpic,
|
// Painter &p,
|
||||||
int top,
|
// PaintUserpic paintUserpic,
|
||||||
const style::icon *icon,
|
// int top,
|
||||||
const Ui::Text::String &text) const {
|
// const style::icon *icon,
|
||||||
const auto savedPen = p.pen();
|
// const Ui::Text::String &text) const {
|
||||||
const auto userpicLeft = st::defaultDialogRow.padding.left();
|
// const auto savedPen = p.pen();
|
||||||
const auto userpicTop = top
|
// const auto userpicLeft = st::defaultDialogRow.padding.left();
|
||||||
+ (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2;
|
// const auto userpicTop = top
|
||||||
paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize);
|
// + (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2;
|
||||||
|
// paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize);
|
||||||
const auto nameleft = st::defaultDialogRow.padding.left()
|
//
|
||||||
+ st::dialogsSearchInPhotoSize
|
// const auto nameleft = st::defaultDialogRow.padding.left()
|
||||||
+ st::dialogsSearchInPhotoPadding;
|
// + st::dialogsSearchInPhotoSize
|
||||||
const auto namewidth = width()
|
// + st::dialogsSearchInPhotoPadding;
|
||||||
- nameleft
|
// const auto namewidth = width()
|
||||||
- st::defaultDialogRow.padding.left()
|
// - nameleft
|
||||||
- st::defaultDialogRow.padding.right()
|
// - st::defaultDialogRow.padding.left()
|
||||||
- st::dialogsCancelSearch.width;
|
// - st::defaultDialogRow.padding.right()
|
||||||
auto rectForName = QRect(
|
// - st::dialogsCancelSearch.width;
|
||||||
nameleft,
|
// auto rectForName = QRect(
|
||||||
top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2,
|
// nameleft,
|
||||||
namewidth,
|
// top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2,
|
||||||
st::semiboldFont->height);
|
// namewidth,
|
||||||
if (icon) {
|
// st::semiboldFont->height);
|
||||||
icon->paint(p, rectForName.topLeft(), width());
|
// if (icon) {
|
||||||
rectForName.setLeft(rectForName.left()
|
// icon->paint(p, rectForName.topLeft(), width());
|
||||||
+ icon->width()
|
// rectForName.setLeft(rectForName.left()
|
||||||
+ st::dialogsChatTypeSkip);
|
// + icon->width()
|
||||||
}
|
// + st::dialogsChatTypeSkip);
|
||||||
p.setPen(savedPen);
|
// }
|
||||||
text.drawLeftElided(
|
// p.setPen(savedPen);
|
||||||
p,
|
// text.drawLeftElided(
|
||||||
rectForName.left(),
|
// p,
|
||||||
rectForName.top(),
|
// rectForName.left(),
|
||||||
rectForName.width(),
|
// rectForName.top(),
|
||||||
width());
|
// rectForName.width(),
|
||||||
}
|
// width());
|
||||||
|
//}
|
||||||
void InnerWidget::paintSearchInPeer(
|
//
|
||||||
Painter &p,
|
//void InnerWidget::paintSearchInPeer(
|
||||||
not_null<PeerData*> peer,
|
// Painter &p,
|
||||||
Ui::PeerUserpicView &userpic,
|
// not_null<PeerData*> peer,
|
||||||
int top,
|
// Ui::PeerUserpicView &userpic,
|
||||||
const Ui::Text::String &text) const {
|
// int top,
|
||||||
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
// const Ui::Text::String &text) const {
|
||||||
peer->paintUserpicLeft(p, userpic, x, y, width(), size);
|
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
||||||
};
|
// peer->paintUserpicLeft(p, userpic, x, y, width(), size);
|
||||||
const auto icon = Ui::ChatTypeIcon(peer);
|
// };
|
||||||
paintSearchInFilter(p, paintUserpic, top, icon, text);
|
// const auto icon = Ui::ChatTypeIcon(peer);
|
||||||
}
|
// paintSearchInFilter(p, paintUserpic, top, icon, text);
|
||||||
|
//}
|
||||||
void InnerWidget::paintSearchInSaved(
|
//
|
||||||
Painter &p,
|
//void InnerWidget::paintSearchInSaved(
|
||||||
int top,
|
// Painter &p,
|
||||||
const Ui::Text::String &text) const {
|
// int top,
|
||||||
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
// const Ui::Text::String &text) const {
|
||||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, width(), size);
|
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
||||||
};
|
// Ui::EmptyUserpic::PaintSavedMessages(p, x, y, width(), size);
|
||||||
paintSearchInFilter(p, paintUserpic, top, nullptr, text);
|
// };
|
||||||
}
|
// paintSearchInFilter(p, paintUserpic, top, nullptr, text);
|
||||||
|
//}
|
||||||
void InnerWidget::paintSearchInReplies(
|
//
|
||||||
Painter &p,
|
//void InnerWidget::paintSearchInReplies(
|
||||||
int top,
|
// Painter &p,
|
||||||
const Ui::Text::String &text) const {
|
// int top,
|
||||||
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
// const Ui::Text::String &text) const {
|
||||||
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, width(), size);
|
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
||||||
};
|
// Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, width(), size);
|
||||||
paintSearchInFilter(p, paintUserpic, top, nullptr, text);
|
// };
|
||||||
}
|
// paintSearchInFilter(p, paintUserpic, top, nullptr, text);
|
||||||
|
//}
|
||||||
void InnerWidget::paintSearchInTopic(
|
//
|
||||||
Painter &p,
|
//void InnerWidget::paintSearchInTopic(
|
||||||
const Ui::PaintContext &context,
|
// Painter &p,
|
||||||
not_null<Data::ForumTopic*> topic,
|
// const Ui::PaintContext &context,
|
||||||
Ui::PeerUserpicView &userpic,
|
// not_null<Data::ForumTopic*> topic,
|
||||||
int top,
|
// Ui::PeerUserpicView &userpic,
|
||||||
const Ui::Text::String &text) const {
|
// int top,
|
||||||
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
// const Ui::Text::String &text) const {
|
||||||
p.translate(x, y);
|
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
||||||
topic->paintUserpic(p, userpic, context);
|
// p.translate(x, y);
|
||||||
p.translate(-x, -y);
|
// topic->paintUserpic(p, userpic, context);
|
||||||
};
|
// p.translate(-x, -y);
|
||||||
paintSearchInFilter(p, paintUserpic, top, nullptr, text);
|
// };
|
||||||
}
|
// paintSearchInFilter(p, paintUserpic, top, nullptr, text);
|
||||||
|
//}
|
||||||
|
|
||||||
void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
|
void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
|
||||||
if (_chatPreviewTouchGlobal || _touchDragStartGlobal) {
|
if (_chatPreviewTouchGlobal || _touchDragStartGlobal) {
|
||||||
|
@ -1989,17 +1978,20 @@ void InnerWidget::resizeEvent(QResizeEvent *e) {
|
||||||
_searchTags->resizeToWidth(width() - 2 * _searchTagsLeft);
|
_searchTags->resizeToWidth(width() - 2 * _searchTagsLeft);
|
||||||
}
|
}
|
||||||
resizeEmpty();
|
resizeEmpty();
|
||||||
moveCancelSearchButtons();
|
moveSearchIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::moveCancelSearchButtons() {
|
void InnerWidget::moveSearchIn() {
|
||||||
const auto widthForCancelButton = qMax(
|
if (!_searchIn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto searchInWidth = std::max(
|
||||||
width(),
|
width(),
|
||||||
st::columnMinimalWidthLeft - _narrowWidth);
|
st::columnMinimalWidthLeft - _narrowWidth);
|
||||||
const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchFromUser->width();
|
_searchIn->resizeToWidth(searchInWidth);
|
||||||
const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2;
|
|
||||||
const auto skip = (_searchTags ? _searchTags->height() : 0);
|
const auto top = (_searchTags ? _searchTags->height() : 0);
|
||||||
_cancelSearchFromUser->moveToLeft(left, skip + top);
|
_searchIn->moveToLeft(0, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::dialogRowReplaced(
|
void InnerWidget::dialogRowReplaced(
|
||||||
|
@ -2656,7 +2648,7 @@ void InnerWidget::applySearchState(SearchState state) {
|
||||||
1
|
1
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
refresh();
|
refresh();
|
||||||
moveCancelSearchButtons();
|
moveSearchIn();
|
||||||
}, _searchTags->lifetime());
|
}, _searchTags->lifetime());
|
||||||
} else {
|
} else {
|
||||||
_searchTags = nullptr;
|
_searchTags = nullptr;
|
||||||
|
@ -2670,17 +2662,12 @@ void InnerWidget::applySearchState(SearchState state) {
|
||||||
if (state.inChat) {
|
if (state.inChat) {
|
||||||
onHashtagFilterUpdate(QStringView());
|
onHashtagFilterUpdate(QStringView());
|
||||||
}
|
}
|
||||||
if (_searchFromShown) {
|
|
||||||
_cancelSearchFromUser->show();
|
|
||||||
_searchFromUserUserpic = _searchFromShown->createUserpicView();
|
|
||||||
} else {
|
|
||||||
_cancelSearchFromUser->hide();
|
|
||||||
_searchFromUserUserpic = {};
|
|
||||||
}
|
|
||||||
refreshSearchInChatLabel();
|
|
||||||
moveCancelSearchButtons();
|
|
||||||
|
|
||||||
_searchState = std::move(state);
|
_searchState = std::move(state);
|
||||||
|
_searchingHashtag = IsHashtagSearchQuery(_searchState.query);
|
||||||
|
|
||||||
|
updateSearchIn();
|
||||||
|
moveSearchIn();
|
||||||
|
|
||||||
auto newFilter = _searchState.query;
|
auto newFilter = _searchState.query;
|
||||||
const auto mentionsSearch = (newFilter == u"@"_q);
|
const auto mentionsSearch = (newFilter == u"@"_q);
|
||||||
const auto words = mentionsSearch
|
const auto words = mentionsSearch
|
||||||
|
@ -2924,12 +2911,20 @@ rpl::producer<> InnerWidget::listBottomReached() const {
|
||||||
return _listBottomReached.events();
|
return _listBottomReached.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<> InnerWidget::cancelSearchFromUserRequests() const {
|
rpl::producer<ChatSearchTab> InnerWidget::changeSearchTabRequests() const {
|
||||||
return _cancelSearchFromUser->clicks() | rpl::to_empty;
|
return _changeSearchTabRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<> InnerWidget::cancelSearchRequests() const {
|
rpl::producer<> InnerWidget::cancelSearchRequests() const {
|
||||||
return _cancelSearch.events();
|
return _cancelSearchRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> InnerWidget::cancelSearchFromRequests() const {
|
||||||
|
return _cancelSearchFromRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> InnerWidget::changeSearchFromRequests() const {
|
||||||
|
return _changeSearchFromRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<Ui::ScrollToRequest> InnerWidget::mustScrollTo() const {
|
rpl::producer<Ui::ScrollToRequest> InnerWidget::mustScrollTo() const {
|
||||||
|
@ -3200,9 +3195,6 @@ void InnerWidget::refreshEmpty() {
|
||||||
} else if (_searchEmptyState != _searchState) {
|
} else if (_searchEmptyState != _searchState) {
|
||||||
_searchEmptyState = _searchState;
|
_searchEmptyState = _searchState;
|
||||||
_searchEmpty = MakeSearchEmpty(this, _searchState);
|
_searchEmpty = MakeSearchEmpty(this, _searchState);
|
||||||
_searchEmpty->linkClicks() | rpl::start_with_next([=] {
|
|
||||||
_cancelSearch.fire({});
|
|
||||||
}, _searchEmpty->lifetime());
|
|
||||||
if (_controller->session().data().chatsListLoaded()) {
|
if (_controller->session().data().chatsListLoaded()) {
|
||||||
_searchEmpty->animate();
|
_searchEmpty->animate();
|
||||||
}
|
}
|
||||||
|
@ -3342,19 +3334,76 @@ auto InnerWidget::searchTagsChanges() const
|
||||||
: rpl::never<std::vector<Data::ReactionId>>();
|
: rpl::never<std::vector<Data::ReactionId>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::refreshSearchInChatLabel() {
|
void InnerWidget::updateSearchIn() {
|
||||||
const auto from = _searchFromShown ? _searchFromShown->name() : u""_q;
|
if (!_searchState.inChat && !_searchingHashtag) {
|
||||||
if (!from.isEmpty()) {
|
_searchIn = nullptr;
|
||||||
const auto fromUserText = tr::lng_dlg_search_from(
|
return;
|
||||||
tr::now,
|
} else if (!_searchIn) {
|
||||||
lt_user,
|
_searchIn = std::make_unique<ChatSearchIn>(this);
|
||||||
Ui::Text::Semibold(from),
|
_searchIn->show();
|
||||||
Ui::Text::WithEntities);
|
_searchIn->changeFromRequests() | rpl::start_to_stream(
|
||||||
_searchFromUserText.setMarkedText(
|
_changeSearchFromRequests,
|
||||||
st::dialogsSearchFromStyle,
|
_searchIn->lifetime());
|
||||||
fromUserText,
|
_searchIn->cancelFromRequests() | rpl::start_to_stream(
|
||||||
Ui::DialogTextOptions());
|
_cancelSearchFromRequests,
|
||||||
|
_searchIn->lifetime());
|
||||||
|
_searchIn->cancelInRequests() | rpl::start_to_stream(
|
||||||
|
_cancelSearchRequests,
|
||||||
|
_searchIn->lifetime());
|
||||||
|
_searchIn->tabChanges() | rpl::start_to_stream(
|
||||||
|
_changeSearchTabRequests,
|
||||||
|
_searchIn->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto sublist = _searchState.inChat.sublist();
|
||||||
|
const auto topic = _searchState.inChat.topic();
|
||||||
|
const auto peer = _searchState.inChat.owningHistory()
|
||||||
|
? _searchState.inChat.owningHistory()->peer.get()
|
||||||
|
: _openedForum
|
||||||
|
? _openedForum->channel().get()
|
||||||
|
: nullptr;
|
||||||
|
const auto topicIcon = !topic
|
||||||
|
? nullptr
|
||||||
|
: topic->iconId()
|
||||||
|
? Ui::MakeEmojiThumbnail(
|
||||||
|
&topic->owner(),
|
||||||
|
Data::SerializeCustomEmojiId(topic->iconId()))
|
||||||
|
: Ui::MakeEmojiThumbnail(
|
||||||
|
&topic->owner(),
|
||||||
|
Data::TopicIconEmojiEntity({
|
||||||
|
.title = (topic->isGeneral()
|
||||||
|
? Data::ForumGeneralIconTitle()
|
||||||
|
: topic->title()),
|
||||||
|
.colorId = (topic->isGeneral()
|
||||||
|
? Data::ForumGeneralIconColor(st::windowSubTextFg->c)
|
||||||
|
: topic->colorId()),
|
||||||
|
}));
|
||||||
|
const auto peerIcon = peer
|
||||||
|
? Ui::MakeUserpicThumbnail(peer)
|
||||||
|
: sublist
|
||||||
|
? Ui::MakeUserpicThumbnail(sublist->peer())
|
||||||
|
: nullptr;
|
||||||
|
const auto myIcon = Ui::MakeIconThumbnail(st::menuIconChats);
|
||||||
|
const auto publicIcon = _searchingHashtag
|
||||||
|
? Ui::MakeIconThumbnail(st::menuIconChannel)
|
||||||
|
: nullptr;
|
||||||
|
const auto peerTabType = (peer && peer->isBroadcast())
|
||||||
|
? ChatSearchPeerTabType::Channel
|
||||||
|
: (peer && (peer->isChat() || peer->isMegagroup()))
|
||||||
|
? ChatSearchPeerTabType::Group
|
||||||
|
: ChatSearchPeerTabType::Chat;
|
||||||
|
const auto fromImage = _searchFromShown
|
||||||
|
? Ui::MakeUserpicThumbnail(_searchFromShown)
|
||||||
|
: nullptr;
|
||||||
|
const auto fromName = _searchFromShown
|
||||||
|
? _searchFromShown->shortName()
|
||||||
|
: QString();
|
||||||
|
_searchIn->apply({
|
||||||
|
{ ChatSearchTab::ThisTopic, topicIcon },
|
||||||
|
{ ChatSearchTab::ThisPeer, peerIcon },
|
||||||
|
{ ChatSearchTab::MyMessages, myIcon },
|
||||||
|
{ ChatSearchTab::PublicPosts, publicIcon },
|
||||||
|
}, _searchState.tab, peerTabType, fromImage, fromName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::repaintSearchResult(int index) {
|
void InnerWidget::repaintSearchResult(int index) {
|
||||||
|
|
|
@ -61,6 +61,7 @@ class FakeRow;
|
||||||
class IndexedList;
|
class IndexedList;
|
||||||
class SearchTags;
|
class SearchTags;
|
||||||
class SearchEmpty;
|
class SearchEmpty;
|
||||||
|
class ChatSearchIn;
|
||||||
|
|
||||||
struct ChosenRow {
|
struct ChosenRow {
|
||||||
Key key;
|
Key key;
|
||||||
|
@ -156,8 +157,11 @@ public:
|
||||||
void setLoadMoreCallback(Fn<void()> callback);
|
void setLoadMoreCallback(Fn<void()> callback);
|
||||||
void setLoadMoreFilteredCallback(Fn<void()> callback);
|
void setLoadMoreFilteredCallback(Fn<void()> callback);
|
||||||
[[nodiscard]] rpl::producer<> listBottomReached() const;
|
[[nodiscard]] rpl::producer<> listBottomReached() const;
|
||||||
[[nodiscard]] rpl::producer<> cancelSearchFromUserRequests() const;
|
[[nodiscard]] auto changeSearchTabRequests() const
|
||||||
|
-> rpl::producer<ChatSearchTab>;
|
||||||
[[nodiscard]] rpl::producer<> cancelSearchRequests() const;
|
[[nodiscard]] rpl::producer<> cancelSearchRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<> cancelSearchFromRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<> changeSearchFromRequests() const;
|
||||||
[[nodiscard]] rpl::producer<ChosenRow> chosenRow() const;
|
[[nodiscard]] rpl::producer<ChosenRow> chosenRow() const;
|
||||||
[[nodiscard]] rpl::producer<> updated() const;
|
[[nodiscard]] rpl::producer<> updated() const;
|
||||||
|
|
||||||
|
@ -358,38 +362,38 @@ private:
|
||||||
void paintSearchTags(
|
void paintSearchTags(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const Ui::PaintContext &context) const;
|
const Ui::PaintContext &context) const;
|
||||||
void paintSearchInChat(
|
//void paintSearchInChat(
|
||||||
Painter &p,
|
// Painter &p,
|
||||||
const Ui::PaintContext &context) const;
|
// const Ui::PaintContext &context) const;
|
||||||
void paintSearchInPeer(
|
//void paintSearchInPeer(
|
||||||
Painter &p,
|
// Painter &p,
|
||||||
not_null<PeerData*> peer,
|
// not_null<PeerData*> peer,
|
||||||
Ui::PeerUserpicView &userpic,
|
// Ui::PeerUserpicView &userpic,
|
||||||
int top,
|
// int top,
|
||||||
const Ui::Text::String &text) const;
|
// const Ui::Text::String &text) const;
|
||||||
void paintSearchInSaved(
|
//void paintSearchInSaved(
|
||||||
Painter &p,
|
// Painter &p,
|
||||||
int top,
|
// int top,
|
||||||
const Ui::Text::String &text) const;
|
// const Ui::Text::String &text) const;
|
||||||
void paintSearchInReplies(
|
//void paintSearchInReplies(
|
||||||
Painter &p,
|
// Painter &p,
|
||||||
int top,
|
// int top,
|
||||||
const Ui::Text::String &text) const;
|
// const Ui::Text::String &text) const;
|
||||||
void paintSearchInTopic(
|
//void paintSearchInTopic(
|
||||||
Painter &p,
|
// Painter &p,
|
||||||
const Ui::PaintContext &context,
|
// const Ui::PaintContext &context,
|
||||||
not_null<Data::ForumTopic*> topic,
|
// not_null<Data::ForumTopic*> topic,
|
||||||
Ui::PeerUserpicView &userpic,
|
// Ui::PeerUserpicView &userpic,
|
||||||
int top,
|
// int top,
|
||||||
const Ui::Text::String &text) const;
|
// const Ui::Text::String &text) const;
|
||||||
template <typename PaintUserpic>
|
//template <typename PaintUserpic>
|
||||||
void paintSearchInFilter(
|
//void paintSearchInFilter(
|
||||||
Painter &p,
|
// Painter &p,
|
||||||
PaintUserpic paintUserpic,
|
// PaintUserpic paintUserpic,
|
||||||
int top,
|
// int top,
|
||||||
const style::icon *icon,
|
// const style::icon *icon,
|
||||||
const Ui::Text::String &text) const;
|
// const Ui::Text::String &text) const;
|
||||||
void refreshSearchInChatLabel();
|
void updateSearchIn();
|
||||||
void repaintSearchResult(int index);
|
void repaintSearchResult(int index);
|
||||||
|
|
||||||
Ui::VideoUserpic *validateVideoUserpic(not_null<Row*> row);
|
Ui::VideoUserpic *validateVideoUserpic(not_null<Row*> row);
|
||||||
|
@ -415,7 +419,7 @@ private:
|
||||||
void savePinnedOrder();
|
void savePinnedOrder();
|
||||||
bool pinnedShiftAnimationCallback(crl::time now);
|
bool pinnedShiftAnimationCallback(crl::time now);
|
||||||
void handleChatListEntryRefreshes();
|
void handleChatListEntryRefreshes();
|
||||||
void moveCancelSearchButtons();
|
void moveSearchIn();
|
||||||
void dragPinnedFromTouch();
|
void dragPinnedFromTouch();
|
||||||
|
|
||||||
void saveChatsFilterScrollState(FilterId filterId);
|
void saveChatsFilterScrollState(FilterId filterId);
|
||||||
|
@ -490,19 +494,22 @@ private:
|
||||||
|
|
||||||
WidgetState _state = WidgetState::Default;
|
WidgetState _state = WidgetState::Default;
|
||||||
|
|
||||||
|
std::unique_ptr<ChatSearchIn> _searchIn;
|
||||||
|
rpl::event_stream<ChatSearchTab> _changeSearchTabRequests;
|
||||||
|
rpl::event_stream<> _cancelSearchRequests;
|
||||||
|
rpl::event_stream<> _cancelSearchFromRequests;
|
||||||
|
rpl::event_stream<> _changeSearchFromRequests;
|
||||||
object_ptr<Ui::RpWidget> _loadingAnimation = { nullptr };
|
object_ptr<Ui::RpWidget> _loadingAnimation = { nullptr };
|
||||||
object_ptr<SearchEmpty> _searchEmpty = { nullptr };
|
object_ptr<SearchEmpty> _searchEmpty = { nullptr };
|
||||||
SearchState _searchEmptyState;
|
SearchState _searchEmptyState;
|
||||||
object_ptr<Ui::FlatLabel> _empty = { nullptr };
|
object_ptr<Ui::FlatLabel> _empty = { nullptr };
|
||||||
object_ptr<Ui::IconButton> _cancelSearchFromUser;
|
|
||||||
rpl::event_stream<> _cancelSearch;
|
|
||||||
|
|
||||||
Ui::DraggingScrollManager _draggingScroll;
|
Ui::DraggingScrollManager _draggingScroll;
|
||||||
|
|
||||||
SearchState _searchState;
|
SearchState _searchState;
|
||||||
|
bool _searchingHashtag = false;
|
||||||
History *_searchInMigrated = nullptr;
|
History *_searchInMigrated = nullptr;
|
||||||
PeerData *_searchFromShown = nullptr;
|
PeerData *_searchFromShown = nullptr;
|
||||||
mutable Ui::PeerUserpicView _searchFromUserUserpic;
|
|
||||||
Ui::Text::String _searchFromUserText;
|
Ui::Text::String _searchFromUserText;
|
||||||
std::unique_ptr<SearchTags> _searchTags;
|
std::unique_ptr<SearchTags> _searchTags;
|
||||||
int _searchTagsLeft = 0;
|
int _searchTagsLeft = 0;
|
||||||
|
|
|
@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_folder.h"
|
#include "data/data_folder.h"
|
||||||
#include "data/data_forum_topic.h"
|
#include "data/data_forum_topic.h"
|
||||||
#include "data/data_saved_sublist.h"
|
#include "data/data_saved_sublist.h"
|
||||||
#include "dialogs/ui/chat_search_tabs.h"
|
#include "dialogs/ui/chat_search_in.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
|
||||||
namespace Dialogs {
|
namespace Dialogs {
|
||||||
|
|
|
@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "base/qt/qt_key_modifiers.h"
|
#include "base/qt/qt_key_modifiers.h"
|
||||||
#include "base/options.h"
|
#include "base/options.h"
|
||||||
#include "dialogs/ui/chat_search_tabs.h"
|
#include "dialogs/ui/chat_search_in.h"
|
||||||
#include "dialogs/ui/dialogs_stories_content.h"
|
#include "dialogs/ui/dialogs_stories_content.h"
|
||||||
#include "dialogs/ui/dialogs_stories_list.h"
|
#include "dialogs/ui/dialogs_stories_list.h"
|
||||||
#include "dialogs/ui/dialogs_suggestions.h"
|
#include "dialogs/ui/dialogs_suggestions.h"
|
||||||
|
@ -336,7 +336,20 @@ Widget::Widget(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
searchCursorMoved();
|
searchCursorMoved();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
_inner->cancelSearchFromUserRequests(
|
_inner->changeSearchTabRequests(
|
||||||
|
) | rpl::filter([=](ChatSearchTab tab) {
|
||||||
|
return _searchState.tab != tab;
|
||||||
|
}) | rpl::start_with_next([=](ChatSearchTab tab) {
|
||||||
|
auto copy = _searchState;
|
||||||
|
copy.tab = tab;
|
||||||
|
applySearchState(std::move(copy));
|
||||||
|
}, lifetime());
|
||||||
|
_inner->cancelSearchRequests(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
setInnerFocus(true);
|
||||||
|
applySearchState({});
|
||||||
|
}, lifetime());
|
||||||
|
_inner->cancelSearchFromRequests(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
auto copy = _searchState;
|
auto copy = _searchState;
|
||||||
copy.fromPeer = nullptr;
|
copy.fromPeer = nullptr;
|
||||||
|
@ -345,10 +358,9 @@ Widget::Widget(
|
||||||
}
|
}
|
||||||
applySearchState(std::move(copy));
|
applySearchState(std::move(copy));
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
_inner->cancelSearchRequests(
|
_inner->changeSearchFromRequests(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
setInnerFocus(true);
|
showSearchFrom();
|
||||||
applySearchState({});
|
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
_inner->chosenRow(
|
_inner->chosenRow(
|
||||||
) | rpl::start_with_next([=](const ChosenRow &row) {
|
) | rpl::start_with_next([=](const ChosenRow &row) {
|
||||||
|
@ -1096,9 +1108,6 @@ void Widget::updateControlsVisibility(bool fast) {
|
||||||
updateJumpToDateVisibility(fast);
|
updateJumpToDateVisibility(fast);
|
||||||
updateSearchFromVisibility(fast);
|
updateSearchFromVisibility(fast);
|
||||||
}
|
}
|
||||||
if (_searchTabs) {
|
|
||||||
_searchTabs->show();
|
|
||||||
}
|
|
||||||
if (_connecting) {
|
if (_connecting) {
|
||||||
_connecting->setForceHidden(false);
|
_connecting->setForceHidden(false);
|
||||||
}
|
}
|
||||||
|
@ -1242,102 +1251,6 @@ void Widget::updateSuggestions(anim::type animated) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::updateSearchTabs() {
|
|
||||||
const auto has = _searchState.inChat || _searchingHashtag;
|
|
||||||
if (!has) {
|
|
||||||
if (_searchTabs) {
|
|
||||||
_searchTabs = nullptr;
|
|
||||||
updateControlsGeometry();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else if (!_searchTabs) {
|
|
||||||
const auto savedSession = &session();
|
|
||||||
const auto markedTextContext = [=](Fn<void()> repaint) {
|
|
||||||
return Core::MarkedTextContext{
|
|
||||||
.session = savedSession,
|
|
||||||
.customEmojiRepaint = std::move(repaint),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
_searchTabs = std::make_unique<ChatSearchTabs>(
|
|
||||||
this,
|
|
||||||
_searchState.tab,
|
|
||||||
std::move(markedTextContext));
|
|
||||||
_searchTabs->setVisible(!_showAnimation);
|
|
||||||
_searchTabs->tabChanges(
|
|
||||||
) | rpl::filter([=](ChatSearchTab tab) {
|
|
||||||
return (_searchState.tab != tab);
|
|
||||||
}) | rpl::start_with_next([=](ChatSearchTab tab) {
|
|
||||||
auto copy = _searchState;
|
|
||||||
copy.tab = tab;
|
|
||||||
applySearchState(std::move(copy));
|
|
||||||
}, _searchTabs->lifetime());
|
|
||||||
}
|
|
||||||
const auto sublist = _searchState.inChat.sublist();
|
|
||||||
const auto topic = _searchState.inChat.topic();
|
|
||||||
const auto peer = _searchState.inChat.owningHistory()
|
|
||||||
? _searchState.inChat.owningHistory()->peer.get()
|
|
||||||
: _openedForum
|
|
||||||
? _openedForum->channel().get()
|
|
||||||
: nullptr;
|
|
||||||
const auto topicShortLabel = !topic
|
|
||||||
? TextWithEntities()
|
|
||||||
: topic->iconId()
|
|
||||||
? Ui::Text::SingleCustomEmoji(
|
|
||||||
Data::SerializeCustomEmojiId(topic->iconId()))
|
|
||||||
: Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({
|
|
||||||
.title = (topic->isGeneral()
|
|
||||||
? Data::ForumGeneralIconTitle()
|
|
||||||
: topic->title()),
|
|
||||||
.colorId = (topic->isGeneral()
|
|
||||||
? Data::ForumGeneralIconColor(st::windowSubTextFg->c)
|
|
||||||
: topic->colorId()),
|
|
||||||
}));
|
|
||||||
const auto peerShortLabel = peer
|
|
||||||
? Ui::Text::SingleCustomEmoji(
|
|
||||||
session().data().customEmojiManager().peerUserpicEmojiData(
|
|
||||||
peer,
|
|
||||||
{},
|
|
||||||
true))
|
|
||||||
: sublist
|
|
||||||
? Ui::Text::SingleCustomEmoji(
|
|
||||||
session().data().customEmojiManager().peerUserpicEmojiData(
|
|
||||||
sublist->peer(),
|
|
||||||
{},
|
|
||||||
true))
|
|
||||||
: TextWithEntities();
|
|
||||||
const auto myShortLabel = DefaultShortLabel(ChatSearchTab::MyMessages);
|
|
||||||
const auto publicShortLabel = _searchingHashtag
|
|
||||||
? DefaultShortLabel(ChatSearchTab::PublicPosts)
|
|
||||||
: TextWithEntities();
|
|
||||||
if ((_searchState.tab == ChatSearchTab::ThisTopic
|
|
||||||
&& !_searchState.inChat.topic())
|
|
||||||
|| (_searchState.tab == ChatSearchTab::ThisPeer
|
|
||||||
&& !_searchState.inChat
|
|
||||||
&& !_openedForum)
|
|
||||||
|| (_searchState.tab == ChatSearchTab::PublicPosts
|
|
||||||
&& !_searchingHashtag)) {
|
|
||||||
_searchState.tab = _searchState.inChat.topic()
|
|
||||||
? ChatSearchTab::ThisTopic
|
|
||||||
: (_searchState.inChat.owningHistory()
|
|
||||||
|| _searchState.inChat.sublist())
|
|
||||||
? ChatSearchTab::ThisPeer
|
|
||||||
: ChatSearchTab::MyMessages;
|
|
||||||
}
|
|
||||||
const auto peerTabType = (peer && peer->isBroadcast())
|
|
||||||
? ChatSearchPeerTabType::Channel
|
|
||||||
: (peer && (peer->isChat() || peer->isMegagroup()))
|
|
||||||
? ChatSearchPeerTabType::Group
|
|
||||||
: ChatSearchPeerTabType::Chat;
|
|
||||||
_searchTabs->setTabShortLabels({
|
|
||||||
{ ChatSearchTab::ThisTopic, topicShortLabel },
|
|
||||||
{ ChatSearchTab::ThisPeer, peerShortLabel },
|
|
||||||
{ ChatSearchTab::MyMessages, myShortLabel },
|
|
||||||
{ ChatSearchTab::PublicPosts, publicShortLabel },
|
|
||||||
}, _searchState.tab, peerTabType);
|
|
||||||
|
|
||||||
updateControlsGeometry();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::changeOpenedSubsection(
|
void Widget::changeOpenedSubsection(
|
||||||
FnMut<void()> change,
|
FnMut<void()> change,
|
||||||
bool fromRight,
|
bool fromRight,
|
||||||
|
@ -2732,9 +2645,8 @@ QString Widget::validateSearchQuery() {
|
||||||
setSearchQuery(fixed.text, fixed.cursorPosition);
|
setSearchQuery(fixed.text, fixed.cursorPosition);
|
||||||
}
|
}
|
||||||
return fixed.text;
|
return fixed.text;
|
||||||
} else if (_searchingHashtag != IsHashtagSearchQuery(query)) {
|
} else {
|
||||||
_searchingHashtag = !_searchingHashtag;
|
_searchingHashtag = IsHashtagSearchQuery(query);
|
||||||
updateSearchTabs();
|
|
||||||
}
|
}
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
@ -2932,7 +2844,7 @@ bool Widget::applySearchState(SearchState state) {
|
||||||
state.tab = (_openedForum && !state.inChat)
|
state.tab = (_openedForum && !state.inChat)
|
||||||
? ChatSearchTab::ThisPeer
|
? ChatSearchTab::ThisPeer
|
||||||
: ChatSearchTab::MyMessages;
|
: ChatSearchTab::MyMessages;
|
||||||
} else if (!state.inChat && !_searchTabs) {
|
} else if (!state.inChat && !_searchingHashtag) {
|
||||||
state.tab = (forum || _openedForum)
|
state.tab = (forum || _openedForum)
|
||||||
? ChatSearchTab::ThisPeer
|
? ChatSearchTab::ThisPeer
|
||||||
: ChatSearchTab::MyMessages;
|
: ChatSearchTab::MyMessages;
|
||||||
|
@ -2966,6 +2878,20 @@ bool Widget::applySearchState(SearchState state) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((state.tab == ChatSearchTab::ThisTopic
|
||||||
|
&& !state.inChat.topic())
|
||||||
|
|| (state.tab == ChatSearchTab::ThisPeer
|
||||||
|
&& !state.inChat
|
||||||
|
&& !_openedForum)
|
||||||
|
|| (state.tab == ChatSearchTab::PublicPosts
|
||||||
|
&& !_searchingHashtag)) {
|
||||||
|
state.tab = state.inChat.topic()
|
||||||
|
? ChatSearchTab::ThisTopic
|
||||||
|
: (state.inChat.owningHistory() || state.inChat.sublist())
|
||||||
|
? ChatSearchTab::ThisPeer
|
||||||
|
: ChatSearchTab::MyMessages;
|
||||||
|
}
|
||||||
|
|
||||||
const auto migrateFrom = (peer && !topic)
|
const auto migrateFrom = (peer && !topic)
|
||||||
? peer->migrateFrom()
|
? peer->migrateFrom()
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
@ -2979,7 +2905,6 @@ bool Widget::applySearchState(SearchState state) {
|
||||||
}
|
}
|
||||||
if (inChatChanged) {
|
if (inChatChanged) {
|
||||||
controller()->setSearchInChat(_searchState.inChat);
|
controller()->setSearchInChat(_searchState.inChat);
|
||||||
updateSearchTabs();
|
|
||||||
}
|
}
|
||||||
if (queryChanged || inChatChanged) {
|
if (queryChanged || inChatChanged) {
|
||||||
updateCancelSearch();
|
updateCancelSearch();
|
||||||
|
@ -3334,9 +3259,6 @@ void Widget::updateControlsGeometry() {
|
||||||
if (_forumRequestsBar) {
|
if (_forumRequestsBar) {
|
||||||
_forumRequestsBar->resizeToWidth(barw);
|
_forumRequestsBar->resizeToWidth(barw);
|
||||||
}
|
}
|
||||||
if (_searchTabs) {
|
|
||||||
_searchTabs->resizeToWidth(barw);
|
|
||||||
}
|
|
||||||
_updateScrollGeometryCached = [=] {
|
_updateScrollGeometryCached = [=] {
|
||||||
const auto moreChatsBarTop = expandedStoriesTop
|
const auto moreChatsBarTop = expandedStoriesTop
|
||||||
+ ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded);
|
+ ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded);
|
||||||
|
@ -3358,13 +3280,8 @@ void Widget::updateControlsGeometry() {
|
||||||
if (_forumReportBar) {
|
if (_forumReportBar) {
|
||||||
_forumReportBar->bar().move(0, forumReportTop);
|
_forumReportBar->bar().move(0, forumReportTop);
|
||||||
}
|
}
|
||||||
const auto searchTabsTop = forumReportTop
|
const auto scrollTop = forumReportTop
|
||||||
+ (_forumReportBar ? _forumReportBar->bar().height() : 0);
|
+ (_forumReportBar ? _forumReportBar->bar().height() : 0);
|
||||||
if (_searchTabs) {
|
|
||||||
_searchTabs->move(0, searchTabsTop);
|
|
||||||
}
|
|
||||||
const auto scrollTop = searchTabsTop
|
|
||||||
+ (_searchTabs ? _searchTabs->height() : 0);
|
|
||||||
const auto scrollHeight = height() - scrollTop - bottomSkip;
|
const auto scrollHeight = height() - scrollTop - bottomSkip;
|
||||||
const auto wasScrollHeight = _scroll->height();
|
const auto wasScrollHeight = _scroll->height();
|
||||||
_scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight);
|
_scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight);
|
||||||
|
|
|
@ -76,7 +76,7 @@ struct ChosenRow;
|
||||||
class InnerWidget;
|
class InnerWidget;
|
||||||
enum class SearchRequestType;
|
enum class SearchRequestType;
|
||||||
class Suggestions;
|
class Suggestions;
|
||||||
class ChatSearchTabs;
|
class ChatSearchIn;
|
||||||
enum class ChatSearchTab : uchar;
|
enum class ChatSearchTab : uchar;
|
||||||
|
|
||||||
class Widget final : public Window::AbstractSectionWidget {
|
class Widget final : public Window::AbstractSectionWidget {
|
||||||
|
@ -255,7 +255,6 @@ private:
|
||||||
void updateScrollUpPosition();
|
void updateScrollUpPosition();
|
||||||
void updateLockUnlockPosition();
|
void updateLockUnlockPosition();
|
||||||
void updateSuggestions(anim::type animated);
|
void updateSuggestions(anim::type animated);
|
||||||
void updateSearchTabs();
|
|
||||||
void processSearchFocusChange();
|
void processSearchFocusChange();
|
||||||
|
|
||||||
[[nodiscard]] bool redirectToSearchPossible() const;
|
[[nodiscard]] bool redirectToSearchPossible() const;
|
||||||
|
@ -294,7 +293,6 @@ private:
|
||||||
QPointer<InnerWidget> _inner;
|
QPointer<InnerWidget> _inner;
|
||||||
std::unique_ptr<Suggestions> _suggestions;
|
std::unique_ptr<Suggestions> _suggestions;
|
||||||
std::vector<std::unique_ptr<Suggestions>> _hidingSuggestions;
|
std::vector<std::unique_ptr<Suggestions>> _hidingSuggestions;
|
||||||
std::unique_ptr<ChatSearchTabs> _searchTabs;
|
|
||||||
class BottomButton;
|
class BottomButton;
|
||||||
object_ptr<BottomButton> _updateTelegram = { nullptr };
|
object_ptr<BottomButton> _updateTelegram = { nullptr };
|
||||||
object_ptr<BottomButton> _loadMoreChats = { nullptr };
|
object_ptr<BottomButton> _loadMoreChats = { nullptr };
|
||||||
|
|
|
@ -33,12 +33,6 @@ void SearchEmpty::setup(Icon icon, rpl::producer<TextWithEntities> text) {
|
||||||
this,
|
this,
|
||||||
std::move(text),
|
std::move(text),
|
||||||
st::defaultPeerListAbout);
|
st::defaultPeerListAbout);
|
||||||
label->setClickHandlerFilter([=](const auto &, Qt::MouseButton button) {
|
|
||||||
if (button == Qt::LeftButton) {
|
|
||||||
_linkClicks.fire({});
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
const auto size = st::recentPeersEmptySize;
|
const auto size = st::recentPeersEmptySize;
|
||||||
const auto animation = [&] {
|
const auto animation = [&] {
|
||||||
switch (icon) {
|
switch (icon) {
|
||||||
|
|
|
@ -26,9 +26,6 @@ public:
|
||||||
rpl::producer<TextWithEntities> text);
|
rpl::producer<TextWithEntities> text);
|
||||||
|
|
||||||
void setMinimalHeight(int minimalHeight);
|
void setMinimalHeight(int minimalHeight);
|
||||||
[[nodiscard]] rpl::producer<> linkClicks() const {
|
|
||||||
return _linkClicks.events();
|
|
||||||
}
|
|
||||||
|
|
||||||
void animate();
|
void animate();
|
||||||
|
|
||||||
|
@ -36,7 +33,6 @@ private:
|
||||||
void setup(Icon icon, rpl::producer<TextWithEntities> text);
|
void setup(Icon icon, rpl::producer<TextWithEntities> text);
|
||||||
|
|
||||||
Fn<void()> _animate;
|
Fn<void()> _animate;
|
||||||
rpl::event_stream<> _linkClicks;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
459
Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp
Normal file
459
Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp
Normal file
|
@ -0,0 +1,459 @@
|
||||||
|
/*
|
||||||
|
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/ui/chat_search_in.h"
|
||||||
|
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
#include "ui/widgets/shadow.h"
|
||||||
|
#include "ui/widgets/menu/menu_item_base.h"
|
||||||
|
#include "ui/dynamic_image.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "styles/style_dialogs.h"
|
||||||
|
#include "styles/style_window.h"
|
||||||
|
|
||||||
|
namespace Dialogs {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class Action final : public Ui::Menu::ItemBase {
|
||||||
|
public:
|
||||||
|
Action(
|
||||||
|
not_null<Ui::PopupMenu*> parentMenu,
|
||||||
|
std::shared_ptr<Ui::DynamicImage> icon,
|
||||||
|
const QString &label,
|
||||||
|
bool chosen);
|
||||||
|
|
||||||
|
bool isEnabled() const override;
|
||||||
|
not_null<QAction*> action() const override;
|
||||||
|
|
||||||
|
void handleKeyPress(not_null<QKeyEvent*> e) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QPoint prepareRippleStartPosition() const override;
|
||||||
|
QImage prepareRippleMask() const override;
|
||||||
|
|
||||||
|
int contentHeight() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void paint(Painter &p);
|
||||||
|
|
||||||
|
void resolveMinWidth();
|
||||||
|
void refreshDimensions();
|
||||||
|
|
||||||
|
const not_null<Ui::PopupMenu*> _parentMenu;
|
||||||
|
const not_null<QAction*> _dummyAction;
|
||||||
|
const style::Menu &_st;
|
||||||
|
const int _height = 0;
|
||||||
|
|
||||||
|
std::shared_ptr<Ui::DynamicImage> _icon;
|
||||||
|
Ui::Text::String _text;
|
||||||
|
bool _checked = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] QString TabLabel(
|
||||||
|
ChatSearchTab tab,
|
||||||
|
ChatSearchPeerTabType type = {}) {
|
||||||
|
switch (tab) {
|
||||||
|
case ChatSearchTab::MyMessages:
|
||||||
|
return tr::lng_search_tab_my_messages(tr::now);
|
||||||
|
case ChatSearchTab::ThisTopic:
|
||||||
|
return tr::lng_search_tab_this_topic(tr::now);
|
||||||
|
case ChatSearchTab::ThisPeer:
|
||||||
|
switch (type) {
|
||||||
|
case ChatSearchPeerTabType::Chat:
|
||||||
|
return tr::lng_search_tab_this_chat(tr::now);
|
||||||
|
case ChatSearchPeerTabType::Channel:
|
||||||
|
return tr::lng_search_tab_this_channel(tr::now);
|
||||||
|
case ChatSearchPeerTabType::Group:
|
||||||
|
return tr::lng_search_tab_this_group(tr::now);
|
||||||
|
}
|
||||||
|
Unexpected("Type in Dialogs::TabLabel.");
|
||||||
|
case ChatSearchTab::PublicPosts:
|
||||||
|
return tr::lng_search_tab_public_posts(tr::now);
|
||||||
|
}
|
||||||
|
Unexpected("Tab in Dialogs::TabLabel.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Action::Action(
|
||||||
|
not_null<Ui::PopupMenu*> parentMenu,
|
||||||
|
std::shared_ptr<Ui::DynamicImage> icon,
|
||||||
|
const QString &label,
|
||||||
|
bool chosen)
|
||||||
|
: ItemBase(parentMenu->menu(), parentMenu->menu()->st())
|
||||||
|
, _parentMenu(parentMenu)
|
||||||
|
, _dummyAction(CreateChild<QAction>(parentMenu->menu().get()))
|
||||||
|
, _st(parentMenu->menu()->st())
|
||||||
|
, _height(st::dialogsSearchInHeight)
|
||||||
|
, _icon(std::move(icon))
|
||||||
|
, _checked(chosen) {
|
||||||
|
const auto parent = parentMenu->menu();
|
||||||
|
|
||||||
|
_text.setText(st::semiboldTextStyle, label);
|
||||||
|
_icon->subscribeToUpdates([=] { update(); });
|
||||||
|
|
||||||
|
initResizeHook(parent->sizeValue());
|
||||||
|
resolveMinWidth();
|
||||||
|
|
||||||
|
paintRequest(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
Painter p(this);
|
||||||
|
paint(p);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
enableMouseSelecting();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::resolveMinWidth() {
|
||||||
|
const auto maxWidth = st::dialogsSearchInPhotoPadding
|
||||||
|
+ st::dialogsSearchInPhotoSize
|
||||||
|
+ st::dialogsSearchInSkip
|
||||||
|
+ _text.maxWidth()
|
||||||
|
+ st::dialogsSearchInCheckSkip
|
||||||
|
+ st::dialogsSearchInCheck.width()
|
||||||
|
+ st::dialogsSearchInCheckSkip;
|
||||||
|
setMinWidth(maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::paint(Painter &p) {
|
||||||
|
const auto enabled = isEnabled();
|
||||||
|
const auto selected = isSelected();
|
||||||
|
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
||||||
|
p.fillRect(0, 0, width(), _height, _st.itemBg);
|
||||||
|
}
|
||||||
|
const auto &bg = selected ? _st.itemBgOver : _st.itemBg;
|
||||||
|
p.fillRect(0, 0, width(), _height, bg);
|
||||||
|
if (enabled) {
|
||||||
|
paintRipple(p, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto x = st::dialogsSearchInPhotoPadding;
|
||||||
|
const auto photos = st::dialogsSearchInPhotoSize;
|
||||||
|
const auto photoy = (height() - photos) / 2;
|
||||||
|
p.drawImage(QRect{ x, photoy, photos, photos }, _icon->image(photos));
|
||||||
|
x += photos + st::dialogsSearchInSkip;
|
||||||
|
const auto available = width()
|
||||||
|
- x
|
||||||
|
- st::dialogsSearchInCheckSkip
|
||||||
|
- st::dialogsSearchInCheck.width()
|
||||||
|
- st::dialogsSearchInCheckSkip;
|
||||||
|
|
||||||
|
p.setPen(!enabled
|
||||||
|
? _st.itemFgDisabled
|
||||||
|
: selected
|
||||||
|
? _st.itemFgOver
|
||||||
|
: _st.itemFg);
|
||||||
|
_text.drawLeftElided(
|
||||||
|
p,
|
||||||
|
x,
|
||||||
|
st::dialogsSearchInNameTop,
|
||||||
|
available,
|
||||||
|
width());
|
||||||
|
x += available;
|
||||||
|
if (_checked) {
|
||||||
|
x += st::dialogsSearchInCheckSkip;
|
||||||
|
const auto &icon = st::dialogsSearchInCheck;
|
||||||
|
const auto icony = (height() - icon.height()) / 2;
|
||||||
|
icon.paint(p, x, icony, width());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Action::isEnabled() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<QAction*> Action::action() const {
|
||||||
|
return _dummyAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint Action::prepareRippleStartPosition() const {
|
||||||
|
return mapFromGlobal(QCursor::pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage Action::prepareRippleMask() const {
|
||||||
|
return Ui::RippleAnimation::RectMask(size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int Action::contentHeight() const {
|
||||||
|
return _height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||||
|
if (!isSelected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto key = e->key();
|
||||||
|
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||||
|
setClicked(Ui::Menu::TriggeredSource::Keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
FixedHashtagSearchQuery FixHashtagSearchQuery(
|
||||||
|
const QString &query,
|
||||||
|
int cursorPosition) {
|
||||||
|
const auto trimmed = query.trimmed();
|
||||||
|
const auto hash = int(trimmed.isEmpty()
|
||||||
|
? query.size()
|
||||||
|
: query.indexOf(trimmed));
|
||||||
|
const auto start = std::min(cursorPosition, hash);
|
||||||
|
auto result = query.mid(0, start);
|
||||||
|
for (const auto &ch : query.mid(start)) {
|
||||||
|
if (ch.isSpace()) {
|
||||||
|
if (cursorPosition > result.size()) {
|
||||||
|
--cursorPosition;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if (result.size() == start) {
|
||||||
|
result += '#';
|
||||||
|
if (ch != '#') {
|
||||||
|
++cursorPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ch != '#') {
|
||||||
|
result += ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.size() == start) {
|
||||||
|
result += '#';
|
||||||
|
++cursorPosition;
|
||||||
|
}
|
||||||
|
return { result, cursorPosition };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsHashtagSearchQuery(const QString &query) {
|
||||||
|
const auto trimmed = query.trimmed();
|
||||||
|
if (trimmed.isEmpty() || trimmed[0] != '#') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto &ch : trimmed) {
|
||||||
|
if (ch.isSpace()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatSearchIn::Section::update() {
|
||||||
|
outer->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatSearchIn::ChatSearchIn(QWidget *parent)
|
||||||
|
: RpWidget(parent) {
|
||||||
|
_in.clicks.events() | rpl::start_with_next([=] {
|
||||||
|
showMenu();
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatSearchIn::~ChatSearchIn() = default;
|
||||||
|
|
||||||
|
void ChatSearchIn::apply(
|
||||||
|
std::vector<PossibleTab> tabs,
|
||||||
|
ChatSearchTab active,
|
||||||
|
ChatSearchPeerTabType peerTabType,
|
||||||
|
std::shared_ptr<Ui::DynamicImage> fromUserpic,
|
||||||
|
QString fromName) {
|
||||||
|
_tabs = std::move(tabs);
|
||||||
|
_peerTabType = peerTabType;
|
||||||
|
_active = active;
|
||||||
|
const auto i = ranges::find(_tabs, active, &PossibleTab::tab);
|
||||||
|
Assert(i != end(_tabs));
|
||||||
|
Assert(i->icon != nullptr);
|
||||||
|
updateSection(
|
||||||
|
&_in,
|
||||||
|
i->icon->clone(),
|
||||||
|
Ui::Text::Semibold(TabLabel(active, peerTabType)));
|
||||||
|
|
||||||
|
auto text = tr::lng_dlg_search_from(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
Ui::Text::Semibold(fromName),
|
||||||
|
Ui::Text::WithEntities);
|
||||||
|
updateSection(&_from, std::move(fromUserpic), std::move(text));
|
||||||
|
|
||||||
|
resizeToWidth(width());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> ChatSearchIn::cancelInRequests() const {
|
||||||
|
return _in.cancelRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> ChatSearchIn::cancelFromRequests() const {
|
||||||
|
return _from.cancelRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> ChatSearchIn::changeFromRequests() const {
|
||||||
|
return _from.clicks.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<ChatSearchTab> ChatSearchIn::tabChanges() const {
|
||||||
|
return _active.changes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatSearchIn::showMenu() {
|
||||||
|
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||||
|
this,
|
||||||
|
st::dialogsSearchInMenu);
|
||||||
|
const auto active = _active.current();
|
||||||
|
auto activeIndex = 0;
|
||||||
|
for (const auto &tab : _tabs) {
|
||||||
|
if (!tab.icon) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto value = tab.tab;
|
||||||
|
if (value == active) {
|
||||||
|
activeIndex = _menu->actions().size();
|
||||||
|
}
|
||||||
|
auto action = base::make_unique_q<Action>(
|
||||||
|
_menu.get(),
|
||||||
|
tab.icon,
|
||||||
|
TabLabel(value, _peerTabType),
|
||||||
|
(value == active));
|
||||||
|
action->setClickedCallback([=] {
|
||||||
|
_active = value;
|
||||||
|
});
|
||||||
|
_menu->addAction(std::move(action));
|
||||||
|
}
|
||||||
|
const auto count = int(_menu->actions().size());
|
||||||
|
const auto bottomLeft = (activeIndex * 2 >= count);
|
||||||
|
const auto single = st::dialogsSearchInHeight;
|
||||||
|
const auto in = mapToGlobal(_in.outer->pos()
|
||||||
|
+ QPoint(0, bottomLeft ? count * single : 0));
|
||||||
|
_menu->setForcedOrigin(bottomLeft
|
||||||
|
? Ui::PanelAnimation::Origin::BottomLeft
|
||||||
|
: Ui::PanelAnimation::Origin::TopLeft);
|
||||||
|
if (_menu->prepareGeometryFor(in)) {
|
||||||
|
_menu->move(_menu->pos() - QPoint(_menu->inner().x(), activeIndex * single));
|
||||||
|
_menu->popupPrepared();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatSearchIn::paintEvent(QPaintEvent *e) {
|
||||||
|
auto p = Painter(this);
|
||||||
|
const auto top = QRect(0, 0, width(), st::searchedBarHeight);
|
||||||
|
p.fillRect(top, st::searchedBarBg);
|
||||||
|
p.fillRect(rect().translated(0, st::searchedBarHeight), st::dialogsBg);
|
||||||
|
|
||||||
|
p.setFont(st::searchedBarFont);
|
||||||
|
p.setPen(st::searchedBarFg);
|
||||||
|
p.drawTextLeft(
|
||||||
|
st::searchedBarPosition.x(),
|
||||||
|
st::searchedBarPosition.y(),
|
||||||
|
width(),
|
||||||
|
tr::lng_dlg_search_in(tr::now));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ChatSearchIn::resizeGetHeight(int newWidth) {
|
||||||
|
auto result = st::searchedBarHeight;
|
||||||
|
if (const auto raw = _in.outer.get()) {
|
||||||
|
raw->resizeToWidth(newWidth);
|
||||||
|
raw->move(0, result);
|
||||||
|
result += raw->height();
|
||||||
|
_in.shadow->setGeometry(0, result, newWidth, st::lineWidth);
|
||||||
|
result += st::lineWidth;
|
||||||
|
}
|
||||||
|
if (const auto raw = _from.outer.get()) {
|
||||||
|
raw->resizeToWidth(newWidth);
|
||||||
|
raw->move(0, result);
|
||||||
|
result += raw->height();
|
||||||
|
_from.shadow->setGeometry(0, result, newWidth, st::lineWidth);
|
||||||
|
result += st::lineWidth;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatSearchIn::updateSection(
|
||||||
|
not_null<Section*> section,
|
||||||
|
std::shared_ptr<Ui::DynamicImage> image,
|
||||||
|
TextWithEntities text) {
|
||||||
|
if (section->subscribed) {
|
||||||
|
section->image->subscribeToUpdates(nullptr);
|
||||||
|
section->subscribed = false;
|
||||||
|
}
|
||||||
|
if (!image) {
|
||||||
|
if (section->outer) {
|
||||||
|
section->cancel = nullptr;
|
||||||
|
section->shadow = nullptr;
|
||||||
|
section->outer = nullptr;
|
||||||
|
section->subscribed = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (!section->outer) {
|
||||||
|
auto button = std::make_unique<Ui::AbstractButton>(this);
|
||||||
|
const auto raw = button.get();
|
||||||
|
section->outer = std::move(button);
|
||||||
|
|
||||||
|
raw->resize(
|
||||||
|
st::columnMinimalWidthLeft,
|
||||||
|
st::dialogsSearchInHeight);
|
||||||
|
|
||||||
|
raw->paintRequest() | rpl::start_with_next([=] {
|
||||||
|
auto p = QPainter(raw);
|
||||||
|
if (!section->subscribed) {
|
||||||
|
section->subscribed = true;
|
||||||
|
section->image->subscribeToUpdates([=] {
|
||||||
|
raw->update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const auto outer = raw->width();
|
||||||
|
const auto size = st::dialogsSearchInPhotoSize;
|
||||||
|
const auto left = st::dialogsSearchInPhotoPadding;
|
||||||
|
const auto top = (st::dialogsSearchInHeight - size) / 2;
|
||||||
|
p.drawImage(
|
||||||
|
QRect{ left, top, size, size },
|
||||||
|
section->image->image(size));
|
||||||
|
|
||||||
|
const auto x = left + size + st::dialogsSearchInSkip;
|
||||||
|
const auto available = outer
|
||||||
|
- st::dialogsSearchInSkip
|
||||||
|
- section->cancel->width()
|
||||||
|
- 2 * st::dialogsSearchInDownSkip
|
||||||
|
- st::dialogsSearchInDown.width()
|
||||||
|
- x;
|
||||||
|
const auto use = std::min(section->text.maxWidth(), available);
|
||||||
|
const auto iconx = x + use + st::dialogsSearchInDownSkip;
|
||||||
|
const auto icony = st::dialogsSearchInDownTop;
|
||||||
|
st::dialogsSearchInDown.paint(p, iconx, icony, outer);
|
||||||
|
p.setPen(st::windowBoldFg);
|
||||||
|
section->text.draw(p, {
|
||||||
|
.position = QPoint(x, st::dialogsSearchInNameTop),
|
||||||
|
.outerWidth = outer,
|
||||||
|
.availableWidth = available,
|
||||||
|
.elisionLines = 1,
|
||||||
|
});
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
section->shadow = std::make_unique<Ui::PlainShadow>(this);
|
||||||
|
section->shadow->show();
|
||||||
|
|
||||||
|
const auto st = &st::dialogsCancelSearchInPeer;
|
||||||
|
section->cancel = std::make_unique<Ui::IconButton>(raw, *st);
|
||||||
|
section->cancel->show();
|
||||||
|
raw->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||||
|
const auto left = size.width() - section->cancel->width();
|
||||||
|
const auto top = (size.height() - st->height) / 2;
|
||||||
|
section->cancel->moveToLeft(left, top);
|
||||||
|
}, section->cancel->lifetime());
|
||||||
|
section->cancel->clicks() | rpl::to_empty | rpl::start_to_stream(
|
||||||
|
section->cancelRequests,
|
||||||
|
section->cancel->lifetime());
|
||||||
|
|
||||||
|
raw->clicks() | rpl::to_empty | rpl::start_to_stream(
|
||||||
|
section->clicks,
|
||||||
|
raw->lifetime());
|
||||||
|
|
||||||
|
raw->show();
|
||||||
|
}
|
||||||
|
section->image = std::move(image);
|
||||||
|
section->text.setMarkedText(st::dialogsSearchFromStyle, std::move(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Dialogs
|
100
Telegram/SourceFiles/dialogs/ui/chat_search_in.h
Normal file
100
Telegram/SourceFiles/dialogs/ui/chat_search_in.h
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
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/unique_qptr.h"
|
||||||
|
#include "ui/rp_widget.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PlainShadow;
|
||||||
|
class DynamicImage;
|
||||||
|
class IconButton;
|
||||||
|
class PopupMenu;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Dialogs {
|
||||||
|
|
||||||
|
enum class ChatSearchTab : uchar {
|
||||||
|
MyMessages,
|
||||||
|
ThisTopic,
|
||||||
|
ThisPeer,
|
||||||
|
PublicPosts,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ChatSearchPeerTabType : uchar {
|
||||||
|
Chat,
|
||||||
|
Channel,
|
||||||
|
Group,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChatSearchIn final : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
explicit ChatSearchIn(QWidget *parent);
|
||||||
|
~ChatSearchIn();
|
||||||
|
|
||||||
|
struct PossibleTab {
|
||||||
|
ChatSearchTab tab = {};
|
||||||
|
std::shared_ptr<Ui::DynamicImage> icon;
|
||||||
|
};
|
||||||
|
void apply(
|
||||||
|
std::vector<PossibleTab> tabs,
|
||||||
|
ChatSearchTab active,
|
||||||
|
ChatSearchPeerTabType peerTabType,
|
||||||
|
std::shared_ptr<Ui::DynamicImage> fromUserpic,
|
||||||
|
QString fromName);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<> cancelInRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<> cancelFromRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<> changeFromRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<ChatSearchTab> tabChanges() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Section {
|
||||||
|
std::unique_ptr<Ui::RpWidget> outer;
|
||||||
|
std::unique_ptr<Ui::IconButton> cancel;
|
||||||
|
std::unique_ptr<Ui::PlainShadow> shadow;
|
||||||
|
std::shared_ptr<Ui::DynamicImage> image;
|
||||||
|
Ui::Text::String text;
|
||||||
|
rpl::event_stream<> clicks;
|
||||||
|
rpl::event_stream<> cancelRequests;
|
||||||
|
bool subscribed = false;
|
||||||
|
|
||||||
|
void update();
|
||||||
|
};
|
||||||
|
|
||||||
|
int resizeGetHeight(int newWidth) override;
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void showMenu();
|
||||||
|
|
||||||
|
void updateSection(
|
||||||
|
not_null<Section*> section,
|
||||||
|
std::shared_ptr<Ui::DynamicImage> image,
|
||||||
|
TextWithEntities text);
|
||||||
|
|
||||||
|
Section _in;
|
||||||
|
Section _from;
|
||||||
|
rpl::variable<ChatSearchTab> _active;
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
|
||||||
|
std::vector<PossibleTab> _tabs;
|
||||||
|
ChatSearchPeerTabType _peerTabType = ChatSearchPeerTabType::Chat;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FixedHashtagSearchQuery {
|
||||||
|
QString text;
|
||||||
|
int cursorPosition = 0;
|
||||||
|
};
|
||||||
|
[[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery(
|
||||||
|
const QString &query,
|
||||||
|
int cursorPosition);
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsHashtagSearchQuery(const QString &query);
|
||||||
|
|
||||||
|
} // namespace Dialogs
|
|
@ -1,201 +0,0 @@
|
||||||
/*
|
|
||||||
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/ui/chat_search_tabs.h"
|
|
||||||
|
|
||||||
#include "lang/lang_keys.h"
|
|
||||||
#include "ui/widgets/discrete_sliders.h"
|
|
||||||
#include "ui/widgets/shadow.h"
|
|
||||||
#include "styles/style_dialogs.h"
|
|
||||||
|
|
||||||
namespace Dialogs {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
[[nodiscard]] QString TabLabel(
|
|
||||||
ChatSearchTab tab,
|
|
||||||
ChatSearchPeerTabType type = {}) {
|
|
||||||
switch (tab) {
|
|
||||||
case ChatSearchTab::MyMessages:
|
|
||||||
return tr::lng_search_tab_my_messages(tr::now);
|
|
||||||
case ChatSearchTab::ThisTopic:
|
|
||||||
return tr::lng_search_tab_this_topic(tr::now);
|
|
||||||
case ChatSearchTab::ThisPeer:
|
|
||||||
switch (type) {
|
|
||||||
case ChatSearchPeerTabType::Chat:
|
|
||||||
return tr::lng_search_tab_this_chat(tr::now);
|
|
||||||
case ChatSearchPeerTabType::Channel:
|
|
||||||
return tr::lng_search_tab_this_channel(tr::now);
|
|
||||||
case ChatSearchPeerTabType::Group:
|
|
||||||
return tr::lng_search_tab_this_group(tr::now);
|
|
||||||
}
|
|
||||||
Unexpected("Type in Dialogs::TabLabel.");
|
|
||||||
case ChatSearchTab::PublicPosts:
|
|
||||||
return tr::lng_search_tab_public_posts(tr::now);
|
|
||||||
}
|
|
||||||
Unexpected("Tab in Dialogs::TabLabel.");
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TextWithEntities DefaultShortLabel(ChatSearchTab tab) {
|
|
||||||
// Return them in QString::fromUtf8 format.
|
|
||||||
switch (tab) {
|
|
||||||
case ChatSearchTab::MyMessages:
|
|
||||||
return { QString::fromUtf8("\xf0\x9f\x93\xa8") };
|
|
||||||
case ChatSearchTab::PublicPosts:
|
|
||||||
return { QString::fromUtf8("\xf0\x9f\x8c\x8e") };
|
|
||||||
}
|
|
||||||
Unexpected("Tab in Dialogs::DefaultShortLabel.");
|
|
||||||
}
|
|
||||||
|
|
||||||
FixedHashtagSearchQuery FixHashtagSearchQuery(
|
|
||||||
const QString &query,
|
|
||||||
int cursorPosition) {
|
|
||||||
const auto trimmed = query.trimmed();
|
|
||||||
const auto hash = int(trimmed.isEmpty()
|
|
||||||
? query.size()
|
|
||||||
: query.indexOf(trimmed));
|
|
||||||
const auto start = std::min(cursorPosition, hash);
|
|
||||||
auto result = query.mid(0, start);
|
|
||||||
for (const auto &ch : query.mid(start)) {
|
|
||||||
if (ch.isSpace()) {
|
|
||||||
if (cursorPosition > result.size()) {
|
|
||||||
--cursorPosition;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
} else if (result.size() == start) {
|
|
||||||
result += '#';
|
|
||||||
if (ch != '#') {
|
|
||||||
++cursorPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ch != '#') {
|
|
||||||
result += ch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result.size() == start) {
|
|
||||||
result += '#';
|
|
||||||
++cursorPosition;
|
|
||||||
}
|
|
||||||
return { result, cursorPosition };
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsHashtagSearchQuery(const QString &query) {
|
|
||||||
const auto trimmed = query.trimmed();
|
|
||||||
if (trimmed.isEmpty() || trimmed[0] != '#') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const auto &ch : trimmed) {
|
|
||||||
if (ch.isSpace()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatSearchTabs::ChatSearchTabs(
|
|
||||||
QWidget *parent,
|
|
||||||
ChatSearchTab active,
|
|
||||||
Fn<std::any(Fn<void()>)> markedTextContext)
|
|
||||||
: RpWidget(parent)
|
|
||||||
, _tabs(std::make_unique<Ui::SettingsSlider>(this, st::dialogsSearchTabs))
|
|
||||||
, _shadow(std::make_unique<Ui::PlainShadow>(this))
|
|
||||||
, _markedTextContext(std::move(markedTextContext))
|
|
||||||
, _active(active) {
|
|
||||||
_tabs->move(st::dialogsSearchTabsPadding, 0);
|
|
||||||
_tabs->sectionActivated(
|
|
||||||
) | rpl::start_with_next([=](int index) {
|
|
||||||
_active = _list[index].value;
|
|
||||||
}, lifetime());
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatSearchTabs::~ChatSearchTabs() = default;
|
|
||||||
|
|
||||||
void ChatSearchTabs::setTabShortLabels(
|
|
||||||
std::vector<ShortLabel> labels,
|
|
||||||
ChatSearchTab active,
|
|
||||||
ChatSearchPeerTabType peerTabType) {
|
|
||||||
const auto &st = st::dialogsSearchTabs;
|
|
||||||
const auto &font = st.labelStyle.font;
|
|
||||||
_list.clear();
|
|
||||||
_list.reserve(labels.size());
|
|
||||||
|
|
||||||
auto widthTotal = 0;
|
|
||||||
for (const auto tab : {
|
|
||||||
ChatSearchTab::ThisTopic,
|
|
||||||
ChatSearchTab::ThisPeer,
|
|
||||||
ChatSearchTab::MyMessages,
|
|
||||||
ChatSearchTab::PublicPosts,
|
|
||||||
}) {
|
|
||||||
const auto i = ranges::find(labels, tab, &ShortLabel::tab);
|
|
||||||
if (i != end(labels) && !i->label.empty()) {
|
|
||||||
const auto label = TabLabel(tab, peerTabType);
|
|
||||||
const auto widthFull = font->width(label) + st.strictSkip;
|
|
||||||
_list.push_back({
|
|
||||||
.value = tab,
|
|
||||||
.label = label,
|
|
||||||
.shortLabel = i->label,
|
|
||||||
.widthFull = widthFull,
|
|
||||||
});
|
|
||||||
widthTotal += widthFull;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto widthSingleEmoji = st::emojiSize + st.strictSkip;
|
|
||||||
for (const auto tab : {
|
|
||||||
ChatSearchTab::PublicPosts,
|
|
||||||
ChatSearchTab::ThisTopic,
|
|
||||||
ChatSearchTab::ThisPeer,
|
|
||||||
ChatSearchTab::MyMessages,
|
|
||||||
}) {
|
|
||||||
const auto i = ranges::find(_list, tab, &Tab::value);
|
|
||||||
if (i != end(_list)) {
|
|
||||||
i->widthThresholdForShort = widthTotal;
|
|
||||||
widthTotal -= i->widthFull;
|
|
||||||
widthTotal += widthSingleEmoji;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
refillTabs(active, width());
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<ChatSearchTab> ChatSearchTabs::tabChanges() const {
|
|
||||||
return _active.changes();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatSearchTabs::refillTabs(
|
|
||||||
ChatSearchTab active,
|
|
||||||
int newWidth) {
|
|
||||||
auto labels = std::vector<TextWithEntities>();
|
|
||||||
const auto available = newWidth - 2 * st::dialogsSearchTabsPadding;
|
|
||||||
for (const auto &tab : _list) {
|
|
||||||
auto label = (available < tab.widthThresholdForShort)
|
|
||||||
? tab.shortLabel
|
|
||||||
: TextWithEntities{ tab.label };
|
|
||||||
labels.push_back(std::move(label));
|
|
||||||
}
|
|
||||||
_tabs->setSections(labels, _markedTextContext([=] { update(); }));
|
|
||||||
|
|
||||||
const auto i = ranges::find(_list, active, &Tab::value);
|
|
||||||
Assert(i != end(_list));
|
|
||||||
_tabs->setActiveSectionFast(i - begin(_list));
|
|
||||||
_tabs->resizeToWidth(newWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ChatSearchTabs::resizeGetHeight(int newWidth) {
|
|
||||||
refillTabs(_active.current(), newWidth);
|
|
||||||
_shadow->setGeometry(
|
|
||||||
0,
|
|
||||||
_tabs->y() + _tabs->height() - st::lineWidth,
|
|
||||||
newWidth,
|
|
||||||
st::lineWidth);
|
|
||||||
return _tabs->height();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatSearchTabs::paintEvent(QPaintEvent *e) {
|
|
||||||
QPainter(this).fillRect(e->rect(), st::dialogsBg);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Dialogs
|
|
|
@ -1,89 +0,0 @@
|
||||||
/*
|
|
||||||
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 "ui/rp_widget.h"
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class SettingsSlider;
|
|
||||||
class PlainShadow;
|
|
||||||
} // namespace Ui
|
|
||||||
|
|
||||||
namespace Dialogs {
|
|
||||||
|
|
||||||
enum class ChatSearchTab : uchar {
|
|
||||||
MyMessages,
|
|
||||||
ThisTopic,
|
|
||||||
ThisPeer,
|
|
||||||
PublicPosts,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ChatSearchPeerTabType : uchar {
|
|
||||||
Chat,
|
|
||||||
Channel,
|
|
||||||
Group,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Available for MyMessages and PublicPosts.
|
|
||||||
[[nodiscard]] TextWithEntities DefaultShortLabel(ChatSearchTab tab);
|
|
||||||
|
|
||||||
class ChatSearchTabs final : public Ui::RpWidget {
|
|
||||||
public:
|
|
||||||
ChatSearchTabs(
|
|
||||||
QWidget *parent,
|
|
||||||
ChatSearchTab active,
|
|
||||||
Fn<std::any(Fn<void()>)> markedTextContext);
|
|
||||||
~ChatSearchTabs();
|
|
||||||
|
|
||||||
// A [custom] emoji to use when there is not enough space for text.
|
|
||||||
// Only tabs with available short labels are shown.
|
|
||||||
struct ShortLabel {
|
|
||||||
ChatSearchTab tab = {};
|
|
||||||
TextWithEntities label;
|
|
||||||
};
|
|
||||||
void setTabShortLabels(
|
|
||||||
std::vector<ShortLabel> labels,
|
|
||||||
ChatSearchTab active,
|
|
||||||
ChatSearchPeerTabType peerTabType);
|
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<ChatSearchTab> tabChanges() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Tab {
|
|
||||||
ChatSearchTab value = {};
|
|
||||||
QString label;
|
|
||||||
TextWithEntities shortLabel;
|
|
||||||
int widthFull = 0;
|
|
||||||
int widthThresholdForShort = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
void refreshTabs(ChatSearchTab active);
|
|
||||||
void refillTabs(ChatSearchTab active, int newWidth);
|
|
||||||
int resizeGetHeight(int newWidth) override;
|
|
||||||
void paintEvent(QPaintEvent *e) override;
|
|
||||||
|
|
||||||
const std::unique_ptr<Ui::SettingsSlider> _tabs;
|
|
||||||
const std::unique_ptr<Ui::PlainShadow> _shadow;
|
|
||||||
const Fn<std::any(Fn<void()>)> _markedTextContext;
|
|
||||||
|
|
||||||
std::vector<Tab> _list;
|
|
||||||
rpl::variable<ChatSearchTab> _active;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FixedHashtagSearchQuery {
|
|
||||||
QString text;
|
|
||||||
int cursorPosition = 0;
|
|
||||||
};
|
|
||||||
[[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery(
|
|
||||||
const QString &query,
|
|
||||||
int cursorPosition);
|
|
||||||
|
|
||||||
[[nodiscard]] bool IsHashtagSearchQuery(const QString &query);
|
|
||||||
|
|
||||||
} // namespace Dialogs
|
|
|
@ -379,6 +379,10 @@ bool Gif::autoplayEnabled() const {
|
||||||
_data);
|
_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Gif::hideMessageText() const {
|
||||||
|
return _data->isVideoMessage();
|
||||||
|
}
|
||||||
|
|
||||||
void Gif::draw(Painter &p, const PaintContext &context) const {
|
void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||||
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
|
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
|
||||||
|
|
||||||
|
|
|
@ -54,9 +54,7 @@ public:
|
||||||
bool spoiler);
|
bool spoiler);
|
||||||
~Gif();
|
~Gif();
|
||||||
|
|
||||||
bool hideMessageText() const override {
|
bool hideMessageText() const override;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw(Painter &p, const PaintContext &context) const override;
|
void draw(Painter &p, const PaintContext &context) const override;
|
||||||
TextState textState(QPoint point, StateRequest request) const override;
|
TextState textState(QPoint point, StateRequest request) const override;
|
||||||
|
|
|
@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
#include "data/data_photo_media.h"
|
#include "data/data_photo_media.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
#include "data/data_story.h"
|
#include "data/data_story.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "ui/empty_userpic.h"
|
#include "ui/empty_userpic.h"
|
||||||
|
@ -28,6 +30,8 @@ class PeerUserpic final : public DynamicImage {
|
||||||
public:
|
public:
|
||||||
PeerUserpic(not_null<PeerData*> peer, bool forceRound);
|
PeerUserpic(not_null<PeerData*> peer, bool forceRound);
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> clone() override;
|
||||||
|
|
||||||
QImage image(int size) override;
|
QImage image(int size) override;
|
||||||
void subscribeToUpdates(Fn<void()> callback) override;
|
void subscribeToUpdates(Fn<void()> callback) override;
|
||||||
|
|
||||||
|
@ -58,7 +62,6 @@ private:
|
||||||
class StoryThumbnail : public DynamicImage {
|
class StoryThumbnail : public DynamicImage {
|
||||||
public:
|
public:
|
||||||
explicit StoryThumbnail(FullStoryId id);
|
explicit StoryThumbnail(FullStoryId id);
|
||||||
virtual ~StoryThumbnail() = default;
|
|
||||||
|
|
||||||
QImage image(int size) override;
|
QImage image(int size) override;
|
||||||
void subscribeToUpdates(Fn<void()> callback) override;
|
void subscribeToUpdates(Fn<void()> callback) override;
|
||||||
|
@ -68,6 +71,9 @@ protected:
|
||||||
Image *image = nullptr;
|
Image *image = nullptr;
|
||||||
bool blurred = false;
|
bool blurred = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] FullStoryId id() const;
|
||||||
|
|
||||||
[[nodiscard]] virtual Main::Session &session() = 0;
|
[[nodiscard]] virtual Main::Session &session() = 0;
|
||||||
[[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0;
|
[[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0;
|
||||||
virtual void clear() = 0;
|
virtual void clear() = 0;
|
||||||
|
@ -85,6 +91,8 @@ class PhotoThumbnail final : public StoryThumbnail {
|
||||||
public:
|
public:
|
||||||
PhotoThumbnail(not_null<PhotoData*> photo, FullStoryId id);
|
PhotoThumbnail(not_null<PhotoData*> photo, FullStoryId id);
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> clone() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Main::Session &session() override;
|
Main::Session &session() override;
|
||||||
Thumb loaded(FullStoryId id) override;
|
Thumb loaded(FullStoryId id) override;
|
||||||
|
@ -99,6 +107,8 @@ class VideoThumbnail final : public StoryThumbnail {
|
||||||
public:
|
public:
|
||||||
VideoThumbnail(not_null<DocumentData*> video, FullStoryId id);
|
VideoThumbnail(not_null<DocumentData*> video, FullStoryId id);
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> clone() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Main::Session &session() override;
|
Main::Session &session() override;
|
||||||
Thumb loaded(FullStoryId id) override;
|
Thumb loaded(FullStoryId id) override;
|
||||||
|
@ -111,6 +121,8 @@ private:
|
||||||
|
|
||||||
class EmptyThumbnail final : public DynamicImage {
|
class EmptyThumbnail final : public DynamicImage {
|
||||||
public:
|
public:
|
||||||
|
std::shared_ptr<DynamicImage> clone() override;
|
||||||
|
|
||||||
QImage image(int size) override;
|
QImage image(int size) override;
|
||||||
void subscribeToUpdates(Fn<void()> callback) override;
|
void subscribeToUpdates(Fn<void()> callback) override;
|
||||||
|
|
||||||
|
@ -121,6 +133,8 @@ private:
|
||||||
|
|
||||||
class SavedMessagesUserpic final : public DynamicImage {
|
class SavedMessagesUserpic final : public DynamicImage {
|
||||||
public:
|
public:
|
||||||
|
std::shared_ptr<DynamicImage> clone() override;
|
||||||
|
|
||||||
QImage image(int size) override;
|
QImage image(int size) override;
|
||||||
void subscribeToUpdates(Fn<void()> callback) override;
|
void subscribeToUpdates(Fn<void()> callback) override;
|
||||||
|
|
||||||
|
@ -132,6 +146,8 @@ private:
|
||||||
|
|
||||||
class RepliesUserpic final : public DynamicImage {
|
class RepliesUserpic final : public DynamicImage {
|
||||||
public:
|
public:
|
||||||
|
std::shared_ptr<DynamicImage> clone() override;
|
||||||
|
|
||||||
QImage image(int size) override;
|
QImage image(int size) override;
|
||||||
void subscribeToUpdates(Fn<void()> callback) override;
|
void subscribeToUpdates(Fn<void()> callback) override;
|
||||||
|
|
||||||
|
@ -141,11 +157,48 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IconThumbnail final : public DynamicImage {
|
||||||
|
public:
|
||||||
|
explicit IconThumbnail(const style::icon &icon);
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> clone() override;
|
||||||
|
|
||||||
|
QImage image(int size) override;
|
||||||
|
void subscribeToUpdates(Fn<void()> callback) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const style::icon &_icon;
|
||||||
|
int _paletteVersion = 0;
|
||||||
|
QImage _frame;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class EmojiThumbnail final : public DynamicImage {
|
||||||
|
public:
|
||||||
|
EmojiThumbnail(not_null<Data::Session*> owner, const QString &data);
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> clone() override;
|
||||||
|
|
||||||
|
QImage image(int size) override;
|
||||||
|
void subscribeToUpdates(Fn<void()> callback) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<Data::Session*> _owner;
|
||||||
|
const QString _data;
|
||||||
|
std::unique_ptr<Ui::Text::CustomEmoji> _emoji;
|
||||||
|
QImage _frame;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
PeerUserpic::PeerUserpic(not_null<PeerData*> peer, bool forceRound)
|
PeerUserpic::PeerUserpic(not_null<PeerData*> peer, bool forceRound)
|
||||||
: _peer(peer)
|
: _peer(peer)
|
||||||
, _forceRound(forceRound) {
|
, _forceRound(forceRound) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> PeerUserpic::clone() {
|
||||||
|
return std::make_shared<PeerUserpic>(_peer, _forceRound);
|
||||||
|
}
|
||||||
|
|
||||||
QImage PeerUserpic::image(int size) {
|
QImage PeerUserpic::image(int size) {
|
||||||
Expects(_subscribed != nullptr);
|
Expects(_subscribed != nullptr);
|
||||||
|
|
||||||
|
@ -280,11 +333,19 @@ void StoryThumbnail::subscribeToUpdates(Fn<void()> callback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FullStoryId StoryThumbnail::id() const {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
|
||||||
PhotoThumbnail::PhotoThumbnail(not_null<PhotoData*> photo, FullStoryId id)
|
PhotoThumbnail::PhotoThumbnail(not_null<PhotoData*> photo, FullStoryId id)
|
||||||
: StoryThumbnail(id)
|
: StoryThumbnail(id)
|
||||||
, _photo(photo) {
|
, _photo(photo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> PhotoThumbnail::clone() {
|
||||||
|
return std::make_shared<PhotoThumbnail>(_photo, id());
|
||||||
|
}
|
||||||
|
|
||||||
Main::Session &PhotoThumbnail::session() {
|
Main::Session &PhotoThumbnail::session() {
|
||||||
return _photo->session();
|
return _photo->session();
|
||||||
}
|
}
|
||||||
|
@ -311,6 +372,10 @@ VideoThumbnail::VideoThumbnail(
|
||||||
, _video(video) {
|
, _video(video) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> VideoThumbnail::clone() {
|
||||||
|
return std::make_shared<VideoThumbnail>(_video, id());
|
||||||
|
}
|
||||||
|
|
||||||
Main::Session &VideoThumbnail::session() {
|
Main::Session &VideoThumbnail::session() {
|
||||||
return _video->session();
|
return _video->session();
|
||||||
}
|
}
|
||||||
|
@ -330,6 +395,10 @@ void VideoThumbnail::clear() {
|
||||||
_media = nullptr;
|
_media = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> EmptyThumbnail::clone() {
|
||||||
|
return std::make_shared<EmptyThumbnail>();
|
||||||
|
}
|
||||||
|
|
||||||
QImage EmptyThumbnail::image(int size) {
|
QImage EmptyThumbnail::image(int size) {
|
||||||
const auto ratio = style::DevicePixelRatio();
|
const auto ratio = style::DevicePixelRatio();
|
||||||
if (_cached.width() != size * ratio) {
|
if (_cached.width() != size * ratio) {
|
||||||
|
@ -345,6 +414,10 @@ QImage EmptyThumbnail::image(int size) {
|
||||||
void EmptyThumbnail::subscribeToUpdates(Fn<void()> callback) {
|
void EmptyThumbnail::subscribeToUpdates(Fn<void()> callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> SavedMessagesUserpic::clone() {
|
||||||
|
return std::make_shared<SavedMessagesUserpic>();
|
||||||
|
}
|
||||||
|
|
||||||
QImage SavedMessagesUserpic::image(int size) {
|
QImage SavedMessagesUserpic::image(int size) {
|
||||||
const auto good = (_frame.width() == size * _frame.devicePixelRatio());
|
const auto good = (_frame.width() == size * _frame.devicePixelRatio());
|
||||||
const auto paletteVersion = style::PaletteVersion();
|
const auto paletteVersion = style::PaletteVersion();
|
||||||
|
@ -367,6 +440,13 @@ QImage SavedMessagesUserpic::image(int size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SavedMessagesUserpic::subscribeToUpdates(Fn<void()> callback) {
|
void SavedMessagesUserpic::subscribeToUpdates(Fn<void()> callback) {
|
||||||
|
if (!callback) {
|
||||||
|
_frame = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> RepliesUserpic::clone() {
|
||||||
|
return std::make_shared<RepliesUserpic>();
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage RepliesUserpic::image(int size) {
|
QImage RepliesUserpic::image(int size) {
|
||||||
|
@ -391,6 +471,90 @@ QImage RepliesUserpic::image(int size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RepliesUserpic::subscribeToUpdates(Fn<void()> callback) {
|
void RepliesUserpic::subscribeToUpdates(Fn<void()> callback) {
|
||||||
|
if (!callback) {
|
||||||
|
_frame = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconThumbnail::IconThumbnail(const style::icon &icon) : _icon(icon) {
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> IconThumbnail::clone() {
|
||||||
|
return std::make_shared<IconThumbnail>(_icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage IconThumbnail::image(int size) {
|
||||||
|
const auto good = (_frame.width() == size * _frame.devicePixelRatio());
|
||||||
|
const auto paletteVersion = style::PaletteVersion();
|
||||||
|
if (!good || _paletteVersion != paletteVersion) {
|
||||||
|
_paletteVersion = paletteVersion;
|
||||||
|
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
if (!good) {
|
||||||
|
_frame = QImage(
|
||||||
|
QSize(size, size) * ratio,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
_frame.setDevicePixelRatio(ratio);
|
||||||
|
}
|
||||||
|
_frame.fill(Qt::transparent);
|
||||||
|
|
||||||
|
auto p = Painter(&_frame);
|
||||||
|
_icon.paintInCenter(p, QRect(0, 0, size, size));
|
||||||
|
}
|
||||||
|
return _frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IconThumbnail::subscribeToUpdates(Fn<void()> callback) {
|
||||||
|
if (!callback) {
|
||||||
|
_frame = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EmojiThumbnail::EmojiThumbnail(
|
||||||
|
not_null<Data::Session*> owner,
|
||||||
|
const QString &data)
|
||||||
|
: _owner(owner)
|
||||||
|
, _data(data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiThumbnail::subscribeToUpdates(Fn<void()> callback) {
|
||||||
|
if (!callback) {
|
||||||
|
_emoji = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_emoji = _owner->customEmojiManager().create(
|
||||||
|
_data,
|
||||||
|
std::move(callback),
|
||||||
|
Data::CustomEmojiSizeTag::Large);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> EmojiThumbnail::clone() {
|
||||||
|
return std::make_shared<EmojiThumbnail>(_owner, _data);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage EmojiThumbnail::image(int size) {
|
||||||
|
Expects(_emoji != nullptr);
|
||||||
|
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
const auto good = (_frame.width() == size * _frame.devicePixelRatio());
|
||||||
|
if (!good) {
|
||||||
|
_frame = QImage(
|
||||||
|
QSize(size, size) * ratio,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
_frame.setDevicePixelRatio(ratio);
|
||||||
|
}
|
||||||
|
_frame.fill(Qt::transparent);
|
||||||
|
|
||||||
|
auto p = Painter(&_frame);
|
||||||
|
_emoji->paint(p, {
|
||||||
|
.textColor = st::windowBoldFg->c,
|
||||||
|
.now = crl::now(),
|
||||||
|
.position = QPoint(0, 0),
|
||||||
|
.paused = false,
|
||||||
|
});
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
return _frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -422,4 +586,14 @@ std::shared_ptr<DynamicImage> MakeStoryThumbnail(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> MakeIconThumbnail(const style::icon &icon) {
|
||||||
|
return std::make_shared<IconThumbnail>(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DynamicImage> MakeEmojiThumbnail(
|
||||||
|
not_null<Data::Session*> owner,
|
||||||
|
const QString &data) {
|
||||||
|
return std::make_shared<EmojiThumbnail>(owner, data);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
@ -11,6 +11,7 @@ class PeerData;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class Story;
|
class Story;
|
||||||
|
class Session;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
@ -24,5 +25,10 @@ class DynamicImage;
|
||||||
[[nodiscard]] std::shared_ptr<DynamicImage> MakeRepliesThumbnail();
|
[[nodiscard]] std::shared_ptr<DynamicImage> MakeRepliesThumbnail();
|
||||||
[[nodiscard]] std::shared_ptr<DynamicImage> MakeStoryThumbnail(
|
[[nodiscard]] std::shared_ptr<DynamicImage> MakeStoryThumbnail(
|
||||||
not_null<Data::Story*> story);
|
not_null<Data::Story*> story);
|
||||||
|
[[nodiscard]] std::shared_ptr<DynamicImage> MakeIconThumbnail(
|
||||||
|
const style::icon &icon);
|
||||||
|
[[nodiscard]] std::shared_ptr<DynamicImage> MakeEmojiThumbnail(
|
||||||
|
not_null<Data::Session*> owner,
|
||||||
|
const QString &data);
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
@ -136,6 +136,7 @@ menuIconGroupCreate: icon {{ "menu/groups_create", menuIconColor }};
|
||||||
menuIconSigned: icon {{ "menu/signed", menuIconColor }};
|
menuIconSigned: icon {{ "menu/signed", menuIconColor }};
|
||||||
menuIconAntispam: icon {{ "menu/antispam", menuIconColor }};
|
menuIconAntispam: icon {{ "menu/antispam", menuIconColor }};
|
||||||
menuIconChatDiscuss: icon {{ "menu/chat_discuss", menuIconColor }};
|
menuIconChatDiscuss: icon {{ "menu/chat_discuss", menuIconColor }};
|
||||||
|
menuIconChats: icon {{ "menu/chats", menuIconColor }};
|
||||||
menuIconBotCommands: icon {{ "menu/bot_commands", menuIconColor }};
|
menuIconBotCommands: icon {{ "menu/bot_commands", menuIconColor }};
|
||||||
menuIconPremium: icon {{ "menu/premium", menuIconColor }};
|
menuIconPremium: icon {{ "menu/premium", menuIconColor }};
|
||||||
menuIconShop: icon {{ "menu/shop", menuIconColor }};
|
menuIconShop: icon {{ "menu/shop", menuIconColor }};
|
||||||
|
|
|
@ -89,8 +89,8 @@ PRIVATE
|
||||||
dialogs/dialogs_three_state_icon.h
|
dialogs/dialogs_three_state_icon.h
|
||||||
dialogs/ui/chat_search_empty.cpp
|
dialogs/ui/chat_search_empty.cpp
|
||||||
dialogs/ui/chat_search_empty.h
|
dialogs/ui/chat_search_empty.h
|
||||||
dialogs/ui/chat_search_tabs.cpp
|
dialogs/ui/chat_search_in.cpp
|
||||||
dialogs/ui/chat_search_tabs.h
|
dialogs/ui/chat_search_in.h
|
||||||
dialogs/ui/dialogs_stories_list.cpp
|
dialogs/ui/dialogs_stories_list.cpp
|
||||||
dialogs/ui/dialogs_stories_list.h
|
dialogs/ui/dialogs_stories_list.h
|
||||||
dialogs/ui/top_peers_strip.cpp
|
dialogs/ui/top_peers_strip.cpp
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit d0a7ff7734b6f887dc6367742c40c50729c032e3
|
Subproject commit 7b48c6a3618c7953046914ca4c6fe739684644b8
|
Loading…
Add table
Reference in a new issue