feat(download): M94 下载页换成真组件 + 真快照 + 真指令(PAGEDL)

页面(crates/app-iced/src/pages/download*):
- Tabs(分类)/ ListItem(版本行)/ filled 安装按钮 / progress / EmptyState / divider
  全部取自 components/ 的真组件;页头动作换成 buttons::text
- 数据只吃 pages::download::Download(app/view.rs 由 AppState 快照转,installed_only 与
  download_query 照用);页面自己不留业务状态,交互只发意图
- 状态:加载(不确定进度 + 文案)/ 空(EmptyState + 刷新 CTA)/ 有数据 / 安装中 / 安装失败
  (失败给可读文案 + 「打开日志」出口)

接线(追加式):
- pages::Message 加 SelectVersion / InstallVersion / CancelInstall / SetCategory
- app/update.rs 翻成 AppCommand::{InstallVersion, CancelInstall, ListVersions}
  (SelectVersion 只改视图模型;「刷新」= 当前分类重新拉,全部走 RefreshAll)
- pages::Model 加 download 字段

文案:28 个 Download* key 进中英两套 catalog(一行一个 key 的全语言);页面零中文字面量,
  core 的中文阶段串(InstallStage::label / message)一律不进界面

i18n 结构:catalog.rs 的既有测试模块原样外置到 catalog/tests.rs(392 行 → 282 行,
  逐字未改),新 key 才装得下 400 行硬上限

测试:app-iced 276 + i18n 23 全绿;clippy -D warnings 与 fmt --check 干净
This commit is contained in:
tower authored and AskaEth committed 2026-11-02 21:36:22 +08:00
1 parent b3f99652bb
commit 57e8380b24
14 files changed
+1633 -247

No files matched your search

+132
View File
@@ -0,0 +1,132 @@
//! 下载页的**演示数据**(截图 harness 与单测的输入,产品路径不读)。
//!
//! 与 `pages/instances/data.rs` 同一条纪律:演示条目是**页面侧常量**,不是业务 DTO——
//! 于是核心 DTO 每改一次字段,基线图不会跟着漂。真页面的数据只走
//! [`Download::from_snapshot`](super::model::Download::from_snapshot)。
//!
//! 演示条目本身也是一步「DTO → 视图模型」的真转换([`VersionRow::of`](super::model::VersionRow)、
//! [`InstallView::of`](super::model::InstallView)):图上看到的副文案 / 进度行就是产品路径
//! 那条转换的产物,不是另写一套拼串。
//!
//! ## 为什么要一个环境变量
//!
//! 状态矩阵(加载 / 空 / 有数据 / 安装中 / 安装失败)都要出图,而 harness 的
//! `--screen` 只选页面、选不了页面状态:
//!
//! ```text
//! ULL_DL_DEMO=empty cargo run -p unlucky-app-iced --example pages_demo -- \
//! --screen=download --screenshot=out.png --screenshot-theme=light --software-renderer
//! ```
//!
//! 取值:`ready`(缺省)/ `loading` / `empty` / `selected` / `installing` / `failed`。
//! **只有 harness 与演示走这里**:产品路径由 `app/view.rs` 从 presenter 快照建模型。
use unlucky_presenter::dto::{
AncientKind, InstallProgressView, InstallStage, VersionKind, VersionSummary,
};
use unlucky_i18n::Catalog;
use super::model::{Category, Download, InstallView, VersionRow};
/// 取证开关的名字([`demo`] 读它)。
pub const DEMO_STATE_ENV: &str = "ULL_DL_DEMO";
/// 演示模型:按 [`DEMO_STATE_ENV`] 选状态,缺省是「有数据」。
pub fn demo(catalog: Catalog) -> Download {
match std::env::var(DEMO_STATE_ENV).ok().as_deref() {
Some("loading") => Download::loading(),
Some("empty") => Download {
loaded: true,
..Download::loading()
},
Some("installing") => Download {
install: Some(install(InstallStage::Downloading, 0.42, catalog)),
selected: Some("1.21.9".to_string()),
..ready(catalog)
},
Some("selected") => Download {
selected: Some("1.21.9".to_string()),
..ready(catalog)
},
Some("failed") => Download {
install: Some(install(InstallStage::Failed, 0.0, catalog)),
selected: Some("1.21.9".to_string()),
..ready(catalog)
},
_ => ready(catalog),
}
}
/// 有数据:六行(三条正式、一条预览、两条远古),与 presenter 的 `mock_versions` 同形。
///
/// 不复用 `mock_data::mock_versions()`:演示数据是**出图的基线**,得比 mock 稳定
/// (mock 随 P3 的假数据一起漂)。
fn ready(catalog: Catalog) -> Download {
fn summary(id: &str, kind: VersionKind, released: &str, installed: bool) -> VersionSummary {
VersionSummary {
id: id.to_string(),
kind,
released: released.to_string(),
installed,
}
}
let versions = vec![
summary("1.21.9", VersionKind::Release, "2026-09-20", false),
summary("1.21.8", VersionKind::Release, "2026-07-11", true),
summary("1.20.4", VersionKind::Release, "2023-12-07", true),
summary("1.21.9-pre3", VersionKind::Snapshot, "2026-09-01", false),
summary(
"rd-132211",
VersionKind::Ancient(AncientKind::Classic),
"2009-05-13",
false,
),
summary(
"a1.2.6",
VersionKind::Ancient(AncientKind::OldAlpha),
"2010-08-11",
false,
),
];
let rows: Vec<VersionRow> = versions
.iter()
.map(|version| VersionRow::of(version, catalog))
.collect();
Download {
// 混合分类 → 没有单个 chip 该亮(这正是 `Category::of` 的反推结果)
category: Category::of(&versions),
rows,
selected: None,
install: None,
loaded: true,
}
}
/// 一次安装的演示快照(文件计数 / 字节 / 速度 / 剩余时间都给真值,进度条画得出来)。
fn install(stage: InstallStage, ratio: f32, catalog: Catalog) -> InstallView {
let total = 512;
let done = (f64::from(ratio) * total as f64) as u64;
let failed = matches!(stage, InstallStage::Failed);
InstallView::of(
&InstallProgressView {
version: "1.21.9".to_string(),
instance: "1.21.9".to_string(),
// 演示也**不**搬 core 的中文串:`loader` 字段这里给的是结构化徽标的英文名
loader: "Vanilla".to_string(),
stage,
files_done: if failed { 137 } else { done },
files_total: total,
bytes_done: 0,
bytes_total: 0,
speed_bps: if failed { 0 } else { 1_258_291 },
eta_secs: if failed { 0 } else { 75 },
message: String::new(),
},
catalog,
)
}