From f31be7784b9ef0790408a353e3d53e6022553d1f Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 15 Mar 2022 15:12:49 +0400 Subject: [PATCH] Redesign edit account settings page. --- .../icons/settings/settings_edit.png | Bin 318 -> 0 bytes .../icons/settings/settings_edit@2x.png | Bin 647 -> 0 bytes .../icons/settings/settings_edit@3x.png | Bin 788 -> 0 bytes .../icons/settings/settings_username.png | Bin 992 -> 0 bytes .../icons/settings/settings_username@2x.png | Bin 2878 -> 0 bytes .../icons/settings/settings_username@3x.png | Bin 3135 -> 0 bytes .../info/profile/info_profile_values.cpp | 1 + .../info/profile/info_profile_values.h | 68 +- Telegram/SourceFiles/settings/settings.style | 51 +- .../settings/settings_information.cpp | 616 +++++++++++++++--- .../settings/settings_information.h | 21 + .../SourceFiles/window/window_main_menu.cpp | 394 +---------- .../SourceFiles/window/window_main_menu.h | 14 - 13 files changed, 613 insertions(+), 552 deletions(-) delete mode 100644 Telegram/Resources/icons/settings/settings_edit.png delete mode 100644 Telegram/Resources/icons/settings/settings_edit@2x.png delete mode 100644 Telegram/Resources/icons/settings/settings_edit@3x.png delete mode 100644 Telegram/Resources/icons/settings/settings_username.png delete mode 100644 Telegram/Resources/icons/settings/settings_username@2x.png delete mode 100644 Telegram/Resources/icons/settings/settings_username@3x.png diff --git a/Telegram/Resources/icons/settings/settings_edit.png b/Telegram/Resources/icons/settings/settings_edit.png deleted file mode 100644 index fad4f64fa136fc0b5f1bd390cd11e8715015705c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 318 zcmV-E0m1%>P)vn)f`btsAgwrxWQfxhp-IX@mu(-canXRER-;kquuFoaDb`$8mhYrfJ%X>4Q?rJZV47E;i=b55r^%&nzn!=-;*aq0@U>xqGNy?hb*rD1 zSgpL6;Ud}V7Wn%0*QzJQ6W*NI8KW1mHf&L!41>e^>&&fAja71&9^A<@UvoYC;*PxS z+E>MU>lVxPvzyKC&4^ikdEy%#C%z?_QW9-~z1dr%f*xI9%AD19H&0#adXCxBV+)z9 z_Qu_K|9w$mhg|DD<}W{M{;5nAI=V<^$MeS9Z^KO11!$aTt(MdHcIC{|qM}Fh*yZ@zH^KC71p zWmUfBF6h{}Pa^q%#+JH{dkz{tucCz)1I1qSCC`O-g-ONLazTq{_3k-DxOLgcFb7!+AiT< z)cWfIYr{BAw4VLV{I}+8+T>#swd9`64PAeo+5fY_m6r7@9|hivd|D*A>~dyGK*Rm( zSG<-wElA9^kU8cg%fr_BMtQpVw@E6te{$^RZ$6nK^t1fLrk1&sOVy(NqO6~9)-HQqnILh@JLFuY)Qp?#p(3m~xB2#}Rn24HwT^vX zo!RWh%vqUA&@`z~h&lvHXI-Wj|M2JYS&E^Bye>$f( zZEDbz`%=HGvd!>lQxcED=}ixQ|Bg0UR1v`AbFT85!O?~!-_JaApFMKmHaUB=SuAH( zH>>Ug6LI#a3z>RsTMk^dslCwT|GC7`K4I2!_Enj;)O|jGarplG@A4!y<|?bX_YR%8 z$l%3pa&G&>cc*WfoHxthTV7e_SZsB6Gv}<&j8k+kFo6`>%~wtoOJJ~FN#L)XYh3Ob6Mw<&;$TQzGid) diff --git a/Telegram/Resources/icons/settings/settings_username.png b/Telegram/Resources/icons/settings/settings_username.png deleted file mode 100644 index 0f55678e93e151bb5996d821dd7c40902d82143d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 992 zcmV<610Vc}P)oI$ z3h9HWg%~rF+Jw$b=iNNX(=&ZOix%&D;B3x)&AH}WbKloJ3;@CpM&D18{XYn~uYiV! zhcP=li@D#bQ)0mq}GsQdO0j%_fPW zNTMiGv)Sy`%jGh~VzCc<{mQo&%w{uXG8s}7rPnZ>PE#NdAh+91Znv8PfdFl7ZPD@Z zF=?7cilR_9nrMx)W*11~Z%3`5y$mJ~%HUDv5zuT#BVCtcTj z-;H6Ip1dzF9F0alC(P&bq^c@yZf^E!f*_EhC?tv^B@zkp`F!N_`6!V{kSL0zC<+OJ z(8F(RY>=v|l+WisC#+Vh)ND4%@Ane`;y8|q#UkmtPHSsxh6d~F>!j;C9UUF@cBkL( zr)INB)oRs%H#m*WW`m|_xV*ds0Jz<5jE;^%mSybh?EDEwp-_M<%NQ9Mfy?Cr09;*N zLDMwYY&HY_U27xy`X%!G{0xrc!1Fu+;Qsy|x~`+YzyEVWS(d?Z9LC1R004J)cc|Cv z7#bQvI2<-K2#3QM92`VnUmqBT0RT804zMf>UDpkG7QjEh7lOecCMG8E@bG~B{e9@V z4wK1*U@(Zu$w^o&7Cb#Y!R2ycetsT;Ab{gISS%I{3=BY$BrGp4!|V0p`uZB%+uH`x zH;={1$;p?FMXS}KQmNE)nMR{Q-ENm2A0H_mj~h0PVZz(nTZBR(jE|3lVHg}99s&UN z_V&jA1&%gaBx1X{l#z zb8~ZFwl)Cd^?J!_wf?<%%d$+VRO&`Sr$CcgJoH89M>}kmSxdyw~@=`&}cM1oR0a${A2Oo|4saH!hZoE6&L6e;#AQ9 O0000 diff --git a/Telegram/Resources/icons/settings/settings_username@2x.png b/Telegram/Resources/icons/settings/settings_username@2x.png deleted file mode 100644 index 7125e84bcc1ca47b0d66967679ac5f696d97778f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2878 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 diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index e89de330e8..0e20488917 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_session.h" #include "boxes/peers/edit_peer_permissions_box.h" +#include "base/unixtime.h" namespace Info { namespace Profile { diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index b3313db446..90791754d6 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -38,37 +38,55 @@ inline auto ToSingleLine() { rpl::producer> MigratedOrMeValue( not_null peer); -rpl::producer NameValue(not_null peer); -rpl::producer PhoneValue(not_null user); -rpl::producer PhoneOrHiddenValue(not_null user); -rpl::producer UsernameValue(not_null user); +[[nodiscard]] rpl::producer NameValue( + not_null peer); +[[nodiscard]] rpl::producer PhoneValue( + not_null user); +[[nodiscard]] rpl::producer PhoneOrHiddenValue( + not_null user); +[[nodiscard]] rpl::producer UsernameValue( + not_null user); [[nodiscard]] TextWithEntities AboutWithEntities( not_null peer, const QString &value); -rpl::producer AboutValue(not_null peer); -rpl::producer LinkValue(not_null peer); -rpl::producer LocationValue( +[[nodiscard]] rpl::producer AboutValue( + not_null peer); +[[nodiscard]] rpl::producer LinkValue(not_null peer); +[[nodiscard]] rpl::producer LocationValue( not_null channel); -rpl::producer NotificationsEnabledValue(not_null peer); -rpl::producer IsContactValue(not_null user); -rpl::producer CanInviteBotToGroupValue(not_null user); -rpl::producer CanShareContactValue(not_null user); -rpl::producer CanAddContactValue(not_null user); -rpl::producer AmInChannelValue(not_null channel); -rpl::producer MembersCountValue(not_null peer); -rpl::producer PendingRequestsCountValue(not_null peer); -rpl::producer AdminsCountValue(not_null peer); -rpl::producer RestrictionsCountValue(not_null peer); -rpl::producer RestrictedCountValue(not_null channel); -rpl::producer KickedCountValue(not_null channel); -rpl::producer SharedMediaCountValue( +[[nodiscard]] rpl::producer NotificationsEnabledValue( + not_null peer); +[[nodiscard]] rpl::producer IsContactValue(not_null user); +[[nodiscard]] rpl::producer CanInviteBotToGroupValue( + not_null user); +[[nodiscard]] rpl::producer CanShareContactValue( + not_null user); +[[nodiscard]] rpl::producer CanAddContactValue( + not_null user); +[[nodiscard]] rpl::producer AmInChannelValue( + not_null channel); +[[nodiscard]] rpl::producer MembersCountValue(not_null peer); +[[nodiscard]] rpl::producer PendingRequestsCountValue( + not_null peer); +[[nodiscard]] rpl::producer AdminsCountValue(not_null peer); +[[nodiscard]] rpl::producer RestrictionsCountValue( + not_null peer); +[[nodiscard]] rpl::producer RestrictedCountValue( + not_null channel); +[[nodiscard]] rpl::producer KickedCountValue( + not_null channel); +[[nodiscard]] rpl::producer SharedMediaCountValue( not_null peer, PeerData *migrated, Storage::SharedMediaType type); -rpl::producer CommonGroupsCountValue(not_null user); -rpl::producer CanAddMemberValue(not_null peer); -rpl::producer FullReactionsCountValue(not_null peer); -rpl::producer AllowedReactionsCountValue(not_null peer); +[[nodiscard]] rpl::producer CommonGroupsCountValue( + not_null user); +[[nodiscard]] rpl::producer CanAddMemberValue( + not_null peer); +[[nodiscard]] rpl::producer FullReactionsCountValue( + not_null peer); +[[nodiscard]] rpl::producer AllowedReactionsCountValue( + not_null peer); enum class Badge { None, @@ -76,7 +94,7 @@ enum class Badge { Scam, Fake, }; -rpl::producer BadgeValue(not_null peer); +[[nodiscard]] rpl::producer BadgeValue(not_null peer); } // namespace Profile } // namespace Info diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 0738868a46..b2390d04bc 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -90,8 +90,6 @@ settingsIconPosition: icon {{ "settings/position", settingsIconFg }}; settingsIconPin: icon {{ "settings/pin", settingsIconFg }}; settingsIconDownload: icon {{ "settings/download", settingsIconFg }}; -settingsSetPhotoSkip: 7px; - settingsCheckbox: Checkbox(defaultBoxCheckbox) { textPosition: point(15px, 1px); } @@ -138,35 +136,16 @@ settingsCloudPasswordLabel: FlatLabel(defaultFlatLabel) { } settingsCloudPasswordLabelPadding: margins(22px, 8px, 10px, 8px); -settingsInfoPhotoHeight: 175px; -settingsInfoPhotoSize: 84px; +settingsInfoPhotoHeight: 161px; +settingsInfoPhotoSize: 100px; settingsInfoPhoto: UserpicButton(defaultUserpicButton) { size: size(settingsInfoPhotoSize, settingsInfoPhotoSize); photoSize: settingsInfoPhotoSize; } -settingsInfoPhotoTop: 17px; -settingsInfoPhotoSkip: 16px; -settingsInfoPhotoSet: defaultActiveButton; +settingsInfoPhotoTop: 0px; +settingsInfoPhotoSkip: 7px; +settingsInfoNameSkip: -1px; -settingsInfoRow: SettingsButton(settingsButtonNoIcon) { - height: 62px; - padding: margins(0px, 0px, 0px, 0px); -} -settingsInfoIconPosition: point(22px, 18px); -settingsInfoValue: FlatLabel(defaultFlatLabel) { - textFg: windowFg; - style: boxTextStyle; - maxHeight: 20px; -} -settingsInfoValuePosition: point(78px, 14px); -settingsInfoAbout: FlatLabel(settingsInfoValue) { - textFg: windowSubTextFg; - style: defaultTextStyle; -} -settingsInfoAboutPosition: point(78px, 34px); -settingsInfoRightSkip: 60px; -settingsInfoEditRight: 14px; -settingsInfoEditIconOver: icon {{ "settings/settings_edit", menuIconFgOver }}; settingsBio: InputField(defaultInputField) { textBg: transparent; textMargins: margins(0px, 7px, 0px, 13px); @@ -178,21 +157,31 @@ settingsBio: InputField(defaultInputField) { placeholderScale: 0.; placeholderFont: normalFont; + border: 0px; + borderActive: 0px; + heightMin: 32px; font: boxTextFont; } settingsInfoAfterSkip: 14px; -settingsInfoName: icon {{ "settings/settings_name", menuIconFg }}; -settingsInfoPhone: icon {{ "settings/settings_phone_number", menuIconFg }}; -settingsInfoUsername: icon {{ "settings/settings_username", menuIconFg }}; -settingsBioMargins: margins(20px, 6px, 20px, 6px); +settingsBioMargins: margins(22px, 6px, 22px, 4px); settingsBioCountdown: FlatLabel(defaultFlatLabel) { style: boxTextStyle; textFg: windowSubTextFg; } -settingsBioLabelPadding: margins(22px, 11px, 22px, 0px); + +settingsCoverName: FlatLabel(defaultFlatLabel) { + style: TextStyle(defaultTextStyle) { + font: font(17px semibold); + linkFont: font(17px semibold); + linkFontOver: font(17px semibold); + } +} +settingsCoverStatus: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; +} settingsPrivacyEditLabelPadding: margins(22px, 11px, 22px, 11px); diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index b43fbd52b6..340e523c2c 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -10,31 +10,44 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "editor/photo_editor_layer_widget.h" #include "settings/settings_common.h" #include "ui/wrap/vertical_layout.h" +#include "ui/wrap/vertical_layout_reorder.h" #include "ui/wrap/padding_wrap.h" +#include "ui/wrap/slide_wrap.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/box_content_divider.h" +#include "ui/boxes/confirm_box.h" +#include "ui/text/text_utilities.h" #include "ui/special_buttons.h" #include "core/application.h" #include "core/core_settings.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "boxes/add_contact_box.h" -#include "ui/boxes/confirm_box.h" #include "boxes/change_phone_box.h" #include "boxes/username_box.h" +#include "data/data_session.h" #include "data/data_user.h" +#include "data/data_peer_values.h" +#include "data/data_changes.h" +#include "dialogs/ui/dialogs_layout.h" #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" +#include "main/main_account.h" #include "main/main_session.h" +#include "main/main_domain.h" +#include "mtproto/mtproto_dc_options.h" #include "window/window_session_controller.h" +#include "window/window_peer_menu.h" #include "apiwrap.h" #include "api/api_peer_photo.h" #include "core/file_utilities.h" #include "base/call_delayed.h" +#include "base/unixtime.h" #include "styles/style_layers.h" #include "styles/style_settings.h" +#include "styles/style_menu_icons.h" #include #include @@ -44,11 +57,65 @@ namespace { constexpr auto kSaveBioTimeout = 1000; +class AccountsList final { +public: + AccountsList( + not_null container, + not_null controller); + + [[nodiscard]] rpl::producer<> currentAccountActivations() const; + +private: + void setup(); + + [[nodiscard]] not_null*> setupAdd(); + void rebuild(); + + const not_null _controller; + const not_null _outer; + int _outerIndex = 0; + + Ui::SlideWrap *_addAccount = nullptr; + base::flat_map< + not_null, + base::unique_qptr> _watched; + + base::unique_qptr _contextMenu; + std::unique_ptr _reorder; + int _reordering = 0; + + rpl::event_stream<> _currentAccountActivations; + + base::binary_guard _accountSwitchGuard; + +}; + +[[nodiscard]] rpl::producer StatusValue( + not_null user) { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + const auto timer = lifetime.make_state(); + const auto push = [=] { + const auto now = base::unixtime::now(); + consumer.put_next(Data::OnlineTextActive(user, now) + ? Ui::Text::Link(Data::OnlineText(user, now)) + : Ui::Text::WithEntities(Data::OnlineText(user, now))); + timer->callOnce(Data::OnlineChangeTimeout(user, now)); + }; + timer->setCallback(push); + user->session().changes().peerFlagsValue( + user, + Data::PeerUpdate::Flag::OnlineStatus + ) | rpl::start_with_next(push, lifetime); + return lifetime; + }; +} + void SetupPhoto( not_null container, not_null controller, not_null self) { - const auto wrap = container->add(object_ptr( + const auto wrap = container->add(object_ptr( container, st::settingsInfoPhotoHeight)); const auto photo = Ui::CreateChild( @@ -57,33 +124,34 @@ void SetupPhoto( self, Ui::UserpicButton::Role::OpenPhoto, st::settingsInfoPhoto); - const auto upload = Ui::CreateChild( + + const auto name = Ui::CreateChild( wrap, - tr::lng_settings_upload(), - st::settingsInfoPhotoSet); - upload->setFullRadius(true); - upload->addClickHandler([=] { - auto callback = [=](QImage &&image) { - self->session().api().peerPhoto().upload(self, std::move(image)); - }; - Editor::PrepareProfilePhotoFromFile( - upload, - &controller->window(), - std::move(callback)); - }); + Info::Profile::NameValue(self), + st::settingsCoverName); + const auto status = Ui::CreateChild( + wrap, + StatusValue(self), + st::settingsCoverStatus); rpl::combine( wrap->widthValue(), photo->widthValue(), - upload->widthValue() - ) | rpl::start_with_next([=](int max, int photoWidth, int uploadWidth) { + name->widthValue(), + status->widthValue() + ) | rpl::start_with_next([=]( + int max, + int photoWidth, + int nameWidth, + int statusWidth) { photo->moveToLeft( (max - photoWidth) / 2, st::settingsInfoPhotoTop); - upload->moveToLeft( - (max - uploadWidth) / 2, - (st::settingsInfoPhotoTop - + photo->height() - + st::settingsInfoPhotoSkip)); + name->moveToLeft( + (max - nameWidth) / 2, + (photo->y() + photo->height() + st::settingsInfoPhotoSkip)); + status->moveToLeft( + (max - statusWidth) / 2, + (name->y() + name->height() + st::settingsInfoNameSkip)); }, photo->lifetime()); } @@ -105,12 +173,13 @@ void AddRow( rpl::producer value, const QString ©Button, Fn edit, - const style::icon &icon) { - const auto wrap = AddButton( + IconDescriptor &&descriptor) { + const auto wrap = AddButtonWithLabel( container, - rpl::single(QString()), - st::settingsInfoRow, - { .icon = &icon }); + std::move(label), + std::move(value) | rpl::map([](const auto &t) { return t.text; }), + st::settingsButton, + std::move(descriptor)); const auto forcopy = Ui::CreateChild(wrap.get()); wrap->setAcceptBoth(); wrap->clicks( @@ -136,62 +205,6 @@ void AddRow( }) | rpl::start_with_next([=](const TextWithEntities &text) { *forcopy = text.text; }, wrap->lifetime()); - const auto text = Ui::CreateChild( - wrap.get(), - std::move(value), - st::settingsInfoValue); - text->setClickHandlerFilter([=](auto&&...) { - edit(); - return false; - }); - base::duplicate( - existing - ) | rpl::start_with_next([=](bool existing) { - wrap->setDisabled(!existing); - text->setAttribute(Qt::WA_TransparentForMouseEvents, existing); - text->setSelectable(existing); - text->setDoubleClickSelectsParagraph(existing); - }, text->lifetime()); - - const auto about = Ui::CreateChild( - wrap.get(), - std::move(label), - st::settingsInfoAbout); - about->setAttribute(Qt::WA_TransparentForMouseEvents); - - const auto button = Ui::CreateChild(wrap.get()); - button->resize(st::settingsInfoEditIconOver.size()); - button->setAttribute(Qt::WA_TransparentForMouseEvents); - button->paintRequest( - ) | rpl::filter([=] { - return (wrap->isOver() || wrap->isDown()) && !wrap->isDisabled(); - }) | rpl::start_with_next([=](QRect clip) { - Painter p(button); - st::settingsInfoEditIconOver.paint(p, QPoint(), button->width()); - }, button->lifetime()); - - wrap->sizeValue( - ) | rpl::start_with_next([=](QSize size) { - const auto width = size.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::settingsInfoEditRight, - (size.height() - button->height()) / 2, - width); - }, wrap->lifetime()); } void SetupRows( @@ -208,7 +221,7 @@ void SetupRows( Info::Profile::NameValue(self), tr::lng_profile_copy_fullname(tr::now), [=] { controller->show(Box(self)); }, - st::settingsInfoName); + { &st::settingsIconUser, kIconLightBlue }); AddRow( container, @@ -216,7 +229,7 @@ void SetupRows( Info::Profile::PhoneValue(self), tr::lng_profile_copy_phone(tr::now), [=] { controller->show(Box(controller)); }, - st::settingsInfoPhone); + { &st::settingsIconCalls, kIconGreen }); auto username = Info::Profile::UsernameValue(self); auto empty = base::duplicate( @@ -251,17 +264,14 @@ void SetupRows( std::move(value), tr::lng_context_copy_mention(tr::now), [=] { controller->show(Box(session)); }, - st::settingsInfoUsername); + { &st::settingsIconArchive, kIconLightOrange }); - AddSkip(container, st::settingsInfoAfterSkip); + AddSkip(container); } void SetupBio( not_null container, not_null self) { - AddDivider(container); - AddSkip(container); - const auto bioStyle = [] { auto result = st::settingsBio; result.textMargins.setRight(st::boxTextFont->spacew @@ -372,14 +382,345 @@ void SetupBio( &self->session()); updated(); - container->add( - object_ptr( - container, - tr::lng_settings_about_bio(), - st::boxDividerLabel), - st::settingsBioLabelPadding); + AddDividerText(container, tr::lng_settings_about_bio()); +} +void SetupAccountsWrap( + not_null container, + not_null controller) { AddSkip(container); + AddDivider(container); + AddSkip(container); + + SetupAccounts(container, controller); +} + +[[nodiscard]] bool IsAltShift(Qt::KeyboardModifiers modifiers) { + return (modifiers & Qt::ShiftModifier) && (modifiers & Qt::AltModifier); +} + +[[nodiscard]] object_ptr MakeAccountButton( + QWidget *parent, + not_null window, + not_null account, + Fn callback) { + const auto active = (account == &Core::App().activeAccount()); + const auto session = &account->session(); + const auto user = session->user(); + + auto text = rpl::single( + user->name + ) | rpl::then(session->changes().realtimeNameUpdates( + user + ) | rpl::map([=] { + return user->name; + })); + auto result = object_ptr( + parent, + std::move(text), + st::mainMenuAddAccountButton); + const auto raw = result.data(); + + struct State { + State(QWidget *parent) : userpic(parent) { + userpic.setAttribute(Qt::WA_TransparentForMouseEvents); + } + + Ui::RpWidget userpic; + std::shared_ptr view; + base::unique_qptr menu; + }; + const auto state = raw->lifetime().make_state(raw); + + if (!active) { + AddUnreadBadge(raw, rpl::single(rpl::empty) | rpl::then( + session->data().unreadBadgeChanges() + ) | rpl::map([=] { + auto &owner = session->data(); + return UnreadBadge{ + owner.unreadBadge(), + owner.unreadBadgeMuted(), + }; + })); + } + + const auto userpicSkip = 2 * st::mainMenuAccountLine + st::lineWidth; + const auto userpicSize = st::mainMenuAccountSize + + userpicSkip * 2; + raw->heightValue( + ) | rpl::start_with_next([=](int height) { + const auto left = st::mainMenuAddAccountButton.iconLeft + + (st::mainMenuAddAccount.width() - userpicSize) / 2; + const auto top = (height - userpicSize) / 2; + state->userpic.setGeometry(left, top, userpicSize, userpicSize); + }, state->userpic.lifetime()); + + state->userpic.paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(&state->userpic); + const auto size = st::mainMenuAccountSize; + const auto line = st::mainMenuAccountLine; + const auto skip = 2 * line + st::lineWidth; + const auto full = size + skip * 2; + user->paintUserpicLeft(p, state->view, skip, skip, full, size); + if (active) { + const auto shift = st::lineWidth + (line * 0.5); + const auto diameter = full - 2 * shift; + const auto rect = QRectF(shift, shift, diameter, diameter); + auto hq = PainterHighQualityEnabler(p); + auto pen = st::windowBgActive->p; // The same as '+' in add. + pen.setWidthF(line); + p.setPen(pen); + p.setBrush(Qt::NoBrush); + p.drawEllipse(rect); + } + }, state->userpic.lifetime()); + + raw->setAcceptBoth(true); + raw->clicks( + ) | rpl::start_with_next([=](Qt::MouseButton which) { + if (which == Qt::LeftButton) { + callback(); + return; + } else if (which != Qt::RightButton) { + return; + } + const auto addAction = [&]( + const QString &text, + Fn callback, + const style::icon *icon) { + return state->menu->addAction( + text, + crl::guard(raw, std::move(callback)), + icon); + }; + if (!state->menu && IsAltShift(raw->clickModifiers())) { + state->menu = base::make_unique_q( + raw, + st::popupMenuWithIcons); + Window::MenuAddMarkAsReadAllChatsAction(window, addAction); + state->menu->popup(QCursor::pos()); + return; + } + if (&session->account() == &Core::App().activeAccount() + || state->menu) { + return; + } + state->menu = base::make_unique_q( + raw, + st::popupMenuWithIcons); + addAction(tr::lng_menu_activate(tr::now), [=] { + Core::App().domain().activate(&session->account()); + }, &st::menuIconProfile); + addAction(tr::lng_settings_logout(tr::now), [=] { + const auto callback = [=](Fn &&close) { + close(); + Core::App().logoutWithChecks(&session->account()); + }; + window->show( + Ui::MakeConfirmBox({ + .text = tr::lng_sure_logout(), + .confirmed = crl::guard(session, callback), + .confirmText = tr::lng_settings_logout(), + .confirmStyle = &st::attentionBoxButton, + }), + Ui::LayerOption::CloseOther); + }, &st::menuIconLeave); + state->menu->popup(QCursor::pos()); + }, raw->lifetime()); + + return result; +} + +[[nodiscard]] std::vector> OrderedAccounts() { + using namespace Main; + + const auto order = Core::App().settings().accountsOrder(); + auto accounts = ranges::views::all( + Core::App().domain().accounts() + ) | ranges::views::transform([](const Domain::AccountWithIndex &a) { + return not_null{ a.account.get() }; + }) | ranges::to_vector; + ranges::stable_sort(accounts, [&]( + not_null a, + not_null b) { + const auto aIt = a->sessionExists() + ? ranges::find(order, a->session().uniqueId()) + : end(order); + const auto bIt = b->sessionExists() + ? ranges::find(order, b->session().uniqueId()) + : end(order); + return aIt < bIt; + }); + return accounts; +} + +AccountsList::AccountsList( + not_null container, + not_null controller) +: _controller(controller) +, _outer(container) +, _outerIndex(container->count()) { + setup(); +} + +rpl::producer<> AccountsList::currentAccountActivations() const { + return _currentAccountActivations.events(); +} + +void AccountsList::setup() { + _addAccount = setupAdd(); + + rpl::single(rpl::empty) | rpl::then( + Core::App().domain().accountsChanges() + ) | rpl::start_with_next([=] { + const auto &list = Core::App().domain().accounts(); + const auto exists = [&](not_null account) { + for (const auto &[index, existing] : list) { + if (account == existing.get()) { + return true; + } + } + return false; + }; + for (auto i = _watched.begin(); i != _watched.end();) { + if (!exists(i->first)) { + i = _watched.erase(i); + } else { + ++i; + } + } + for (const auto &[index, account] : list) { + if (_watched.emplace(account.get()).second) { + account->sessionChanges( + ) | rpl::start_with_next([=](Main::Session *session) { + rebuild(); + }, _outer->lifetime()); + } + } + rebuild(); + }, _outer->lifetime()); +} + + +not_null*> AccountsList::setupAdd() { + const auto result = _outer->add( + object_ptr>( + _outer.get(), + CreateButton( + _outer.get(), + tr::lng_menu_add_account(), + st::mainMenuAddAccountButton, + { + &st::mainMenuAddAccount, + 0, + IconType::Round, + &st::windowBgActive + })))->setDuration(0); + const auto button = result->entity(); + + const auto add = [=](MTP::Environment environment) { + Core::App().preventOrInvoke([=] { + Core::App().domain().addActivated(environment); + }); + }; + + button->setAcceptBoth(true); + button->clicks( + ) | rpl::start_with_next([=](Qt::MouseButton which) { + if (which == Qt::LeftButton) { + add(MTP::Environment::Production); + return; + } else if (which != Qt::RightButton + || !IsAltShift(button->clickModifiers())) { + return; + } + _contextMenu = base::make_unique_q(_outer); + _contextMenu->addAction("Production Server", [=] { + add(MTP::Environment::Production); + }); + _contextMenu->addAction("Test Server", [=] { + add(MTP::Environment::Test); + }); + _contextMenu->popup(QCursor::pos()); + }, button->lifetime()); + + return result; +} + +void AccountsList::rebuild() { + const auto inner = _outer->insert( + _outerIndex, + object_ptr(_outer.get())); + + _reorder = std::make_unique(inner); + _reorder->updates( + ) | rpl::start_with_next([=](Ui::VerticalLayoutReorder::Single data) { + using State = Ui::VerticalLayoutReorder::State; + if (data.state == State::Started) { + ++_reordering; + } else { + Ui::PostponeCall(inner, [=] { + --_reordering; + }); + if (data.state == State::Applied) { + std::vector order; + order.reserve(inner->count()); + for (auto i = 0; i < inner->count(); i++) { + for (const auto &[account, button] : _watched) { + if (button.get() == inner->widgetAt(i)) { + order.push_back(account->session().uniqueId()); + } + } + } + Core::App().settings().setAccountsOrder(order); + Core::App().saveSettings(); + } + } + }, inner->lifetime()); + + const auto list = OrderedAccounts(); + for (const auto &account : list) { + auto i = _watched.find(account); + Assert(i != _watched.end()); + + auto &button = i->second; + if (!account->sessionExists() || list.size() == 1) { + button = nullptr; + } else if (!button) { + auto callback = [=] { + if (_reordering) { + return; + } + if (account == &Core::App().domain().active()) { + _currentAccountActivations.fire({}); + return; + } + auto activate = [=, guard = _accountSwitchGuard.make_guard()]{ + if (guard) { + _reorder->finishReordering(); + Core::App().domain().maybeActivate(account); + } + }; + base::call_delayed( + st::defaultRippleAnimation.hideDuration, + account, + std::move(activate)); + }; + button.reset(inner->add(MakeAccountButton( + inner, + _controller, + account, + std::move(callback)))); + } + } + inner->resizeToWidth(_outer->width()); + + _addAccount->toggle( + (inner->count() < Main::Domain::kMaxAccounts), + anim::type::instant); + + _reorder->start(); } } // namespace @@ -397,10 +738,91 @@ void Information::setupContent( const auto self = controller->session().user(); SetupPhoto(content, controller, self); - SetupRows(content, controller, self); SetupBio(content, self); + SetupRows(content, controller, self); + SetupAccountsWrap(content, controller); Ui::ResizeFitChild(this, content); } +AccountsEvents SetupAccounts( + not_null container, + not_null controller) { + const auto list = container->lifetime().make_state( + container, + controller); + return { + .currentAccountActivations = list->currentAccountActivations(), + }; +} + +Dialogs::Ui::UnreadBadgeStyle BadgeStyle() { + auto result = Dialogs::Ui::UnreadBadgeStyle(); + result.font = st::mainMenuBadgeFont; + result.size = st::mainMenuBadgeSize; + result.sizeId = Dialogs::Ui::UnreadBadgeInMainMenu; + return result; +} + +void AddUnreadBadge( + not_null button, + rpl::producer value) { + struct State { + State(QWidget *parent) : widget(parent) { + widget.setAttribute(Qt::WA_TransparentForMouseEvents); + } + + Ui::RpWidget widget; + Dialogs::Ui::UnreadBadgeStyle st = BadgeStyle(); + int count = 0; + QString string; + }; + const auto state = button->lifetime().make_state(button); + + std::move( + value + ) | rpl::start_with_next([=](UnreadBadge badge) { + state->st.muted = badge.muted; + state->count = badge.count; + if (!state->count) { + state->widget.hide(); + return; + } + state->string = Lang::FormatCountToShort(state->count).string; + state->widget.resize(CountUnreadBadgeSize(state->string, state->st)); + if (state->widget.isHidden()) { + state->widget.show(); + } + }, state->widget.lifetime()); + + state->widget.paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(&state->widget); + Dialogs::Ui::PaintUnreadBadge( + p, + state->string, + state->widget.width(), + 0, + state->st); + }, state->widget.lifetime()); + + rpl::combine( + button->sizeValue(), + state->widget.sizeValue(), + state->widget.shownValue() + ) | rpl::start_with_next([=](QSize outer, QSize inner, bool shown) { + auto padding = button->st().padding; + if (shown) { + state->widget.moveToRight( + padding.right(), + (outer.height() - inner.height()) / 2, + outer.width()); + padding.setRight(padding.right() + + inner.width() + + button->st().style.font->spacew); + } + button->setPaddingOverride(padding); + }, state->widget.lifetime()); +} + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_information.h b/Telegram/SourceFiles/settings/settings_information.h index 085f39ff96..21fbb081b5 100644 --- a/Telegram/SourceFiles/settings/settings_information.h +++ b/Telegram/SourceFiles/settings/settings_information.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common.h" +namespace Dialogs::Ui { +struct UnreadBadgeStyle; +} // namespace Dialogs::Ui + namespace Settings { class Information : public Section { @@ -22,4 +26,21 @@ private: }; +struct AccountsEvents { + rpl::producer<> currentAccountActivations; +}; +AccountsEvents SetupAccounts( + not_null container, + not_null controller); + +[[nodiscard]] Dialogs::Ui::UnreadBadgeStyle BadgeStyle(); + +struct UnreadBadge { + int count = 0; + bool muted = false; +}; +void AddUnreadBadge( + not_null button, + rpl::producer value); + } // namespace Settings diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 4a031a393a..a05a1ca9e1 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_account.h" #include "support/support_templates.h" #include "settings/settings_common.h" +#include "settings/settings_information.h" #include "base/qt_signal_producer.h" #include "boxes/about_box.h" #include "ui/boxes/confirm_box.h" @@ -66,10 +67,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Window { namespace { -[[nodiscard]] bool IsAltShift(Qt::KeyboardModifiers modifiers) { - return (modifiers & Qt::ShiftModifier) && (modifiers & Qt::AltModifier); -} - void ShowCallsBox(not_null window) { auto controller = std::make_unique(window); const auto initBox = [ @@ -111,234 +108,6 @@ void ShowCallsBox(not_null window) { window->show(Box(std::move(controller), initBox)); } -[[nodiscard]] std::vector> OrderedAccounts() { - const auto order = Core::App().settings().accountsOrder(); - auto accounts = ranges::views::all( - Core::App().domain().accounts() - ) | ranges::views::transform([](const Main::Domain::AccountWithIndex &a) { - return not_null{ a.account.get() }; - }) | ranges::to_vector; - ranges::stable_sort(accounts, [&]( - not_null a, - not_null b) { - const auto aIt = a->sessionExists() - ? ranges::find(order, a->session().uniqueId()) - : end(order); - const auto bIt = b->sessionExists() - ? ranges::find(order, b->session().uniqueId()) - : end(order); - return aIt < bIt; - }); - return accounts; -} - -struct UnreadBadge { - int count = 0; - bool muted = false; -}; - -[[nodiscard]] Dialogs::Ui::UnreadBadgeStyle BadgeStyle() { - auto result = Dialogs::Ui::UnreadBadgeStyle(); - result.font = st::mainMenuBadgeFont; - result.size = st::mainMenuBadgeSize; - result.sizeId = Dialogs::Ui::UnreadBadgeInMainMenu; - return result; -} - -void AddUnreadBadge( - not_null button, - rpl::producer value) { - struct State { - State(QWidget *parent) : widget(parent) { - widget.setAttribute(Qt::WA_TransparentForMouseEvents); - } - - Ui::RpWidget widget; - Dialogs::Ui::UnreadBadgeStyle st = BadgeStyle(); - int count = 0; - QString string; - }; - const auto state = button->lifetime().make_state(button); - - std::move( - value - ) | rpl::start_with_next([=](UnreadBadge badge) { - state->st.muted = badge.muted; - state->count = badge.count; - if (!state->count) { - state->widget.hide(); - return; - } - state->string = Lang::FormatCountToShort(state->count).string; - state->widget.resize(CountUnreadBadgeSize(state->string, state->st)); - if (state->widget.isHidden()) { - state->widget.show(); - } - }, state->widget.lifetime()); - - state->widget.paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(&state->widget); - Dialogs::Ui::PaintUnreadBadge( - p, - state->string, - state->widget.width(), - 0, - state->st); - }, state->widget.lifetime()); - - rpl::combine( - button->sizeValue(), - state->widget.sizeValue(), - state->widget.shownValue() - ) | rpl::start_with_next([=](QSize outer, QSize inner, bool shown) { - auto padding = button->st().padding; - if (shown) { - state->widget.moveToRight( - padding.right(), - (outer.height() - inner.height()) / 2, - outer.width()); - padding.setRight(padding.right() - + inner.width() - + button->st().style.font->spacew); - } - button->setPaddingOverride(padding); - }, state->widget.lifetime()); -} - -[[nodiscard]] object_ptr MakeAccountButton( - QWidget *parent, - not_null window, - not_null account, - Fn callback) { - const auto active = (account == &Core::App().activeAccount()); - const auto session = &account->session(); - const auto user = session->user(); - - auto text = rpl::single( - user->name - ) | rpl::then(session->changes().realtimeNameUpdates( - user - ) | rpl::map([=] { - return user->name; - })); - auto result = object_ptr( - parent, - std::move(text), - st::mainMenuAddAccountButton); - const auto raw = result.data(); - - struct State { - State(QWidget *parent) : userpic(parent) { - userpic.setAttribute(Qt::WA_TransparentForMouseEvents); - } - - Ui::RpWidget userpic; - std::shared_ptr view; - base::unique_qptr menu; - }; - const auto state = raw->lifetime().make_state(raw); - - if (!active) { - AddUnreadBadge(raw, rpl::single(rpl::empty) | rpl::then( - session->data().unreadBadgeChanges() - ) | rpl::map([=] { - auto &owner = session->data(); - return UnreadBadge{ - owner.unreadBadge(), - owner.unreadBadgeMuted(), - }; - })); - } - - const auto userpicSkip = 2 * st::mainMenuAccountLine + st::lineWidth; - const auto userpicSize = st::mainMenuAccountSize - + userpicSkip * 2; - raw->heightValue( - ) | rpl::start_with_next([=](int height) { - const auto left = st::mainMenuAddAccountButton.iconLeft - + (st::mainMenuAddAccount.width() - userpicSize) / 2; - const auto top = (height - userpicSize) / 2; - state->userpic.setGeometry(left, top, userpicSize, userpicSize); - }, state->userpic.lifetime()); - - state->userpic.paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(&state->userpic); - const auto size = st::mainMenuAccountSize; - const auto line = st::mainMenuAccountLine; - const auto skip = 2 * line + st::lineWidth; - const auto full = size + skip * 2; - user->paintUserpicLeft(p, state->view, skip, skip, full, size); - if (active) { - const auto shift = st::lineWidth + (line * 0.5); - const auto diameter = full - 2 * shift; - const auto rect = QRectF(shift, shift, diameter, diameter); - auto hq = PainterHighQualityEnabler(p); - auto pen = st::windowBgActive->p; // The same as '+' in add. - pen.setWidthF(line); - p.setPen(pen); - p.setBrush(Qt::NoBrush); - p.drawEllipse(rect); - } - }, state->userpic.lifetime()); - - raw->setAcceptBoth(true); - raw->clicks( - ) | rpl::start_with_next([=](Qt::MouseButton which) { - if (which == Qt::LeftButton) { - callback(); - return; - } else if (which != Qt::RightButton) { - return; - } - const auto addAction = [&]( - const QString &text, - Fn callback, - const style::icon *icon) { - return state->menu->addAction( - text, - crl::guard(raw, std::move(callback)), - icon); - }; - if (!state->menu && IsAltShift(raw->clickModifiers())) { - state->menu = base::make_unique_q( - raw, - st::popupMenuWithIcons); - MenuAddMarkAsReadAllChatsAction(window, addAction); - state->menu->popup(QCursor::pos()); - return; - } - if (&session->account() == &Core::App().activeAccount() - || state->menu) { - return; - } - state->menu = base::make_unique_q( - raw, - st::popupMenuWithIcons); - addAction(tr::lng_menu_activate(tr::now), [=] { - Core::App().domain().activate(&session->account()); - }, &st::menuIconProfile); - addAction(tr::lng_settings_logout(tr::now), [=] { - const auto callback = [=](Fn &&close) { - close(); - Core::App().logoutWithChecks(&session->account()); - }; - window->show( - Ui::MakeConfirmBox({ - .text = tr::lng_sure_logout(), - .confirmed = crl::guard(session, callback), - .confirmText = tr::lng_settings_logout(), - .confirmStyle = &st::attentionBoxButton, - }), - Ui::LayerOption::CloseOther); - }, &st::menuIconLeave); - state->menu->popup(QCursor::pos()); - }, raw->lifetime()); - - return result; -} - } // namespace class MainMenu::ToggleAccountsButton final : public Ui::AbstractButton { @@ -462,7 +231,7 @@ void MainMenu::ToggleAccountsButton::paintUnreadBadge(Painter &p) { return; } - auto st = BadgeStyle(); + auto st = Settings::BadgeStyle(); const auto right = width() - st::mainMenuTogglePosition.x() - st::mainMenuToggleSize * 3; @@ -484,7 +253,7 @@ void MainMenu::ToggleAccountsButton::validateUnreadBadge() { } _unreadBadge = computeUnreadBadge(); - auto st = BadgeStyle(); + auto st = Settings::BadgeStyle(); _rightSkip = base + Dialogs::Ui::CountUnreadBadgeSize(_unreadBadge, st).width() + 2 * st::mainMenuToggleSize; @@ -739,6 +508,7 @@ void MainMenu::setupArchive() { return folder && (folder->id() == Data::Folder::kId); }) | rpl::take(1); + using namespace Settings; AddUnreadBadge(button, rpl::single(rpl::empty) | rpl::then(std::move( folderValue ) | rpl::map([=](not_null folder) { @@ -780,38 +550,14 @@ void MainMenu::setupAccounts() { const auto inner = _accounts->entity(); inner->add(object_ptr(inner, st::mainMenuSkip)); - _addAccount = setupAddAccount(inner); + auto events = Settings::SetupAccounts(inner, _controller); inner->add(object_ptr(inner, st::mainMenuSkip)); - rpl::single(rpl::empty) | rpl::then( - Core::App().domain().accountsChanges() + std::move( + events.currentAccountActivations ) | rpl::start_with_next([=] { - const auto &list = Core::App().domain().accounts(); - const auto exists = [&](not_null account) { - for (const auto &[index, existing] : list) { - if (account == existing.get()) { - return true; - } - } - return false; - }; - for (auto i = _watched.begin(); i != _watched.end();) { - if (!exists(i->first)) { - i = _watched.erase(i); - } else { - ++i; - } - } - for (const auto &[index, account] : list) { - if (_watched.emplace(account.get()).second) { - account->sessionChanges( - ) | rpl::start_with_next([=](Main::Session *session) { - rebuildAccounts(); - }, lifetime()); - } - } - rebuildAccounts(); - }, lifetime()); + closeLayer(); + }, inner->lifetime()); _accounts->toggleOn(Core::App().settings().mainMenuAccountsShownValue()); _accounts->finishAnimating(); @@ -819,128 +565,6 @@ void MainMenu::setupAccounts() { _shadow->setDuration(0)->toggleOn(_accounts->shownValue()); } -void MainMenu::rebuildAccounts() { - const auto inner = _accounts->entity()->insert( - 1, // After skip with the fixed height. - object_ptr(_accounts.get())); - - _reorder = std::make_unique(inner); - _reorder->updates( - ) | rpl::start_with_next([=](Ui::VerticalLayoutReorder::Single data) { - using State = Ui::VerticalLayoutReorder::State; - if (data.state == State::Started) { - ++_reordering; - } else { - Ui::PostponeCall(inner, [=] { - --_reordering; - }); - if (data.state == State::Applied) { - std::vector order; - order.reserve(inner->count()); - for (auto i = 0; i < inner->count(); i++) { - for (const auto &[account, button] : _watched) { - if (button.get() == inner->widgetAt(i)) { - order.push_back(account->session().uniqueId()); - } - } - } - Core::App().settings().setAccountsOrder(order); - Core::App().saveSettings(); - } - } - }, inner->lifetime()); - - for (const auto &account : OrderedAccounts()) { - auto i = _watched.find(account); - Assert(i != _watched.end()); - - auto &button = i->second; - if (!account->sessionExists()) { - button = nullptr; - } else if (!button) { - auto callback = [=] { - if (_reordering) { - return; - } - if (account == &Core::App().domain().active()) { - closeLayer(); - return; - } - auto activate = [=, guard = _accountSwitchGuard.make_guard()]{ - if (guard) { - _reorder->finishReordering(); - Core::App().domain().maybeActivate(account); - } - }; - base::call_delayed( - st::defaultRippleAnimation.hideDuration, - account, - std::move(activate)); - }; - button.reset(inner->add(MakeAccountButton( - inner, - _controller, - account, - std::move(callback)))); - } - } - inner->resizeToWidth(_accounts->width()); - - _addAccount->toggle( - (inner->count() < Main::Domain::kMaxAccounts), - anim::type::instant); - - _reorder->start(); -} - -not_null*> MainMenu::setupAddAccount( - not_null container) { - using namespace Settings; - - const auto result = container->add( - object_ptr>( - container.get(), - CreateButton( - container.get(), - tr::lng_menu_add_account(), - st::mainMenuAddAccountButton, - { - &st::mainMenuAddAccount, - 0, - IconType::Round, - &st::windowBgActive - })))->setDuration(0); - const auto button = result->entity(); - - const auto add = [=](MTP::Environment environment) { - Core::App().preventOrInvoke([=] { - Core::App().domain().addActivated(environment); - }); - }; - - button->setAcceptBoth(true); - button->clicks( - ) | rpl::start_with_next([=](Qt::MouseButton which) { - if (which == Qt::LeftButton) { - add(MTP::Environment::Production); - return; - } else if (which != Qt::RightButton - || !IsAltShift(button->clickModifiers())) { - return; - } - _contextMenu = base::make_unique_q(this); - _contextMenu->addAction("Production Server", [=] { - add(MTP::Environment::Production); - }); - _contextMenu->addAction("Test Server", [=] { - add(MTP::Environment::Test); - }); - _contextMenu->popup(QCursor::pos()); - }, button->lifetime()); - - return result; -} - void MainMenu::setupAccountsToggle() { _toggleAccounts->show(); _toggleAccounts->setClickedCallback([=] { toggleAccounts(); }); diff --git a/Telegram/SourceFiles/window/window_main_menu.h b/Telegram/SourceFiles/window/window_main_menu.h index d9bbb03561..fcb9a2875b 100644 --- a/Telegram/SourceFiles/window/window_main_menu.h +++ b/Telegram/SourceFiles/window/window_main_menu.h @@ -20,7 +20,6 @@ class UserpicButton; class PopupMenu; class ScrollArea; class VerticalLayout; -class VerticalLayoutReorder; class RippleButton; class PlainShadow; class SettingsButton; @@ -57,12 +56,8 @@ private: void setupUserpicButton(); void setupAccounts(); void setupAccountsToggle(); - [[nodiscard]] auto setupAddAccount( - not_null container) - -> not_null*>; void setupArchive(); void setupMenu(); - void rebuildAccounts(); void updateControlsGeometry(); void updateInnerControlsGeometry(); void updatePhone(); @@ -75,12 +70,8 @@ private: object_ptr _resetScaleButton = { nullptr }; object_ptr _scroll; not_null _inner; - base::flat_map< - not_null, - base::unique_qptr> _watched; not_null _topShadowSkip; not_null*> _accounts; - Ui::SlideWrap *_addAccount = nullptr; not_null*> _shadow; not_null _menu; not_null _footer; @@ -91,11 +82,6 @@ private: base::Timer _nightThemeSwitch; base::unique_qptr _contextMenu; - std::unique_ptr _reorder; - int _reordering = 0; - - base::binary_guard _accountSwitchGuard; - QString _phoneText; };