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
@@ -11,7 +11,7 @@
|
||||
//! |---|---|---|
|
||||
//! | 「安装」 | [`Message::InstallVersion`] | `AppCommand::InstallVersion(id)` |
|
||||
//! | 「取消安装」 | [`Message::CancelInstall`] | `AppCommand::CancelInstall` |
|
||||
//! | 「打开日志」 | [`Message::Navigate(Route::Logs)`] | `AppState::navigate`(不派指令) |
|
||||
//! | 「打开日志」 | [`Message::Navigate`](`Route::Logs`) | `AppState::navigate`(不派指令) |
|
||||
//!
|
||||
//! 按钮按下**不改任何本地状态**:进度条读的是 `model.install`,它只由 presenter 的
|
||||
//! `InstallProgress` 事件灌进来(`apply` → 视图模型重建)。于是「点了没变」= 后端还没回事件。
|
||||
@@ -46,16 +46,51 @@ use crate::theme::Ui;
|
||||
|
||||
use super::super::frame;
|
||||
use super::super::Message;
|
||||
use super::model::{Download, InstallView, VersionRow};
|
||||
use super::model::{Download, InstallView};
|
||||
|
||||
/// 面板要画哪一种形态(**决策与画法分开**:决策是纯函数,单测直接钉它)。
|
||||
///
|
||||
/// 分开的理由就是本轮复审那件事:「装完之后面板还占着、用户再也装不了第二个版本」——
|
||||
/// 这是**决策**错了,画法本身没错;把决策抽出来,测试才能在不开图形环境的情况下断言它。
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(super) enum PanelState<'a> {
|
||||
/// 没有安装、也没选中:一句「选一个版本再安装」
|
||||
Hint,
|
||||
/// 选中了一个版本:给出「安装」(已装的给禁用态)
|
||||
Ready {
|
||||
/// 选中的版本 id(= `AppCommand::InstallVersion` 的载荷)
|
||||
id: &'a str,
|
||||
/// 本地已经装过(按钮禁用,列表行上也有「已安装」+ 勾)
|
||||
installed: bool,
|
||||
},
|
||||
/// 有安装(进行中 / 失败):进度或错误面板
|
||||
Install(&'a InstallView),
|
||||
}
|
||||
|
||||
/// 面板此刻该画哪一种(纯函数,无 iced)。
|
||||
///
|
||||
/// 顺序是**安装优先**:有安装时面板讲安装,讲不了列表;安装结束后 `model.install` 由
|
||||
/// [`Download::from_snapshot`](super::model::Download::from_snapshot) 清掉,于是这里自然回到
|
||||
/// [`PanelState::Ready`] / [`PanelState::Hint`],用户能接着选下一个版本。
|
||||
pub(super) fn panel_state(model: &Download) -> PanelState<'_> {
|
||||
match &model.install {
|
||||
Some(install) => PanelState::Install(install),
|
||||
None => match model.selected_row() {
|
||||
Some(row) => PanelState::Ready {
|
||||
id: row.id.as_str(),
|
||||
installed: row.installed,
|
||||
},
|
||||
None => PanelState::Hint,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 底部的安装面板。
|
||||
pub(super) fn panel<'a>(ui: &'a Ui, catalog: Catalog, model: &'a Download) -> Element<'a, Message> {
|
||||
let content = match &model.install {
|
||||
Some(install) => installing(ui, catalog, install),
|
||||
None => match model.selected_row() {
|
||||
Some(entry) => ready(ui, catalog, entry),
|
||||
None => hint(ui, catalog),
|
||||
},
|
||||
let content = match panel_state(model) {
|
||||
PanelState::Install(install) => installing(ui, catalog, install),
|
||||
PanelState::Ready { id, installed } => ready(ui, catalog, id, installed),
|
||||
PanelState::Hint => hint(ui, catalog),
|
||||
};
|
||||
|
||||
container(content)
|
||||
@@ -79,24 +114,24 @@ fn hint<'a>(ui: &'a Ui, catalog: Catalog) -> Element<'a, Message> {
|
||||
///
|
||||
/// 已装的版本给**禁用**的 Outlined 按钮(`Button::disabled` 只清消息、保留几何):
|
||||
/// 「已装」是列表行上的事实(overline + 勾),按钮再点一次不会更清楚。
|
||||
fn ready<'a>(ui: &'a Ui, catalog: Catalog, entry: &'a VersionRow) -> Element<'a, Message> {
|
||||
fn ready<'a>(ui: &'a Ui, catalog: Catalog, id: &'a str, installed: bool) -> Element<'a, Message> {
|
||||
let label = container(frame::text_role(
|
||||
ui,
|
||||
TypeRole::BodyMedium,
|
||||
false,
|
||||
entry.id.as_str(),
|
||||
id,
|
||||
ui.color(Role::OnSurface),
|
||||
))
|
||||
.width(Length::Fill);
|
||||
|
||||
let action = if entry.installed {
|
||||
let action = if installed {
|
||||
buttons::outlined(ui, Button::disabled(catalog.text(Key::DownloadInstall)))
|
||||
} else {
|
||||
buttons::filled(
|
||||
ui,
|
||||
Button::new(
|
||||
catalog.text(Key::DownloadInstall),
|
||||
Message::InstallVersion(entry.id.clone()),
|
||||
Message::InstallVersion(id.to_string()),
|
||||
)
|
||||
.leading(IconKind::Download),
|
||||
)
|
||||
@@ -200,14 +235,24 @@ fn failed<'a>(ui: &'a Ui, catalog: Catalog, install: &'a InstallView) -> Element
|
||||
ui.color(Role::OnErrorContainer),
|
||||
);
|
||||
|
||||
let action = buttons::filled(
|
||||
ui,
|
||||
Button::new(
|
||||
catalog.text(Key::DownloadOpenLogs),
|
||||
Message::Navigate(Route::Logs),
|
||||
)
|
||||
.leading(IconKind::Logs),
|
||||
);
|
||||
let actions = row![
|
||||
buttons::filled(
|
||||
ui,
|
||||
Button::new(
|
||||
catalog.text(Key::DownloadOpenLogs),
|
||||
Message::Navigate(Route::Logs),
|
||||
)
|
||||
.leading(IconKind::Logs),
|
||||
),
|
||||
// 失败把面板钉住了:不给出口的话用户既重试不了、也装不了别的版本(只能重启)。
|
||||
// `CancelInstall` 在 host 侧就是「清 install + 发 InstallProgress(None)」,对没在跑的任务是安全 no-op。
|
||||
buttons::text(
|
||||
ui,
|
||||
Button::new(catalog.text(Key::DownloadDismiss), Message::CancelInstall),
|
||||
),
|
||||
]
|
||||
.spacing(ui.space(Space::X2))
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
container(
|
||||
column![
|
||||
@@ -220,7 +265,7 @@ fn failed<'a>(ui: &'a Ui, catalog: Catalog, install: &'a InstallView) -> Element
|
||||
catalog.text(Key::DownloadFailedHint),
|
||||
ui.color(Role::OnErrorContainer),
|
||||
),
|
||||
action,
|
||||
actions,
|
||||
]
|
||||
.spacing(ui.space(Space::X2))
|
||||
.width(Length::Fill),
|
||||
@@ -237,3 +282,6 @@ fn failed<'a>(ui: &'a Ui, catalog: Catalog, install: &'a InstallView) -> Element
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
Reference in new issue
Block a user