Display and follow unread mentions in history.
This commit is contained in:
		
							parent
							
								
									7ad21ff713
								
							
						
					
					
						commit
						e209737b1a
					
				
					 31 changed files with 747 additions and 357 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								Telegram/Resources/icons/history_unread_mention.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Telegram/Resources/icons/history_unread_mention.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 583 B | 
							
								
								
									
										
											BIN
										
									
								
								Telegram/Resources/icons/history_unread_mention@2x.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Telegram/Resources/icons/history_unread_mention@2x.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 995 B | 
|  | @ -43,6 +43,9 @@ constexpr auto kSaveCloudDraftTimeout = 1000; // save draft to the cloud with 1 | |||
| constexpr auto kSaveDraftBeforeQuitTimeout = 1500; // give the app 1.5 secs to save drafts to cloud when quitting
 | ||||
| constexpr auto kSmallDelayMs = 5; | ||||
| constexpr auto kStickersUpdateTimeout = 3600000; // update not more than once in an hour
 | ||||
| constexpr auto kUnreadMentionsPreloadIfLess = 5; | ||||
| constexpr auto kUnreadMentionsFirstRequestLimit = 10; | ||||
| constexpr auto kUnreadMentionsNextRequestLimit = 100; | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
|  | @ -1644,9 +1647,9 @@ void ApiWrap::applyUpdateNoPtsCheck(const MTPUpdate &update) { | |||
| 
 | ||||
| 	case mtpc_updateReadMessagesContents: { | ||||
| 		auto &d = update.c_updateReadMessagesContents(); | ||||
| 		auto &v = d.vmessages.v; | ||||
| 		for (auto i = 0, l = v.size(); i < l; ++i) { | ||||
| 			if (auto item = App::histItemById(NoChannel, v.at(i).v)) { | ||||
| 		auto possiblyReadMentions = base::flat_set<MsgId>(); | ||||
| 		for_const (auto &msgId, d.vmessages.v) { | ||||
| 			if (auto item = App::histItemById(NoChannel, msgId.v)) { | ||||
| 				if (item->isMediaUnread()) { | ||||
| 					item->markMediaRead(); | ||||
| 					Ui::repaintHistoryItem(item); | ||||
|  | @ -1656,8 +1659,12 @@ void ApiWrap::applyUpdateNoPtsCheck(const MTPUpdate &update) { | |||
| 						item->history()->peer->asUser()->madeAction(when); | ||||
| 					} | ||||
| 				} | ||||
| 			} else { | ||||
| 				// Perhaps it was an unread mention!
 | ||||
| 				possiblyReadMentions.insert(msgId.v); | ||||
| 			} | ||||
| 		} | ||||
| 		checkForUnreadMentions(possiblyReadMentions); | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateReadHistoryInbox: { | ||||
|  | @ -1762,4 +1769,40 @@ void ApiWrap::jumpToDate(gsl::not_null<PeerData*> peer, const QDate &date) { | |||
| 	}).send(); | ||||
| } | ||||
| 
 | ||||
| void ApiWrap::preloadEnoughUnreadMentions(gsl::not_null<History*> history) { | ||||
| 	auto fullCount = history->getUnreadMentionsCount(); | ||||
| 	auto loadedCount = history->getUnreadMentionsLoadedCount(); | ||||
| 	auto allLoaded = (fullCount >= 0) ? (loadedCount >= fullCount) : false; | ||||
| 	if (fullCount < 0 || loadedCount >= kUnreadMentionsPreloadIfLess || allLoaded) { | ||||
| 		return; | ||||
| 	} | ||||
| 	if (_unreadMentionsRequests.contains(history)) { | ||||
| 		return; | ||||
| 	} | ||||
| 	auto offsetId = loadedCount ? history->getMaxLoadedUnreadMention() : 1; | ||||
| 	auto limit = loadedCount ? kUnreadMentionsNextRequestLimit : kUnreadMentionsFirstRequestLimit; | ||||
| 	auto addOffset = loadedCount ? -(limit + 1) : -limit; | ||||
| 	auto maxId = 0; | ||||
| 	auto minId = 0; | ||||
| 	auto requestId = request(MTPmessages_GetUnreadMentions(history->peer->input, MTP_int(offsetId), MTP_int(addOffset), MTP_int(limit), MTP_int(maxId), MTP_int(minId))).done([this, history](const MTPmessages_Messages &result) { | ||||
| 		_unreadMentionsRequests.remove(history); | ||||
| 		history->addUnreadMentionsSlice(result); | ||||
| 	}).fail([this, history](const RPCError &error) { | ||||
| 		_unreadMentionsRequests.remove(history); | ||||
| 	}).send(); | ||||
| 	_unreadMentionsRequests.emplace(history, requestId); | ||||
| } | ||||
| 
 | ||||
| void ApiWrap::checkForUnreadMentions(const base::flat_set<MsgId> &possiblyReadMentions, ChannelData *channel) { | ||||
| 	for (auto msgId : possiblyReadMentions) { | ||||
| 		requestMessageData(channel, msgId, [](ChannelData *channel, MsgId msgId) { | ||||
| 			if (auto item = App::histItemById(channel, msgId)) { | ||||
| 				if (item->mentionsMe()) { | ||||
| 					item->markMediaRead(); | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| ApiWrap::~ApiWrap() = default; | ||||
|  |  | |||
|  | @ -23,6 +23,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org | |||
| #include "base/timer.h" | ||||
| #include "core/single_timer.h" | ||||
| #include "mtproto/sender.h" | ||||
| #include "base/flat_map.h" | ||||
| #include "base/flat_set.h" | ||||
| 
 | ||||
| class AuthSession; | ||||
| 
 | ||||
|  | @ -98,6 +100,9 @@ public: | |||
| 
 | ||||
| 	void jumpToDate(gsl::not_null<PeerData*> peer, const QDate &date); | ||||
| 
 | ||||
| 	void preloadEnoughUnreadMentions(gsl::not_null<History*> history); | ||||
| 	void checkForUnreadMentions(const base::flat_set<MsgId> &possiblyReadMentions, ChannelData *channel = nullptr); | ||||
| 
 | ||||
| 	~ApiWrap(); | ||||
| 
 | ||||
| private: | ||||
|  | @ -189,6 +194,8 @@ private: | |||
| 
 | ||||
| 	mtpRequestId _contactsStatusesRequestId = 0; | ||||
| 
 | ||||
| 	base::flat_map<gsl::not_null<History*>, mtpRequestId> _unreadMentionsRequests; | ||||
| 
 | ||||
| 	base::Observable<PeerData*> _fullPeerUpdated; | ||||
| 
 | ||||
| }; | ||||
|  |  | |||
|  | @ -25,11 +25,23 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org | |||
| // We use base::variant<> alias and base::get_if() helper while we don't have std::variant<>.
 | ||||
| namespace base { | ||||
| 
 | ||||
| template <typename... Types> | ||||
| using variant = mapbox::util::variant<Types...>; | ||||
| struct null_variant_type { | ||||
| }; | ||||
| 
 | ||||
| inline constexpr null_variant_type null_variant() { | ||||
| 	return null_variant_type {}; | ||||
| } | ||||
| 
 | ||||
| inline bool operator<(null_variant_type a, null_variant_type b) { | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| inline bool operator==(null_variant_type a, null_variant_type b) { | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| template <typename... Types> | ||||
| using optional_variant = variant<std::nullptr_t, Types...>; | ||||
| using variant = mapbox::util::variant<Types...>; | ||||
| 
 | ||||
| template <typename T, typename... Types> | ||||
| inline T *get_if(variant<Types...> *v) { | ||||
|  | @ -41,9 +53,36 @@ inline const T *get_if(const variant<Types...> *v) { | |||
| 	return (v && v->template is<T>()) ? &v->template get_unchecked<T>() : nullptr; | ||||
| } | ||||
| 
 | ||||
| template <typename... Types> | ||||
| using optional_variant = variant<null_variant_type, Types...>; | ||||
| 
 | ||||
| template <typename... Types> | ||||
| inline bool is_null_variant(const optional_variant<Types...> &variant) { | ||||
| 	return get_if<std::nullptr_t>(&variant) != nullptr; | ||||
| 	return get_if<null_variant_type>(&variant) != nullptr; | ||||
| } | ||||
| 
 | ||||
| template <typename Type> | ||||
| using optional = optional_variant<Type>; | ||||
| 
 | ||||
| using null_optional_type = null_variant_type; | ||||
| 
 | ||||
| template <typename Type> | ||||
| inline Type *get_if(optional<Type> *v) { | ||||
| 	return (v && v->template is<Type>()) ? &v->template get_unchecked<Type>() : nullptr; | ||||
| } | ||||
| 
 | ||||
| template <typename Type> | ||||
| inline const Type *get_if(const optional<Type> *v) { | ||||
| 	return (v && v->template is<Type>()) ? &v->template get_unchecked<Type>() : nullptr; | ||||
| } | ||||
| 
 | ||||
| template <typename Type> | ||||
| inline bool is_null_optional(const optional<Type> &optional) { | ||||
| 	return is_null_variant(optional); | ||||
| } | ||||
| 
 | ||||
| inline constexpr null_optional_type null_optional() { | ||||
| 	return null_optional_type {}; | ||||
| } | ||||
| 
 | ||||
| } // namespace base
 | ||||
|  |  | |||
|  | @ -979,7 +979,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) { | |||
| 	_previewTimer.stop(); | ||||
| 
 | ||||
| 	auto pressed = _pressed; | ||||
| 	setPressed(nullptr); | ||||
| 	setPressed(base::null_variant()); | ||||
| 	if (pressed != _selected) { | ||||
| 		update(); | ||||
| 	} | ||||
|  | @ -1104,8 +1104,8 @@ void StickersListWidget::enterFromChildEvent(QEvent *e, QWidget *child) { | |||
| } | ||||
| 
 | ||||
| void StickersListWidget::clearSelection() { | ||||
| 	setPressed(nullptr); | ||||
| 	setSelected(nullptr); | ||||
| 	setPressed(base::null_variant()); | ||||
| 	setSelected(base::null_variant()); | ||||
| 	update(); | ||||
| } | ||||
| 
 | ||||
|  | @ -1398,7 +1398,7 @@ void StickersListWidget::updateSelected() { | |||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto newSelected = OverState { nullptr }; | ||||
| 	auto newSelected = OverState { base::null_variant() }; | ||||
| 	auto p = mapFromGlobal(_lastMousePosition); | ||||
| 	if (!rect().contains(p) | ||||
| 		|| p.y() < getVisibleTop() || p.y() >= getVisibleBottom() | ||||
|  |  | |||
|  | @ -226,8 +226,8 @@ private: | |||
| 
 | ||||
| 	Footer *_footer = nullptr; | ||||
| 
 | ||||
| 	OverState _selected = nullptr; | ||||
| 	OverState _pressed = nullptr; | ||||
| 	OverState _selected = base::null_variant(); | ||||
| 	OverState _pressed = base::null_variant(); | ||||
| 	QPoint _lastMousePosition; | ||||
| 
 | ||||
| 	Text _megagroupSetAbout; | ||||
|  |  | |||
|  | @ -1458,6 +1458,7 @@ void DialogsInner::dialogsReceived(const QVector<MTPDialog> &added) { | |||
| 		} | ||||
| 
 | ||||
| 		auto history = App::historyFromDialog(peerId, d.vunread_count.v, d.vread_inbox_max_id.v, d.vread_outbox_max_id.v); | ||||
| 		history->setUnreadMentionsCount(d.vunread_mentions_count.v); | ||||
| 		auto peer = history->peer; | ||||
| 		if (auto channel = peer->asChannel()) { | ||||
| 			if (d.has_pts()) { | ||||
|  |  | |||
|  | @ -293,8 +293,9 @@ void RowPainter::paint(Painter &p, const Row *row, int fullWidth, bool active, b | |||
| 		cloudDraft = nullptr; // Draw item, if draft is older.
 | ||||
| 	} | ||||
| 	paintRow(p, row, history, item, cloudDraft, displayDate(), fullWidth, active, selected, onlyBackground, ms, [&p, fullWidth, active, selected, ms, history, unreadCount](int nameleft, int namewidth, HistoryItem *item) { | ||||
| 		int availableWidth = namewidth; | ||||
| 		int texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; | ||||
| 		auto availableWidth = namewidth; | ||||
| 		auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; | ||||
| 		auto hadOneBadge = false; | ||||
| 		if (unreadCount) { | ||||
| 			auto counter = QString::number(unreadCount); | ||||
| 			auto mutedCounter = history->mute(); | ||||
|  | @ -307,10 +308,31 @@ void RowPainter::paint(Painter &p, const Row *row, int fullWidth, bool active, b | |||
| 			st.muted = history->mute(); | ||||
| 			paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth); | ||||
| 			availableWidth -= unreadWidth + st.padding; | ||||
| 
 | ||||
| 			hadOneBadge = true; | ||||
| 		} else if (history->isPinnedDialog()) { | ||||
| 			auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); | ||||
| 			icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); | ||||
| 			availableWidth -= icon.width() + st::dialogsUnreadPadding; | ||||
| 
 | ||||
| 			hadOneBadge = true; | ||||
| 		} | ||||
| 		if (history->hasUnreadMentions()) { | ||||
| 			auto counter = qsl("@"); | ||||
| 			auto unreadRight = fullWidth - st::dialogsPadding.x() - (namewidth - availableWidth); | ||||
| 			if (hadOneBadge) { | ||||
| 				unreadRight -= st::dialogsUnreadPadding; | ||||
| 			} | ||||
| 			auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2; | ||||
| 			auto unreadWidth = 0; | ||||
| 
 | ||||
| 			UnreadBadgeStyle st; | ||||
| 			st.active = active; | ||||
| 			st.muted = history->mute(); | ||||
| 			st.padding = 0; | ||||
| 			st.textTop = 0; | ||||
| 			paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth); | ||||
| 			availableWidth -= unreadWidth + st.padding + (hadOneBadge ? st::dialogsUnreadPadding : 0); | ||||
| 		} | ||||
| 		auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); | ||||
| 		if (!history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { | ||||
|  |  | |||
|  | @ -80,7 +80,7 @@ History::History(const PeerId &peerId) | |||
| 	if (peer->isUser() && peer->asUser()->botInfo) { | ||||
| 		outboxReadBefore = INT_MAX; | ||||
| 	} | ||||
| 	for (auto &countData : overviewCountData) { | ||||
| 	for (auto &countData : _overviewCountData) { | ||||
| 		countData = -1; // not loaded yet
 | ||||
| 	} | ||||
| } | ||||
|  | @ -1144,23 +1144,10 @@ HistoryItem *History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaB | |||
| } | ||||
| 
 | ||||
| bool History::addToOverview(MediaOverviewType type, MsgId msgId, AddToOverviewMethod method) { | ||||
| 	bool adding = false; | ||||
| 	switch (method) { | ||||
| 	case AddToOverviewNew: | ||||
| 	case AddToOverviewFront: adding = (overviewIds[type].constFind(msgId) == overviewIds[type].cend()); break; | ||||
| 	case AddToOverviewBack: adding = (overviewCountData[type] != 0); break; | ||||
| 	} | ||||
| 	if (!adding) return false; | ||||
| 
 | ||||
| 	overviewIds[type].insert(msgId); | ||||
| 	switch (method) { | ||||
| 	case AddToOverviewNew: | ||||
| 	case AddToOverviewBack: overview[type].push_back(msgId); break; | ||||
| 	case AddToOverviewFront: overview[type].push_front(msgId); break; | ||||
| 	} | ||||
| 	_overview[type].insert(msgId); | ||||
| 	if (method == AddToOverviewNew) { | ||||
| 		if (overviewCountData[type] > 0) { | ||||
| 			++overviewCountData[type]; | ||||
| 		if (_overviewCountData[type] > 0) { | ||||
| 			++_overviewCountData[type]; | ||||
| 		} | ||||
| 		Notify::mediaOverviewUpdated(peer, type); | ||||
| 	} | ||||
|  | @ -1168,24 +1155,97 @@ bool History::addToOverview(MediaOverviewType type, MsgId msgId, AddToOverviewMe | |||
| } | ||||
| 
 | ||||
| void History::eraseFromOverview(MediaOverviewType type, MsgId msgId) { | ||||
| 	if (overviewIds[type].isEmpty()) return; | ||||
| 	auto i = _overview[type].find(msgId); | ||||
| 	if (i == _overview[type].cend()) return; | ||||
| 
 | ||||
| 	auto i = overviewIds[type].find(msgId); | ||||
| 	if (i == overviewIds[type].cend()) return; | ||||
| 
 | ||||
| 	overviewIds[type].erase(i); | ||||
| 	for (auto i = overview[type].begin(), e = overview[type].end(); i != e; ++i) { | ||||
| 		if ((*i) == msgId) { | ||||
| 			overview[type].erase(i); | ||||
| 			if (overviewCountData[type] > 0) { | ||||
| 				--overviewCountData[type]; | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 	_overview[type].erase(i); | ||||
| 	if (_overviewCountData[type] > 0) { | ||||
| 		--_overviewCountData[type]; | ||||
| 	} | ||||
| 	Notify::mediaOverviewUpdated(peer, type); | ||||
| } | ||||
| 
 | ||||
| void History::setUnreadMentionsCount(int count) { | ||||
| 	if (_unreadMentions.size() > count) { | ||||
| 		LOG(("API Warning: real mentions count is greater than received mentions count")); | ||||
| 		count = _unreadMentions.size(); | ||||
| 	} | ||||
| 	_unreadMentionsCount = count; | ||||
| } | ||||
| 
 | ||||
| bool History::addToUnreadMentions(MsgId msgId, AddToOverviewMethod method) { | ||||
| 	auto count = base::get_if(&_unreadMentionsCount); | ||||
| 	auto allLoaded = count ? (_unreadMentions.size() >= *count) : false; | ||||
| 	if (allLoaded) { | ||||
| 		if (method == AddToOverviewNew) { | ||||
| 			++*count; | ||||
| 			_unreadMentions.insert(msgId); | ||||
| 			return true; | ||||
| 		} | ||||
| 	} else if (!_unreadMentions.empty() && method != AddToOverviewNew) { | ||||
| 		_unreadMentions.insert(msgId); | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| void History::eraseFromUnreadMentions(MsgId msgId) { | ||||
| 	_unreadMentions.remove(msgId); | ||||
| 	if (auto count = base::get_if(&_unreadMentionsCount)) { | ||||
| 		if (*count > 0) { | ||||
| 			--*count; | ||||
| 		} | ||||
| 	} | ||||
| 	Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged); | ||||
| } | ||||
| 
 | ||||
| void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) { | ||||
| 	auto count = 0; | ||||
| 	auto messages = (const QVector<MTPMessage>*)nullptr; | ||||
| 	auto getMessages = [](auto &list) { | ||||
| 		App::feedUsers(list.vusers); | ||||
| 		App::feedChats(list.vchats); | ||||
| 		return &list.vmessages.v; | ||||
| 	}; | ||||
| 	switch (result.type()) { | ||||
| 	case mtpc_messages_messages: { | ||||
| 		auto &d = result.c_messages_messages(); | ||||
| 		messages = getMessages(d); | ||||
| 		count = messages->size(); | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_messages_messagesSlice: { | ||||
| 		auto &d = result.c_messages_messagesSlice(); | ||||
| 		messages = getMessages(d); | ||||
| 		count = d.vcount.v; | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_messages_channelMessages: { | ||||
| 		LOG(("API Error: unexpected messages.channelMessages in History::addUnreadMentionsSlice")); | ||||
| 		auto &d = result.c_messages_channelMessages(); | ||||
| 		messages = getMessages(d); | ||||
| 		count = d.vcount.v; | ||||
| 	} break; | ||||
| 
 | ||||
| 	default: Unexpected("type in History::addUnreadMentionsSlice"); | ||||
| 	} | ||||
| 
 | ||||
| 	auto added = false; | ||||
| 	for (auto &message : *messages) { | ||||
| 		if (auto item = addToHistory(message)) { | ||||
| 			if (item->mentionsMe() && item->isMediaUnread()) { | ||||
| 				_unreadMentions.insert(item->id); | ||||
| 				added = true; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if (!added) { | ||||
| 		count = _unreadMentions.size(); | ||||
| 	} | ||||
| 	setUnreadMentionsCount(count); | ||||
| 	Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged); | ||||
| } | ||||
| 
 | ||||
| HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) { | ||||
| 	Expects(!isBuildingFrontBlock()); | ||||
| 	addItemToBlock(adding); | ||||
|  | @ -1527,16 +1587,8 @@ void History::checkAddAllToOverview() { | |||
| 	} | ||||
| 
 | ||||
| 	int32 mask = 0; | ||||
| 	for (int32 i = 0; i < OverviewCount; ++i) { | ||||
| 		if (overviewCountData[i] == 0) continue; // all loaded
 | ||||
| 		if (!overview[i].isEmpty() || !overviewIds[i].isEmpty()) { | ||||
| 			overview[i].clear(); | ||||
| 			overviewIds[i].clear(); | ||||
| 			mask |= (1 << i); | ||||
| 		} | ||||
| 	} | ||||
| 	for_const (HistoryBlock *block, blocks) { | ||||
| 		for_const (HistoryItem *item, block->items) { | ||||
| 	for_const (auto block, blocks) { | ||||
| 		for_const (auto item, block->items) { | ||||
| 			mask |= item->addToOverview(AddToOverviewBack); | ||||
| 		} | ||||
| 	} | ||||
|  | @ -2077,18 +2129,15 @@ void History::clear(bool leaveItems) { | |||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	for (int32 i = 0; i < OverviewCount; ++i) { | ||||
| 		if (!overview[i].isEmpty() || !overviewIds[i].isEmpty()) { | ||||
| 			if (leaveItems) { | ||||
| 				if (overviewCountData[i] == 0) { | ||||
| 					overviewCountData[i] = overview[i].size(); | ||||
| 	if (!leaveItems) { | ||||
| 		for (auto i = 0; i != OverviewCount; ++i) { | ||||
| 			if (!_overview[i].isEmpty()) { | ||||
| 				_overviewCountData[i] = -1; // not loaded yet
 | ||||
| 				_overview[i].clear(); | ||||
| 				if (!App::quitting()) { | ||||
| 					Notify::mediaOverviewUpdated(peer, MediaOverviewType(i)); | ||||
| 				} | ||||
| 			} else { | ||||
| 				overviewCountData[i] = -1; // not loaded yet
 | ||||
| 			} | ||||
| 			overview[i].clear(); | ||||
| 			overviewIds[i].clear(); | ||||
| 			if (!App::quitting()) Notify::mediaOverviewUpdated(peer, MediaOverviewType(i)); | ||||
| 		} | ||||
| 	} | ||||
| 	clearBlocks(leaveItems); | ||||
|  | @ -2222,14 +2271,14 @@ void History::overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages | |||
| 		App::feedUsers(d.vusers); | ||||
| 		App::feedChats(d.vchats); | ||||
| 		v = &d.vmessages.v; | ||||
| 		overviewCountData[overviewIndex] = 0; | ||||
| 		_overviewCountData[overviewIndex] = 0; | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_messages_messagesSlice: { | ||||
| 		auto &d(result.c_messages_messagesSlice()); | ||||
| 		App::feedUsers(d.vusers); | ||||
| 		App::feedChats(d.vchats); | ||||
| 		overviewCountData[overviewIndex] = d.vcount.v; | ||||
| 		_overviewCountData[overviewIndex] = d.vcount.v; | ||||
| 		v = &d.vmessages.v; | ||||
| 	} break; | ||||
| 
 | ||||
|  | @ -2242,7 +2291,7 @@ void History::overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages | |||
| 		} | ||||
| 		App::feedUsers(d.vusers); | ||||
| 		App::feedChats(d.vchats); | ||||
| 		overviewCountData[overviewIndex] = d.vcount.v; | ||||
| 		_overviewCountData[overviewIndex] = d.vcount.v; | ||||
| 		v = &d.vmessages.v; | ||||
| 	} break; | ||||
| 
 | ||||
|  | @ -2250,42 +2299,22 @@ void History::overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages | |||
| 	} | ||||
| 
 | ||||
| 	if (!onlyCounts && v->isEmpty()) { | ||||
| 		overviewCountData[overviewIndex] = 0; | ||||
| 	} else if (overviewCountData[overviewIndex] > 0) { | ||||
| 		for_const (auto msgId, overviewIds[overviewIndex]) { | ||||
| 			if (msgId < 0) { | ||||
| 				++overviewCountData[overviewIndex]; | ||||
| 			} else { | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		_overviewCountData[overviewIndex] = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	for (QVector<MTPMessage>::const_iterator i = v->cbegin(), e = v->cend(); i != e; ++i) { | ||||
| 		HistoryItem *item = App::histories().addNewMessage(*i, NewMessageExisting); | ||||
| 		if (item && overviewIds[overviewIndex].constFind(item->id) == overviewIds[overviewIndex].cend()) { | ||||
| 			overviewIds[overviewIndex].insert(item->id); | ||||
| 			overview[overviewIndex].push_front(item->id); | ||||
| 	for (auto i = v->cbegin(), e = v->cend(); i != e; ++i) { | ||||
| 		if (auto item = App::histories().addNewMessage(*i, NewMessageExisting)) { | ||||
| 			_overview[overviewIndex].insert(item->id); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void History::changeMsgId(MsgId oldId, MsgId newId) { | ||||
| 	for (auto i = 0; i < OverviewCount; ++i) { | ||||
| 		auto j = overviewIds[i].find(oldId); | ||||
| 		if (j != overviewIds[i].cend()) { | ||||
| 			overviewIds[i].erase(j); | ||||
| 			auto index = overview[i].indexOf(oldId); | ||||
| 			if (overviewIds[i].constFind(newId) == overviewIds[i].cend()) { | ||||
| 				overviewIds[i].insert(newId); | ||||
| 				if (index >= 0) { | ||||
| 					overview[i][index] = newId; | ||||
| 				} else { | ||||
| 					overview[i].push_back(newId); | ||||
| 				} | ||||
| 			} else if (index >= 0) { | ||||
| 				overview[i].removeAt(index); | ||||
| 			} | ||||
| 	for (auto i = 0; i != OverviewCount; ++i) { | ||||
| 		auto j = _overview[i].find(oldId); | ||||
| 		if (j != _overview[i].cend()) { | ||||
| 			_overview[i].erase(j); | ||||
| 			_overview[i].insert(newId); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -25,6 +25,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org | |||
| #include "ui/effects/send_action_animations.h" | ||||
| #include "base/observer.h" | ||||
| #include "base/timer.h" | ||||
| #include "base/variant.h" | ||||
| #include "base/flat_set.h" | ||||
| 
 | ||||
| void HistoryInit(); | ||||
| 
 | ||||
|  | @ -349,7 +351,27 @@ public: | |||
| 	// if this returns false there is no need to even try to handle them
 | ||||
| 	bool canHaveFromPhotos() const; | ||||
| 
 | ||||
| 	typedef QList<HistoryBlock*> Blocks; | ||||
| 	int getUnreadMentionsLoadedCount() const { | ||||
| 		return _unreadMentions.size(); | ||||
| 	} | ||||
| 	MsgId getMinLoadedUnreadMention() const { | ||||
| 		return _unreadMentions.empty() ? 0 : _unreadMentions.front(); | ||||
| 	} | ||||
| 	MsgId getMaxLoadedUnreadMention() const { | ||||
| 		return _unreadMentions.empty() ? 0 : _unreadMentions.back(); | ||||
| 	} | ||||
| 	int getUnreadMentionsCount(int notLoadedValue = -1) const { | ||||
| 		return base::is_null_optional(_unreadMentionsCount) ? notLoadedValue : *base::get_if(&_unreadMentionsCount); | ||||
| 	} | ||||
| 	bool hasUnreadMentions() const { | ||||
| 		return (getUnreadMentionsCount() > 0); | ||||
| 	} | ||||
| 	void setUnreadMentionsCount(int count); | ||||
| 	bool addToUnreadMentions(MsgId msgId, AddToOverviewMethod method); | ||||
| 	void eraseFromUnreadMentions(MsgId msgId); | ||||
| 	void addUnreadMentionsSlice(const MTPmessages_Messages &result); | ||||
| 
 | ||||
| 	using Blocks = QList<HistoryBlock*>; | ||||
| 	Blocks blocks; | ||||
| 
 | ||||
| 	int width = 0; | ||||
|  | @ -441,37 +463,33 @@ public: | |||
| 	mutable const HistoryItem *textCachedFor = nullptr; // cache
 | ||||
| 	mutable Text lastItemTextCache; | ||||
| 
 | ||||
| 	typedef QList<MsgId> MediaOverview; | ||||
| 	MediaOverview overview[OverviewCount]; | ||||
| 
 | ||||
| 	bool overviewCountLoaded(int32 overviewIndex) const { | ||||
| 		return overviewCountData[overviewIndex] >= 0; | ||||
| 		return _overviewCountData[overviewIndex] >= 0; | ||||
| 	} | ||||
| 	bool overviewLoaded(int32 overviewIndex) const { | ||||
| 		return overviewCount(overviewIndex) == overview[overviewIndex].size(); | ||||
| 		return overviewCount(overviewIndex) == _overview[overviewIndex].size(); | ||||
| 	} | ||||
| 	int32 overviewCount(int32 overviewIndex, int32 defaultValue = -1) const { | ||||
| 		int32 result = overviewCountData[overviewIndex], loaded = overview[overviewIndex].size(); | ||||
| 	int overviewCount(int32 overviewIndex, int32 defaultValue = -1) const { | ||||
| 		auto result = _overviewCountData[overviewIndex]; | ||||
| 		auto loaded = _overview[overviewIndex].size(); | ||||
| 		if (result < 0) return defaultValue; | ||||
| 		if (result < loaded) { | ||||
| 			if (result > 0) { | ||||
| 				const_cast<History*>(this)->overviewCountData[overviewIndex] = 0; | ||||
| 				const_cast<History*>(this)->_overviewCountData[overviewIndex] = 0; | ||||
| 			} | ||||
| 			return loaded; | ||||
| 		} | ||||
| 		return result; | ||||
| 	} | ||||
| 	const OrderedSet<MsgId> &overview(int32 overviewIndex) const { | ||||
| 		return _overview[overviewIndex]; | ||||
| 	} | ||||
| 	MsgId overviewMinId(int32 overviewIndex) const { | ||||
| 		for_const (auto msgId, overviewIds[overviewIndex]) { | ||||
| 			if (msgId > 0) { | ||||
| 				return msgId; | ||||
| 			} | ||||
| 		} | ||||
| 		return 0; | ||||
| 		return _overview[overviewIndex].empty() ? 0 : *_overview[overviewIndex].begin(); | ||||
| 	} | ||||
| 	void overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages &result, bool onlyCounts = false); | ||||
| 	bool overviewHasMsgId(int32 overviewIndex, MsgId msgId) const { | ||||
| 		return overviewIds[overviewIndex].constFind(msgId) != overviewIds[overviewIndex].cend(); | ||||
| 		return _overview[overviewIndex].contains(msgId); | ||||
| 	} | ||||
| 
 | ||||
| 	void changeMsgId(MsgId oldId, MsgId newId); | ||||
|  | @ -537,9 +555,12 @@ private: | |||
| 	Q_DECL_CONSTEXPR friend inline QFlags<Flags::enum_type> operator~(Flags::enum_type f) noexcept { | ||||
| 		return ~QFlags<Flags::enum_type>(f); | ||||
| 	} | ||||
| 	Flags _flags; | ||||
| 	bool _mute; | ||||
| 	int32 _unreadCount = 0; | ||||
| 	Flags _flags = { 0 }; | ||||
| 	bool _mute = false; | ||||
| 	int _unreadCount = 0; | ||||
| 
 | ||||
| 	base::optional<int> _unreadMentionsCount = base::null_optional(); | ||||
| 	base::flat_set<MsgId> _unreadMentions; | ||||
| 
 | ||||
| 	Dialogs::RowsByLetter _chatListLinks[2]; | ||||
| 	Dialogs::RowsByLetter &chatListLinks(Dialogs::Mode list) { | ||||
|  | @ -555,9 +576,8 @@ private: | |||
| 	} | ||||
| 	uint64 _sortKeyInChatList = 0; // like ((unixtime) << 32) | (incremented counter)
 | ||||
| 
 | ||||
| 	using MediaOverviewIds = OrderedSet<MsgId>; | ||||
| 	MediaOverviewIds overviewIds[OverviewCount]; | ||||
| 	int32 overviewCountData[OverviewCount]; // -1 - not loaded, 0 - all loaded, > 0 - count, but not all loaded
 | ||||
| 	OrderedSet<MsgId> _overview[OverviewCount]; | ||||
| 	int32 _overviewCountData[OverviewCount]; // -1 - not loaded, 0 - all loaded, > 0 - count, but not all loaded
 | ||||
| 
 | ||||
| 	// A pointer to the block that is currently being built.
 | ||||
| 	// We hold this pointer so we can destroy it while building
 | ||||
|  |  | |||
|  | @ -69,6 +69,12 @@ historyToDownBadgeSize: 22px; | |||
| historyToDownShownAfter: 480px; | ||||
| historyToDownDuration: 150; | ||||
| 
 | ||||
| historyUnreadMentions: TwoIconButton(historyToDown) { | ||||
| 	iconAbove: icon {{ "history_unread_mention", historyToDownFg, point(16px, 16px) }}; | ||||
| 	iconAboveOver: icon {{ "history_unread_mention", historyToDownFgOver, point(16px, 16px) }}; | ||||
| } | ||||
| historyUnreadMentionsSkip: 4px; | ||||
| 
 | ||||
| membersInnerWidth: 310px; | ||||
| membersInnerHeightMax: 360px; | ||||
| membersInnerDropdown: InnerDropdown(defaultInnerDropdown) { | ||||
|  |  | |||
|  | @ -384,6 +384,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) { | |||
| 		HistoryLayout::paintEmpty(p, width(), height()); | ||||
| 	} | ||||
| 	if (!noHistoryDisplayed) { | ||||
| 		auto readMentions = HistoryItemsMap(); | ||||
| 
 | ||||
| 		adjustCurrent(clip.top()); | ||||
| 
 | ||||
| 		auto selEnd = _selected.cend(); | ||||
|  | @ -428,6 +430,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) { | |||
| 				if (item->hasViews()) { | ||||
| 					App::main()->scheduleViewIncrement(item); | ||||
| 				} | ||||
| 				if (item->mentionsMe() && item->isMediaUnread()) { | ||||
| 					readMentions.insert(item); | ||||
| 				} | ||||
| 
 | ||||
| 				int32 h = item->height(); | ||||
| 				p.translate(0, h); | ||||
|  | @ -475,6 +480,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) { | |||
| 					if (item->hasViews()) { | ||||
| 						App::main()->scheduleViewIncrement(item); | ||||
| 					} | ||||
| 					if (item->mentionsMe() && item->isMediaUnread()) { | ||||
| 						readMentions.insert(item); | ||||
| 					} | ||||
| 				} | ||||
| 				p.translate(0, h); | ||||
| 				y += h; | ||||
|  | @ -493,6 +501,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) { | |||
| 			p.restore(); | ||||
| 		} | ||||
| 
 | ||||
| 		if (!readMentions.empty() && App::wnd()->doWeReadMentions()) { | ||||
| 			App::main()->mediaMarkRead(readMentions); | ||||
| 		} | ||||
| 
 | ||||
| 		if (mtop >= 0 || htop >= 0) { | ||||
| 			enumerateUserpics([&p, &clip](gsl::not_null<HistoryMessage*> message, int userpicTop) { | ||||
| 				// stop the enumeration if the userpic is below the painted rect
 | ||||
|  |  | |||
|  | @ -575,7 +575,7 @@ public: | |||
| 		return _flags & MTPDmessage::Flag::f_mentioned; | ||||
| 	} | ||||
| 	bool isMediaUnread() const { | ||||
| 		return (_flags & MTPDmessage::Flag::f_media_unread) && (channelId() == NoChannel); | ||||
| 		return _flags & MTPDmessage::Flag::f_media_unread; | ||||
| 	} | ||||
| 	void markMediaRead() { | ||||
| 		_flags &= ~MTPDmessage::Flag::f_media_unread; | ||||
|  | @ -625,7 +625,7 @@ public: | |||
| 		return _flags & MTPDmessage::Flag::f_post; | ||||
| 	} | ||||
| 	bool indexInOverview() const { | ||||
| 		return (id > 0) && (!history()->isChannel() || history()->isMegagroup() || isPost()); | ||||
| 		return (id > 0); | ||||
| 	} | ||||
| 	bool isSilent() const { | ||||
| 		return _flags & MTPDmessage::Flag::f_silent; | ||||
|  |  | |||
|  | @ -37,6 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org | |||
| #include "styles/style_widgets.h" | ||||
| #include "styles/style_history.h" | ||||
| #include "window/notifications_manager.h" | ||||
| #include "observer_peer.h" | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
|  | @ -1232,6 +1233,13 @@ void HistoryMessage::applyEditionToEmpty() { | |||
| 	finishEditionToEmpty(); | ||||
| } | ||||
| 
 | ||||
| void HistoryMessage::markMediaAsReadHook() { | ||||
| 	if (mentionsMe()) { | ||||
| 		history()->updateChatListEntry(); | ||||
| 		history()->eraseFromUnreadMentions(id); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool HistoryMessage::displayForwardedFrom() const { | ||||
| 	if (auto forwarded = Get<HistoryMessageForwarded>()) { | ||||
| 		return Has<HistoryMessageVia>() | ||||
|  | @ -1276,6 +1284,11 @@ int32 HistoryMessage::addToOverview(AddToOverviewMethod method) { | |||
| 			result |= (1 << OverviewLinks); | ||||
| 		} | ||||
| 	} | ||||
| 	if (mentionsMe() && isMediaUnread()) { | ||||
| 		if (history()->addToUnreadMentions(id, method)) { | ||||
| 			Notify::peerUpdatedDelayed(history()->peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged); | ||||
| 		} | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
|  | @ -1286,6 +1299,9 @@ void HistoryMessage::eraseFromOverview() { | |||
| 	if (hasTextLinks()) { | ||||
| 		history()->eraseFromOverview(OverviewLinks, id); | ||||
| 	} | ||||
| 	if (mentionsMe() && isMediaUnread()) { | ||||
| 		history()->eraseFromUnreadMentions(id); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| TextWithEntities HistoryMessage::selectedText(TextSelection selection) const { | ||||
|  |  | |||
|  | @ -166,6 +166,8 @@ private: | |||
| 	int performResizeGetHeight(); | ||||
| 	void applyEditionToEmpty(); | ||||
| 
 | ||||
| 	void markMediaAsReadHook() override; | ||||
| 
 | ||||
| 	bool displayForwardedFrom() const; | ||||
| 	void paintFromName(Painter &p, QRect &trect, bool selected) const; | ||||
| 	void paintForwardedInfo(Painter &p, QRect &trect, bool selected) const; | ||||
|  |  | |||
|  | @ -612,6 +612,7 @@ HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null<Window::Controller*> | |||
| , _topBar(this, controller) | ||||
| , _scroll(this, st::historyScroll, false) | ||||
| , _historyDown(_scroll, st::historyToDown) | ||||
| , _unreadMentions(_scroll, st::historyUnreadMentions) | ||||
| , _fieldAutocomplete(this) | ||||
| , _send(this) | ||||
| , _unblock(this, lang(lng_unblock_button).toUpper(), st::historyUnblock) | ||||
|  | @ -639,7 +640,8 @@ HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null<Window::Controller*> | |||
| 	subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); | ||||
| 	connect(_topBar, &Window::TopBarWidget::clicked, this, [this] { topBarClick(); }); | ||||
| 	connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); | ||||
| 	connect(_historyDown, SIGNAL(clicked()), this, SLOT(onHistoryToEnd())); | ||||
| 	_historyDown->setClickedCallback([this] { historyDownClicked(); }); | ||||
| 	_unreadMentions->setClickedCallback([this] { showNextUnreadMention(); }); | ||||
| 	connect(_fieldBarCancel, SIGNAL(clicked()), this, SLOT(onFieldBarCancel())); | ||||
| 	_send->setClickedCallback([this] { sendButtonClicked(); }); | ||||
| 	connect(_unblock, SIGNAL(clicked()), this, SLOT(onUnblock())); | ||||
|  | @ -703,6 +705,7 @@ HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null<Window::Controller*> | |||
| 	updateScrollColors(); | ||||
| 
 | ||||
| 	_historyDown->installEventFilter(this); | ||||
| 	_unreadMentions->installEventFilter(this); | ||||
| 
 | ||||
| 	_fieldAutocomplete->hide(); | ||||
| 	connect(_fieldAutocomplete, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onMentionInsert(UserData*))); | ||||
|  | @ -769,9 +772,10 @@ HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null<Window::Controller*> | |||
| 			scrollToCurrentVoiceMessage(pair.from.contextId(), pair.to); | ||||
| 		} | ||||
| 	}); | ||||
| 	subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::ChannelRightsChanged, [this](const Notify::PeerUpdate &update) { | ||||
| 	subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::ChannelRightsChanged | Notify::PeerUpdate::Flag::UnreadMentionsChanged, [this](const Notify::PeerUpdate &update) { | ||||
| 		if (update.peer == _peer) { | ||||
| 			onPreviewCheck(); | ||||
| 			if (update.flags & Notify::PeerUpdate::Flag::ChannelRightsChanged) onPreviewCheck(); | ||||
| 			if (update.flags & Notify::PeerUpdate::Flag::UnreadMentionsChanged) updateUnreadMentionsVisibility(); | ||||
| 		} | ||||
| 	})); | ||||
| 	subscribe(controller->window()->widgetGrabbed(), [this] { | ||||
|  | @ -1966,6 +1970,7 @@ void HistoryWidget::updateControlsVisibility() { | |||
| 		_topBar->setVisible(_peer != nullptr); | ||||
| 	} | ||||
| 	updateHistoryDownVisibility(); | ||||
| 	updateUnreadMentionsVisibility(); | ||||
| 	if (!_history || _a_show.animating()) { | ||||
| 		if (_tabbedSection && !_tabbedSection->isHidden()) { | ||||
| 			_tabbedSection->beforeHiding(); | ||||
|  | @ -2166,6 +2171,9 @@ void HistoryWidget::newUnreadMsg(History *history, HistoryItem *item) { | |||
| 			destroyUnreadBar(); | ||||
| 		} | ||||
| 		if (App::wnd()->doWeReadServerHistory()) { | ||||
| 			if (item->mentionsMe() && item->isMediaUnread()) { | ||||
| 				App::main()->mediaMarkRead(item); | ||||
| 			} | ||||
| 			historyWasRead(ReadServerHistoryChecks::ForceRequest); | ||||
| 			return; | ||||
| 		} | ||||
|  | @ -2194,9 +2202,7 @@ void HistoryWidget::historyWasRead(ReadServerHistoryChecks checks) { | |||
| void HistoryWidget::unreadCountChanged(History *history) { | ||||
| 	if (history == _history || history == _migrated) { | ||||
| 		updateHistoryDownVisibility(); | ||||
| 		if (_historyDown) { | ||||
| 			_historyDown->setUnreadCount(_history->unreadCount() + (_migrated ? _migrated->unreadCount() : 0)); | ||||
| 		} | ||||
| 		_historyDown->setUnreadCount(_history->unreadCount() + (_migrated ? _migrated->unreadCount() : 0)); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -2373,6 +2379,12 @@ bool HistoryWidget::doWeReadServerHistory() const { | |||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| bool HistoryWidget::doWeReadMentions() const { | ||||
| 	if (!_history || !_list) return true; | ||||
| 	if (_firstLoadRequest || _a_show.animating()) return false; | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool HistoryWidget::historyHasNotFreezedUnreadBar(History *history) const { | ||||
| 	if (history && history->showFrom && !history->showFrom->detached() && history->unreadBar) { | ||||
| 		if (auto unreadBar = history->unreadBar->Get<HistoryMessageUnreadBar>()) { | ||||
|  | @ -2614,7 +2626,7 @@ void HistoryWidget::onWindowVisibleChanged() { | |||
| 	QTimer::singleShot(0, this, SLOT(preloadHistoryIfNeeded())); | ||||
| } | ||||
| 
 | ||||
| void HistoryWidget::onHistoryToEnd() { | ||||
| void HistoryWidget::historyDownClicked() { | ||||
| 	if (_replyReturn && _replyReturn->history() == _history) { | ||||
| 		showHistory(_peer->id, _replyReturn->id); | ||||
| 	} else if (_replyReturn && _replyReturn->history() == _migrated) { | ||||
|  | @ -2624,6 +2636,10 @@ void HistoryWidget::onHistoryToEnd() { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| void HistoryWidget::showNextUnreadMention() { | ||||
| 	showHistory(_peer->id, _history->getMinLoadedUnreadMention()); | ||||
| } | ||||
| 
 | ||||
| void HistoryWidget::saveEditMsg() { | ||||
| 	if (_saveEditMsgRequestId) return; | ||||
| 
 | ||||
|  | @ -2921,6 +2937,7 @@ void HistoryWidget::showAnimated(Window::SlideDirection direction, const Window: | |||
| 	show(); | ||||
| 	_topBar->updateControlsVisibility(); | ||||
| 	historyDownAnimationFinish(); | ||||
| 	unreadMentionsAnimationFinish(); | ||||
| 	_topShadow->setVisible(params.withTopBarShadow ? false : true); | ||||
| 	_cacheOver = App::main()->grabForShowAnimation(params); | ||||
| 
 | ||||
|  | @ -2952,6 +2969,7 @@ void HistoryWidget::animationCallback() { | |||
| 	update(); | ||||
| 	if (!_a_show.animating()) { | ||||
| 		historyDownAnimationFinish(); | ||||
| 		unreadMentionsAnimationFinish(); | ||||
| 		_cacheUnder = _cacheOver = QPixmap(); | ||||
| 		doneShow(); | ||||
| 	} | ||||
|  | @ -2981,6 +2999,7 @@ void HistoryWidget::finishAnimation() { | |||
| 	_topShadow->setVisible(_peer != nullptr); | ||||
| 	_topBar->setVisible(_peer != nullptr); | ||||
| 	historyDownAnimationFinish(); | ||||
| 	unreadMentionsAnimationFinish(); | ||||
| } | ||||
| 
 | ||||
| void HistoryWidget::historyDownAnimationFinish() { | ||||
|  | @ -2988,6 +3007,11 @@ void HistoryWidget::historyDownAnimationFinish() { | |||
| 	updateHistoryDownPosition(); | ||||
| } | ||||
| 
 | ||||
| void HistoryWidget::unreadMentionsAnimationFinish() { | ||||
| 	_unreadMentionsShown.finish(); | ||||
| 	updateUnreadMentionsPosition(); | ||||
| } | ||||
| 
 | ||||
| void HistoryWidget::step_recording(float64 ms, bool timer) { | ||||
| 	float64 dt = ms / AudioVoiceMsgUpdateView; | ||||
| 	if (dt >= 1) { | ||||
|  | @ -3341,7 +3365,7 @@ bool HistoryWidget::insertBotCommand(const QString &cmd) { | |||
| } | ||||
| 
 | ||||
| bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) { | ||||
| 	if (obj == _historyDown && e->type() == QEvent::Wheel) { | ||||
| 	if ((obj == _historyDown || obj == _unreadMentions) && e->type() == QEvent::Wheel) { | ||||
| 		return _scroll->viewportEvent(e); | ||||
| 	} | ||||
| 	return TWidget::eventFilter(obj, e); | ||||
|  | @ -4816,6 +4840,11 @@ void HistoryWidget::updateHistoryGeometry(bool initial, bool loadedDown, const S | |||
| 		if (!_historyDownShown.animating()) { | ||||
| 			// _historyDown is a child widget of _scroll, not me.
 | ||||
| 			_historyDown->moveToRight(st::historyToDownPosition.x(), _scroll->height() - _historyDown->height() - st::historyToDownPosition.y()); | ||||
| 			if (!_unreadMentionsShown.animating()) { | ||||
| 				// _unreadMentions is a child widget of _scroll, not me.
 | ||||
| 				auto additionalSkip = _historyDownIsShown ? (_historyDown->height() + st::historyUnreadMentionsSkip) : 0; | ||||
| 				_unreadMentions->moveToRight(st::historyToDownPosition.x(), _scroll->height() - additionalSkip - st::historyToDownPosition.y()); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		controller()->floatPlayerAreaUpdated().notify(true); | ||||
|  | @ -5011,6 +5040,7 @@ void HistoryWidget::updateHistoryDownPosition() { | |||
| 	if (shouldBeHidden != _historyDown->isHidden()) { | ||||
| 		_historyDown->setVisible(!shouldBeHidden); | ||||
| 	} | ||||
| 	updateUnreadMentionsPosition(); | ||||
| } | ||||
| 
 | ||||
| void HistoryWidget::updateHistoryDownVisibility() { | ||||
|  | @ -5025,7 +5055,7 @@ void HistoryWidget::updateHistoryDownVisibility() { | |||
| 		} | ||||
| 		return (_list->itemTop(history->showFrom) >= _scroll->scrollTop() + _scroll->height()); | ||||
| 	}; | ||||
| 	auto historyDownIsVisible = [this, &haveUnreadBelowBottom]() { | ||||
| 	auto historyDownIsVisible = [this, &haveUnreadBelowBottom] { | ||||
| 		if (!_history || _firstLoadRequest) { | ||||
| 			return false; | ||||
| 		} | ||||
|  | @ -5047,6 +5077,41 @@ void HistoryWidget::updateHistoryDownVisibility() { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| void HistoryWidget::updateUnreadMentionsPosition() { | ||||
| 	// _unreadMentions is a child widget of _scroll, not me.
 | ||||
| 	auto right = anim::interpolate(-_unreadMentions->width(), st::historyToDownPosition.x(), _unreadMentionsShown.current(_unreadMentionsIsShown ? 1. : 0.)); | ||||
| 	auto shift = anim::interpolate(0, _historyDown->height() + st::historyUnreadMentionsSkip, _historyDownShown.current(_historyDownIsShown ? 1. : 0.)); | ||||
| 	auto top = _scroll->height() - _unreadMentions->height() - st::historyToDownPosition.y() - shift; | ||||
| 	_unreadMentions->moveToRight(right, top); | ||||
| 	auto shouldBeHidden = !_unreadMentionsIsShown && !_unreadMentionsShown.animating(); | ||||
| 	if (shouldBeHidden != _unreadMentions->isHidden()) { | ||||
| 		_unreadMentions->setVisible(!shouldBeHidden); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void HistoryWidget::updateUnreadMentionsVisibility() { | ||||
| 	if (_a_show.animating()) return; | ||||
| 
 | ||||
| 	auto showUnreadMentions = _peer && (_peer->isChat() || _peer->isMegagroup()); | ||||
| 	if (showUnreadMentions) { | ||||
| 		Auth().api().preloadEnoughUnreadMentions(_history); | ||||
| 	} | ||||
| 	auto unreadMentionsIsVisible = [this, showUnreadMentions] { | ||||
| 		if (!showUnreadMentions || _firstLoadRequest) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		return (_history->getUnreadMentionsLoadedCount() > 0); | ||||
| 	}; | ||||
| 	auto unreadMentionsIsShown = unreadMentionsIsVisible(); | ||||
| 	if (unreadMentionsIsShown) { | ||||
| 		_unreadMentions->setUnreadCount(_history->getUnreadMentionsCount()); | ||||
| 	} | ||||
| 	if (_unreadMentionsIsShown != unreadMentionsIsShown) { | ||||
| 		_unreadMentionsIsShown = unreadMentionsIsShown; | ||||
| 		_unreadMentionsShown.start([this] { updateUnreadMentionsPosition(); }, _unreadMentionsIsShown ? 0. : 1., _unreadMentionsIsShown ? 1. : 0., st::historyToDownDuration); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void HistoryWidget::mousePressEvent(QMouseEvent *e) { | ||||
| 	_replyForwardPressed = QRect(0, _field->y() - st::historySendPadding - st::historyReplyHeight, st::historyReplySkip, st::historyReplyHeight).contains(e->pos()); | ||||
| 	if (_replyForwardPressed && !_fieldBarCancel->isHidden()) { | ||||
|  | @ -6448,6 +6513,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { | |||
| 
 | ||||
| 	auto ms = getms(); | ||||
| 	_historyDownShown.step(ms); | ||||
| 	_unreadMentionsShown.step(ms); | ||||
| 	auto progress = _a_show.current(ms, 1.); | ||||
| 	if (_a_show.animating()) { | ||||
| 		auto animationWidth = (!_tabbedSection || _tabbedSection->isHidden()) ? width() : _chatWidth; | ||||
|  |  | |||
|  | @ -179,6 +179,7 @@ public: | |||
| 
 | ||||
| 	void windowShown(); | ||||
| 	bool doWeReadServerHistory() const; | ||||
| 	bool doWeReadMentions() const; | ||||
| 
 | ||||
| 	void leaveToChildEvent(QEvent *e, QWidget *child) override; | ||||
| 	void dragEnterEvent(QDragEnterEvent *e) override; | ||||
|  | @ -316,6 +317,8 @@ public: | |||
| 
 | ||||
| 	void updateHistoryDownPosition(); | ||||
| 	void updateHistoryDownVisibility(); | ||||
| 	void updateUnreadMentionsPosition(); | ||||
| 	void updateUnreadMentionsVisibility(); | ||||
| 
 | ||||
| 	void updateFieldSubmitSettings(); | ||||
| 
 | ||||
|  | @ -412,7 +415,6 @@ public slots: | |||
| 	void onReportSpamClear(); | ||||
| 
 | ||||
| 	void onScroll(); | ||||
| 	void onHistoryToEnd(); | ||||
| 	void onSend(bool ctrlShiftEnter = false, MsgId replyTo = -1); | ||||
| 
 | ||||
| 	void onUnblock(); | ||||
|  | @ -488,6 +490,8 @@ private: | |||
| 	void updateTabbedSelectorSectionShown(); | ||||
| 	void recountChatWidth(); | ||||
| 	void setReportSpamStatus(DBIPeerReportSpamStatus status); | ||||
| 	void historyDownClicked(); | ||||
| 	void showNextUnreadMention(); | ||||
| 
 | ||||
| 	void animationCallback(); | ||||
| 	void updateOverStates(QPoint pos); | ||||
|  | @ -496,6 +500,7 @@ private: | |||
| 	void recordUpdateCallback(QPoint globalPos); | ||||
| 	void chooseAttach(); | ||||
| 	void historyDownAnimationFinish(); | ||||
| 	void unreadMentionsAnimationFinish(); | ||||
| 	void sendButtonClicked(); | ||||
| 	SendingFilesLists getSendingFilesLists(const QList<QUrl> &files); | ||||
| 	SendingFilesLists getSendingFilesLists(const QStringList &files); | ||||
|  | @ -749,6 +754,10 @@ private: | |||
| 	bool _historyDownIsShown = false; | ||||
| 	object_ptr<Ui::HistoryDownButton> _historyDown; | ||||
| 
 | ||||
| 	Animation _unreadMentionsShown; | ||||
| 	bool _unreadMentionsIsShown = false; | ||||
| 	object_ptr<Ui::HistoryDownButton> _unreadMentions; | ||||
| 
 | ||||
| 	object_ptr<FieldAutocomplete> _fieldAutocomplete; | ||||
| 
 | ||||
| 	UserData *_inlineBot = nullptr; | ||||
|  |  | |||
|  | @ -63,6 +63,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org | |||
| #include "media/player/media_player_float.h" | ||||
| #include "base/qthelp_regex.h" | ||||
| #include "base/qthelp_url.h" | ||||
| #include "base/flat_set.h" | ||||
| #include "window/themes/window_theme.h" | ||||
| #include "window/player_wrap_widget.h" | ||||
| #include "styles/style_boxes.h" | ||||
|  | @ -1636,7 +1637,7 @@ void MainWidget::loadMediaBack(PeerData *peer, MediaOverviewType type, bool many | |||
| 	} | ||||
| 
 | ||||
| 	auto minId = history->overviewMinId(type); | ||||
| 	auto limit = (many || history->overview[type].size() > MediaOverviewStartPerPage) ? SearchPerPage : MediaOverviewStartPerPage; | ||||
| 	auto limit = (many || history->overview(type).size() > MediaOverviewStartPerPage) ? SearchPerPage : MediaOverviewStartPerPage; | ||||
| 	auto filter = TypeToMediaFilter(type); | ||||
| 	if (filter.type() == mtpc_inputMessagesFilterEmpty) { | ||||
| 		return; | ||||
|  | @ -1974,9 +1975,9 @@ void MainWidget::inlineResultLoadFailed(FileLoader *loader, bool started) { | |||
| 	//Ui::repaintInlineItem();
 | ||||
| } | ||||
| 
 | ||||
| void MainWidget::mediaMarkRead(DocumentData *data) { | ||||
| 	const DocumentItems &items(App::documentItems()); | ||||
| 	DocumentItems::const_iterator i = items.constFind(data); | ||||
| void MainWidget::mediaMarkRead(gsl::not_null<DocumentData*> data) { | ||||
| 	auto &items = App::documentItems(); | ||||
| 	auto i = items.constFind(data); | ||||
| 	if (i != items.cend()) { | ||||
| 		mediaMarkRead(i.value()); | ||||
| 	} | ||||
|  | @ -1984,18 +1985,39 @@ void MainWidget::mediaMarkRead(DocumentData *data) { | |||
| 
 | ||||
| void MainWidget::mediaMarkRead(const HistoryItemsMap &items) { | ||||
| 	QVector<MTPint> markedIds; | ||||
| 	QMap<ChannelData*, QVector<MTPint>> channelMarkedIds; | ||||
| 	markedIds.reserve(items.size()); | ||||
| 	for_const (auto item, items) { | ||||
| 		if (!item->out() && item->isMediaUnread()) { | ||||
| 		if ((!item->out() || item->mentionsMe()) && item->isMediaUnread()) { | ||||
| 			item->markMediaRead(); | ||||
| 			if (item->id > 0) { | ||||
| 				markedIds.push_back(MTP_int(item->id)); | ||||
| 				if (auto channel = item->history()->peer->asChannel()) { | ||||
| 					channelMarkedIds[channel].push_back(MTP_int(item->id)); | ||||
| 				} else { | ||||
| 					markedIds.push_back(MTP_int(item->id)); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if (!markedIds.isEmpty()) { | ||||
| 		MTP::send(MTPmessages_ReadMessageContents(MTP_vector<MTPint>(markedIds)), rpcDone(&MainWidget::messagesAffected, (PeerData*)0)); | ||||
| 	} | ||||
| 	for (auto i = channelMarkedIds.cbegin(), e = channelMarkedIds.cend(); i != e; ++i) { | ||||
| 		MTP::send(MTPchannels_ReadMessageContents(i.key()->inputChannel, MTP_vector<MTPint>(i.value()))); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void MainWidget::mediaMarkRead(gsl::not_null<HistoryItem*> item) { | ||||
| 	if ((!item->out() || item->mentionsMe()) && item->isMediaUnread()) { | ||||
| 		item->markMediaRead(); | ||||
| 		if (item->id > 0) { | ||||
| 			if (auto channel = item->history()->peer->asChannel()) { | ||||
| 				MTP::send(MTPchannels_ReadMessageContents(channel->inputChannel, MTP_vector<MTPint>(1, MTP_int(item->id)))); | ||||
| 			} else { | ||||
| 				MTP::send(MTPmessages_ReadMessageContents(MTP_vector<MTPint>(1, MTP_int(item->id))), rpcDone(&MainWidget::messagesAffected, (PeerData*)0)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void MainWidget::updateOnlineDisplay() { | ||||
|  | @ -2341,7 +2363,7 @@ void MainWidget::onViewsIncrement() { | |||
| 		for (ViewsIncrementMap::const_iterator j = i.value().cbegin(), end = i.value().cend(); j != end; ++j) { | ||||
| 			ids.push_back(MTP_int(j.key())); | ||||
| 		} | ||||
| 		mtpRequestId req = MTP::send(MTPmessages_GetMessagesViews(i.key()->input, MTP_vector<MTPint>(ids), MTP_bool(true)), rpcDone(&MainWidget::viewsIncrementDone, ids), rpcFail(&MainWidget::viewsIncrementFail), 0, 5); | ||||
| 		auto req = MTP::send(MTPmessages_GetMessagesViews(i.key()->input, MTP_vector<MTPint>(ids), MTP_bool(true)), rpcDone(&MainWidget::viewsIncrementDone, ids), rpcFail(&MainWidget::viewsIncrementFail), 0, 5); | ||||
| 		_viewsIncrementRequests.insert(i.key(), req); | ||||
| 		i = _viewsToIncrement.erase(i); | ||||
| 	} | ||||
|  | @ -3532,6 +3554,7 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha | |||
| 				h->setUnreadCount(d.vunread_count.v); | ||||
| 				h->inboxReadBefore = d.vread_inbox_max_id.v + 1; | ||||
| 			} | ||||
| 			h->setUnreadMentionsCount(d.vunread_mentions_count.v); | ||||
| 			if (_history->peer() == channel) { | ||||
| 				_history->updateHistoryDownVisibility(); | ||||
| 				_history->preloadHistoryIfNeeded(); | ||||
|  | @ -3607,13 +3630,13 @@ void MainWidget::gotRangeDifference(ChannelData *channel, const MTPupdates_Chann | |||
| 	bool isFinal = true; | ||||
| 	switch (diff.type()) { | ||||
| 	case mtpc_updates_channelDifferenceEmpty: { | ||||
| 		const auto &d(diff.c_updates_channelDifferenceEmpty()); | ||||
| 		auto &d = diff.c_updates_channelDifferenceEmpty(); | ||||
| 		nextRequestPts = d.vpts.v; | ||||
| 		isFinal = d.is_final(); | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updates_channelDifferenceTooLong: { | ||||
| 		const auto &d(diff.c_updates_channelDifferenceTooLong()); | ||||
| 		auto &d = diff.c_updates_channelDifferenceTooLong(); | ||||
| 
 | ||||
| 		App::feedUsers(d.vusers); | ||||
| 		App::feedChats(d.vchats); | ||||
|  | @ -3623,7 +3646,7 @@ void MainWidget::gotRangeDifference(ChannelData *channel, const MTPupdates_Chann | |||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updates_channelDifference: { | ||||
| 		const auto &d(diff.c_updates_channelDifference()); | ||||
| 		auto &d = diff.c_updates_channelDifference(); | ||||
| 
 | ||||
| 		App::feedUsers(d.vusers); | ||||
| 		App::feedChats(d.vchats); | ||||
|  | @ -4362,6 +4385,10 @@ bool MainWidget::doWeReadServerHistory() const { | |||
| 	return isActive() && !_wideSection && !_overview && _history->doWeReadServerHistory(); | ||||
| } | ||||
| 
 | ||||
| bool MainWidget::doWeReadMentions() const { | ||||
| 	return isActive() && !_wideSection && !_overview && _history->doWeReadMentions(); | ||||
| } | ||||
| 
 | ||||
| bool MainWidget::lastWasOnline() const { | ||||
| 	return _lastWasOnline; | ||||
| } | ||||
|  | @ -4731,6 +4758,8 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { | |||
| 
 | ||||
| void MainWidget::feedUpdate(const MTPUpdate &update) { | ||||
| 	switch (update.type()) { | ||||
| 
 | ||||
| 	// New messages.
 | ||||
| 	case mtpc_updateNewMessage: { | ||||
| 		auto &d = update.c_updateNewMessage(); | ||||
| 
 | ||||
|  | @ -4750,6 +4779,43 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { | |||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateNewChannelMessage: { | ||||
| 		auto &d = update.c_updateNewChannelMessage(); | ||||
| 		auto channel = App::channelLoaded(peerToChannel(peerFromMessage(d.vmessage))); | ||||
| 		auto isDataLoaded = allDataLoadedForMessage(d.vmessage); | ||||
| 		if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) { | ||||
| 			MTP_LOG(0, ("getDifference { good - after not all data loaded in updateNewChannelMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); | ||||
| 
 | ||||
| 			// Request last active supergroup participants if the 'from' user was not loaded yet.
 | ||||
| 			// This will optimize similar getDifference() calls for almost all next messages.
 | ||||
| 			if (isDataLoaded == DataIsLoadedResult::FromNotLoaded && channel && channel->isMegagroup()) { | ||||
| 				if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.isEmpty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) { | ||||
| 					Auth().api().requestLastParticipants(channel); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (!_byMinChannelTimer.isActive()) { // getDifference after timeout
 | ||||
| 				_byMinChannelTimer.start(WaitForSkippedTimeout); | ||||
| 			} | ||||
| 			return; | ||||
| 		} | ||||
| 		if (channel && !_handlingChannelDifference) { | ||||
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | ||||
| 				return; | ||||
| 			} else if (channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update)) { | ||||
| 				// We could've added an item.
 | ||||
| 				// Better would be for history to be subscribed to new messages.
 | ||||
| 				_history->peerMessagesUpdated(); | ||||
| 			} | ||||
| 		} else { | ||||
| 			Auth().api().applyUpdateNoPtsCheck(update); | ||||
| 
 | ||||
| 			// We could've added an item.
 | ||||
| 			// Better would be for history to be subscribed to new messages.
 | ||||
| 			_history->peerMessagesUpdated(); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateMessageID: { | ||||
| 		auto &d = update.c_updateMessageID(); | ||||
| 		auto msg = App::histItemByRandom(d.vrandom_id.v); | ||||
|  | @ -4779,11 +4845,58 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { | |||
| 		App::historyUnregSentData(d.vrandom_id.v); | ||||
| 	} break; | ||||
| 
 | ||||
| 	// Message contents being read.
 | ||||
| 	case mtpc_updateReadMessagesContents: { | ||||
| 		auto &d = update.c_updateReadMessagesContents(); | ||||
| 		ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateChannelReadMessagesContents: { | ||||
| 		auto &d = update.c_updateChannelReadMessagesContents(); | ||||
| 		auto channel = App::channelLoaded(d.vchannel_id.v); | ||||
| 		if (!channel) { | ||||
| 			if (!_byMinChannelTimer.isActive()) { // getDifference after timeout
 | ||||
| 				_byMinChannelTimer.start(WaitForSkippedTimeout); | ||||
| 			} | ||||
| 			return; | ||||
| 		} | ||||
| 		auto possiblyReadMentions = base::flat_set<MsgId>(); | ||||
| 		for_const (auto &msgId, d.vmessages.v) { | ||||
| 			if (auto item = App::histItemById(channel, msgId.v)) { | ||||
| 				if (item->isMediaUnread()) { | ||||
| 					item->markMediaRead(); | ||||
| 					Ui::repaintHistoryItem(item); | ||||
| 				} | ||||
| 			} else { | ||||
| 				// Perhaps it was an unread mention!
 | ||||
| 				possiblyReadMentions.insert(msgId.v); | ||||
| 			} | ||||
| 		} | ||||
| 		Auth().api().checkForUnreadMentions(possiblyReadMentions, channel); | ||||
| 	} break; | ||||
| 
 | ||||
| 	// Edited messages.
 | ||||
| 	case mtpc_updateEditMessage: { | ||||
| 		auto &d = update.c_updateEditMessage(); | ||||
| 		ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateEditChannelMessage: { | ||||
| 		auto &d = update.c_updateEditChannelMessage(); | ||||
| 		auto channel = App::channelLoaded(peerToChannel(peerFromMessage(d.vmessage))); | ||||
| 
 | ||||
| 		if (channel && !_handlingChannelDifference) { | ||||
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | ||||
| 				return; | ||||
| 			} else { | ||||
| 				channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); | ||||
| 			} | ||||
| 		} else { | ||||
| 			Auth().api().applyUpdateNoPtsCheck(update); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	// Messages being read.
 | ||||
| 	case mtpc_updateReadHistoryInbox: { | ||||
| 		auto &d = update.c_updateReadHistoryInbox(); | ||||
| 		ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); | ||||
|  | @ -4798,6 +4911,53 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { | |||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateReadChannelInbox: { | ||||
| 		auto &d = update.c_updateReadChannelInbox(); | ||||
| 		App::feedInboxRead(peerFromChannel(d.vchannel_id.v), d.vmax_id.v); | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateReadChannelOutbox: { | ||||
| 		auto &d = update.c_updateReadChannelOutbox(); | ||||
| 		auto peerId = peerFromChannel(d.vchannel_id.v); | ||||
| 		auto when = requestingDifference() ? 0 : unixtime(); | ||||
| 		App::feedOutboxRead(peerId, d.vmax_id.v, when); | ||||
| 		if (_history->peer() && _history->peer()->id == peerId) { | ||||
| 			_history->update(); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	// Deleted messages.
 | ||||
| 	case mtpc_updateDeleteMessages: { | ||||
| 		auto &d = update.c_updateDeleteMessages(); | ||||
| 
 | ||||
| 		if (ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update)) { | ||||
| 			// We could've removed some items.
 | ||||
| 			// Better would be for history to be subscribed to removed messages.
 | ||||
| 			_history->peerMessagesUpdated(); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateDeleteChannelMessages: { | ||||
| 		auto &d = update.c_updateDeleteChannelMessages(); | ||||
| 		auto channel = App::channelLoaded(d.vchannel_id.v); | ||||
| 
 | ||||
| 		if (channel && !_handlingChannelDifference) { | ||||
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | ||||
| 				return; | ||||
| 			} else if (channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update)) { | ||||
| 				// We could've removed some items.
 | ||||
| 				// Better would be for history to be subscribed to removed messages.
 | ||||
| 				_history->peerMessagesUpdated(); | ||||
| 			} | ||||
| 		} else { | ||||
| 			// We could've removed some items.
 | ||||
| 			// Better would be for history to be subscribed to removed messages.
 | ||||
| 			_history->peerMessagesUpdated(); | ||||
| 
 | ||||
| 			Auth().api().applyUpdateNoPtsCheck(update); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateWebPage: { | ||||
| 		auto &d = update.c_updateWebPage(); | ||||
| 
 | ||||
|  | @ -4809,13 +4969,23 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { | |||
| 		ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateDeleteMessages: { | ||||
| 		auto &d = update.c_updateDeleteMessages(); | ||||
| 	case mtpc_updateChannelWebPage: { | ||||
| 		auto &d = update.c_updateChannelWebPage(); | ||||
| 
 | ||||
| 		if (ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update)) { | ||||
| 			// We could've removed some items.
 | ||||
| 			// Better would be for history to be subscribed to removed messages.
 | ||||
| 			_history->peerMessagesUpdated(); | ||||
| 		// update web page anyway
 | ||||
| 		App::feedWebPage(d.vwebpage); | ||||
| 		_history->updatePreview(); | ||||
| 		webPagesOrGamesUpdate(); | ||||
| 
 | ||||
| 		auto channel = App::channelLoaded(d.vchannel_id.v); | ||||
| 		if (channel && !_handlingChannelDifference) { | ||||
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | ||||
| 				return; | ||||
| 			} else { | ||||
| 				channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); | ||||
| 			} | ||||
| 		} else { | ||||
| 			Auth().api().applyUpdateNoPtsCheck(update); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
|  | @ -5052,7 +5222,6 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { | |||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	/////// Channel updates
 | ||||
| 	case mtpc_updateChannel: { | ||||
| 		auto &d = update.c_updateChannel(); | ||||
| 		if (auto channel = App::channelLoaded(d.vchannel_id.v)) { | ||||
|  | @ -5067,63 +5236,6 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { | |||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateNewChannelMessage: { | ||||
| 		auto &d = update.c_updateNewChannelMessage(); | ||||
| 		auto channel = App::channelLoaded(peerToChannel(peerFromMessage(d.vmessage))); | ||||
| 		auto isDataLoaded = allDataLoadedForMessage(d.vmessage); | ||||
| 		if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) { | ||||
| 			MTP_LOG(0, ("getDifference { good - after not all data loaded in updateNewChannelMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); | ||||
| 
 | ||||
| 			// Request last active supergroup participants if the 'from' user was not loaded yet.
 | ||||
| 			// This will optimize similar getDifference() calls for almost all next messages.
 | ||||
| 			if (isDataLoaded == DataIsLoadedResult::FromNotLoaded && channel && channel->isMegagroup()) { | ||||
| 				if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.isEmpty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) { | ||||
| 					Auth().api().requestLastParticipants(channel); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (!_byMinChannelTimer.isActive()) { // getDifference after timeout
 | ||||
| 				_byMinChannelTimer.start(WaitForSkippedTimeout); | ||||
| 			} | ||||
| 			return; | ||||
| 		} | ||||
| 		if (channel && !_handlingChannelDifference) { | ||||
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | ||||
| 				return; | ||||
| 			} else if (channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update)) { | ||||
| 				// We could've added an item.
 | ||||
| 				// Better would be for history to be subscribed to new messages.
 | ||||
| 				_history->peerMessagesUpdated(); | ||||
| 			} | ||||
| 		} else { | ||||
| 			Auth().api().applyUpdateNoPtsCheck(update); | ||||
| 
 | ||||
| 			// We could've added an item.
 | ||||
| 			// Better would be for history to be subscribed to new messages.
 | ||||
| 			_history->peerMessagesUpdated(); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateEditChannelMessage: { | ||||
| 		auto &d = update.c_updateEditChannelMessage(); | ||||
| 		auto channel = App::channelLoaded(peerToChannel(peerFromMessage(d.vmessage))); | ||||
| 
 | ||||
| 		if (channel && !_handlingChannelDifference) { | ||||
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | ||||
| 				return; | ||||
| 			} else { | ||||
| 				channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); | ||||
| 			} | ||||
| 		} else { | ||||
| 			Auth().api().applyUpdateNoPtsCheck(update); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateEditMessage: { | ||||
| 		auto &d = update.c_updateEditMessage(); | ||||
| 		ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateChannelPinnedMessage: { | ||||
| 		auto &d = update.c_updateChannelPinnedMessage(); | ||||
| 
 | ||||
|  | @ -5135,62 +5247,6 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { | |||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateReadChannelInbox: { | ||||
| 		auto &d = update.c_updateReadChannelInbox(); | ||||
| 		App::feedInboxRead(peerFromChannel(d.vchannel_id.v), d.vmax_id.v); | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateReadChannelOutbox: { | ||||
| 		auto &d = update.c_updateReadChannelOutbox(); | ||||
| 		auto peerId = peerFromChannel(d.vchannel_id.v); | ||||
| 		auto when = requestingDifference() ? 0 : unixtime(); | ||||
| 		App::feedOutboxRead(peerId, d.vmax_id.v, when); | ||||
| 		if (_history->peer() && _history->peer()->id == peerId) { | ||||
| 			_history->update(); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateChannelWebPage: { | ||||
| 		auto &d = update.c_updateChannelWebPage(); | ||||
| 
 | ||||
| 		// update web page anyway
 | ||||
| 		App::feedWebPage(d.vwebpage); | ||||
| 		_history->updatePreview(); | ||||
| 		webPagesOrGamesUpdate(); | ||||
| 
 | ||||
| 		auto channel = App::channelLoaded(d.vchannel_id.v); | ||||
| 		if (channel && !_handlingChannelDifference) { | ||||
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | ||||
| 				return; | ||||
| 			} else { | ||||
| 				channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); | ||||
| 			} | ||||
| 		} else { | ||||
| 			Auth().api().applyUpdateNoPtsCheck(update); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateDeleteChannelMessages: { | ||||
| 		auto &d = update.c_updateDeleteChannelMessages(); | ||||
| 		auto channel = App::channelLoaded(d.vchannel_id.v); | ||||
| 
 | ||||
| 		if (channel && !_handlingChannelDifference) { | ||||
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | ||||
| 				return; | ||||
| 			} else if (channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update)) { | ||||
| 				// We could've removed some items.
 | ||||
| 				// Better would be for history to be subscribed to removed messages.
 | ||||
| 				_history->peerMessagesUpdated(); | ||||
| 			} | ||||
| 		} else { | ||||
| 			// We could've removed some items.
 | ||||
| 			// Better would be for history to be subscribed to removed messages.
 | ||||
| 			_history->peerMessagesUpdated(); | ||||
| 
 | ||||
| 			Auth().api().applyUpdateNoPtsCheck(update); | ||||
| 		} | ||||
| 	} break; | ||||
| 
 | ||||
| 	case mtpc_updateChannelTooLong: { | ||||
| 		auto &d = update.c_updateChannelTooLong(); | ||||
| 		if (auto channel = App::channelLoaded(d.vchannel_id.v)) { | ||||
|  |  | |||
|  | @ -227,6 +227,7 @@ public: | |||
| 
 | ||||
| 	bool isActive() const; | ||||
| 	bool doWeReadServerHistory() const; | ||||
| 	bool doWeReadMentions() const; | ||||
| 	bool lastWasOnline() const; | ||||
| 	TimeMs lastSetOnline() const; | ||||
| 
 | ||||
|  | @ -347,8 +348,9 @@ public: | |||
| 	void cancelForwarding(History *history); | ||||
| 	void finishForwarding(History *history, bool silent); // send them
 | ||||
| 
 | ||||
| 	void mediaMarkRead(DocumentData *data); | ||||
| 	void mediaMarkRead(gsl::not_null<DocumentData*> data); | ||||
| 	void mediaMarkRead(const HistoryItemsMap &items); | ||||
| 	void mediaMarkRead(gsl::not_null<HistoryItem*> item); | ||||
| 
 | ||||
| 	void webPageUpdated(WebPageData *page); | ||||
| 	void gameUpdated(GameData *game); | ||||
|  |  | |||
|  | @ -516,6 +516,11 @@ bool MainWindow::doWeReadServerHistory() { | |||
| 	return isActive() && _main && !Ui::isLayerShown() && _main->doWeReadServerHistory(); | ||||
| } | ||||
| 
 | ||||
| bool MainWindow::doWeReadMentions() { | ||||
| 	updateIsActive(0); | ||||
| 	return isActive() && _main && !Ui::isLayerShown() && _main->doWeReadMentions(); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::checkHistoryActivation() { | ||||
| 	if (_main && doWeReadServerHistory()) { | ||||
| 		_main->markActiveHistoryAsRead(); | ||||
|  |  | |||
|  | @ -93,6 +93,7 @@ public: | |||
| 	PasscodeWidget *passcodeWidget(); | ||||
| 
 | ||||
| 	bool doWeReadServerHistory(); | ||||
| 	bool doWeReadMentions(); | ||||
| 
 | ||||
| 	void activate(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -150,9 +150,9 @@ void Instance::rebuildPlaylist(Data *data) { | |||
| 
 | ||||
| 	data->playlist.clear(); | ||||
| 	if (data->history && data->history->loadedAtBottom()) { | ||||
| 		auto &historyOverview = data->history->overview[data->overview]; | ||||
| 		auto &historyOverview = data->history->overview(data->overview); | ||||
| 		if (data->migrated && data->migrated->loadedAtBottom() && data->history->loadedAtTop()) { | ||||
| 			auto &migratedOverview = data->migrated->overview[data->overview]; | ||||
| 			auto &migratedOverview = data->migrated->overview(data->overview); | ||||
| 			data->playlist.reserve(migratedOverview.size() + historyOverview.size()); | ||||
| 			for_const (auto msgId, migratedOverview) { | ||||
| 				data->playlist.push_back(FullMsgId(data->migrated->channelId(), msgId)); | ||||
|  |  | |||
|  | @ -205,19 +205,22 @@ void MediaView::mediaOverviewUpdated(const Notify::PeerUpdate &update) { | |||
| 
 | ||||
| 	if (_history && (_history->peer == update.peer || (_migrated && _migrated->peer == update.peer)) && (update.mediaTypesMask & (1 << _overview)) && _msgid) { | ||||
| 		_index = -1; | ||||
| 		auto i = 0; | ||||
| 		if (_msgmigrated) { | ||||
| 			for (int i = 0, l = _migrated->overview[_overview].size(); i < l; ++i) { | ||||
| 				if (_migrated->overview[_overview].at(i) == _msgid) { | ||||
| 			for_const (auto msgId, _migrated->overview(_overview)) { | ||||
| 				if (msgId == _msgid) { | ||||
| 					_index = i; | ||||
| 					break; | ||||
| 				} | ||||
| 				++i; | ||||
| 			} | ||||
| 		} else { | ||||
| 			for (int i = 0, l = _history->overview[_overview].size(); i < l; ++i) { | ||||
| 				if (_history->overview[_overview].at(i) == _msgid) { | ||||
| 			for_const (auto msgId, _history->overview(_overview)) { | ||||
| 				if (msgId == _msgid) { | ||||
| 					_index = i; | ||||
| 					break; | ||||
| 				} | ||||
| 				++i; | ||||
| 			} | ||||
| 		} | ||||
| 		updateControls(); | ||||
|  | @ -227,7 +230,7 @@ void MediaView::mediaOverviewUpdated(const Notify::PeerUpdate &update) { | |||
| 
 | ||||
| 		_index = -1; | ||||
| 		for (int i = 0, l = _user->photos.size(); i < l; ++i) { | ||||
| 			if (_user->photos.at(i) == _photo) { | ||||
| 			if (_user->photos[i] == _photo) { | ||||
| 				_index = i; | ||||
| 				break; | ||||
| 			} | ||||
|  | @ -392,19 +395,19 @@ void MediaView::updateControls() { | |||
| 	updateHeader(); | ||||
| 	if (_photo || (_history && _overview != OverviewCount)) { | ||||
| 		_leftNavVisible = (_index > 0) || (_index == 0 && ( | ||||
| 			(!_msgmigrated && _history && _history->overview[_overview].size() < _history->overviewCount(_overview)) || | ||||
| 			(_msgmigrated && _migrated && _migrated->overview[_overview].size() < _migrated->overviewCount(_overview)) || | ||||
| 			(!_msgmigrated && _history && _migrated && (!_migrated->overview[_overview].isEmpty() || _migrated->overviewCount(_overview) > 0)))) || | ||||
| 			(!_msgmigrated && _history && _history->overview(_overview).size() < _history->overviewCount(_overview)) || | ||||
| 			(_msgmigrated && _migrated && _migrated->overview(_overview).size() < _migrated->overviewCount(_overview)) || | ||||
| 			(!_msgmigrated && _history && _migrated && (!_migrated->overview(_overview).isEmpty() || _migrated->overviewCount(_overview) > 0)))) || | ||||
| 			(_index < 0 && _photo == _additionalChatPhoto && | ||||
| 				((_history && _history->overviewCount(_overview) > 0) || | ||||
| 				(_migrated && _history->overviewLoaded(_overview) && _migrated->overviewCount(_overview) > 0)) | ||||
| 			); | ||||
| 		_rightNavVisible = (_index >= 0) && ( | ||||
| 			(!_msgmigrated && _history && _index + 1 < _history->overview[_overview].size()) || | ||||
| 			(_msgmigrated && _migrated && _index + 1 < _migrated->overview[_overview].size()) || | ||||
| 			(_msgmigrated && _migrated && _history && (!_history->overview[_overview].isEmpty() || _history->overviewCount(_overview) > 0)) || | ||||
| 			(!_msgmigrated && _history && _index + 1 == _history->overview[_overview].size() && _additionalChatPhoto) || | ||||
| 			(_msgmigrated && _migrated && _index + 1 == _migrated->overview[_overview].size() && _history->overviewCount(_overview) == 0 && _additionalChatPhoto) || | ||||
| 			(!_msgmigrated && _history && _index + 1 < _history->overview(_overview).size()) || | ||||
| 			(_msgmigrated && _migrated && _index + 1 < _migrated->overview(_overview).size()) || | ||||
| 			(_msgmigrated && _migrated && _history && (!_history->overview(_overview).isEmpty() || _history->overviewCount(_overview) > 0)) || | ||||
| 			(!_msgmigrated && _history && _index + 1 == _history->overview(_overview).size() && _additionalChatPhoto) || | ||||
| 			(_msgmigrated && _migrated && _index + 1 == _migrated->overview(_overview).size() && _history->overviewCount(_overview) == 0 && _additionalChatPhoto) || | ||||
| 			(!_history && _user && (_index + 1 < _user->photos.size() || _index + 1 < _user->photosCount))); | ||||
| 		if (_msgmigrated && !_history->overviewLoaded(_overview)) { | ||||
| 			_leftNavVisible = _rightNavVisible = false; | ||||
|  | @ -2154,10 +2157,10 @@ bool MediaView::moveToNext(int32 delta) { | |||
| 			auto lastChatPhoto = computeLastOverviewChatPhoto(); | ||||
| 			if (lastChatPhoto.item) { | ||||
| 				if (lastChatPhoto.item->history() == _history) { | ||||
| 					_index = _history->overview[_overview].size() - 1; | ||||
| 					_index = _history->overview(_overview).size() - 1; | ||||
| 					_msgmigrated = false; | ||||
| 				} else { | ||||
| 					_index = _migrated->overview[_overview].size() - 1; | ||||
| 					_index = _migrated->overview(_overview).size() - 1; | ||||
| 					_msgmigrated = true; | ||||
| 				} | ||||
| 				_msgid = lastChatPhoto.item->id; | ||||
|  | @ -2186,14 +2189,14 @@ bool MediaView::moveToNext(int32 delta) { | |||
| 	if (_history && _overview != OverviewCount) { | ||||
| 		bool newMigrated = _msgmigrated; | ||||
| 		if (!newMigrated && newIndex < 0 && _migrated) { | ||||
| 			newIndex += _migrated->overview[_overview].size(); | ||||
| 			newIndex += _migrated->overview(_overview).size(); | ||||
| 			newMigrated = true; | ||||
| 		} else if (newMigrated && newIndex >= _migrated->overview[_overview].size()) { | ||||
| 			newIndex -= _migrated->overview[_overview].size() + (_history->overviewCount(_overview) - _history->overview[_overview].size()); | ||||
| 		} else if (newMigrated && newIndex >= _migrated->overview(_overview).size()) { | ||||
| 			newIndex -= _migrated->overview(_overview).size() + (_history->overviewCount(_overview) - _history->overview(_overview).size()); | ||||
| 			newMigrated = false; | ||||
| 		} | ||||
| 		if (newIndex >= 0 && newIndex < (newMigrated ? _migrated : _history)->overview[_overview].size()) { | ||||
| 			if (auto item = App::histItemById(newMigrated ? 0 : _channel, (newMigrated ? _migrated : _history)->overview[_overview][newIndex])) { | ||||
| 		if (newIndex >= 0 && newIndex < (newMigrated ? _migrated : _history)->overview(_overview).size()) { | ||||
| 			if (auto item = App::histItemById(newMigrated ? 0 : _channel, getMsgIdFromOverview(newMigrated ? _migrated : _history, newIndex))) { | ||||
| 				_index = newIndex; | ||||
| 				_msgid = item->id; | ||||
| 				_msgmigrated = (item->history() == _migrated); | ||||
|  | @ -2214,7 +2217,7 @@ bool MediaView::moveToNext(int32 delta) { | |||
| 					preloadData(delta); | ||||
| 				} | ||||
| 			} | ||||
| 		} else if (!newMigrated && newIndex == _history->overview[_overview].size() && _additionalChatPhoto) { | ||||
| 		} else if (!newMigrated && newIndex == _history->overview(_overview).size() && _additionalChatPhoto) { | ||||
| 			_index = -1; | ||||
| 			_msgid = 0; | ||||
| 			_msgmigrated = false; | ||||
|  | @ -2243,28 +2246,29 @@ void MediaView::preloadData(int32 delta) { | |||
| 	bool indexOfMigratedItem = _msgmigrated; | ||||
| 	if (_index < 0) { | ||||
| 		if (_overview != OverviewChatPhotos || !_history) return; | ||||
| 		indexInOverview = _history->overview[OverviewChatPhotos].size(); | ||||
| 		indexInOverview = _history->overview(OverviewChatPhotos).size(); | ||||
| 		indexOfMigratedItem = false; | ||||
| 	} | ||||
| 	if (!_user && _overview == OverviewCount) return; | ||||
| 
 | ||||
| 	int32 from = indexInOverview + (delta ? delta : -1), to = indexInOverview + (delta ? delta * MediaOverviewPreloadCount : 1); | ||||
| 	auto from = indexInOverview + (delta ? delta : -1); | ||||
| 	auto to = indexInOverview + (delta ? delta * MediaOverviewPreloadCount : 1); | ||||
| 	if (from > to) qSwap(from, to); | ||||
| 	if (_history && _overview != OverviewCount) { | ||||
| 		int32 forgetIndex = indexInOverview - delta * 2; | ||||
| 		History *forgetHistory = indexOfMigratedItem ? _migrated : _history; | ||||
| 		auto forgetIndex = indexInOverview - delta * 2; | ||||
| 		auto forgetHistory = indexOfMigratedItem ? _migrated : _history; | ||||
| 		if (_migrated) { | ||||
| 			if (indexOfMigratedItem && forgetIndex >= _migrated->overview[_overview].size()) { | ||||
| 			if (indexOfMigratedItem && forgetIndex >= _migrated->overview(_overview).size()) { | ||||
| 				forgetHistory = _history; | ||||
| 				forgetIndex -= _migrated->overview[_overview].size() + (_history->overviewCount(_overview) - _history->overview[_overview].size()); | ||||
| 				forgetIndex -= _migrated->overview(_overview).size() + (_history->overviewCount(_overview) - _history->overview(_overview).size()); | ||||
| 			} else if (!indexOfMigratedItem && forgetIndex < 0) { | ||||
| 				forgetHistory = _migrated; | ||||
| 				forgetIndex += _migrated->overview[_overview].size(); | ||||
| 				forgetIndex += _migrated->overview(_overview).size(); | ||||
| 			} | ||||
| 		} | ||||
| 		if (forgetIndex >= 0 && forgetIndex < forgetHistory->overview[_overview].size() && (forgetHistory != (indexOfMigratedItem ? _migrated : _history) || forgetIndex != indexInOverview)) { | ||||
| 			if (HistoryItem *item = App::histItemById(forgetHistory->channelId(), forgetHistory->overview[_overview][forgetIndex])) { | ||||
| 				if (HistoryMedia *media = item->getMedia()) { | ||||
| 		if (forgetIndex >= 0 && forgetIndex < forgetHistory->overview(_overview).size() && (forgetHistory != (indexOfMigratedItem ? _migrated : _history) || forgetIndex != indexInOverview)) { | ||||
| 			if (auto item = App::histItemById(forgetHistory->channelId(), getMsgIdFromOverview(forgetHistory, forgetIndex))) { | ||||
| 				if (auto media = item->getMedia()) { | ||||
| 					switch (media->type()) { | ||||
| 					case MediaTypePhoto: static_cast<HistoryPhoto*>(media)->photo()->forget(); break; | ||||
| 					case MediaTypeFile: | ||||
|  | @ -2280,23 +2284,23 @@ void MediaView::preloadData(int32 delta) { | |||
| 			History *previewHistory = indexOfMigratedItem ? _migrated : _history; | ||||
| 			int32 previewIndex = i; | ||||
| 			if (_migrated) { | ||||
| 				if (indexOfMigratedItem && previewIndex >= _migrated->overview[_overview].size()) { | ||||
| 				if (indexOfMigratedItem && previewIndex >= _migrated->overview(_overview).size()) { | ||||
| 					previewHistory = _history; | ||||
| 					previewIndex -= _migrated->overview[_overview].size() + (_history->overviewCount(_overview) - _history->overview[_overview].size()); | ||||
| 					previewIndex -= _migrated->overview(_overview).size() + (_history->overviewCount(_overview) - _history->overview(_overview).size()); | ||||
| 				} else if (!indexOfMigratedItem && previewIndex < 0) { | ||||
| 					previewHistory = _migrated; | ||||
| 					previewIndex += _migrated->overview[_overview].size(); | ||||
| 					previewIndex += _migrated->overview(_overview).size(); | ||||
| 				} | ||||
| 			} | ||||
| 			if (previewIndex >= 0 && previewIndex < previewHistory->overview[_overview].size() && (previewHistory != (indexOfMigratedItem ? _migrated : _history) || previewIndex != indexInOverview)) { | ||||
| 				if (HistoryItem *item = App::histItemById(previewHistory->channelId(), previewHistory->overview[_overview][previewIndex])) { | ||||
| 					if (HistoryMedia *media = item->getMedia()) { | ||||
| 			if (previewIndex >= 0 && previewIndex < previewHistory->overview(_overview).size() && (previewHistory != (indexOfMigratedItem ? _migrated : _history) || previewIndex != indexInOverview)) { | ||||
| 				if (auto item = App::histItemById(previewHistory->channelId(), getMsgIdFromOverview(previewHistory, previewIndex))) { | ||||
| 					if (auto media = item->getMedia()) { | ||||
| 						switch (media->type()) { | ||||
| 						case MediaTypePhoto: static_cast<HistoryPhoto*>(media)->photo()->download(); break; | ||||
| 						case MediaTypeFile: | ||||
| 						case MediaTypeVideo: | ||||
| 						case MediaTypeGif: { | ||||
| 							DocumentData *doc = media->getDocument(); | ||||
| 							auto doc = media->getDocument(); | ||||
| 							doc->thumb->load(); | ||||
| 							doc->automaticLoad(item); | ||||
| 						} break; | ||||
|  | @ -2751,12 +2755,14 @@ void MediaView::updateImage() { | |||
| } | ||||
| 
 | ||||
| void MediaView::findCurrent() { | ||||
| 	auto i = 0; | ||||
| 	if (_msgmigrated) { | ||||
| 		for (int i = 0, l = _migrated->overview[_overview].size(); i < l; ++i) { | ||||
| 			if (_migrated->overview[_overview].at(i) == _msgid) { | ||||
| 		for (auto msgId : _migrated->overview(_overview)) { | ||||
| 			if (msgId == _msgid) { | ||||
| 				_index = i; | ||||
| 				break; | ||||
| 			} | ||||
| 			++i; | ||||
| 		} | ||||
| 		if (!_history->overviewCountLoaded(_overview)) { | ||||
| 			loadBack(); | ||||
|  | @ -2766,11 +2772,12 @@ void MediaView::findCurrent() { | |||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		for (int i = 0, l = _history->overview[_overview].size(); i < l; ++i) { | ||||
| 			if (_history->overview[_overview].at(i) == _msgid) { | ||||
| 		for (auto msgId : _history->overview(_overview)) { | ||||
| 			if (msgId == _msgid) { | ||||
| 				_index = i; | ||||
| 				break; | ||||
| 			} | ||||
| 			++i; | ||||
| 		} | ||||
| 		if (!_history->overviewLoaded(_overview)) { | ||||
| 			if (!_history->overviewCountLoaded(_overview) || (_index < 2 && _history->overviewCount(_overview) > 0) || (_index < 1 && _migrated && !_migrated->overviewLoaded(_overview))) { | ||||
|  | @ -2799,7 +2806,7 @@ void MediaView::loadBack() { | |||
| 				App::main()->loadMediaBack(_migrated->peer, _overview); | ||||
| 			} else { | ||||
| 				App::main()->loadMediaBack(_history->peer, _overview); | ||||
| 				if (_migrated && _index == 0 && (_migrated->overviewCount(_overview) < 0 || _migrated->overview[_overview].isEmpty()) && !_migrated->overviewLoaded(_overview)) { | ||||
| 				if (_migrated && _index == 0 && (_migrated->overviewCount(_overview) < 0 || _migrated->overview(_overview).isEmpty()) && !_migrated->overviewLoaded(_overview)) { | ||||
| 					App::main()->loadMediaBack(_migrated->peer, _overview); | ||||
| 				} | ||||
| 			} | ||||
|  | @ -2816,7 +2823,8 @@ void MediaView::loadBack() { | |||
| MediaView::LastChatPhoto MediaView::computeLastOverviewChatPhoto() { | ||||
| 	LastChatPhoto emptyResult = { nullptr, nullptr }; | ||||
| 	auto lastPhotoInOverview = [&emptyResult](auto history, auto list) -> LastChatPhoto { | ||||
| 		if (auto item = App::histItemById(history->channelId(), list.back())) { | ||||
| 		auto end = list.end(); | ||||
| 		if (auto item = App::histItemById(history->channelId(), *--end)) { | ||||
| 			if (auto media = item->getMedia()) { | ||||
| 				if (media->type() == MediaTypePhoto && !item->toHistoryMessage()) { | ||||
| 					return { item, static_cast<HistoryPhoto*>(media)->photo() }; | ||||
|  | @ -2827,13 +2835,13 @@ MediaView::LastChatPhoto MediaView::computeLastOverviewChatPhoto() { | |||
| 	}; | ||||
| 
 | ||||
| 	if (!_history) return emptyResult; | ||||
| 	auto &list = _history->overview[OverviewChatPhotos]; | ||||
| 	auto &list = _history->overview(OverviewChatPhotos); | ||||
| 	if (!list.isEmpty()) { | ||||
| 		return lastPhotoInOverview(_history, list); | ||||
| 	} | ||||
| 
 | ||||
| 	if (!_migrated || !_history->overviewLoaded(OverviewChatPhotos)) return emptyResult; | ||||
| 	auto &migratedList = _migrated->overview[OverviewChatPhotos]; | ||||
| 	auto &migratedList = _migrated->overview(OverviewChatPhotos); | ||||
| 	if (!migratedList.isEmpty()) { | ||||
| 		return lastPhotoInOverview(_migrated, migratedList); | ||||
| 	} | ||||
|  | @ -2890,17 +2898,17 @@ void MediaView::updateHeader() { | |||
| 	int32 index = _index, count = 0, addcount = (_migrated && _overview != OverviewCount) ? _migrated->overviewCount(_overview) : 0; | ||||
| 	if (_history) { | ||||
| 		if (_overview != OverviewCount) { | ||||
| 			bool lastOverviewPhotoLoaded = (!_history->overview[_overview].isEmpty() || ( | ||||
| 				_migrated && _history->overviewCount(_overview) == 0 && !_migrated->overview[_overview].isEmpty())); | ||||
| 			bool lastOverviewPhotoLoaded = (!_history->overview(_overview).isEmpty() || ( | ||||
| 				_migrated && _history->overviewCount(_overview) == 0 && !_migrated->overview(_overview).isEmpty())); | ||||
| 			count = _history->overviewCount(_overview); | ||||
| 			if (addcount >= 0 && count >= 0) { | ||||
| 				count += addcount; | ||||
| 			} | ||||
| 			if (index >= 0 && (_msgmigrated ? (count >= 0 && addcount >= 0 && _history->overviewLoaded(_overview)) : (count >= 0))) { | ||||
| 				if (_msgmigrated) { | ||||
| 					index += addcount - _migrated->overview[_overview].size(); | ||||
| 					index += addcount - _migrated->overview(_overview).size(); | ||||
| 				} else { | ||||
| 					index += count - _history->overview[_overview].size(); | ||||
| 					index += count - _history->overview(_overview).size(); | ||||
| 				} | ||||
| 				if (_additionalChatPhoto && lastOverviewPhotoLoaded) { | ||||
| 					++count; | ||||
|  | @ -2948,3 +2956,15 @@ float64 MediaView::overLevel(OverState control) const { | |||
| 	auto i = _animOpacities.constFind(control); | ||||
| 	return (i == _animOpacities.cend()) ? (_over == control ? 1 : 0) : i->current(); | ||||
| } | ||||
| 
 | ||||
| MsgId MediaView::getMsgIdFromOverview(gsl::not_null<History*> history, int index) const { | ||||
| 	auto &overview = history->overview(_overview); | ||||
| 	if (index >= 0 && index < overview.size()) { | ||||
| 		auto it = overview.begin(); | ||||
| 		for (auto i = 0; i != index; ++i) { | ||||
| 			++it; | ||||
| 		} | ||||
| 		return *it; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  |  | |||
|  | @ -224,6 +224,8 @@ private: | |||
| 	bool updateOverState(OverState newState); | ||||
| 	float64 overLevel(OverState control) const; | ||||
| 
 | ||||
| 	MsgId getMsgIdFromOverview(gsl::not_null<History*> history, int index) const; | ||||
| 
 | ||||
| 	QBrush _transparentBrush; | ||||
| 
 | ||||
| 	PhotoData *_photo = nullptr; | ||||
|  | @ -294,7 +296,7 @@ private: | |||
| 	UserData *_user = nullptr; // if user profile photos overview
 | ||||
| 
 | ||||
| 	// There can be additional first photo in chat photos overview, that is not
 | ||||
| 	// in the _history->overview[OverviewChatPhotos] (if the item was deleted).
 | ||||
| 	// in the _history->overview(OverviewChatPhotos) (if the item was deleted).
 | ||||
| 	PhotoData *_additionalChatPhoto = nullptr; | ||||
| 
 | ||||
| 	// We save the information about the reason of the current mediaview show:
 | ||||
|  |  | |||
|  | @ -51,6 +51,7 @@ struct PeerUpdate { | |||
| 		MembersChanged            = 0x00000200U, | ||||
| 		AdminsChanged             = 0x00000400U, | ||||
| 		BannedUsersChanged        = 0x00000800U, | ||||
| 		UnreadMentionsChanged     = 0x00001000U, | ||||
| 
 | ||||
| 		// For users
 | ||||
| 		UserCanShareContact       = 0x00010000U, | ||||
|  |  | |||
|  | @ -180,7 +180,7 @@ MsgId OverviewInner::itemMsgId(MsgId msgId) const { | |||
| } | ||||
| 
 | ||||
| int32 OverviewInner::migratedIndexSkip() const { | ||||
| 	return (_migrated && _history->overviewLoaded(_type)) ? _migrated->overview[_type].size() : 0; | ||||
| 	return (_migrated && _history->overviewLoaded(_type)) ? _migrated->overview(_type).size() : 0; | ||||
| } | ||||
| 
 | ||||
| void OverviewInner::fixItemIndex(int32 ¤t, MsgId msgId) const { | ||||
|  | @ -742,7 +742,7 @@ int32 OverviewInner::itemTop(const FullMsgId &msgId) const { | |||
| void OverviewInner::preloadMore() { | ||||
| 	if (_inSearch) { | ||||
| 		if (!_searchRequest) { | ||||
| 			MTPmessagesFilter filter = (_type == OverviewLinks) ? MTP_inputMessagesFilterUrl() : MTP_inputMessagesFilterDocument(); | ||||
| 			auto filter = (_type == OverviewLinks) ? MTP_inputMessagesFilterUrl() : MTP_inputMessagesFilterDocument(); | ||||
| 			if (!_searchFull) { | ||||
| 				_searchRequest = MTP::send(MTPmessages_Search(MTP_flags(0), _history->peer->input, MTP_string(_searchQuery), MTP_inputUserEmpty(), filter, MTP_int(0), MTP_int(0), MTP_int(_lastSearchId), MTP_int(0), MTP_int(SearchPerPage), MTP_int(0), MTP_int(0)), rpcDone(&OverviewInner::searchReceived, _lastSearchId ? SearchFromOffset : SearchFromStart), rpcFail(&OverviewInner::searchFailed, _lastSearchId ? SearchFromOffset : SearchFromStart)); | ||||
| 				if (!_lastSearchId) { | ||||
|  | @ -762,7 +762,7 @@ void OverviewInner::preloadMore() { | |||
| } | ||||
| 
 | ||||
| bool OverviewInner::preloadLocal() { | ||||
| 	if (_itemsToBeLoaded >= migratedIndexSkip() + _history->overview[_type].size()) return false; | ||||
| 	if (_itemsToBeLoaded >= migratedIndexSkip() + _history->overview(_type).size()) return false; | ||||
| 	_itemsToBeLoaded += LinksOverviewPerPage; | ||||
| 	mediaOverviewUpdated(); | ||||
| 	return true; | ||||
|  | @ -800,7 +800,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) { | |||
| 	auto ms = getms(); | ||||
| 	Overview::Layout::PaintContext context(ms, _selMode); | ||||
| 
 | ||||
| 	if (_history->overview[_type].isEmpty() && (!_migrated || !_history->overviewLoaded(_type) || _migrated->overview[_type].isEmpty())) { | ||||
| 	if (_history->overview(_type).isEmpty() && (!_migrated || !_history->overviewLoaded(_type) || _migrated->overview(_type).isEmpty())) { | ||||
| 		HistoryLayout::paintEmpty(p, _width, height()); | ||||
| 		return; | ||||
| 	} else if (_inSearch && _searchResults.isEmpty() && _searchFull && (!_migrated || _searchFullMigrated) && !_searchTimer.isActive()) { | ||||
|  | @ -1625,17 +1625,28 @@ void OverviewInner::onTouchScrollTimer() { | |||
| 
 | ||||
| void OverviewInner::mediaOverviewUpdated() { | ||||
| 	if (_type == OverviewPhotos || _type == OverviewVideos) { | ||||
| 		History::MediaOverview &o(_history->overview[_type]), *migratedOverview = _migrated ? &_migrated->overview[_type] : 0; | ||||
| 		int32 migrateCount = migratedIndexSkip(); | ||||
| 		int32 wasCount = _items.size(), fullCount = (migrateCount + o.size()); | ||||
| 		int32 tocheck = qMin(fullCount, _itemsToBeLoaded); | ||||
| 		auto &o = _history->overview(_type); | ||||
| 		auto migratedOverview = _migrated ? &_migrated->overview(_type) : nullptr; | ||||
| 		auto migrateCount = migratedIndexSkip(); | ||||
| 		auto wasCount = _items.size(); | ||||
| 		auto fullCount = (migrateCount + o.size()); | ||||
| 		auto tocheck = qMin(fullCount, _itemsToBeLoaded); | ||||
| 		_items.reserve(tocheck); | ||||
| 
 | ||||
| 		int32 index = 0; | ||||
| 		bool allGood = true; | ||||
| 		for (int32 i = fullCount, l = fullCount - tocheck; i > l;) { | ||||
| 		auto index = 0; | ||||
| 		auto allGood = true; | ||||
| 		auto migrateIt = migratedOverview ? migratedOverview->end() : o.end(); | ||||
| 		auto it = o.end(); | ||||
| 		for (auto i = fullCount, l = fullCount - tocheck; i > l;) { | ||||
| 			--i; | ||||
| 			MsgId msgid = ((i < migrateCount) ? -migratedOverview->at(i) : o.at(i - migrateCount)); | ||||
| 			auto msgid = MsgId(0); | ||||
| 			if (i < migrateCount) { | ||||
| 				--migrateIt; | ||||
| 				msgid = -(*migrateIt); | ||||
| 			} else { | ||||
| 				--it; | ||||
| 				msgid = *it; | ||||
| 			} | ||||
| 			if (allGood) { | ||||
| 				if (_items.size() > index && complexMsgId(_items.at(index)->getItem()) == msgid) { | ||||
| 					++index; | ||||
|  | @ -1657,54 +1668,70 @@ void OverviewInner::mediaOverviewUpdated() { | |||
| 		bool dateEveryMonth = (_type == OverviewFiles), dateEveryDay = (_type == OverviewLinks); | ||||
| 		bool withDates = (dateEveryMonth || dateEveryDay); | ||||
| 
 | ||||
| 		History::MediaOverview &o(_history->overview[_type]), *migratedOverview = _migrated ? &_migrated->overview[_type] : 0; | ||||
| 		int32 migrateCount = migratedIndexSkip(); | ||||
| 		int32 l = _inSearch ? _searchResults.size() : (migrateCount + o.size()), tocheck = qMin(l, _itemsToBeLoaded); | ||||
| 		auto &o = _history->overview(_type); | ||||
| 		auto migratedOverview = _migrated ? &_migrated->overview(_type) : nullptr; | ||||
| 		auto migrateCount = migratedIndexSkip(); | ||||
| 		auto l = _inSearch ? _searchResults.size() : (migrateCount + o.size()); | ||||
| 		auto tocheck = qMin(l, _itemsToBeLoaded); | ||||
| 		_items.reserve((withDates ? 2 : 1) * tocheck); // day items
 | ||||
| 
 | ||||
| 		int32 top = 0, index = 0; | ||||
| 		auto migrateIt = migratedOverview ? migratedOverview->end() : o.end(); | ||||
| 		auto it = o.end(); | ||||
| 
 | ||||
| 		auto top = 0; | ||||
| 		auto count = 0; | ||||
| 		bool allGood = true; | ||||
| 		QDate prevDate; | ||||
| 		for (int32 i = 0; i < tocheck; ++i) { | ||||
| 			MsgId msgid = _inSearch ? _searchResults.at(l - i - 1) : ((l - i - 1 < migrateCount) ? -migratedOverview->at(l - i - 1) : o.at(l - i - 1 - migrateCount)); | ||||
| 		for (auto i = 0; i < tocheck; ++i) { | ||||
| 			auto msgid = MsgId(0); | ||||
| 			auto index = l - i - 1; | ||||
| 			if (_inSearch) { | ||||
| 				msgid = _searchResults[index]; | ||||
| 			} else if (index < migrateCount) { | ||||
| 				--migrateIt; | ||||
| 				msgid = -(*migrateIt); | ||||
| 			} else { | ||||
| 				--it; | ||||
| 				msgid = *it; | ||||
| 			} | ||||
| 			if (allGood) { | ||||
| 				if (_items.size() > index && complexMsgId(_items.at(index)->getItem()) == msgid) { | ||||
| 					if (withDates) prevDate = _items.at(index)->getItem()->date.date(); | ||||
| 					top = _items.at(index)->Get<Overview::Layout::Info>()->top; | ||||
| 				if (_items.size() > count && complexMsgId(_items.at(count)->getItem()) == msgid) { | ||||
| 					if (withDates) prevDate = _items.at(count)->getItem()->date.date(); | ||||
| 					top = _items.at(count)->Get<Overview::Layout::Info>()->top; | ||||
| 					if (!_reversed) { | ||||
| 						top += _items.at(index)->height(); | ||||
| 						top += _items.at(count)->height(); | ||||
| 					} | ||||
| 					++index; | ||||
| 					++count; | ||||
| 					continue; | ||||
| 				} | ||||
| 				if (_items.size() > index + 1 && !_items.at(index)->toMediaItem() && complexMsgId(_items.at(index + 1)->getItem()) == msgid) { // day item
 | ||||
| 					++index; | ||||
| 					if (withDates) prevDate = _items.at(index)->getItem()->date.date(); | ||||
| 					top = _items.at(index)->Get<Overview::Layout::Info>()->top; | ||||
| 				if (_items.size() > count + 1 && !_items.at(count)->toMediaItem() && complexMsgId(_items.at(count + 1)->getItem()) == msgid) { // day item
 | ||||
| 					++count; | ||||
| 					if (withDates) prevDate = _items.at(count)->getItem()->date.date(); | ||||
| 					top = _items.at(count)->Get<Overview::Layout::Info>()->top; | ||||
| 					if (!_reversed) { | ||||
| 						top += _items.at(index)->height(); | ||||
| 						top += _items.at(count)->height(); | ||||
| 					} | ||||
| 					++index; | ||||
| 					++count; | ||||
| 					continue; | ||||
| 				} | ||||
| 				allGood = false; | ||||
| 			} | ||||
| 			HistoryItem *item = App::histItemById(itemChannel(msgid), itemMsgId(msgid)); | ||||
| 			auto item = App::histItemById(itemChannel(msgid), itemMsgId(msgid)); | ||||
| 			auto layout = layoutPrepare(item); | ||||
| 			if (!layout) continue; | ||||
| 
 | ||||
| 			if (withDates) { | ||||
| 				QDate date = item->date.date(); | ||||
| 				if (!index || (index > 0 && (dateEveryMonth ? (date.month() != prevDate.month() || date.year() != prevDate.year()) : (date != prevDate)))) { | ||||
| 					top += setLayoutItem(index, layoutPrepare(date, dateEveryMonth), top); | ||||
| 					++index; | ||||
| 				if (!count || (count > 0 && (dateEveryMonth ? (date.month() != prevDate.month() || date.year() != prevDate.year()) : (date != prevDate)))) { | ||||
| 					top += setLayoutItem(count, layoutPrepare(date, dateEveryMonth), top); | ||||
| 					++count; | ||||
| 					prevDate = date; | ||||
| 				} | ||||
| 			} | ||||
| 			top += setLayoutItem(index, layout, top); | ||||
| 			++index; | ||||
| 			top += setLayoutItem(count, layout, top); | ||||
| 			++count; | ||||
| 		} | ||||
| 		if (_items.size() > index) _items.resize(index); | ||||
| 		if (_items.size() > count) _items.resize(count); | ||||
| 
 | ||||
| 		_height = top; | ||||
| 	} | ||||
|  | @ -2214,9 +2241,9 @@ void OverviewWidget::mediaOverviewUpdated(const Notify::PeerUpdate &update) { | |||
| 	History *m = (update.peer && update.peer->migrateFrom()) ? App::historyLoaded(update.peer->migrateFrom()->id) : 0; | ||||
| 	if (h) { | ||||
| 		for (int32 i = 0; i < OverviewCount; ++i) { | ||||
| 			if (!h->overview[i].isEmpty() || h->overviewCount(i) > 0 || i == type()) { | ||||
| 			if (!h->overview(i).isEmpty() || h->overviewCount(i) > 0 || i == type()) { | ||||
| 				mask |= (1 << i); | ||||
| 			} else if (m && (!m->overview[i].isEmpty() || m->overviewCount(i) > 0)) { | ||||
| 			} else if (m && (!m->overview(i).isEmpty() || m->overviewCount(i) > 0)) { | ||||
| 				mask |= (1 << i); | ||||
| 			} | ||||
| 		} | ||||
|  |  | |||
|  | @ -210,7 +210,7 @@ private: | |||
| 	bool _searchFull = false; | ||||
| 	bool _searchFullMigrated = false; | ||||
| 	mtpRequestId _searchRequest = 0; | ||||
| 	History::MediaOverview _searchResults; | ||||
| 	QList<MsgId> _searchResults; | ||||
| 	MsgId _lastSearchId = 0; | ||||
| 	MsgId _lastSearchMigratedId = 0; | ||||
| 	int _searchedCount = 0; | ||||
|  |  | |||
|  | @ -234,7 +234,7 @@ QKeySequence setShortcut(const QString &keys, const QString &command) { | |||
| 		if (it == DataPtr->commands.cend()) { | ||||
| 			LOG(("Warning: could not find shortcut command handler '%1'").arg(command)); | ||||
| 		} else { | ||||
| 			auto shortcut = std::make_unique<QShortcut>(seq, Messenger::Instance().getGlobalShortcutParent(), nullptr, nullptr, Qt::ApplicationShortcut); | ||||
| 			auto shortcut = std::make_unique<QShortcut>(seq, Messenger::Instance().getActiveWindow(), nullptr, nullptr, Qt::ApplicationShortcut); | ||||
| 			if (!DataPtr->autoRepeatCommands.contains(command)) { | ||||
| 				shortcut->setAutoRepeat(false); | ||||
| 			} | ||||
|  |  | |||
|  | @ -73,8 +73,10 @@ void HistoryDownButton::paintEvent(QPaintEvent *e) { | |||
| } | ||||
| 
 | ||||
| void HistoryDownButton::setUnreadCount(int unreadCount) { | ||||
| 	_unreadCount = unreadCount; | ||||
| 	update(); | ||||
| 	if (_unreadCount != unreadCount) { | ||||
| 		_unreadCount = unreadCount; | ||||
| 		update(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| EmojiButton::EmojiButton(QWidget *parent, const style::IconButton &st) : RippleButton(parent, st.ripple) | ||||
|  |  | |||
|  | @ -1,4 +1,6 @@ | |||
| <(src_loc)/base/build_config.h | ||||
| <(src_loc)/base/flat_map.h | ||||
| <(src_loc)/base/flat_set.h | ||||
| <(src_loc)/base/lambda.h | ||||
| <(src_loc)/base/observer.cpp | ||||
| <(src_loc)/base/observer.h | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 John Preston
						John Preston