diff --git a/Telegram/Resources/art/sprite.png b/Telegram/Resources/art/sprite.png
index 450efca53..def492ee1 100644
Binary files a/Telegram/Resources/art/sprite.png and b/Telegram/Resources/art/sprite.png differ
diff --git a/Telegram/Resources/art/sprite_200x.png b/Telegram/Resources/art/sprite_200x.png
index ed5987c10..9098c1f0d 100644
Binary files a/Telegram/Resources/art/sprite_200x.png and b/Telegram/Resources/art/sprite_200x.png differ
diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style
index 67e166752..50f2cbc91 100644
--- a/Telegram/Resources/basic.style
+++ b/Telegram/Resources/basic.style
@@ -91,15 +91,6 @@ boxTitleFont: font(boxFontSize bold);
 boxTitlePosition: point(26px, 28px);
 boxTitleHeight: 54px;
 
-boxBlueTitleBg: #6393b5;
-boxBlueTitleAdditionalFg: #dae9f5;
-boxBlueTitleAdditionalSkip: 12px;
-boxBlueTitlePosition: point(23px, 18px);
-boxBlueCloseIcon: sprite(120px, 108px, 12px, 12px);
-boxBlueCloseBg: #c8e1f0;
-boxBlueCloseDuration: 150;
-boxBlueShadow: sprite(132px, 108px, 1px, 4px);
-
 boxButtonFont: font(boxFontSize semibold);
 defaultBoxButton: RoundButton {
 	textFg: #2f9fea;
@@ -193,6 +184,7 @@ defaultInputArea: InputArea {
 	heightMax: 128px;
 }
 defaultInputField: InputField {
+	textBg: white;
 	textFg: black;
 	textMargins: margins(0px, 6px, 0px, 4px);
 	textAlign: align(topleft);
@@ -216,15 +208,6 @@ defaultInputField: InputField {
 
 	height: 32px;
 }
-dialogsSearchField: InputField(defaultInputField) {
-	textMargins: margins(34px, 7px, 34px, 7px);
-
-	iconSprite: sprite(227px, 21px, 24px, 24px);
-	iconPosition: point(6px, 5px);
-
-	width: 240px;
-	height: 34px;
-}
 defaultCheckbox: Checkbox {
 	textFg: black;
 	textBg: white;
@@ -343,52 +326,8 @@ boxScroll: flatScroll(solidScroll) {
 boxScrollSkip: 6px;
 boxScrollShadowBg: #00000012;
 
-boxSearchField: InputField(defaultInputField) {
-	textMargins: margins(41px, 16px, 41px, 0px);
-
-	placeholderFg: #999;
-	placeholderFgActive: #aaa;
-	placeholderMargins: margins(4px, 0px, 4px, 0px);
-
-	border: 0px;
-	borderActive: 0px;
-	borderError: 0px;
-
-	height: 48px;
-
-	iconSprite: sprite(227px, 21px, 24px, 24px);
-	iconPosition: point(15px, 14px);
-
-	font: normalFont;
-}
-boxSearchCancel: iconedButton {
-	color: white;
-	bgColor: white;
-	overBgColor: white;
-	font: font(fsize);
-
-	opacity: 0.3;
-	overOpacity: 0.4;
-
-	textPos: point(0px, 0px);
-	downTextPos: point(0px, 0px);
-
-	duration: 150;
-	cursor: cursor(pointer);
-
-	icon: sprite(133px, 108px, 12px, 12px);
-	iconPos: point(8px, 18px);
-	downIcon: sprite(133px, 108px, 12px, 12px);
-	downIconPos: point(8px, 18px);
-
-	width: 41px;
-	height: 48px;
-}
-
 titleBg: #6389a8;
 titleHeight: 39px;
-titleIconPos: point(7px, 7px);
-titleIconImg: sprite(161px, 100px, 26px, 26px);
 titleFont: font(17px);
 titlePos: point(44px, 29px);
 titleMenuOffset: 36px;
@@ -788,12 +727,11 @@ dlgFilter: flatInput(inpDefGray) {
 	bgColor: #f2f2f2;
 	phColor: #949494;
 	phFocusColor: #a4a4a4;
-	imgRect: sprite(227px, 21px, 24px, 24px);
+	icon: icon {{ "box_search_icon", #aaaaaa, point(10px, 9px) }};
 
 	width: 240px;
 	height: 34px;
 	textMrg: margins(34px, 2px, 34px, 4px);
-	imgPos: point(6px, 5px);
 }
 
 topBarHeight: 54px;
@@ -959,8 +897,6 @@ msgDateImgBgSelected: #1c4a7187;
 msgDateImgPadding: point(8px, 2px);
 msgDateImgCheckSpace: 4px;
 
-msgDogImg: sprite(216px, 92px, 126px, 126px);
-
 collapseButton: flatButton(btnDefFlat) {
 	font: msgServiceFont;
 	overFont: msgServiceFont;
@@ -1340,9 +1276,6 @@ layerPadding: margins(10px, 10px, 10px, 10px);
 contactPadding: margins(49px, 22px, 0px, 6px);
 contactSkip: 13px;
 contactPhoneSkip: 30px;
-contactUserIcon: sprite(120px, 90px, 18px, 18px);
-contactPhoneIcon: sprite(138px, 90px, 18px, 18px);
-contactIconTop: 10px;
 
 contactsPhotoSize: 42px;
 contactsPadding: margins(16px, 7px, 16px, 7px);
@@ -1354,15 +1287,7 @@ contactsStatusFg: #999999;
 contactsStatusFgOver: #7c99b2;
 contactsStatusFgOnline: #3b8dcc;
 contactsBgOver: overBg;
-contactsBgActive: #6f9cbd;
 contactsCheckPosition: point(8px, 16px);
-contactsCheckIcon: sprite(187px, 61px, 18px, 14px);
-contactsCheckActiveIcon: sprite(187px, 75px, 18px, 14px);
-contactsNewItemHeight: 53px;
-contactsNewItemIcon: sprite(307px, 248px, 22px, 16px);
-contactsNewItemIconPosition: point(29px, 19px);
-contactsNewItemTop: 18px;
-contactsNewItemFg: #4b82af;
 contactsAboutBg: #f7f7f7;
 contactsAboutShadow: #0000001F;
 contactsAdminCheckbox: Checkbox(defaultBoxCheckbox) {
@@ -1886,8 +1811,6 @@ medviewSaveMsgShown: 2000;
 medviewSaveMsgHiding: 2500;
 medviewSaveMsg: #000000b2;
 
-mvTransparentBrush: sprite(9px, 124px, 8px, 8px);
-
 // Mac specific
 
 macAccessoryWidth: 450.;
diff --git a/Telegram/Resources/basic_types.style b/Telegram/Resources/basic_types.style
index 73918d110..f3baf7e69 100644
--- a/Telegram/Resources/basic_types.style
+++ b/Telegram/Resources/basic_types.style
@@ -130,8 +130,7 @@ flatInput {
 	font: font;
 	cursor: cursor;
 
-	imgRect: sprite;
-	imgPos: point;
+	icon: icon;
 
 	borderWidth: pixels;
 	borderColor: color;
@@ -394,6 +393,7 @@ InputArea {
 }
 
 InputField {
+	textBg: color;
 	textFg: color;
 	textMargins: margins;
 	textAlign: align;
@@ -418,9 +418,6 @@ InputField {
 
 	width: pixels;
 	height: pixels;
-
-	iconSprite: sprite;
-	iconPosition: point;
 }
 
 PeerAvatarButton {
diff --git a/Telegram/Resources/icons/add_contact_phone.png b/Telegram/Resources/icons/add_contact_phone.png
new file mode 100644
index 000000000..f649ba2fd
Binary files /dev/null and b/Telegram/Resources/icons/add_contact_phone.png differ
diff --git a/Telegram/Resources/icons/add_contact_phone@2x.png b/Telegram/Resources/icons/add_contact_phone@2x.png
new file mode 100644
index 000000000..b01522770
Binary files /dev/null and b/Telegram/Resources/icons/add_contact_phone@2x.png differ
diff --git a/Telegram/Resources/icons/add_contact_user.png b/Telegram/Resources/icons/add_contact_user.png
new file mode 100644
index 000000000..1d494cde8
Binary files /dev/null and b/Telegram/Resources/icons/add_contact_user.png differ
diff --git a/Telegram/Resources/icons/add_contact_user@2x.png b/Telegram/Resources/icons/add_contact_user@2x.png
new file mode 100644
index 000000000..a45ef3073
Binary files /dev/null and b/Telegram/Resources/icons/add_contact_user@2x.png differ
diff --git a/Telegram/Resources/icons/box_button_close.png b/Telegram/Resources/icons/box_button_close.png
new file mode 100644
index 000000000..57abcfb82
Binary files /dev/null and b/Telegram/Resources/icons/box_button_close.png differ
diff --git a/Telegram/Resources/icons/box_button_close@2x.png b/Telegram/Resources/icons/box_button_close@2x.png
new file mode 100644
index 000000000..bf2a19e72
Binary files /dev/null and b/Telegram/Resources/icons/box_button_close@2x.png differ
diff --git a/Telegram/Resources/icons/box_search_cancel.png b/Telegram/Resources/icons/box_search_cancel.png
new file mode 100644
index 000000000..7fa112a33
Binary files /dev/null and b/Telegram/Resources/icons/box_search_cancel.png differ
diff --git a/Telegram/Resources/icons/box_search_cancel@2x.png b/Telegram/Resources/icons/box_search_cancel@2x.png
new file mode 100644
index 000000000..40d3def3d
Binary files /dev/null and b/Telegram/Resources/icons/box_search_cancel@2x.png differ
diff --git a/Telegram/Resources/icons/box_search_icon.png b/Telegram/Resources/icons/box_search_icon.png
new file mode 100644
index 000000000..047352607
Binary files /dev/null and b/Telegram/Resources/icons/box_search_icon.png differ
diff --git a/Telegram/Resources/icons/box_search_icon@2x.png b/Telegram/Resources/icons/box_search_icon@2x.png
new file mode 100644
index 000000000..4d2c66f86
Binary files /dev/null and b/Telegram/Resources/icons/box_search_icon@2x.png differ
diff --git a/Telegram/Resources/icons/box_title_shadow.png b/Telegram/Resources/icons/box_title_shadow.png
new file mode 100644
index 000000000..bba09b240
Binary files /dev/null and b/Telegram/Resources/icons/box_title_shadow.png differ
diff --git a/Telegram/Resources/icons/box_title_shadow@2x.png b/Telegram/Resources/icons/box_title_shadow@2x.png
new file mode 100644
index 000000000..7d97eb59e
Binary files /dev/null and b/Telegram/Resources/icons/box_title_shadow@2x.png differ
diff --git a/Telegram/Resources/icons/contacts_add.png b/Telegram/Resources/icons/contacts_add.png
new file mode 100644
index 000000000..98f770cc6
Binary files /dev/null and b/Telegram/Resources/icons/contacts_add.png differ
diff --git a/Telegram/Resources/icons/contacts_add@2x.png b/Telegram/Resources/icons/contacts_add@2x.png
new file mode 100644
index 000000000..7fc3c170f
Binary files /dev/null and b/Telegram/Resources/icons/contacts_add@2x.png differ
diff --git a/Telegram/Resources/icons/history_empty_dog.png b/Telegram/Resources/icons/history_empty_dog.png
new file mode 100644
index 000000000..3e6fef342
Binary files /dev/null and b/Telegram/Resources/icons/history_empty_dog.png differ
diff --git a/Telegram/Resources/icons/history_empty_dog@2x.png b/Telegram/Resources/icons/history_empty_dog@2x.png
new file mode 100644
index 000000000..248860d6b
Binary files /dev/null and b/Telegram/Resources/icons/history_empty_dog@2x.png differ
diff --git a/Telegram/Resources/icons/title_icon.png b/Telegram/Resources/icons/title_icon.png
new file mode 100644
index 000000000..12772e6a3
Binary files /dev/null and b/Telegram/Resources/icons/title_icon.png differ
diff --git a/Telegram/Resources/icons/title_icon@2x.png b/Telegram/Resources/icons/title_icon@2x.png
new file mode 100644
index 000000000..ad40f1fea
Binary files /dev/null and b/Telegram/Resources/icons/title_icon@2x.png differ
diff --git a/Telegram/Resources/icons/title_icon_bg.png b/Telegram/Resources/icons/title_icon_bg.png
new file mode 100644
index 000000000..ebdf86291
Binary files /dev/null and b/Telegram/Resources/icons/title_icon_bg.png differ
diff --git a/Telegram/Resources/icons/title_icon_bg@2x.png b/Telegram/Resources/icons/title_icon_bg@2x.png
new file mode 100644
index 000000000..33924433d
Binary files /dev/null and b/Telegram/Resources/icons/title_icon_bg@2x.png differ
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index facd674d1..6bfed1347 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -2819,41 +2819,7 @@ namespace {
 
 		uint64 max = qMax(1ULL, components[maxtomin[0]]), mid = qMax(1ULL, components[maxtomin[1]]), min = qMax(1ULL, components[maxtomin[2]]);
 
-		QImage dog = App::sprite().toImage().copy(st::msgDogImg.rect());
-		QImage::Format f = dog.format();
-		if (f != QImage::Format_ARGB32 && f != QImage::Format_ARGB32_Premultiplied) {
-			dog = dog.convertToFormat(QImage::Format_ARGB32_Premultiplied);
-		}
-		uchar *dogBits = dog.bits();
-		if (max != min) {
-			float64 coef = float64(mid - min) / float64(max - min);
-			for (int i = 0, s = dog.width() * dog.height() * 4; i < s; i += 4) {
-				int dogmaxtomin[3] = { i, i + 1, i + 2 };
-				if (dogBits[dogmaxtomin[0]] < dogBits[dogmaxtomin[1]]) {
-					qSwap(dogmaxtomin[0], dogmaxtomin[1]);
-				}
-				if (dogBits[dogmaxtomin[1]] < dogBits[dogmaxtomin[2]]) {
-					qSwap(dogmaxtomin[1], dogmaxtomin[2]);
-					if (dogBits[dogmaxtomin[0]] < dogBits[dogmaxtomin[1]]) {
-						qSwap(dogmaxtomin[0], dogmaxtomin[1]);
-					}
-				}
-				uchar result[3];
-				result[maxtomin[0]] = dogBits[dogmaxtomin[0]];
-				result[maxtomin[2]] = dogBits[dogmaxtomin[2]];
-				result[maxtomin[1]] = uchar(qRound(result[maxtomin[2]] + (result[maxtomin[0]] - result[maxtomin[2]]) * coef));
-				dogBits[i] = result[2];
-				dogBits[i + 1] = result[1];
-				dogBits[i + 2] = result[0];
-			}
-		} else {
-			for (int i = 0, s = dog.width() * dog.height() * 4; i < s; i += 4) {
-				uchar b = dogBits[i], g = dogBits[i + 1], r = dogBits[i + 2];
-				dogBits[i] = dogBits[i + 1] = dogBits[i + 2] = (r + r + b + g + g + g) / 6;
-			}
-		}
-
-		Window::chatBackground()->init(id, pixmapFromImageInPlace(std_::move(img)), pixmapFromImageInPlace(std_::move(dog)));
+		Window::chatBackground()->init(id, pixmapFromImageInPlace(std_::move(img)));
 
 		memcpy(componentsScroll, components, sizeof(components));
 		memcpy(componentsPoint, components, sizeof(components));
diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp
index a9f4974f4..6df51ba70 100644
--- a/Telegram/SourceFiles/boxes/abstractbox.cpp
+++ b/Telegram/SourceFiles/boxes/abstractbox.cpp
@@ -26,16 +26,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "abstractbox.h"
 #include "mainwidget.h"
 #include "mainwindow.h"
+#include "styles/style_boxes.h"
 
 void BlueTitleShadow::paintEvent(QPaintEvent *e) {
 	Painter p(this);
 
 	QRect r(e->rect());
-	p.drawPixmap(QRect(r.left(), 0, r.width(), height()), App::sprite(), st::boxBlueShadow.rect());
+	st::boxBlueTitleShadow.fill(p, QRect(r.left(), 0, r.width(), height()));
 }
 
 BlueTitleClose::BlueTitleClose(QWidget *parent) : Button(parent)
-, a_iconFg(st::boxBlueCloseBg->c)
+, a_iconFg(st::boxBlueCloseFg->c)
 , _a_over(animation(this, &BlueTitleClose::step_over)) {
 	resize(st::boxTitleHeight, st::boxTitleHeight);
 	setCursor(style::cur_pointer);
@@ -44,7 +45,7 @@ BlueTitleClose::BlueTitleClose(QWidget *parent) : Button(parent)
 
 void BlueTitleClose::onStateChange(int oldState, ButtonStateChangeSource source) {
 	if ((oldState & StateOver) != (_state & StateOver)) {
-		a_iconFg.start(((_state & StateOver) ? st::white : st::boxBlueCloseBg)->c);
+		a_iconFg.start(((_state & StateOver) ? st::boxBlueCloseOverFg : st::boxBlueCloseFg)->c);
 		_a_over.start();
 	}
 }
@@ -57,28 +58,23 @@ void BlueTitleClose::step_over(float64 ms, bool timer) {
 	} else {
 		a_iconFg.update(dt, anim::linear);
 	}
-	if (timer) update((st::boxTitleHeight - st::boxBlueCloseIcon.pxWidth()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.pxHeight()) / 2, st::boxBlueCloseIcon.pxWidth(), st::boxBlueCloseIcon.pxHeight());
+	if (timer) update((st::boxTitleHeight - st::boxBlueCloseIcon.width()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.height()) / 2, st::boxBlueCloseIcon.width(), st::boxBlueCloseIcon.height());
 }
 
 void BlueTitleClose::paintEvent(QPaintEvent *e) {
 	Painter p(this);
 
-	QRect r(e->rect()), s((st::boxTitleHeight - st::boxBlueCloseIcon.pxWidth()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.pxHeight()) / 2, st::boxBlueCloseIcon.pxWidth(), st::boxBlueCloseIcon.pxHeight());
+	QRect r(e->rect()), s((st::boxTitleHeight - st::boxBlueCloseIcon.width()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.height()) / 2, st::boxBlueCloseIcon.width(), st::boxBlueCloseIcon.height());
 	if (!s.contains(r)) {
-		p.fillRect(r, st::boxBlueTitleBg->b);
+		p.fillRect(r, st::boxBlueTitleBg);
 	}
 	if (s.intersects(r)) {
 		p.fillRect(s.intersected(r), a_iconFg.current());
-		p.drawSprite(s.topLeft(), st::boxBlueCloseIcon);
+		st::boxBlueCloseIcon.paint(p, s.topLeft(), width());
 	}
 }
 
-AbstractBox::AbstractBox(int32 w) : LayerWidget()
-, _maxHeight(0)
-, _closed(false)
-, _blueTitle(false)
-, _blueClose(0)
-, _blueShadow(0) {
+AbstractBox::AbstractBox(int w) : LayerWidget() {
 	setAttribute(Qt::WA_OpaquePaintEvent);
 	resize(w, 0);
 }
@@ -101,7 +97,7 @@ void AbstractBox::resizeEvent(QResizeEvent *e) {
 	}
 	if (_blueShadow) {
 		_blueShadow->moveToLeft(0, st::boxTitleHeight);
-		_blueShadow->resize(width(), st::boxBlueShadow.pxHeight());
+		_blueShadow->resize(width(), st::boxBlueTitleShadow.height());
 	}
 	LayerWidget::resizeEvent(e);
 }
@@ -165,8 +161,8 @@ void AbstractBox::resizeMaxHeight(int32 newWidth, int32 maxHeight) {
 	}
 }
 
-int32 AbstractBox::countHeight() const {
-	return qMin(_maxHeight, App::wnd()->height() - int32(2 * st::boxVerticalMargin));
+int AbstractBox::countHeight() const {
+	return qMin(_maxHeight, App::wnd()->height() - 2 * st::boxVerticalMargin);
 }
 
 void AbstractBox::onClose() {
@@ -201,24 +197,28 @@ ScrollableBox::ScrollableBox(const style::flatScroll &scroll, int32 w) : Abstrac
 }
 
 void ScrollableBox::resizeEvent(QResizeEvent *e) {
-	_scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip);
+	updateScrollGeometry();
 	AbstractBox::resizeEvent(e);
 }
 
-void ScrollableBox::init(QWidget *inner, int bottomSkip, int topSkip) {
-	_bottomSkip = bottomSkip;
-	_topSkip = topSkip;
-	_scroll->setWidget(inner);
-	_scroll->setFocusPolicy(Qt::NoFocus);
-	ScrollableBox::resizeEvent(nullptr);
-}
-
-void ScrollableBox::initOwned(QWidget *inner, int bottomSkip, int topSkip) {
+void ScrollableBox::init(ScrolledWidget *inner, int bottomSkip, int topSkip) {
 	_bottomSkip = bottomSkip;
 	_topSkip = topSkip;
 	_scroll->setOwnedWidget(inner);
 	_scroll->setFocusPolicy(Qt::NoFocus);
-	ScrollableBox::resizeEvent(nullptr);
+	updateScrollGeometry();
+}
+
+void ScrollableBox::setScrollSkips(int bottomSkip, int topSkip) {
+	if (_topSkip != topSkip || _bottomSkip != bottomSkip) {
+		_topSkip = topSkip;
+		_bottomSkip = bottomSkip;
+		updateScrollGeometry();
+	}
+}
+
+void ScrollableBox::updateScrollGeometry() {
+	_scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip);
 }
 
 ItemListBox::ItemListBox(const style::flatScroll &scroll, int32 w) : ScrollableBox(scroll, w) {
diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h
index c8a7a7f2f..4c6e28b1a 100644
--- a/Telegram/SourceFiles/boxes/abstractbox.h
+++ b/Telegram/SourceFiles/boxes/abstractbox.h
@@ -52,7 +52,7 @@ class AbstractBox : public LayerWidget, protected base::Subscriber {
 	Q_OBJECT
 
 public:
-	AbstractBox(int32 w = st::boxWideWidth);
+	AbstractBox(int w = st::boxWideWidth);
 	void parentResized() override;
 	void showDone() override {
 		showAll();
@@ -83,14 +83,14 @@ protected:
 	}
 
 private:
-	int32 _maxHeight;
-	int32 countHeight() const;
+	int _maxHeight = 0;
+	int countHeight() const;
 
-	bool _closed;
+	bool _closed = false;
 
-	bool _blueTitle;
-	BlueTitleClose *_blueClose;
-	BlueTitleShadow *_blueShadow;
+	bool _blueTitle = false;
+	BlueTitleClose *_blueClose = nullptr;
+	BlueTitleShadow *_blueShadow = nullptr;
 
 };
 
@@ -105,8 +105,8 @@ public:
 	ScrollableBox(const style::flatScroll &scroll, int w = st::boxWideWidth);
 
 protected:
-	void init(QWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight);
-	void initOwned(QWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight);
+	void init(ScrolledWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight);
+	void setScrollSkips(int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight);
 
 	void resizeEvent(QResizeEvent *e) override;
 
@@ -115,8 +115,10 @@ protected:
 	}
 
 private:
+	void updateScrollGeometry();
+
 	ChildWidget<ScrollArea> _scroll;
-	int32 _topSkip, _bottomSkip;
+	int _topSkip, _bottomSkip;
 
 };
 
diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp
index 5e561ff33..2ef31e477 100644
--- a/Telegram/SourceFiles/boxes/addcontactbox.cpp
+++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp
@@ -109,8 +109,8 @@ void AddContactBox::paintEvent(QPaintEvent *e) {
 	paintTitle(p, _boxTitle);
 
 	if (_retry.isHidden()) {
-		p.drawSpriteLeft(st::boxPadding.left(), _first.y() + st::contactIconTop, width(), st::contactUserIcon);
-		p.drawSpriteLeft(st::boxPadding.left(), _phone.y() + st::contactIconTop, width(), st::contactPhoneIcon);
+		st::contactUserIcon.paint(p, st::boxPadding.left(), _first.y() + st::contactIconTop, width());
+		st::contactPhoneIcon.paint(p, st::boxPadding.left(), _phone.y() + st::contactIconTop, width());
 	} else {
 		p.setPen(st::black->p);
 		p.setFont(st::boxTextFont->f);
diff --git a/Telegram/SourceFiles/boxes/backgroundbox.cpp b/Telegram/SourceFiles/boxes/backgroundbox.cpp
index 9f2700dea..181ff3d49 100644
--- a/Telegram/SourceFiles/boxes/backgroundbox.cpp
+++ b/Telegram/SourceFiles/boxes/backgroundbox.cpp
@@ -27,11 +27,41 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "window/chat_background.h"
 #include "styles/style_overview.h"
 
-BackgroundInner::BackgroundInner() :
-_bgCount(0), _rows(0), _over(-1), _overDown(-1) {
+BackgroundBox::BackgroundBox() : ItemListBox(st::backgroundScroll)
+, _inner(this) {
+	init(_inner);
+
+	connect(_inner, SIGNAL(backgroundChosen(int)), this, SLOT(onBackgroundChosen(int)));
+
+	prepare();
+}
+
+void BackgroundBox::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+	if (paint(p)) return;
+
+	paintTitle(p, lang(lng_backgrounds_header));
+}
+
+void BackgroundBox::onBackgroundChosen(int index) {
+	if (index >= 0 && index < App::cServerBackgrounds().size()) {
+		const App::WallPaper &paper(App::cServerBackgrounds().at(index));
+		if (App::main()) App::main()->setChatBackground(paper);
+
+		using Update = Window::ChatBackgroundUpdate;
+		Window::chatBackground()->notify(Update(Update::Type::Start, !paper.id));
+	}
+	onClose();
+}
+
+BackgroundBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent)
+, _bgCount(0)
+, _rows(0)
+, _over(-1)
+, _overDown(-1) {
 	if (App::cServerBackgrounds().isEmpty()) {
 		resize(BackgroundsInRow * (st::backgroundSize.width() + st::backgroundPadding) + st::backgroundPadding, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
-		MTP::send(MTPaccount_GetWallPapers(), rpcDone(&BackgroundInner::gotWallpapers));
+		MTP::send(MTPaccount_GetWallPapers(), rpcDone(&Inner::gotWallpapers));
 	} else {
 		updateWallpapers();
 	}
@@ -40,7 +70,7 @@ _bgCount(0), _rows(0), _over(-1), _overDown(-1) {
 	setMouseTracking(true);
 }
 
-void BackgroundInner::gotWallpapers(const MTPVector<MTPWallPaper> &result) {
+void BackgroundBox::Inner::gotWallpapers(const MTPVector<MTPWallPaper> &result) {
 	App::WallPapers wallpapers;
 
 	wallpapers.push_back(App::WallPaper(0, ImagePtr(st::msgBG0), ImagePtr(st::msgBG0)));
@@ -98,7 +128,7 @@ void BackgroundInner::gotWallpapers(const MTPVector<MTPWallPaper> &result) {
 	updateWallpapers();
 }
 
-void BackgroundInner::updateWallpapers() {
+void BackgroundBox::Inner::updateWallpapers() {
 	_bgCount = App::cServerBackgrounds().size();
 	_rows = _bgCount / BackgroundsInRow;
 	if (_bgCount % BackgroundsInRow) ++_rows;
@@ -111,7 +141,7 @@ void BackgroundInner::updateWallpapers() {
 	}
 }
 
-void BackgroundInner::paintEvent(QPaintEvent *e) {
+void BackgroundBox::Inner::paintEvent(QPaintEvent *e) {
 	QRect r(e->rect());
 	Painter p(this);
 
@@ -145,7 +175,7 @@ void BackgroundInner::paintEvent(QPaintEvent *e) {
 	}
 }
 
-void BackgroundInner::mouseMoveEvent(QMouseEvent *e) {
+void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) {
 	int x = e->pos().x(), y = e->pos().y();
 	int row = int((y - st::backgroundPadding) / (st::backgroundSize.height() + st::backgroundPadding));
 	if (y - row * (st::backgroundSize.height() + st::backgroundPadding) > st::backgroundPadding + st::backgroundSize.height()) row = _rows + 1;
@@ -161,11 +191,11 @@ void BackgroundInner::mouseMoveEvent(QMouseEvent *e) {
 	}
 }
 
-void BackgroundInner::mousePressEvent(QMouseEvent *e) {
+void BackgroundBox::Inner::mousePressEvent(QMouseEvent *e) {
 	_overDown = _over;
 }
 
-void BackgroundInner::mouseReleaseEvent(QMouseEvent *e) {
+void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
 	if (_overDown == _over && _over >= 0) {
 		emit backgroundChosen(_over);
 	} else if (_over < 0) {
@@ -173,33 +203,5 @@ void BackgroundInner::mouseReleaseEvent(QMouseEvent *e) {
 	}
 }
 
-void BackgroundInner::resizeEvent(QResizeEvent *e) {
-}
-
-BackgroundBox::BackgroundBox() : ItemListBox(st::backgroundScroll)
-, _inner() {
-
-	init(&_inner);
-
-	connect(&_inner, SIGNAL(backgroundChosen(int)), this, SLOT(onBackgroundChosen(int)));
-
-	prepare();
-}
-
-void BackgroundBox::paintEvent(QPaintEvent *e) {
-	Painter p(this);
-	if (paint(p)) return;
-
-	paintTitle(p, lang(lng_backgrounds_header));
-}
-
-void BackgroundBox::onBackgroundChosen(int index) {
-	if (index >= 0 && index < App::cServerBackgrounds().size()) {
-		const App::WallPaper &paper(App::cServerBackgrounds().at(index));
-		if (App::main()) App::main()->setChatBackground(paper);
-
-		using Update = Window::ChatBackgroundUpdate;
-		Window::chatBackground()->notify(Update(Update::Type::Start, !paper.id));
-	}
-	onClose();
+void BackgroundBox::Inner::resizeEvent(QResizeEvent *e) {
 }
diff --git a/Telegram/SourceFiles/boxes/backgroundbox.h b/Telegram/SourceFiles/boxes/backgroundbox.h
index ebdcab053..798593658 100644
--- a/Telegram/SourceFiles/boxes/backgroundbox.h
+++ b/Telegram/SourceFiles/boxes/backgroundbox.h
@@ -23,11 +23,30 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "abstractbox.h"
 #include "core/lambda_wrap.h"
 
-class BackgroundInner : public TWidget, public RPCSender, private base::Subscriber {
+class BackgroundBox : public ItemListBox {
 	Q_OBJECT
 
 public:
-	BackgroundInner();
+	BackgroundBox();
+
+public slots:
+	void onBackgroundChosen(int index);
+
+protected:
+	void paintEvent(QPaintEvent *e) override;
+
+private:
+	class Inner;
+	ChildWidget<Inner> _inner;
+
+};
+
+// This class is hold in header because it requires Qt preprocessing.
+class BackgroundBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
+	Q_OBJECT
+
+public:
+	Inner(QWidget *parent);
 
 signals:
 	void backgroundChosen(int index);
@@ -47,20 +66,3 @@ private:
 	int32 _over, _overDown;
 
 };
-
-class BackgroundBox : public ItemListBox {
-	Q_OBJECT
-
-public:
-	BackgroundBox();
-
-public slots:
-	void onBackgroundChosen(int index);
-
-protected:
-	void paintEvent(QPaintEvent *e) override;
-
-private:
-	BackgroundInner _inner;
-
-};
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 306381f21..f50ed827e 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -19,6 +19,17 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 */
 using "basic.style";
+using "ui/widgets/widgets.style";
+
+boxBlueTitleBg: #6393b5;
+boxBlueTitleAdditionalFg: #dae9f5;
+boxBlueTitleAdditionalSkip: 12px;
+boxBlueTitlePosition: point(23px, 18px);
+boxBlueTitleShadow: icon {{ "box_title_shadow", windowShadowFg }};
+boxBlueCloseFg: #c8e1f0;
+boxBlueCloseOverFg: #ffffff;
+boxBlueCloseIcon: icon {{ "box_button_close", boxBlueTitleBg }};
+boxBlueCloseDuration: 150;
 
 confirmInviteTitle: flatLabel(labelDefFlat) {
 	font: font(16px semibold);
@@ -67,26 +78,108 @@ aboutRevokePublicLabel: flatLabel(labelDefFlat) {
 	textFg: windowTextFg;
 }
 
+contactUserIcon: icon {{ "add_contact_user", #999999 }};
+contactPhoneIcon: icon {{ "add_contact_phone", #999999 }};
+contactIconTop: 10px;
+
+contactsNewItemHeight: 53px;
+contactsNewItemIcon: icon {{ "contacts_add", #749fc2, point(29px, 19px) }};
+contactsNewItemTop: 18px;
+contactsNewItemFg: #4b82af;
+
+contactsMultiSelect: MultiSelect {
+	padding: margins(8px, 8px, 8px, 8px);
+	maxHeight: 104px;
+	scroll: flatScroll(solidScroll) {
+		deltat: 3px;
+		deltab: 3px;
+		round: 1px;
+		width: 8px;
+		deltax: 3px;
+		hiding: 1000;
+	}
+
+	item: MultiSelectItem {
+		padding: margins(6px, 7px, 12px, 0px);
+		maxWidth: 128px;
+		height: 32px;
+		font: normalFont;
+		textBg: contactsBgOver;
+		textFg: windowTextFg;
+		textActiveBg: windowActiveBg;
+		textActiveFg: white;
+		deleteFg: white;
+		deleteLeft: 10px;
+		deleteStroke: 2px;
+		duration: 150;
+		minScale: 0.3;
+	}
+	itemSkip: 8px;
+
+	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: 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;
+}
+contactsPhotoCheckbox: RoundImageCheckbox {
+	imageRadius: 21px;
+	imageSmallRadius: 18px;
+	selectWidth: 2px;
+	selectFg: windowActiveBg;
+	selectDuration: 150;
+	checkBorder: windowBg;
+	checkBg: windowActiveBg;
+	checkRadius: 10px;
+	checkSmallRadius: 3px;
+	checkIcon: icon {{ "default_checkbox_check", windowBg, point(3px, 6px) }};
+}
+contactsPhotoDisabledCheckFg: #bbbbbb;
+contactsNameCheckedFg: #2b88b8;
+
 localStorageBoxSkip: 10px;
 
 shareRowsTop: 12px;
 shareRowHeight: 108px;
-sharePhotoRadius: 28px;
-sharePhotoSmallRadius: 24px;
 sharePhotoTop: 6px;
-shareSelectWidth: 2px;
-shareSelectFg: windowActiveBg;
-shareCheckBorder: windowBg;
-shareCheckBg: windowActiveBg;
-shareCheckRadius: 10px;
-shareCheckSmallRadius: 3px;
-shareCheckIcon: icon {{ "default_checkbox_check", windowBg, point(3px, 6px) }};
+sharePhotoCheckbox: RoundImageCheckbox(contactsPhotoCheckbox) {
+	imageRadius: 28px;
+	imageSmallRadius: 24px;
+}
 shareNameFont: font(11px);
 shareNameFg: windowTextFg;
 shareNameActiveFg: btnYesColor;
 shareNameTop: 6px;
 shareColumnSkip: 6px;
-shareSelectDuration: 150;
 shareActivateDuration: 150;
 shareScrollDuration: 300;
 
diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp
index b006dedbb..dff590fce 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.cpp
+++ b/Telegram/SourceFiles/boxes/contactsbox.cpp
@@ -23,12 +23,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 
 #include "dialogs/dialogs_indexed_list.h"
 #include "styles/style_dialogs.h"
+#include "styles/style_history.h"
+#include "styles/style_boxes.h"
 #include "lang.h"
 #include "boxes/addcontactbox.h"
 #include "mainwidget.h"
 #include "mainwindow.h"
 #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"
@@ -38,7 +42,520 @@ QString cantInviteError() {
 	return lng_cant_invite_not_contact(lt_more_info, textcmdLink(qsl("https://telegram.me/spambot"), lang(lng_cant_more_info)));
 }
 
-ContactsInner::ContactsInner(CreatingGroupType creating) : TWidget()
+ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll)
+, _inner(this, CreatingGroupNone)
+, _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) {
+	init();
+}
+
+ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox(st::boxScroll)
+, _inner(this, CreatingGroupGroup)
+, _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)
+, _creationName(name)
+, _creationPhoto(photo) {
+	init();
+}
+
+ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll)
+, _inner(this, channel, MembersFilter::Recent, MembersAlreadyIn())
+, _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) {
+	init();
+}
+
+ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already) : ItemListBox((filter == MembersFilter::Admins) ? st::contactsScroll : st::boxScroll)
+, _inner(this, channel, filter, already)
+, _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) {
+	init();
+}
+
+ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st::boxScroll)
+, _inner(this, chat, 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) {
+	init();
+}
+
+ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll)
+, _inner(this, bot)
+, _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) {
+	init();
+}
+
+void ContactsBox::init() {
+	_select->resizeToWidth(st::boxWideWidth);
+	myEnsureResized(_select);
+
+	auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat();
+	auto topSkip = getTopScrollSkip();
+	auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip;
+	ItemListBox::init(_inner, bottomSkip, topSkip);
+
+	connect(_inner, SIGNAL(addRequested()), App::wnd(), SLOT(onShowAddContact()));
+	_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();
+		_cancel.hide();
+	} else if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins) {
+		connect(&_next, SIGNAL(clicked()), this, SLOT(onSaveAdmins()));
+		_bottomShadow = new ScrollableBoxShadow(this);
+	} else if (_inner->chat() || _inner->channel()) {
+		connect(&_next, SIGNAL(clicked()), this, SLOT(onInvite()));
+		_bottomShadow = new ScrollableBoxShadow(this);
+	} else if (_inner->creating() != CreatingGroupNone) {
+		connect(&_next, SIGNAL(clicked()), this, SLOT(onCreate()));
+		_bottomShadow = new ScrollableBoxShadow(this);
+	} else {
+		_next.hide();
+		_cancel.hide();
+	}
+	connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
+	connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
+	_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->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()));
+
+	_searchTimer.setSingleShot(true);
+	connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername()));
+
+	prepare();
+}
+
+bool ContactsBox::onSearchByUsername(bool searchCache) {
+	auto q = _select->entity()->getQuery();
+	if (q.isEmpty()) {
+		if (_peopleRequest) {
+			_peopleRequest = 0;
+		}
+		return true;
+	}
+	if (q.size() >= MinUsernameLength) {
+		if (searchCache) {
+			PeopleCache::const_iterator i = _peopleCache.constFind(q);
+			if (i != _peopleCache.cend()) {
+				_peopleQuery = q;
+				_peopleRequest = 0;
+				peopleReceived(i.value(), 0);
+				return true;
+			}
+		} else if (_peopleQuery != q) {
+			_peopleQuery = q;
+			_peopleFull = false;
+			_peopleRequest = MTP::send(MTPcontacts_Search(MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit)), rpcDone(&ContactsBox::peopleReceived), rpcFail(&ContactsBox::peopleFailed));
+			_peopleQueries.insert(_peopleRequest, _peopleQuery);
+		}
+	}
+	return false;
+}
+
+void ContactsBox::onNeedSearchByUsername() {
+	if (!onSearchByUsername(true)) {
+		_searchTimer.start(AutoSearchTimeout);
+	}
+}
+
+void ContactsBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId req) {
+	QString q = _peopleQuery;
+
+	PeopleQueries::iterator i = _peopleQueries.find(req);
+	if (i != _peopleQueries.cend()) {
+		q = i.value();
+		_peopleCache[q] = result;
+		_peopleQueries.erase(i);
+	}
+
+	if (_peopleRequest == req) {
+		switch (result.type()) {
+		case mtpc_contacts_found: {
+			App::feedUsers(result.c_contacts_found().vusers);
+			App::feedChats(result.c_contacts_found().vchats);
+			_inner->peopleReceived(q, result.c_contacts_found().vresults.c_vector().v);
+		} break;
+		}
+
+		_peopleRequest = 0;
+		_inner->updateSelection();
+		onScroll();
+	}
+}
+
+bool ContactsBox::peopleFailed(const RPCError &error, mtpRequestId req) {
+	if (MTP::isDefaultHandledError(error)) return false;
+
+	if (_peopleRequest == req) {
+		_peopleRequest = 0;
+		_peopleFull = true;
+	}
+	return true;
+}
+
+void ContactsBox::showAll() {
+	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();
+	} else if (_inner->chat() || _inner->channel()) {
+		_next.show();
+		_cancel.show();
+	} else if (_inner->creating() != CreatingGroupNone) {
+		_next.show();
+		_cancel.show();
+	} else {
+		_next.hide();
+		_cancel.hide();
+	}
+	_topShadow.show();
+	if (_bottomShadow) _bottomShadow->show();
+	ItemListBox::showAll();
+}
+
+void ContactsBox::doSetInnerFocus() {
+	if (_select->isHidden()) {
+		_inner->setFocus();
+	} else {
+		_select->entity()->setInnerFocus();
+	}
+}
+
+void ContactsBox::onSubmit() {
+	_inner->chooseParticipant();
+}
+
+void ContactsBox::keyPressEvent(QKeyEvent *e) {
+	auto focused = focusWidget();
+	if (_select == focused || _select->isAncestorOf(focusWidget())) {
+		if (e->key() == Qt::Key_Down) {
+			_inner->selectSkip(1);
+		} else if (e->key() == Qt::Key_Up) {
+			_inner->selectSkip(-1);
+		} else if (e->key() == Qt::Key_PageDown) {
+			_inner->selectSkipPage(scrollArea()->height(), 1);
+		} else if (e->key() == Qt::Key_PageUp) {
+			_inner->selectSkipPage(scrollArea()->height(), -1);
+		} else {
+			ItemListBox::keyPressEvent(e);
+		}
+	} else {
+		ItemListBox::keyPressEvent(e);
+	}
+}
+
+void ContactsBox::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+	if (paint(p)) return;
+
+	bool addingAdmin = _inner->channel() && _inner->membersFilter() == MembersFilter::Admins;
+	if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins) {
+		paintTitle(p, lang(lng_channel_admins));
+	} else if (_inner->chat() || _inner->creating() != CreatingGroupNone) {
+		QString title(lang(addingAdmin ? lng_channel_add_admin : lng_profile_add_participant));
+		QString additional((addingAdmin || (_inner->channel() && !_inner->channel()->isMegagroup())) ? QString() : QString("%1 / %2").arg(_inner->selectedCount()).arg(Global::MegagroupSizeMax()));
+		paintTitle(p, title, additional);
+	} else if (_inner->sharingBotGame()) {
+		paintTitle(p, lang(lng_bot_choose_chat));
+	} else if (_inner->bot()) {
+		paintTitle(p, lang(lng_bot_choose_group));
+	} else {
+		paintTitle(p, lang(lng_contacts_header));
+	}
+}
+
+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 = getTopScrollSkip();
+	auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip;
+	setScrollSkips(bottomSkip, topSkip);
+	auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight;
+	if (scrollHeightDelta) {
+		scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta);
+	}
+
+	_topShadow.setGeometry(0, topSkip, width(), st::lineWidth);
+}
+
+void ContactsBox::resizeEvent(QResizeEvent *e) {
+	ItemListBox::resizeEvent(e);
+
+	_select->resizeToWidth(width());
+	_select->moveToLeft(0, st::boxTitleHeight);
+
+	updateScrollSkips();
+
+	_inner->resize(width(), _inner->height());
+	_next.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _next.height());
+	_cancel.moveToRight(st::boxButtonPadding.right() + _next.width() + st::boxButtonPadding.left(), _next.y());
+	if (_bottomShadow) _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _next.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth);
+}
+
+void ContactsBox::closePressed() {
+	if (_inner->channel() && !_inner->hasAlreadyMembersInChannel()) {
+		Ui::showPeerHistory(_inner->channel(), ShowAtTheEndMsgId);
+	}
+}
+
+void ContactsBox::onFilterUpdate(const QString &filter) {
+	scrollArea()->scrollToY(0);
+	_inner->updateFilter(filter);
+}
+
+void ContactsBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) {
+	using AddItemWay = Ui::MultiSelect::AddItemWay;
+	auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default;
+	_select->entity()->addItem(peer->id, peer->shortName(), st::windowActiveBg, PaintUserpicCallback(peer), addItemWay);
+}
+
+void ContactsBox::onPeerSelectedChanged(PeerData *peer, bool checked) {
+	if (checked) {
+		addPeerToMultiSelect(peer);
+		_select->entity()->clearQuery();
+	} else {
+		_select->entity()->removeItem(peer->id);
+	}
+	update();
+}
+
+void ContactsBox::onInvite() {
+	QVector<UserData*> users(_inner->selected());
+	if (users.isEmpty()) {
+		_select->entity()->setInnerFocus();
+		return;
+	}
+
+	App::main()->addParticipants(_inner->chat() ? (PeerData*)_inner->chat() : _inner->channel(), users);
+	if (_inner->chat()) {
+		Ui::hideLayer();
+		Ui::showPeerHistory(_inner->chat(), ShowAtTheEndMsgId);
+	} else {
+		onClose();
+	}
+}
+
+void ContactsBox::onCreate() {
+	if (_saveRequestId) return;
+
+	auto users = _inner->selectedInputs();
+	if (users.isEmpty() || (users.size() == 1 && users.at(0).type() == mtpc_inputUserSelf)) {
+		_select->entity()->setInnerFocus();
+		return;
+	}
+	_saveRequestId = MTP::send(MTPmessages_CreateChat(MTP_vector<MTPInputUser>(users), MTP_string(_creationName)), rpcDone(&ContactsBox::creationDone), rpcFail(&ContactsBox::creationFail));
+}
+
+void ContactsBox::onSaveAdmins() {
+	if (_saveRequestId) return;
+
+	_inner->saving(true);
+	_saveRequestId = MTP::send(MTPmessages_ToggleChatAdmins(_inner->chat()->inputChat, MTP_bool(!_inner->allAdmins())), rpcDone(&ContactsBox::saveAdminsDone), rpcFail(&ContactsBox::saveAdminsFail));
+}
+
+void ContactsBox::saveAdminsDone(const MTPUpdates &result) {
+	App::main()->sentUpdatesReceived(result);
+	saveSelectedAdmins();
+}
+
+void ContactsBox::saveSelectedAdmins() {
+	if (_inner->allAdmins() && !_inner->chat()->participants.isEmpty()) {
+		onClose();
+	} else {
+		_saveRequestId = MTP::send(MTPmessages_GetFullChat(_inner->chat()->inputChat), rpcDone(&ContactsBox::getAdminsDone), rpcFail(&ContactsBox::saveAdminsFail));
+	}
+}
+
+void ContactsBox::getAdminsDone(const MTPmessages_ChatFull &result) {
+	App::api()->processFullPeer(_inner->chat(), result);
+	if (_inner->allAdmins()) {
+		onClose();
+		return;
+	}
+	ChatData::Admins curadmins = _inner->chat()->admins;
+	QVector<UserData*> newadmins = _inner->selected(), appoint;
+	if (!newadmins.isEmpty()) {
+		appoint.reserve(newadmins.size());
+		for (int32 i = 0, l = newadmins.size(); i < l; ++i) {
+			ChatData::Admins::iterator c = curadmins.find(newadmins.at(i));
+			if (c == curadmins.cend()) {
+				if (newadmins.at(i)->id != peerFromUser(_inner->chat()->creator)) {
+					appoint.push_back(newadmins.at(i));
+				}
+			} else {
+				curadmins.erase(c);
+			}
+		}
+	}
+	_saveRequestId = 0;
+
+	for_const (UserData *user, curadmins) {
+		MTP::send(MTPmessages_EditChatAdmin(_inner->chat()->inputChat, user->inputUser, MTP_boolFalse()), rpcDone(&ContactsBox::removeAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10);
+	}
+	for_const (UserData *user, appoint) {
+		MTP::send(MTPmessages_EditChatAdmin(_inner->chat()->inputChat, user->inputUser, MTP_boolTrue()), rpcDone(&ContactsBox::setAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10);
+	}
+	MTP::sendAnything();
+
+	_saveRequestId = curadmins.size() + appoint.size();
+	if (!_saveRequestId) {
+		onClose();
+	}
+}
+
+void ContactsBox::setAdminDone(UserData *user, const MTPBool &result) {
+	if (mtpIsTrue(result)) {
+		if (_inner->chat()->noParticipantInfo()) {
+			App::api()->requestFullPeer(_inner->chat());
+		} else {
+			_inner->chat()->admins.insert(user);
+		}
+	}
+	--_saveRequestId;
+	if (!_saveRequestId) {
+		emit App::main()->peerUpdated(_inner->chat());
+		onClose();
+	}
+}
+
+void ContactsBox::removeAdminDone(UserData *user, const MTPBool &result) {
+	if (mtpIsTrue(result)) {
+		_inner->chat()->admins.remove(user);
+	}
+	--_saveRequestId;
+	if (!_saveRequestId) {
+		emit App::main()->peerUpdated(_inner->chat());
+		onClose();
+	}
+}
+
+bool ContactsBox::saveAdminsFail(const RPCError &error) {
+	if (MTP::isDefaultHandledError(error)) return true;
+	_saveRequestId = 0;
+	_inner->saving(false);
+	if (error.type() == qstr("CHAT_NOT_MODIFIED")) {
+		saveSelectedAdmins();
+	}
+	return false;
+}
+
+bool ContactsBox::editAdminFail(const RPCError &error) {
+	if (MTP::isDefaultHandledError(error)) return true;
+	--_saveRequestId;
+	_inner->chat()->invalidateParticipants();
+	if (!_saveRequestId) {
+		if (error.type() == qstr("USER_RESTRICTED")) {
+			Ui::showLayer(new InformBox(lang(lng_cant_do_this)));
+			return true;
+		}
+		onClose();
+	}
+	return false;
+}
+
+void ContactsBox::onScroll() {
+	_inner->loadProfilePhotos(scrollArea()->scrollTop());
+}
+
+void ContactsBox::creationDone(const MTPUpdates &updates) {
+	Ui::hideLayer();
+
+	App::main()->sentUpdatesReceived(updates);
+	const QVector<MTPChat> *v = 0;
+	switch (updates.type()) {
+	case mtpc_updates: v = &updates.c_updates().vchats.c_vector().v; break;
+	case mtpc_updatesCombined: v = &updates.c_updatesCombined().vchats.c_vector().v; break;
+	default: LOG(("API Error: unexpected update cons %1 (ContactsBox::creationDone)").arg(updates.type())); break;
+	}
+
+	PeerData *peer = 0;
+	if (v && !v->isEmpty() && v->front().type() == mtpc_chat) {
+		peer = App::chat(v->front().c_chat().vid.v);
+		if (peer) {
+			if (!_creationPhoto.isNull()) {
+				App::app()->uploadProfilePhoto(_creationPhoto, peer->id);
+			}
+			Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
+		}
+	} else {
+		LOG(("API Error: chat not found in updates (ContactsBox::creationDone)"));
+	}
+}
+
+bool ContactsBox::creationFail(const RPCError &error) {
+	if (MTP::isDefaultHandledError(error)) return false;
+
+	_saveRequestId = 0;
+	if (error.type() == "NO_CHAT_TITLE") {
+		onClose();
+		return true;
+	} else if (error.type() == "USERS_TOO_FEW") {
+		_select->entity()->setInnerFocus();
+		return true;
+	} else if (error.type() == "PEER_FLOOD") {
+		Ui::showLayer(new InformBox(cantInviteError()), KeepOtherLayers);
+		return true;
+	} else if (error.type() == qstr("USER_RESTRICTED")) {
+		Ui::showLayer(new InformBox(lang(lng_cant_do_this)));
+		return true;
+	}
+	return false;
+}
+
+ContactsBox::Inner::ContactData::ContactData(PeerData *peer, base::lambda_wrap<void()> updateCallback)
+: checkbox(std_::make_unique<Ui::RoundImageCheckbox>(st::contactsPhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer))) {
+}
+
+ContactsBox::Inner::Inner(QWidget *parent, CreatingGroupType creating) : ScrolledWidget(parent)
 , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
 , _newItemHeight(creating == CreatingGroupNone ? st::contactsNewItemHeight : 0)
 , _creating(creating)
@@ -48,7 +565,7 @@ ContactsInner::ContactsInner(CreatingGroupType creating) : TWidget()
 	init();
 }
 
-ContactsInner::ContactsInner(ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already) : TWidget()
+ContactsBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already) : ScrolledWidget(parent)
 , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
 , _channel(channel)
 , _membersFilter(membersFilter)
@@ -66,19 +583,19 @@ namespace {
 	}
 }
 
-ContactsInner::ContactsInner(ChatData *chat, MembersFilter membersFilter) : TWidget()
+ContactsBox::Inner::Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter) : ScrolledWidget(parent)
 , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
 , _chat(chat)
 , _membersFilter(membersFilter)
 , _allAdmins(this, lang(lng_chat_all_members_admins), !_chat->adminsEnabled(), st::contactsAdminCheckbox)
-, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right() - st::contactsCheckPosition.x() * 2 - st::contactsCheckIcon.pxWidth())
+, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right())
 , _aboutAllAdmins(st::boxTextFont, lang(lng_chat_about_all_admins), _defaultOptions, _aboutWidth)
 , _aboutAdmins(st::boxTextFont, lang(lng_chat_about_admins), _defaultOptions, _aboutWidth)
-, _customList((membersFilter == MembersFilterRecent) ? std_::unique_ptr<Dialogs::IndexedList>() : std_::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Add))
-, _contacts((membersFilter == MembersFilterRecent) ? App::main()->contactsList() : _customList.get())
+, _customList((membersFilter == MembersFilter::Recent) ? std_::unique_ptr<Dialogs::IndexedList>() : std_::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Add))
+, _contacts((membersFilter == MembersFilter::Recent) ? App::main()->contactsList() : _customList.get())
 , _addContactLnk(this, lang(lng_add_contact_button)) {
 	initList();
-	if (membersFilter == MembersFilterAdmins) {
+	if (membersFilter == MembersFilter::Admins) {
 		_newItemHeight = st::contactsNewItemHeight + qMax(_aboutAllAdmins.countHeight(_aboutWidth), _aboutAdmins.countHeight(_aboutWidth)) + st::contactsAboutHeight;
 		if (_contacts->isEmpty()) {
 			App::api()->requestFullPeer(_chat);
@@ -88,7 +605,7 @@ ContactsInner::ContactsInner(ChatData *chat, MembersFilter membersFilter) : TWid
 }
 
 template <typename FilterCallback>
-void ContactsInner::addDialogsToList(FilterCallback callback) {
+void ContactsBox::Inner::addDialogsToList(FilterCallback callback) {
 	auto v = App::main()->dialogsList();
 	for_const (auto row, *v) {
 		auto peer = row->history()->peer;
@@ -98,7 +615,7 @@ void ContactsInner::addDialogsToList(FilterCallback callback) {
 	}
 }
 
-ContactsInner::ContactsInner(UserData *bot) : TWidget()
+ContactsBox::Inner::Inner(QWidget *parent, UserData *bot) : ScrolledWidget(parent)
 , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
 , _bot(bot)
 , _allAdmins(this, lang(lng_chat_all_members_admins), false, st::contactsAdminCheckbox)
@@ -128,7 +645,7 @@ ContactsInner::ContactsInner(UserData *bot) : TWidget()
 	init();
 }
 
-void ContactsInner::init() {
+void ContactsBox::Inner::init() {
 	subscribe(FileDownload::ImageLoaded(), [this] { update(); });
 	connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact()));
 	connect(&_allAdmins, SIGNAL(changed()), this, SLOT(onAllAdminsChanged()));
@@ -148,8 +665,8 @@ void ContactsInner::init() {
 	connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*)));
 }
 
-void ContactsInner::initList() {
-	if (!_chat || _membersFilter != MembersFilterAdmins) return;
+void ContactsBox::Inner::initList() {
+	if (!_chat || _membersFilter != MembersFilter::Admins) return;
 
 	QList<UserData*> admins, others;
 	admins.reserve(_chat->admins.size() + 1);
@@ -161,7 +678,9 @@ void ContactsInner::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(), true);
+			if (!_checkedContacts.contains(i.key())) {
+				_checkedContacts.insert(i.key());
+			}
 		} else {
 			others.push_back(i.key());
 		}
@@ -181,14 +700,14 @@ void ContactsInner::initList() {
 	}
 }
 
-void ContactsInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
+void ContactsBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
 	if (bot()) {
 		_contacts->peerNameChanged(peer, oldNames, oldChars);
 	}
 	peerUpdated(peer);
 }
 
-void ContactsInner::onAddBot() {
+void ContactsBox::Inner::onAddBot() {
 	if (auto &info = _bot->botInfo) {
 		if (!info->shareGameShortName.isEmpty()) {
 			MTPmessages_SendMedia::Flags sendFlags = 0;
@@ -212,27 +731,27 @@ void ContactsInner::onAddBot() {
 	Ui::showPeerHistory(_addToPeer, ShowAtUnreadMsgId);
 }
 
-void ContactsInner::onAddAdmin() {
+void ContactsBox::Inner::onAddAdmin() {
 	if (_addAdminRequestId) return;
-	_addAdminRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _addAdmin->inputUser, MTP_channelRoleEditor()), rpcDone(&ContactsInner::addAdminDone), rpcFail(&ContactsInner::addAdminFail));
+	_addAdminRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _addAdmin->inputUser, MTP_channelRoleEditor()), rpcDone(&Inner::addAdminDone), rpcFail(&Inner::addAdminFail));
 }
 
-void ContactsInner::onNoAddAdminBox(QObject *obj) {
+void ContactsBox::Inner::onNoAddAdminBox(QObject *obj) {
 	if (obj == _addAdminBox) {
 		_addAdminBox = 0;
 	}
 }
 
-void ContactsInner::onAllAdminsChanged() {
-	if (_saving) {
-		if (_allAdmins.checked() != _allAdminsChecked) {
-			_allAdmins.setChecked(_allAdminsChecked);
-		}
+void ContactsBox::Inner::onAllAdminsChanged() {
+	if (_saving && _allAdmins.checked() != _allAdminsChecked) {
+		_allAdmins.setChecked(_allAdminsChecked);
+	} else if (_allAdminsChangedCallback) {
+		_allAdminsChangedCallback();
 	}
 	update();
 }
 
-void ContactsInner::addAdminDone(const MTPUpdates &result, mtpRequestId req) {
+void ContactsBox::Inner::addAdminDone(const MTPUpdates &result, mtpRequestId req) {
 	if (App::main()) App::main()->sentUpdatesReceived(result);
 	if (req != _addAdminRequestId) return;
 
@@ -257,7 +776,7 @@ void ContactsInner::addAdminDone(const MTPUpdates &result, mtpRequestId req) {
 	emit adminAdded();
 }
 
-bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) {
+bool ContactsBox::Inner::addAdminFail(const RPCError &error, mtpRequestId req) {
 	if (MTP::isDefaultHandledError(error)) return false;
 
 	if (req != _addAdminRequestId) return true;
@@ -276,16 +795,16 @@ bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) {
 	return true;
 }
 
-void ContactsInner::saving(bool flag) {
+void ContactsBox::Inner::saving(bool flag) {
 	_saving = flag;
 	_allAdminsChecked = _allAdmins.checked();
 	update();
 }
 
-void ContactsInner::peerUpdated(PeerData *peer) {
+void ContactsBox::Inner::peerUpdated(PeerData *peer) {
 	if (_chat && (!peer || peer == _chat)) {
 		bool inited = false;
-		if (_membersFilter == MembersFilterAdmins && _contacts->isEmpty() && !_chat->participants.isEmpty()) {
+		if (_membersFilter == MembersFilter::Admins && _contacts->isEmpty() && !_chat->participants.isEmpty()) {
 			initList();
 			inited = true;
 		}
@@ -333,7 +852,7 @@ void ContactsInner::peerUpdated(PeerData *peer) {
 	}
 }
 
-void ContactsInner::loadProfilePhotos(int32 yFrom) {
+void ContactsBox::Inner::loadProfilePhotos(int32 yFrom) {
 	int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5;
 	MTP::clearLoaderPriorities();
 
@@ -364,47 +883,43 @@ void ContactsInner::loadProfilePhotos(int32 yFrom) {
 	}
 }
 
-ContactsInner::ContactData *ContactsInner::contactData(Dialogs::Row *row) {
+ContactsBox::Inner::ContactData *ContactsBox::Inner::contactData(Dialogs::Row *row) {
 	ContactData *data = (ContactData*)row->attached;
 	if (!data) {
 		PeerData *peer = row->history()->peer;
 		ContactsData::const_iterator i = _contactsData.constFind(peer);
 		if (i == _contactsData.cend()) {
-			_contactsData.insert(peer, data = new ContactData());
+			data = usingMultiSelect() ? new ContactData(peer, [this, peer] { updateRowWithPeer(peer); }) : new ContactData();
+			_contactsData.insert(peer, data);
 			if (peer->isUser()) {
 				if (_chat) {
-					if (_membersFilter == MembersFilterRecent) {
-						data->inchat = _chat->participants.contains(peer->asUser());
-					} else {
-						data->inchat = false;
+					if (_membersFilter == MembersFilter::Recent) {
+						data->disabledChecked = _chat->participants.contains(peer->asUser());
 					}
 				} else if (_creating == CreatingGroupGroup) {
-					data->inchat = (peerToUser(peer->id) == MTP::authedId());
+					data->disabledChecked = (peerToUser(peer->id) == MTP::authedId());
 				} else if (_channel) {
-					data->inchat = (peerToUser(peer->id) == MTP::authedId()) || _already.contains(peer->asUser());
-				} else {
-					data->inchat = false;
+					data->disabledChecked = (peerToUser(peer->id) == MTP::authedId()) || _already.contains(peer->asUser());
 				}
-			} else {
-				data->inchat = false;
 			}
-			data->onlineColor = false;
-			data->check = _checkedContacts.contains(peer);
+			if (usingMultiSelect() && _checkedContacts.contains(peer)) {
+				data->checkbox->setChecked(true, Ui::RoundImageCheckbox::SetStyle::Fast);
+			}
 			data->name.setText(st::contactsNameFont, peer->name, _textNameOptions);
 			if (peer->isUser()) {
-				data->online = App::onlineText(peer->asUser(), _time);
-				data->onlineColor = App::onlineColorUse(peer->asUser(), _time);
+				data->statusText = App::onlineText(peer->asUser(), _time);
+				data->statusHasOnlineColor = App::onlineColorUse(peer->asUser(), _time);
 			} else if (peer->isChat()) {
 				ChatData *chat = peer->asChat();
 				if (!chat->amIn()) {
-					data->online = lang(lng_chat_status_unaccessible);
+					data->statusText = lang(lng_chat_status_unaccessible);
 				} else {
-					data->online = lng_chat_status_members(lt_count, chat->count);
+					data->statusText = lng_chat_status_members(lt_count, chat->count);
 				}
 			} else if (peer->isMegagroup()) {
-				data->online = lang(lng_group_status);
+				data->statusText = lang(lng_group_status);
 			} else if (peer->isChannel()) {
-				data->online = lang(lng_channel_status);
+				data->statusText = lang(lng_channel_status);
 			}
 		} else {
 			data = i.value();
@@ -414,52 +929,61 @@ ContactsInner::ContactData *ContactsInner::contactData(Dialogs::Row *row) {
 	return data;
 }
 
-void ContactsInner::paintDialog(Painter &p, PeerData *peer, ContactData *data, bool sel) {
+void ContactsBox::Inner::paintDialog(Painter &p, uint64 ms, PeerData *peer, ContactData *data, bool sel) {
 	UserData *user = peer->asUser();
 
-	bool inverse = data->inchat || data->check;
-	if (_chat && _membersFilter == MembersFilterAdmins) {
-		inverse = false;
+	if (_chat && _membersFilter == MembersFilter::Admins) {
 		if (_allAdmins.checked() || peer->id == peerFromUser(_chat->creator) || _saving) {
 			sel = false;
 		}
 	} else {
-		if (data->inchat || data->check || selectedCount() >= Global::MegagroupSizeMax()) {
+		if (data->disabledChecked || selectedCount() >= Global::MegagroupSizeMax()) {
 			sel = false;
 		}
 	}
-	p.fillRect(0, 0, width(), _rowHeight, inverse ? st::contactsBgActive : (sel ? st::contactsBgOver : st::white));
-	p.setPen(inverse ? st::white : st::black);
-	peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width());
 
-	int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
-	int32 iconw = (_chat || _creating != CreatingGroupNone) ? (st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth()) : 0;
-	int32 namew = width() - namex - st::contactsPadding.right() - iconw;
+	auto paintDisabledCheck = data->disabledChecked;
+	if (_chat && _membersFilter == MembersFilter::Admins) {
+		if (peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) {
+			paintDisabledCheck = true;
+		}
+	}
+
+	auto checkedRatio = 0.;
+	p.fillRect(0, 0, width(), _rowHeight, sel ? st::contactsBgOver : st::white);
+	if (paintDisabledCheck) {
+		paintDisabledCheckUserpic(p, peer, st::contactsPadding.left(), st::contactsPadding.top(), width());
+	} else if (usingMultiSelect()) {
+		checkedRatio = data->checkbox->checkedAnimationRatio();
+		data->checkbox->paint(p, ms, st::contactsPadding.left(), st::contactsPadding.top(), width());
+	} else {
+		peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width());
+	}
+
+	int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
+	int namew = width() - namex - st::contactsPadding.right();
 	if (peer->isVerified()) {
 		auto icon = &st::dialogsVerifiedIcon;
 		namew -= icon->width();
 		icon->paint(p, namex + qMin(data->name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width());
 	}
+	if (checkedRatio > 0) {
+		if (checkedRatio < 1) {
+			p.setPen(style::interpolate(st::black, st::contactsNameCheckedFg, checkedRatio));
+		} else {
+			p.setPen(st::contactsNameCheckedFg);
+		}
+	} else {
+		p.setPen(st::black);
+	}
 	data->name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width());
 
-	if (_chat || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilterAdmins))) {
-		if (_chat && _membersFilter == MembersFilterAdmins) {
-			if (sel || data->check || peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) {
-				if (!data->check || peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) p.setOpacity(0.5);
-				p.drawSpriteRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + st::contactsCheckPosition.y(), width(), st::contactsCheckIcon);
-				if (!data->check || peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) p.setOpacity(1);
-			}
-		} else if (sel || data->check) {
-			p.drawSpriteRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + st::contactsCheckPosition.y(), width(), (data->check ? st::contactsCheckActiveIcon : st::contactsCheckIcon));
-		}
-	}
-
-	bool uname = (user || peer->isChannel()) && (data->online.at(0) == '@');
+	bool uname = (user || peer->isChannel()) && (data->statusText.at(0) == '@');
 	p.setFont(st::contactsStatusFont->f);
-	if (uname && !data->inchat && !data->check && !_lastQuery.isEmpty() && peer->userName().startsWith(_lastQuery, Qt::CaseInsensitive)) {
-		int32 availw = width() - namex - st::contactsPadding.right() - iconw;
+	if (uname && !_lastQuery.isEmpty() && peer->userName().startsWith(_lastQuery, Qt::CaseInsensitive)) {
+		int availw = width() - namex - st::contactsPadding.right();
 		QString first = '@' + peer->userName().mid(0, _lastQuery.size()), second = peer->userName().mid(_lastQuery.size());
-		int32 w = st::contactsStatusFont->width(first);
+		int w = st::contactsStatusFont->width(first);
 		if (w >= availw || second.isEmpty()) {
 			p.setPen(st::contactsStatusFgOnline);
 			p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), st::contactsStatusFont->elided(first, availw));
@@ -472,25 +996,59 @@ void ContactsInner::paintDialog(Painter &p, PeerData *peer, ContactData *data, b
 			p.drawTextLeft(namex + w, st::contactsPadding.top() + st::contactsStatusTop, width() + w, second);
 		}
 	} else {
-		if (inverse) {
-			p.setPen(st::white);
-		} else if ((user && (uname || data->onlineColor)) || (peer->isChannel() && uname)) {
+		if ((user && (uname || data->statusHasOnlineColor)) || (peer->isChannel() && uname)) {
 			p.setPen(st::contactsStatusFgOnline);
 		} else {
 			p.setPen(sel ? st::contactsStatusFgOver : st::contactsStatusFg);
 		}
-		p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->online);
+		p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->statusText);
 	}
 }
 
-void ContactsInner::paintEvent(QPaintEvent *e) {
+// Emulates Ui::RoundImageCheckbox::paint() in a checked state.
+void ContactsBox::Inner::paintDisabledCheckUserpic(Painter &p, PeerData *peer, int x, int y, int outerWidth) const {
+	auto userpicRadius = st::contactsPhotoCheckbox.imageSmallRadius;
+	auto userpicShift = st::contactsPhotoCheckbox.imageRadius - userpicRadius;
+	auto userpicDiameter = st::contactsPhotoCheckbox.imageRadius * 2;
+	auto userpicLeft = x + userpicShift;
+	auto userpicTop = y + userpicShift;
+	auto userpicEllipse = rtlrect(x, y, userpicDiameter, userpicDiameter, outerWidth);
+	auto userpicBorderPen = st::contactsPhotoDisabledCheckFg->p;
+	userpicBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth);
+
+	auto iconDiameter = 2 * st::contactsPhotoCheckbox.checkRadius;
+	auto iconLeft = x + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter;
+	auto iconTop = y + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter;
+	auto iconEllipse = rtlrect(iconLeft, iconTop, iconDiameter, iconDiameter, outerWidth);
+	auto iconBorderPen = st::contactsPhotoCheckbox.checkBorder->p;
+	iconBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth);
+
+	peer->paintUserpicLeft(p, userpicRadius * 2, userpicLeft, userpicTop, width());
+
+	p.setRenderHint(QPainter::HighQualityAntialiasing, true);
+
+	p.setPen(userpicBorderPen);
+	p.setBrush(Qt::NoBrush);
+	p.drawEllipse(userpicEllipse);
+
+	p.setPen(iconBorderPen);
+	p.setBrush(st::contactsPhotoDisabledCheckFg);
+	p.drawEllipse(iconEllipse);
+
+	p.setRenderHint(QPainter::HighQualityAntialiasing, false);
+
+	st::contactsPhotoCheckbox.checkIcon.paint(p, iconEllipse.topLeft(), outerWidth);
+}
+
+void ContactsBox::Inner::paintEvent(QPaintEvent *e) {
 	QRect r(e->rect());
 	Painter p(this);
 
 	p.setClipRect(r);
 	_time = unixtime();
-	p.fillRect(r, st::white->b);
+	p.fillRect(r, st::white);
 
+	uint64 ms = getms();
 	int32 yFrom = r.y(), yTo = r.y() + r.height();
 	if (_filter.isEmpty()) {
 		if (!_contacts->isEmpty() || !_byUsername.isEmpty()) {
@@ -500,13 +1058,12 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 					p.fillRect(0, _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, width(), st::lineWidth, st::shadowColor);
 					p.setPen(st::black);
 					p.drawTextLeft(st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_chat_all_members_admins));
-					int32 iconw = st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth();
-					int32 aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right() - iconw;
+					int aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right();
 					(_allAdmins.checked() ? _aboutAllAdmins : _aboutAdmins).draw(p, st::contactsPadding.left(), st::contactsNewItemHeight + st::contactsAboutTop, aboutw);
 				} else {
 					p.fillRect(0, 0, width(), st::contactsNewItemHeight, (_newItemSel ? st::contactsBgOver : st::white)->b);
 					p.setFont(st::contactsNameFont);
-					p.drawSpriteLeft(st::contactsNewItemIconPosition.x(), st::contactsNewItemIconPosition.y(), width(), st::contactsNewItemIcon);
+					st::contactsNewItemIcon.paint(p, 0, 0, width());
 					p.setPen(st::contactsNewItemFg);
 					p.drawTextLeft(st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_add_contact_button));
 				}
@@ -521,7 +1078,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 					if ((*i)->pos() * _rowHeight >= yTo) {
 						break;
 					}
-					paintDialog(p, (*i)->history()->peer, contactData(*i), (*i == _sel));
+					paintDialog(p, ms, (*i)->history()->peer, contactData(*i), (*i == _sel));
 					p.translate(0, _rowHeight);
 				}
 				yFrom -= _contacts->size() * _rowHeight;
@@ -541,7 +1098,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 				int32 to = ceilclamp(yTo, _rowHeight, 0, _byUsername.size());
 				p.translate(0, from * _rowHeight);
 				for (; from < to; ++from) {
-					paintDialog(p, _byUsername[from], d_byUsername[from], (_byUsernameSel == from));
+					paintDialog(p, ms, _byUsername[from], d_byUsername[from], (_byUsernameSel == from));
 					p.translate(0, _rowHeight);
 				}
 			}
@@ -550,14 +1107,13 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 			int32 skip = 0;
 			if (bot()) {
 				text = lang((cDialogsReceived() && !_searching) ? (sharingBotGame() ? lng_bot_no_chats : lng_bot_no_groups) : lng_contacts_loading);
-			} else if (_chat && _membersFilter == MembersFilterAdmins) {
+			} else if (_chat && _membersFilter == MembersFilter::Admins) {
 				text = lang(lng_contacts_loading);
 				p.fillRect(0, 0, width(), _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, st::contactsAboutBg);
 				p.fillRect(0, _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, width(), st::lineWidth, st::shadowColor);
 				p.setPen(st::black);
 				p.drawTextLeft(st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_chat_all_members_admins));
-				int32 iconw = st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth();
-				int32 aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right() - iconw;
+				int aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right();
 				(_allAdmins.checked() ? _aboutAllAdmins : _aboutAdmins).draw(p, st::contactsPadding.left(), st::contactsNewItemHeight + st::contactsAboutTop, aboutw);
 				p.translate(0, _newItemHeight);
 			} else if (cContactsReceived() && !_searching) {
@@ -577,7 +1133,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 			QString text;
 			if (bot()) {
 				text = lang((cDialogsReceived() && !_searching) ? (sharingBotGame() ? lng_bot_chats_not_found : lng_bot_groups_not_found) : lng_contacts_loading);
-			} else if (_chat && _membersFilter == MembersFilterAdmins) {
+			} else if (_chat && _membersFilter == MembersFilter::Admins) {
 				text = lang(_chat->participants.isEmpty() ? lng_contacts_loading : lng_contacts_not_found);
 			} else {
 				text = lang((cContactsReceived() && !_searching) ? lng_contacts_not_found : lng_contacts_loading);
@@ -589,7 +1145,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 				int32 to = ceilclamp(yTo, _rowHeight, 0, _filtered.size());
 				p.translate(0, from * _rowHeight);
 				for (; from < to; ++from) {
-					paintDialog(p, _filtered[from]->history()->peer, contactData(_filtered[from]), (_filteredSel == from));
+					paintDialog(p, ms, _filtered[from]->history()->peer, contactData(_filtered[from]), (_filteredSel == from));
 					p.translate(0, _rowHeight);
 				}
 			}
@@ -606,7 +1162,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 				int32 to = ceilclamp(yTo, _rowHeight, 0, _byUsernameFiltered.size());
 				p.translate(0, from * _rowHeight);
 				for (; from < to; ++from) {
-					paintDialog(p, _byUsernameFiltered[from], d_byUsernameFiltered[from], (_byUsernameSel == from));
+					paintDialog(p, ms, _byUsernameFiltered[from], d_byUsernameFiltered[from], (_byUsernameSel == from));
 					p.translate(0, _rowHeight);
 				}
 			}
@@ -614,32 +1170,77 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 	}
 }
 
-void ContactsInner::enterEvent(QEvent *e) {
+void ContactsBox::Inner::enterEvent(QEvent *e) {
 	setMouseTracking(true);
 }
 
-void ContactsInner::updateSelectedRow() {
+int ContactsBox::Inner::getSelectedRowTop() const {
 	if (_filter.isEmpty()) {
-		if (_newItemSel) {
-			update(0, 0, width(), st::contactsNewItemHeight);
-		}
 		if (_sel) {
-			update(0, _newItemHeight + _sel->pos() * _rowHeight, width(), _rowHeight);
-		}
-		if (_byUsernameSel >= 0) {
-			update(0, _newItemHeight + _contacts->size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight, width(), _rowHeight);
+			return _newItemHeight + (_sel->pos() * _rowHeight);
+		} else if (_byUsernameSel >= 0) {
+			return _newItemHeight + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (_byUsernameSel * _rowHeight);
 		}
 	} else {
 		if (_filteredSel >= 0) {
-			update(0, _filteredSel * _rowHeight, width(), _rowHeight);
+			return (_filteredSel * _rowHeight);
+		} else if (_byUsernameSel >= 0) {
+			return (_filtered.size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight);
 		}
-		if (_byUsernameSel >= 0) {
-			update(0, _filtered.size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight, width(), _rowHeight);
+	}
+	return -1;
+}
+
+void ContactsBox::Inner::updateSelectedRow() {
+	if (_filter.isEmpty() && _newItemSel) {
+		update(0, 0, width(), st::contactsNewItemHeight);
+	} else {
+		auto rowTop = getSelectedRowTop();
+		if (rowTop >= 0) {
+			updateRowWithTop(rowTop);
 		}
 	}
 }
 
-void ContactsInner::leaveEvent(QEvent *e) {
+void ContactsBox::Inner::updateRowWithTop(int rowTop) {
+	update(0, rowTop, width(), _rowHeight);
+}
+
+int ContactsBox::Inner::getRowTopWithPeer(PeerData *peer) const {
+	if (_filter.isEmpty()) {
+		for (auto i = _contacts->cbegin(), end = _contacts->cend(); i != end; ++i) {
+			if ((*i)->history()->peer == peer) {
+				return _newItemHeight + ((*i)->pos() * _rowHeight);
+			}
+		}
+		for (auto i = 0, count = _byUsername.size(); i != count; ++i) {
+			if (_byUsername[i] == peer) {
+				return _newItemHeight + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (i * _rowHeight);
+			}
+		}
+	} else {
+		for (auto i = 0, count = _filtered.size(); i != count; ++i) {
+			if (_filtered[i]->history()->peer == peer) {
+				return (i * _rowHeight);
+			}
+		}
+		for (auto i = 0, count = _byUsernameFiltered.size(); i != count; ++i) {
+			if (_byUsernameFiltered[i] == peer) {
+				return (_contacts->size() * _rowHeight) + st::searchedBarHeight + (i * _rowHeight);
+			}
+		}
+	}
+	return -1;
+}
+
+void ContactsBox::Inner::updateRowWithPeer(PeerData *peer) {
+	auto rowTop = getRowTopWithPeer(peer);
+	if (rowTop >= 0) {
+		updateRowWithTop(rowTop);
+	}
+}
+
+void ContactsBox::Inner::leaveEvent(QEvent *e) {
 	_mouseSel = false;
 	setMouseTracking(false);
 	if (_newItemSel || _sel || _filteredSel >= 0 || _byUsernameSel >= 0) {
@@ -650,51 +1251,57 @@ void ContactsInner::leaveEvent(QEvent *e) {
 	}
 }
 
-void ContactsInner::mouseMoveEvent(QMouseEvent *e) {
+void ContactsBox::Inner::mouseMoveEvent(QMouseEvent *e) {
 	_mouseSel = true;
 	_lastMousePos = e->globalPos();
-	updateSel();
+	updateSelection();
 }
 
-void ContactsInner::mousePressEvent(QMouseEvent *e) {
+void ContactsBox::Inner::mousePressEvent(QMouseEvent *e) {
 	_mouseSel = true;
 	_lastMousePos = e->globalPos();
-	updateSel();
+	updateSelection();
 	if (e->button() == Qt::LeftButton) {
 		chooseParticipant();
 	}
 }
 
-void ContactsInner::chooseParticipant() {
+void ContactsBox::Inner::chooseParticipant() {
 	if (_saving) return;
-	bool addingAdmin = (_channel && _membersFilter == MembersFilterAdmins);
-	if (!addingAdmin && (_chat || _creating != CreatingGroupNone)) {
+	bool addingAdmin = (_channel && _membersFilter == MembersFilter::Admins);
+	if (!addingAdmin && usingMultiSelect()) {
 		_time = unixtime();
 		if (_filter.isEmpty()) {
 			if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) {
-				if (d_byUsername[_byUsernameSel]->inchat) return;
-				changeCheckState(d_byUsername[_byUsernameSel], _byUsername[_byUsernameSel]);
-			} else {
-				if (!_sel || contactData(_sel)->inchat) return;
+				auto data = d_byUsername[_byUsernameSel];
+				auto peer = _byUsername[_byUsernameSel];
+				if (data->disabledChecked) return;
+
+				changeCheckState(data, peer);
+			} else if (_sel) {
+				auto data = contactData(_sel);
+				auto peer = _sel->history()->peer;
+				if (data->disabledChecked) return;
+
 				changeCheckState(_sel);
 			}
 		} else {
 			if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) {
-				if (d_byUsernameFiltered[_byUsernameSel]->inchat) return;
-				changeCheckState(d_byUsernameFiltered[_byUsernameSel], _byUsernameFiltered[_byUsernameSel]);
+				auto data = d_byUsernameFiltered[_byUsernameSel];
+				auto peer = _byUsernameFiltered[_byUsernameSel];
+				if (data->disabledChecked) return;
 
-				ContactData *moving = d_byUsernameFiltered[_byUsernameSel];
-				int32 i = 0, l = d_byUsername.size();
+				int i = 0, l = d_byUsername.size();
 				for (; i < l; ++i) {
-					if (d_byUsername[i] == moving) {
+					if (d_byUsername[i] == data) {
 						break;
 					}
 				}
 				if (i == l) {
-					d_byUsername.push_back(moving);
-					_byUsername.push_back(_byUsernameFiltered[_byUsernameSel]);
+					d_byUsername.push_back(data);
+					_byUsername.push_back(peer);
 					for (i = 0, l = _byUsernameDatas.size(); i < l;) {
-						if (_byUsernameDatas[i] == moving) {
+						if (_byUsernameDatas[i] == data) {
 							_byUsernameDatas.removeAt(i);
 							--l;
 						} else {
@@ -702,11 +1309,15 @@ void ContactsInner::chooseParticipant() {
 						}
 					}
 				}
-			} else {
-				if (_filteredSel < 0 || _filteredSel >= _filtered.size() || contactData(_filtered[_filteredSel])->inchat) return;
-				changeCheckState(_filtered[_filteredSel]);
+
+				changeCheckState(data, peer);
+			} else if (_filteredSel >= 0 && _filteredSel < _filtered.size()) {
+				auto data = contactData(_filtered[_filteredSel]);
+				auto peer = _filtered[_filteredSel]->history()->peer;
+				if (data->disabledChecked) return;
+
+				changeCheckState(data, peer);
 			}
-			emit selectAllQuery();
 		}
 	} else {
 		PeerData *peer = 0;
@@ -765,30 +1376,51 @@ void ContactsInner::chooseParticipant() {
 	update();
 }
 
-void ContactsInner::changeCheckState(Dialogs::Row *row) {
+void ContactsBox::Inner::changeCheckState(Dialogs::Row *row) {
 	changeCheckState(contactData(row), row->history()->peer);
 }
 
-void ContactsInner::changeCheckState(ContactData *data, PeerData *peer) {
-	int32 cnt = _selCount;
-	if (data->check) {
-		data->check = false;
-		_checkedContacts.remove(peer);
-		--_selCount;
+void ContactsBox::Inner::changeCheckState(ContactData *data, PeerData *peer) {
+	t_assert(usingMultiSelect());
+
+	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())) {
-		data->check = true;
-		_checkedContacts.insert(peer, true);
-		++_selCount;
+		changePeerCheckState(data, peer, true);
 	} else if (_channel && !_channel->isMegagroup()) {
 		Ui::showLayer(new MaxInviteBox(_channel->inviteLink()), KeepOtherLayers);
 	} else if (!_channel && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) {
 		Ui::showLayer(new InformBox(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers);
 	}
-	if (cnt != _selCount) emit chosenChanged();
 }
 
-int32 ContactsInner::selectedCount() const {
-	int32 result = _selCount;
+void ContactsBox::Inner::peerUnselected(PeerData *peer) {
+	// If data is nullptr we simply won't do anything.
+	auto data = _contactsData.value(peer, nullptr);
+	changePeerCheckState(data, peer, false, ChangeStateWay::SkipCallback);
+}
+
+void ContactsBox::Inner::setPeerSelectedChangedCallback(base::lambda_unique<void(PeerData *peer, bool selected)> callback) {
+	_peerSelectedChangedCallback = std_::move(callback);
+}
+
+void ContactsBox::Inner::changePeerCheckState(ContactData *data, PeerData *peer, bool checked, ChangeStateWay useCallback) {
+	if (data) {
+		data->checkbox->setChecked(checked);
+	}
+	if (checked) {
+		_checkedContacts.insert(peer);
+	} else {
+		_checkedContacts.remove(peer);
+	}
+	if (useCallback != ChangeStateWay::SkipCallback && _peerSelectedChangedCallback) {
+		_peerSelectedChangedCallback(peer, checked);
+	}
+}
+
+int32 ContactsBox::Inner::selectedCount() const {
+	auto result = _checkedContacts.size();
 	if (_chat) {
 		result += qMax(_chat->count, 1);
 	} else if (_channel) {
@@ -799,7 +1431,7 @@ int32 ContactsInner::selectedCount() const {
 	return result;
 }
 
-void ContactsInner::updateSel() {
+void ContactsBox::Inner::updateSelection() {
 	if (!_mouseSel) return;
 
 	QPoint p(mapFromGlobal(_lastMousePos));
@@ -807,7 +1439,7 @@ void ContactsInner::updateSel() {
 	if (_filter.isEmpty()) {
 		bool newItemSel = false;
 		if (_newItemHeight) {
-			if (in && (p.y() >= 0) && (p.y() < _newItemHeight) && !(_chat && _membersFilter == MembersFilterAdmins)) {
+			if (in && (p.y() >= 0) && (p.y() < _newItemHeight) && !(_chat && _membersFilter == MembersFilter::Admins)) {
 				newItemSel = true;
 			}
 			p.setY(p.y() - _newItemHeight);
@@ -835,7 +1467,7 @@ void ContactsInner::updateSel() {
 	}
 }
 
-void ContactsInner::updateFilter(QString filter) {
+void ContactsBox::Inner::updateFilter(QString filter) {
 	_lastQuery = filter.toLower().trimmed();
 	filter = textSearchKey(filter);
 
@@ -933,14 +1565,14 @@ void ContactsInner::updateFilter(QString filter) {
 			}
 			_filteredSel = -1;
 			if (!_filtered.isEmpty()) {
-				for (_filteredSel = 0; (_filteredSel < _filtered.size()) && contactData(_filtered[_filteredSel])->inchat;) {
+				for (_filteredSel = 0; (_filteredSel < _filtered.size()) && contactData(_filtered[_filteredSel])->disabledChecked;) {
 					++_filteredSel;
 				}
 				if (_filteredSel == _filtered.size()) _filteredSel = -1;
 			}
 			_byUsernameSel = -1;
 			if (_filteredSel < 0 && !_byUsernameFiltered.isEmpty()) {
-				for (_byUsernameSel = 0; (_byUsernameSel < _byUsernameFiltered.size()) && d_byUsernameFiltered[_byUsernameSel]->inchat;) {
+				for (_byUsernameSel = 0; (_byUsernameSel < _byUsernameFiltered.size()) && d_byUsernameFiltered[_byUsernameSel]->disabledChecked;) {
 					++_byUsernameSel;
 				}
 				if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1;
@@ -948,7 +1580,7 @@ void ContactsInner::updateFilter(QString filter) {
 			_mouseSel = false;
 			refresh();
 
-			if ((!bot() || sharingBotGame()) && (!_chat || _membersFilter != MembersFilterAdmins)) {
+			if ((!bot() || sharingBotGame()) && (!_chat || _membersFilter != MembersFilter::Admins)) {
 				_searching = true;
 				emit searchByUsername();
 			}
@@ -958,7 +1590,7 @@ void ContactsInner::updateFilter(QString filter) {
 	}
 }
 
-void ContactsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) {
+void ContactsBox::Inner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) {
 	if (!_filter.isEmpty()) {
 		for (FilteredDialogs::iterator i = _filtered.begin(), e = _filtered.end(); i != e;) {
 			if (*i == oldRow) { // this row is shown in filtered and maybe is in contacts!
@@ -986,64 +1618,66 @@ void ContactsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newR
 	resize(width(), newh);
 }
 
-void ContactsInner::peopleReceived(const QString &query, const QVector<MTPPeer> &people) {
+void ContactsBox::Inner::peopleReceived(const QString &query, const QVector<MTPPeer> &people) {
 	_lastQuery = query.toLower().trimmed();
 	if (_lastQuery.at(0) == '@') _lastQuery = _lastQuery.mid(1);
 	int32 already = _byUsernameFiltered.size();
 	_byUsernameFiltered.reserve(already + people.size());
 	d_byUsernameFiltered.reserve(already + people.size());
 	for (QVector<MTPPeer>::const_iterator i = people.cbegin(), e = people.cend(); i != e; ++i) {
-		PeerId peerId = peerFromMTP(*i);
-		int32 j = 0;
+		auto peerId = peerFromMTP(*i);
+		int j = 0;
 		for (; j < already; ++j) {
 			if (_byUsernameFiltered[j]->id == peerId) break;
 		}
 		if (j == already) {
-			PeerData *p = App::peer(peerId);
-			if (!p) continue;
+			auto peer = App::peer(peerId);
+			if (!peer) continue;
 
 			if (_channel || _chat || _creating != CreatingGroupNone) {
-				if (p->isUser()) {
-					if (p->asUser()->botInfo) {
+				if (peer->isUser()) {
+					if (peer->asUser()->botInfo) {
 						if (_chat || _creating == CreatingGroupGroup) { // skip bot's that can't be invited to groups
-							if (p->asUser()->botInfo->cantJoinGroups) continue;
+							if (peer->asUser()->botInfo->cantJoinGroups) continue;
 						}
 						if (_channel) {
-							if (!_channel->isMegagroup() && _membersFilter != MembersFilterAdmins) continue;
+							if (!_channel->isMegagroup() && _membersFilter != MembersFilter::Admins) continue;
 						}
 					}
 				} else {
 					continue; // skip
 				}
 			} else if (sharingBotGame()) {
-				if (!p->canWrite()) {
+				if (!peer->canWrite()) {
 					continue;
 				}
-				if (auto channel = p->asChannel()) {
+				if (auto channel = peer->asChannel()) {
 					if (channel->isBroadcast()) {
 						continue;
 					}
 				}
 			}
 
-			ContactData *d = new ContactData();
-			_byUsernameDatas.push_back(d);
-			d->inchat = _chat ? _chat->participants.contains(p->asUser()) : ((_creating == CreatingGroupGroup || _channel) ? (p == App::self()) : false);
-			d->check = _checkedContacts.contains(p);
-			d->name.setText(st::contactsNameFont, p->name, _textNameOptions);
-			d->online = '@' + p->userName();
+			auto data = usingMultiSelect() ? new ContactData(peer, [this, peer] { updateRowWithPeer(peer); }) : new ContactData();
+			_byUsernameDatas.push_back(data);
+			data->disabledChecked = _chat ? _chat->participants.contains(peer->asUser()) : ((_creating == CreatingGroupGroup || _channel) ? (peer == App::self()) : false);
+			if (usingMultiSelect() && _checkedContacts.contains(peer)) {
+				data->checkbox->setChecked(true, Ui::RoundImageCheckbox::SetStyle::Fast);
+			}
+			data->name.setText(st::contactsNameFont, peer->name, _textNameOptions);
+			data->statusText = '@' + peer->userName();
 
-			_byUsernameFiltered.push_back(p);
-			d_byUsernameFiltered.push_back(d);
+			_byUsernameFiltered.push_back(peer);
+			d_byUsernameFiltered.push_back(data);
 		}
 	}
 	_searching = false;
 	refresh();
 }
 
-void ContactsInner::refresh() {
+void ContactsBox::Inner::refresh() {
 	if (_filter.isEmpty()) {
-		if (_chat && _membersFilter == MembersFilterAdmins) {
+		if (_chat && _membersFilter == MembersFilter::Admins) {
 			if (_allAdmins.isHidden()) _allAdmins.show();
 		} else {
 			if (!_allAdmins.isHidden()) _allAdmins.hide();
@@ -1051,7 +1685,7 @@ void ContactsInner::refresh() {
 		if (!_contacts->isEmpty() || !_byUsername.isEmpty()) {
 			if (!_addContactLnk.isHidden()) _addContactLnk.hide();
 			resize(width(), _newItemHeight + (_contacts->size() * _rowHeight) + (_byUsername.isEmpty() ? 0 : (st::searchedBarHeight + _byUsername.size() * _rowHeight)));
-		} else if (_chat && _membersFilter == MembersFilterAdmins) {
+		} else if (_chat && _membersFilter == MembersFilter::Admins) {
 			if (!_addContactLnk.isHidden()) _addContactLnk.hide();
 			resize(width(), _newItemHeight + st::noContactsHeight);
 		} else {
@@ -1074,33 +1708,33 @@ void ContactsInner::refresh() {
 	update();
 }
 
-ChatData *ContactsInner::chat() const {
+ChatData *ContactsBox::Inner::chat() const {
 	return _chat;
 }
 
-ChannelData *ContactsInner::channel() const {
+ChannelData *ContactsBox::Inner::channel() const {
 	return _channel;
 }
 
-MembersFilter ContactsInner::membersFilter() const {
+MembersFilter ContactsBox::Inner::membersFilter() const {
 	return _membersFilter;
 }
 
-UserData *ContactsInner::bot() const {
+UserData *ContactsBox::Inner::bot() const {
 	return _bot;
 }
 
-bool ContactsInner::sharingBotGame() const {
+bool ContactsBox::Inner::sharingBotGame() const {
 	return (_bot && _bot->botInfo) ? !_bot->botInfo->shareGameShortName.isEmpty() : false;
 }
 
-CreatingGroupType ContactsInner::creating() const {
+CreatingGroupType ContactsBox::Inner::creating() const {
 	return _creating;
 }
 
-ContactsInner::~ContactsInner() {
-	for (ContactsData::iterator i = _contactsData.begin(), e = _contactsData.end(); i != e; ++i) {
-		delete *i;
+ContactsBox::Inner::~Inner() {
+	for (auto contactData : base::take(_contactsData)) {
+		delete contactData;
 	}
 	if (_bot) {
 		if (auto &info = _bot->botInfo) {
@@ -1110,12 +1744,12 @@ ContactsInner::~ContactsInner() {
 	}
 }
 
-void ContactsInner::resizeEvent(QResizeEvent *e) {
+void ContactsBox::Inner::resizeEvent(QResizeEvent *e) {
 	_addContactLnk.move((width() - _addContactLnk.width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2);
 	_allAdmins.moveToLeft(st::contactsPadding.left(), st::contactsNewItemTop);
 }
 
-void ContactsInner::selectSkip(int32 dir) {
+void ContactsBox::Inner::selectSkip(int32 dir) {
 	_time = unixtime();
 	_mouseSel = false;
 	if (_filter.isEmpty()) {
@@ -1133,7 +1767,7 @@ void ContactsInner::selectSkip(int32 dir) {
 		}
 		cur += dir;
 		if (cur <= 0) {
-			_newItemSel = (_chat && _membersFilter == MembersFilterAdmins) ? false : (_newItemHeight ? true : false);
+			_newItemSel = (_chat && _membersFilter == MembersFilter::Admins) ? false : (_newItemHeight ? true : false);
 			_sel = (!_newItemHeight && !_contacts->isEmpty()) ? *_contacts->cbegin() : nullptr;
 			_byUsernameSel = (!_newItemHeight && _contacts->isEmpty() && !_byUsername.isEmpty()) ? 0 : -1;
 		} else if (cur >= _contacts->size() + (_newItemHeight ? 1 : 0)) {
@@ -1160,33 +1794,33 @@ void ContactsInner::selectSkip(int32 dir) {
 			_byUsernameSel = -1;
 		}
 		if (dir > 0) {
-			for (auto i = _contacts->cfind(_sel), end = _contacts->cend(); i != end && contactData(*i)->inchat; ++i) {
+			for (auto i = _contacts->cfind(_sel), end = _contacts->cend(); i != end && contactData(*i)->disabledChecked; ++i) {
 				_sel = *i;
 			}
-			if (_sel && contactData(_sel)->inchat) {
+			if (_sel && contactData(_sel)->disabledChecked) {
 				_sel = nullptr;
 			}
 			if (!_sel) {
 				if (!_byUsername.isEmpty()) {
 					if (_byUsernameSel < 0) _byUsernameSel = 0;
-					for (; _byUsernameSel < _byUsername.size() && d_byUsername[_byUsernameSel]->inchat;) {
+					for (; _byUsernameSel < _byUsername.size() && d_byUsername[_byUsernameSel]->disabledChecked;) {
 						++_byUsernameSel;
 					}
 					if (_byUsernameSel == _byUsername.size()) _byUsernameSel = -1;
 				}
 			}
 		} else {
-			while (_byUsernameSel >= 0 && d_byUsername[_byUsernameSel]->inchat) {
+			while (_byUsernameSel >= 0 && d_byUsername[_byUsernameSel]->disabledChecked) {
 				--_byUsernameSel;
 			}
 			if (_byUsernameSel < 0) {
 				if (!_contacts->isEmpty()) {
 					if (!_newItemSel && !_sel) _sel = *(_contacts->cend() - 1);
 					if (_sel) {
-						for (auto i = _contacts->cfind(_sel), b = _contacts->cbegin(); i != b && contactData(*i)->inchat; --i) {
+						for (auto i = _contacts->cfind(_sel), b = _contacts->cbegin(); i != b && contactData(*i)->disabledChecked; --i) {
 							_sel = *i;
 						}
-						if (contactData(_sel)->inchat) {
+						if (contactData(_sel)->disabledChecked) {
 							_sel = nullptr;
 						}
 					}
@@ -1215,27 +1849,27 @@ void ContactsInner::selectSkip(int32 dir) {
 			_byUsernameSel = -1;
 		}
 		if (dir > 0) {
-			while (_filteredSel >= 0 && _filteredSel < _filtered.size() && contactData(_filtered[_filteredSel])->inchat) {
+			while (_filteredSel >= 0 && _filteredSel < _filtered.size() && contactData(_filtered[_filteredSel])->disabledChecked) {
 				++_filteredSel;
 			}
 			if (_filteredSel < 0 || _filteredSel >= _filtered.size()) {
 				_filteredSel = -1;
 				if (!_byUsernameFiltered.isEmpty()) {
 					if (_byUsernameSel < 0) _byUsernameSel = 0;
-					for (; _byUsernameSel < _byUsernameFiltered.size() && d_byUsernameFiltered[_byUsernameSel]->inchat;) {
+					for (; _byUsernameSel < _byUsernameFiltered.size() && d_byUsernameFiltered[_byUsernameSel]->disabledChecked;) {
 						++_byUsernameSel;
 					}
 					if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1;
 				}
 			}
 		} else {
-			while (_byUsernameSel >= 0 && d_byUsernameFiltered[_byUsernameSel]->inchat) {
+			while (_byUsernameSel >= 0 && d_byUsernameFiltered[_byUsernameSel]->disabledChecked) {
 				--_byUsernameSel;
 			}
 			if (_byUsernameSel < 0) {
 				if (!_filtered.isEmpty()) {
 					if (_filteredSel < 0) _filteredSel = _filtered.size() - 1;
-					for (; _filteredSel >= 0 && contactData(_filtered[_filteredSel])->inchat;) {
+					for (; _filteredSel >= 0 && contactData(_filtered[_filteredSel])->disabledChecked;) {
 						--_filteredSel;
 					}
 				}
@@ -1251,1126 +1885,58 @@ void ContactsInner::selectSkip(int32 dir) {
 	update();
 }
 
-void ContactsInner::selectSkipPage(int32 h, int32 dir) {
+void ContactsBox::Inner::selectSkipPage(int32 h, int32 dir) {
 	int32 points = h / _rowHeight;
 	if (!points) return;
 	selectSkip(points * dir);
 }
 
-QVector<UserData*> ContactsInner::selected() {
+QVector<UserData*> ContactsBox::Inner::selected() {
 	QVector<UserData*> result;
+	if (!usingMultiSelect()) {
+		return result;
+	}
+
 	for_const (auto row, *_contacts) {
 		if (_checkedContacts.contains(row->history()->peer)) {
 			contactData(row); // fill _contactsData
 		}
 	}
 	result.reserve(_contactsData.size());
-	for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
-		if (i.value()->check && i.key()->isUser()) {
+	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) {
-		if (d_byUsername[i]->check && _byUsername[i]->isUser()) {
+	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());
 		}
 	}
 	return result;
 }
 
-QVector<MTPInputUser> ContactsInner::selectedInputs() {
+QVector<MTPInputUser> ContactsBox::Inner::selectedInputs() {
 	QVector<MTPInputUser> result;
+	if (!usingMultiSelect()) {
+		return result;
+	}
+
 	for_const (auto row, *_contacts) {
 		if (_checkedContacts.contains(row->history()->peer)) {
 			contactData(row); // fill _contactsData
 		}
 	}
 	result.reserve(_contactsData.size());
-	for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
-		if (i.value()->check && i.key()->isUser()) {
+	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) {
-		if (d_byUsername[i]->check && _byUsername[i]->isUser()) {
+	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);
 		}
 	}
 	return result;
 }
-
-PeerData *ContactsInner::selectedUser() {
-	for_const (auto row, *_contacts) {
-		if (_checkedContacts.contains(row->history()->peer)) {
-			contactData(row); // fill _contactsData
-		}
-	}
-	for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
-		if (i.value()->check) {
-			return i.key();
-		}
-	}
-	for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
-		if (d_byUsername[i]->check) {
-			return _byUsername[i];
-		}
-	}
-	return 0;
-}
-
-ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll)
-, _inner(CreatingGroupNone)
-, _filter(this, st::boxSearchField, lang(lng_participant_filter))
-, _filterCancel(this, st::boxSearchCancel)
-, _next(this, lang(lng_create_group_next), st::defaultBoxButton)
-, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
-, _topShadow(this)
-, _bottomShadow(0)
-, _saveRequestId(0) {
-	init();
-}
-
-ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox(st::boxScroll)
-, _inner(CreatingGroupGroup)
-, _filter(this, st::boxSearchField, lang(lng_participant_filter))
-, _filterCancel(this, st::boxSearchCancel)
-, _next(this, lang(lng_create_group_create), st::defaultBoxButton)
-, _cancel(this, lang(lng_create_group_back), st::cancelBoxButton)
-, _topShadow(this)
-, _bottomShadow(0)
-, _saveRequestId(0)
-, _creationName(name)
-, _creationPhoto(photo) {
-	init();
-}
-
-ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll)
-, _inner(channel, MembersFilterRecent, MembersAlreadyIn())
-, _filter(this, st::boxSearchField, lang(lng_participant_filter))
-, _filterCancel(this, st::boxSearchCancel)
-, _next(this, lang(lng_participant_invite), st::defaultBoxButton)
-, _cancel(this, lang(lng_create_group_skip), st::cancelBoxButton)
-, _topShadow(this)
-, _bottomShadow(0)
-, _saveRequestId(0) {
-	init();
-}
-
-ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already) : ItemListBox((filter == MembersFilterAdmins) ? st::contactsScroll : st::boxScroll)
-, _inner(channel, filter, already)
-, _filter(this, st::boxSearchField, lang(lng_participant_filter))
-, _filterCancel(this, st::boxSearchCancel)
-, _next(this, lang(lng_participant_invite), st::defaultBoxButton)
-, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
-, _topShadow(this)
-, _bottomShadow(0)
-, _saveRequestId(0) {
-	init();
-}
-
-ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st::boxScroll)
-, _inner(chat, filter)
-, _filter(this, st::boxSearchField, lang(lng_participant_filter))
-, _filterCancel(this, st::boxSearchCancel)
-, _next(this, lang((filter == MembersFilterAdmins) ? lng_settings_save : lng_participant_invite), st::defaultBoxButton)
-, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
-, _topShadow(this)
-, _bottomShadow(0)
-, _saveRequestId(0) {
-	init();
-}
-
-ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll)
-, _inner(bot)
-, _filter(this, st::boxSearchField, lang(lng_participant_filter))
-, _filterCancel(this, st::boxSearchCancel)
-, _next(this, lang(lng_create_group_next), st::defaultBoxButton)
-, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
-, _topShadow(this)
-, _bottomShadow(0)
-, _saveRequestId(0) {
-	init();
-}
-
-void ContactsBox::init() {
-	bool inviting = (_inner.creating() == CreatingGroupGroup) || (_inner.channel() && _inner.membersFilter() == MembersFilterRecent) || _inner.chat();
-	int32 topSkip = st::boxTitleHeight + _filter.height();
-	int32 bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip;
-	ItemListBox::init(&_inner, bottomSkip, topSkip);
-
-	connect(&_inner, SIGNAL(chosenChanged()), this, SLOT(onChosenChanged()));
-	connect(&_inner, SIGNAL(addRequested()), App::wnd(), SLOT(onShowAddContact()));
-	if (_inner.channel() && _inner.membersFilter() == MembersFilterAdmins) {
-		_next.hide();
-		_cancel.hide();
-	} else if (_inner.chat() && _inner.membersFilter() == MembersFilterAdmins) {
-		connect(&_next, SIGNAL(clicked()), this, SLOT(onSaveAdmins()));
-		_bottomShadow = new ScrollableBoxShadow(this);
-	} else if (_inner.chat() || _inner.channel()) {
-		connect(&_next, SIGNAL(clicked()), this, SLOT(onInvite()));
-		_bottomShadow = new ScrollableBoxShadow(this);
-	} else if (_inner.creating() != CreatingGroupNone) {
-		connect(&_next, SIGNAL(clicked()), this, SLOT(onCreate()));
-		_bottomShadow = new ScrollableBoxShadow(this);
-	} else {
-		_next.hide();
-		_cancel.hide();
-	}
-	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)), this, SLOT(onSubmit()));
-	connect(&_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel()));
-	connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int)));
-	connect(&_inner, SIGNAL(selectAllQuery()), &_filter, SLOT(selectAll()));
-	connect(&_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername()));
-	connect(&_inner, SIGNAL(adminAdded()), this, SIGNAL(adminAdded()));
-
-	_filterCancel.setAttribute(Qt::WA_OpaquePaintEvent);
-
-	_searchTimer.setSingleShot(true);
-	connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername()));
-
-	prepare();
-}
-
-bool ContactsBox::onSearchByUsername(bool searchCache) {
-	QString q = _filter.getLastText().trimmed();
-	if (q.isEmpty()) {
-		if (_peopleRequest) {
-			_peopleRequest = 0;
-		}
-		return true;
-	}
-	if (q.size() >= MinUsernameLength) {
-		if (searchCache) {
-			PeopleCache::const_iterator i = _peopleCache.constFind(q);
-			if (i != _peopleCache.cend()) {
-				_peopleQuery = q;
-				_peopleRequest = 0;
-				peopleReceived(i.value(), 0);
-				return true;
-			}
-		} else if (_peopleQuery != q) {
-			_peopleQuery = q;
-			_peopleFull = false;
-			_peopleRequest = MTP::send(MTPcontacts_Search(MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit)), rpcDone(&ContactsBox::peopleReceived), rpcFail(&ContactsBox::peopleFailed));
-			_peopleQueries.insert(_peopleRequest, _peopleQuery);
-		}
-	}
-	return false;
-}
-
-void ContactsBox::onNeedSearchByUsername() {
-	if (!onSearchByUsername(true)) {
-		_searchTimer.start(AutoSearchTimeout);
-	}
-}
-
-void ContactsBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId req) {
-	QString q = _peopleQuery;
-
-	PeopleQueries::iterator i = _peopleQueries.find(req);
-	if (i != _peopleQueries.cend()) {
-		q = i.value();
-		_peopleCache[q] = result;
-		_peopleQueries.erase(i);
-	}
-
-	if (_peopleRequest == req) {
-		switch (result.type()) {
-		case mtpc_contacts_found: {
-			App::feedUsers(result.c_contacts_found().vusers);
-			App::feedChats(result.c_contacts_found().vchats);
-			_inner.peopleReceived(q, result.c_contacts_found().vresults.c_vector().v);
-		} break;
-		}
-
-		_peopleRequest = 0;
-		_inner.updateSel();
-		onScroll();
-	}
-}
-
-bool ContactsBox::peopleFailed(const RPCError &error, mtpRequestId req) {
-	if (MTP::isDefaultHandledError(error)) return false;
-
-	if (_peopleRequest == req) {
-		_peopleRequest = 0;
-		_peopleFull = true;
-	}
-	return true;
-}
-
-void ContactsBox::showAll() {
-	_filter.show();
-	if (_filter.getLastText().isEmpty()) {
-		_filterCancel.hide();
-	} else {
-		_filterCancel.show();
-	}
-	if (_inner.channel() && _inner.membersFilter() == MembersFilterAdmins) {
-		_next.hide();
-		_cancel.hide();
-	} else if (_inner.chat() || _inner.channel()) {
-		_next.show();
-		_cancel.show();
-	} else if (_inner.creating() != CreatingGroupNone) {
-		_next.show();
-		_cancel.show();
-	} else {
-		_next.hide();
-		_cancel.hide();
-	}
-	_topShadow.show();
-	if (_bottomShadow) _bottomShadow->show();
-	ItemListBox::showAll();
-}
-
-void ContactsBox::doSetInnerFocus() {
-	_filter.setFocus();
-}
-
-void ContactsBox::onSubmit() {
-	_inner.chooseParticipant();
-}
-
-void ContactsBox::keyPressEvent(QKeyEvent *e) {
-	if (_filter.hasFocus()) {
-		if (e->key() == Qt::Key_Down) {
-			_inner.selectSkip(1);
-		} else if (e->key() == Qt::Key_Up) {
-			_inner.selectSkip(-1);
-		} else if (e->key() == Qt::Key_PageDown) {
-			_inner.selectSkipPage(scrollArea()->height(), 1);
-		} else if (e->key() == Qt::Key_PageUp) {
-			_inner.selectSkipPage(scrollArea()->height(), -1);
-		} else {
-			ItemListBox::keyPressEvent(e);
-		}
-	} else {
-		ItemListBox::keyPressEvent(e);
-	}
-}
-
-void ContactsBox::paintEvent(QPaintEvent *e) {
-	Painter p(this);
-	if (paint(p)) return;
-
-	bool addingAdmin = _inner.channel() && _inner.membersFilter() == MembersFilterAdmins;
-	if (_inner.chat() && _inner.membersFilter() == MembersFilterAdmins) {
-		paintTitle(p, lang(lng_channel_admins));
-	} else if (_inner.chat() || _inner.creating() != CreatingGroupNone) {
-		QString title(lang(addingAdmin ? lng_channel_add_admin : lng_profile_add_participant));
-		QString additional((addingAdmin || (_inner.channel() && !_inner.channel()->isMegagroup())) ? QString() : QString("%1 / %2").arg(_inner.selectedCount()).arg(Global::MegagroupSizeMax()));
-		paintTitle(p, title, additional);
-	} else if (_inner.sharingBotGame()) {
-		paintTitle(p, lang(lng_bot_choose_chat));
-	} else if (_inner.bot()) {
-		paintTitle(p, lang(lng_bot_choose_group));
-	} else {
-		paintTitle(p, lang(lng_contacts_header));
-	}
-}
-
-void ContactsBox::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());
-	_next.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _next.height());
-	_cancel.moveToRight(st::boxButtonPadding.right() + _next.width() + st::boxButtonPadding.left(), _next.y());
-	_topShadow.setGeometry(0, st::boxTitleHeight + _filter.height(), width(), st::lineWidth);
-	if (_bottomShadow) _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _next.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth);
-}
-
-void ContactsBox::closePressed() {
-	if (_inner.channel() && !_inner.hasAlreadyMembersInChannel()) {
-		Ui::showPeerHistory(_inner.channel(), ShowAtTheEndMsgId);
-	}
-}
-
-void ContactsBox::onFilterCancel() {
-	_filter.setText(QString());
-}
-
-void ContactsBox::onFilterUpdate() {
-	scrollArea()->scrollToY(0);
-	if (_filter.getLastText().isEmpty()) {
-		_filterCancel.hide();
-	} else {
-		_filterCancel.show();
-	}
-	_inner.updateFilter(_filter.getLastText());
-}
-
-void ContactsBox::onChosenChanged() {
-	update();
-}
-
-void ContactsBox::onInvite() {
-	QVector<UserData*> users(_inner.selected());
-	if (users.isEmpty()) {
-		_filter.setFocus();
-		_filter.showError();
-		return;
-	}
-
-	App::main()->addParticipants(_inner.chat() ? (PeerData*)_inner.chat() : _inner.channel(), users);
-	if (_inner.chat()) {
-		Ui::hideLayer();
-		Ui::showPeerHistory(_inner.chat(), ShowAtTheEndMsgId);
-	} else {
-		onClose();
-	}
-}
-
-void ContactsBox::onCreate() {
-	if (_saveRequestId) return;
-
-	MTPVector<MTPInputUser> users(MTP_vector<MTPInputUser>(_inner.selectedInputs()));
-	const auto &v(users.c_vector().v);
-	if (v.isEmpty() || (v.size() == 1 && v.at(0).type() == mtpc_inputUserSelf)) {
-		_filter.setFocus();
-		_filter.showError();
-		return;
-	}
-	_saveRequestId = MTP::send(MTPmessages_CreateChat(MTP_vector<MTPInputUser>(v), MTP_string(_creationName)), rpcDone(&ContactsBox::creationDone), rpcFail(&ContactsBox::creationFail));
-}
-
-void ContactsBox::onSaveAdmins() {
-	if (_saveRequestId) return;
-
-	_inner.saving(true);
-	_saveRequestId = MTP::send(MTPmessages_ToggleChatAdmins(_inner.chat()->inputChat, MTP_bool(!_inner.allAdmins())), rpcDone(&ContactsBox::saveAdminsDone), rpcFail(&ContactsBox::saveAdminsFail));
-}
-
-void ContactsBox::saveAdminsDone(const MTPUpdates &result) {
-	App::main()->sentUpdatesReceived(result);
-	saveSelectedAdmins();
-}
-
-void ContactsBox::saveSelectedAdmins() {
-	if (_inner.allAdmins() && !_inner.chat()->participants.isEmpty()) {
-		onClose();
-	} else {
-		_saveRequestId = MTP::send(MTPmessages_GetFullChat(_inner.chat()->inputChat), rpcDone(&ContactsBox::getAdminsDone), rpcFail(&ContactsBox::saveAdminsFail));
-	}
-}
-
-void ContactsBox::getAdminsDone(const MTPmessages_ChatFull &result) {
-	App::api()->processFullPeer(_inner.chat(), result);
-	if (_inner.allAdmins()) {
-		onClose();
-		return;
-	}
-	ChatData::Admins curadmins = _inner.chat()->admins;
-	QVector<UserData*> newadmins = _inner.selected(), appoint;
-	if (!newadmins.isEmpty()) {
-		appoint.reserve(newadmins.size());
-		for (int32 i = 0, l = newadmins.size(); i < l; ++i) {
-			ChatData::Admins::iterator c = curadmins.find(newadmins.at(i));
-			if (c == curadmins.cend()) {
-				if (newadmins.at(i)->id != peerFromUser(_inner.chat()->creator)) {
-					appoint.push_back(newadmins.at(i));
-				}
-			} else {
-				curadmins.erase(c);
-			}
-		}
-	}
-	_saveRequestId = 0;
-
-	for_const (UserData *user, curadmins) {
-		MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, user->inputUser, MTP_boolFalse()), rpcDone(&ContactsBox::removeAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10);
-	}
-	for_const (UserData *user, appoint) {
-		MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, user->inputUser, MTP_boolTrue()), rpcDone(&ContactsBox::setAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10);
-	}
-	MTP::sendAnything();
-
-	_saveRequestId = curadmins.size() + appoint.size();
-	if (!_saveRequestId) {
-		onClose();
-	}
-}
-
-void ContactsBox::setAdminDone(UserData *user, const MTPBool &result) {
-	if (mtpIsTrue(result)) {
-		if (_inner.chat()->noParticipantInfo()) {
-			App::api()->requestFullPeer(_inner.chat());
-		} else {
-			_inner.chat()->admins.insert(user);
-		}
-	}
-	--_saveRequestId;
-	if (!_saveRequestId) {
-		emit App::main()->peerUpdated(_inner.chat());
-		onClose();
-	}
-}
-
-void ContactsBox::removeAdminDone(UserData *user, const MTPBool &result) {
-	if (mtpIsTrue(result)) {
-		_inner.chat()->admins.remove(user);
-	}
-	--_saveRequestId;
-	if (!_saveRequestId) {
-		emit App::main()->peerUpdated(_inner.chat());
-		onClose();
-	}
-}
-
-bool ContactsBox::saveAdminsFail(const RPCError &error) {
-	if (MTP::isDefaultHandledError(error)) return true;
-	_saveRequestId = 0;
-	_inner.saving(false);
-	if (error.type() == qstr("CHAT_NOT_MODIFIED")) {
-		saveSelectedAdmins();
-	}
-	return false;
-}
-
-bool ContactsBox::editAdminFail(const RPCError &error) {
-	if (MTP::isDefaultHandledError(error)) return true;
-	--_saveRequestId;
-	_inner.chat()->invalidateParticipants();
-	if (!_saveRequestId) {
-		if (error.type() == qstr("USER_RESTRICTED")) {
-			Ui::showLayer(new InformBox(lang(lng_cant_do_this)));
-			return true;
-		}
-		onClose();
-	}
-	return false;
-}
-
-void ContactsBox::onScroll() {
-	_inner.loadProfilePhotos(scrollArea()->scrollTop());
-}
-
-void ContactsBox::creationDone(const MTPUpdates &updates) {
-	Ui::hideLayer();
-
-	App::main()->sentUpdatesReceived(updates);
-	const QVector<MTPChat> *v = 0;
-	switch (updates.type()) {
-	case mtpc_updates: v = &updates.c_updates().vchats.c_vector().v; break;
-	case mtpc_updatesCombined: v = &updates.c_updatesCombined().vchats.c_vector().v; break;
-	default: LOG(("API Error: unexpected update cons %1 (ContactsBox::creationDone)").arg(updates.type())); break;
-	}
-
-	PeerData *peer = 0;
-	if (v && !v->isEmpty() && v->front().type() == mtpc_chat) {
-		peer = App::chat(v->front().c_chat().vid.v);
-		if (peer) {
-			if (!_creationPhoto.isNull()) {
-				App::app()->uploadProfilePhoto(_creationPhoto, peer->id);
-			}
-			Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
-		}
-	} else {
-		LOG(("API Error: chat not found in updates (ContactsBox::creationDone)"));
-	}
-}
-
-bool ContactsBox::creationFail(const RPCError &error) {
-	if (MTP::isDefaultHandledError(error)) return false;
-
-	_saveRequestId = 0;
-	if (error.type() == "NO_CHAT_TITLE") {
-		onClose();
-		return true;
-	} else if (error.type() == "USERS_TOO_FEW") {
-		_filter.setFocus();
-		_filter.showError();
-		return true;
-	} else if (error.type() == "PEER_FLOOD") {
-		Ui::showLayer(new InformBox(cantInviteError()), KeepOtherLayers);
-		return true;
-	} else if (error.type() == qstr("USER_RESTRICTED")) {
-		Ui::showLayer(new InformBox(lang(lng_cant_do_this)));
-		return true;
-	}
-	return false;
-}
-
-MembersInner::MembersInner(ChannelData *channel, MembersFilter filter) : TWidget()
-, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
-, _newItemHeight((channel->amCreator() && (channel->membersCount() < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilterAdmins)) ? st::contactsNewItemHeight : 0)
-, _newItemSel(false)
-, _channel(channel)
-, _filter(filter)
-, _kickText(lang(lng_profile_kick))
-, _time(0)
-, _kickWidth(st::normalFont->width(_kickText))
-, _sel(-1)
-, _kickSel(-1)
-, _kickDown(-1)
-, _mouseSel(false)
-, _kickConfirm(0)
-, _kickRequestId(0)
-, _kickBox(0)
-, _loading(true)
-, _loadingRequestId(0)
-, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right())
-, _about(_aboutWidth)
-, _aboutHeight(0) {
-	subscribe(FileDownload::ImageLoaded(), [this] { update(); });
-
-	connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)));
-	connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*)));
-
-	refresh();
-
-	load();
-}
-
-void MembersInner::load() {
-	if (!_loadingRequestId) {
-		_loadingRequestId = MTP::send(MTPchannels_GetParticipants(_channel->inputChannel, (_filter == MembersFilterRecent) ? MTP_channelParticipantsRecent() : MTP_channelParticipantsAdmins(), MTP_int(0), MTP_int(Global::ChatSizeMax())), rpcDone(&MembersInner::membersReceived), rpcFail(&MembersInner::membersFailed));
-	}
-}
-
-void MembersInner::paintEvent(QPaintEvent *e) {
-	QRect r(e->rect());
-	Painter p(this);
-
-	_time = unixtime();
-	p.fillRect(r, st::white->b);
-
-	int32 yFrom = r.y() - st::membersPadding.top(), yTo = r.y() + r.height() - st::membersPadding.top();
-	p.translate(0, st::membersPadding.top());
-	if (_rows.isEmpty()) {
-		p.setFont(st::noContactsFont->f);
-		p.setPen(st::noContactsColor->p);
-		p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center);
-	} else {
-		if (_newItemHeight) {
-			p.fillRect(0, 0, width(), _newItemHeight, (_newItemSel ? st::contactsBgOver : st::white)->b);
-			p.drawSpriteLeft(st::contactsNewItemIconPosition.x(), st::contactsNewItemIconPosition.y(), width(), st::contactsNewItemIcon);
-			p.setFont(st::contactsNameFont);
-			p.setPen(st::contactsNewItemFg);
-			p.drawTextLeft(st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(_filter == MembersFilterAdmins ? lng_channel_add_admins : lng_channel_add_members));
-
-			yFrom -= _newItemHeight;
-			yTo -= _newItemHeight;
-			p.translate(0, _newItemHeight);
-		}
-		int32 from = floorclamp(yFrom, _rowHeight, 0, _rows.size());
-		int32 to = ceilclamp(yTo, _rowHeight, 0, _rows.size());
-		p.translate(0, from * _rowHeight);
-		for (; from < to; ++from) {
-			bool sel = (from == _sel);
-			bool kickSel = (from == _kickSel && (_kickDown < 0 || from == _kickDown));
-			bool kickDown = kickSel && (from == _kickDown);
-			paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown);
-			p.translate(0, _rowHeight);
-		}
-		if (to == _rows.size() && _filter == MembersFilterRecent && (_rows.size() < _channel->membersCount() || _rows.size() >= Global::ChatSizeMax())) {
-			p.setPen(st::stickersReorderFg);
-			_about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center);
-		}
-	}
-}
-
-void MembersInner::enterEvent(QEvent *e) {
-	setMouseTracking(true);
-}
-
-void MembersInner::leaveEvent(QEvent *e) {
-	_mouseSel = false;
-	setMouseTracking(false);
-	if (_sel >= 0) {
-		clearSel();
-	}
-}
-
-void MembersInner::mouseMoveEvent(QMouseEvent *e) {
-	_mouseSel = true;
-	_lastMousePos = e->globalPos();
-	updateSel();
-}
-
-void MembersInner::mousePressEvent(QMouseEvent *e) {
-	_mouseSel = true;
-	_lastMousePos = e->globalPos();
-	updateSel();
-	if (e->button() == Qt::LeftButton && _kickSel < 0) {
-		chooseParticipant();
-	}
-	_kickDown = _kickSel;
-	update();
-}
-
-void MembersInner::mouseReleaseEvent(QMouseEvent *e) {
-	_mouseSel = true;
-	_lastMousePos = e->globalPos();
-	updateSel();
-	if (_kickDown >= 0 && _kickDown == _kickSel && !_kickRequestId) {
-		_kickConfirm = _rows.at(_kickSel);
-		if (_kickBox) _kickBox->deleteLater();
-		_kickBox = new ConfirmBox((_filter == MembersFilterRecent ? (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel) : lng_profile_sure_kick_admin)(lt_user, _kickConfirm->firstName));
-		connect(_kickBox, SIGNAL(confirmed()), this, SLOT(onKickConfirm()));
-		connect(_kickBox, SIGNAL(destroyed(QObject*)), this, SLOT(onKickBoxDestroyed(QObject*)));
-		Ui::showLayer(_kickBox, KeepOtherLayers);
-	}
-	_kickDown = -1;
-}
-
-void MembersInner::onKickBoxDestroyed(QObject *obj) {
-	if (_kickBox == obj) {
-		_kickBox = 0;
-	}
-}
-
-void MembersInner::onKickConfirm() {
-	if (_filter == MembersFilterRecent) {
-		_kickRequestId = MTP::send(MTPchannels_KickFromChannel(_channel->inputChannel, _kickConfirm->inputUser, MTP_bool(true)), rpcDone(&MembersInner::kickDone), rpcFail(&MembersInner::kickFail));
-	} else {
-		_kickRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _kickConfirm->inputUser, MTP_channelRoleEmpty()), rpcDone(&MembersInner::kickAdminDone), rpcFail(&MembersInner::kickFail));
-	}
-}
-
-void MembersInner::paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown) {
-	UserData *user = peer->asUser();
-
-	p.fillRect(0, 0, width(), _rowHeight, (sel ? st::contactsBgOver : st::white)->b);
-	peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width());
-
-	p.setPen(st::black);
-
-	int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
-	int32 namew = width() - namex - st::contactsPadding.right() - (data->canKick ? (_kickWidth + st::contactsCheckPosition.x() * 2) : 0);
-	if (peer->isVerified()) {
-		auto icon = &st::dialogsVerifiedIcon;
-		namew -= icon->width();
-		icon->paint(p, namex + qMin(data->name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width());
-	}
-	data->name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width());
-
-	if (data->canKick) {
-		p.setFont((kickSel ? st::linkOverFont : st::linkFont)->f);
-		if (kickDown) {
-			p.setPen(st::btnDefLink.downColor->p);
-		} else {
-			p.setPen(st::btnDefLink.color->p);
-		}
-		p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), _kickText, _kickWidth);
-	}
-
-	p.setFont(st::contactsStatusFont->f);
-	p.setPen(data->onlineColor ? st::contactsStatusFgOnline : (sel ? st::contactsStatusFgOver : st::contactsStatusFg));
-	p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->online);
-}
-
-void MembersInner::selectSkip(int32 dir) {
-	_time = unixtime();
-	_mouseSel = false;
-
-	int cur = -1;
-	if (_newItemHeight && _newItemSel) {
-		cur = 0;
-	} else if (_sel >= 0) {
-		cur = _sel + (_newItemHeight ? 1 : 0);
-	}
-	cur += dir;
-	if (cur <= 0) {
-		_newItemSel = _newItemHeight ? true : false;
-		_sel = (_newItemSel || _rows.isEmpty()) ? -1 : 0;
-	} else if (cur >= _rows.size() + (_newItemHeight ? 1 : 0)) {
-		_sel = -1;
-	} else {
-		_sel = cur - (_newItemHeight ? 1 : 0);
-	}
-	if (dir > 0) {
-		if (_sel < 0 || _sel >= _rows.size()) {
-			_sel = -1;
-		}
-	} else {
-		if (!_rows.isEmpty()) {
-			if (_sel < 0 && !_newItemSel) _sel = _rows.size() - 1;
-		}
-	}
-	if (_newItemSel) {
-		emit mustScrollTo(0, _newItemHeight);
-	} else if (_sel >= 0) {
-		emit mustScrollTo(_newItemHeight + _sel * _rowHeight, _newItemHeight + (_sel + 1) * _rowHeight);
-	}
-
-	update();
-}
-
-void MembersInner::selectSkipPage(int32 h, int32 dir) {
-	int32 points = h / _rowHeight;
-	if (!points) return;
-	selectSkip(points * dir);
-}
-
-void MembersInner::loadProfilePhotos(int32 yFrom) {
-	int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5;
-	MTP::clearLoaderPriorities();
-
-	if (yTo < 0) return;
-	if (yFrom < 0) yFrom = 0;
-
-	if (!_rows.isEmpty()) {
-		int32 from = (yFrom - _newItemHeight) / _rowHeight;
-		if (from < 0) from = 0;
-		if (from < _rows.size()) {
-			int32 to = ((yTo - _newItemHeight) / _rowHeight) + 1;
-			if (to > _rows.size()) to = _rows.size();
-
-			for (; from < to; ++from) {
-				_rows[from]->loadUserpic();
-			}
-		}
-	}
-}
-
-void MembersInner::chooseParticipant() {
-	if (_newItemSel) {
-		emit addRequested();
-		return;
-	}
-	if (_sel < 0 || _sel >= _rows.size()) return;
-	if (PeerData *peer = _rows[_sel]) {
-		Ui::hideLayer();
-		Ui::showPeerProfile(peer);
-	}
-}
-
-void MembersInner::refresh() {
-	if (_rows.isEmpty()) {
-		resize(width(), st::membersPadding.top() + st::noContactsHeight + st::membersPadding.bottom());
-		_aboutHeight = 0;
-	} else {
-		_about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size()));
-		_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
-		if (_filter != MembersFilterRecent || (_rows.size() >= _channel->membersCount() && _rows.size() < Global::ChatSizeMax())) {
-			_aboutHeight = 0;
-		}
-		resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight);
-	}
-	update();
-}
-
-ChannelData *MembersInner::channel() const {
-	return _channel;
-}
-
-MembersFilter MembersInner::filter() const {
-	return _filter;
-}
-
-MembersAlreadyIn MembersInner::already() const {
-	MembersAlreadyIn result;
-	for_const (auto peer, _rows) {
-		if (peer->isUser()) {
-			result.insert(peer->asUser());
-		}
-	}
-	return result;
-}
-
-void MembersInner::clearSel() {
-	updateSelectedRow();
-	_newItemSel = false;
-	_sel = _kickSel = _kickDown = -1;
-	_lastMousePos = QCursor::pos();
-	updateSel();
-}
-
-MembersInner::MemberData *MembersInner::data(int32 index) {
-	if (MemberData *result = _datas.at(index)) {
-		return result;
-	}
-	MemberData *result = _datas[index] = new MemberData();
-	result->name.setText(st::contactsNameFont, _rows[index]->name, _textNameOptions);
-	int32 t = unixtime();
-	result->online = App::onlineText(_rows[index], t);// lng_mediaview_date_time(lt_date, _dates[index].date().toString(qsl("dd.MM.yy")), lt_time, _dates[index].time().toString(cTimeFormat()));
-	result->onlineColor = App::onlineColorUse(_rows[index], t);
-	if (_filter == MembersFilterRecent) {
-		result->canKick = (_channel->amCreator() || _channel->amEditor() || _channel->amModerator()) ? (_roles[index] == MemberRoleNone) : false;
-	} else if (_filter == MembersFilterAdmins) {
-		result->canKick = _channel->amCreator() ? (_roles[index] == MemberRoleEditor || _roles[index] == MemberRoleModerator) : false;
-	} else {
-		result->canKick = false;
-	}
-	return result;
-}
-
-void MembersInner::clear() {
-	for (int32 i = 0, l = _datas.size(); i < l; ++i) {
-		delete _datas.at(i);
-	}
-	_datas.clear();
-	_rows.clear();
-	_dates.clear();
-	_roles.clear();
-	if (_kickBox) _kickBox->deleteLater();
-	clearSel();
-}
-
-MembersInner::~MembersInner() {
-	clear();
-}
-
-void MembersInner::updateSel() {
-	if (!_mouseSel) return;
-
-	QPoint p(mapFromGlobal(_lastMousePos));
-	p.setY(p.y() - st::membersPadding.top());
-	bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos));
-	bool newItemSel = (in && p.y() >= 0 && p.y() < _newItemHeight);
-	int32 newSel = (in && !newItemSel && p.y() >= _newItemHeight && p.y() < _newItemHeight + _rows.size() * _rowHeight) ? ((p.y() - _newItemHeight) / _rowHeight) : -1;
-	int32 newKickSel = newSel;
-	if (newSel >= 0 && (!data(newSel)->canKick || !QRect(width() - _kickWidth - st::contactsPadding.right() - st::contactsCheckPosition.x(), _newItemHeight + newSel * _rowHeight + st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, _kickWidth, st::normalFont->height).contains(p))) {
-		newKickSel = -1;
-	}
-	if (newSel != _sel || newKickSel != _kickSel || newItemSel != _newItemSel) {
-		updateSelectedRow();
-		_newItemSel = newItemSel;
-		_sel = newSel;
-		_kickSel = newKickSel;
-		updateSelectedRow();
-		setCursor(_kickSel >= 0 ? style::cur_pointer : style::cur_default);
-	}
-}
-
-void MembersInner::peerUpdated(PeerData *peer) {
-	update();
-}
-
-void MembersInner::updateSelectedRow() {
-	if (_newItemSel) {
-		update(0, st::membersPadding.top(), width(), _newItemHeight);
-	}
-	if (_sel >= 0) {
-		update(0, st::membersPadding.top() + _newItemHeight + _sel * _rowHeight, width(), _rowHeight);
-	}
-}
-
-void MembersInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
-	for (int32 i = 0, l = _rows.size(); i < l; ++i) {
-		if (_rows.at(i) == peer) {
-			if (_datas.at(i)) {
-				_datas.at(i)->name.setText(st::contactsNameFont, peer->name, _textNameOptions);
-				update(0, st::membersPadding.top() + i * _rowHeight, width(), _rowHeight);
-			} else {
-				break;
-			}
-		}
-	}
-}
-
-void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req) {
-	clear();
-	_loadingRequestId = 0;
-
-	if (result.type() == mtpc_channels_channelParticipants) {
-		const auto &d(result.c_channels_channelParticipants());
-		const auto &v(d.vparticipants.c_vector().v);
-		_rows.reserve(v.size());
-		_datas.reserve(v.size());
-		_dates.reserve(v.size());
-		_roles.reserve(v.size());
-
-		if (_filter == MembersFilterRecent && _channel->membersCount() < d.vcount.v) {
-			_channel->setMembersCount(d.vcount.v);
-			if (App::main()) emit App::main()->peerUpdated(_channel);
-		} else if (_filter == MembersFilterAdmins && _channel->adminsCount() < d.vcount.v) {
-			_channel->setAdminsCount(d.vcount.v);
-			if (App::main()) emit App::main()->peerUpdated(_channel);
-		}
-		App::feedUsers(d.vusers);
-
-		for (QVector<MTPChannelParticipant>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
-			int32 userId = 0, addedTime = 0;
-			MemberRole role = MemberRoleNone;
-			switch (i->type()) {
-			case mtpc_channelParticipant:
-				userId = i->c_channelParticipant().vuser_id.v;
-				addedTime = i->c_channelParticipant().vdate.v;
-				break;
-			case mtpc_channelParticipantSelf:
-				role = MemberRoleSelf;
-				userId = i->c_channelParticipantSelf().vuser_id.v;
-				addedTime = i->c_channelParticipantSelf().vdate.v;
-				break;
-			case mtpc_channelParticipantModerator:
-				role = MemberRoleModerator;
-				userId = i->c_channelParticipantModerator().vuser_id.v;
-				addedTime = i->c_channelParticipantModerator().vdate.v;
-				break;
-			case mtpc_channelParticipantEditor:
-				role = MemberRoleEditor;
-				userId = i->c_channelParticipantEditor().vuser_id.v;
-				addedTime = i->c_channelParticipantEditor().vdate.v;
-				break;
-			case mtpc_channelParticipantKicked:
-				userId = i->c_channelParticipantKicked().vuser_id.v;
-				addedTime = i->c_channelParticipantKicked().vdate.v;
-				role = MemberRoleKicked;
-				break;
-			case mtpc_channelParticipantCreator:
-				userId = i->c_channelParticipantCreator().vuser_id.v;
-				addedTime = _channel->date;
-				role = MemberRoleCreator;
-				break;
-			}
-			if (UserData *user = App::userLoaded(userId)) {
-				_rows.push_back(user);
-				_dates.push_back(date(addedTime));
-				_roles.push_back(role);
-				_datas.push_back(0);
-			}
-		}
-
-		// update admins if we got all of them
-		if (_filter == MembersFilterAdmins && _channel->isMegagroup() && _rows.size() < Global::ChatSizeMax()) {
-			_channel->mgInfo->lastAdmins.clear();
-			for (int32 i = 0, l = _rows.size(); i != l; ++i) {
-				if (_roles.at(i) == MemberRoleCreator || _roles.at(i) == MemberRoleEditor) {
-					_channel->mgInfo->lastAdmins.insert(_rows.at(i));
-				}
-			}
-
-			Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged);
-		}
-	}
-	if (_rows.isEmpty()) {
-		_rows.push_back(App::self());
-		_dates.push_back(date(MTP_int(_channel->date)));
-		_roles.push_back(MemberRoleSelf);
-		_datas.push_back(0);
-	}
-
-	clearSel();
-	_loading = false;
-	refresh();
-
-	emit loaded();
-}
-
-bool MembersInner::membersFailed(const RPCError &error, mtpRequestId req) {
-	if (MTP::isDefaultHandledError(error)) return false;
-
-	Ui::hideLayer();
-	return true;
-}
-
-void MembersInner::kickDone(const MTPUpdates &result, mtpRequestId req) {
-	App::main()->sentUpdatesReceived(result);
-
-	if (_kickRequestId != req) return;
-	removeKicked();
-	if (_kickBox) _kickBox->onClose();
-}
-
-void MembersInner::kickAdminDone(const MTPUpdates &result, mtpRequestId req) {
-	if (_kickRequestId != req) return;
-	if (App::main()) App::main()->sentUpdatesReceived(result);
-	removeKicked();
-	if (_kickBox) _kickBox->onClose();
-}
-
-bool MembersInner::kickFail(const RPCError &error, mtpRequestId req) {
-	if (MTP::isDefaultHandledError(error)) return false;
-
-	if (_kickBox) _kickBox->onClose();
-	load();
-	return true;
-}
-
-void MembersInner::removeKicked() {
-	_kickRequestId = 0;
-	int32 index = _rows.indexOf(_kickConfirm);
-	if (index >= 0) {
-		_rows.removeAt(index);
-		delete _datas.at(index);
-		_datas.removeAt(index);
-		_dates.removeAt(index);
-		_roles.removeAt(index);
-		clearSel();
-		if (_filter == MembersFilterRecent && _channel->membersCount() > 1) {
-			_channel->setMembersCount(_channel->membersCount() - 1);
-			if (App::main()) emit App::main()->peerUpdated(_channel);
-		} else if (_filter == MembersFilterAdmins && _channel->adminsCount() > 1) {
-			_channel->setAdminsCount(_channel->adminsCount() - 1);
-			if (App::main()) emit App::main()->peerUpdated(_channel);
-		}
-		refresh();
-	}
-	_kickConfirm = 0;
-}
-
-MembersBox::MembersBox(ChannelData *channel, MembersFilter filter) : ItemListBox(st::boxScroll)
-, _inner(channel, filter)
-, _addBox(0) {
-	ItemListBox::init(&_inner);
-
-	connect(&_inner, SIGNAL(addRequested()), this, SLOT(onAdd()));
-
-	connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
-	connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int)));
-	connect(&_inner, SIGNAL(loaded()), this, SLOT(onLoaded()));
-
-	connect(&_loadTimer, SIGNAL(timeout()), &_inner, SLOT(load()));
-
-	prepare();
-}
-
-void MembersBox::keyPressEvent(QKeyEvent *e) {
-	if (e->key() == Qt::Key_Down) {
-		_inner.selectSkip(1);
-	} else if (e->key() == Qt::Key_Up) {
-		_inner.selectSkip(-1);
-	} else if (e->key() == Qt::Key_PageDown) {
-		_inner.selectSkipPage(scrollArea()->height(), 1);
-	} else if (e->key() == Qt::Key_PageUp) {
-		_inner.selectSkipPage(scrollArea()->height(), -1);
-	} else {
-		ItemListBox::keyPressEvent(e);
-	}
-}
-
-void MembersBox::paintEvent(QPaintEvent *e) {
-	Painter p(this);
-	if (paint(p)) return;
-
-	QString title(lang(_inner.filter() == MembersFilterRecent ? lng_channel_members : lng_channel_admins));
-	paintTitle(p, title);
-}
-
-void MembersBox::resizeEvent(QResizeEvent *e) {
-	ItemListBox::resizeEvent(e);
-	_inner.resize(width(), _inner.height());
-}
-
-void MembersBox::onScroll() {
-	_inner.loadProfilePhotos(scrollArea()->scrollTop());
-}
-
-void MembersBox::onAdd() {
-	if (_inner.filter() == MembersFilterRecent && _inner.channel()->membersCount() >= (_inner.channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) {
-		Ui::showLayer(new MaxInviteBox(_inner.channel()->inviteLink()), KeepOtherLayers);
-		return;
-	}
-	ContactsBox *box = new ContactsBox(_inner.channel(), _inner.filter(), _inner.already());
-	if (_inner.filter() == MembersFilterRecent) {
-		Ui::showLayer(box);
-	} else {
-		_addBox = box;
-		connect(_addBox, SIGNAL(adminAdded()), this, SLOT(onAdminAdded()));
-		Ui::showLayer(_addBox, KeepOtherLayers);
-	}
-}
-
-void MembersBox::onAdminAdded() {
-	if (!_addBox) return;
-	_addBox->onClose();
-	_addBox = 0;
-	_loadTimer.start(ReloadChannelMembersTimeout);
-}
diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h
index 646421224..0db72c0dd 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.h
+++ b/Telegram/SourceFiles/boxes/contactsbox.h
@@ -22,174 +22,27 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 
 #include "abstractbox.h"
 #include "core/single_timer.h"
+#include "ui/effects/round_image_checkbox.h"
+#include "boxes/members_box.h"
 
 namespace Dialogs {
 class Row;
 class IndexedList;
 } // namespace Dialogs
 
-enum MembersFilter {
-	MembersFilterRecent,
-	MembersFilterAdmins,
-};
-using MembersAlreadyIn = OrderedSet<UserData*>;
+namespace Ui {
+class MultiSelect;
+template <typename Widget>
+class WidgetSlideWrap;
+} // namespace Ui
 
 QString cantInviteError();
 
-class ConfirmBox;
-class ContactsInner : public TWidget, public RPCSender, private base::Subscriber {
-	Q_OBJECT
-
-private:
-	struct ContactData;
-
-public:
-	ContactsInner(CreatingGroupType creating = CreatingGroupNone);
-	ContactsInner(ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already);
-	ContactsInner(ChatData *chat, MembersFilter membersFilter);
-	ContactsInner(UserData *bot);
-	void init();
-	void initList();
-
-	void paintDialog(Painter &p, PeerData *peer, ContactData *data, bool sel);
-	void updateFilter(QString filter = QString());
-
-	void selectSkip(int32 dir);
-	void selectSkipPage(int32 h, int32 dir);
-
-	QVector<UserData*> selected();
-	QVector<MTPInputUser> selectedInputs();
-	PeerData *selectedUser();
-	bool allAdmins() const {
-		return _allAdmins.checked();
-	}
-
-	void loadProfilePhotos(int32 yFrom);
-	void chooseParticipant();
-	void changeCheckState(Dialogs::Row *row);
-	void changeCheckState(ContactData *data, PeerData *peer);
-
-	void peopleReceived(const QString &query, const QVector<MTPPeer> &people);
-
-	void refresh();
-
-	ChatData *chat() const;
-	ChannelData *channel() const;
-	MembersFilter membersFilter() const;
-	UserData *bot() const;
-	CreatingGroupType creating() const;
-
-	bool sharingBotGame() const;
-
-	int32 selectedCount() const;
-	bool hasAlreadyMembersInChannel() const {
-		return !_already.isEmpty();
-	}
-
-	void saving(bool flag);
-
-	~ContactsInner();
-
-signals:
-	void mustScrollTo(int ymin, int ymax);
-	void selectAllQuery();
-	void searchByUsername();
-	void chosenChanged();
-	void adminAdded();
-	void addRequested();
-
-public slots:
-	void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow);
-
-	void updateSel();
-	void peerUpdated(PeerData *peer);
-	void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
-
-	void onAddBot();
-	void onAddAdmin();
-	void onNoAddAdminBox(QObject *obj);
-
-	void onAllAdminsChanged();
-
-protected:
-	void paintEvent(QPaintEvent *e) override;
-	void enterEvent(QEvent *e) override;
-	void leaveEvent(QEvent *e) override;
-	void mouseMoveEvent(QMouseEvent *e) override;
-	void mousePressEvent(QMouseEvent *e) override;
-	void resizeEvent(QResizeEvent *e) override;
-
-private:
-	void updateSelectedRow();
-	void addAdminDone(const MTPUpdates &result, mtpRequestId req);
-	bool addAdminFail(const RPCError &error, mtpRequestId req);
-
-	template <typename FilterCallback>
-	void addDialogsToList(FilterCallback callback);
-
-	int32 _rowHeight;
-	int _newItemHeight = 0;
-	bool _newItemSel = false;
-
-	ChatData *_chat = nullptr;
-	ChannelData *_channel = nullptr;
-	MembersFilter _membersFilter = MembersFilterRecent;
-	UserData *_bot = nullptr;
-	CreatingGroupType _creating = CreatingGroupNone;
-	MembersAlreadyIn _already;
-
-	Checkbox _allAdmins;
-	int32 _aboutWidth;
-	Text _aboutAllAdmins, _aboutAdmins;
-
-	PeerData *_addToPeer = nullptr;
-	UserData *_addAdmin = nullptr;
-	mtpRequestId _addAdminRequestId = 0;
-	ConfirmBox *_addAdminBox = nullptr;
-
-	int32 _time;
-
-	std_::unique_ptr<Dialogs::IndexedList> _customList;
-	Dialogs::IndexedList *_contacts = nullptr;
-	Dialogs::Row *_sel = nullptr;
-	QString _filter;
-	typedef QVector<Dialogs::Row*> FilteredDialogs;
-	FilteredDialogs _filtered;
-	int _filteredSel = -1;
-	bool _mouseSel = false;
-
-	int _selCount = 0;
-
-	struct ContactData {
-		Text name;
-		QString online;
-		bool onlineColor;
-		bool inchat;
-		bool check;
+inline Ui::RoundImageCheckbox::PaintRoundImage PaintUserpicCallback(PeerData *peer) {
+	return [peer](Painter &p, int x, int y, int outerWidth, int size) {
+		peer->paintUserpicLeft(p, size, x, y, outerWidth);
 	};
-	typedef QMap<PeerData*, ContactData*> ContactsData;
-	ContactsData _contactsData;
-	typedef QMap<PeerData*, bool> CheckedContacts;
-	CheckedContacts _checkedContacts;
-
-	ContactData *contactData(Dialogs::Row *row);
-
-	bool _searching = false;
-	QString _lastQuery;
-	typedef QVector<PeerData*> ByUsernameRows;
-	typedef QVector<ContactData*> ByUsernameDatas;
-	ByUsernameRows _byUsername, _byUsernameFiltered;
-	ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas
-	ByUsernameDatas _byUsernameDatas;
-	int _byUsernameSel = -1;
-
-	QPoint _lastMousePos;
-	LinkButton _addContactLnk;
-
-	bool _saving = false;
-	bool _allAdminsChecked = false;
-
-};
+}
 
 class ContactsBox : public ItemListBox, public RPCSender {
 	Q_OBJECT
@@ -205,10 +58,7 @@ public:
 signals:
 	void adminAdded();
 
-public slots:
-	void onFilterUpdate();
-	void onFilterCancel();
-	void onChosenChanged();
+private slots:
 	void onScroll();
 
 	void onInvite();
@@ -231,15 +81,21 @@ 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);
 
-	ContactsInner _inner;
-	InputField _filter;
-	IconedButton _filterCancel;
+	class Inner;
+	ChildWidget<Inner> _inner;
+	ChildWidget<Ui::WidgetSlideWrap<Ui::MultiSelect>> _select;
 
 	BoxButton _next, _cancel;
 	MembersFilter _membersFilter;
 
-	ScrollableBoxShadow _topShadow, *_bottomShadow;
+	ScrollableBoxShadow _topShadow;
+	ScrollableBoxShadow *_bottomShadow = nullptr;
 
 	void peopleReceived(const MTPcontacts_Found &result, mtpRequestId req);
 	bool peopleFailed(const RPCError &error, mtpRequestId req);
@@ -255,7 +111,7 @@ private:
 	typedef QMap<mtpRequestId, QString> PeopleQueries;
 	PeopleQueries _peopleQueries;
 
-	int32 _saveRequestId;
+	mtpRequestId _saveRequestId = 0;
 
 	// saving admins
 	void saveAdminsDone(const MTPUpdates &result);
@@ -275,50 +131,75 @@ private:
 
 };
 
-class MembersInner : public TWidget, public RPCSender, private base::Subscriber {
+// This class is hold in header because it requires Qt preprocessing.
+class ContactsBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
 	Q_OBJECT
 
-private:
-	struct MemberData;
-
 public:
-	MembersInner(ChannelData *channel, MembersFilter filter);
+	Inner(QWidget *parent, CreatingGroupType creating = CreatingGroupNone);
+	Inner(QWidget *parent, ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already);
+	Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter);
+	Inner(QWidget *parent, UserData *bot);
 
-	void paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown);
+	void setPeerSelectedChangedCallback(base::lambda_unique<void(PeerData *peer, bool selected)> callback);
+	void peerUnselected(PeerData *peer);
+
+	void updateFilter(QString filter = QString());
+	void updateSelection();
 
 	void selectSkip(int32 dir);
 	void selectSkipPage(int32 h, int32 dir);
 
+	QVector<UserData*> selected();
+	QVector<MTPInputUser> selectedInputs();
+	bool allAdmins() const {
+		return _allAdmins.checked();
+	}
+	void setAllAdminsChangedCallback(base::lambda_unique<void()> allAdminsChangedCallback) {
+		_allAdminsChangedCallback = std_::move(allAdminsChangedCallback);
+	}
+
 	void loadProfilePhotos(int32 yFrom);
 	void chooseParticipant();
 
+	void peopleReceived(const QString &query, const QVector<MTPPeer> &people);
+
 	void refresh();
 
+	ChatData *chat() const;
 	ChannelData *channel() const;
-	MembersFilter filter() const;
+	MembersFilter membersFilter() const;
+	UserData *bot() const;
+	CreatingGroupType creating() const;
 
-	bool isLoaded() const {
-		return !_loading;
+	bool sharingBotGame() const;
+
+	int32 selectedCount() const;
+	bool hasAlreadyMembersInChannel() const {
+		return !_already.isEmpty();
 	}
-	void clearSel();
 
-	MembersAlreadyIn already() const;
+	void saving(bool flag);
 
-	~MembersInner();
+	~Inner();
 
 signals:
 	void mustScrollTo(int ymin, int ymax);
+	void searchByUsername();
+	void adminAdded();
 	void addRequested();
-	void loaded();
 
-public slots:
-	void load();
+private slots:
+	void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow);
 
-	void updateSel();
 	void peerUpdated(PeerData *peer);
 	void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
-	void onKickConfirm();
-	void onKickBoxDestroyed(QObject *obj);
+
+	void onAddBot();
+	void onAddAdmin();
+	void onNoAddAdminBox(QObject *obj);
+
+	void onAllAdminsChanged();
 
 protected:
 	void paintEvent(QPaintEvent *e) override;
@@ -326,96 +207,103 @@ protected:
 	void leaveEvent(QEvent *e) override;
 	void mouseMoveEvent(QMouseEvent *e) override;
 	void mousePressEvent(QMouseEvent *e) override;
-	void mouseReleaseEvent(QMouseEvent *e) override;
-
-private:
-	void updateSelectedRow();
-	MemberData *data(int32 index);
-
-	void membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req);
-	bool membersFailed(const RPCError &error, mtpRequestId req);
-
-	void kickDone(const MTPUpdates &result, mtpRequestId req);
-	void kickAdminDone(const MTPUpdates &result, mtpRequestId req);
-	bool kickFail(const RPCError &error, mtpRequestId req);
-	void removeKicked();
-
-	void clear();
-
-	int32 _rowHeight, _newItemHeight;
-	bool _newItemSel;
-
-	ChannelData *_channel;
-	MembersFilter _filter;
-
-	QString _kickText;
-	int32 _time, _kickWidth;
-
-	int32 _sel, _kickSel, _kickDown;
-	bool _mouseSel;
-
-	UserData *_kickConfirm;
-	mtpRequestId _kickRequestId;
-
-	ConfirmBox *_kickBox;
-
-	enum MemberRole {
-		MemberRoleNone,
-		MemberRoleSelf,
-		MemberRoleCreator,
-		MemberRoleEditor,
-		MemberRoleModerator,
-		MemberRoleKicked
-	};
-
-	struct MemberData {
-		Text name;
-		QString online;
-		bool onlineColor;
-		bool canKick;
-	};
-
-	bool _loading;
-	mtpRequestId _loadingRequestId;
-	typedef QVector<UserData*> MemberRows;
-	typedef QVector<QDateTime> MemberDates;
-	typedef QVector<MemberRole> MemberRoles;
-	typedef QVector<MemberData*> MemberDatas;
-	MemberRows _rows;
-	MemberDates _dates;
-	MemberRoles _roles;
-	MemberDatas _datas;
-
-	int32 _aboutWidth;
-	Text _about;
-	int32 _aboutHeight;
-
-	QPoint _lastMousePos;
-
-};
-
-class MembersBox : public ItemListBox {
-	Q_OBJECT
-
-public:
-	MembersBox(ChannelData *channel, MembersFilter filter);
-
-public slots:
-	void onScroll();
-
-	void onAdd();
-	void onAdminAdded();
-
-protected:
-	void keyPressEvent(QKeyEvent *e) override;
-	void paintEvent(QPaintEvent *e) override;
 	void resizeEvent(QResizeEvent *e) override;
 
 private:
-	MembersInner _inner;
+	struct ContactData {
+		ContactData() = default;
+		ContactData(PeerData *peer, base::lambda_wrap<void()> updateCallback);
 
-	ContactsBox *_addBox;
+		std_::unique_ptr<Ui::RoundImageCheckbox> checkbox;
+		Text name;
+		QString statusText;
+		bool statusHasOnlineColor = false;
+		bool disabledChecked = false;
+	};
 
-	SingleTimer _loadTimer;
+	void init();
+	void initList();
+
+	void updateRowWithTop(int rowTop);
+	int getSelectedRowTop() const;
+	void updateSelectedRow();
+	int getRowTopWithPeer(PeerData *peer) const;
+	void updateRowWithPeer(PeerData *peer);
+	void addAdminDone(const MTPUpdates &result, mtpRequestId req);
+	bool addAdminFail(const RPCError &error, mtpRequestId req);
+
+	void paintDialog(Painter &p, uint64 ms, PeerData *peer, ContactData *data, bool sel);
+	void paintDisabledCheckUserpic(Painter &p, PeerData *peer, int x, int y, int outerWidth) const;
+
+	void changeCheckState(Dialogs::Row *row);
+	void changeCheckState(ContactData *data, PeerData *peer);
+	enum class ChangeStateWay {
+		Default,
+		SkipCallback,
+	};
+	void changePeerCheckState(ContactData *data, PeerData *peer, bool checked, ChangeStateWay useCallback = ChangeStateWay::Default);
+
+	template <typename FilterCallback>
+	void addDialogsToList(FilterCallback callback);
+
+	bool usingMultiSelect() const {
+		return (_chat != nullptr) || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilter::Admins));
+	}
+
+	base::lambda_unique<void(PeerData *peer, bool selected)> _peerSelectedChangedCallback;
+
+	int32 _rowHeight;
+	int _newItemHeight = 0;
+	bool _newItemSel = false;
+
+	ChatData *_chat = nullptr;
+	ChannelData *_channel = nullptr;
+	MembersFilter _membersFilter = MembersFilter::Recent;
+	UserData *_bot = nullptr;
+	CreatingGroupType _creating = CreatingGroupNone;
+	MembersAlreadyIn _already;
+
+	Checkbox _allAdmins;
+	int32 _aboutWidth;
+	Text _aboutAllAdmins, _aboutAdmins;
+	base::lambda_unique<void()> _allAdminsChangedCallback;
+
+	PeerData *_addToPeer = nullptr;
+	UserData *_addAdmin = nullptr;
+	mtpRequestId _addAdminRequestId = 0;
+	ConfirmBox *_addAdminBox = nullptr;
+
+	int32 _time;
+
+	std_::unique_ptr<Dialogs::IndexedList> _customList;
+	Dialogs::IndexedList *_contacts = nullptr;
+	Dialogs::Row *_sel = nullptr;
+	QString _filter;
+	using FilteredDialogs = QVector<Dialogs::Row*>;
+	FilteredDialogs _filtered;
+	int _filteredSel = -1;
+	bool _mouseSel = false;
+
+	using ContactsData = QMap<PeerData*, ContactData*>;
+	ContactsData _contactsData;
+	using CheckedContacts = OrderedSet<PeerData*>;
+	CheckedContacts _checkedContacts;
+
+	ContactData *contactData(Dialogs::Row *row);
+
+	bool _searching = false;
+	QString _lastQuery;
+	using ByUsernameRows = QVector<PeerData*>;
+	using ByUsernameDatas = QVector<ContactData*>;
+	ByUsernameRows _byUsername, _byUsernameFiltered;
+	ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas
+	ByUsernameDatas _byUsernameDatas;
+	int _byUsernameSel = -1;
+
+	QPoint _lastMousePos;
+	LinkButton _addContactLnk;
+
+	bool _saving = false;
+	bool _allAdminsChecked = false;
 
 };
diff --git a/Telegram/SourceFiles/boxes/members_box.cpp b/Telegram/SourceFiles/boxes/members_box.cpp
new file mode 100644
index 000000000..8290a5bec
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/members_box.cpp
@@ -0,0 +1,608 @@
+/*
+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-2016 John Preston, https://desktop.telegram.org
+*/
+#include "stdafx.h"
+#include "boxes/members_box.h"
+
+#include "styles/style_boxes.h"
+#include "styles/style_dialogs.h"
+#include "lang.h"
+#include "mainwidget.h"
+#include "mainwindow.h"
+#include "boxes/contactsbox.h"
+#include "boxes/confirmbox.h"
+#include "observer_peer.h"
+
+MembersBox::MembersBox(ChannelData *channel, MembersFilter filter) : ItemListBox(st::boxScroll)
+, _inner(this, channel, filter) {
+	ItemListBox::init(_inner);
+
+	connect(_inner, SIGNAL(addRequested()), this, SLOT(onAdd()));
+
+	connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
+	connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int)));
+
+	connect(&_loadTimer, SIGNAL(timeout()), _inner, SLOT(load()));
+
+	prepare();
+}
+
+void MembersBox::keyPressEvent(QKeyEvent *e) {
+	if (e->key() == Qt::Key_Down) {
+		_inner->selectSkip(1);
+	} else if (e->key() == Qt::Key_Up) {
+		_inner->selectSkip(-1);
+	} else if (e->key() == Qt::Key_PageDown) {
+		_inner->selectSkipPage(scrollArea()->height(), 1);
+	} else if (e->key() == Qt::Key_PageUp) {
+		_inner->selectSkipPage(scrollArea()->height(), -1);
+	} else {
+		ItemListBox::keyPressEvent(e);
+	}
+}
+
+void MembersBox::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+	if (paint(p)) return;
+
+	QString title(lang(_inner->filter() == MembersFilter::Recent ? lng_channel_members : lng_channel_admins));
+	paintTitle(p, title);
+}
+
+void MembersBox::resizeEvent(QResizeEvent *e) {
+	ItemListBox::resizeEvent(e);
+	_inner->resize(width(), _inner->height());
+}
+
+void MembersBox::onScroll() {
+	_inner->loadProfilePhotos(scrollArea()->scrollTop());
+}
+
+void MembersBox::onAdd() {
+	if (_inner->filter() == MembersFilter::Recent && _inner->channel()->membersCount() >= (_inner->channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) {
+		Ui::showLayer(new MaxInviteBox(_inner->channel()->inviteLink()), KeepOtherLayers);
+		return;
+	}
+	ContactsBox *box = new ContactsBox(_inner->channel(), _inner->filter(), _inner->already());
+	if (_inner->filter() == MembersFilter::Recent) {
+		Ui::showLayer(box);
+	} else {
+		_addBox = box;
+		connect(_addBox, SIGNAL(adminAdded()), this, SLOT(onAdminAdded()));
+		Ui::showLayer(_addBox, KeepOtherLayers);
+	}
+}
+
+void MembersBox::onAdminAdded() {
+	if (!_addBox) return;
+	_addBox->onClose();
+	_addBox = 0;
+	_loadTimer.start(ReloadChannelMembersTimeout);
+}
+
+MembersBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter filter) : ScrolledWidget(parent)
+, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
+, _newItemHeight((channel->amCreator() && (channel->membersCount() < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilter::Admins)) ? st::contactsNewItemHeight : 0)
+, _newItemSel(false)
+, _channel(channel)
+, _filter(filter)
+, _kickText(lang(lng_profile_kick))
+, _time(0)
+, _kickWidth(st::normalFont->width(_kickText))
+, _sel(-1)
+, _kickSel(-1)
+, _kickDown(-1)
+, _mouseSel(false)
+, _kickConfirm(0)
+, _kickRequestId(0)
+, _kickBox(0)
+, _loading(true)
+, _loadingRequestId(0)
+, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right())
+, _about(_aboutWidth)
+, _aboutHeight(0) {
+	subscribe(FileDownload::ImageLoaded(), [this] { update(); });
+
+	connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)));
+	connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*)));
+
+	refresh();
+
+	load();
+}
+
+void MembersBox::Inner::load() {
+	if (!_loadingRequestId) {
+		_loadingRequestId = MTP::send(MTPchannels_GetParticipants(_channel->inputChannel, (_filter == MembersFilter::Recent) ? MTP_channelParticipantsRecent() : MTP_channelParticipantsAdmins(), MTP_int(0), MTP_int(Global::ChatSizeMax())), rpcDone(&Inner::membersReceived), rpcFail(&Inner::membersFailed));
+	}
+}
+
+void MembersBox::Inner::paintEvent(QPaintEvent *e) {
+	QRect r(e->rect());
+	Painter p(this);
+
+	_time = unixtime();
+	p.fillRect(r, st::white->b);
+
+	int32 yFrom = r.y() - st::membersPadding.top(), yTo = r.y() + r.height() - st::membersPadding.top();
+	p.translate(0, st::membersPadding.top());
+	if (_rows.isEmpty()) {
+		p.setFont(st::noContactsFont->f);
+		p.setPen(st::noContactsColor->p);
+		p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center);
+	} else {
+		if (_newItemHeight) {
+			p.fillRect(0, 0, width(), _newItemHeight, (_newItemSel ? st::contactsBgOver : st::white)->b);
+			st::contactsNewItemIcon.paint(p, 0, 0, width());
+			p.setFont(st::contactsNameFont);
+			p.setPen(st::contactsNewItemFg);
+			p.drawTextLeft(st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(_filter == MembersFilter::Admins ? lng_channel_add_admins : lng_channel_add_members));
+
+			yFrom -= _newItemHeight;
+			yTo -= _newItemHeight;
+			p.translate(0, _newItemHeight);
+		}
+		int32 from = floorclamp(yFrom, _rowHeight, 0, _rows.size());
+		int32 to = ceilclamp(yTo, _rowHeight, 0, _rows.size());
+		p.translate(0, from * _rowHeight);
+		for (; from < to; ++from) {
+			bool sel = (from == _sel);
+			bool kickSel = (from == _kickSel && (_kickDown < 0 || from == _kickDown));
+			bool kickDown = kickSel && (from == _kickDown);
+			paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown);
+			p.translate(0, _rowHeight);
+		}
+		if (to == _rows.size() && _filter == MembersFilter::Recent && (_rows.size() < _channel->membersCount() || _rows.size() >= Global::ChatSizeMax())) {
+			p.setPen(st::stickersReorderFg);
+			_about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center);
+		}
+	}
+}
+
+void MembersBox::Inner::enterEvent(QEvent *e) {
+	setMouseTracking(true);
+}
+
+void MembersBox::Inner::leaveEvent(QEvent *e) {
+	_mouseSel = false;
+	setMouseTracking(false);
+	if (_sel >= 0) {
+		clearSel();
+	}
+}
+
+void MembersBox::Inner::mouseMoveEvent(QMouseEvent *e) {
+	_mouseSel = true;
+	_lastMousePos = e->globalPos();
+	updateSel();
+}
+
+void MembersBox::Inner::mousePressEvent(QMouseEvent *e) {
+	_mouseSel = true;
+	_lastMousePos = e->globalPos();
+	updateSel();
+	if (e->button() == Qt::LeftButton && _kickSel < 0) {
+		chooseParticipant();
+	}
+	_kickDown = _kickSel;
+	update();
+}
+
+void MembersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
+	_mouseSel = true;
+	_lastMousePos = e->globalPos();
+	updateSel();
+	if (_kickDown >= 0 && _kickDown == _kickSel && !_kickRequestId) {
+		_kickConfirm = _rows.at(_kickSel);
+		if (_kickBox) _kickBox->deleteLater();
+		_kickBox = new ConfirmBox((_filter == MembersFilter::Recent ? (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel) : lng_profile_sure_kick_admin)(lt_user, _kickConfirm->firstName));
+		connect(_kickBox, SIGNAL(confirmed()), this, SLOT(onKickConfirm()));
+		connect(_kickBox, SIGNAL(destroyed(QObject*)), this, SLOT(onKickBoxDestroyed(QObject*)));
+		Ui::showLayer(_kickBox, KeepOtherLayers);
+	}
+	_kickDown = -1;
+}
+
+void MembersBox::Inner::onKickBoxDestroyed(QObject *obj) {
+	if (_kickBox == obj) {
+		_kickBox = 0;
+	}
+}
+
+void MembersBox::Inner::onKickConfirm() {
+	if (_filter == MembersFilter::Recent) {
+		_kickRequestId = MTP::send(MTPchannels_KickFromChannel(_channel->inputChannel, _kickConfirm->inputUser, MTP_bool(true)), rpcDone(&Inner::kickDone), rpcFail(&Inner::kickFail));
+	} else {
+		_kickRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _kickConfirm->inputUser, MTP_channelRoleEmpty()), rpcDone(&Inner::kickAdminDone), rpcFail(&Inner::kickFail));
+	}
+}
+
+void MembersBox::Inner::paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown) {
+	UserData *user = peer->asUser();
+
+	p.fillRect(0, 0, width(), _rowHeight, (sel ? st::contactsBgOver : st::white)->b);
+	peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width());
+
+	p.setPen(st::black);
+
+	int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
+	int32 namew = width() - namex - st::contactsPadding.right() - (data->canKick ? (_kickWidth + st::contactsCheckPosition.x() * 2) : 0);
+	if (peer->isVerified()) {
+		auto icon = &st::dialogsVerifiedIcon;
+		namew -= icon->width();
+		icon->paint(p, namex + qMin(data->name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width());
+	}
+	data->name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width());
+
+	if (data->canKick) {
+		p.setFont((kickSel ? st::linkOverFont : st::linkFont)->f);
+		if (kickDown) {
+			p.setPen(st::btnDefLink.downColor->p);
+		} else {
+			p.setPen(st::btnDefLink.color->p);
+		}
+		p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), _kickText, _kickWidth);
+	}
+
+	p.setFont(st::contactsStatusFont->f);
+	p.setPen(data->onlineColor ? st::contactsStatusFgOnline : (sel ? st::contactsStatusFgOver : st::contactsStatusFg));
+	p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->online);
+}
+
+void MembersBox::Inner::selectSkip(int32 dir) {
+	_time = unixtime();
+	_mouseSel = false;
+
+	int cur = -1;
+	if (_newItemHeight && _newItemSel) {
+		cur = 0;
+	} else if (_sel >= 0) {
+		cur = _sel + (_newItemHeight ? 1 : 0);
+	}
+	cur += dir;
+	if (cur <= 0) {
+		_newItemSel = _newItemHeight ? true : false;
+		_sel = (_newItemSel || _rows.isEmpty()) ? -1 : 0;
+	} else if (cur >= _rows.size() + (_newItemHeight ? 1 : 0)) {
+		_sel = -1;
+	} else {
+		_sel = cur - (_newItemHeight ? 1 : 0);
+	}
+	if (dir > 0) {
+		if (_sel < 0 || _sel >= _rows.size()) {
+			_sel = -1;
+		}
+	} else {
+		if (!_rows.isEmpty()) {
+			if (_sel < 0 && !_newItemSel) _sel = _rows.size() - 1;
+		}
+	}
+	if (_newItemSel) {
+		emit mustScrollTo(0, _newItemHeight);
+	} else if (_sel >= 0) {
+		emit mustScrollTo(_newItemHeight + _sel * _rowHeight, _newItemHeight + (_sel + 1) * _rowHeight);
+	}
+
+	update();
+}
+
+void MembersBox::Inner::selectSkipPage(int32 h, int32 dir) {
+	int32 points = h / _rowHeight;
+	if (!points) return;
+	selectSkip(points * dir);
+}
+
+void MembersBox::Inner::loadProfilePhotos(int32 yFrom) {
+	int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5;
+	MTP::clearLoaderPriorities();
+
+	if (yTo < 0) return;
+	if (yFrom < 0) yFrom = 0;
+
+	if (!_rows.isEmpty()) {
+		int32 from = (yFrom - _newItemHeight) / _rowHeight;
+		if (from < 0) from = 0;
+		if (from < _rows.size()) {
+			int32 to = ((yTo - _newItemHeight) / _rowHeight) + 1;
+			if (to > _rows.size()) to = _rows.size();
+
+			for (; from < to; ++from) {
+				_rows[from]->loadUserpic();
+			}
+		}
+	}
+}
+
+void MembersBox::Inner::chooseParticipant() {
+	if (_newItemSel) {
+		emit addRequested();
+		return;
+	}
+	if (_sel < 0 || _sel >= _rows.size()) return;
+	if (PeerData *peer = _rows[_sel]) {
+		Ui::hideLayer();
+		Ui::showPeerProfile(peer);
+	}
+}
+
+void MembersBox::Inner::refresh() {
+	if (_rows.isEmpty()) {
+		resize(width(), st::membersPadding.top() + st::noContactsHeight + st::membersPadding.bottom());
+		_aboutHeight = 0;
+	} else {
+		_about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size()));
+		_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
+		if (_filter != MembersFilter::Recent || (_rows.size() >= _channel->membersCount() && _rows.size() < Global::ChatSizeMax())) {
+			_aboutHeight = 0;
+		}
+		resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight);
+	}
+	update();
+}
+
+ChannelData *MembersBox::Inner::channel() const {
+	return _channel;
+}
+
+MembersFilter MembersBox::Inner::filter() const {
+	return _filter;
+}
+
+MembersAlreadyIn MembersBox::Inner::already() const {
+	MembersAlreadyIn result;
+	for_const (auto peer, _rows) {
+		if (peer->isUser()) {
+			result.insert(peer->asUser());
+		}
+	}
+	return result;
+}
+
+void MembersBox::Inner::clearSel() {
+	updateSelectedRow();
+	_newItemSel = false;
+	_sel = _kickSel = _kickDown = -1;
+	_lastMousePos = QCursor::pos();
+	updateSel();
+}
+
+MembersBox::Inner::MemberData *MembersBox::Inner::data(int32 index) {
+	if (MemberData *result = _datas.at(index)) {
+		return result;
+	}
+	MemberData *result = _datas[index] = new MemberData();
+	result->name.setText(st::contactsNameFont, _rows[index]->name, _textNameOptions);
+	int32 t = unixtime();
+	result->online = App::onlineText(_rows[index], t);// lng_mediaview_date_time(lt_date, _dates[index].date().toString(qsl("dd.MM.yy")), lt_time, _dates[index].time().toString(cTimeFormat()));
+	result->onlineColor = App::onlineColorUse(_rows[index], t);
+	if (_filter == MembersFilter::Recent) {
+		result->canKick = (_channel->amCreator() || _channel->amEditor() || _channel->amModerator()) ? (_roles[index] == MemberRole::None) : false;
+	} else if (_filter == MembersFilter::Admins) {
+		result->canKick = _channel->amCreator() ? (_roles[index] == MemberRole::Editor || _roles[index] == MemberRole::Moderator) : false;
+	} else {
+		result->canKick = false;
+	}
+	return result;
+}
+
+void MembersBox::Inner::clear() {
+	for (int32 i = 0, l = _datas.size(); i < l; ++i) {
+		delete _datas.at(i);
+	}
+	_datas.clear();
+	_rows.clear();
+	_dates.clear();
+	_roles.clear();
+	if (_kickBox) _kickBox->deleteLater();
+	clearSel();
+}
+
+MembersBox::Inner::~Inner() {
+	clear();
+}
+
+void MembersBox::Inner::updateSel() {
+	if (!_mouseSel) return;
+
+	QPoint p(mapFromGlobal(_lastMousePos));
+	p.setY(p.y() - st::membersPadding.top());
+	bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos));
+	bool newItemSel = (in && p.y() >= 0 && p.y() < _newItemHeight);
+	int32 newSel = (in && !newItemSel && p.y() >= _newItemHeight && p.y() < _newItemHeight + _rows.size() * _rowHeight) ? ((p.y() - _newItemHeight) / _rowHeight) : -1;
+	int32 newKickSel = newSel;
+	if (newSel >= 0 && (!data(newSel)->canKick || !QRect(width() - _kickWidth - st::contactsPadding.right() - st::contactsCheckPosition.x(), _newItemHeight + newSel * _rowHeight + st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, _kickWidth, st::normalFont->height).contains(p))) {
+		newKickSel = -1;
+	}
+	if (newSel != _sel || newKickSel != _kickSel || newItemSel != _newItemSel) {
+		updateSelectedRow();
+		_newItemSel = newItemSel;
+		_sel = newSel;
+		_kickSel = newKickSel;
+		updateSelectedRow();
+		setCursor(_kickSel >= 0 ? style::cur_pointer : style::cur_default);
+	}
+}
+
+void MembersBox::Inner::peerUpdated(PeerData *peer) {
+	update();
+}
+
+void MembersBox::Inner::updateSelectedRow() {
+	if (_newItemSel) {
+		update(0, st::membersPadding.top(), width(), _newItemHeight);
+	}
+	if (_sel >= 0) {
+		update(0, st::membersPadding.top() + _newItemHeight + _sel * _rowHeight, width(), _rowHeight);
+	}
+}
+
+void MembersBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
+	for (int32 i = 0, l = _rows.size(); i < l; ++i) {
+		if (_rows.at(i) == peer) {
+			if (_datas.at(i)) {
+				_datas.at(i)->name.setText(st::contactsNameFont, peer->name, _textNameOptions);
+				update(0, st::membersPadding.top() + i * _rowHeight, width(), _rowHeight);
+			} else {
+				break;
+			}
+		}
+	}
+}
+
+void MembersBox::Inner::membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req) {
+	clear();
+	_loadingRequestId = 0;
+
+	if (result.type() == mtpc_channels_channelParticipants) {
+		const auto &d(result.c_channels_channelParticipants());
+		const auto &v(d.vparticipants.c_vector().v);
+		_rows.reserve(v.size());
+		_datas.reserve(v.size());
+		_dates.reserve(v.size());
+		_roles.reserve(v.size());
+
+		if (_filter == MembersFilter::Recent && _channel->membersCount() < d.vcount.v) {
+			_channel->setMembersCount(d.vcount.v);
+			if (App::main()) emit App::main()->peerUpdated(_channel);
+		} else if (_filter == MembersFilter::Admins && _channel->adminsCount() < d.vcount.v) {
+			_channel->setAdminsCount(d.vcount.v);
+			if (App::main()) emit App::main()->peerUpdated(_channel);
+		}
+		App::feedUsers(d.vusers);
+
+		for (QVector<MTPChannelParticipant>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
+			int32 userId = 0, addedTime = 0;
+			MemberRole role = MemberRole::None;
+			switch (i->type()) {
+			case mtpc_channelParticipant:
+				userId = i->c_channelParticipant().vuser_id.v;
+				addedTime = i->c_channelParticipant().vdate.v;
+				break;
+			case mtpc_channelParticipantSelf:
+				role = MemberRole::Self;
+				userId = i->c_channelParticipantSelf().vuser_id.v;
+				addedTime = i->c_channelParticipantSelf().vdate.v;
+				break;
+			case mtpc_channelParticipantModerator:
+				role = MemberRole::Moderator;
+				userId = i->c_channelParticipantModerator().vuser_id.v;
+				addedTime = i->c_channelParticipantModerator().vdate.v;
+				break;
+			case mtpc_channelParticipantEditor:
+				role = MemberRole::Editor;
+				userId = i->c_channelParticipantEditor().vuser_id.v;
+				addedTime = i->c_channelParticipantEditor().vdate.v;
+				break;
+			case mtpc_channelParticipantKicked:
+				userId = i->c_channelParticipantKicked().vuser_id.v;
+				addedTime = i->c_channelParticipantKicked().vdate.v;
+				role = MemberRole::Kicked;
+				break;
+			case mtpc_channelParticipantCreator:
+				userId = i->c_channelParticipantCreator().vuser_id.v;
+				addedTime = _channel->date;
+				role = MemberRole::Creator;
+				break;
+			}
+			if (UserData *user = App::userLoaded(userId)) {
+				_rows.push_back(user);
+				_dates.push_back(date(addedTime));
+				_roles.push_back(role);
+				_datas.push_back(0);
+			}
+		}
+
+		// update admins if we got all of them
+		if (_filter == MembersFilter::Admins && _channel->isMegagroup() && _rows.size() < Global::ChatSizeMax()) {
+			_channel->mgInfo->lastAdmins.clear();
+			for (int32 i = 0, l = _rows.size(); i != l; ++i) {
+				if (_roles.at(i) == MemberRole::Creator || _roles.at(i) == MemberRole::Editor) {
+					_channel->mgInfo->lastAdmins.insert(_rows.at(i));
+				}
+			}
+
+			Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged);
+		}
+	}
+	if (_rows.isEmpty()) {
+		_rows.push_back(App::self());
+		_dates.push_back(date(MTP_int(_channel->date)));
+		_roles.push_back(MemberRole::Self);
+		_datas.push_back(0);
+	}
+
+	clearSel();
+	_loading = false;
+	refresh();
+
+	emit loaded();
+}
+
+bool MembersBox::Inner::membersFailed(const RPCError &error, mtpRequestId req) {
+	if (MTP::isDefaultHandledError(error)) return false;
+
+	Ui::hideLayer();
+	return true;
+}
+
+void MembersBox::Inner::kickDone(const MTPUpdates &result, mtpRequestId req) {
+	App::main()->sentUpdatesReceived(result);
+
+	if (_kickRequestId != req) return;
+	removeKicked();
+	if (_kickBox) _kickBox->onClose();
+}
+
+void MembersBox::Inner::kickAdminDone(const MTPUpdates &result, mtpRequestId req) {
+	if (_kickRequestId != req) return;
+	if (App::main()) App::main()->sentUpdatesReceived(result);
+	removeKicked();
+	if (_kickBox) _kickBox->onClose();
+}
+
+bool MembersBox::Inner::kickFail(const RPCError &error, mtpRequestId req) {
+	if (MTP::isDefaultHandledError(error)) return false;
+
+	if (_kickBox) _kickBox->onClose();
+	load();
+	return true;
+}
+
+void MembersBox::Inner::removeKicked() {
+	_kickRequestId = 0;
+	int32 index = _rows.indexOf(_kickConfirm);
+	if (index >= 0) {
+		_rows.removeAt(index);
+		delete _datas.at(index);
+		_datas.removeAt(index);
+		_dates.removeAt(index);
+		_roles.removeAt(index);
+		clearSel();
+		if (_filter == MembersFilter::Recent && _channel->membersCount() > 1) {
+			_channel->setMembersCount(_channel->membersCount() - 1);
+			if (App::main()) emit App::main()->peerUpdated(_channel);
+		} else if (_filter == MembersFilter::Admins && _channel->adminsCount() > 1) {
+			_channel->setAdminsCount(_channel->adminsCount() - 1);
+			if (App::main()) emit App::main()->peerUpdated(_channel);
+		}
+		refresh();
+	}
+	_kickConfirm = 0;
+}
diff --git a/Telegram/SourceFiles/boxes/members_box.h b/Telegram/SourceFiles/boxes/members_box.h
new file mode 100644
index 000000000..f93f7a7b2
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/members_box.h
@@ -0,0 +1,178 @@
+/*
+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-2016 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+#include "abstractbox.h"
+#include "core/single_timer.h"
+#include "ui/effects/round_image_checkbox.h"
+
+class ContactsBox;
+class ConfirmBox;
+
+enum class MembersFilter {
+	Recent,
+	Admins,
+};
+using MembersAlreadyIn = OrderedSet<UserData*>;
+
+class MembersBox : public ItemListBox {
+	Q_OBJECT
+
+public:
+	MembersBox(ChannelData *channel, MembersFilter filter);
+
+public slots:
+	void onScroll();
+
+	void onAdd();
+	void onAdminAdded();
+
+protected:
+	void keyPressEvent(QKeyEvent *e) override;
+	void paintEvent(QPaintEvent *e) override;
+	void resizeEvent(QResizeEvent *e) override;
+
+private:
+	class Inner;
+	ChildWidget<Inner> _inner;
+
+	ContactsBox *_addBox = nullptr;
+
+	SingleTimer _loadTimer;
+
+};
+
+// This class is hold in header because it requires Qt preprocessing.
+class MembersBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
+	Q_OBJECT
+
+public:
+	Inner(QWidget *parent, ChannelData *channel, MembersFilter filter);
+
+	void selectSkip(int32 dir);
+	void selectSkipPage(int32 h, int32 dir);
+
+	void loadProfilePhotos(int32 yFrom);
+	void chooseParticipant();
+
+	void refresh();
+
+	ChannelData *channel() const;
+	MembersFilter filter() const;
+
+	bool isLoaded() const {
+		return !_loading;
+	}
+	void clearSel();
+
+	MembersAlreadyIn already() const;
+
+	~Inner();
+
+signals:
+	void mustScrollTo(int ymin, int ymax);
+	void addRequested();
+	void loaded();
+
+public slots:
+	void load();
+
+	void updateSel();
+	void peerUpdated(PeerData *peer);
+	void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
+	void onKickConfirm();
+	void onKickBoxDestroyed(QObject *obj);
+
+protected:
+	void paintEvent(QPaintEvent *e) override;
+	void enterEvent(QEvent *e) override;
+	void leaveEvent(QEvent *e) override;
+	void mouseMoveEvent(QMouseEvent *e) override;
+	void mousePressEvent(QMouseEvent *e) override;
+	void mouseReleaseEvent(QMouseEvent *e) override;
+
+private:
+	struct MemberData {
+		Text name;
+		QString online;
+		bool onlineColor;
+		bool canKick;
+	};
+
+	void updateSelectedRow();
+	MemberData *data(int32 index);
+
+	void paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown);
+
+	void membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req);
+	bool membersFailed(const RPCError &error, mtpRequestId req);
+
+	void kickDone(const MTPUpdates &result, mtpRequestId req);
+	void kickAdminDone(const MTPUpdates &result, mtpRequestId req);
+	bool kickFail(const RPCError &error, mtpRequestId req);
+	void removeKicked();
+
+	void clear();
+
+	int32 _rowHeight, _newItemHeight;
+	bool _newItemSel;
+
+	ChannelData *_channel;
+	MembersFilter _filter;
+
+	QString _kickText;
+	int32 _time, _kickWidth;
+
+	int32 _sel, _kickSel, _kickDown;
+	bool _mouseSel;
+
+	UserData *_kickConfirm;
+	mtpRequestId _kickRequestId;
+
+	ConfirmBox *_kickBox;
+
+	enum class MemberRole {
+		None,
+		Self,
+		Creator,
+		Editor,
+		Moderator,
+		Kicked
+	};
+
+	bool _loading;
+	mtpRequestId _loadingRequestId;
+	typedef QVector<UserData*> MemberRows;
+	typedef QVector<QDateTime> MemberDates;
+	typedef QVector<MemberRole> MemberRoles;
+	typedef QVector<MemberData*> MemberDatas;
+	MemberRows _rows;
+	MemberDates _dates;
+	MemberRoles _roles;
+	MemberDatas _datas;
+
+	int32 _aboutWidth;
+	Text _about;
+	int32 _aboutHeight;
+
+	QPoint _lastMousePos;
+
+};
diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp
index b2fc879fa..52003f6ff 100644
--- a/Telegram/SourceFiles/boxes/sessionsbox.cpp
+++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp
@@ -30,213 +30,23 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "countries.h"
 #include "confirmbox.h"
 
-SessionsInner::SessionsInner(SessionsList *list, SessionData *current) : TWidget()
-, _list(list)
-, _current(current)
-, _terminating(0)
-, _terminateAll(this, lang(lng_sessions_terminate_all), st::redBoxLinkButton)
-, _terminateBox(0) {
-	connect(&_terminateAll, SIGNAL(clicked()), this, SLOT(onTerminateAll()));
-	_terminateAll.hide();
-	setAttribute(Qt::WA_OpaquePaintEvent);
-}
-
-void SessionsInner::paintEvent(QPaintEvent *e) {
-	QRect r(e->rect());
-	Painter p(this);
-
-	p.fillRect(r, st::white->b);
-	int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPos.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip;
-	int32 w = width();
-
-	if (_current->active.isEmpty() && _list->isEmpty()) {
-		p.setFont(st::noContactsFont->f);
-		p.setPen(st::noContactsColor->p);
-		p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center);
-		return;
-	}
-
-	if (r.y() <= st::sessionCurrentHeight) {
-		p.translate(0, st::sessionCurrentPadding.top());
-		p.setFont(st::sessionNameFont->f);
-		p.setPen(st::black->p);
-		p.drawTextLeft(x, st::sessionPadding.top(), w, _current->name, _current->nameWidth);
-
-		p.setFont(st::sessionActiveFont->f);
-		p.setPen(st::sessionActiveColor->p);
-		p.drawTextRight(x, st::sessionPadding.top(), w, _current->active, _current->activeWidth);
-
-		p.setFont(st::sessionInfoFont->f);
-		p.setPen(st::black->p);
-		p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, _current->info, _current->infoWidth);
-		p.setPen(st::sessionInfoColor->p);
-		p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, _current->ip, _current->ipWidth);
-	}
-	p.translate(0, st::sessionCurrentHeight - st::sessionCurrentPadding.top());
-	if (_list->isEmpty()) {
-		p.setFont(st::sessionInfoFont->f);
-		p.setPen(st::sessionInfoColor->p);
-		p.drawText(QRect(st::sessionPadding.left(), 0, width() - st::sessionPadding.left() - st::sessionPadding.right(), st::noContactsHeight), lang(lng_sessions_other_desc), style::al_topleft);
-		return;
-	}
-
-	p.setFont(st::linkFont->f);
-	int32 count = _list->size();
-	int32 from = floorclamp(r.y() - st::sessionCurrentHeight, st::sessionHeight, 0, count);
-	int32 to = ceilclamp(r.y() + r.height() - st::sessionCurrentHeight, st::sessionHeight, 0, count);
-	p.translate(0, from * st::sessionHeight);
-	for (int32 i = from; i < to; ++i) {
-		const SessionData &auth(_list->at(i));
-
-		p.setFont(st::sessionNameFont->f);
-		p.setPen(st::black->p);
-		p.drawTextLeft(x, st::sessionPadding.top(), w, auth.name, auth.nameWidth);
-
-		p.setFont(st::sessionActiveFont->f);
-		p.setPen(st::sessionActiveColor->p);
-		p.drawTextRight(xact, st::sessionPadding.top(), w, auth.active, auth.activeWidth);
-
-		p.setFont(st::sessionInfoFont->f);
-		p.setPen(st::black->p);
-		p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, auth.info, auth.infoWidth);
-		p.setPen(st::sessionInfoColor->p);
-		p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, auth.ip, auth.ipWidth);
-
-		p.translate(0, st::sessionHeight);
-	}
-}
-
-void SessionsInner::onTerminate() {
-	for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) {
-		if (i.value()->getState() & Button::StateOver) {
-			_terminating = i.key();
-
-			if (_terminateBox) _terminateBox->deleteLater();
-			_terminateBox = new ConfirmBox(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton);
-			connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateSure()));
-			connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*)));
-			Ui::showLayer(_terminateBox, KeepOtherLayers);
-		}
-	}
-}
-
-void SessionsInner::onTerminateSure() {
-	if (_terminateBox) {
-		_terminateBox->onClose();
-		_terminateBox = 0;
-	}
-	MTP::send(MTPaccount_ResetAuthorization(MTP_long(_terminating)), rpcDone(&SessionsInner::terminateDone, _terminating), rpcFail(&SessionsInner::terminateFail, _terminating));
-	TerminateButtons::iterator i = _terminateButtons.find(_terminating);
-	if (i != _terminateButtons.cend()) {
-		i.value()->clearState();
-		i.value()->hide();
-	}
-}
-
-void SessionsInner::onTerminateAll() {
-	if (_terminateBox) _terminateBox->deleteLater();
-	_terminateBox = new ConfirmBox(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton);
-	connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateAllSure()));
-	connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*)));
-	Ui::showLayer(_terminateBox, KeepOtherLayers);
-}
-
-void SessionsInner::onTerminateAllSure() {
-	if (_terminateBox) {
-		_terminateBox->onClose();
-		_terminateBox = 0;
-	}
-	MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&SessionsInner::terminateAllDone), rpcFail(&SessionsInner::terminateAllFail));
-	emit terminateAll();
-}
-
-void SessionsInner::onNoTerminateBox(QObject *obj) {
-	if (obj == _terminateBox) _terminateBox = 0;
-}
-
-void SessionsInner::terminateDone(uint64 hash, const MTPBool &result) {
-	for (int32 i = 0, l = _list->size(); i < l; ++i) {
-		if (_list->at(i).hash == hash) {
-			_list->removeAt(i);
-			break;
-		}
-	}
-	listUpdated();
-	emit oneTerminated();
-}
-
-bool SessionsInner::terminateFail(uint64 hash, const RPCError &error) {
-	if (MTP::isDefaultHandledError(error)) return false;
-
-	TerminateButtons::iterator i = _terminateButtons.find(hash);
-	if (i != _terminateButtons.end()) {
-		i.value()->show();
-		return true;
-	}
-	return false;
-}
-
-void SessionsInner::terminateAllDone(const MTPBool &result) {
-	emit allTerminated();
-}
-
-bool SessionsInner::terminateAllFail(const RPCError &error) {
-	if (MTP::isDefaultHandledError(error)) return false;
-	emit allTerminated();
-	return true;
-}
-
-void SessionsInner::resizeEvent(QResizeEvent *e) {
-	_terminateAll.moveToLeft(st::sessionPadding.left(), st::sessionCurrentPadding.top() + st::sessionHeight + st::sessionCurrentPadding.bottom());
-}
-
-void SessionsInner::listUpdated() {
-	if (_list->isEmpty()) {
-		_terminateAll.hide();
-	} else {
-		_terminateAll.show();
-	}
-	for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) {
-		i.value()->move(0, -1);
-	}
-	for (int32 i = 0, l = _list->size(); i < l; ++i) {
-		TerminateButtons::iterator j = _terminateButtons.find(_list->at(i).hash);
-		if (j == _terminateButtons.cend()) {
-			j = _terminateButtons.insert(_list->at(i).hash, new IconedButton(this, st::sessionTerminate));
-			connect(j.value(), SIGNAL(clicked()), this, SLOT(onTerminate()));
-		}
-		j.value()->moveToRight(st::sessionTerminateSkip, st::sessionCurrentHeight + i * st::sessionHeight + st::sessionTerminateTop, width());
-		j.value()->show();
-	}
-	for (TerminateButtons::iterator i = _terminateButtons.begin(); i != _terminateButtons.cend();) {
-		if (i.value()->y() >= 0) {
-			++i;
-		} else {
-			delete i.value();
-			i = _terminateButtons.erase(i);
-		}
-	}
-	resize(width(), _list->isEmpty() ? (st::sessionCurrentHeight + st::noContactsHeight) : (st::sessionCurrentHeight + _list->size() * st::sessionHeight));
-	update();
-}
-
 SessionsBox::SessionsBox() : ScrollableBox(st::sessionsScroll)
 , _loading(true)
-, _inner(&_list, &_current)
+, _inner(this, &_list, &_current)
 , _shadow(this)
 , _done(this, lang(lng_about_done), st::defaultBoxButton)
 , _shortPollRequest(0) {
 	setMaxHeight(st::sessionsHeight);
 
 	connect(&_done, SIGNAL(clicked()), this, SLOT(onClose()));
-	connect(&_inner, SIGNAL(oneTerminated()), this, SLOT(onOneTerminated()));
-	connect(&_inner, SIGNAL(allTerminated()), this, SLOT(onAllTerminated()));
-	connect(&_inner, SIGNAL(terminateAll()), this, SLOT(onTerminateAll()));
+	connect(_inner, SIGNAL(oneTerminated()), this, SLOT(onOneTerminated()));
+	connect(_inner, SIGNAL(allTerminated()), this, SLOT(onAllTerminated()));
+	connect(_inner, SIGNAL(terminateAll()), this, SLOT(onTerminateAll()));
 	connect(App::wnd(), SIGNAL(newAuthorization()), this, SLOT(onNewAuthorization()));
 	connect(&_shortPollTimer, SIGNAL(timeout()), this, SLOT(onShortPollAuthorizations()));
 
-	init(&_inner, st::boxButtonPadding.bottom() + _done.height() + st::boxButtonPadding.top(), st::boxTitleHeight);
-	_inner.resize(width(), st::noContactsHeight);
+	init(_inner, st::boxButtonPadding.bottom() + _done.height() + st::boxButtonPadding.top(), st::boxTitleHeight);
+	_inner->resize(width(), st::noContactsHeight);
 
 	prepare();
 
@@ -291,7 +101,7 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) {
 
 	for (int32 i = 0; i < l; ++i) {
 		const auto &d(v.at(i).c_authorization());
-		SessionData data;
+		Data data;
 		data.hash = d.vhash.v;
 
 		QString appName, appVer = qs(d.vapp_version), systemVer = qs(d.vsystem_version), deviceModel = qs(d.vdevice_model);
@@ -383,7 +193,7 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) {
 			}
 		}
 	}
-	_inner.listUpdated();
+	_inner->listUpdated();
 	if (!_done.isHidden()) {
 		showAll();
 		update();
@@ -430,4 +240,194 @@ void SessionsBox::onTerminateAll() {
 		showAll();
 		update();
 	}
-}
\ No newline at end of file
+}
+
+SessionsBox::Inner::Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current) : ScrolledWidget(parent)
+, _list(list)
+, _current(current)
+, _terminating(0)
+, _terminateAll(this, lang(lng_sessions_terminate_all), st::redBoxLinkButton)
+, _terminateBox(0) {
+	connect(&_terminateAll, SIGNAL(clicked()), this, SLOT(onTerminateAll()));
+	_terminateAll.hide();
+	setAttribute(Qt::WA_OpaquePaintEvent);
+}
+
+void SessionsBox::Inner::paintEvent(QPaintEvent *e) {
+	QRect r(e->rect());
+	Painter p(this);
+
+	p.fillRect(r, st::white->b);
+	int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPos.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip;
+	int32 w = width();
+
+	if (_current->active.isEmpty() && _list->isEmpty()) {
+		p.setFont(st::noContactsFont->f);
+		p.setPen(st::noContactsColor->p);
+		p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center);
+		return;
+	}
+
+	if (r.y() <= st::sessionCurrentHeight) {
+		p.translate(0, st::sessionCurrentPadding.top());
+		p.setFont(st::sessionNameFont->f);
+		p.setPen(st::black->p);
+		p.drawTextLeft(x, st::sessionPadding.top(), w, _current->name, _current->nameWidth);
+
+		p.setFont(st::sessionActiveFont->f);
+		p.setPen(st::sessionActiveColor->p);
+		p.drawTextRight(x, st::sessionPadding.top(), w, _current->active, _current->activeWidth);
+
+		p.setFont(st::sessionInfoFont->f);
+		p.setPen(st::black->p);
+		p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, _current->info, _current->infoWidth);
+		p.setPen(st::sessionInfoColor->p);
+		p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, _current->ip, _current->ipWidth);
+	}
+	p.translate(0, st::sessionCurrentHeight - st::sessionCurrentPadding.top());
+	if (_list->isEmpty()) {
+		p.setFont(st::sessionInfoFont->f);
+		p.setPen(st::sessionInfoColor->p);
+		p.drawText(QRect(st::sessionPadding.left(), 0, width() - st::sessionPadding.left() - st::sessionPadding.right(), st::noContactsHeight), lang(lng_sessions_other_desc), style::al_topleft);
+		return;
+	}
+
+	p.setFont(st::linkFont->f);
+	int32 count = _list->size();
+	int32 from = floorclamp(r.y() - st::sessionCurrentHeight, st::sessionHeight, 0, count);
+	int32 to = ceilclamp(r.y() + r.height() - st::sessionCurrentHeight, st::sessionHeight, 0, count);
+	p.translate(0, from * st::sessionHeight);
+	for (int32 i = from; i < to; ++i) {
+		const SessionsBox::Data &auth(_list->at(i));
+
+		p.setFont(st::sessionNameFont->f);
+		p.setPen(st::black->p);
+		p.drawTextLeft(x, st::sessionPadding.top(), w, auth.name, auth.nameWidth);
+
+		p.setFont(st::sessionActiveFont->f);
+		p.setPen(st::sessionActiveColor->p);
+		p.drawTextRight(xact, st::sessionPadding.top(), w, auth.active, auth.activeWidth);
+
+		p.setFont(st::sessionInfoFont->f);
+		p.setPen(st::black->p);
+		p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, auth.info, auth.infoWidth);
+		p.setPen(st::sessionInfoColor->p);
+		p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, auth.ip, auth.ipWidth);
+
+		p.translate(0, st::sessionHeight);
+	}
+}
+
+void SessionsBox::Inner::onTerminate() {
+	for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) {
+		if (i.value()->getState() & Button::StateOver) {
+			_terminating = i.key();
+
+			if (_terminateBox) _terminateBox->deleteLater();
+			_terminateBox = new ConfirmBox(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton);
+			connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateSure()));
+			connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*)));
+			Ui::showLayer(_terminateBox, KeepOtherLayers);
+		}
+	}
+}
+
+void SessionsBox::Inner::onTerminateSure() {
+	if (_terminateBox) {
+		_terminateBox->onClose();
+		_terminateBox = 0;
+	}
+	MTP::send(MTPaccount_ResetAuthorization(MTP_long(_terminating)), rpcDone(&Inner::terminateDone, _terminating), rpcFail(&Inner::terminateFail, _terminating));
+	TerminateButtons::iterator i = _terminateButtons.find(_terminating);
+	if (i != _terminateButtons.cend()) {
+		i.value()->clearState();
+		i.value()->hide();
+	}
+}
+
+void SessionsBox::Inner::onTerminateAll() {
+	if (_terminateBox) _terminateBox->deleteLater();
+	_terminateBox = new ConfirmBox(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton);
+	connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateAllSure()));
+	connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*)));
+	Ui::showLayer(_terminateBox, KeepOtherLayers);
+}
+
+void SessionsBox::Inner::onTerminateAllSure() {
+	if (_terminateBox) {
+		_terminateBox->onClose();
+		_terminateBox = 0;
+	}
+	MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&Inner::terminateAllDone), rpcFail(&Inner::terminateAllFail));
+	emit terminateAll();
+}
+
+void SessionsBox::Inner::onNoTerminateBox(QObject *obj) {
+	if (obj == _terminateBox) _terminateBox = 0;
+}
+
+void SessionsBox::Inner::terminateDone(uint64 hash, const MTPBool &result) {
+	for (int32 i = 0, l = _list->size(); i < l; ++i) {
+		if (_list->at(i).hash == hash) {
+			_list->removeAt(i);
+			break;
+		}
+	}
+	listUpdated();
+	emit oneTerminated();
+}
+
+bool SessionsBox::Inner::terminateFail(uint64 hash, const RPCError &error) {
+	if (MTP::isDefaultHandledError(error)) return false;
+
+	TerminateButtons::iterator i = _terminateButtons.find(hash);
+	if (i != _terminateButtons.end()) {
+		i.value()->show();
+		return true;
+	}
+	return false;
+}
+
+void SessionsBox::Inner::terminateAllDone(const MTPBool &result) {
+	emit allTerminated();
+}
+
+bool SessionsBox::Inner::terminateAllFail(const RPCError &error) {
+	if (MTP::isDefaultHandledError(error)) return false;
+	emit allTerminated();
+	return true;
+}
+
+void SessionsBox::Inner::resizeEvent(QResizeEvent *e) {
+	_terminateAll.moveToLeft(st::sessionPadding.left(), st::sessionCurrentPadding.top() + st::sessionHeight + st::sessionCurrentPadding.bottom());
+}
+
+void SessionsBox::Inner::listUpdated() {
+	if (_list->isEmpty()) {
+		_terminateAll.hide();
+	} else {
+		_terminateAll.show();
+	}
+	for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) {
+		i.value()->move(0, -1);
+	}
+	for (int32 i = 0, l = _list->size(); i < l; ++i) {
+		TerminateButtons::iterator j = _terminateButtons.find(_list->at(i).hash);
+		if (j == _terminateButtons.cend()) {
+			j = _terminateButtons.insert(_list->at(i).hash, new IconedButton(this, st::sessionTerminate));
+			connect(j.value(), SIGNAL(clicked()), this, SLOT(onTerminate()));
+		}
+		j.value()->moveToRight(st::sessionTerminateSkip, st::sessionCurrentHeight + i * st::sessionHeight + st::sessionTerminateTop, width());
+		j.value()->show();
+	}
+	for (TerminateButtons::iterator i = _terminateButtons.begin(); i != _terminateButtons.cend();) {
+		if (i.value()->y() >= 0) {
+			++i;
+		} else {
+			delete i.value();
+			i = _terminateButtons.erase(i);
+		}
+	}
+	resize(width(), _list->isEmpty() ? (st::sessionCurrentHeight + st::noContactsHeight) : (st::sessionCurrentHeight + _list->size() * st::sessionHeight));
+	update();
+}
diff --git a/Telegram/SourceFiles/boxes/sessionsbox.h b/Telegram/SourceFiles/boxes/sessionsbox.h
index 851bfa8fb..f591464f4 100644
--- a/Telegram/SourceFiles/boxes/sessionsbox.h
+++ b/Telegram/SourceFiles/boxes/sessionsbox.h
@@ -25,20 +25,58 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 
 class ConfirmBox;
 
-struct SessionData {
-	uint64 hash;
-
-	int32 activeTime;
-	int32 nameWidth, activeWidth, infoWidth, ipWidth;
-	QString name, active, info, ip;
-};
-typedef QList<SessionData> SessionsList;
-
-class SessionsInner : public TWidget, public RPCSender {
+class SessionsBox : public ScrollableBox, public RPCSender {
 	Q_OBJECT
 
 public:
-	SessionsInner(SessionsList *list, SessionData *current);
+	SessionsBox();
+
+public slots:
+	void onOneTerminated();
+	void onAllTerminated();
+	void onTerminateAll();
+	void onShortPollAuthorizations();
+	void onNewAuthorization();
+
+protected:
+	void resizeEvent(QResizeEvent *e) override;
+	void paintEvent(QPaintEvent *e) override;
+
+	void showAll() override;
+
+private:
+	struct Data {
+		uint64 hash;
+
+		int32 activeTime;
+		int32 nameWidth, activeWidth, infoWidth, ipWidth;
+		QString name, active, info, ip;
+	};
+	using List = QList<Data>;
+
+	void gotAuthorizations(const MTPaccount_Authorizations &result);
+
+	bool _loading;
+
+	Data _current;
+	List _list;
+
+	class Inner;
+	ChildWidget<Inner> _inner;
+	ScrollableBoxShadow _shadow;
+	BoxButton _done;
+
+	SingleTimer _shortPollTimer;
+	mtpRequestId _shortPollRequest;
+
+};
+
+// This class is hold in header because it requires Qt preprocessing.
+class SessionsBox::Inner : public ScrolledWidget, public RPCSender {
+	Q_OBJECT
+
+public:
+	Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current);
 
 	void listUpdated();
 
@@ -65,8 +103,8 @@ private:
 	void terminateAllDone(const MTPBool &res);
 	bool terminateAllFail(const RPCError &error);
 
-	SessionsList *_list;
-	SessionData *_current;
+	SessionsBox::List *_list;
+	SessionsBox::Data *_current;
 
 	typedef QMap<uint64, IconedButton*> TerminateButtons;
 	TerminateButtons _terminateButtons;
@@ -76,39 +114,3 @@ private:
 	ConfirmBox *_terminateBox;
 
 };
-
-class SessionsBox : public ScrollableBox, public RPCSender {
-	Q_OBJECT
-
-public:
-	SessionsBox();
-
-public slots:
-	void onOneTerminated();
-	void onAllTerminated();
-	void onTerminateAll();
-	void onShortPollAuthorizations();
-	void onNewAuthorization();
-
-protected:
-	void resizeEvent(QResizeEvent *e) override;
-	void paintEvent(QPaintEvent *e) override;
-
-	void showAll() override;
-
-private:
-	void gotAuthorizations(const MTPaccount_Authorizations &result);
-
-	bool _loading;
-
-	SessionData _current;
-	SessionsList _list;
-
-	SessionsInner _inner;
-	ScrollableBoxShadow _shadow;
-	BoxButton _done;
-
-	SingleTimer _shortPollTimer;
-	mtpRequestId _shortPollRequest;
-
-};
diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp
index a7cb219ce..a0054c2d2 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,36 +33,46 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "boxes/confirmbox.h"
 #include "apiwrap.h"
 #include "ui/toast/toast.h"
+#include "ui/widgets/multi_select.h"
 #include "history/history_media_types.h"
+#include "boxes/contactsbox.h"
 
 ShareBox::ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) : ItemListBox(st::boxScroll)
 , _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);
+	myEnsureResized(_select);
+
+	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()));
@@ -71,8 +82,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;
@@ -140,7 +172,7 @@ bool ShareBox::peopleFailed(const RPCError &error, mtpRequestId requestId) {
 }
 
 void ShareBox::doSetInnerFocus() {
-	_filter->setFocus();
+	_select->setInnerFocus();
 }
 
 void ShareBox::paintEvent(QPaintEvent *e) {
@@ -152,17 +184,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) {
@@ -192,13 +228,26 @@ 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) {
+	using AddItemWay = Ui::MultiSelect::AddItemWay;
+	auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default;
+	_select->addItem(peer->id, peer->shortName(), st::windowActiveBg, 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() {
@@ -240,9 +289,7 @@ void ShareBox::onScroll() {
 	_inner->setVisibleTopBottom(scrollTop, scrollTop + scroll->height());
 }
 
-namespace internal {
-
-ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : ScrolledWidget(parent)
+ShareBox::Inner::Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : ScrolledWidget(parent)
 , _filterCallback(std_::move(filterCallback))
 , _chatsIndexed(std_::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Add)) {
 	_rowsTop = st::shareRowsTop;
@@ -260,8 +307,6 @@ ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallbac
 	_filter = qsl("a");
 	updateFilter();
 
-	prepareWideCheckIcons();
-
 	using UpdateFlag = Notify::PeerUpdate::Flag;
 	auto observeEvents = UpdateFlag::NameChanged | UpdateFlag::PhotoChanged;
 	subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) {
@@ -270,19 +315,19 @@ ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallbac
 	subscribe(FileDownload::ImageLoaded(), [this] { update(); });
 }
 
-void ShareInner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
+void ShareBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
 	loadProfilePhotos(visibleTop);
 }
 
-void ShareInner::activateSkipRow(int direction) {
+void ShareBox::Inner::activateSkipRow(int direction) {
 	activateSkipColumn(direction * _columnCount);
 }
 
-int ShareInner::displayedChatsCount() const {
+int ShareBox::Inner::displayedChatsCount() const {
 	return _filter.isEmpty() ? _chatsIndexed->size() : (_filtered.size() + d_byUsernameFiltered.size());
 }
 
-void ShareInner::activateSkipColumn(int direction) {
+void ShareBox::Inner::activateSkipColumn(int direction) {
 	if (_active < 0) {
 		if (direction > 0) {
 			setActive(0);
@@ -300,11 +345,11 @@ void ShareInner::activateSkipColumn(int direction) {
 	setActive(active);
 }
 
-void ShareInner::activateSkipPage(int pageHeight, int direction) {
+void ShareBox::Inner::activateSkipPage(int pageHeight, int direction) {
 	activateSkipRow(direction * (pageHeight / _rowHeight));
 }
 
-void ShareInner::notifyPeerUpdated(const Notify::PeerUpdate &update) {
+void ShareBox::Inner::notifyPeerUpdated(const Notify::PeerUpdate &update) {
 	if (update.flags & Notify::PeerUpdate::Flag::NameChanged) {
 		_chatsIndexed->peerNameChanged(update.peer, update.oldNames, update.oldNameFirstChars);
 	}
@@ -312,7 +357,7 @@ void ShareInner::notifyPeerUpdated(const Notify::PeerUpdate &update) {
 	updateChat(update.peer);
 }
 
-void ShareInner::updateChat(PeerData *peer) {
+void ShareBox::Inner::updateChat(PeerData *peer) {
 	auto i = _dataMap.find(peer);
 	if (i != _dataMap.cend()) {
 		updateChatName(i.value(), peer);
@@ -320,11 +365,11 @@ void ShareInner::updateChat(PeerData *peer) {
 	}
 }
 
-void ShareInner::updateChatName(Chat *chat, PeerData *peer) {
+void ShareBox::Inner::updateChatName(Chat *chat, PeerData *peer) {
 	chat->name.setText(st::shareNameFont, peer->name, _textNameOptions);
 }
 
-void ShareInner::repaintChatAtIndex(int index) {
+void ShareBox::Inner::repaintChatAtIndex(int index) {
 	if (index < 0) return;
 
 	auto row = index / _columnCount;
@@ -332,7 +377,7 @@ void ShareInner::repaintChatAtIndex(int index) {
 	update(rtlrect(_rowsLeft + qFloor(column * _rowWidthReal), row * _rowHeight, _rowWidth, _rowHeight, width()));
 }
 
-ShareInner::Chat *ShareInner::getChatAtIndex(int index) {
+ShareBox::Inner::Chat *ShareBox::Inner::getChatAtIndex(int index) {
 	if (index < 0) return nullptr;
 	auto row = ([this, index]() -> Dialogs::Row* {
 		if (_filter.isEmpty()) return _chatsIndexed->rowAtY(index, 1);
@@ -351,11 +396,11 @@ ShareInner::Chat *ShareInner::getChatAtIndex(int index) {
 	return nullptr;
 }
 
-void ShareInner::repaintChat(PeerData *peer) {
+void ShareBox::Inner::repaintChat(PeerData *peer) {
 	repaintChatAtIndex(chatIndex(peer));
 }
 
-int ShareInner::chatIndex(PeerData *peer) const {
+int ShareBox::Inner::chatIndex(PeerData *peer) const {
 	int index = 0;
 	if (_filter.isEmpty()) {
 		for_const (auto row, _chatsIndexed->all()) {
@@ -381,7 +426,7 @@ int ShareInner::chatIndex(PeerData *peer) const {
 	return -1;
 }
 
-void ShareInner::loadProfilePhotos(int yFrom) {
+void ShareBox::Inner::loadProfilePhotos(int yFrom) {
 	if (yFrom < 0) {
 		yFrom = 0;
 	}
@@ -420,13 +465,14 @@ void ShareInner::loadProfilePhotos(int yFrom) {
 	}
 }
 
-ShareInner::Chat *ShareInner::getChat(Dialogs::Row *row) {
+ShareBox::Inner::Chat *ShareBox::Inner::getChat(Dialogs::Row *row) {
 	auto data = static_cast<Chat*>(row->attached);
 	if (!data) {
 		auto peer = row->history()->peer;
 		auto i = _dataMap.constFind(peer);
 		if (i == _dataMap.cend()) {
-			_dataMap.insert(peer, data = new Chat(peer));
+			data = new Chat(peer, [this, peer] { repaintChat(peer); });
+			_dataMap.insert(peer, data);
 			updateChatName(data, peer);
 		} else {
 			data = i.value();
@@ -436,12 +482,12 @@ ShareInner::Chat *ShareInner::getChat(Dialogs::Row *row) {
 	return data;
 }
 
-void ShareInner::setActive(int active) {
+void ShareBox::Inner::setActive(int active) {
 	if (active != _active) {
 		auto changeNameFg = [this](int index, style::color from, style::color to) {
 			if (auto chat = getChatAtIndex(index)) {
-				chat->nameFg.start([this, chat] {
-					repaintChat(chat->peer);
+				chat->nameFg.start([this, peer = chat->peer] {
+					repaintChat(peer);
 				}, from->c, to->c, st::shareActivateDuration);
 			}
 		};
@@ -453,71 +499,14 @@ void ShareInner::setActive(int active) {
 	emit mustScrollTo(y, y + _rowHeight);
 }
 
-void ShareInner::paintChat(Painter &p, Chat *chat, int index) {
+void ShareBox::Inner::paintChat(Painter &p, uint64 ms, Chat *chat, int index) {
 	auto x = _rowsLeft + qFloor((index % _columnCount) * _rowWidthReal);
 	auto y = _rowsTop + (index / _columnCount) * _rowHeight;
 
-	auto selectionLevel = chat->selection.current(chat->selected ? 1. : 0.);
-
-	auto w = width();
-	auto photoLeft = (_rowWidth - (st::sharePhotoRadius * 2)) / 2;
+	auto outerWidth = width();
+	auto photoLeft = (_rowWidth - (st::sharePhotoCheckbox.imageRadius * 2)) / 2;
 	auto photoTop = st::sharePhotoTop;
-	if (chat->selection.animating()) {
-		p.setRenderHint(QPainter::SmoothPixmapTransform, true);
-		auto userpicRadius = qRound(WideCacheScale * (st::sharePhotoRadius + (st::sharePhotoSmallRadius - st::sharePhotoRadius) * selectionLevel));
-		auto userpicShift = WideCacheScale * st::sharePhotoRadius - userpicRadius;
-		auto userpicLeft = x + photoLeft - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift;
-		auto userpicTop = y + photoTop - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift;
-		auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2);
-		auto from = QRect(QPoint(0, 0), chat->wideUserpicCache.size());
-		p.drawPixmapLeft(to, w, chat->wideUserpicCache, from);
-		p.setRenderHint(QPainter::SmoothPixmapTransform, false);
-	} else {
-		if (!chat->wideUserpicCache.isNull()) {
-			chat->wideUserpicCache = QPixmap();
-		}
-		auto userpicRadius = chat->selected ? st::sharePhotoSmallRadius : st::sharePhotoRadius;
-		auto userpicShift = st::sharePhotoRadius - userpicRadius;
-		auto userpicLeft = x + photoLeft + userpicShift;
-		auto userpicTop = y + photoTop + userpicShift;
-		chat->peer->paintUserpicLeft(p, userpicRadius * 2, userpicLeft, userpicTop, w);
-	}
-
-	if (selectionLevel > 0) {
-		p.setRenderHint(QPainter::HighQualityAntialiasing, true);
-		p.setOpacity(snap(selectionLevel, 0., 1.));
-		p.setBrush(Qt::NoBrush);
-		QPen pen = st::shareSelectFg;
-		pen.setWidth(st::shareSelectWidth);
-		p.setPen(pen);
-		p.drawEllipse(myrtlrect(x + photoLeft, y + photoTop, st::sharePhotoRadius * 2, st::sharePhotoRadius * 2));
-		p.setOpacity(1.);
-		p.setRenderHint(QPainter::HighQualityAntialiasing, false);
-	}
-
-	removeFadeOutedIcons(chat);
-	p.setRenderHint(QPainter::SmoothPixmapTransform, true);
-	for (auto &icon : chat->icons) {
-		auto fadeIn = icon.fadeIn.current(1.);
-		auto fadeOut = icon.fadeOut.current(1.);
-		auto iconRadius = qRound(WideCacheScale * (st::shareCheckSmallRadius + fadeOut * (st::shareCheckRadius - st::shareCheckSmallRadius)));
-		auto iconShift = WideCacheScale * st::shareCheckRadius - iconRadius;
-		auto iconLeft = x + photoLeft + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift;
-		auto iconTop = y + photoTop + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift;
-		auto to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2);
-		auto from = QRect(QPoint(0, 0), _wideCheckIconCache.size());
-		auto opacity = fadeIn * fadeOut;
-		p.setOpacity(opacity);
-		if (fadeOut < 1.) {
-			p.drawPixmapLeft(to, w, icon.wideCheckCache, from);
-		} else {
-			auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + fadeIn * 3 * st::shareCheckRadius);
-			p.drawPixmapLeft(QRect(iconLeft, iconTop, divider, iconRadius * 2), w, _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height()));
-			p.drawPixmapLeft(QRect(iconLeft + divider, iconTop, iconRadius * 2 - divider, iconRadius * 2), w, _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height()));
-		}
-	}
-	p.setRenderHint(QPainter::SmoothPixmapTransform, false);
-	p.setOpacity(1.);
+	chat->checkbox.paint(p, ms, x + photoLeft, y + photoTop, outerWidth);
 
 	if (chat->nameFg.animating()) {
 		p.setPen(chat->nameFg.current());
@@ -527,16 +516,20 @@ void ShareInner::paintChat(Painter &p, Chat *chat, int index) {
 
 	auto nameWidth = (_rowWidth - st::shareColumnSkip);
 	auto nameLeft = st::shareColumnSkip / 2;
-	auto nameTop = photoTop + st::sharePhotoRadius * 2 + st::shareNameTop;
-	chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, w, 2, style::al_top, 0, -1, 0, true);
+	auto nameTop = photoTop + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop;
+	chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, outerWidth, 2, style::al_top, 0, -1, 0, true);
 }
 
-ShareInner::Chat::Chat(PeerData *peer) : peer(peer), name(st::sharePhotoRadius * 2) {
+ShareBox::Inner::Chat::Chat(PeerData *peer, base::lambda_wrap<void()> updateCallback)
+: peer(peer)
+, checkbox(st::sharePhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer))
+, name(st::sharePhotoCheckbox.imageRadius * 2) {
 }
 
-void ShareInner::paintEvent(QPaintEvent *e) {
+void ShareBox::Inner::paintEvent(QPaintEvent *e) {
 	Painter p(this);
 
+	auto ms = getms();
 	auto r = e->rect();
 	p.setClipRect(r);
 	p.fillRect(r, st::white);
@@ -552,7 +545,7 @@ void ShareInner::paintEvent(QPaintEvent *e) {
 				if (indexFrom >= indexTo) {
 					break;
 				}
-				paintChat(p, getChat(*i), indexFrom);
+				paintChat(p, ms, getChat(*i), indexFrom);
 				++indexFrom;
 			}
 		} else {
@@ -573,7 +566,7 @@ void ShareInner::paintEvent(QPaintEvent *e) {
 					if (indexFrom >= _filtered.size()) {
 						break;
 					}
-					paintChat(p, getChat(_filtered[indexFrom]), indexFrom);
+					paintChat(p, ms, getChat(_filtered[indexFrom]), indexFrom);
 					++indexFrom;
 				}
 				indexFrom -= filteredSize;
@@ -585,7 +578,7 @@ void ShareInner::paintEvent(QPaintEvent *e) {
 					if (indexFrom >= d_byUsernameFiltered.size()) {
 						break;
 					}
-					paintChat(p, d_byUsernameFiltered[indexFrom], filteredSize + indexFrom);
+					paintChat(p, ms, d_byUsernameFiltered[indexFrom], filteredSize + indexFrom);
 					++indexFrom;
 				}
 			}
@@ -593,27 +586,27 @@ void ShareInner::paintEvent(QPaintEvent *e) {
 	}
 }
 
-void ShareInner::enterEvent(QEvent *e) {
+void ShareBox::Inner::enterEvent(QEvent *e) {
 	setMouseTracking(true);
 }
 
-void ShareInner::leaveEvent(QEvent *e) {
+void ShareBox::Inner::leaveEvent(QEvent *e) {
 	setMouseTracking(false);
 }
 
-void ShareInner::mouseMoveEvent(QMouseEvent *e) {
+void ShareBox::Inner::mouseMoveEvent(QMouseEvent *e) {
 	updateUpon(e->pos());
 	setCursor((_upon >= 0) ? style::cur_pointer : style::cur_default);
 }
 
-void ShareInner::updateUpon(const QPoint &pos) {
+void ShareBox::Inner::updateUpon(const QPoint &pos) {
 	auto x = pos.x(), y = pos.y();
 	auto row = (y - _rowsTop) / _rowHeight;
 	auto column = qFloor((x - _rowsLeft) / _rowWidthReal);
 	auto left = _rowsLeft + qFloor(column * _rowWidthReal) + st::shareColumnSkip / 2;
 	auto top = _rowsTop + row * _rowHeight + st::sharePhotoTop;
 	auto xupon = (x >= left) && (x < left + (_rowWidth - st::shareColumnSkip));
-	auto yupon = (y >= top) && (y < top + st::sharePhotoRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2);
+	auto yupon = (y >= top) && (y < top + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2);
 	auto upon = (xupon && yupon) ? (row * _columnCount + column) : -1;
 	if (upon >= displayedChatsCount()) {
 		upon = -1;
@@ -621,41 +614,26 @@ void ShareInner::updateUpon(const QPoint &pos) {
 	_upon = upon;
 }
 
-void ShareInner::mousePressEvent(QMouseEvent *e) {
+void ShareBox::Inner::mousePressEvent(QMouseEvent *e) {
 	if (e->button() == Qt::LeftButton) {
 		updateUpon(e->pos());
 		changeCheckState(getChatAtIndex(_upon));
 	}
 }
 
-void ShareInner::onSelectActive() {
+void ShareBox::Inner::onSelectActive() {
 	changeCheckState(getChatAtIndex(_active > 0 ? _active : 0));
 }
 
-void ShareInner::resizeEvent(QResizeEvent *e) {
-	_columnSkip = (width() - _columnCount * st::sharePhotoRadius * 2) / float64(_columnCount + 1);
-	_rowWidthReal = st::sharePhotoRadius * 2 + _columnSkip;
+void ShareBox::Inner::resizeEvent(QResizeEvent *e) {
+	_columnSkip = (width() - _columnCount * st::sharePhotoCheckbox.imageRadius * 2) / float64(_columnCount + 1);
+	_rowWidthReal = st::sharePhotoCheckbox.imageRadius * 2 + _columnSkip;
 	_rowsLeft = qFloor(_columnSkip / 2);
 	_rowWidth = qFloor(_rowWidthReal);
 	update();
 }
 
-struct AnimBumpy {
-	AnimBumpy(float64 bump) : bump(bump)
-		, dt0(bump - sqrt(bump * (bump - 1.)))
-		, k(1 / (2 * dt0 - 1)) {
-	}
-	float64 bump;
-	float64 dt0;
-	float64 k;
-};
-
-float64 anim_bumpy(const float64 &delta, const float64 &dt) {
-	static AnimBumpy data = { 1.25 };
-	return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0));
-}
-
-void ShareInner::changeCheckState(Chat *chat) {
+void ShareBox::Inner::changeCheckState(Chat *chat) {
 	if (!chat) return;
 
 	if (!_filter.isEmpty()) {
@@ -664,115 +642,44 @@ void ShareInner::changeCheckState(Chat *chat) {
 			row = _chatsIndexed->addToEnd(App::history(chat->peer)).value(0);
 		}
 		chat = getChat(row);
-		if (!chat->selected) {
+		if (!chat->checkbox.checked()) {
 			_chatsIndexed->moveToTop(chat->peer);
 		}
-		emit filterCancel();
 	}
 
-	chat->selected = !chat->selected;
-	if (chat->selected) {
+	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);
-		chat->icons.push_back(Chat::Icon());
-		chat->icons.back().fadeIn.start([this, chat] {
-			repaintChat(chat->peer);
-		}, 0, 1, st::shareSelectDuration);
+		setActive(chatIndex(chat->peer));
 	} else {
 		_selected.remove(chat->peer);
-		prepareWideCheckIconCache(&chat->icons.back());
-		chat->icons.back().fadeOut.start([this, chat] {
-			repaintChat(chat->peer);
-			removeFadeOutedIcons(chat); // this call can destroy current lambda
-		}, 1, 0, st::shareSelectDuration);
 	}
-	prepareWideUserpicCache(chat);
-	chat->selection.start([this, chat] {
-		repaintChat(chat->peer);
-	}, chat->selected ? 0 : 1, chat->selected ? 1 : 0, st::shareSelectDuration, anim_bumpy);
-	if (chat->selected) {
-		setActive(chatIndex(chat->peer));
-	}
-	emit selectedChanged();
-}
-
-void ShareInner::removeFadeOutedIcons(Chat *chat) {
-	while (!chat->icons.empty() && !chat->icons.front().fadeIn.animating() && !chat->icons.front().fadeOut.animating()) {
-		if (chat->icons.size() > 1 || !chat->selected) {
-			chat->icons.erase(chat->icons.begin());
-		} else {
-			break;
-		}
+	if (useCallback != ChangeStateWay::SkipCallback && _peerSelectedChangedCallback) {
+		_peerSelectedChangedCallback(chat->peer, checked);
 	}
 }
 
-void ShareInner::prepareWideUserpicCache(Chat *chat) {
-	if (chat->wideUserpicCache.isNull()) {
-		auto size = st::sharePhotoRadius * 2;
-		auto wideSize = size * WideCacheScale;
-		QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
-		cache.setDevicePixelRatio(cRetinaFactor());
-		{
-			Painter p(&cache);
-			p.setCompositionMode(QPainter::CompositionMode_Source);
-			p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
-			p.setCompositionMode(QPainter::CompositionMode_SourceOver);
-			chat->peer->paintUserpic(p, size, (wideSize - size) / 2, (wideSize - size) / 2);
-		}
-		chat->wideUserpicCache = App::pixmapFromImageInPlace(std_::move(cache));
-		chat->wideUserpicCache.setDevicePixelRatio(cRetinaFactor());
-	}
-}
-
-void ShareInner::prepareWideCheckIconCache(Chat::Icon *icon) {
-	QImage wideCache(_wideCheckCache.width(), _wideCheckCache.height(), QImage::Format_ARGB32_Premultiplied);
-	wideCache.setDevicePixelRatio(cRetinaFactor());
-	{
-		Painter p(&wideCache);
-		p.setCompositionMode(QPainter::CompositionMode_Source);
-		auto iconRadius = WideCacheScale * st::shareCheckRadius;
-		auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + icon->fadeIn.current(1.) * 3 * st::shareCheckRadius);
-		p.drawPixmapLeft(QRect(0, 0, divider, iconRadius * 2), width(), _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height()));
-		p.drawPixmapLeft(QRect(divider, 0, iconRadius * 2 - divider, iconRadius * 2), width(), _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height()));
-	}
-	icon->wideCheckCache = App::pixmapFromImageInPlace(std_::move(wideCache));
-	icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor());
-}
-
-void ShareInner::prepareWideCheckIcons() {
-	auto size = st::shareCheckRadius * 2;
-	auto wideSize = size * WideCacheScale;
-	QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
-	cache.setDevicePixelRatio(cRetinaFactor());
-	{
-		Painter p(&cache);
-		p.setCompositionMode(QPainter::CompositionMode_Source);
-		p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
-		p.setCompositionMode(QPainter::CompositionMode_SourceOver);
-		p.setRenderHint(QPainter::HighQualityAntialiasing, true);
-		auto pen = st::shareCheckBorder->p;
-		pen.setWidth(st::shareSelectWidth);
-		p.setPen(pen);
-		p.setBrush(st::shareCheckBg);
-		auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
-		p.drawEllipse(ellipse);
-	}
-	QImage cacheIcon = cache;
-	{
-		Painter p(&cacheIcon);
-		auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
-		st::shareCheckIcon.paint(p, ellipse.topLeft(), wideSize);
-	}
-	_wideCheckCache = App::pixmapFromImageInPlace(std_::move(cache));
-	_wideCheckCache.setDevicePixelRatio(cRetinaFactor());
-	_wideCheckIconCache = App::pixmapFromImageInPlace(std_::move(cacheIcon));
-	_wideCheckIconCache.setDevicePixelRatio(cRetinaFactor());
-}
-
-bool ShareInner::hasSelected() const {
+bool ShareBox::Inner::hasSelected() const {
 	return _selected.size();
 }
 
-void ShareInner::updateFilter(QString filter) {
+void ShareBox::Inner::updateFilter(QString filter) {
 	_lastQuery = filter.toLower().trimmed();
 	filter = textSearchKey(filter);
 
@@ -851,7 +758,7 @@ void ShareInner::updateFilter(QString filter) {
 	}
 }
 
-void ShareInner::peopleReceived(const QString &query, const QVector<MTPPeer> &people) {
+void ShareBox::Inner::peopleReceived(const QString &query, const QVector<MTPPeer> &people) {
 	_lastQuery = query.toLower().trimmed();
 	if (_lastQuery.at(0) == '@') _lastQuery = _lastQuery.mid(1);
 	int32 already = _byUsernameFiltered.size();
@@ -867,7 +774,7 @@ void ShareInner::peopleReceived(const QString &query, const QVector<MTPPeer> &pe
 			auto *peer = App::peer(peerId);
 			if (!peer || !_filterCallback(peer)) continue;
 
-			auto chat = new Chat(peer);
+			auto chat = new Chat(peer, [this, peer] { repaintChat(peer); });
 			updateChatName(chat, peer);
 			if (auto row = _chatsIndexed->getRow(peer->id)) {
 				continue;
@@ -881,7 +788,7 @@ void ShareInner::peopleReceived(const QString &query, const QVector<MTPPeer> &pe
 	refresh();
 }
 
-void ShareInner::refresh() {
+void ShareBox::Inner::refresh() {
 	auto count = displayedChatsCount();
 	if (count) {
 		auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0);
@@ -892,25 +799,23 @@ void ShareInner::refresh() {
 	update();
 }
 
-ShareInner::~ShareInner() {
+ShareBox::Inner::~Inner() {
 	for_const (auto chat, _dataMap) {
 		delete chat;
 	}
 }
 
-QVector<PeerData*> ShareInner::selected() const {
+QVector<PeerData*> ShareBox::Inner::selected() const {
 	QVector<PeerData*> result;
 	result.reserve(_dataMap.size());
 	for_const (auto chat, _dataMap) {
-		if (chat->selected) {
+		if (chat->checkbox.checked()) {
 			result.push_back(chat->peer);
 		}
 	}
 	return result;
 }
 
-} // namespace internal
-
 QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId) {
 	auto shareHashData = QByteArray(0x10, Qt::Uninitialized);
 	auto shareHashDataInts = reinterpret_cast<int32*>(shareHashData.data());
diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h
index 7f7d8e99d..449c56242 100644
--- a/Telegram/SourceFiles/boxes/sharebox.h
+++ b/Telegram/SourceFiles/boxes/sharebox.h
@@ -24,20 +24,21 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "core/lambda_wrap.h"
 #include "core/observer.h"
 #include "core/vector_of_moveable.h"
+#include "ui/effects/round_image_checkbox.h"
 
 namespace Dialogs {
 class Row;
 class IndexedList;
 } // namespace Dialogs
 
-namespace internal {
-class ShareInner;
-} // namespace internal
-
 namespace Notify {
 struct PeerUpdate;
 } // namespace Notify
 
+namespace Ui {
+class MultiSelect;
+} // namespace Ui
+
 QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId);
 void shareGameScoreByHash(const QString &hash);
 
@@ -51,8 +52,6 @@ public:
 	ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback);
 
 private slots:
-	void onFilterUpdate();
-	void onFilterCancel();
 	void onScroll();
 
 	bool onSearchByUsername(bool searchCache = false);
@@ -60,7 +59,6 @@ private slots:
 
 	void onSubmit();
 	void onCopyLink();
-	void onSelectedChanged();
 
 	void onMustScrollTo(int top, int bottom);
 
@@ -72,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);
@@ -81,9 +86,9 @@ private:
 	CopyCallback _copyCallback;
 	SubmitCallback _submitCallback;
 
-	ChildWidget<internal::ShareInner> _inner;
-	ChildWidget<InputField> _filter;
-	ChildWidget<IconedButton> _filterCancel;
+	class Inner;
+	ChildWidget<Inner> _inner;
+	ChildWidget<Ui::MultiSelect> _select;
 
 	ChildWidget<BoxButton> _copy;
 	ChildWidget<BoxButton> _share;
@@ -107,13 +112,15 @@ private:
 
 };
 
-namespace internal {
-
-class ShareInner : public ScrolledWidget, public RPCSender, private base::Subscriber {
+// This class is hold in header because it requires Qt preprocessing.
+class ShareBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
 	Q_OBJECT
 
 public:
-	ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallback);
+	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;
@@ -126,16 +133,14 @@ public:
 	void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
 	void updateFilter(QString filter = QString());
 
-	~ShareInner();
+	~Inner();
 
 public slots:
 	void onSelectActive();
 
 signals:
 	void mustScrollTo(int ymin, int ymax);
-	void filterCancel();
 	void searchByUsername();
-	void selectedChanged();
 
 protected:
 	void paintEvent(QPaintEvent *e) override;
@@ -151,36 +156,29 @@ private:
 
 	int displayedChatsCount() const;
 
-	static constexpr int WideCacheScale = 4;
 	struct Chat {
-		Chat(PeerData *peer);
+		Chat(PeerData *peer, base::lambda_wrap<void()> updateCallback);
+
 		PeerData *peer;
+		Ui::RoundImageCheckbox checkbox;
 		Text name;
-		bool selected = false;
-		QPixmap wideUserpicCache;
 		ColorAnimation nameFg;
-		FloatAnimation selection;
-		struct Icon {
-			FloatAnimation fadeIn;
-			FloatAnimation fadeOut;
-			QPixmap wideCheckCache;
-		};
-		std_::vector_of_moveable<Icon> icons;
 	};
-	void paintChat(Painter &p, Chat *chat, int index);
+	void paintChat(Painter &p, uint64 ms, Chat *chat, int index);
 	void updateChat(PeerData *peer);
 	void updateChatName(Chat *chat, PeerData *peer);
 	void repaintChat(PeerData *peer);
-	void removeFadeOutedIcons(Chat *chat);
-	void prepareWideUserpicCache(Chat *chat);
-	void prepareWideCheckIconCache(Chat::Icon *icon);
-	void prepareWideCheckIcons();
 	int chatIndex(PeerData *peer) const;
 	void repaintChatAtIndex(int index);
 	Chat *getChatAtIndex(int index);
 
 	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);
@@ -204,13 +202,13 @@ private:
 	using FilteredDialogs = QVector<Dialogs::Row*>;
 	FilteredDialogs _filtered;
 
-	QPixmap _wideCheckCache, _wideCheckIconCache;
-
 	using DataMap = QMap<PeerData*, Chat*>;
 	DataMap _dataMap;
 	using SelectedChats = OrderedSet<PeerData*>;
 	SelectedChats _selected;
 
+	base::lambda_unique<void(PeerData *peer, bool selected)> _peerSelectedChangedCallback;
+
 	ChatData *data(Dialogs::Row *row);
 
 	bool _searching = false;
@@ -221,5 +219,3 @@ private:
 	ByUsernameDatas d_byUsernameFiltered;
 
 };
-
-} // namespace internal
diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp
new file mode 100644
index 000000000..907a5f9d8
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/stickers_box.cpp
@@ -0,0 +1,1254 @@
+/*
+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-2016 John Preston, https://desktop.telegram.org
+*/
+#include "stdafx.h"
+#include "stickers_box.h"
+
+#include "lang.h"
+#include "mainwidget.h"
+#include "stickers/stickers.h"
+#include "boxes/confirmbox.h"
+#include "boxes/stickersetbox.h"
+#include "apiwrap.h"
+#include "localstorage.h"
+#include "dialogs/dialogs_layout.h"
+#include "styles/style_stickers.h"
+
+namespace {
+
+constexpr int kArchivedLimitFirstRequest = 10;
+constexpr int kArchivedLimitPerPage = 30;
+
+} // namespace
+
+int32 stickerPacksCount(bool includeDisabledOfficial) {
+	int32 result = 0;
+	auto &order = Global::StickerSetsOrder();
+	auto &sets = Global::StickerSets();
+	for (int i = 0, l = order.size(); i < l; ++i) {
+		auto it = sets.constFind(order.at(i));
+		if (it != sets.cend()) {
+			if (!(it->flags & MTPDstickerSet::Flag::f_archived) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeDisabledOfficial)) {
+				++result;
+			}
+		}
+	}
+	return result;
+}
+
+StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll)
+, _section(section)
+, _inner(this, section)
+, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top())
+, _about(st::boxTextFont, lang((section == Section::Archived) ? lng_stickers_packs_archived : lng_stickers_reorder), _defaultOptions, _aboutWidth) {
+	setup();
+}
+
+StickersBox::StickersBox(const Stickers::Order &archivedIds) : ItemListBox(st::boxScroll)
+, _section(Section::ArchivedPart)
+, _inner(this, archivedIds)
+, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top())
+, _about(st::boxTextFont, lang(lng_stickers_packs_archived), _defaultOptions, _aboutWidth) {
+	setup();
+}
+
+void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result) {
+	_archivedRequestId = 0;
+	if (result.type() != mtpc_messages_archivedStickers) {
+		return;
+	}
+
+	auto &stickers = result.c_messages_archivedStickers();
+	auto &archived = Global::RefArchivedStickerSetsOrder();
+	if (offsetId) {
+		auto index = archived.indexOf(offsetId);
+		if (index >= 0) {
+			archived = archived.mid(0, index + 1);
+		}
+	} else {
+		archived.clear();
+	}
+
+	bool addedSet = false;
+	auto &v = stickers.vsets.c_vector().v;
+	for_const (auto &stickerSet, v) {
+		const MTPDstickerSet *setData = nullptr;
+		switch (stickerSet.type()) {
+		case mtpc_stickerSetCovered: {
+			auto &d = stickerSet.c_stickerSetCovered();
+			if (d.vset.type() == mtpc_stickerSet) {
+				setData = &d.vset.c_stickerSet();
+			}
+		} break;
+		case mtpc_stickerSetMultiCovered: {
+			auto &d = stickerSet.c_stickerSetMultiCovered();
+			if (d.vset.type() == mtpc_stickerSet) {
+				setData = &d.vset.c_stickerSet();
+			}
+		} break;
+		}
+		if (!setData) continue;
+
+		if (auto set = Stickers::feedSet(*setData)) {
+			auto index = archived.indexOf(set->id);
+			if (archived.isEmpty() || index != archived.size() - 1) {
+				if (index < archived.size() - 1) {
+					archived.removeAt(index);
+				}
+				archived.push_back(set->id);
+			}
+			if (_section == Section::Archived) {
+				if (_inner->appendSet(*set)) {
+					addedSet = true;
+					if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
+						App::api()->scheduleStickerSetRequest(set->id, set->access);
+					}
+				}
+			}
+		}
+	}
+	if (_section == Section::Installed && !archived.isEmpty()) {
+		Local::writeArchivedStickers();
+		rebuildList();
+	} else if (_section == Section::Archived) {
+		if (addedSet) {
+			_inner->updateSize();
+			setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight)));
+			_inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0);
+			App::api()->requestStickerSets();
+		} else {
+			_allArchivedLoaded = v.isEmpty() || (offsetId != 0);
+		}
+	}
+	checkLoadMoreArchived();
+}
+
+void StickersBox::setup() {
+	if (_section == Section::Installed) {
+		Local::readArchivedStickers();
+		if (Global::ArchivedStickerSetsOrder().isEmpty()) {
+			MTPmessages_GetArchivedStickers::Flags flags = 0;
+			_archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL));
+		}
+	} else if (_section == Section::Archived) {
+		// Reload the archived list.
+		MTPmessages_GetArchivedStickers::Flags flags = 0;
+		_archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL));
+
+		auto &sets = Global::StickerSets();
+		for_const (auto setId, Global::ArchivedStickerSetsOrder()) {
+			auto it = sets.constFind(setId);
+			if (it != sets.cend()) {
+				if (it->stickers.isEmpty() && (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
+					App::api()->scheduleStickerSetRequest(setId, it->access);
+				}
+			}
+		}
+		App::api()->requestStickerSets();
+	}
+
+	int bottomSkip = st::boxPadding.bottom();
+	if (_section == Section::Installed) {
+		_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
+		_topShadow.create(this, st::contactsAboutShadow);
+
+		_save.create(this, lang(lng_settings_save), st::defaultBoxButton);
+		connect(_save, SIGNAL(clicked()), this, SLOT(onSave()));
+
+		_cancel.create(this, lang(lng_cancel), st::cancelBoxButton);
+		connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
+
+		_bottomShadow.create(this);
+		bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom();
+	} else if (_section == Section::ArchivedPart) {
+		_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
+		_topShadow.create(this, st::contactsAboutShadow);
+
+		_save.create(this, lang(lng_box_ok), st::defaultBoxButton);
+		connect(_save, SIGNAL(clicked()), this, SLOT(onClose()));
+	} else if (_section == Section::Archived) {
+		_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
+		_topShadow.create(this, st::contactsAboutShadow);
+	}
+	ItemListBox::init(_inner, bottomSkip, st::boxTitleHeight + _aboutHeight);
+	setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight)));
+
+	connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated()));
+	App::main()->updateStickers();
+
+	connect(_inner, SIGNAL(checkDraggingScroll(int)), this, SLOT(onCheckDraggingScroll(int)));
+	connect(_inner, SIGNAL(noDraggingScroll()), this, SLOT(onNoDraggingScroll()));
+	connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer()));
+	connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
+	_scrollTimer.setSingleShot(false);
+
+	rebuildList();
+
+	prepare();
+}
+
+void StickersBox::onScroll() {
+	updateVisibleTopBottom();
+	checkLoadMoreArchived();
+}
+
+void StickersBox::updateVisibleTopBottom() {
+	auto visibleTop = scrollArea()->scrollTop();
+	auto visibleBottom = visibleTop + scrollArea()->height();
+	_inner->setVisibleTopBottom(visibleTop, visibleBottom);
+}
+
+void StickersBox::checkLoadMoreArchived() {
+	if (_section != Section::Archived) return;
+
+	int scrollTop = scrollArea()->scrollTop(), scrollTopMax = scrollArea()->scrollTopMax();
+	if (scrollTop + PreloadHeightsCount * scrollArea()->height() >= scrollTopMax) {
+		if (!_archivedRequestId && !_allArchivedLoaded) {
+			uint64 lastId = 0;
+			for (auto setId = Global::ArchivedStickerSetsOrder().cend(), e = Global::ArchivedStickerSetsOrder().cbegin(); setId != e;) {
+				--setId;
+				auto it = Global::StickerSets().constFind(*setId);
+				if (it != Global::StickerSets().cend()) {
+					if (it->flags & MTPDstickerSet::Flag::f_archived) {
+						lastId = it->id;
+						break;
+					}
+				}
+			}
+			MTPmessages_GetArchivedStickers::Flags flags = 0;
+			_archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(lastId), MTP_int(kArchivedLimitPerPage)), rpcDone(&StickersBox::getArchivedDone, lastId));
+		}
+	}
+}
+
+int32 StickersBox::countHeight() const {
+	int bottomSkip = st::boxPadding.bottom();
+	if (_section == Section::Installed) {
+		bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom();
+	}
+	return st::boxTitleHeight + _aboutHeight + _inner->height() + bottomSkip;
+}
+
+void StickersBox::disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req) {
+	_disenableRequests.remove(req);
+	if (_disenableRequests.isEmpty()) {
+		saveOrder();
+	}
+}
+
+bool StickersBox::disenableFail(const RPCError &error, mtpRequestId req) {
+	if (MTP::isDefaultHandledError(error)) return false;
+	_disenableRequests.remove(req);
+	if (_disenableRequests.isEmpty()) {
+		saveOrder();
+	}
+	return true;
+}
+
+void StickersBox::saveOrder() {
+	auto order = _inner->getOrder();
+	if (order.size() > 1) {
+		QVector<MTPlong> mtpOrder;
+		mtpOrder.reserve(order.size());
+		for (int i = 0, l = order.size(); i < l; ++i) {
+			mtpOrder.push_back(MTP_long(order.at(i)));
+		}
+
+		MTPmessages_ReorderStickerSets::Flags flags = 0;
+		_reorderRequest = MTP::send(MTPmessages_ReorderStickerSets(MTP_flags(flags), MTP_vector<MTPlong>(mtpOrder)), rpcDone(&StickersBox::reorderDone), rpcFail(&StickersBox::reorderFail));
+	} else {
+		reorderDone(MTP_boolTrue());
+	}
+}
+
+void StickersBox::reorderDone(const MTPBool &result) {
+	_reorderRequest = 0;
+	onClose();
+}
+
+bool StickersBox::reorderFail(const RPCError &result) {
+	if (MTP::isDefaultHandledError(result)) return false;
+	_reorderRequest = 0;
+	Global::SetLastStickersUpdate(0);
+	App::main()->updateStickers();
+	onClose();
+	return true;
+}
+
+void StickersBox::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+	if (paint(p)) return;
+
+	auto title = ([this]() {
+		if (_section == Section::Installed) {
+			return lang(lng_stickers_packs);
+		} else if (_section == Section::Featured) {
+			return lang(lng_stickers_featured);
+		}
+		return lang(lng_stickers_archived);
+	})();
+	paintTitle(p, title);
+	p.translate(0, st::boxTitleHeight);
+
+	if (_aboutHeight > 0) {
+		p.fillRect(0, 0, width(), _aboutHeight, st::contactsAboutBg);
+		p.setPen(st::stickersReorderFg);
+		_about.draw(p, st::stickersReorderPadding.top(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center);
+	}
+}
+
+void StickersBox::closePressed() {
+	if (!_disenableRequests.isEmpty()) {
+		for_const (auto requestId, _disenableRequests) {
+			MTP::cancel(requestId);
+		}
+		_disenableRequests.clear();
+		Global::SetLastStickersUpdate(0);
+		App::main()->updateStickers();
+	} else if (_reorderRequest) {
+		MTP::cancel(_reorderRequest);
+		_reorderRequest = 0;
+		Global::SetLastStickersUpdate(0);
+		App::main()->updateStickers();
+	}
+}
+
+StickersBox::~StickersBox() {
+	if (_section == Section::Archived) {
+		Local::writeArchivedStickers();
+	}
+}
+
+void StickersBox::resizeEvent(QResizeEvent *e) {
+	ItemListBox::resizeEvent(e);
+	_inner->resize(width(), _inner->height());
+	_inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0);
+	updateVisibleTopBottom();
+	if (_topShadow) {
+		_topShadow->setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth);
+	}
+	if (_save) {
+		_save->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save->height());
+	}
+	if (_cancel) {
+		_cancel->moveToRight(st::boxButtonPadding.right() + _save->width() + st::boxButtonPadding.left(), _save->y());
+		_bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _save->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth);
+	}
+}
+
+void StickersBox::onStickersUpdated() {
+	if (_section == Section::Installed || _section == Section::Featured) {
+		rebuildList();
+	} else {
+		_inner->updateRows();
+	}
+}
+
+void StickersBox::rebuildList() {
+	_inner->rebuild();
+	setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight)));
+	_inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0);
+}
+
+void StickersBox::onCheckDraggingScroll(int localY) {
+	if (localY < scrollArea()->scrollTop()) {
+		_scrollDelta = localY - scrollArea()->scrollTop();
+	} else if (localY >= scrollArea()->scrollTop() + scrollArea()->height()) {
+		_scrollDelta = localY - scrollArea()->scrollTop() - scrollArea()->height() + 1;
+	} else {
+		_scrollDelta = 0;
+	}
+	if (_scrollDelta) {
+		_scrollTimer.start(15);
+	} else {
+		_scrollTimer.stop();
+	}
+}
+
+void StickersBox::onNoDraggingScroll() {
+	_scrollTimer.stop();
+}
+
+void StickersBox::onScrollTimer() {
+	int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
+	scrollArea()->scrollToY(scrollArea()->scrollTop() + d);
+}
+
+void StickersBox::onSave() {
+	if (!_inner->savingStart()) {
+		return;
+	}
+
+	bool writeRecent = false, writeArchived = false;
+	auto &recent = cGetRecentStickers();
+	auto &sets = Global::RefStickerSets();
+
+	auto reorder = _inner->getOrder(), disabled = _inner->getDisabledSets();
+	for (int32 i = 0, l = disabled.size(); i < l; ++i) {
+		auto it = sets.find(disabled.at(i));
+		if (it != sets.cend()) {
+			for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) {
+				if (it->stickers.indexOf(i->first) >= 0) {
+					i = recent.erase(i);
+					writeRecent = true;
+				} else {
+					++i;
+				}
+			}
+			if (!(it->flags & MTPDstickerSet::Flag::f_archived)) {
+				MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
+				if (it->flags & MTPDstickerSet::Flag::f_official) {
+					_disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolTrue()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5));
+					it->flags |= MTPDstickerSet::Flag::f_archived;
+					auto index = Global::RefArchivedStickerSetsOrder().indexOf(it->id);
+					if (index < 0) {
+						Global::RefArchivedStickerSetsOrder().push_front(it->id);
+						writeArchived = true;
+					}
+				} else {
+					_disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5));
+					int removeIndex = Global::StickerSetsOrder().indexOf(it->id);
+					if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex);
+					if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) {
+						sets.erase(it);
+					} else {
+						if (it->flags & MTPDstickerSet::Flag::f_archived) {
+							writeArchived = true;
+						}
+						it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_archived);
+					}
+				}
+			}
+		}
+	}
+
+	// Clear all installed flags, set only for sets from order.
+	for (auto &set : sets) {
+		if (!(set.flags & MTPDstickerSet::Flag::f_archived)) {
+			set.flags &= ~MTPDstickerSet::Flag::f_installed;
+		}
+	}
+
+	auto &order(Global::RefStickerSetsOrder());
+	order.clear();
+	for (int i = 0, l = reorder.size(); i < l; ++i) {
+		auto it = sets.find(reorder.at(i));
+		if (it != sets.cend()) {
+			if ((it->flags & MTPDstickerSet::Flag::f_archived) && !disabled.contains(it->id)) {
+				MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
+				_disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolFalse()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5));
+				it->flags &= ~MTPDstickerSet::Flag::f_archived;
+				writeArchived = true;
+			}
+			order.push_back(reorder.at(i));
+			it->flags |= MTPDstickerSet::Flag::f_installed;
+		}
+	}
+	for (auto it = sets.begin(); it != sets.cend();) {
+		if ((it->flags & MTPDstickerSet_ClientFlag::f_featured)
+			|| (it->flags & MTPDstickerSet::Flag::f_installed)
+			|| (it->flags & MTPDstickerSet::Flag::f_archived)
+			|| (it->flags & MTPDstickerSet_ClientFlag::f_special)) {
+			++it;
+		} else {
+			it = sets.erase(it);
+		}
+	}
+
+	Local::writeInstalledStickers();
+	if (writeRecent) Local::writeUserSettings();
+	if (writeArchived) Local::writeArchivedStickers();
+	emit App::main()->stickersUpdated();
+
+	if (_disenableRequests.isEmpty()) {
+		saveOrder();
+	} else {
+		MTP::sendAnything();
+	}
+}
+
+void StickersBox::showAll() {
+	if (_topShadow) {
+		_topShadow->show();
+	}
+	if (_save) {
+		_save->show();
+	}
+	if (_cancel) {
+		_cancel->show();
+		_bottomShadow->show();
+	}
+	ItemListBox::showAll();
+}
+
+StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : ScrolledWidget(parent)
+, _section(section)
+, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
+, _a_shifting(animation(this, &Inner::step_shifting))
+, _itemsTop(st::membersPadding.top())
+, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent)))
+, _removeWidth(st::normalFont->width(lang(lng_stickers_remove)))
+, _returnWidth(st::normalFont->width(lang(lng_stickers_return)))
+, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore)))
+, _aboveShadow(st::boxShadow) {
+	setup();
+}
+
+StickersBox::Inner::Inner(QWidget *parent, const Stickers::Order &archivedIds) : ScrolledWidget(parent)
+, _section(StickersBox::Section::ArchivedPart)
+, _archivedIds(archivedIds)
+, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
+, _a_shifting(animation(this, &Inner::step_shifting))
+, _itemsTop(st::membersPadding.top())
+, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent)))
+, _removeWidth(st::normalFont->width(lang(lng_stickers_remove)))
+, _returnWidth(st::normalFont->width(lang(lng_stickers_return)))
+, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore)))
+, _aboveShadow(st::boxShadow) {
+	setup();
+}
+
+void StickersBox::Inner::setup() {
+	subscribe(FileDownload::ImageLoaded(), [this] { update(); });
+	setMouseTracking(true);
+}
+
+void StickersBox::Inner::onImageLoaded() {
+	update();
+	readVisibleSets();
+}
+
+void StickersBox::Inner::paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const {
+	if (selected) {
+		p.fillRect(0, y, width(), _buttonHeight, st::contactsBgOver);
+	}
+	p.setFont(st::stickersFeaturedFont);
+	p.setPen(st::stickersFeaturedPen);
+	p.drawTextLeft(st::stickersFeaturedPosition.x(), y + st::stickersFeaturedPosition.y(), width(), text);
+
+	if (badgeCounter) {
+		Dialogs::Layout::UnreadBadgeStyle unreadSt;
+		unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox;
+		unreadSt.size = st::stickersFeaturedBadgeSize;
+		int unreadRight = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x());
+		if (rtl()) unreadRight = width() - unreadRight;
+		int unreadTop = y + (_buttonHeight - st::stickersFeaturedBadgeSize) / 2;
+		Dialogs::Layout::paintUnreadCount(p, QString::number(badgeCounter), unreadRight, unreadTop, unreadSt);
+	}
+}
+
+void StickersBox::Inner::paintEvent(QPaintEvent *e) {
+	QRect r(e->rect());
+	Painter p(this);
+
+	_a_shifting.step();
+
+	p.fillRect(r, st::white);
+	p.setClipRect(r);
+
+	int y = st::membersPadding.top();
+	if (_hasFeaturedButton) {
+		auto selected = (_selected == -2);
+		paintButton(p, y, selected, lang(lng_stickers_featured), Global::FeaturedStickerSetsUnreadCount());
+		y += _buttonHeight;
+	}
+	if (_hasArchivedButton) {
+		auto selected = (_selected == -1);
+		paintButton(p, y, selected, lang(lng_stickers_archived), 0);
+		y += _buttonHeight;
+	}
+
+	if (_rows.isEmpty()) {
+		p.setFont(st::noContactsFont);
+		p.setPen(st::noContactsColor);
+		p.drawText(QRect(0, y, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center);
+	} else {
+		p.translate(0, _itemsTop);
+
+		int32 yFrom = r.y() - _itemsTop, yTo = r.y() + r.height() - _itemsTop;
+		int32 from = floorclamp(yFrom - _rowHeight, _rowHeight, 0, _rows.size());
+		int32 to = ceilclamp(yTo + _rowHeight, _rowHeight, 0, _rows.size());
+		p.translate(0, from * _rowHeight);
+		for (int32 i = from; i < to; ++i) {
+			if (i != _above) {
+				paintRow(p, i);
+			}
+			p.translate(0, _rowHeight);
+		}
+		if (from <= _above && _above < to) {
+			p.translate(0, (_above - to) * _rowHeight);
+			paintRow(p, _above);
+		}
+	}
+}
+
+void StickersBox::Inner::paintRow(Painter &p, int32 index) {
+	const StickerSetRow *s(_rows.at(index));
+
+	int32 xadd = 0, yadd = s->yadd.current();
+	if (xadd || yadd) p.translate(xadd, yadd);
+
+	if (_section == Section::Installed) {
+		bool removeSel = (index == _actionSel && (_actionDown < 0 || index == _actionDown));
+		bool removeDown = removeSel && (index == _actionDown);
+
+		p.setFont(removeSel ? st::linkOverFont : st::linkFont);
+		if (removeDown) {
+			p.setPen(st::btnDefLink.downColor);
+		} else {
+			p.setPen(st::btnDefLink.color);
+		}
+		int32 remWidth = s->recent ? _clearWidth : (s->disabled ? (s->official ? _restoreWidth : _returnWidth) : _removeWidth);
+		QString remText = lang(s->recent ? lng_stickers_clear_recent : (s->disabled ? (s->official ? lng_stickers_restore : lng_stickers_return) : lng_stickers_remove));
+		p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), remText, remWidth);
+
+		if (index == _above) {
+			float64 current = _aboveShadowFadeOpacity.current();
+			if (_started >= 0) {
+				float64 o = aboveShadowOpacity();
+				if (o > current) {
+					_aboveShadowFadeOpacity = anim::fvalue(o, o);
+					current = o;
+				}
+			}
+			p.setOpacity(current);
+			QRect row(myrtlrect(_aboveShadow.getDimensions(st::boxShadowShift).left(), st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - _aboveShadow.getDimensions(st::boxShadowShift).right(), _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2)));
+			_aboveShadow.paint(p, row, st::boxShadowShift);
+			p.fillRect(row, st::white);
+			p.setOpacity(1);
+		}
+	} else if (s->installed && !s->disabled) {
+		int addw = st::stickersAddSize.width();
+		int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (addw + st::stickersFeaturedInstalled.width()) / 2);
+		int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2;
+		st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width());
+	} else {
+		int addw = st::stickersAddSize.width();
+		int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw;
+		int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2;
+		QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height()));
+
+		auto textBg = (_actionSel == index) ? st::defaultActiveButton.textBgOver : st::defaultActiveButton.textBg;
+		App::roundRect(p, add, textBg, ImageRoundRadius::Small);
+		int iconx = addx + (st::stickersAddSize.width() - st::stickersAddIcon.width()) / 2;
+		int icony = addy + (st::stickersAddSize.height() - st::stickersAddIcon.height()) / 2;
+		icony += (_actionSel == index && _actionDown == index) ? (st::defaultActiveButton.downTextTop - st::defaultActiveButton.textTop) : 0;
+		st::stickersAddIcon.paint(p, QPoint(iconx, icony), width());
+	}
+
+	if (s->disabled && _section == Section::Installed) {
+		p.setOpacity(st::stickersRowDisabledOpacity);
+	}
+	if (s->sticker) {
+		s->sticker->thumb->load();
+		QPixmap pix(s->sticker->thumb->pix(s->pixw, s->pixh));
+		p.drawPixmapLeft(st::contactsPadding.left() + (st::contactsPhotoSize - s->pixw) / 2, st::contactsPadding.top() + (st::contactsPhotoSize - s->pixh) / 2, width(), pix);
+	}
+
+	int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
+	int namey = st::contactsPadding.top() + st::contactsNameTop;
+	int statusx = namex;
+	int statusy = st::contactsPadding.top() + st::contactsStatusTop;
+
+	p.setFont(st::contactsNameFont);
+	p.setPen(st::black);
+	p.drawTextLeft(namex, namey, width(), s->title, s->titleWidth);
+
+	if (s->unread) {
+		p.setPen(Qt::NoPen);
+		p.setBrush(st::stickersFeaturedUnreadBg);
+
+		p.setRenderHint(QPainter::HighQualityAntialiasing, true);
+		p.drawEllipse(rtlrect(namex + s->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
+		p.setRenderHint(QPainter::HighQualityAntialiasing, false);
+	}
+
+	p.setFont(st::contactsStatusFont);
+	p.setPen(st::contactsStatusFg);
+	p.drawTextLeft(statusx, statusy, width(), lng_stickers_count(lt_count, s->count));
+
+	p.setOpacity(1);
+	if (xadd || yadd) p.translate(-xadd, -yadd);
+}
+
+void StickersBox::Inner::mousePressEvent(QMouseEvent *e) {
+	if (_saving) return;
+	if (_dragging >= 0) mouseReleaseEvent(e);
+	_mouse = e->globalPos();
+	onUpdateSelected();
+
+	_pressed = _selected;
+	if (_actionSel >= 0) {
+		_actionDown = _actionSel;
+		update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
+	} else if (_selected >= 0 && _section == Section::Installed && !_rows.at(_selected)->recent) {
+		_above = _dragging = _started = _selected;
+		_dragStart = mapFromGlobal(_mouse);
+	}
+}
+
+void StickersBox::Inner::mouseMoveEvent(QMouseEvent *e) {
+	if (_saving) return;
+	_mouse = e->globalPos();
+	onUpdateSelected();
+}
+
+void StickersBox::Inner::onUpdateSelected() {
+	if (_saving) return;
+	QPoint local(mapFromGlobal(_mouse));
+	if (_dragging >= 0) {
+		int32 shift = 0;
+		uint64 ms = getms();
+		int firstSetIndex = 0;
+		if (_rows.at(firstSetIndex)->recent) {
+			++firstSetIndex;
+		}
+		if (_dragStart.y() > local.y() && _dragging > 0) {
+			shift = -floorclamp(_dragStart.y() - local.y() + (_rowHeight / 2), _rowHeight, 0, _dragging - firstSetIndex);
+			for (int32 from = _dragging, to = _dragging + shift; from > to; --from) {
+				qSwap(_rows[from], _rows[from - 1]);
+				_rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() - _rowHeight, 0);
+				_animStartTimes[from] = ms;
+			}
+		} else if (_dragStart.y() < local.y() && _dragging + 1 < _rows.size()) {
+			shift = floorclamp(local.y() - _dragStart.y() + (_rowHeight / 2), _rowHeight, 0, _rows.size() - _dragging - 1);
+			for (int32 from = _dragging, to = _dragging + shift; from < to; ++from) {
+				qSwap(_rows[from], _rows[from + 1]);
+				_rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() + _rowHeight, 0);
+				_animStartTimes[from] = ms;
+			}
+		}
+		if (shift) {
+			_dragging += shift;
+			_above = _dragging;
+			_dragStart.setY(_dragStart.y() + shift * _rowHeight);
+			if (!_a_shifting.animating()) {
+				_a_shifting.start();
+			}
+		}
+		_rows.at(_dragging)->yadd = anim::ivalue(local.y() - _dragStart.y(), local.y() - _dragStart.y());
+		_animStartTimes[_dragging] = 0;
+		_a_shifting.step(getms(), true);
+
+		emit checkDraggingScroll(local.y());
+	} else {
+		bool in = rect().marginsRemoved(QMargins(0, _itemsTop, 0, st::membersPadding.bottom())).contains(local);
+		int selected = -2;
+		int actionSel = -1;
+		if (in) {
+			selected = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1);
+
+			if (_section == Section::Installed) {
+				int remw = _rows.at(selected)->recent ? _clearWidth : (_rows.at(selected)->disabled ? (_rows.at(selected)->official ? _restoreWidth : _returnWidth) : _removeWidth);
+				QRect rem(myrtlrect(width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - remw, st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, remw, st::normalFont->height));
+				actionSel = rem.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1;
+			} else if (_rows.at(selected)->installed && !_rows.at(selected)->disabled) {
+				actionSel = -1;
+			} else {
+				int addw = st::stickersAddSize.width();
+				int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw;
+				int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2;
+				QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height()));
+				actionSel = add.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1;
+			}
+		} else if (_hasFeaturedButton && QRect(0, st::membersPadding.top(), width(), _buttonHeight).contains(local)) {
+			selected = -2;
+		} else if (_hasArchivedButton && QRect(0, st::membersPadding.top() + (_hasFeaturedButton ? _buttonHeight : 0), width(), _buttonHeight).contains(local)) {
+			selected = -1;
+		} else {
+			selected = -3;
+		}
+		if (_selected != selected) {
+			if (((_selected == -1) != (selected == -1)) || ((_selected == -2) != (selected == -2))) {
+				update();
+			}
+			if (_section != Section::Installed && ((_selected >= 0 || _pressed >= 0) != (selected >= 0 || _pressed >= 0))) {
+				setCursor((selected >= 0 || _pressed >= 0) ? style::cur_pointer : style::cur_default);
+			}
+			_selected = selected;
+		}
+		setActionSel(actionSel);
+		emit noDraggingScroll();
+	}
+}
+
+void StickersBox::Inner::onClearRecent() {
+	if (_clearBox) {
+		_clearBox->onClose();
+	}
+
+	auto &sets = Global::RefStickerSets();
+	bool removedCloud = (sets.remove(Stickers::CloudRecentSetId) != 0);
+	bool removedCustom = (sets.remove(Stickers::CustomSetId) != 0);
+
+	auto &recent = cGetRecentStickers();
+	if (!recent.isEmpty()) {
+		recent.clear();
+		Local::writeUserSettings();
+	}
+
+	if (removedCustom) Local::writeInstalledStickers();
+	if (removedCloud) Local::writeRecentStickers();
+	emit App::main()->updateStickers();
+	rebuild();
+
+	MTPmessages_ClearRecentStickers::Flags flags = 0;
+	MTP::send(MTPmessages_ClearRecentStickers(MTP_flags(flags)));
+}
+
+void StickersBox::Inner::onClearBoxDestroyed(QObject *box) {
+	if (box == _clearBox) {
+		_clearBox = nullptr;
+	}
+}
+
+float64 StickersBox::Inner::aboveShadowOpacity() const {
+	if (_above < 0) return 0;
+
+	int32 dx = 0;
+	int32 dy = qAbs(_above * _rowHeight + _rows.at(_above)->yadd.current() - _started * _rowHeight);
+	return qMin((dx + dy)  * 2. / _rowHeight, 1.);
+}
+
+void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
+	auto pressed = _pressed;
+	_pressed = -2;
+
+	if (_section != Section::Installed && _selected < 0 && pressed >= 0) {
+		setCursor(style::cur_default);
+	}
+
+	if (_saving) return;
+
+	_mouse = e->globalPos();
+	onUpdateSelected();
+	if (_actionDown == _actionSel && _actionSel >= 0) {
+		if (_section == Section::Installed) {
+			if (_rows[_actionDown]->recent) {
+				_clearBox = new ConfirmBox(lang(lng_stickers_clear_recent_sure), lang(lng_stickers_clear_recent));
+				connect(_clearBox, SIGNAL(confirmed()), this, SLOT(onClearRecent()));
+				connect(_clearBox, SIGNAL(destroyed(QObject*)), this, SLOT(onClearBoxDestroyed(QObject*)));
+				Ui::showLayer(_clearBox, KeepOtherLayers);
+			} else {
+				_rows[_actionDown]->disabled = !_rows[_actionDown]->disabled;
+			}
+		} else {
+			installSet(_rows[_actionDown]->id);
+		}
+	} else if (_dragging >= 0) {
+		QPoint local(mapFromGlobal(_mouse));
+		_rows[_dragging]->yadd.start(0);
+		_aboveShadowFadeStart = _animStartTimes[_dragging] = getms();
+		_aboveShadowFadeOpacity = anim::fvalue(aboveShadowOpacity(), 0);
+		if (!_a_shifting.animating()) {
+			_a_shifting.start();
+		}
+
+		_dragging = _started = -1;
+	} else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) {
+		if (_selected == -2) {
+			_selected = -3;
+			Ui::showLayer(new StickersBox(Section::Featured), KeepOtherLayers);
+		} else if (_selected == -1) {
+			_selected = -3;
+			Ui::showLayer(new StickersBox(Section::Archived), KeepOtherLayers);
+		} else if (_selected >= 0 && _section != Section::Installed) {
+			auto &sets = Global::RefStickerSets();
+			auto it = sets.find(_rows.at(pressed)->id);
+			if (it != sets.cend()) {
+				_selected = -3;
+				Ui::showLayer(new StickerSetBox(Stickers::inputSetId(*it)), KeepOtherLayers);
+			}
+		}
+	}
+	if (_actionDown >= 0) {
+		update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight);
+		_actionDown = -1;
+	}
+}
+
+void StickersBox::Inner::leaveEvent(QEvent *e) {
+	_mouse = QPoint(-1, -1);
+	onUpdateSelected();
+}
+
+void StickersBox::Inner::installSet(uint64 setId) {
+	auto &sets = Global::RefStickerSets();
+	auto it = sets.find(setId);
+	if (it == sets.cend()) {
+		rebuild();
+		return;
+	}
+
+	MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), rpcDone(&Inner::installDone), rpcFail(&Inner::installFail, setId));
+
+	Stickers::installLocally(setId);
+}
+
+void StickersBox::Inner::installDone(const MTPmessages_StickerSetInstallResult &result) {
+	if (result.type() == mtpc_messages_stickerSetInstallResultArchive) {
+		Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive());
+	}
+}
+
+bool StickersBox::Inner::installFail(uint64 setId, const RPCError &error) {
+	if (MTP::isDefaultHandledError(error)) return false;
+
+	auto &sets = Global::RefStickerSets();
+	auto it = sets.find(setId);
+	if (it == sets.cend()) {
+		rebuild();
+		return true;
+	}
+
+	Stickers::undoInstallLocally(setId);
+	return true;
+}
+
+void StickersBox::Inner::step_shifting(uint64 ms, bool timer) {
+	bool animating = false;
+	int32 updateMin = -1, updateMax = 0;
+	for (int32 i = 0, l = _animStartTimes.size(); i < l; ++i) {
+		uint64 start = _animStartTimes.at(i);
+		if (start) {
+			if (updateMin < 0) updateMin = i;
+			updateMax = i;
+			if (start + st::stickersRowDuration > ms && ms >= start) {
+				_rows.at(i)->yadd.update(float64(ms - start) / st::stickersRowDuration, anim::sineInOut);
+				animating = true;
+			} else {
+				_rows.at(i)->yadd.finish();
+				_animStartTimes[i] = 0;
+			}
+		}
+	}
+	if (_aboveShadowFadeStart) {
+		if (updateMin < 0 || updateMin > _above) updateMin = _above;
+		if (updateMax < _above) updateMin = _above;
+		if (_aboveShadowFadeStart + st::stickersRowDuration > ms && ms > _aboveShadowFadeStart) {
+			_aboveShadowFadeOpacity.update(float64(ms - _aboveShadowFadeStart) / st::stickersRowDuration, anim::sineInOut);
+			animating = true;
+		} else {
+			_aboveShadowFadeOpacity.finish();
+			_aboveShadowFadeStart = 0;
+		}
+	}
+	if (timer) {
+		if (_dragging >= 0) {
+			if (updateMin < 0 || updateMin > _dragging) updateMin = _dragging;
+			if (updateMax < _dragging) updateMax = _dragging;
+		}
+		if (updateMin >= 0) {
+			update(0, _itemsTop + _rowHeight * (updateMin - 1), width(), _rowHeight * (updateMax - updateMin + 3));
+		}
+	}
+	if (!animating) {
+		_above = _dragging;
+		_a_shifting.stop();
+	}
+}
+
+void StickersBox::Inner::clear() {
+	for (int32 i = 0, l = _rows.size(); i < l; ++i) {
+		delete _rows.at(i);
+	}
+	_rows.clear();
+	_animStartTimes.clear();
+	_aboveShadowFadeStart = 0;
+	_aboveShadowFadeOpacity = anim::fvalue(0, 0);
+	_a_shifting.stop();
+	_above = _dragging = _started = -1;
+	_selected = -3;
+	_pressed = -3;
+	_actionDown = -1;
+	setActionSel(-1);
+	update();
+}
+
+void StickersBox::Inner::setActionSel(int32 actionSel) {
+	if (actionSel != _actionSel) {
+		if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
+		_actionSel = actionSel;
+		if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
+		if (_section == Section::Installed) {
+			setCursor((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel)) ? style::cur_pointer : style::cur_default);
+		}
+	}
+}
+
+void StickersBox::Inner::rebuild() {
+	_hasFeaturedButton = _hasArchivedButton = false;
+	_itemsTop = st::membersPadding.top();
+	_buttonHeight = st::stickersFeaturedHeight;
+	if (_section == Section::Installed) {
+		if (!Global::FeaturedStickerSetsOrder().isEmpty()) {
+			_itemsTop += _buttonHeight;
+			_hasFeaturedButton = true;
+		}
+		if (!Global::ArchivedStickerSetsOrder().isEmpty()) {
+			_itemsTop += _buttonHeight;
+			_hasArchivedButton = true;
+		}
+		if (_itemsTop > st::membersPadding.top()) {
+			_itemsTop += st::membersPadding.top();
+		}
+	}
+
+	int maxNameWidth = countMaxNameWidth();
+
+	clear();
+	auto &order = ([this]() -> const Stickers::Order & {
+		if (_section == Section::Installed) {
+			return Global::StickerSetsOrder();
+		} else if (_section == Section::Featured) {
+			return Global::FeaturedStickerSetsOrder();
+		} else if (_section == Section::Archived) {
+			return Global::ArchivedStickerSetsOrder();
+		}
+		return _archivedIds;
+	})();
+	_rows.reserve(order.size() + 1);
+	_animStartTimes.reserve(order.size() + 1);
+
+	auto &sets = Global::StickerSets();
+	if (_section == Section::Installed) {
+		auto cloudIt = sets.constFind(Stickers::CloudRecentSetId);
+		if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) {
+			rebuildAppendSet(cloudIt.value(), maxNameWidth);
+		}
+	}
+	for_const (auto setId, order) {
+		auto it = sets.constFind(setId);
+		if (it == sets.cend()) {
+			continue;
+		}
+
+		rebuildAppendSet(it.value(), maxNameWidth);
+
+		if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
+			App::api()->scheduleStickerSetRequest(it->id, it->access);
+		}
+	}
+	App::api()->requestStickerSets();
+	updateSize();
+}
+
+void StickersBox::Inner::updateSize() {
+	resize(width(), _itemsTop + _rows.size() * _rowHeight + st::membersPadding.bottom());
+}
+
+void StickersBox::Inner::updateRows() {
+	int maxNameWidth = countMaxNameWidth();
+	auto &sets = Global::StickerSets();
+	for_const (auto row, _rows) {
+		auto it = sets.constFind(row->id);
+		if (it != sets.cend()) {
+			auto &set = it.value();
+			if (!row->sticker) {
+				DocumentData *sticker = nullptr;
+				int pixw = 0, pixh = 0;
+				fillSetCover(set, &sticker, &pixw, &pixh);
+				if (sticker) {
+					row->sticker = sticker;
+					row->pixw = pixw;
+					row->pixh = pixh;
+				}
+			}
+			fillSetFlags(set, &row->recent, &row->installed, &row->official, &row->unread, &row->disabled);
+			if (_section == Section::Installed) {
+				row->disabled = false;
+			}
+			row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth);
+			row->count = fillSetCount(set);
+		}
+	}
+	update();
+}
+
+bool StickersBox::Inner::appendSet(const Stickers::Set &set) {
+	for_const (auto row, _rows) {
+		if (row->id == set.id) {
+			return false;
+		}
+	}
+	rebuildAppendSet(set, countMaxNameWidth());
+	return true;
+}
+
+int StickersBox::Inner::countMaxNameWidth() const {
+	int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
+	int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x();
+	if (_section == Section::Installed) {
+		namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth);
+	} else {
+		namew -= st::stickersAddIcon.width() - st::defaultActiveButton.width;
+		namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip;
+	}
+	return namew;
+}
+
+void StickersBox::Inner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) {
+	bool recent = false, installed = false, official = false, unread = false, disabled = false;
+	fillSetFlags(set, &recent, &installed, &official, &unread, &disabled);
+	if (_section == Section::Installed && disabled) {
+		return;
+	}
+
+	DocumentData *sticker = nullptr;
+	int pixw = 0, pixh = 0;
+	fillSetCover(set, &sticker, &pixw, &pixh);
+
+	int titleWidth = 0;
+	QString title = fillSetTitle(set, maxNameWidth, &titleWidth);
+	int count = fillSetCount(set);
+
+	_rows.push_back(new StickerSetRow(set.id, sticker, count, title, titleWidth, installed, official, unread, disabled, recent, pixw, pixh));
+	_animStartTimes.push_back(0);
+}
+
+void StickersBox::Inner::fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const {
+	if (set.stickers.isEmpty()) {
+		*outSticker = nullptr;
+		*outWidth = *outHeight = 0;
+		return;
+	}
+	auto sticker = *outSticker = set.stickers.front();
+
+	auto pixw = sticker->thumb->width();
+	auto pixh = sticker->thumb->height();
+	if (pixw > st::contactsPhotoSize) {
+		if (pixw > pixh) {
+			pixh = (pixh * st::contactsPhotoSize) / pixw;
+			pixw = st::contactsPhotoSize;
+		} else {
+			pixw = (pixw * st::contactsPhotoSize) / pixh;
+			pixh = st::contactsPhotoSize;
+		}
+	} else if (pixh > st::contactsPhotoSize) {
+		pixw = (pixw * st::contactsPhotoSize) / pixh;
+		pixh = st::contactsPhotoSize;
+	}
+	*outWidth = pixw;
+	*outHeight = pixh;
+}
+
+int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const {
+	int result = set.stickers.isEmpty() ? set.count : set.stickers.size(), added = 0;
+	if (set.id == Stickers::CloudRecentSetId) {
+		auto customIt = Global::StickerSets().constFind(Stickers::CustomSetId);
+		if (customIt != Global::StickerSets().cend()) {
+			added = customIt->stickers.size();
+			for_const (auto &sticker, cGetRecentStickers()) {
+				if (customIt->stickers.indexOf(sticker.first) < 0) {
+					++added;
+				}
+			}
+		} else {
+			added = cGetRecentStickers().size();
+		}
+	}
+	return result + added;
+}
+
+QString StickersBox::Inner::fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const {
+	auto result = set.title;
+	int titleWidth = st::contactsNameFont->width(result);
+	if (titleWidth > maxNameWidth) {
+		result = st::contactsNameFont->elided(result, maxNameWidth);
+		titleWidth = st::contactsNameFont->width(result);
+	}
+	if (outTitleWidth) {
+		*outTitleWidth = titleWidth;
+	}
+	return result;
+}
+
+void StickersBox::Inner::fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled) {
+	*outRecent = (set.id == Stickers::CloudRecentSetId);
+	*outInstalled = true;
+	*outOfficial = true;
+	*outUnread = false;
+	*outDisabled = false;
+	if (!*outRecent) {
+		*outInstalled = (set.flags & MTPDstickerSet::Flag::f_installed);
+		*outOfficial = (set.flags & MTPDstickerSet::Flag::f_official);
+		*outDisabled = (set.flags & MTPDstickerSet::Flag::f_archived);
+		if (_section == Section::Featured) {
+			*outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread);
+		}
+	}
+}
+
+Stickers::Order StickersBox::Inner::getOrder() const {
+	Stickers::Order result;
+	result.reserve(_rows.size());
+	for (int32 i = 0, l = _rows.size(); i < l; ++i) {
+		if (_rows.at(i)->disabled || _rows.at(i)->recent) {
+			continue;
+		}
+		result.push_back(_rows.at(i)->id);
+	}
+	return result;
+}
+
+Stickers::Order StickersBox::Inner::getDisabledSets() const {
+	Stickers::Order result;
+	result.reserve(_rows.size());
+	for (int32 i = 0, l = _rows.size(); i < l; ++i) {
+		if (_rows.at(i)->disabled) {
+			result.push_back(_rows.at(i)->id);
+		}
+	}
+	return result;
+}
+
+void StickersBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
+	if (_section == Section::Featured) {
+		_visibleTop = visibleTop;
+		_visibleBottom = visibleBottom;
+		readVisibleSets();
+	}
+}
+
+void StickersBox::Inner::readVisibleSets() {
+	auto itemsVisibleTop = _visibleTop - _itemsTop;
+	auto itemsVisibleBottom = _visibleBottom - _itemsTop;
+	int rowFrom = floorclamp(itemsVisibleTop, _rowHeight, 0, _rows.size());
+	int rowTo = ceilclamp(itemsVisibleBottom, _rowHeight, 0, _rows.size());
+	for (int i = rowFrom; i < rowTo; ++i) {
+		if (!_rows[i]->unread) {
+			continue;
+		}
+		if (i * _rowHeight < itemsVisibleTop || (i + 1) * _rowHeight > itemsVisibleBottom) {
+			continue;
+		}
+		if (!_rows[i]->sticker || _rows[i]->sticker->thumb->loaded() || _rows[i]->sticker->loaded()) {
+			Stickers::markFeaturedAsRead(_rows[i]->id);
+		}
+	}
+}
+
+void StickersBox::Inner::setVisibleScrollbar(int32 width) {
+	_scrollbar = width;
+}
+
+StickersBox::Inner::~Inner() {
+	clear();
+}
diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h
new file mode 100644
index 000000000..3524a7db8
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/stickers_box.h
@@ -0,0 +1,239 @@
+/*
+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-2016 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+#include "abstractbox.h"
+
+class ConfirmBox;
+
+namespace Ui {
+class PlainShadow;
+} // namespace Ui
+
+class StickersBox : public ItemListBox, public RPCSender {
+	Q_OBJECT
+
+public:
+	enum class Section {
+		Installed,
+		Featured,
+		Archived,
+		ArchivedPart,
+	};
+	StickersBox(Section section = Section::Installed);
+	StickersBox(const Stickers::Order &archivedIds);
+
+	~StickersBox();
+
+public slots:
+	void onStickersUpdated();
+
+	void onCheckDraggingScroll(int localY);
+	void onNoDraggingScroll();
+	void onScrollTimer();
+
+	void onSave();
+
+private slots:
+	void onScroll();
+
+protected:
+	void resizeEvent(QResizeEvent *e) override;
+	void paintEvent(QPaintEvent *e) override;
+
+	void closePressed() override;
+	void showAll() override;
+
+private:
+	void setup();
+	int32 countHeight() const;
+	void rebuildList();
+
+	void disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req);
+	bool disenableFail(const RPCError &error, mtpRequestId req);
+	void reorderDone(const MTPBool &result);
+	bool reorderFail(const RPCError &result);
+	void saveOrder();
+
+	void updateVisibleTopBottom();
+	void checkLoadMoreArchived();
+	void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result);
+
+	Section _section;
+
+	class Inner;
+	ChildWidget<Inner> _inner;
+	ChildWidget<BoxButton> _save = { nullptr };
+	ChildWidget<BoxButton> _cancel = { nullptr };
+	OrderedSet<mtpRequestId> _disenableRequests;
+	mtpRequestId _reorderRequest = 0;
+	ChildWidget<Ui::PlainShadow> _topShadow = { nullptr };
+	ChildWidget<ScrollableBoxShadow> _bottomShadow = { nullptr };
+
+	QTimer _scrollTimer;
+	int32 _scrollDelta = 0;
+
+	int _aboutWidth = 0;
+	Text _about;
+	int _aboutHeight = 0;
+
+	mtpRequestId _archivedRequestId = 0;
+	bool _allArchivedLoaded = false;
+
+};
+
+int32 stickerPacksCount(bool includeDisabledOfficial = false);
+
+// This class is hold in header because it requires Qt preprocessing.
+class StickersBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
+	Q_OBJECT
+
+public:
+	using Section = StickersBox::Section;
+	Inner(QWidget *parent, Section section);
+	Inner(QWidget *parent, const Stickers::Order &archivedIds);
+
+	void rebuild();
+	void updateSize();
+	void updateRows(); // refresh only pack cover stickers
+	bool appendSet(const Stickers::Set &set);
+	bool savingStart() {
+		if (_saving) return false;
+		_saving = true;
+		return true;
+	}
+
+	Stickers::Order getOrder() const;
+	Stickers::Order getDisabledSets() const;
+
+	void setVisibleScrollbar(int32 width);
+	void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
+
+	~Inner();
+
+protected:
+	void paintEvent(QPaintEvent *e) override;
+	void mousePressEvent(QMouseEvent *e) override;
+	void mouseMoveEvent(QMouseEvent *e) override;
+	void mouseReleaseEvent(QMouseEvent *e) override;
+	void leaveEvent(QEvent *e) override;
+
+signals:
+	void checkDraggingScroll(int localY);
+	void noDraggingScroll();
+
+public slots:
+	void onUpdateSelected();
+	void onClearRecent();
+	void onClearBoxDestroyed(QObject *box);
+
+private slots:
+	void onImageLoaded();
+
+private:
+	void setup();
+	void paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const;
+
+	void step_shifting(uint64 ms, bool timer);
+	void paintRow(Painter &p, int32 index);
+	void clear();
+	void setActionSel(int32 actionSel);
+	float64 aboveShadowOpacity() const;
+
+	void readVisibleSets();
+
+	void installSet(uint64 setId);
+	void installDone(const MTPmessages_StickerSetInstallResult &result);
+	bool installFail(uint64 setId, const RPCError &error);
+
+	Section _section;
+	Stickers::Order _archivedIds;
+
+	int32 _rowHeight;
+	struct StickerSetRow {
+		StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id)
+			, sticker(sticker)
+			, count(count)
+			, title(title)
+			, titleWidth(titleWidth)
+			, installed(installed)
+			, official(official)
+			, unread(unread)
+			, disabled(disabled)
+			, recent(recent)
+			, pixw(pixw)
+			, pixh(pixh)
+			, yadd(0, 0) {
+		}
+		uint64 id;
+		DocumentData *sticker;
+		int32 count;
+		QString title;
+		int titleWidth;
+		bool installed, official, unread, disabled, recent;
+		int32 pixw, pixh;
+		anim::ivalue yadd;
+	};
+	using StickerSetRows = QList<StickerSetRow*>;
+
+	void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth);
+	void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
+	int fillSetCount(const Stickers::Set &set) const;
+	QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const;
+	void fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled);
+
+	int countMaxNameWidth() const;
+
+	StickerSetRows _rows;
+	QList<uint64> _animStartTimes;
+	uint64 _aboveShadowFadeStart = 0;
+	anim::fvalue _aboveShadowFadeOpacity = { 0., 0. };
+	Animation _a_shifting;
+
+	int _visibleTop = 0;
+	int _visibleBottom = 0;
+	int _itemsTop = 0;
+
+	bool _saving = false;
+
+	int _actionSel = -1;
+	int _actionDown = -1;
+
+	int _clearWidth, _removeWidth, _returnWidth, _restoreWidth;
+
+	ConfirmBox *_clearBox = nullptr;
+
+	int _buttonHeight = 0;
+	bool _hasFeaturedButton = false;
+	bool _hasArchivedButton = false;
+
+	QPoint _mouse;
+	int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button
+	int _pressed = -2;
+	QPoint _dragStart;
+	int _started = -1;
+	int _dragging = -1;
+	int _above = -1;
+
+	Ui::RectShadow _aboveShadow;
+
+	int32 _scrollbar = 0;
+};
diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp
index bbd97414c..3bf665a98 100644
--- a/Telegram/SourceFiles/boxes/stickersetbox.cpp
+++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp
@@ -19,9 +19,9 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 */
 #include "stdafx.h"
-#include "lang.h"
+#include "boxes/stickersetbox.h"
 
-#include "stickersetbox.h"
+#include "lang.h"
 #include "mainwidget.h"
 #include "mainwindow.h"
 #include "stickers/stickers.h"
@@ -32,20 +32,128 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "styles/style_boxes.h"
 #include "styles/style_stickers.h"
 
-namespace {
+StickerSetBox::StickerSetBox(const MTPInputStickerSet &set) : ScrollableBox(st::stickersScroll)
+, _inner(this, set)
+, _shadow(this)
+, _add(this, lang(lng_stickers_add_pack), st::defaultBoxButton)
+, _share(this, lang(lng_stickers_share_pack), st::defaultBoxButton)
+, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
+, _done(this, lang(lng_about_done), st::defaultBoxButton) {
+	setMaxHeight(st::stickersMaxHeight);
+	connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated()));
 
-constexpr int kArchivedLimitFirstRequest = 10;
-constexpr int kArchivedLimitPerPage = 30;
+	init(_inner, st::boxButtonPadding.bottom() + _cancel.height() + st::boxButtonPadding.top());
 
-} // namespace
+	connect(&_add, SIGNAL(clicked()), this, SLOT(onAddStickers()));
+	connect(&_share, SIGNAL(clicked()), this, SLOT(onShareStickers()));
+	connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
+	connect(&_done, SIGNAL(clicked()), this, SLOT(onClose()));
 
-StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : ScrolledWidget()
+	connect(_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons()));
+	connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
+
+	connect(_inner, SIGNAL(installed(uint64)), this, SLOT(onInstalled(uint64)));
+
+	onStickersUpdated();
+
+	onScroll();
+
+	prepare();
+}
+
+void StickerSetBox::onInstalled(uint64 setId) {
+	emit installed(setId);
+	onClose();
+}
+
+void StickerSetBox::onStickersUpdated() {
+	showAll();
+}
+
+void StickerSetBox::onAddStickers() {
+	_inner->install();
+}
+
+void StickerSetBox::onShareStickers() {
+	QString url = qsl("https://telegram.me/addstickers/") + _inner->shortName();
+	QApplication::clipboard()->setText(url);
+	Ui::showLayer(new InformBox(lang(lng_stickers_copied)));
+}
+
+void StickerSetBox::onUpdateButtons() {
+	if (!_cancel.isHidden() || !_done.isHidden()) {
+		showAll();
+	}
+}
+
+void StickerSetBox::onScroll() {
+	auto scroll = scrollArea();
+	auto scrollTop = scroll->scrollTop();
+	_inner->setVisibleTopBottom(scrollTop, scrollTop + scroll->height());
+}
+
+void StickerSetBox::showAll() {
+	ScrollableBox::showAll();
+	int32 cnt = _inner->notInstalled();
+	if (_inner->loaded()) {
+		_shadow.show();
+		if (_inner->notInstalled()) {
+			_add.show();
+			_cancel.show();
+			_share.hide();
+			_done.hide();
+		} else if (_inner->official()) {
+			_add.hide();
+			_share.hide();
+			_cancel.hide();
+			_done.show();
+		} else {
+			_share.show();
+			_cancel.show();
+			_add.hide();
+			_done.hide();
+		}
+	} else {
+		_shadow.hide();
+		_add.hide();
+		_share.hide();
+		_cancel.show();
+		_done.hide();
+	}
+	resizeEvent(0);
+	update();
+}
+
+void StickerSetBox::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+	if (paint(p)) return;
+
+	paintTitle(p, _inner->title());
+}
+
+void StickerSetBox::resizeEvent(QResizeEvent *e) {
+	ScrollableBox::resizeEvent(e);
+	_inner->resize(width(), _inner->height());
+	_shadow.setGeometry(0, height() - st::boxButtonPadding.bottom() - _cancel.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth);
+	_add.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _add.height());
+	_share.moveToRight(st::boxButtonPadding.right(), _add.y());
+	_done.moveToRight(st::boxButtonPadding.right(), _add.y());
+	if (_add.isHidden() && _share.isHidden()) {
+		_cancel.moveToRight(st::boxButtonPadding.right(), _add.y());
+	} else if (_add.isHidden()) {
+		_cancel.moveToRight(st::boxButtonPadding.right() + _share.width() + st::boxButtonPadding.left(), _add.y());
+	} else {
+		_cancel.moveToRight(st::boxButtonPadding.right() + _add.width() + st::boxButtonPadding.left(), _add.y());
+	}
+}
+
+StickerSetBox::Inner::Inner(QWidget *parent, const MTPInputStickerSet &set) : ScrolledWidget(parent)
 , _input(set) {
 	switch (set.type()) {
 	case mtpc_inputStickerSetID: _setId = set.c_inputStickerSetID().vid.v; _setAccess = set.c_inputStickerSetID().vaccess_hash.v; break;
 	case mtpc_inputStickerSetShortName: _setShortName = qs(set.c_inputStickerSetShortName().vshort_name); break;
 	}
-	MTP::send(MTPmessages_GetStickerSet(_input), rpcDone(&StickerSetInner::gotSet), rpcFail(&StickerSetInner::failedSet));
+	MTP::send(MTPmessages_GetStickerSet(_input), rpcDone(&Inner::gotSet), rpcFail(&Inner::failedSet));
 	App::main()->updateStickers();
 
 	subscribe(FileDownload::ImageLoaded(), [this] { update(); });
@@ -56,7 +164,7 @@ StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : ScrolledWidget
 	connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview()));
 }
 
-void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) {
+void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
 	_pack.clear();
 	_emoji.clear();
 	_packOvers.clear();
@@ -126,7 +234,7 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) {
 	emit updateButtons();
 }
 
-bool StickerSetInner::failedSet(const RPCError &error) {
+bool StickerSetBox::Inner::failedSet(const RPCError &error) {
 	if (MTP::isDefaultHandledError(error)) return false;
 
 	_loaded = true;
@@ -136,7 +244,7 @@ bool StickerSetInner::failedSet(const RPCError &error) {
 	return true;
 }
 
-void StickerSetInner::installDone(const MTPmessages_StickerSetInstallResult &result) {
+void StickerSetBox::Inner::installDone(const MTPmessages_StickerSetInstallResult &result) {
 	auto &sets = Global::RefStickerSets();
 
 	bool wasArchived = (_setFlags & MTPDstickerSet::Flag::f_archived);
@@ -189,7 +297,7 @@ void StickerSetInner::installDone(const MTPmessages_StickerSetInstallResult &res
 	emit installed(_setId);
 }
 
-bool StickerSetInner::installFail(const RPCError &error) {
+bool StickerSetBox::Inner::installFail(const RPCError &error) {
 	if (MTP::isDefaultHandledError(error)) return false;
 
 	Ui::showLayer(new InformBox(lang(lng_stickers_not_found)));
@@ -197,14 +305,14 @@ bool StickerSetInner::installFail(const RPCError &error) {
 	return true;
 }
 
-void StickerSetInner::mousePressEvent(QMouseEvent *e) {
+void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) {
 	int index = stickerFromGlobalPos(e->globalPos());
 	if (index >= 0 && index < _pack.size()) {
 		_previewTimer.start(QApplication::startDragTime());
 	}
 }
 
-void StickerSetInner::mouseMoveEvent(QMouseEvent *e) {
+void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) {
 	updateSelected();
 	if (_previewShown >= 0) {
 		int index = stickerFromGlobalPos(e->globalPos());
@@ -215,7 +323,7 @@ void StickerSetInner::mouseMoveEvent(QMouseEvent *e) {
 	}
 }
 
-void StickerSetInner::mouseReleaseEvent(QMouseEvent *e) {
+void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
 	if (_previewShown >= 0) {
 		_previewShown = -1;
 		return;
@@ -233,7 +341,7 @@ void StickerSetInner::mouseReleaseEvent(QMouseEvent *e) {
 	}
 }
 
-void StickerSetInner::updateSelected() {
+void StickerSetBox::Inner::updateSelected() {
 	auto index = stickerFromGlobalPos(QCursor::pos());
 	if (isMasksSet()) {
 		index = -1;
@@ -246,7 +354,7 @@ void StickerSetInner::updateSelected() {
 	}
 }
 
-void StickerSetInner::startOverAnimation(int index, float64 from, float64 to) {
+void StickerSetBox::Inner::startOverAnimation(int index, float64 from, float64 to) {
 	if (index >= 0 && index < _packOvers.size()) {
 		_packOvers[index].start([this, index] {
 			int row = index / StickerPanPerRow;
@@ -258,7 +366,7 @@ void StickerSetInner::startOverAnimation(int index, float64 from, float64 to) {
 	}
 }
 
-void StickerSetInner::onPreview() {
+void StickerSetBox::Inner::onPreview() {
 	int index = stickerFromGlobalPos(QCursor::pos());
 	if (index >= 0 && index < _pack.size()) {
 		_previewShown = index;
@@ -266,7 +374,7 @@ void StickerSetInner::onPreview() {
 	}
 }
 
-int32 StickerSetInner::stickerFromGlobalPos(const QPoint &p) const {
+int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const {
 	QPoint l(mapFromGlobal(p));
 	if (rtl()) l.setX(width() - l.x());
 	int32 row = (l.y() >= st::stickersPadding.top()) ? qFloor((l.y() - st::stickersPadding.top()) / st::stickersSize.height()) : -1;
@@ -278,7 +386,7 @@ int32 StickerSetInner::stickerFromGlobalPos(const QPoint &p) const {
 	return -1;
 }
 
-void StickerSetInner::paintEvent(QPaintEvent *e) {
+void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
 	QRect r(e->rect());
 	Painter p(this);
 
@@ -331,1376 +439,42 @@ void StickerSetInner::paintEvent(QPaintEvent *e) {
 	}
 }
 
-void StickerSetInner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
+void StickerSetBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
 	_visibleTop = visibleTop;
 	_visibleBottom = visibleBottom;
 }
 
-bool StickerSetInner::loaded() const {
+bool StickerSetBox::Inner::loaded() const {
 	return _loaded && !_pack.isEmpty();
 }
 
-int32 StickerSetInner::notInstalled() const {
+int32 StickerSetBox::Inner::notInstalled() const {
 	if (!_loaded) return 0;
 	auto it = Global::StickerSets().constFind(_setId);
 	if (it == Global::StickerSets().cend() || !(it->flags & MTPDstickerSet::Flag::f_installed) || (it->flags & MTPDstickerSet::Flag::f_archived)) return _pack.size();
 	return 0;
 }
 
-bool StickerSetInner::official() const {
+bool StickerSetBox::Inner::official() const {
 	return _loaded && _setShortName.isEmpty();
 }
 
-QString StickerSetInner::title() const {
+QString StickerSetBox::Inner::title() const {
 	return _loaded ? (_pack.isEmpty() ? lang(lng_attach_failed) : _title) : lang(lng_contacts_loading);
 }
 
-QString StickerSetInner::shortName() const {
+QString StickerSetBox::Inner::shortName() const {
 	return _setShortName;
 }
 
-void StickerSetInner::install() {
+void StickerSetBox::Inner::install() {
 	if (isMasksSet()) {
 		Ui::showLayer(new InformBox(lang(lng_stickers_masks_pack)), KeepOtherLayers);
 		return;
 	}
 	if (_installRequest) return;
-	_installRequest = MTP::send(MTPmessages_InstallStickerSet(_input, MTP_bool(false)), rpcDone(&StickerSetInner::installDone), rpcFail(&StickerSetInner::installFail));
+	_installRequest = MTP::send(MTPmessages_InstallStickerSet(_input, MTP_bool(false)), rpcDone(&Inner::installDone), rpcFail(&Inner::installFail));
 }
 
-StickerSetInner::~StickerSetInner() {
-}
-
-StickerSetBox::StickerSetBox(const MTPInputStickerSet &set) : ScrollableBox(st::stickersScroll)
-, _inner(set)
-, _shadow(this)
-, _add(this, lang(lng_stickers_add_pack), st::defaultBoxButton)
-, _share(this, lang(lng_stickers_share_pack), st::defaultBoxButton)
-, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
-, _done(this, lang(lng_about_done), st::defaultBoxButton) {
-	setMaxHeight(st::stickersMaxHeight);
-	connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated()));
-
-	init(&_inner, st::boxButtonPadding.bottom() + _cancel.height() + st::boxButtonPadding.top());
-
-	connect(&_add, SIGNAL(clicked()), this, SLOT(onAddStickers()));
-	connect(&_share, SIGNAL(clicked()), this, SLOT(onShareStickers()));
-	connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
-	connect(&_done, SIGNAL(clicked()), this, SLOT(onClose()));
-
-	connect(&_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons()));
-	connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
-
-	connect(&_inner, SIGNAL(installed(uint64)), this, SLOT(onInstalled(uint64)));
-
-	onStickersUpdated();
-
-	onScroll();
-
-	prepare();
-}
-
-void StickerSetBox::onInstalled(uint64 setId) {
-	emit installed(setId);
-	onClose();
-}
-
-void StickerSetBox::onStickersUpdated() {
-	showAll();
-}
-
-void StickerSetBox::onAddStickers() {
-	_inner.install();
-}
-
-void StickerSetBox::onShareStickers() {
-	QString url = qsl("https://telegram.me/addstickers/") + _inner.shortName();
-	QApplication::clipboard()->setText(url);
-	Ui::showLayer(new InformBox(lang(lng_stickers_copied)));
-}
-
-void StickerSetBox::onUpdateButtons() {
-	if (!_cancel.isHidden() || !_done.isHidden()) {
-		showAll();
-	}
-}
-
-void StickerSetBox::onScroll() {
-	auto scroll = scrollArea();
-	auto scrollTop = scroll->scrollTop();
-	_inner.setVisibleTopBottom(scrollTop, scrollTop + scroll->height());
-}
-
-void StickerSetBox::showAll() {
-	ScrollableBox::showAll();
-	int32 cnt = _inner.notInstalled();
-	if (_inner.loaded()) {
-		_shadow.show();
-		if (_inner.notInstalled()) {
-			_add.show();
-			_cancel.show();
-			_share.hide();
-			_done.hide();
-		} else if (_inner.official()) {
-			_add.hide();
-			_share.hide();
-			_cancel.hide();
-			_done.show();
-		} else {
-			_share.show();
-			_cancel.show();
-			_add.hide();
-			_done.hide();
-		}
-	} else {
-		_shadow.hide();
-		_add.hide();
-		_share.hide();
-		_cancel.show();
-		_done.hide();
-	}
-	resizeEvent(0);
-	update();
-}
-
-void StickerSetBox::paintEvent(QPaintEvent *e) {
-	Painter p(this);
-	if (paint(p)) return;
-
-	paintTitle(p, _inner.title());
-}
-
-void StickerSetBox::resizeEvent(QResizeEvent *e) {
-	ScrollableBox::resizeEvent(e);
-	_inner.resize(width(), _inner.height());
-	_shadow.setGeometry(0, height() - st::boxButtonPadding.bottom() - _cancel.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth);
-	_add.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _add.height());
-	_share.moveToRight(st::boxButtonPadding.right(), _add.y());
-	_done.moveToRight(st::boxButtonPadding.right(), _add.y());
-	if (_add.isHidden() && _share.isHidden()) {
-		_cancel.moveToRight(st::boxButtonPadding.right(), _add.y());
-	} else if (_add.isHidden()) {
-		_cancel.moveToRight(st::boxButtonPadding.right() + _share.width() + st::boxButtonPadding.left(), _add.y());
-	} else {
-		_cancel.moveToRight(st::boxButtonPadding.right() + _add.width() + st::boxButtonPadding.left(), _add.y());
-	}
-}
-
-namespace internal {
-
-StickersInner::StickersInner(StickersBox::Section section) : ScrolledWidget()
-, _section(section)
-, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
-, _a_shifting(animation(this, &StickersInner::step_shifting))
-, _itemsTop(st::membersPadding.top())
-, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent)))
-, _removeWidth(st::normalFont->width(lang(lng_stickers_remove)))
-, _returnWidth(st::normalFont->width(lang(lng_stickers_return)))
-, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore)))
-, _aboveShadow(st::boxShadow) {
-	setup();
-}
-
-StickersInner::StickersInner(const Stickers::Order &archivedIds) : ScrolledWidget()
-, _section(StickersBox::Section::ArchivedPart)
-, _archivedIds(archivedIds)
-, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
-, _a_shifting(animation(this, &StickersInner::step_shifting))
-, _itemsTop(st::membersPadding.top())
-, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent)))
-, _removeWidth(st::normalFont->width(lang(lng_stickers_remove)))
-, _returnWidth(st::normalFont->width(lang(lng_stickers_return)))
-, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore)))
-, _aboveShadow(st::boxShadow) {
-	setup();
-}
-
-void StickersInner::setup() {
-	subscribe(FileDownload::ImageLoaded(), [this] { update(); });
-	setMouseTracking(true);
-}
-
-void StickersInner::onImageLoaded() {
-	update();
-	readVisibleSets();
-}
-
-void StickersInner::paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const {
-	if (selected) {
-		p.fillRect(0, y, width(), _buttonHeight, st::contactsBgOver);
-	}
-	p.setFont(st::stickersFeaturedFont);
-	p.setPen(st::stickersFeaturedPen);
-	p.drawTextLeft(st::stickersFeaturedPosition.x(), y + st::stickersFeaturedPosition.y(), width(), text);
-
-	if (badgeCounter) {
-		Dialogs::Layout::UnreadBadgeStyle unreadSt;
-		unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox;
-		unreadSt.size = st::stickersFeaturedBadgeSize;
-		int unreadRight = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x());
-		if (rtl()) unreadRight = width() - unreadRight;
-		int unreadTop = y + (_buttonHeight - st::stickersFeaturedBadgeSize) / 2;
-		Dialogs::Layout::paintUnreadCount(p, QString::number(badgeCounter), unreadRight, unreadTop, unreadSt);
-	}
-}
-
-void StickersInner::paintEvent(QPaintEvent *e) {
-	QRect r(e->rect());
-	Painter p(this);
-
-	_a_shifting.step();
-
-	p.fillRect(r, st::white);
-	p.setClipRect(r);
-
-	int y = st::membersPadding.top();
-	if (_hasFeaturedButton) {
-		auto selected = (_selected == -2);
-		paintButton(p, y, selected, lang(lng_stickers_featured), Global::FeaturedStickerSetsUnreadCount());
-		y += _buttonHeight;
-	}
-	if (_hasArchivedButton) {
-		auto selected = (_selected == -1);
-		paintButton(p, y, selected, lang(lng_stickers_archived), 0);
-		y += _buttonHeight;
-	}
-
-	if (_rows.isEmpty()) {
-		p.setFont(st::noContactsFont);
-		p.setPen(st::noContactsColor);
-		p.drawText(QRect(0, y, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center);
-	} else {
-		p.translate(0, _itemsTop);
-
-		int32 yFrom = r.y() - _itemsTop, yTo = r.y() + r.height() - _itemsTop;
-		int32 from = floorclamp(yFrom - _rowHeight, _rowHeight, 0, _rows.size());
-		int32 to = ceilclamp(yTo + _rowHeight, _rowHeight, 0, _rows.size());
-		p.translate(0, from * _rowHeight);
-		for (int32 i = from; i < to; ++i) {
-			if (i != _above) {
-				paintRow(p, i);
-			}
-			p.translate(0, _rowHeight);
-		}
-		if (from <= _above && _above < to) {
-			p.translate(0, (_above - to) * _rowHeight);
-			paintRow(p, _above);
-		}
-	}
-}
-
-void StickersInner::paintRow(Painter &p, int32 index) {
-	const StickerSetRow *s(_rows.at(index));
-
-	int32 xadd = 0, yadd = s->yadd.current();
-	if (xadd || yadd) p.translate(xadd, yadd);
-
-	if (_section == Section::Installed) {
-		bool removeSel = (index == _actionSel && (_actionDown < 0 || index == _actionDown));
-		bool removeDown = removeSel && (index == _actionDown);
-
-		p.setFont(removeSel ? st::linkOverFont : st::linkFont);
-		if (removeDown) {
-			p.setPen(st::btnDefLink.downColor);
-		} else {
-			p.setPen(st::btnDefLink.color);
-		}
-		int32 remWidth = s->recent ? _clearWidth : (s->disabled ? (s->official ? _restoreWidth : _returnWidth) : _removeWidth);
-		QString remText = lang(s->recent ? lng_stickers_clear_recent : (s->disabled ? (s->official ? lng_stickers_restore : lng_stickers_return) : lng_stickers_remove));
-		p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), remText, remWidth);
-
-		if (index == _above) {
-			float64 current = _aboveShadowFadeOpacity.current();
-			if (_started >= 0) {
-				float64 o = aboveShadowOpacity();
-				if (o > current) {
-					_aboveShadowFadeOpacity = anim::fvalue(o, o);
-					current = o;
-				}
-			}
-			p.setOpacity(current);
-			QRect row(myrtlrect(_aboveShadow.getDimensions(st::boxShadowShift).left(), st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - _aboveShadow.getDimensions(st::boxShadowShift).right(), _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2)));
-			_aboveShadow.paint(p, row, st::boxShadowShift);
-			p.fillRect(row, st::white);
-			p.setOpacity(1);
-		}
-	} else if (s->installed && !s->disabled) {
-		int addw = st::stickersAddSize.width();
-		int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (addw + st::stickersFeaturedInstalled.width()) / 2);
-		int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2;
-		st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width());
-	} else {
-		int addw = st::stickersAddSize.width();
-		int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw;
-		int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2;
-		QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height()));
-
-		auto textBg = (_actionSel == index) ? st::defaultActiveButton.textBgOver : st::defaultActiveButton.textBg;
-		App::roundRect(p, add, textBg, ImageRoundRadius::Small);
-		int iconx = addx + (st::stickersAddSize.width() - st::stickersAddIcon.width()) / 2;
-		int icony = addy + (st::stickersAddSize.height() - st::stickersAddIcon.height()) / 2;
-		icony += (_actionSel == index && _actionDown == index) ? (st::defaultActiveButton.downTextTop - st::defaultActiveButton.textTop) : 0;
-		st::stickersAddIcon.paint(p, QPoint(iconx, icony), width());
-	}
-
-	if (s->disabled && _section == Section::Installed) {
-		p.setOpacity(st::stickersRowDisabledOpacity);
-	}
-	if (s->sticker) {
-		s->sticker->thumb->load();
-		QPixmap pix(s->sticker->thumb->pix(s->pixw, s->pixh));
-		p.drawPixmapLeft(st::contactsPadding.left() + (st::contactsPhotoSize - s->pixw) / 2, st::contactsPadding.top() + (st::contactsPhotoSize - s->pixh) / 2, width(), pix);
-	}
-
-	int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
-	int namey = st::contactsPadding.top() + st::contactsNameTop;
-	int statusx = namex;
-	int statusy = st::contactsPadding.top() + st::contactsStatusTop;
-
-	p.setFont(st::contactsNameFont);
-	p.setPen(st::black);
-	p.drawTextLeft(namex, namey, width(), s->title, s->titleWidth);
-
-	if (s->unread) {
-		p.setPen(Qt::NoPen);
-		p.setBrush(st::stickersFeaturedUnreadBg);
-
-		p.setRenderHint(QPainter::HighQualityAntialiasing, true);
-		p.drawEllipse(rtlrect(namex + s->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
-		p.setRenderHint(QPainter::HighQualityAntialiasing, false);
-	}
-
-	p.setFont(st::contactsStatusFont);
-	p.setPen(st::contactsStatusFg);
-	p.drawTextLeft(statusx, statusy, width(), lng_stickers_count(lt_count, s->count));
-
-	p.setOpacity(1);
-	if (xadd || yadd) p.translate(-xadd, -yadd);
-}
-
-void StickersInner::mousePressEvent(QMouseEvent *e) {
-	if (_saving) return;
-	if (_dragging >= 0) mouseReleaseEvent(e);
-	_mouse = e->globalPos();
-	onUpdateSelected();
-
-	_pressed = _selected;
-	if (_actionSel >= 0) {
-		_actionDown = _actionSel;
-		update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
-	} else if (_selected >= 0 && _section == Section::Installed && !_rows.at(_selected)->recent) {
-		_above = _dragging = _started = _selected;
-		_dragStart = mapFromGlobal(_mouse);
-	}
-}
-
-void StickersInner::mouseMoveEvent(QMouseEvent *e) {
-	if (_saving) return;
-	_mouse = e->globalPos();
-	onUpdateSelected();
-}
-
-void StickersInner::onUpdateSelected() {
-	if (_saving) return;
-	QPoint local(mapFromGlobal(_mouse));
-	if (_dragging >= 0) {
-		int32 shift = 0;
-		uint64 ms = getms();
-		int firstSetIndex = 0;
-		if (_rows.at(firstSetIndex)->recent) {
-			++firstSetIndex;
-		}
-		if (_dragStart.y() > local.y() && _dragging > 0) {
-			shift = -floorclamp(_dragStart.y() - local.y() + (_rowHeight / 2), _rowHeight, 0, _dragging - firstSetIndex);
-			for (int32 from = _dragging, to = _dragging + shift; from > to; --from) {
-				qSwap(_rows[from], _rows[from - 1]);
-				_rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() - _rowHeight, 0);
-				_animStartTimes[from] = ms;
-			}
-		} else if (_dragStart.y() < local.y() && _dragging + 1 < _rows.size()) {
-			shift = floorclamp(local.y() - _dragStart.y() + (_rowHeight / 2), _rowHeight, 0, _rows.size() - _dragging - 1);
-			for (int32 from = _dragging, to = _dragging + shift; from < to; ++from) {
-				qSwap(_rows[from], _rows[from + 1]);
-				_rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() + _rowHeight, 0);
-				_animStartTimes[from] = ms;
-			}
-		}
-		if (shift) {
-			_dragging += shift;
-			_above = _dragging;
-			_dragStart.setY(_dragStart.y() + shift * _rowHeight);
-			if (!_a_shifting.animating()) {
-				_a_shifting.start();
-			}
-		}
-		_rows.at(_dragging)->yadd = anim::ivalue(local.y() - _dragStart.y(), local.y() - _dragStart.y());
-		_animStartTimes[_dragging] = 0;
-		_a_shifting.step(getms(), true);
-
-		emit checkDraggingScroll(local.y());
-	} else {
-		bool in = rect().marginsRemoved(QMargins(0, _itemsTop, 0, st::membersPadding.bottom())).contains(local);
-		int selected = -2;
-		int actionSel = -1;
-		if (in) {
-			selected = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1);
-
-			if (_section == Section::Installed) {
-				int remw = _rows.at(selected)->recent ? _clearWidth : (_rows.at(selected)->disabled ? (_rows.at(selected)->official ? _restoreWidth : _returnWidth) : _removeWidth);
-				QRect rem(myrtlrect(width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - remw, st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, remw, st::normalFont->height));
-				actionSel = rem.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1;
-			} else if (_rows.at(selected)->installed && !_rows.at(selected)->disabled) {
-				actionSel = -1;
-			} else {
-				int addw = st::stickersAddSize.width();
-				int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw;
-				int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2;
-				QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height()));
-				actionSel = add.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1;
-			}
-		} else if (_hasFeaturedButton && QRect(0, st::membersPadding.top(), width(), _buttonHeight).contains(local)) {
-			selected = -2;
-		} else if (_hasArchivedButton && QRect(0, st::membersPadding.top() + (_hasFeaturedButton ? _buttonHeight : 0), width(), _buttonHeight).contains(local)) {
-			selected = -1;
-		} else {
-			selected = -3;
-		}
-		if (_selected != selected) {
-			if (((_selected == -1) != (selected == -1)) || ((_selected == -2) != (selected == -2))) {
-				update();
-			}
-			if (_section != Section::Installed && ((_selected >= 0 || _pressed >= 0) != (selected >= 0 || _pressed >= 0))) {
-				setCursor((selected >= 0 || _pressed >= 0) ? style::cur_pointer : style::cur_default);
-			}
-			_selected = selected;
-		}
-		setActionSel(actionSel);
-		emit noDraggingScroll();
-	}
-}
-
-void StickersInner::onClearRecent() {
-	if (_clearBox) {
-		_clearBox->onClose();
-	}
-
-	auto &sets = Global::RefStickerSets();
-	bool removedCloud = (sets.remove(Stickers::CloudRecentSetId) != 0);
-	bool removedCustom = (sets.remove(Stickers::CustomSetId) != 0);
-
-	auto &recent = cGetRecentStickers();
-	if (!recent.isEmpty()) {
-		recent.clear();
-		Local::writeUserSettings();
-	}
-
-	if (removedCustom) Local::writeInstalledStickers();
-	if (removedCloud) Local::writeRecentStickers();
-	emit App::main()->updateStickers();
-	rebuild();
-
-	MTPmessages_ClearRecentStickers::Flags flags = 0;
-	MTP::send(MTPmessages_ClearRecentStickers(MTP_flags(flags)));
-}
-
-void StickersInner::onClearBoxDestroyed(QObject *box) {
-	if (box == _clearBox) {
-		_clearBox = nullptr;
-	}
-}
-
-float64 StickersInner::aboveShadowOpacity() const {
-	if (_above < 0) return 0;
-
-	int32 dx = 0;
-	int32 dy = qAbs(_above * _rowHeight + _rows.at(_above)->yadd.current() - _started * _rowHeight);
-	return qMin((dx + dy)  * 2. / _rowHeight, 1.);
-}
-
-void StickersInner::mouseReleaseEvent(QMouseEvent *e) {
-	auto pressed = _pressed;
-	_pressed = -2;
-
-	if (_section != Section::Installed && _selected < 0 && pressed >= 0) {
-		setCursor(style::cur_default);
-	}
-
-	if (_saving) return;
-
-	_mouse = e->globalPos();
-	onUpdateSelected();
-	if (_actionDown == _actionSel && _actionSel >= 0) {
-		if (_section == Section::Installed) {
-			if (_rows[_actionDown]->recent) {
-				_clearBox = new ConfirmBox(lang(lng_stickers_clear_recent_sure), lang(lng_stickers_clear_recent));
-				connect(_clearBox, SIGNAL(confirmed()), this, SLOT(onClearRecent()));
-				connect(_clearBox, SIGNAL(destroyed(QObject*)), this, SLOT(onClearBoxDestroyed(QObject*)));
-				Ui::showLayer(_clearBox, KeepOtherLayers);
-			} else {
-				_rows[_actionDown]->disabled = !_rows[_actionDown]->disabled;
-			}
-		} else {
-			installSet(_rows[_actionDown]->id);
-		}
-	} else if (_dragging >= 0) {
-		QPoint local(mapFromGlobal(_mouse));
-		_rows[_dragging]->yadd.start(0);
-		_aboveShadowFadeStart = _animStartTimes[_dragging] = getms();
-		_aboveShadowFadeOpacity = anim::fvalue(aboveShadowOpacity(), 0);
-		if (!_a_shifting.animating()) {
-			_a_shifting.start();
-		}
-
-		_dragging = _started = -1;
-	} else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) {
-		if (_selected == -2) {
-			_selected = -3;
-			Ui::showLayer(new StickersBox(Section::Featured), KeepOtherLayers);
-		} else if (_selected == -1) {
-			_selected = -3;
-			Ui::showLayer(new StickersBox(Section::Archived), KeepOtherLayers);
-		} else if (_selected >= 0 && _section != Section::Installed) {
-			auto &sets = Global::RefStickerSets();
-			auto it = sets.find(_rows.at(pressed)->id);
-			if (it != sets.cend()) {
-				_selected = -3;
-				Ui::showLayer(new StickerSetBox(Stickers::inputSetId(*it)), KeepOtherLayers);
-			}
-		}
-	}
-	if (_actionDown >= 0) {
-		update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight);
-		_actionDown = -1;
-	}
-}
-
-void StickersInner::leaveEvent(QEvent *e) {
-	_mouse = QPoint(-1, -1);
-	onUpdateSelected();
-}
-
-void StickersInner::installSet(uint64 setId) {
-	auto &sets = Global::RefStickerSets();
-	auto it = sets.find(setId);
-	if (it == sets.cend()) {
-		rebuild();
-		return;
-	}
-
-	MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), rpcDone(&StickersInner::installDone), rpcFail(&StickersInner::installFail, setId));
-
-	Stickers::installLocally(setId);
-}
-
-void StickersInner::installDone(const MTPmessages_StickerSetInstallResult &result) {
-	if (result.type() == mtpc_messages_stickerSetInstallResultArchive) {
-		Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive());
-	}
-}
-
-bool StickersInner::installFail(uint64 setId, const RPCError &error) {
-	if (MTP::isDefaultHandledError(error)) return false;
-
-	auto &sets = Global::RefStickerSets();
-	auto it = sets.find(setId);
-	if (it == sets.cend()) {
-		rebuild();
-		return true;
-	}
-
-	Stickers::undoInstallLocally(setId);
-	return true;
-}
-
-void StickersInner::step_shifting(uint64 ms, bool timer) {
-	bool animating = false;
-	int32 updateMin = -1, updateMax = 0;
-	for (int32 i = 0, l = _animStartTimes.size(); i < l; ++i) {
-		uint64 start = _animStartTimes.at(i);
-		if (start) {
-			if (updateMin < 0) updateMin = i;
-			updateMax = i;
-			if (start + st::stickersRowDuration > ms && ms >= start) {
-				_rows.at(i)->yadd.update(float64(ms - start) / st::stickersRowDuration, anim::sineInOut);
-				animating = true;
-			} else {
-				_rows.at(i)->yadd.finish();
-				_animStartTimes[i] = 0;
-			}
-		}
-	}
-	if (_aboveShadowFadeStart) {
-		if (updateMin < 0 || updateMin > _above) updateMin = _above;
-		if (updateMax < _above) updateMin = _above;
-		if (_aboveShadowFadeStart + st::stickersRowDuration > ms && ms > _aboveShadowFadeStart) {
-			_aboveShadowFadeOpacity.update(float64(ms - _aboveShadowFadeStart) / st::stickersRowDuration, anim::sineInOut);
-			animating = true;
-		} else {
-			_aboveShadowFadeOpacity.finish();
-			_aboveShadowFadeStart = 0;
-		}
-	}
-	if (timer) {
-		if (_dragging >= 0) {
-			if (updateMin < 0 || updateMin > _dragging) updateMin = _dragging;
-			if (updateMax < _dragging) updateMax = _dragging;
-		}
-		if (updateMin >= 0) {
-			update(0, _itemsTop + _rowHeight * (updateMin - 1), width(), _rowHeight * (updateMax - updateMin + 3));
-		}
-	}
-	if (!animating) {
-		_above = _dragging;
-		_a_shifting.stop();
-	}
-}
-
-void StickersInner::clear() {
-	for (int32 i = 0, l = _rows.size(); i < l; ++i) {
-		delete _rows.at(i);
-	}
-	_rows.clear();
-	_animStartTimes.clear();
-	_aboveShadowFadeStart = 0;
-	_aboveShadowFadeOpacity = anim::fvalue(0, 0);
-	_a_shifting.stop();
-	_above = _dragging = _started = -1;
-	_selected = -3;
-	_pressed = -3;
-	_actionDown = -1;
-	setActionSel(-1);
-	update();
-}
-
-void StickersInner::setActionSel(int32 actionSel) {
-	if (actionSel != _actionSel) {
-		if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
-		_actionSel = actionSel;
-		if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
-		if (_section == Section::Installed) {
-			setCursor((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel)) ? style::cur_pointer : style::cur_default);
-		}
-	}
-}
-
-void StickersInner::rebuild() {
-	_hasFeaturedButton = _hasArchivedButton = false;
-	_itemsTop = st::membersPadding.top();
-	_buttonHeight = st::stickersFeaturedHeight;
-	if (_section == Section::Installed) {
-		if (!Global::FeaturedStickerSetsOrder().isEmpty()) {
-			_itemsTop += _buttonHeight;
-			_hasFeaturedButton = true;
-		}
-		if (!Global::ArchivedStickerSetsOrder().isEmpty()) {
-			_itemsTop += _buttonHeight;
-			_hasArchivedButton = true;
-		}
-		if (_itemsTop > st::membersPadding.top()) {
-			_itemsTop += st::membersPadding.top();
-		}
-	}
-
-	int maxNameWidth = countMaxNameWidth();
-
-	clear();
-	auto &order = ([this]() -> const Stickers::Order & {
-		if (_section == Section::Installed) {
-			return Global::StickerSetsOrder();
-		} else if (_section == Section::Featured) {
-			return Global::FeaturedStickerSetsOrder();
-		} else if (_section == Section::Archived) {
-			return Global::ArchivedStickerSetsOrder();
-		}
-		return _archivedIds;
-	})();
-	_rows.reserve(order.size() + 1);
-	_animStartTimes.reserve(order.size() + 1);
-
-	auto &sets = Global::StickerSets();
-	if (_section == Section::Installed) {
-		auto cloudIt = sets.constFind(Stickers::CloudRecentSetId);
-		if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) {
-			rebuildAppendSet(cloudIt.value(), maxNameWidth);
-		}
-	}
-	for_const (auto setId, order) {
-		auto it = sets.constFind(setId);
-		if (it == sets.cend()) {
-			continue;
-		}
-
-		rebuildAppendSet(it.value(), maxNameWidth);
-
-		if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
-			App::api()->scheduleStickerSetRequest(it->id, it->access);
-		}
-	}
-	App::api()->requestStickerSets();
-	updateSize();
-}
-
-void StickersInner::updateSize() {
-	resize(width(), _itemsTop + _rows.size() * _rowHeight + st::membersPadding.bottom());
-}
-
-void StickersInner::updateRows() {
-	int maxNameWidth = countMaxNameWidth();
-	auto &sets = Global::StickerSets();
-	for_const (auto row, _rows) {
-		auto it = sets.constFind(row->id);
-		if (it != sets.cend()) {
-			auto &set = it.value();
-			if (!row->sticker) {
-				DocumentData *sticker = nullptr;
-				int pixw = 0, pixh = 0;
-				fillSetCover(set, &sticker, &pixw, &pixh);
-				if (sticker) {
-					row->sticker = sticker;
-					row->pixw = pixw;
-					row->pixh = pixh;
-				}
-			}
-			fillSetFlags(set, &row->recent, &row->installed, &row->official, &row->unread, &row->disabled);
-			if (_section == Section::Installed) {
-				row->disabled = false;
-			}
-			row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth);
-			row->count = fillSetCount(set);
-		}
-	}
-	update();
-}
-
-bool StickersInner::appendSet(const Stickers::Set &set) {
-	for_const (auto row, _rows) {
-		if (row->id == set.id) {
-			return false;
-		}
-	}
-	rebuildAppendSet(set, countMaxNameWidth());
-	return true;
-}
-
-int StickersInner::countMaxNameWidth() const {
-	int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
-	int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x();
-	if (_section == Section::Installed) {
-		namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth);
-	} else {
-		namew -= st::stickersAddIcon.width() - st::defaultActiveButton.width;
-		namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip;
-	}
-	return namew;
-}
-
-void StickersInner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) {
-	bool recent = false, installed = false, official = false, unread = false, disabled = false;
-	fillSetFlags(set, &recent, &installed, &official, &unread, &disabled);
-	if (_section == Section::Installed && disabled) {
-		return;
-	}
-
-	DocumentData *sticker = nullptr;
-	int pixw = 0, pixh = 0;
-	fillSetCover(set, &sticker, &pixw, &pixh);
-
-	int titleWidth = 0;
-	QString title = fillSetTitle(set, maxNameWidth, &titleWidth);
-	int count = fillSetCount(set);
-
-	_rows.push_back(new StickerSetRow(set.id, sticker, count, title, titleWidth, installed, official, unread, disabled, recent, pixw, pixh));
-	_animStartTimes.push_back(0);
-}
-
-void StickersInner::fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const {
-	if (set.stickers.isEmpty()) {
-		*outSticker = nullptr;
-		*outWidth = *outHeight = 0;
-		return;
-	}
-	auto sticker = *outSticker = set.stickers.front();
-
-	auto pixw = sticker->thumb->width();
-	auto pixh = sticker->thumb->height();
-	if (pixw > st::contactsPhotoSize) {
-		if (pixw > pixh) {
-			pixh = (pixh * st::contactsPhotoSize) / pixw;
-			pixw = st::contactsPhotoSize;
-		} else {
-			pixw = (pixw * st::contactsPhotoSize) / pixh;
-			pixh = st::contactsPhotoSize;
-		}
-	} else if (pixh > st::contactsPhotoSize) {
-		pixw = (pixw * st::contactsPhotoSize) / pixh;
-		pixh = st::contactsPhotoSize;
-	}
-	*outWidth = pixw;
-	*outHeight = pixh;
-}
-
-int StickersInner::fillSetCount(const Stickers::Set &set) const {
-	int result = set.stickers.isEmpty() ? set.count : set.stickers.size(), added = 0;
-	if (set.id == Stickers::CloudRecentSetId) {
-		auto customIt = Global::StickerSets().constFind(Stickers::CustomSetId);
-		if (customIt != Global::StickerSets().cend()) {
-			added = customIt->stickers.size();
-			for_const (auto &sticker, cGetRecentStickers()) {
-				if (customIt->stickers.indexOf(sticker.first) < 0) {
-					++added;
-				}
-			}
-		} else {
-			added = cGetRecentStickers().size();
-		}
-	}
-	return result + added;
-}
-
-QString StickersInner::fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const {
-	auto result = set.title;
-	int titleWidth = st::contactsNameFont->width(result);
-	if (titleWidth > maxNameWidth) {
-		result = st::contactsNameFont->elided(result, maxNameWidth);
-		titleWidth = st::contactsNameFont->width(result);
-	}
-	if (outTitleWidth) {
-		*outTitleWidth = titleWidth;
-	}
-	return result;
-}
-
-void StickersInner::fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled) {
-	*outRecent = (set.id == Stickers::CloudRecentSetId);
-	*outInstalled = true;
-	*outOfficial = true;
-	*outUnread = false;
-	*outDisabled = false;
-	if (!*outRecent) {
-		*outInstalled = (set.flags & MTPDstickerSet::Flag::f_installed);
-		*outOfficial = (set.flags & MTPDstickerSet::Flag::f_official);
-		*outDisabled = (set.flags & MTPDstickerSet::Flag::f_archived);
-		if (_section == Section::Featured) {
-			*outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread);
-		}
-	}
-}
-
-Stickers::Order StickersInner::getOrder() const {
-	Stickers::Order result;
-	result.reserve(_rows.size());
-	for (int32 i = 0, l = _rows.size(); i < l; ++i) {
-		if (_rows.at(i)->disabled || _rows.at(i)->recent) {
-			continue;
-		}
-		result.push_back(_rows.at(i)->id);
-	}
-	return result;
-}
-
-Stickers::Order StickersInner::getDisabledSets() const {
-	Stickers::Order result;
-	result.reserve(_rows.size());
-	for (int32 i = 0, l = _rows.size(); i < l; ++i) {
-		if (_rows.at(i)->disabled) {
-			result.push_back(_rows.at(i)->id);
-		}
-	}
-	return result;
-}
-
-void StickersInner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
-	if (_section == Section::Featured) {
-		_visibleTop = visibleTop;
-		_visibleBottom = visibleBottom;
-		readVisibleSets();
-	}
-}
-
-void StickersInner::readVisibleSets() {
-	auto itemsVisibleTop = _visibleTop - _itemsTop;
-	auto itemsVisibleBottom = _visibleBottom - _itemsTop;
-	int rowFrom = floorclamp(itemsVisibleTop, _rowHeight, 0, _rows.size());
-	int rowTo = ceilclamp(itemsVisibleBottom, _rowHeight, 0, _rows.size());
-	for (int i = rowFrom; i < rowTo; ++i) {
-		if (!_rows[i]->unread) {
-			continue;
-		}
-		if (i * _rowHeight < itemsVisibleTop || (i + 1) * _rowHeight > itemsVisibleBottom) {
-			continue;
-		}
-		if (!_rows[i]->sticker || _rows[i]->sticker->thumb->loaded() || _rows[i]->sticker->loaded()) {
-			Stickers::markFeaturedAsRead(_rows[i]->id);
-		}
-	}
-}
-
-void StickersInner::setVisibleScrollbar(int32 width) {
-	_scrollbar = width;
-}
-
-StickersInner::~StickersInner() {
-	clear();
-}
-
-} // namespace internal
-
-StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll)
-, _section(section)
-, _inner(section)
-, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top())
-, _about(st::boxTextFont, lang((section == Section::Archived) ? lng_stickers_packs_archived : lng_stickers_reorder), _defaultOptions, _aboutWidth) {
-	setup();
-}
-
-StickersBox::StickersBox(const Stickers::Order &archivedIds) : ItemListBox(st::boxScroll)
-, _section(Section::ArchivedPart)
-, _inner(archivedIds)
-, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top())
-, _about(st::boxTextFont, lang(lng_stickers_packs_archived), _defaultOptions, _aboutWidth) {
-	setup();
-}
-
-void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result) {
-	_archivedRequestId = 0;
-	if (result.type() != mtpc_messages_archivedStickers) {
-		return;
-	}
-
-	auto &stickers = result.c_messages_archivedStickers();
-	auto &archived = Global::RefArchivedStickerSetsOrder();
-	if (offsetId) {
-		auto index = archived.indexOf(offsetId);
-		if (index >= 0) {
-			archived = archived.mid(0, index + 1);
-		}
-	} else {
-		archived.clear();
-	}
-
-	bool addedSet = false;
-	auto &v = stickers.vsets.c_vector().v;
-	for_const (auto &stickerSet, v) {
-		const MTPDstickerSet *setData = nullptr;
-		switch (stickerSet.type()) {
-		case mtpc_stickerSetCovered: {
-			auto &d = stickerSet.c_stickerSetCovered();
-			if (d.vset.type() == mtpc_stickerSet) {
-				setData = &d.vset.c_stickerSet();
-			}
-		} break;
-		case mtpc_stickerSetMultiCovered: {
-			auto &d = stickerSet.c_stickerSetMultiCovered();
-			if (d.vset.type() == mtpc_stickerSet) {
-				setData = &d.vset.c_stickerSet();
-			}
-		} break;
-		}
-		if (!setData) continue;
-
-		if (auto set = Stickers::feedSet(*setData)) {
-			auto index = archived.indexOf(set->id);
-			if (archived.isEmpty() || index != archived.size() - 1) {
-				if (index < archived.size() - 1) {
-					archived.removeAt(index);
-				}
-				archived.push_back(set->id);
-			}
-			if (_section == Section::Archived) {
-				if (_inner->appendSet(*set)) {
-					addedSet = true;
-					if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
-						App::api()->scheduleStickerSetRequest(set->id, set->access);
-					}
-				}
-			}
-		}
-	}
-	if (_section == Section::Installed && !archived.isEmpty()) {
-		Local::writeArchivedStickers();
-		rebuildList();
-	} else if (_section == Section::Archived) {
-		if (addedSet) {
-			_inner->updateSize();
-			setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight)));
-			_inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0);
-			App::api()->requestStickerSets();
-		} else {
-			_allArchivedLoaded = v.isEmpty() || (offsetId != 0);
-		}
-	}
-	checkLoadMoreArchived();
-}
-
-void StickersBox::setup() {
-	if (_section == Section::Installed) {
-		Local::readArchivedStickers();
-		if (Global::ArchivedStickerSetsOrder().isEmpty()) {
-			MTPmessages_GetArchivedStickers::Flags flags = 0;
-			_archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL));
-		}
-	} else if (_section == Section::Archived) {
-		// Reload the archived list.
-		MTPmessages_GetArchivedStickers::Flags flags = 0;
-		_archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL));
-
-		auto &sets = Global::StickerSets();
-		for_const (auto setId, Global::ArchivedStickerSetsOrder()) {
-			auto it = sets.constFind(setId);
-			if (it != sets.cend()) {
-				if (it->stickers.isEmpty() && (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
-					App::api()->scheduleStickerSetRequest(setId, it->access);
-				}
-			}
-		}
-		App::api()->requestStickerSets();
-	}
-
-	int bottomSkip = st::boxPadding.bottom();
-	if (_section == Section::Installed) {
-		_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
-		_topShadow.create(this, st::contactsAboutShadow);
-
-		_save.create(this, lang(lng_settings_save), st::defaultBoxButton);
-		connect(_save, SIGNAL(clicked()), this, SLOT(onSave()));
-
-		_cancel.create(this, lang(lng_cancel), st::cancelBoxButton);
-		connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
-
-		_bottomShadow.create(this);
-		bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom();
-	} else if (_section == Section::ArchivedPart) {
-		_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
-		_topShadow.create(this, st::contactsAboutShadow);
-
-		_save.create(this, lang(lng_box_ok), st::defaultBoxButton);
-		connect(_save, SIGNAL(clicked()), this, SLOT(onClose()));
-	} else if (_section == Section::Archived) {
-		_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
-		_topShadow.create(this, st::contactsAboutShadow);
-	}
-	ItemListBox::init(_inner, bottomSkip, st::boxTitleHeight + _aboutHeight);
-	setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight)));
-
-	connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated()));
-	App::main()->updateStickers();
-
-	connect(_inner, SIGNAL(checkDraggingScroll(int)), this, SLOT(onCheckDraggingScroll(int)));
-	connect(_inner, SIGNAL(noDraggingScroll()), this, SLOT(onNoDraggingScroll()));
-	connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer()));
-	connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
-	_scrollTimer.setSingleShot(false);
-
-	rebuildList();
-
-	prepare();
-}
-
-void StickersBox::onScroll() {
-	updateVisibleTopBottom();
-	checkLoadMoreArchived();
-}
-
-void StickersBox::updateVisibleTopBottom() {
-	auto visibleTop = scrollArea()->scrollTop();
-	auto visibleBottom = visibleTop + scrollArea()->height();
-	_inner->setVisibleTopBottom(visibleTop, visibleBottom);
-}
-
-void StickersBox::checkLoadMoreArchived() {
-	if (_section != Section::Archived) return;
-
-	int scrollTop = scrollArea()->scrollTop(), scrollTopMax = scrollArea()->scrollTopMax();
-	if (scrollTop + PreloadHeightsCount * scrollArea()->height() >= scrollTopMax) {
-		if (!_archivedRequestId && !_allArchivedLoaded) {
-			uint64 lastId = 0;
-			for (auto setId = Global::ArchivedStickerSetsOrder().cend(), e = Global::ArchivedStickerSetsOrder().cbegin(); setId != e;) {
-				--setId;
-				auto it = Global::StickerSets().constFind(*setId);
-				if (it != Global::StickerSets().cend()) {
-					if (it->flags & MTPDstickerSet::Flag::f_archived) {
-						lastId = it->id;
-						break;
-					}
-				}
-			}
-			MTPmessages_GetArchivedStickers::Flags flags = 0;
-			_archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(lastId), MTP_int(kArchivedLimitPerPage)), rpcDone(&StickersBox::getArchivedDone, lastId));
-		}
-	}
-}
-
-int32 StickersBox::countHeight() const {
-	int bottomSkip = st::boxPadding.bottom();
-	if (_section == Section::Installed) {
-		bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom();
-	}
-	return st::boxTitleHeight + _aboutHeight + _inner->height() + bottomSkip;
-}
-
-void StickersBox::disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req) {
-	_disenableRequests.remove(req);
-	if (_disenableRequests.isEmpty()) {
-		saveOrder();
-	}
-}
-
-bool StickersBox::disenableFail(const RPCError &error, mtpRequestId req) {
-	if (MTP::isDefaultHandledError(error)) return false;
-	_disenableRequests.remove(req);
-	if (_disenableRequests.isEmpty()) {
-		saveOrder();
-	}
-	return true;
-}
-
-void StickersBox::saveOrder() {
-	auto order = _inner->getOrder();
-	if (order.size() > 1) {
-		QVector<MTPlong> mtpOrder;
-		mtpOrder.reserve(order.size());
-		for (int i = 0, l = order.size(); i < l; ++i) {
-			mtpOrder.push_back(MTP_long(order.at(i)));
-		}
-
-		MTPmessages_ReorderStickerSets::Flags flags = 0;
-		_reorderRequest = MTP::send(MTPmessages_ReorderStickerSets(MTP_flags(flags), MTP_vector<MTPlong>(mtpOrder)), rpcDone(&StickersBox::reorderDone), rpcFail(&StickersBox::reorderFail));
-	} else {
-		reorderDone(MTP_boolTrue());
-	}
-}
-
-void StickersBox::reorderDone(const MTPBool &result) {
-	_reorderRequest = 0;
-	onClose();
-}
-
-bool StickersBox::reorderFail(const RPCError &result) {
-	if (MTP::isDefaultHandledError(result)) return false;
-	_reorderRequest = 0;
-	Global::SetLastStickersUpdate(0);
-	App::main()->updateStickers();
-	onClose();
-	return true;
-}
-
-void StickersBox::paintEvent(QPaintEvent *e) {
-	Painter p(this);
-	if (paint(p)) return;
-
-	auto title = ([this]() {
-		if (_section == Section::Installed) {
-			return lang(lng_stickers_packs);
-		} else if (_section == Section::Featured) {
-			return lang(lng_stickers_featured);
-		}
-		return lang(lng_stickers_archived);
-	})();
-	paintTitle(p, title);
-	p.translate(0, st::boxTitleHeight);
-
-	if (_aboutHeight > 0) {
-		p.fillRect(0, 0, width(), _aboutHeight, st::contactsAboutBg);
-		p.setPen(st::stickersReorderFg);
-		_about.draw(p, st::stickersReorderPadding.top(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center);
-	}
-}
-
-void StickersBox::closePressed() {
-	if (!_disenableRequests.isEmpty()) {
-		for_const (auto requestId, _disenableRequests) {
-			MTP::cancel(requestId);
-		}
-		_disenableRequests.clear();
-		Global::SetLastStickersUpdate(0);
-		App::main()->updateStickers();
-	} else if (_reorderRequest) {
-		MTP::cancel(_reorderRequest);
-		_reorderRequest = 0;
-		Global::SetLastStickersUpdate(0);
-		App::main()->updateStickers();
-	}
-}
-
-StickersBox::~StickersBox() {
-	if (_section == Section::Archived) {
-		Local::writeArchivedStickers();
-	}
-}
-
-void StickersBox::resizeEvent(QResizeEvent *e) {
-	ItemListBox::resizeEvent(e);
-	_inner->resize(width(), _inner->height());
-	_inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0);
-	updateVisibleTopBottom();
-	if (_topShadow) {
-		_topShadow->setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth);
-	}
-	if (_save) {
-		_save->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save->height());
-	}
-	if (_cancel) {
-		_cancel->moveToRight(st::boxButtonPadding.right() + _save->width() + st::boxButtonPadding.left(), _save->y());
-		_bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _save->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth);
-	}
-}
-
-void StickersBox::onStickersUpdated() {
-	if (_section == Section::Installed || _section == Section::Featured) {
-		rebuildList();
-	} else {
-		_inner->updateRows();
-	}
-}
-
-void StickersBox::rebuildList() {
-	_inner->rebuild();
-	setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight)));
-	_inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0);
-}
-
-void StickersBox::onCheckDraggingScroll(int localY) {
-	if (localY < scrollArea()->scrollTop()) {
-		_scrollDelta = localY - scrollArea()->scrollTop();
-	} else if (localY >= scrollArea()->scrollTop() + scrollArea()->height()) {
-		_scrollDelta = localY - scrollArea()->scrollTop() - scrollArea()->height() + 1;
-	} else {
-		_scrollDelta = 0;
-	}
-	if (_scrollDelta) {
-		_scrollTimer.start(15);
-	} else {
-		_scrollTimer.stop();
-	}
-}
-
-void StickersBox::onNoDraggingScroll() {
-	_scrollTimer.stop();
-}
-
-void StickersBox::onScrollTimer() {
-	int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
-	scrollArea()->scrollToY(scrollArea()->scrollTop() + d);
-}
-
-void StickersBox::onSave() {
-	if (!_inner->savingStart()) {
-		return;
-	}
-
-	bool writeRecent = false, writeArchived = false;
-	auto &recent = cGetRecentStickers();
-	auto &sets = Global::RefStickerSets();
-
-	auto reorder = _inner->getOrder(), disabled = _inner->getDisabledSets();
-	for (int32 i = 0, l = disabled.size(); i < l; ++i) {
-		auto it = sets.find(disabled.at(i));
-		if (it != sets.cend()) {
-			for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) {
-				if (it->stickers.indexOf(i->first) >= 0) {
-					i = recent.erase(i);
-					writeRecent = true;
-				} else {
-					++i;
-				}
-			}
-			if (!(it->flags & MTPDstickerSet::Flag::f_archived)) {
-				MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
-				if (it->flags & MTPDstickerSet::Flag::f_official) {
-					_disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolTrue()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5));
-					it->flags |= MTPDstickerSet::Flag::f_archived;
-					auto index = Global::RefArchivedStickerSetsOrder().indexOf(it->id);
-					if (index < 0) {
-						Global::RefArchivedStickerSetsOrder().push_front(it->id);
-						writeArchived = true;
-					}
-				} else {
-					_disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5));
-					int removeIndex = Global::StickerSetsOrder().indexOf(it->id);
-					if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex);
-					if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) {
-						sets.erase(it);
-					} else {
-						if (it->flags & MTPDstickerSet::Flag::f_archived) {
-							writeArchived = true;
-						}
-						it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_archived);
-					}
-				}
-			}
-		}
-	}
-
-	// Clear all installed flags, set only for sets from order.
-	for (auto &set : sets) {
-		if (!(set.flags & MTPDstickerSet::Flag::f_archived)) {
-			set.flags &= ~MTPDstickerSet::Flag::f_installed;
-		}
-	}
-
-	auto &order(Global::RefStickerSetsOrder());
-	order.clear();
-	for (int i = 0, l = reorder.size(); i < l; ++i) {
-		auto it = sets.find(reorder.at(i));
-		if (it != sets.cend()) {
-			if ((it->flags & MTPDstickerSet::Flag::f_archived) && !disabled.contains(it->id)) {
-				MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
-				_disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolFalse()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5));
-				it->flags &= ~MTPDstickerSet::Flag::f_archived;
-				writeArchived = true;
-			}
-			order.push_back(reorder.at(i));
-			it->flags |= MTPDstickerSet::Flag::f_installed;
-		}
-	}
-	for (auto it = sets.begin(); it != sets.cend();) {
-		if ((it->flags & MTPDstickerSet_ClientFlag::f_featured)
-			|| (it->flags & MTPDstickerSet::Flag::f_installed)
-			|| (it->flags & MTPDstickerSet::Flag::f_archived)
-			|| (it->flags & MTPDstickerSet_ClientFlag::f_special)) {
-			++it;
-		} else {
-			it = sets.erase(it);
-		}
-	}
-
-	Local::writeInstalledStickers();
-	if (writeRecent) Local::writeUserSettings();
-	if (writeArchived) Local::writeArchivedStickers();
-	emit App::main()->stickersUpdated();
-
-	if (_disenableRequests.isEmpty()) {
-		saveOrder();
-	} else {
-		MTP::sendAnything();
-	}
-}
-
-void StickersBox::showAll() {
-	if (_topShadow) {
-		_topShadow->show();
-	}
-	if (_save) {
-		_save->show();
-	}
-	if (_cancel) {
-		_cancel->show();
-		_bottomShadow->show();
-	}
-	ItemListBox::showAll();
-}
-
-int32 stickerPacksCount(bool includeDisabledOfficial) {
-	int32 result = 0;
-	auto &order = Global::StickerSetsOrder();
-	auto &sets = Global::StickerSets();
-	for (int i = 0, l = order.size(); i < l; ++i) {
-		auto it = sets.constFind(order.at(i));
-		if (it != sets.cend()) {
-			if (!(it->flags & MTPDstickerSet::Flag::f_archived) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeDisabledOfficial)) {
-				++result;
-			}
-		}
-	}
-	return result;
+StickerSetBox::Inner::~Inner() {
 }
diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h
index 1f9cc7561..8252ab4ab 100644
--- a/Telegram/SourceFiles/boxes/stickersetbox.h
+++ b/Telegram/SourceFiles/boxes/stickersetbox.h
@@ -24,15 +24,52 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "core/vector_of_moveable.h"
 
 class ConfirmBox;
+
 namespace Ui {
 class PlainShadow;
 } // namespace Ui
 
-class StickerSetInner : public ScrolledWidget, public RPCSender, private base::Subscriber {
+class StickerSetBox : public ScrollableBox, public RPCSender {
 	Q_OBJECT
 
 public:
-	StickerSetInner(const MTPInputStickerSet &set);
+	StickerSetBox(const MTPInputStickerSet &set);
+
+public slots:
+	void onStickersUpdated();
+	void onAddStickers();
+	void onShareStickers();
+	void onUpdateButtons();
+
+	void onScroll();
+
+private slots:
+	void onInstalled(uint64 id);
+
+signals:
+	void installed(uint64 id);
+
+protected:
+	void paintEvent(QPaintEvent *e) override;
+	void resizeEvent(QResizeEvent *e) override;
+
+	void showAll() override;
+
+private:
+	class Inner;
+	ChildWidget<Inner> _inner;
+	ScrollableBoxShadow _shadow;
+	BoxButton _add, _share, _cancel, _done;
+	QString _title;
+
+};
+
+// This class is hold in header because it requires Qt preprocessing.
+class StickerSetBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
+	Q_OBJECT
+
+public:
+	Inner(QWidget *parent, const MTPInputStickerSet &set);
 
 	bool loaded() const;
 	int32 notInstalled() const;
@@ -43,7 +80,7 @@ public:
 	void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
 	void install();
 
-	~StickerSetInner();
+	~Inner();
 
 protected:
 	void mousePressEvent(QMouseEvent *e) override;
@@ -96,253 +133,3 @@ private:
 	int _previewShown = -1;
 
 };
-
-class StickerSetBox : public ScrollableBox, public RPCSender {
-	Q_OBJECT
-
-public:
-	StickerSetBox(const MTPInputStickerSet &set);
-
-public slots:
-	void onStickersUpdated();
-	void onAddStickers();
-	void onShareStickers();
-	void onUpdateButtons();
-
-	void onScroll();
-
-private slots:
-	void onInstalled(uint64 id);
-
-signals:
-	void installed(uint64 id);
-
-protected:
-	void paintEvent(QPaintEvent *e) override;
-	void resizeEvent(QResizeEvent *e) override;
-
-	void showAll() override;
-
-private:
-	StickerSetInner _inner;
-	ScrollableBoxShadow _shadow;
-	BoxButton _add, _share, _cancel, _done;
-	QString _title;
-
-};
-
-namespace internal {
-class StickersInner;
-} // namespace internal
-
-class StickersBox : public ItemListBox, public RPCSender {
-	Q_OBJECT
-
-public:
-	enum class Section {
-		Installed,
-		Featured,
-		Archived,
-		ArchivedPart,
-	};
-	StickersBox(Section section = Section::Installed);
-	StickersBox(const Stickers::Order &archivedIds);
-
-	~StickersBox();
-
-public slots:
-	void onStickersUpdated();
-
-	void onCheckDraggingScroll(int localY);
-	void onNoDraggingScroll();
-	void onScrollTimer();
-
-	void onSave();
-
-private slots:
-	void onScroll();
-
-protected:
-	void resizeEvent(QResizeEvent *e) override;
-	void paintEvent(QPaintEvent *e) override;
-
-	void closePressed() override;
-	void showAll() override;
-
-private:
-	void setup();
-	int32 countHeight() const;
-	void rebuildList();
-
-	void disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req);
-	bool disenableFail(const RPCError &error, mtpRequestId req);
-	void reorderDone(const MTPBool &result);
-	bool reorderFail(const RPCError &result);
-	void saveOrder();
-
-	void updateVisibleTopBottom();
-	void checkLoadMoreArchived();
-	void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result);
-
-	Section _section;
-
-	ChildWidget<internal::StickersInner> _inner;
-	ChildWidget<BoxButton> _save = { nullptr };
-	ChildWidget<BoxButton> _cancel = { nullptr };
-	OrderedSet<mtpRequestId> _disenableRequests;
-	mtpRequestId _reorderRequest = 0;
-	ChildWidget<Ui::PlainShadow> _topShadow = { nullptr };
-	ChildWidget<ScrollableBoxShadow> _bottomShadow = { nullptr };
-
-	QTimer _scrollTimer;
-	int32 _scrollDelta = 0;
-
-	int _aboutWidth = 0;
-	Text _about;
-	int _aboutHeight = 0;
-
-	mtpRequestId _archivedRequestId = 0;
-	bool _allArchivedLoaded = false;
-
-};
-
-int32 stickerPacksCount(bool includeDisabledOfficial = false);
-
-namespace internal {
-
-class StickersInner : public ScrolledWidget, public RPCSender, private base::Subscriber {
-	Q_OBJECT
-
-public:
-	using Section = StickersBox::Section;
-	StickersInner(Section section);
-	StickersInner(const Stickers::Order &archivedIds);
-
-	void rebuild();
-	void updateSize();
-	void updateRows(); // refresh only pack cover stickers
-	bool appendSet(const Stickers::Set &set);
-	bool savingStart() {
-		if (_saving) return false;
-		_saving = true;
-		return true;
-	}
-
-	Stickers::Order getOrder() const;
-	Stickers::Order getDisabledSets() const;
-
-	void setVisibleScrollbar(int32 width);
-	void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
-
-	~StickersInner();
-
-protected:
-	void paintEvent(QPaintEvent *e) override;
-	void mousePressEvent(QMouseEvent *e) override;
-	void mouseMoveEvent(QMouseEvent *e) override;
-	void mouseReleaseEvent(QMouseEvent *e) override;
-	void leaveEvent(QEvent *e) override;
-
-signals:
-	void checkDraggingScroll(int localY);
-	void noDraggingScroll();
-
-public slots:
-	void onUpdateSelected();
-	void onClearRecent();
-	void onClearBoxDestroyed(QObject *box);
-
-private slots:
-	void onImageLoaded();
-
-private:
-	void setup();
-	void paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const;
-
-	void step_shifting(uint64 ms, bool timer);
-	void paintRow(Painter &p, int32 index);
-	void clear();
-	void setActionSel(int32 actionSel);
-	float64 aboveShadowOpacity() const;
-
-	void readVisibleSets();
-
-	void installSet(uint64 setId);
-	void installDone(const MTPmessages_StickerSetInstallResult &result);
-	bool installFail(uint64 setId, const RPCError &error);
-
-	Section _section;
-	Stickers::Order _archivedIds;
-
-	int32 _rowHeight;
-	struct StickerSetRow {
-		StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id)
-			, sticker(sticker)
-			, count(count)
-			, title(title)
-			, titleWidth(titleWidth)
-			, installed(installed)
-			, official(official)
-			, unread(unread)
-			, disabled(disabled)
-			, recent(recent)
-			, pixw(pixw)
-			, pixh(pixh)
-			, yadd(0, 0) {
-		}
-		uint64 id;
-		DocumentData *sticker;
-		int32 count;
-		QString title;
-		int titleWidth;
-		bool installed, official, unread, disabled, recent;
-		int32 pixw, pixh;
-		anim::ivalue yadd;
-	};
-	using StickerSetRows = QList<StickerSetRow*>;
-
-	void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth);
-	void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
-	int fillSetCount(const Stickers::Set &set) const;
-	QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const;
-	void fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled);
-
-	int countMaxNameWidth() const;
-
-	StickerSetRows _rows;
-	QList<uint64> _animStartTimes;
-	uint64 _aboveShadowFadeStart = 0;
-	anim::fvalue _aboveShadowFadeOpacity = { 0., 0. };
-	Animation _a_shifting;
-
-	int _visibleTop = 0;
-	int _visibleBottom = 0;
-	int _itemsTop = 0;
-
-	bool _saving = false;
-
-	int _actionSel = -1;
-	int _actionDown = -1;
-
-	int _clearWidth, _removeWidth, _returnWidth, _restoreWidth;
-
-	ConfirmBox *_clearBox = nullptr;
-
-	int _buttonHeight = 0;
-	bool _hasFeaturedButton = false;
-	bool _hasArchivedButton = false;
-
-	QPoint _mouse;
-	int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button
-	int _pressed = -2;
-	QPoint _dragStart;
-	int _started = -1;
-	int _dragging = -1;
-	int _above = -1;
-
-	Ui::RectShadow _aboveShadow;
-
-	int32 _scrollbar = 0;
-};
-
-} // namespace internal
diff --git a/Telegram/SourceFiles/codegen/style/parsed_file.cpp b/Telegram/SourceFiles/codegen/style/parsed_file.cpp
index f45ecebad..1f5ab35f9 100644
--- a/Telegram/SourceFiles/codegen/style/parsed_file.cpp
+++ b/Telegram/SourceFiles/codegen/style/parsed_file.cpp
@@ -243,7 +243,7 @@ structure::Variable ParsedFile::readVariable(const QString &name) {
 	structure::Variable result = { composeFullName(name) };
 	if (auto value = readValue()) {
 		result.value = value;
-		if (value.type().tag != structure::TypeTag::Struct) {
+		if (value.type().tag != structure::TypeTag::Struct || !value.copyOf().empty()) {
 			assertNextToken(BasicType::Semicolon);
 		}
 	}
diff --git a/Telegram/SourceFiles/core/lambda_wrap.h b/Telegram/SourceFiles/core/lambda_wrap.h
index fc0ce2224..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) {
 	}
@@ -359,6 +360,10 @@ public:
 		}
 	}
 
+	lambda_wrap clone() const {
+		return *this;
+	}
+
 	template <typename Lambda, typename = IsOther<Lambda>>
 	lambda_wrap(const Lambda &other) : Parent(&internal::lambda_wrap_helper_copy<Lambda, Return, Args...>::instance, typename Parent::Private()) {
 		internal::lambda_wrap_helper_copy<Lambda, Return, Args...>::construct_copy_lambda_method(this->storage_, &other);
diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style
index d09ad184b..ae7d0e116 100644
--- a/Telegram/SourceFiles/history/history.style
+++ b/Telegram/SourceFiles/history/history.style
@@ -35,6 +35,9 @@ historyToDownPaddingTop: 10px;
 historyToDownBadgeFont: semiboldFont;
 historyToDownBadgeSize: 22px;
 
+historyEmptyDog: icon {{ "history_empty_dog", #ffffff }};
+historyEmptySize: 128px;
+
 membersInnerScroll: flatScroll(solidScroll) {
 	deltat: 3px;
 	deltab: 3px;
diff --git a/Telegram/SourceFiles/history/history_service_layout.cpp b/Telegram/SourceFiles/history/history_service_layout.cpp
index fa0db9f69..b69742070 100644
--- a/Telegram/SourceFiles/history/history_service_layout.cpp
+++ b/Telegram/SourceFiles/history/history_service_layout.cpp
@@ -334,6 +334,16 @@ QVector<int> ServiceMessagePainter::countLineWidths(const Text &text, const QRec
 	return lineWidths;
 }
 
+void paintEmpty(Painter &p, int width, int height) {
+	auto position = QPoint((width - st::historyEmptySize) / 2, ((height - st::historyEmptySize) * 4) / 9);
+	p.setPen(Qt::NoPen);
+	p.setBrush(App::msgServiceBg());
+	p.setRenderHint(QPainter::HighQualityAntialiasing);
+	p.drawEllipse(rtlrect(position.x(), position.y(), st::historyEmptySize, st::historyEmptySize, width));
+	p.setRenderHint(QPainter::HighQualityAntialiasing, false);
+	st::historyEmptyDog.paint(p, position.x() + (st::historyEmptySize - st::historyEmptyDog.width()) / 2, position.y() + (st::historyEmptySize - st::historyEmptyDog.height()) / 2, width);
+}
+
 void serviceColorsUpdated() {
 	if (serviceMessageStyle) {
 		for (auto &corner : serviceMessageStyle->corners) {
diff --git a/Telegram/SourceFiles/history/history_service_layout.h b/Telegram/SourceFiles/history/history_service_layout.h
index 4c9beee4d..faae5d450 100644
--- a/Telegram/SourceFiles/history/history_service_layout.h
+++ b/Telegram/SourceFiles/history/history_service_layout.h
@@ -48,6 +48,8 @@ private:
 
 };
 
+void paintEmpty(Painter &p, int width, int height);
+
 void serviceColorsUpdated();
 
 } // namespace HistoryLayout
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index a8917224e..e9a7beee6 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -376,8 +376,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 			textstyleRestore();
 		}
 	} else if (noHistoryDisplayed) {
-		QPoint dogPos((width() - st::msgDogImg.pxWidth()) / 2, ((height() - st::msgDogImg.pxHeight()) * 4) / 9);
-		p.drawPixmap(dogPos, Window::chatBackground()->dog());
+		HistoryLayout::paintEmpty(p, width(), height());
 	}
 	if (!noHistoryDisplayed) {
 		adjustCurrent(r.top());
@@ -8776,8 +8775,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
 		}
 		if (_scroll.isHidden()) {
 			p.setClipRect(_scroll.geometry());
-			QPoint dogPos((width() - st::msgDogImg.pxWidth()) / 2, ((height() - _field.height() - 2 * st::sendPadding - st::msgDogImg.pxHeight()) * 4) / 9);
-			p.drawPixmap(dogPos, Window::chatBackground()->dog());
+			HistoryLayout::paintEmpty(p, width(), height() - _field.height() - 2 * st::sendPadding);
 		}
 	} else {
 		style::font font(st::msgServiceFont);
diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp
index 4aa94953a..bcdcd1e2e 100644
--- a/Telegram/SourceFiles/layout.cpp
+++ b/Telegram/SourceFiles/layout.cpp
@@ -230,11 +230,6 @@ style::color documentSelectedColor(int32 colorIndex) {
 	return colors[colorIndex & 3];
 }
 
-style::sprite documentCorner(int32 colorIndex) {
-	static style::sprite corners[] = { st::msgFileBlue, st::msgFileGreen, st::msgFileRed, st::msgFileYellow };
-	return corners[colorIndex & 3];
-}
-
 RoundCorners documentCorners(int32 colorIndex) {
 	return RoundCorners(DocBlueCorners + (colorIndex & 3));
 }
diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h
index 385050401..bb5ff4307 100644
--- a/Telegram/SourceFiles/layout.h
+++ b/Telegram/SourceFiles/layout.h
@@ -86,7 +86,6 @@ style::color documentColor(int32 colorIndex);
 style::color documentDarkColor(int32 colorIndex);
 style::color documentOverColor(int32 colorIndex);
 style::color documentSelectedColor(int32 colorIndex);
-style::sprite documentCorner(int32 colorIndex);
 RoundCorners documentCorners(int32 colorIndex);
 bool documentIsValidMediaFile(const QString &filepath);
 
diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp
index e48dcbaeb..7ce562e00 100644
--- a/Telegram/SourceFiles/mainwindow.cpp
+++ b/Telegram/SourceFiles/mainwindow.cpp
@@ -715,7 +715,7 @@ HitTestType MainWindow::hitTest(const QPoint &p) const {
 }
 
 QRect MainWindow::iconRect() const {
-	return QRect(st::titleIconPos + title->geometry().topLeft(), st::titleIconImg.pxSize());
+	return title->iconRect();
 }
 
 bool MainWindow::eventFilter(QObject *obj, QEvent *e) {
diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style
index e2888cc70..7f43918ef 100644
--- a/Telegram/SourceFiles/media/view/mediaview.style
+++ b/Telegram/SourceFiles/media/view/mediaview.style
@@ -85,19 +85,28 @@ mediaviewClose: icon {{ "mediaview_close", #ffffff }};
 mediaviewSave: icon {{ "mediaview_download", #ffffff }};
 mediaviewMore: icon {{ "mediaview_more", #ffffff }};
 
+mediaviewFileRedCornerFg: #d55959;
+mediaviewFileYellowCornerFg: #e8a659;
+mediaviewFileGreenCornerFg: #49a957;
+mediaviewFileBlueCornerFg: #599dcf;
+
 mediaviewFileRed: icon {
 	{ size(25px, 25px), #ffffff },
-	{ "mediaview_file_corner", #d55959 },
+	{ "mediaview_file_corner", mediaviewFileRedCornerFg },
 };
 mediaviewFileYellow: icon {
 	{ size(25px, 25px), #ffffff },
-	{ "mediaview_file_corner", #e8a659 },
+	{ "mediaview_file_corner", mediaviewFileYellowCornerFg },
 };
 mediaviewFileGreen: icon {
 	{ size(25px, 25px), #ffffff },
-	{ "mediaview_file_corner", #49a957 },
+	{ "mediaview_file_corner", mediaviewFileGreenCornerFg },
 };
 mediaviewFileBlue: icon {
 	{ size(25px, 25px), #ffffff },
-	{ "mediaview_file_corner", #599dcf },
+	{ "mediaview_file_corner", mediaviewFileBlueCornerFg },
 };
+
+mediaviewTransparentBg: #ffffff;
+mediaviewTransparentFg: #cccccc;
+mediaviewTransparentSize: 4px;
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index 82d603b4c..eb3c30909 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -103,7 +103,7 @@ MediaView::MediaView() : TWidget(App::wnd())
 		}
 	});
 
-	_transparentBrush = QBrush(App::sprite().copy(st::mvTransparentBrush.rect()));
+	generateTransparentBrush();
 
 	setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint);
 	moveToScreen();
@@ -2628,6 +2628,19 @@ void MediaView::loadBack() {
 	}
 }
 
+void MediaView::generateTransparentBrush() {
+	auto size = st::mediaviewTransparentSize * cIntRetinaFactor();
+	auto transparent = QImage(2 * size, 2 * size, QImage::Format_ARGB32_Premultiplied);
+	transparent.fill(st::mediaviewTransparentBg->c);
+	{
+		Painter p(&transparent);
+		p.fillRect(rtlrect(0, size, size, size, 2 * size), st::mediaviewTransparentFg);
+		p.fillRect(rtlrect(size, 0, size, size, 2 * size), st::mediaviewTransparentFg);
+	}
+	transparent.setDevicePixelRatio(cRetinaFactor());
+	_transparentBrush = QBrush(transparent);
+}
+
 MediaView::LastChatPhoto MediaView::computeLastOverviewChatPhoto() {
 	LastChatPhoto emptyResult = { nullptr, nullptr };
 	auto lastPhotoInOverview = [&emptyResult](auto history, auto list) -> LastChatPhoto {
diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h
index 320b86fb5..b86383165 100644
--- a/Telegram/SourceFiles/mediaview.h
+++ b/Telegram/SourceFiles/mediaview.h
@@ -136,6 +136,8 @@ private:
 	void findCurrent();
 	void loadBack();
 
+	void generateTransparentBrush();
+
 	void updateCursor();
 	void setZoomLevel(int newZoom);
 
diff --git a/Telegram/SourceFiles/overview/overview.style b/Telegram/SourceFiles/overview/overview.style
index c010d0191..7250e6d15 100644
--- a/Telegram/SourceFiles/overview/overview.style
+++ b/Telegram/SourceFiles/overview/overview.style
@@ -20,6 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 */
 using "basic.style";
 using "history/history.style";
+using "media/view/mediaview.style";
 
 OverviewFileLayout {
 	maxWidth: pixels;
diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp
index b58b9a468..ff5f18a72 100644
--- a/Telegram/SourceFiles/overviewwidget.cpp
+++ b/Telegram/SourceFiles/overviewwidget.cpp
@@ -34,6 +34,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "application.h"
 #include "overview/overview_layout.h"
 #include "history/history_media_types.h"
+#include "history/history_service_layout.h"
 #include "media/media_audio.h"
 
 // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
@@ -778,8 +779,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
 	Overview::Layout::PaintContext context(ms, _selMode);
 
 	if (_history->overview[_type].isEmpty() && (!_migrated || !_history->overviewLoaded(_type) || _migrated->overview[_type].isEmpty())) {
-		QPoint dogPos((_width - st::msgDogImg.pxWidth()) / 2, ((height() - st::msgDogImg.pxHeight()) * 4) / 9);
-		p.drawPixmap(dogPos, Window::chatBackground()->dog());
+		HistoryLayout::paintEmpty(p, _width, height());
 		return;
 	} else if (_inSearch && _searchResults.isEmpty() && _searchFull && (!_migrated || _searchFullMigrated) && !_searchTimer.isActive()) {
 		p.setFont(st::noContactsFont->f);
diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp
index d9ac7434d..96dd3da7a 100644
--- a/Telegram/SourceFiles/profile/profile_cover.cpp
+++ b/Telegram/SourceFiles/profile/profile_cover.cpp
@@ -529,14 +529,14 @@ void CoverWidget::onAddMember() {
 		if (_peerChat->count >= Global::ChatSizeMax() && _peerChat->amCreator()) {
 			Ui::showLayer(new ConvertToSupergroupBox(_peerChat));
 		} else {
-			Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent));
+			Ui::showLayer(new ContactsBox(_peerChat, MembersFilter::Recent));
 		}
 	} else if (_peerChannel && _peerChannel->mgInfo) {
 		MembersAlreadyIn already;
 		for_const (auto user, _peerChannel->mgInfo->lastParticipants) {
 			already.insert(user);
 		}
-		Ui::showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already));
+		Ui::showLayer(new ContactsBox(_peerChannel, MembersFilter::Recent, already));
 	}
 }
 
diff --git a/Telegram/SourceFiles/profile/profile_members_widget.cpp b/Telegram/SourceFiles/profile/profile_members_widget.cpp
index 022f96772..2f9b1a6c8 100644
--- a/Telegram/SourceFiles/profile/profile_members_widget.cpp
+++ b/Telegram/SourceFiles/profile/profile_members_widget.cpp
@@ -700,13 +700,13 @@ int ChannelMembersWidget::resizeGetHeight(int newWidth) {
 
 void ChannelMembersWidget::onAdmins() {
 	if (auto channel = peer()->asChannel()) {
-		Ui::showLayer(new MembersBox(channel, MembersFilterAdmins));
+		Ui::showLayer(new MembersBox(channel, MembersFilter::Admins));
 	}
 }
 
 void ChannelMembersWidget::onMembers() {
 	if (auto channel = peer()->asChannel()) {
-		Ui::showLayer(new MembersBox(channel, MembersFilterRecent));
+		Ui::showLayer(new MembersBox(channel, MembersFilter::Recent));
 	}
 }
 
diff --git a/Telegram/SourceFiles/profile/profile_settings_widget.cpp b/Telegram/SourceFiles/profile/profile_settings_widget.cpp
index d94ec9868..4c6d254c8 100644
--- a/Telegram/SourceFiles/profile/profile_settings_widget.cpp
+++ b/Telegram/SourceFiles/profile/profile_settings_widget.cpp
@@ -164,9 +164,9 @@ void SettingsWidget::onNotificationsChange() {
 
 void SettingsWidget::onManageAdmins() {
 	if (auto chat = peer()->asChat()) {
-		Ui::showLayer(new ContactsBox(chat, MembersFilterAdmins));
+		Ui::showLayer(new ContactsBox(chat, MembersFilter::Admins));
 	} else if (auto channel = peer()->asChannel()) {
-		Ui::showLayer(new MembersBox(channel, MembersFilterAdmins));
+		Ui::showLayer(new MembersBox(channel, MembersFilter::Admins));
 	}
 }
 
diff --git a/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp b/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp
index 53ae1053a..329da42a4 100644
--- a/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp
@@ -29,7 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "mainwidget.h"
 #include "mainwindow.h"
 #include "boxes/emojibox.h"
-#include "boxes/stickersetbox.h"
+#include "boxes/stickers_box.h"
 #include "boxes/downloadpathbox.h"
 #include "boxes/connectionbox.h"
 #include "boxes/confirmbox.h"
diff --git a/Telegram/SourceFiles/stickers/emoji_pan.cpp b/Telegram/SourceFiles/stickers/emoji_pan.cpp
index a7f0e5563..74dca4181 100644
--- a/Telegram/SourceFiles/stickers/emoji_pan.cpp
+++ b/Telegram/SourceFiles/stickers/emoji_pan.cpp
@@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "styles/style_stickers.h"
 #include "boxes/confirmbox.h"
 #include "boxes/stickersetbox.h"
+#include "boxes/stickers_box.h"
 #include "inline_bots/inline_bot_result.h"
 #include "inline_bots/inline_bot_layout_item.h"
 #include "dialogs/dialogs_layout.h"
diff --git a/Telegram/SourceFiles/stickers/stickers.cpp b/Telegram/SourceFiles/stickers/stickers.cpp
index 266ede52d..0a47e900d 100644
--- a/Telegram/SourceFiles/stickers/stickers.cpp
+++ b/Telegram/SourceFiles/stickers/stickers.cpp
@@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "stdafx.h"
 #include "stickers.h"
 
-#include "boxes/stickersetbox.h"
+#include "boxes/stickers_box.h"
 #include "boxes/confirmbox.h"
 #include "lang.h"
 #include "apiwrap.h"
diff --git a/Telegram/SourceFiles/stickers/stickers.style b/Telegram/SourceFiles/stickers/stickers.style
index c215aadcf..c1a9168a1 100644
--- a/Telegram/SourceFiles/stickers/stickers.style
+++ b/Telegram/SourceFiles/stickers/stickers.style
@@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 */
 using "basic.style";
 
+using "boxes/boxes.style";
+
 featuredStickersHeader: 45px;
 featuredStickersSkip: 15px;
 
diff --git a/Telegram/SourceFiles/title.cpp b/Telegram/SourceFiles/title.cpp
index 0a34528a2..9722ac9d4 100644
--- a/Telegram/SourceFiles/title.cpp
+++ b/Telegram/SourceFiles/title.cpp
@@ -31,6 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "media/player/media_player_title_button.h"
 #include "media/player/media_player_panel.h"
 #include "media/player/media_player_instance.h"
+#include "styles/style_window.h"
 
 class TitleWidget::Hider : public TWidget {
 public:
@@ -126,9 +127,9 @@ void TitleWidget::paintEvent(QPaintEvent *e) {
 		auto chooseText = lang(inlineSwitchChoose ? lng_inline_switch_choose : lng_forward_choose);
 		p.drawText(st::titleMenuOffset - st::titleTextButton.width / 2, st::titleTextButton.textTop + st::titleTextButton.font->ascent, chooseText);
 	}
-	p.drawSprite(st::titleIconPos, st::titleIconImg);
+	st::titleIcon.paint(p, st::titleIconPosition, width());
 	if (Adaptive::OneColumn() && !_counter.isNull() && App::main()) {
-		p.drawPixmap(st::titleIconPos.x() + st::titleIconImg.pxWidth() - (_counter.width() / cIntRetinaFactor()), st::titleIconPos.y() + st::titleIconImg.pxHeight() - (_counter.height() / cIntRetinaFactor()), _counter);
+		p.drawPixmap(st::titleCounterPosition, _counter);
 	}
 }
 
@@ -335,11 +336,11 @@ void TitleWidget::updateCounter() {
 		}
 		_counter = App::pixmapFromImageInPlace(App::wnd()->iconWithCounter(size, counter, bg, false));
 		_counter.setDevicePixelRatio(cRetinaFactor());
-		update(QRect(st::titleIconPos, st::titleIconImg.pxSize()));
+		update(QRect(st::titleCounterPosition, _counter.size() / cIntRetinaFactor()));
 	} else {
 		if (!_counter.isNull()) {
+			update(QRect(st::titleCounterPosition, _counter.size() / cIntRetinaFactor()));
 			_counter = QPixmap();
-			update(QRect(st::titleIconPos, st::titleIconImg.pxSize()));
 		}
 	}
 }
@@ -395,7 +396,7 @@ HitTestType TitleWidget::hitTest(const QPoint &p) {
 	int x(p.x()), y(p.y()), w(width()), h(height());
 	if (!Adaptive::OneColumn() && _hider && x >= App::main()->dlgsWidth()) return HitTestType::None;
 
-	if (x >= st::titleIconPos.x() && y >= st::titleIconPos.y() && x < st::titleIconPos.x() + st::titleIconImg.pxWidth() && y < st::titleIconPos.y() + st::titleIconImg.pxHeight()) {
+	if (x >= st::titleIconPosition.x() && y >= st::titleIconPosition.y() && x < st::titleIconPosition.x() + st::titleIcon.width() && y < st::titleIconPosition.y() + st::titleIcon.height()) {
 		return HitTestType::Icon;
 	} else if (false
 		|| (_player && _player->geometry().contains(p))
@@ -420,3 +421,7 @@ HitTestType TitleWidget::hitTest(const QPoint &p) {
 	}
 	return HitTestType::None;
 }
+
+QRect TitleWidget::iconRect() const {
+	return myrtlrect(QRect(st::titleIconPosition, st::titleIcon.size()));
+}
diff --git a/Telegram/SourceFiles/title.h b/Telegram/SourceFiles/title.h
index 6cfd8edff..ea278669e 100644
--- a/Telegram/SourceFiles/title.h
+++ b/Telegram/SourceFiles/title.h
@@ -42,6 +42,7 @@ public:
 	void maximizedChanged(bool maximized, bool force = false);
 
 	HitTestType hitTest(const QPoint &p);
+	QRect iconRect() const;
 
 	void setHideLevel(float64 level);
 
diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h
index 1018053f2..8f2b7f427 100644
--- a/Telegram/SourceFiles/ui/animation.h
+++ b/Telegram/SourceFiles/ui/animation.h
@@ -102,6 +102,22 @@ namespace anim {
     float64 easeInQuint(const float64 &delta, const float64 &dt);
     float64 easeOutQuint(const float64 &delta, const float64 &dt);
 
+	template <int BumpRatioNumerator, int BumpRatioDenominator>
+	float64 bumpy(const float64 &delta, const float64 &dt) {
+		struct Bumpy {
+			Bumpy()
+				: bump(BumpRatioNumerator / float64(BumpRatioDenominator))
+				, dt0(bump - sqrt(bump * (bump - 1.)))
+				, k(1 / (2 * dt0 - 1)) {
+			}
+			float64 bump;
+			float64 dt0;
+			float64 k;
+		};
+		static Bumpy data;
+		return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0));
+	}
+
 	class fvalue { // float animated value
 	public:
 		using ValueType = float64;
@@ -468,6 +484,15 @@ public:
 	using ValueType = typename AnimType::ValueType;
 	using Callback = base::lambda_unique<void()>;
 
+	void step(uint64 ms) {
+		if (_data) {
+			_data->a_animation.step(ms);
+			if (_data && !_data->a_animation.animating()) {
+				_data.reset();
+			}
+		}
+	}
+
 	bool animating() const {
 		if (_data) {
 			if (_data->a_animation.animating()) {
@@ -478,11 +503,8 @@ public:
 		return false;
 	}
 	bool animating(uint64 ms) {
-		if (animating()) {
-			_data->a_animation.step(ms);
-			return animating();
-		}
-		return false;
+		step(ms);
+		return animating();
 	}
 
 	ValueType current() const {
@@ -499,7 +521,7 @@ public:
 	template <typename Lambda>
 	void start(Lambda &&updateCallback, const ValueType &from, const ValueType &to, float64 duration, anim::transition transition = anim::linear) {
 		if (!_data) {
-			_data = std_::make_unique<Data>(from, std_::move(updateCallback));
+			_data = std_::make_unique<Data>(from, std_::forward<Lambda>(updateCallback));
 		}
 		_data->value.start(to);
 		_data->duration = duration;
@@ -517,11 +539,17 @@ public:
 
 private:
 	struct Data {
-		Data(const ValueType &from, Callback &&updateCallback)
+		template <typename Lambda, typename = std_::enable_if_t<std_::is_rvalue_reference<Lambda&&>::value>>
+		Data(const ValueType &from, Lambda &&updateCallback)
 			: value(from, from)
 			, a_animation(animation(this, &Data::step))
 			, updateCallback(std_::move(updateCallback)) {
 		}
+		Data(const ValueType &from, const base::lambda_wrap<void()> &updateCallback)
+			: value(from, from)
+			, a_animation(animation(this, &Data::step))
+			, updateCallback(base::lambda_wrap<void()>(updateCallback)) {
+		}
 		void step(float64 ms, bool timer) {
 			auto dt = (ms >= duration) ? 1. : (ms / duration);
 			if (dt >= 1) {
diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp
index 40a2c0f16..c4a390e9c 100644
--- a/Telegram/SourceFiles/ui/countryinput.cpp
+++ b/Telegram/SourceFiles/ui/countryinput.cpp
@@ -24,8 +24,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "lang.h"
 #include "application.h"
 #include "ui/scrollarea.h"
+#include "ui/widgets/multi_select.h"
 #include "boxes/contactsbox.h"
 #include "countries.h"
+#include "styles/style_boxes.h"
 
 namespace {
 
@@ -63,7 +65,8 @@ namespace {
 		countriesFiltered.reserve(countriesCount);
 		countriesNames.resize(countriesCount);
 	}
-}
+
+} // namespace
 
 const CountriesByCode &countriesByCode() {
 	initCountries();
@@ -192,7 +195,73 @@ void CountryInput::setText(const QString &newText) {
 	_text = _st.font->elided(newText, width() - _st.textMrg.left() - _st.textMrg.right());
 }
 
-CountrySelectInner::CountrySelectInner() : TWidget()
+CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxWidth)
+, _inner(this)
+, _select(this, st::contactsMultiSelect, lang(lng_country_ph))
+, _topShadow(this) {
+	_select->resizeToWidth(st::boxWidth);
+
+	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&)));
+
+	prepare();
+}
+
+void CountrySelectBox::onSubmit() {
+	_inner->chooseCountry();
+}
+
+void CountrySelectBox::keyPressEvent(QKeyEvent *e) {
+	if (e->key() == Qt::Key_Down) {
+		_inner->selectSkip(1);
+	} else if (e->key() == Qt::Key_Up) {
+		_inner->selectSkip(-1);
+	} else if (e->key() == Qt::Key_PageDown) {
+		_inner->selectSkipPage(scrollArea()->height(), 1);
+	} else if (e->key() == Qt::Key_PageUp) {
+		_inner->selectSkipPage(scrollArea()->height(), -1);
+	} else {
+		ItemListBox::keyPressEvent(e);
+	}
+}
+
+void CountrySelectBox::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+	if (paint(p)) return;
+
+	paintTitle(p, lang(lng_country_select));
+}
+
+void CountrySelectBox::resizeEvent(QResizeEvent *e) {
+	ItemListBox::resizeEvent(e);
+
+	_select->resizeToWidth(width());
+	_select->moveToLeft(0, st::boxTitleHeight);
+
+	_inner->resizeToWidth(width());
+	_topShadow.setGeometry(0, st::boxTitleHeight + _select->height(), width(), st::lineWidth);
+}
+
+void CountrySelectBox::showAll() {
+	_select->show();
+	_topShadow.show();
+	ItemListBox::showAll();
+}
+
+void CountrySelectBox::onFilterUpdate(const QString &query) {
+	scrollArea()->scrollToY(0);
+	_inner->updateFilter(query);
+}
+
+void CountrySelectBox::doSetInnerFocus() {
+	_select->setInnerFocus();
+}
+
+CountrySelectBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent)
 , _rowHeight(st::countryRowHeight)
 , _sel(0)
 , _mouseSel(false) {
@@ -239,7 +308,7 @@ CountrySelectInner::CountrySelectInner() : TWidget()
 	updateFilter();
 }
 
-void CountrySelectInner::paintEvent(QPaintEvent *e) {
+void CountrySelectBox::Inner::paintEvent(QPaintEvent *e) {
 	Painter p(this);
 	QRect r(e->rect());
 	p.setClipRect(r);
@@ -283,11 +352,11 @@ void CountrySelectInner::paintEvent(QPaintEvent *e) {
 	}
 }
 
-void CountrySelectInner::enterEvent(QEvent *e) {
+void CountrySelectBox::Inner::enterEvent(QEvent *e) {
 	setMouseTracking(true);
 }
 
-void CountrySelectInner::leaveEvent(QEvent *e) {
+void CountrySelectBox::Inner::leaveEvent(QEvent *e) {
 	_mouseSel = false;
 	setMouseTracking(false);
 	if (_sel >= 0) {
@@ -296,13 +365,13 @@ void CountrySelectInner::leaveEvent(QEvent *e) {
 	}
 }
 
-void CountrySelectInner::mouseMoveEvent(QMouseEvent *e) {
+void CountrySelectBox::Inner::mouseMoveEvent(QMouseEvent *e) {
 	_mouseSel = true;
 	_lastMousePos = e->globalPos();
 	updateSel();
 }
 
-void CountrySelectInner::mousePressEvent(QMouseEvent *e) {
+void CountrySelectBox::Inner::mousePressEvent(QMouseEvent *e) {
 	_mouseSel = true;
 	_lastMousePos = e->globalPos();
 	updateSel();
@@ -311,7 +380,7 @@ void CountrySelectInner::mousePressEvent(QMouseEvent *e) {
 	}
 }
 
-void CountrySelectInner::updateFilter(QString filter) {
+void CountrySelectBox::Inner::updateFilter(QString filter) {
 	filter = textSearchKey(filter);
 
 	QStringList f;
@@ -366,7 +435,7 @@ void CountrySelectInner::updateFilter(QString filter) {
 	}
 }
 
-void CountrySelectInner::selectSkip(int32 dir) {
+void CountrySelectBox::Inner::selectSkip(int32 dir) {
 	_mouseSel = false;
 
 	int cur = (_sel >= 0) ? _sel : -1;
@@ -384,13 +453,13 @@ void CountrySelectInner::selectSkip(int32 dir) {
 	update();
 }
 
-void CountrySelectInner::selectSkipPage(int32 h, int32 dir) {
+void CountrySelectBox::Inner::selectSkipPage(int32 h, int32 dir) {
 	int32 points = h / _rowHeight;
 	if (!points) return;
 	selectSkip(points * dir);
 }
 
-void CountrySelectInner::chooseCountry() {
+void CountrySelectBox::Inner::chooseCountry() {
 	QString result;
 	if (_filter.isEmpty()) {
 		if (_sel >= 0 && _sel < countriesAll.size()) {
@@ -404,11 +473,11 @@ void CountrySelectInner::chooseCountry() {
 	emit countryChosen(result);
 }
 
-void CountrySelectInner::refresh() {
+void CountrySelectBox::Inner::refresh() {
 	resize(width(), countriesNow->length() ? (countriesNow->length() * _rowHeight + st::countriesSkip) : st::noContactsHeight);
 }
 
-void CountrySelectInner::updateSel() {
+void CountrySelectBox::Inner::updateSel() {
 	if (!_mouseSel) return;
 
 	QPoint p(mapFromGlobal(_lastMousePos));
@@ -422,89 +491,8 @@ void CountrySelectInner::updateSel() {
 	}
 }
 
-void CountrySelectInner::updateSelectedRow() {
+void CountrySelectBox::Inner::updateSelectedRow() {
 	if (_sel >= 0) {
 		update(0, st::countriesSkip + _sel * _rowHeight, width(), _rowHeight);
 	}
 }
-
-CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxWidth)
-, _inner()
-, _filter(this, st::boxSearchField, lang(lng_country_ph))
-, _filterCancel(this, st::boxSearchCancel)
-, _topShadow(this) {
-	ItemListBox::init(&_inner, st::boxScrollSkip, st::boxTitleHeight + _filter.height());
-
-	connect(&_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate()));
-	connect(&_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
-	connect(&_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel()));
-	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();
-}
-
-void CountrySelectBox::onSubmit() {
-	_inner.chooseCountry();
-}
-
-void CountrySelectBox::keyPressEvent(QKeyEvent *e) {
-	if (e->key() == Qt::Key_Down) {
-		_inner.selectSkip(1);
-	} else if (e->key() == Qt::Key_Up) {
-		_inner.selectSkip(-1);
-	} else if (e->key() == Qt::Key_PageDown) {
-		_inner.selectSkipPage(scrollArea()->height(), 1);
-	} else if (e->key() == Qt::Key_PageUp) {
-		_inner.selectSkipPage(scrollArea()->height(), -1);
-	} else {
-		ItemListBox::keyPressEvent(e);
-	}
-}
-
-void CountrySelectBox::paintEvent(QPaintEvent *e) {
-	Painter p(this);
-	if (paint(p)) return;
-
-	paintTitle(p, lang(lng_country_select));
-}
-
-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);
-}
-
-void CountrySelectBox::showAll() {
-	_filter.show();
-	if (_filter.getLastText().isEmpty()) {
-		_filterCancel.hide();
-	} else {
-		_filterCancel.show();
-	}
-	_topShadow.show();
-	ItemListBox::showAll();
-}
-
-void CountrySelectBox::onFilterCancel() {
-	_filter.setText(QString());
-}
-
-void CountrySelectBox::onFilterUpdate() {
-	scrollArea()->scrollToY(0);
-	if (_filter.getLastText().isEmpty()) {
-		_filterCancel.hide();
-	} else {
-		_filterCancel.show();
-	}
-	_inner.updateFilter(_filter.getLastText());
-}
-
-void CountrySelectBox::doSetInnerFocus() {
-	_filter.setFocus();
-}
diff --git a/Telegram/SourceFiles/ui/countryinput.h b/Telegram/SourceFiles/ui/countryinput.h
index 8292f0187..fe3b217a9 100644
--- a/Telegram/SourceFiles/ui/countryinput.h
+++ b/Telegram/SourceFiles/ui/countryinput.h
@@ -30,6 +30,10 @@ QString findValidCode(QString fullCode);
 
 class CountrySelect;
 
+namespace Ui {
+class MultiSelect;
+} // namespace Ui
+
 class CountryInput : public QWidget {
 	Q_OBJECT
 
@@ -63,11 +67,47 @@ private:
 
 };
 
-class CountrySelectInner : public TWidget {
+namespace internal {
+class CountrySelectInner;
+} // namespace internal
+
+class CountrySelectBox : public ItemListBox {
 	Q_OBJECT
 
 public:
-	CountrySelectInner();
+	CountrySelectBox();
+
+signals:
+	void countryChosen(const QString &iso);
+
+public slots:
+	void onSubmit();
+
+protected:
+	void keyPressEvent(QKeyEvent *e) override;
+	void paintEvent(QPaintEvent *e) override;
+	void resizeEvent(QResizeEvent *e) override;
+
+	void doSetInnerFocus() override;
+	void showAll() override;
+
+private:
+	void onFilterUpdate(const QString &query);
+
+	class Inner;
+	ChildWidget<Inner> _inner;
+	ChildWidget<Ui::MultiSelect> _select;
+
+	ScrollableBoxShadow _topShadow;
+
+};
+
+// This class is hold in header because it requires Qt preprocessing.
+class CountrySelectBox::Inner : public ScrolledWidget {
+	Q_OBJECT
+
+public:
+	Inner(QWidget *parent);
 
 	void updateFilter(QString filter = QString());
 
@@ -104,34 +144,3 @@ private:
 	QPoint _lastMousePos;
 
 };
-
-class CountrySelectBox : public ItemListBox {
-	Q_OBJECT
-
-public:
-	CountrySelectBox();
-
-signals:
-	void countryChosen(const QString &iso);
-
-public slots:
-	void onFilterUpdate();
-	void onFilterCancel();
-	void onSubmit();
-
-protected:
-	void keyPressEvent(QKeyEvent *e) override;
-	void paintEvent(QPaintEvent *e) override;
-	void resizeEvent(QResizeEvent *e) override;
-
-	void doSetInnerFocus() override;
-	void showAll() override;
-
-private:
-	CountrySelectInner _inner;
-	InputField _filter;
-	IconedButton _filterCancel;
-
-	ScrollableBoxShadow _topShadow;
-
-};
diff --git a/Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp b/Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp
new file mode 100644
index 000000000..f0fcb5456
--- /dev/null
+++ b/Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp
@@ -0,0 +1,217 @@
+/*
+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-2016 John Preston, https://desktop.telegram.org
+*/
+#include "stdafx.h"
+#include "ui/effects/round_image_checkbox.h"
+
+namespace Ui {
+namespace {
+
+static constexpr int kWideScale = 4;
+
+void prepareCheckCaches(const style::RoundImageCheckbox *st, QPixmap &checkBgCache, QPixmap &checkFullCache) {
+	auto size = st->checkRadius * 2;
+	auto wideSize = size * kWideScale;
+	auto cache = QImage(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
+	cache.setDevicePixelRatio(cRetinaFactor());
+	{
+		Painter p(&cache);
+		p.setCompositionMode(QPainter::CompositionMode_Source);
+		p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
+		p.setCompositionMode(QPainter::CompositionMode_SourceOver);
+		p.setRenderHint(QPainter::HighQualityAntialiasing, true);
+		auto pen = st->checkBorder->p;
+		pen.setWidth(st->selectWidth);
+		p.setPen(pen);
+		p.setBrush(st->checkBg);
+		auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
+		p.drawEllipse(ellipse);
+	}
+	auto cacheIcon = cache;
+	{
+		Painter p(&cacheIcon);
+		auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
+		st->checkIcon.paint(p, ellipse.topLeft(), wideSize);
+	}
+	checkBgCache = App::pixmapFromImageInPlace(std_::move(cache));
+	checkBgCache.setDevicePixelRatio(cRetinaFactor());
+	checkFullCache = App::pixmapFromImageInPlace(std_::move(cacheIcon));
+	checkFullCache.setDevicePixelRatio(cRetinaFactor());
+}
+
+} // namespace
+
+RoundImageCheckbox::RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda_wrap<void()> updateCallback, PaintRoundImage paintRoundImage)
+: _st(st)
+, _updateCallback(std_::move(updateCallback))
+, _paintRoundImage(std_::move(paintRoundImage)) {
+	prepareCheckCaches(&_st, _wideCheckBgCache, _wideCheckFullCache);
+}
+
+void RoundImageCheckbox::paint(Painter &p, uint64 ms, int x, int y, int outerWidth) {
+	_selection.step(ms);
+	for (auto &icon : _icons) {
+		icon.fadeIn.step(ms);
+		icon.fadeOut.step(ms);
+	}
+	removeFadeOutedIcons();
+
+	auto selectionLevel = _selection.current(_checked ? 1. : 0.);
+	if (_selection.animating()) {
+		auto userpicRadius = qRound(kWideScale * (_st.imageRadius + (_st.imageSmallRadius - _st.imageRadius) * selectionLevel));
+		auto userpicShift = kWideScale * _st.imageRadius - userpicRadius;
+		auto userpicLeft = x - (kWideScale - 1) * _st.imageRadius + userpicShift;
+		auto userpicTop = y - (kWideScale - 1) * _st.imageRadius + userpicShift;
+		auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2);
+		auto from = QRect(QPoint(0, 0), _wideCache.size());
+
+		p.setRenderHint(QPainter::SmoothPixmapTransform, true);
+		p.drawPixmapLeft(to, outerWidth, _wideCache, from);
+		p.setRenderHint(QPainter::SmoothPixmapTransform, false);
+	} else {
+		if (!_wideCache.isNull()) {
+			_wideCache = QPixmap();
+		}
+		auto userpicRadius = _checked ? _st.imageSmallRadius : _st.imageRadius;
+		auto userpicShift = _st.imageRadius - userpicRadius;
+		auto userpicLeft = x + userpicShift;
+		auto userpicTop = y + userpicShift;
+		_paintRoundImage(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
+	}
+
+	if (selectionLevel > 0) {
+		p.setRenderHint(QPainter::HighQualityAntialiasing, true);
+		p.setOpacity(snap(selectionLevel, 0., 1.));
+		p.setBrush(Qt::NoBrush);
+		auto pen = _st.selectFg->p;
+		pen.setWidth(_st.selectWidth);
+		p.setPen(pen);
+		p.drawEllipse(rtlrect(x, y, _st.imageRadius * 2, _st.imageRadius * 2, outerWidth));
+		p.setOpacity(1.);
+		p.setRenderHint(QPainter::HighQualityAntialiasing, false);
+	}
+
+	p.setRenderHint(QPainter::SmoothPixmapTransform, true);
+	for (auto &icon : _icons) {
+		auto fadeIn = icon.fadeIn.current(1.);
+		auto fadeOut = icon.fadeOut.current(1.);
+		auto iconRadius = qRound(kWideScale * (_st.checkSmallRadius + fadeOut * (_st.checkRadius - _st.checkSmallRadius)));
+		auto iconShift = kWideScale * _st.checkRadius - iconRadius;
+		auto iconLeft = x + 2 * _st.imageRadius + _st.selectWidth - 2 * _st.checkRadius - (kWideScale - 1) * _st.checkRadius + iconShift;
+		auto iconTop = y + 2 * _st.imageRadius + _st.selectWidth - 2 * _st.checkRadius - (kWideScale - 1) * _st.checkRadius + iconShift;
+		auto to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2);
+		auto from = QRect(QPoint(0, 0), _wideCheckFullCache.size());
+		auto opacity = fadeIn * fadeOut;
+		p.setOpacity(opacity);
+		if (fadeOut < 1.) {
+			p.drawPixmapLeft(to, outerWidth, icon.wideCheckCache, from);
+		} else {
+			auto divider = qRound((kWideScale - 2) * _st.checkRadius + fadeIn * 3 * _st.checkRadius);
+			p.drawPixmapLeft(QRect(iconLeft, iconTop, divider, iconRadius * 2), outerWidth, _wideCheckFullCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckFullCache.height()));
+			p.drawPixmapLeft(QRect(iconLeft + divider, iconTop, iconRadius * 2 - divider, iconRadius * 2), outerWidth, _wideCheckBgCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckBgCache.width() - divider * cIntRetinaFactor(), _wideCheckBgCache.height()));
+		}
+	}
+	p.setRenderHint(QPainter::SmoothPixmapTransform, false);
+	p.setOpacity(1.);
+}
+
+float64 RoundImageCheckbox::checkedAnimationRatio() const {
+	return snap(_selection.current(_checked ? 1. : 0.), 0., 1.);
+}
+
+void RoundImageCheckbox::setChecked(bool checked, SetStyle speed) {
+	if (_checked == checked) {
+		if (speed != SetStyle::Animated) {
+			if (!_icons.isEmpty()) {
+				_icons.back().fadeIn.finish();
+				_icons.back().fadeOut.finish();
+			}
+			_selection.finish();
+		}
+		return;
+	}
+	_checked = checked;
+	if (_checked) {
+		_icons.push_back(Icon());
+		_icons.back().fadeIn.start(_updateCallback, 0, 1, _st.selectDuration);
+		if (speed != SetStyle::Animated) {
+			_icons.back().fadeIn.finish();
+		}
+	} else {
+		_icons.back().fadeOut.start(_updateCallback, 1, 0, _st.selectDuration);
+		if (speed == SetStyle::Animated) {
+			prepareWideCheckIconCache(&_icons.back());
+		} else {
+			_icons.back().fadeOut.finish();
+		}
+	}
+	if (speed == SetStyle::Animated) {
+		prepareWideCache();
+		_selection.start(_updateCallback, _checked ? 0 : 1, _checked ? 1 : 0, _st.selectDuration, anim::bumpy<125, 100>);
+	} else {
+		_selection.finish();
+	}
+}
+
+void RoundImageCheckbox::removeFadeOutedIcons() {
+	while (!_icons.empty() && !_icons.front().fadeIn.animating() && !_icons.front().fadeOut.animating()) {
+		if (_icons.size() > 1 || !_checked) {
+			_icons.erase(_icons.begin());
+		} else {
+			break;
+		}
+	}
+}
+
+void RoundImageCheckbox::prepareWideCache() {
+	if (_wideCache.isNull()) {
+		auto size = _st.imageRadius * 2;
+		auto wideSize = size * kWideScale;
+		QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
+		cache.setDevicePixelRatio(cRetinaFactor());
+		{
+			Painter p(&cache);
+			p.setCompositionMode(QPainter::CompositionMode_Source);
+			p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
+			p.setCompositionMode(QPainter::CompositionMode_SourceOver);
+			_paintRoundImage(p, (wideSize - size) / 2, (wideSize - size) / 2, wideSize, size);
+		}
+		_wideCache = App::pixmapFromImageInPlace(std_::move(cache));
+	}
+}
+
+void RoundImageCheckbox::prepareWideCheckIconCache(Icon *icon) {
+	auto cacheWidth = _wideCheckBgCache.width() / _wideCheckBgCache.devicePixelRatio();
+	auto cacheHeight = _wideCheckBgCache.height() / _wideCheckBgCache.devicePixelRatio();
+	auto wideCache = QImage(cacheWidth * cIntRetinaFactor(), cacheHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
+	wideCache.setDevicePixelRatio(cRetinaFactor());
+	{
+		Painter p(&wideCache);
+		p.setCompositionMode(QPainter::CompositionMode_Source);
+		auto iconRadius = kWideScale * _st.checkRadius;
+		auto divider = qRound((kWideScale - 2) * _st.checkRadius + icon->fadeIn.current(1.) * (kWideScale - 1) * _st.checkRadius);
+		p.drawPixmapLeft(QRect(0, 0, divider, iconRadius * 2), cacheWidth, _wideCheckFullCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckFullCache.height()));
+		p.drawPixmapLeft(QRect(divider, 0, iconRadius * 2 - divider, iconRadius * 2), cacheWidth, _wideCheckBgCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckBgCache.width() - divider * cIntRetinaFactor(), _wideCheckBgCache.height()));
+	}
+	icon->wideCheckCache = App::pixmapFromImageInPlace(std_::move(wideCache));
+	icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor());
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/effects/round_image_checkbox.h b/Telegram/SourceFiles/ui/effects/round_image_checkbox.h
new file mode 100644
index 000000000..3b9d0f79a
--- /dev/null
+++ b/Telegram/SourceFiles/ui/effects/round_image_checkbox.h
@@ -0,0 +1,68 @@
+/*
+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-2016 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+#include "styles/style_widgets.h"
+
+namespace Ui {
+
+class RoundImageCheckbox {
+public:
+	using PaintRoundImage = base::lambda_unique<void(Painter &p, int x, int y, int outerWidth, int size)>;
+	RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda_wrap<void()> updateCallback, PaintRoundImage paintRoundImage);
+
+	void paint(Painter &p, uint64 ms, int x, int y, int outerWidth);
+	float64 checkedAnimationRatio() const;
+
+	bool checked() const {
+		return _checked;
+	}
+	enum class SetStyle {
+		Animated,
+		Fast,
+	};
+	void setChecked(bool checked, SetStyle speed = SetStyle::Animated);
+
+private:
+	struct Icon {
+		FloatAnimation fadeIn;
+		FloatAnimation fadeOut;
+		QPixmap wideCheckCache;
+	};
+	void removeFadeOutedIcons();
+	void prepareWideCache();
+	void prepareWideCheckIconCache(Icon *icon);
+
+	const style::RoundImageCheckbox &_st;
+	base::lambda_wrap<void()> _updateCallback;
+	PaintRoundImage _paintRoundImage;
+
+	bool _checked = false;
+	QPixmap _wideCache;
+	FloatAnimation _selection;
+	std_::vector_of_moveable<Icon> _icons;
+
+	// Those pixmaps are shared among all checkboxes that have the same style.
+	QPixmap _wideCheckBgCache, _wideCheckFullCache;
+
+};
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/flatinput.cpp b/Telegram/SourceFiles/ui/flatinput.cpp
index cc63eb4fe..5f835f9a8 100644
--- a/Telegram/SourceFiles/ui/flatinput.cpp
+++ b/Telegram/SourceFiles/ui/flatinput.cpp
@@ -184,8 +184,8 @@ void FlatInput::paintEvent(QPaintEvent *e) {
 	p.drawRoundedRect(QRectF(0, 0, width(), height()).marginsRemoved(QMarginsF(_st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2.)), st::buttonRadius - (_st.borderWidth / 2.), st::buttonRadius - (_st.borderWidth / 2.));
 	p.setRenderHint(QPainter::HighQualityAntialiasing, false);
 
-	if (_st.imgRect.pxWidth()) {
-		p.drawSprite(_st.imgPos, _st.imgRect);
+	if (!_st.icon.empty()) {
+		_st.icon.paint(p, 0, 0, width());
 	}
 
 	bool phDraw = _phVisible;
@@ -683,10 +683,10 @@ void InputArea::checkContentHeight() {
 	}
 }
 
-InputArea::InputAreaInner::InputAreaInner(InputArea *parent) : QTextEdit(parent) {
+InputArea::Inner::Inner(InputArea *parent) : QTextEdit(parent) {
 }
 
-bool InputArea::InputAreaInner::viewportEvent(QEvent *e) {
+bool InputArea::Inner::viewportEvent(QEvent *e) {
 	if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
 		QTouchEvent *ev = static_cast<QTouchEvent*>(e);
 		if (ev->device()->type() == QTouchDevice::TouchScreen) {
@@ -790,7 +790,7 @@ void InputArea::contextMenuEvent(QContextMenuEvent *e) {
 	_inner.contextMenuEvent(e);
 }
 
-void InputArea::InputAreaInner::focusInEvent(QFocusEvent *e) {
+void InputArea::Inner::focusInEvent(QFocusEvent *e) {
 	f()->focusInInner();
 	QTextEdit::focusInEvent(e);
 	emit f()->focused();
@@ -807,7 +807,7 @@ void InputArea::focusInInner() {
 	}
 }
 
-void InputArea::InputAreaInner::focusOutEvent(QFocusEvent *e) {
+void InputArea::Inner::focusOutEvent(QFocusEvent *e) {
 	f()->focusOutInner();
 	QTextEdit::focusOutEvent(e);
 	emit f()->blurred();
@@ -943,7 +943,7 @@ void InputArea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
 	c.insertText(objectReplacement, imageFormat);
 }
 
-QVariant InputArea::InputAreaInner::loadResource(int type, const QUrl &name) {
+QVariant InputArea::Inner::loadResource(int type, const QUrl &name) {
 	QString imageName = name.toDisplayString();
 	if (imageName.startsWith(qstr("emoji://e."))) {
 		if (EmojiPtr emoji = emojiFromUrl(imageName)) {
@@ -1193,7 +1193,7 @@ void InputArea::updatePlaceholder() {
 	}
 }
 
-QMimeData *InputArea::InputAreaInner::createMimeDataFromSelection() const {
+QMimeData *InputArea::Inner::createMimeDataFromSelection() const {
 	QMimeData *result = new QMimeData();
 	QTextCursor c(textCursor());
 	int32 start = c.selectionStart(), end = c.selectionEnd();
@@ -1211,7 +1211,7 @@ void InputArea::setCtrlEnterSubmit(CtrlEnterSubmit ctrlEnterSubmit) {
 	_ctrlEnterSubmit = ctrlEnterSubmit;
 }
 
-void InputArea::InputAreaInner::keyPressEvent(QKeyEvent *e) {
+void InputArea::Inner::keyPressEvent(QKeyEvent *e) {
 	bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier);
 	bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier);
 	bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier);
@@ -1276,11 +1276,11 @@ void InputArea::InputAreaInner::keyPressEvent(QKeyEvent *e) {
 	}
 }
 
-void InputArea::InputAreaInner::paintEvent(QPaintEvent *e) {
+void InputArea::Inner::paintEvent(QPaintEvent *e) {
 	return QTextEdit::paintEvent(e);
 }
 
-void InputArea::InputAreaInner::contextMenuEvent(QContextMenuEvent *e) {
+void InputArea::Inner::contextMenuEvent(QContextMenuEvent *e) {
 	if (QMenu *menu = createStandardContextMenu()) {
 		(new PopupMenu(menu))->popup(e->globalPos());
 	}
@@ -1338,7 +1338,9 @@ InputField::InputField(QWidget *parent, const style::InputField &st, const QStri
 
 	_inner.setWordWrapMode(QTextOption::NoWrap);
 
-	setAttribute(Qt::WA_OpaquePaintEvent);
+	if (_st.textBg->c.alphaF() >= 1.) {
+		setAttribute(Qt::WA_OpaquePaintEvent);
+	}
 
 	_inner.setFont(_st.font->f);
 	_inner.setAlignment(_st.textAlign);
@@ -1380,10 +1382,10 @@ void InputField::onTouchTimer() {
 	_touchRightButton = true;
 }
 
-InputField::InputFieldInner::InputFieldInner(InputField *parent) : QTextEdit(parent) {
+InputField::Inner::Inner(InputField *parent) : QTextEdit(parent) {
 }
 
-bool InputField::InputFieldInner::viewportEvent(QEvent *e) {
+bool InputField::Inner::viewportEvent(QEvent *e) {
 	if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
 		QTouchEvent *ev = static_cast<QTouchEvent*>(e);
 		if (ev->device()->type() == QTouchDevice::TouchScreen) {
@@ -1436,8 +1438,18 @@ void InputField::touchEvent(QTouchEvent *e) {
 void InputField::paintEvent(QPaintEvent *e) {
 	Painter p(this);
 
+	auto ms = getms();
+	if (_a_placeholderShift.animating()) {
+		_a_placeholderShift.step(ms);
+	}
+	if (_a_placeholderFg.animating()) {
+		_a_placeholderFg.step(ms);
+	}
+
 	QRect r(rect().intersected(e->rect()));
-	p.fillRect(r, st::white->b);
+	if (_st.textBg->c.alphaF() > 0.) {
+		p.fillRect(r, _st.textBg);
+	}
 	if (_st.border) {
 		p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b);
 	}
@@ -1446,9 +1458,6 @@ void InputField::paintEvent(QPaintEvent *e) {
 		p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, a_borderFg.current());
 		p.setOpacity(1);
 	}
-	if (_st.iconSprite.pxWidth()) {
-		p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite);
-	}
 
 	bool drawPlaceholder = _placeholderVisible;
 	if (_a_placeholderShift.animating()) {
@@ -1490,7 +1499,7 @@ void InputField::contextMenuEvent(QContextMenuEvent *e) {
 	_inner.contextMenuEvent(e);
 }
 
-void InputField::InputFieldInner::focusInEvent(QFocusEvent *e) {
+void InputField::Inner::focusInEvent(QFocusEvent *e) {
 	f()->focusInInner();
 	QTextEdit::focusInEvent(e);
 	emit f()->focused();
@@ -1507,7 +1516,7 @@ void InputField::focusInInner() {
 	}
 }
 
-void InputField::InputFieldInner::focusOutEvent(QFocusEvent *e) {
+void InputField::Inner::focusOutEvent(QFocusEvent *e) {
 	f()->focusOutInner();
 	QTextEdit::focusOutEvent(e);
 	emit f()->blurred();
@@ -1643,7 +1652,7 @@ void InputField::insertEmoji(EmojiPtr emoji, QTextCursor c) {
 	c.insertText(objectReplacement, imageFormat);
 }
 
-QVariant InputField::InputFieldInner::loadResource(int type, const QUrl &name) {
+QVariant InputField::Inner::loadResource(int type, const QUrl &name) {
 	QString imageName = name.toDisplayString();
 	if (imageName.startsWith(qstr("emoji://e."))) {
 		if (EmojiPtr emoji = emojiFromUrl(imageName)) {
@@ -1889,9 +1898,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);
@@ -1899,6 +1906,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) {
@@ -1928,7 +1942,7 @@ void InputField::setPlaceholderHidden(bool forcePlaceholderHidden) {
 	updatePlaceholder();
 }
 
-QMimeData *InputField::InputFieldInner::createMimeDataFromSelection() const {
+QMimeData *InputField::Inner::createMimeDataFromSelection() const {
 	QMimeData *result = new QMimeData();
 	QTextCursor c(textCursor());
 	int32 start = c.selectionStart(), end = c.selectionEnd();
@@ -1942,7 +1956,7 @@ void InputField::customUpDown(bool custom) {
 	_customUpDown = custom;
 }
 
-void InputField::InputFieldInner::keyPressEvent(QKeyEvent *e) {
+void InputField::Inner::keyPressEvent(QKeyEvent *e) {
 	bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier);
 	bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier);
 	bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier), ctrlGood = true;
@@ -1979,36 +1993,41 @@ void InputField::InputFieldInner::keyPressEvent(QKeyEvent *e) {
 		}
 #endif // Q_OS_MAC
 	} else {
-		QTextCursor tc(textCursor());
+		auto oldCursorPosition = textCursor().position();
 		if (enter && ctrl) {
 			e->setModifiers(e->modifiers() & ~Qt::ControlModifier);
 		}
 		QTextEdit::keyPressEvent(e);
-		if (tc == textCursor()) {
+		auto currentCursor = textCursor();
+		if (textCursor().position() == oldCursorPosition) {
 			bool check = false;
 			if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) {
-				tc.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
+				oldCursorPosition = currentCursor.position();
+				currentCursor.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
 				check = true;
 			} else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) {
-				tc.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
+				oldCursorPosition = currentCursor.position();
+				currentCursor.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
 				check = true;
+			} else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right || e->key() == Qt::Key_Backspace) {
+				e->ignore();
 			}
 			if (check) {
-				if (tc == textCursor()) {
+				if (oldCursorPosition == currentCursor.position()) {
 					e->ignore();
 				} else {
-					setTextCursor(tc);
+					setTextCursor(currentCursor);
 				}
 			}
 		}
 	}
 }
 
-void InputField::InputFieldInner::paintEvent(QPaintEvent *e) {
+void InputField::Inner::paintEvent(QPaintEvent *e) {
 	return QTextEdit::paintEvent(e);
 }
 
-void InputField::InputFieldInner::contextMenuEvent(QContextMenuEvent *e) {
+void InputField::Inner::contextMenuEvent(QContextMenuEvent *e) {
 	if (QMenu *menu = createStandardContextMenu()) {
 		(new PopupMenu(menu))->popup(e->globalPos());
 	}
@@ -2167,9 +2186,6 @@ void MaskedInputField::paintEvent(QPaintEvent *e) {
 		p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, a_borderFg.current());
 		p.setOpacity(1);
 	}
-	if (_st.iconSprite.pxWidth()) {
-		p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite);
-	}
 
 	p.setClipRect(r);
 	paintPlaceholder(p);
diff --git a/Telegram/SourceFiles/ui/flatinput.h b/Telegram/SourceFiles/ui/flatinput.h
index 2535a019d..f730be8f8 100644
--- a/Telegram/SourceFiles/ui/flatinput.h
+++ b/Telegram/SourceFiles/ui/flatinput.h
@@ -262,10 +262,9 @@ private:
 	bool heightAutoupdated();
 	void checkContentHeight();
 
-	friend class InputAreaInner;
-	class InputAreaInner : public QTextEdit {
+	class Inner : public QTextEdit {
 	public:
-		InputAreaInner(InputArea *parent);
+		Inner(InputArea *parent);
 
 		QVariant loadResource(int type, const QUrl &name) override;
 
@@ -286,6 +285,7 @@ private:
 		friend class InputArea;
 
 	};
+	friend class Inner;
 
 	void focusInInner();
 	void focusOutInner();
@@ -294,7 +294,7 @@ private:
 
 	void startBorderAnimation();
 
-	InputAreaInner _inner;
+	Inner _inner;
 
 	QString _oldtext;
 
@@ -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);
@@ -431,10 +432,9 @@ private:
 	int32 _maxLength;
 	bool _forcePlaceholderHidden = false;
 
-	friend class InputFieldInner;
-	class InputFieldInner : public QTextEdit {
+	class Inner : public QTextEdit {
 	public:
-		InputFieldInner(InputField *parent);
+		Inner(InputField *parent);
 
 		QVariant loadResource(int type, const QUrl &name) override;
 
@@ -455,6 +455,7 @@ private:
 		friend class InputField;
 
 	};
+	friend class Inner;
 
 	void focusInInner();
 	void focusOutInner();
@@ -463,7 +464,7 @@ private:
 
 	void startBorderAnimation();
 
-	InputFieldInner _inner;
+	Inner _inner;
 
 	QString _oldtext;
 
diff --git a/Telegram/SourceFiles/ui/style/style_core_icon.cpp b/Telegram/SourceFiles/ui/style/style_core_icon.cpp
index acad1c618..2df173ae9 100644
--- a/Telegram/SourceFiles/ui/style/style_core_icon.cpp
+++ b/Telegram/SourceFiles/ui/style/style_core_icon.cpp
@@ -134,7 +134,7 @@ void MonoIcon::ensureLoaded() const {
 		if (size > sizeTag.size() && !memcmp(data, sizeTag.data(), sizeTag.size())) {
 			size -= sizeTag.size();
 			data += sizeTag.size();
-			QByteArray baForStream(reinterpret_cast<const char*>(data), size);
+			auto baForStream = QByteArray::fromRawData(reinterpret_cast<const char*>(data), size);
 			QBuffer buffer(&baForStream);
 			buffer.open(QIODevice::ReadOnly);
 
@@ -207,7 +207,7 @@ int Icon::width() const {
 int Icon::height() const {
 	if (_height < 0) {
 		_height = 0;
-		for_const (const auto &part, _parts) {
+		for_const (auto &part, _parts) {
 			accumulate_max(_height, part.offset().x() + part.height());
 		}
 	}
diff --git a/Telegram/SourceFiles/ui/style/style_core_icon.h b/Telegram/SourceFiles/ui/style/style_core_icon.h
index 822a491a9..c4e6b73a7 100644
--- a/Telegram/SourceFiles/ui/style/style_core_icon.h
+++ b/Telegram/SourceFiles/ui/style/style_core_icon.h
@@ -105,6 +105,9 @@ public:
 		return std_::make_unique<Icon>(ColoredCopy { *this, colors });
 	}
 
+	bool empty() const {
+		return _parts.empty();
+	}
 	void paint(QPainter &p, const QPoint &pos, int outerw) const;
 	void paint(QPainter &p, int x, int y, int outerw) const {
 		paint(p, QPoint(x, y), outerw);
@@ -115,6 +118,9 @@ public:
 	void fill(QPainter &p, const QRect &rect) const;
 	int width() const;
 	int height() const;
+	QSize size() const {
+		return QSize(width(), height());
+	}
 
 private:
 	struct ColoredCopy {
diff --git a/Telegram/SourceFiles/ui/widgets/filled_slider.h b/Telegram/SourceFiles/ui/widgets/filled_slider.h
index 7037b9544..87bb6e7ed 100644
--- a/Telegram/SourceFiles/ui/widgets/filled_slider.h
+++ b/Telegram/SourceFiles/ui/widgets/filled_slider.h
@@ -21,10 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #pragma once
 
 #include "ui/widgets/continuous_slider.h"
-
-namespace style {
-struct FilledSlider;
-} // namespace style
+#include "styles/style_widgets.h"
 
 namespace Ui {
 
diff --git a/Telegram/SourceFiles/ui/widgets/media_slider.h b/Telegram/SourceFiles/ui/widgets/media_slider.h
index 4bf1967d3..e01e29af5 100644
--- a/Telegram/SourceFiles/ui/widgets/media_slider.h
+++ b/Telegram/SourceFiles/ui/widgets/media_slider.h
@@ -21,10 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #pragma once
 
 #include "ui/widgets/continuous_slider.h"
-
-namespace style {
-struct MediaSlider;
-} // namespace style
+#include "styles/style_widgets.h"
 
 namespace Ui {
 
diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp
new file mode 100644
index 000000000..bef2de71b
--- /dev/null
+++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp
@@ -0,0 +1,856 @@
+/*
+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-2016 John Preston, https://desktop.telegram.org
+*/
+#include "stdafx.h"
+#include "ui/widgets/multi_select.h"
+
+#include "styles/style_widgets.h"
+#include "ui/buttons/icon_button.h"
+#include "lang.h"
+
+namespace Ui {
+namespace {
+
+constexpr int kWideScale = 3;
+
+} // namespace
+
+class MultiSelect::Inner::Item {
+public:
+	Item(const style::MultiSelectItem &st, uint64 id, const QString &text, const 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_wrap<void()> updateCallback) {
+		_updateCallback = std_::move(updateCallback);
+	}
+	void setText(const QString &text);
+	void paint(Painter &p, int outerWidth, uint64 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, uint64 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_wrap<void()> updateCallback, int fromX, int toX, int y, float64 duration)
+			: fromX(fromX)
+			, toX(toX)
+			, y(y) {
+			x.start(std_::move(updateCallback), fromX, toX, duration);
+		}
+		IntAnimation x;
+		int fromX, toX;
+		int y;
+	};
+	std_::vector_of_moveable<SlideAnimation> _copies;
+	int _x = -1;
+	int _y = -1;
+	int _width = 0;
+	Text _text;
+	const style::color &_color;
+	bool _over = false;
+	QPixmap _cache;
+	FloatAnimation _visibility;
+	FloatAnimation _overOpacity;
+	bool _overDelete = false;
+	bool _active = false;
+	PaintRoundImage _paintRoundImage;
+	base::lambda_wrap<void()> _updateCallback;
+	bool _hiding = false;
+
+};
+
+MultiSelect::Inner::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, const style::color &color, PaintRoundImage paintRoundImage)
+: _st(st)
+, _id(id)
+, _color(color)
+, _paintRoundImage(std_::move(paintRoundImage)) {
+	setText(text);
+}
+
+void MultiSelect::Inner::Item::setText(const QString &text) {
+	_text.setText(_st.font, 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, uint64 ms) {
+	if (!_cache.isNull() && !_visibility.animating(ms)) {
+		if (_hiding) {
+			return;
+		} else {
+			_cache = QPixmap();
+		}
+	}
+	if (_copies.empty()) {
+		paintOnce(p, _x, _y, outerWidth, ms);
+	} else {
+		for (auto i = _copies.begin(), e = _copies.end(); i != e;) {
+			auto x = i->x.current(getms(), _x);
+			auto y = i->y;
+			auto animating = i->x.animating();
+			if (animating || (y == _y)) {
+				paintOnce(p, x, y, outerWidth, ms);
+			}
+			if (animating) {
+				++i;
+			} else {
+				i = _copies.erase(i);
+				e = _copies.end();
+			}
+		}
+	}
+}
+
+void MultiSelect::Inner::Item::paintOnce(Painter &p, int x, int y, int outerWidth, uint64 ms) {
+	if (!_cache.isNull()) {
+		paintCached(p, x, y, outerWidth);
+		return;
+	}
+
+	auto radius = _st.height / 2;
+	auto inner = rtlrect(x + radius, y, _width - radius, _st.height, outerWidth);
+
+	auto clipEnabled = p.hasClipping();
+	auto clip = clipEnabled ? p.clipRegion() : QRegion();
+	p.setRenderHint(QPainter::HighQualityAntialiasing);
+	p.setClipRect(inner);
+
+	p.setPen(Qt::NoPen);
+	p.setBrush(_active ? _st.textActiveBg : _st.textBg);
+	p.drawRoundedRect(rtlrect(x, y, _width, _st.height, outerWidth), radius, radius);
+
+	if (clipEnabled) {
+		p.setClipRegion(clip);
+	} else {
+		p.setClipping(false);
+	}
+	p.setRenderHint(QPainter::HighQualityAntialiasing, false);
+
+	auto overOpacity = _overOpacity.current(ms, _over ? 1. : 0.);
+	if (overOpacity < 1.) {
+		_paintRoundImage(p, x, y, outerWidth, _st.height);
+	}
+	if (overOpacity > 0.) {
+		paintDeleteButton(p, x, y, outerWidth, overOpacity);
+	}
+
+	auto textLeft = _st.height + _st.padding.left();
+	auto textWidth = _width - textLeft - _st.padding.right();
+	p.setPen(_active ? _st.textActiveFg : _st.textFg);
+	_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) {
+	p.setOpacity(overOpacity);
+	p.setRenderHint(QPainter::HighQualityAntialiasing);
+	p.setPen(Qt::NoPen);
+	p.setBrush(_color);
+	p.drawEllipse(rtlrect(x, y, _st.height, _st.height, outerWidth));
+
+	auto deleteScale = overOpacity + _st.minScale * (1. - overOpacity);
+	auto deleteSkip = deleteScale * _st.deleteLeft + (1. - deleteScale) * (_st.height / 2);
+	auto sqrt2 = sqrt(2.);
+	auto deleteLeft = rtlpoint(x + deleteSkip, 0, outerWidth).x() + 0.;
+	auto deleteTop = y + deleteSkip + 0.;
+	auto deleteWidth = _st.height - 2 * deleteSkip;
+	auto deleteHeight = _st.height - 2 * deleteSkip;
+	auto deleteStroke = _st.deleteStroke / sqrt2;
+	QPointF pathDelete[] = {
+		{ deleteLeft, deleteTop + deleteStroke },
+		{ deleteLeft + deleteStroke, deleteTop },
+		{ deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) - deleteStroke },
+		{ deleteLeft + deleteWidth - deleteStroke, deleteTop },
+		{ deleteLeft + deleteWidth, deleteTop + deleteStroke },
+		{ deleteLeft + (deleteWidth / 2.) + deleteStroke, deleteTop + (deleteHeight / 2.) },
+		{ deleteLeft + deleteWidth, deleteTop + deleteHeight - deleteStroke },
+		{ deleteLeft + deleteWidth - deleteStroke, deleteTop + deleteHeight },
+		{ deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) + deleteStroke },
+		{ deleteLeft + deleteStroke, deleteTop + deleteHeight },
+		{ deleteLeft, deleteTop + deleteHeight - deleteStroke },
+		{ deleteLeft + (deleteWidth / 2.) - deleteStroke, deleteTop + (deleteHeight / 2.) },
+	};
+	if (overOpacity < 1.) {
+		auto alpha = -(overOpacity - 1.) * M_PI_2;
+		auto cosalpha = cos(alpha);
+		auto sinalpha = sin(alpha);
+		auto shiftx = deleteLeft + (deleteWidth / 2.);
+		auto shifty = deleteTop + (deleteHeight / 2.);
+		for (auto &point : pathDelete) {
+			auto x = point.x() - shiftx;
+			auto y = point.y() - shifty;
+			point.setX(shiftx + x * cosalpha - y * sinalpha);
+			point.setY(shifty + y * cosalpha + x * sinalpha);
+		}
+	}
+	QPainterPath path;
+	path.moveTo(pathDelete[0]);
+	for (int i = 1; i != base::array_size(pathDelete); ++i) {
+		path.lineTo(pathDelete[i]);
+	}
+	p.fillPath(path, _st.deleteFg);
+
+	p.setRenderHint(QPainter::HighQualityAntialiasing, false);
+	p.setOpacity(1.);
+}
+
+bool MultiSelect::Inner::Item::paintCached(Painter &p, int x, int y, int outerWidth) {
+	auto opacity = _visibility.current(_hiding ? 0. : 1.);
+	auto scale = opacity + _st.minScale * (1. - opacity);
+	auto height = opacity * _cache.height() / _cache.devicePixelRatio();
+	auto width = opacity * _cache.width() / _cache.devicePixelRatio();
+
+	p.setOpacity(opacity);
+	p.setRenderHint(QPainter::SmoothPixmapTransform, true);
+	p.drawPixmap(rtlrect(x + (_width - width) / 2., y + (_st.height - height) / 2., width, height, outerWidth), _cache);
+	p.setRenderHint(QPainter::SmoothPixmapTransform, false);
+	p.setOpacity(1.);
+	return true;
+}
+
+void MultiSelect::Inner::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() {
+	_overDelete = false;
+	setOver(false);
+}
+
+void MultiSelect::Inner::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;
+		auto leftHidden = -_width - maxVisiblePadding;
+		auto rightHidden = outerWidth + maxVisiblePadding;
+		for (auto i = _copies.begin(), e = _copies.end(); i != e;) {
+			if (i->x.animating()) {
+				if (i->y == y) {
+					i->x.start(_updateCallback, i->toX, x, _st.duration);
+					found = true;
+				} else {
+					i->x.start(_updateCallback, i->fromX, (i->toX > i->fromX) ? rightHidden : leftHidden, _st.duration);
+				}
+				++i;
+			} else {
+				i = _copies.erase(i);
+				e = _copies.end();
+			}
+		}
+		if (_copies.empty()) {
+			if (_y == y) {
+				auto copy = SlideAnimation(_updateCallback, _x, x, _y, _st.duration);
+				_copies.push_back(std_::move(copy));
+			} else {
+				auto copyHiding = SlideAnimation(_updateCallback, _x, (y > _y) ? rightHidden : leftHidden, _y, _st.duration);
+				_copies.push_back(std_::move(copyHiding));
+				auto copyShowing = SlideAnimation(_updateCallback, (y > _y) ? leftHidden : rightHidden, x, y, _st.duration);
+				_copies.push_back(std_::move(copyShowing));
+			}
+		} else if (!found) {
+			auto copy = SlideAnimation(_updateCallback, (y > _y) ? leftHidden : rightHidden, x, y, _st.duration);
+			_copies.push_back(std_::move(copy));
+		}
+	}
+	_x = x;
+	_y = y;
+}
+
+QRect MultiSelect::Inner::Item::paintArea(int outerWidth) const {
+	if (_copies.empty()) {
+		return rect();
+	}
+	auto yMin = 0, yMax = 0;
+	for_const (auto &copy, _copies) {
+		accumulate_max(yMax, copy.y);
+		if (yMin) {
+			accumulate_min(yMin, copy.y);
+		} else {
+			yMin = copy.y;
+		}
+	}
+	return QRect(0, yMin, outerWidth, yMax - yMin + _st.height);
+}
+
+void MultiSelect::Inner::Item::prepareCache() {
+	if (!_cache.isNull()) return;
+
+	t_assert(!_visibility.animating());
+	auto cacheWidth = _width * kWideScale * cIntRetinaFactor();
+	auto cacheHeight = _st.height * kWideScale * cIntRetinaFactor();
+	auto data = QImage(cacheWidth, cacheHeight, QImage::Format_ARGB32_Premultiplied);
+	data.fill(Qt::transparent);
+	data.setDevicePixelRatio(cRetinaFactor());
+	{
+		Painter p(&data);
+		paintOnce(p, _width * (kWideScale - 1) / 2, _st.height  * (kWideScale - 1) / 2, cacheWidth, getms());
+	}
+	_cache = App::pixmapFromImageInPlace(std_::move(data));
+}
+
+void MultiSelect::Inner::Item::setVisibleAnimated(bool visible) {
+	_hiding = !visible;
+	prepareCache();
+	auto from = visible ? 0. : 1.;
+	auto to = visible ? 1. : 0.;
+	auto transition = visible ? anim::bumpy<1125, 1000> : anim::linear;
+	_visibility.start(_updateCallback, from, to, _st.duration, transition);
+}
+
+void MultiSelect::Inner::Item::setOver(bool over) {
+	if (over != _over) {
+		_over = over;
+		_overOpacity.start(_updateCallback, _over ? 0. : 1., _over ? 1. : 0., _st.duration);
+	}
+}
+
+MultiSelect::MultiSelect(QWidget *parent, const style::MultiSelect &st, const QString &placeholder) : TWidget(parent)
+, _st(st)
+, _scroll(this, _st.scroll)
+, _inner(this, st, placeholder, [this](int activeTop, int activeBottom) { scrollTo(activeTop, activeBottom); }) {
+	_scroll->setOwnedWidget(_inner);
+	_scroll->installEventFilter(this);
+	_inner->setResizedCallback([this](int innerHeightDelta) {
+		auto newHeight = resizeGetHeight(width());
+		if (innerHeightDelta > 0) {
+			_scroll->scrollToY(_scroll->scrollTop() + innerHeightDelta);
+		}
+		if (newHeight != height()) {
+			resize(width(), newHeight);
+			if (_resizedCallback) {
+				_resizedCallback();
+			}
+		}
+	});
+	_inner->setQueryChangedCallback([this](const QString &query) {
+		_scroll->scrollToY(_scroll->scrollTopMax());
+		if (_queryChangedCallback) {
+			_queryChangedCallback(query);
+		}
+	});
+
+	setAttribute(Qt::WA_OpaquePaintEvent);
+}
+
+bool MultiSelect::eventFilter(QObject *o, QEvent *e) {
+	if (o == _scroll && e->type() == QEvent::KeyPress) {
+		e->ignore();
+		return true;
+	}
+	return false;
+}
+
+void MultiSelect::scrollTo(int activeTop, int activeBottom) {
+	auto scrollTop = _scroll->scrollTop();
+	auto scrollHeight = _scroll->height();
+	auto scrollBottom = scrollTop + scrollHeight;
+	if (scrollTop > activeTop) {
+		_scroll->scrollToY(activeTop);
+	} else if (scrollBottom < activeBottom) {
+		_scroll->scrollToY(activeBottom - scrollHeight);
+	}
+}
+
+void MultiSelect::setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback) {
+	_queryChangedCallback = std_::move(callback);
+}
+
+void MultiSelect::setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback) {
+	_inner->setSubmittedCallback(std_::move(callback));
+}
+
+void MultiSelect::setResizedCallback(base::lambda_unique<void()> callback) {
+	_resizedCallback = std_::move(callback);
+}
+
+void MultiSelect::setInnerFocus() {
+	if (_inner->setInnerFocus()) {
+		_scroll->scrollToY(_scroll->scrollTopMax());
+	}
+}
+
+void MultiSelect::clearQuery() {
+	_inner->clearQuery();
+}
+
+QString MultiSelect::getQuery() const {
+	return _inner->getQuery();
+}
+
+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) {
+	_inner->setItemRemovedCallback(std_::move(callback));
+}
+
+void MultiSelect::removeItem(uint64 itemId) {
+	_inner->removeItem(itemId);
+}
+
+int MultiSelect::resizeGetHeight(int newWidth) {
+	if (newWidth != _inner->width()) {
+		_inner->resizeToWidth(newWidth);
+	}
+	auto newHeight = qMin(_inner->height(), _st.maxHeight);
+	_scroll->setGeometryToLeft(0, 0, newWidth, newHeight);
+	return newHeight;
+}
+
+MultiSelect::Inner::Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback) : ScrolledWidget(parent)
+, _st(st)
+, _scrollCallback(std_::move(callback))
+, _field(this, _st.field, placeholder)
+, _cancel(this, _st.fieldCancel) {
+	_field->customUpDown(true);
+	connect(_field, SIGNAL(focused()), this, SLOT(onFieldFocused()));
+	connect(_field, SIGNAL(changed()), this, SLOT(onQueryChanged()));
+	connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSubmitted(bool)));
+	_cancel->hide();
+	_cancel->setClickedCallback([this] {
+		clearQuery();
+		_field->setFocus();
+	});
+	setMouseTracking(true);
+}
+
+void MultiSelect::Inner::onQueryChanged() {
+	auto query = getQuery();
+	_cancel->setVisible(!query.isEmpty());
+	updateFieldGeometry();
+	if (_queryChangedCallback) {
+		_queryChangedCallback(query);
+	}
+}
+
+QString MultiSelect::Inner::getQuery() const {
+	return _field->getLastText().trimmed();
+}
+
+bool MultiSelect::Inner::setInnerFocus() {
+	if (_active >= 0) {
+		setFocus();
+	} else if (!_field->hasFocus()) {
+		_field->setFocus();
+		return true;
+	}
+	return false;
+}
+
+void MultiSelect::Inner::clearQuery() {
+	_field->setText(QString());
+}
+
+void MultiSelect::Inner::setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback) {
+	_queryChangedCallback = std_::move(callback);
+}
+
+void MultiSelect::Inner::setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback) {
+	_submittedCallback = std_::move(callback);
+}
+
+void MultiSelect::Inner::updateFieldGeometry() {
+	auto fieldFinalWidth = _fieldWidth;
+	if (!_cancel->isHidden()) {
+		fieldFinalWidth -= _st.fieldCancelSkip;
+	}
+	_field->resizeToWidth(fieldFinalWidth);
+	_field->moveToLeft(_st.padding.left() + _fieldLeft, _st.padding.top() + _fieldTop);
+}
+
+void MultiSelect::Inner::updateHasAnyItems(bool hasAnyItems) {
+	_field->setPlaceholderHidden(hasAnyItems);
+	updateCursor();
+	_iconOpacity.start([this] {
+		rtlupdate(_st.padding.left(), _st.padding.top(), _st.fieldIcon.width(), _st.fieldIcon.height());
+	}, hasAnyItems ? 1. : 0., hasAnyItems ? 0. : 1., _st.item.duration);
+}
+
+void MultiSelect::Inner::updateCursor() {
+	setCursor(_items.empty() ? style::cur_text : (_overDelete ? style::cur_pointer : style::cur_default));
+}
+
+void MultiSelect::Inner::setActiveItem(int active, ChangeActiveWay skipSetFocus) {
+	if (_active == active) return;
+
+	if (_active >= 0) {
+		t_assert(_active < _items.size());
+		_items[_active]->setActive(false);
+	}
+	_active = active;
+	if (_active >= 0) {
+		t_assert(_active < _items.size());
+		_items[_active]->setActive(true);
+	}
+	if (skipSetFocus != ChangeActiveWay::SkipSetFocus) {
+		setInnerFocus();
+	}
+	if (_scrollCallback) {
+		auto rect = (_active >= 0) ? _items[_active]->rect() : _field->geometry().translated(-_st.padding.left(), -_st.padding.top());
+		_scrollCallback(rect.y(), rect.y() + rect.height() + _st.padding.top() + _st.padding.bottom());
+	}
+	update();
+}
+
+void MultiSelect::Inner::setActiveItemPrevious() {
+	if (_active > 0) {
+		setActiveItem(_active - 1);
+	} else if (_active < 0 && !_items.empty()) {
+		setActiveItem(_items.size() - 1);
+	}
+}
+
+void MultiSelect::Inner::setActiveItemNext() {
+	if (_active >= 0 && _active + 1 < _items.size()) {
+		setActiveItem(_active + 1);
+	} else {
+		setActiveItem(-1);
+	}
+}
+
+int MultiSelect::Inner::resizeGetHeight(int newWidth) {
+	computeItemsGeometry(newWidth);
+	updateFieldGeometry();
+
+	auto cancelLeft = _fieldLeft + _fieldWidth + _st.padding.right() - _cancel->width();
+	auto cancelTop = _fieldTop - _st.padding.top();
+	_cancel->moveToLeft(_st.padding.left() + cancelLeft, _st.padding.top() + cancelTop);
+
+	return _field->y() + _field->height() + _st.padding.bottom();
+}
+
+void MultiSelect::Inner::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+
+	auto ms = getms();
+	_height.step(ms);
+	_iconOpacity.step(ms);
+
+	auto paintRect = e->rect();
+	p.fillRect(paintRect, st::windowBg);
+
+	auto offset = QPoint(rtl() ? _st.padding.right() : _st.padding.left(), _st.padding.top());
+	p.translate(offset);
+	paintRect.translate(-offset);
+
+	auto outerWidth = width() - _st.padding.left() - _st.padding.right();
+	auto iconOpacity = _iconOpacity.current(_items.empty() ? 1. : 0.);
+	if (iconOpacity > 0.) {
+		p.setOpacity(iconOpacity);
+		_st.fieldIcon.paint(p, 0, 0, outerWidth);
+		p.setOpacity(1.);
+	}
+
+	auto checkRect = myrtlrect(paintRect);
+	auto paintMargins = itemPaintMargins();
+	for (auto i = _removingItems.begin(), e = _removingItems.end(); i != e;) {
+		auto item = *i;
+		auto itemRect = item->paintArea(outerWidth);
+		itemRect = itemRect.marginsAdded(paintMargins);
+		if (checkRect.intersects(itemRect)) {
+			item->paint(p, outerWidth, ms);
+		}
+		if (item->hideFinished()) {
+			i = _removingItems.erase(i);
+			e = _removingItems.end();
+		} else {
+			++i;
+		}
+	}
+	for_const (auto item, _items) {
+		auto itemRect = item->paintArea(outerWidth);
+		itemRect = itemRect.marginsAdded(paintMargins);
+		if (checkRect.y() + checkRect.height() <= itemRect.y()) {
+			break;
+		} else if (checkRect.intersects(itemRect)) {
+			item->paint(p, outerWidth, ms);
+		}
+	}
+}
+
+QMargins MultiSelect::Inner::itemPaintMargins() const {
+	return {
+		qMax(_st.itemSkip, _st.padding.left()),
+		_st.itemSkip,
+		qMax(_st.itemSkip, _st.padding.right()),
+		_st.itemSkip,
+	};
+}
+
+void MultiSelect::Inner::leaveEvent(QEvent *e) {
+	clearSelection();
+}
+
+void MultiSelect::Inner::mouseMoveEvent(QMouseEvent *e) {
+	updateSelection(e->pos());
+}
+
+void MultiSelect::Inner::keyPressEvent(QKeyEvent *e) {
+	if (_active >= 0) {
+		t_assert(_active < _items.size());
+		if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
+			auto itemId = _items[_active]->id();
+			setActiveItemNext();
+			removeItem(itemId);
+		} else if (e->key() == Qt::Key_Left) {
+			setActiveItemPrevious();
+		} else if (e->key() == Qt::Key_Right) {
+			setActiveItemNext();
+		} else if (e->key() == Qt::Key_Escape) {
+			setActiveItem(-1);
+		} else {
+			e->ignore();
+		}
+	} else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace) {
+		setActiveItemPrevious();
+	} else {
+		e->ignore();
+	}
+}
+
+void MultiSelect::Inner::onFieldFocused() {
+	setActiveItem(-1, ChangeActiveWay::SkipSetFocus);
+}
+
+void MultiSelect::Inner::updateSelection(QPoint mousePosition) {
+	auto point = myrtlpoint(mousePosition) - QPoint(_st.padding.left(), _st.padding.right());
+	auto selected = -1;
+	for (auto i = 0, size = _items.size(); i != size; ++i) {
+		auto itemRect = _items[i]->rect();
+		if (itemRect.y() > point.y()) {
+			break;
+		} else if (itemRect.contains(point)) {
+			point -= itemRect.topLeft();
+			selected = i;
+			break;
+		}
+	}
+	if (_selected != selected) {
+		if (_selected >= 0) {
+			t_assert(_selected < _items.size());
+			_items[_selected]->leaveEvent();
+		}
+		_selected = selected;
+		update();
+	}
+	auto overDelete = false;
+	if (_selected >= 0) {
+		_items[_selected]->mouseMoveEvent(point);
+		overDelete = _items[_selected]->isOverDelete();
+	}
+	if (_overDelete != overDelete) {
+		_overDelete = overDelete;
+		updateCursor();
+	}
+}
+
+void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) {
+	if (_overDelete) {
+		t_assert(_selected >= 0);
+		t_assert(_selected < _items.size());
+		removeItem(_items[_selected]->id());
+	} else if (_selected >= 0) {
+		setActiveItem(_selected);
+	} else {
+		setInnerFocus();
+	}
+}
+
+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());
+		itemRect = itemRect.translated(_st.padding.left(), _st.padding.top());
+		itemRect = itemRect.marginsAdded(itemPaintMargins());
+		rtlupdate(itemRect);
+	});
+	_items.push_back(item.release());
+	updateItemsGeometry();
+	if (wasEmpty) {
+		updateHasAnyItems(true);
+	}
+	if (way != AddItemWay::SkipAnimation) {
+		_items.back()->showAnimated();
+	} else {
+		_field->finishPlaceholderAnimation();
+		finishHeightAnimation();
+	}
+}
+
+void MultiSelect::Inner::computeItemsGeometry(int newWidth) {
+	newWidth -= _st.padding.left() + _st.padding.right();
+
+	auto itemLeft = 0;
+	auto itemTop = 0;
+	auto widthLeft = newWidth;
+	auto maxVisiblePadding = qMax(_st.padding.left(), _st.padding.right());
+	for_const (auto item, _items) {
+		auto itemWidth = item->getWidth();
+		t_assert(itemWidth <= newWidth);
+		if (itemWidth > widthLeft) {
+			itemLeft = 0;
+			itemTop += _st.item.height + _st.itemSkip;
+			widthLeft = newWidth;
+		}
+		item->setPosition(itemLeft, itemTop, newWidth, maxVisiblePadding);
+		itemLeft += itemWidth + _st.itemSkip;
+		widthLeft -= itemWidth + _st.itemSkip;
+	}
+
+	auto fieldMinWidth = _st.fieldMinWidth + _st.fieldCancelSkip;
+	t_assert(fieldMinWidth <= newWidth);
+	if (fieldMinWidth > widthLeft) {
+		_fieldLeft = 0;
+		_fieldTop = itemTop + _st.item.height + _st.itemSkip;
+	} else {
+		_fieldLeft = itemLeft + (_items.empty() ? _st.fieldIconSkip : 0);
+		_fieldTop = itemTop;
+	}
+	_fieldWidth = newWidth - _fieldLeft;
+}
+
+void MultiSelect::Inner::updateItemsGeometry() {
+	computeItemsGeometry(width());
+	updateFieldGeometry();
+	auto newHeight = resizeGetHeight(width());
+	if (newHeight == _newHeight) return;
+
+	_newHeight = newHeight;
+	_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);
+		}
+		update();
+	}
+}
+
+void MultiSelect::Inner::finishHeightAnimation() {
+	_height.finish();
+	updateHeightStep();
+}
+
+void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) {
+	for (int i = 0, count = _items.size(); i != count; ++i) {
+		auto item = _items[i];
+		if (item->id() == itemId) {
+			item->setText(text);
+			updateItemsGeometry();
+			return;
+		}
+	}
+}
+
+void MultiSelect::Inner::setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback) {
+	_itemRemovedCallback = std_::move(callback);
+}
+
+void MultiSelect::Inner::setResizedCallback(base::lambda_unique<void(int heightDelta)> callback) {
+	_resizedCallback = std_::move(callback);
+}
+
+void MultiSelect::Inner::removeItem(uint64 itemId) {
+	for (int i = 0, count = _items.size(); i != count; ++i) {
+		auto item = _items[i];
+		if (item->id() == itemId) {
+			clearSelection();
+			_items.removeAt(i);
+			if (_active == i) {
+				_active = -1;
+			} else if (_active > i) {
+				--_active;
+			}
+			_removingItems.insert(item);
+			item->hideAnimated();
+
+			updateItemsGeometry();
+			if (_items.empty()) {
+				updateHasAnyItems(false);
+			}
+			auto point = QCursor::pos();
+			if (auto parent = parentWidget()) {
+				if (parent->rect().contains(parent->mapFromGlobal(point))) {
+					updateSelection(mapFromGlobal(point));
+				}
+			}
+			break;
+		}
+	}
+	if (_itemRemovedCallback) {
+		_itemRemovedCallback(itemId);
+	}
+	setInnerFocus();
+}
+
+MultiSelect::Inner::~Inner() {
+	for (auto item : base::take(_items)) {
+		delete item;
+	}
+	for (auto item : base::take(_removingItems)) {
+		delete item;
+	}
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.h b/Telegram/SourceFiles/ui/widgets/multi_select.h
new file mode 100644
index 000000000..01854db33
--- /dev/null
+++ b/Telegram/SourceFiles/ui/widgets/multi_select.h
@@ -0,0 +1,169 @@
+/*
+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-2016 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+#include "styles/style_widgets.h"
+
+class InputField;
+
+namespace Ui {
+
+class IconButton;
+
+class MultiSelect : public TWidget {
+public:
+	MultiSelect(QWidget *parent, const style::MultiSelect &st, const QString &placeholder = QString());
+
+	QString getQuery() const;
+	void setInnerFocus();
+	void clearQuery();
+
+	void setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback);
+	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, AddItemWay way = AddItemWay::Default);
+	void setItemText(uint64 itemId, const QString &text);
+
+	void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback);
+	void removeItem(uint64 itemId);
+
+protected:
+	int resizeGetHeight(int newWidth) override;
+	bool eventFilter(QObject *o, QEvent *e) override;
+
+private:
+	void scrollTo(int activeTop, int activeBottom);
+
+	const style::MultiSelect &_st;
+
+	ChildWidget<ScrollArea> _scroll;
+
+	class Inner;
+	ChildWidget<Inner> _inner;
+
+	base::lambda_unique<void()> _resizedCallback;
+	base::lambda_unique<void(const QString &query)> _queryChangedCallback;
+
+};
+
+// This class is hold in header because it requires Qt preprocessing.
+class MultiSelect::Inner : public ScrolledWidget {
+	Q_OBJECT
+
+public:
+	using ScrollCallback = base::lambda_unique<void(int activeTop, int activeBottom)>;
+	Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback);
+
+	QString getQuery() const;
+	bool setInnerFocus();
+	void clearQuery();
+
+	void setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback);
+	void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
+
+	class 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);
+	void removeItem(uint64 itemId);
+
+	void setResizedCallback(base::lambda_unique<void(int heightDelta)> callback);
+
+	~Inner();
+
+protected:
+	int resizeGetHeight(int newWidth) override;
+
+	void paintEvent(QPaintEvent *e) override;
+	void leaveEvent(QEvent *e) override;
+	void mouseMoveEvent(QMouseEvent *e) override;
+	void mousePressEvent(QMouseEvent *e) override;
+	void keyPressEvent(QKeyEvent *e) override;
+
+private slots:
+	void onQueryChanged();
+	void onSubmitted(bool ctrlShiftEnter) {
+		if (_submittedCallback) {
+			_submittedCallback(ctrlShiftEnter);
+		}
+	}
+	void onFieldFocused();
+
+private:
+	void computeItemsGeometry(int newWidth);
+	void updateItemsGeometry();
+	void updateFieldGeometry();
+	void updateHasAnyItems(bool hasAnyItems);
+	void updateSelection(QPoint mousePosition);
+	void clearSelection() {
+		updateSelection(QPoint(-1, -1));
+	}
+	void updateCursor();
+	void updateHeightStep();
+	void finishHeightAnimation();
+	enum class ChangeActiveWay {
+		Default,
+		SkipSetFocus,
+	};
+	void setActiveItem(int active, ChangeActiveWay skipSetFocus = ChangeActiveWay::Default);
+	void setActiveItemPrevious();
+	void setActiveItemNext();
+
+	QMargins itemPaintMargins() const;
+
+	const style::MultiSelect &_st;
+	FloatAnimation _iconOpacity;
+
+	ScrollCallback _scrollCallback;
+
+	using Items = QList<Item*>;
+	Items _items;
+	using RemovingItems = OrderedSet<Item*>;
+	RemovingItems _removingItems;
+
+	int _selected = -1;
+	int _active = -1;
+	bool _overDelete = false;
+
+	int _fieldLeft = 0;
+	int _fieldTop = 0;
+	int _fieldWidth = 0;
+	ChildWidget<InputField> _field;
+	ChildWidget<Ui::IconButton> _cancel;
+
+	int _newHeight = 0;
+	IntAnimation _height;
+
+	base::lambda_unique<void(const QString &query)> _queryChangedCallback;
+	base::lambda_unique<void(bool ctrlShiftEnter)> _submittedCallback;
+	base::lambda_unique<void(uint64 itemId)> _itemRemovedCallback;
+	base::lambda_unique<void(int heightDelta)> _resizedCallback;
+
+};
+
+} // namespace Ui
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 3128f0521..d4ab8f7a1 100644
--- a/Telegram/SourceFiles/ui/widgets/widgets.style
+++ b/Telegram/SourceFiles/ui/widgets/widgets.style
@@ -54,6 +54,51 @@ FilledSlider {
 	duration: int;
 }
 
+RoundImageCheckbox {
+	imageRadius: pixels;
+	imageSmallRadius: pixels;
+	selectWidth: pixels;
+	selectFg: color;
+	selectDuration: int;
+	checkBorder: color;
+	checkBg: color;
+	checkRadius: pixels;
+	checkSmallRadius: pixels;
+	checkIcon: icon;
+}
+
+MultiSelectItem {
+	padding: margins;
+	maxWidth: pixels;
+	height: pixels;
+	font: font;
+	textBg: color;
+	textFg: color;
+	textActiveBg: color;
+	textActiveFg: color;
+	deleteFg: color;
+	deleteLeft: pixels;
+	deleteStroke: pixels;
+	duration: int;
+	minScale: double;
+}
+
+MultiSelect {
+	padding: margins;
+	maxHeight: pixels;
+	scroll: flatScroll;
+
+	item: MultiSelectItem;
+	itemSkip: pixels;
+
+	field: InputField;
+	fieldMinWidth: pixels;
+	fieldIcon: icon;
+	fieldIconSkip: pixels;
+	fieldCancel: IconButton;
+	fieldCancelSkip: pixels;
+}
+
 widgetSlideDuration: 200;
 
 discreteSliderHeight: 39px;
diff --git a/Telegram/SourceFiles/window/chat_background.cpp b/Telegram/SourceFiles/window/chat_background.cpp
index 294e53471..315ac129f 100644
--- a/Telegram/SourceFiles/window/chat_background.cpp
+++ b/Telegram/SourceFiles/window/chat_background.cpp
@@ -41,10 +41,9 @@ void ChatBackground::initIfEmpty() {
 	}
 }
 
-void ChatBackground::init(int32 id, QPixmap &&image, QPixmap &&dog) {
+void ChatBackground::init(int32 id, QPixmap &&image) {
 	_id = id;
 	_image = std_::move(image);
-	_dog = std_::move(dog);
 
 	notify(ChatBackgroundUpdate(ChatBackgroundUpdate::Type::New, _tile));
 }
@@ -52,7 +51,6 @@ void ChatBackground::init(int32 id, QPixmap &&image, QPixmap &&dog) {
 void ChatBackground::reset() {
 	_id = 0;
 	_image = QPixmap();
-	_dog = QPixmap();
 	_tile = false;
 
 	notify(ChatBackgroundUpdate(ChatBackgroundUpdate::Type::New, _tile));
@@ -66,10 +64,6 @@ const QPixmap &ChatBackground::image() const {
 	return _image;
 }
 
-const QPixmap &ChatBackground::dog() const {
-	return _dog;
-}
-
 bool ChatBackground::tile() const {
 	return _tile;
 }
diff --git a/Telegram/SourceFiles/window/chat_background.h b/Telegram/SourceFiles/window/chat_background.h
index 67a2bf72d..6840dbc2f 100644
--- a/Telegram/SourceFiles/window/chat_background.h
+++ b/Telegram/SourceFiles/window/chat_background.h
@@ -39,19 +39,17 @@ class ChatBackground : public base::Observable<ChatBackgroundUpdate> {
 public:
 	bool empty() const;
 	void initIfEmpty();
-	void init(int32 id, QPixmap &&image, QPixmap &&dog);
+	void init(int32 id, QPixmap &&image);
 	void reset();
 
 	int32 id() const;
 	const QPixmap &image() const;
-	const QPixmap &dog() const;
 	bool tile() const;
 	void setTile(bool tile);
 
 private:
 	int32 _id = 0;
 	QPixmap _image;
-	QPixmap _dog;
 	bool _tile = false;
 
 };
diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style
index 721d756c9..8722c4d3b 100644
--- a/Telegram/SourceFiles/window/window.style
+++ b/Telegram/SourceFiles/window/window.style
@@ -21,6 +21,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 
 using "basic.style";
 
+titleIconPosition: point(9px, 9px);
+titleIcon: icon {
+	{ "title_icon_bg", #49708f },
+	{ "title_icon", #ffffff, point(4px, 4px) }
+};
+titleCounterPosition: point(17px, 17px);
+
 notifyBg: white;
 notifyBorder: #f1f1f1;
 notifyBorderWidth: 1px;
diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp
index 5a49cd29b..f6609c10f 100644
--- a/Telegram/gyp/Telegram.gyp
+++ b/Telegram/gyp/Telegram.gyp
@@ -183,6 +183,8 @@
       '<(src_loc)/boxes/languagebox.h',
       '<(src_loc)/boxes/localstoragebox.cpp',
       '<(src_loc)/boxes/localstoragebox.h',
+      '<(src_loc)/boxes/members_box.cpp',
+      '<(src_loc)/boxes/members_box.h',
       '<(src_loc)/boxes/notifications_box.cpp',
       '<(src_loc)/boxes/notifications_box.h',
       '<(src_loc)/boxes/passcodebox.cpp',
@@ -199,6 +201,8 @@
       '<(src_loc)/boxes/sharebox.h',
       '<(src_loc)/boxes/stickersetbox.cpp',
       '<(src_loc)/boxes/stickersetbox.h',
+      '<(src_loc)/boxes/stickers_box.cpp',
+      '<(src_loc)/boxes/stickers_box.h',
       '<(src_loc)/boxes/usernamebox.cpp',
       '<(src_loc)/boxes/usernamebox.h',
       '<(src_loc)/core/basic_types.h',
@@ -455,6 +459,8 @@
       '<(src_loc)/ui/effects/radial_animation.h',
       '<(src_loc)/ui/effects/rect_shadow.cpp',
       '<(src_loc)/ui/effects/rect_shadow.h',
+      '<(src_loc)/ui/effects/round_image_checkbox.cpp',
+      '<(src_loc)/ui/effects/round_image_checkbox.h',
       '<(src_loc)/ui/style/style_core.cpp',
       '<(src_loc)/ui/style/style_core.h',
       '<(src_loc)/ui/style/style_core_color.cpp',
@@ -487,6 +493,8 @@
       '<(src_loc)/ui/widgets/label_simple.h',
       '<(src_loc)/ui/widgets/media_slider.cpp',
       '<(src_loc)/ui/widgets/media_slider.h',
+      '<(src_loc)/ui/widgets/multi_select.cpp',
+      '<(src_loc)/ui/widgets/multi_select.h',
       '<(src_loc)/ui/widgets/shadow.cpp',
       '<(src_loc)/ui/widgets/shadow.h',
       '<(src_loc)/ui/widgets/widget_slide_wrap.h',