diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 820e9a1e8..119c3414e 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -49,25 +49,6 @@ confirmInviteUserName: flatLabel(labelDefFlat) {
 }
 confirmInviteUserNameTop: 227px;
 
-stickersAddIcon: icon {
-	{ "stickers_add", #ffffff },
-};
-stickersAddSize: size(30px, 24px);
-
-stickersFeaturedHeight: 32px;
-stickersFeaturedFont: contactsNameFont;
-stickersFeaturedPosition: point(16px, 6px);
-stickersFeaturedBadgeFont: semiboldFont;
-stickersFeaturedBadgeSize: 21px;
-stickersFeaturedPen: contactsNewItemFg;
-stickersFeaturedUnreadBg: msgFileInBg;
-stickersFeaturedUnreadSize: 5px;
-stickersFeaturedUnreadSkip: 5px;
-stickersFeaturedUnreadTop: 7px;
-stickersFeaturedInstalled: icon {
-	{ "mediaview_save_check", #40ace3 }
-};
-
 confirmPhoneAboutLabel: flatLabel(labelDefFlat) {
 	width: 282px;
 }
diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp
index f3d50db54..5f094e795 100644
--- a/Telegram/SourceFiles/boxes/stickersetbox.cpp
+++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp
@@ -30,6 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "localstorage.h"
 #include "dialogs/dialogs_layout.h"
 #include "styles/style_boxes.h"
+#include "styles/style_stickers.h"
 
 namespace {
 
@@ -423,7 +424,7 @@ void StickerSetBox::resizeEvent(QResizeEvent *e) {
 
 namespace internal {
 
-StickersInner::StickersInner(StickersBox::Section section) : TWidget()
+StickersInner::StickersInner(StickersBox::Section section) : ScrolledWidget()
 , _section(section)
 , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
 , _a_shifting(animation(this, &StickersInner::step_shifting))
@@ -436,7 +437,7 @@ StickersInner::StickersInner(StickersBox::Section section) : TWidget()
 	setup();
 }
 
-StickersInner::StickersInner(const Stickers::Order &archivedIds) : TWidget()
+StickersInner::StickersInner(const Stickers::Order &archivedIds) : ScrolledWidget()
 , _section(StickersBox::Section::ArchivedPart)
 , _archivedIds(archivedIds)
 , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
@@ -451,10 +452,15 @@ StickersInner::StickersInner(const Stickers::Order &archivedIds) : TWidget()
 }
 
 void StickersInner::setup() {
-	connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update()));
+	connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(onImageLoaded()));
 	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);
@@ -587,18 +593,18 @@ void StickersInner::paintRow(Painter &p, int32 index) {
 	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, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
+		p.drawEllipse(rtlrect(namex + s->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
 		p.setRenderHint(QPainter::HighQualityAntialiasing, false);
-		namex += st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip;
 	}
-	p.setFont(st::contactsNameFont);
-	p.setPen(st::black);
-	p.drawTextLeft(namex, namey, width(), s->title);
 
 	p.setFont(st::contactsStatusFont);
 	p.setPen(st::contactsStatusFg);
@@ -967,19 +973,6 @@ void StickersInner::rebuild() {
 	}
 	App::api()->requestStickerSets();
 	updateSize();
-
-	if (_section == Section::Featured && Global::FeaturedStickerSetsUnreadCount()) {
-		Global::SetFeaturedStickerSetsUnreadCount(0);
-		QVector<MTPlong> readIds;
-		readIds.reserve(Global::StickerSets().size());
-		for (auto &set : Global::RefStickerSets()) {
-			if (set.flags & MTPDstickerSet_ClientFlag::f_unread) {
-				set.flags &= ~MTPDstickerSet_ClientFlag::f_unread;
-				readIds.push_back(MTP_long(set.id));
-			}
-		}
-		MTP::send(MTPmessages_ReadFeaturedStickers(MTP_vector<MTPlong>(readIds)), rpcDone(&StickersInner::readFeaturedDone), rpcFail(&StickersInner::readFeaturedFail));
-	}
 }
 
 void StickersInner::updateSize() {
@@ -1007,7 +1000,7 @@ void StickersInner::updateRows() {
 			if (_section == Section::Installed) {
 				row->disabled = false;
 			}
-			row->title = fillSetTitle(set, maxNameWidth);
+			row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth);
 			row->count = fillSetCount(set);
 		}
 	}
@@ -1031,6 +1024,7 @@ int StickersInner::countMaxNameWidth() const {
 		namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth);
 	} else {
 		namew -= st::stickersAddIcon.width() - st::defaultActiveButton.width;
+		namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip;
 	}
 	return namew;
 }
@@ -1046,10 +1040,11 @@ void StickersInner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth)
 	int pixw = 0, pixh = 0;
 	fillSetCover(set, &sticker, &pixw, &pixh);
 
-	QString title = fillSetTitle(set, maxNameWidth);
+	int titleWidth = 0;
+	QString title = fillSetTitle(set, maxNameWidth, &titleWidth);
 	int count = fillSetCount(set);
 
-	_rows.push_back(new StickerSetRow(set.id, sticker, count, title, installed, official, unread, disabled, recent, pixw, pixh));
+	_rows.push_back(new StickerSetRow(set.id, sticker, count, title, titleWidth, installed, official, unread, disabled, recent, pixw, pixh));
 	_animStartTimes.push_back(0);
 }
 
@@ -1097,11 +1092,15 @@ int StickersInner::fillSetCount(const Stickers::Set &set) const {
 	return result + added;
 }
 
-QString StickersInner::fillSetTitle(const Stickers::Set &set, int maxNameWidth) const {
+QString StickersInner::fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const {
 	auto result = set.title;
-	int32 titleWidth = st::contactsNameFont->width(result);
+	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;
 }
@@ -1117,35 +1116,11 @@ void StickersInner::fillSetFlags(const Stickers::Set &set, bool *outRecent, bool
 		*outOfficial = (set.flags & MTPDstickerSet::Flag::f_official);
 		*outDisabled = (set.flags & MTPDstickerSet::Flag::f_archived);
 		if (_section == Section::Featured) {
-			*outUnread = _unreadSets.contains(set.id);
-			if (!*outUnread && (set.flags & MTPDstickerSet_ClientFlag::f_unread)) {
-				*outUnread = true;
-				_unreadSets.insert(set.id);
-			}
+			*outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread);
 		}
 	}
 }
 
-void StickersInner::readFeaturedDone(const MTPBool &result) {
-	Local::writeFeaturedStickers();
-	emit App::main()->stickersUpdated();
-}
-
-bool StickersInner::readFeaturedFail(const RPCError &error) {
-	if (MTP::isDefaultHandledError(error)) return false;
-
-	int unreadCount = 0;
-	for_const (auto &set, Global::StickerSets()) {
-		if (!(set.flags & MTPDstickerSet::Flag::f_installed)) {
-			if (set.flags & MTPDstickerSet_ClientFlag::f_unread) {
-				++unreadCount;
-			}
-		}
-	}
-	Global::SetFeaturedStickerSetsUnreadCount(unreadCount);
-	return true;
-}
-
 Stickers::Order StickersInner::getOrder() const {
 	Stickers::Order result;
 	result.reserve(_rows.size());
@@ -1169,6 +1144,32 @@ Stickers::Order StickersInner::getDisabledSets() const {
 	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;
 }
@@ -1329,9 +1330,16 @@ void StickersBox::setup() {
 }
 
 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;
 
@@ -1456,6 +1464,7 @@ 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);
 	}
diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h
index ef73191b2..99bc272b9 100644
--- a/Telegram/SourceFiles/boxes/stickersetbox.h
+++ b/Telegram/SourceFiles/boxes/stickersetbox.h
@@ -28,7 +28,6 @@ class StickerSetInner : public TWidget, public RPCSender {
 	Q_OBJECT
 
 public:
-
 	StickerSetInner(const MTPInputStickerSet &set);
 
 	void mousePressEvent(QMouseEvent *e);
@@ -49,16 +48,13 @@ public:
 	~StickerSetInner();
 
 public slots:
-
 	void onPreview();
 
 signals:
-
 	void updateButtons();
 	void installed(uint64 id);
 
 private:
-
 	int32 stickerFromGlobalPos(const QPoint &p) const;
 
 	void gotSet(const MTPmessages_StickerSet &set);
@@ -169,6 +165,7 @@ private:
 	bool reorderFail(const RPCError &result);
 	void saveOrder();
 
+	void updateVisibleTopBottom();
 	void checkLoadMoreArchived();
 	void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result);
 
@@ -198,7 +195,7 @@ int32 stickerPacksCount(bool includeDisabledOfficial = false);
 
 namespace internal {
 
-class StickersInner : public TWidget, public RPCSender {
+class StickersInner : public ScrolledWidget, public RPCSender {
 	Q_OBJECT
 
 public:
@@ -220,6 +217,7 @@ public:
 	Stickers::Order getDisabledSets() const;
 
 	void setVisibleScrollbar(int32 width);
+	void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
 
 	~StickersInner();
 
@@ -239,6 +237,9 @@ public slots:
 	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;
@@ -249,21 +250,22 @@ private:
 	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);
-	void readFeaturedDone(const MTPBool &result);
-	bool readFeaturedFail(const RPCError &error);
 
 	Section _section;
 	Stickers::Order _archivedIds;
 
 	int32 _rowHeight;
 	struct StickerSetRow {
-		StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id)
+		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)
@@ -277,6 +279,7 @@ private:
 		DocumentData *sticker;
 		int32 count;
 		QString title;
+		int titleWidth;
 		bool installed, official, unread, disabled, recent;
 		int32 pixw, pixh;
 		anim::ivalue yadd;
@@ -286,7 +289,7 @@ private:
 	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) 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;
@@ -297,7 +300,9 @@ private:
 	anim::fvalue _aboveShadowFadeOpacity = { 0., 0. };
 	Animation _a_shifting;
 
-	int32 _itemsTop;
+	int _visibleTop = 0;
+	int _visibleBottom = 0;
+	int _itemsTop = 0;
 
 	bool _saving = false;
 
@@ -312,9 +317,6 @@ private:
 	bool _hasFeaturedButton = false;
 	bool _hasArchivedButton = false;
 
-	// Remember all the unread set ids to display unread dots.
-	OrderedSet<uint64> _unreadSets;
-
 	QPoint _mouse;
 	int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button
 	int _pressed = -2;
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index f83fdb289..604c6c204 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -3836,14 +3836,14 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic
 	_featuredStickersUpdateRequest = 0;
 
 	if (stickers.type() != mtpc_messages_featuredStickers) return;
-	auto &d(stickers.c_messages_featuredStickers());
+	auto &d = stickers.c_messages_featuredStickers();
 
 	OrderedSet<uint64> unread;
 	for_const (auto &unreadSetId, d.vunread.c_vector().v) {
 		unread.insert(unreadSetId.v);
 	}
 
-	auto &d_sets(d.vsets.c_vector().v);
+	auto &d_sets = d.vsets.c_vector().v;
 
 	auto &setsOrder = Global::RefFeaturedStickerSetsOrder();
 	setsOrder.clear();
diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp
index 1d3b2082a..722a387bf 100644
--- a/Telegram/SourceFiles/layerwidget.cpp
+++ b/Telegram/SourceFiles/layerwidget.cpp
@@ -27,6 +27,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "mainwindow.h"
 #include "mainwidget.h"
 #include "ui/filedialog.h"
+#include "styles/style_stickers.h"
+
+namespace {
+
+constexpr int kStickerPreviewEmojiLimit = 10;
+
+} // namespace
 
 void LayerWidget::setInnerFocus() {
 	auto focused = App::wnd()->focusWidget();
@@ -339,6 +346,7 @@ void LayerStackWidget::activateLayer(LayerWidget *l) {
 		startShow();
 	} else {
 		l->show();
+		l->showDone();
 		if (App::wnd()) App::wnd()->setInnerFocus();
 		updateLayerBox();
 	}
@@ -426,7 +434,8 @@ LayerStackWidget::~LayerStackWidget() {
 
 MediaPreviewWidget::MediaPreviewWidget(QWidget *parent) : TWidget(parent)
 , a_shown(0, 0)
-, _a_shown(animation(this, &MediaPreviewWidget::step_shown)) {
+, _a_shown(animation(this, &MediaPreviewWidget::step_shown))
+, _emojiSize(EmojiSizes[EIndex + 1] / cIntRetinaFactor()) {
 	setAttribute(Qt::WA_TransparentForMouseEvents);
 	connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update()));
 }
@@ -435,8 +444,8 @@ void MediaPreviewWidget::paintEvent(QPaintEvent *e) {
 	Painter p(this);
 	QRect r(e->rect());
 
-	const QPixmap &draw(currentImage());
-	int w = draw.width() / cIntRetinaFactor(), h = draw.height() / cIntRetinaFactor();
+	auto &image = currentImage();
+	int w = image.width() / cIntRetinaFactor(), h = image.height() / cIntRetinaFactor();
 	if (_a_shown.animating()) {
 		float64 shown = a_shown.current();
 		p.setOpacity(shown);
@@ -444,7 +453,17 @@ void MediaPreviewWidget::paintEvent(QPaintEvent *e) {
 //		h = qMax(qRound(h * (st::stickerPreviewMin + ((1. - st::stickerPreviewMin) * shown)) / 2.) * 2 + int(h % 2), 1);
 	}
 	p.fillRect(r, st::stickerPreviewBg);
-	p.drawPixmap((width() - w) / 2, (height() - h) / 2, draw);
+	p.drawPixmap((width() - w) / 2, (height() - h) / 2, image);
+	if (!_emojiList.isEmpty()) {
+		int emojiCount = _emojiList.size();
+		int emojiWidth = emojiCount * _emojiSize + (emojiCount - 1) * st::stickerEmojiSkip;
+		int emojiLeft = (width() - emojiWidth) / 2;
+		int esize = _emojiSize * cIntRetinaFactor();
+		for_const (auto emoji, _emojiList) {
+			p.drawPixmapLeft(emojiLeft, (height() - h) / 2 - _emojiSize * 2, width(), App::emojiLarge(), QRect(emoji->x * esize, emoji->y * esize, esize, esize));
+			emojiLeft += _emojiSize + st::stickerEmojiSkip;
+		}
+	}
 }
 
 void MediaPreviewWidget::resizeEvent(QResizeEvent *e) {
@@ -472,6 +491,7 @@ void MediaPreviewWidget::showPreview(DocumentData *document) {
 	startShow();
 	_photo = nullptr;
 	_document = document;
+	fillEmojiString();
 	resetGifAndCache();
 }
 
@@ -510,6 +530,42 @@ void MediaPreviewWidget::hidePreview() {
 	resetGifAndCache();
 }
 
+void MediaPreviewWidget::fillEmojiString() {
+	auto getStickerEmojiList = [this](uint64 setId) {
+		QList<EmojiPtr> result;
+		auto &sets = Global::StickerSets();
+		auto it = sets.constFind(setId);
+		if (it == sets.cend()) {
+			return result;
+		}
+		for (auto i = it->emoji.cbegin(), e = it->emoji.cend(); i != e; ++i) {
+			for_const (auto document, *i) {
+				if (document == _document) {
+					result.append(i.key());
+					if (result.size() >= kStickerPreviewEmojiLimit) {
+						return result;
+					}
+				}
+			}
+		}
+		return result;
+	};
+
+	if (auto sticker = _document->sticker()) {
+		auto &inputSet = sticker->set;
+		if (inputSet.type() == mtpc_inputStickerSetID) {
+			_emojiList = getStickerEmojiList(inputSet.c_inputStickerSetID().vid.v);
+		} else {
+			_emojiList.clear();
+			if (auto emoji = emojiFromText(sticker->alt)) {
+				_emojiList.append(emoji);
+			}
+		}
+	} else {
+		_emojiList.clear();
+	}
+}
+
 void MediaPreviewWidget::resetGifAndCache() {
 	if (_gif) {
 		if (gif()) {
diff --git a/Telegram/SourceFiles/layerwidget.h b/Telegram/SourceFiles/layerwidget.h
index ffba58d3f..14d1fbc84 100644
--- a/Telegram/SourceFiles/layerwidget.h
+++ b/Telegram/SourceFiles/layerwidget.h
@@ -133,7 +133,6 @@ class MediaPreviewWidget : public TWidget {
 	Q_OBJECT
 
 public:
-
 	MediaPreviewWidget(QWidget *parent);
 
 	void paintEvent(QPaintEvent *e);
@@ -148,10 +147,10 @@ public:
 	~MediaPreviewWidget();
 
 private:
-
 	QSize currentDimensions() const;
 	QPixmap currentImage() const;
 	void startShow();
+	void fillEmojiString();
 	void resetGifAndCache();
 
 	anim::fvalue a_shown;
@@ -163,6 +162,9 @@ private:
 		return (!_gif || _gif == Media::Clip::BadReader) ? false : true;
 	}
 
+	int _emojiSize;
+	QList<EmojiPtr> _emojiList;
+
 	void clipCallback(Media::Clip::Notification notification);
 
 	enum CacheStatus {
diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp
index b662396c9..066004bc0 100644
--- a/Telegram/SourceFiles/localstorage.cpp
+++ b/Telegram/SourceFiles/localstorage.cpp
@@ -3215,6 +3215,7 @@ namespace Local {
 				it = sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags)));
 			}
 			auto &set = it.value();
+			auto inputSet = MTP_inputStickerSetID(MTP_long(set.id), MTP_long(set.access));
 
 			if (scnt < 0) { // disabled not loaded set
 				if (!set.count || set.stickers.isEmpty()) {
@@ -3240,6 +3241,11 @@ namespace Local {
 
 				if (fillStickers) {
 					set.stickers.push_back(document);
+					if (!(set.flags & MTPDstickerSet_ClientFlag::f_special)) {
+						if (document->sticker()->set.type() != mtpc_inputStickerSetID) {
+							document->sticker()->set = inputSet;
+						}
+					}
 					++set.count;
 				}
 			}
@@ -3287,6 +3293,8 @@ namespace Local {
 	}
 
 	void writeInstalledStickers() {
+		if (!Global::started()) return;
+
 		_writeStickerSets(_installedStickersKey, [](const Stickers::Set &set) {
 			if (set.id == Stickers::CloudRecentSetId) { // separate file for recent
 				return StickerSetCheckResult::Skip;
@@ -3306,6 +3314,8 @@ namespace Local {
 	}
 
 	void writeFeaturedStickers() {
+		if (!Global::started()) return;
+
 		_writeStickerSets(_featuredStickersKey, [](const Stickers::Set &set) {
 			if (set.id == Stickers::CloudRecentSetId) { // separate file for recent
 				return StickerSetCheckResult::Skip;
@@ -3323,6 +3333,8 @@ namespace Local {
 	}
 
 	void writeRecentStickers() {
+		if (!Global::started()) return;
+
 		_writeStickerSets(_recentStickersKey, [](const Stickers::Set &set) {
 			if (set.id != Stickers::CloudRecentSetId || set.stickers.isEmpty()) {
 				return StickerSetCheckResult::Skip;
@@ -3332,6 +3344,8 @@ namespace Local {
 	}
 
 	void writeArchivedStickers() {
+		if (!Global::started()) return;
+
 		_writeStickerSets(_archivedStickersKey, [](const Stickers::Set &set) {
 			if (!(set.flags & MTPDstickerSet::Flag::f_archived) || set.stickers.isEmpty()) {
 				return StickerSetCheckResult::Skip;
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 8feba33bc..cde8d0048 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -4691,15 +4691,18 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 						writeArchived = true;
 					}
 				}
-
-				const auto &v(set.vdocuments.c_vector().v);
+				auto inputSet = MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access));
+				auto &v = set.vdocuments.c_vector().v;
 				it->stickers.clear();
 				it->stickers.reserve(v.size());
 				for (int32 i = 0, l = v.size(); i < l; ++i) {
-					DocumentData *doc = App::feedDocument(v.at(i));
+					auto doc = App::feedDocument(v.at(i));
 					if (!doc || !doc->sticker()) continue;
 
 					it->stickers.push_back(doc);
+					if (doc->sticker()->set.type() != mtpc_inputStickerSetID) {
+						doc->sticker()->set = inputSet;
+					}
 				}
 				it->emoji.clear();
 				auto &packs = set.vpacks.c_vector().v;
@@ -4780,16 +4783,11 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 	} break;
 
 	case mtpc_updateReadFeaturedStickers: {
-		for (auto &set : Global::RefStickerSets()) {
-			if (set.flags & MTPDstickerSet_ClientFlag::f_unread) {
-				set.flags &= ~MTPDstickerSet_ClientFlag::f_unread;
-			}
-		}
-		if (Global::FeaturedStickerSetsUnreadCount()) {
-			Global::SetFeaturedStickerSetsUnreadCount(0);
-			Local::writeFeaturedStickers();
-			emit stickersUpdated();
-		}
+		// We read some of the featured stickers, perhaps not all of them.
+		// Here we don't know what featured sticker sets were read, so we
+		// request all of them once again.
+		Global::SetLastFeaturedStickersUpdate(0);
+		App::main()->updateStickers();
 	} break;
 
 	////// Cloud saved GIFs
diff --git a/Telegram/SourceFiles/stickers/emoji_pan.cpp b/Telegram/SourceFiles/stickers/emoji_pan.cpp
index f84cc454b..a39f1e194 100644
--- a/Telegram/SourceFiles/stickers/emoji_pan.cpp
+++ b/Telegram/SourceFiles/stickers/emoji_pan.cpp
@@ -287,7 +287,7 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) {
 	p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x * esize, _variants[variant]->y * esize, esize, esize));
 }
 
-EmojiPanInner::EmojiPanInner() : TWidget()
+EmojiPanInner::EmojiPanInner() : ScrolledWidget()
 , _maxHeight(int(st::emojiPanMaxHeight) - st::rbEmoji.height)
 , _a_selected(animation(this, &EmojiPanInner::step_selected)) {
 	resize(st::emojiPanWidth - st::emojiScroll.width, countHeight());
@@ -316,8 +316,9 @@ void EmojiPanInner::setMaxHeight(int32 h) {
 	resize(st::emojiPanWidth - st::emojiScroll.width, countHeight());
 }
 
-void EmojiPanInner::setScrollTop(int top) {
-	_top = top;
+void EmojiPanInner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
+	_visibleTop = visibleTop;
+	_visibleBottom = visibleBottom;
 }
 
 int EmojiPanInner::countHeight() {
@@ -519,7 +520,7 @@ void EmojiPanInner::onShowPicker() {
 			int32 size = (c == tab) ? (sel - (sel % EmojiPanPerRow)) : _counts[c], rows = (size / EmojiPanPerRow) + ((size % EmojiPanPerRow) ? 1 : 0);
 			y += st::emojiPanHeader + (rows * st::emojiPanSize.height());
 		}
-		y -= _picker.height() - st::buttonRadius + _top;
+		y -= _picker.height() - st::buttonRadius + _visibleTop;
 		if (y < 0) {
 			y += _picker.height() - st::buttonRadius + st::emojiPanSize.height() - st::buttonRadius;
 		}
@@ -788,7 +789,7 @@ void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) {
 	update();
 }
 
-StickerPanInner::StickerPanInner() : TWidget()
+StickerPanInner::StickerPanInner() : ScrolledWidget()
 , _a_selected(animation(this, &StickerPanInner::step_selected))
 , _section(cShowingSavedGifs() ? Section::Gifs : Section::Stickers)
 , _addText(lang(lng_stickers_featured_add).toUpper())
@@ -800,7 +801,7 @@ StickerPanInner::StickerPanInner() : TWidget()
 	setFocusPolicy(Qt::NoFocus);
 	setAttribute(Qt::WA_OpaquePaintEvent);
 
-	connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update()));
+	connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(onImageLoaded()));
 	connect(&_settings, SIGNAL(clicked()), this, SLOT(onSettings()));
 
 	_previewTimer.setSingleShot(true);
@@ -816,11 +817,47 @@ void StickerPanInner::setMaxHeight(int32 h) {
 	_settings.moveToLeft((st::emojiPanWidth - _settings.width()) / 2, height() / 3);
 }
 
-void StickerPanInner::setScrollTop(int top) {
-	if (top == _top) return;
+void StickerPanInner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
+	_visibleBottom = visibleBottom;
+	if (_visibleTop != visibleTop) {
+		_visibleTop = visibleTop;
+		_lastScrolled = getms();
+	}
+	if (_section == Section::Featured) {
+		readVisibleSets();
+	}
+}
 
-	_lastScrolled = getms();
-	_top = top;
+void StickerPanInner::readVisibleSets() {
+	auto itemsVisibleTop = _visibleTop - st::emojiPanHeader;
+	auto itemsVisibleBottom = _visibleBottom - st::emojiPanHeader;
+	auto rowHeight = featuredRowHeight();
+	int rowFrom = floorclamp(itemsVisibleTop, rowHeight, 0, _featuredSets.size());
+	int rowTo = ceilclamp(itemsVisibleBottom, rowHeight, 0, _featuredSets.size());
+	for (int i = rowFrom; i < rowTo; ++i) {
+		auto &set = _featuredSets[i];
+		if (!(set.flags & MTPDstickerSet_ClientFlag::f_unread)) {
+			continue;
+		}
+		if (i * rowHeight < itemsVisibleTop || (i + 1) * rowHeight > itemsVisibleBottom) {
+			continue;
+		}
+		int count = qMin(set.pack.size(), static_cast<int>(StickerPanPerRow));
+		int loaded = 0;
+		for (int j = 0; j < count; ++j) {
+			if (set.pack[j]->thumb->loaded() || set.pack[j]->loaded()) {
+				++loaded;
+			}
+		}
+		if (loaded == count) {
+			Stickers::markFeaturedAsRead(set.id);
+		}
+	}
+}
+
+void StickerPanInner::onImageLoaded() {
+	update();
+	readVisibleSets();
 }
 
 int StickerPanInner::featuredRowHeight() const {
@@ -973,6 +1010,9 @@ void StickerPanInner::paintStickers(Painter &p, const QRect &r) {
 
 				widthForTitle -= add.width() - (st::featuredStickersAdd.width / 2);
 			}
+			if (set.flags & MTPDstickerSet_ClientFlag::f_unread) {
+				widthForTitle -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip;
+			}
 
 			auto titleText = set.title;
 			auto titleWidth = st::featuredStickersHeaderFont->width(titleText);
@@ -984,6 +1024,15 @@ void StickerPanInner::paintStickers(Painter &p, const QRect &r) {
 			p.setPen(st::featuredStickersHeaderFg);
 			p.drawTextLeft(st::emojiPanHeaderLeft, y + st::featuredStickersHeaderTop, width(), titleText, titleWidth);
 
+			if (set.flags & MTPDstickerSet_ClientFlag::f_unread) {
+				p.setPen(Qt::NoPen);
+				p.setBrush(st::stickersFeaturedUnreadBg);
+
+				p.setRenderHint(QPainter::HighQualityAntialiasing, true);
+				p.drawEllipse(rtlrect(st::emojiPanHeaderLeft + titleWidth + st::stickersFeaturedUnreadSkip, y + st::featuredStickersHeaderTop + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
+				p.setRenderHint(QPainter::HighQualityAntialiasing, false);
+			}
+
 			p.setFont(st::featuredStickersSubheaderFont);
 			p.setPen(st::featuredStickersSubheaderFg);
 			p.drawTextLeft(st::emojiPanHeaderLeft, y + st::featuredStickersSubheaderTop, width(), lng_stickers_count(lt_count, size));
@@ -1246,7 +1295,6 @@ bool StickerPanInner::showSectionIcons() const {
 }
 
 void StickerPanInner::clearSelection(bool fast) {
-	_lastMousePos = mapToGlobal(QPoint(-10, -10));
 	if (fast) {
 		if (showingInlineItems()) {
 			if (_selected >= 0) {
@@ -1287,7 +1335,10 @@ void StickerPanInner::clearSelection(bool fast) {
 		_a_selected.stop();
 		update();
 	} else {
+		auto pos = _lastMousePos;
+		_lastMousePos = mapToGlobal(QPoint(-10, -10));
 		updateSelected();
+		_lastMousePos = pos;
 	}
 }
 
@@ -1324,35 +1375,62 @@ void StickerPanInner::hideFinish(bool completely) {
 }
 
 void StickerPanInner::refreshStickers() {
-	clearSelection(true);
+	auto stickersShown = (_section == Section::Stickers || _section == Section::Featured);
+	if (stickersShown) {
+		clearSelection(true);
+	}
 
 	_mySets.clear();
 	_mySets.reserve(Global::StickerSetsOrder().size() + 1);
 
 	refreshRecentStickers(false);
 	for_const (auto setId, Global::StickerSetsOrder()) {
-		appendSet(_mySets, setId);
+		appendSet(_mySets, setId, AppendSkip::Archived);
 	}
 
 	_featuredSets.clear();
 	_featuredSets.reserve(Global::FeaturedStickerSetsOrder().size());
 
 	for_const (auto setId, Global::FeaturedStickerSetsOrder()) {
-		appendSet(_featuredSets, setId);
+		appendSet(_featuredSets, setId, AppendSkip::Installed);
 	}
 
-	if (_section == Section::Stickers) {
+	if (stickersShown) {
 		int h = countHeight();
 		if (h != height()) resize(width(), h);
 
-		_settings.setVisible(_mySets.isEmpty());
+		_settings.setVisible(_section == Section::Stickers && _mySets.isEmpty());
 	} else {
 		_settings.hide();
 	}
 
 	emit refreshIcons();
 
-	updateSelected();
+	// Hack: skip over animations to the very end,
+	// so that currently selected sticker won't get
+	// blinking background when refreshing stickers.
+	if (stickersShown) {
+		updateSelected();
+		int sel = _selected, tab = sel / MatrixRowShift, xsel = -1;
+		if (sel >= 0) {
+			auto &sets = shownSets();
+			if (tab < sets.size() && sets[tab].id == Stickers::RecentSetId && sel >= tab * MatrixRowShift + sets[tab].pack.size()) {
+				xsel = sel;
+				sel -= sets[tab].pack.size();
+			}
+			auto i = _animations.find(sel + 1);
+			if (i != _animations.cend()) {
+				i.value() = (i.value() >= st::emojiPanDuration) ? (i.value() - st::emojiPanDuration) : 0;
+			}
+			if (xsel >= 0) {
+				auto j = _animations.find(xsel + 1);
+				if (j != _animations.cend()) {
+					j.value() = (j.value() >= st::emojiPanDuration) ? (j.value() - st::emojiPanDuration) : 0;
+				}
+			}
+			step_selected(getms(), true);
+		}
+	}
 }
 
 bool StickerPanInner::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth) {
@@ -1790,17 +1868,19 @@ bool StickerPanInner::ui_isInlineItemVisible(const InlineItem *layout) {
 		top += _inlineRows.at(i).height;
 	}
 
-	return (top < _top + _maxHeight) && (top + _inlineRows.at(row).items.at(col)->height() > _top);
+	return (top < _visibleTop + _maxHeight) && (top + _inlineRows[row].items[col]->height() > _visibleTop);
 }
 
 bool StickerPanInner::ui_isInlineItemBeingChosen() {
 	return showingInlineItems();
 }
 
-void StickerPanInner::appendSet(Sets &to, uint64 setId) {
+void StickerPanInner::appendSet(Sets &to, uint64 setId, AppendSkip skip) {
 	auto &sets = Global::StickerSets();
 	auto it = sets.constFind(setId);
-	if (it == sets.cend() || (it->flags & MTPDstickerSet::Flag::f_archived) || it->stickers.isEmpty()) return;
+	if (it == sets.cend() || it->stickers.isEmpty()) return;
+	if ((skip == AppendSkip::Archived) && (it->flags & MTPDstickerSet::Flag::f_archived)) return;
+	if ((skip == AppendSkip::Installed) && (it->flags & MTPDstickerSet::Flag::f_installed) && !(it->flags & MTPDstickerSet::Flag::f_archived)) return;
 
 	to.push_back(Set(it->id, it->flags, it->title, it->stickers.size() + 1, it->stickers));
 }
@@ -1879,7 +1959,7 @@ void StickerPanInner::fillIcons(QList<StickerIcon> &icons) {
 	if (!cSavedGifs().isEmpty()) {
 		icons.push_back(StickerIcon(Stickers::NoneSetId));
 	}
-	if (Global::FeaturedStickerSetsUnreadCount()) {
+	if (Global::FeaturedStickerSetsUnreadCount() && !_featuredSets.isEmpty()) {
 		icons.push_back(StickerIcon(Stickers::FeaturedSetId));
 	}
 
@@ -1906,7 +1986,7 @@ void StickerPanInner::fillIcons(QList<StickerIcon> &icons) {
 		}
 	}
 
-	if (!Global::FeaturedStickerSetsUnreadCount() && !Global::FeaturedStickerSetsOrder().empty()) {
+	if (!Global::FeaturedStickerSetsUnreadCount() && !_featuredSets.isEmpty()) {
 		icons.push_back(StickerIcon(Stickers::FeaturedSetId));
 	}
 }
@@ -2671,14 +2751,14 @@ void EmojiPan::paintEvent(QPaintEvent *e) {
 							p.drawPixmapLeft(x + (st::rbEmoji.width - s.pixw) / 2, _iconsTop + (st::rbEmoji.height - s.pixh) / 2, width(), pix);
 							x += st::rbEmoji.width;
 						} else {
-							if (selxrel != x) {
+							if (true || selxrel != x) {
 								p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), getSpecialSetIcon(s.setId, false));
 							}
-							if (selxrel < x + st::rbEmoji.width && selxrel > x - st::rbEmoji.width) {
-								p.setOpacity(1 - (qAbs(selxrel - x) / float64(st::rbEmoji.width)));
-								p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), getSpecialSetIcon(s.setId, true));
-								p.setOpacity(1);
-							}
+							//if (selxrel < x + st::rbEmoji.width && selxrel > x - st::rbEmoji.width) {
+							//	p.setOpacity(1 - (qAbs(selxrel - x) / float64(st::rbEmoji.width)));
+							//	p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), getSpecialSetIcon(s.setId, true));
+							//	p.setOpacity(1);
+							//}
 							if (s.setId == Stickers::FeaturedSetId) {
 								paintFeaturedStickerSetsBadge(p, x);
 							}
@@ -3330,7 +3410,7 @@ void EmojiPan::onScrollEmoji() {
 		_noTabUpdate = false;
 	}
 
-	e_inner.setScrollTop(st);
+	e_inner.setVisibleTopBottom(st, st + e_scroll.height());
 }
 
 void EmojiPan::onScrollStickers() {
@@ -3343,7 +3423,7 @@ void EmojiPan::onScrollStickers() {
 		onInlineRequest();
 	}
 
-	s_inner.setScrollTop(st);
+	s_inner.setVisibleTopBottom(st, st + s_scroll.height());
 }
 
 void EmojiPan::validateSelectedIcon(bool animated) {
diff --git a/Telegram/SourceFiles/stickers/emoji_pan.h b/Telegram/SourceFiles/stickers/emoji_pan.h
index ff6d609b5..16c8edc77 100644
--- a/Telegram/SourceFiles/stickers/emoji_pan.h
+++ b/Telegram/SourceFiles/stickers/emoji_pan.h
@@ -113,7 +113,7 @@ private:
 };
 
 class EmojiPanel;
-class EmojiPanInner : public TWidget {
+class EmojiPanInner : public ScrolledWidget {
 	Q_OBJECT
 
 public:
@@ -122,13 +122,6 @@ public:
 	void setMaxHeight(int32 h);
 	void paintEvent(QPaintEvent *e) override;
 
-	void mousePressEvent(QMouseEvent *e) override;
-	void mouseReleaseEvent(QMouseEvent *e) override;
-	void mouseMoveEvent(QMouseEvent *e) override;
-	void leaveEvent(QEvent *e) override;
-	void leaveToChildEvent(QEvent *e, QWidget *child) override;
-	void enterFromChildEvent(QEvent *e, QWidget *child) override;
-
 	void step_selected(uint64 ms, bool timer);
 	void hideFinish();
 
@@ -140,13 +133,20 @@ public:
 
 	void refreshRecent();
 
-	void setScrollTop(int top);
+	void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
 
 	void fillPanels(QVector<EmojiPanel*> &panels);
 	void refreshPanels(QVector<EmojiPanel*> &panels);
 
-public slots:
+protected:
+	void mousePressEvent(QMouseEvent *e) override;
+	void mouseReleaseEvent(QMouseEvent *e) override;
+	void mouseMoveEvent(QMouseEvent *e) override;
+	void leaveEvent(QEvent *e) override;
+	void leaveToChildEvent(QEvent *e, QWidget *child) override;
+	void enterFromChildEvent(QEvent *e, QWidget *child) override;
 
+public slots:
 	void updateSelected();
 
 	void onShowPicker();
@@ -156,7 +156,6 @@ public slots:
 	bool checkPickerHide();
 
 signals:
-
 	void selected(EmojiPtr emoji);
 
 	void switchToStickers();
@@ -168,7 +167,6 @@ signals:
 	void saveConfigDelayed(int32 delay);
 
 private:
-
 	int32 _maxHeight;
 
 	int countHeight();
@@ -180,7 +178,9 @@ private:
 	Animations _animations;
 	Animation _a_selected;
 
-	int _top = 0, _counts[emojiTabCount];
+	int _visibleTop = 0;
+	int _visibleBottom = 0;
+	int _counts[emojiTabCount];
 
 	QVector<EmojiPtr> _emojis[emojiTabCount];
 	QVector<float64> _hovers[emojiTabCount];
@@ -207,23 +207,15 @@ struct StickerIcon {
 	int pixh = 0;
 };
 
-class StickerPanInner : public TWidget {
+class StickerPanInner : public ScrolledWidget {
 	Q_OBJECT
 
 public:
-
 	StickerPanInner();
 
 	void setMaxHeight(int32 h);
 	void paintEvent(QPaintEvent *e) override;
 
-	void mousePressEvent(QMouseEvent *e) override;
-	void mouseReleaseEvent(QMouseEvent *e) override;
-	void mouseMoveEvent(QMouseEvent *e) override;
-	void leaveEvent(QEvent *e) override;
-	void leaveToChildEvent(QEvent *e, QWidget *child) override;
-	void enterFromChildEvent(QEvent *e, QWidget *child) override;
-
 	void step_selected(uint64 ms, bool timer);
 
 	void hideFinish(bool completely);
@@ -247,7 +239,7 @@ public:
 	void fillPanels(QVector<EmojiPanel*> &panels);
 	void refreshPanels(QVector<EmojiPanel*> &panels);
 
-	void setScrollTop(int top);
+	void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
 	void preloadImages();
 
 	uint64 currentSet(int yOffset) const;
@@ -264,12 +256,21 @@ public:
 
 	~StickerPanInner();
 
+protected:
+	void mousePressEvent(QMouseEvent *e) override;
+	void mouseReleaseEvent(QMouseEvent *e) override;
+	void mouseMoveEvent(QMouseEvent *e) override;
+	void leaveEvent(QEvent *e) override;
+	void leaveToChildEvent(QEvent *e, QWidget *child) override;
+	void enterFromChildEvent(QEvent *e, QWidget *child) override;
+
 private slots:
 	void updateSelected();
 	void onSettings();
 	void onPreview();
 	void onUpdateInlineItems();
 	void onSwitchPm();
+	void onImageLoaded();
 
 signals:
 	void selected(DocumentData *sticker);
@@ -310,6 +311,7 @@ private:
 		return const_cast<StickerPanInner*>(this)->shownSets();
 	}
 	int featuredRowHeight() const;
+	void readVisibleSets();
 
 	bool showingInlineItems() const { // Gifs or Inline results
 		return (_section == Section::Inlines) || (_section == Section::Gifs);
@@ -324,7 +326,11 @@ private:
 
 	void refreshSwitchPmButton(const InlineCacheEntry *entry);
 
-	void appendSet(Sets &to, uint64 setId);
+	enum class AppendSkip {
+		Archived,
+		Installed,
+	};
+	void appendSet(Sets &to, uint64 setId, AppendSkip skip);
 
 	void selectEmoji(EmojiPtr emoji);
 	QRect stickerRect(int tab, int sel);
@@ -335,7 +341,8 @@ private:
 	Animations _animations;
 	Animation _a_selected;
 
-	int _top = 0;
+	int _visibleTop = 0;
+	int _visibleBottom = 0;
 
 	Sets _mySets;
 	Sets _featuredSets;
diff --git a/Telegram/SourceFiles/stickers/stickers.cpp b/Telegram/SourceFiles/stickers/stickers.cpp
index 4f5412d62..266ede52d 100644
--- a/Telegram/SourceFiles/stickers/stickers.cpp
+++ b/Telegram/SourceFiles/stickers/stickers.cpp
@@ -19,15 +19,22 @@ 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 "stickers.h"
 
 #include "boxes/stickersetbox.h"
 #include "boxes/confirmbox.h"
+#include "lang.h"
 #include "apiwrap.h"
 #include "localstorage.h"
 #include "mainwidget.h"
 
 namespace Stickers {
+namespace {
+
+constexpr int kReadFeaturedSetsTimeoutMs = 1000;
+internal::FeaturedReader *FeaturedReaderInstance = nullptr;
+
+} // namespace
 
 void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) {
 	auto &v = d.vsets.c_vector().v;
@@ -140,4 +147,61 @@ void undoInstallLocally(uint64 setId) {
 	Ui::showLayer(new InformBox(lang(lng_stickers_not_found)), KeepOtherLayers);
 }
 
+void markFeaturedAsRead(uint64 setId) {
+	if (!FeaturedReaderInstance) {
+		if (auto main = App::main()) {
+			FeaturedReaderInstance = new internal::FeaturedReader(main);
+		} else {
+			return;
+		}
+	}
+	FeaturedReaderInstance->scheduleRead(setId);
+}
+
+namespace internal {
+
+void readFeaturedDone() {
+	Local::writeFeaturedStickers();
+	if (App::main()) {
+		emit App::main()->stickersUpdated();
+	}
+}
+
+FeaturedReader::FeaturedReader(QObject *parent) : QObject(parent)
+, _timer(new QTimer(this)) {
+	_timer->setSingleShot(true);
+	connect(_timer, SIGNAL(timeout()), this, SLOT(onReadSets()));
+}
+
+void FeaturedReader::scheduleRead(uint64 setId) {
+	if (!_setIds.contains(setId)) {
+		_setIds.insert(setId);
+		_timer->start(kReadFeaturedSetsTimeoutMs);
+	}
+}
+
+void FeaturedReader::onReadSets() {
+	auto &sets = Global::RefStickerSets();
+	auto count = Global::FeaturedStickerSetsUnreadCount();
+	QVector<MTPlong> wrappedIds;
+	wrappedIds.reserve(_setIds.size());
+	for_const (auto setId, _setIds) {
+		auto it = sets.find(setId);
+		if (it != sets.cend()) {
+			it->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
+			wrappedIds.append(MTP_long(setId));
+			if (count) {
+				--count;
+			}
+		}
+	}
+	_setIds.clear();
+
+	if (!wrappedIds.empty()) {
+		MTP::send(MTPmessages_ReadFeaturedStickers(MTP_vector<MTPlong>(wrappedIds)), rpcDone(&readFeaturedDone));
+		Global::SetFeaturedStickerSetsUnreadCount(count);
+	}
+}
+
+} // namespace internal
 } // namespace Stickers
diff --git a/Telegram/SourceFiles/stickers/stickers.h b/Telegram/SourceFiles/stickers/stickers.h
index 06df73d47..c2449c6af 100644
--- a/Telegram/SourceFiles/stickers/stickers.h
+++ b/Telegram/SourceFiles/stickers/stickers.h
@@ -25,5 +25,25 @@ namespace Stickers {
 void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d);
 void installLocally(uint64 setId);
 void undoInstallLocally(uint64 setId);
+void markFeaturedAsRead(uint64 setId);
 
+namespace internal {
+
+class FeaturedReader : public QObject {
+	Q_OBJECT
+
+public:
+	FeaturedReader(QObject *parent);
+	void scheduleRead(uint64 setId);
+
+private slots:
+	void onReadSets();
+
+private:
+	QTimer *_timer;
+	OrderedSet<uint64> _setIds;
+
+};
+
+} // namespace internal
 } // namespace Stickers
diff --git a/Telegram/SourceFiles/stickers/stickers.style b/Telegram/SourceFiles/stickers/stickers.style
index 11168295c..ae74a48f7 100644
--- a/Telegram/SourceFiles/stickers/stickers.style
+++ b/Telegram/SourceFiles/stickers/stickers.style
@@ -37,3 +37,24 @@ featuredStickersAdd: RoundButton(defaultActiveButton) {
 	textTop: 4px;
 	downTextTop: 5px;
 }
+
+stickerEmojiSkip: 5px;
+
+stickersAddIcon: icon {
+	{ "stickers_add", #ffffff },
+};
+stickersAddSize: size(30px, 24px);
+
+stickersFeaturedHeight: 32px;
+stickersFeaturedFont: contactsNameFont;
+stickersFeaturedPosition: point(16px, 6px);
+stickersFeaturedBadgeFont: semiboldFont;
+stickersFeaturedBadgeSize: 21px;
+stickersFeaturedPen: contactsNewItemFg;
+stickersFeaturedUnreadBg: msgFileInBg;
+stickersFeaturedUnreadSize: 5px;
+stickersFeaturedUnreadSkip: 5px;
+stickersFeaturedUnreadTop: 7px;
+stickersFeaturedInstalled: icon {
+	{ "mediaview_save_check", #40ace3 }
+};
diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp
index e6098fa03..459c48387 100644
--- a/Telegram/SourceFiles/structs.cpp
+++ b/Telegram/SourceFiles/structs.cpp
@@ -1155,7 +1155,9 @@ void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes
 			}
 			if (sticker()) {
 				sticker()->alt = qs(d.valt);
-				sticker()->set = d.vstickerset;
+				if (sticker()->set.type() != mtpc_inputStickerSetID || d.vstickerset.type() == mtpc_inputStickerSetID) {
+					sticker()->set = d.vstickerset;
+				}
 			}
 		} break;
 		case mtpc_documentAttributeVideo: {
diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h
index 3fc4cec2e..f9627501c 100644
--- a/Telegram/SourceFiles/structs.h
+++ b/Telegram/SourceFiles/structs.h
@@ -1029,12 +1029,10 @@ struct DocumentAdditionalData {
 };
 
 struct StickerData : public DocumentAdditionalData {
-	StickerData() : set(MTP_inputStickerSetEmpty()) {
-	}
 	ImagePtr img;
 	QString alt;
 
-	MTPInputStickerSet set;
+	MTPInputStickerSet set = MTP_inputStickerSetEmpty();
 	bool setInstalled() const;
 
 	StorageImageLocation loc; // doc thumb location