From be8aed6a956b8390669bfefdf25a254a367b4e92 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 16 Mar 2019 19:58:32 +0300 Subject: [PATCH] Added GroupTypeBox with Controller. --- Telegram/Resources/langs/lang.strings | 5 +- .../boxes/peers/edit_peer_group_type_box.cpp | 810 ++++++++++++++++++ .../boxes/peers/edit_peer_group_type_box.h | 59 ++ .../boxes/peers/edit_peer_info_box.cpp | 78 +- Telegram/SourceFiles/info/info.style | 11 +- Telegram/gyp/telegram_sources.txt | 6 +- 6 files changed, 934 insertions(+), 35 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/peers/edit_peer_group_type_box.cpp create mode 100644 Telegram/SourceFiles/boxes/peers/edit_peer_group_type_box.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 84206bcc8..22d4d0d68 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -805,8 +805,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_peer_permissions" = "Permissions"; "lng_manage_peer_group_type" = "Group type"; -"lng_manage_private_group_title" = "Private"; -"lng_manage_public_group_title" = "Public"; +"lng_manage_peer_channel_type" = "Channel type"; +"lng_manage_private_peer_title" = "Private"; +"lng_manage_public_peer_title" = "Public"; "lng_manage_history_visibility_title" = "Chat history for new members"; "lng_manage_history_visibility_shown" = "Visible"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_group_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_group_type_box.cpp new file mode 100644 index 000000000..1f4ca4e68 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_group_type_box.cpp @@ -0,0 +1,810 @@ +/* +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 "boxes/peers/edit_peer_group_type_box.h" + +#include "apiwrap.h" +#include "apiwrap.h" +#include "apiwrap.h" +#include "auth_session.h" +#include "auth_session.h" +#include "auth_session.h" +#include "boxes/add_contact_box.h" +#include "boxes/add_contact_box.h" +#include "boxes/confirm_box.h" +#include "boxes/confirm_box.h" +#include "boxes/peer_list_controllers.h" +#include "boxes/peer_list_controllers.h" +#include "boxes/peers/edit_participants_box.h" +#include "boxes/peers/edit_participants_box.h" +#include "boxes/peers/edit_participants_box.h" +#include "boxes/peers/edit_peer_group_type_box.h" +#include "boxes/peers/edit_peer_history_visibility_box.h" +#include "boxes/peers/edit_peer_info_box.h" +#include "boxes/peers/edit_peer_permissions_box.h" +#include "boxes/peers/edit_peer_permissions_box.h" +#include "boxes/photo_crop_box.h" +#include "boxes/stickers_box.h" +#include "boxes/stickers_box.h" +#include "chat_helpers/emoji_suggestions_widget.h" +#include "chat_helpers/emoji_suggestions_widget.h" +#include "core/application.h" +#include "data/data_channel.h" +#include "data/data_channel.h" +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_chat.h" +#include "data/data_chat.h" +#include "data/data_peer.h" +#include "data/data_peer.h" +#include "history/admin_log/history_admin_log_section.h" +#include "history/admin_log/history_admin_log_section.h" +#include "info/profile/info_profile_button.h" +#include "info/profile/info_profile_button.h" +#include "info/profile/info_profile_button.h" +#include "info/profile/info_profile_icon.h" +#include "info/profile/info_profile_values.h" +#include "info/profile/info_profile_values.h" +#include "lang/lang_keys.h" +#include "lang/lang_keys.h" +#include "lang/lang_keys.h" +#include "mainwidget.h" +#include "mainwidget.h" +#include "mainwindow.h" +#include "mainwindow.h" +#include "mtproto/sender.h" +#include "mtproto/sender.h" +#include "observer_peer.h" +#include "styles/style_boxes.h" +#include "styles/style_boxes.h" +#include "styles/style_boxes.h" +#include "styles/style_info.h" +#include "styles/style_info.h" +#include "styles/style_info.h" +#include "ui/rp_widget.h" +#include "ui/special_buttons.h" +#include "ui/special_buttons.h" +#include "ui/toast/toast.h" +#include "ui/toast/toast.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_controller.h" +#include "window/window_controller.h" +#include +#include +#include +#include +#include + + +#include "styles/style_boxes.h" +#include "styles/style_dialogs.h" +#include "lang/lang_keys.h" +#include "mtproto/sender.h" +#include "base/flat_set.h" +#include "boxes/confirm_box.h" +#include "boxes/photo_crop_box.h" +#include "boxes/peer_list_controllers.h" +#include "boxes/peers/add_participants_box.h" +#include "boxes/peers/edit_participant_box.h" +#include "boxes/peers/edit_participants_box.h" +#include "core/file_utilities.h" +#include "core/application.h" +#include "chat_helpers/emoji_suggestions_widget.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" +#include "ui/toast/toast.h" +#include "ui/special_buttons.h" +#include "ui/text_options.h" +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_user.h" +#include "data/data_session.h" +#include "mainwidget.h" +#include "mainwindow.h" +#include "apiwrap.h" +#include "observer_peer.h" +#include "auth_session.h" + +namespace { + + +std::optional privacySavedValue; +std::optional usernameSavedValue; + +std::shared_ptr> privacyButtons; +Ui::SlideWrap *usernameWrap = nullptr; +Ui::UsernameInput *usernameInput = nullptr; +base::unique_qptr usernameResult; +const style::FlatLabel *usernameResultStyle = nullptr; + +Ui::SlideWrap *createInviteLinkWrap = nullptr; +// Ui::SlideWrap *_editInviteLinkWrap = nullptr; +Ui::FlatLabel *inviteLink = nullptr; + +PeerData *peer = nullptr; + +bool allowSave = false; + + +mtpRequestId _checkUsernameRequestId = 0; +UsernameState _usernameState = UsernameState::Normal; +rpl::event_stream> _usernameResultTexts; + +bool isGroup = false; + +void AddRoundButton( + not_null container, + Privacy value, + LangKey groupTextKey, + LangKey channelTextKey, + LangKey groupAboutKey, + LangKey channelAboutKey) { + container->add(object_ptr>( + container, + privacyButtons, + value, + lang(isGroup ? groupTextKey : channelTextKey), + st::defaultBoxCheckbox)); + container->add(object_ptr>( + container, + object_ptr( + container, + Lang::Viewer(isGroup ? groupAboutKey : channelAboutKey), + st::editPeerPrivacyLabel), + st::editPeerPrivacyLabelMargins)); + container->add(object_ptr( + container, + st::editPeerPrivacyBottomSkip)); +}; + +void FillGroupedButtons( + not_null parent, + not_null peer, + std::optional savedValue = std::nullopt) { + + const auto canEditUsername = [&] { + if (const auto chat = peer->asChat()) { + return chat->canEditUsername(); + } else if (const auto channel = peer->asChannel()) { + return channel->canEditUsername(); + } + Unexpected("Peer type in Controller::createPrivaciesEdit."); + }(); + if (!canEditUsername) { + return; + } + + const auto result = parent->add(object_ptr>( + parent, + object_ptr(parent), + st::editPeerPrivaciesMargins)); + auto container = result->entity(); + + const auto isPublic = peer->isChannel() + && peer->asChannel()->isPublic(); + privacyButtons = std::make_shared>( + savedValue.value_or(isPublic ? Privacy::Public : Privacy::Private)); + + AddRoundButton( + container, + Privacy::Public, + lng_create_public_group_title, + lng_create_public_channel_title, + lng_create_public_group_about, + lng_create_public_channel_about); + AddRoundButton( + container, + Privacy::Private, + lng_create_private_group_title, + lng_create_private_channel_title, + lng_create_private_group_about, + lng_create_private_channel_about); + + // privacyButtons->setChangedCallback([this](Privacy value) { + // privacyChanged(value); + // }); + if (!isPublic) { + // checkUsernameAvailability(); + } + + // return std::move(result); +} + +void FillContent( + not_null parent, + not_null peer, + std::optional savedValue = std::nullopt) { + + FillGroupedButtons(parent, peer, savedValue); +} + +void SetFocusUsername() { + if (usernameInput) { + usernameInput->setFocus(); + } +} + +QString GetUsernameInput() { + return usernameInput->getLastText().trimmed(); +} + +bool InviteLinkShown() { + return !privacyButtons + || (privacyButtons->value() == Privacy::Private); +} + +QString InviteLinkText() { + if (const auto channel = peer->asChannel()) { + return channel->inviteLink(); + } else if (const auto chat = peer->asChat()) { + return chat->inviteLink(); + } + return QString(); +} + +} // namespace + +namespace { + +constexpr auto kUsernameCheckTimeout = crl::time(200); +constexpr auto kMinUsernameLength = 5; +constexpr auto kMaxGroupChannelTitle = 255; // See also add_contact_box. +constexpr auto kMaxChannelDescription = 255; // See also add_contact_box. + +class Controller + : public base::has_weak_ptr + , private MTP::Sender { +public: + Controller( + not_null container, + not_null peer); + + void createContent(); + +private: + + object_ptr createPrivaciesEdit(); + object_ptr createUsernameEdit(); + object_ptr createInviteLinkCreate(); + object_ptr createInviteLinkEdit(); + + void observeInviteLink(); + + void privacyChanged(Privacy value); + + void checkUsernameAvailability(); + void askUsernameRevoke(); + void usernameChanged(); + void showUsernameError(rpl::producer &&error); + void showUsernameGood(); + void showUsernameResult( + rpl::producer &&text, + not_null st); + + bool canEditInviteLink() const; + void refreshEditInviteLink(); + void refreshCreateInviteLink(); + void createInviteLink(); + void revokeInviteLink(); + void exportInviteLink(const QString &confirmation); + + void subscribeToMigration(); + void migrate(not_null channel); + + not_null _peer; + bool _isGroup = false; + + base::unique_qptr _wrap; + base::Timer _checkUsernameTimer; + mtpRequestId _checkUsernameRequestId = 0; + UsernameState _usernameState = UsernameState::Normal; + rpl::event_stream> _usernameResultTexts; + + Ui::SlideWrap *_editInviteLinkWrap = nullptr; + + rpl::lifetime _lifetime; + +}; + +Controller::Controller( + not_null container, + not_null peer) +: _peer(peer) +, _isGroup(_peer->isChat() || _peer->isMegagroup()) +, _wrap(container) +, _checkUsernameTimer([=] { checkUsernameAvailability(); }) { + subscribeToMigration(); + _peer->updateFull(); +} + +void Controller::subscribeToMigration() { + SubscribeToMigration( + _peer, + _lifetime, + [=](not_null channel) { migrate(channel); }); +} + +void Controller::migrate(not_null channel) { + _peer = channel; + observeInviteLink(); + _peer->updateFull(); +} + +void Controller::createContent() { + privacyButtons->setChangedCallback([this](Privacy value) { + privacyChanged(value); + }); + if (privacyButtons->value() == Privacy::Private) { + checkUsernameAvailability(); + } + + // _wrap->add(createPrivaciesEdit()); + _wrap->add(createInviteLinkCreate()); + _wrap->add(createInviteLinkEdit()); + _wrap->add(createUsernameEdit()); +} + +object_ptr Controller::createUsernameEdit() { + Expects(_wrap != nullptr); + + const auto channel = _peer->asChannel(); + const auto username = usernameSavedValue.value_or(channel ? channel->username : QString()); + + auto result = object_ptr>( + _wrap, + object_ptr(_wrap), + st::editPeerUsernameMargins); + usernameWrap = result.data(); + + auto container = result->entity(); + container->add(object_ptr( + container, + Lang::Viewer(lng_create_group_link), + st::editPeerSectionLabel)); + auto placeholder = container->add(object_ptr( + container)); + placeholder->setAttribute(Qt::WA_TransparentForMouseEvents); + usernameInput = Ui::AttachParentChild( + container, + object_ptr( + container, + st::setupChannelLink, + Fn(), + username, + true)); + usernameInput->heightValue( + ) | rpl::start_with_next([placeholder](int height) { + placeholder->resize(placeholder->width(), height); + }, placeholder->lifetime()); + placeholder->widthValue( + ) | rpl::start_with_next([this](int width) { + usernameInput->resize( + width, + usernameInput->height()); + }, placeholder->lifetime()); + usernameInput->move(placeholder->pos()); + + QObject::connect( + usernameInput, + &Ui::UsernameInput::changed, + [this] { usernameChanged(); }); + + auto shown = (privacyButtons->value() == Privacy::Public); + result->toggle(shown, anim::type::instant); + + return std::move(result); +} + +void Controller::privacyChanged(Privacy value) { + auto toggleEditUsername = [&] { + usernameWrap->toggle( + (value == Privacy::Public), + anim::type::instant); + }; + auto refreshVisibilities = [&] { + // Now first we need to hide that was shown. + // Otherwise box will change own Y position. + + if (value == Privacy::Public) { + refreshCreateInviteLink(); + refreshEditInviteLink(); + toggleEditUsername(); + + usernameResult = nullptr; + checkUsernameAvailability(); + } else { + toggleEditUsername(); + refreshCreateInviteLink(); + refreshEditInviteLink(); + } + }; + if (value == Privacy::Public) { + if (_usernameState == UsernameState::TooMany) { + askUsernameRevoke(); + return; + } else if (_usernameState == UsernameState::NotAvailable) { + privacyButtons->setValue(Privacy::Private); + return; + } + refreshVisibilities(); + usernameInput->setDisplayFocused(true); + SetFocusUsername(); + // _box->scrollToWidget(usernameInput); + } else { + request(base::take(_checkUsernameRequestId)).cancel(); + _checkUsernameTimer.cancel(); + refreshVisibilities(); + SetFocusUsername(); + } +} + +void Controller::checkUsernameAvailability() { + if (!usernameInput) { + return; + } + auto initial = (privacyButtons->value() != Privacy::Public); + auto checking = initial + ? qsl(".bad.") + : GetUsernameInput(); + if (checking.size() < kMinUsernameLength) { + return; + } + if (_checkUsernameRequestId) { + request(_checkUsernameRequestId).cancel(); + } + const auto channel = _peer->migrateToOrMe()->asChannel(); + const auto username = channel ? channel->username : QString(); + _checkUsernameRequestId = request(MTPchannels_CheckUsername( + channel ? channel->inputChannel : MTP_inputChannelEmpty(), + MTP_string(checking) + )).done([=](const MTPBool &result) { + _checkUsernameRequestId = 0; + if (initial) { + return; + } + if (!mtpIsTrue(result) && checking != username) { + showUsernameError( + Lang::Viewer(lng_create_channel_link_occupied)); + } else { + showUsernameGood(); + } + }).fail([=](const RPCError &error) { + _checkUsernameRequestId = 0; + const auto &type = error.type(); + _usernameState = UsernameState::Normal; + if (type == qstr("CHANNEL_PUBLIC_GROUP_NA")) { + _usernameState = UsernameState::NotAvailable; + privacyButtons->setValue(Privacy::Private); + } else if (type == qstr("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { + _usernameState = UsernameState::TooMany; + if (privacyButtons->value() == Privacy::Public) { + askUsernameRevoke(); + } + } else if (initial) { + if (privacyButtons->value() == Privacy::Public) { + usernameResult = nullptr; + SetFocusUsername(); + // _box->scrollToWidget(usernameInput); + } + } else if (type == qstr("USERNAME_INVALID")) { + showUsernameError( + Lang::Viewer(lng_create_channel_link_invalid)); + } else if (type == qstr("USERNAME_OCCUPIED") + && checking != username) { + showUsernameError( + Lang::Viewer(lng_create_channel_link_occupied)); + } + }).send(); +} + +void Controller::askUsernameRevoke() { + privacyButtons->setValue(Privacy::Private); + auto revokeCallback = crl::guard(this, [this] { + _usernameState = UsernameState::Normal; + privacyButtons->setValue(Privacy::Public); + checkUsernameAvailability(); + }); + Ui::show( + Box(std::move(revokeCallback)), + LayerOption::KeepOther); +} + +void Controller::usernameChanged() { + allowSave = false; + auto username = GetUsernameInput(); + if (username.isEmpty()) { + usernameResult = nullptr; + _checkUsernameTimer.cancel(); + return; + } + auto bad = ranges::find_if(username, [](QChar ch) { + return (ch < 'A' || ch > 'Z') + && (ch < 'a' || ch > 'z') + && (ch < '0' || ch > '9') + && (ch != '_'); + }) != username.end(); + if (bad) { + showUsernameError( + Lang::Viewer(lng_create_channel_link_bad_symbols)); + } else if (username.size() < kMinUsernameLength) { + showUsernameError( + Lang::Viewer(lng_create_channel_link_too_short)); + } else { + usernameResult = nullptr; + _checkUsernameTimer.callOnce(kUsernameCheckTimeout); + } +} + +void Controller::showUsernameError(rpl::producer &&error) { + showUsernameResult(std::move(error), &st::editPeerUsernameError); +} + +void Controller::showUsernameGood() { + allowSave = true; + showUsernameResult( + Lang::Viewer(lng_create_channel_link_available), + &st::editPeerUsernameGood); +} + +void Controller::showUsernameResult( + rpl::producer &&text, + not_null st) { + if (!usernameResult + || usernameResultStyle != st) { + usernameResultStyle = st; + usernameResult = base::make_unique_q( + usernameWrap, + _usernameResultTexts.events() | rpl::flatten_latest(), + *st); + auto label = usernameResult.get(); + label->show(); + label->widthValue( + ) | rpl::start_with_next([label] { + label->moveToRight( + st::editPeerUsernamePosition.x(), + st::editPeerUsernamePosition.y()); + }, label->lifetime()); + } + _usernameResultTexts.fire(std::move(text)); +} + +void Controller::createInviteLink() { + exportInviteLink(lang(_isGroup + ? lng_group_invite_about + : lng_group_invite_about_channel)); +} + +void Controller::revokeInviteLink() { + exportInviteLink(lang(lng_group_invite_about_new)); +} + +void Controller::exportInviteLink(const QString &confirmation) { + auto boxPointer = std::make_shared>(); + auto callback = crl::guard(this, [=] { + if (const auto strong = *boxPointer) { + strong->closeBox(); + } + _peer->session().api().exportInviteLink(_peer->migrateToOrMe()); + }); + auto box = Box( + confirmation, + std::move(callback)); + *boxPointer = Ui::show(std::move(box), LayerOption::KeepOther); +} + +bool Controller::canEditInviteLink() const { + if (const auto channel = _peer->asChannel()) { + return channel->amCreator() + || (channel->adminRights() & ChatAdminRight::f_invite_users); + } else if (const auto chat = _peer->asChat()) { + return chat->amCreator() + || (chat->adminRights() & ChatAdminRight::f_invite_users); + } + return false; +} + +void Controller::observeInviteLink() { + if (!_editInviteLinkWrap) { + return; + } + // return; // + Notify::PeerUpdateValue( + _peer, + Notify::PeerUpdate::Flag::InviteLinkChanged + ) | rpl::start_with_next([=] { + refreshCreateInviteLink(); + refreshEditInviteLink(); + }, _editInviteLinkWrap->lifetime()); +} + +object_ptr Controller::createInviteLinkEdit() { + Expects(_wrap != nullptr); + + if (!canEditInviteLink()) { + return nullptr; + } + + auto result = object_ptr>( + _wrap, + object_ptr(_wrap), + st::editPeerInviteLinkMargins); + _editInviteLinkWrap = result.data(); + + auto container = result->entity(); + container->add(object_ptr( + container, + Lang::Viewer(lng_profile_invite_link_section), + st::editPeerSectionLabel)); + container->add(object_ptr( + container, + st::editPeerInviteLinkSkip)); + + inviteLink = container->add(object_ptr( + container, + st::editPeerInviteLink)); + inviteLink->setSelectable(true); + inviteLink->setContextCopyText(QString()); + inviteLink->setBreakEverywhere(true); + inviteLink->setClickHandlerFilter([=](auto&&...) { + QApplication::clipboard()->setText(InviteLinkText()); + Ui::Toast::Show(lang(lng_group_invite_copied)); + return false; + }); + + container->add(object_ptr( + container, + st::editPeerInviteLinkSkip)); + container->add(object_ptr( + container, + lang(lng_group_invite_create_new), + st::editPeerInviteLinkButton) + )->addClickHandler([=] { revokeInviteLink(); }); + + observeInviteLink(); + + return std::move(result); +} + +void Controller::refreshEditInviteLink() { + auto link = InviteLinkText(); + auto text = TextWithEntities(); + if (!link.isEmpty()) { + text.text = link; + auto remove = qstr("https://"); + if (text.text.startsWith(remove)) { + text.text.remove(0, remove.size()); + } + text.entities.push_back(EntityInText( + EntityInTextCustomUrl, + 0, + text.text.size(), + link)); + } + inviteLink->setMarkedText(text); + + // Hack to expand FlatLabel width to naturalWidth again. + _editInviteLinkWrap->resizeToWidth(st::boxWideWidth); + + _editInviteLinkWrap->toggle( + InviteLinkShown() && !link.isEmpty(), + anim::type::instant); +} + +object_ptr Controller::createInviteLinkCreate() { + Expects(_wrap != nullptr); + + if (!canEditInviteLink()) { + return nullptr; + } + + auto result = object_ptr>( + _wrap, + object_ptr(_wrap), + st::editPeerInviteLinkMargins); + auto container = result->entity(); + + container->add(object_ptr( + container, + Lang::Viewer(lng_profile_invite_link_section), + st::editPeerSectionLabel)); + container->add(object_ptr( + container, + st::editPeerInviteLinkSkip)); + + container->add(object_ptr( + _wrap, + lang(lng_group_invite_create), + st::editPeerInviteLinkButton) + )->addClickHandler([this] { + createInviteLink(); + }); + createInviteLinkWrap = result.data(); + + observeInviteLink(); + + return std::move(result); +} + +void Controller::refreshCreateInviteLink() { + createInviteLinkWrap->toggle( + InviteLinkShown() && InviteLinkText().isEmpty(), + anim::type::instant); +} + +} // namespace + +EditPeerGroupTypeBox::EditPeerGroupTypeBox( + QWidget*, + not_null p, + FnMut savedCallback, + std::optional privacySaved, + std::optional usernameSaved) +: _peer(p) +, _savedCallback(std::move(savedCallback)) { + peer = p; + privacySavedValue = privacySaved; + usernameSavedValue = usernameSaved; + allowSave = !usernameSaved->isEmpty(); +} + +void EditPeerGroupTypeBox::prepare() { + _peer->updateFull(); + + setTitle(langFactory((peer->isChat() || peer->isMegagroup()) + ? lng_manage_peer_group_type + : lng_manage_peer_channel_type)); + + addButton(langFactory(lng_settings_save), [=] { + const auto v = privacyButtons->value(); + if (!allowSave && (v == Privacy::Public)) { + SetFocusUsername(); + return; + } + + auto local = std::move(_savedCallback); + local(v, + (v == Privacy::Public) + ? GetUsernameInput() + : QString()); // We dont need username with private type. + closeBox(); + }); + addButton(langFactory(lng_cancel), [=] { closeBox(); }); + + setupContent(); +} + +void EditPeerGroupTypeBox::setupContent() { + isGroup = (_peer->isChat() || _peer->isMegagroup()); + + const auto content = Ui::CreateChild(this); + FillContent(content, _peer, privacySavedValue); + + auto controller = Ui::CreateChild(this, content, _peer); + _focusRequests.events( + ) | rpl::start_with_next( + [=] { SetFocusUsername(); }, + lifetime()); + controller->createContent(); + // setDimensionsToContent(st::boxWidth, content); + setDimensionsToContent(st::boxWideWidth, content); +} diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_group_type_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_group_type_box.h new file mode 100644 index 000000000..989c6abce --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_group_type_box.h @@ -0,0 +1,59 @@ +/* +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 +*/ +#pragma once + +#include "boxes/abstract_box.h" +#include "base/timer.h" + +namespace style { +struct InfoProfileCountButton; +} // namespace style + +namespace Ui { +class VerticalLayout; +} // namespace Ui + +namespace Info { +namespace Profile { +class Button; +} // namespace Profile +} // namespace Info + +enum class Privacy { + Public, + Private, +}; + +enum class UsernameState { + Normal, + TooMany, + NotAvailable, +}; + +class EditPeerGroupTypeBox : public BoxContent { +public: + + EditPeerGroupTypeBox( + QWidget*, + not_null p, + FnMut savedCallback, + std::optional privacySaved = std::nullopt, + std::optional usernameSaved = std::nullopt); + +protected: + void prepare() override; + +private: + void setupContent(); + + not_null _peer; + FnMut _savedCallback; + + rpl::event_stream<> _focusRequests; + +}; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 5d1fd6771..be01569f6 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -281,15 +281,6 @@ public: void setFocus(); private: - enum class Privacy { - Public, - Private, - }; - enum class UsernameState { - Normal, - TooMany, - NotAvailable, - }; struct Controls { Ui::InputField *title = nullptr; Ui::InputField *description = nullptr; @@ -308,8 +299,10 @@ private: Ui::Checkbox *signatures = nullptr; - std::optional historyVisibilitySavedValue = std::nullopt; Ui::SlideWrap *historyVisibilityWrap = nullptr; + std::optional historyVisibilitySavedValue = std::nullopt; + std::optional privacySavedValue = std::nullopt; + std::optional usernameSavedValue = std::nullopt; }; struct Saving { std::optional username; @@ -638,7 +631,7 @@ object_ptr Controller::createPrivaciesEdit() { container, Lang::Viewer(_isGroup ? groupAboutKey : channelAboutKey), st::editPeerPrivacyLabel), - st::editPeerPrivacyLabelMargins)); + st::editPeerHistoryVisibilityLabelMargins)); container->add(object_ptr( container, st::editPeerPrivacyBottomSkip)); @@ -683,30 +676,57 @@ object_ptr Controller::createPrivaciesButtons() { return nullptr; } + // Bug with defaultValue here. const auto channel = _peer->asChannel(); auto defaultValue = (!channel || channel->hiddenPreHistory()) ? HistoryVisibility::Hidden : HistoryVisibility::Visible; - const auto update = std::make_shared>(); + auto defaultValuePrivacy = (_peer->isChannel() + && _peer->asChannel()->isPublic()) + ? Privacy::Public + : Privacy::Private; + + const auto updateHistoryVisibility = std::make_shared>(); + const auto updateType = std::make_shared>(); auto result = object_ptr>( _wrap, object_ptr(_wrap), st::editPeerTopButtonsLayoutMargins); auto resultContainer = result->entity(); + + const auto boxCallback = [=](Privacy checked, QString publicLink) { + updateType->fire(std::move(checked)); + _controls.privacySavedValue = checked; + _controls.usernameSavedValue = publicLink; + refreshHistoryVisibility(); + }; + const auto buttonCallback = [=]{ + Ui::show(Box( + _peer, + boxCallback, + _controls.privacySavedValue, + _controls.usernameSavedValue + ), LayerOption::KeepOther); + }; AddButtonWithText( resultContainer, - std::move(Lang::Viewer(lng_manage_peer_group_type)), - update->events( - ) | rpl::map([](HistoryVisibility count) { - return HistoryVisibility::Visible == count ? QString("A") : QString("B"); - }), - [] {LOG(("BUTTON")); }); + std::move(Lang::Viewer((_peer->isChat() || _peer->isMegagroup()) + ? lng_manage_peer_group_type + : lng_manage_peer_channel_type)), - const auto addPrivaciesButton = [=](LangKey privacyTextKey, Ui::VerticalLayout* container) { + updateType->events( + ) | rpl::map([](Privacy flag) { + return lang(Privacy::Public == flag + ? lng_manage_public_peer_title + : lng_manage_private_peer_title); + }), + buttonCallback); + + const auto addHistoryVisibilityButton = [=](LangKey privacyTextKey, Ui::VerticalLayout* container) { const auto boxCallback = [=](HistoryVisibility checked) { - update->fire(std::move(checked)); + updateHistoryVisibility->fire(std::move(checked)); _controls.historyVisibilitySavedValue = checked; }; const auto buttonCallback = [=]{ @@ -719,7 +739,7 @@ object_ptr Controller::createPrivaciesButtons() { AddButtonWithText( container, std::move(Lang::Viewer(privacyTextKey)), - update->events( + updateHistoryVisibility->events( ) | rpl::map([](HistoryVisibility flag) { return lang(HistoryVisibility::Visible == flag ? lng_manage_history_visibility_shown @@ -734,9 +754,10 @@ object_ptr Controller::createPrivaciesButtons() { st::boxOptionListPadding)); // Empty margins. _controls.historyVisibilityWrap = wrapLayout; - addPrivaciesButton(lng_manage_history_visibility_title, wrapLayout->entity()); + addHistoryVisibilityButton(lng_manage_history_visibility_title, wrapLayout->entity()); - update->fire(std::move(defaultValue)); + updateHistoryVisibility->fire(std::move(defaultValue)); + updateType->fire(std::move(defaultValuePrivacy)); refreshHistoryVisibility(); return std::move(result); @@ -1164,7 +1185,8 @@ void Controller::refreshHistoryVisibility() { return; } auto historyVisibilityShown = !_controls.privacy - || (_controls.privacy->value() == Privacy::Private); + || (_controls.privacy->value() == Privacy::Private) + || (_controls.privacySavedValue == Privacy::Private); _controls.historyVisibilityWrap->toggle( historyVisibilityShown, anim::type::normal); @@ -1302,11 +1324,15 @@ std::optional Controller::validate() const { bool Controller::validateUsername(Saving &to) const { if (!_controls.privacy) { return true; - } else if (_controls.privacy->value() == Privacy::Private) { + } else if (_controls.privacySavedValue == Privacy::Private) { to.username = QString(); return true; } - auto username = _controls.username->getLastText().trimmed(); + auto username = _controls.usernameSavedValue.value_or( + _peer->isChannel() + ? _peer->asChannel()->username + : QString() + ); if (username.isEmpty()) { _controls.username->showError(); _box->scrollToWidget(_controls.username); diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 973473292..1bd4bf37e 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -650,14 +650,15 @@ editPeerTitle: defaultInputField; editPeerTitleMargins: margins(27px, 21px, 23px, 8px); editPeerDescription: newGroupDescription; editPeerDescriptionMargins: margins(23px, 5px, 23px, 16px); -editPeerPrivaciesMargins: margins(23px, 10px, 23px, 0px); +editPeerPrivaciesMargins: margins(15px, 0px, 23px, 0px); editPeerPrivacyTopSkip: 10px; editPeerPrivacyBottomSkip: 16px; editPeerPrivacyLabel: FlatLabel(defaultFlatLabel) { minWidth: 220px; textFg: windowSubTextFg; } -editPeerPrivacyLabelMargins: margins(34px, 0px, 48px, 0px); +editPeerHistoryVisibilityLabelMargins: margins(34px, 0px, 48px, 0px); +editPeerPrivacyLabelMargins: margins(34px, 0px, 34px, 0px); editPeerSectionLabel: FlatLabel(boxTitle) { style: TextStyle(defaultTextStyle) { font: font(15px semibold); @@ -672,7 +673,7 @@ editPeerInviteLink: FlatLabel(defaultFlatLabel) { style: boxTextStyle; } editPeerInviteLinkButton: boxLinkButton; -editPeerUsernameMargins: margins(0px, 10px, 0px, 13px); +editPeerUsernameMargins: margins(15px, 2px, 35px, 2px); editPeerUsernameGood: FlatLabel(defaultFlatLabel) { textFg: boxTextFgGood; style: boxTextStyle; @@ -680,9 +681,9 @@ editPeerUsernameGood: FlatLabel(defaultFlatLabel) { editPeerUsernameError: FlatLabel(editPeerUsernameGood) { textFg: boxTextFgError; } -editPeerUsernamePosition: point(0px, 10px); +editPeerUsernamePosition: point(35px, 3px); editPeerInviteLinkSkip: 10px; -editPeerInviteLinkMargins: margins(23px, 10px, 14px, 16px); +editPeerInviteLinkMargins: margins(15px, 10px, 14px, 16px); editPeerSignaturesMargins: margins(23px, 10px, 23px, 16px); editPeerInvitesMargins: margins(23px, 10px, 23px, 16px); editPeerInvitesTopSkip: 10px; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 84455b942..5795eab8d 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -6,10 +6,12 @@ <(src_loc)/boxes/peers/edit_participants_box.h <(src_loc)/boxes/peers/edit_peer_info_box.cpp <(src_loc)/boxes/peers/edit_peer_info_box.h -<(src_loc)/boxes/peers/edit_peer_permissions_box.cpp -<(src_loc)/boxes/peers/edit_peer_permissions_box.h +<(src_loc)/boxes/peers/edit_peer_group_type_box.cpp +<(src_loc)/boxes/peers/edit_peer_group_type_box.h <(src_loc)/boxes/peers/edit_peer_history_visibility_box.cpp <(src_loc)/boxes/peers/edit_peer_history_visibility_box.h +<(src_loc)/boxes/peers/edit_peer_permissions_box.cpp +<(src_loc)/boxes/peers/edit_peer_permissions_box.h <(src_loc)/boxes/peers/manage_peer_box.cpp <(src_loc)/boxes/peers/manage_peer_box.h <(src_loc)/boxes/about_box.cpp