From bbe6d2d13bb83301fa83ef42d54ef54cd09547e2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 9 Sep 2018 20:38:08 +0300 Subject: [PATCH] Implement information settings section. --- Telegram/Resources/langs/lang.strings | 5 + Telegram/SourceFiles/apiwrap.cpp | 31 +++ Telegram/SourceFiles/apiwrap.h | 6 + .../SourceFiles/boxes/add_contact_box.cpp | 9 +- Telegram/SourceFiles/boxes/add_contact_box.h | 4 + Telegram/SourceFiles/info/info.style | 8 + .../SourceFiles/info/info_content_widget.cpp | 8 + .../SourceFiles/info/info_content_widget.h | 3 + Telegram/SourceFiles/info/info_controller.cpp | 8 + Telegram/SourceFiles/info/info_controller.h | 4 + Telegram/SourceFiles/info/info_top_bar.cpp | 7 + Telegram/SourceFiles/info/info_top_bar.h | 17 +- .../SourceFiles/info/info_wrap_widget.cpp | 20 ++ Telegram/SourceFiles/info/info_wrap_widget.h | 1 + .../info/profile/info_profile_cover.cpp | 18 +- .../info/profile/info_profile_cover.h | 1 - .../info/profile/info_profile_values.cpp | 10 + .../info/profile/info_profile_values.h | 2 + .../info/settings/info_settings_widget.cpp | 20 +- .../info/settings/info_settings_widget.h | 9 + Telegram/SourceFiles/rpl/variable.h | 1 + Telegram/SourceFiles/settings/settings.style | 24 ++ .../SourceFiles/settings/settings_common.h | 6 + .../settings/settings_information.cpp | 256 +++++++++++++++++- .../settings/settings_information.h | 5 + .../SourceFiles/settings/settings_main.cpp | 1 - 26 files changed, 459 insertions(+), 25 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5ff5cc7f8..967f39d87 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -344,6 +344,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_sessions_about" = "Control your sessions on other devices."; "lng_settings_passcode_disable" = "Disable passcode"; "lng_settings_password_disable" = "Disable cloud password"; +"lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco"; +"lng_settings_name_label" = "Name"; +"lng_settings_username_label" = "Username"; +"lng_settings_phone_label" = "Phone number"; +"lng_settings_username_add" = "Add username"; "lng_backgrounds_header" = "Choose your new chat background"; "lng_theme_sure_keep" = "Keep this theme?"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 6718e2eb5..074b281e7 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4946,6 +4946,37 @@ auto ApiWrap::passwordStateCurrent() const : base::none; } +void ApiWrap::saveSelfBio(const QString &text, FnMut done) { + if (_saveBioRequestId) { + if (text != _saveBioText) { + request(_saveBioRequestId).cancel(); + } else { + if (done) { + _saveBioDone = std::move(done); + } + return; + } + } + _saveBioText = text; + _saveBioDone = std::move(done); + _saveBioRequestId = request(MTPaccount_UpdateProfile( + MTP_flags(MTPaccount_UpdateProfile::Flag::f_about), + MTPstring(), + MTPstring(), + MTP_string(text) + )).done([=](const MTPUser &result) { + _saveBioRequestId = 0; + + App::feedUsers(MTP_vector(1, result)); + App::self()->setAbout(_saveBioText); + if (_saveBioDone) { + _saveBioDone(); + } + }).fail([=](const RPCError &error) { + _saveBioRequestId = 0; + }).send(); +} + void ApiWrap::readServerHistory(not_null history) { if (history->unreadCount()) { readServerHistoryForce(history); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index fbcedf0e3..ab3f31d13 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -336,6 +336,8 @@ public: rpl::producer passwordState() const; base::optional passwordStateCurrent() const; + void saveSelfBio(const QString &text, FnMut done); + ~ApiWrap(); private: @@ -672,4 +674,8 @@ private: std::unique_ptr _passwordState; rpl::event_stream _passwordStateChanges; + mtpRequestId _saveBioRequestId = 0; + FnMut _saveBioDone; + QString _saveBioText; + }; diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 5c77ec5e2..bcbab78c5 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -34,17 +34,18 @@ namespace { constexpr auto kMaxGroupChannelTitle = 255; // See also edit_peer_info_box. constexpr auto kMaxChannelDescription = 255; // See also edit_peer_info_box. -constexpr auto kMaxBioLength = 70; constexpr auto kMinUsernameLength = 5; +} // namespace + style::InputField CreateBioFieldStyle() { auto result = st::newGroupDescription; - result.textMargins.setRight(st::boxTextFont->spacew + st::boxTextFont->width(QString::number(kMaxBioLength))); + result.textMargins.setRight( + st::boxTextFont->spacew + + st::boxTextFont->width(QString::number(kMaxBioLength))); return result; } -} // namespace - QString PeerFloodErrorText(PeerFloodType type) { auto link = textcmdLink( Messenger::Instance().createInternalLinkFull(qsl("spambot")), diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index 582fb2004..9fd65912c 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class ConfirmBox; class PeerListBox; +constexpr auto kMaxBioLength = 70; + +style::InputField CreateBioFieldStyle(); + namespace Ui { class FlatLabel; class InputField; diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 4c67885a0..59a3250c9 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -155,6 +155,10 @@ infoTopBarCall: IconButton(infoTopBarNotifications) { icon: icon {{ "top_bar_call", boxTitleCloseFg }}; iconOver: icon {{ "top_bar_call", boxTitleCloseFgOver }}; } +infoTopBarSave: IconButton(infoTopBarNotifications) { + icon: icon {{ "passport_ready", windowActiveTextFg }}; + iconOver: icon {{ "passport_ready", windowActiveTextFg}}; +} infoTopBarForward: IconButton(infoTopBarBack) { width: 46px; icon: icon {{ "info_media_forward", boxTitleCloseFg }}; @@ -222,6 +226,10 @@ infoLayerTopBarCall: IconButton(infoLayerTopBarNotifications) { icon: icon {{ "top_bar_call", boxTitleCloseFg }}; iconOver: icon {{ "top_bar_call", boxTitleCloseFgOver }}; } +infoLayerTopBarSave: IconButton(infoLayerTopBarNotifications) { + icon: icon {{ "passport_ready", windowActiveTextFg }}; + iconOver: icon {{ "passport_ready", windowActiveTextFg }}; +} infoLayerTopBarForward: IconButton(infoLayerTopBarBack) { width: 45px; icon: icon {{ "info_media_forward", boxTitleCloseFg }}; diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index f0f5d808d..bad0918eb 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -222,6 +222,14 @@ rpl::producer ContentWidget::selectedListValue() const { return rpl::single(SelectedItems(Storage::SharedMediaType::Photo)); } +rpl::producer ContentWidget::canSaveChanges() const { + return rpl::single(false); +} + +void ContentWidget::saveChanges(FnMut done) { + done(); +} + void ContentWidget::refreshSearchField(bool shown) { auto search = _controller->searchFieldController(); if (search && shown) { diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index 7b97a8b99..b964e9cb7 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -73,6 +73,9 @@ public: virtual void cancelSelection() { } + virtual rpl::producer canSaveChanges() const; + virtual void saveChanges(FnMut done); + protected: template Widget *setInnerWidget(object_ptr inner) { diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp index 4a8835486..ee2d134ea 100644 --- a/Telegram/SourceFiles/info/info_controller.cpp +++ b/Telegram/SourceFiles/info/info_controller.cpp @@ -269,6 +269,14 @@ rpl::producer Controller::mediaSource( limitAfter); } +void Controller::setCanSaveChanges(rpl::producer can) { + _canSaveChanges = std::move(can); +} + +rpl::producer Controller::canSaveChanges() const { + return _canSaveChanges.value(); +} + Controller::~Controller() = default; } // namespace Info diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index e6094b2b3..fc8b217a0 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -188,6 +188,9 @@ public: return base::take(_searchStartsFocused); } + void setCanSaveChanges(rpl::producer can); + rpl::producer canSaveChanges() const; + void saveSearchState(not_null memento); void showSection( @@ -218,6 +221,7 @@ private: std::unique_ptr _searchFieldController; std::unique_ptr _searchController; rpl::variable _seachEnabledByContent = false; + rpl::variable _canSaveChanges = false; bool _searchStartsFocused = false; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 2aa23e577..763f06284 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -152,6 +152,13 @@ Ui::FadeWrap *TopBar::pushButton( return weak; } +void TopBar::forceButtonVisibility( + Ui::FadeWrap *button, + rpl::producer shown) { + _updateControlCallbacks.erase(button); + button->toggleOn(std::move(shown)); +} + void TopBar::setSearchField( base::unique_qptr field, rpl::producer &&shown, diff --git a/Telegram/SourceFiles/info/info_top_bar.h b/Telegram/SourceFiles/info/info_top_bar.h index 3b28dcf5c..e83a03167 100644 --- a/Telegram/SourceFiles/info/info_top_bar.h +++ b/Telegram/SourceFiles/info/info_top_bar.h @@ -56,6 +56,17 @@ public: return result; } + template + ButtonWidget *addButtonWithVisibility( + base::unique_qptr button, + rpl::producer shown) { + auto result = button.get(); + forceButtonVisibility( + pushButton(std::move(button)), + std::move(shown)); + return result; + } + void createSearchView( not_null controller, rpl::producer &&shown, @@ -79,7 +90,11 @@ private: void updateControlsGeometry(int newWidth); void updateDefaultControlsGeometry(int newWidth); void updateSelectionControlsGeometry(int newWidth); - Ui::FadeWrap *pushButton(base::unique_qptr button); + Ui::FadeWrap *pushButton( + base::unique_qptr button); + void forceButtonVisibility( + Ui::FadeWrap *button, + rpl::producer shown); void removeButton(not_null button); void startHighlightAnimation(); void updateControlsVisibility(anim::type animated); diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index e81066d0f..4defacf4b 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -387,6 +387,9 @@ void WrapWidget::createTopBar() { } else if (section.type() == Section::Type::Settings && section.settingsType() == Section::SettingsType::Main) { addTopBarMenuButton(); + } else if (section.type() == Section::Type::Settings + && section.settingsType() == Section::SettingsType::Information) { + addContentSaveButton(); } _topBar->lower(); @@ -409,6 +412,23 @@ void WrapWidget::addTopBarMenuButton() { }); } +void WrapWidget::addContentSaveButton() { + Expects(_topBar != nullptr); + + _topBar->addButtonWithVisibility( + base::make_unique_q( + _topBar, + (wrap() == Wrap::Layer + ? st::infoLayerTopBarSave + : st::infoTopBarSave)), + _controller->canSaveChanges() + )->addClickHandler([=] { + _content->saveChanges(crl::guard(_content.data(), [=] { + _controller->showBackFromStack(); + })); + }); +} + void WrapWidget::addProfileCallsButton() { Expects(_topBar != nullptr); diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h index 3badfa09c..b4a8b0074 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.h +++ b/Telegram/SourceFiles/info/info_wrap_widget.h @@ -192,6 +192,7 @@ private: bool requireTopBarSearch() const; void addTopBarMenuButton(); + void addContentSaveButton(); void addProfileCallsButton(); void addProfileNotificationsButton(); void showTopBarMenu(); diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index eb32d25e4..6d571ab33 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -274,12 +274,13 @@ Cover *Cover::setOnlineCount(rpl::producer &&count) { void Cover::initViewers() { using Flag = Notify::PeerUpdate::Flag; - Notify::PeerUpdateValue( - _peer, - Flag::NameChanged - ) | rpl::start_with_next( - [this] { refreshNameText(); }, - lifetime()); + NameValue( + _peer + ) | rpl::start_with_next([=](const TextWithEntities &name) { + _name->setText(name.text); + refreshNameGeometry(width()); + }, lifetime()); + Notify::PeerUpdateValue( _peer, Flag::UserOnlineChanged | Flag::MembersChanged @@ -331,11 +332,6 @@ void Cover::setVerified(bool verified) { refreshNameGeometry(width()); } -void Cover::refreshNameText() { - _name->setText(App::peerName(_peer)); - refreshNameGeometry(width()); -} - void Cover::refreshStatusText() { auto hasMembersLink = [&] { if (auto megagroup = _peer->asMegagroup()) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index bf089e542..4993f5245 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -76,7 +76,6 @@ public: private: void setupChildGeometry(); void initViewers(); - void refreshNameText(); void refreshStatusText(); void refreshNameGeometry(int newWidth); void refreshStatusGeometry(int newWidth); diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index de133e4c8..2bf13aaf9 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -23,6 +23,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Info { namespace Profile { +rpl::producer NameValue( + not_null peer) { + return Notify::PeerUpdateValue( + peer, + Notify::PeerUpdate::Flag::NameChanged + ) | rpl::map([=] { + return App::peerName(peer); + }) | WithEmptyEntities(); +} + rpl::producer PhoneValue( not_null user) { return Notify::PeerUpdateValue( diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index 91709631c..1526e18e6 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -42,6 +42,8 @@ inline auto ToUpperValue() { }); } +rpl::producer NameValue( + not_null peer); rpl::producer PhoneValue( not_null user); rpl::producer BioValue( diff --git a/Telegram/SourceFiles/info/settings/info_settings_widget.cpp b/Telegram/SourceFiles/info/settings/info_settings_widget.cpp index 6914c2269..a9bb64cbd 100644 --- a/Telegram/SourceFiles/info/settings/info_settings_widget.cpp +++ b/Telegram/SourceFiles/info/settings/info_settings_widget.cpp @@ -41,16 +41,18 @@ Widget::Widget( not_null controller) : ContentWidget(parent, controller) , _self(controller->key().settingsSelf()) -, _type(controller->section().settingsType()) { - const auto inner = setInnerWidget(::Settings::CreateSection( +, _type(controller->section().settingsType()) +, _inner(setInnerWidget(::Settings::CreateSection( _type, this, controller->parentController(), - _self)); - inner->sectionShowOther( + _self))) { + _inner->sectionShowOther( ) | rpl::start_with_next([=](Type type) { this->controller()->showSettings(type); - }, inner->lifetime()); + }, _inner->lifetime()); + + controller->setCanSaveChanges(_inner->sectionCanSaveChanges()); } not_null Widget::self() const { @@ -76,6 +78,14 @@ void Widget::setInternalState( restoreState(memento); } +rpl::producer Widget::canSaveChanges() const { + return _inner->sectionCanSaveChanges(); +} + +void Widget::saveChanges(FnMut done) { + _inner->sectionSaveChanges(std::move(done)); +} + std::unique_ptr Widget::doCreateMemento() { auto result = std::make_unique(self(), _type); saveState(result.get()); diff --git a/Telegram/SourceFiles/info/settings/info_settings_widget.h b/Telegram/SourceFiles/info/settings/info_settings_widget.h index e4eaba05c..11bf1b1fb 100644 --- a/Telegram/SourceFiles/info/settings/info_settings_widget.h +++ b/Telegram/SourceFiles/info/settings/info_settings_widget.h @@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_content_widget.h" #include "info/info_controller.h" +namespace Settings { +class Section; +} // namespace Settings + namespace Info { namespace Settings { @@ -59,6 +63,9 @@ public: const QRect &geometry, not_null memento); + rpl::producer canSaveChanges() const override; + void saveChanges(FnMut done) override; + private: void saveState(not_null memento); void restoreState(not_null memento); @@ -68,6 +75,8 @@ private: not_null _self; Type _type = Type(); + not_null<::Settings::Section*> _inner; + }; } // namespace Settings diff --git a/Telegram/SourceFiles/rpl/variable.h b/Telegram/SourceFiles/rpl/variable.h index f5d46c799..81ac32b86 100644 --- a/Telegram/SourceFiles/rpl/variable.h +++ b/Telegram/SourceFiles/rpl/variable.h @@ -114,6 +114,7 @@ public: | start_with_next([this](auto &&data) { assign(std::forward(data)); }, _lifetime); + return *this; } Type current() const { diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 1b653d99c..c0faba4d9 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -76,3 +76,27 @@ settingsCloudPasswordLabel: FlatLabel(defaultFlatLabel) { maxHeight: 20px; } settingsCloudPasswordLabelPadding: margins(22px, 8px, 10px, 8px); + +settingsInfoRowHeight: 62px; +settingsInfoIconPosition: point(24px, 16px); +settingsInfoValue: FlatLabel(defaultFlatLabel) { + textFg: windowFg; + style: boxTextStyle; + maxHeight: 20px; +} +settingsInfoValuePosition: point(78px, 14px); +settingsInfoAbout: FlatLabel(settingsInfoValue) { + textFg: windowSubTextFg; + style: defaultTextStyle; +} +settingsInfoAboutPosition: point(78px, 33px); +settingsInfoRightSkip: 60px; +settingsInfoEditPosition: point(12px, 12px); +settingsInfoEdit: IconButton(defaultIconButton) { +} + +settingsBioMargins: margins(20px, 2px, 20px, 2px); +settingsBioCountdown: FlatLabel(defaultFlatLabel) { + style: boxTextStyle; + textFg: windowSubTextFg; +} diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h index 045219c1b..27a5a5e24 100644 --- a/Telegram/SourceFiles/settings/settings_common.h +++ b/Telegram/SourceFiles/settings/settings_common.h @@ -49,6 +49,12 @@ public: virtual rpl::producer sectionShowOther() { return rpl::never(); } + virtual rpl::producer sectionCanSaveChanges() { + return rpl::single(false); + } + virtual void sectionSaveChanges(FnMut done) { + done(); + } }; diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index 341314ded..92a2a6d82 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -8,12 +8,253 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_information.h" #include "settings/settings_common.h" -#include "boxes/abstract_box.h" #include "ui/wrap/vertical_layout.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "boxes/add_contact_box.h" +#include "boxes/change_phone_box.h" +#include "boxes/username_box.h" +#include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" +#include "auth_session.h" +#include "apiwrap.h" #include "styles/style_settings.h" +#include "styles/style_old_settings.h" namespace Settings { +namespace { + +void AddRow( + not_null container, + rpl::producer label, + rpl::producer value, + const QString ©Text, + const style::IconButton &editSt, + Fn edit, + const style::icon &icon) { + const auto wrap = container->add(object_ptr( + container, + st::settingsInfoRowHeight)); + + wrap->paintRequest( + ) | rpl::start_with_next([=, &icon] { + Painter p(wrap); + icon.paint(p, st::settingsInfoIconPosition, wrap->width()); + }, wrap->lifetime()); + + auto existing = base::duplicate( + value + ) | rpl::map([](const TextWithEntities &text) { + return text.entities.isEmpty(); + }); + const auto text = Ui::CreateChild( + wrap, + std::move(value), + st::settingsInfoValue); + text->setClickHandlerFilter([=](auto&&...) { + edit(); + return false; + }); + base::duplicate( + existing + ) | rpl::start_with_next([=](bool existing) { + text->setSelectable(existing); + text->setDoubleClickSelectsParagraph(existing); + text->setContextCopyText(existing ? copyText : QString()); + }, text->lifetime()); + + const auto about = Ui::CreateChild( + wrap, + std::move(label), + st::settingsInfoAbout); + + const auto button = Ui::CreateChild( + wrap, + editSt); + button->addClickHandler(edit); + button->showOn(std::move(existing)); + + wrap->widthValue( + ) | rpl::start_with_next([=](int width) { + text->resizeToWidth(width + - st::settingsInfoValuePosition.x() + - st::settingsInfoRightSkip); + text->moveToLeft( + st::settingsInfoValuePosition.x(), + st::settingsInfoValuePosition.y(), + width); + about->resizeToWidth(width + - st::settingsInfoAboutPosition.x() + - st::settingsInfoRightSkip); + about->moveToLeft( + st::settingsInfoAboutPosition.x(), + st::settingsInfoAboutPosition.y(), + width); + button->moveToRight( + st::settingsInfoEditPosition.x(), + st::settingsInfoEditPosition.y(), + width); + }, wrap->lifetime()); +} + +void SetupRows( + not_null container, + not_null self) { + AddDivider(container); + AddSkip(container); + + AddRow( + container, + Lang::Viewer(lng_settings_name_label), + Info::Profile::NameValue(self), + lang(lng_profile_copy_fullname), + st::settingsEditButton, + [=] { Ui::show(Box(self)); }, + st::settingsEditButton.icon); + + AddRow( + container, + Lang::Viewer(lng_settings_phone_label), + Info::Profile::PhoneValue(self), + lang(lng_profile_copy_phone), + st::settingsEditButton, + [] { Ui::show(Box()); }, + st::settingsEditButton.icon); + + auto username = Info::Profile::UsernameValue(self); + auto empty = base::duplicate( + username + ) | rpl::map([](const TextWithEntities &username) { + return username.text.isEmpty(); + }); + auto label = rpl::combine( + Lang::Viewer(lng_settings_username_label), + std::move(empty) + ) | rpl::map([](const QString &label, bool empty) { + return empty ? "t.me/username" : label; + }); + auto value = rpl::combine( + std::move(username), + Lang::Viewer(lng_settings_username_add) + ) | rpl::map([](const TextWithEntities &username, const QString &add) { + if (!username.text.isEmpty()) { + return username; + } + auto result = TextWithEntities{ add }; + result.entities.push_back(EntityInText( + EntityInTextCustomUrl, + 0, + add.size(), + "internal:edit_username")); + return result; + }); + AddRow( + container, + std::move(label), + std::move(value), + lang(lng_context_copy_mention), + st::settingsEditButton, + [=] { Ui::show(Box()); }, + st::settingsEditButton.icon); + + AddSkip(container); +} + +struct BioManager { + rpl::producer canSave; + Fn done)> save; +}; + +BioManager SetupBio( + not_null container, + not_null self) { + AddDivider(container); + AddSkip(container); + + const auto bioStyle = [] { + auto result = CreateBioFieldStyle(); + return result; + }; + const auto style = Ui::AttachAsChild(container, bioStyle()); + const auto current = Ui::AttachAsChild(container, self->about()); + const auto changed = Ui::AttachAsChild( + container, + rpl::event_stream()); + const auto bio = container->add( + object_ptr( + container, + *style, + Ui::InputField::Mode::MultiLine, + langFactory(lng_bio_placeholder), + *current), + st::settingsBioMargins); + + const auto countdown = Ui::CreateChild( + container.get(), + QString(), + Ui::FlatLabel::InitType::Simple, + st::settingsBioCountdown); + + rpl::combine( + bio->geometryValue(), + countdown->widthValue() + ) | rpl::start_with_next([=](QRect geometry, int width) { + countdown->move( + geometry.x() + geometry.width() - width, + geometry.y() + style->textMargins.top()); + }, countdown->lifetime()); + + const auto updated = [=] { + auto text = bio->getLastText(); + if (text.indexOf('\n') >= 0) { + auto position = bio->textCursor().position(); + bio->setText(text.replace('\n', ' ')); + auto cursor = bio->textCursor(); + cursor.setPosition(position); + bio->setTextCursor(cursor); + } + changed->fire(*current != text); + const auto countLeft = qMax(kMaxBioLength - text.size(), 0); + countdown->setText(QString::number(countLeft)); + }; + const auto save = [=](FnMut done) { + Auth().api().saveSelfBio( + TextUtilities::PrepareForSending(bio->getLastText()), + std::move(done)); + }; + + Info::Profile::BioValue( + self + ) | rpl::start_with_next([=](const TextWithEntities &text) { + *current = text.text; + changed->fire(*current != bio->getLastText()); + }, bio->lifetime()); + + bio->setMaxLength(kMaxBioLength); + bio->setSubmitSettings(Ui::InputField::SubmitSettings::Both); + auto cursor = bio->textCursor(); + cursor.setPosition(bio->getLastText().size()); + bio->setTextCursor(cursor); + QObject::connect(bio, &Ui::InputField::submitted, [=] { + save(nullptr); + }); + QObject::connect(bio, &Ui::InputField::changed, updated); + bio->setInstantReplaces(Ui::InstantReplaces::Default()); + bio->setInstantReplacesEnabled(Global::ReplaceEmojiValue()); + updated(); + + AddSkip(container); + AddDividerText(container, Lang::Viewer(lng_settings_about_bio)); + + return BioManager{ + changed->events() | rpl::distinct_until_changed(), + save + }; +} + +} // namespace Information::Information(QWidget *parent, not_null self) : Section(parent) @@ -21,10 +262,21 @@ Information::Information(QWidget *parent, not_null self) setupContent(); } +rpl::producer Information::sectionCanSaveChanges() { + return _canSaveChanges.value(); +} + +void Information::sectionSaveChanges(FnMut done) { + _save(std::move(done)); +} + void Information::setupContent() { const auto content = Ui::CreateChild(this); - content->add(object_ptr(content)); + SetupRows(content, _self); + auto manager = SetupBio(content, _self); + _canSaveChanges = std::move(manager.canSave); + _save = std::move(manager.save); Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/settings/settings_information.h b/Telegram/SourceFiles/settings/settings_information.h index c5150e2ee..0e1fd01cf 100644 --- a/Telegram/SourceFiles/settings/settings_information.h +++ b/Telegram/SourceFiles/settings/settings_information.h @@ -15,10 +15,15 @@ class Information : public Section { public: Information(QWidget *parent, not_null self); + rpl::producer sectionCanSaveChanges() override; + void sectionSaveChanges(FnMut done) override; + private: void setupContent(); not_null _self; + rpl::variable _canSaveChanges; + Fn done)> _save; }; diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index f1c2073fe..91cd012f7 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common.h" #include "settings/settings_codes.h" -#include "boxes/abstract_box.h" #include "boxes/language_box.h" #include "boxes/confirm_box.h" #include "boxes/about_box.h"