diff --git a/crates/app-iced/src/app/update.rs b/crates/app-iced/src/app/update.rs index da98614..36e0218 100644 --- a/crates/app-iced/src/app/update.rs +++ b/crates/app-iced/src/app/update.rs @@ -41,6 +41,8 @@ use super::message::{route_of, Message}; use super::state::{App, IDLE_TICK, TICK, TICK_MS, WATCH_WINDOW}; use super::CAPTURE_AT_FRAME; +mod download; + impl App { /// 一条消息进来,改状态并返回后续任务。 pub(super) fn update(&mut self, message: Message) -> Task { @@ -275,6 +277,11 @@ impl App { /// 页面消息:导航走路由栈,动作翻成 `AppCommand`。 fn on_page(&mut self, message: pages::Message) { + // 下载页的四支(含「刷新下载源」)在 `update/download.rs`:本文件 M93 合入后已贴 400 行 + if self.on_download(&message) { + return; + } + match message { pages::Message::Navigate(route) => { self.state.navigate(route); @@ -284,32 +291,12 @@ 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), - // ── 下载页(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) => { - // 分类是**本页的过滤条件**,不是后端事实的乐观回显:真 host 会把 - // `ListVersions` 的分类参数丢掉、回全量清单(复审 P2-1),所以过滤在本页做。 - // 写进视图模型 → 立刻按它重建(`refresh_view` 会把 category 带回去并据此过滤行), - // 于是 chip 与列表用同一个谓词,不存在「chip 说正式版、列表是全量」的两份真相。 - self.view.download.category = category; - self.view_dirty = true; - self.refresh_view(); - // 列表内容仍然由后端给:真指令照派(presenter 修好 `spawn_list` 之后它就是那一路) - 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)"), - }, + pages::Message::Action(action) => { + eprintln!("[page] 占位动作 {action:?} pressed(没有对应 AppCommand)"); + } + // 下载页四支 +「刷新下载源」已由上面的 `on_download` 消化,这里只是穷尽性兜底 + _ => {} } } diff --git a/crates/app-iced/src/app/update/download.rs b/crates/app-iced/src/app/update/download.rs new file mode 100644 index 0000000..85ea7d7 --- /dev/null +++ b/crates/app-iced/src/app/update/download.rs @@ -0,0 +1,57 @@ +//! 下载页在 app 层的那几支(M94)。 +//! +//! 为什么单独一个文件:M93(外观三件套接线)合入后 `update.rs` 已经贴到 400 行,下载页再往里塞 +//! 四个 arm 就顶破硬线;拆出来是既不动 M93 的代码、又能守住 ≤400 的办法(`app/update/**` 属本 +//! mission scope)。 +//! +//! ## 四支各自的纪律 +//! +//! | 消息 | 落点 | 为什么 | +//! |---|---|---| +//! | [`pages::Message::SelectVersion`] | **只改视图模型** | 选中是页面内状态,不是业务命令 | +//! | [`pages::Message::InstallVersion`] | `AppCommand::InstallVersion` | 真指令,回事件后才画进度 | +//! | [`pages::Message::CancelInstall`] | `AppCommand::CancelInstall` | 同上(host 侧对没在跑的任务是 no-op) | +//! | [`pages::Message::SetCategory`] | 写分类 → 重建 → `AppCommand::ListVersions` | 见下 | +//! +//! [「刷新下载源」](pages::Action::RefreshSources) 也在这里:它不是占位动作,具体分类 → 重拉该分类、 +//! 「全部」→ 让 host 重扫一遍(`RefreshAll`)。 +//! +//! ## 分类为什么既写视图模型、又派指令 +//! +//! 真 host 的 `AppCommand::ListVersions(_)` 会丢掉分类参数、回全量清单(复审 P2-1),所以**过滤在 +//! 本页做**:写进视图模型 → 立刻 `refresh_view()`(它会把 category 带回去、按同一谓词过滤行)→ +//! 再派指令。这不是「乐观回显」:写的是**过滤条件本身**,chip 与列表用同一个 +//! [`Category::matches`](crate::pages::Category),于是不可能出现「chip 说正式版、列表是全量」的两份真相。 + +use super::*; + +impl App { + /// 下载页的消息。**处理了返回 `true`**(`on_page` 里就不用再写一遍这些 arm)。 + pub(super) fn on_download(&mut self, message: &pages::Message) -> bool { + match message { + pages::Message::SelectVersion(id) => { + self.view.download.selected = Some(id.clone()); + } + pages::Message::InstallVersion(id) => { + self.dispatch(AppCommand::InstallVersion(id.clone())); + } + pages::Message::CancelInstall => self.dispatch(AppCommand::CancelInstall), + pages::Message::SetCategory(category) => { + self.view.download.category = *category; + self.view_dirty = true; + self.refresh_view(); + if let Some(kind) = category.command() { + self.dispatch(AppCommand::ListVersions(kind)); + } + } + pages::Message::Action(pages::Action::RefreshSources) => { + match self.view.download.category.command() { + Some(kind) => self.dispatch(AppCommand::ListVersions(kind)), + None => self.dispatch(AppCommand::RefreshAll), + } + } + _ => return false, + } + true + } +}