fix(download): M94 r2 —— 安装终态出口 + 分类真机过滤(复审 P1-1 / P2-1)
P1-1 安装一旦收尾就再也装不了第二个版本(终态面板没有出口)
- 页面侧:`Download::from_snapshot` 把 **Done / Cancelled** 当结束(`InstallView::finished`),
面板让回列表 —— 装完一个版本可以直接选下一个继续装(图:r2-done / r2-done-next)。
不清 `Failed`:失败态带着用户要的东西(停在哪个文件 + 去日志页)。
- 面板加真出口:失败态「Open logs」(实心)+「Dismiss」(文本 → `Message::CancelInstall`,
host 侧对没在跑的任务是安全 no-op),修掉「失败后连重试都不可达」。
- 决策抽成纯函数 `install::panel_state()`(Hint / Ready / Install),`panel()` 只是它的画法,
于是「能不能再装」可以被单测钉死,不用开图形环境。
- 两条新测试**不走 MockBackend**(它从不发 `InstallProgress`,终态永远跑不到 ⇒ 假绿):
直接构造 `AppState` + `apply(CoreEvent::InstallProgress(..))`。
P2-1 分类 chip 在真 host 上是空控件
- `from_snapshot(state, category, catalog)` 收分类并按 `Category::matches` **本地过滤**
(真 host 的 `ListVersions(_)` 丢掉分类参数、回全量清单);
- 分类不再从列表反推(`Category::of` 只留给演示首帧),`app/view.rs::model` 收分类、
`refresh_view` 把上一份视图模型的分类带回去 ⇒ 事件回来 chip 不会跳回「全部」;
- `update.rs` 的 `SetCategory` 写分类后**立刻重建**(chip 与列表同一个谓词),再派真指令。
p3(顺带)
- 失败态与占位态共存:列表空/加载中时若安装还在跑,面板照样出现在底部(r2-installing-empty);
- 修 `install.rs` 的坏 rustdoc 链接;`install.rs` / `model.rs` 的测试模块外置到子文件守住 400 行。
测试文件:`pages/download/{model,install}/tests.rs`(新),`pages/download/tests.rs` 里的
`from_snapshot` 调用补分类参数。
实证(回退法):删掉 `.filter(|view| !view.finished())` → 两条终态测试红并点名
「Done 之后不许再占着安装面板」;删掉 `.filter(|category.matches(..))` → 两条分类测试红
(「只剩正式版」left=3 right=1)。放回修复 → 全绿。
门禁:fmt --check 干净、clippy -D warnings 干净、app-iced 280 + 集成 6 + i18n 23 全绿(-j 4)。
This commit is contained in:
1 parent
9d6ee70301
commit
6453c76750
12 files changed
+432
-74
No files matched your search
@@ -231,6 +231,14 @@ impl InstallView {
|
||||
self.stage.is_terminal()
|
||||
}
|
||||
|
||||
/// **结束且没什么可说**的收尾:成功(`Done`)与取消(`Cancelled`)。
|
||||
///
|
||||
/// 这两种终态页面没有动作可给,留着只会让安装面板停在一个点不动的状态上(复审 p2 的根因)。
|
||||
/// 失败**不在其中**:它还要给原因出口。
|
||||
pub fn finished(&self) -> bool {
|
||||
matches!(self.stage, InstallStage::Done | InstallStage::Cancelled)
|
||||
}
|
||||
|
||||
/// 还在跑(「取消安装」只在这时给)。
|
||||
pub fn running(&self) -> bool {
|
||||
!self.stage.is_terminal()
|
||||
@@ -273,13 +281,21 @@ impl Download {
|
||||
|
||||
/// presenter 快照 → 下载页模型(**唯一的真数据入口**)。
|
||||
///
|
||||
/// 快照里的两个过滤条件(`installed_only` / `download_query`)原样照用:它们是 presenter 的
|
||||
/// 状态,页面不该另存一份;过滤后为空就是「空态」(`loaded` 仍为 `true`)。
|
||||
pub fn from_snapshot(state: &AppState, catalog: Catalog) -> Self {
|
||||
/// 三个过滤条件都是「过滤」,不是「乐观回显」:
|
||||
/// - `installed_only` / `download_query` 是 presenter 自己的状态,页面不该另存一份;
|
||||
/// - `category` 是**本页的过滤条件**(参数传进来,调用方持有它):真 host 的
|
||||
/// `AppCommand::ListVersions(_)` 会丢掉分类参数、回全量清单(见 `host/dispatch.rs`),
|
||||
/// 所以分类**必须在页面这一侧过滤**,否则 chip 只是点亮一下、列表照旧是全量(复审 P2-1)。
|
||||
/// 过滤谓词与 chip 的选中判断是同一个 [`Category::matches`],于是「chip 说的」与「列表画的」
|
||||
/// 永远是同一份真相,事件回来重建也不会跳回「全部」。
|
||||
///
|
||||
/// 过滤后为空就是「空态」(`loaded` 仍为 `true`)。
|
||||
pub fn from_snapshot(state: &AppState, category: Category, catalog: Catalog) -> Self {
|
||||
let query = state.download_query.trim().to_ascii_lowercase();
|
||||
let rows = state
|
||||
.versions
|
||||
.iter()
|
||||
.filter(|version| category.matches(version.kind))
|
||||
.filter(|version| !state.installed_only || version.installed)
|
||||
.filter(|version| query.is_empty() || version.id.to_ascii_lowercase().contains(&query))
|
||||
.map(|version| VersionRow::of(version, catalog))
|
||||
@@ -288,13 +304,23 @@ impl Download {
|
||||
let install = state
|
||||
.install
|
||||
.as_ref()
|
||||
.map(|view| InstallView::of(view, catalog));
|
||||
.map(|view| InstallView::of(view, catalog))
|
||||
// 安装**已经成功结束/被取消**就不再占用面板(复审 p2,本轮的修复点):
|
||||
// presenter 只在 `InstallProgress(None)`(取消指令)时清 `AppState.install`,`Done`
|
||||
// 会一直留在快照里 ⇒ 面板被一个**没有动作**的终态钉死,用户装完第一个版本就再也发不出
|
||||
// 第二次安装(只能重启进程)。界面这里把「结束」当结束:面板让回列表。
|
||||
//
|
||||
// 为什么不连 `Failed` 一起清:失败态带着用户要的东西(停在哪个文件 + 去日志页),
|
||||
// 那正是 P2-4 要它一眼可辨的理由。
|
||||
.filter(|view| !view.finished());
|
||||
// 快照没有「选中」字段:正在安装的那个版本就是「选中」(见模块文档的缺口表)。
|
||||
let selected = install.as_ref().map(|view| view.version.clone());
|
||||
|
||||
Self {
|
||||
rows,
|
||||
category: Category::of(&state.versions),
|
||||
// 分类不再从列表反推(那正是「chip 跳回全部」的来源):谁选的就是谁。
|
||||
// [`Category::of`] 只留给「不经过用户选择」的入口(演示数据的首帧)。
|
||||
category,
|
||||
selected,
|
||||
install,
|
||||
loaded: !state.versions.is_empty(),
|
||||
@@ -317,3 +343,6 @@ impl Download {
|
||||
self.loaded && self.rows.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
Reference in new issue
Block a user