Commit Graph
5 Commits
Author SHA1 Message Date
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 11a122216b feat(ui): Phase 2 C2 — Mock 消息模型 + Message list + Empty state
- ui/message-card.slint: export enum Priority + struct MessageItem +
  MessageCard 组件(§7 优先级圆点 + 标题 + 应用·预览 + 相对时间 +
  URL 指示)
- ui/message-list.slint: VerticalLayout + for 渲染 MessageCard
  (避开 std-widgets 的 include_paths 配置需求)
- ui/empty-state.slint: §9 ✦ glyph(Path 自绘)+ "You're all caught up"
  + Test Notification 按钮
- ui/app.slint: messages.length 切换 list / empty-state;点击回
  调到 MainWindow 顶层 callback
- src/models.rs: mock_messages() 生成 12 条覆盖 4 个 priority 的
  假消息;format_relative() 输出 "2 min ago" / Yesterday 等
- src/lib.rs: ui.set_messages(mock_messages_model()) 装载;wire
  on_test_notification_clicked / on_message_card_clicked

避坑:
- Slint 1.18 不允许覆盖 Rectangle 内建 border-color,命名用 card-border
- ModelRc::from 需要 Rc<VecModel<T>> 不是裸 VecModel

验证:cargo build / build --release 都 exit 0,0 warning
- debug   26.55 MB, subsystem = WINDOWS_CUI
- release  7.94 MB, subsystem = WINDOWS_GUI
2026-09-19 04:39:24 +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
KennethCheng a3a6a7c91d chore: 初始化 Windows-Gotify-Client-Rust 项目骨架
使用 Rust + Slint 重建一个轻量、现代、Windows 原生体验良好的 Gotify
桌面客户端。本次只搭目录骨架与占位文件,未进入 Phase 1(Bootstrap)。

本次提交内容
------------
- 保留 AGENTS.md(1363 行定制 Agent 实施计划,含 8 阶段路线图)
- 新建 Cargo.toml([package] 元数据 + AGENTS.md §44 依赖模板 + §42
  release profile;无 [[bin]],等 Phase 1 才加 src/main.rs)
- 新建 README.md(项目导览,引用 AGENTS.md)
- 新建 .gitignore(Rust / Slint / Windows / IDE / 密钥)
- 新建 LICENSE(MIT 占位)
- 按 AGENTS.md §4 建好全部 10 个目录,每个空目录放 .gitkeep:
    assets/        assets/fonts/
    ui/            scripts/
    src/           src/gotify/
    src/storage/   src/windows/
    src/services/  tests/

未提交(Phase 1 起才落地)
------------------------
- src/main.rs 及所有 .rs 业务代码
- ui/*.slint 视图
- scripts/build-release.ps1 等构建脚本
- Cargo.lock(当前 Cargo.toml 是占位、无可编译代码)

技术栈约束(来自 AGENTS.md §1 / §2)
------------------------------------
- Rust stable + Slint 1.17.x + Windows 10/11
- 显式禁用:Tauri / Wails / Electron / WebView2 / WPF / WinUI 3 / Node 运行时
- 单一主程序,无浏览器运行时依赖;UI 由 Slint 原生渲染

仓库
----
远程:https://gitea.kennethcheng.cc/KennethCheng/Windows-Gotify-Client-Rust
2026-09-19 04:02:51 +08:00