feat(gotify): Phase 3 C2 — UI 接线 + Connection status + 真实数据
- 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)
This commit is contained in:
+52
-27
@@ -1,9 +1,8 @@
|
||||
// Main window + sidebar (AGENTS.md §6, §7).
|
||||
//
|
||||
// Phase 2 C3 wires the Settings page, About dialog, and window-state
|
||||
// persistence. The Appearance section in `settings-page.slint` is the only
|
||||
// settings UI that drives real state today — every other control is a
|
||||
// shell that Phase 5 hooks to `AppConfig`.
|
||||
// Phase 3 C2 wires the live connection status (dot + status text under the
|
||||
// title bar) and accepts a `test-connection-requested` callback fired by
|
||||
// the Settings → Connection section.
|
||||
|
||||
import { AppearanceMode } from "./theme.slint";
|
||||
import { NavItem, SectionLabel } from "./components.slint";
|
||||
@@ -26,11 +25,9 @@ export component MainWindow inherits Window {
|
||||
min-width: 760px;
|
||||
min-height: 520px;
|
||||
|
||||
// Theme switch — Rust-controllable via `ui.set_theme(...)`.
|
||||
in-out property <AppearanceMode> theme: AppearanceMode.Dark;
|
||||
out property <bool> is-dark: theme != AppearanceMode.Light;
|
||||
|
||||
// Palette — all derived from `is-dark` (AGENTS.md §52).
|
||||
out property <color> bg: is-dark ? #1f2024 : #f5f6f8;
|
||||
out property <color> surface: is-dark ? #28292d : #ffffff;
|
||||
out property <color> surface-elev: is-dark ? #2f3035 : #fafbfc;
|
||||
@@ -58,13 +55,13 @@ export component MainWindow inherits Window {
|
||||
callback message-card-clicked(int);
|
||||
|
||||
// ── Phase 2 C3: Settings / About / window state ──────────────
|
||||
// Initial appearance radio (0=System/1=Light/2=Dark). Maps 1:1 to
|
||||
// AppearanceMode: System=0, Light=1, Dark=2.
|
||||
in-out property <int> appearance-index: 2;
|
||||
|
||||
// Show About dialog (one-shot — Rust closes it after handling).
|
||||
callback about-clicked();
|
||||
callback close-requested-save-state();
|
||||
|
||||
// ── Phase 3 C2: live connection status ───────────────────────
|
||||
in-out property <bool> is-connected: false;
|
||||
in-out property <string> status-text: "Disconnected";
|
||||
callback test-connection-requested();
|
||||
|
||||
HorizontalLayout {
|
||||
// §7 Sidebar width: 220-240px.
|
||||
@@ -83,12 +80,32 @@ export component MainWindow inherits Window {
|
||||
font-weight: 700;
|
||||
}
|
||||
Text {
|
||||
text: "Windows Client · Phase 2";
|
||||
text: "Windows Client · Phase 3";
|
||||
color: text-muted;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
Rectangle { height: 16px; }
|
||||
Rectangle { height: 12px; }
|
||||
|
||||
// Live status row (Phase 3 C2): dot + text.
|
||||
HorizontalLayout {
|
||||
spacing: 6px;
|
||||
alignment: center;
|
||||
Rectangle {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background: is-connected ? success : text-muted;
|
||||
}
|
||||
Text {
|
||||
text: is-connected ? "Connected" : "Disconnected";
|
||||
color: is-connected ? success : text-muted;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { height: 12px; }
|
||||
|
||||
SectionLabel { label: "MESSAGES"; text-color: text-muted; }
|
||||
NavItem {
|
||||
@@ -113,19 +130,19 @@ export component MainWindow inherits Window {
|
||||
label: "High";
|
||||
selected: false;
|
||||
has-accent: false;
|
||||
clicked => { /* TODO Phase 2 C2 */ }
|
||||
clicked => { /* TODO Phase 3 C2: priority filter */ }
|
||||
}
|
||||
NavItem {
|
||||
label: "Normal";
|
||||
selected: false;
|
||||
has-accent: false;
|
||||
clicked => { /* TODO Phase 2 C2 */ }
|
||||
clicked => { /* TODO Phase 3 C2: priority filter */ }
|
||||
}
|
||||
NavItem {
|
||||
label: "Low";
|
||||
selected: false;
|
||||
has-accent: false;
|
||||
clicked => { /* TODO Phase 2 C2 */ }
|
||||
clicked => { /* TODO Phase 3 C2: priority filter */ }
|
||||
}
|
||||
|
||||
Rectangle { height: 12px; }
|
||||
@@ -151,9 +168,10 @@ export component MainWindow inherits Window {
|
||||
Rectangle {
|
||||
background: bg;
|
||||
|
||||
if root.selection == SidebarSelection.All: VerticalLayout {
|
||||
VerticalLayout {
|
||||
// Title bar with status text under it.
|
||||
padding: 24px;
|
||||
spacing: 12px;
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: messages.length == 0 ? "Messages" : "Messages · " + messages.length;
|
||||
@@ -161,11 +179,18 @@ export component MainWindow inherits Window {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
Text {
|
||||
text: status-text;
|
||||
color: text-muted;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
Rectangle { height: 12px; }
|
||||
|
||||
if messages.length > 0: MessageList {
|
||||
messages: messages;
|
||||
selected-index: root.selected-message-index;
|
||||
height: parent.height - 48px;
|
||||
vertical-stretch: 1;
|
||||
message-clicked(idx) => {
|
||||
root.selected-message-index = idx;
|
||||
root.message-card-clicked(idx);
|
||||
@@ -173,6 +198,7 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
|
||||
if messages.length == 0: EmptyState {
|
||||
vertical-stretch: 1;
|
||||
test-notification => {
|
||||
root.test-notification-clicked();
|
||||
}
|
||||
@@ -187,11 +213,14 @@ export component MainWindow inherits Window {
|
||||
idx == 1 ? AppearanceMode.Light :
|
||||
AppearanceMode.Dark;
|
||||
}
|
||||
test-connection-requested => {
|
||||
root.test-connection-requested();
|
||||
}
|
||||
}
|
||||
|
||||
// About is rendered via a Dialog overlay — not in the right pane.
|
||||
// A separate About row keeps the layout stable; the dialog is
|
||||
// shown via `about-clicked` callback to Rust.
|
||||
// About entry stays as a simple right-pane marker; the
|
||||
// modal AboutDialog overlay is wired through AboutDialog in
|
||||
// this component (visibility toggled via `about-clicked`).
|
||||
if root.selection == SidebarSelection.About: Rectangle {
|
||||
background: bg;
|
||||
Text {
|
||||
@@ -205,9 +234,5 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
// Modal About dialog — opened when the user clicks About in the sidebar.
|
||||
AboutDialog {
|
||||
// visible-by-default is false. Toggled via Rust by listening to
|
||||
// `about-clicked`. C3 wires the bare open/close plumbing.
|
||||
}
|
||||
AboutDialog {}
|
||||
}
|
||||
@@ -137,6 +137,10 @@ export component SettingsPage inherits Rectangle {
|
||||
// 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;
|
||||
@@ -151,6 +155,29 @@ export component SettingsPage inherits Rectangle {
|
||||
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";
|
||||
|
||||
Reference in New Issue
Block a user