| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171 |
- /*
- 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 "settings/settings_information.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/fields/input_field.h"
- #include "ui/widgets/popup_menu.h"
- #include "ui/widgets/box_content_divider.h"
- #include "ui/widgets/menu/menu_add_action_callback_factory.h"
- #include "ui/boxes/confirm_box.h"
- #include "ui/controls/userpic_button.h"
- #include "ui/text/text_utilities.h"
- #include "ui/delayed_activation.h"
- #include "ui/painter.h"
- #include "ui/vertical_list.h"
- #include "ui/unread_badge_paint.h"
- #include "ui/ui_utility.h"
- #include "core/application.h"
- #include "core/click_handler_types.h"
- #include "core/core_settings.h"
- #include "chat_helpers/emoji_suggestions_widget.h"
- #include "boxes/add_contact_box.h"
- #include "boxes/premium_limits_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 "data/data_channel.h"
- #include "data/data_premium_limits.h"
- #include "info/profile/info_profile_values.h"
- #include "info/profile/info_profile_badge.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_controller.h"
- #include "window/window_peer_menu.h"
- #include "apiwrap.h"
- #include "api/api_peer_photo.h"
- #include "api/api_user_names.h"
- #include "api/api_user_privacy.h"
- #include "base/call_delayed.h"
- #include "base/options.h"
- #include "base/unixtime.h"
- #include "base/random.h"
- #include "styles/style_chat.h" // popupMenuExpandedSeparator
- #include "styles/style_dialogs.h" // dialogsPremiumIcon
- #include "styles/style_layers.h"
- #include "styles/style_settings.h"
- #include "styles/style_menu_icons.h"
- #include "styles/style_window.h"
- #include <QtGui/QGuiApplication>
- #include <QtCore/QBuffer>
- namespace Settings {
- namespace {
- constexpr auto kSaveBioTimeout = 1000;
- constexpr auto kPlayStatusLimit = 2;
- class ComposedBadge final : public Ui::RpWidget {
- public:
- ComposedBadge(
- not_null<Ui::RpWidget*> parent,
- not_null<Ui::SettingsButton*> button,
- not_null<Main::Session*> session,
- rpl::producer<QString> &&text,
- bool hasUnread,
- Fn<bool()> animationPaused);
- private:
- rpl::variable<QString> _text;
- rpl::event_stream<int> _unreadWidth;
- rpl::event_stream<int> _premiumWidth;
- QPointer<Ui::RpWidget> _unread;
- Info::Profile::Badge _badge;
- };
- ComposedBadge::ComposedBadge(
- not_null<Ui::RpWidget*> parent,
- not_null<Ui::SettingsButton*> button,
- not_null<Main::Session*> session,
- rpl::producer<QString> &&text,
- bool hasUnread,
- Fn<bool()> animationPaused)
- : Ui::RpWidget(parent)
- , _text(std::move(text))
- , _badge(
- this,
- st::settingsInfoPeerBadge,
- session,
- Info::Profile::BadgeContentForPeer(session->user()),
- nullptr,
- std::move(animationPaused),
- kPlayStatusLimit,
- Info::Profile::BadgeType::Premium) {
- if (hasUnread) {
- _unread = CreateUnread(this, rpl::single(
- rpl::empty
- ) | rpl::then(
- session->data().unreadBadgeChanges()
- ) | rpl::map([=] {
- auto &owner = session->data();
- return Badge::UnreadBadge{
- owner.unreadBadge(),
- owner.unreadBadgeMuted(),
- };
- }));
- rpl::combine(
- _unread->shownValue(),
- _unread->widthValue()
- ) | rpl::map([=](bool shown, int width) {
- return shown ? width : 0;
- }) | rpl::start_to_stream(_unreadWidth, _unread->lifetime());
- }
- _badge.updated(
- ) | rpl::start_with_next([=] {
- if (const auto button = _badge.widget()) {
- button->widthValue(
- ) | rpl::start_to_stream(_premiumWidth, button->lifetime());
- } else {
- _premiumWidth.fire(0);
- }
- }, lifetime());
- auto textWidth = _text.value() | rpl::map([=] {
- return button->fullTextWidth();
- });
- rpl::combine(
- _unreadWidth.events_starting_with(_unread ? _unread->width() : 0),
- _premiumWidth.events_starting_with(_badge.widget()
- ? _badge.widget()->width()
- : 0),
- std::move(textWidth),
- button->sizeValue()
- ) | rpl::start_with_next([=](
- int unreadWidth,
- int premiumWidth,
- int textWidth,
- const QSize &buttonSize) {
- const auto &st = button->st();
- const auto skip = st.style.font->spacew;
- const auto textRightPosition = st.padding.left()
- + textWidth
- + skip;
- const auto minWidth = unreadWidth + premiumWidth + skip;
- const auto maxTextWidth = buttonSize.width()
- - minWidth
- - st.padding.right();
- const auto finalTextRight = std::min(textRightPosition, maxTextWidth);
- resize(
- buttonSize.width() - st.padding.right() - finalTextRight,
- buttonSize.height());
- _badge.move(
- 0,
- st.padding.top(),
- buttonSize.height() - st.padding.top());
- if (_unread) {
- _unread->moveToRight(
- 0,
- (buttonSize.height() - _unread->height()) / 2);
- }
- }, lifetime());
- }
- class AccountsList final {
- public:
- AccountsList(
- not_null<Ui::VerticalLayout*> container,
- not_null<Window::SessionController*> controller);
- [[nodiscard]] rpl::producer<> closeRequests() const;
- private:
- void setup();
- [[nodiscard]] not_null<Ui::SlideWrap<Ui::SettingsButton>*> setupAdd();
- void rebuild();
- const not_null<Window::SessionController*> _controller;
- const not_null<Ui::VerticalLayout*> _outer;
- int _outerIndex = 0;
- Ui::SlideWrap<Ui::SettingsButton> *_addAccount = nullptr;
- base::flat_map<
- not_null<Main::Account*>,
- base::unique_qptr<Ui::SettingsButton>> _watched;
- base::unique_qptr<Ui::PopupMenu> _contextMenu;
- std::unique_ptr<Ui::VerticalLayoutReorder> _reorder;
- int _reordering = 0;
- rpl::event_stream<> _closeRequests;
- base::binary_guard _accountSwitchGuard;
- };
- [[nodiscard]] rpl::producer<TextWithEntities> StatusValue(
- not_null<UserData*> user) {
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- const auto timer = lifetime.make_state<base::Timer>();
- 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<Ui::VerticalLayout*> container,
- not_null<Window::SessionController*> controller,
- not_null<UserData*> self) {
- const auto wrap = container->add(object_ptr<Ui::FixedHeightWidget>(
- container,
- st::settingsInfoPhotoHeight));
- const auto photo = Ui::CreateChild<Ui::UserpicButton>(
- wrap,
- controller,
- self,
- Ui::UserpicButton::Role::OpenPhoto,
- Ui::UserpicButton::Source::PeerPhoto,
- st::settingsInfoPhoto);
- const auto upload = CreateUploadSubButton(wrap, controller);
- upload->chosenImages(
- ) | rpl::start_with_next([=](Ui::UserpicButton::ChosenImage &&chosen) {
- auto &image = chosen.image;
- UpdatePhotoLocally(self, image);
- photo->showCustom(base::duplicate(image));
- self->session().api().peerPhoto().upload(
- self,
- {
- std::move(image),
- chosen.markup.documentId,
- chosen.markup.colors,
- });
- }, upload->lifetime());
- const auto name = Ui::CreateChild<Ui::FlatLabel>(
- wrap,
- Info::Profile::NameValue(self),
- st::settingsCoverName);
- const auto status = Ui::CreateChild<Ui::FlatLabel>(
- wrap,
- StatusValue(self),
- st::settingsCoverStatus);
- status->setAttribute(Qt::WA_TransparentForMouseEvents);
- rpl::combine(
- wrap->widthValue(),
- photo->widthValue(),
- Info::Profile::NameValue(self),
- status->widthValue()
- ) | rpl::start_with_next([=](
- int max,
- int photoWidth,
- const QString&,
- int statusWidth) {
- photo->moveToLeft(
- (max - photoWidth) / 2,
- st::settingsInfoPhotoTop);
- upload->moveToLeft(
- ((max - photoWidth) / 2
- + photoWidth
- - upload->width()
- + st::settingsInfoUploadLeft),
- photo->y() + photo->height() - upload->height());
- const auto skip = st::settingsButton.iconLeft;
- name->resizeToNaturalWidth(max - 2 * skip);
- name->moveToLeft(
- (max - name->width()) / 2,
- (photo->y() + photo->height() + st::settingsInfoPhotoSkip));
- status->moveToLeft(
- (max - statusWidth) / 2,
- (name->y() + name->height() + st::settingsInfoNameSkip));
- }, photo->lifetime());
- }
- void ShowMenu(
- QWidget *parent,
- const QString ©Button,
- const QString &text) {
- const auto menu = Ui::CreateChild<Ui::PopupMenu>(parent);
- menu->addAction(copyButton, [=] {
- QGuiApplication::clipboard()->setText(text);
- });
- menu->popup(QCursor::pos());
- }
- void AddRow(
- not_null<Ui::VerticalLayout*> container,
- rpl::producer<QString> label,
- rpl::producer<TextWithEntities> value,
- const QString ©Button,
- Fn<void()> edit,
- IconDescriptor &&descriptor) {
- const auto wrap = AddButtonWithLabel(
- container,
- std::move(label),
- std::move(value) | rpl::map([](const auto &t) { return t.text; }),
- st::settingsButton,
- std::move(descriptor));
- const auto forcopy = Ui::CreateChild<QString>(wrap.get());
- wrap->setAcceptBoth();
- wrap->clicks(
- ) | rpl::filter([=] {
- return !wrap->isDisabled();
- }) | rpl::start_with_next([=](Qt::MouseButton button) {
- if (button == Qt::LeftButton) {
- edit();
- } else if (!forcopy->isEmpty()) {
- ShowMenu(wrap, copyButton, *forcopy);
- }
- }, wrap->lifetime());
- auto existing = base::duplicate(
- value
- ) | rpl::map([](const TextWithEntities &text) {
- return text.entities.isEmpty();
- });
- base::duplicate(
- value
- ) | rpl::filter([](const TextWithEntities &text) {
- return text.entities.isEmpty();
- }) | rpl::start_with_next([=](const TextWithEntities &text) {
- *forcopy = text.text;
- }, wrap->lifetime());
- }
- void SetupBirthday(
- not_null<Ui::VerticalLayout*> container,
- not_null<Window::SessionController*> controller,
- not_null<UserData*> self) {
- const auto session = &self->session();
- Ui::AddSkip(container);
- auto value = rpl::combine(
- Info::Profile::BirthdayValue(self),
- tr::lng_settings_birthday_add()
- ) | rpl::map([](Data::Birthday birthday, const QString &add) {
- const auto text = Data::BirthdayText(birthday);
- return TextWithEntities{ !text.isEmpty() ? text : add };
- });
- const auto edit = [=] {
- Core::App().openInternalUrl(
- u"internal:edit_birthday"_q,
- QVariant::fromValue(ClickHandlerContext{
- .sessionWindow = base::make_weak(controller),
- }));
- };
- AddRow(
- container,
- tr::lng_settings_birthday_label(),
- std::move(value),
- tr::lng_mediaview_copy(tr::now),
- edit,
- { &st::menuIconGiftPremium });
- const auto key = Api::UserPrivacy::Key::Birthday;
- session->api().userPrivacy().reload(key);
- auto isExactlyContacts = session->api().userPrivacy().value(
- key
- ) | rpl::map([=](const Api::UserPrivacy::Rule &value) {
- return (value.option == Api::UserPrivacy::Option::Contacts)
- && value.always.peers.empty()
- && !value.always.premiums
- && value.never.peers.empty();
- }) | rpl::distinct_until_changed();
- Ui::AddSkip(container);
- Ui::AddDividerText(container, rpl::conditional(
- std::move(isExactlyContacts),
- tr::lng_settings_birthday_contacts(
- lt_link,
- tr::lng_settings_birthday_contacts_link(
- ) | Ui::Text::ToLink(u"internal:edit_privacy_birthday"_q),
- Ui::Text::WithEntities),
- tr::lng_settings_birthday_about(
- lt_link,
- tr::lng_settings_birthday_about_link(
- ) | Ui::Text::ToLink(u"internal:edit_privacy_birthday"_q),
- Ui::Text::WithEntities)));
- }
- void SetupPersonalChannel(
- not_null<Ui::VerticalLayout*> container,
- not_null<Window::SessionController*> controller,
- not_null<UserData*> self) {
- Ui::AddSkip(container);
- auto value = rpl::combine(
- Info::Profile::PersonalChannelValue(self),
- tr::lng_settings_channel_add()
- ) | rpl::map([](ChannelData *channel, const QString &add) {
- return TextWithEntities{ channel ? channel->name() : add };
- });
- const auto edit = [=] {
- Core::App().openInternalUrl(
- u"internal:edit_personal_channel"_q,
- QVariant::fromValue(ClickHandlerContext{
- .sessionWindow = base::make_weak(controller),
- }));
- };
- AddRow(
- container,
- tr::lng_settings_channel_label(),
- std::move(value),
- tr::lng_mediaview_copy(tr::now),
- edit,
- { &st::menuIconChannel });
- Ui::AddSkip(container);
- Ui::AddDivider(container);
- }
- void SetupRows(
- not_null<Ui::VerticalLayout*> container,
- not_null<Window::SessionController*> controller,
- not_null<UserData*> self) {
- const auto session = &self->session();
- Ui::AddSkip(container);
- AddRow(
- container,
- tr::lng_settings_name_label(),
- Info::Profile::NameValue(self) | Ui::Text::ToWithEntities(),
- tr::lng_profile_copy_fullname(tr::now),
- [=] { controller->show(Box<EditNameBox>(self)); },
- { &st::menuIconProfile });
- const auto showChangePhone = [=] {
- controller->show(
- Ui::MakeInformBox(tr::lng_change_phone_error()));
- controller->window().activate();
- };
- AddRow(
- container,
- tr::lng_settings_phone_label(),
- Info::Profile::PhoneValue(self),
- tr::lng_profile_copy_phone(tr::now),
- showChangePhone,
- { &st::menuIconPhone });
- auto username = Info::Profile::UsernameValue(self);
- auto empty = base::duplicate(
- username
- ) | rpl::map([](const TextWithEntities &username) {
- return username.text.isEmpty();
- });
- auto label = rpl::combine(
- tr::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),
- tr::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({
- EntityType::CustomUrl,
- 0,
- int(add.size()),
- "internal:edit_username" });
- return result;
- });
- session->api().usernames().requestToCache(session->user());
- AddRow(
- container,
- std::move(label),
- std::move(value),
- tr::lng_context_copy_mention(tr::now),
- [=] {
- const auto box = controller->show(
- Box(UsernamesBox, session->user()));
- box->boxClosing(
- ) | rpl::start_with_next([=] {
- session->api().usernames().requestToCache(session->user());
- }, box->lifetime());
- },
- { &st::menuIconUsername });
- Ui::AddSkip(container);
- Ui::AddDividerText(container, tr::lng_settings_username_about());
- }
- void SetupBio(
- not_null<Ui::VerticalLayout*> container,
- not_null<UserData*> self) {
- const auto limits = Data::PremiumLimits(&self->session());
- const auto defaultLimit = limits.aboutLengthDefault();
- const auto premiumLimit = limits.aboutLengthPremium();
- const auto bioStyle = [=] {
- auto result = st::settingsBio;
- result.textMargins.setRight(st::boxTextFont->spacew
- + st::boxTextFont->width('-' + QString::number(premiumLimit)));
- return result;
- };
- const auto style = Ui::AttachAsChild(container, bioStyle());
- const auto current = Ui::AttachAsChild(container, self->about());
- const auto changed = Ui::CreateChild<rpl::event_stream<bool>>(
- container.get());
- const auto bio = container->add(
- object_ptr<Ui::InputField>(
- container,
- *style,
- Ui::InputField::Mode::MultiLine,
- tr::lng_bio_placeholder(),
- *current),
- st::settingsBioMargins);
- const auto countdown = Ui::CreateChild<Ui::FlatLabel>(
- container.get(),
- QString(),
- 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 assign = [=](QString text) {
- auto position = bio->textCursor().position();
- bio->setText(text.replace('\n', ' '));
- auto cursor = bio->textCursor();
- cursor.setPosition(position);
- bio->setTextCursor(cursor);
- };
- const auto updated = [=] {
- auto text = bio->getLastText();
- if (text.indexOf('\n') >= 0) {
- assign(text);
- text = bio->getLastText();
- }
- changed->fire(*current != text);
- const auto limit = self->isPremium() ? premiumLimit : defaultLimit;
- const auto countLeft = limit - Ui::ComputeFieldCharacterCount(bio);
- countdown->setText(QString::number(countLeft));
- countdown->setTextColorOverride(
- countLeft < 0 ? st::boxTextFgError->c : std::optional<QColor>());
- };
- const auto save = [=] {
- self->session().api().saveSelfBio(
- TextUtilities::PrepareForSending(bio->getLastText()));
- };
- Info::Profile::AboutValue(
- self
- ) | rpl::start_with_next([=](const TextWithEntities &text) {
- const auto wasChanged = (*current != bio->getLastText());
- *current = text.text;
- if (wasChanged) {
- changed->fire(*current != bio->getLastText());
- } else {
- assign(text.text);
- *current = bio->getLastText();
- }
- }, bio->lifetime());
- const auto generation = Ui::CreateChild<int>(bio);
- changed->events(
- ) | rpl::start_with_next([=](bool changed) {
- if (changed) {
- const auto saved = *generation = std::abs(*generation) + 1;
- base::call_delayed(kSaveBioTimeout, bio, [=] {
- if (*generation == saved) {
- save();
- *generation = 0;
- }
- });
- } else if (*generation > 0) {
- *generation = -*generation;
- }
- }, bio->lifetime());
- // We need 'bio' to still exist here as InputField, so we add this
- // to 'container' lifetime, not to the 'bio' lifetime.
- container->lifetime().add([=] {
- if (*generation > 0) {
- save();
- }
- });
- bio->setMaxLength(premiumLimit * 2);
- bio->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
- auto cursor = bio->textCursor();
- cursor.setPosition(bio->getLastText().size());
- bio->setTextCursor(cursor);
- bio->submits() | rpl::start_with_next([=] { save(); }, bio->lifetime());
- bio->changes() | rpl::start_with_next(updated, bio->lifetime());
- bio->setInstantReplaces(Ui::InstantReplaces::Default());
- bio->setInstantReplacesEnabled(
- Core::App().settings().replaceEmojiValue());
- Ui::Emoji::SuggestionsController::Init(
- container->window(),
- bio,
- &self->session());
- updated();
- Ui::AddDividerText(container, tr::lng_settings_about_bio());
- }
- void SetupAccountsWrap(
- not_null<Ui::VerticalLayout*> container,
- not_null<Window::SessionController*> controller) {
- Ui::AddSkip(container);
- SetupAccounts(container, controller);
- }
- [[nodiscard]] bool IsAltShift(Qt::KeyboardModifiers modifiers) {
- return (modifiers & Qt::ShiftModifier) && (modifiers & Qt::AltModifier);
- }
- [[nodiscard]] object_ptr<Ui::SettingsButton> MakeAccountButton(
- QWidget *parent,
- not_null<Window::SessionController*> window,
- not_null<Main::Account*> account,
- Fn<void(Qt::KeyboardModifiers)> callback,
- bool locked) {
- const auto active = (account == &window->session().account());
- 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<Ui::SettingsButton>(
- parent,
- rpl::duplicate(text),
- st::mainMenuAddAccountButton);
- const auto raw = result.data();
- {
- const auto container = Badge::AddRight(raw);
- const auto composedBadge = Ui::CreateChild<ComposedBadge>(
- container.get(),
- raw,
- session,
- std::move(text),
- !active,
- [=] { return window->isGifPausedAtLeastFor(
- Window::GifPauseReason::Layer); });
- composedBadge->sizeValue(
- ) | rpl::start_with_next([=](const QSize &s) {
- container->resize(s);
- }, container->lifetime());
- }
- struct State {
- State(QWidget *parent) : userpic(parent) {
- userpic.setAttribute(Qt::WA_TransparentForMouseEvents);
- }
- Ui::RpWidget userpic;
- Ui::PeerUserpicView view;
- base::unique_qptr<Ui::PopupMenu> menu;
- };
- const auto state = raw->lifetime().make_state<State>(raw);
- 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::settingsIconAdd.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(raw->clickModifiers());
- return;
- } else if (which == Qt::MiddleButton) {
- callback(Qt::ControlModifier);
- return;
- } else if (which != Qt::RightButton) {
- return;
- }
- if (state->menu) {
- return;
- }
- const auto isActive = session == &window->session();
- state->menu = base::make_unique_q<Ui::PopupMenu>(
- raw,
- st::popupMenuExpandedSeparator);
- const auto addAction = Ui::Menu::CreateAddActionCallback(
- state->menu);
- if (!isActive) {
- addAction(tr::lng_context_new_window(tr::now), [=] {
- Ui::PreventDelayedActivation();
- callback(Qt::ControlModifier);
- }, &st::menuIconNewWindow);
- Window::AddSeparatorAndShiftUp(addAction);
- }
- addAction(tr::lng_profile_copy_phone(tr::now), [=] {
- const auto phone = rpl::variable<TextWithEntities>(
- Info::Profile::PhoneValue(session->user()));
- QGuiApplication::clipboard()->setText(phone.current().text);
- }, &st::menuIconCopy);
- if (!locked) {
- if (!isActive) {
- addAction(tr::lng_menu_activate(tr::now), [=] {
- callback({});
- }, &st::menuIconProfile);
- }
- Window::MenuAddMarkAsReadAllChatsAction(
- session,
- window->uiShow(),
- addAction);
- }
- if (!isActive) {
- auto logoutCallback = [=] {
- const auto callback = [=](Fn<void()> &&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);
- };
- addAction({
- .text = tr::lng_settings_logout(tr::now),
- .handler = std::move(logoutCallback),
- .icon = &st::menuIconLeaveAttention,
- .isAttention = true,
- });
- }
- state->menu->popup(QCursor::pos());
- }, raw->lifetime());
- return result;
- }
- AccountsList::AccountsList(
- not_null<Ui::VerticalLayout*> container,
- not_null<Window::SessionController*> controller)
- : _controller(controller)
- , _outer(container)
- , _outerIndex(container->count()) {
- setup();
- }
- rpl::producer<> AccountsList::closeRequests() const {
- return _closeRequests.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<Main::Account*> 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([=] {
- rebuild();
- }, _outer->lifetime());
- }
- }
- rebuild();
- }, _outer->lifetime());
- Core::App().domain().maxAccountsChanges(
- ) | rpl::start_with_next([=] {
- // Full rebuild.
- for (auto i = _watched.begin(); i != _watched.end(); i++) {
- i->second = nullptr;
- }
- rebuild();
- }, _outer->lifetime());
- }
- not_null<Ui::SlideWrap<Ui::SettingsButton>*> AccountsList::setupAdd() {
- const auto result = _outer->add(
- object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
- _outer.get(),
- CreateButtonWithIcon(
- _outer.get(),
- tr::lng_menu_add_account(),
- st::mainMenuAddAccountButton,
- {
- &st::settingsIconAdd,
- IconType::Round,
- &st::windowBgActive
- })))->setDuration(0);
- const auto button = result->entity();
- using Environment = MTP::Environment;
- const auto add = [=](Environment environment, bool newWindow = false) {
- auto &domain = _controller->session().domain();
- auto found = false;
- for (const auto &[index, account] : domain.accounts()) {
- const auto raw = account.get();
- if (!raw->sessionExists()
- && raw->mtp().environment() == environment) {
- found = true;
- }
- }
- if (!found && domain.accounts().size() >= domain.maxAccounts()) {
- _controller->show(
- Box(AccountsLimitBox, &_controller->session()));
- } else if (newWindow) {
- domain.addActivated(environment, true);
- } else {
- _controller->window().preventOrInvoke([=] {
- _controller->session().domain().addActivated(environment);
- });
- }
- };
- button->setAcceptBoth(true);
- button->clicks(
- ) | rpl::start_with_next([=](Qt::MouseButton which) {
- if (which == Qt::LeftButton) {
- const auto modifiers = button->clickModifiers();
- const auto newWindow = (modifiers & Qt::ControlModifier);
- add(Environment::Production, newWindow);
- return;
- } else if (which != Qt::RightButton
- || !IsAltShift(button->clickModifiers())) {
- return;
- }
- _contextMenu = base::make_unique_q<Ui::PopupMenu>(_outer);
- _contextMenu->addAction("Production Server", [=] {
- add(Environment::Production);
- });
- _contextMenu->addAction("Test Server", [=] {
- add(Environment::Test);
- });
- _contextMenu->popup(QCursor::pos());
- }, button->lifetime());
- return result;
- }
- void AccountsList::rebuild() {
- const auto inner = _outer->insert(
- _outerIndex,
- object_ptr<Ui::VerticalLayout>(_outer.get()));
- _reorder = std::make_unique<Ui::VerticalLayoutReorder>(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<uint64> 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 premiumLimit = _controller->session().domain().maxAccounts();
- const auto list = _controller->session().domain().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) {
- const auto nextIsLocked = (inner->count() >= premiumLimit);
- auto callback = [=](Qt::KeyboardModifiers modifiers) {
- if (_reordering) {
- return;
- }
- if (account == &_controller->session().account()) {
- _closeRequests.fire({});
- return;
- }
- const auto newWindow = (modifiers & Qt::ControlModifier);
- auto activate = [=, guard = _accountSwitchGuard.make_guard()]{
- if (guard) {
- _reorder->finishReordering();
- if (newWindow) {
- _closeRequests.fire({});
- Core::App().ensureSeparateWindowFor(account);
- }
- Core::App().domain().maybeActivate(account);
- }
- };
- if (const auto window = Core::App().separateWindowFor(
- account)) {
- _closeRequests.fire({});
- window->activate();
- } else {
- base::call_delayed(
- st::defaultRippleAnimation.hideDuration,
- account,
- std::move(activate));
- }
- };
- button.reset(inner->add(MakeAccountButton(
- inner,
- _controller,
- account,
- std::move(callback),
- nextIsLocked)));
- }
- }
- inner->resizeToWidth(_outer->width());
- const auto count = int(list.size());
- _reorder->addPinnedInterval(
- premiumLimit,
- std::max(1, count - premiumLimit));
- _addAccount->toggle(
- (count < Main::Domain::kPremiumMaxAccounts),
- anim::type::instant);
- _reorder->start();
- }
- } // namespace
- Information::Information(
- QWidget *parent,
- not_null<Window::SessionController*> controller)
- : Section(parent) {
- setupContent(controller);
- }
- rpl::producer<QString> Information::title() {
- return tr::lng_settings_section_info();
- }
- void Information::setupContent(
- not_null<Window::SessionController*> controller) {
- const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
- const auto self = controller->session().user();
- SetupPhoto(content, controller, self);
- SetupBio(content, self);
- SetupRows(content, controller, self);
- SetupPersonalChannel(content, controller, self);
- SetupBirthday(content, controller, self);
- SetupAccountsWrap(content, controller);
- Ui::ResizeFitChild(this, content);
- }
- AccountsEvents SetupAccounts(
- not_null<Ui::VerticalLayout*> container,
- not_null<Window::SessionController*> controller) {
- const auto list = container->lifetime().make_state<AccountsList>(
- container,
- controller);
- return {
- .closeRequests = list->closeRequests(),
- };
- }
- void UpdatePhotoLocally(not_null<UserData*> user, const QImage &image) {
- auto bytes = QByteArray();
- auto buffer = QBuffer(&bytes);
- image.save(&buffer, "JPG", 87);
- user->setUserpic(
- base::RandomValue<PhotoId>(),
- ImageLocation(
- { .data = InMemoryLocation{ .bytes = bytes } },
- image.width(),
- image.height()),
- false);
- }
- namespace Badge {
- Ui::UnreadBadgeStyle Style() {
- auto result = Ui::UnreadBadgeStyle();
- result.font = st::mainMenuBadgeFont;
- result.size = st::mainMenuBadgeSize;
- result.sizeId = Ui::UnreadBadgeSize::MainMenu;
- return result;
- }
- not_null<Ui::RpWidget*> AddRight(
- not_null<Ui::SettingsButton*> button) {
- const auto widget = Ui::CreateChild<Ui::RpWidget>(button.get());
- rpl::combine(
- button->sizeValue(),
- widget->sizeValue(),
- widget->shownValue()
- ) | rpl::start_with_next([=](QSize outer, QSize inner, bool shown) {
- auto padding = button->st().padding;
- if (shown) {
- widget->moveToRight(
- padding.right(),
- (outer.height() - inner.height()) / 2,
- outer.width());
- padding.setRight(padding.right() + inner.width());
- }
- button->setPaddingOverride(padding);
- button->update();
- }, widget->lifetime());
- return widget;
- }
- not_null<Ui::RpWidget*> CreateUnread(
- not_null<Ui::RpWidget*> container,
- rpl::producer<UnreadBadge> value) {
- struct State {
- State(QWidget *parent) : widget(parent) {
- widget.setAttribute(Qt::WA_TransparentForMouseEvents);
- }
- Ui::RpWidget widget;
- Ui::UnreadBadgeStyle st = Style();
- int count = 0;
- QString string;
- };
- const auto state = container->lifetime().make_state<State>(container);
- 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(Ui::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);
- Ui::PaintUnreadBadge(
- p,
- state->string,
- state->widget.width(),
- 0,
- state->st);
- }, state->widget.lifetime());
- return &state->widget;
- }
- void AddUnread(
- not_null<Ui::SettingsButton*> button,
- rpl::producer<UnreadBadge> value) {
- const auto container = AddRight(button);
- const auto badge = CreateUnread(container, std::move(value));
- badge->sizeValue(
- ) | rpl::start_with_next([=](const QSize &s) {
- container->resize(s);
- }, container->lifetime());
- }
- } // namespace Badge
- } // namespace Settings
|