458 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			458 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop application for the Telegram messaging service.
 | |
| 
 | |
| For license and copyright information please follow this link:
 | |
| https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | |
| */
 | |
| #include "info/feed/info_feed_channels_controllers.h"
 | |
| 
 | |
| #include "data/data_feed.h"
 | |
| #include "data/data_session.h"
 | |
| #include "data/data_channel.h"
 | |
| #include "info/info_controller.h"
 | |
| #include "lang/lang_keys.h"
 | |
| #include "history/history.h"
 | |
| #include "window/window_peer_menu.h"
 | |
| #include "ui/widgets/popup_menu.h"
 | |
| #include "ui/toast/toast.h"
 | |
| #include "auth_session.h"
 | |
| #include "mainwidget.h"
 | |
| #include "apiwrap.h"
 | |
| #include "styles/style_widgets.h"
 | |
| #include "styles/style_info.h"
 | |
| #include "styles/style_boxes.h"
 | |
| 
 | |
| namespace Info {
 | |
| namespace FeedProfile {
 | |
| namespace {
 | |
| 
 | |
| constexpr auto kChannelsInFeedMin = 4;
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| class ChannelsController::Row final : public PeerListRow {
 | |
| public:
 | |
| 	Row(not_null<History*> history);
 | |
| 
 | |
| 	QSize actionSize() const override;
 | |
| 	QMargins actionMargins() const override;
 | |
| 	void paintAction(
 | |
| 		Painter &p,
 | |
| 		int x,
 | |
| 		int y,
 | |
| 		int outerWidth,
 | |
| 		bool selected,
 | |
| 		bool actionSelected) override;
 | |
| 
 | |
| 	not_null<History*> history() const {
 | |
| 		return _history;
 | |
| 	}
 | |
| 
 | |
| private:
 | |
| 	not_null<History*> _history;
 | |
| 
 | |
| };
 | |
| 
 | |
| ChannelsController::Row::Row(not_null<History*> history)
 | |
| : PeerListRow(history->peer)
 | |
| , _history(history) {
 | |
| }
 | |
| 
 | |
| QSize ChannelsController::Row::actionSize() const {
 | |
| 	return QRect(
 | |
| 			QPoint(),
 | |
| 			st::smallCloseIcon.size()).marginsAdded(
 | |
| 				st::infoFeedLeaveIconMargins).size();
 | |
| }
 | |
| 
 | |
| QMargins ChannelsController::Row::actionMargins() const {
 | |
| 	return QMargins(
 | |
| 		0,
 | |
| 		(st::infoCommonGroupsList.item.height - actionSize().height()) / 2,
 | |
| 		0,
 | |
| 		0);
 | |
| }
 | |
| 
 | |
| void ChannelsController::Row::paintAction(
 | |
| 		Painter &p,
 | |
| 		int x,
 | |
| 		int y,
 | |
| 		int outerWidth,
 | |
| 		bool selected,
 | |
| 		bool actionSelected) {
 | |
| 	if (selected) {
 | |
| 		x += st::infoFeedLeaveIconMargins.left();
 | |
| 		y += st::infoFeedLeaveIconMargins.top();
 | |
| 		(actionSelected
 | |
| 			? st::smallCloseIconOver
 | |
| 			: st::smallCloseIcon).paint(p, x, y, outerWidth);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| ChannelsController::ChannelsController(not_null<Controller*> controller)
 | |
| : PeerListController()
 | |
| , _controller(controller)
 | |
| , _feed(_controller->key().feed()) {
 | |
| 	if (!_feed->channelsLoaded()) {
 | |
| //		Auth().api().requestFeedChannels(_feed); // #feed
 | |
| 	}
 | |
| 	_controller->setSearchEnabledByContent(false);
 | |
| }
 | |
| 
 | |
| auto ChannelsController::createRow(not_null<History*> history)
 | |
| -> std::unique_ptr<Row> {
 | |
| 	auto result = std::make_unique<Row>(history);
 | |
| 	result->setCustomStatus(QString());
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| std::unique_ptr<PeerListRow> ChannelsController::createRestoredRow(
 | |
| 		not_null<PeerData*> peer) {
 | |
| 	return createRow(peer->owner().history(peer));
 | |
| }
 | |
| 
 | |
| void ChannelsController::prepare() {
 | |
| 	setSearchNoResultsText(lang(lng_feed_channels_not_found));
 | |
| 	delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
 | |
| 	delegate()->peerListSetTitle(langFactory(lng_info_feed_channels));
 | |
| 
 | |
| 	rebuildRows();
 | |
| 	using Flag = Data::FeedUpdateFlag;
 | |
| 	Auth().data().feedUpdated(
 | |
| 	) | rpl::filter([=](const Data::FeedUpdate &update) {
 | |
| 		return (update.feed == _feed) && (update.flag == Flag::Channels);
 | |
| 	}) | rpl::filter([=] {
 | |
| 		return _feed->channelsLoaded();
 | |
| 	}) | rpl::start_with_next([=] {
 | |
| 		rebuildRows();
 | |
| 	}, lifetime());
 | |
| }
 | |
| 
 | |
| void ChannelsController::rebuildRows() {
 | |
| 	if (!_feed->channelsLoaded()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto &channels = _feed->channels();
 | |
| 	auto count = delegate()->peerListFullRowsCount();
 | |
| 	for (auto i = 0; i != count;) {
 | |
| 		const auto row = delegate()->peerListRowAt(i);
 | |
| 		const auto peer = row->peer();
 | |
| 		if (ranges::find_if(channels, [=](not_null<History*> history) {
 | |
| 			return (history->peer == peer);
 | |
| 		}) != end(channels)) {
 | |
| 			++i;
 | |
| 		} else {
 | |
| 			delegate()->peerListRemoveRow(row);
 | |
| 			--count;
 | |
| 		}
 | |
| 	}
 | |
| 	for (const auto history : channels) {
 | |
| 		if (auto row = createRow(history)) {
 | |
| 			delegate()->peerListAppendRow(std::move(row));
 | |
| 		}
 | |
| 	}
 | |
| 	delegate()->peerListRefreshRows();
 | |
| }
 | |
| 
 | |
| std::unique_ptr<PeerListState> ChannelsController::saveState() const {
 | |
| 	auto result = PeerListController::saveState();
 | |
| 	auto my = std::make_unique<SavedState>();
 | |
| 	using Flag = Data::FeedUpdateFlag;
 | |
| 
 | |
| 	// Must not capture `this` here, because it dies before my->lifetime.
 | |
| 	Auth().data().feedUpdated(
 | |
| 	) | rpl::filter([feed = _feed](const Data::FeedUpdate &update) {
 | |
| 		return (update.feed == feed) && (update.flag == Flag::Channels);
 | |
| 	}) | rpl::start_with_next([state = result.get()] {
 | |
| 		state->controllerState = nullptr;
 | |
| 	}, my->lifetime);
 | |
| 	result->controllerState = std::move(my);
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void ChannelsController::restoreState(
 | |
| 		std::unique_ptr<PeerListState> state) {
 | |
| 	PeerListController::restoreState(std::move(state));
 | |
| }
 | |
| 
 | |
| void ChannelsController::rowClicked(not_null<PeerListRow*> row) {
 | |
| 	_controller->parentController()->showPeerHistory(
 | |
| 		row->peer(),
 | |
| 		Window::SectionShow::Way::Forward);
 | |
| }
 | |
| 
 | |
| void ChannelsController::rowActionClicked(not_null<PeerListRow*> row) {
 | |
| 	Window::DeleteAndLeaveHandler(row->peer())();
 | |
| }
 | |
| 
 | |
| base::unique_qptr<Ui::PopupMenu> ChannelsController::rowContextMenu(
 | |
| 		QWidget *parent,
 | |
| 		not_null<PeerListRow*> row) {
 | |
| 	auto my = static_cast<Row*>(row.get());
 | |
| 	auto channel = my->history()->peer->asChannel();
 | |
| 
 | |
| 	auto result = base::make_unique_q<Ui::PopupMenu>(parent);
 | |
| 	Window::PeerMenuAddMuteAction(channel, [&](
 | |
| 			const QString &text,
 | |
| 			Fn<void()> handler) {
 | |
| 		return result->addAction(text, handler);
 | |
| 	});
 | |
| 	//result->addAction( // #feed
 | |
| 	//	lang(lng_feed_ungroup),
 | |
| 	//	[=] { Window::ToggleChannelGrouping(channel, false); });
 | |
| 
 | |
| 	result->addAction(
 | |
| 		lang(lng_profile_leave_channel),
 | |
| 		Window::DeleteAndLeaveHandler(channel));
 | |
| 
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void NotificationsController::Start(not_null<Data::Feed*> feed) {
 | |
| 	const auto initBox = [=](not_null<PeerListBox*> box) {
 | |
| 		box->addButton(langFactory(lng_settings_save), [=] {
 | |
| 			const auto count = box->peerListFullRowsCount();
 | |
| 			for (auto i = 0; i != count; ++i) {
 | |
| 				const auto row = box->peerListRowAt(i);
 | |
| 				const auto peer = row->peer();
 | |
| 				const auto muted = !row->checked();
 | |
| 				if (muted != Auth().data().notifyIsMuted(peer)) {
 | |
| 					Auth().data().updateNotifySettings(
 | |
| 						peer,
 | |
| 						(muted
 | |
| 							? Data::NotifySettings::kDefaultMutePeriod
 | |
| 							: 0));
 | |
| 				}
 | |
| 			}
 | |
| 			box->closeBox();
 | |
| 		});
 | |
| 		box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); });
 | |
| 	};
 | |
| 	Ui::show(Box<PeerListBox>(
 | |
| 		std::make_unique<NotificationsController>(feed),
 | |
| 		initBox));
 | |
| }
 | |
| 
 | |
| NotificationsController::NotificationsController(
 | |
| 	not_null<Data::Feed*> feed)
 | |
| : _feed(feed) {
 | |
| }
 | |
| 
 | |
| void NotificationsController::prepare() {
 | |
| 	setSearchNoResultsText(lang(lng_feed_channels_not_found));
 | |
| 	delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
 | |
| 	delegate()->peerListSetTitle(langFactory(lng_feed_notifications));
 | |
| 
 | |
| 	loadMoreRows();
 | |
| }
 | |
| 
 | |
| void NotificationsController::loadMoreRows() {
 | |
| 	if (_preloadRequestId || _allLoaded) {
 | |
| 		return;
 | |
| 	}
 | |
| 	// const auto hash = 0;
 | |
| 	//_preloadRequestId = request(MTPmessages_GetDialogs( // #feed
 | |
| 	//	MTP_flags(MTPmessages_GetDialogs::Flag::f_feed_id),
 | |
| 	//	MTP_int(_feed->id()),
 | |
| 	//	MTP_int(_preloadOffsetDate),
 | |
| 	//	MTP_int(_preloadOffsetId),
 | |
| 	//	_preloadPeer ? _preloadPeer->input : MTP_inputPeerEmpty(),
 | |
| 	//	MTP_int(Data::Feed::kChannelsLimit),
 | |
| 	//	MTP_int(hash)
 | |
| 	//)).done([=](const MTPmessages_Dialogs &result) {
 | |
| 	//	applyFeedDialogs(result);
 | |
| 	//	_preloadRequestId = 0;
 | |
| 	//}).fail([=](const RPCError &error) {
 | |
| 	//	_preloadRequestId = 0;
 | |
| 	//}).send();
 | |
| }
 | |
| 
 | |
| void NotificationsController::applyFeedDialogs(
 | |
| 		const MTPmessages_Dialogs &result) {
 | |
| 	const auto [dialogsList, messagesList] = [&] {
 | |
| 		const auto process = [&](const auto &data) {
 | |
| 			_feed->owner().processUsers(data.vusers);
 | |
| 			_feed->owner().processChats(data.vchats);
 | |
| 			return std::make_tuple(&data.vdialogs.v, &data.vmessages.v);
 | |
| 		};
 | |
| 		switch (result.type()) {
 | |
| 		case mtpc_messages_dialogs:
 | |
| 			_allLoaded = true;
 | |
| 			return process(result.c_messages_dialogs());
 | |
| 
 | |
| 		case mtpc_messages_dialogsSlice:
 | |
| 			LOG(("API Error: "
 | |
| 				"Unexpected dialogsSlice in feed dialogs list."));
 | |
| 			return process(result.c_messages_dialogsSlice());
 | |
| 		}
 | |
| 		Unexpected("Type in NotificationsController::applyFeedDialogs");
 | |
| 	}();
 | |
| 
 | |
| 	App::feedMsgs(*messagesList, NewMessageLast);
 | |
| 
 | |
| 	if (dialogsList->empty()) {
 | |
| 		_allLoaded = true;
 | |
| 	}
 | |
| 	auto channels = std::vector<not_null<ChannelData*>>();
 | |
| 	channels.reserve(dialogsList->size());
 | |
| 	for (const auto &dialog : *dialogsList) {
 | |
| 		dialog.match([&](const MTPDdialog &data) {
 | |
| 			if (const auto peerId = peerFromMTP(data.vpeer)) {
 | |
| 				if (peerIsChannel(peerId)) { // #TODO archive
 | |
| 					const auto history = Auth().data().history(peerId);
 | |
| 					const auto channel = history->peer->asChannel();
 | |
| 					history->applyDialog(data);
 | |
| 					channels.emplace_back(channel);
 | |
| 				} else {
 | |
| 					LOG(("API Error: "
 | |
| 						"Unexpected non-channel in folder dialogs list."));
 | |
| 				}
 | |
| 			}
 | |
| 		}, [&](const MTPDdialogFolder &data) {
 | |
| 			LOG(("API Error: Unexpected dialogFolder in folder dialogs list."));
 | |
| 		});
 | |
| 	}
 | |
| 	if (!channels.empty()) {
 | |
| 		auto notMutedChannels = ranges::view::all(
 | |
| 			channels
 | |
| 		) | ranges::view::filter([](not_null<ChannelData*> channel) {
 | |
| 			return !Auth().data().notifyIsMuted(channel);
 | |
| 		});
 | |
| 		delegate()->peerListAddSelectedRows(notMutedChannels);
 | |
| 		for (const auto channel : channels) {
 | |
| 			delegate()->peerListAppendRow(createRow(channel));
 | |
| 		}
 | |
| 	}
 | |
| 	delegate()->peerListRefreshRows();
 | |
| }
 | |
| 
 | |
| void NotificationsController::rowClicked(not_null<PeerListRow*> row) {
 | |
| 	delegate()->peerListSetRowChecked(row, !row->checked());
 | |
| }
 | |
| 
 | |
| std::unique_ptr<PeerListRow> NotificationsController::createRow(
 | |
| 		not_null<ChannelData*> channel) {
 | |
| 	return std::make_unique<PeerListRow>(channel);
 | |
| }
 | |
| 
 | |
| void EditController::Start(
 | |
| 		not_null<Data::Feed*> feed,
 | |
| 		ChannelData *channel) {
 | |
| 	const auto initBox = [=](not_null<PeerListBox*> box) {
 | |
| 		box->addButton(langFactory(lng_settings_save), [=] {
 | |
| 			auto channels = std::vector<not_null<ChannelData*>>();
 | |
| 			const auto main = App::main();
 | |
| 			const auto count = box->peerListFullRowsCount();
 | |
| 			for (auto i = 0; i != count; ++i) {
 | |
| 				const auto row = box->peerListRowAt(i);
 | |
| 				if (row->checked()) {
 | |
| 					channels.push_back(row->peer()->asChannel());
 | |
| 				}
 | |
| 			}
 | |
| 			if (channels.size() < kChannelsInFeedMin) {
 | |
| 				Ui::Toast::Show(lng_feed_select_more_channels(
 | |
| 					lt_count,
 | |
| 					kChannelsInFeedMin));
 | |
| 				return;
 | |
| 			}
 | |
| 			box->closeBox();
 | |
| 			//Auth().api().setFeedChannels(feed, channels); // #feed
 | |
| 		});
 | |
| 		box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); });
 | |
| 	};
 | |
| 	Ui::show(Box<PeerListBox>(
 | |
| 		std::make_unique<EditController>(feed, channel),
 | |
| 		initBox));
 | |
| }
 | |
| 
 | |
| EditController::EditController(
 | |
| 	not_null<Data::Feed*> feed,
 | |
| 	ChannelData *channel)
 | |
| : _feed(feed) {
 | |
| //, _startWithChannel(channel) { // #feed
 | |
| }
 | |
| 
 | |
| void EditController::prepare() {
 | |
| 	setSearchNoResultsText(lang(lng_feed_channels_not_found));
 | |
| 	delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
 | |
| 	delegate()->peerListSetTitle(langFactory(
 | |
| 		(_feed->channels().size() < kChannelsInFeedMin
 | |
| 			? lng_feed_create_new
 | |
| 			: lng_feed_edit_title)));
 | |
| 
 | |
| 	loadMoreRows();
 | |
| }
 | |
| 
 | |
| void EditController::loadMoreRows() {
 | |
| 	if (_preloadRequestId || _allLoaded) {
 | |
| 		return;
 | |
| 	}
 | |
| 	//const auto hash = 0; // #feed
 | |
| 	//_preloadRequestId = request(MTPchannels_GetFeedSources(
 | |
| 	//	MTP_flags(0),
 | |
| 	//	MTP_int(0),
 | |
| 	//	MTP_int(hash)
 | |
| 	//)).done([=](const MTPchannels_FeedSources &result) {
 | |
| 	//	applyFeedSources(result);
 | |
| 	//	_preloadRequestId = 0;
 | |
| 	//}).fail([=](const RPCError &error) {
 | |
| 	//	_preloadRequestId = 0;
 | |
| 	//}).send();
 | |
| }
 | |
| // #feed
 | |
| //void EditController::applyFeedSources(
 | |
| //		const MTPchannels_FeedSources &result) {
 | |
| //	auto channels = std::vector<not_null<ChannelData*>>();
 | |
| //
 | |
| //	switch (result.type()) {
 | |
| //	case mtpc_channels_feedSourcesNotModified:
 | |
| //		LOG(("API Error: Unexpected channels.feedSourcesNotModified."));
 | |
| //		break;
 | |
| //
 | |
| //	case mtpc_channels_feedSources: {
 | |
| //		const auto &data = result.c_channels_feedSources();
 | |
| //		Auth().api().applyFeedSources(data);
 | |
| //
 | |
| //		for (const auto &chat : data.vchats.v) {
 | |
| //			if (chat.type() == mtpc_channel) {
 | |
| //				channels.push_back(_feed->owner().channel(chat.c_channel().vid.v));
 | |
| //			}
 | |
| //		}
 | |
| //	} break;
 | |
| //
 | |
| //	default: Unexpected("Type in channels.getFeedSources response.");
 | |
| //	}
 | |
| //
 | |
| //	_allLoaded = true;
 | |
| //	if (channels.size() < kChannelsInFeedMin) {
 | |
| //		setDescriptionText(lng_feed_too_few_channels(
 | |
| //			lt_count,
 | |
| //			kChannelsInFeedMin));
 | |
| //		delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled);
 | |
| //	} else {
 | |
| //		auto alreadyInFeed = ranges::view::all(
 | |
| //			channels
 | |
| //		) | ranges::view::filter([&](not_null<ChannelData*> channel) {
 | |
| //			return (channel->feed() == _feed)
 | |
| //				|| (channel == _startWithChannel);
 | |
| //		});
 | |
| //		delegate()->peerListAddSelectedRows(alreadyInFeed);
 | |
| //		for (const auto channel : channels) {
 | |
| //			delegate()->peerListAppendRow(createRow(channel));
 | |
| //		}
 | |
| //	}
 | |
| //	delegate()->peerListRefreshRows();
 | |
| //}
 | |
| 
 | |
| void EditController::rowClicked(not_null<PeerListRow*> row) {
 | |
| 	delegate()->peerListSetRowChecked(row, !row->checked());
 | |
| }
 | |
| 
 | |
| std::unique_ptr<PeerListRow> EditController::createRow(
 | |
| 		not_null<ChannelData*> channel) {
 | |
| 	return std::make_unique<PeerListRow>(channel);
 | |
| }
 | |
| 
 | |
| } // namespace FeedProfile
 | |
| } // namespace Info
 | 
