① update.auto_check / update.channel 登记进 core setting schema(1.6.0)
- 新增 setting/keys/update.rs(两键 + UPDATE_CHANNELS,只允许 stable);
SettingSchema 加 with_all 供域键表批量登记。
- 新增 setting/update_prefs.rs:cache/update/prefs.json → launcher.json 的一次性迁移。
四条硬约束:源指纹闸门(幂等)、不丢 false、新值胜出、旧文件改名 .migrated 后不再被读;
主配置不可写时不动旧文件。收据 config/update-prefs-migration.json。
- presenter Runtime 建会话时迁一次并把结局留在 migration();prefs 字段换成
auto_check + 每次派发从主配置读 channel(不再硬编码 Channel::Stable)。
保留旧字段 auto_check 的真实来源(原有测试 cases 会断言)。
- CLI config get/set/dump 先跑同一次迁移(幂等),输出里给 migration 段与收据路径。
② presenter/dto/events.rs 的「本次会话不再弹提示条」注释改成真实语义
(记的是版本号:同版本压住、更高版本照弹、手动检查清掉)。
③ 检查/下载进行中按钮禁用:pages/update/busy.rs 的 busy/can_check/can_apply;
设置页两颗按钮改画禁用(不是消失),app 层 on_update_action 再挡一道
(忙碌时 CheckNow/Apply 不派发);新增两条用例(含「忙完恢回可点」)。
④ 内容库低高度:content_layout::MIN_BODY_HEIGHT=460 + BodyMode(按固定件+三整行算出来,
用例守着算式);frame::body_height 从 Ui::metrics 算正文可用高,pages::Model 带 window_height。
≥460 列表自己滚;<460 主列整列一个滚动条、列表按自然高度铺开。
⑤ 启动错误文案本地化:presenter 删掉 launch_summary / 「启动失败:」前缀 / 五条中文阶段前缀,
改发 LaunchFailureView{stage,detail};LaunchFinished 改带 exit_code/uptime_ms/failure,
Toast 由 app 层按 i18n key 现求(与 install_cancelled 同构)。InstancesFailed 拆成
InstancesFailedStage(列表单行)+ LaunchStageFailed(详情/Toast 全文)。
⑥ 导入结果块:ImportReportView 加 failure_stage(NotStarted/RolledBack/RollbackIncomplete,
由 core 的 rollback 机器位推出);标题、图标、底色三处分档,预检失败不再说「已回滚」。
顺带:六处注释/文档漂移(update.rs 的分工表与偏好落点、pack 的「已整包回滚」、
build_request 的 Errors、settings/update 的按钮禁用说明等),
以及为守 400 行把 update/cli-config/core-keys/presenter-dto/presenter-launch/i18n-golden
里贴线或越线的文件按模块拆开(纯搬移,行为不变)。
108 lines
5.5 KiB
Rust
108 lines
5.5 KiB
Rust
//! 快照 → 视图模型的重建口径(从 `state.rs` 搬出来守 400 行硬线;M119 拆,纯搬不改)。
|
||
//!
|
||
//! 全仓只有 [`App::refresh_view`] 这一个「presenter 快照 → 页面要画的东西」的转换点,
|
||
//! 由 [`App::view_dirty`](super::App) 门控(M90 r2/P2-1 起):只有真的改了状态的事情才置位,
|
||
//! 30 Hz 空转不重建(重建要把 N 个实例的名字/版本串克隆一遍)。
|
||
|
||
use unlucky_presenter::toast::ToastKind;
|
||
|
||
use crate::app::view;
|
||
use crate::pages;
|
||
|
||
use unlucky_presenter::router::Route;
|
||
|
||
use super::App;
|
||
|
||
impl App {
|
||
/// 把 presenter 的快照转换成页面视图模型(**展示转换在 UI 侧做**)。
|
||
///
|
||
/// 代价是「克隆 N 个实例的名字/版本串」,所以只在 [`App::view_dirty`] 置位时调用(P2-1)。
|
||
pub(in crate::app) fn refresh_view(&mut self) {
|
||
// **不变式先对齐再算宽度**(M104 / 复审 r1 P2-1):`Route::Agent` ⟺ `RailState::Detached`。
|
||
// 每一次路由变化都会走到这里,所以它是「档位 ↔ 路由」的唯一点;放在算宽度之前,
|
||
// 是为了让模型拿到的 `content_width` 与页面头部读到的档位来自同一份状态。
|
||
self.sync_agent_route();
|
||
// M119:把「栏可见吗」写回 presenter(未读口径的输入,两个窄入口:`navigate` + `prefs_mut`)。
|
||
// 必须在 `sync_agent_route` 之后:档位刚被它对齐过,这里读到的才是这一帧的真值。
|
||
self.sync_agent_presenter();
|
||
|
||
// 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;
|
||
// M106:提示语跟着**生效语言**走(M105 留的 `view::ui_lang()` 占位已删——它恒英文,
|
||
// 中文界面会弹出英文提示,那正是 M105 r1 复审 P2-2 要堵的同屏两语)。
|
||
let text = pages::cancelled_toast(self.lang());
|
||
self.state.toasts.push(ToastKind::Info, text);
|
||
}
|
||
|
||
// M161:启动收尾的提示语也在这里弹(presenter 只发结构:结局 + 退出码 + 失败阶段)。
|
||
// 此前 presenter 直接推一条自己拼好的中文 Toast,英文界面会弹中文。
|
||
if self.state.launch_ends != self.launch_ends_seen {
|
||
self.launch_ends_seen = self.state.launch_ends;
|
||
if let Some(finished) = self.state.last_launch_end.as_deref() {
|
||
let text = view::instances::launch_toast(finished, self.lang());
|
||
let kind = if finished.outcome == unlucky_presenter::dto::LogOutcome::Ok
|
||
&& finished.failure.is_none()
|
||
{
|
||
ToastKind::Success
|
||
} else {
|
||
ToastKind::Error
|
||
};
|
||
self.state.toasts.push(kind, text);
|
||
}
|
||
}
|
||
|
||
// 分类是**页面自己持有的过滤条件**(真 host 丢弃 `ListVersions` 的分类参数):
|
||
// 重建视图模型时带回去,否则每次事件都会把 chip 打回「全部」(复审 P2-1)。
|
||
let category = self.view.download.category;
|
||
// 草稿同理(M105):它也是页面自己持有的输入,重建模型时带回去,
|
||
// 否则每来一条事件(连安装进度都算)用户填的加载器就被抹回「原版」。
|
||
let draft = self.download_draft.clone();
|
||
// 右栏占掉的宽度先扣掉:面板展开时页面按更窄的宽度排版(「收缩而不是溢出」,M104)
|
||
let width = self.agent.content_width(self.width);
|
||
// M106:语言形参跟着 `LangState` 走(`draft` 那一位是 M105 的安装草稿)。
|
||
self.view = super::super::view::model(
|
||
&self.state,
|
||
width,
|
||
self.height,
|
||
self.options.force_state,
|
||
category,
|
||
&draft,
|
||
self.lang(),
|
||
&self.content,
|
||
);
|
||
// M157:启动参数表单的重播种(换实例必重播种、同实例的脏草稿不覆盖)。
|
||
self.sync_launch_form();
|
||
// 账户页(M114):与 `view` 同一次门控重建(实现见 `view/account_page.rs`,
|
||
// 皮肤解码走缓存,所以重复重建不重解图)。
|
||
self.rebuild_account_page(width);
|
||
self.view_dirty = false;
|
||
self.refreshes += 1;
|
||
}
|
||
|
||
/// 把详情页当前实例的启动参数快照灌进表单(唯一的重播种点,见 `pages/instances/state.rs`)。
|
||
///
|
||
/// 不在详情页(或实例已不在快照里)时解绑:下次进详情页会重新播种,
|
||
/// 免得「A 的草稿被当成 B 的初值」。
|
||
fn sync_launch_form(&mut self) {
|
||
let Route::Detail(id) = self.state.route() else {
|
||
self.launch_form.bound = None;
|
||
return;
|
||
};
|
||
match self
|
||
.state
|
||
.instances
|
||
.iter()
|
||
.find(|instance| &instance.id == id)
|
||
{
|
||
Some(instance) => {
|
||
let view = instance.launch.clone();
|
||
self.launch_form.sync(&view);
|
||
}
|
||
None => self.launch_form.bound = None,
|
||
}
|
||
}
|
||
}
|