fix(ui): Phase 7 C4 — 卡片间距修复 + 全 UI 翻译为简体中文
两个问题修一处:\n\n1. 布局 bug:filter 切到 Max/High/Normal/Low 后可见卡片之间留出巨大空白。原因:Slint isible: false 在 for 循环里没真正折叠 layout 槽位,被隐藏的卡片仍占 76px 高 + 8px spacing。修法:把过滤从 Slint 侧搬到 Rust 侧,新增 filtered_model:UI 绑 filtered_model,shared_model 作为全量 canonical;切换过滤时 rebuild_filtered_model + WS drain 用 push_message_to_models 同步两表;测试连接 / tray restart 也都双表更新。\n\n2. UI 默认中文:sidebar(MESSAGES/SERVERS/FILTERS/APP → 消息/服务器/过滤/应用;All/Max/High/Normal/Low/Settings/About → 全部/最高/高/普通/低/设置/关于)、详情面板(Open link → 打开链接)、空态(You're all caught up → 暂时没有新消息;Test Notification → 发送测试通知)、设置页(5 个分组 + 所有 toggle + Appearance radio)、AboutDialog、托盘菜单(打开/暂停通知/设置.../重新连接/退出/新消息:N)、状态文本(Disconnected → 未连接;mock feed → 模拟数据;messages → 条消息)。\n\n新增 priority-filter-changed(int) 回调,sidebar 切过滤时通知 Rust 重建 filtered model。SharedModel/FilteredModel 同步逻辑:HTTP catch-up → WS drain push → 测试连接 → tray restart 都用 helper 函数维护一致性。
This commit is contained in:
+38
-85
@@ -1,13 +1,11 @@
|
||||
// Main window + sidebar (AGENTS.md §6, §7).
|
||||
//
|
||||
// Phase 7 C3 additions:
|
||||
// - Sidebar priority filter (All / Low / Normal / High / Max)
|
||||
// flips `priority-filter` on MainWindow; MessageList hides
|
||||
// non-matching cards via `visible` (see message-list.slint).
|
||||
// - Right-hand detail panel that opens when a message is clicked:
|
||||
// app + priority dot, title, absolute timestamp, full body,
|
||||
// and an "Open Link" button (re-uses the URL opener wired up
|
||||
// in Phase 7 C2).
|
||||
// Phase 7 C4:
|
||||
// - UI 字符串全部翻译为简体中文(AGENTS.md §55:默认中文)。
|
||||
// - 优先级过滤从 Slint 侧 visible 谓词改为 Rust 侧重新构建
|
||||
// filtered VecModel(参见 message-list.slint 的注释)。
|
||||
// - 新增 `priority-filter-changed(int)` 回调,Sidebar 切换过滤
|
||||
// 时通知 Rust 重建 filtered model。
|
||||
|
||||
import { AppearanceMode } from "./theme.slint";
|
||||
import { NavItem, SectionLabel } from "./components.slint";
|
||||
@@ -53,42 +51,28 @@ export component MainWindow inherits Window {
|
||||
|
||||
in-out property <SidebarSelection> selection: SidebarSelection.All;
|
||||
|
||||
// ── Phase 7 C3 fix: AboutDialog overlay. The previous version
|
||||
// inherited Slint's `Dialog` (a PopupWindow-derived type) and
|
||||
// instantiated it inline; without a runtime `.show()` call,
|
||||
// close() was a no-op and the Close button became inert. We
|
||||
// now drive the dialog with a plain boolean (toggled from Rust)
|
||||
// and render it as a manual Rectangle overlay in `if
|
||||
// root.show-about: AboutDialog { … }`. See dialogs.slint. ──
|
||||
// ── Phase 7 C4 fix: AboutDialog overlay (see dialogs.slint header) ──
|
||||
in-out property <bool> show-about: false;
|
||||
callback close-about();
|
||||
|
||||
// ── Phase 2 C2: mock messages + selection ────────────────────
|
||||
// ── 消息 + 选择 ──
|
||||
in-out property <[MessageItem]> messages;
|
||||
in-out property <int> selected-message-index: -1;
|
||||
callback test-notification-clicked();
|
||||
callback message-card-clicked(int);
|
||||
|
||||
// ── Phase 7 C3: priority filter (0=All, 1=Low, 2=Normal,
|
||||
// 3=High, 4=Max). The integer values intentionally align with
|
||||
// `PriorityFilter` (0=All is the special "no filter" sentinel;
|
||||
// 1..4 map to the four priority buckets). See message-list.slint
|
||||
// for the matching `visible` predicate. ──
|
||||
// ── Phase 7 C4: 优先级过滤。sidebar 的高亮状态由此属性驱动,但
|
||||
// 真正的列表过滤已搬到 Rust 侧(每次切换重建 filtered VecModel,
|
||||
// 避免 Slint `visible: false` 在 for 循环里不折叠空间导致的布局
|
||||
// bug)。0=全部, 1=低, 2=普通, 3=高, 4=最高 ──
|
||||
in-out property <int> priority-filter: 0;
|
||||
callback priority-filter-changed(int);
|
||||
|
||||
// ── Phase 2 C3: Settings / About / window state ──────────────
|
||||
// ── 设置 / About / 窗口状态 ──
|
||||
in-out property <int> appearance-index: 2;
|
||||
callback about-clicked();
|
||||
// Phase 5 C2: fires whenever the user picks a new theme in Settings.
|
||||
// Rust persists it into `state.json::last_theme`.
|
||||
callback appearance-changed-persist(int);
|
||||
|
||||
// ── Phase 6 C2: live AppConfig toggles (Settings page) ─────────
|
||||
// Forwarded from SettingsPage so the Rust runtime can:
|
||||
// 1. Persist into `config.json`
|
||||
// 2. For `start_with_windows`, also write HKCU Run.
|
||||
// 3. For `close_to_tray`, immediately affect the close-requested
|
||||
// handler below without a restart.
|
||||
in-out property <bool> desktop-notify: true;
|
||||
in-out property <bool> sound: true;
|
||||
in-out property <bool> notify-high: true;
|
||||
@@ -98,9 +82,8 @@ export component MainWindow inherits Window {
|
||||
in-out property <bool> close-to-tray: true;
|
||||
callback setting-toggled(string, bool);
|
||||
|
||||
// ── Phase 3 C2: live connection status ───────────────────────
|
||||
in-out property <bool> is-connected: false;
|
||||
in-out property <string> status-text: "Disconnected";
|
||||
in-out property <string> status-text: "未连接";
|
||||
callback test-connection-requested();
|
||||
|
||||
HorizontalLayout {
|
||||
@@ -120,7 +103,7 @@ export component MainWindow inherits Window {
|
||||
font-weight: 700;
|
||||
}
|
||||
Text {
|
||||
text: "Windows Client · Phase 7";
|
||||
text: "Windows 客户端 · Phase 7";
|
||||
color: text-muted;
|
||||
font-size: 10px;
|
||||
}
|
||||
@@ -138,7 +121,7 @@ export component MainWindow inherits Window {
|
||||
background: is-connected ? success : text-muted;
|
||||
}
|
||||
Text {
|
||||
text: is-connected ? "Connected" : "Disconnected";
|
||||
text: is-connected ? "已连接" : "未连接";
|
||||
color: is-connected ? success : text-muted;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
@@ -147,37 +130,32 @@ export component MainWindow inherits Window {
|
||||
|
||||
Rectangle { height: 12px; }
|
||||
|
||||
SectionLabel { label: "MESSAGES"; text-color: text-muted; }
|
||||
SectionLabel { label: "消息"; text-color: text-muted; }
|
||||
NavItem {
|
||||
label: "All";
|
||||
// Phase 7 C3: highlight the All nav item whenever
|
||||
// the priority filter is off (i.e. the user is
|
||||
// looking at the unfiltered list).
|
||||
label: "全部";
|
||||
selected: root.selection == SidebarSelection.All
|
||||
&& root.priority-filter == 0;
|
||||
clicked => {
|
||||
root.selection = SidebarSelection.All;
|
||||
root.priority-filter = 0;
|
||||
root.priority-filter-changed(0);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { height: 12px; }
|
||||
|
||||
SectionLabel { label: "SERVERS"; text-color: text-muted; }
|
||||
SectionLabel { label: "服务器"; text-color: text-muted; }
|
||||
Text {
|
||||
text: "(no servers configured)";
|
||||
text: "(未配置服务器)";
|
||||
color: text-muted;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
Rectangle { height: 12px; }
|
||||
|
||||
SectionLabel { label: "FILTERS"; text-color: text-muted; }
|
||||
// Phase 7 C3: each row maps to one priority bucket.
|
||||
// The dot's color is the priority color so the row
|
||||
// doubles as a legend.
|
||||
SectionLabel { label: "过滤"; text-color: text-muted; }
|
||||
NavItem {
|
||||
label: "Max";
|
||||
label: "最高";
|
||||
selected: root.selection == SidebarSelection.All
|
||||
&& root.priority-filter == 4;
|
||||
has-accent: true;
|
||||
@@ -185,10 +163,11 @@ export component MainWindow inherits Window {
|
||||
clicked => {
|
||||
root.selection = SidebarSelection.All;
|
||||
root.priority-filter = 4;
|
||||
root.priority-filter-changed(4);
|
||||
}
|
||||
}
|
||||
NavItem {
|
||||
label: "High";
|
||||
label: "高";
|
||||
selected: root.selection == SidebarSelection.All
|
||||
&& root.priority-filter == 3;
|
||||
has-accent: true;
|
||||
@@ -196,10 +175,11 @@ export component MainWindow inherits Window {
|
||||
clicked => {
|
||||
root.selection = SidebarSelection.All;
|
||||
root.priority-filter = 3;
|
||||
root.priority-filter-changed(3);
|
||||
}
|
||||
}
|
||||
NavItem {
|
||||
label: "Normal";
|
||||
label: "普通";
|
||||
selected: root.selection == SidebarSelection.All
|
||||
&& root.priority-filter == 2;
|
||||
has-accent: true;
|
||||
@@ -207,10 +187,11 @@ export component MainWindow inherits Window {
|
||||
clicked => {
|
||||
root.selection = SidebarSelection.All;
|
||||
root.priority-filter = 2;
|
||||
root.priority-filter-changed(2);
|
||||
}
|
||||
}
|
||||
NavItem {
|
||||
label: "Low";
|
||||
label: "低";
|
||||
selected: root.selection == SidebarSelection.All
|
||||
&& root.priority-filter == 1;
|
||||
has-accent: true;
|
||||
@@ -218,29 +199,24 @@ export component MainWindow inherits Window {
|
||||
clicked => {
|
||||
root.selection = SidebarSelection.All;
|
||||
root.priority-filter = 1;
|
||||
root.priority-filter-changed(1);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { height: 12px; }
|
||||
|
||||
SectionLabel { label: "APP"; text-color: text-muted; }
|
||||
SectionLabel { label: "应用"; text-color: text-muted; }
|
||||
NavItem {
|
||||
label: "Settings";
|
||||
label: "设置";
|
||||
selected: root.selection == SidebarSelection.Settings;
|
||||
clicked => { root.selection = SidebarSelection.Settings; }
|
||||
}
|
||||
NavItem {
|
||||
label: "About";
|
||||
label: "关于";
|
||||
selected: root.selection == SidebarSelection.About;
|
||||
clicked => {
|
||||
root.selection = SidebarSelection.About;
|
||||
root.about-clicked();
|
||||
// Phase 7 C3 fix: surface the (now
|
||||
// Rectangle-overlay) About dialog. The
|
||||
// Rust `about-clicked` callback still runs
|
||||
// for telemetry; the actual show happens
|
||||
// here so the dialog opens the moment the
|
||||
// user clicks the nav row.
|
||||
root.show-about = true;
|
||||
}
|
||||
}
|
||||
@@ -251,28 +227,22 @@ export component MainWindow inherits Window {
|
||||
Rectangle {
|
||||
background: bg;
|
||||
|
||||
// Phase 7 C3: split the right pane horizontally so the
|
||||
// detail panel can sit alongside the message list when
|
||||
// the user picks a card. The detail panel is hidden when
|
||||
// no card is selected or when the user is on Settings /
|
||||
// About (which have their own full-pane content).
|
||||
HorizontalLayout {
|
||||
padding: 0;
|
||||
spacing: 0;
|
||||
|
||||
// Messages column (always visible when on "All").
|
||||
// 消息列(All 视图时可见)。
|
||||
Rectangle {
|
||||
background: bg;
|
||||
horizontal-stretch: 1;
|
||||
visible: root.selection == SidebarSelection.All;
|
||||
|
||||
VerticalLayout {
|
||||
// Title bar with status text under it.
|
||||
padding: 24px;
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: messages.length == 0 ? "Messages" : "Messages · " + messages.length;
|
||||
text: messages.length == 0 ? "消息" : "消息 · " + messages.length;
|
||||
color: text-primary;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
@@ -288,7 +258,6 @@ export component MainWindow inherits Window {
|
||||
if messages.length > 0: MessageList {
|
||||
messages: messages;
|
||||
selected-index: root.selected-message-index;
|
||||
priority-filter: root.priority-filter;
|
||||
vertical-stretch: 1;
|
||||
message-clicked(idx) => {
|
||||
root.selected-message-index = idx;
|
||||
@@ -305,11 +274,7 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 7 C3: detail panel. Visible when a card is
|
||||
// selected AND the user is on the All view. Always
|
||||
// reads from `messages[selected-message-index]` so
|
||||
// it stays in sync with whatever the card list
|
||||
// currently shows.
|
||||
// 详情面板:选中消息且在 All 视图时显示。
|
||||
if root.selection == SidebarSelection.All
|
||||
&& root.selected-message-index >= 0
|
||||
&& root.selected-message-index < root.messages.length: Rectangle {
|
||||
@@ -379,9 +344,6 @@ export component MainWindow inherits Window {
|
||||
vertical-stretch: 1;
|
||||
}
|
||||
|
||||
// Open-link button: only when the message
|
||||
// carries a click URL. Reuses the same
|
||||
// Rust callback as the card click.
|
||||
if root.messages[root.selected-message-index].has_click_url: Rectangle {
|
||||
height: 32px;
|
||||
background: accent;
|
||||
@@ -396,7 +358,7 @@ export component MainWindow inherits Window {
|
||||
y: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text: "Open link ↗";
|
||||
text: "打开链接 ↗";
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
@@ -431,18 +393,9 @@ export component MainWindow inherits Window {
|
||||
root.setting-toggled(label, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 7 C3 fix: the About page is no longer a separate
|
||||
// right-pane marker. Clicking the sidebar About row opens
|
||||
// a modal dialog overlay (see `if root.show-about:
|
||||
// AboutDialog { … }` further down), so there's nothing
|
||||
// to show in the right pane for `selection == About`.
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 7 C3 fix: conditional About overlay driven by
|
||||
// `show-about`. See the comment on the property above for why
|
||||
// we no longer use Slint's `Dialog` type here.
|
||||
if root.show-about: AboutDialog {
|
||||
close-dialog => {
|
||||
root.show-about = false;
|
||||
|
||||
Reference in New Issue
Block a user