fix(dlflow): M105 r1 —— 取消后不再逐个派发剩余文件;取消证据补强;取消提示进 i18n
复审三条(p2-3items / fix-then-merge,其中两条按 p1 对待):
**P2-1 取消后仍为每个剩余文件各发一次请求(p1)**
`select` 先轮询左支 ⇒ `install_one` 第一次 poll 就把 `engine.download(…)` 派出去了;
只把标志检查写在右支,等于取消后「每一个剩余 item 都创建并至少 poll 一次」
(一次整文件 sha1 + 一次请求),盘上已就绪的还会第一 poll 就 `Ready` 并回调 `sink`。
修:`async move` 第一句就 `if is_cancelled(cancel) { return None; }`,这些 item 连
`install_one` 都不进。守门用例 `cancelling_mid_run_stops_creating_remaining_files`
(三文件全在盘上、并发 1、sink 首次回调即置标志 ⇒ 数 sink 回调次数):
修后 1,删掉那两行变异后 **3**(见报告,红/绿均实跑)。
模块文档同步改成与实现一致,并把复审点名的固有边界写进注释:取消瞬间最多有
`concurrency`(GUI 传 16)个 future 已经过了那道门,其中「盘上已就绪」的仍会跑完自己的
回调(靠 presenter 的安装代次丢掉这些进度事件),真在下载的会在同一次 poll 被取消。
**P2-3 真网络装置的断言比声称的弱,且有一条探的是恒不成立的路径(p1)**
① `.ullpart` 原来只断言「5 s 后不再增长」⇒「留下一个不增长的非 0 中间文件」会绿;
改成 `assert_eq!(parts_after, 0)`。
② 实例登记原来探 `<game>/instances`(根本不存在 ⇒ 恒 false,什么也没证明);
改成读 `LauncherLayout::profiles_config()` 的 `read_profiles()` 并断言 0 条。
③ 模块文档里那条探针的路径一并订正。
变异自证:留一个 20 字节 `.ullpart` ⇒ 红(`left: 20 / right: 0`);
走真 `instance::register()` 造一条 profile ⇒ 红(`profiles.json 里 1 条实例`)。
**P2-2 取消文案仍是硬编码中文,与新选项区同屏一中一英**
`presenter` 没有 i18n 依赖,所以不是搬常量能解决的。改成发稳定信号:
新增 `CoreEvent::InstallCancelled`(不带文本),`AppState::install_cancelled` 只计数;
app 层在 `refresh_view` 里比对计数、按 `Key::DownloadCancelled` 现求文本弹一次 Toast
(`pages::download::cancelled_toast`)。`dispatch.rs` 的中文 Toast 删掉。
语言入口收成 `app::view::ui_lang()` 一处(现恒为英文;M106 接上语言只改这里,
M106 合并前中文译文在产品路径不可达)。守门:`cancelling_announces_once_in_the_page_language`
(只弹一条、文案来自目录、不含中文、重复 refresh 不重复弹)。
门禁(真实退出码):fmt=0、clippy(--workspace --all-targets -j 2 -D warnings)=0、
`cargo test -p unlucky-app-iced --lib`=0(440 passed)、`cargo test -p unlucky-core`=0、
`cargo test --workspace --exclude unlucky-app -j 2`=0(57 个 target 全 ok,0 failed)。
This commit is contained in:
1 parent
30b1ff6f48
commit
609bcb3044
14 files changed
+240
-23
No files matched your search
@@ -25,6 +25,7 @@ use unlucky_presenter::dto::Backend;
|
||||
use unlucky_presenter::prefs::ThemeMode;
|
||||
use unlucky_presenter::settings_bridge::SettingsBridge;
|
||||
use unlucky_presenter::state::AppState;
|
||||
use unlucky_presenter::toast::ToastKind;
|
||||
|
||||
use crate::cli::Options;
|
||||
use crate::pages;
|
||||
@@ -136,6 +137,9 @@ pub struct App {
|
||||
/// 页面自己存不住「用户正在填但还没确认」的东西;这与分类筛选(`view.download.category`)
|
||||
/// 是同一个理由、同一个做法。
|
||||
pub(super) download_draft: pages::InstallDraft,
|
||||
/// 已经提示过的取消次数(M105):`AppState::install_cancelled` 是「发生过几次取消」,
|
||||
/// 这里是「其中几次已经弹过提示」。两者不等 ⇒ 弹一次 i18n 提示。
|
||||
pub(super) install_cancelled_seen: u64,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -224,6 +228,7 @@ impl App {
|
||||
agent: crate::shell::agent::demo::from_env().unwrap_or_default(),
|
||||
// 下载页安装草稿(M105):出厂态 = 没选中、原版、两个空串
|
||||
download_draft: pages::InstallDraft::default(),
|
||||
install_cancelled_seen: 0,
|
||||
};
|
||||
// 配色来源档可能已经指向一张背景图:那时 `Ui` 必须由解析出的方案构造,而不是 baseline。
|
||||
app.rebuild_ui();
|
||||
@@ -285,6 +290,15 @@ impl App {
|
||||
// 是为了让模型拿到的 `content_width` 与页面头部读到的档位来自同一份状态。
|
||||
self.sync_agent_route();
|
||||
|
||||
// M105:取消安装的提示语在**这里**弹(`refresh_view` 是事件 apply 之后必经的那一处)。
|
||||
// 文案由页面层求(`pages::cancelled_toast`),presenter 只发信号 ——
|
||||
// 否则会出现「选项区英文、取消弹窗中文」的同屏两语(复审 P2-2)。
|
||||
if self.state.install_cancelled != self.install_cancelled_seen {
|
||||
self.install_cancelled_seen = self.state.install_cancelled;
|
||||
let text = pages::cancelled_toast(super::view::ui_lang());
|
||||
self.state.toasts.push(ToastKind::Info, text);
|
||||
}
|
||||
|
||||
// 分类是**页面自己持有的过滤条件**(真 host 丢弃 `ListVersions` 的分类参数):
|
||||
// 重建视图模型时带回去,否则每次事件都会把 chip 打回「全部」(复审 P2-1)。
|
||||
let category = self.view.download.category;
|
||||
|
||||
@@ -171,3 +171,52 @@ fn cancelling_gives_the_panel_back_to_the_install_options() {
|
||||
);
|
||||
assert!(options.can_confirm, "取消后要能立刻再装一次");
|
||||
}
|
||||
|
||||
/// **取消提示只说一次,且说的是页面语言**(复审 P2-2)。
|
||||
///
|
||||
/// 修前:`presenter` 在取消时直接推一条**写死的中文** Toast,而下载页此刻渲染英文 ⇒
|
||||
/// 选项区英文、取消弹窗中文,同屏两语。现在 host 只发信号(`CoreEvent::InstallCancelled`),
|
||||
/// 文案由页面层按 `Key::DownloadCancelled` 现求,语言跟页面走。
|
||||
///
|
||||
/// 抓得住:谁把中文写回 presenter(类型上已经做不到——它没有 i18n 依赖)、
|
||||
/// 或者 app 层每次 `refresh_view` 都重复弹(下面的 `items().len() == 1`)。
|
||||
/// 抓不住:`Lang` 本身接得对不对(`view::ui_lang()` 现恒为英文,那是 M106 的范围)。
|
||||
#[test]
|
||||
fn cancelling_announces_once_in_the_page_language() {
|
||||
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(),
|
||||
);
|
||||
assert!(app.state.toasts.is_empty(), "出厂态不该有 Toast");
|
||||
|
||||
app.state.apply(AppEvent::Core(
|
||||
unlucky_presenter::dto::CoreEvent::InstallCancelled,
|
||||
));
|
||||
app.refresh_view();
|
||||
let items = app.state.toasts.items();
|
||||
assert_eq!(items.len(), 1, "取消要提示一条(且只一条)");
|
||||
let text = items[0].text.clone();
|
||||
assert_eq!(
|
||||
text,
|
||||
pages::cancelled_toast(super::super::view::ui_lang()),
|
||||
"提示语必须来自 i18n 目录(不是 presenter 写死的中文)"
|
||||
);
|
||||
assert!(
|
||||
!text.contains('取') && !text.contains('消'),
|
||||
"页面此刻是英文,取消提示不该混中文:{text}"
|
||||
);
|
||||
|
||||
// 再重建一次视图:不该重复弹(信号是计数,不是「每拍都算一次」)
|
||||
app.refresh_view();
|
||||
assert_eq!(app.state.toasts.items().len(), 1, "同一次取消只提示一次");
|
||||
let _ = app.state.toasts.len();
|
||||
}
|
||||
@@ -93,6 +93,13 @@ impl App {
|
||||
|
||||
// --------------------------------------------------------------- 快照 → 视图模型
|
||||
|
||||
/// **产品路径此刻的界面语言**(唯一入口):语言还没接到设置(M106 的范围),所以恒为英文。
|
||||
/// 收成一处是为了别让第二个地方(如取消提示)各硬编码一次;M106 只改这里。
|
||||
/// **M106 合并前中文译文在产品路径上不可达**(页面与提示语都是英文)。
|
||||
pub(super) fn ui_lang() -> Lang {
|
||||
Lang::En
|
||||
}
|
||||
|
||||
/// 把 presenter 的快照转成页面视图模型(**唯一**的展示转换点)。
|
||||
///
|
||||
/// `forced` 是 `--force-state` 的落点(P3-1):它只影响**画出来的**状态层,不改任何状态机。
|
||||
@@ -130,8 +137,7 @@ pub(super) fn model(
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// 语言入口还没接到 `Ui`(`theme.rs` 归 M93),产品路径先英文。
|
||||
let lang = Lang::En;
|
||||
let lang = ui_lang();
|
||||
|
||||
pages::Model {
|
||||
screen,
|
||||
@@ -282,13 +288,8 @@ mod tests {
|
||||
/// 也抓不住不消费 `model.forced` 的裸 `iced::button`(设置页「外观」区就是)。
|
||||
/// 测试用的薄包装:默认宽度 / 默认分类 / 空草稿。
|
||||
fn at(state: &AppState, forced: Option<crate::cli::ForcedState>) -> pages::Model {
|
||||
model(
|
||||
state,
|
||||
1180.0,
|
||||
forced,
|
||||
pages::Category::All,
|
||||
&pages::InstallDraft::default(),
|
||||
)
|
||||
let draft = pages::InstallDraft::default();
|
||||
model(state, 1180.0, forced, pages::Category::All, &draft)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -75,7 +75,7 @@ mod states;
|
||||
mod tests;
|
||||
|
||||
pub use demo::demo;
|
||||
pub use model::{Category, Download};
|
||||
pub use model::{cancelled_toast, Category, Download};
|
||||
pub use options::{InstallDraft, Loader};
|
||||
|
||||
use iced::widget::column;
|
||||
|
||||
@@ -29,10 +29,20 @@ use unlucky_presenter::dto::{
|
||||
};
|
||||
use unlucky_presenter::state::AppState;
|
||||
|
||||
use unlucky_i18n::{Catalog, Key};
|
||||
use unlucky_i18n::{Catalog, Key, Lang};
|
||||
|
||||
use super::options::{InstallDraft, OptionsView};
|
||||
|
||||
/// 取消安装之后那一句提示(M105)。
|
||||
///
|
||||
/// 为什么文案在页面层而不是 presenter:取消的**信号**由 host 发(`CoreEvent::InstallCancelled`),
|
||||
/// 但 presenter 不依赖 `unlucky-i18n`,写死的中文会和页面语言打架(复审 P2-2 抓的就是这个:
|
||||
/// 选项区是英文、取消弹窗是中文)。所以文本按 `Key` 现求,语言跟着页面走。
|
||||
#[must_use]
|
||||
pub fn cancelled_toast(lang: Lang) -> String {
|
||||
lang.catalog().text(Key::DownloadCancelled).to_owned()
|
||||
}
|
||||
|
||||
/// 分类筛选(页面上的四个 filter chip)。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Category {
|
||||
|
||||
@@ -123,7 +123,7 @@ pub(crate) mod settings;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub use download::{Category, Download, InstallDraft, Loader};
|
||||
pub use download::{cancelled_toast, Category, Download, InstallDraft, Loader};
|
||||
pub use layout::{FrameMetrics, PageLayout, PaneKit, WidthClass};
|
||||
pub use logs::{Confidence, Finding, Level, Line, Logs, Outcome};
|
||||
pub use message::{Action, Message};
|
||||
|
||||
Reference in new issue
Block a user