feat(app-iced): M114 账户页三轨登录(微软正版 / 第三方皮肤站 / 本地档案)
- 账户页:当前账户卡 + 全部账户列表 + 空态三入口 + 皮肤区;五项动作(添加/切换/删除/重新登录/校验) - 三轨添加流程:设备码(用户码+链接+倒计时+过期重试+取消)、授权码(浏览器+可复制链接+取消)、 本地档案(名字校验)、第三方站(服务地址 + 服务发现校验 + 密码登录,密码掩码且用完即弃) - 导航:账户为一等页,入口是轨底头像位(未登录=描边剪影 / 已登录=首字母块+名字;窄高收成图标条) - 凭据红线:DTO 侧形状脱敏 + 状态层二次脱敏 + 密码掩码;Inject-JWT 测试断言视图模型干净(已做变异取证) - presenter:authlib 登录/服务发现/皮肤抓取(复用下载引擎的 HTTP 客户端,无新增依赖)、 皮肤原图经 DTO 到 UI 裁剪成头像(既有 image 依赖,未改 Cargo.toml) - i18n:新增 account 域 51 条中英文案(离线档案口径全量写入),金表同步
This commit is contained in:
1 parent
e7b04c4afe
commit
1ff00bbe50
43 files changed
+4343
-117
No files matched your search
@@ -23,6 +23,12 @@ use unlucky_design::spacing::{LayoutMetrics, Space};
|
||||
use unlucky_design::typography::TypeRole;
|
||||
use unlucky_i18n::{Key, Lang};
|
||||
|
||||
mod account;
|
||||
|
||||
pub use account::RailAccount;
|
||||
|
||||
use account::account_item;
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::cli::ForcedState;
|
||||
use crate::icons;
|
||||
@@ -42,10 +48,12 @@ pub enum NavItem {
|
||||
Settings,
|
||||
/// AI 助手。
|
||||
Ai,
|
||||
/// 账户页(**不进主序列**:入口是轨底的头像位,见 [`RailAccount`])。
|
||||
Account,
|
||||
}
|
||||
|
||||
impl NavItem {
|
||||
/// 全部占位项(顺序 = 导航栏自上而下)。
|
||||
/// 全部**主序列**项(顺序 = 导航栏自上而下)。账户不在其中:它固定贴在轨底。
|
||||
pub const ALL: [NavItem; 5] = [
|
||||
NavItem::Instances,
|
||||
NavItem::Download,
|
||||
@@ -62,6 +70,8 @@ impl NavItem {
|
||||
NavItem::Logs => Key::NavLogs,
|
||||
NavItem::Settings => Key::NavSettings,
|
||||
NavItem::Ai => Key::NavAi,
|
||||
// M114:轨底头像位的可访问名(不在主序列里,但 `router` 的 `nav()` 会问到它)。
|
||||
NavItem::Account => Key::AccountTitle,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +91,7 @@ impl NavItem {
|
||||
NavItem::Logs => IconKind::Logs,
|
||||
NavItem::Settings => IconKind::Settings,
|
||||
NavItem::Ai => IconKind::Ai,
|
||||
NavItem::Account => IconKind::User,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,27 +100,41 @@ impl NavItem {
|
||||
pub(super) fn rail<'a>(
|
||||
ui: &'a Ui,
|
||||
selected: NavItem,
|
||||
account: &RailAccount,
|
||||
forced: Option<ForcedState>,
|
||||
lang: Lang,
|
||||
) -> Element<'a, Message> {
|
||||
let metrics = ui.metrics();
|
||||
let indicator = indicator_size(metrics);
|
||||
let compact = account.compact;
|
||||
|
||||
let items = NavItem::ALL
|
||||
.iter()
|
||||
.map(|item| rail_item(ui, *item, *item == selected, indicator, forced, lang))
|
||||
// `ALL` 本来就不含 `Account`(它固定在轨底),filter 只是把这条意图写明。
|
||||
.filter(|item| **item != NavItem::Account)
|
||||
.map(|item| rail_item(ui, *item, *item == selected, indicator, compact, forced, lang))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// 轨顶留白走 container padding,而不是「先塞一个 spacer 元素」:后者会被 column 的
|
||||
// spacing(4)再加一档,实测变 28(视觉复审 P3-5)。
|
||||
//
|
||||
// 轨底色与其它面板同源(M93):`ui.layer` 已含面板 α,导航轨是面板层的一部分。
|
||||
container(column(items).spacing(metrics.nav_item_gap))
|
||||
.width(nav_rail_width(metrics))
|
||||
.height(Length::Fill)
|
||||
.padding(Padding::from([ui.space(Space::X6), 0.0]))
|
||||
.style(move |_theme| filled(ui.layer(MaterialLayer::Canvas)))
|
||||
.into()
|
||||
//
|
||||
// 账户头像位**固定贴在轨底**(M114):主序列四项在上,中间一段 Fill 把头像顶到底部——
|
||||
// 于是「账户」不是第 6 个导航项,轨的长度也不会随页数增长。
|
||||
container(
|
||||
column![
|
||||
column(items).spacing(metrics.nav_item_gap),
|
||||
iced::widget::space::vertical(),
|
||||
account_item(ui, selected, account, indicator, forced),
|
||||
]
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.width(nav_rail_width(metrics))
|
||||
.height(Length::Fill)
|
||||
.padding(Padding::from([ui.space(Space::X6), 0.0]))
|
||||
.style(move |_theme| filled(ui.layer(MaterialLayer::Canvas)))
|
||||
.into()
|
||||
}
|
||||
|
||||
fn rail_item<'a>(
|
||||
@@ -117,6 +142,7 @@ fn rail_item<'a>(
|
||||
item: NavItem,
|
||||
selected: bool,
|
||||
indicator: (f32, f32),
|
||||
compact: bool,
|
||||
forced: Option<ForcedState>,
|
||||
lang: Lang,
|
||||
) -> Element<'a, Message> {
|
||||
@@ -152,26 +178,32 @@ fn rail_item<'a>(
|
||||
});
|
||||
|
||||
let item_height = nav_item_height(ui, indicator_height);
|
||||
let height = if compact {
|
||||
indicator_height
|
||||
} else {
|
||||
item_height
|
||||
};
|
||||
// 列必须显式占满轨宽,否则它会 shrink 到 56,`align_x(Center)` 就变成空操作,
|
||||
// 药丸会贴左缘(x=0..55 而不是 16..71)——r1 实测踩到的第二个坑。
|
||||
let stack = column![
|
||||
pill,
|
||||
label(
|
||||
let mut stack = column![pill]
|
||||
.width(nav_rail_width(ui.metrics()))
|
||||
.align_x(Alignment::Center)
|
||||
.height(height);
|
||||
if !compact {
|
||||
// M114 的「窄高收成图标条」+ M106 的「按语言取词」并存。
|
||||
stack = stack.push(label(
|
||||
ui,
|
||||
TypeRole::LabelMedium,
|
||||
false,
|
||||
item.label(lang),
|
||||
label_color
|
||||
),
|
||||
]
|
||||
.spacing(ui.space(Space::X1))
|
||||
.width(nav_rail_width(ui.metrics()))
|
||||
.align_x(Alignment::Center)
|
||||
.height(item_height);
|
||||
label_color,
|
||||
));
|
||||
}
|
||||
stack = stack.spacing(ui.space(Space::X1));
|
||||
|
||||
button(stack)
|
||||
.width(nav_rail_width(ui.metrics()))
|
||||
.height(item_height)
|
||||
.height(height)
|
||||
.padding(0)
|
||||
.style(move |_theme: &IcedTheme, status| {
|
||||
let background = match overridden(forced, status) {
|
||||
|
||||
Reference in new issue
Block a user