Search for messages from a specific user in group.

This commit is contained in:
John Preston 2017-08-01 18:55:51 +03:00
parent 8c60ac78aa
commit 6f746c238a
14 changed files with 485 additions and 163 deletions

View File

@ -1188,6 +1188,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_search_found_results#one" = "Found {count} message";
"lng_search_found_results#other" = "Found {count} messages";
"lng_search_global_results" = "Global search results";
"lng_search_messages_from" = "Show messages from";
"lng_media_save_progress" = "{ready} of {total} {mb}";
"lng_mediaview_save_as" = "Save As...";

View File

@ -301,25 +301,7 @@ contactsMultiSelect: MultiSelect {
hiding: 1000;
}
item: MultiSelectItem {
padding: margins(6px, 7px, 12px, 0px);
maxWidth: 128px;
height: 32px;
style: defaultTextStyle;
textBg: contactsBgOver;
textFg: windowFg;
textActiveBg: activeButtonBg;
textActiveFg: activeButtonFg;
deleteFg: activeButtonFg;
deleteCross: CrossAnimation {
size: 32px;
skip: 10px;
stroke: 2px;
minScale: 0.3;
}
duration: 150;
minScale: 0.3;
}
item: defaultMultiSelectItem;
itemSkip: 8px;
field: contactsSearchField;

View File

@ -112,6 +112,10 @@ dialogsSearchFrom: IconButton(dialogsCalendar) {
icon: icon {{ "dialogs_search_from", dialogsMenuIconFg }};
iconOver: icon {{ "dialogs_search_from", dialogsMenuIconFgOver }};
}
dialogsSearchFromPadding: margins(10px, 10px, 10px, 10px);
dialogsSearchFromBubble: MultiSelectItem(defaultMultiSelectItem) {
maxWidth: 240px;
}
dialogsFilter: FlatInput(defaultFlatInput) {
font: font(fsize);

View File

@ -22,8 +22,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "dialogs/dialogs_indexed_list.h"
#include "dialogs/dialogs_layout.h"
#include "dialogs/dialogs_search_from_controllers.h"
#include "styles/style_dialogs.h"
#include "styles/style_chat_helpers.h"
#include "boxes/contacts_box.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "data/data_drafts.h"
@ -37,6 +39,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "auth_session.h"
#include "window/notifications_manager.h"
#include "window/window_controller.h"
#include "ui/widgets/multi_select.h"
namespace {
@ -45,6 +48,11 @@ constexpr auto kStartReorderThreshold = 30;
} // namespace
class DialogsInner::SearchFromBubble : public Ui::MultiSelect::Item {
public:
using Item::Item;
};
struct DialogsInner::ImportantSwitch {
Dialogs::RippleRow row;
};
@ -123,8 +131,18 @@ int DialogsInner::peerSearchOffset() const {
}
int DialogsInner::searchedOffset() const {
int result = peerSearchOffset() + (_peerSearchResults.empty() ? 0 : ((_peerSearchResults.size() * st::dialogsRowHeight) + st::searchedBarHeight));
if (_searchInPeer) result += st::dialogsRowHeight;
auto result = peerSearchOffset() + (_peerSearchResults.empty() ? 0 : ((_peerSearchResults.size() * st::dialogsRowHeight) + st::searchedBarHeight));
if (_searchInPeer) {
result += searchInPeerSkip();
}
return result;
}
int DialogsInner::searchInPeerSkip() const {
auto result = st::dialogsRowHeight;
if (_searchFromUserBubble) {
result += st::lineWidth + st::dialogsSearchFromPadding.top() + _searchFromUserBubble->rect().height() + st::dialogsSearchFromPadding.bottom();
}
return result;
}
@ -300,8 +318,8 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
}
if (_searchInPeer) {
paintSearchInPeer(p, fullWidth, paintingOther);
p.translate(0, st::dialogsRowHeight);
paintSearchInPeer(p, fullWidth, paintingOther, ms);
p.translate(0, searchInPeerSkip());
if (_state == FilteredState && _searchResults.empty()) {
p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg);
if (!paintingOther) {
@ -409,9 +427,13 @@ void DialogsInner::paintPeerSearchResult(Painter &p, const PeerSearchResult *res
peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width());
}
void DialogsInner::paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground) const {
QRect fullRect(0, 0, fullWidth, st::dialogsRowHeight);
void DialogsInner::paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground, TimeMs ms) const {
auto height = searchInPeerSkip();
auto fullRect = QRect(0, 0, fullWidth, height);
p.fillRect(fullRect, st::dialogsBg);
if (_searchFromUserBubble) {
p.fillRect(QRect(0, st::dialogsRowHeight, width(), st::lineWidth), st::shadowFg);
}
if (onlyBackground) return;
_searchInPeer->paintUserpicLeft(p, st::dialogsPadding.x(), st::dialogsPadding.y(), getFullWidth(), st::dialogsPhotoSize);
@ -432,14 +454,19 @@ void DialogsInner::paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackgro
p.setPen(st::dialogsNameFg);
_searchInPeer->nameText.drawElided(p, rectForName.left(), rectForName.top(), rectForName.width());
if (_searchFromUserBubble) {
_searchFromUserBubble->paint(p, width(), ms);
}
}
void DialogsInner::activate() {
}
void DialogsInner::mouseMoveEvent(QMouseEvent *e) {
auto position = e->pos();
_mouseSelection = true;
updateSelected(e->pos());
updateSelected(position);
}
void DialogsInner::clearIrrelevantState() {
@ -465,6 +492,15 @@ void DialogsInner::updateSelected(QPoint localPos) {
if (updateReorderPinned(localPos)) {
return;
}
if (_searchFromUserBubble) {
if (_searchFromUserBubble->rect().contains(localPos)) {
_searchFromUserBubble->mouseMoveEvent(localPos - _searchFromUserBubble->rect().topLeft());
} else {
_searchFromUserBubble->leaveEvent();
}
}
if (!_mouseSelection) {
return;
}
@ -542,10 +578,26 @@ void DialogsInner::updateSelected(QPoint localPos) {
}
}
void DialogsInner::handleSearchFromUserClick() {
Expects(_searchFromUserBubble != nullptr);
if (_searchFromUserBubble->isOverDelete()) {
searchFromUserChanged.notify(nullptr);
} else {
Dialogs::ShowSearchFromBox(_searchInPeer, base::lambda_guarded(this, [this](gsl::not_null<UserData*> user) {
Ui::hideLayer();
searchFromUserChanged.notify(user);
}));
}
}
void DialogsInner::mousePressEvent(QMouseEvent *e) {
_mouseSelection = true;
updateSelected(e->pos());
if (_searchFromUserBubble && _searchFromUserBubble->rect().contains(e->pos())) {
return handleSearchFromUserClick();
}
_pressButton = e->button();
setPressed(_selected);
setImportantSwitchPressed(_importantSwitchSelected);
@ -904,6 +956,13 @@ void DialogsInner::resizeEvent(QResizeEvent *e) {
_addContactLnk->move((width() - _addContactLnk->width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2);
auto widthForCancelButton = qMax(width() + otherWidth(), st::dialogsWidthMin);
_cancelSearchInPeer->moveToLeft(widthForCancelButton - st::dialogsFilterSkip - st::dialogsFilterPadding.x() - _cancelSearchInPeer->width(), (st::dialogsRowHeight - st::dialogsCancelSearchInPeer.height) / 2);
updateSearchFromBubble();
}
void DialogsInner::updateSearchFromBubble() {
if (_searchFromUserBubble) {
_searchFromUserBubble->setPosition(st::dialogsSearchFromPadding.left(), st::dialogsRowHeight + st::lineWidth + st::dialogsSearchFromPadding.top(), width(), st::dialogsSearchFromPadding.left());
}
}
void DialogsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) {
@ -1134,6 +1193,9 @@ void DialogsInner::updateSelectedRow(PeerData *peer) {
void DialogsInner::leaveEventHook(QEvent *e) {
setMouseTracking(false);
clearSelection();
if (_searchFromUserBubble) {
_searchFromUserBubble->leaveEvent();
}
}
void DialogsInner::dragLeft() {
@ -1233,15 +1295,8 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) {
newFilter = words.isEmpty() ? QString() : words.join(' ');
if (newFilter != _filter || force) {
_filter = newFilter;
if (!_searchInPeer && _filter.isEmpty()) {
_state = DefaultState;
_hashtagResults.clear();
_filterResults.clear();
_peerSearchResults.clear();
_searchResults.clear();
_lastSearchDate = 0;
_lastSearchPeer = 0;
_lastSearchId = _lastSearchMigratedId = 0;
if (_filter.isEmpty() && !_searchFromUser) {
clearFilter();
} else {
QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi;
@ -1316,8 +1371,8 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) {
}
}
}
refresh(true);
}
refresh(true);
setMouseSelection(false, true);
}
if (_state != DefaultState) {
@ -1695,9 +1750,17 @@ bool DialogsInner::hasFilteredResults() const {
return !_filterResults.isEmpty() && _hashtagResults.empty();
}
void DialogsInner::searchInPeer(PeerData *peer) {
void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
_searchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : nullptr;
_searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : nullptr;
_searchFromUser = from;
if (_searchFromUser) {
_searchFromUserBubble = std::make_unique<SearchFromBubble>(st::dialogsSearchFromBubble, _searchFromUser->id, App::peerName(_searchFromUser), st::activeButtonBg, PaintUserpicCallback(_searchFromUser));
_searchFromUserBubble->setUpdateCallback([this] { update(0, st::dialogsRowHeight + st::lineWidth, width(), searchInPeerSkip() - st::dialogsRowHeight - st::lineWidth); });
updateSearchFromBubble();
} else {
_searchFromUserBubble.reset();
}
if (_searchInPeer) {
onHashtagFilterUpdate(QStringRef());
_cancelSearchInPeer->show();
@ -1708,7 +1771,7 @@ void DialogsInner::searchInPeer(PeerData *peer) {
}
void DialogsInner::clearFilter() {
if (_state == FilteredState || _state == SearchedState) {
if (_state == FilteredState || _state == SearchedState || _searchInPeer) {
if (_searchInPeer) {
_state = FilteredState;
} else {

View File

@ -96,7 +96,7 @@ public:
State state() const;
bool hasFilteredResults() const;
void searchInPeer(PeerData *peer);
void searchInPeer(PeerData *peer, UserData *from);
void onFilterUpdate(QString newFilter, bool force = false);
void onHashtagFilterUpdate(QStringRef newFilter);
@ -108,6 +108,8 @@ public:
}
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
base::Observable<UserData*> searchFromUserChanged;
void notify_userIsContactChanged(UserData *user, bool fromThisApp);
void notify_historyMuteUpdated(History *history);
@ -187,14 +189,17 @@ private:
int filteredOffset() const;
int peerSearchOffset() const;
int searchedOffset() const;
int searchInPeerSkip() const;
void paintDialog(Painter &p, Dialogs::Row *row, int fullWidth, PeerData *active, PeerData *selected, bool onlyBackground, TimeMs ms);
void paintPeerSearchResult(Painter &p, const PeerSearchResult *result, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) const;
void paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground) const;
void paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground, TimeMs ms) const;
void clearSelection();
void clearSearchResults(bool clearPeerSearchResults = true);
void updateSelectedRow(PeerData *peer = 0);
void updateSearchFromBubble();
void handleSearchFromUserClick();
Dialogs::IndexedList *shownDialogs() const {
return (Global::DialogsMode() == Dialogs::Mode::Important) ? _dialogsImportant.get() : _dialogs.get();
@ -279,6 +284,9 @@ private:
PeerData *_searchInPeer = nullptr;
PeerData *_searchInMigrated = nullptr;
UserData *_searchFromUser = nullptr;
class SearchFromBubble; // Just a wrap for Ui::MultiSelect::Item.
std::unique_ptr<SearchFromBubble> _searchFromUserBubble;
PeerData *_menuPeer = nullptr;
Ui::PopupMenu *_menu = nullptr;

View File

@ -0,0 +1,134 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "dialogs/dialogs_search_from_controllers.h"
#include "lang/lang_keys.h"
#include "observer_peer.h"
#include "auth_session.h"
#include "apiwrap.h"
namespace Dialogs {
void ShowSearchFromBox(PeerData *peer, base::lambda<void(gsl::not_null<UserData*>)> callback) {
auto createController = [peer, callback = std::move(callback)]()->std::unique_ptr<PeerListController> {
if (peer) {
if (auto chat = peer->asChat()) {
return std::make_unique<Dialogs::ChatSearchFromController>(chat, std::move(callback));
} else if (auto group = peer->asMegagroup()) {
return std::make_unique<Dialogs::ChannelSearchFromController>(group, std::move(callback));
}
}
return nullptr;
};
if (auto controller = createController()) {
Ui::show(Box<PeerListBox>(std::move(controller), [](PeerListBox *box) {
box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); });
}), KeepOtherLayers);
}
}
ChatSearchFromController::ChatSearchFromController(gsl::not_null<ChatData*> chat, base::lambda<void(gsl::not_null<UserData*>)> callback) : PeerListController()
, _chat(chat)
, _callback(std::move(callback)) {
}
void ChatSearchFromController::prepare() {
setSearchNoResultsText(lang(lng_blocked_list_not_found));
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
delegate()->peerListSetTitle(langFactory(lng_search_messages_from));
rebuildRows();
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::MembersChanged, [this](const Notify::PeerUpdate &update) {
if (update.peer == _chat) {
rebuildRows();
}
}));
}
void ChatSearchFromController::rowClicked(gsl::not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
_callback(row->peer()->asUser());
}
void ChatSearchFromController::rebuildRows() {
auto ms = getms();
auto wasEmpty = !delegate()->peerListFullRowsCount();
auto now = unixtime();
QMultiMap<int32, UserData*> ordered;
if (_chat->noParticipantInfo()) {
AuthSession::Current().api().requestFullPeer(_chat);
} else if (!_chat->participants.isEmpty()) {
for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
auto user = i.key();
ordered.insertMulti(App::onlineForSort(user, now), user);
}
}
for_const (auto user, _chat->lastAuthors) {
if (user->isInaccessible()) continue;
appendRow(user);
if (!ordered.isEmpty()) {
ordered.remove(App::onlineForSort(user, now), user);
}
}
if (!ordered.isEmpty()) {
for (auto i = ordered.cend(), b = ordered.cbegin(); i != b;) {
appendRow(*(--i));
}
}
checkForEmptyRows();
delegate()->peerListRefreshRows();
}
void ChatSearchFromController::checkForEmptyRows() {
if (delegate()->peerListFullRowsCount()) {
setDescriptionText(QString());
} else {
setDescriptionText(lang(lng_contacts_loading));
}
}
void ChatSearchFromController::appendRow(gsl::not_null<UserData*> user) {
if (!delegate()->peerListFindRow(user->id)) {
delegate()->peerListAppendRow(std::make_unique<PeerListRow>(user));
}
}
ChannelSearchFromController::ChannelSearchFromController(gsl::not_null<ChannelData*> channel, base::lambda<void(gsl::not_null<UserData*>)> callback) : ParticipantsBoxController(channel, ParticipantsBoxController::Role::Members)
, _callback(std::move(callback)) {
}
void ChannelSearchFromController::prepare() {
ParticipantsBoxController::prepare();
delegate()->peerListSetTitle(langFactory(lng_search_messages_from));
}
void ChannelSearchFromController::rowClicked(gsl::not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
_callback(row->peer()->asUser());
}
std::unique_ptr<PeerListRow> ChannelSearchFromController::createRow(gsl::not_null<UserData*> user) const {
return std::make_unique<PeerListRow>(user);
}
} // namespace Dialogs

View File

@ -0,0 +1,62 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "boxes/peer_list_box.h"
#include "profile/profile_channel_controllers.h"
namespace Dialogs {
void ShowSearchFromBox(PeerData *peer, base::lambda<void(gsl::not_null<UserData*>)> callback);
class ChatSearchFromController : public PeerListController, protected base::Subscriber {
public:
ChatSearchFromController(gsl::not_null<ChatData*> chat, base::lambda<void(gsl::not_null<UserData*>)> callback);
void prepare() override;
void rowClicked(gsl::not_null<PeerListRow*> row) override;
private:
void rebuildRows();
void checkForEmptyRows();
void appendRow(gsl::not_null<UserData*> user);
gsl::not_null<ChatData*> _chat;
base::lambda<void(gsl::not_null<UserData*>)> _callback;
};
class ChannelSearchFromController : public Profile::ParticipantsBoxController {
public:
ChannelSearchFromController(gsl::not_null<ChannelData*> channel, base::lambda<void(gsl::not_null<UserData*>)> callback);
void prepare() override;
void rowClicked(gsl::not_null<PeerListRow*> row) override;
protected:
std::unique_ptr<PeerListRow> createRow(gsl::not_null<UserData*> user) const override;
private:
base::lambda<void(gsl::not_null<UserData*>)> _callback;
};
} // namespace Dialogs

View File

@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "dialogs/dialogs_widget.h"
#include "dialogs/dialogs_inner_widget.h"
#include "dialogs/dialogs_search_from_controllers.h"
#include "styles/style_dialogs.h"
#include "ui/widgets/buttons.h"
#include "lang/lang_keys.h"
@ -32,7 +33,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "auth_session.h"
#include "messenger.h"
#include "ui/effects/widget_fade_wrap.h"
#include "boxes/peer_list_box.h"
#include "window/window_controller.h"
#include "profile/profile_channel_controllers.h"
class DialogsWidget::UpdateButton : public Ui::RippleButton {
public:
@ -83,7 +86,7 @@ void DialogsWidget::UpdateButton::paintEvent(QPaintEvent *e) {
DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null<Window::Controller*> controller) : Window::AbstractSectionWidget(parent, controller)
, _mainMenuToggle(this, st::dialogsMenuToggle)
, _filter(this, st::dialogsFilter, langFactory(lng_dlg_filter))
, _searchFromUser(this, object_ptr<Ui::IconButton>(this, st::dialogsSearchFrom))
, _chooseFromUser(this, object_ptr<Ui::IconButton>(this, st::dialogsSearchFrom))
, _jumpToDate(this, object_ptr<Ui::IconButton>(this, st::dialogsCalendar))
, _cancelSearch(this, st::dialogsCancelSearch)
, _lockUnlock(this, st::dialogsLock)
@ -97,6 +100,10 @@ DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null<Window::Controller*>
connect(_inner, SIGNAL(completeHashtag(QString)), this, SLOT(onCompleteHashtag(QString)));
connect(_inner, SIGNAL(refreshHashtags()), this, SLOT(onFilterCursorMoved()));
connect(_inner, SIGNAL(cancelSearchInPeer()), this, SLOT(onCancelSearchInPeer()));
subscribe(_inner->searchFromUserChanged, [this](UserData *user) {
setSearchInPeer(_searchInPeer, user);
onFilterUpdate(true);
});
connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged()));
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll()));
connect(_filter, SIGNAL(cancelled()), this, SLOT(onCancel()));
@ -114,7 +121,7 @@ DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null<Window::Controller*>
_cancelSearch->setClickedCallback([this] { onCancelSearch(); });
_jumpToDate->entity()->setClickedCallback([this] { if (_searchInPeer) this->controller()->showJumpToDate(_searchInPeer, QDate()); });
_searchFromUser->entity()->setClickedCallback([this] { if (_searchInPeer->isChat() || _searchInPeer->isMegagroup()) showSearchFrom(); });
_chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); });
_lockUnlock->setVisible(Global::LocalPasscode());
subscribe(Global::RefLocalPasscodeChanged(), [this] { updateLockUnlockVisibility(); });
_lockUnlock->setClickedCallback([this] {
@ -144,6 +151,7 @@ DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null<Window::Controller*>
_filter->customUpDown(true);
updateJumpToDateVisibility(true);
updateSearchFromVisibility(true);
}
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
@ -241,7 +249,7 @@ void DialogsWidget::showAnimated(Window::SlideDirection direction, const Window:
_filter->hide();
_cancelSearch->hideFast();
_jumpToDate->hideFast();
_searchFromUser->hideFast();
_chooseFromUser->hideFast();
_lockUnlock->hide();
int delta = st::slideShift;
@ -270,6 +278,7 @@ void DialogsWidget::animationCallback() {
_filter->show();
updateLockUnlockVisibility();
updateJumpToDateVisibility(true);
updateSearchFromVisibility(true);
onFilterUpdate();
if (App::wnd()) App::wnd()->setInnerFocus();
@ -450,8 +459,8 @@ void DialogsWidget::onDraggingScrollTimer() {
}
bool DialogsWidget::onSearchMessages(bool searchCache) {
QString q = _filter->getLastText().trimmed();
if (q.isEmpty()) {
auto q = _filter->getLastText().trimmed();
if (q.isEmpty() && !_searchFromUser) {
MTP::cancel(base::take(_searchRequest));
MTP::cancel(base::take(_peerSearchRequest));
return true;
@ -460,17 +469,20 @@ bool DialogsWidget::onSearchMessages(bool searchCache) {
SearchCache::const_iterator i = _searchCache.constFind(q);
if (i != _searchCache.cend()) {
_searchQuery = q;
_searchQueryFrom = _searchFromUser;
_searchFull = _searchFullMigrated = false;
MTP::cancel(base::take(_searchRequest));
searchReceived(_searchInPeer ? DialogsSearchPeerFromStart : DialogsSearchFromStart, i.value(), 0);
return true;
}
} else if (_searchQuery != q) {
} else if (_searchQuery != q || _searchQueryFrom != _searchFromUser) {
_searchQuery = q;
_searchQueryFrom = _searchFromUser;
_searchFull = _searchFullMigrated = false;
MTP::cancel(base::take(_searchRequest));
if (_searchInPeer) {
_searchRequest = MTP::send(MTPmessages_Search(MTP_flags(0), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart));
auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0);
_searchRequest = MTP::send(MTPmessages_Search(flags, _searchInPeer->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart));
} else {
_searchRequest = MTP::send(MTPmessages_SearchGlobal(MTP_string(_searchQuery), MTP_int(0), MTP_inputPeerEmpty(), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchFromStart));
}
@ -532,7 +544,8 @@ void DialogsWidget::onSearchMore() {
auto offsetPeer = _inner->lastSearchPeer();
auto offsetId = _inner->lastSearchId();
if (_searchInPeer) {
_searchRequest = MTP::send(MTPmessages_Search(MTP_flags(0), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart));
auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0);
_searchRequest = MTP::send(MTPmessages_Search(flags, _searchInPeer->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart));
} else {
_searchRequest = MTP::send(MTPmessages_SearchGlobal(MTP_string(_searchQuery), MTP_int(offsetDate), offsetPeer ? offsetPeer->input : MTP_inputPeerEmpty(), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart));
}
@ -541,7 +554,8 @@ void DialogsWidget::onSearchMore() {
}
} else if (_searchInMigrated && !_searchFullMigrated) {
auto offsetMigratedId = _inner->lastSearchMigratedId();
_searchRequest = MTP::send(MTPmessages_Search(MTP_flags(0), _searchInMigrated->input, MTP_string(_searchQuery), MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart));
auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0);
_searchRequest = MTP::send(MTPmessages_Search(flags, _searchInMigrated->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart));
}
}
}
@ -783,9 +797,7 @@ void DialogsWidget::onFilterUpdate(bool force) {
auto filterText = _filter->getLastText();
_inner->onFilterUpdate(filterText, force);
if (filterText.isEmpty()) {
_searchCache.clear();
_searchQueries.clear();
_searchQuery = QString();
clearSearchCache();
_cancelSearch->hideAnimated();
} else {
_cancelSearch->showAnimated();
@ -805,19 +817,42 @@ void DialogsWidget::searchInPeer(PeerData *peer) {
onFilterUpdate(true);
}
void DialogsWidget::setSearchInPeer(PeerData *peer) {
void DialogsWidget::setSearchInPeer(PeerData *peer, UserData *from) {
auto searchInPeerUpdated = false;
auto newSearchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : nullptr;
_searchInMigrated = newSearchInPeer ? newSearchInPeer->migrateFrom() : nullptr;
if (newSearchInPeer != _searchInPeer) {
searchInPeerUpdated = (newSearchInPeer != _searchInPeer);
if (searchInPeerUpdated) {
_searchInPeer = newSearchInPeer;
from = nullptr;
controller()->searchInPeerChanged().notify(_searchInPeer, true);
updateJumpToDateVisibility();
} else if (!_searchInPeer) {
from = nullptr;
}
_inner->searchInPeer(_searchInPeer);
if (_searchFromUser != from || searchInPeerUpdated) {
_searchFromUser = from;
updateSearchFromVisibility();
clearSearchCache();
}
_inner->searchInPeer(_searchInPeer, _searchFromUser);
}
void DialogsWidget::clearSearchCache() {
_searchCache.clear();
_searchQueries.clear();
_searchQuery = QString();
_searchQueryFrom = nullptr;
MTP::cancel(base::take(_searchRequest));
}
void DialogsWidget::showSearchFrom() {
auto peer = _searchInPeer;
Dialogs::ShowSearchFromBox(peer, base::lambda_guarded(this, [this, peer](gsl::not_null<UserData*> user) {
Ui::hideLayer();
setSearchInPeer(peer, user);
onFilterUpdate(true);
}));
}
void DialogsWidget::onFilterCursorMoved(int from, int to) {
@ -883,12 +918,14 @@ void DialogsWidget::updateJumpToDateVisibility(bool fast) {
} else {
_jumpToDate->toggleAnimated(jumpToDateVisible);
}
}
auto searchFromUserVisible = _searchInPeer && (_searchInPeer->isChat() || _searchInPeer->isMegagroup());
void DialogsWidget::updateSearchFromVisibility(bool fast) {
auto searchFromUserVisible = _searchInPeer && (_searchInPeer->isChat() || _searchInPeer->isMegagroup()) && !_searchFromUser;
if (fast) {
_searchFromUser->toggleFast(searchFromUserVisible);
_chooseFromUser->toggleFast(searchFromUserVisible);
} else {
_searchFromUser->toggleAnimated(searchFromUserVisible);
_chooseFromUser->toggleAnimated(searchFromUserVisible);
}
}
@ -909,10 +946,11 @@ void DialogsWidget::updateControlsGeometry() {
_filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height());
auto mainMenuLeft = anim::interpolate(st::dialogsFilterPadding.x(), (smallLayoutWidth - _mainMenuToggle->width()) / 2, smallLayoutRatio);
_mainMenuToggle->moveToLeft(mainMenuLeft, filterAreaTop + st::dialogsFilterPadding.y());
_lockUnlock->moveToLeft(filterLeft + filterWidth + st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y());
_cancelSearch->moveToLeft(filterLeft + filterWidth - _cancelSearch->width(), _filter->y());
_jumpToDate->moveToLeft(filterLeft + filterWidth - _jumpToDate->width(), _filter->y());
_searchFromUser->moveToLeft(filterLeft + filterWidth - _jumpToDate->width() - _searchFromUser->width(), _filter->y());
auto right = filterLeft + filterWidth;
_lockUnlock->moveToLeft(right + st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y());
_cancelSearch->moveToLeft(right - _cancelSearch->width(), _filter->y());
right -= _jumpToDate->width(); _jumpToDate->moveToLeft(right, _filter->y());
right -= _chooseFromUser->width(); _chooseFromUser->moveToLeft(right, _filter->y());
auto scrollTop = filterAreaTop + filterAreaHeight;
auto addToScroll = App::main() ? App::main()->contentScrollAddToY() : 0;

View File

@ -150,11 +150,13 @@ private:
void searchReceived(DialogsSearchRequestType type, const MTPmessages_Messages &result, mtpRequestId requestId);
void peerSearchReceived(const MTPcontacts_Found &result, mtpRequestId requestId);
void setSearchInPeer(PeerData *peer);
void setSearchInPeer(PeerData *peer, UserData *from = nullptr);
void showSearchFrom();
void showMainMenu();
void clearSearchCache();
void updateLockUnlockVisibility();
void updateJumpToDateVisibility(bool fast = false);
void updateSearchFromVisibility(bool fast = false);
void updateControlsGeometry();
void updateForwardBar();
@ -180,7 +182,7 @@ private:
object_ptr<Ui::IconButton> _forwardCancel = { nullptr };
object_ptr<Ui::IconButton> _mainMenuToggle;
object_ptr<Ui::FlatInput> _filter;
object_ptr<Ui::WidgetScaledFadeWrap<Ui::IconButton>> _searchFromUser;
object_ptr<Ui::WidgetScaledFadeWrap<Ui::IconButton>> _chooseFromUser;
object_ptr<Ui::WidgetScaledFadeWrap<Ui::IconButton>> _jumpToDate;
object_ptr<Ui::CrossButton> _cancelSearch;
object_ptr<Ui::IconButton> _lockUnlock;
@ -195,6 +197,7 @@ private:
PeerData *_searchInPeer = nullptr;
PeerData *_searchInMigrated = nullptr;
UserData *_searchFromUser = nullptr;
QTimer _searchTimer;
@ -203,6 +206,7 @@ private:
mtpRequestId _peerSearchRequest = 0;
QString _searchQuery;
UserData *_searchQueryFrom = nullptr;
bool _searchFull = false;
bool _searchFullMigrated = false;
mtpRequestId _searchRequest = 0;

View File

@ -65,6 +65,9 @@ public:
template <typename Callback>
static void HandleParticipant(const MTPChannelParticipant &participant, Role role, gsl::not_null<Additional*> additional, Callback callback);
protected:
virtual std::unique_ptr<PeerListRow> createRow(gsl::not_null<UserData*> user) const;
private:
static std::unique_ptr<PeerListSearchController> CreateSearchController(gsl::not_null<ChannelData*> channel, Role role, gsl::not_null<Additional*> additional);
@ -78,7 +81,6 @@ private:
bool appendRow(gsl::not_null<UserData*> user);
bool prependRow(gsl::not_null<UserData*> user);
bool removeRow(gsl::not_null<UserData*> user);
std::unique_ptr<PeerListRow> createRow(gsl::not_null<UserData*> user) const;
void refreshCustomStatus(gsl::not_null<PeerListRow*> row) const;
bool feedMegagroupLastParticipants();

View File

@ -34,89 +34,7 @@ constexpr int kWideScale = 3;
} // namespace
class MultiSelect::Inner::Item {
public:
Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage);
uint64 id() const {
return _id;
}
int getWidth() const {
return _width;
}
QRect rect() const {
return QRect(_x, _y, _width, _st.height);
}
bool isOverDelete() const {
return _overDelete;
}
void setActive(bool active) {
_active = active;
}
void setPosition(int x, int y, int outerWidth, int maxVisiblePadding);
QRect paintArea(int outerWidth) const;
void setUpdateCallback(base::lambda<void()> updateCallback) {
_updateCallback = updateCallback;
}
void setText(const QString &text);
void paint(Painter &p, int outerWidth, TimeMs ms);
void mouseMoveEvent(QPoint point);
void leaveEvent();
void showAnimated() {
setVisibleAnimated(true);
}
void hideAnimated() {
setVisibleAnimated(false);
}
bool hideFinished() const {
return (_hiding && !_visibility.animating());
}
private:
void setOver(bool over);
void paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms);
void paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity);
bool paintCached(Painter &p, int x, int y, int outerWidth);
void prepareCache();
void setVisibleAnimated(bool visible);
const style::MultiSelectItem &_st;
uint64 _id;
struct SlideAnimation {
SlideAnimation(base::lambda<void()> updateCallback, int fromX, int toX, int y, float64 duration)
: fromX(fromX)
, toX(toX)
, y(y) {
x.start(updateCallback, fromX, toX, duration);
}
Animation x;
int fromX, toX;
int y;
};
std::vector<SlideAnimation> _copies;
int _x = -1;
int _y = -1;
int _width = 0;
Text _text;
style::color _color;
bool _over = false;
QPixmap _cache;
Animation _visibility;
Animation _overOpacity;
bool _overDelete = false;
bool _active = false;
PaintRoundImage _paintRoundImage;
base::lambda<void()> _updateCallback;
bool _hiding = false;
};
MultiSelect::Inner::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage)
MultiSelect::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage)
: _st(st)
, _id(id)
, _color(color)
@ -124,13 +42,13 @@ MultiSelect::Inner::Item::Item(const style::MultiSelectItem &st, uint64 id, cons
setText(text);
}
void MultiSelect::Inner::Item::setText(const QString &text) {
void MultiSelect::Item::setText(const QString &text) {
_text.setText(_st.style, text, _textNameOptions);
_width = _st.height + _st.padding.left() + _text.maxWidth() + _st.padding.right();
accumulate_min(_width, _st.maxWidth);
}
void MultiSelect::Inner::Item::paint(Painter &p, int outerWidth, TimeMs ms) {
void MultiSelect::Item::paint(Painter &p, int outerWidth, TimeMs ms) {
if (!_cache.isNull() && !_visibility.animating(ms)) {
if (_hiding) {
return;
@ -158,7 +76,7 @@ void MultiSelect::Inner::Item::paint(Painter &p, int outerWidth, TimeMs ms) {
}
}
void MultiSelect::Inner::Item::paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms) {
void MultiSelect::Item::paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms) {
if (!_cache.isNull()) {
paintCached(p, x, y, outerWidth);
return;
@ -198,7 +116,7 @@ void MultiSelect::Inner::Item::paintOnce(Painter &p, int x, int y, int outerWidt
_text.drawLeftElided(p, x + textLeft, y + _st.padding.top(), textWidth, outerWidth);
}
void MultiSelect::Inner::Item::paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity) {
void MultiSelect::Item::paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity) {
p.setOpacity(overOpacity);
p.setPen(Qt::NoPen);
@ -213,7 +131,7 @@ void MultiSelect::Inner::Item::paintDeleteButton(Painter &p, int x, int y, int o
p.setOpacity(1.);
}
bool MultiSelect::Inner::Item::paintCached(Painter &p, int x, int y, int outerWidth) {
bool MultiSelect::Item::paintCached(Painter &p, int x, int y, int outerWidth) {
PainterHighQualityEnabler hq(p);
auto opacity = _visibility.current(_hiding ? 0. : 1.);
@ -227,18 +145,18 @@ bool MultiSelect::Inner::Item::paintCached(Painter &p, int x, int y, int outerWi
return true;
}
void MultiSelect::Inner::Item::mouseMoveEvent(QPoint point) {
void MultiSelect::Item::mouseMoveEvent(QPoint point) {
if (!_cache.isNull()) return;
_overDelete = QRect(0, 0, _st.height, _st.height).contains(point);
setOver(true);
}
void MultiSelect::Inner::Item::leaveEvent() {
void MultiSelect::Item::leaveEvent() {
_overDelete = false;
setOver(false);
}
void MultiSelect::Inner::Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) {
void MultiSelect::Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) {
if (_x >= 0 && _y >= 0 && (_x != x || _y != y)) {
// Make an animation if it is not the first setPosition().
auto found = false;
@ -277,7 +195,7 @@ void MultiSelect::Inner::Item::setPosition(int x, int y, int outerWidth, int max
_y = y;
}
QRect MultiSelect::Inner::Item::paintArea(int outerWidth) const {
QRect MultiSelect::Item::paintArea(int outerWidth) const {
if (_copies.empty()) {
return rect();
}
@ -293,7 +211,7 @@ QRect MultiSelect::Inner::Item::paintArea(int outerWidth) const {
return QRect(0, yMin, outerWidth, yMax - yMin + _st.height);
}
void MultiSelect::Inner::Item::prepareCache() {
void MultiSelect::Item::prepareCache() {
if (!_cache.isNull()) return;
t_assert(!_visibility.animating());
@ -309,7 +227,7 @@ void MultiSelect::Inner::Item::prepareCache() {
_cache = App::pixmapFromImageInPlace(std::move(data));
}
void MultiSelect::Inner::Item::setVisibleAnimated(bool visible) {
void MultiSelect::Item::setVisibleAnimated(bool visible) {
_hiding = !visible;
prepareCache();
auto from = visible ? 0. : 1.;
@ -318,7 +236,7 @@ void MultiSelect::Inner::Item::setVisibleAnimated(bool visible) {
_visibility.start(_updateCallback, from, to, _st.duration, transition);
}
void MultiSelect::Inner::Item::setOver(bool over) {
void MultiSelect::Item::setOver(bool over) {
if (over != _over) {
_over = over;
_overOpacity.start(_updateCallback, _over ? 0. : 1., _over ? 1. : 0., _st.duration);
@ -407,7 +325,7 @@ void MultiSelect::addItem(uint64 itemId, const QString &text, style::color color
}
void MultiSelect::addItemInBunch(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage) {
_inner->addItemInBunch(std::make_unique<Inner::Item>(_st.item, itemId, text, color, std::move(paintRoundImage)));
_inner->addItemInBunch(std::make_unique<Item>(_st.item, itemId, text, color, std::move(paintRoundImage)));
}
void MultiSelect::finishItemsBunch() {

View File

@ -57,6 +57,8 @@ public:
QVector<uint64> getItems() const;
bool hasItem(uint64 itemId) const;
class Item;
protected:
int resizeGetHeight(int newWidth) override;
bool eventFilter(QObject *o, QEvent *e) override;
@ -91,7 +93,6 @@ public:
void setQueryChangedCallback(base::lambda<void(const QString &query)> callback);
void setSubmittedCallback(base::lambda<void(bool ctrlShiftEnter)> callback);
class Item;
void addItemInBunch(std::unique_ptr<Item> item);
void finishItemsBunch(AddItemWay way);
void setItemText(uint64 itemId, const QString &text);
@ -176,4 +177,87 @@ private:
};
class MultiSelect::Item {
public:
Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage);
uint64 id() const {
return _id;
}
int getWidth() const {
return _width;
}
QRect rect() const {
return QRect(_x, _y, _width, _st.height);
}
bool isOverDelete() const {
return _overDelete;
}
void setActive(bool active) {
_active = active;
}
void setPosition(int x, int y, int outerWidth, int maxVisiblePadding);
QRect paintArea(int outerWidth) const;
void setUpdateCallback(base::lambda<void()> updateCallback) {
_updateCallback = updateCallback;
}
void setText(const QString &text);
void paint(Painter &p, int outerWidth, TimeMs ms);
void mouseMoveEvent(QPoint point);
void leaveEvent();
void showAnimated() {
setVisibleAnimated(true);
}
void hideAnimated() {
setVisibleAnimated(false);
}
bool hideFinished() const {
return (_hiding && !_visibility.animating());
}
private:
void setOver(bool over);
void paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms);
void paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity);
bool paintCached(Painter &p, int x, int y, int outerWidth);
void prepareCache();
void setVisibleAnimated(bool visible);
const style::MultiSelectItem &_st;
uint64 _id;
struct SlideAnimation {
SlideAnimation(base::lambda<void()> updateCallback, int fromX, int toX, int y, float64 duration)
: fromX(fromX)
, toX(toX)
, y(y) {
x.start(updateCallback, fromX, toX, duration);
}
Animation x;
int fromX, toX;
int y;
};
std::vector<SlideAnimation> _copies;
int _x = -1;
int _y = -1;
int _width = 0;
Text _text;
style::color _color;
bool _over = false;
QPixmap _cache;
Animation _visibility;
Animation _overOpacity;
bool _overDelete = false;
bool _active = false;
PaintRoundImage _paintRoundImage;
base::lambda<void()> _updateCallback;
bool _hiding = false;
};
} // namespace Ui

View File

@ -741,6 +741,26 @@ defaultIconButton: IconButton {
iconPosition: point(-1px, -1px);
}
defaultMultiSelectItem: MultiSelectItem {
padding: margins(6px, 7px, 12px, 0px);
maxWidth: 128px;
height: 32px;
style: defaultTextStyle;
textBg: contactsBgOver;
textFg: windowFg;
textActiveBg: activeButtonBg;
textActiveFg: activeButtonFg;
deleteFg: activeButtonFg;
deleteCross: CrossAnimation {
size: 32px;
skip: 10px;
stroke: 2px;
minScale: 0.3;
}
duration: 150;
minScale: 0.3;
}
widgetSlideDuration: 200;
widgetFadeDuration: 200;

View File

@ -144,6 +144,8 @@
<(src_loc)/dialogs/dialogs_list.h
<(src_loc)/dialogs/dialogs_row.cpp
<(src_loc)/dialogs/dialogs_row.h
<(src_loc)/dialogs/dialogs_search_from_controllers.cpp
<(src_loc)/dialogs/dialogs_search_from_controllers.h
<(src_loc)/dialogs/dialogs_widget.cpp
<(src_loc)/dialogs/dialogs_widget.h
<(src_loc)/history/history.cpp