/* 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/similar_channels/info_similar_channels_widget.h" #include "api/api_chat_participants.h" #include "apiwrap.h" #include "boxes/peer_list_box.h" #include "data/data_channel.h" #include "data/data_peer_values.h" #include "data/data_premium_limits.h" #include "data/data_session.h" #include "info/info_controller.h" #include "main/main_session.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/tooltip.h" #include "ui/ui_utility.h" #include "lang/lang_keys.h" #include "settings/settings_premium.h" #include "window/window_session_controller.h" #include "styles/style_info.h" #include "styles/style_widgets.h" namespace Info::SimilarChannels { namespace { class ListController final : public PeerListController { public: ListController( not_null controller, not_null channel); Main::Session &session() const override; void prepare() override; void rowClicked(not_null row) override; void loadMoreRows() override; std::unique_ptr createRestoredRow( not_null peer) override { return createRow(peer); } std::unique_ptr saveState() const override; void restoreState(std::unique_ptr state) override; void setContentWidget(not_null widget); [[nodiscard]] rpl::producer unlockHeightValue() const; private: std::unique_ptr createRow(not_null peer); void setupUnlock(); void rebuild(); struct SavedState : SavedStateBase { }; const not_null _controller; const not_null _channel; Ui::RpWidget *_content = nullptr; Ui::RpWidget *_unlock = nullptr; rpl::variable _unlockHeight; }; ListController::ListController( not_null controller, not_null channel) : PeerListController() , _controller(controller) , _channel(channel) { } Main::Session &ListController::session() const { return _channel->session(); } std::unique_ptr ListController::createRow( not_null peer) { auto result = std::make_unique(peer); if (const auto channel = peer->asChannel()) { if (const auto count = channel->membersCount(); count > 1) { result->setCustomStatus( tr::lng_chat_status_subscribers( tr::now, lt_count_decimal, count)); } } return result; } void ListController::prepare() { delegate()->peerListSetTitle(tr::lng_similar_channels_title()); const auto participants = &_channel->session().api().chatParticipants(); Data::AmPremiumValue( &_channel->session() ) | rpl::start_with_next([=] { participants->loadSimilarChannels(_channel); rebuild(); }, lifetime()); participants->similarLoaded( ) | rpl::filter( rpl::mappers::_1 == _channel ) | rpl::start_with_next([=] { rebuild(); }, lifetime()); } void ListController::setContentWidget(not_null widget) { _content = widget; } rpl::producer ListController::unlockHeightValue() const { return _unlockHeight.value(); } void ListController::rebuild() { const auto participants = &_channel->session().api().chatParticipants(); const auto &list = participants->similar(_channel); for (const auto channel : list.list) { if (!delegate()->peerListFindRow(channel->id.value)) { delegate()->peerListAppendRow(createRow(channel)); } } if (!list.more || _channel->session().premium() || !_channel->session().premiumPossible()) { delete base::take(_unlock); _unlockHeight = 0; } else if (!_unlock) { setupUnlock(); } delegate()->peerListRefreshRows(); } void ListController::setupUnlock() { Expects(_content != nullptr); _unlock = Ui::CreateChild(_content); _unlock->show(); const auto button = ::Settings::CreateLockedButton( _unlock, tr::lng_similar_channels_show_more(), st::similarChannelsLock, rpl::single(true)); button->setClickedCallback([=] { const auto window = _controller->parentController(); ::Settings::ShowPremium(window, u"similar_channels"_q); }); const auto upto = Data::PremiumLimits( &_channel->session()).similarChannelsPremium(); const auto about = Ui::CreateChild( _unlock, tr::lng_similar_channels_premium_all( lt_count, rpl::single(upto * 1.), lt_link, tr::lng_similar_channels_premium_all_link( ) | Ui::Text::ToBold() | Ui::Text::ToLink(), Ui::Text::RichLangValue), st::similarChannelsLockAbout); about->setClickHandlerFilter([=](const auto &...) { const auto window = _controller->parentController(); ::Settings::ShowPremium(window, u"similar_channels"_q); return false; }); rpl::combine( _content->sizeValue(), tr::lng_similar_channels_show_more() ) | rpl::start_with_next([=](QSize size, const auto &) { auto top = st::similarChannelsLockFade + st::similarChannelsLockPadding.top(); button->setGeometry( st::similarChannelsLockPadding.left(), top, (size.width() - st::similarChannelsLockPadding.left() - st::similarChannelsLockPadding.right()), button->height()); top += button->height() + st::similarChannelsLockPadding.bottom(); const auto minWidth = st::similarChannelsLockAbout.minWidth; const auto maxWidth = std::max( minWidth + 1, (size.width() - st::similarChannelsLockAboutPadding.left() - st::similarChannelsLockAboutPadding.right())); const auto countAboutHeight = [&](int width) { about->resizeToWidth(width); return about->height(); }; const auto desired = Ui::FindNiceTooltipWidth( minWidth, maxWidth, countAboutHeight); about->resizeToWidth(desired); about->move((size.width() - about->width()) / 2, top); top += about->height() + st::similarChannelsLockAboutPadding.bottom(); _unlock->setGeometry(0, size.height() - top, size.width(), top); }, _unlock->lifetime()); _unlockHeight = _unlock->heightValue(); _unlock->paintRequest( ) | rpl::start_with_next([=] { auto p = QPainter(_unlock); const auto width = _unlock->width(); const auto fade = st::similarChannelsLockFade; auto gradient = QLinearGradient(0, 0, 0, fade); gradient.setStops({ { 0., QColor(255, 255, 255, 0) }, { 1., st::windowBg->c }, }); p.fillRect(0, 0, width, fade, gradient); p.fillRect(0, fade, width, _unlock->height() - fade, st::windowBg); }, _unlock->lifetime()); } void ListController::loadMoreRows() { } std::unique_ptr ListController::saveState() const { auto result = PeerListController::saveState(); auto my = std::make_unique(); result->controllerState = std::move(my); return result; } void ListController::restoreState( std::unique_ptr state) { auto typeErasedState = state ? state->controllerState.get() : nullptr; if (dynamic_cast(typeErasedState)) { PeerListController::restoreState(std::move(state)); } } void ListController::rowClicked(not_null row) { _controller->parentController()->showPeerHistory( row->peer(), Window::SectionShow::Way::Forward); } } // namespace class InnerWidget final : public Ui::RpWidget , private PeerListContentDelegate { public: InnerWidget( QWidget *parent, not_null controller, not_null channel); [[nodiscard]] not_null channel() const { return _channel; } rpl::producer scrollToRequests() const; int desiredHeight() const; void saveState(not_null memento); void restoreState(not_null memento); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; private: using ListWidget = PeerListContent; // PeerListContentDelegate interface. void peerListSetTitle(rpl::producer title) override; void peerListSetAdditionalTitle(rpl::producer title) override; bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; void peerListScrollToTop() override; void peerListAddSelectedPeerInBunch( not_null peer) override; void peerListAddSelectedRowInBunch( not_null row) override; void peerListFinishSelectedRowsBunch() override; void peerListSetDescription( object_ptr description) override; std::shared_ptr peerListUiShow() override; object_ptr setupList( RpWidget *parent, not_null controller); const std::shared_ptr _show; not_null _controller; const not_null _channel; std::unique_ptr _listController; object_ptr _list; rpl::event_stream _scrollToRequests; }; InnerWidget::InnerWidget( QWidget *parent, not_null controller, not_null channel) : RpWidget(parent) , _show(controller->uiShow()) , _controller(controller) , _channel(channel) , _listController(std::make_unique(controller, _channel)) , _list(setupList(this, _listController.get())) { setContent(_list.data()); _listController->setDelegate(static_cast(this)); } void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { setChildVisibleTopBottom(_list, visibleTop, visibleBottom); } void InnerWidget::saveState(not_null memento) { memento->setListState(_listController->saveState()); } void InnerWidget::restoreState(not_null memento) { _listController->restoreState(memento->listState()); } rpl::producer InnerWidget::scrollToRequests() const { return _scrollToRequests.events(); } int InnerWidget::desiredHeight() const { auto desired = 0; desired += _list->fullRowsCount() * st::infoMembersList.item.height; return qMax(height(), desired); } object_ptr InnerWidget::setupList( RpWidget *parent, not_null controller) { controller->setStyleOverrides(&st::infoMembersList); auto result = object_ptr( parent, controller); controller->setContentWidget(this); result->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { auto addmin = (request.ymin < 0) ? 0 : st::infoCommonGroupsMargin.top(); auto addmax = (request.ymax < 0) ? 0 : st::infoCommonGroupsMargin.top(); _scrollToRequests.fire({ request.ymin + addmin, request.ymax + addmax }); }, result->lifetime()); result->moveToLeft(0, st::infoCommonGroupsMargin.top()); parent->widthValue( ) | rpl::start_with_next([list = result.data()](int newWidth) { list->resizeToWidth(newWidth); }, result->lifetime()); rpl::combine( result->heightValue(), controller->unlockHeightValue() ) | rpl::start_with_next([=](int listHeight, int unlockHeight) { auto newHeight = st::infoCommonGroupsMargin.top() + listHeight + (unlockHeight ? (unlockHeight - st::similarChannelsLockOverlap) : st::infoCommonGroupsMargin.bottom()); parent->resize(parent->width(), std::max(newHeight, 0)); }, result->lifetime()); return result; } void InnerWidget::peerListSetTitle(rpl::producer title) { } void InnerWidget::peerListSetAdditionalTitle(rpl::producer title) { } bool InnerWidget::peerListIsRowChecked(not_null row) { return false; } int InnerWidget::peerListSelectedRowsCount() { return 0; } void InnerWidget::peerListScrollToTop() { _scrollToRequests.fire({ -1, -1 }); } void InnerWidget::peerListAddSelectedPeerInBunch(not_null peer) { Unexpected("Item selection in Info::Profile::Members."); } void InnerWidget::peerListAddSelectedRowInBunch(not_null row) { Unexpected("Item selection in Info::Profile::Members."); } void InnerWidget::peerListFinishSelectedRowsBunch() { } void InnerWidget::peerListSetDescription( object_ptr description) { description.destroy(); } std::shared_ptr InnerWidget::peerListUiShow() { return _show; } Memento::Memento(not_null channel) : ContentMemento(channel, nullptr, PeerId()) { } Section Memento::section() const { return Section(Section::Type::SimilarChannels); } not_null Memento::channel() const { return peer()->asChannel(); } object_ptr Memento::createWidget( QWidget *parent, not_null controller, const QRect &geometry) { auto result = object_ptr(parent, controller, channel()); result->setInternalState(geometry, this); return result; } void Memento::setListState(std::unique_ptr state) { _listState = std::move(state); } std::unique_ptr Memento::listState() { return std::move(_listState); } Memento::~Memento() = default; Widget::Widget( QWidget *parent, not_null controller, not_null channel) : ContentWidget(parent, controller) { _inner = setInnerWidget(object_ptr( this, controller, channel)); } rpl::producer Widget::title() { return tr::lng_similar_channels_title(); } not_null Widget::channel() const { return _inner->channel(); } bool Widget::showInternal(not_null memento) { if (!controller()->validateMementoPeer(memento)) { return false; } if (auto similarMemento = dynamic_cast(memento.get())) { if (similarMemento->channel() == channel()) { restoreState(similarMemento); return true; } } return false; } void Widget::setInternalState( const QRect &geometry, not_null memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); } std::shared_ptr Widget::doCreateMemento() { auto result = std::make_shared(channel()); saveState(result.get()); return result; } void Widget::saveState(not_null memento) { memento->setScrollTop(scrollTopSave()); _inner->saveState(memento); } void Widget::restoreState(not_null memento) { _inner->restoreState(memento); scrollTopRestore(memento->scrollTop()); } } // namespace Info::SimilarChannels