mirror of https://github.com/procxx/kepka.git
				
				
				
			Support pinned locally in filters.
This commit is contained in:
		
							parent
							
								
									483d4e5a4e
								
							
						
					
					
						commit
						e27a8fe058
					
				| 
						 | 
				
			
			@ -484,7 +484,9 @@ void ApiWrap::applyUpdates(
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void ApiWrap::savePinnedOrder(Data::Folder *folder) {
 | 
			
		||||
	const auto &order = _session->data().pinnedChatsOrder(folder);
 | 
			
		||||
	const auto &order = _session->data().pinnedChatsOrder(
 | 
			
		||||
		folder,
 | 
			
		||||
		FilterId());
 | 
			
		||||
	const auto input = [](const Dialogs::Key &key) {
 | 
			
		||||
		if (const auto history = key.history()) {
 | 
			
		||||
			return MTP_inputDialogPeer(history->peer->input);
 | 
			
		||||
| 
						 | 
				
			
			@ -513,7 +515,7 @@ void ApiWrap::toggleHistoryArchived(
 | 
			
		|||
	if (const auto already = _historyArchivedRequests.take(history)) {
 | 
			
		||||
		request(already->first).cancel();
 | 
			
		||||
	}
 | 
			
		||||
	const auto isPinned = history->isPinnedDialog();
 | 
			
		||||
	const auto isPinned = history->isPinnedDialog(0);
 | 
			
		||||
	const auto archiveId = Data::Folder::kId;
 | 
			
		||||
	const auto requestId = request(MTPfolders_EditPeerFolders(
 | 
			
		||||
		MTP_vector<MTPInputFolderPeer>(
 | 
			
		||||
| 
						 | 
				
			
			@ -980,7 +982,7 @@ void ApiWrap::requestPinnedDialogs(Data::Folder *folder) {
 | 
			
		|||
		result.match([&](const MTPDmessages_peerDialogs &data) {
 | 
			
		||||
			_session->data().processUsers(data.vusers());
 | 
			
		||||
			_session->data().processChats(data.vchats());
 | 
			
		||||
			_session->data().clearPinnedChats(folder);
 | 
			
		||||
			_session->data().clearPinnedChats(folder, FilterId());
 | 
			
		||||
			_session->data().applyDialogs(
 | 
			
		||||
				folder,
 | 
			
		||||
				data.vmessages().v,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -106,20 +106,24 @@ not_null<FilterChatsPreview*> SetupChatsPreview(
 | 
			
		|||
			data->title(),
 | 
			
		||||
			(data->flags() & ~flag),
 | 
			
		||||
			data->always(),
 | 
			
		||||
			data->pinned(),
 | 
			
		||||
			data->never());
 | 
			
		||||
	}, preview->lifetime());
 | 
			
		||||
 | 
			
		||||
	preview->peerRemoved(
 | 
			
		||||
	) | rpl::start_with_next([=](not_null<History*> history) {
 | 
			
		||||
		auto always = data->always();
 | 
			
		||||
		auto pinned = data->pinned();
 | 
			
		||||
		auto never = data->never();
 | 
			
		||||
		always.remove(history);
 | 
			
		||||
		pinned.erase(ranges::remove(pinned, history), end(pinned));
 | 
			
		||||
		never.remove(history);
 | 
			
		||||
		*data = Data::ChatFilter(
 | 
			
		||||
			data->id(),
 | 
			
		||||
			data->title(),
 | 
			
		||||
			data->flags(),
 | 
			
		||||
			std::move(always),
 | 
			
		||||
			std::move(pinned),
 | 
			
		||||
			std::move(never));
 | 
			
		||||
	}, preview->lifetime());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -272,7 +276,7 @@ void EditExceptions(
 | 
			
		|||
			const auto peers = box->peerListCollectSelectedRows();
 | 
			
		||||
			auto &&histories = ranges::view::all(
 | 
			
		||||
				peers
 | 
			
		||||
			) | ranges::view::transform([=](not_null<PeerData*> peer) {
 | 
			
		||||
				) | ranges::view::transform([=](not_null<PeerData*> peer) {
 | 
			
		||||
				return window->session().data().history(peer);
 | 
			
		||||
			});
 | 
			
		||||
			auto changed = base::flat_set<not_null<History*>>{
 | 
			
		||||
| 
						 | 
				
			
			@ -283,11 +287,17 @@ void EditExceptions(
 | 
			
		|||
			for (const auto &history : changed) {
 | 
			
		||||
				removeFrom.remove(history);
 | 
			
		||||
			}
 | 
			
		||||
			auto pinned = data->pinned();
 | 
			
		||||
			pinned.erase(ranges::remove_if(pinned, [&](not_null<History*> history) {
 | 
			
		||||
				const auto contains = changed.contains(history);
 | 
			
		||||
				return include ? !contains : contains;
 | 
			
		||||
			}), end(pinned));
 | 
			
		||||
			*data = Data::ChatFilter(
 | 
			
		||||
				data->id(),
 | 
			
		||||
				data->title(),
 | 
			
		||||
				(data->flags() & ~options) | rawController->chosenOptions(),
 | 
			
		||||
				include ? std::move(changed) : std::move(removeFrom),
 | 
			
		||||
				std::move(pinned),
 | 
			
		||||
				include ? std::move(removeFrom) : std::move(changed));
 | 
			
		||||
			refresh();
 | 
			
		||||
			box->closeBox();
 | 
			
		||||
| 
						 | 
				
			
			@ -407,6 +417,7 @@ void EditFilterBox(
 | 
			
		|||
			title,
 | 
			
		||||
			data->flags(),
 | 
			
		||||
			data->always(),
 | 
			
		||||
			data->pinned(),
 | 
			
		||||
			data->never());
 | 
			
		||||
		box->closeBox();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -380,7 +380,10 @@ object_ptr<Ui::RpWidget> EditFilterChatsListController::prepareTypesList() {
 | 
			
		|||
		if (_selected & flag) {
 | 
			
		||||
			if (const auto row = delegate->peerListFindRow(TypeId(flag))) {
 | 
			
		||||
				content->changeCheckState(row, true, anim::type::instant);
 | 
			
		||||
				this->delegate()->peerListSetForeignRowChecked(row, true);
 | 
			
		||||
				this->delegate()->peerListSetForeignRowChecked(
 | 
			
		||||
					row,
 | 
			
		||||
					true,
 | 
			
		||||
					anim::type::instant);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -397,7 +400,8 @@ object_ptr<Ui::RpWidget> EditFilterChatsListController::prepareTypesList() {
 | 
			
		|||
	) | rpl::start_with_next([=](RowSelectionChange update) {
 | 
			
		||||
		this->delegate()->peerListSetForeignRowChecked(
 | 
			
		||||
			update.row,
 | 
			
		||||
			update.checked);
 | 
			
		||||
			update.checked,
 | 
			
		||||
			anim::type::normal);
 | 
			
		||||
	}, _lifetime);
 | 
			
		||||
 | 
			
		||||
	_deselectOption = [=](PeerListRowId itemId) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -203,9 +203,10 @@ void PeerListBox::peerListSetRowChecked(
 | 
			
		|||
 | 
			
		||||
void PeerListBox::peerListSetForeignRowChecked(
 | 
			
		||||
		not_null<PeerListRow*> row,
 | 
			
		||||
		bool checked) {
 | 
			
		||||
		bool checked,
 | 
			
		||||
		anim::type animated) {
 | 
			
		||||
	if (checked) {
 | 
			
		||||
		addSelectItem(row, anim::type::normal);
 | 
			
		||||
		addSelectItem(row, animated);
 | 
			
		||||
 | 
			
		||||
		// This call deletes row from _searchRows.
 | 
			
		||||
		_select->entity()->clearQuery();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -271,7 +271,10 @@ public:
 | 
			
		|||
	virtual void peerListConvertRowToSearchResult(not_null<PeerListRow*> row) = 0;
 | 
			
		||||
	virtual bool peerListIsRowChecked(not_null<PeerListRow*> row) = 0;
 | 
			
		||||
	virtual void peerListSetRowChecked(not_null<PeerListRow*> row, bool checked) = 0;
 | 
			
		||||
	virtual void peerListSetForeignRowChecked(not_null<PeerListRow*> row, bool checked) = 0;
 | 
			
		||||
	virtual void peerListSetForeignRowChecked(
 | 
			
		||||
		not_null<PeerListRow*> row,
 | 
			
		||||
		bool checked,
 | 
			
		||||
		anim::type animated) = 0;
 | 
			
		||||
	virtual not_null<PeerListRow*> peerListRowAt(int index) = 0;
 | 
			
		||||
	virtual void peerListRefreshRows() = 0;
 | 
			
		||||
	virtual void peerListScrollToTop() = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -724,7 +727,8 @@ public:
 | 
			
		|||
	}
 | 
			
		||||
	void peerListSetForeignRowChecked(
 | 
			
		||||
		not_null<PeerListRow*> row,
 | 
			
		||||
		bool checked) override {
 | 
			
		||||
		bool checked,
 | 
			
		||||
		anim::type animated) override {
 | 
			
		||||
	}
 | 
			
		||||
	int peerListFullRowsCount() override {
 | 
			
		||||
		return _content->fullRowsCount();
 | 
			
		||||
| 
						 | 
				
			
			@ -816,7 +820,8 @@ public:
 | 
			
		|||
		bool checked) override;
 | 
			
		||||
	void peerListSetForeignRowChecked(
 | 
			
		||||
		not_null<PeerListRow*> row,
 | 
			
		||||
		bool checked) override;
 | 
			
		||||
		bool checked,
 | 
			
		||||
		anim::type animated) override;
 | 
			
		||||
	bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
 | 
			
		||||
	int peerListSelectedRowsCount() override;
 | 
			
		||||
	std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,10 +25,12 @@ ChatFilter::ChatFilter(
 | 
			
		|||
	const QString &title,
 | 
			
		||||
	Flags flags,
 | 
			
		||||
	base::flat_set<not_null<History*>> always,
 | 
			
		||||
	std::vector<not_null<History*>> pinned,
 | 
			
		||||
	base::flat_set<not_null<History*>> never)
 | 
			
		||||
: _id(id)
 | 
			
		||||
, _title(title)
 | 
			
		||||
, _always(std::move(always))
 | 
			
		||||
, _pinned(std::move(pinned))
 | 
			
		||||
, _never(std::move(never))
 | 
			
		||||
, _flags(flags) {
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -66,17 +68,26 @@ ChatFilter ChatFilter::FromTL(
 | 
			
		|||
		}) | ranges::view::transform([](History *history) {
 | 
			
		||||
			return not_null<History*>(history);
 | 
			
		||||
		});
 | 
			
		||||
		auto &&always = ranges::view::all(
 | 
			
		||||
		auto &&always = ranges::view::concat(
 | 
			
		||||
			data.vinclude_peers().v
 | 
			
		||||
		) | to_histories;
 | 
			
		||||
		auto pinned = ranges::view::all(
 | 
			
		||||
			data.vpinned_peers().v
 | 
			
		||||
		) | to_histories | ranges::to_vector;
 | 
			
		||||
		auto &&never = ranges::view::all(
 | 
			
		||||
			data.vexclude_peers().v
 | 
			
		||||
		) | to_histories;
 | 
			
		||||
		auto &&all = ranges::view::concat(always, pinned);
 | 
			
		||||
		auto list = base::flat_set<not_null<History*>>{
 | 
			
		||||
			all.begin(),
 | 
			
		||||
			all.end()
 | 
			
		||||
		};
 | 
			
		||||
		return ChatFilter(
 | 
			
		||||
			data.vid().v,
 | 
			
		||||
			qs(data.vtitle()),
 | 
			
		||||
			flags,
 | 
			
		||||
			{ always.begin(), always.end() },
 | 
			
		||||
			std::move(list),
 | 
			
		||||
			std::move(pinned),
 | 
			
		||||
			{ never.begin(), never.end() });
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -94,10 +105,17 @@ MTPDialogFilter ChatFilter::tl() const {
 | 
			
		|||
		| ((_flags & Flag::NoArchived)
 | 
			
		||||
			? TLFlag::f_exclude_archived
 | 
			
		||||
			: TLFlag(0));
 | 
			
		||||
	auto always = QVector<MTPInputPeer>();
 | 
			
		||||
	always.reserve(_always.size());
 | 
			
		||||
	for (const auto history : _always) {
 | 
			
		||||
		always.push_back(history->peer->input);
 | 
			
		||||
	auto always = _always;
 | 
			
		||||
	auto pinned = QVector<MTPInputPeer>();
 | 
			
		||||
	pinned.reserve(_pinned.size());
 | 
			
		||||
	for (const auto history : _pinned) {
 | 
			
		||||
		pinned.push_back(history->peer->input);
 | 
			
		||||
		always.remove(history);
 | 
			
		||||
	}
 | 
			
		||||
	auto include = QVector<MTPInputPeer>();
 | 
			
		||||
	include.reserve(always.size());
 | 
			
		||||
	for (const auto history : always) {
 | 
			
		||||
		include.push_back(history->peer->input);
 | 
			
		||||
	}
 | 
			
		||||
	auto never = QVector<MTPInputPeer>();
 | 
			
		||||
	never.reserve(_never.size());
 | 
			
		||||
| 
						 | 
				
			
			@ -109,8 +127,8 @@ MTPDialogFilter ChatFilter::tl() const {
 | 
			
		|||
		MTP_int(_id),
 | 
			
		||||
		MTP_string(_title),
 | 
			
		||||
		MTPstring(), // emoticon
 | 
			
		||||
		MTP_vector<MTPInputPeer>(),
 | 
			
		||||
		MTP_vector<MTPInputPeer>(always),
 | 
			
		||||
		MTP_vector<MTPInputPeer>(pinned),
 | 
			
		||||
		MTP_vector<MTPInputPeer>(include),
 | 
			
		||||
		MTP_vector<MTPInputPeer>(never));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -130,6 +148,10 @@ const base::flat_set<not_null<History*>> &ChatFilter::always() const {
 | 
			
		|||
	return _always;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::vector<not_null<History*>> &ChatFilter::pinned() const {
 | 
			
		||||
	return _pinned;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const base::flat_set<not_null<History*>> &ChatFilter::never() const {
 | 
			
		||||
	return _never;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -196,7 +218,7 @@ not_null<Dialogs::MainList*> ChatFilters::chatsList(FilterId filterId) {
 | 
			
		|||
	if (!pointer) {
 | 
			
		||||
		pointer = std::make_unique<Dialogs::MainList>(
 | 
			
		||||
			filterId,
 | 
			
		||||
			rpl::single(1));
 | 
			
		||||
			rpl::single(ChatFilter::kPinnedLimit));
 | 
			
		||||
	}
 | 
			
		||||
	return pointer.get();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -284,7 +306,7 @@ void ChatFilters::applyInsert(ChatFilter filter, int position) {
 | 
			
		|||
 | 
			
		||||
	_list.insert(
 | 
			
		||||
		begin(_list) + position,
 | 
			
		||||
		ChatFilter(filter.id(), {}, {}, {}, {}));
 | 
			
		||||
		ChatFilter(filter.id(), {}, {}, {}, {}, {}));
 | 
			
		||||
	applyChange(*(begin(_list) + position), std::move(filter));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -301,7 +323,7 @@ void ChatFilters::applyRemove(int position) {
 | 
			
		|||
	Expects(position >= 0 && position < _list.size());
 | 
			
		||||
 | 
			
		||||
	const auto i = begin(_list) + position;
 | 
			
		||||
	applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}));
 | 
			
		||||
	applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}));
 | 
			
		||||
	_list.erase(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -309,12 +331,21 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
 | 
			
		|||
	const auto rulesChanged = (filter.flags() != updated.flags())
 | 
			
		||||
		|| (filter.always() != updated.always())
 | 
			
		||||
		|| (filter.never() != updated.never());
 | 
			
		||||
	const auto pinnedChanged = (filter.pinned() != updated.pinned());
 | 
			
		||||
	if (!rulesChanged
 | 
			
		||||
		&& !pinnedChanged
 | 
			
		||||
		&& filter.title() == updated.title()) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	if (rulesChanged) {
 | 
			
		||||
		const auto id = filter.id();
 | 
			
		||||
		const auto filterList = _owner->chatsFilters().chatsList(id);
 | 
			
		||||
		const auto feedHistory = [&](not_null<History*> history) {
 | 
			
		||||
			const auto now = updated.contains(history);
 | 
			
		||||
			const auto was = filter.contains(history);
 | 
			
		||||
			if (now) {
 | 
			
		||||
				history->applyFilterPinnedIndex(id, updated);
 | 
			
		||||
			}
 | 
			
		||||
			if (now != was) {
 | 
			
		||||
				if (now) {
 | 
			
		||||
					history->addToChatList(id, filterList);
 | 
			
		||||
| 
						 | 
				
			
			@ -334,8 +365,10 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
 | 
			
		|||
		if (const auto folder = _owner->folderLoaded(Data::Folder::kId)) {
 | 
			
		||||
			feedList(folder->chatsList());
 | 
			
		||||
		}
 | 
			
		||||
	} else if (filter.title() == updated.title()) {
 | 
			
		||||
		return false;
 | 
			
		||||
	} else if (pinnedChanged) {
 | 
			
		||||
		const auto id = filter.id();
 | 
			
		||||
		const auto filterList = _owner->chatsFilters().chatsList(id);
 | 
			
		||||
		filterList->pinned()->applyList(updated.pinned());
 | 
			
		||||
	}
 | 
			
		||||
	filter = std::move(updated);
 | 
			
		||||
	return true;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,12 +34,15 @@ public:
 | 
			
		|||
	friend constexpr inline bool is_flag_type(Flag) { return true; };
 | 
			
		||||
	using Flags = base::flags<Flag>;
 | 
			
		||||
 | 
			
		||||
	static constexpr int kPinnedLimit = 100;
 | 
			
		||||
 | 
			
		||||
	ChatFilter() = default;
 | 
			
		||||
	ChatFilter(
 | 
			
		||||
		FilterId id,
 | 
			
		||||
		const QString &title,
 | 
			
		||||
		Flags flags,
 | 
			
		||||
		base::flat_set<not_null<History*>> always,
 | 
			
		||||
		std::vector<not_null<History*>> pinned,
 | 
			
		||||
		base::flat_set<not_null<History*>> never);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] static ChatFilter FromTL(
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +54,7 @@ public:
 | 
			
		|||
	[[nodiscard]] QString title() const;
 | 
			
		||||
	[[nodiscard]] Flags flags() const;
 | 
			
		||||
	[[nodiscard]] const base::flat_set<not_null<History*>> &always() const;
 | 
			
		||||
	[[nodiscard]] const std::vector<not_null<History*>> &pinned() const;
 | 
			
		||||
	[[nodiscard]] const base::flat_set<not_null<History*>> &never() const;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool contains(not_null<History*> history) const;
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +63,7 @@ private:
 | 
			
		|||
	FilterId _id = 0;
 | 
			
		||||
	QString _title;
 | 
			
		||||
	base::flat_set<not_null<History*>> _always;
 | 
			
		||||
	std::vector<not_null<History*>> _pinned;
 | 
			
		||||
	base::flat_set<not_null<History*>> _never;
 | 
			
		||||
	Flags _flags;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -347,7 +347,7 @@ void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
 | 
			
		|||
	if (folderId != 0) {
 | 
			
		||||
		LOG(("API Error: Nested folders detected."));
 | 
			
		||||
	}
 | 
			
		||||
	owner().setChatPinned(this, data.is_pinned());
 | 
			
		||||
	owner().setChatPinned(this, FilterId(), data.is_pinned());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// #feed
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -780,7 +780,7 @@ void Session::deleteConversationLocally(not_null<PeerData*> peer) {
 | 
			
		|||
	const auto history = historyLoaded(peer);
 | 
			
		||||
	if (history) {
 | 
			
		||||
		if (history->folderKnown()) {
 | 
			
		||||
			setChatPinned(history, false);
 | 
			
		||||
			setChatPinned(history, FilterId(), false);
 | 
			
		||||
		}
 | 
			
		||||
		App::main()->removeDialog(history);
 | 
			
		||||
		history->clear(peer->isChannel()
 | 
			
		||||
| 
						 | 
				
			
			@ -1459,11 +1459,16 @@ MessageIdsList Session::itemOrItsGroup(not_null<HistoryItem*> item) const {
 | 
			
		|||
	return { 1, item->fullId() };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Session::setChatPinned(const Dialogs::Key &key, bool pinned) {
 | 
			
		||||
void Session::setChatPinned(
 | 
			
		||||
		const Dialogs::Key &key,
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		bool pinned) {
 | 
			
		||||
	Expects(key.entry()->folderKnown());
 | 
			
		||||
 | 
			
		||||
	const auto list = chatsList(key.entry()->folder())->pinned();
 | 
			
		||||
	list->setPinned(key, pinned);
 | 
			
		||||
	const auto list = filterId
 | 
			
		||||
		? chatsFilters().chatsList(filterId)
 | 
			
		||||
		: chatsList(key.entry()->folder());
 | 
			
		||||
	list->pinned()->setPinned(key, pinned);
 | 
			
		||||
	notifyPinnedDialogsOrderUpdated();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1549,32 +1554,53 @@ void Session::applyDialog(
 | 
			
		|||
	setPinnedFromDialog(folder, data.is_pinned());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Session::pinnedChatsCount(Data::Folder *folder) const {
 | 
			
		||||
	return pinnedChatsOrder(folder).size();
 | 
			
		||||
int Session::pinnedChatsCount(
 | 
			
		||||
		Data::Folder *folder,
 | 
			
		||||
		FilterId filterId) const {
 | 
			
		||||
	Expects(!folder || !filterId);
 | 
			
		||||
 | 
			
		||||
	if (!filterId) {
 | 
			
		||||
		return pinnedChatsOrder(folder, filterId).size();
 | 
			
		||||
	}
 | 
			
		||||
	const auto &list = chatsFilters().list();
 | 
			
		||||
	const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
 | 
			
		||||
	return (i != end(list)) ? i->pinned().size() : 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Session::pinnedChatsLimit(Data::Folder *folder) const {
 | 
			
		||||
	return folder
 | 
			
		||||
int Session::pinnedChatsLimit(
 | 
			
		||||
		Data::Folder *folder,
 | 
			
		||||
		FilterId filterId) const {
 | 
			
		||||
	return filterId
 | 
			
		||||
		? Data::ChatFilter::kPinnedLimit
 | 
			
		||||
		: folder
 | 
			
		||||
		? Global::PinnedDialogsInFolderMax()
 | 
			
		||||
		: Global::PinnedDialogsCountMax();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
 | 
			
		||||
		Data::Folder *folder) const {
 | 
			
		||||
	return chatsList(folder)->pinned()->order();
 | 
			
		||||
		Data::Folder *folder,
 | 
			
		||||
		FilterId filterId) const {
 | 
			
		||||
	const auto list = filterId
 | 
			
		||||
		? chatsFilters().chatsList(filterId)
 | 
			
		||||
		: chatsList(folder);
 | 
			
		||||
	return list->pinned()->order();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Session::clearPinnedChats(Data::Folder *folder) {
 | 
			
		||||
void Session::clearPinnedChats(Data::Folder *folder, FilterId filterId) {
 | 
			
		||||
	chatsList(folder)->pinned()->clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Session::reorderTwoPinnedChats(
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		const Dialogs::Key &key1,
 | 
			
		||||
		const Dialogs::Key &key2) {
 | 
			
		||||
	Expects(key1.entry()->folderKnown() && key2.entry()->folderKnown());
 | 
			
		||||
	Expects(key1.entry()->folder() == key2.entry()->folder());
 | 
			
		||||
	Expects(filterId || (key1.entry()->folder() == key2.entry()->folder()));
 | 
			
		||||
 | 
			
		||||
	chatsList(key1.entry()->folder())->pinned()->reorder(key1, key2);
 | 
			
		||||
	const auto list = filterId
 | 
			
		||||
		? chatsFilters().chatsList(filterId)
 | 
			
		||||
		: chatsList(key1.entry()->folder());
 | 
			
		||||
	list->pinned()->reorder(key1, key2);
 | 
			
		||||
	notifyPinnedDialogsOrderUpdated();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3349,6 +3375,7 @@ auto Session::refreshChatListEntry(
 | 
			
		|||
		const auto filterList = chatsFilters().chatsList(id);
 | 
			
		||||
		auto filterResult = RefreshChatListEntryResult();
 | 
			
		||||
		if (filter.contains(history)) {
 | 
			
		||||
			history->applyFilterPinnedIndex(id, filter);
 | 
			
		||||
			filterResult.changed = !entry->inChatList(id);
 | 
			
		||||
			if (filterResult.changed) {
 | 
			
		||||
				entry->addToChatList(id, filterList);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -362,16 +362,21 @@ public:
 | 
			
		|||
		const QVector<MTPDialog> &dialogs,
 | 
			
		||||
		std::optional<int> count = std::nullopt);
 | 
			
		||||
 | 
			
		||||
	int pinnedChatsCount(Data::Folder *folder) const;
 | 
			
		||||
	int pinnedChatsLimit(Data::Folder *folder) const;
 | 
			
		||||
	int pinnedChatsCount(Data::Folder *folder, FilterId filterId) const;
 | 
			
		||||
	int pinnedChatsLimit(Data::Folder *folder, FilterId filterId) const;
 | 
			
		||||
	const std::vector<Dialogs::Key> &pinnedChatsOrder(
 | 
			
		||||
		Data::Folder *folder) const;
 | 
			
		||||
	void setChatPinned(const Dialogs::Key &key, bool pinned);
 | 
			
		||||
	void clearPinnedChats(Data::Folder *folder);
 | 
			
		||||
		Data::Folder *folder,
 | 
			
		||||
		FilterId filterId) const;
 | 
			
		||||
	void setChatPinned(
 | 
			
		||||
		const Dialogs::Key &key,
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		bool pinned);
 | 
			
		||||
	void clearPinnedChats(Data::Folder *folder, FilterId filterId);
 | 
			
		||||
	void applyPinnedChats(
 | 
			
		||||
		Data::Folder *folder,
 | 
			
		||||
		const QVector<MTPDialogPeer> &list);
 | 
			
		||||
	void reorderTwoPinnedChats(
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		const Dialogs::Key &key1,
 | 
			
		||||
		const Dialogs::Key &key2);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,19 +55,34 @@ Main::Session &Entry::session() const {
 | 
			
		|||
	return _owner->session();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Entry::cachePinnedIndex(int index) {
 | 
			
		||||
	if (_pinnedIndex != index) {
 | 
			
		||||
		const auto wasPinned = isPinnedDialog();
 | 
			
		||||
		_pinnedIndex = index;
 | 
			
		||||
		if (session().supportMode()) {
 | 
			
		||||
			// Force reorder in support mode.
 | 
			
		||||
			_sortKeyInChatList = 0;
 | 
			
		||||
		}
 | 
			
		||||
		updateChatListSortPosition();
 | 
			
		||||
		updateChatListEntry();
 | 
			
		||||
		if (wasPinned != isPinnedDialog()) {
 | 
			
		||||
			changedChatListPinHook();
 | 
			
		||||
void Entry::pinnedIndexChanged(int was, int now) {
 | 
			
		||||
	if (session().supportMode()) {
 | 
			
		||||
		// Force reorder in support mode.
 | 
			
		||||
		_sortKeyInChatList = 0;
 | 
			
		||||
	}
 | 
			
		||||
	updateChatListSortPosition();
 | 
			
		||||
	updateChatListEntry();
 | 
			
		||||
	if ((was != 0) != (now != 0)) {
 | 
			
		||||
		changedChatListPinHook();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Entry::cachePinnedIndex(FilterId filterId, int index) {
 | 
			
		||||
	const auto i = _pinnedIndex.find(filterId);
 | 
			
		||||
	const auto was = (i != end(_pinnedIndex)) ? i->second : 0;
 | 
			
		||||
	if (index == was) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (!index) {
 | 
			
		||||
		_pinnedIndex.erase(i);
 | 
			
		||||
		pinnedIndexChanged(was, index);
 | 
			
		||||
	} else {
 | 
			
		||||
		if (!was) {
 | 
			
		||||
			_pinnedIndex.emplace(filterId, index);
 | 
			
		||||
		} else {
 | 
			
		||||
			i->second = index;
 | 
			
		||||
		}
 | 
			
		||||
		pinnedIndexChanged(was, index);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -97,9 +112,7 @@ void Entry::updateChatListSortPosition() {
 | 
			
		|||
	const auto fixedIndex = fixedOnTopIndex();
 | 
			
		||||
	_sortKeyInChatList = fixedIndex
 | 
			
		||||
		? FixedOnTopDialogPos(fixedIndex)
 | 
			
		||||
		: isPinnedDialog()
 | 
			
		||||
		? PinnedDialogPos(_pinnedIndex)
 | 
			
		||||
		: _sortKeyByDate;
 | 
			
		||||
		: computeSortPosition(0);
 | 
			
		||||
	if (needUpdateInChatList()) {
 | 
			
		||||
		setChatListExistence(true);
 | 
			
		||||
	} else {
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +120,23 @@ void Entry::updateChatListSortPosition() {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Entry::lookupPinnedIndex(FilterId filterId) const {
 | 
			
		||||
	if (filterId) {
 | 
			
		||||
		const auto i = _pinnedIndex.find(filterId);
 | 
			
		||||
		return (i != end(_pinnedIndex)) ? i->second : 0;
 | 
			
		||||
	} else if (!_pinnedIndex.empty()) {
 | 
			
		||||
		return _pinnedIndex.front().first
 | 
			
		||||
			? 0
 | 
			
		||||
			: _pinnedIndex.front().second;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64 Entry::computeSortPosition(FilterId filterId) const {
 | 
			
		||||
	const auto index = lookupPinnedIndex(filterId);
 | 
			
		||||
	return index ? PinnedDialogPos(index) : _sortKeyByDate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Entry::updateChatListExistence() {
 | 
			
		||||
	setChatListExistence(shouldBeInChatList());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -205,6 +235,9 @@ void Entry::removeFromChatList(
 | 
			
		|||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	_chatListLinks.erase(i);
 | 
			
		||||
	if (isPinnedDialog(filterId)) {
 | 
			
		||||
		owner().setChatPinned(_key, filterId, false);
 | 
			
		||||
	}
 | 
			
		||||
	list->removeEntry(_key);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,10 +32,9 @@ struct RowsByLetter {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
enum class SortMode {
 | 
			
		||||
	Complex = 0x00,
 | 
			
		||||
	Date    = 0x01,
 | 
			
		||||
	Name    = 0x02,
 | 
			
		||||
	Add     = 0x04,
 | 
			
		||||
	Date    = 0x00,
 | 
			
		||||
	Name    = 0x01,
 | 
			
		||||
	Add     = 0x02,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PositionChange {
 | 
			
		||||
| 
						 | 
				
			
			@ -117,19 +116,18 @@ public:
 | 
			
		|||
		QChar letter,
 | 
			
		||||
		not_null<Row*> row);
 | 
			
		||||
	void updateChatListEntry() const;
 | 
			
		||||
	bool isPinnedDialog() const {
 | 
			
		||||
		return _pinnedIndex > 0;
 | 
			
		||||
	[[nodiscard]] bool isPinnedDialog(FilterId filterId) const {
 | 
			
		||||
		return lookupPinnedIndex(filterId) != 0;
 | 
			
		||||
	}
 | 
			
		||||
	void cachePinnedIndex(int index);
 | 
			
		||||
	void cachePinnedIndex(FilterId filterId, int index);
 | 
			
		||||
	bool isProxyPromoted() const {
 | 
			
		||||
		return _isProxyPromoted;
 | 
			
		||||
	}
 | 
			
		||||
	void cacheProxyPromoted(bool promoted);
 | 
			
		||||
	uint64 sortKeyInChatList() const {
 | 
			
		||||
		return _sortKeyInChatList;
 | 
			
		||||
	}
 | 
			
		||||
	uint64 sortKeyByDate() const {
 | 
			
		||||
		return _sortKeyByDate;
 | 
			
		||||
	[[nodiscard]] uint64 sortKeyInChatList(FilterId filterId) const {
 | 
			
		||||
		return filterId
 | 
			
		||||
			? computeSortPosition(filterId)
 | 
			
		||||
			: _sortKeyInChatList;
 | 
			
		||||
	}
 | 
			
		||||
	void updateChatListSortPosition();
 | 
			
		||||
	void setChatListTimeId(TimeId date);
 | 
			
		||||
| 
						 | 
				
			
			@ -194,8 +192,12 @@ protected:
 | 
			
		|||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] int lookupPinnedIndex(FilterId filterId) const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	virtual void changedChatListPinHook();
 | 
			
		||||
	void pinnedIndexChanged(int was, int now);
 | 
			
		||||
	[[nodiscard]] uint64 computeSortPosition(FilterId filterId) const;
 | 
			
		||||
 | 
			
		||||
	void setChatListExistence(bool exists);
 | 
			
		||||
	RowsByLetter *chatListLinks(FilterId filterId);
 | 
			
		||||
| 
						 | 
				
			
			@ -208,7 +210,7 @@ private:
 | 
			
		|||
	base::flat_map<FilterId, RowsByLetter> _chatListLinks;
 | 
			
		||||
	uint64 _sortKeyInChatList = 0;
 | 
			
		||||
	uint64 _sortKeyByDate = 0;
 | 
			
		||||
	int _pinnedIndex = 0;
 | 
			
		||||
	base::flat_map<FilterId, int> _pinnedIndex;
 | 
			
		||||
	bool _isProxyPromoted = false;
 | 
			
		||||
	TimeId _timeId = 0;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,10 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
 | 
			
		||||
namespace Dialogs {
 | 
			
		||||
 | 
			
		||||
IndexedList::IndexedList(SortMode sortMode)
 | 
			
		||||
IndexedList::IndexedList(SortMode sortMode, FilterId filterId)
 | 
			
		||||
: _sortMode(sortMode)
 | 
			
		||||
, _list(sortMode)
 | 
			
		||||
, _empty(sortMode) {
 | 
			
		||||
, _filterId(filterId)
 | 
			
		||||
, _list(sortMode, filterId)
 | 
			
		||||
, _empty(sortMode, filterId) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RowsByLetter IndexedList::addToEnd(Key key) {
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +29,7 @@ RowsByLetter IndexedList::addToEnd(Key key) {
 | 
			
		|||
	for (const auto ch : key.entry()->chatListFirstLetters()) {
 | 
			
		||||
		auto j = _index.find(ch);
 | 
			
		||||
		if (j == _index.cend()) {
 | 
			
		||||
			j = _index.emplace(ch, _sortMode).first;
 | 
			
		||||
			j = _index.emplace(ch, _sortMode, _filterId).first;
 | 
			
		||||
		}
 | 
			
		||||
		result.letters.emplace(ch, j->second.addToEnd(key));
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +45,7 @@ Row *IndexedList::addByName(Key key) {
 | 
			
		|||
	for (const auto ch : key.entry()->chatListFirstLetters()) {
 | 
			
		||||
		auto j = _index.find(ch);
 | 
			
		||||
		if (j == _index.cend()) {
 | 
			
		||||
			j = _index.emplace(ch, _sortMode).first;
 | 
			
		||||
			j = _index.emplace(ch, _sortMode, _filterId).first;
 | 
			
		||||
		}
 | 
			
		||||
		j->second.addByName(key);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +80,8 @@ void IndexedList::movePinned(Row *row, int deltaSign) {
 | 
			
		|||
		Assert(swapPinnedIndexWith != cbegin());
 | 
			
		||||
		--swapPinnedIndexWith;
 | 
			
		||||
	}
 | 
			
		||||
	Auth().data().reorderTwoPinnedChats(
 | 
			
		||||
	row->key().entry()->owner().reorderTwoPinnedChats(
 | 
			
		||||
		_filterId,
 | 
			
		||||
		row->key(),
 | 
			
		||||
		(*swapPinnedIndexWith)->key());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +89,7 @@ void IndexedList::movePinned(Row *row, int deltaSign) {
 | 
			
		|||
void IndexedList::peerNameChanged(
 | 
			
		||||
		not_null<PeerData*> peer,
 | 
			
		||||
		const base::flat_set<QChar> &oldLetters) {
 | 
			
		||||
	Expects(_sortMode != SortMode::Date && _sortMode != SortMode::Complex);
 | 
			
		||||
	Expects(_sortMode != SortMode::Date);
 | 
			
		||||
 | 
			
		||||
	if (const auto history = peer->owner().historyLoaded(peer)) {
 | 
			
		||||
		if (_sortMode == SortMode::Name) {
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +104,7 @@ void IndexedList::peerNameChanged(
 | 
			
		|||
		FilterId filterId,
 | 
			
		||||
		not_null<PeerData*> peer,
 | 
			
		||||
		const base::flat_set<QChar> &oldLetters) {
 | 
			
		||||
	Expects(_sortMode == SortMode::Date || _sortMode == SortMode::Complex);
 | 
			
		||||
	Expects(_sortMode == SortMode::Date);
 | 
			
		||||
 | 
			
		||||
	if (const auto history = peer->owner().historyLoaded(peer)) {
 | 
			
		||||
		adjustNames(filterId, history, oldLetters);
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +141,7 @@ void IndexedList::adjustByName(
 | 
			
		|||
		for (auto ch : toAdd) {
 | 
			
		||||
			auto j = _index.find(ch);
 | 
			
		||||
			if (j == _index.cend()) {
 | 
			
		||||
				j = _index.emplace(ch, _sortMode).first;
 | 
			
		||||
				j = _index.emplace(ch, _sortMode, _filterId).first;
 | 
			
		||||
			}
 | 
			
		||||
			j->second.addByName(key);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +167,7 @@ void IndexedList::adjustNames(
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for (auto ch : toRemove) {
 | 
			
		||||
		if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) {
 | 
			
		||||
		if (_sortMode == SortMode::Date) {
 | 
			
		||||
			history->removeChatListEntryByLetter(filterId, ch);
 | 
			
		||||
		}
 | 
			
		||||
		if (auto it = _index.find(ch); it != _index.cend()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -175,10 +177,10 @@ void IndexedList::adjustNames(
 | 
			
		|||
	for (auto ch : toAdd) {
 | 
			
		||||
		auto j = _index.find(ch);
 | 
			
		||||
		if (j == _index.cend()) {
 | 
			
		||||
			j = _index.emplace(ch, _sortMode).first;
 | 
			
		||||
			j = _index.emplace(ch, _sortMode, _filterId).first;
 | 
			
		||||
		}
 | 
			
		||||
		auto row = j->second.addToEnd(key);
 | 
			
		||||
		if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) {
 | 
			
		||||
		if (_sortMode == SortMode::Date) {
 | 
			
		||||
			history->addChatListEntryByLetter(filterId, ch, row);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@ namespace Dialogs {
 | 
			
		|||
 | 
			
		||||
class IndexedList {
 | 
			
		||||
public:
 | 
			
		||||
	IndexedList(SortMode sortMode);
 | 
			
		||||
	IndexedList(SortMode sortMode, FilterId filterId = 0);
 | 
			
		||||
 | 
			
		||||
	RowsByLetter addToEnd(Key key);
 | 
			
		||||
	Row *addByName(Key key);
 | 
			
		||||
| 
						 | 
				
			
			@ -81,6 +81,7 @@ private:
 | 
			
		|||
		const base::flat_set<QChar> &oldChars);
 | 
			
		||||
 | 
			
		||||
	SortMode _sortMode = SortMode();
 | 
			
		||||
	FilterId _filterId = 0;
 | 
			
		||||
	List _list, _empty;
 | 
			
		||||
	base::flat_map<QChar, List> _index;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,12 +66,14 @@ int FixedOnTopDialogsCount(not_null<Dialogs::IndexedList*> list) {
 | 
			
		|||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int PinnedDialogsCount(not_null<Dialogs::IndexedList*> list) {
 | 
			
		||||
int PinnedDialogsCount(
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		not_null<Dialogs::IndexedList*> list) {
 | 
			
		||||
	auto result = 0;
 | 
			
		||||
	for (const auto row : *list) {
 | 
			
		||||
		if (row->entry()->fixedOnTopIndex()) {
 | 
			
		||||
			continue;
 | 
			
		||||
		} else if (!row->entry()->isPinnedDialog()) {
 | 
			
		||||
		} else if (!row->entry()->isPinnedDialog(filterId)) {
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		++result;
 | 
			
		||||
| 
						 | 
				
			
			@ -1075,7 +1077,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
 | 
			
		|||
		});
 | 
			
		||||
	}
 | 
			
		||||
	if (anim::Disabled()
 | 
			
		||||
		&& (!_pressed || !_pressed->entry()->isPinnedDialog())) {
 | 
			
		||||
		&& (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) {
 | 
			
		||||
		mousePressReleased(e->globalPos(), e->button());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1091,7 +1093,9 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) {
 | 
			
		|||
	if (updateReorderIndexGetCount() < 2) {
 | 
			
		||||
		_dragging = nullptr;
 | 
			
		||||
	} else {
 | 
			
		||||
		const auto &order = session().data().pinnedChatsOrder(_openedFolder);
 | 
			
		||||
		const auto &order = session().data().pinnedChatsOrder(
 | 
			
		||||
			_openedFolder,
 | 
			
		||||
			_filterId);
 | 
			
		||||
		_pinnedOnDragStart = base::flat_set<Key>{
 | 
			
		||||
			order.begin(),
 | 
			
		||||
			order.end()
 | 
			
		||||
| 
						 | 
				
			
			@ -1103,14 +1107,14 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
int InnerWidget::countPinnedIndex(Row *ofRow) {
 | 
			
		||||
	if (!ofRow || !ofRow->entry()->isPinnedDialog()) {
 | 
			
		||||
	if (!ofRow || !ofRow->entry()->isPinnedDialog(_filterId)) {
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	auto result = 0;
 | 
			
		||||
	for (const auto row : *shownDialogs()) {
 | 
			
		||||
		if (row->entry()->fixedOnTopIndex()) {
 | 
			
		||||
			continue;
 | 
			
		||||
		} else if (!row->entry()->isPinnedDialog()) {
 | 
			
		||||
		} else if (!row->entry()->isPinnedDialog(_filterId)) {
 | 
			
		||||
			break;
 | 
			
		||||
		} else if (row == ofRow) {
 | 
			
		||||
			return result;
 | 
			
		||||
| 
						 | 
				
			
			@ -1121,7 +1125,9 @@ int InnerWidget::countPinnedIndex(Row *ofRow) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void InnerWidget::savePinnedOrder() {
 | 
			
		||||
	const auto &newOrder = session().data().pinnedChatsOrder(_openedFolder);
 | 
			
		||||
	const auto &newOrder = session().data().pinnedChatsOrder(
 | 
			
		||||
		_openedFolder,
 | 
			
		||||
		_filterId);
 | 
			
		||||
	if (newOrder.size() != _pinnedOnDragStart.size()) {
 | 
			
		||||
		return; // Something has changed in the set of pinned chats.
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -1130,7 +1136,11 @@ void InnerWidget::savePinnedOrder() {
 | 
			
		|||
			return; // Something has changed in the set of pinned chats.
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	session().api().savePinnedOrder(_openedFolder);
 | 
			
		||||
	if (_filterId) {
 | 
			
		||||
		// #TODO pinned reorder data and to server
 | 
			
		||||
	} else {
 | 
			
		||||
		session().api().savePinnedOrder(_openedFolder);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void InnerWidget::finishReorderPinned() {
 | 
			
		||||
| 
						 | 
				
			
			@ -1162,7 +1172,7 @@ int InnerWidget::updateReorderIndexGetCount() {
 | 
			
		|||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto count = Dialogs::PinnedDialogsCount(shownDialogs());
 | 
			
		||||
	const auto count = Dialogs::PinnedDialogsCount(_filterId, shownDialogs());
 | 
			
		||||
	Assert(index < count);
 | 
			
		||||
	if (count < 2) {
 | 
			
		||||
		stopReorderPinned();
 | 
			
		||||
| 
						 | 
				
			
			@ -1789,6 +1799,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
 | 
			
		|||
		Window::FillPeerMenu(
 | 
			
		||||
			_controller,
 | 
			
		||||
			history->peer,
 | 
			
		||||
			_filterId,
 | 
			
		||||
			[&](const QString &text, Fn<void()> callback) {
 | 
			
		||||
				return _menu->addAction(text, std::move(callback));
 | 
			
		||||
			},
 | 
			
		||||
| 
						 | 
				
			
			@ -2541,6 +2552,7 @@ void InnerWidget::switchToFilter(FilterId filterId) {
 | 
			
		|||
			&Data::ChatFilter::id)) {
 | 
			
		||||
		filterId = 0;
 | 
			
		||||
	}
 | 
			
		||||
	stopReorderPinned();
 | 
			
		||||
	_filterId = filterId;
 | 
			
		||||
	refreshWithCollapsedRows(true);
 | 
			
		||||
	_collapsedSelected = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -2983,7 +2995,7 @@ void InnerWidget::setupShortcuts() {
 | 
			
		|||
		for (const auto [command, index] : pinned) {
 | 
			
		||||
			request->check(command) && request->handle([=, index = index] {
 | 
			
		||||
				const auto list = session().data().chatsList()->indexed();
 | 
			
		||||
				const auto count = Dialogs::PinnedDialogsCount(list);
 | 
			
		||||
				const auto count = Dialogs::PinnedDialogsCount(_filterId, list);
 | 
			
		||||
				if (index >= count) {
 | 
			
		||||
					return false;
 | 
			
		||||
				}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -218,6 +218,7 @@ void paintRow(
 | 
			
		|||
		not_null<const BasicRow*> row,
 | 
			
		||||
		not_null<Entry*> entry,
 | 
			
		||||
		Dialogs::Key chat,
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		PeerData *from,
 | 
			
		||||
		const HiddenSenderInfo *hiddenSenderInfo,
 | 
			
		||||
		HistoryItem *item,
 | 
			
		||||
| 
						 | 
				
			
			@ -322,7 +323,7 @@ void paintRow(
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		auto availableWidth = namewidth;
 | 
			
		||||
		if (entry->isPinnedDialog() && !entry->fixedOnTopIndex()) {
 | 
			
		||||
		if (entry->isPinnedDialog(filterId) && (filterId || !entry->fixedOnTopIndex())) {
 | 
			
		||||
			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;
 | 
			
		||||
| 
						 | 
				
			
			@ -349,7 +350,7 @@ void paintRow(
 | 
			
		|||
		}
 | 
			
		||||
	} else if (!item) {
 | 
			
		||||
		auto availableWidth = namewidth;
 | 
			
		||||
		if (entry->isPinnedDialog() && !entry->fixedOnTopIndex()) {
 | 
			
		||||
		if (entry->isPinnedDialog(filterId) && (filterId || !entry->fixedOnTopIndex())) {
 | 
			
		||||
			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;
 | 
			
		||||
| 
						 | 
				
			
			@ -366,7 +367,7 @@ void paintRow(
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		paintItemCallback(nameleft, namewidth);
 | 
			
		||||
	} else if (entry->isPinnedDialog() && !entry->fixedOnTopIndex()) {
 | 
			
		||||
	} else if (entry->isPinnedDialog(filterId) && (filterId || !entry->fixedOnTopIndex())) {
 | 
			
		||||
		auto availableWidth = namewidth;
 | 
			
		||||
		auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon));
 | 
			
		||||
		icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth);
 | 
			
		||||
| 
						 | 
				
			
			@ -613,7 +614,7 @@ void paintUnreadCount(
 | 
			
		|||
void RowPainter::paint(
 | 
			
		||||
		Painter &p,
 | 
			
		||||
		not_null<const Row*> row,
 | 
			
		||||
		int filterId,
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		int fullWidth,
 | 
			
		||||
		bool active,
 | 
			
		||||
		bool selected,
 | 
			
		||||
| 
						 | 
				
			
			@ -669,9 +670,8 @@ void RowPainter::paint(
 | 
			
		|||
	const auto displayPinnedIcon = !displayUnreadCounter
 | 
			
		||||
		&& !displayMentionBadge
 | 
			
		||||
		&& !displayUnreadMark
 | 
			
		||||
		&& !filterId
 | 
			
		||||
		&& entry->isPinnedDialog()
 | 
			
		||||
		&& !entry->fixedOnTopIndex();
 | 
			
		||||
		&& entry->isPinnedDialog(filterId)
 | 
			
		||||
		&& (filterId || !entry->fixedOnTopIndex());
 | 
			
		||||
 | 
			
		||||
	const auto from = history
 | 
			
		||||
		? (history->peer->migrateTo()
 | 
			
		||||
| 
						 | 
				
			
			@ -749,6 +749,7 @@ void RowPainter::paint(
 | 
			
		|||
		row,
 | 
			
		||||
		entry,
 | 
			
		||||
		row->key(),
 | 
			
		||||
		filterId,
 | 
			
		||||
		from,
 | 
			
		||||
		nullptr,
 | 
			
		||||
		item,
 | 
			
		||||
| 
						 | 
				
			
			@ -872,6 +873,7 @@ void RowPainter::paint(
 | 
			
		|||
		row,
 | 
			
		||||
		history,
 | 
			
		||||
		history,
 | 
			
		||||
		FilterId(),
 | 
			
		||||
		from,
 | 
			
		||||
		hiddenSenderInfo,
 | 
			
		||||
		item,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,7 @@ public:
 | 
			
		|||
	static void paint(
 | 
			
		||||
		Painter &p,
 | 
			
		||||
		not_null<const Row*> row,
 | 
			
		||||
		int filterId,
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		int fullWidth,
 | 
			
		||||
		bool active,
 | 
			
		||||
		bool selected,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
 | 
			
		||||
namespace Dialogs {
 | 
			
		||||
 | 
			
		||||
List::List(SortMode sortMode) : _sortMode(sortMode) {
 | 
			
		||||
List::List(SortMode sortMode, FilterId filterId)
 | 
			
		||||
: _sortMode(sortMode)
 | 
			
		||||
, _filterId(filterId) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
List::const_iterator List::cfind(Row *value) const {
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +34,7 @@ not_null<Row*> List::addToEnd(Key key) {
 | 
			
		|||
		std::make_unique<Row>(key, _rows.size())
 | 
			
		||||
	).first->second.get();
 | 
			
		||||
	_rows.emplace_back(result);
 | 
			
		||||
	if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) {
 | 
			
		||||
	if (_sortMode == SortMode::Date) {
 | 
			
		||||
		adjustByDate(result);
 | 
			
		||||
	}
 | 
			
		||||
	return result;
 | 
			
		||||
| 
						 | 
				
			
			@ -82,20 +84,20 @@ void List::adjustByName(not_null<Row*> row) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void List::adjustByDate(not_null<Row*> row) {
 | 
			
		||||
	Expects(_sortMode == SortMode::Date || _sortMode == SortMode::Complex);
 | 
			
		||||
	Expects(_sortMode == SortMode::Date);
 | 
			
		||||
 | 
			
		||||
	const auto key = row->sortKey(_sortMode);
 | 
			
		||||
	const auto key = row->sortKey(_filterId);
 | 
			
		||||
	const auto index = row->pos();
 | 
			
		||||
	const auto i = _rows.begin() + index;
 | 
			
		||||
	const auto before = std::find_if(i + 1, _rows.end(), [&](Row *row) {
 | 
			
		||||
		return (row->sortKey(_sortMode) <= key);
 | 
			
		||||
		return (row->sortKey(_filterId) <= key);
 | 
			
		||||
	});
 | 
			
		||||
	if (before != i + 1) {
 | 
			
		||||
		rotate(i, i + 1, before);
 | 
			
		||||
	} else {
 | 
			
		||||
		const auto from = std::make_reverse_iterator(i);
 | 
			
		||||
		const auto after = std::find_if(from, _rows.rend(), [&](Row *row) {
 | 
			
		||||
			return (row->sortKey(_sortMode) >= key);
 | 
			
		||||
			return (row->sortKey(_filterId) >= key);
 | 
			
		||||
		}).base();
 | 
			
		||||
		if (after != i) {
 | 
			
		||||
			rotate(after, i, i + 1);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@ enum class SortMode;
 | 
			
		|||
 | 
			
		||||
class List final {
 | 
			
		||||
public:
 | 
			
		||||
	List(SortMode sortMode);
 | 
			
		||||
	List(SortMode sortMode, FilterId filterId = 0);
 | 
			
		||||
	List(const List &other) = delete;
 | 
			
		||||
	List &operator=(const List &other) = delete;
 | 
			
		||||
	List(List &&other) = default;
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +78,7 @@ private:
 | 
			
		|||
		std::vector<not_null<Row*>>::iterator last);
 | 
			
		||||
 | 
			
		||||
	SortMode _sortMode = SortMode();
 | 
			
		||||
	FilterId _filterId = 0;
 | 
			
		||||
	std::vector<not_null<Row*>> _rows;
 | 
			
		||||
	std::map<Key, std::unique_ptr<Row>> _rowByKey;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,8 +14,8 @@ namespace Dialogs {
 | 
			
		|||
 | 
			
		||||
MainList::MainList(FilterId filterId, rpl::producer<int> pinnedLimit)
 | 
			
		||||
: _filterId(filterId)
 | 
			
		||||
, _all(filterId ? SortMode::Date : SortMode::Complex)
 | 
			
		||||
, _pinned(1) {
 | 
			
		||||
, _all(SortMode::Date, filterId)
 | 
			
		||||
, _pinned(filterId, 1) {
 | 
			
		||||
	_unreadState.known = true;
 | 
			
		||||
 | 
			
		||||
	std::move(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,11 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
 | 
			
		||||
#include "dialogs/dialogs_key.h"
 | 
			
		||||
#include "dialogs/dialogs_entry.h"
 | 
			
		||||
#include "history/history.h"
 | 
			
		||||
#include "data/data_session.h"
 | 
			
		||||
 | 
			
		||||
namespace Dialogs {
 | 
			
		||||
 | 
			
		||||
PinnedList::PinnedList(int limit) : _limit(limit) {
 | 
			
		||||
PinnedList::PinnedList(FilterId filterId, int limit)
 | 
			
		||||
: _filterId(filterId)
 | 
			
		||||
, _limit(limit) {
 | 
			
		||||
	Expects(limit > 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +44,7 @@ int PinnedList::addPinnedGetPosition(const Key &key) {
 | 
			
		|||
	applyLimit(_limit - 1);
 | 
			
		||||
	const auto position = int(_data.size());
 | 
			
		||||
	_data.push_back(key);
 | 
			
		||||
	key.entry()->cachePinnedIndex(position + 1);
 | 
			
		||||
	key.entry()->cachePinnedIndex(_filterId, position + 1);
 | 
			
		||||
	return position;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -54,19 +57,34 @@ void PinnedList::setPinned(const Key &key, bool pinned) {
 | 
			
		|||
			const auto begin = _data.begin();
 | 
			
		||||
			std::rotate(begin, begin + position, begin + position + 1);
 | 
			
		||||
			for (auto i = 0; i != position + 1; ++i) {
 | 
			
		||||
				_data[i].entry()->cachePinnedIndex(i + 1);
 | 
			
		||||
				_data[i].entry()->cachePinnedIndex(_filterId, i + 1);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else if (const auto it = ranges::find(_data, key); it != end(_data)) {
 | 
			
		||||
		const auto index = int(it - begin(_data));
 | 
			
		||||
		_data.erase(it);
 | 
			
		||||
		key.entry()->cachePinnedIndex(0);
 | 
			
		||||
		key.entry()->cachePinnedIndex(_filterId, 0);
 | 
			
		||||
		for (auto i = index, count = int(size(_data)); i != count; ++i) {
 | 
			
		||||
			_data[i].entry()->cachePinnedIndex(i + 1);
 | 
			
		||||
			_data[i].entry()->cachePinnedIndex(_filterId, i + 1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PinnedList::applyFilterPinned(
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		not_null<History*> history,
 | 
			
		||||
		int index) {
 | 
			
		||||
	Expects(index > 0);
 | 
			
		||||
 | 
			
		||||
	history->cachePinnedIndex(filterId, index);
 | 
			
		||||
 | 
			
		||||
	const auto key = Key{ history };
 | 
			
		||||
	if (ranges::find(_data, key) == end(_data)) {
 | 
			
		||||
		_data.push_back(key);
 | 
			
		||||
		// #TODO pinned
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PinnedList::applyLimit(int limit) {
 | 
			
		||||
	Expects(limit >= 0);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -83,18 +101,32 @@ void PinnedList::applyList(
 | 
			
		|||
		not_null<Data::Session*> owner,
 | 
			
		||||
		const QVector<MTPDialogPeer> &list) {
 | 
			
		||||
	clear();
 | 
			
		||||
	for (const auto &peer : ranges::view::reverse(list)) {
 | 
			
		||||
	for (const auto &peer : list) {
 | 
			
		||||
		peer.match([&](const MTPDdialogPeer &data) {
 | 
			
		||||
			if (const auto peerId = peerFromMTP(data.vpeer())) {
 | 
			
		||||
				setPinned(owner->history(peerId), true);
 | 
			
		||||
				addPinned(owner->history(peerId));
 | 
			
		||||
			}
 | 
			
		||||
		}, [&](const MTPDdialogPeerFolder &data) {
 | 
			
		||||
			const auto folderId = data.vfolder_id().v;
 | 
			
		||||
			setPinned(owner->folder(folderId), true);
 | 
			
		||||
			addPinned(owner->folder(data.vfolder_id().v));
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PinnedList::applyList(const std::vector<not_null<History*>> &list) {
 | 
			
		||||
	Expects(_filterId != 0);
 | 
			
		||||
 | 
			
		||||
	clear();
 | 
			
		||||
	const auto count = int(list.size());
 | 
			
		||||
	_data.reserve(count);
 | 
			
		||||
	for (auto i = 0; i != count; ++i) {
 | 
			
		||||
		const auto history = list[i];
 | 
			
		||||
		if (history->inChatList()) {
 | 
			
		||||
			_data.emplace_back(history);
 | 
			
		||||
			history->cachePinnedIndex(_filterId, i + 1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PinnedList::reorder(const Key &key1, const Key &key2) {
 | 
			
		||||
	const auto index1 = ranges::find(_data, key1) - begin(_data);
 | 
			
		||||
	const auto index2 = ranges::find(_data, key2) - begin(_data);
 | 
			
		||||
| 
						 | 
				
			
			@ -102,8 +134,8 @@ void PinnedList::reorder(const Key &key1, const Key &key2) {
 | 
			
		|||
	Assert(index2 >= 0 && index2 < _data.size());
 | 
			
		||||
	Assert(index1 != index2);
 | 
			
		||||
	std::swap(_data[index1], _data[index2]);
 | 
			
		||||
	key1.entry()->cachePinnedIndex(index2 + 1);
 | 
			
		||||
	key2.entry()->cachePinnedIndex(index1 + 1);
 | 
			
		||||
	key1.entry()->cachePinnedIndex(_filterId, index2 + 1);
 | 
			
		||||
	key2.entry()->cachePinnedIndex(_filterId, index1 + 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Dialogs
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
*/
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
class History;
 | 
			
		||||
 | 
			
		||||
namespace Data {
 | 
			
		||||
class Session;
 | 
			
		||||
} // namespace Data
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +19,7 @@ class Key;
 | 
			
		|||
 | 
			
		||||
class PinnedList final {
 | 
			
		||||
public:
 | 
			
		||||
	explicit PinnedList(int limit);
 | 
			
		||||
	PinnedList(FilterId filterId, int limit);
 | 
			
		||||
 | 
			
		||||
	void setLimit(int limit);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,11 +30,17 @@ public:
 | 
			
		|||
	// if (pinned) places on the first place in the list.
 | 
			
		||||
	void setPinned(const Key &key, bool pinned);
 | 
			
		||||
 | 
			
		||||
	void applyFilterPinned(
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		not_null<History*> history,
 | 
			
		||||
		int index);
 | 
			
		||||
 | 
			
		||||
	void clear();
 | 
			
		||||
 | 
			
		||||
	void applyList(
 | 
			
		||||
		not_null<Data::Session*> owner,
 | 
			
		||||
		const QVector<MTPDialogPeer> &list);
 | 
			
		||||
	void applyList(const std::vector<not_null<History*>> &list);
 | 
			
		||||
	void reorder(const Key &key1, const Key &key2);
 | 
			
		||||
 | 
			
		||||
	const std::vector<Key> &order() const {
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +51,7 @@ private:
 | 
			
		|||
	int addPinnedGetPosition(const Key &key);
 | 
			
		||||
	void applyLimit(int limit);
 | 
			
		||||
 | 
			
		||||
	FilterId _filterId = 0;
 | 
			
		||||
	int _limit = 0;
 | 
			
		||||
	std::vector<Key> _data;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -220,10 +220,8 @@ Row::Row(Key key, int pos) : _id(key), _pos(pos) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64 Row::sortKey(SortMode mode) const {
 | 
			
		||||
	return (mode == SortMode::Complex)
 | 
			
		||||
		? _id.entry()->sortKeyInChatList()
 | 
			
		||||
		: _id.entry()->sortKeyByDate();
 | 
			
		||||
uint64 Row::sortKey(FilterId filterId) const {
 | 
			
		||||
	return _id.entry()->sortKeyInChatList(filterId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Row::validateListEntryCache() const {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -90,7 +90,7 @@ public:
 | 
			
		|||
	int pos() const {
 | 
			
		||||
		return _pos;
 | 
			
		||||
	}
 | 
			
		||||
	uint64 sortKey(SortMode mode) const;
 | 
			
		||||
	uint64 sortKey(FilterId filterId) const;
 | 
			
		||||
 | 
			
		||||
	void validateListEntryCache() const;
 | 
			
		||||
	const Ui::Text::String &listEntryCache() const {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1921,9 +1921,6 @@ void History::setFolderPointer(Data::Folder *folder) {
 | 
			
		|||
	if (_folder == folder) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (isPinnedDialog()) {
 | 
			
		||||
		owner().setChatPinned(this, false);
 | 
			
		||||
	}
 | 
			
		||||
	auto &filters = owner().chatsFilters();
 | 
			
		||||
	const auto wasKnown = folderKnown();
 | 
			
		||||
	const auto wasInList = inChatList();
 | 
			
		||||
| 
						 | 
				
			
			@ -1946,6 +1943,7 @@ void History::setFolderPointer(Data::Folder *folder) {
 | 
			
		|||
		for (const auto &filter : filters.list()) {
 | 
			
		||||
			if (filter.contains(this)) {
 | 
			
		||||
				const auto id = filter.id();
 | 
			
		||||
				applyFilterPinnedIndex(id, filter);
 | 
			
		||||
				addToChatList(id, filters.chatsList(id));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -1959,6 +1957,24 @@ void History::setFolderPointer(Data::Folder *folder) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void History::applyFilterPinnedIndex(
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		const Data::ChatFilter &filter) {
 | 
			
		||||
	const auto &pinned = filter.pinned();
 | 
			
		||||
	const auto i = ranges::find(pinned, this);
 | 
			
		||||
	if (i == end(pinned)) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	const auto index = (i - begin(pinned)) + 1;
 | 
			
		||||
	if (index == lookupPinnedIndex(filterId)) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	owner().chatsFilters().chatsList(filterId)->pinned()->applyFilterPinned(
 | 
			
		||||
		filterId,
 | 
			
		||||
		this,
 | 
			
		||||
		index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
 | 
			
		||||
	const auto folderId = data.vfolder_id().value_or_empty();
 | 
			
		||||
	if (!folderKnown()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1968,7 +1984,7 @@ void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
 | 
			
		|||
			clearFolder();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	owner().setChatPinned(this, data.is_pinned());
 | 
			
		||||
	owner().setChatPinned(this, FilterId(), data.is_pinned());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TimeId History::adjustedChatListTimeId() const {
 | 
			
		||||
| 
						 | 
				
			
			@ -2576,7 +2592,7 @@ bool History::useProxyPromotion() const {
 | 
			
		|||
	if (!isProxyPromoted()) {
 | 
			
		||||
		return false;
 | 
			
		||||
	} else if (const auto channel = peer->asChannel()) {
 | 
			
		||||
		return !isPinnedDialog() && !channel->amIn();
 | 
			
		||||
		return !isPinnedDialog(FilterId()) && !channel->amIn();
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2595,7 +2611,7 @@ bool History::trackUnreadMessages() const {
 | 
			
		|||
bool History::shouldBeInChatList() const {
 | 
			
		||||
	if (peer->migrateTo() || !folderKnown()) {
 | 
			
		||||
		return false;
 | 
			
		||||
	} else if (isPinnedDialog()) {
 | 
			
		||||
	} else if (isPinnedDialog(FilterId())) {
 | 
			
		||||
		return true;
 | 
			
		||||
	} else if (const auto channel = peer->asChannel()) {
 | 
			
		||||
		if (!channel->amIn()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,7 @@ namespace Data {
 | 
			
		|||
struct Draft;
 | 
			
		||||
class Session;
 | 
			
		||||
class Folder;
 | 
			
		||||
class ChatFilter;
 | 
			
		||||
} // namespace Data
 | 
			
		||||
 | 
			
		||||
namespace Dialogs {
 | 
			
		||||
| 
						 | 
				
			
			@ -368,6 +369,10 @@ public:
 | 
			
		|||
		HistoryItem *folderDialogItem = nullptr);
 | 
			
		||||
	void clearFolder();
 | 
			
		||||
 | 
			
		||||
	void applyFilterPinnedIndex(
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		const Data::ChatFilter &filter);
 | 
			
		||||
 | 
			
		||||
	// Interface for Data::Histories.
 | 
			
		||||
	void setInboxReadTill(MsgId upTo);
 | 
			
		||||
	std::optional<int> countStillUnreadLocal(MsgId readTillId) const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -226,6 +226,7 @@ void TopBarWidget::showMenu() {
 | 
			
		|||
		Window::FillPeerMenu(
 | 
			
		||||
			_controller,
 | 
			
		||||
			peer,
 | 
			
		||||
			FilterId(),
 | 
			
		||||
			addAction,
 | 
			
		||||
			Window::PeerMenuSource::History);
 | 
			
		||||
	} else if (const auto folder = _activeChat.folder()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -574,6 +574,7 @@ void WrapWidget::showTopBarMenu() {
 | 
			
		|||
		Window::FillPeerMenu(
 | 
			
		||||
			_controller->parentController(),
 | 
			
		||||
			peer,
 | 
			
		||||
			FilterId(),
 | 
			
		||||
			addAction,
 | 
			
		||||
			Window::PeerMenuSource::Profile);
 | 
			
		||||
	//} else if (const auto feed = key().feed()) { // #feed
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,6 +64,7 @@ public:
 | 
			
		|||
	Filler(
 | 
			
		||||
		not_null<SessionController*> controller,
 | 
			
		||||
		not_null<PeerData*> peer,
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		const PeerMenuCallback &addAction,
 | 
			
		||||
		PeerMenuSource source);
 | 
			
		||||
	void fill();
 | 
			
		||||
| 
						 | 
				
			
			@ -84,6 +85,7 @@ private:
 | 
			
		|||
 | 
			
		||||
	not_null<SessionController*> _controller;
 | 
			
		||||
	not_null<PeerData*> _peer;
 | 
			
		||||
	FilterId _filterId = 0;
 | 
			
		||||
	const PeerMenuCallback &_addAction;
 | 
			
		||||
	PeerMenuSource _source;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +117,7 @@ private:
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
History *FindWastedPin(not_null<Data::Session*> data, Data::Folder *folder) {
 | 
			
		||||
	const auto &order = data->pinnedChatsOrder(folder);
 | 
			
		||||
	const auto &order = data->pinnedChatsOrder(folder, FilterId());
 | 
			
		||||
	for (const auto &pinned : order) {
 | 
			
		||||
		if (const auto history = pinned.history()) {
 | 
			
		||||
			if (history->peer->isChat()
 | 
			
		||||
| 
						 | 
				
			
			@ -134,24 +136,25 @@ void AddChatMembers(
 | 
			
		|||
	AddParticipantsBoxController::Start(navigation, chat);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PinnedLimitReached(Dialogs::Key key) {
 | 
			
		||||
bool PinnedLimitReached(Dialogs::Key key, FilterId filterId) {
 | 
			
		||||
	Expects(key.entry()->folderKnown());
 | 
			
		||||
 | 
			
		||||
	const auto entry = key.entry();
 | 
			
		||||
	const auto owner = &entry->owner();
 | 
			
		||||
	const auto folder = entry->folder();
 | 
			
		||||
	const auto pinnedCount = owner->pinnedChatsCount(folder);
 | 
			
		||||
	const auto pinnedMax = owner->pinnedChatsLimit(folder);
 | 
			
		||||
	const auto pinnedCount = owner->pinnedChatsCount(folder, filterId);
 | 
			
		||||
	const auto pinnedMax = owner->pinnedChatsLimit(folder, filterId);
 | 
			
		||||
	if (pinnedCount < pinnedMax) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	// Some old chat, that was converted, maybe is still pinned.
 | 
			
		||||
	if (const auto wasted = FindWastedPin(owner, folder)) {
 | 
			
		||||
		owner->setChatPinned(wasted, false);
 | 
			
		||||
		owner->setChatPinned(key, true);
 | 
			
		||||
	const auto wasted = filterId ? nullptr : FindWastedPin(owner, folder);
 | 
			
		||||
	if (wasted) {
 | 
			
		||||
		owner->setChatPinned(wasted, FilterId(), false);
 | 
			
		||||
		owner->setChatPinned(key, FilterId(), true);
 | 
			
		||||
		entry->session().api().savePinnedOrder(folder);
 | 
			
		||||
	} else {
 | 
			
		||||
		auto errorText = tr::lng_error_pinned_max(
 | 
			
		||||
		const auto errorText = tr::lng_error_pinned_max(
 | 
			
		||||
			tr::now,
 | 
			
		||||
			lt_count,
 | 
			
		||||
			pinnedMax);
 | 
			
		||||
| 
						 | 
				
			
			@ -165,12 +168,12 @@ void TogglePinnedDialog(Dialogs::Key key) {
 | 
			
		|||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	const auto owner = &key.entry()->owner();
 | 
			
		||||
	const auto isPinned = !key.entry()->isPinnedDialog();
 | 
			
		||||
	if (isPinned && PinnedLimitReached(key)) {
 | 
			
		||||
	const auto isPinned = !key.entry()->isPinnedDialog(0);
 | 
			
		||||
	if (isPinned && PinnedLimitReached(key, 0)) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	owner->setChatPinned(key, isPinned);
 | 
			
		||||
	owner->setChatPinned(key, FilterId(), isPinned);
 | 
			
		||||
	const auto flags = isPinned
 | 
			
		||||
		? MTPmessages_ToggleDialogPin::Flag::f_pinned
 | 
			
		||||
		: MTPmessages_ToggleDialogPin::Flag(0);
 | 
			
		||||
| 
						 | 
				
			
			@ -194,13 +197,50 @@ void TogglePinnedDialog(Dialogs::Key key) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TogglePinnedDialog(Dialogs::Key key, FilterId filterId) {
 | 
			
		||||
	if (!filterId) {
 | 
			
		||||
		return TogglePinnedDialog(key);
 | 
			
		||||
	}
 | 
			
		||||
	const auto owner = &key.entry()->owner();
 | 
			
		||||
	const auto isPinned = !key.entry()->isPinnedDialog(filterId);
 | 
			
		||||
	if (isPinned && PinnedLimitReached(key, filterId)) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	owner->setChatPinned(key, filterId, isPinned);
 | 
			
		||||
	// #TODO pinned save data and to server
 | 
			
		||||
	//const auto flags = isPinned
 | 
			
		||||
	//	? MTPmessages_ToggleDialogPin::Flag::f_pinned
 | 
			
		||||
	//	: MTPmessages_ToggleDialogPin::Flag(0);
 | 
			
		||||
	//if (const auto history = key.history()) {
 | 
			
		||||
	//	history->session().api().request(MTPmessages_ToggleDialogPin(
 | 
			
		||||
	//		MTP_flags(flags),
 | 
			
		||||
	//		MTP_inputDialogPeer(key.history()->peer->input)
 | 
			
		||||
	//	)).done([=](const MTPBool &result) {
 | 
			
		||||
	//		owner->notifyPinnedDialogsOrderUpdated();
 | 
			
		||||
	//	}).send();
 | 
			
		||||
	//} else if (const auto folder = key.folder()) {
 | 
			
		||||
	//	folder->session().api().request(MTPmessages_ToggleDialogPin(
 | 
			
		||||
	//		MTP_flags(flags),
 | 
			
		||||
	//		MTP_inputDialogPeerFolder(MTP_int(folder->id()))
 | 
			
		||||
	//	)).send();
 | 
			
		||||
	//}
 | 
			
		||||
	if (isPinned) {
 | 
			
		||||
		if (const auto main = App::main()) {
 | 
			
		||||
			main->dialogsToUp();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Filler::Filler(
 | 
			
		||||
	not_null<SessionController*> controller,
 | 
			
		||||
	not_null<PeerData*> peer,
 | 
			
		||||
	FilterId filterId,
 | 
			
		||||
	const PeerMenuCallback &addAction,
 | 
			
		||||
	PeerMenuSource source)
 | 
			
		||||
: _controller(controller)
 | 
			
		||||
, _peer(peer)
 | 
			
		||||
, _filterId(filterId)
 | 
			
		||||
, _addAction(addAction)
 | 
			
		||||
, _source(source) {
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -242,27 +282,29 @@ bool Filler::showTogglePin() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void Filler::addTogglePin() {
 | 
			
		||||
	auto peer = _peer;
 | 
			
		||||
	const auto filterId = _filterId;
 | 
			
		||||
	const auto peer = _peer;
 | 
			
		||||
	auto isPinned = false;
 | 
			
		||||
	if (auto history = peer->owner().historyLoaded(peer)) {
 | 
			
		||||
		isPinned = history->isPinnedDialog();
 | 
			
		||||
	if (const auto history = peer->owner().historyLoaded(peer)) {
 | 
			
		||||
		isPinned = history->isPinnedDialog(filterId);
 | 
			
		||||
	}
 | 
			
		||||
	auto pinText = [](bool isPinned) {
 | 
			
		||||
	const auto pinText = [](bool isPinned) {
 | 
			
		||||
		return isPinned
 | 
			
		||||
			? tr::lng_context_unpin_from_top(tr::now)
 | 
			
		||||
			: tr::lng_context_pin_to_top(tr::now);
 | 
			
		||||
	};
 | 
			
		||||
	auto pinToggle = [=] {
 | 
			
		||||
		TogglePinnedDialog(peer->owner().history(peer));
 | 
			
		||||
	const auto pinToggle = [=] {
 | 
			
		||||
		TogglePinnedDialog(peer->owner().history(peer), filterId);
 | 
			
		||||
	};
 | 
			
		||||
	auto pinAction = _addAction(pinText(isPinned), pinToggle);
 | 
			
		||||
	const auto pinAction = _addAction(pinText(isPinned), pinToggle);
 | 
			
		||||
 | 
			
		||||
	const auto lifetime = Ui::CreateChild<rpl::lifetime>(pinAction);
 | 
			
		||||
	Notify::PeerUpdateViewer(
 | 
			
		||||
		peer,
 | 
			
		||||
		Notify::PeerUpdate::Flag::ChatPinnedChanged
 | 
			
		||||
	) | rpl::start_with_next([peer, pinAction, pinText] {
 | 
			
		||||
		auto isPinned = peer->owner().history(peer)->isPinnedDialog();
 | 
			
		||||
	) | rpl::start_with_next([=] {
 | 
			
		||||
		const auto history = peer->owner().history(peer);
 | 
			
		||||
		const auto isPinned = history->isPinnedDialog(filterId);
 | 
			
		||||
		pinAction->setText(pinText(isPinned));
 | 
			
		||||
	}, *lifetime);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1041,9 +1083,10 @@ Fn<void()> DeleteAndLeaveHandler(not_null<PeerData*> peer) {
 | 
			
		|||
void FillPeerMenu(
 | 
			
		||||
		not_null<SessionController*> controller,
 | 
			
		||||
		not_null<PeerData*> peer,
 | 
			
		||||
		FilterId filterId,
 | 
			
		||||
		const PeerMenuCallback &callback,
 | 
			
		||||
		PeerMenuSource source) {
 | 
			
		||||
	Filler filler(controller, peer, callback, source);
 | 
			
		||||
	Filler filler(controller, peer, filterId, callback, source);
 | 
			
		||||
	filler.fill();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,7 @@ using PeerMenuCallback = Fn<QAction*(
 | 
			
		|||
void FillPeerMenu(
 | 
			
		||||
	not_null<SessionController*> controller,
 | 
			
		||||
	not_null<PeerData*> peer,
 | 
			
		||||
	FilterId filterId,
 | 
			
		||||
	const PeerMenuCallback &addAction,
 | 
			
		||||
	PeerMenuSource source);
 | 
			
		||||
void FillFolderMenu(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue