Commit Graph
4 Commits
Author SHA1 Message Date
KennethCheng e1622b5959 feat(windows): Phase 6 C2 — close-to-tray + Settings toggle 持久化
- ui/settings-page.slint: SettingsToggleRow 的 toggled callback
  从 (bool) 改成 (string, bool) — 把 row 自己的 label + 新 value
  一起交给 caller;row 内部先 value=!value 再 emit;value 改
  in-out property 让内部可以 flip;SettingsPage 加 callback
  toggle-persist(string, bool);7 个真 toggle 全部 wire 到
  toggle-persist(Notifications 4 + Startup 3)
- ui/app.slint: MainWindow 加 in-out property ×7 +
  setting-toggled(string, bool) callback;启动从 ActiveConfig
  装载;SettingsPage section 把 7 个 property forward 给 page +
  toggle-persist 转接到 root.setting-toggled
- src/lib.rs:
  · 启动从 ActiveConfig 读 7 个 toggle 设到 MainWindow
  · 新 on_setting_toggled 回调:load AppConfig → 按 label 改
    对应字段 → save_config;start_with_windows 额外调
    startup::set_enabled 写 HKCU Run(Phase 6 C1)
  · on_close_requested 看 close_to_tray:true 时
    window().hide() + KeepWindowShown;false 时保持原来的
    HideWindow 行为

避坑:
- Slint 1.x callback 不能直接用 label 区分,必须 caller 传
  string 参数;改造 (bool) → (string, bool)
- 设置 in-out property 让 row 内部能 flip value 然后 emit
  新值(caller 收到的就是 post-flip 值)
- close_to_tray 在 UI MainWindow 上是 in-out property,Rust
  端用 get_close_to_tray() 实时读,所以 toggle 立刻影响
  close_requested 行为(不需要重启)
- on_close_requested 内部读 get_close_to_tray 要 upgrade()
  MainWindow weak handle,捕获空时 fallback false(默认
  close 而不是 hide)

验证:cargo build / build --release 都 exit 0,0 warning
- cargo test: 15 passed; 0 failed
- debug   35.64 MB, subsystem = WINDOWS_CUI
- release  9.78 MB, subsystem = WINDOWS_GUI
2026-09-19 07:14:35 +08:00
KennethCheng 0cf7a4eaaa 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)
2026-09-19 05:18:26 +08:00
KennethCheng 680fbfa0ad feat(ui): Phase 2 C3 — Settings 页 + Dialogs + 窗口状态保存
- src/storage.rs: AppState (last_window_width/height) + load/save
  atomic write 到 %APPDATA%/WindowsGotify/state.json(§18)
- ui/settings-page.slint: 5 分组(Connection / Notifications / Startup
  / Appearance / Advanced),Appearance 实时切主题(System/Light/Dark
  radio);Advanced 折叠
- ui/dialogs.slint: AboutDialog + ConfirmDialog(§54)
- ui/server-card.slint: 单 server 行(name/url/enabled toggle/edit/remove)
- ui/app.slint: settings 页切换、About 导航、theme 联动
- src/lib.rs: load_state → 启动时应用窗口尺寸;ui.window()
  .on_close_requested 在 OS 关闭前 save_state;wire about/close
  callbacks
- src/error.rs: 加 AppError::Storage(String) 变体
- Cargo.toml: 加 serde + serde_json

避坑:
- Slint 1.x 同文件内 export component 必须在引用前定义(sub-components
  移到 SettingsPage 上面)
- Slint 不支持 padding 两值简写 (padding: 0 16px) → 用 padding-* 四属性
- Slint 不支持 Rectangle 内建 border-color 覆盖 → 命名 card-border
- Slint 不支持 alignment: right → 用 spacer + horizontal-stretch: 1
- 修了一处 .slint 文件被 PowerShell Set-Content GBK 损坏的问题(已
  通过 write 工具 + UTF-8 no BOM 重写并清理 ui/ 所有 .slint)

验证:cargo build / build --release 都 exit 0,0 warning
- debug   27.55 MB, subsystem = WINDOWS_CUI
- release  8.10 MB, subsystem = WINDOWS_GUI
2026-09-19 04:47:54 +08:00
KennethCheng 49cbd9e371 feat(ui): Phase 2 C1 — MainWindow + Sidebar + 颜色/主题系统
完成 AGENTS.md §57 Phase 2 的前 3 项:MainWindow 布局、Sidebar、Dark 主题
基础。剩余的 Message list / Empty state / Settings / Light-Switch /
Window state 留到 C2 / C3。

技术栈变化
----------

本次最大的工程变化是把 Slint UI 从 `slint!{}` macro 内嵌迁到独立
`.slint` 文件 + build.rs 编译:

  ui/app.slint           MainWindow + Sidebar 骨架(Phase 2 C1 完整实现)
  ui/theme.slint         export enum AppearanceMode { System, Light, Dark }
                         (不再有 export global Theme / Palette,见下文)
  ui/components.slint    NavItem / SectionLabel / Divider(Dark 默认颜色)
  ui/message-card.slint  占位 stub(C2 实现)
  ui/message-list.slint  占位 stub(C2 实现)
  ui/empty-state.slint   占位 stub(C2 实现)
  ui/settings-page.slint 占位 stub(C3 实现)
  ui/server-card.slint   占位 stub(C3 实现)
  ui/dialogs.slint       占位 stub(C3 实现)
  build.rs               slint_build::compile("ui/app.slint"),
                         把生成的 .rs 路径写入 SLINT_INCLUDE_GENERATED
  src/lib.rs             `slint::include_modules!()` 引入编译产物;
                         `MainWindow::new()` 拿 UI 实例;
                         `ui.set_theme(AppearanceMode::Dark)` 初始化
                         (C3 改成从 state.json 读 + 监听用户切换)

为什么不用 `include_slint!` —— 那是 `slint_interpreter` crate 提供的
API,不在 `slint` 1.18 主 crate 里。官方推荐的"build-time precompile"
路线是 `slint_build::compile()` + `slint::include_modules!()`,文档明确
写了这一步。

为什么不用 export global Palette / Theme
---------------------------------------

最初一版我把 Theme + Palette 都做成 `export global`,期望从 Rust 端
用 `ui.global::<Theme>().set_appearance(...)` 切换 Light/Dark。失败
了:slint-build 1.18 在生成 Rust 代码时把这两个 global 都 dead-code
消除了,generated 的 pub use 列表里只剩 `MainWindow` 和
`SidebarSelection`。原因——global 的所有 binding 在编译期都被简化为
常量(Theme.appearance 默认 Dark,Palette.background 绑定直接 inline 成
`#1f2024`),没有任何 dynamic 引用保留 global 的符号表。

解决方案:把 Theme + Palette 全部搬到 MainWindow 上作为 out properties,
由 `MainWindow.theme` 触发 binding 重新计算。Rust 端用
`ui.set_theme(AppearanceMode::Light)` 切。代价是 sidebar 的 NavItem
暂时只有 Dark 默认色(fixed `#2f3035` 等),Light theme 在 C3 通过
property forwarding 扩展 NavItem 时补齐——这是 C1 阶段明确接受的折衷。

这一节值得写进 commit 而不是悄悄改掉——后面任何看到代码的人都会好奇
"为什么 palette 不直接 export global?",是这个原因。下次有 Phase 3+ 的人
碰这块也省得踩同样的坑。

Slint 版本对齐
--------------

之前 commit 时钉过 `slint = "1"`(让 cargo 选最新 1.x 稳定版),现在
slint-build 1.18.0 引入 build-deps:`slint-build`, `i-slint-compiler` 等。
两者锁到同一个 1.18.0 版本是 Slint 自身的 forward-compat 约束
(slint-build 的 `compat-1-18` feature 在 build.rs 启动时强校验)。

MainWindow / Sidebar 布局细节
-----------------------------

按 AGENTS.md §6 / §7:

- 960×680 preferred,760×520 min —— Slint 1.18 不允许同时设
  `width` + `min-width`,用 `preferred-width`/`preferred-height` 替代
  (之前第一次 build 报 `Cannot specify both 'width' and 'min-width'`,
  这里记录一下)。
- 232px sidebar 宽,含 AGENTS.md §7 列出的 6 个 section
  (logo + All + Servers 占位 + Filters 三个 + Settings + About)
- SidebarSelection enum 驱动右侧 view-switcher,三个 view 都有占位
  文字(Messages / Settings / About),C2 / C3 替换实际内容
- AGENTS.md §52 颜色 token 全部按 dark / light 两套列出,由
  `MainWindow.is-dark` 派生;优先级颜色(§7 message card)也按
  §7 Low/Normal/High/Max 映射

未做(按计划归到后续 commit)
------------------------------

C2:
  - src/models.rs(MessageItem + Priority enum,AGENTS.md §34)
  - src/mock.rs(生成 10 条 mock 消息)
  - ui/message-card.slint 真实内容
  - ui/message-list.slint 真实内容(ListView 接 Rust Model adapter)
  - ui/empty-state.slint 真实内容(§9 "You're all caught up" + ✦ + Test button)
  - main 区 if-switch 真正根据 list.is_empty 选 list / empty-state

C3:
  - src/storage/* —— 持久化 state.json
  - ui/settings-page.slint 真实内容(§10 五个分组 + Advanced 折叠)
  - ui/server-card.slint 真实内容
  - ui/dialogs.slint 真实内容(About + confirm)
  - sidebar NavItem 接 palette forwarding,Light theme 真的换 sidebar 颜色
  - 主题切到 Light/System 实时生效(System 跟随 Windows 主题)

验证
----

  cargo build              exit 0, 5.10s(增量)
  cargo build --release    exit 0(slint + skia + accesskit 全重编,约 8 分钟)
  warnings                 0
  debug   .exe             26.01 MB, subsystem = 3 (WINDOWS_CUI / console)
  release .exe              7.87 MB, subsystem = 2 (WINDOWS_GUI)

Phase 2 C1 验收:能在 Windows 上打开 MainWindow(960×680、Dark、含完整
Sidebar),通过 release profile 的 WINDOWS_GUI subsystem 验证无 console。
Sidebar 切换 Settings/About 时右侧文案占位更新——C2/C3 替换为实际 view。
2026-09-19 04:34:06 +08:00