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:
1 parent
b3f99652bb
commit
57e8380b24
14 files changed
+1633
-247
No files matched your search
@@ -0,0 +1,121 @@
|
||||
//! 版本列表:分类 Tabs + 计数头 + `ListItem` 行(全是 `components/` 的真组件)。
|
||||
//!
|
||||
//! | 位置 | M84(占位) | M94(真组件) |
|
||||
//! |---|---|---|
|
||||
//! | 分类 | 无(整页只有一行假状态) | [`navigation::tabs`] 的 Primary 档(48 高、3px `Primary` 指示条) |
|
||||
//! | 列表行 | 无 | [`navigation::list_item`] 的两行档(**60** = `row_height` 44 + 每多一行 16) |
|
||||
//! | 计数头 | 无 | `LabelMedium` + `OnSurfaceVariant`(与实例页列表头同一个语言) |
|
||||
//! | 已装标记 | 无 | overline `Installed` + trailing `Check`(`Primary`) |
|
||||
//!
|
||||
//! 行高从「手搓 56」变成 60 的理由与实例页完全一样:真 `ListItem` 自己的几何真源,页面不覆盖。
|
||||
|
||||
use iced::widget::{column, scrollable};
|
||||
use iced::{Element, Length, Padding};
|
||||
|
||||
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::navigation::list_item::{list_item, ListItem};
|
||||
use crate::app::components::navigation::tabs::{tabs, Tab, Tabs};
|
||||
use crate::theme::Ui;
|
||||
|
||||
use super::super::frame;
|
||||
use super::super::Message;
|
||||
use super::model::{Category, Download, VersionRow};
|
||||
|
||||
/// 行与行的间距(`Space::X1`;与实例页同值,行高差异全部来自 `ListItem` 自己的几何)。
|
||||
const ROW_GAP: Space = Space::X1;
|
||||
|
||||
/// 分类筛选(Primary Tabs:选中项下方 3px `Primary` 指示条)。
|
||||
///
|
||||
/// 点一个分类 = `Message::SetCategory`,由 app 层翻成 `AppCommand::ListVersions`——
|
||||
/// 页面**不**自己过滤列表(列表内容 = 快照里有什么),所以「点了但没反应」只会发生在
|
||||
/// presenter 没回列表的时候,不会出现「页面过滤过、后端还是旧分类」的两份真相。
|
||||
pub(super) fn categories<'a>(
|
||||
ui: &'a Ui,
|
||||
catalog: Catalog,
|
||||
current: Category,
|
||||
) -> Element<'a, Message> {
|
||||
let mut row = Tabs::primary();
|
||||
for category in Category::ALL {
|
||||
let mut tab = Tab::new(
|
||||
catalog.text(category.label_key()),
|
||||
Message::SetCategory(category),
|
||||
);
|
||||
if category == current {
|
||||
tab = tab.selected();
|
||||
}
|
||||
row = row.tab(tab);
|
||||
}
|
||||
tabs(ui, row)
|
||||
}
|
||||
|
||||
/// 计数头(`LabelMedium`):一行 = 一个 key,数字走占位符,不拼片段。
|
||||
pub(super) fn head<'a>(ui: &'a Ui, catalog: Catalog, model: &Download) -> Element<'a, Message> {
|
||||
frame::text_role_owned(
|
||||
ui,
|
||||
TypeRole::LabelMedium,
|
||||
false,
|
||||
catalog.render(
|
||||
Key::DownloadCount,
|
||||
&[("count", &model.rows.len().to_string())],
|
||||
),
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
)
|
||||
}
|
||||
|
||||
/// 版本行列表(**独立滚动**:只有列表滚,安装面板钉在底部不跟着滚)。
|
||||
pub(super) fn list<'a>(ui: &'a Ui, catalog: Catalog, model: &'a Download) -> Element<'a, Message> {
|
||||
let rows = column(
|
||||
model
|
||||
.rows
|
||||
.iter()
|
||||
.map(|entry| version_row(ui, catalog, model, entry))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.spacing(ui.space(ROW_GAP))
|
||||
.padding(Padding::from([0.0, ui.space(Space::X2)]))
|
||||
.width(Length::Fill);
|
||||
|
||||
scrollable(rows)
|
||||
.direction(scrollable::Direction::Vertical(
|
||||
scrollable::Scrollbar::new()
|
||||
.width(frame::SCROLLBAR_WIDTH)
|
||||
.scroller_width(frame::SCROLLBAR_WIDTH)
|
||||
.margin(frame::SCROLLBAR_MARGIN),
|
||||
))
|
||||
.height(Length::Fill)
|
||||
.style(frame::scroll_style(ui))
|
||||
.into()
|
||||
}
|
||||
|
||||
/// 一行:headline = 版本 id,supporting = `正式版 · 发布于 2026-09-20`,已装给勾。
|
||||
///
|
||||
/// 点一行 = `Message::SelectVersion(id)`(**页面内选中**,不 dispatch);「安装」是旁边那个
|
||||
/// 独立按钮,点它才派 `AppCommand::InstallVersion`。选中不做乐观回显:行的高亮来自模型,
|
||||
/// 模型在 app 层被改写之后才变。
|
||||
fn version_row<'a>(
|
||||
ui: &'a Ui,
|
||||
catalog: Catalog,
|
||||
model: &Download,
|
||||
entry: &'a VersionRow,
|
||||
) -> Element<'a, Message> {
|
||||
let mut item = ListItem::new(entry.id.as_str(), Message::SelectVersion(entry.id.clone()))
|
||||
.supporting(entry.supporting.as_str())
|
||||
.leading(IconKind::Download);
|
||||
|
||||
if entry.installed {
|
||||
item = item
|
||||
.overline(catalog.text(Key::DownloadInstalled))
|
||||
.trailing(IconKind::Check);
|
||||
}
|
||||
if model.selected.as_deref() == Some(entry.id.as_str()) {
|
||||
item = item.selected();
|
||||
}
|
||||
|
||||
list_item(ui, item)
|
||||
}
|
||||
Reference in new issue
Block a user