/* 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/profile/info_profile_actions.h" #include "data/data_peer_values.h" #include "data/data_session.h" #include "data/data_folder.h" #include "data/data_forum_topic.h" #include "data/data_channel.h" #include "data/data_changes.h" #include "data/data_user.h" #include "data/notify/data_notify_settings.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/shadow.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/box_content_divider.h" #include "ui/widgets/popup_menu.h" #include "ui/boxes/report_box.h" #include "ui/boxes/confirm_box.h" #include "ui/layers/generic_box.h" #include "ui/toast/toast.h" #include "ui/text/text_utilities.h" // Ui::Text::ToUpper #include "ui/text/text_variant.h" #include "history/history_location_manager.h" // LocationClickHandler. #include "history/view/history_view_context_menu.h" // HistoryView::ShowReportPeerBox #include "boxes/abstract_box.h" #include "boxes/peer_list_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/add_contact_box.h" #include "boxes/peers/add_bot_to_chat_box.h" #include "boxes/peers/edit_contact_box.h" #include "boxes/report_messages_box.h" #include "boxes/translate_box.h" #include "lang/lang_keys.h" #include "menu/menu_mute.h" #include "history/history.h" #include "info/info_controller.h" #include "info/info_memento.h" #include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_phone_menu.h" #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_text.h" #include "support/support_helper.h" #include "window/window_session_controller.h" #include "window/window_controller.h" // Window::Controller::show. #include "window/window_peer_menu.h" #include "mainwidget.h" #include "mainwindow.h" // MainWindow::controller. #include "main/main_session.h" #include "core/application.h" #include "core/click_handler_types.h" #include "settings/settings_common.h" #include "apiwrap.h" #include "api/api_blocked_peers.h" #include "styles/style_info.h" #include "styles/style_boxes.h" #include "styles/style_menu_icons.h" #include #include namespace Info { namespace Profile { namespace { [[nodiscard]] rpl::producer UsernamesSubtext( not_null peer, rpl::producer fallback) { return rpl::combine( UsernamesValue(peer), std::move(fallback) ) | rpl::map([](std::vector usernames, QString text) { if (usernames.size() < 2) { return TextWithEntities{ .text = text }; } else { auto result = TextWithEntities(); result.append(tr::lng_info_usernames_label(tr::now)); result.append(' '); auto &&subrange = ranges::make_subrange( begin(usernames) + 1, end(usernames)); for (auto &username : std::move(subrange)) { const auto isLast = (usernames.back() == username); result.append(Ui::Text::Link( '@' + base::take(username.text), username.entities.front().data())); if (!isLast) { result.append(u", "_q); } } return result; } }); } [[nodiscard]] Fn UsernamesLinkCallback( not_null peer, std::shared_ptr show, const QString &addToLink) { return [=](QString link) { if (!link.startsWith(u"https://"_q)) { link = peer->session().createInternalLinkFull(peer->userName()) + addToLink; } if (!link.isEmpty()) { QGuiApplication::clipboard()->setText(link); show->showToast(tr::lng_username_copied(tr::now)); } }; } [[nodiscard]] object_ptr CreateSkipWidget( not_null parent) { return Ui::CreateSkipWidget(parent, st::infoProfileSkip); } [[nodiscard]] object_ptr> CreateSlideSkipWidget( not_null parent) { auto result = Ui::CreateSlideSkipWidget( parent, st::infoProfileSkip); result->setDuration(st::infoSlideDuration); return result; } template auto AddActionButton( not_null parent, Text &&text, ToggleOn &&toggleOn, Callback &&callback, const style::icon *icon, const style::SettingsButton &st = st::infoSharedMediaButton) { auto result = parent->add(object_ptr>( parent, object_ptr( parent, std::move(text), st)) ); result->setDuration( st::infoSlideDuration )->toggleOn( std::move(toggleOn) )->entity()->addClickHandler(std::move(callback)); result->finishAnimating(); if (icon) { object_ptr( result, *icon, st::infoSharedMediaButtonIconPosition); } return result; }; template [[nodiscard]] auto AddMainButton( not_null parent, Text &&text, ToggleOn &&toggleOn, Callback &&callback, Ui::MultiSlideTracker &tracker) { tracker.track(AddActionButton( parent, std::move(text) | Ui::Text::ToUpper(), std::move(toggleOn), std::move(callback), nullptr, st::infoMainButton)); } class DetailsFiller { public: DetailsFiller( not_null controller, not_null parent, not_null peer); DetailsFiller( not_null controller, not_null parent, not_null topic); object_ptr fill(); private: object_ptr setupInfo(); object_ptr setupMuteToggle(); void setupMainButtons(); Ui::MultiSlideTracker fillTopicButtons(); Ui::MultiSlideTracker fillUserButtons( not_null user); Ui::MultiSlideTracker fillChannelButtons( not_null channel); template < typename Widget, typename = std::enable_if_t< std::is_base_of_v>> Widget *add( object_ptr &&child, const style::margins &margin = style::margins()) { return _wrap->add( std::move(child), margin); } not_null _controller; not_null _parent; not_null _peer; Data::ForumTopic *_topic = nullptr; object_ptr _wrap; }; class ActionsFiller { public: ActionsFiller( not_null controller, not_null parent, not_null peer); object_ptr fill(); private: void addInviteToGroupAction(not_null user); void addShareContactAction(not_null user); void addEditContactAction(not_null user); void addDeleteContactAction(not_null user); void addBotCommandActions(not_null user); void addReportAction(); void addBlockAction(not_null user); void addLeaveChannelAction(not_null channel); void addJoinChannelAction(not_null channel); void fillUserActions(not_null user); void fillChannelActions(not_null channel); not_null _controller; not_null _parent; not_null _peer; object_ptr _wrap = { nullptr }; }; DetailsFiller::DetailsFiller( not_null controller, not_null parent, not_null peer) : _controller(controller) , _parent(parent) , _peer(peer) , _wrap(_parent) { } DetailsFiller::DetailsFiller( not_null controller, not_null parent, not_null topic) : _controller(controller) , _parent(parent) , _peer(topic->peer()) , _topic(topic) , _wrap(_parent) { } template bool SetClickContext( const ClickHandlerPtr &handler, const ClickContext &context) { if (const auto casted = std::dynamic_pointer_cast(handler)) { casted->T::onClick(context); return true; } return false; } object_ptr DetailsFiller::setupInfo() { auto result = object_ptr(_wrap); auto tracker = Ui::MultiSlideTracker(); // Fill context for a mention / hashtag / bot command link. const auto infoClickFilter = [=, peer = _peer.get(), window = _controller->parentController()]( const ClickHandlerPtr &handler, Qt::MouseButton button) { const auto context = ClickContext{ button, QVariant::fromValue(ClickHandlerContext{ .sessionWindow = base::make_weak(window), .peer = peer, }) }; if (SetClickContext(handler, context)) { return false; } else if (SetClickContext(handler, context)) { return false; } else if (SetClickContext(handler, context)) { return false; } else if (SetClickContext(handler, context)) { return false; } else if (SetClickContext(handler, context)) { return false; } return true; }; const auto addTranslateToMenu = [&, peer = _peer.get(), controller = _controller->parentController()]( not_null label, rpl::producer &&text) { struct State { rpl::variable labelText; }; const auto state = label->lifetime().make_state(); state->labelText = std::move(text); label->setContextMenuHook([=]( Ui::FlatLabel::ContextMenuRequest request) { label->fillContextMenu(request); if (Ui::SkipTranslate(state->labelText.current())) { return; } auto item = tr::lng_context_translate(tr::now); request.menu->addAction(std::move(item), [=] { controller->window().show(Box( Ui::TranslateBox, peer, MsgId(), state->labelText.current(), false)); }); }); }; const auto addInfoLineGeneric = [&]( v::text::data &&label, rpl::producer &&text, const style::FlatLabel &textSt = st::infoLabeled, const style::margins &padding = st::infoProfileLabeledPadding) { auto line = CreateTextWithLabel( result, v::text::take_marked(std::move(label)), std::move(text), st::infoLabel, textSt, padding); tracker.track(result->add(std::move(line.wrap))); line.text->setClickHandlerFilter(infoClickFilter); return line; }; const auto addInfoLine = [&]( v::text::data &&label, rpl::producer &&text, const style::FlatLabel &textSt = st::infoLabeled, const style::margins &padding = st::infoProfileLabeledPadding) { return addInfoLineGeneric( std::move(label), std::move(text), textSt, padding); }; const auto addInfoOneLine = [&]( v::text::data &&label, rpl::producer &&text, const QString &contextCopyText, const style::margins &padding = st::infoProfileLabeledPadding) { auto result = addInfoLine( std::move(label), std::move(text), st::infoLabeledOneLine, padding); result.text->setDoubleClickSelectsParagraph(true); result.text->setContextCopyText(contextCopyText); return result; }; if (const auto user = _peer->asUser()) { const auto controller = _controller->parentController(); if (user->session().supportMode()) { addInfoLineGeneric( user->session().supportHelper().infoLabelValue(user), user->session().supportHelper().infoTextValue(user)); } { const auto phoneLabel = addInfoOneLine( tr::lng_info_mobile_label(), PhoneOrHiddenValue(user), tr::lng_profile_copy_phone(tr::now)).text; const auto hook = [=](Ui::FlatLabel::ContextMenuRequest request) { phoneLabel->fillContextMenu(request); AddPhoneMenu(request.menu, user); }; phoneLabel->setContextMenuHook(hook); } auto label = user->isBot() ? tr::lng_info_about_label() : tr::lng_info_bio_label(); addTranslateToMenu( addInfoLine(std::move(label), AboutValue(user)).text, AboutValue(user)); const auto usernameLine = addInfoOneLine( UsernamesSubtext(_peer, tr::lng_info_username_label()), UsernameValue(user, true) | rpl::map([=](TextWithEntities u) { return u.text.isEmpty() ? TextWithEntities() : Ui::Text::Link( u, user->session().createInternalLinkFull( u.text.mid(1))); }), QString(), st::infoProfileLabeledUsernamePadding); const auto callback = UsernamesLinkCallback( _peer, controller->uiShow(), QString()); const auto hook = [=](Ui::FlatLabel::ContextMenuRequest request) { if (!request.link) { return; } const auto text = request.link->copyToClipboardContextItemText(); if (text.isEmpty()) { return; } const auto link = request.link->copyToClipboardText(); request.menu->addAction( text, [=] { QGuiApplication::clipboard()->setText(link); }); const auto last = link.lastIndexOf('/'); if (last < 0) { return; } const auto mention = '@' + link.mid(last + 1); if (mention.size() < 2) { return; } request.menu->addAction( tr::lng_context_copy_mention(tr::now), [=] { QGuiApplication::clipboard()->setText(mention); }); }; usernameLine.text->overrideLinkClickHandler(callback); usernameLine.subtext->overrideLinkClickHandler(callback); usernameLine.text->setContextMenuHook(hook); usernameLine.subtext->setContextMenuHook(hook); const auto usernameLabel = usernameLine.text; if (user->isBot()) { const auto copyUsername = Ui::CreateChild( usernameLabel->parentWidget(), st::infoProfileLabeledButtonCopy); result->sizeValue( ) | rpl::start_with_next([=] { const auto s = usernameLabel->parentWidget()->size(); copyUsername->moveToRight( 0, (s.height() - copyUsername->height()) / 2); }, copyUsername->lifetime()); copyUsername->setClickedCallback([=] { const auto link = user->session().createInternalLinkFull( user->userName()); if (!link.isEmpty()) { QGuiApplication::clipboard()->setText(link); controller->showToast(tr::lng_username_copied(tr::now)); } return false; }); } AddMainButton( result, tr::lng_info_add_as_contact(), CanAddContactValue(user), [=] { controller->window().show(Box(EditContactBox, controller, user)); }, tracker); } else { const auto topicRootId = _topic ? _topic->rootId() : 0; const auto addToLink = topicRootId ? ('/' + QString::number(topicRootId.bare)) : QString(); auto linkText = LinkValue( _peer, true ) | rpl::map([=](const QString &link) { return link.isEmpty() ? TextWithEntities() : Ui::Text::Link( (link.startsWith(u"https://"_q) ? link.mid(u"https://"_q.size()) : link) + addToLink, link + addToLink); }); auto linkLine = addInfoOneLine( (topicRootId ? tr::lng_info_link_label(Ui::Text::WithEntities) : UsernamesSubtext(_peer, tr::lng_info_link_label())), std::move(linkText), QString()); const auto controller = _controller->parentController(); const auto linkCallback = UsernamesLinkCallback( _peer, controller->uiShow(), addToLink); linkLine.text->overrideLinkClickHandler(linkCallback); linkLine.subtext->overrideLinkClickHandler(linkCallback); if (const auto channel = _topic ? nullptr : _peer->asChannel()) { auto locationText = LocationValue( channel ) | rpl::map([](const ChannelLocation *location) { return location ? Ui::Text::Link( TextUtilities::SingleLine(location->address), LocationClickHandler::Url(location->point)) : TextWithEntities(); }); addInfoOneLine( tr::lng_info_location_label(), std::move(locationText), QString() ).text->setLinksTrusted(); } const auto about = addInfoLine( tr::lng_info_about_label(), _topic ? rpl::single(TextWithEntities()) : AboutValue(_peer)); if (!_topic) { addTranslateToMenu(about.text, AboutValue(_peer)); } } if (!_peer->isSelf()) { // No notifications toggle for Self => no separator. result->add(object_ptr>( result, object_ptr(result), st::infoProfileSeparatorPadding) )->setDuration( st::infoSlideDuration )->toggleOn( std::move(tracker).atLeastOneShownValue() ); } object_ptr( result, st::infoIconInformation, st::infoInformationIconPosition); return result; } object_ptr DetailsFiller::setupMuteToggle() { const auto peer = _peer; const auto topicRootId = _topic ? _topic->rootId() : MsgId(); const auto makeThread = [=] { return topicRootId ? static_cast(peer->forumTopicFor(topicRootId)) : peer->owner().history(peer).get(); }; auto result = object_ptr( _wrap, tr::lng_profile_enable_notifications(), st::infoNotificationsButton); result->toggleOn(_topic ? NotificationsEnabledValue(_topic) : NotificationsEnabledValue(peer), true); result->setAcceptBoth(); const auto notifySettings = &peer->owner().notifySettings(); MuteMenu::SetupMuteMenu( result.data(), result->clicks( ) | rpl::filter([=](Qt::MouseButton button) { if (button == Qt::RightButton) { return true; } const auto topic = topicRootId ? peer->forumTopicFor(topicRootId) : nullptr; Assert(!topicRootId || topic != nullptr); const auto is = topic ? notifySettings->isMuted(topic) : notifySettings->isMuted(peer); if (is) { if (topic) { notifySettings->update(topic, { .unmute = true }); } else { notifySettings->update(peer, { .unmute = true }); } return false; } else { return true; } }) | rpl::to_empty, makeThread, _controller->uiShow()); object_ptr( result, st::infoIconNotifications, st::infoNotificationsIconPosition); return result; } void DetailsFiller::setupMainButtons() { auto wrapButtons = [=](auto &&callback) { auto topSkip = _wrap->add(CreateSlideSkipWidget(_wrap)); auto tracker = callback(); topSkip->toggleOn(std::move(tracker).atLeastOneShownValue()); }; if (_topic) { wrapButtons([=] { return fillTopicButtons(); }); } else if (const auto user = _peer->asUser()) { wrapButtons([=] { return fillUserButtons(user); }); } else if (const auto channel = _peer->asChannel()) { if (!channel->isMegagroup()) { wrapButtons([=] { return fillChannelButtons(channel); }); } } } Ui::MultiSlideTracker DetailsFiller::fillTopicButtons() { using namespace rpl::mappers; Ui::MultiSlideTracker tracker; const auto window = _controller->parentController(); const auto forum = _topic->forum(); auto showTopicsVisible = rpl::combine( window->adaptive().oneColumnValue(), window->shownForum().value(), _1 || (_2 != forum)); AddMainButton( _wrap, tr::lng_forum_show_topics_list(), std::move(showTopicsVisible), [=] { window->showForum(forum); }, tracker); return tracker; } Ui::MultiSlideTracker DetailsFiller::fillUserButtons( not_null user) { using namespace rpl::mappers; Ui::MultiSlideTracker tracker; auto window = _controller->parentController(); auto addSendMessageButton = [&] { auto activePeerValue = window->activeChatValue( ) | rpl::map([](Dialogs::Key key) { return key.peer(); }); auto sendMessageVisible = rpl::combine( _controller->wrapValue(), std::move(activePeerValue), (_1 != Wrap::Side) || (_2 != user)); auto sendMessage = [window, user] { window->showPeerHistory( user, Window::SectionShow::Way::Forward); }; AddMainButton( _wrap, tr::lng_profile_send_message(), std::move(sendMessageVisible), std::move(sendMessage), tracker); }; if (user->isSelf()) { auto separator = _wrap->add(object_ptr>( _wrap, object_ptr(_wrap), st::infoProfileSeparatorPadding) )->setDuration( st::infoSlideDuration ); addSendMessageButton(); separator->toggleOn( std::move(tracker).atLeastOneShownValue() ); } else { addSendMessageButton(); } return tracker; } Ui::MultiSlideTracker DetailsFiller::fillChannelButtons( not_null channel) { using namespace rpl::mappers; Ui::MultiSlideTracker tracker; auto window = _controller->parentController(); auto activePeerValue = window->activeChatValue( ) | rpl::map([](Dialogs::Key key) { return key.peer(); }); auto viewChannelVisible = rpl::combine( _controller->wrapValue(), std::move(activePeerValue), (_1 != Wrap::Side) || (_2 != channel)); auto viewChannel = [=] { window->showPeerHistory( channel, Window::SectionShow::Way::Forward); }; AddMainButton( _wrap, tr::lng_profile_view_channel(), std::move(viewChannelVisible), std::move(viewChannel), tracker); return tracker; } object_ptr DetailsFiller::fill() { Expects(!_topic || !_topic->creating()); add(object_ptr(_wrap)); add(CreateSkipWidget(_wrap)); add(setupInfo()); if (!_peer->isSelf()) { add(setupMuteToggle()); } setupMainButtons(); add(CreateSkipWidget(_wrap)); return std::move(_wrap); } ActionsFiller::ActionsFiller( not_null controller, not_null parent, not_null peer) : _controller(controller) , _parent(parent) , _peer(peer) { } void ActionsFiller::addInviteToGroupAction( not_null user) { const auto notEmpty = [](const QString &value) { return !value.isEmpty(); }; const auto controller = _controller->parentController(); AddActionButton( _wrap, InviteToChatButton(user) | rpl::filter(notEmpty), InviteToChatButton(user) | rpl::map(notEmpty), [=] { AddBotToGroupBoxController::Start(controller, user); }, &st::infoIconAddMember); const auto about = _wrap->add( object_ptr>( _wrap.data(), object_ptr(_wrap.data()))); about->toggleOn(InviteToChatAbout(user) | rpl::map(notEmpty)); ::Settings::AddSkip(about->entity()); ::Settings::AddDividerText( about->entity(), InviteToChatAbout(user) | rpl::filter(notEmpty)); ::Settings::AddSkip(about->entity()); about->finishAnimating(); } void ActionsFiller::addShareContactAction(not_null user) { const auto controller = _controller->parentController(); AddActionButton( _wrap, tr::lng_info_share_contact(), CanShareContactValue(user), [=] { Window::PeerMenuShareContactBox(controller, user); }, &st::infoIconShare); } void ActionsFiller::addEditContactAction(not_null user) { const auto controller = _controller->parentController(); AddActionButton( _wrap, tr::lng_info_edit_contact(), IsContactValue(user), [=] { controller->window().show(Box(EditContactBox, controller, user)); }, &st::infoIconEdit); } void ActionsFiller::addDeleteContactAction(not_null user) { const auto controller = _controller->parentController(); AddActionButton( _wrap, tr::lng_info_delete_contact(), IsContactValue(user), [=] { Window::PeerMenuDeleteContact(controller, user); }, &st::infoIconDelete); } void ActionsFiller::addBotCommandActions(not_null user) { auto findBotCommand = [user](const QString &command) { if (!user->isBot()) { return QString(); } for (const auto &data : user->botInfo->commands) { const auto isSame = !data.command.compare( command, Qt::CaseInsensitive); if (isSame) { return data.command; } } return QString(); }; auto hasBotCommandValue = [=](const QString &command) { return user->session().changes().peerFlagsValue( user, Data::PeerUpdate::Flag::BotCommands ) | rpl::map([=] { return !findBotCommand(command).isEmpty(); }); }; auto sendBotCommand = [=, window = _controller->parentController()]( const QString &command) { const auto original = findBotCommand(command); if (original.isEmpty()) { return; } BotCommandClickHandler('/' + original).onClick(ClickContext{ Qt::LeftButton, QVariant::fromValue(ClickHandlerContext{ .sessionWindow = base::make_weak(window), .peer = user, }) }); }; auto addBotCommand = [=]( rpl::producer text, const QString &command, const style::icon *icon = nullptr) { AddActionButton( _wrap, std::move(text), hasBotCommandValue(command), [=] { sendBotCommand(command); }, icon); }; addBotCommand( tr::lng_profile_bot_help(), u"help"_q, &st::infoIconInformation); addBotCommand(tr::lng_profile_bot_settings(), u"settings"_q); addBotCommand(tr::lng_profile_bot_privacy(), u"privacy"_q); } void ActionsFiller::addReportAction() { const auto peer = _peer; const auto controller = _controller->parentController(); const auto report = [=] { ShowReportPeerBox(controller, peer); }; AddActionButton( _wrap, tr::lng_profile_report(), rpl::single(true), report, &st::infoIconReport, st::infoBlockButton); } void ActionsFiller::addBlockAction(not_null user) { const auto controller = _controller->parentController(); const auto window = &controller->window(); auto text = user->session().changes().peerFlagsValue( user, Data::PeerUpdate::Flag::IsBlocked ) | rpl::map([=] { switch (user->blockStatus()) { case UserData::BlockStatus::Blocked: return ((user->isBot() && !user->isSupport()) ? tr::lng_profile_restart_bot : tr::lng_profile_unblock_user)(); case UserData::BlockStatus::NotBlocked: default: return ((user->isBot() && !user->isSupport()) ? tr::lng_profile_block_bot : tr::lng_profile_block_user)(); } }) | rpl::flatten_latest( ) | rpl::start_spawning(_wrap->lifetime()); auto toggleOn = rpl::duplicate( text ) | rpl::map([](const QString &text) { return !text.isEmpty(); }); auto callback = [=] { if (user->isBlocked()) { Window::PeerMenuUnblockUserWithBotRestart(user); if (user->isBot()) { controller->showPeerHistory(user); } } else if (user->isBot()) { user->session().api().blockedPeers().block(user); } else { window->show(Box( Window::PeerMenuBlockUserBox, window, user, v::null, v::null)); } }; AddActionButton( _wrap, rpl::duplicate(text), std::move(toggleOn), std::move(callback), &st::infoIconBlock, st::infoBlockButton); } void ActionsFiller::addLeaveChannelAction(not_null channel) { Expects(_controller->parentController()); AddActionButton( _wrap, tr::lng_profile_leave_channel(), AmInChannelValue(channel), Window::DeleteAndLeaveHandler( _controller->parentController(), channel), &st::infoIconLeave); } void ActionsFiller::addJoinChannelAction( not_null channel) { using namespace rpl::mappers; auto joinVisible = AmInChannelValue(channel) | rpl::map(!_1) | rpl::start_spawning(_wrap->lifetime()); AddActionButton( _wrap, tr::lng_profile_join_channel(), rpl::duplicate(joinVisible), [=] { channel->session().api().joinChannel(channel); }, &st::infoIconAddMember); _wrap->add(object_ptr>( _wrap, CreateSkipWidget( _wrap, st::infoBlockButtonSkip)) )->setDuration( st::infoSlideDuration )->toggleOn( rpl::duplicate(joinVisible) ); } void ActionsFiller::fillUserActions(not_null user) { if (user->isBot()) { addInviteToGroupAction(user); } addShareContactAction(user); if (!user->isSelf()) { addEditContactAction(user); addDeleteContactAction(user); } if (!user->isSelf() && !user->isSupport()) { if (user->isBot()) { addBotCommandActions(user); } _wrap->add(CreateSkipWidget( _wrap, st::infoBlockButtonSkip)); if (user->isBot()) { addReportAction(); } addBlockAction(user); } } void ActionsFiller::fillChannelActions( not_null channel) { using namespace rpl::mappers; addJoinChannelAction(channel); addLeaveChannelAction(channel); if (!channel->amCreator()) { addReportAction(); } } object_ptr ActionsFiller::fill() { auto wrapResult = [=](auto &&callback) { _wrap = object_ptr(_parent); _wrap->add(CreateSkipWidget(_wrap)); callback(); _wrap->add(CreateSkipWidget(_wrap)); return std::move(_wrap); }; if (auto user = _peer->asUser()) { return wrapResult([=] { fillUserActions(user); }); } else if (auto channel = _peer->asChannel()) { if (channel->isMegagroup()) { return { nullptr }; } return wrapResult([=] { fillChannelActions(channel); }); } return { nullptr }; } } // namespace object_ptr SetupDetails( not_null controller, not_null parent, not_null peer) { DetailsFiller filler(controller, parent, peer); return filler.fill(); } object_ptr SetupDetails( not_null controller, not_null parent, not_null topic) { DetailsFiller filler(controller, parent, topic); return filler.fill(); } object_ptr SetupActions( not_null controller, not_null parent, not_null peer) { ActionsFiller filler(controller, parent, peer); return filler.fill(); } void SetupAddChannelMember( not_null navigation, not_null parent, not_null channel) { auto add = Ui::CreateChild( parent.get(), st::infoMembersAddMember); add->showOn(CanAddMemberValue(channel)); add->addClickHandler([=] { Window::PeerMenuAddChannelMembers(navigation, channel); }); parent->widthValue( ) | rpl::start_with_next([add](int newWidth) { auto availableWidth = newWidth - st::infoMembersButtonPosition.x(); add->moveToLeft( availableWidth - add->width(), st::infoMembersButtonPosition.y(), newWidth); }, add->lifetime()); } object_ptr SetupChannelMembers( not_null controller, not_null parent, not_null peer) { using namespace rpl::mappers; auto channel = peer->asChannel(); if (!channel || channel->isMegagroup()) { return { nullptr }; } auto membersShown = rpl::combine( MembersCountValue(channel), Data::PeerFlagValue( channel, ChannelDataFlag::CanViewParticipants), (_1 > 0) && _2); auto membersText = tr::lng_chat_status_subscribers( lt_count_decimal, MembersCountValue(channel) | tr::to_count()); auto membersCallback = [=] { controller->showSection(std::make_shared( channel, Section::Type::Members)); }; auto result = object_ptr>( parent, object_ptr(parent)); result->setDuration( st::infoSlideDuration )->toggleOn( std::move(membersShown) ); auto members = result->entity(); members->add(object_ptr(members)); members->add(CreateSkipWidget(members)); auto button = AddActionButton( members, std::move(membersText), rpl::single(true), std::move(membersCallback), nullptr)->entity(); SetupAddChannelMember(controller, button, channel); object_ptr( members, st::infoIconMembers, st::infoChannelMembersIconPosition); members->add(CreateSkipWidget(members)); return result; } } // namespace Profile } // namespace Info