refactor(arcanegram): settings submenus + input polish

This commit is contained in:
devilreef 2026-05-01 12:27:37 +06:00
parent db1bf8b947
commit 0620ee457b
Signed by: devilreef
SSH key fingerprint: SHA256:UZisRr4iuXx+IhkbZnR655L2RWAT6o2rgbGv5F/6m3Y
6 changed files with 243 additions and 62 deletions

View file

@ -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…";

View file

@ -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;
}

View file

@ -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<Ui::InputField>(
container,
st::settingsBio,
st::agFastMessageField,
tr::ag_fast_messages_placeholder(),
item.value()),
st::settingsBioMargins);

View file

@ -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 <rpl/map.h>
#include <rpl/producer.h>
// 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<Page> {
public:
Page(QWidget *parent, not_null<Window::SessionController*> controller);
void Setup(::Settings::Builder::SectionBuilder &builder) {
[[nodiscard]] rpl::producer<QString> 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<QString> {
@ -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<Window::SessionController*> controller)
: Section(parent, controller) {
setupContent();
}
void Page::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(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

View file

@ -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<Ui::VerticalLayout*> container) {
while (container->count()) {
delete container->widgetAt(0);
}
const auto &set = List();
if (set.empty()) {
container->add(
object_ptr<Ui::FlatLabel>(
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<Ui::SettingsButton>(
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<Ui::FlatLabel>(
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<Ui::SettingsButton>(
container,
rpl::single(label),
st::settingsButtonNoIcon));
row->setClickedCallback([id] { Unhide(id); });
}
}
class Page : public Section<Page> {
public:
Page(QWidget *parent, not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<QString> 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<Ui::VerticalLayout>(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<Window::SessionController*> controller)
: Section(parent, controller) {
setupContent();
}
void Page::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(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<Ui::VerticalLayout>(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

View file

@ -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<Appearance> {
public:
Appearance(QWidget *parent, not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<QString> 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<Window::SessionController*> controller)
: Section(parent, controller) {
setupContent();
}
void Appearance::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(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({