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:
1 parent
b3f99652bb
commit
57e8380b24
14 files changed
+1633
-247
No files matched your search
@@ -284,10 +284,26 @@ impl App {
|
||||
pages::Message::Launch(id) => self.dispatch(AppCommand::LaunchInstance(id)),
|
||||
pages::Message::Stop(id) => self.dispatch(AppCommand::StopInstance(id)),
|
||||
pages::Message::Appearance(change) => self.on_appearance(change),
|
||||
// 还没接后端的占位动作:只打日志(哪几个、为什么,见 pages 模块文档的偏离登记)
|
||||
pages::Message::Action(action) => {
|
||||
eprintln!("[page] 占位动作 {action:?} pressed(没有对应 AppCommand)");
|
||||
// ── 下载页(M94):选中是**页面内**状态(不 dispatch),其余三条都派真指令。
|
||||
pages::Message::SelectVersion(id) => self.view.download.selected = Some(id),
|
||||
pages::Message::InstallVersion(id) => self.dispatch(AppCommand::InstallVersion(id)),
|
||||
pages::Message::CancelInstall => self.dispatch(AppCommand::CancelInstall),
|
||||
pages::Message::SetCategory(category) => {
|
||||
// chip 立刻反映选中(视图模型),列表由后端回 `VersionListChanged` 换掉
|
||||
self.view.download.category = category;
|
||||
if let Some(kind) = category.command() {
|
||||
self.dispatch(AppCommand::ListVersions(kind));
|
||||
}
|
||||
}
|
||||
// 还没接后端的占位动作:只打日志(哪几个、为什么,见 pages 模块文档的偏离登记)
|
||||
pages::Message::Action(action) => match action {
|
||||
// 「刷新下载源」不是占位动作:具体分类 → 重拉该分类;「全部」→ 让 host 重扫一遍
|
||||
pages::Action::RefreshSources => match self.view.download.category.command() {
|
||||
Some(kind) => self.dispatch(AppCommand::ListVersions(kind)),
|
||||
None => self.dispatch(AppCommand::RefreshAll),
|
||||
},
|
||||
_ => eprintln!("[page] 占位动作 {action:?} pressed(没有对应 AppCommand)"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,3 +399,6 @@ fn env_start_route() -> Option<Route> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod download_wiring;
|
||||
@@ -0,0 +1,65 @@
|
||||
//! 下载页(M94)在 app 层的接线测试:**命令出得去、状态不自己变**。
|
||||
//!
|
||||
//! 从 `app/update.rs` 外置(那边 348 行 + 这个模块会顶破 400 行硬上限;同一句话的理由见
|
||||
//! `crates/i18n/src/catalog/tests.rs`)。`use super::*` 依然指向 `crate::app::update`。
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use unlucky_presenter::dto::{AppEvent, Backend, VersionCategory};
|
||||
use unlucky_presenter::settings_bridge::SettingsBridge;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// 只记账、**不回事件**的后端(同 `app/tests/presenter.rs`):命令出了 UI 层,状态一个字节都不许变。
|
||||
/// 不用 `MockBackend`:它同步回事件,「本地乐观回显」与「事件回流」终态相同,断言会**假绿**。
|
||||
struct Recorder(Rc<RefCell<Vec<AppCommand>>>);
|
||||
|
||||
impl Backend for Recorder {
|
||||
fn drain_events(&mut self) -> Vec<AppEvent> {
|
||||
Vec::new()
|
||||
}
|
||||
fn dispatch(&mut self, command: AppCommand) {
|
||||
self.0.borrow_mut().push(command);
|
||||
}
|
||||
}
|
||||
|
||||
/// 四条下载页消息:三条派真指令,`SelectVersion` 只改视图模型(P3-2 的不乐观回显)。
|
||||
/// 抓得住:意图被写成「本地改状态就算完」、或派错指令;抓不住:host 收到后真的开始下载。
|
||||
#[test]
|
||||
fn download_intents_dispatch_real_commands() {
|
||||
let log: Rc<RefCell<Vec<AppCommand>>> = Rc::new(RefCell::new(Vec::new()));
|
||||
let families = crate::theme::Families {
|
||||
text: "Segoe UI",
|
||||
display: "Segoe UI",
|
||||
mono: "Consolas",
|
||||
};
|
||||
let mut app = App::with_presenter(
|
||||
crate::cli::Options::default(),
|
||||
families,
|
||||
SchemeFlavor::Light,
|
||||
Box::new(Recorder(Rc::clone(&log))),
|
||||
SettingsBridge::memory_only(),
|
||||
);
|
||||
app.on_page(pages::Message::InstallVersion("1.21.9".to_string()));
|
||||
app.on_page(pages::Message::CancelInstall);
|
||||
app.on_page(pages::Message::SetCategory(pages::Category::Release));
|
||||
app.on_page(pages::Message::SelectVersion("1.21.8".to_string()));
|
||||
let expected = vec![
|
||||
AppCommand::InstallVersion("1.21.9".to_string()),
|
||||
AppCommand::CancelInstall,
|
||||
AppCommand::ListVersions(VersionCategory::Release),
|
||||
];
|
||||
assert_eq!(
|
||||
*log.borrow(),
|
||||
expected,
|
||||
"三条各派一条;`SelectVersion` 不派"
|
||||
);
|
||||
assert_eq!(app.view.download.selected.as_deref(), Some("1.21.8"));
|
||||
assert_eq!(app.view.download.category, pages::Category::Release);
|
||||
assert_eq!(
|
||||
app.view.download.rows.len(),
|
||||
0,
|
||||
"recorder 不回事件 ⇒ 列表不该冒出来"
|
||||
);
|
||||
}
|
||||
@@ -113,6 +113,7 @@ pub(super) fn model(
|
||||
window_width: width,
|
||||
active: route.nav(),
|
||||
instances: pages::Instances { rows, detail },
|
||||
download: pages::Download::from_snapshot(state, unlucky_i18n::Lang::En.catalog()),
|
||||
forced: forced.map(|state| match state {
|
||||
crate::cli::ForcedState::Hover => Interaction::Hovered,
|
||||
crate::cli::ForcedState::Pressed => Interaction::Pressed,
|
||||
|
||||
Reference in new issue
Block a user