- src/config.rs: AppConfig::placeholder() 硬编码 NAS 地址 + 假 token (§20 token 永不日志;Phase 5 接持久化前占位) - src/models.rs: gotify_messages_to_items 把 GotifyMessage 列表转 Slint MessageItem[];try_fetch_real_messages 一次完成 health → applications → messages(50);失败 None 让调用方 fallback mock - src/gotify/mod.rs: re-export ServerConfig - src/lib.rs: run() 启动时调 try_fetch_real_messages,成功用真实数 据 + Connected + 服务端 URL,失败 fallback mock + Disconnected; on_test_connection_requested 重新跑同一路径 - Cargo.toml: 加 chrono 0.4 (RFC3339 解析) - ui/app.slint: is-connected (绿/灰 dot) + status-text (顶部副标) - ui/settings-page.slint: Connection section 加 Test Connection 按钮 (in-progress 时禁用显示 "Testing...") 避坑: - Gotify RFC3339 date 解析失败 silent fallback 到 unix=0(显示 "just now" 而不是崩) - Gotify priority 0..10 → Slint Priority 4 桶分桶:<=0 Low / <=3 Normal / <=7 High / else Max - ServerConfig 在 crate::gotify::model 子模块,mod.rs 必须 re-export 验证:cargo build / build --release 都 exit 0,0 warning - cargo test --test gotify_api: 6 passed - debug 33.36 MB, subsystem = WINDOWS_CUI - release 9.43 MB, subsystem = WINDOWS_GUI(chrono +1.3 MB)
327 lines
10 KiB
Plaintext
327 lines
10 KiB
Plaintext
// AGENTS.md §10 / §11 settings page.
|
|
//
|
|
// C3 lays out the five sections (Connection / Notifications / Startup /
|
|
// Appearance / Advanced) with controls. The Appearance section is the only
|
|
// one wired back to the model: the radio selection drives
|
|
// `MainWindow.theme` via the `appearance-changed` callback. Everything else
|
|
// is a UI shell — Phase 5 wires it to `AppConfig` + Settings → state.json.
|
|
//
|
|
// Sub-components MUST be defined before the components that reference them
|
|
// in Slint 1.x, so `SettingsToggleRow` / `AppearanceRadio` come first.
|
|
|
|
import { SectionLabel } from "./components.slint";
|
|
import { ServerCard } from "./server-card.slint";
|
|
|
|
// ─── Sub-components ────────────────────────────────────────────────
|
|
|
|
export component SettingsToggleRow inherits Rectangle {
|
|
in property <string> label;
|
|
in property <bool> value: false;
|
|
in property <bool> enabled: true;
|
|
callback toggled(bool);
|
|
|
|
in property <color> row-bg: #28292d;
|
|
in property <color> row-border: #3a3b40;
|
|
in property <color> label-color: #e6e6e6;
|
|
in property <color> toggle-on: #4ea1ff;
|
|
in property <color> toggle-off: #6a6a6a;
|
|
|
|
width: 100%;
|
|
height: 44px;
|
|
border-radius: 8px;
|
|
background: row-bg;
|
|
border-color: row-border;
|
|
border-width: 1px;
|
|
opacity: enabled ? 1.0 : 0.5;
|
|
|
|
HorizontalLayout {
|
|
padding-top: 0;
|
|
padding-bottom: 0;
|
|
padding-left: 16px;
|
|
padding-right: 16px;
|
|
alignment: center;
|
|
|
|
Text {
|
|
text: label;
|
|
color: label-color;
|
|
font-size: 13px;
|
|
horizontal-stretch: 1;
|
|
}
|
|
|
|
Rectangle {
|
|
width: 36px;
|
|
height: 20px;
|
|
border-radius: 10px;
|
|
background: value ? toggle-on : toggle-off;
|
|
y: parent.height / 2 - self.height / 2;
|
|
|
|
Rectangle {
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 8px;
|
|
background: #ffffff;
|
|
x: value ? parent.width - self.width - 2px : 2px;
|
|
y: (parent.height - self.height) / 2;
|
|
}
|
|
TouchArea {
|
|
enabled: root.enabled;
|
|
clicked => {
|
|
root.toggled(!root.value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export component AppearanceRadio inherits Rectangle {
|
|
in property <string> label;
|
|
in property <string> description;
|
|
in property <bool> selected: false;
|
|
callback chosen();
|
|
|
|
in property <color> row-bg: #28292d;
|
|
in property <color> row-border: #3a3b40;
|
|
in property <color> row-border-selected: #4ea1ff;
|
|
in property <color> label-color: #e6e6e6;
|
|
in property <color> hint-color: #6a6a6a;
|
|
|
|
width: 160px;
|
|
height: 64px;
|
|
border-radius: 8px;
|
|
background: row-bg;
|
|
border-color: selected ? row-border-selected : row-border;
|
|
border-width: selected ? 2px : 1px;
|
|
|
|
VerticalLayout {
|
|
padding: 12px;
|
|
spacing: 2px;
|
|
|
|
Text {
|
|
text: label;
|
|
color: label-color;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
Text {
|
|
text: description;
|
|
color: hint-color;
|
|
font-size: 11px;
|
|
}
|
|
}
|
|
|
|
TouchArea {
|
|
clicked => {
|
|
root.chosen();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ─── Main page ─────────────────────────────────────────────────────
|
|
|
|
export component SettingsPage inherits Rectangle {
|
|
// Appearance (live wired)
|
|
in-out property <int> appearance-index: 2;
|
|
callback appearance-changed(int);
|
|
|
|
// Notifications (UI shell)
|
|
in-out property <bool> desktop-notify: true;
|
|
in-out property <bool> sound: true;
|
|
in-out property <bool> notify-high: true;
|
|
in-out property <bool> notify-max: true;
|
|
|
|
// Startup (UI shell)
|
|
in-out property <bool> start-with-windows: false;
|
|
in-out property <bool> start-minimized: false;
|
|
in-out property <bool> close-to-tray: true;
|
|
|
|
// Advanced (collapsed by default per §10)
|
|
in-out property <bool> advanced-expanded: false;
|
|
|
|
// Connection (Phase 3 C2)
|
|
in-out property <bool> test-in-progress: false;
|
|
callback test-connection-requested();
|
|
|
|
// Tokens
|
|
in property <color> divider-color: #3a3b40;
|
|
in property <color> hint-color: #6a6a6a;
|
|
|
|
background: transparent;
|
|
|
|
VerticalLayout {
|
|
padding: 24px;
|
|
spacing: 24px;
|
|
|
|
// CONNECTION
|
|
VerticalLayout {
|
|
spacing: 8px;
|
|
SectionLabel { label: "CONNECTION"; text-color: hint-color; }
|
|
|
|
// "Test Connection" button — Phase 3 C2 wires this to the
|
|
// real `GotifyApi::health` round-trip.
|
|
Rectangle {
|
|
width: 160px;
|
|
height: 32px;
|
|
background: #2f3035;
|
|
border-radius: 6px;
|
|
Text {
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
text: root.test-in-progress ? "Testing..." : "Test Connection";
|
|
color: #e6e6e6;
|
|
font-size: 12px;
|
|
}
|
|
TouchArea {
|
|
enabled: !root.test-in-progress;
|
|
clicked => {
|
|
root.test-connection-requested();
|
|
}
|
|
}
|
|
}
|
|
|
|
ServerCard {
|
|
server-name: "Home";
|
|
server-url: "https://gotify.example.com";
|
|
enabled: true;
|
|
is-current: true;
|
|
}
|
|
ServerCard {
|
|
server-name: "NAS";
|
|
server-url: "http://192.168.50.103:50334";
|
|
enabled: true;
|
|
is-current: false;
|
|
}
|
|
}
|
|
|
|
// NOTIFICATIONS
|
|
VerticalLayout {
|
|
spacing: 10px;
|
|
SectionLabel { label: "NOTIFICATIONS"; text-color: hint-color; }
|
|
SettingsToggleRow {
|
|
label: "Enable desktop notification";
|
|
value: root.desktop-notify;
|
|
}
|
|
SettingsToggleRow {
|
|
label: "Play sound";
|
|
value: root.sound;
|
|
enabled: root.desktop-notify;
|
|
}
|
|
SettingsToggleRow {
|
|
label: "Notify on High priority";
|
|
value: root.notify-high;
|
|
enabled: root.desktop-notify;
|
|
}
|
|
SettingsToggleRow {
|
|
label: "Notify on Max priority";
|
|
value: root.notify-max;
|
|
enabled: root.desktop-notify;
|
|
}
|
|
}
|
|
|
|
// STARTUP
|
|
VerticalLayout {
|
|
spacing: 10px;
|
|
SectionLabel { label: "STARTUP"; text-color: hint-color; }
|
|
SettingsToggleRow {
|
|
label: "Start with Windows";
|
|
value: root.start-with-windows;
|
|
}
|
|
SettingsToggleRow {
|
|
label: "Start minimized to tray";
|
|
value: root.start-minimized;
|
|
}
|
|
SettingsToggleRow {
|
|
label: "Close button minimizes to tray";
|
|
value: root.close-to-tray;
|
|
}
|
|
}
|
|
|
|
// APPEARANCE (live wired)
|
|
VerticalLayout {
|
|
spacing: 10px;
|
|
SectionLabel { label: "APPEARANCE"; text-color: hint-color; }
|
|
|
|
HorizontalLayout {
|
|
spacing: 12px;
|
|
|
|
AppearanceRadio {
|
|
label: "System";
|
|
description: "Follow Windows theme";
|
|
selected: root.appearance-index == 0;
|
|
chosen => {
|
|
root.appearance-index = 0;
|
|
root.appearance-changed(0);
|
|
}
|
|
}
|
|
AppearanceRadio {
|
|
label: "Light";
|
|
description: "Bright neutrals";
|
|
selected: root.appearance-index == 1;
|
|
chosen => {
|
|
root.appearance-index = 1;
|
|
root.appearance-changed(1);
|
|
}
|
|
}
|
|
AppearanceRadio {
|
|
label: "Dark";
|
|
description: "Graphite";
|
|
selected: root.appearance-index == 2;
|
|
chosen => {
|
|
root.appearance-index = 2;
|
|
root.appearance-changed(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
label: "Compact message density";
|
|
value: false;
|
|
enabled: false;
|
|
}
|
|
}
|
|
|
|
// ADVANCED (collapsed by default per §10)
|
|
VerticalLayout {
|
|
spacing: 10px;
|
|
|
|
HorizontalLayout {
|
|
spacing: 8px;
|
|
alignment: center;
|
|
Text {
|
|
text: advanced-expanded ? "v" : ">";
|
|
color: hint-color;
|
|
font-size: 11px;
|
|
}
|
|
Text {
|
|
text: "ADVANCED";
|
|
color: hint-color;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
}
|
|
TouchArea {
|
|
clicked => {
|
|
root.advanced-expanded = !root.advanced-expanded;
|
|
}
|
|
}
|
|
}
|
|
|
|
if advanced-expanded: VerticalLayout {
|
|
spacing: 10px;
|
|
SettingsToggleRow {
|
|
label: "Log level: Info (release default)";
|
|
value: false;
|
|
enabled: false;
|
|
}
|
|
SettingsToggleRow {
|
|
label: "Clear local state (messages + window size)";
|
|
value: false;
|
|
enabled: false;
|
|
}
|
|
SettingsToggleRow {
|
|
label: "Reset settings to defaults";
|
|
value: false;
|
|
enabled: false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |