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:
tower authored and AskaEth committed 2026-11-02 21:36:22 +08:00
1 parent b3f99652bb
commit 57e8380b24
14 files changed
+1633 -247

No files matched your search

@@ -0,0 +1,72 @@
//! 加载态与空态(**没有可用版本数据时页面不能是白屏**)。
//!
//! | 状态 | 触发条件(都能从快照推出来) | 画什么 |
//! |---|---|---|
//! | 加载 | `model.is_loading()`:快照里还没有版本列表 | 不确定态进度条 + 「正在加载版本列表…」+ 一句说明 |
//! | 空 | `model.is_empty()`:列表拉到了,但过滤后一行不剩 | 真 [`empty_state`](图标块 96 + 24 标题 + 说明 + 一个刷新 CTA) |
//!
//! 两者都**不留白**:加载态给得出「还在等」的视觉证据(进度条不是装饰,是唯一的非文字信号),
//! 空态给得出「下一步做什么」(刷新)。
//!
//! 拉取**失败**在这条通道上没有结构化事件(presenter 只有 `VersionListChanged`),所以列表拉不到
//! 会停在加载态而不是谎报「没有版本」——加载态的说明文案里就写着这件事。安装失败的错误态在
//! [`super::install`] 里(那里的 `Failed` 阶段是结构化字段,可以放心翻译)。
use iced::widget::column;
use iced::{Element, Length};
use unlucky_design::color::Role;
use unlucky_design::icons::IconKind;
use unlucky_design::spacing::Space;
use unlucky_design::typography::TypeRole;
use unlucky_i18n::{Catalog, Key};
use crate::app::components::feedback::{empty_state, linear, EmptyState, Progress};
use crate::theme::Ui;
use super::super::frame;
use super::super::{Action, Message};
/// 不确定态进度条的相位(没有帧订阅,取一帧静态相位;见 `install.rs` 同名常量)。
const INDETERMINATE_PHASE: f32 = 0.35;
/// 加载态。
pub(super) fn loading<'a>(ui: &'a Ui, catalog: Catalog) -> Element<'a, Message> {
column![
frame::text_role(
ui,
TypeRole::BodyLarge,
true,
catalog.text(Key::DownloadLoading),
ui.color(Role::OnSurface),
),
linear(ui, Progress::indeterminate(INDETERMINATE_PHASE)),
frame::text_role(
ui,
TypeRole::BodySmall,
false,
catalog.text(Key::DownloadLoadingHint),
ui.color(Role::OnSurfaceVariant),
),
]
.spacing(ui.space(Space::X3))
.width(Length::Fill)
.into()
}
/// 空态(真 `EmptyState`:CTA 就是标准 Filled 按钮,走按钮家族)。
pub(super) fn empty<'a>(ui: &'a Ui, catalog: Catalog) -> Element<'a, Message> {
empty_state(
ui,
EmptyState::new(
IconKind::Download,
catalog.text(Key::DownloadEmptyTitle),
catalog.text(Key::DownloadEmptyBody),
)
.action(
catalog.text(Key::DownloadRefresh),
Message::Action(Action::RefreshSources),
),
)
}