From 5dd8eab6061831ddee077b990b76aa1824c662eb Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 7 Aug 2015 15:11:50 +0300
Subject: [PATCH] version 0.8.47.dev - search in conversation, clear history,
 delete conversation, saving cleared conversations locally, removed contacts
 from left column

---
 Telegram/PrepareWin.bat                     |  10 +-
 Telegram/Resources/lang.strings             |   6 +-
 Telegram/SourceFiles/apiwrap.cpp            |  25 +++
 Telegram/SourceFiles/apiwrap.h              |   3 +
 Telegram/SourceFiles/app.cpp                |  30 ++-
 Telegram/SourceFiles/app.h                  |   1 +
 Telegram/SourceFiles/application.cpp        |   4 +-
 Telegram/SourceFiles/boxes/contactsbox.cpp  |  37 ++-
 Telegram/SourceFiles/boxes/contactsbox.h    |   4 +-
 Telegram/SourceFiles/config.h               |   6 +-
 Telegram/SourceFiles/dialogswidget.cpp      |  38 +++-
 Telegram/SourceFiles/dialogswidget.h        |   1 +
 Telegram/SourceFiles/gui/images.h           |  10 +
 Telegram/SourceFiles/history.cpp            |  22 +-
 Telegram/SourceFiles/history.h              |   6 +-
 Telegram/SourceFiles/historywidget.cpp      |  10 +-
 Telegram/SourceFiles/localstorage.cpp       | 236 +++++++++++++++++++-
 Telegram/SourceFiles/localstorage.h         |   4 +
 Telegram/SourceFiles/mainwidget.cpp         |  13 +-
 Telegram/SourceFiles/settings.cpp           |   3 +
 Telegram/SourceFiles/settings.h             |   6 +
 Telegram/SourceFiles/structs.cpp            |  39 +++-
 Telegram/SourceFiles/structs.h              |  22 +-
 Telegram/SourceFiles/telegram.qrc           |  80 +++----
 Telegram/Telegram.plist                     |   2 +-
 Telegram/Telegram.rc                        | Bin 5540 -> 5540 bytes
 Telegram/Telegram.vcxproj                   |   1 +
 Telegram/Telegram.xcodeproj/project.pbxproj |  12 +-
 Telegram/Version.sh                         |   2 +-
 29 files changed, 492 insertions(+), 141 deletions(-)

diff --git a/Telegram/PrepareWin.bat b/Telegram/PrepareWin.bat
index 0ccb9abc5..b5b72657c 100644
--- a/Telegram/PrepareWin.bat
+++ b/Telegram/PrepareWin.bat
@@ -1,11 +1,11 @@
 @echo OFF
 
 set "AppVersionStrMajor=0.8"
-set "AppVersion=8046"
-set "AppVersionStrSmall=0.8.46"
-set "AppVersionStr=0.8.46"
-set "AppVersionStrFull=0.8.46.0"
-set "DevChannel=0"
+set "AppVersion=8047"
+set "AppVersionStrSmall=0.8.47"
+set "AppVersionStr=0.8.47"
+set "AppVersionStrFull=0.8.47.0"
+set "DevChannel=1"
 
 if %DevChannel% neq 0 goto preparedev
 
diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings
index 1eb67ca5f..b462bcd6d 100644
--- a/Telegram/Resources/lang.strings
+++ b/Telegram/Resources/lang.strings
@@ -156,6 +156,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 "lng_dlg_new_group_name" = "Group name";
 "lng_dlg_create_group" = "Create";
 "lng_no_contacts" = "You have no contacts";
+"lng_no_chats" = "Your chats will be here";
 "lng_contacts_loading" = "Loading..";
 "lng_contacts_not_found" = "No contacts found";
 "lng_dlg_search_chat" = "Search in this chat";
@@ -385,6 +386,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 "lng_create_group_title" = "New Group";
 
 "lng_failed_add_participant" = "Could not add user. Try again later.";
+"lng_failed_add_not_mutual" = "Sorry, if a person left a group, only a\nmutual contact can bring them back\n(they need to have your phone\nnumber, and you need theirs).";
 
 "lng_sure_delete_contact" = "Are you sure, you want to delete {contact} from your contact list?";
 "lng_sure_delete_history" = "Are you sure, you want to delete all message history with {contact}?\n\nThis action cannot be undone.";
@@ -469,7 +471,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 "lng_message_ph" = "Write a message..";
 "lng_record_cancel" = "Release outside this field to cancel";
 "lng_empty_history" = "";
-"lng_willbe_history" = "Please select chat to start messaging";
+"lng_willbe_history" = "Please select a chat to start messaging";
 "lng_message_with_from" = "[c]{from}:[/c] {message}";
 "lng_from_you" = "You";
 "lng_bot_description" = "What can this bot do?";
@@ -627,7 +629,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 
 "lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}";
 "lng_new_version_minor" = "— Bug fixes and other minor improvements";
-"lng_new_version_text" = "— Improved in-app media playback\n— Bug fixes and other minor improvements";
+"lng_new_version_text" = "— Search for messages in conversation\n— Clear messages history in groups\n— Contacts without messages are hidden from the conversations list";
 
 "lng_menu_insert_unicode" = "Insert Unicode control character";
 
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index c93bce807..75d8187c7 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -225,6 +225,23 @@ void ApiWrap::requestPeer(PeerData *peer) {
 	_peerRequests.insert(peer, req);
 }
 
+void ApiWrap::requestPeers(const QList<PeerData*> &peers) {
+	QVector<MTPint> chats;
+	QVector<MTPInputUser> users;
+	chats.reserve(peers.size());
+	users.reserve(peers.size());
+	for (QList<PeerData*>::const_iterator i = peers.cbegin(), e = peers.cend(); i != e; ++i) {
+		if (!*i || _fullPeerRequests.contains(*i) || _peerRequests.contains(*i)) continue;
+		if ((*i)->chat) {
+			chats.push_back(MTP_int(App::chatFromPeer((*i)->id)));
+		} else {
+			users.push_back((*i)->asUser()->inputUser);
+		}
+	}
+	if (!chats.isEmpty()) MTP::send(MTPmessages_GetChats(MTP_vector<MTPint>(chats)), rpcDone(&ApiWrap::gotChats));
+	if (!users.isEmpty()) MTP::send(MTPusers_GetUsers(MTP_vector<MTPInputUser>(users)), rpcDone(&ApiWrap::gotUsers));
+}
+
 void ApiWrap::gotChat(PeerData *peer, const MTPmessages_Chats &result) {
 	_peerRequests.remove(peer);
 	
@@ -249,6 +266,14 @@ void ApiWrap::gotUser(PeerData *peer, const MTPVector<MTPUser> &result) {
 	}
 }
 
+void ApiWrap::gotChats(const MTPmessages_Chats &result) {
+	App::feedChats(result.c_messages_chats().vchats);
+}
+
+void ApiWrap::gotUsers(const MTPVector<MTPUser> &result) {
+	App::feedUsers(result);
+}
+
 bool ApiWrap::gotPeerFailed(PeerData *peer, const RPCError &error) {
 	if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false;
 
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index f73561c4c..a57eb6bd9 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -32,6 +32,7 @@ public:
 
 	void requestFullPeer(PeerData *peer);
 	void requestPeer(PeerData *peer);
+	void requestPeers(const QList<PeerData*> &peers);
 
 	void requestWebPageDelayed(WebPageData *page);
 	void clearWebPageRequest(WebPageData *page);
@@ -72,6 +73,8 @@ private:
 	
 	void gotChat(PeerData *peer, const MTPmessages_Chats &result);
 	void gotUser(PeerData *peer, const MTPVector<MTPUser> &result);
+	void gotChats(const MTPmessages_Chats &result);
+	void gotUsers(const MTPVector<MTPUser> &result);
 	bool gotPeerFailed(PeerData *peer, const RPCError &err);
 	PeerRequests _peerRequests;
 
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 120fae069..ac3773b9e 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -416,6 +416,8 @@ namespace App {
 					bool showPhone = !isServiceUser(data->id) && !(flags & (MTPDuser_flag_self | MTPDuser_flag_contact | MTPDuser_flag_mutual_contact));
 					bool showPhoneChanged = !isServiceUser(data->id) && !(flags & (MTPDuser_flag_self)) && ((showPhone && data->contact) || (!showPhone && !data->contact));
 
+					// see also Local::readPeer
+
 					QString pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) ? formatPhone(phone) : QString()) : data->nameOrPhone;
 
 					data->setName(fname, lname, pname, uname);
@@ -500,7 +502,6 @@ namespace App {
 				data->count = d.vparticipants_count.v;
 				data->left = d.vleft.v;
 				data->forbidden = false;
-				data->access = 0;
 				if (data->version < d.vversion.v) {
 					data->version = d.vversion.v;
 					data->participants = ChatData::Participants();
@@ -519,7 +520,6 @@ namespace App {
 				data->count = -1;
 				data->left = false;
 				data->forbidden = true;
-				data->access = 0;
 			} break;
 			case mtpc_geoChat: {
 				const MTPDgeoChat &d(chat.c_geoChat());
@@ -760,27 +760,23 @@ namespace App {
 		return ImagePtr();
 	}
 
+	StorageImageLocation imageLocation(int32 w, int32 h, const MTPFileLocation &loc) {
+		if (loc.type() == mtpc_fileLocation) {
+			const MTPDfileLocation &l(loc.c_fileLocation());
+			return StorageImageLocation(w, h, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v);
+		}
+		return StorageImageLocation(w, h, 0, 0, 0, 0);
+	}
+
 	StorageImageLocation imageLocation(const MTPPhotoSize &size) {
 		switch (size.type()) {
 		case mtpc_photoSize: {
 			const MTPDphotoSize &d(size.c_photoSize());
-			if (d.vlocation.type() == mtpc_fileLocation) {
-				const MTPDfileLocation &l(d.vlocation.c_fileLocation());
-				return StorageImageLocation(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v);
-			}
+			return imageLocation(d.vw.v, d.vh.v, d.vlocation);
 		} break;
 		case mtpc_photoCachedSize: {
 			const MTPDphotoCachedSize &d(size.c_photoCachedSize());
-			if (d.vlocation.type() == mtpc_fileLocation) {
-				const MTPDfileLocation &l(d.vlocation.c_fileLocation());
-				const string &s(d.vbytes.c_string().v);
-				QByteArray bytes(s.data(), s.size());
-				return StorageImageLocation(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v);
-			} else if (d.vlocation.type() == mtpc_fileLocationUnavailable) {
-				const string &s(d.vbytes.c_string().v);
-				QByteArray bytes(s.data(), s.size());
-				return StorageImageLocation(d.vw.v, d.vh.v, 0, 0, 0, 0);
-			}
+			return imageLocation(d.vw.v, d.vh.v, d.vlocation);
 		} break;
 		}
 		return StorageImageLocation();
@@ -1697,6 +1693,8 @@ namespace App {
 		randomData.clear();
 		mutedPeers.clear();
 		updatedPeers.clear();
+		cSetSavedPeers(SavedPeers());
+		cSetSavedPeersByTime(SavedPeersByTime());
 		for (PeersData::const_iterator i = peersData.cbegin(), e = peersData.cend(); i != e; ++i) {
 			delete *i;
 		}
diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h
index c1befed22..1033ef90d 100644
--- a/Telegram/SourceFiles/app.h
+++ b/Telegram/SourceFiles/app.h
@@ -127,6 +127,7 @@ namespace App {
 	int32 maxMsgId();
 
 	ImagePtr image(const MTPPhotoSize &size);
+	StorageImageLocation imageLocation(int32 w, int32 h, const MTPFileLocation &loc);
 	StorageImageLocation imageLocation(const MTPPhotoSize &size);
 
 	PhotoData *feedPhoto(const MTPPhoto &photo, const PreparedPhotoThumbs &thumbs);
diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp
index d4c43461f..1d314fa9b 100644
--- a/Telegram/SourceFiles/application.cpp
+++ b/Telegram/SourceFiles/application.cpp
@@ -658,8 +658,8 @@ void Application::checkMapVersion() {
 		psRegisterCustomScheme();
 		if (Local::oldMapVersion()) {
 			QString versionFeatures;
-			if (cDevVersion() && Local::oldMapVersion() < 8044) {
-				versionFeatures = QString::fromUtf8("\xe2\x80\x94 Sending media and recording audio status display");// .replace('@', qsl("@") + QChar(0x200D));
+			if (cDevVersion() && Local::oldMapVersion() < 8047) {
+				versionFeatures = QString::fromUtf8("\xe2\x80\x94 Search for messages in conversation\n\xe2\x80\x94 Clear messages history in groups\n\xe2\x80\x94 Contacts without messages are hidden from the conversations list");// .replace('@', qsl("@") + QChar(0x200D));
 			} else if (!cDevVersion() && Local::oldMapVersion() < 8045) {
 				versionFeatures = lang(lng_new_version_minor).trimmed();
 			}
diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp
index 311b26b6b..c5cec8baa 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.cpp
+++ b/Telegram/SourceFiles/boxes/contactsbox.cpp
@@ -60,7 +60,7 @@ _byUsernameSel(-1),
 _addContactLnk(this, lang(lng_add_contact_button)) {
 	DialogsIndexed &v(App::main()->dialogsList());
 	for (DialogRow *r = v.list.begin; r != v.list.end; r = r->next) {
-		if (r->history->peer->chat && !r->history->peer->asChat()->forbidden) {
+		if (r->history->peer->chat && !r->history->peer->asChat()->forbidden && !r->history->peer->asChat()->left) {
 			_contacts->addToEnd(r->history);
 		}
 	}
@@ -104,7 +104,7 @@ void ContactsInner::onAddBot() {
 
 void ContactsInner::peerUpdated(PeerData *peer) {
 	if (_chat && (!peer || peer == _chat)) {
-		if (_chat->forbidden) {
+		if (_chat->forbidden || _chat->left) {
 			App::wnd()->hideLayer();
 		} else if (!_chat->participants.isEmpty() || _chat->count <= 0) {
 			for (ContactsData::iterator i = _contactsData.begin(), e = _contactsData.end(); i != e; ++i) {
@@ -180,11 +180,11 @@ ContactsInner::ContactData *ContactsInner::contactData(DialogRow *row) {
 		if (i == _contactsData.cend()) {
 			_contactsData.insert(peer, data = new ContactData());
 			data->inchat = (_chat && !peer->chat) ? _chat->participants.contains(peer->asUser()) : false;
-			data->check = false;
+			data->check = _checkedContacts.contains(peer);
 			data->name.setText(st::profileListNameFont, peer->name, _textNameOptions);
 			if (peer->chat) {
 				ChatData *chat = peer->asChat();
-				if (chat->forbidden) {
+				if (chat->forbidden || chat->left) {
 					data->online = lang(lng_chat_status_unaccessible);
 				} else {
 					data->online = lng_chat_status_members(lt_count, chat->count);
@@ -401,7 +401,7 @@ void ContactsInner::chooseParticipant() {
 		if (_filter.isEmpty()) {
 			if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) {
 				if (d_byUsername[_byUsernameSel]->inchat) return;
-				changeCheckState(d_byUsername[_byUsernameSel]);
+				changeCheckState(d_byUsername[_byUsernameSel], _byUsername[_byUsernameSel]);
 			} else {
 				if (!_sel || contactData(_sel)->inchat) return;
 				changeCheckState(_sel);
@@ -409,7 +409,7 @@ void ContactsInner::chooseParticipant() {
 		} else {
 			if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) {
 				if (d_byUsernameFiltered[_byUsernameSel]->inchat) return;
-				changeCheckState(d_byUsernameFiltered[_byUsernameSel]);
+				changeCheckState(d_byUsernameFiltered[_byUsernameSel], _byUsernameFiltered[_byUsernameSel]);
 
 				ContactData *moving = d_byUsernameFiltered[_byUsernameSel];
 				int32 i = 0, l = d_byUsername.size();
@@ -470,15 +470,17 @@ void ContactsInner::chooseParticipant() {
 }
 
 void ContactsInner::changeCheckState(DialogRow *row) {
-	changeCheckState(contactData(row));
+	changeCheckState(contactData(row), row->history->peer);
 }
 
-void ContactsInner::changeCheckState(ContactData *data) {
+void ContactsInner::changeCheckState(ContactData *data, PeerData *peer) {
 	if (data->check) {
 		data->check = false;
+		_checkedContacts.remove(peer);
 		--_selCount;
 	} else if (_selCount + (_chat ? _chat->count : 0) < cMaxGroupCount()) {
 		data->check = true;
+		_checkedContacts.insert(peer, true);
 		++_selCount;
 	}
 }
@@ -693,7 +695,7 @@ void ContactsInner::peopleReceived(const QString &query, const QVector<MTPContac
 			ContactData *d = new ContactData();
 			_byUsernameDatas.push_back(d);
 			d->inchat = _chat ? _chat->participants.contains(u) : false;
-			d->check = false;
+			d->check = _checkedContacts.contains(u);
 			d->name.setText(st::profileListNameFont, u->name, _textNameOptions);
 			d->online = '@' + u->username;
 
@@ -880,6 +882,11 @@ void ContactsInner::selectSkipPage(int32 h, int32 dir) {
 
 QVector<UserData*> ContactsInner::selected() {
 	QVector<UserData*> result;
+	for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) {
+		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()->chat) {
@@ -896,10 +903,15 @@ QVector<UserData*> ContactsInner::selected() {
 
 QVector<MTPInputUser> ContactsInner::selectedInputs() {
 	QVector<MTPInputUser> result;
+	for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) {
+		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()->chat) {
-			result.push_back(i.key()->inputUser);
+			result.push_back(i.key()->asUser()->inputUser);
 		}
 	}
 	for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
@@ -911,6 +923,11 @@ QVector<MTPInputUser> ContactsInner::selectedInputs() {
 }
 
 PeerData *ContactsInner::selectedUser() {
+	for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) {
+		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();
diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h
index 7fd8fc834..5b59f8a63 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.h
+++ b/Telegram/SourceFiles/boxes/contactsbox.h
@@ -53,7 +53,7 @@ public:
 	void loadProfilePhotos(int32 yFrom);
 	void chooseParticipant();
 	void changeCheckState(DialogRow *row);
-	void changeCheckState(ContactData *data);
+	void changeCheckState(ContactData *data, PeerData *peer);
 
 	void peopleReceived(const QString &query, const QVector<MTPContactFound> &people);
 
@@ -109,6 +109,8 @@ private:
 	};
 	typedef QMap<PeerData*, ContactData*> ContactsData;
 	ContactsData _contactsData;
+	typedef QMap<PeerData*, bool> CheckedContacts;
+	CheckedContacts _checkedContacts;
 
 	ContactData *contactData(DialogRow *row);
 
diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h
index 377fea046..3a7421a80 100644
--- a/Telegram/SourceFiles/config.h
+++ b/Telegram/SourceFiles/config.h
@@ -17,9 +17,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 */
 #pragma once
 
-static const int32 AppVersion = 8046;
-static const wchar_t *AppVersionStr = L"0.8.46";
-static const bool DevVersion = false;
+static const int32 AppVersion = 8047;
+static const wchar_t *AppVersionStr = L"0.8.47";
+static const bool DevVersion = true;
 
 static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";
 static const wchar_t *AppName = L"Telegram Desktop";
diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp
index 427f87d3f..771efe276 100644
--- a/Telegram/SourceFiles/dialogswidget.cpp
+++ b/Telegram/SourceFiles/dialogswidget.cpp
@@ -86,12 +86,12 @@ void DialogsListWidget::paintEvent(QPaintEvent *e) {
 		if (otherStart) {
 			dialogs.list.paint(p, width(), r.top(), r.bottom(), active, selected);
 		}
-		if (contactsNoDialogs.list.count) {
+		if (contactsNoDialogs.list.count && false) {
 			contactsNoDialogs.list.paint(p, width(), r.top() - otherStart, r.bottom() - otherStart, active, selected);
 		} else if (!otherStart) {
 			p.setFont(st::noContactsFont->f);
 			p.setPen(st::noContactsColor->p);
-			p.drawText(QRect(0, 0, width(), st::noContactsHeight - (cContactsReceived() ? st::noContactsFont->height : 0)), lang(cContactsReceived() ? lng_no_contacts : lng_contacts_loading), style::al_center);
+			p.drawText(QRect(0, 0, width(), st::noContactsHeight - (cContactsReceived() ? st::noContactsFont->height : 0)), lang(cContactsReceived() ? lng_no_chats : lng_contacts_loading), style::al_center);
 		}
 	} else if (_state == FilteredState || _state == SearchedState) {
 		if (!hashtagResults.isEmpty()) {
@@ -317,7 +317,7 @@ void DialogsListWidget::onUpdateSelected(bool force) {
 		if (newSel) {
 			contactSel = false;
 		} else {
-			newSel = contactsNoDialogs.list.rowAtY(mouseY - otherStart, st::dlgHeight);
+			newSel = 0;// contactsNoDialogs.list.rowAtY(mouseY - otherStart, st::dlgHeight);
 			contactSel = true;
 		}
 		if (newSel != sel) {
@@ -442,6 +442,8 @@ void DialogsListWidget::removePeer(PeerData *peer) {
 		}
 	}
 
+	Local::removeSavedPeer(peer);
+
 	emit App::main()->dialogsUpdated();
 
 	refresh();
@@ -481,7 +483,7 @@ void DialogsListWidget::dlgUpdated(History *history) {
 		if (i != dialogs.list.rowByPeer.cend()) {
 			update(0, i.value()->pos * st::dlgHeight, width(), st::dlgHeight);
 		} else {
-			i = contactsNoDialogs.list.rowByPeer.find(history->peer->id);
+			i = contactsNoDialogs.list.rowByPeer.end();// find(history->peer->id);
 			if (i != contactsNoDialogs.list.rowByPeer.cend()) {
 				update(0, (dialogs.list.count + i.value()->pos) * st::dlgHeight, width(), st::dlgHeight);
 			}
@@ -771,6 +773,16 @@ void DialogsListWidget::dialogsReceived(const QVector<MTPDialog> &added) {
 	refresh();
 }
 
+void DialogsListWidget::addAllSavedPeers() {
+	SavedPeersByTime &saved(cRefSavedPeersByTime());
+	while (!saved.isEmpty()) {
+		History *history = App::history(saved.last()->id);
+		history->dialogs = dialogs.addToEnd(history);
+		contactsNoDialogs.del(history->peer);
+		saved.remove(saved.lastKey(), saved.last());
+	}
+}
+
 void DialogsListWidget::searchReceived(const QVector<MTPMessage> &messages, bool fromStart, int32 fullCount) {
 	if (fromStart) {
 		clearSearchResults(false);
@@ -810,7 +822,7 @@ void DialogsListWidget::contactsReceived(const QVector<MTPContact> &contacts) {
 			App::self()->contact = 1;
 		}
 	}
-	if (!sel && contactsNoDialogs.list.count) {
+	if (!sel && contactsNoDialogs.list.count && false) {
 		sel = contactsNoDialogs.list.begin;
 		contactSel = true;
 	}
@@ -827,11 +839,11 @@ int32 DialogsListWidget::addNewContact(int32 uid, bool select) {
 	if (i == dialogs.list.rowByPeer.cend()) {
 		DialogRow *added = contactsNoDialogs.addByName(history);
 		if (!added) return -1;
-		if (select) {
+		if (select && false) {
 			sel = added;
 			contactSel = true;
 		}
-		if (contactsNoDialogs.list.count == 1 && !dialogs.list.count) refresh();
+//		if (contactsNoDialogs.list.count == 1 && !dialogs.list.count) refresh();
 		return added ? ((dialogs.list.count + added->pos) * st::dlgHeight) : -1;
 	}
 	if (select) {
@@ -844,7 +856,7 @@ int32 DialogsListWidget::addNewContact(int32 uid, bool select) {
 void DialogsListWidget::refresh(bool toTop) {
 	int32 h = 0;
 	if (_state == DefaultState) {
-		h = (dialogs.list.count + contactsNoDialogs.list.count) * st::dlgHeight;
+		h = (dialogs.list.count/* + contactsNoDialogs.list.count*/) * st::dlgHeight;
 		if (h) {
 			if (!_addContactLnk.isHidden()) _addContactLnk.hide();
 		} else {
@@ -936,6 +948,15 @@ void DialogsListWidget::clearFilter() {
 
 void DialogsListWidget::addDialog(const MTPDdialog &dialog) {
 	History *history = App::history(App::peerFromMTP(dialog.vpeer), dialog.vunread_count.v, dialog.vread_inbox_max_id.v);
+	if (history->lastMsg) {
+		SavedPeersByTime &saved(cRefSavedPeersByTime());
+		while (!saved.isEmpty() && history->lastMsg->date < saved.lastKey()) {
+			History *history = App::history(saved.last()->id);
+			history->dialogs = dialogs.addToEnd(history);
+			contactsNoDialogs.del(history->peer);
+			saved.remove(saved.lastKey(), saved.last());
+		}
+	}
 	History::DialogLinks links = dialogs.addToEnd(history);
 	history->dialogs = links;
 	contactsNoDialogs.del(history->peer);
@@ -1714,6 +1735,7 @@ void DialogsWidget::onSearchMore(MsgId minMsgId) {
 void DialogsWidget::loadDialogs() {
 	if (dlgPreloading) return;
 	if (dlgCount >= 0 && dlgOffset >= dlgCount) {
+		list.addAllSavedPeers();
 		cSetDialogsReceived(true);
 		return;
 	}
diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h
index ec7c1fa13..036a7695a 100644
--- a/Telegram/SourceFiles/dialogswidget.h
+++ b/Telegram/SourceFiles/dialogswidget.h
@@ -27,6 +27,7 @@ public:
 	DialogsListWidget(QWidget *parent, MainWidget *main);
 
 	void dialogsReceived(const QVector<MTPDialog> &dialogs);
+	void addAllSavedPeers();
 	void searchReceived(const QVector<MTPMessage> &messages, bool fromStart, int32 fullCount);
 	void peopleReceived(const QString &query, const QVector<MTPContactFound> &people);
 	void showMore(int32 pixels);
diff --git a/Telegram/SourceFiles/gui/images.h b/Telegram/SourceFiles/gui/images.h
index c0d9ecd88..8ebec1dc2 100644
--- a/Telegram/SourceFiles/gui/images.h
+++ b/Telegram/SourceFiles/gui/images.h
@@ -29,6 +29,9 @@ struct StorageImageLocation {
 	}
 	StorageImageLocation(int32 width, int32 height, const MTPDfileLocation &location) : width(width), height(height), dc(location.vdc_id.v), volume(location.vvolume_id.v), local(location.vlocal_id.v), secret(location.vsecret.v) {
 	}
+	bool isNull() const {
+		return !dc;
+	}
 	int32 width, height;
 	int32 dc;
 	uint64 volume;
@@ -36,6 +39,13 @@ struct StorageImageLocation {
 	uint64 secret;
 };
 
+inline bool operator==(const StorageImageLocation &a, const StorageImageLocation &b) {
+	return !memcmp(&a, &b, sizeof(StorageImageLocation));
+}
+inline bool operator!=(const StorageImageLocation &a, const StorageImageLocation &b) {
+	return !(a == b);
+}
+
 class Image {
 public:
 
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index 2716d1c4d..a486510a7 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -613,7 +613,7 @@ HistoryItem *Histories::addToBack(const MTPmessage &msg, int msgState) {
 	if (!h.value()->loadedAtBottom()) {
 		HistoryItem *item = h.value()->addToHistory(msg);
 		if (item) {
-			h.value()->lastMsg = item;
+			h.value()->setLastMessage(item);
 			if (msgState > 0) {
 				h.value()->newItemAdded(item);
 			}
@@ -908,7 +908,8 @@ HistoryItem *History::doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem *
 		}
 	}
 	to->push_back(adding);
-	lastMsg = adding;
+	setLastMessage(adding);
+
 	adding->y = to->height;
 	if (width) {
 		int32 dh = adding->resize(width);
@@ -919,6 +920,7 @@ HistoryItem *History::doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem *
 	if (newMsg) {
 		newItemAdded(adding);
 	}
+
 	HistoryMedia *media = adding->getMedia(true);
 	if (media) {
 		HistoryMediaType mt = media->type();
@@ -1414,14 +1416,24 @@ void History::getReadyFor(MsgId msgId) {
 	}
 }
 
+void History::setLastMessage(HistoryItem *msg) {
+	if (msg) {
+		if (!lastMsg) Local::removeSavedPeer(peer);
+		lastMsg = msg;
+		lastMsgDate = msg->date;
+	} else {
+		lastMsg = 0;
+	}
+}
+
 void History::fixLastMessage(bool wasAtBottom) {
 	if (wasAtBottom && isEmpty()) {
 		wasAtBottom = false;
 	}
 	if (wasAtBottom) {
-		lastMsg = back()->back();
+		setLastMessage(back()->back());
 	} else {
-		lastMsg = 0;
+		setLastMessage(0);
 		if (App::main()) {
 			App::main()->checkPeerHistory(peer);
 		}
@@ -1482,7 +1494,7 @@ void History::clear(bool leaveItems) {
 		showFrom = 0;
 	}
 	if (!leaveItems) {
-		lastMsg = 0;
+		setLastMessage(0);
 	}
 	for (int32 i = 0; i < OverviewCount; ++i) {
 		if (!_overview[i].isEmpty() || !_overviewIds[i].isEmpty()) {
diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h
index 9e4a0810f..1379928b5 100644
--- a/Telegram/SourceFiles/history.h
+++ b/Telegram/SourceFiles/history.h
@@ -204,6 +204,7 @@ struct History : public QList<HistoryBlock*> {
 	bool isReadyFor(MsgId msgId, bool check = false) const; // has messages for showing history at msgId
 	void getReadyFor(MsgId msgId);
 
+	void setLastMessage(HistoryItem *msg);
 	void fixLastMessage(bool wasAtBottom);
 
 	MsgId minMsgId() const;
@@ -218,6 +219,7 @@ struct History : public QList<HistoryBlock*> {
 	PeerData *peer;
 	bool oldLoaded, newLoaded;
 	HistoryItem *lastMsg;
+	QDateTime lastMsgDate;
 
 	typedef QList<HistoryItem*> NotifyQueue;
 	NotifyQueue notifies;
@@ -446,11 +448,11 @@ struct DialogsList {
 
 		DialogRow *row = addToEnd(history), *change = row;
 		const QString &peerName(history->peer->name);
-		while (change->prev && change->prev->history->peer->name > peerName) {
+		while (change->prev && change->prev->history->peer->name.compare(peerName, Qt::CaseInsensitive) > 0) {
 			change = change->prev;
 		}
 		if (!insertBefore(row, change)) {
-			while (change->next != end && change->next->history->peer->name < peerName) {
+			while (change->next != end && change->next->history->peer->name.compare(peerName, Qt::CaseInsensitive) < 0) {
 				change = change->next;
 			}
 			insertAfter(row, change);
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index aaf668f36..6e506cc1a 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -2724,7 +2724,7 @@ void HistoryWidget::showPeerHistory(const PeerId &peerId, MsgId showAtMsgId) {
 			update();
 			return;
 		}
-		if (_history->mySendActions.contains(SendActionTyping)) updateSendAction(_history, SendActionTyping, false);
+		if (_history->mySendActions.contains(SendActionTyping)) updateSendAction(_history, SendActionTyping, -1);
 	}
 
 	stopGif();
@@ -2912,7 +2912,7 @@ void HistoryWidget::updateControlsVisibility() {
 	} else {
 		_scroll.show();
 	}
-	if (!_peer->chat || !_peer->asChat()->forbidden) {
+	if ((_peer->chat && !_peer->asChat()->forbidden && !_peer->asChat()->left) || (!_peer->chat && _peer->asUser()->access != UserNoAccess)) {
 		checkMentionDropdown();
 		if (isBotStart()) {
 			if (_botStart.isHidden()) {
@@ -4002,7 +4002,7 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) {
 	int32 t = unixtime();
 	if (_peer->chat) {
 		ChatData *chat = _peer->asChat();
-		if (chat->forbidden) {
+		if (chat->forbidden || chat->left) {
 			text = lang(lng_chat_status_unaccessible);
 		} else if (chat->participants.isEmpty()) {
 			text = _titlePeerText.isEmpty() ? lng_chat_status_members(lt_count, chat->count < 0 ? 0 : chat->count) : _titlePeerText;
@@ -4391,7 +4391,7 @@ void HistoryWidget::onAudioProgress(MsgId newId) {
 	HistoryItem *item = App::histItemById(newId);
 	if (item) {
 		AudioData *audio = (item->getMedia() && item->getMedia()->type() == MediaTypeAudio) ? static_cast<HistoryAudio*>(item->getMedia())->audio() : 0;
-		updateSendAction(item->history(), SendActionUploadAudio, audio->uploadOffset);
+		updateSendAction(item->history(), SendActionUploadAudio, audio ? audio->uploadOffset : 0);
 		msgUpdated(item->history()->peer->id, item);
 	}
 }
@@ -4540,7 +4540,7 @@ void HistoryWidget::updateListSize(int32 addToY, bool initial, bool loadedDown,
 	if (isBotStart()) {
 		newScrollHeight -= _botStart.height();
 	} else {
-		if (!_peer->chat || !_peer->asChat()->forbidden) {
+		if ((_peer->chat && !_peer->asChat()->forbidden && !_peer->asChat()->left) || (!_peer->chat && _peer->asUser()->access != UserNoAccess)) {
 			newScrollHeight -= (_field.height() + 2 * st::sendPadding);
 		}
 		if (replyToId() || App::main()->hasForwardingItems() || (_previewData && _previewData->pendingTill >= 0)) {
diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp
index bf141a300..b3036fce4 100644
--- a/Telegram/SourceFiles/localstorage.cpp
+++ b/Telegram/SourceFiles/localstorage.cpp
@@ -18,6 +18,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 #include "stdafx.h"
 #include "localstorage.h"
 
+#include "mainwidget.h"
 #include "lang.h"
 
 namespace {
@@ -505,6 +506,7 @@ namespace {
 		lskUserSettings      = 0x09, // no data
 		lskRecentHashtags    = 0x0a, // no data
 		lskStickers          = 0x0b, // no data
+		lskSavedPeers        = 0x0c, // no data
 	};
 
 	typedef QMap<PeerId, FileKey> DraftsMap;
@@ -530,6 +532,8 @@ namespace {
 	FileKey _recentHashtagsKey = 0;
 	bool _recentHashtagsWereRead = false;
 
+	FileKey _savedPeersKey = 0;
+
 	typedef QPair<FileKey, qint32> FileDesc; // file, size
 	typedef QMap<StorageKey, FileDesc> StorageMap;
 	StorageMap _imagesMap, _stickerImagesMap, _audiosMap;
@@ -1432,7 +1436,7 @@ namespace {
 		DraftsNotReadMap draftsNotReadMap;
 		StorageMap imagesMap, stickerImagesMap, audiosMap;
 		qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0;
-		quint64 locationsKey = 0, recentStickersKeyOld = 0, stickersKey = 0, backgroundKey = 0, userSettingsKey = 0, recentHashtagsKey = 0;
+		quint64 locationsKey = 0, recentStickersKeyOld = 0, stickersKey = 0, backgroundKey = 0, userSettingsKey = 0, recentHashtagsKey = 0, savedPeersKey = 0;
 		while (!map.stream.atEnd()) {
 			quint32 keyType;
 			map.stream >> keyType;
@@ -1512,6 +1516,9 @@ namespace {
 			case lskStickers: {
 				map.stream >> stickersKey;
 			} break;
+			case lskSavedPeers: {
+				map.stream >> savedPeersKey;
+			} break;
 			default:
 				LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType));
 				return Local::ReadMapFailed;
@@ -1535,6 +1542,7 @@ namespace {
 		_locationsKey = locationsKey;
 		_recentStickersKeyOld = recentStickersKeyOld;
 		_stickersKey = stickersKey;
+		_savedPeersKey = savedPeersKey;
 		_backgroundKey = backgroundKey;
 		_userSettingsKey = userSettingsKey;
 		_recentHashtagsKey = recentHashtagsKey;
@@ -1599,6 +1607,7 @@ namespace {
 		if (_locationsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 		if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64);
 		if (_stickersKey) mapSize += sizeof(quint32) + sizeof(quint64);
+		if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64);
 		if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64);
 		if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 		if (_recentHashtagsKey) mapSize += sizeof(quint32) + sizeof(quint64);
@@ -1642,6 +1651,9 @@ namespace {
 		if (_stickersKey) {
 			mapData.stream << quint32(lskStickers) << quint64(_stickersKey);
 		}
+		if (_savedPeersKey) {
+			mapData.stream << quint32(lskSavedPeers) << quint64(_savedPeersKey);
+		}
 		if (_backgroundKey) {
 			mapData.stream << quint32(lskBackground) << quint64(_backgroundKey);
 		}
@@ -1900,7 +1912,7 @@ namespace Local {
 		_draftsNotReadMap.clear();
 		_stickerImagesMap.clear();
 		_audiosMap.clear();
-		_locationsKey = _recentStickersKeyOld = _stickersKey = _backgroundKey = _userSettingsKey = _recentHashtagsKey = 0;
+		_locationsKey = _recentStickersKeyOld = _stickersKey = _backgroundKey = _userSettingsKey = _recentHashtagsKey = _savedPeersKey = 0;
 		_mapChanged = true;
 		_writeMap(WriteMapNow);
 
@@ -2293,6 +2305,23 @@ namespace Local {
 		return _storageAudiosSize;
 	}
 
+	void _writeStorageImageLocation(QDataStream &stream, const StorageImageLocation &loc) {
+		stream << qint32(loc.width) << qint32(loc.height);
+		stream << qint32(loc.dc) << quint64(loc.volume) << qint32(loc.local) << quint64(loc.secret);
+	}
+
+	uint32 _storageImageLocationSize() {
+		// width + height + dc + volume + local + secret
+		return sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(quint64) + sizeof(qint32) + sizeof(quint64);
+	}
+
+	StorageImageLocation _readStorageImageLocation(FileReadDescriptor &from) {
+		qint32 thumbWidth, thumbHeight, thumbDc, thumbLocal;
+		quint64 thumbVolume, thumbSecret;
+		from.stream >> thumbWidth >> thumbHeight >> thumbDc >> thumbVolume >> thumbLocal >> thumbSecret;
+		return StorageImageLocation(thumbWidth, thumbHeight, thumbDc, thumbVolume, thumbLocal, thumbSecret);
+	}
+
 	void _writeStickerSet(QDataStream &stream, uint64 setId) {
 		StickerSets::const_iterator it = cStickerSets().constFind(setId);
 		if (it == cStickerSets().cend()) return;
@@ -2321,8 +2350,7 @@ namespace Local {
 				stream << qint32(StickerSetTypeEmpty);
 			} break;
 			}
-			const StorageImageLocation &loc(doc->sticker()->loc);
-			stream << qint32(loc.width) << qint32(loc.height) << qint32(loc.dc) << quint64(loc.volume) << qint32(loc.local) << quint64(loc.secret);
+			_writeStorageImageLocation(stream, doc->sticker()->loc);
 		}
 	}
 
@@ -2358,8 +2386,8 @@ namespace Local {
 					// id + access + date + namelen + name + mimelen + mime + dc + size + width + height + type + alt + type-of-set
 					size += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + _stringSize(doc->name) + _stringSize(doc->mime) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + _stringSize(doc->sticker()->alt) + sizeof(qint32);
 
-					// thumb-width + thumb-height + thumb-dc + thumb-volume + thumb-local + thumb-secret
-					size += sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(quint64) + sizeof(qint32) + sizeof(quint64);
+					// loc
+					size += _storageImageLocationSize();
 				}
 				++setsCount;
 			}
@@ -2519,9 +2547,7 @@ namespace Local {
 				qint32 date, dc, size, width, height, type, typeOfSet;
 				stickers.stream >> id >> access >> date >> name >> mime >> dc >> size >> width >> height >> type >> alt >> typeOfSet;
 
-				qint32 thumbWidth, thumbHeight, thumbDc, thumbLocal;
-				quint64 thumbVolume, thumbSecret;
-				stickers.stream >> thumbWidth >> thumbHeight >> thumbDc >> thumbVolume >> thumbLocal >> thumbSecret;
+				StorageImageLocation thumb(_readStorageImageLocation(stickers));
 
 				if (read.contains(id)) continue;
 				read.insert(id, true);
@@ -2552,7 +2578,6 @@ namespace Local {
 					attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height)));
 				}
 
-				StorageImageLocation thumb(thumbWidth, thumbHeight, thumbDc, thumbVolume, thumbLocal, thumbSecret);
 				DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, thumb.dc ? ImagePtr(thumb) : ImagePtr(), dc, size, thumb);
 				if (!doc->sticker()) continue;
 
@@ -2705,6 +2730,193 @@ namespace Local {
 		cSetRecentSearchHashtags(search);
 	}
 
+	uint32 _peerSize(PeerData *peer) {
+		uint32 result = sizeof(quint64) + sizeof(quint64) + _storageImageLocationSize();
+		if (peer->chat) {
+			ChatData *chat = peer->asChat();
+
+			// name + count + date + version + admin + forbidden + left + invitationUrl
+			result += _stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + _stringSize(chat->invitationUrl);
+		} else {
+			UserData *user = peer->asUser();
+
+			// first + last + phone + username + access + onlineTill + contact + botInfoVersion
+			result += _stringSize(user->firstName) + _stringSize(user->lastName) + _stringSize(user->phone) + _stringSize(user->username) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32);
+		}
+		return result;
+	}
+
+	void _writePeer(QDataStream &stream, PeerData *peer) {
+		stream << quint64(peer->id) << quint64(peer->photoId);
+		_writeStorageImageLocation(stream, peer->photoLoc);
+		if (peer->chat) {
+			ChatData *chat = peer->asChat();
+
+			stream << chat->name << qint32(chat->count) << qint32(chat->date) << qint32(chat->version) << qint32(chat->admin);
+			stream << qint32(chat->forbidden ? 1 : 0) << qint32(chat->left ? 1 : 0) << chat->invitationUrl;
+		} else {
+			UserData *user = peer->asUser();
+
+			stream << user->firstName << user->lastName << user->phone << user->username << quint64(user->access) << qint32(user->onlineTill) << qint32(user->contact) << qint32(user->botInfo ? user->botInfo->version : -1);
+		}
+	}
+
+	PeerData *_readPeer(FileReadDescriptor &from) {
+		PeerData *result = 0;
+		quint64 peerId = 0, photoId = 0;
+		from.stream >> peerId >> photoId;
+
+		StorageImageLocation photoLoc(_readStorageImageLocation(from));
+
+		result = App::peer(peerId);
+		result->loaded = true;
+		if (result->chat) {
+			ChatData *chat = result->asChat();
+
+			QString name, invitationUrl;
+			qint32 count, date, version, admin, forbidden, left;
+			from.stream >> name >> count >> date >> version >> admin >> forbidden >> left >> invitationUrl;
+
+			chat->updateName(name, QString(), QString());
+			chat->count = count;
+			chat->date = date;
+			chat->version = version;
+			chat->admin = admin;
+			chat->forbidden = (forbidden == 1);
+			chat->left = (left == 1);
+			chat->invitationUrl = invitationUrl;
+
+			chat->input = MTP_inputPeerChat(MTP_int(App::chatFromPeer(chat->id)));
+
+			chat->photo = photoLoc.isNull() ? ImagePtr(chatDefPhoto(chat->colorIndex)) : ImagePtr(photoLoc);
+		} else {
+			UserData *user = result->asUser();
+
+			QString first, last, phone, username;
+			quint64 access;
+			qint32 onlineTill, contact, botInfoVersion;
+			from.stream >> first >> last >> phone >> username >> access >> onlineTill >> contact >> botInfoVersion;
+
+			bool showPhone = !isServiceUser(user->id) && (user->id != MTP::authedId()) && (contact <= 0);
+			QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString();
+
+			user->setName(first, last, pname, username);
+
+			user->access = access;
+			user->onlineTill = onlineTill;
+			user->contact = contact;
+			user->setBotInfoVersion(botInfoVersion);
+
+			if (user->id == MTP::authedId()) {
+				user->input = MTP_inputPeerSelf();
+				user->inputUser = MTP_inputUserSelf();
+			} else if (user->contact > 0 || !user->access) {
+				user->input = MTP_inputPeerContact(MTP_int(App::userFromPeer(user->id)));
+				user->inputUser = MTP_inputUserContact(MTP_int(App::userFromPeer(user->id)));
+			} else {
+				user->input = MTP_inputPeerForeign(MTP_int(App::userFromPeer(user->id)), MTP_long(user->access));
+				user->inputUser = MTP_inputUserForeign(MTP_int(App::userFromPeer(user->id)), MTP_long(user->access));
+			}
+
+			user->photo = photoLoc.isNull() ? ImagePtr(userDefPhoto(user->colorIndex)) : ImagePtr(photoLoc);
+		}
+		App::markPeerUpdated(result);
+		emit App::main()->peerPhotoChanged(result);
+		return result;
+	}
+
+	void writeSavedPeers() {
+		if (!_working()) return;
+
+		const SavedPeers &saved(cSavedPeers());
+		if (saved.isEmpty()) {
+			if (_savedPeersKey) {
+				clearKey(_savedPeersKey);
+				_savedPeersKey = 0;
+				_mapChanged = true;
+			}
+			_writeMap();
+		} else {
+			if (!_savedPeersKey) {
+				_savedPeersKey = genKey();
+				_mapChanged = true;
+				_writeMap(WriteMapFast);
+			}
+			quint32 size = sizeof(quint32);
+			for (SavedPeers::const_iterator i = saved.cbegin(); i != saved.cend(); ++i) {
+				size += _peerSize(i.key()) + _dateTimeSize();
+			}
+
+			EncryptedDescriptor data(size);
+			data.stream << quint32(saved.size());
+			for (SavedPeers::const_iterator i = saved.cbegin(); i != saved.cend(); ++i) {
+				_writePeer(data.stream, i.key());
+				data.stream << i.value();
+			}
+
+			FileWriteDescriptor file(_savedPeersKey);
+			file.writeEncrypted(data);
+		}
+	}
+
+	void readSavedPeers() {
+		if (!_savedPeersKey) return;
+
+		FileReadDescriptor saved;
+		if (!readEncryptedFile(saved, _savedPeersKey)) {
+			clearKey(_savedPeersKey);
+			_savedPeersKey = 0;
+			_writeMap();
+			return;
+		}
+
+		quint32 count = 0;
+		saved.stream >> count;
+		cRefSavedPeers().clear();
+		cRefSavedPeersByTime().clear();
+		QList<PeerData*> peers;
+		peers.reserve(count);
+		for (uint32 i = 0; i < count; ++i) {
+			PeerData *peer = _readPeer(saved);
+			if (!peer) break;
+
+			QDateTime t;
+			saved.stream >> t;
+			
+			cRefSavedPeers().insert(peer, t);
+			cRefSavedPeersByTime().insert(t, peer);
+			peers.push_back(peer);
+		}
+		App::emitPeerUpdated();
+		App::api()->requestPeers(peers);
+	}
+
+	void addSavedPeer(PeerData *peer, const QDateTime &position) {
+		SavedPeers &savedPeers(cRefSavedPeers());
+		SavedPeers::iterator i = savedPeers.find(peer);
+		if (i == savedPeers.cend()) {
+			savedPeers.insert(peer, position);
+		} else if (i.value() != position) {
+			cRefSavedPeersByTime().remove(i.value(), peer);
+			i.value() = position;
+			cRefSavedPeersByTime().insert(i.value(), peer);
+		}
+		writeSavedPeers();
+	}
+
+	void removeSavedPeer(PeerData *peer) {
+		SavedPeers &savedPeers(cRefSavedPeers());
+		if (savedPeers.isEmpty()) return;
+
+		SavedPeers::iterator i = savedPeers.find(peer);
+		if (i != savedPeers.cend()) {
+			cRefSavedPeersByTime().remove(i.value(), peer);
+			savedPeers.erase(i);
+
+			writeSavedPeers();
+		}
+	}
+
 	struct ClearManagerData {
 		QThread *thread;
 		StorageMap images, stickers, audios;
@@ -2764,6 +2976,10 @@ namespace Local {
 				_recentHashtagsKey = 0;
 				_mapChanged = true;
 			}
+			if (_savedPeersKey) {
+				_savedPeersKey = 0;
+				_mapChanged = true;
+			}
 			_writeMap();
 		} else {
 			if (task & ClearManagerStorage) {
diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h
index d3df02d3b..65fe3c7cf 100644
--- a/Telegram/SourceFiles/localstorage.h
+++ b/Telegram/SourceFiles/localstorage.h
@@ -141,4 +141,8 @@ namespace Local {
 	void writeRecentHashtags();
 	void readRecentHashtags();
 
+	void addSavedPeer(PeerData *peer, const QDateTime &position);
+	void removeSavedPeer(PeerData *peer);
+	void readSavedPeers();
+
 };
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 47922db5b..0013557e3 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -788,7 +788,7 @@ void MainWidget::deleteConversation(PeerData *peer) {
 void MainWidget::clearHistory(PeerData *peer) {
 	History *h = App::history(peer->id);
 	if (h->lastMsg) {
-//		Local::savePeerPosition(h->peer, h->lastMsg->date);
+		Local::addSavedPeer(h->peer, h->lastMsg->date);
 	}
 	h->clear();
 	h->newLoaded = h->oldLoaded = true;
@@ -813,6 +813,8 @@ bool MainWidget::addParticipantFail(UserData *user, const RPCError &error) {
 
 	QString text = lang(lng_failed_add_participant);
 	if (error.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group
+	} else if (error.type() == "USER_NOT_MUTUAL_CONTACT") { // trying to return user who does not have me in contacts
+		text = lang(lng_failed_add_not_mutual);
 	} else if (error.type() == "USER_ALREADY_PARTICIPANT" && user->botInfo) {
 		text = lang(lng_bot_already_in_group);
 	}
@@ -856,7 +858,12 @@ void MainWidget::checkedHistory(PeerData *peer, const MTPmessages_Messages &resu
 		if ((profile && profile->peer() == peer) || (overview && overview->peer() == peer) || _stack.contains(peer) || history.peer() == peer) {
 			showDialogs();
 		}
-		dialogs.removePeer(peer);
+		if (peer->chat && peer->asChat()->left) {
+			dialogs.removePeer(peer);
+		} else {
+			History *h = App::historyLoaded(peer->id);
+			if (h) Local::addSavedPeer(peer, h->lastMsgDate);
+		}
 	} else {
 		History *h = App::historyLoaded(peer->id);
 		if (!h->lastMsg) {
@@ -2757,6 +2764,8 @@ void MainWidget::start(const MTPUser &user) {
 		Local::writeMtpData();
 	}
 
+	Local::readSavedPeers();
+
 	cSetOtherOnline(0);
 	App::feedUsers(MTP_vector<MTPUser>(1, user));
 	App::app()->startUpdateCheck();
diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp
index b8eb89701..a9a61afba 100644
--- a/Telegram/SourceFiles/settings.cpp
+++ b/Telegram/SourceFiles/settings.cpp
@@ -159,6 +159,9 @@ int gOtherOnline = 0;
 
 float64 gSongVolume = 0.9;
 
+SavedPeers gSavedPeers;
+SavedPeersByTime gSavedPeersByTime;
+
 void settingsParseArgs(int argc, char *argv[]) {
 	gCustomNotifies = true;
 #ifdef Q_OS_MAC
diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h
index 7443c09e0..f5b577fbf 100644
--- a/Telegram/SourceFiles/settings.h
+++ b/Telegram/SourceFiles/settings.h
@@ -308,4 +308,10 @@ DeclareSetting(int, OtherOnline);
 
 DeclareSetting(float64, SongVolume);
 
+struct PeerData;
+typedef QMap<PeerData*, QDateTime> SavedPeers;
+typedef QMultiMap<QDateTime, PeerData*> SavedPeersByTime;
+DeclareRefSetting(SavedPeers, SavedPeers);
+DeclareRefSetting(SavedPeersByTime, SavedPeersByTime);
+
 void settingsParseArgs(int argc, char *argv[]);
diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp
index d60be556e..080b346af 100644
--- a/Telegram/SourceFiles/structs.cpp
+++ b/Telegram/SourceFiles/structs.cpp
@@ -90,10 +90,10 @@ NotifySettingsPtr globalNotifyAllPtr = UnknownNotifySettings, globalNotifyUsersP
 PeerData::PeerData(const PeerId &id) : id(id)
 , loaded(false)
 , chat(App::isChat(id))
-, access(0)
 , colorIndex(peerColorIndex(id))
 , color(peerColor(colorIndex))
 , photo(chat ? chatDefPhoto(colorIndex) : userDefPhoto(colorIndex))
+, photoId(UnknownPeerPhotoId)
 , nameVersion(0)
 , notify(UnknownNotifySettings)
 {
@@ -132,14 +132,16 @@ void PeerData::updateName(const QString &newName, const QString &newNameOrPhone,
 	}
 }
 
-void UserData::setPhoto(const MTPUserProfilePhoto &p) {
+void UserData::setPhoto(const MTPUserProfilePhoto &p) { // see Local::readPeer as well
 	PhotoId newPhotoId = photoId;
 	ImagePtr newPhoto = photo;
+	StorageImageLocation newPhotoLoc = photoLoc;
 	switch (p.type()) {
 	case mtpc_userProfilePhoto: {
 		const MTPDuserProfilePhoto d(p.c_userProfilePhoto());
 		newPhotoId = d.vphoto_id.v;
-		newPhoto = ImagePtr(160, 160, d.vphoto_small, userDefPhoto(colorIndex));
+		newPhotoLoc = App::imageLocation(160, 160, d.vphoto_small);
+		newPhoto = newPhotoLoc.isNull() ? userDefPhoto(colorIndex) : ImagePtr(newPhotoLoc);
 		//App::feedPhoto(App::photoFromUserPhoto(MTP_int(id & 0xFFFFFFFF), MTP_int(unixtime()), p));
 	} break;
 	default: {
@@ -151,11 +153,13 @@ void UserData::setPhoto(const MTPUserProfilePhoto &p) {
 		} else {
 			newPhoto = userDefPhoto(colorIndex);
 		}
+		newPhotoLoc = StorageImageLocation();
 	} break;
 	}
-	if (newPhotoId != photoId || newPhoto.v() != photo.v()) {
+	if (newPhotoId != photoId || newPhoto.v() != photo.v() || newPhotoLoc != photoLoc) {
 		photoId = newPhotoId;
 		photo = newPhoto;
+		photoLoc = newPhotoLoc;
 		emit App::main()->peerPhotoChanged(this);
 	}
 }
@@ -226,6 +230,7 @@ void UserData::setBotInfoVersion(int32 version) {
 		botInfo->inited = false;
 	}
 }
+
 void UserData::setBotInfo(const MTPBotInfo &info) {
 	switch (info.type()) {
 	case mtpc_botInfoEmpty:
@@ -305,23 +310,33 @@ void UserData::madeAction() {
 	}
 }
 
-void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) {
+void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see Local::readPeer as well
+	PhotoId newPhotoId = photoId;
+	ImagePtr newPhoto = photo;
+	StorageImageLocation newPhotoLoc = photoLoc;
 	switch (p.type()) {
 	case mtpc_chatPhoto: {
 		const MTPDchatPhoto d(p.c_chatPhoto());
-		photo = ImagePtr(160, 160, d.vphoto_small, chatDefPhoto(colorIndex));
-		photoFull = ImagePtr(640, 640, d.vphoto_big, chatDefPhoto(colorIndex));
 		if (phId != UnknownPeerPhotoId) {
-			photoId = phId;
+			newPhotoId = phId;
 		}
+		newPhotoLoc = App::imageLocation(160, 160, d.vphoto_small);
+		newPhoto = newPhotoLoc.isNull() ? chatDefPhoto(colorIndex) : ImagePtr(newPhotoLoc);
+//		photoFull = ImagePtr(640, 640, d.vphoto_big, chatDefPhoto(colorIndex));
 	} break;
 	default: {
-		photo = chatDefPhoto(colorIndex);
-		photoFull = ImagePtr();
-		photoId = 0;
+		newPhotoId = 0;
+		newPhotoLoc = StorageImageLocation();
+		newPhoto = chatDefPhoto(colorIndex);
+//		photoFull = ImagePtr();
 	} break;
 	}
-	emit App::main()->peerPhotoChanged(this);
+	if (newPhotoId != photoId || newPhoto.v() != photo.v() || newPhotoLoc != photoLoc) {
+		photoId = newPhotoId;
+		photo = newPhoto;
+		photoLoc = newPhotoLoc;
+		emit App::main()->peerPhotoChanged(this);
+	}
 }
 
 void PhotoLink::onClick(Qt::MouseButton button) const {
diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h
index 152d47b1c..6bcdd6c5c 100644
--- a/Telegram/SourceFiles/structs.h
+++ b/Telegram/SourceFiles/structs.h
@@ -59,8 +59,10 @@ style::color peerColor(int32 index);
 ImagePtr userDefPhoto(int32 index);
 ImagePtr chatDefPhoto(int32 index);
 
-struct ChatData;
+static const PhotoId UnknownPeerPhotoId = 0xFFFFFFFFFFFFFFFFULL;
+
 struct UserData;
+struct ChatData;
 struct PeerData {
 	PeerData(const PeerId &id);
 	virtual ~PeerData() {
@@ -94,13 +96,13 @@ struct PeerData {
 
 	bool loaded;
 	bool chat;
-	uint64 access;
 	MTPinputPeer input;
-	MTPinputUser inputUser;
 
 	int32 colorIndex;
 	style::color color;
 	ImagePtr photo;
+	PhotoId photoId;
+	StorageImageLocation photoLoc;
 
 	int32 nameVersion;
 
@@ -165,11 +167,9 @@ struct BotInfo {
 	QString startToken, startGroupToken;
 };
 
-static const PhotoId UnknownPeerPhotoId = 0xFFFFFFFFFFFFFFFFULL;
-
 struct PhotoData;
 struct UserData : public PeerData {
-	UserData(const PeerId &id) : PeerData(id), photoId(UnknownPeerPhotoId), lnk(new PeerLink(this)), onlineTill(0), contact(-1), photosCount(-1), botInfo(0) {
+	UserData(const PeerId &id) : PeerData(id), access(0), lnk(new PeerLink(this)), onlineTill(0), contact(-1), photosCount(-1), botInfo(0) {
 	}
 	void setPhoto(const MTPUserProfilePhoto &photo);
 	void setName(const QString &first, const QString &last, const QString &phoneName, const QString &username);
@@ -180,12 +180,15 @@ struct UserData : public PeerData {
 
 	void madeAction(); // pseudo-online
 
+	uint64 access;
+
+	MTPinputUser inputUser;
+
 	QString firstName;
 	QString lastName;
 	QString username;
 	QString phone;
 	Text nameText;
-	PhotoId photoId;
 	TextLinkPtr lnk;
 	int32 onlineTill;
 	int32 contact; // -1 - not contact, cant add (self, empty, deleted, foreign), 0 - not contact, can add (request), 1 - contact
@@ -198,7 +201,7 @@ struct UserData : public PeerData {
 };
 
 struct ChatData : public PeerData {
-	ChatData(const PeerId &id) : PeerData(id), count(0), date(0), version(0), left(false), forbidden(true), botStatus(0), photoId(UnknownPeerPhotoId) {
+	ChatData(const PeerId &id) : PeerData(id), count(0), date(0), version(0), left(false), forbidden(true), botStatus(0) {
 	}
 	void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = UnknownPeerPhotoId);
 	int32 count;
@@ -216,8 +219,7 @@ struct ChatData : public PeerData {
 	typedef QMap<UserData*, bool> MarkupSenders;
 	MarkupSenders markupSenders;
 	int32 botStatus; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other
-	ImagePtr photoFull;
-	PhotoId photoId;
+//	ImagePtr photoFull;
 	QString invitationUrl;
 	// geo
 };
diff --git a/Telegram/SourceFiles/telegram.qrc b/Telegram/SourceFiles/telegram.qrc
index 3418022e0..6a0af5648 100644
--- a/Telegram/SourceFiles/telegram.qrc
+++ b/Telegram/SourceFiles/telegram.qrc
@@ -1,42 +1,42 @@
 <RCC>
-  <qresource prefix="/gui">
-    <file>art/fonts/OpenSans-Regular.ttf</file>
-    <file>art/fonts/OpenSans-Bold.ttf</file>
-    <file>art/fonts/OpenSans-Semibold.ttf</file>
-    <file>art/newmsg.wav</file>
-    <file>art/bg.jpg</file>
-    <file>art/bg0.png</file>
-    <file>art/sprite.png</file>
-    <file>art/sprite_125x.png</file>
-    <file>art/sprite_150x.png</file>
-    <file>art/sprite_200x.png</file>
-    <file>art/blank.gif</file>
-    <file>art/icon256.png</file>
-    <file>art/iconbig256.png</file>
-  </qresource>
-  <qresource prefix="/ava">
-    <file>art/chatcolor1.png</file>
-    <file>art/chatcolor2.png</file>
-    <file>art/chatcolor3.png</file>
-    <file>art/chatcolor4.png</file>
-    <file>art/usercolor1.png</file>
-    <file>art/usercolor2.png</file>
-    <file>art/usercolor3.png</file>
-    <file>art/usercolor4.png</file>
-    <file>art/usercolor5.png</file>
-    <file>art/usercolor6.png</file>
-    <file>art/usercolor7.png</file>
-    <file>art/usercolor8.png</file>
-  </qresource>
-  <qresource prefix="/qt-project.org">
-    <file>qmime/freedesktop.org.xml</file>
-  </qresource>
-  <qresource prefix="/langs">
-    <file alias="lang_it.strings">langs/lang_it.strings</file>
-    <file alias="lang_es.strings">langs/lang_es.strings</file>
-    <file alias="lang_de.strings">langs/lang_de.strings</file>
-    <file alias="lang_nl.strings">langs/lang_nl.strings</file>
-    <file alias="lang_pt_BR.strings">langs/lang_pt_BR.strings</file>
-    <file alias="lang_ko.strings">langs/lang_ko.strings</file>
-  </qresource>
+    <qresource prefix="/gui">
+        <file>art/fonts/OpenSans-Regular.ttf</file>
+        <file>art/fonts/OpenSans-Bold.ttf</file>
+        <file>art/fonts/OpenSans-Semibold.ttf</file>
+        <file>art/newmsg.wav</file>
+        <file>art/bg.jpg</file>
+        <file>art/bg0.png</file>
+        <file>art/sprite.png</file>
+        <file>art/sprite_125x.png</file>
+        <file>art/sprite_150x.png</file>
+        <file>art/sprite_200x.png</file>
+        <file>art/blank.gif</file>
+        <file>art/icon256.png</file>
+        <file>art/iconbig256.png</file>
+    </qresource>
+    <qresource prefix="/ava">
+        <file>art/chatcolor1.png</file>
+        <file>art/chatcolor2.png</file>
+        <file>art/chatcolor3.png</file>
+        <file>art/chatcolor4.png</file>
+        <file>art/usercolor1.png</file>
+        <file>art/usercolor2.png</file>
+        <file>art/usercolor3.png</file>
+        <file>art/usercolor4.png</file>
+        <file>art/usercolor5.png</file>
+        <file>art/usercolor6.png</file>
+        <file>art/usercolor7.png</file>
+        <file>art/usercolor8.png</file>
+    </qresource>
+    <qresource prefix="/qt-project.org">
+        <file>qmime/freedesktop.org.xml</file>
+    </qresource>
+    <qresource prefix="/langs">
+        <file alias="lang_it.strings">langs/lang_it.strings</file>
+        <file alias="lang_es.strings">langs/lang_es.strings</file>
+        <file alias="lang_de.strings">langs/lang_de.strings</file>
+        <file alias="lang_nl.strings">langs/lang_nl.strings</file>
+        <file alias="lang_pt_BR.strings">langs/lang_pt_BR.strings</file>
+        <file alias="lang_ko.strings">langs/lang_ko.strings</file>
+    </qresource>
 </RCC>
diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist
index 745d81e8e..b066496f6 100644
--- a/Telegram/Telegram.plist
+++ b/Telegram/Telegram.plist
@@ -11,7 +11,7 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>0.8.46</string>
+	<string>0.8.47</string>
         <key>LSMinimumSystemVersion</key>
         <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
 	<key>CFBundleSignature</key>
diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc
index dee5d4b5f7d0cfaf265e476220a164294dd29cb2..14b5bc3c7f1352997641b0788d79ece1cb29d006 100644
GIT binary patch
delta 53
zcmZ3Yy+nIM5ig_p<YHc9M)S?fc_%WlnltDz7);(Mpu0IsfQJ#z6`#CBP-yWIK^6d;
C&<>6O

delta 53
zcmZ3Yy+nIM5ig_J<YHc9MzhV!c_%Wlnlb1x7);(Mpu0IsfQJ#z6`#CBP-yWIK^6d;
COAd+v

diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj
index 2797647c0..80b18b34f 100644
--- a/Telegram/Telegram.vcxproj
+++ b/Telegram/Telegram.vcxproj
@@ -130,6 +130,7 @@
       <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+      <AdditionalOptions>/Zm110 %(AdditionalOptions)</AdditionalOptions>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj
index 175b9d219..d0f9fc4ba 100644
--- a/Telegram/Telegram.xcodeproj/project.pbxproj
+++ b/Telegram/Telegram.xcodeproj/project.pbxproj
@@ -1707,7 +1707,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.8.46;
+				CURRENT_PROJECT_VERSION = 0.8.47;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
 				GCC_OPTIMIZATION_LEVEL = 0;
@@ -1725,7 +1725,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				COPY_PHASE_STRIP = YES;
-				CURRENT_PROJECT_VERSION = 0.8.46;
+				CURRENT_PROJECT_VERSION = 0.8.47;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_OPTIMIZATION_LEVEL = fast;
 				GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h;
@@ -1751,10 +1751,10 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CODE_SIGN_IDENTITY = "";
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.8.46;
+				CURRENT_PROJECT_VERSION = 0.8.47;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DYLIB_COMPATIBILITY_VERSION = 0.8;
-				DYLIB_CURRENT_VERSION = 0.8.46;
+				DYLIB_CURRENT_VERSION = 0.8.47;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				FRAMEWORK_SEARCH_PATHS = "";
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@@ -1885,10 +1885,10 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CODE_SIGN_IDENTITY = "";
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.8.46;
+				CURRENT_PROJECT_VERSION = 0.8.47;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DYLIB_COMPATIBILITY_VERSION = 0.8;
-				DYLIB_CURRENT_VERSION = 0.8.46;
+				DYLIB_CURRENT_VERSION = 0.8.47;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				FRAMEWORK_SEARCH_PATHS = "";
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
diff --git a/Telegram/Version.sh b/Telegram/Version.sh
index 8f625316a..8761287d7 100755
--- a/Telegram/Version.sh
+++ b/Telegram/Version.sh
@@ -1,2 +1,2 @@
-echo 0.8 8046 0.8.46 0
+echo 0.8 8047 0.8.47 1
 # AppVersionStrMajor AppVersion AppVersionStr DevChannel