From 8bc4e3816be27db27c099cd58445d9d6bed5317a Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Sat, 22 Oct 2016 16:03:20 +0300
Subject: [PATCH] ShareBox and CountrySelect use Ui::MultiSelect as a search
 field.

---
 Telegram/SourceFiles/boxes/boxes.style        |  65 +++++----
 Telegram/SourceFiles/boxes/contactsbox.cpp    | 131 ++++++++++++------
 Telegram/SourceFiles/boxes/contactsbox.h      |  10 +-
 Telegram/SourceFiles/boxes/sharebox.cpp       | 131 ++++++++++++++----
 Telegram/SourceFiles/boxes/sharebox.h         |  27 ++--
 Telegram/SourceFiles/core/lambda_wrap.h       |   5 +-
 Telegram/SourceFiles/ui/countryinput.cpp      |  52 +++----
 Telegram/SourceFiles/ui/countryinput.h        |  10 +-
 Telegram/SourceFiles/ui/flatinput.cpp         |  11 +-
 Telegram/SourceFiles/ui/flatinput.h           |   1 +
 .../SourceFiles/ui/widgets/multi_select.cpp   |  38 +++--
 .../SourceFiles/ui/widgets/multi_select.h     |  10 +-
 .../ui/widgets/widget_slide_wrap.h            |   2 +-
 Telegram/SourceFiles/ui/widgets/widgets.style |   2 +-
 14 files changed, 317 insertions(+), 178 deletions(-)

diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 47d4a5737..2ce8107c1 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -68,37 +68,6 @@ aboutRevokePublicLabel: flatLabel(labelDefFlat) {
 	textFg: windowTextFg;
 }
 
-boxSearchField: InputField(defaultInputField) {
-	textBg: transparent;
-	textMargins: margins(2px, 7px, 2px, 0px);
-
-	placeholderFg: #999;
-	placeholderFgActive: #aaa;
-	placeholderMargins: margins(2px, 0px, 2px, 0px);
-	duration: 150;
-
-	border: 0px;
-	borderActive: 0px;
-	borderError: 0px;
-
-	height: 32px;
-
-	font: normalFont;
-}
-boxSearchCancel: IconButton {
-	width: 41px;
-	height: 48px;
-
-	opacity: 0.3;
-	overOpacity: 0.4;
-
-	icon: icon {{ "box_search_cancel", #000000 }};
-	iconPosition: point(8px, 18px);
-	downIconPosition: point(8px, 19px);
-
-	duration: 150;
-}
-
 contactsMultiSelect: MultiSelect {
 	padding: margins(8px, 8px, 8px, 8px);
 	maxHeight: 104px;
@@ -128,12 +97,40 @@ contactsMultiSelect: MultiSelect {
 	}
 	itemSkip: 8px;
 
-	field: boxSearchField;
+	field: InputField(defaultInputField) {
+		textBg: transparent;
+		textMargins: margins(2px, 7px, 2px, 0px);
+
+		placeholderFg: #999;
+		placeholderFgActive: #aaa;
+		placeholderMargins: margins(2px, 0px, 2px, 0px);
+
+		border: 0px;
+		borderActive: 0px;
+		borderError: 0px;
+
+		height: 32px;
+
+		font: normalFont;
+	}
+	fieldMinWidth: 42px;
 	fieldIcon: icon {{ "box_search_icon", #aaaaaa, point(11px, 9px) }};
 	fieldIconSkip: 36px;
-	fieldCancel: boxSearchCancel;
+
+	fieldCancel: IconButton {
+		width: 41px;
+		height: 48px;
+
+		opacity: 0.3;
+		overOpacity: 0.4;
+
+		icon: icon {{ "box_search_cancel", #000000 }};
+		iconPosition: point(8px, 18px);
+		downIconPosition: point(8px, 19px);
+
+		duration: 150;
+	}
 	fieldCancelSkip: 34px;
-	fieldMinWidth: 42px;
 }
 contactsPhotoCheckbox: RoundImageCheckbox {
 	imageRadius: 21px;
diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp
index cbf186b8f..090bbf80c 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.cpp
+++ b/Telegram/SourceFiles/boxes/contactsbox.cpp
@@ -32,6 +32,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "application.h"
 #include "ui/filedialog.h"
 #include "ui/widgets/multi_select.h"
+#include "ui/widgets/widget_slide_wrap.h"
 #include "boxes/photocropbox.h"
 #include "boxes/confirmbox.h"
 #include "observer_peer.h"
@@ -43,7 +44,7 @@ QString cantInviteError() {
 
 ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll)
 , _inner(this, CreatingGroupNone)
-, _select(this, st::contactsMultiSelect, lang(lng_participant_filter))
+, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); })
 , _next(this, lang(lng_create_group_next), st::defaultBoxButton)
 , _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 , _topShadow(this) {
@@ -52,7 +53,7 @@ ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll)
 
 ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox(st::boxScroll)
 , _inner(this, CreatingGroupGroup)
-, _select(this, st::contactsMultiSelect, lang(lng_participant_filter))
+, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); })
 , _next(this, lang(lng_create_group_create), st::defaultBoxButton)
 , _cancel(this, lang(lng_create_group_back), st::cancelBoxButton)
 , _topShadow(this)
@@ -63,7 +64,7 @@ ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox
 
 ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll)
 , _inner(this, channel, MembersFilter::Recent, MembersAlreadyIn())
-, _select(this, st::contactsMultiSelect, lang(lng_participant_filter))
+, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); })
 , _next(this, lang(lng_participant_invite), st::defaultBoxButton)
 , _cancel(this, lang(lng_create_group_skip), st::cancelBoxButton)
 , _topShadow(this) {
@@ -72,7 +73,7 @@ ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll)
 
 ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already) : ItemListBox((filter == MembersFilter::Admins) ? st::contactsScroll : st::boxScroll)
 , _inner(this, channel, filter, already)
-, _select(this, st::contactsMultiSelect, lang(lng_participant_filter))
+, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); })
 , _next(this, lang(lng_participant_invite), st::defaultBoxButton)
 , _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 , _topShadow(this) {
@@ -81,7 +82,7 @@ ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const Membe
 
 ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st::boxScroll)
 , _inner(this, chat, filter)
-, _select(this, st::contactsMultiSelect, lang(lng_participant_filter))
+, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); })
 , _next(this, lang((filter == MembersFilter::Admins) ? lng_settings_save : lng_participant_invite), st::defaultBoxButton)
 , _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 , _topShadow(this) {
@@ -90,7 +91,7 @@ ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st:
 
 ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll)
 , _inner(this, bot)
-, _select(this, st::contactsMultiSelect, lang(lng_participant_filter))
+, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); })
 , _next(this, lang(lng_create_group_next), st::defaultBoxButton)
 , _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 , _topShadow(this) {
@@ -101,7 +102,7 @@ void ContactsBox::init() {
 	_select->resizeToWidth(st::boxWideWidth);
 
 	auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat();
-	auto topSkip = st::boxTitleHeight + _select->height();
+	auto topSkip = getTopScrollSkip();
 	auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip;
 	ItemListBox::init(_inner, bottomSkip, topSkip);
 
@@ -109,6 +110,20 @@ void ContactsBox::init() {
 	_inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) {
 		onPeerSelectedChanged(peer, checked);
 	});
+	for (auto i : _inner->selected()) {
+		addPeerToMultiSelect(i, true);
+	}
+	_inner->setAllAdminsChangedCallback([this] {
+		if (_inner->allAdmins()) {
+			_select->entity()->clearQuery();
+			_select->slideUp();
+			_inner->setFocus();
+		} else {
+			_select->slideDown();
+			_select->entity()->setInnerFocus();
+		}
+		updateScrollSkips();
+	});
 
 	if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) {
 		_next.hide();
@@ -128,15 +143,14 @@ void ContactsBox::init() {
 	}
 	connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
 	connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
-	_select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); });
-	_select->setItemRemovedCallback([this](uint64 itemId) {
+	_select->entity()->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); });
+	_select->entity()->setItemRemovedCallback([this](uint64 itemId) {
 		if (auto peer = App::peerLoaded(itemId)) {
 			_inner->peerUnselected(peer);
 			update();
 		}
 	});
-	_select->setSubmittedCallback([this](bool) { onSubmit(); });
-	_select->setResizedCallback([this] { updateScrollSkips(); });
+	_select->entity()->setSubmittedCallback([this](bool) { onSubmit(); });
 	connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int)));
 	connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername()));
 	connect(_inner, SIGNAL(adminAdded()), this, SIGNAL(adminAdded()));
@@ -148,7 +162,7 @@ void ContactsBox::init() {
 }
 
 bool ContactsBox::onSearchByUsername(bool searchCache) {
-	auto q = _select->getQuery();
+	auto q = _select->entity()->getQuery();
 	if (q.isEmpty()) {
 		if (_peopleRequest) {
 			_peopleRequest = 0;
@@ -216,7 +230,11 @@ bool ContactsBox::peopleFailed(const RPCError &error, mtpRequestId req) {
 }
 
 void ContactsBox::showAll() {
-	_select->show();
+	if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins && _inner->allAdmins()) {
+		_select->hideFast();
+	} else {
+		_select->showFast();
+	}
 	if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) {
 		_next.hide();
 		_cancel.hide();
@@ -236,7 +254,11 @@ void ContactsBox::showAll() {
 }
 
 void ContactsBox::doSetInnerFocus() {
-	_select->setInnerFocus();
+	if (_select->isHidden()) {
+		_inner->setFocus();
+	} else {
+		_select->entity()->setInnerFocus();
+	}
 }
 
 void ContactsBox::onSubmit() {
@@ -282,10 +304,18 @@ void ContactsBox::paintEvent(QPaintEvent *e) {
 	}
 }
 
+int ContactsBox::getTopScrollSkip() const {
+	auto result = st::boxTitleHeight;
+	if (!_select->isHidden()) {
+		result += _select->height();
+	}
+	return result;
+}
+
 void ContactsBox::updateScrollSkips() {
 	auto oldScrollHeight = scrollArea()->height();
 	auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat();
-	auto topSkip = st::boxTitleHeight + _select->height();
+	auto topSkip = getTopScrollSkip();
 	auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip;
 	setScrollSkips(bottomSkip, topSkip);
 	auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight;
@@ -293,7 +323,7 @@ void ContactsBox::updateScrollSkips() {
 		scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta);
 	}
 
-	_topShadow.setGeometry(0, st::boxTitleHeight + _select->height(), width(), st::lineWidth);
+	_topShadow.setGeometry(0, topSkip, width(), st::lineWidth);
 }
 
 void ContactsBox::resizeEvent(QResizeEvent *e) {
@@ -321,24 +351,30 @@ void ContactsBox::onFilterUpdate(const QString &filter) {
 	_inner->updateFilter(filter);
 }
 
+void ContactsBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) {
+	auto getColor = [peer]() -> const style::color & {
+		switch (peer->colorIndex) {
+		case 1: return st::historyPeer2UserpicFg;
+		case 2: return st::historyPeer3UserpicFg;
+		case 3: return st::historyPeer4UserpicFg;
+		case 4: return st::historyPeer5UserpicFg;
+		case 5: return st::historyPeer6UserpicFg;
+		case 6: return st::historyPeer7UserpicFg;
+		case 7: return st::historyPeer8UserpicFg;
+		default: return st::historyPeer1UserpicFg;
+		}
+	};
+	using AddItemWay = Ui::MultiSelect::AddItemWay;
+	auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default;
+	_select->entity()->addItem(peer->id, peer->shortName(), getColor(), PaintUserpicCallback(peer), addItemWay);
+}
+
 void ContactsBox::onPeerSelectedChanged(PeerData *peer, bool checked) {
 	if (checked) {
-		auto getColor = [peer]() -> const style::color &{
-			switch (peer->colorIndex) {
-			case 1: return st::historyPeer2UserpicFg;
-			case 2: return st::historyPeer3UserpicFg;
-			case 3: return st::historyPeer4UserpicFg;
-			case 4: return st::historyPeer5UserpicFg;
-			case 5: return st::historyPeer6UserpicFg;
-			case 6: return st::historyPeer7UserpicFg;
-			case 7: return st::historyPeer8UserpicFg;
-			default: return st::historyPeer1UserpicFg;
-			}
-		};
-		_select->addItem(peer->id, peer->shortName(), getColor(), PaintUserpicCallback(peer));
-		_select->clearQuery();
+		addPeerToMultiSelect(peer);
+		_select->entity()->clearQuery();
 	} else {
-		_select->removeItem(peer->id);
+		_select->entity()->removeItem(peer->id);
 	}
 	update();
 }
@@ -346,7 +382,7 @@ void ContactsBox::onPeerSelectedChanged(PeerData *peer, bool checked) {
 void ContactsBox::onInvite() {
 	QVector<UserData*> users(_inner->selected());
 	if (users.isEmpty()) {
-		_select->setInnerFocus();
+		_select->entity()->setInnerFocus();
 		return;
 	}
 
@@ -364,7 +400,7 @@ void ContactsBox::onCreate() {
 
 	auto users = _inner->selectedInputs();
 	if (users.isEmpty() || (users.size() == 1 && users.at(0).type() == mtpc_inputUserSelf)) {
-		_select->setInnerFocus();
+		_select->entity()->setInnerFocus();
 		return;
 	}
 	_saveRequestId = MTP::send(MTPmessages_CreateChat(MTP_vector<MTPInputUser>(users), MTP_string(_creationName)), rpcDone(&ContactsBox::creationDone), rpcFail(&ContactsBox::creationFail));
@@ -514,7 +550,7 @@ bool ContactsBox::creationFail(const RPCError &error) {
 		onClose();
 		return true;
 	} else if (error.type() == "USERS_TOO_FEW") {
-		_select->setInnerFocus();
+		_select->entity()->setInnerFocus();
 		return true;
 	} else if (error.type() == "PEER_FLOOD") {
 		Ui::showLayer(new InformBox(cantInviteError()), KeepOtherLayers);
@@ -641,7 +677,7 @@ void ContactsBox::Inner::init() {
 }
 
 void ContactsBox::Inner::initList() {
-	if (!usingMultiSelect()) return;
+	if (!_chat || _membersFilter != MembersFilter::Admins) return;
 
 	QList<UserData*> admins, others;
 	admins.reserve(_chat->admins.size() + 1);
@@ -653,7 +689,9 @@ void ContactsBox::Inner::initList() {
 		if (i.key()->id == peerFromUser(_chat->creator)) continue;
 		if (!_allAdmins.checked() && _chat->admins.contains(i.key())) {
 			admins.push_back(i.key());
-			_checkedContacts.insert(i.key());
+			if (!_checkedContacts.contains(i.key())) {
+				_checkedContacts.insert(i.key());
+			}
 		} else {
 			others.push_back(i.key());
 		}
@@ -716,10 +754,10 @@ void ContactsBox::Inner::onNoAddAdminBox(QObject *obj) {
 }
 
 void ContactsBox::Inner::onAllAdminsChanged() {
-	if (_saving) {
-		if (_allAdmins.checked() != _allAdminsChecked) {
-			_allAdmins.setChecked(_allAdminsChecked);
-		}
+	if (_saving && _allAdmins.checked() != _allAdminsChecked) {
+		_allAdmins.setChecked(_allAdminsChecked);
+	} else if (_allAdminsChangedCallback) {
+		_allAdminsChangedCallback();
 	}
 	update();
 }
@@ -1355,7 +1393,8 @@ void ContactsBox::Inner::changeCheckState(Dialogs::Row *row) {
 void ContactsBox::Inner::changeCheckState(ContactData *data, PeerData *peer) {
 	t_assert(usingMultiSelect());
 
-	if (data->checkbox->checked()) {
+	if (_chat && _membersFilter == MembersFilter::Admins && _allAdmins.checked()) {
+	} else if (data->checkbox->checked()) {
 		changePeerCheckState(data, peer, false);
 	} else if (selectedCount() < ((_channel && _channel->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) {
 		changePeerCheckState(data, peer, true);
@@ -1385,7 +1424,7 @@ void ContactsBox::Inner::changePeerCheckState(ContactData *data, PeerData *peer,
 	} else {
 		_checkedContacts.remove(peer);
 	}
-	if (useCallback != ChangeStateWay::SkipCallback) {
+	if (useCallback != ChangeStateWay::SkipCallback && _peerSelectedChangedCallback) {
 		_peerSelectedChangedCallback(peer, checked);
 	}
 }
@@ -1874,12 +1913,12 @@ QVector<UserData*> ContactsBox::Inner::selected() {
 		}
 	}
 	result.reserve(_contactsData.size());
-	for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
+	for (auto i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
 		if (i.value()->checkbox->checked() && i.key()->isUser()) {
 			result.push_back(i.key()->asUser());
 		}
 	}
-	for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
+	for (int i = 0, l = _byUsername.size(); i < l; ++i) {
 		if (d_byUsername[i]->checkbox->checked() && _byUsername[i]->isUser()) {
 			result.push_back(_byUsername[i]->asUser());
 		}
@@ -1899,12 +1938,12 @@ QVector<MTPInputUser> ContactsBox::Inner::selectedInputs() {
 		}
 	}
 	result.reserve(_contactsData.size());
-	for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
+	for (auto i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
 		if (i.value()->checkbox->checked() && i.key()->isUser()) {
 			result.push_back(i.key()->asUser()->inputUser);
 		}
 	}
-	for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
+	for (int i = 0, l = _byUsername.size(); i < l; ++i) {
 		if (d_byUsername[i]->checkbox->checked() && _byUsername[i]->isUser()) {
 			result.push_back(_byUsername[i]->asUser()->inputUser);
 		}
diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h
index 9685bdd20..b5dbdee5c 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.h
+++ b/Telegram/SourceFiles/boxes/contactsbox.h
@@ -32,6 +32,8 @@ class IndexedList;
 
 namespace Ui {
 class MultiSelect;
+template <typename Widget>
+class WidgetSlideWrap;
 } // namespace Ui
 
 QString cantInviteError();
@@ -79,13 +81,15 @@ protected:
 
 private:
 	void init();
+	int getTopScrollSkip() const;
 	void updateScrollSkips();
 	void onFilterUpdate(const QString &filter);
 	void onPeerSelectedChanged(PeerData *peer, bool checked);
+	void addPeerToMultiSelect(PeerData *peer, bool skipAnimation = false);
 
 	class Inner;
 	ChildWidget<Inner> _inner;
-	ChildWidget<Ui::MultiSelect> _select;
+	ChildWidget<Ui::WidgetSlideWrap<Ui::MultiSelect>> _select;
 
 	BoxButton _next, _cancel;
 	MembersFilter _membersFilter;
@@ -151,6 +155,9 @@ public:
 	bool allAdmins() const {
 		return _allAdmins.checked();
 	}
+	void setAllAdminsChangedCallback(base::lambda_unique<void()> allAdminsChangedCallback) {
+		_allAdminsChangedCallback = std_::move(allAdminsChangedCallback);
+	}
 
 	void loadProfilePhotos(int32 yFrom);
 	void chooseParticipant();
@@ -259,6 +266,7 @@ private:
 	Checkbox _allAdmins;
 	int32 _aboutWidth;
 	Text _aboutAllAdmins, _aboutAdmins;
+	base::lambda_unique<void()> _allAdminsChangedCallback;
 
 	PeerData *_addToPeer = nullptr;
 	UserData *_addAdmin = nullptr;
diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp
index ca2323138..76c99e7a4 100644
--- a/Telegram/SourceFiles/boxes/sharebox.cpp
+++ b/Telegram/SourceFiles/boxes/sharebox.cpp
@@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 
 #include "dialogs/dialogs_indexed_list.h"
 #include "styles/style_boxes.h"
+#include "styles/style_history.h"
 #include "observer_peer.h"
 #include "lang.h"
 #include "mainwindow.h"
@@ -32,7 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "boxes/confirmbox.h"
 #include "apiwrap.h"
 #include "ui/toast/toast.h"
-#include "ui/buttons/icon_button.h"
+#include "ui/widgets/multi_select.h"
 #include "history/history_media_types.h"
 #include "boxes/contactsbox.h"
 
@@ -40,30 +41,37 @@ ShareBox::ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback,
 , _copyCallback(std_::move(copyCallback))
 , _submitCallback(std_::move(submitCallback))
 , _inner(this, std_::move(filterCallback))
-, _filter(this, st::boxSearchField, lang(lng_participant_filter))
-, _filterCancel(this, st::boxSearchCancel)
+, _select(this, st::contactsMultiSelect, lang(lng_participant_filter))
 , _copy(this, lang(lng_share_copy_link), st::defaultBoxButton)
 , _share(this, lang(lng_share_confirm), st::defaultBoxButton)
 , _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 , _topShadow(this)
 , _bottomShadow(this) {
-	int topSkip = st::boxTitleHeight + _filter->height();
-	int bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom();
+	_select->resizeToWidth(st::boxWideWidth);
+
+	auto topSkip = getTopScrollSkip();
+	auto bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom();
 	init(_inner, bottomSkip, topSkip);
 
-	connect(_inner, SIGNAL(selectedChanged()), this, SLOT(onSelectedChanged()));
 	connect(_inner, SIGNAL(mustScrollTo(int,int)), this, SLOT(onMustScrollTo(int,int)));
 	connect(_copy, SIGNAL(clicked()), this, SLOT(onCopyLink()));
 	connect(_share, SIGNAL(clicked()), this, SLOT(onSubmit()));
 	connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
 	connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
-	connect(_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate()));
-	connect(_filter, SIGNAL(submitted(bool)), _inner, SLOT(onSelectActive()));
-	connect(_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel()));
-	connect(_inner, SIGNAL(filterCancel()), this, SLOT(onFilterCancel()));
+	_select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); });
+	_select->setItemRemovedCallback([this](uint64 itemId) {
+		if (auto peer = App::peerLoaded(itemId)) {
+			_inner->peerUnselected(peer);
+			onSelectedChanged();
+			update();
+		}
+	});
+	_select->setResizedCallback([this] { updateScrollSkips(); });
+	_select->setSubmittedCallback([this](bool) { _inner->onSelectActive(); });
 	connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername()));
-
-	_filterCancel->setAttribute(Qt::WA_OpaquePaintEvent);
+	_inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) {
+		onPeerSelectedChanged(peer, checked);
+	});
 
 	_searchTimer.setSingleShot(true);
 	connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername()));
@@ -73,8 +81,29 @@ ShareBox::ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback,
 	prepare();
 }
 
+int ShareBox::getTopScrollSkip() const {
+	auto result = st::boxTitleHeight;
+	if (!_select->isHidden()) {
+		result += _select->height();
+	}
+	return result;
+}
+
+void ShareBox::updateScrollSkips() {
+	auto oldScrollHeight = scrollArea()->height();
+	auto topSkip = getTopScrollSkip();
+	auto bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom();
+	setScrollSkips(bottomSkip, topSkip);
+	auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight;
+	if (scrollHeightDelta) {
+		scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta);
+	}
+
+	_topShadow->setGeometry(0, topSkip, width(), st::lineWidth);
+}
+
 bool ShareBox::onSearchByUsername(bool searchCache) {
-	auto query = _filter->getLastText().trimmed();
+	auto query = _select->getQuery();
 	if (query.isEmpty()) {
 		if (_peopleRequest) {
 			_peopleRequest = 0;
@@ -142,7 +171,7 @@ bool ShareBox::peopleFailed(const RPCError &error, mtpRequestId requestId) {
 }
 
 void ShareBox::doSetInnerFocus() {
-	_filter->setFocus();
+	_select->setInnerFocus();
 }
 
 void ShareBox::paintEvent(QPaintEvent *e) {
@@ -154,17 +183,21 @@ void ShareBox::paintEvent(QPaintEvent *e) {
 
 void ShareBox::resizeEvent(QResizeEvent *e) {
 	ItemListBox::resizeEvent(e);
-	_filter->resize(width(), _filter->height());
-	_filter->moveToLeft(0, st::boxTitleHeight);
-	_filterCancel->moveToRight(0, st::boxTitleHeight);
+
+	_select->resizeToWidth(width());
+	_select->moveToLeft(0, st::boxTitleHeight);
+
+	updateScrollSkips();
+
 	_inner->resizeToWidth(width());
 	moveButtons();
-	_topShadow->setGeometry(0, st::boxTitleHeight + _filter->height(), width(), st::lineWidth);
+	_topShadow->setGeometry(0, getTopScrollSkip(), width(), st::lineWidth);
 	_bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _share->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth);
 }
 
 void ShareBox::keyPressEvent(QKeyEvent *e) {
-	if (_filter->hasFocus()) {
+	auto focused = focusWidget();
+	if (_select == focused || _select->isAncestorOf(focusWidget())) {
 		if (e->key() == Qt::Key_Up) {
 			_inner->activateSkipColumn(-1);
 		} else if (e->key() == Qt::Key_Down) {
@@ -194,13 +227,38 @@ void ShareBox::updateButtonsVisibility() {
 	_cancel->setVisible(hasSelected);
 }
 
-void ShareBox::onFilterCancel() {
-	_filter->setText(QString());
+void ShareBox::onFilterUpdate(const QString &query) {
+	scrollArea()->scrollToY(0);
+	_inner->updateFilter(query);
 }
 
-void ShareBox::onFilterUpdate() {
-	_filterCancel->setVisible(!_filter->getLastText().isEmpty());
-	_inner->updateFilter(_filter->getLastText());
+void ShareBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) {
+	auto getColor = [peer]() -> const style::color & {
+		switch (peer->colorIndex) {
+		case 1: return st::historyPeer2UserpicFg;
+		case 2: return st::historyPeer3UserpicFg;
+		case 3: return st::historyPeer4UserpicFg;
+		case 4: return st::historyPeer5UserpicFg;
+		case 5: return st::historyPeer6UserpicFg;
+		case 6: return st::historyPeer7UserpicFg;
+		case 7: return st::historyPeer8UserpicFg;
+		default: return st::historyPeer1UserpicFg;
+		}
+	};
+	using AddItemWay = Ui::MultiSelect::AddItemWay;
+	auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default;
+	_select->addItem(peer->id, peer->shortName(), getColor(), PaintUserpicCallback(peer), addItemWay);
+}
+
+void ShareBox::onPeerSelectedChanged(PeerData *peer, bool checked) {
+	if (checked) {
+		addPeerToMultiSelect(peer);
+		_select->clearQuery();
+	} else {
+		_select->removeItem(peer->id);
+	}
+	onSelectedChanged();
+	update();
 }
 
 void ShareBox::onSubmit() {
@@ -597,17 +655,34 @@ void ShareBox::Inner::changeCheckState(Chat *chat) {
 		if (!chat->checkbox.checked()) {
 			_chatsIndexed->moveToTop(chat->peer);
 		}
-		emit filterCancel();
 	}
 
-	chat->checkbox.setChecked(!chat->checkbox.checked());
-	if (chat->checkbox.checked()) {
+	changePeerCheckState(chat, !chat->checkbox.checked());
+}
+
+void ShareBox::Inner::peerUnselected(PeerData *peer) {
+	// If data is nullptr we simply won't do anything.
+	auto chat = _dataMap.value(peer, nullptr);
+	changePeerCheckState(chat, false, ChangeStateWay::SkipCallback);
+}
+
+void ShareBox::Inner::setPeerSelectedChangedCallback(base::lambda_unique<void(PeerData *peer, bool selected)> callback) {
+	_peerSelectedChangedCallback = std_::move(callback);
+}
+
+void ShareBox::Inner::changePeerCheckState(Chat *chat, bool checked, ChangeStateWay useCallback) {
+	if (chat) {
+		chat->checkbox.setChecked(checked);
+	}
+	if (checked) {
 		_selected.insert(chat->peer);
 		setActive(chatIndex(chat->peer));
 	} else {
 		_selected.remove(chat->peer);
 	}
-	emit selectedChanged();
+	if (useCallback != ChangeStateWay::SkipCallback && _peerSelectedChangedCallback) {
+		_peerSelectedChangedCallback(chat->peer, checked);
+	}
 }
 
 bool ShareBox::Inner::hasSelected() const {
diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h
index 96da0fb1c..7629d608e 100644
--- a/Telegram/SourceFiles/boxes/sharebox.h
+++ b/Telegram/SourceFiles/boxes/sharebox.h
@@ -36,7 +36,7 @@ struct PeerUpdate;
 } // namespace Notify
 
 namespace Ui {
-class IconButton;
+class MultiSelect;
 } // namespace Ui
 
 QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId);
@@ -52,8 +52,6 @@ public:
 	ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback);
 
 private slots:
-	void onFilterUpdate();
-	void onFilterCancel();
 	void onScroll();
 
 	bool onSearchByUsername(bool searchCache = false);
@@ -61,7 +59,6 @@ private slots:
 
 	void onSubmit();
 	void onCopyLink();
-	void onSelectedChanged();
 
 	void onMustScrollTo(int top, int bottom);
 
@@ -73,8 +70,15 @@ protected:
 	void doSetInnerFocus() override;
 
 private:
+	void onFilterUpdate(const QString &query);
+	void onSelectedChanged();
 	void moveButtons();
 	void updateButtonsVisibility();
+	int getTopScrollSkip() const;
+	void updateScrollSkips();
+
+	void addPeerToMultiSelect(PeerData *peer, bool skipAnimation = false);
+	void onPeerSelectedChanged(PeerData *peer, bool checked);
 
 	void peopleReceived(const MTPcontacts_Found &result, mtpRequestId requestId);
 	bool peopleFailed(const RPCError &error, mtpRequestId requestId);
@@ -84,8 +88,7 @@ private:
 
 	class Inner;
 	ChildWidget<Inner> _inner;
-	ChildWidget<InputField> _filter;
-	ChildWidget<Ui::IconButton> _filterCancel;
+	ChildWidget<Ui::MultiSelect> _select;
 
 	ChildWidget<BoxButton> _copy;
 	ChildWidget<BoxButton> _share;
@@ -116,6 +119,9 @@ class ShareBox::Inner : public ScrolledWidget, public RPCSender, private base::S
 public:
 	Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback);
 
+	void setPeerSelectedChangedCallback(base::lambda_unique<void(PeerData *peer, bool selected)> callback);
+	void peerUnselected(PeerData *peer);
+
 	QVector<PeerData*> selected() const;
 	bool hasSelected() const;
 
@@ -134,9 +140,7 @@ public slots:
 
 signals:
 	void mustScrollTo(int ymin, int ymax);
-	void filterCancel();
 	void searchByUsername();
-	void selectedChanged();
 
 protected:
 	void paintEvent(QPaintEvent *e) override;
@@ -170,6 +174,11 @@ private:
 
 	void loadProfilePhotos(int yFrom);
 	void changeCheckState(Chat *chat);
+	enum class ChangeStateWay {
+		Default,
+		SkipCallback,
+	};
+	void changePeerCheckState(Chat *chat, bool checked, ChangeStateWay useCallback = ChangeStateWay::Default);
 
 	Chat *getChat(Dialogs::Row *row);
 	void setActive(int active);
@@ -198,6 +207,8 @@ private:
 	using SelectedChats = OrderedSet<PeerData*>;
 	SelectedChats _selected;
 
+	base::lambda_unique<void(PeerData *peer, bool selected)> _peerSelectedChangedCallback;
+
 	ChatData *data(Dialogs::Row *row);
 
 	bool _searching = false;
diff --git a/Telegram/SourceFiles/core/lambda_wrap.h b/Telegram/SourceFiles/core/lambda_wrap.h
index f75ceb5ef..3e211781d 100644
--- a/Telegram/SourceFiles/core/lambda_wrap.h
+++ b/Telegram/SourceFiles/core/lambda_wrap.h
@@ -60,7 +60,7 @@ struct lambda_wrap_helper_base {
 
 protected:
 	static void bad_construct_copy(void *lambda, const void *source) {
-		throw std::exception();
+		t_assert(!"base::lambda bad_construct_copy() called!");
 	}
 
 };
@@ -72,7 +72,8 @@ struct lambda_wrap_empty : public lambda_wrap_helper_base<Return, Args...> {
 	static void construct_move_other_method(void *lambda, void *source) {
 	}
 	static Return call_method(const void *lambda, Args... args) {
-		throw std::exception();
+		t_assert(!"base::lambda empty call_method() called!");
+		return Return();
 	}
 	static void destruct_method(const void *lambda) {
 	}
diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp
index a13c1712b..c4a390e9c 100644
--- a/Telegram/SourceFiles/ui/countryinput.cpp
+++ b/Telegram/SourceFiles/ui/countryinput.cpp
@@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "lang.h"
 #include "application.h"
 #include "ui/scrollarea.h"
-#include "ui/buttons/icon_button.h"
+#include "ui/widgets/multi_select.h"
 #include "boxes/contactsbox.h"
 #include "countries.h"
 #include "styles/style_boxes.h"
@@ -65,7 +65,8 @@ namespace {
 		countriesFiltered.reserve(countriesCount);
 		countriesNames.resize(countriesCount);
 	}
-}
+
+} // namespace
 
 const CountriesByCode &countriesByCode() {
 	initCountries();
@@ -196,19 +197,17 @@ void CountryInput::setText(const QString &newText) {
 
 CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxWidth)
 , _inner(this)
-, _filter(this, st::boxSearchField, lang(lng_country_ph))
-, _filterCancel(this, st::boxSearchCancel)
+, _select(this, st::contactsMultiSelect, lang(lng_country_ph))
 , _topShadow(this) {
-	ItemListBox::init(_inner, st::boxScrollSkip, st::boxTitleHeight + _filter->height());
+	_select->resizeToWidth(st::boxWidth);
 
-	connect(_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate()));
-	connect(_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
-	connect(_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel()));
+	ItemListBox::init(_inner, st::boxScrollSkip, st::boxTitleHeight + _select->height());
+
+	_select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); });
+	_select->setSubmittedCallback([this](bool) { onSubmit(); });
 	connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int)));
 	connect(_inner, SIGNAL(countryChosen(const QString&)), this, SIGNAL(countryChosen(const QString&)));
 
-	_filterCancel->setAttribute(Qt::WA_OpaquePaintEvent);
-
 	prepare();
 }
 
@@ -239,40 +238,27 @@ void CountrySelectBox::paintEvent(QPaintEvent *e) {
 
 void CountrySelectBox::resizeEvent(QResizeEvent *e) {
 	ItemListBox::resizeEvent(e);
-	_filter->resize(width(), _filter->height());
-	_filter->moveToLeft(0, st::boxTitleHeight);
-	_filterCancel->moveToRight(0, st::boxTitleHeight);
-	_inner->resize(width(), _inner->height());
-	_topShadow.setGeometry(0, st::boxTitleHeight + _filter->height(), width(), st::lineWidth);
+
+	_select->resizeToWidth(width());
+	_select->moveToLeft(0, st::boxTitleHeight);
+
+	_inner->resizeToWidth(width());
+	_topShadow.setGeometry(0, st::boxTitleHeight + _select->height(), width(), st::lineWidth);
 }
 
 void CountrySelectBox::showAll() {
-	_filter->show();
-	if (_filter->getLastText().isEmpty()) {
-		_filterCancel->hide();
-	} else {
-		_filterCancel->show();
-	}
+	_select->show();
 	_topShadow.show();
 	ItemListBox::showAll();
 }
 
-void CountrySelectBox::onFilterCancel() {
-	_filter->setText(QString());
-}
-
-void CountrySelectBox::onFilterUpdate() {
+void CountrySelectBox::onFilterUpdate(const QString &query) {
 	scrollArea()->scrollToY(0);
-	if (_filter->getLastText().isEmpty()) {
-		_filterCancel->hide();
-	} else {
-		_filterCancel->show();
-	}
-	_inner->updateFilter(_filter->getLastText());
+	_inner->updateFilter(query);
 }
 
 void CountrySelectBox::doSetInnerFocus() {
-	_filter->setFocus();
+	_select->setInnerFocus();
 }
 
 CountrySelectBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent)
diff --git a/Telegram/SourceFiles/ui/countryinput.h b/Telegram/SourceFiles/ui/countryinput.h
index 971b192cb..fe3b217a9 100644
--- a/Telegram/SourceFiles/ui/countryinput.h
+++ b/Telegram/SourceFiles/ui/countryinput.h
@@ -29,10 +29,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 QString findValidCode(QString fullCode);
 
 class CountrySelect;
-class InputField;
 
 namespace Ui {
-class IconButton;
+class MultiSelect;
 } // namespace Ui
 
 class CountryInput : public QWidget {
@@ -82,8 +81,6 @@ signals:
 	void countryChosen(const QString &iso);
 
 public slots:
-	void onFilterUpdate();
-	void onFilterCancel();
 	void onSubmit();
 
 protected:
@@ -95,10 +92,11 @@ protected:
 	void showAll() override;
 
 private:
+	void onFilterUpdate(const QString &query);
+
 	class Inner;
 	ChildWidget<Inner> _inner;
-	ChildWidget<InputField> _filter;
-	ChildWidget<Ui::IconButton> _filterCancel;
+	ChildWidget<Ui::MultiSelect> _select;
 
 	ScrollableBoxShadow _topShadow;
 
diff --git a/Telegram/SourceFiles/ui/flatinput.cpp b/Telegram/SourceFiles/ui/flatinput.cpp
index 9cb1f3aa2..7b5966b4d 100644
--- a/Telegram/SourceFiles/ui/flatinput.cpp
+++ b/Telegram/SourceFiles/ui/flatinput.cpp
@@ -1890,9 +1890,7 @@ void InputField::step_placeholderFg(float64 ms, bool timer) {
 void InputField::step_placeholderShift(float64 ms, bool timer) {
 	float64 dt = ms / _st.duration;
 	if (dt >= 1) {
-		_a_placeholderShift.stop();
-		a_placeholderLeft.finish();
-		a_placeholderOpacity.finish();
+		finishPlaceholderAnimation();
 	} else {
 		a_placeholderLeft.update(dt, anim::linear);
 		a_placeholderOpacity.update(dt, anim::linear);
@@ -1900,6 +1898,13 @@ void InputField::step_placeholderShift(float64 ms, bool timer) {
 	if (timer) update();
 }
 
+void InputField::finishPlaceholderAnimation() {
+	_a_placeholderShift.stop();
+	a_placeholderLeft.finish();
+	a_placeholderOpacity.finish();
+	update();
+}
+
 void InputField::step_border(float64 ms, bool timer) {
 	float64 dt = ms / _st.duration;
 	if (dt >= 1) {
diff --git a/Telegram/SourceFiles/ui/flatinput.h b/Telegram/SourceFiles/ui/flatinput.h
index 9b0ee960d..f730be8f8 100644
--- a/Telegram/SourceFiles/ui/flatinput.h
+++ b/Telegram/SourceFiles/ui/flatinput.h
@@ -343,6 +343,7 @@ public:
 	}
 	void updatePlaceholder();
 	void setPlaceholderHidden(bool forcePlaceholderHidden);
+	void finishPlaceholderAnimation();
 
 	void step_placeholderFg(float64 ms, bool timer);
 	void step_placeholderShift(float64 ms, bool timer);
diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp
index 373ad7e92..ef0336f7f 100644
--- a/Telegram/SourceFiles/ui/widgets/multi_select.cpp
+++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp
@@ -433,8 +433,8 @@ QString MultiSelect::getQuery() const {
 	return _inner->getQuery();
 }
 
-void MultiSelect::addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage) {
-	_inner->addItem(std_::make_unique<Inner::Item>(_st.item, itemId, text, color, std_::move(paintRoundImage)));
+void MultiSelect::addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage, AddItemWay way) {
+	_inner->addItem(std_::make_unique<Inner::Item>(_st.item, itemId, text, color, std_::move(paintRoundImage)), way);
 }
 
 void MultiSelect::setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback) {
@@ -710,7 +710,7 @@ void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) {
 	}
 }
 
-void MultiSelect::Inner::addItem(std_::unique_ptr<Item> item) {
+void MultiSelect::Inner::addItem(std_::unique_ptr<Item> item, AddItemWay way) {
 	auto wasEmpty = _items.empty();
 	item->setUpdateCallback([this, item = item.get()] {
 		auto itemRect = item->paintArea(width() - _st.padding.left() - _st.padding.top());
@@ -723,7 +723,12 @@ void MultiSelect::Inner::addItem(std_::unique_ptr<Item> item) {
 	if (wasEmpty) {
 		updateHasAnyItems(true);
 	}
-	_items.back()->showAnimated();
+	if (way != AddItemWay::SkipAnimation) {
+		_items.back()->showAnimated();
+	} else {
+		_field->finishPlaceholderAnimation();
+		finishHeightAnimation();
+	}
 }
 
 void MultiSelect::Inner::computeItemsGeometry(int newWidth) {
@@ -765,16 +770,23 @@ void MultiSelect::Inner::updateItemsGeometry() {
 	if (newHeight == _newHeight) return;
 
 	_newHeight = newHeight;
-	_height.start([this] {
-		auto newHeight = _height.current(_newHeight);
-		if (auto heightDelta = newHeight - height()) {
-			resize(width(), newHeight);
-			if (_resizedCallback) {
-				_resizedCallback(heightDelta);
-			}
-			update();
+	_height.start([this] { updateHeightStep(); }, height(), _newHeight, _st.item.duration);
+}
+
+void MultiSelect::Inner::updateHeightStep() {
+	auto newHeight = _height.current(_newHeight);
+	if (auto heightDelta = newHeight - height()) {
+		resize(width(), newHeight);
+		if (_resizedCallback) {
+			_resizedCallback(heightDelta);
 		}
-	}, height(), _newHeight, _st.item.duration);
+		update();
+	}
+}
+
+void MultiSelect::Inner::finishHeightAnimation() {
+	_height.finish();
+	updateHeightStep();
 }
 
 void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) {
diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.h b/Telegram/SourceFiles/ui/widgets/multi_select.h
index 6226fc4fc..01854db33 100644
--- a/Telegram/SourceFiles/ui/widgets/multi_select.h
+++ b/Telegram/SourceFiles/ui/widgets/multi_select.h
@@ -40,8 +40,12 @@ public:
 	void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
 	void setResizedCallback(base::lambda_unique<void()> callback);
 
+	enum class AddItemWay {
+		Default,
+		SkipAnimation,
+	};
 	using PaintRoundImage = base::lambda_unique<void(Painter &p, int x, int y, int outerWidth, int size)>;
-	void addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage);
+	void addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage, AddItemWay way = AddItemWay::Default);
 	void setItemText(uint64 itemId, const QString &text);
 
 	void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback);
@@ -82,7 +86,7 @@ public:
 	void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
 
 	class Item;
-	void addItem(std_::unique_ptr<Item> item);
+	void addItem(std_::unique_ptr<Item> item, AddItemWay way);
 	void setItemText(uint64 itemId, const QString &text);
 
 	void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback);
@@ -120,6 +124,8 @@ private:
 		updateSelection(QPoint(-1, -1));
 	}
 	void updateCursor();
+	void updateHeightStep();
+	void finishHeightAnimation();
 	enum class ChangeActiveWay {
 		Default,
 		SkipSetFocus,
diff --git a/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h b/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h
index 380e65209..4c8ebc0ec 100644
--- a/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h
+++ b/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h
@@ -30,7 +30,7 @@ public:
 	using UpdateCallback = base::lambda_unique<void()>;
 	WidgetSlideWrap(QWidget *parent, Widget *entity
 		, style::margins entityPadding
-		, UpdateCallback &&updateCallback
+		, UpdateCallback updateCallback
 		, int duration = st::widgetSlideDuration) : TWidget(parent)
 	, _entity(entity)
 	, _padding(entityPadding)
diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style
index 8e34c14e4..d4ab8f7a1 100644
--- a/Telegram/SourceFiles/ui/widgets/widgets.style
+++ b/Telegram/SourceFiles/ui/widgets/widgets.style
@@ -92,11 +92,11 @@ MultiSelect {
 	itemSkip: pixels;
 
 	field: InputField;
+	fieldMinWidth: pixels;
 	fieldIcon: icon;
 	fieldIconSkip: pixels;
 	fieldCancel: IconButton;
 	fieldCancelSkip: pixels;
-	fieldMinWidth: pixels;
 }
 
 widgetSlideDuration: 200;