From 0620ee457b31b2452b7439a40524743e22195e21 Mon Sep 17 00:00:00 2001 From: devilreef Date: Fri, 1 May 2026 12:27:37 +0600 Subject: [PATCH] refactor(arcanegram): settings submenus + input polish --- res/lang/lang_arcanegram.strings | 14 +- src/cpp/arcanegram/arcanegram.style | 19 +++ .../arcanegram/features/ag_fast_messages.cpp | 3 +- .../arcanegram/features/ag_sync_feature.cpp | 83 +++++++++-- .../ui/ag_hidden_users_settings.cpp | 133 ++++++++++++------ src/cpp/arcanegram/ui/ag_settings_main.cpp | 53 ++++++- 6 files changed, 243 insertions(+), 62 deletions(-) diff --git a/res/lang/lang_arcanegram.strings b/res/lang/lang_arcanegram.strings index 2fc8a89..ef303cf 100644 --- a/res/lang/lang_arcanegram.strings +++ b/res/lang/lang_arcanegram.strings @@ -19,4 +19,16 @@ "ag_fast_messages_title" = "Fast messages"; "ag_fast_messages_info" = "Type a message per slot. Bind keys in Settings → Chat Settings → Keyboard Shortcuts."; "ag_fast_messages_slot" = "Slot {index}"; -"ag_fast_messages_placeholder" = "Message text (e.g. /dban begging)"; +"ag_fast_messages_placeholder" = "Message text"; +"ag_settings_appearance" = "Chat appearance"; +"ag_settings_sync_title" = "Cloud sync"; +"ag_sync_enabled_setting" = "Enable cloud sync"; +"ag_sync_enabled_info" = "Sync arcanegram-only settings between your devices via the arcanesync backend."; +"ag_sync_endpoint_placeholder" = "Endpoint URL"; +"ag_sync_status_disabled" = "Disabled"; +"ag_sync_status_ready" = "Ready"; +"ag_sync_status_synced" = "Synced"; +"ag_sync_status_syncing" = "Syncing…"; +"ag_sync_status_offline" = "Offline"; +"ag_sync_status_offline_pending" = "Offline · {n} pending"; +"ag_sync_status_conflict" = "Resolving conflict…"; diff --git a/src/cpp/arcanegram/arcanegram.style b/src/cpp/arcanegram/arcanegram.style index d0959fa..1b42765 100644 --- a/src/cpp/arcanegram/arcanegram.style +++ b/src/cpp/arcanegram/arcanegram.style @@ -1,3 +1,22 @@ using "ui/widgets/widgets.style"; menuIconArcanegramSettings: icon {{ "arcanegram/settings/arcanegram_settings", menuIconColor }}; + +agFastMessageField: InputField(defaultInputField) { + textBg: transparent; + textMargins: margins(2px, 8px, 2px, 12px); + + placeholderFg: placeholderFg; + placeholderFgActive: placeholderFgActive; + placeholderFgError: placeholderFgActive; + placeholderMargins: margins(0px, 0px, 0px, 0px); + placeholderScale: 0.; + placeholderFont: normalFont; + + border: 0px; + borderActive: 0px; + + heightMin: 40px; + + style: defaultTextStyle; +} diff --git a/src/cpp/arcanegram/features/ag_fast_messages.cpp b/src/cpp/arcanegram/features/ag_fast_messages.cpp index d4db2f8..e9c5dc9 100644 --- a/src/cpp/arcanegram/features/ag_fast_messages.cpp +++ b/src/cpp/arcanegram/features/ag_fast_messages.cpp @@ -10,6 +10,7 @@ #include "main/main_session.h" #include "settings/settings_builder.h" #include "settings/settings_common_session.h" +#include "styles/style_arcanegram.h" #include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include "ui/vertical_list.h" @@ -35,7 +36,7 @@ void AddSlotField( const auto field = container->add( object_ptr( container, - st::settingsBio, + st::agFastMessageField, tr::ag_fast_messages_placeholder(), item.value()), st::settingsBioMargins); diff --git a/src/cpp/arcanegram/features/ag_sync_feature.cpp b/src/cpp/arcanegram/features/ag_sync_feature.cpp index dbcf071..3a7ed0c 100644 --- a/src/cpp/arcanegram/features/ag_sync_feature.cpp +++ b/src/cpp/arcanegram/features/ag_sync_feature.cpp @@ -2,53 +2,72 @@ #include "arcanegram/ag_config.h" #include "arcanegram/sync/ag_sync_engine.h" +#include "arcanegram/ui/ag_settings_main.h" #include "arcanegram/ui/ag_settings_widgets.h" +#include "lang/lang_keys.h" #include "settings/settings_builder.h" +#include "settings/settings_common_session.h" +#include "styles/style_menu_icons.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" #include #include -// todo: replace hardcoded strings with tr::ag_sync_* once the misc__lang-keys -// stgit patch is updated to declare them. - namespace Arcanegram::CloudSync { namespace { +using namespace ::Settings; +using namespace ::Settings::Builder; + QString FormatStatus(const Sync::StatusInfo &info) { switch (info.status) { case Sync::Status::Disabled: - return u"disabled"_q; + return tr::ag_sync_status_disabled(tr::now); case Sync::Status::Idle: return info.lastSyncedAt - ? u"synced"_q - : u"ready"_q; + ? tr::ag_sync_status_synced(tr::now) + : tr::ag_sync_status_ready(tr::now); case Sync::Status::Syncing: - return u"syncing…"_q; + return tr::ag_sync_status_syncing(tr::now); case Sync::Status::Offline: { const auto base = info.pendingCount - ? u"offline · %1 pending"_q.arg(info.pendingCount) - : u"offline"_q; + ? tr::ag_sync_status_offline_pending( + tr::now, + lt_n, + QString::number(info.pendingCount)) + : tr::ag_sync_status_offline(tr::now); return info.message.isEmpty() ? base : (base + u" · "_q + info.message); } case Sync::Status::Conflict: - return u"resolving conflict…"_q; + return tr::ag_sync_status_conflict(tr::now); } return {}; } -} // namespace +class Page : public Section { +public: + Page(QWidget *parent, not_null controller); -void Setup(::Settings::Builder::SectionBuilder &builder) { + [[nodiscard]] rpl::producer title() override { + return tr::ag_settings_sync_title(); + } + +private: + void setupContent(); +}; + +void BuildPage(SectionBuilder &builder) { Arcanegram::Settings::AddBoolRow( builder, u"arcanegram/sync_enabled"_q, - rpl::single(u"cloud sync (experimental)"_q), - rpl::single(u"sync arcanegram-only settings between your devices via the arcanesync backend."_q), + tr::ag_sync_enabled_setting(), + tr::ag_sync_enabled_info(), Config::Sync::Enabled); Arcanegram::Settings::AddTextRow( builder, - rpl::single(u"endpoint"_q), + tr::ag_sync_endpoint_placeholder(), Config::Sync::Endpoint); auto statusText = []() -> rpl::producer { @@ -60,4 +79,38 @@ void Setup(::Settings::Builder::SectionBuilder &builder) { Arcanegram::Settings::AddStatusRow(builder, std::move(statusText)); } +const auto kPageMeta = BuildHelper({ + .id = Page::Id(), + .parentId = Arcanegram::Settings::Id(), + .title = &tr::ag_settings_sync_title, + .icon = &st::menuIconLink, +}, [](SectionBuilder &builder) { + BuildPage(builder); +}); + +const SectionBuildMethod kPageSection = kPageMeta.build; + +Page::Page( + QWidget *parent, + not_null controller) +: Section(parent, controller) { + setupContent(); +} + +void Page::setupContent() { + const auto content = Ui::CreateChild(this); + build(content, kPageSection); + Ui::ResizeFitChild(this, content); +} + +} // namespace + +void Setup(::Settings::Builder::SectionBuilder &builder) { + builder.addSectionButton({ + .title = tr::ag_settings_sync_title(), + .targetSection = Page::Id(), + .icon = { &st::menuIconLink }, + }); +} + } // namespace Arcanegram::CloudSync diff --git a/src/cpp/arcanegram/ui/ag_hidden_users_settings.cpp b/src/cpp/arcanegram/ui/ag_hidden_users_settings.cpp index 17b4351..f5695b1 100644 --- a/src/cpp/arcanegram/ui/ag_hidden_users_settings.cpp +++ b/src/cpp/arcanegram/ui/ag_hidden_users_settings.cpp @@ -1,6 +1,7 @@ #include "arcanegram/ui/ag_hidden_users_settings.h" #include "arcanegram/features/ag_hidden_users.h" +#include "arcanegram/ui/ag_settings_main.h" #include "core/application.h" #include "data/data_peer.h" #include "data/data_session.h" @@ -9,64 +10,114 @@ #include "main/main_domain.h" #include "main/main_session.h" #include "settings/settings_builder.h" +#include "settings/settings_common_session.h" +#include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" namespace Arcanegram::HiddenUsers { namespace { +using namespace ::Settings; +using namespace ::Settings::Builder; + [[nodiscard]] PeerData *AnyLoadedPeer(PeerId id) { - for (const auto &entry : Core::App().domain().accounts()) { - if (const auto session = entry.account->maybeSession()) { - if (const auto peer = session->data().peerLoaded(id)) { - return peer; - } - } - } - return nullptr; + for (const auto &entry : Core::App().domain().accounts()) { + if (const auto session = entry.account->maybeSession()) { + if (const auto peer = session->data().peerLoaded(id)) { + return peer; + } + } + } + return nullptr; } void Rebuild(not_null container) { - while (container->count()) { - delete container->widgetAt(0); - } - const auto &set = List(); - if (set.empty()) { - container->add( - object_ptr( - container, - tr::ag_hidden_users_empty(), - st::boxDividerLabel), - st::defaultBoxDividerLabelPadding); - return; - } - for (const auto &id : set) { - const auto peer = AnyLoadedPeer(id); - const auto label = peer - ? peer->name() - : QString::number(id.value); - const auto row = container->add( - object_ptr( - container, - rpl::single(label), - st::settingsButtonNoIcon)); - row->setClickedCallback([id] { Unhide(id); }); - } + while (container->count()) { + delete container->widgetAt(0); + } + const auto &set = List(); + if (set.empty()) { + container->add( + object_ptr( + container, + tr::ag_hidden_users_empty(), + st::boxDividerLabel), + st::defaultBoxDividerLabelPadding); + return; + } + for (const auto &id : set) { + const auto peer = AnyLoadedPeer(id); + const auto label = peer + ? peer->name() + : QString::number(id.value); + const auto row = container->add( + object_ptr( + container, + rpl::single(label), + st::settingsButtonNoIcon)); + row->setClickedCallback([id] { Unhide(id); }); + } +} + +class Page : public Section { +public: + Page(QWidget *parent, not_null controller); + + [[nodiscard]] rpl::producer title() override { + return tr::ag_hidden_users_title(); + } + +private: + void setupContent(); +}; + +void BuildPage(SectionBuilder &builder) { + const auto outer = builder.container(); + const auto inner = outer->add(object_ptr(outer)); + Rebuild(inner); + Changes( + ) | rpl::on_next([=](PeerId) { + Rebuild(inner); + }, inner->lifetime()); + builder.addDividerText(tr::ag_hidden_users_info()); +} + +const auto kPageMeta = BuildHelper({ + .id = Page::Id(), + .parentId = Arcanegram::Settings::Id(), + .title = &tr::ag_hidden_users_title, + .icon = &st::menuIconStealth, +}, [](SectionBuilder &builder) { + BuildPage(builder); +}); + +const SectionBuildMethod kPageSection = kPageMeta.build; + +Page::Page( + QWidget *parent, + not_null controller) +: Section(parent, controller) { + setupContent(); +} + +void Page::setupContent() { + const auto content = Ui::CreateChild(this); + build(content, kPageSection); + Ui::ResizeFitChild(this, content); } } // namespace void Setup(::Settings::Builder::SectionBuilder &builder) { - const auto outer = builder.container(); - const auto inner = outer->add(object_ptr(outer)); - Rebuild(inner); - Changes( - ) | rpl::on_next([=](PeerId) { - Rebuild(inner); - }, inner->lifetime()); - builder.addDividerText(tr::ag_hidden_users_info()); + builder.addSectionButton({ + .title = tr::ag_hidden_users_title(), + .targetSection = Page::Id(), + .icon = { &st::menuIconStealth }, + }); } } // namespace Arcanegram::HiddenUsers diff --git a/src/cpp/arcanegram/ui/ag_settings_main.cpp b/src/cpp/arcanegram/ui/ag_settings_main.cpp index 2fd7666..1c74813 100644 --- a/src/cpp/arcanegram/ui/ag_settings_main.cpp +++ b/src/cpp/arcanegram/ui/ag_settings_main.cpp @@ -1,9 +1,9 @@ #include "arcanegram/ui/ag_settings_main.h" -#include "arcanegram/features/ag_forwarded_header.h" -#include "arcanegram/features/ag_show_seconds.h" #include "arcanegram/features/ag_chat_wallpaper.h" #include "arcanegram/features/ag_fast_messages.h" +#include "arcanegram/features/ag_forwarded_header.h" +#include "arcanegram/features/ag_show_seconds.h" #include "arcanegram/features/ag_sync_feature.h" #include "arcanegram/ui/ag_hidden_users_settings.h" #include "lang/lang_keys.h" @@ -11,6 +11,7 @@ #include "settings/settings_builder.h" #include "settings/settings_common_session.h" #include "styles/style_arcanegram.h" +#include "styles/style_menu_icons.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" @@ -32,13 +33,57 @@ private: void setupContent(); }; -void BuildContent(SectionBuilder &builder) { +class Appearance : public Section { +public: + Appearance(QWidget *parent, not_null controller); + + [[nodiscard]] rpl::producer title() override { + return tr::ag_settings_appearance(); + } + +private: + void setupContent(); +}; + +void BuildAppearance(SectionBuilder &builder) { Arcanegram::ForwardedHeader::Setup(builder); Arcanegram::Time::Setup(builder); Arcanegram::ChatWallpaper::Setup(builder); +} + +const auto kAppearanceMeta = BuildHelper({ + .id = Appearance::Id(), + .parentId = Main::Id(), + .title = &tr::ag_settings_appearance, + .icon = &st::menuIconPalette, +}, [](SectionBuilder &builder) { + BuildAppearance(builder); +}); + +const SectionBuildMethod kAppearanceSection = kAppearanceMeta.build; + +Appearance::Appearance( + QWidget *parent, + not_null controller) +: Section(parent, controller) { + setupContent(); +} + +void Appearance::setupContent() { + const auto content = Ui::CreateChild(this); + build(content, kAppearanceSection); + Ui::ResizeFitChild(this, content); +} + +void BuildContent(SectionBuilder &builder) { + builder.addSectionButton({ + .title = tr::ag_settings_appearance(), + .targetSection = Appearance::Id(), + .icon = { &st::menuIconPalette }, + }); Arcanegram::HiddenUsers::Setup(builder); - Arcanegram::CloudSync::Setup(builder); Arcanegram::FastMessages::Setup(builder); + Arcanegram::CloudSync::Setup(builder); } const auto kMeta = BuildHelper({