From d607f0768af4210eba7da0bacd9ac00aca53cfe4 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 18 Nov 2016 19:27:47 +0300
Subject: [PATCH] Closed version 10019009: bubbles with tails.

---
 Telegram/Resources/basic.style                |   6 +-
 Telegram/Resources/icons/bubble_tail.png      | Bin 0 -> 144 bytes
 Telegram/Resources/icons/bubble_tail@2x.png   | Bin 0 -> 287 bytes
 Telegram/Resources/winrc/Telegram.rc          |   8 +-
 Telegram/Resources/winrc/Updater.rc           |   8 +-
 Telegram/SourceFiles/app.cpp                  |   1 +
 Telegram/SourceFiles/core/version.h           |   2 +-
 Telegram/SourceFiles/history.cpp              |  44 +++--
 Telegram/SourceFiles/history/history.style    |  13 ++
 Telegram/SourceFiles/history/history_item.cpp |  33 +++-
 Telegram/SourceFiles/history/history_item.h   |  15 +-
 Telegram/SourceFiles/history/history_media.h  |   3 +
 .../SourceFiles/history/history_media_types.h |  13 ++
 .../SourceFiles/history/history_message.cpp   |  13 +-
 .../history/history_service_layout.cpp        |  23 +++
 .../history/history_service_layout.h          |   7 +
 Telegram/SourceFiles/historywidget.cpp        | 165 +++++++++++-------
 Telegram/SourceFiles/historywidget.h          |  22 ++-
 Telegram/SourceFiles/mtproto/core_types.h     |  11 +-
 Telegram/build/version                        |   2 +-
 20 files changed, 272 insertions(+), 117 deletions(-)
 create mode 100644 Telegram/Resources/icons/bubble_tail.png
 create mode 100644 Telegram/Resources/icons/bubble_tail@2x.png

diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style
index f4bf337d3..235043880 100644
--- a/Telegram/Resources/basic.style
+++ b/Telegram/Resources/basic.style
@@ -79,7 +79,7 @@ linkCropLimit: 360px;
 linkFont: normalFont;
 linkOverFont: font(fsize underline);
 
-dateRadius: 10px;
+dateRadius: 6px;
 buttonRadius: 3px;
 
 lnkText: #0f7dc7;
@@ -104,8 +104,8 @@ msgMinWidth: 190px;
 msgPhotoSize: 33px;
 msgPhotoSkip: 40px;
 msgPadding: margins(13px, 7px, 13px, 8px);
-msgMargin: margins(13px, 10px, 53px, 2px);
-msgMarginTopAttached: 3px;
+msgMargin: margins(16px, 6px, 56px, 2px);
+msgMarginTopAttached: 1px;
 msgLnkPadding: 2px; // for media open / save links
 msgBorder: #f0f0f0;
 msgInBg: #ffffff;
diff --git a/Telegram/Resources/icons/bubble_tail.png b/Telegram/Resources/icons/bubble_tail.png
new file mode 100644
index 0000000000000000000000000000000000000000..451a3c7d4290a9bb12e0f9156e6287a7fd6dc03e
GIT binary patch
literal 144
zcmeAS@N?(olHy`uVBq!ia0vp^tU%1e!3HE(C6wy|sc=sh#}JO0y=NAR9#9ZDdf|d(
z1ao5oPgn}WG-m~Yj&@D<U!ltDJD6KuZu}sh++*=uXX$}%rYC!^&k2@av(@X;!8x1D
t7`PNye6KTDy5!odcM{1bo_%|q$0#Ng`AKeui#5;+22WQ%mvv4FO#nQVGh6@w

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/bubble_tail@2x.png b/Telegram/Resources/icons/bubble_tail@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..a1cdf916ebb0fb7c5b88ba5ba6d3575796f23785
GIT binary patch
literal 287
zcmV+)0pR|LP)<h;3K|Lk000e1NJLTq000UA000;W1^@s6!z^qq0002yNkl<ZD3QI>
zFOGyT7zOY*lTRQJNDN140-^$esNfjA90I`sI0Ui=!2ys61fnEw#e~hU(_JRn&Az0n
zzwb}d^ag-<BlmaEKS2@sUB^?oh+yCM6Nuv&Q4}FO=c!OBg)~jCVV>vETK^1_Btco0
zsHzIU<7I-!7(?eAz4yEyBSJC8Aj`7Hy@*gmUb@wF&0!emy(dW`gaEK<8gARhBf%Z5
zwVdaf&v}8y7>?t}uQ@@jHJx)W=meEg?EC(6`5-Kcf?d~fUDqHYNmy&ywk?-s`4*8R
lrfIrFBzZgLU*+%r%m*W|9SAV!AY%Xk002ovPDHLkV1lPlc@Y2r

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 6f364b393..15f81695f 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -34,8 +34,8 @@ IDI_ICON1               ICON                    "..\\art\\icon256.ico"
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 0,10,19,8
- PRODUCTVERSION 0,10,19,8
+ FILEVERSION 0,10,19,9
+ PRODUCTVERSION 0,10,19,9
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -51,10 +51,10 @@ BEGIN
         BLOCK "040904b0"
         BEGIN
             VALUE "CompanyName", "Telegram Messenger LLP"
-            VALUE "FileVersion", "0.10.19.8"
+            VALUE "FileVersion", "0.10.19.9"
             VALUE "LegalCopyright", "Copyright (C) 2014-2016"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "0.10.19.8"
+            VALUE "ProductVersion", "0.10.19.9"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 79ff23db8..bff3862db 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 0,10,19,8
- PRODUCTVERSION 0,10,19,8
+ FILEVERSION 0,10,19,9
+ PRODUCTVERSION 0,10,19,9
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -43,10 +43,10 @@ BEGIN
         BEGIN
             VALUE "CompanyName", "Telegram Messenger LLP"
             VALUE "FileDescription", "Telegram Updater"
-            VALUE "FileVersion", "0.10.19.8"
+            VALUE "FileVersion", "0.10.19.9"
             VALUE "LegalCopyright", "Copyright (C) 2014-2016"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "0.10.19.8"
+            VALUE "ProductVersion", "0.10.19.9"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 312bc8eb3..2c2513c03 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -2198,6 +2198,7 @@ namespace {
 
 	int msgRadius() {
 		static int MsgRadius = ([]() {
+			return st::historyMessageRadius;
 			auto minMsgHeight = (st::msgPadding.top() + st::msgFont->height + st::msgPadding.bottom());
 			return minMsgHeight / 2;
 		})();
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 99f826d4a..c35b27dad 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 
 #include "core/utils.h"
 
-#define BETA_VERSION_MACRO (10019008ULL)
+#define BETA_VERSION_MACRO (10019009ULL)
 
 constexpr int AppVersion = 10020;
 constexpr str_const AppVersionStr = "0.10.20";
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index 038a91514..82e3a326b 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -1637,18 +1637,22 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex,
 	t_assert(blockIndex >= 0);
 	t_assert(blockIndex < blocks.size());
 	t_assert(itemIndex >= 0);
-	t_assert(itemIndex <= blocks.at(blockIndex)->items.size());
+	t_assert(itemIndex <= blocks[blockIndex]->items.size());
 
-	HistoryBlock *block = blocks.at(blockIndex);
+	auto block = blocks.at(blockIndex);
 
 	newItem->attachToBlock(block, itemIndex);
 	block->items.insert(itemIndex, newItem);
 	newItem->previousItemChanged();
-	for (int i = itemIndex + 1, l = block->items.size(); i < l; ++i) {
-		block->items.at(i)->setIndexInBlock(i);
-	}
 	if (itemIndex + 1 < block->items.size()) {
-		block->items.at(itemIndex + 1)->previousItemChanged();
+		for (int i = itemIndex + 1, l = block->items.size(); i < l; ++i) {
+			block->items[i]->setIndexInBlock(i);
+		}
+		block->items[itemIndex + 1]->previousItemChanged();
+	} else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->items.empty()) {
+		blocks[blockIndex + 1]->items.front()->previousItemChanged();
+	} else {
+		newItem->nextItemChanged();
 	}
 
 	return newItem;
@@ -1666,14 +1670,18 @@ HistoryBlock *History::finishBuildingFrontBlock() {
 	t_assert(isBuildingFrontBlock());
 
 	// Some checks if there was some message history already
-	HistoryBlock *block = _buildingFrontBlock->block;
-	if (block && blocks.size() > 1) {
-		HistoryItem *last = block->items.back(); // ... item, item, item, last ], [ first, item, item ...
-		HistoryItem *first = blocks.at(1)->items.front();
+	auto block = _buildingFrontBlock->block;
+	if (block) {
+		if (blocks.size() > 1) {
+			auto last = block->items.back(); // ... item, item, item, last ], [ first, item, item ...
+			auto first = blocks.at(1)->items.front();
 
-		// we've added a new front block, so previous item for
-		// the old first item of a first block was changed
-		first->previousItemChanged();
+			// we've added a new front block, so previous item for
+			// the old first item of a first block was changed
+			first->previousItemChanged();
+		} else {
+			block->items.back()->nextItemChanged();
+		}
 	}
 
 	_buildingFrontBlock = nullptr;
@@ -2106,11 +2114,13 @@ void History::removeBlock(HistoryBlock *block) {
 
 	int index = block->indexInHistory();
 	blocks.removeAt(index);
-	for (int i = index, l = blocks.size(); i < l; ++i) {
-		blocks.at(i)->setIndexInHistory(i);
-	}
 	if (index < blocks.size()) {
+		for (int i = index, l = blocks.size(); i < l; ++i) {
+			blocks.at(i)->setIndexInHistory(i);
+		}
 		blocks.at(index)->items.front()->previousItemChanged();
+	} else if (!blocks.empty() && !blocks.back()->items.empty()) {
+		blocks.back()->items.back()->nextItemChanged();
 	}
 }
 
@@ -2176,6 +2186,8 @@ void HistoryBlock::removeItem(HistoryItem *item) {
 		items.at(itemIndex)->previousItemChanged();
 	} else if (blockIndex + 1 < history->blocks.size()) {
 		history->blocks.at(blockIndex + 1)->items.front()->previousItemChanged();
+	} else if (!history->blocks.empty() && !history->blocks.back()->items.empty()) {
+		history->blocks.back()->items.back()->nextItemChanged();
 	}
 
 	if (items.isEmpty()) {
diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style
index a05310bc4..dd8fe42fe 100644
--- a/Telegram/SourceFiles/history/history.style
+++ b/Telegram/SourceFiles/history/history.style
@@ -412,3 +412,16 @@ mentionFg: #777777;
 mentionFgOver: #707070;
 mentionFgActive: #0080c0;
 mentionFgOverActive: #0077b3;
+
+historyDateFadeDuration: 200;
+
+historyPhotoLeft: 14px;
+historyMessageRadius: 6px;
+historyBubbleTailInLeft: icon {{ "bubble_tail", msgInBg }};
+historyBubbleTailInLeftSelected: icon {{ "bubble_tail", msgInBgSelected }};
+historyBubbleTailOutLeft: icon {{ "bubble_tail", msgOutBg }};
+historyBubbleTailOutLeftSelected: icon {{ "bubble_tail", msgOutBgSelected }};
+historyBubbleTailInRight: icon {{ "bubble_tail-flip_horizontal", msgInBg }};
+historyBubbleTailInRightSelected: icon {{ "bubble_tail-flip_horizontal", msgInBgSelected }};
+historyBubbleTailOutRight: icon {{ "bubble_tail-flip_horizontal", msgOutBg }};
+historyBubbleTailOutRightSelected: icon {{ "bubble_tail-flip_horizontal", msgOutBgSelected }};
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 342958c57..2111f7b86 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -621,6 +621,9 @@ void HistoryItem::finishEditionToEmpty() {
 	if (auto next = nextItem()) {
 		next->previousItemChanged();
 	}
+	if (auto previous = previousItem()) {
+		previous->nextItemChanged();
+	}
 }
 
 void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
@@ -690,16 +693,22 @@ void HistoryItem::previousItemChanged() {
 	recountAttachToPrevious();
 }
 
+// Called only if there is no more next item! Not always when it changes!
+void HistoryItem::nextItemChanged() {
+	setAttachToNext(false);
+}
+
 void HistoryItem::recountAttachToPrevious() {
 	bool attach = false;
-	if (!isPost() && !Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) {
-		if (auto previos = previousItem()) {
-			attach = !previos->isPost()
-				&& !previos->serviceMsg()
-				&& !previos->isEmpty()
-				&& previos->from() == from()
-				&& (qAbs(previos->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta);
+	if (auto previous = previousItem()) {
+		if (!isPost() && !Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) {
+			attach = !previous->isPost()
+				&& !previous->serviceMsg()
+				&& !previous->isEmpty()
+				&& previous->from() == from()
+				&& (qAbs(previous->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta);
 		}
+		previous->setAttachToNext(attach);
 	}
 	if (attach && !(_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
 		_flags |= MTPDmessage_ClientFlag::f_attach_to_previous;
@@ -710,6 +719,16 @@ void HistoryItem::recountAttachToPrevious() {
 	}
 }
 
+void HistoryItem::setAttachToNext(bool attachToNext) {
+	if (attachToNext && !(_flags & MTPDmessage_ClientFlag::f_attach_to_next)) {
+		_flags |= MTPDmessage_ClientFlag::f_attach_to_next;
+		Global::RefPendingRepaintItems().insert(this);
+	} else if (!attachToNext && (_flags & MTPDmessage_ClientFlag::f_attach_to_next)) {
+		_flags &= ~MTPDmessage_ClientFlag::f_attach_to_next;
+		Global::RefPendingRepaintItems().insert(this);
+	}
+}
+
 void HistoryItem::setId(MsgId newId) {
 	history()->changeMsgId(id, newId);
 	id = newId;
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index fc12f2673..a09115f2f 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -652,7 +652,9 @@ public:
 	virtual bool hasBubble() const {
 		return false;
 	}
-	virtual void previousItemChanged();
+
+	void previousItemChanged();
+	void nextItemChanged();
 
 	virtual TextWithEntities selectedText(TextSelection selection) const {
 		return { qsl("[-]"), EntitiesInText() };
@@ -845,6 +847,9 @@ public:
 	bool isAttachedToPrevious() const {
 		return _flags & MTPDmessage_ClientFlag::f_attach_to_previous;
 	}
+	bool isAttachedToNext() const {
+		return _flags & MTPDmessage_ClientFlag::f_attach_to_next;
+	}
 	bool displayDate() const {
 		return Has<HistoryMessageDate>();
 	}
@@ -909,16 +914,20 @@ protected:
 		return nullptr;
 	}
 
-	// this should be used only in previousItemChanged()
+	// this should be called only from previousItemChanged()
 	// to add required bits to the Composer mask
 	// after that always use Has<HistoryMessageDate>()
 	void recountDisplayDate();
 
-	// this should be used only in previousItemChanged() or when
+	// this should be called only from previousItemChanged() or when
 	// HistoryMessageDate or HistoryMessageUnreadBar bit is changed in the Composer mask
 	// then the result should be cached in a client side flag MTPDmessage_ClientFlag::f_attach_to_previous
 	void recountAttachToPrevious();
 
+	// this should be called only recountAttachToPrevious() of the next item
+	// or when the next item is removed through nextItemChanged() call
+	void setAttachToNext(bool attachToNext);
+
 	const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
 		return const_cast<HistoryItem*>(this)->inlineReplyMarkup();
 	}
diff --git a/Telegram/SourceFiles/history/history_media.h b/Telegram/SourceFiles/history/history_media.h
index fac47c497..eea88a510 100644
--- a/Telegram/SourceFiles/history/history_media.h
+++ b/Telegram/SourceFiles/history/history_media.h
@@ -168,6 +168,9 @@ public:
 	bool isBubbleBottom() const {
 		return (_inBubbleState == MediaInBubbleState::Bottom) || (_inBubbleState == MediaInBubbleState::None);
 	}
+	virtual bool skipBubbleTail() const {
+		return false;
+	}
 
 	// Sometimes click on media in message is overloaded by the messsage:
 	// (for example it can open a link or a game instead of opening media)
diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h
index c2871553a..3de506cfd 100644
--- a/Telegram/SourceFiles/history/history_media_types.h
+++ b/Telegram/SourceFiles/history/history_media_types.h
@@ -172,6 +172,9 @@ public:
 	bool hideFromName() const override {
 		return true;
 	}
+	bool skipBubbleTail() const override {
+		return isBubbleBottom();
+	}
 	bool isReadyForOpen() const override {
 		return _data->loaded();
 	}
@@ -259,6 +262,9 @@ public:
 	bool hideFromName() const override {
 		return true;
 	}
+	bool skipBubbleTail() const override {
+		return isBubbleBottom();
+	}
 
 protected:
 	float64 dataProgress() const override {
@@ -484,6 +490,9 @@ public:
 	bool hideFromName() const override {
 		return true;
 	}
+	bool skipBubbleTail() const override {
+		return isBubbleBottom();
+	}
 	bool isReadyForOpen() const override {
 		return _data->loaded();
 	}
@@ -882,6 +891,10 @@ public:
 		return true;
 	}
 
+	bool skipBubbleTail() const override {
+		return isBubbleBottom();
+	}
+
 private:
 	TextSelection toDescriptionSelection(TextSelection selection) const {
 		return internal::unshiftSelection(selection, _title);
diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp
index 77444931b..fcf94c7b2 100644
--- a/Telegram/SourceFiles/history/history_message.cpp
+++ b/Telegram/SourceFiles/history/history_message.cpp
@@ -1290,12 +1290,11 @@ void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, u
 
 		auto mediaDisplayed = _media && _media->isDisplayed();
 		auto top = marginTop();
-		QRect r(left, top, width, height - top - marginBottom());
+		auto r = QRect(left, top, width, height - top - marginBottom());
 
-		auto &bg = selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg);
-		auto &sh = selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow);
-		RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners));
-		App::roundRect(p, r, bg, cors, &sh);
+		auto skipTail = isAttachedToNext() || (_media && _media->skipBubbleTail());
+		auto displayTail = skipTail ? HistoryLayout::BubbleTail::None : (outbg && !Adaptive::Wide()) ? HistoryLayout::BubbleTail::Right : HistoryLayout::BubbleTail::Left;
+		HistoryLayout::paintBubble(p, r, _history->width, selected, outbg, displayTail);
 
 		QRect trect(r.marginsAdded(-st::msgPadding));
 		if (mediaDisplayed && _media->isBubbleTop()) {
@@ -1335,7 +1334,7 @@ void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, u
 			HistoryMessage::drawInfo(p, r.x() + r.width(), r.y() + r.height(), 2 * r.x() + r.width(), selected, InfoDisplayDefault);
 		}
 	} else if (_media) {
-		int32 top = marginTop();
+		auto top = marginTop();
 		p.translate(left, top);
 		_media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms);
 		p.translate(-left, -top);
@@ -1744,7 +1743,7 @@ QString HistoryMessage::notificationHeader() const {
 }
 
 bool HistoryMessage::displayFromPhoto() const {
-	return hasFromPhoto() && !isAttachedToPrevious();
+	return hasFromPhoto() && !isAttachedToNext();
 }
 
 bool HistoryMessage::hasFromPhoto() const {
diff --git a/Telegram/SourceFiles/history/history_service_layout.cpp b/Telegram/SourceFiles/history/history_service_layout.cpp
index 121800cab..beb585ba0 100644
--- a/Telegram/SourceFiles/history/history_service_layout.cpp
+++ b/Telegram/SourceFiles/history/history_service_layout.cpp
@@ -352,4 +352,27 @@ void serviceColorsUpdated() {
 	}
 }
 
+void paintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, BubbleTail tail) {
+	auto &bg = selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg);
+	auto &sh = selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow);
+	auto cors = selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners);
+	auto parts = App::RectPart::TopFull | App::RectPart::NoTopBottom | App::RectPart::Bottom;
+	if (tail == BubbleTail::Right) {
+		parts |= App::RectPart::BottomLeft;
+		p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg);
+		auto &tail = selected ? st::historyBubbleTailOutRightSelected : st::historyBubbleTailOutRight;
+		tail.paint(p, rect.x() + rect.width(), rect.y() + rect.height() - tail.height(), outerWidth);
+		p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh);
+	} else if (tail == BubbleTail::Left) {
+		parts |= App::RectPart::BottomRight;
+		p.fillRect(rect.x(), rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg);
+		auto &tail = selected ? (outbg ? st::historyBubbleTailOutLeftSelected : st::historyBubbleTailInLeftSelected) : (outbg ? st::historyBubbleTailOutLeft : st::historyBubbleTailInLeft);
+		tail.paint(p, rect.x() - tail.width(), rect.y() + rect.height() - tail.height(), outerWidth);
+		p.fillRect(rect.x() - tail.width(), rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh);
+	} else {
+		parts |= App::RectPart::BottomFull;
+	}
+	App::roundRect(p, rect, bg, cors, &sh, parts);
+}
+
 } // namespace HistoryLayout
diff --git a/Telegram/SourceFiles/history/history_service_layout.h b/Telegram/SourceFiles/history/history_service_layout.h
index faae5d450..d38482829 100644
--- a/Telegram/SourceFiles/history/history_service_layout.h
+++ b/Telegram/SourceFiles/history/history_service_layout.h
@@ -52,4 +52,11 @@ void paintEmpty(Painter &p, int width, int height);
 
 void serviceColorsUpdated();
 
+enum class BubbleTail {
+	None,
+	Left,
+	Right,
+};
+void paintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, BubbleTail tail);
+
 } // namespace HistoryLayout
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index 73a1fc60c..bb1149ae7 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -175,24 +175,29 @@ void HistoryInner::repaintItem(const HistoryItem *item) {
 }
 
 namespace {
-	// helper binary search for an item in a list that is not completely below the given bottom of the visible area
-	// is applied once for blocks list in a history and once for items list in the found block
-	template <typename T>
-	int binarySearchBlocksOrItems(const T &list, int bottom) {
-		int start = 0, end = list.size();
-		while (end - start > 1) {
-			int middle = (start + end) / 2;
-			if (list.at(middle)->y >= bottom) {
-				end = middle;
-			} else {
-				start = middle;
-			}
+
+// helper binary search for an item in a list that is not completely
+// above the given top of the visible area or below the given bottom of the visible area
+// is applied once for blocks list in a history and once for items list in the found block
+template <bool TopToBottom, typename T>
+int binarySearchBlocksOrItems(const T &list, int edge) {
+	auto start = 0, end = list.size();
+	while (end - start > 1) {
+		auto middle = (start + end) / 2;
+		auto top = list[middle]->y;
+		auto chooseLeft = (TopToBottom ? (top <= edge) : (top < edge));
+		if (chooseLeft) {
+			start = middle;
+		} else {
+			end = middle;
 		}
-		return start;
 	}
+	return start;
 }
 
-template <typename Method>
+} // namespace
+
+template <bool TopToBottom, typename Method>
 void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Method method) {
 	// no displayed messages in this history
 	if (historytop < 0 || history->isEmpty()) {
@@ -202,43 +207,82 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
 		return;
 	}
 
+	auto searchEdge = TopToBottom ? _visibleAreaTop : _visibleAreaBottom;
+
 	// binary search for blockIndex of the first block that is not completely below the visible area
-	int blockIndex = binarySearchBlocksOrItems(history->blocks, _visibleAreaBottom - historytop);
+	auto blockIndex = binarySearchBlocksOrItems<TopToBottom>(history->blocks, searchEdge - historytop);
 
 	// binary search for itemIndex of the first item that is not completely below the visible area
-	HistoryBlock *block = history->blocks.at(blockIndex);
-	int blocktop = historytop + block->y;
-	int itemIndex = binarySearchBlocksOrItems(block->items, _visibleAreaBottom - blocktop);
+	auto block = history->blocks.at(blockIndex);
+	auto blocktop = historytop + block->y;
+	auto blockbottom = blocktop + block->height;
+	auto itemIndex = binarySearchBlocksOrItems<TopToBottom>(block->items, searchEdge - blocktop);
 
 	while (true) {
-		while (itemIndex >= 0) {
-			HistoryItem *item = block->items.at(itemIndex--);
-			int itemtop = blocktop + item->y;
-			int itembottom = itemtop + item->height();
+		while (true) {
+			auto item = block->items.at(itemIndex);
+			auto itemtop = blocktop + item->y;
+			auto itembottom = itemtop + item->height();
 
-			// binary search should've skipped all the items that are below the visible area
-			t_assert(itemtop < _visibleAreaBottom);
+			// binary search should've skipped all the items that are above / below the visible area
+			if (TopToBottom) {
+				t_assert(itembottom > _visibleAreaTop);
+			} else {
+				t_assert(itemtop < _visibleAreaBottom);
+			}
 
 			if (!method(item, itemtop, itembottom)) {
 				return;
 			}
 
-			// skip all the items that are above the visible area
-			if (itemtop <= _visibleAreaTop) {
+			// skip all the items that are below / above the visible area
+			if (TopToBottom) {
+				if (itembottom >= _visibleAreaBottom) {
+					return;
+				}
+			} else {
+				if (itemtop <= _visibleAreaTop) {
+					return;
+				}
+			}
+
+			if (TopToBottom) {
+				if (++itemIndex >= block->items.size()) {
+					break;
+				}
+			} else {
+				if (--itemIndex < 0) {
+					break;
+				}
+			}
+		}
+
+		// skip all the rest blocks that are below / above the visible area
+		if (TopToBottom) {
+			if (blockbottom >= _visibleAreaBottom) {
+				return;
+			}
+		} else {
+			if (blocktop <= _visibleAreaTop) {
 				return;
 			}
 		}
 
-		// skip all the rest blocks that are above the visible area
-		if (blocktop <= _visibleAreaTop) {
-			return;
-		}
-
-		if (--blockIndex < 0) {
-			return;
+		if (TopToBottom) {
+			if (++blockIndex >= history->blocks.size()) {
+				return;
+			}
+		} else {
+			if (--blockIndex < 0) {
+				return;
+			}
+		}
+		block = history->blocks.at(blockIndex);
+		blocktop = historytop + block->y;
+		blockbottom = blocktop + block->height;
+		if (TopToBottom) {
+			itemIndex = 0;
 		} else {
-			block = history->blocks.at(blockIndex);
-			blocktop = historytop + block->y;
 			itemIndex = block->items.size() - 1;
 		}
 	}
@@ -250,47 +294,48 @@ void HistoryInner::enumerateUserpics(Method method) {
 		return;
 	}
 
-	// find and remember the bottom of an attached messages pack
-	// -1 means we didn't find an attached to previous message yet
-	int lowestAttachedItemBottom = -1;
+	// find and remember the top of an attached messages pack
+	// -1 means we didn't find an attached to next message yet
+	int lowestAttachedItemTop = -1;
 
-	auto userpicCallback = [this, &lowestAttachedItemBottom, &method](HistoryItem *item, int itemtop, int itembottom) {
+	auto userpicCallback = [this, &lowestAttachedItemTop, &method](HistoryItem *item, int itemtop, int itembottom) {
 		// skip all service messages
 		auto message = item->toHistoryMessage();
 		if (!message) return true;
 
-		if (lowestAttachedItemBottom < 0 && message->isAttachedToPrevious()) {
-			lowestAttachedItemBottom = itembottom - message->marginBottom();
+		if (lowestAttachedItemTop < 0 && message->isAttachedToNext()) {
+			lowestAttachedItemTop = itemtop + message->marginTop();
 		}
 
 		// call method on a userpic for all messages that have it and for those who are not showing it
-		// because of their attachment to the previous message if they are top-most visible
-		if (message->displayFromPhoto() || (message->hasFromPhoto() && itemtop <= _visibleAreaTop)) {
-			if (lowestAttachedItemBottom < 0) {
-				lowestAttachedItemBottom = itembottom - message->marginBottom();
+		// because of their attachment to the next message if they are bottom-most visible
+		if (message->displayFromPhoto() || (message->hasFromPhoto() && itembottom >= _visibleAreaBottom)) {
+			if (lowestAttachedItemTop < 0) {
+				lowestAttachedItemTop = itemtop + message->marginTop();
 			}
-			// attach userpic to the top of the visible area with the same margin as it is from the left side
-			int userpicTop = qMax(itemtop + message->marginTop(), _visibleAreaTop + st::msgMargin.left());
+			// attach userpic to the bottom of the visible area with the same margin as the last message
+			auto userpicMinBottomSkip = st::historyPaddingBottom + st::msgMargin.bottom();
+			auto userpicBottom = qMin(itembottom - message->marginBottom(), _visibleAreaBottom - userpicMinBottomSkip);
 
-			// do not let the userpic go below the attached messages pack bottom line
-			userpicTop = qMin(userpicTop, lowestAttachedItemBottom - st::msgPhotoSize);
+			// do not let the userpic go above the attached messages pack top line
+			userpicBottom = qMax(userpicBottom, lowestAttachedItemTop + st::msgPhotoSize);
 
 			// call the template callback function that was passed
 			// and return if it finished everything it needed
-			if (!method(message, userpicTop)) {
+			if (!method(message, userpicBottom - st::msgPhotoSize)) {
 				return false;
 			}
 		}
 
-		// forget the found bottom of the pack, search for the next one from scratch
-		if (!message->isAttachedToPrevious()) {
-			lowestAttachedItemBottom = -1;
+		// forget the found top of the pack, search for the next one from scratch
+		if (!message->isAttachedToNext()) {
+			lowestAttachedItemTop = -1;
 		}
 
 		return true;
 	};
 
-	enumerateItems(userpicCallback);
+	enumerateItems<EnumItemsDirection::TopToBottom>(userpicCallback);
 }
 
 template <typename Method>
@@ -346,7 +391,7 @@ void HistoryInner::enumerateDates(Method method) {
 		return true;
 	};
 
-	enumerateItems(dateCallback);
+	enumerateItems<EnumItemsDirection::BottomToTop>(dateCallback);
 }
 
 void HistoryInner::paintEvent(QPaintEvent *e) {
@@ -494,13 +539,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 		if (mtop >= 0 || htop >= 0) {
 			enumerateUserpics([&p, &r](HistoryMessage *message, int userpicTop) {
 				// stop the enumeration if the userpic is above the painted rect
-				if (userpicTop + st::msgPhotoSize <= r.top()) {
+				if (userpicTop >= r.top() + r.height()) {
 					return false;
 				}
 
 				// paint the userpic if it intersects the painted rect
-				if (userpicTop < r.top() + r.height()) {
-					message->from()->paintUserpicLeft(p, st::msgPhotoSize, st::msgMargin.left(), userpicTop, message->history()->width);
+				if (userpicTop + st::msgPhotoSize > r.top()) {
+					message->from()->paintUserpicLeft(p, st::msgPhotoSize, st::historyPhotoLeft, userpicTop, message->history()->width);
 				}
 				return true;
 			});
@@ -1704,7 +1749,7 @@ void HistoryInner::toggleScrollDateShown() {
 	_scrollDateShown = !_scrollDateShown;
 	auto from = _scrollDateShown ? 0. : 1.;
 	auto to = _scrollDateShown ? 1. : 0.;
-	_scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::historyAttach.duration);
+	_scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::historyDateFadeDuration);
 }
 
 void HistoryInner::repaintScrollDateCallback() {
@@ -1975,7 +2020,7 @@ void HistoryInner::onUpdateSelected() {
 		}
 		dragState = item->getState(m.x(), m.y(), request);
 		lnkhost = item;
-		if (!dragState.link && m.x() >= st::msgMargin.left() && m.x() < st::msgMargin.left() + st::msgPhotoSize) {
+		if (!dragState.link && m.x() >= st::historyPhotoLeft && m.x() < st::historyPhotoLeft + st::msgPhotoSize) {
 			if (auto msg = item->toHistoryMessage()) {
 				if (msg->hasFromPhoto()) {
 					enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool {
diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h
index 2ee78b572..e4ba2c91a 100644
--- a/Telegram/SourceFiles/historywidget.h
+++ b/Telegram/SourceFiles/historywidget.h
@@ -278,24 +278,32 @@ private:
 	HistoryItem *_scrollDateLastItem = nullptr;
 	int _scrollDateLastItemTop = 0;
 
+	enum class EnumItemsDirection {
+		TopToBottom,
+		BottomToTop,
+	};
 	// this function finds all history items that are displayed and calls template method
-	// for each found message (from the bottom to the top) in the passed history with passed top offset
+	// for each found message (in given direction) in the passed history with passed top offset
 	//
 	// method has "bool (*Method)(HistoryItem *item, int itemtop, int itembottom)" signature
 	// if it returns false the enumeration stops immidiately
-	template <typename Method>
+	template <bool TopToBottom, typename Method>
 	void enumerateItemsInHistory(History *history, int historytop, Method method);
 
-	template <typename Method>
+	template <EnumItemsDirection direction, typename Method>
 	void enumerateItems(Method method) {
-		enumerateItemsInHistory(_history, historyTop(), method);
-		if (_migrated) {
-			enumerateItemsInHistory(_migrated, migratedTop(), method);
+		constexpr auto TopToBottom = (direction == EnumItemsDirection::TopToBottom);
+		if (TopToBottom && _migrated) {
+			enumerateItemsInHistory<TopToBottom>(_migrated, migratedTop(), method);
+		}
+		enumerateItemsInHistory<TopToBottom>(_history, historyTop(), method);
+		if (!TopToBottom && _migrated) {
+			enumerateItemsInHistory<TopToBottom>(_migrated, migratedTop(), method);
 		}
 	}
 
 	// this function finds all userpics on the left that are displayed and calls template method
-	// for each found userpic (from the bottom to the top) using enumerateItems() method
+	// for each found userpic (from the top to the bottom) using enumerateItems() method
 	//
 	// method has "bool (*Method)(HistoryMessage *message, int userpicTop)" signature
 	// if it returns false the enumeration stops immidiately
diff --git a/Telegram/SourceFiles/mtproto/core_types.h b/Telegram/SourceFiles/mtproto/core_types.h
index f716ed230..edf3dd9c3 100644
--- a/Telegram/SourceFiles/mtproto/core_types.h
+++ b/Telegram/SourceFiles/mtproto/core_types.h
@@ -999,17 +999,20 @@ enum class MTPDmessage_ClientFlag : int32 {
 	// message is attached to previous one when displaying the history
 	f_attach_to_previous = (1 << 25),
 
+	// message is attached to next one when displaying the history
+	f_attach_to_next = (1 << 24),
+
 	// message was sent from inline bot, need to re-set media when sent
-	f_from_inline_bot = (1 << 24),
+	f_from_inline_bot = (1 << 23),
 
 	// message has a switch inline keyboard button, need to return to inline
-	f_has_switch_inline_button = (1 << 23),
+	f_has_switch_inline_button = (1 << 22),
 
 	// message is generated on the client side and should be unread
-	f_clientside_unread = (1 << 22),
+	f_clientside_unread = (1 << 21),
 
 	// update this when adding new client side flags
-	MIN_FIELD = (1 << 22),
+	MIN_FIELD = (1 << 21),
 };
 DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)
 
diff --git a/Telegram/build/version b/Telegram/build/version
index 8a3949576..0b19c091b 100644
--- a/Telegram/build/version
+++ b/Telegram/build/version
@@ -3,4 +3,4 @@ AppVersionStrMajor 0.10
 AppVersionStrSmall 0.10.20
 AppVersionStr      0.10.20
 AlphaChannel       0
-BetaVersion        10019008
+BetaVersion        10019009