diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index db0984c76..497e13b17 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -203,6 +203,8 @@ PRIVATE boxes/peers/edit_peer_requests_box.h boxes/peers/edit_peer_type_box.cpp boxes/peers/edit_peer_type_box.h + boxes/peers/edit_peer_usernames_list.cpp + boxes/peers/edit_peer_usernames_list.h boxes/peers/peer_short_info_box.cpp boxes/peers/peer_short_info_box.h boxes/peers/prepare_short_info_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index cab0d6a7e..eaa6f7f7e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -407,6 +407,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_username_link" = "This link opens a chat with you:"; "lng_username_copied" = "Link copied to clipboard."; +"lng_usernames_active" = "active"; +"lng_usernames_non_active" = "non active"; +"lng_usernames_subtitle" = "Usernames order"; +"lng_usernames_activate_description" = "Do you want to show this username on your info page?"; +"lng_usernames_activate_confirm" = "Show"; +"lng_channel_usernames_subtitle" = "Links order"; +"lng_usernames_deactivate_description" = "Do you want to hide this username from your info page?"; +"lng_usernames_deactivate_confirm" = "Hide"; +"lng_usernames_deactivate_error" = "Sorry, you can't deactivate this username from your info page. "; +"lng_usernames_description" = "Drag and drop links to change the order in which they will be displayed on your info page."; + +"lng_channel_usernames_activate_description" = "Do you want to show this link on the channel info page?"; +"lng_channel_usernames_deactivate_description" = "Do you want to hide this link from the channel info page?"; +"lng_channel_usernames_deactivate_error" = "Sorry, you can't deactivate this link from the channel info page. "; +"lng_channel_usernames_description" = "Drag and drop links to change the order in which they will be displayed on the channel info page."; + "lng_bio_title" = "Edit your bio"; "lng_bio_placeholder" = "Bio"; "lng_bio_about" = "You can add a few lines about yourself. Anyone who opens your profile will see this text."; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_usernames_list.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_usernames_list.cpp new file mode 100644 index 000000000..e3a8f8c7b --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_usernames_list.cpp @@ -0,0 +1,270 @@ +/* +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_usernames_list.h" + +#include "api/api_user_names.h" +#include "apiwrap.h" +#include "data/data_peer.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "settings/settings_common.h" +#include "ui/boxes/confirm_box.h" +#include "ui/layers/show.h" +#include "ui/painter.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/vertical_layout_reorder.h" +#include "styles/style_boxes.h" // contactsStatusFont. +#include "styles/style_info.h" +#include "styles/style_settings.h" + +class UsernamesList::Row final : public Ui::SettingsButton { +public: + Row(not_null parent, const Data::Username &data); + + [[nodiscard]] const Data::Username &username() const; + + int resizeGetHeight(int newWidth) override; + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + const style::PeerListItem &_st; + const Data::Username _data; + const QString _status; + const QRect _iconRect; + Ui::Text::String _title; + +}; + +UsernamesList::Row::Row( + not_null parent, + const Data::Username &data) +: Ui::SettingsButton(parent, rpl::never()) +, _st(st::inviteLinkListItem) +, _data(data) +, _status(data.active + ? tr::lng_usernames_active(tr::now) + : tr::lng_usernames_non_active(tr::now)) +, _iconRect( + _st.photoPosition.x() + st::inviteLinkIconSkip, + _st.photoPosition.y() + st::inviteLinkIconSkip, + _st.photoSize - st::inviteLinkIconSkip * 2, + _st.photoSize - st::inviteLinkIconSkip * 2) +, _title(_st.nameStyle, '@' + data.username) { +} + +const Data::Username &UsernamesList::Row::username() const { + return _data; +} + +int UsernamesList::Row::resizeGetHeight(int newWidth) { + return _st.height; +} + +void UsernamesList::Row::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + + const auto paintOver = (isOver() || isDown()) && !isDisabled(); + Ui::SettingsButton::paintBg(p, e->rect(), paintOver); + Ui::SettingsButton::paintRipple(p, 0, 0); + + const auto &color = _data.active ? st::msgFile1Bg : st::windowSubTextFg; + p.setPen(Qt::NoPen); + p.setBrush(color); + { + auto hq = PainterHighQualityEnabler(p); + p.drawEllipse(_iconRect); + } + (!_data.active + ? st::inviteLinkRevokedIcon + : st::inviteLinkIcon).paintInCenter(p, _iconRect); + + p.setPen(_st.nameFg); + _title.drawLeft( + p, + _st.namePosition.x(), + _st.namePosition.y(), + width(), + width() - _st.namePosition.x()); + + p.setPen(_data.active + ? _st.statusFgActive + : paintOver + ? _st.statusFgOver + : _st.statusFg); + p.setFont(st::contactsStatusFont); + p.drawTextLeft( + _st.statusPosition.x(), + _st.statusPosition.y(), + width() - _st.statusPosition.x(), + _status); +} + +UsernamesList::UsernamesList( + not_null parent, + not_null peer, + std::shared_ptr show) +: RpWidget(parent) +, _show(show) +, _peer(peer) { + load(); +} + +void UsernamesList::load() { + _loadLifetime = _peer->session().api().usernames().loadUsernames( + _peer + ) | rpl::start_with_next([=](const Data::Usernames &usernames) { + if (usernames.empty()) { + _container = nullptr; + resize(0, 0); + } else { + rebuild(usernames); + } + }); +} + +void UsernamesList::rebuild(const Data::Usernames &usernames) { + if (_reorder) { + _reorder->cancel(); + } + _rows.clear(); + _rows.reserve(usernames.size()); + _container = base::make_unique_q(this); + + { + Settings::AddSkip(_container); + _container->add( + object_ptr( + _container, + _peer->isSelf() + ? tr::lng_usernames_subtitle() + : tr::lng_channel_usernames_subtitle(), + st::settingsSubsectionTitle), + st::settingsSubsectionTitlePadding); + } + + const auto content = _container->add( + object_ptr(_container)); + for (const auto &username : usernames) { + const auto row = content->add( + object_ptr(content, username)); + _rows.push_back(row); + row->addClickHandler([=] { + if (_reordering || (!_peer->isSelf() && !_peer->isChannel())) { + return; + } + + if (username.username == _peer->userName()) { + _show->showBox( + Ui::MakeInformBox(_peer->isSelf() + ? tr::lng_usernames_deactivate_error() + : tr::lng_channel_usernames_deactivate_error()), + Ui::LayerOption::KeepOther); + return; + } + + auto text = _peer->isSelf() + ? (username.active + ? tr::lng_usernames_deactivate_description() + : tr::lng_usernames_activate_description()) + : (username.active + ? tr::lng_channel_usernames_deactivate_description() + : tr::lng_channel_usernames_activate_description()); + + auto confirmText = username.active + ? tr::lng_usernames_deactivate_confirm() + : tr::lng_usernames_activate_confirm(); + + auto args = Ui::ConfirmBoxArgs{ + .text = std::move(text), + .confirmed = crl::guard(this, [=](Fn close) { + auto &api = _peer->session().api(); + _toggleLifetime = api.usernames().reorder( + _peer, + order() + ) | rpl::start_with_done([=] { + auto &api = _peer->session().api(); + _toggleLifetime = api.usernames().toggle( + _peer, + username.username, + !username.active + ) | rpl::start_with_done([=] { + load(); + }); + }); + close(); + }), + .confirmText = std::move(confirmText), + }; + _show->showBox( + Ui::MakeConfirmBox(std::move(args)), + Ui::LayerOption::KeepOther); + }); + } + + _reorder = std::make_unique(content); + + { + const auto it = ranges::find_if(usernames, [&]( + const Data::Username username) { + return !username.active; + }); + if (it != end(usernames)) { + const auto from = std::distance(begin(usernames), it); + const auto length = std::distance(it, end(usernames)); + _reorder->addPinnedInterval(from, length); + } + } + _reorder->start(); + + _reorder->updates( + ) | rpl::start_with_next([=](Ui::VerticalLayoutReorder::Single data) { + using State = Ui::VerticalLayoutReorder::State; + if (data.state == State::Started) { + ++_reordering; + } else { + Ui::PostponeCall(content, [=] { + --_reordering; + }); + if (data.state == State::Applied) { + base::reorder( + _rows, + data.oldPosition, + data.newPosition); + } + } + }, content->lifetime()); + + { + Settings::AddSkip(_container); + Settings::AddDividerText( + _container, + _peer->isSelf() + ? tr::lng_usernames_description() + : tr::lng_channel_usernames_description()); + } + + Ui::ResizeFitChild(this, _container.get()); + content->show(); + _container->show(); +} + +std::vector UsernamesList::order() const { + return ranges::views::all( + _rows + ) | ranges::views::filter([](not_null row) { + return row->username().active; + }) | ranges::views::transform([](not_null row) { + return row->username().username; + }) | ranges::to_vector; +} + +rpl::producer<> UsernamesList::save() { + return _peer->session().api().usernames().reorder(_peer, order()); +} diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_usernames_list.h b/Telegram/SourceFiles/boxes/peers/edit_peer_usernames_list.h new file mode 100644 index 000000000..b0a07c065 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_usernames_list.h @@ -0,0 +1,52 @@ +/* +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 "base/unique_qptr.h" +#include "ui/rp_widget.h" + +class PeerData; + +namespace Ui { +class VerticalLayout; +class VerticalLayoutReorder; +class Show; +} // namespace Ui + +namespace Data { +struct Username; +} // namespace Data + +class UsernamesList final : public Ui::RpWidget { +public: + UsernamesList( + not_null parent, + not_null peer, + std::shared_ptr show); + + [[nodiscard]] rpl::producer<> save(); + [[nodiscard]] std::vector order() const; + +private: + void rebuild(const std::vector &usernames); + void load(); + + class Row; + + base::unique_qptr _container; + std::unique_ptr _reorder; + std::shared_ptr _show; + const not_null _peer; + std::vector _rows; + + int _reordering = 0; + + rpl::lifetime _loadLifetime; + rpl::lifetime _toggleLifetime; + +}; diff --git a/Telegram/SourceFiles/boxes/username_box.cpp b/Telegram/SourceFiles/boxes/username_box.cpp index 077075682..e90885441 100644 --- a/Telegram/SourceFiles/boxes/username_box.cpp +++ b/Telegram/SourceFiles/boxes/username_box.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/username_box.h" +#include "boxes/peers/edit_peer_usernames_list.h" #include "base/timer.h" #include "boxes/peers/edit_peer_common.h" #include "data/data_session.h" @@ -33,9 +34,8 @@ public: UsernameEditor(not_null, not_null session); void setInnerFocus(); - void save(); - - [[nodiscard]] rpl::producer<> closeRequests() const; + rpl::producer<> submitted() const; + rpl::producer<> save(); protected: void paintEvent(QPaintEvent *e) override; @@ -64,7 +64,7 @@ private: base::Timer _checkTimer; - rpl::event_stream<> _closeRequests; + rpl::event_stream<> _saved; }; @@ -87,7 +87,6 @@ UsernameEditor::UsernameEditor( : tr::lng_username_available(tr::now); connect(_username, &Ui::MaskedInputField::changed, [=] { changed(); }); - connect(_username, &Ui::MaskedInputField::submitted, [=] { save(); }); resize( width(), @@ -96,8 +95,15 @@ UsernameEditor::UsernameEditor( + st::usernameSkip)); } -rpl::producer<> UsernameEditor::closeRequests() const { - return _closeRequests.events(); +rpl::producer<> UsernameEditor::submitted() const { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + QObject::connect( + _username, + &Ui::MaskedInputField::submitted, + [=] { consumer.put_next({}); }); + return lifetime; + }; } void UsernameEditor::setInnerFocus() { @@ -144,9 +150,9 @@ void UsernameEditor::resizeEvent(QResizeEvent *e) { _username->moveToLeft(_padding.left(), _padding.top()); } -void UsernameEditor::save() { +rpl::producer<> UsernameEditor::save() { if (_saveRequestId) { - return; + return _saved.events(); } _sentUsername = getName(); @@ -155,11 +161,12 @@ void UsernameEditor::save() { )).done([=](const MTPUser &result) { _saveRequestId = 0; _session->data().processUser(result); - _closeRequests.fire({}); + _saved.fire_done(); }).fail([=](const MTP::Error &error) { _saveRequestId = 0; updateFail(error.type()); }).send(); + return _saved.events(); } void UsernameEditor::check() { @@ -240,7 +247,7 @@ void UsernameEditor::updateFail(const QString &error) { TextUtilities::SingleLine(self->lastName), TextUtilities::SingleLine(self->nameOrPhone), TextUtilities::SingleLine(_sentUsername)); - _closeRequests.fire({}); + _saved.fire_done(); } else if (error == qstr("USERNAME_INVALID")) { _username->setFocus(); _username->showError(); @@ -287,10 +294,6 @@ void UsernamesBox( const auto editor = box->addRow( object_ptr(box, session), {}); - editor->closeRequests( - ) | rpl::start_with_next([=] { - box->closeBox(); - }, editor->lifetime()); container->add(object_ptr( container, @@ -300,6 +303,25 @@ void UsernamesBox( st::boxDividerLabel), st::settingsDividerLabelPadding)); - box->addButton(tr::lng_settings_save(), [=] { editor->save(); }); + const auto list = box->addRow( + object_ptr( + box, + session->user(), + std::make_shared(box)), + {}); + + const auto finish = [=] { + list->save( + ) | rpl::start_with_done([=] { + editor->save( + ) | rpl::start_with_done([=] { + box->closeBox(); + }, box->lifetime()); + }, box->lifetime()); + }; + editor->submitted( + ) | rpl::start_with_next(finish, editor->lifetime()); + + box->addButton(tr::lng_settings_save(), finish); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); }