/* 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 "window/window_peer_menu.h" #include "lang/lang_keys.h" #include "boxes/confirm_box.h" #include "boxes/mute_settings_box.h" #include "boxes/add_contact_box.h" #include "boxes/report_box.h" #include "boxes/create_poll_box.h" #include "boxes/peers/add_participants_box.h" #include "boxes/peers/manage_peer_box.h" #include "boxes/peers/edit_peer_info_box.h" #include "ui/toast/toast.h" #include "auth_session.h" #include "apiwrap.h" #include "mainwidget.h" #include "mainwindow.h" #include "observer_peer.h" #include "styles/style_boxes.h" #include "history/history.h" #include "window/window_controller.h" #include "support/support_helper.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "info/feed/info_feed_channels_controllers.h" #include "info/profile/info_profile_values.h" #include "data/data_session.h" #include "data/data_feed.h" #include "data/data_poll.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_drafts.h" #include "data/data_user.h" #include "dialogs/dialogs_key.h" namespace Window { namespace { class Filler { public: Filler( not_null controller, not_null peer, const PeerMenuCallback &addAction, PeerMenuSource source); void fill(); private: bool showInfo(); void addPinToggle(); void addInfo(); void addSearch(); void addToggleUnreadMark(); void addUserActions(not_null user); void addBlockUser(not_null user); void addChatActions(not_null chat); void addChannelActions(not_null channel); not_null _controller; not_null _peer; const PeerMenuCallback &_addAction; PeerMenuSource _source; }; class FeedFiller { public: FeedFiller( not_null controller, not_null feed, const PeerMenuCallback &addAction, PeerMenuSource source); void fill(); private: bool showInfo(); void addPinToggle(); void addInfo(); void addSearch(); void addNotifications(); void addUngroup(); not_null _controller; not_null _feed; const PeerMenuCallback &_addAction; PeerMenuSource _source; }; History *FindWastedPin() { const auto &order = Auth().data().pinnedDialogsOrder(); for (const auto pinned : order) { if (const auto history = pinned.history()) { if (history->peer->isChat() && history->peer->asChat()->isDeactivated() && !history->inChatList(Dialogs::Mode::All)) { return history; } } } return nullptr; } void AddChatMembers(not_null chat) { AddParticipantsBoxController::Start(chat); } bool PinnedLimitReached(Dialogs::Key key) { const auto pinnedCount = Auth().data().pinnedDialogsCount(); const auto pinnedMax = Global::PinnedDialogsCountMax(); if (pinnedCount < pinnedMax) { return false; } // Some old chat, that was converted, maybe is still pinned. if (const auto wasted = FindWastedPin()) { Auth().data().setPinnedDialog(wasted, false); Auth().data().setPinnedDialog(key, true); Auth().api().savePinnedOrder(); } else { auto errorText = lng_error_pinned_max( lt_count, pinnedMax); Ui::show(Box(errorText)); } return true; } void TogglePinnedDialog(Dialogs::Key key) { const auto isPinned = !key.entry()->isPinnedDialog(); if (isPinned && PinnedLimitReached(key)) { return; } Auth().data().setPinnedDialog(key, isPinned); auto flags = MTPmessages_ToggleDialogPin::Flags(0); if (isPinned) { flags |= MTPmessages_ToggleDialogPin::Flag::f_pinned; } //MTP::send(MTPmessages_ToggleDialogPin( // #feed // MTP_flags(flags), // key.history() // ? MTP_inputDialogPeer(key.history()->peer->input) // : MTP_inputDialogPeerFeed(MTP_int(key.feed()->id())))); if (key.history()) { MTP::send(MTPmessages_ToggleDialogPin( MTP_flags(flags), MTP_inputDialogPeer(key.history()->peer->input))); } if (isPinned) { if (const auto main = App::main()) { main->dialogsToUp(); } } } Filler::Filler( not_null controller, not_null peer, const PeerMenuCallback &addAction, PeerMenuSource source) : _controller(controller) , _peer(peer) , _addAction(addAction) , _source(source) { } bool Filler::showInfo() { if (_source == PeerMenuSource::Profile || _peer->isSelf()) { return false; } else if (_controller->activeChatCurrent().peer() != _peer) { return true; } else if (!Adaptive::ThreeColumn()) { return true; } else if ( !Auth().settings().thirdSectionInfoEnabled() && !Auth().settings().tabbedReplacedWithInfo()) { return true; } return false; } void Filler::addPinToggle() { auto peer = _peer; auto isPinned = false; if (auto history = peer->owner().historyLoaded(peer)) { isPinned = history->isPinnedDialog(); } auto pinText = [](bool isPinned) { return lang(isPinned ? lng_context_unpin_from_top : lng_context_pin_to_top); }; auto pinToggle = [=] { TogglePinnedDialog(peer->owner().history(peer)); }; auto pinAction = _addAction(pinText(isPinned), pinToggle); const auto lifetime = Ui::CreateChild(pinAction); Notify::PeerUpdateViewer( peer, Notify::PeerUpdate::Flag::ChatPinnedChanged ) | rpl::start_with_next([peer, pinAction, pinText] { auto isPinned = peer->owner().history(peer)->isPinnedDialog(); pinAction->setText(pinText(isPinned)); }, *lifetime); } void Filler::addInfo() { auto controller = _controller; auto peer = _peer; auto infoKey = (peer->isChat() || peer->isMegagroup()) ? lng_context_view_group : (peer->isUser() ? lng_context_view_profile : lng_context_view_channel); _addAction(lang(infoKey), [=] { controller->showPeerInfo(peer); }); } void Filler::addSearch() { _addAction(lang(lng_profile_search_messages), [peer = _peer] { App::main()->searchInChat(peer->owner().history(peer)); }); } void Filler::addToggleUnreadMark() { const auto peer = _peer; const auto isUnread = [](not_null peer) { if (const auto history = peer->owner().historyLoaded(peer)) { return (history->chatListUnreadCount() > 0) || (history->chatListUnreadMark()); } return false; }; const auto label = [=](not_null peer) { return lang(isUnread(peer) ? lng_context_mark_read : lng_context_mark_unread); }; auto action = _addAction(label(peer), [=] { const auto markAsRead = isUnread(peer); const auto handle = [&](not_null history) { if (markAsRead) { Auth().api().readServerHistory(history); } else { Auth().api().changeDialogUnreadMark(history, !markAsRead); } }; const auto history = peer->owner().history(peer); handle(history); if (markAsRead) { if (const auto migrated = history->migrateSibling()) { handle(migrated); } } }); const auto lifetime = Ui::CreateChild(action); Notify::PeerUpdateViewer( _peer, Notify::PeerUpdate::Flag::UnreadViewChanged ) | rpl::start_with_next([=] { action->setText(label(peer)); }, *lifetime); } void Filler::addBlockUser(not_null user) { auto blockText = [](not_null user) { return lang(user->isBlocked() ? (user->botInfo ? lng_profile_restart_bot : lng_profile_unblock_user) : (user->botInfo ? lng_profile_block_bot : lng_profile_block_user)); }; auto blockAction = _addAction(blockText(user), [=] { if (user->isBlocked()) { Auth().api().unblockUser(user); } else { Auth().api().blockUser(user); } }); const auto lifetime = Ui::CreateChild(blockAction); Notify::PeerUpdateViewer( _peer, Notify::PeerUpdate::Flag::UserIsBlocked ) | rpl::start_with_next([=] { blockAction->setText(blockText(user)); }, *lifetime); if (user->blockStatus() == UserData::BlockStatus::Unknown) { Auth().api().requestFullPeer(user); } } void Filler::addUserActions(not_null user) { if (_source != PeerMenuSource::ChatsList) { if (Auth().supportMode()) { _addAction("Edit support info", [=] { Auth().supportHelper().editInfo(user); }); } if (user->isContact()) { if (!user->isSelf()) { _addAction( lang(lng_info_share_contact), [user] { PeerMenuShareContactBox(user); }); _addAction( lang(lng_info_edit_contact), [user] { Ui::show(Box(user)); }); _addAction( lang(lng_info_delete_contact), [user] { PeerMenuDeleteContact(user); }); } } else if (user->canShareThisContact()) { if (!user->isSelf()) { _addAction( lang(lng_info_add_as_contact), [user] { PeerMenuAddContact(user); }); } _addAction( lang(lng_info_share_contact), [user] { PeerMenuShareContactBox(user); }); } else if (user->botInfo && !user->botInfo->cantJoinGroups) { _addAction( lang(lng_profile_invite_to_group), [user] { AddBotToGroupBoxController::Start(user); }); } _addAction( lang(lng_profile_export_chat), [=] { PeerMenuExportChat(user); }); } _addAction( lang(lng_profile_delete_conversation), DeleteAndLeaveHandler(user)); _addAction( lang(lng_profile_clear_history), ClearHistoryHandler(user)); if (!user->isInaccessible() && user != Auth().user()) { addBlockUser(user); } } void Filler::addChatActions(not_null chat) { if (_source != PeerMenuSource::ChatsList) { if (ManagePeerBox::Available(chat)) { const auto text = lang(lng_manage_group_title); _addAction(text, [=] { Ui::show(Box(chat)); }); } if (chat->canAddMembers()) { _addAction( lang(lng_profile_add_participant), [chat] { AddChatMembers(chat); }); } if (chat->canSendPolls()) { _addAction( lang(lng_polls_create), [=] { PeerMenuCreatePoll(chat); }); } _addAction( lang(lng_profile_export_chat), [=] { PeerMenuExportChat(chat); }); } _addAction( lang(lng_profile_clear_and_exit), DeleteAndLeaveHandler(_peer)); _addAction( lang(lng_profile_clear_history), ClearHistoryHandler(_peer)); } void Filler::addChannelActions(not_null channel) { auto isGroup = channel->isMegagroup(); if (!isGroup) { const auto feed = channel->feed(); const auto grouped = (feed != nullptr); if (!grouped || feed->channels().size() > 1) { //_addAction( // #feed // lang(grouped ? lng_feed_ungroup : lng_feed_group), // [=] { ToggleChannelGrouping(channel, !grouped); }); } } if (_source != PeerMenuSource::ChatsList) { if (ManagePeerBox::Available(channel)) { const auto text = lang(isGroup ? lng_manage_group_title : lng_manage_channel_title); _addAction(text, [channel] { Ui::show(Box(channel)); }); } if (channel->canAddMembers()) { _addAction( lang(lng_channel_add_members), [channel] { PeerMenuAddChannelMembers(channel); }); } if (channel->canSendPolls()) { _addAction( lang(lng_polls_create), [=] { PeerMenuCreatePoll(channel); }); } _addAction( lang(isGroup ? lng_profile_export_chat : lng_profile_export_channel), [=] { PeerMenuExportChat(channel); }); } if (channel->amIn()) { if (isGroup && !channel->isPublic()) { _addAction( lang(lng_profile_clear_history), ClearHistoryHandler(channel)); } auto text = lang(isGroup ? lng_profile_leave_group : lng_profile_leave_channel); _addAction(text, DeleteAndLeaveHandler(channel)); } else { auto text = lang(isGroup ? lng_profile_join_group : lng_profile_join_channel); _addAction( text, [channel] { Auth().api().joinChannel(channel); }); } if (_source != PeerMenuSource::ChatsList) { auto needReport = !channel->amCreator() && (!isGroup || channel->isPublic()); if (needReport) { _addAction(lang(lng_profile_report), [channel] { Ui::show(Box(channel)); }); } } } void Filler::fill() { if (_source == PeerMenuSource::ChatsList) { if (const auto history = _peer->owner().historyLoaded(_peer)) { if (!history->useProxyPromotion()) { addPinToggle(); } } } if (showInfo()) { addInfo(); } if (_source != PeerMenuSource::Profile && !_peer->isSelf()) { PeerMenuAddMuteAction(_peer, _addAction); } if (_source == PeerMenuSource::ChatsList) { addSearch(); addToggleUnreadMark(); } if (const auto user = _peer->asUser()) { addUserActions(user); } else if (const auto chat = _peer->asChat()) { addChatActions(chat); } else if (const auto channel = _peer->asChannel()) { addChannelActions(channel); } } FeedFiller::FeedFiller( not_null controller, not_null feed, const PeerMenuCallback &addAction, PeerMenuSource source) : _controller(controller) , _feed(feed) , _addAction(addAction) , _source(source) { } void FeedFiller::fill() { if (_source == PeerMenuSource::ChatsList) { addPinToggle(); } if (showInfo()) { addInfo(); } addNotifications(); if (_source == PeerMenuSource::ChatsList) { addSearch(); } addUngroup(); } bool FeedFiller::showInfo() { if (_source == PeerMenuSource::Profile) { return false; } else if (_controller->activeChatCurrent().feed() != _feed) { return true; } else if (!Adaptive::ThreeColumn()) { return true; } else if ( !Auth().settings().thirdSectionInfoEnabled() && !Auth().settings().tabbedReplacedWithInfo()) { return true; } return false; } void FeedFiller::addPinToggle() { const auto feed = _feed; const auto isPinned = feed->isPinnedDialog(); const auto pinText = [](bool isPinned) { return lang(isPinned ? lng_context_unpin_from_top : lng_context_pin_to_top); }; _addAction(pinText(isPinned), [=] { TogglePinnedDialog(feed); }); } void FeedFiller::addInfo() { auto controller = _controller; auto feed = _feed; _addAction(lang(lng_context_view_feed_info), [=] { controller->showSection(Info::Memento( feed, Info::Section(Info::Section::Type::Profile))); }); } void FeedFiller::addNotifications() { const auto feed = _feed; _addAction(lang(lng_feed_notifications), [=] { Info::FeedProfile::NotificationsController::Start(feed); }); } void FeedFiller::addSearch() { const auto feed = _feed; _addAction(lang(lng_profile_search_messages), [=] { App::main()->searchInChat(feed); }); } void FeedFiller::addUngroup() { const auto feed = _feed; //_addAction(lang(lng_feed_ungroup_all), [=] { // #feed // PeerMenuUngroupFeed(feed); //}); } } // namespace void PeerMenuExportChat(not_null peer) { Auth().data().startExport(peer); } void PeerMenuDeleteContact(not_null user) { auto text = lng_sure_delete_contact( lt_contact, App::peerName(user)); auto deleteSure = [=] { Ui::hideLayer(); MTP::send( MTPcontacts_DeleteContact(user->inputUser), App::main()->rpcDone( &MainWidget::deletedContact, user.get())); }; auto box = Box( text, lang(lng_box_delete), std::move(deleteSure)); Ui::show(std::move(box)); } void PeerMenuAddContact(not_null user) { Ui::show(Box( user->firstName, user->lastName, Auth().data().findContactPhone(user))); } void PeerMenuShareContactBox(not_null user) { const auto weak = std::make_shared>(); auto callback = [=](not_null peer) { if (!peer->canWrite()) { Ui::show(Box( lang(lng_forward_share_cant)), LayerOption::KeepOther); return; } else if (peer->isSelf()) { auto options = ApiWrap::SendOptions(peer->owner().history(peer)); Auth().api().shareContact(user, options); Ui::Toast::Show(lang(lng_share_done)); if (auto strong = *weak) { strong->closeBox(); } return; } auto recipient = peer->isUser() ? peer->name : '\xAB' + peer->name + '\xBB'; Ui::show(Box( lng_forward_share_contact(lt_recipient, recipient), lang(lng_forward_send), [peer, user] { const auto history = peer->owner().history(peer); Ui::showPeerHistory(history, ShowAtTheEndMsgId); auto options = ApiWrap::SendOptions(history); Auth().api().shareContact(user, options); }), LayerOption::KeepOther); }; *weak = Ui::show(Box( std::make_unique(std::move(callback)), [](not_null box) { box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); })); } void PeerMenuCreatePoll(not_null peer) { const auto box = Ui::show(Box()); const auto lock = box->lifetime().make_state(false); box->submitRequests( ) | rpl::start_with_next([=](const PollData &result) { if (std::exchange(*lock, true)) { return; } auto options = ApiWrap::SendOptions(peer->owner().history(peer)); if (const auto id = App::main()->currentReplyToIdFor(options.history)) { options.replyTo = id; } if (const auto localDraft = options.history->localDraft()) { options.clearDraft = localDraft->textWithTags.text.isEmpty(); } Auth().api().createPoll(result, options, crl::guard(box, [=] { box->closeBox(); }), crl::guard(box, [=](const RPCError &error) { *lock = false; box->submitFailed(lang(lng_attach_failed)); })); }, box->lifetime()); } QPointer ShowForwardMessagesBox( MessageIdsList &&items, FnMut &&successCallback) { const auto weak = std::make_shared>(); auto callback = [ ids = std::move(items), callback = std::move(successCallback), weak ](not_null peer) mutable { if (peer->isSelf()) { auto items = Auth().data().idsToItems(ids); if (!items.empty()) { auto options = ApiWrap::SendOptions(peer->owner().history(peer)); options.generateLocal = false; Auth().api().forwardMessages(std::move(items), options, [] { Ui::Toast::Show(lang(lng_share_done)); }); } } else { App::main()->setForwardDraft(peer->id, std::move(ids)); } if (const auto strong = *weak) { strong->closeBox(); } if (callback) { callback(); } }; auto initBox = [](not_null box) { box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); }; *weak = Ui::show(Box( std::make_unique(std::move(callback)), std::move(initBox)), LayerOption::KeepOther); return weak->data(); } void PeerMenuAddChannelMembers(not_null channel) { if (!channel->isMegagroup() && channel->membersCount() >= Global::ChatSizeMax()) { Ui::show( Box(channel), LayerOption::KeepOther); return; } auto callback = [=](const MTPchannels_ChannelParticipants &result) { Auth().api().parseChannelParticipants(channel, result, [&]( int availableCount, const QVector &list) { auto already = ( list ) | ranges::view::transform([](const MTPChannelParticipant &p) { return p.match([](const auto &data) { return data.vuser_id.v; }); }) | ranges::view::transform([](UserId userId) { return Auth().data().userLoaded(userId); }) | ranges::view::filter([](UserData *user) { return (user != nullptr); }) | ranges::to_vector; AddParticipantsBoxController::Start( channel, { already.begin(), already.end() }); }); }; Auth().api().requestChannelMembersForAdd(channel, callback); } void PeerMenuAddMuteAction( not_null peer, const PeerMenuCallback &addAction) { Auth().data().requestNotifySettings(peer); const auto muteText = [](bool isMuted) { return lang(isMuted ? lng_enable_notifications_from_tray : lng_disable_notifications_from_tray); }; const auto muteAction = addAction(QString("-"), [=] { if (!Auth().data().notifyIsMuted(peer)) { Ui::show(Box(peer)); } else { Auth().data().updateNotifySettings(peer, 0); } }); const auto lifetime = Ui::CreateChild(muteAction); Info::Profile::NotificationsEnabledValue( peer ) | rpl::start_with_next([=](bool enabled) { muteAction->setText(muteText(!enabled)); }, *lifetime); } // #feed //void PeerMenuUngroupFeed(not_null feed) { // Ui::show(Box( // lang(lng_feed_sure_ungroup_all), // lang(lng_feed_ungroup_sure), // [=] { Ui::hideLayer(); Auth().api().ungroupAllFromFeed(feed); })); //} // //void ToggleChannelGrouping(not_null channel, bool group) { // const auto callback = [=] { // Ui::Toast::Show(lang(group // ? lng_feed_channel_added // : lng_feed_channel_removed)); // }; // if (group) { // const auto feed = Auth().data().feed(Data::Feed::kId); // if (feed->channels().size() < 2) { // Info::FeedProfile::EditController::Start(feed, channel); // return; // } // } // Auth().api().toggleChannelGrouping( // channel, // group, // callback); //} Fn ClearHistoryHandler(not_null peer) { return [peer] { const auto weak = std::make_shared>(); const auto text = peer->isSelf() ? lang(lng_sure_delete_saved_messages) : peer->isUser() ? lng_sure_delete_history(lt_contact, peer->name) : lng_sure_delete_group_history(lt_group, peer->name); auto callback = [=] { if (auto strong = *weak) { strong->closeBox(); } Auth().api().clearHistory(peer); }; *weak = Ui::show( Box( text, lang(lng_box_delete), st::attentionBoxButton, std::move(callback)), LayerOption::KeepOther); }; } Fn DeleteAndLeaveHandler(not_null peer) { return [peer] { const auto warningText = peer->isSelf() ? lang(lng_sure_delete_saved_messages) : peer->isUser() ? lng_sure_delete_history(lt_contact, peer->name) : peer->isChat() ? lng_sure_delete_and_exit(lt_group, peer->name) : lang(peer->isMegagroup() ? lng_sure_leave_group : lng_sure_leave_channel); const auto confirmText = lang(peer->isUser() ? lng_box_delete : lng_box_leave); const auto &confirmStyle = peer->isChannel() ? st::defaultBoxButton : st::attentionBoxButton; auto callback = [peer] { Ui::hideLayer(); const auto controller = App::wnd()->controller(); if (controller->activeChatCurrent().peer() == peer) { Ui::showChatsList(); } if (peer->isUser()) { App::main()->deleteConversation(peer); } else if (const auto chat = peer->asChat()) { App::main()->deleteAndExit(chat); } else if (const auto channel = peer->asChannel()) { // Don't delete old history by default, // because Android app doesn't. // //if (auto migrateFrom = channel->migrateFrom()) { // App::main()->deleteConversation(migrateFrom); //} Auth().api().leaveChannel(channel); } }; Ui::show( Box( warningText, confirmText, confirmStyle, std::move(callback)), LayerOption::KeepOther); }; } void FillPeerMenu( not_null controller, not_null peer, const PeerMenuCallback &callback, PeerMenuSource source) { Filler filler(controller, peer, callback, source); filler.fill(); } void FillFeedMenu( not_null controller, not_null feed, const PeerMenuCallback &callback, PeerMenuSource source) { FeedFiller filler(controller, feed, callback, source); filler.fill(); } } // namespace Window