feat(app-iced): Java 页「下载一个 JDK」+ 三个设置键(M197)

用户原话:「我们内置了那么多镜像源 甚至用户不能手动选择指定版本下载。」
「自动下载 jdk17 作为基准版本,后续在设置中提供多版本可供用户下载。」

- core:新增 `game::java::catalog`(8/11/17/21/25 五档 × 能承接的发行版 × 「该装哪档」),
  数据全部来自 `JdkVendor::supports` 与 `vendor::order`,不复制第二份真相。
- progress:`SourceFailed` 补一个结构化的 `kind`(在这之前只有中文 `reason`,双语界面
  要么放弃语言、要么放弃「哪个源为什么退避」这条用户可见信息)。
- app-iced:`downloads` 模块真下(后台线程 + `ProgressEmitter` + 快照),三态(选择列表 /
  下载中 / 失败)都能说出「命中的源 / 退避原因 / 试过哪些源」;成功后重扫并(在
  `java.auto_install` 开着且没有默认 JDK 时)把新装的那份设为默认。
- 三个设置键做成可改 UI:`java.installRoot` / `java.vendorPriority` / `java.auto_install`,
  各自带「现在生效什么、从哪一层来」(数据目录模式 / 地区默认序),改完回读落盘值。
- `State::satisfied` 换成带方向的 `Fit`:1.12.2 + Java 21 不再被说成「低于要求」。
- i18n:删掉 `SettingsJavaEmptyBody` / `SettingsJavaAddHint` 的 markdown 反引号;
  `SettingsJavaNeverScannedBody` 改成不与首屏自动扫矛盾的表述。
- 出图三张(选择列表 / 下载中 / 失败)在 DevDocs/artifacts/m197/。

顺带拆文件守 400 行:`golden/tail.rs` 的 M174 段搬到 `golden/tail_ai.rs`,
`pages/settings/java/{context,prefs,prefs_store,downloads,download_view}` 各自成模块。
This commit is contained in:
AskaEth committed 2026-11-08 20:49:56 +08:00
1 parent 329c529bea
commit ce463cc9b5
33 files changed
+3839 -322

No files matched your search

@@ -4,6 +4,11 @@
//! 的编辑态。编辑态只让**内存**越界(`validate()` 一次只报第一个错,所以图上看到的是「内存超出
//! 允许范围」+ 内存框标红;引号那条另有单测,不靠这张图证明)。
//!
//! M197 加了三档,对应「下载一个 JDK」的三态(任务书要的三张图):`download`(版本选择列表)/
//! `downloading`(下载中:百分比 + 当前源 + 一次退避)/ `download-failed`(失败:最后试到的源)。
//! 三档都把三个设置键摆成生产上真会出现的值(自定义落点 + 自己排的发行版顺序 + 自动准备开着),
//! 于是一张图里能同时看到「版本目录」与「三个键」。
//!
//! 与 `ULL_AI_STATE` / `ULL_UPDATE_STATE` / `ULL_FONT_STATUS` 同一族取证开关:只影响这一次进程,
//! 产品路径不设它。种子里的路径是**写死的示例路径**(真实机器上没有),所以它只在出图通道里出现;
//! 产品路径的真值一律来自 `core::game::java::staged` 的真扫盘。
@@ -69,7 +74,108 @@ pub fn build(raw: &str) -> Option<State> {
seeded.notice = Some(Notice::Refused(Refusal::MemoryRange));
return Some(seeded);
}
// ── M197:「下载一个 JDK」的三态
"download" => {
let mut seeded = build("two")?;
seeded.context = download_context(21);
seeded.prefs = seeded_prefs();
seeded.effective = seeded_effective();
return Some(seeded);
}
"downloading" => {
let seeded = build("download")?;
seeded.downloads.seed_report(super::downloads::Report {
job: Some(super::downloads::Job {
major: 21,
phase: super::downloads::Phase::Downloading,
downloaded: 96_468_992,
total: Some(210_763_776),
source: Some("tuna".to_string()),
last: Some(super::downloads::Event::Retry {
source: "tuna".to_string(),
kind: unlucky_core::download::DownloadErrorKind::ServerError,
attempt: 2,
retry_in_ms: 2_000,
}),
}),
tries: vec![super::downloads::Try {
source: "tuna".to_string(),
kind: unlucky_core::download::DownloadErrorKind::ServerError,
}],
failure: None,
success: None,
});
return Some(seeded);
}
"download-failed" => {
let seeded = build("download")?;
seeded.downloads.seed_report(super::downloads::Report {
job: None,
// 出图要能看见「试过哪些源、各自为什么」:两条真实形态的失败。
tries: vec![
super::downloads::Try {
source: "tuna".to_string(),
kind: unlucky_core::download::DownloadErrorKind::NotFound,
},
super::downloads::Try {
source: "github".to_string(),
kind: unlucky_core::download::DownloadErrorKind::Timeout,
},
],
success: None,
failure: Some(super::downloads::Failure {
major: 21,
kind: unlucky_core::game::java::JavaErrorKind::Network,
source: Some("github".to_string()),
}),
});
return Some(seeded);
}
_ => return None,
}
Some(state)
}
/// 出图用的「当前实例要求」(`1.20.5` 起要 21)。
fn download_context(minimum: u32) -> Context {
Context {
required: Some(Requirement {
instance: "生存服".to_string(),
version: "1.20.5".to_string(),
minimum,
maximum: None,
}),
layer: Some(Layer::Global),
effective_memory_mb: Some(4096),
}
}
/// 出图用的三键:自定义落点 + 自己排的顺序(第一行不是默认的那家)+ 自动准备开着。
fn seeded_prefs() -> super::prefs::Prefs {
super::prefs::Prefs {
install_root: "D:/Java".to_string(),
saved_install_root: "D:/Java".to_string(),
auto_install: true,
saved_auto_install: true,
priority: vec!["microsoft", "temurin", "oracle", "zulu"],
saved_priority: vec!["microsoft", "temurin", "oracle", "zulu"],
}
}
/// 出图用的生效值(与 [`seeded_prefs`] 一致:落点与顺序都来自设置)。
fn seeded_effective() -> super::prefs::Effective {
super::prefs::Effective {
root: "D:/Java".to_string(),
root_mode: "custom",
root_layer: super::prefs::RootLayer::Setting,
root_unresolved: false,
order: vec![
"Microsoft OpenJDK",
"Eclipse Temurin",
"Oracle OpenJDK",
"Azul Zulu",
],
order_layer: super::prefs::OrderLayer::Setting,
domestic: true,
}
}