/* 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); 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() const { return _history; } private: not_null _history; }; ChannelsController::Row::Row(not_null 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) : PeerListController() , _controller(controller) , _feed(_controller->key().feed()) { if (!_feed->channelsLoaded()) { // Auth().api().requestFeedChannels(_feed); // #feed } _controller->setSearchEnabledByContent(false); } auto ChannelsController::createRow(not_null history) -> std::unique_ptr { auto result = std::make_unique(history); result->setCustomStatus(QString()); return result; } std::unique_ptr ChannelsController::createRestoredRow( not_null 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) { 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 ChannelsController::saveState() const { auto result = PeerListController::saveState(); auto my = std::make_unique(); 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 state) { PeerListController::restoreState(std::move(state)); } void ChannelsController::rowClicked(not_null row) { _controller->parentController()->showPeerHistory( row->peer(), Window::SectionShow::Way::Forward); } void ChannelsController::rowActionClicked(not_null row) { Window::DeleteAndLeaveHandler(row->peer())(); } base::unique_qptr ChannelsController::rowContextMenu( QWidget *parent, not_null row) { auto my = static_cast(row.get()); auto channel = my->history()->peer->asChannel(); auto result = base::make_unique_q(parent); Window::PeerMenuAddMuteAction(channel, [&]( const QString &text, Fn 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 feed) { const auto initBox = [=](not_null 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( std::make_unique(feed), initBox)); } NotificationsController::NotificationsController( not_null 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>(); 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 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 row) { delegate()->peerListSetRowChecked(row, !row->checked()); } std::unique_ptr NotificationsController::createRow( not_null channel) { return std::make_unique(channel); } void EditController::Start( not_null feed, ChannelData *channel) { const auto initBox = [=](not_null box) { box->addButton(langFactory(lng_settings_save), [=] { auto channels = std::vector>(); 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( std::make_unique(feed, channel), initBox)); } EditController::EditController( not_null 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>(); // // 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 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 row) { delegate()->peerListSetRowChecked(row, !row->checked()); } std::unique_ptr EditController::createRow( not_null channel) { return std::make_unique(channel); } } // namespace FeedProfile } // namespace Info