fix(app-iced, presenter): M114 r4 —— 登录面板 Close 不再是死按钮 + 面板态删掉原文字段(把「画原文」变成编译错)
复审 r2 P2-1:`Account::Close` 只在 `phase.busy()` 时派 `CancelDeviceLogin`,而 `Page::build` 只要非 Idle 就强制回 Microsoft ⇒ Failed/Succeeded 下 Close 点了没反应。 - 判据改成「两条轨任一非 Idle 就取消」;host 侧 `CancelDeviceLogin` 本来就把两条轨收成 Idle - mock 补 `AppCommand::CancelDeviceLogin` 分支(按真 host 契约发 Idle),否则 mock 下不可验 - 新增 headless 回归闸:失败/成功/第三方轨三种态 → Close → `panel == None` (变异实证:判据退回 `busy()` ⇒ 该用例红,打印 left: Microsoft / right: None) 复审 r2 P2-2:`DevicePanel`/`AuthlibPanel` 删掉 `message`/`detail` 字段。 视图没有材料可画,「把后端原文画上屏」从测试问题变成**类型错误**; `tests_text.rs` 的 docstring 改成真实边界(只覆盖模型 + i18n 查表,视图靠类型 + 出图)。 顺带(塔看图标出):alert 的 `Code: …` 与标题同排,省一整行高度, 避免 1180×760 把 All accounts 第三行挤到折线下;新增 `closed` 取证场景出「关掉之后」的对照图。
This commit is contained in:
1 parent
bc71834bc3
commit
f3e20b670e
9 files changed
+188
-81
No files matched your search
@@ -14,7 +14,7 @@
|
||||
//! `Task` 是惰性 stream,丢掉返回值等于永不执行(日志页的 Copy summary 就是正确写法)。
|
||||
|
||||
use iced::Task;
|
||||
use unlucky_presenter::dto::{AccountKind, AppCommand, AuthlibRequest};
|
||||
use unlucky_presenter::dto::{AccountKind, AppCommand, AuthlibRequest, DeviceLoginPhase};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::pages::account::{Hint, Message as Account, Panel, Track};
|
||||
@@ -43,8 +43,13 @@ impl App {
|
||||
}
|
||||
Account::Close => {
|
||||
self.account_form.close();
|
||||
// 关面板同时收掉登录任务(避免关掉面板后后台还在轮询、也不会凭空多一个账户)
|
||||
if self.state.device_login.phase.busy() || self.state.authlib_login.phase.busy() {
|
||||
// 关面板必须把**两条轨**的登录态都收回 Idle(复审 r2 P2-1:只看 `busy()` 时,
|
||||
// `Failed`/`Succeeded` 不在 busy ⇒ 状态仍是非 Idle ⇒ `Page::build` 又把面板
|
||||
// 强制回 Microsoft,按钮成了死按钮)。`CancelDeviceLogin` 在 host 侧本来就
|
||||
// 同时收两条轨并发 Idle,所以判据用「非 Idle」而不是「busy」。
|
||||
if self.state.device_login.phase != DeviceLoginPhase::Idle
|
||||
|| self.state.authlib_login.phase != DeviceLoginPhase::Idle
|
||||
{
|
||||
self.dispatch(AppCommand::CancelDeviceLogin);
|
||||
}
|
||||
self.view_dirty = true;
|
||||
@@ -188,6 +193,11 @@ pub(super) fn force_account_state(app: &mut App) {
|
||||
app.account_form.login = "steve@example.com".to_string();
|
||||
app.account_form.password = "not-a-real-password".to_string();
|
||||
}
|
||||
// 「关掉之后」:mock 给的是 failed 种子,这里按一次 Close(走真实的
|
||||
// `on_account` → `CancelDeviceLogin` 通道),出「面板真的关了」的对照图。
|
||||
"closed" => {
|
||||
let _ = app.on_account(crate::pages::account::Message::Close);
|
||||
}
|
||||
// 有账号时「Add an account」展开态(P2-4 的收敛后形态)
|
||||
"add" => {
|
||||
app.account_form.section_open = true;
|
||||
@@ -198,7 +208,99 @@ pub(super) fn force_account_state(app: &mut App) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::pages::account::{Form, Panel, Track};
|
||||
use unlucky_design::color::SchemeFlavor;
|
||||
use unlucky_presenter::dto::{
|
||||
AppEvent, AuthlibLoginView, CoreEvent, DeviceLoginPhase, DeviceLoginView, LoginFailure,
|
||||
};
|
||||
use unlucky_presenter::mock::MockBackend;
|
||||
use unlucky_presenter::settings_bridge::SettingsBridge;
|
||||
|
||||
use crate::pages::account::{Form, Message as Account, Panel, Track};
|
||||
use crate::theme::Families;
|
||||
|
||||
use super::App;
|
||||
|
||||
/// 不碰磁盘的 App(mock 后端 + 内存设置桥),与 `app/tests.rs` 的 `headless` 同源。
|
||||
fn headless() -> App {
|
||||
App::with_presenter(
|
||||
crate::cli::Options::default(),
|
||||
Families {
|
||||
text: "Segoe UI",
|
||||
display: "Segoe UI",
|
||||
mono: "Consolas",
|
||||
},
|
||||
SchemeFlavor::Light,
|
||||
Box::new(MockBackend::new()),
|
||||
SettingsBridge::memory_only(),
|
||||
)
|
||||
}
|
||||
|
||||
fn seed(app: &mut App, event: CoreEvent) {
|
||||
app.state.apply(AppEvent::Core(event));
|
||||
app.refresh_view();
|
||||
}
|
||||
|
||||
/// **复审 r2 P2-1 的回归闸**:登录失败/成功态下按 `Close`,面板必须真的关掉。
|
||||
///
|
||||
/// 为什么这条以前是死按钮:`Page::build` 只要 `device_login.phase != Idle` 就把面板强制
|
||||
/// 回 Microsoft,而 `Close` 只在 `busy()`(Requesting/Waiting)时才派 `CancelDeviceLogin`
|
||||
/// ⇒ Failed/Succeeded 下点了没反应。现在判据改成「非 Idle」,两条轨都覆盖。
|
||||
///
|
||||
/// 抓得住:判据退回 `busy()`;`Close` 忘了 dispatch;`CancelDeviceLogin` 没有把状态收回 Idle。
|
||||
/// 抓不住:`CancelDeviceLogin` 在**真 host** 上的副作用(这里跑的是 mock,但 mock 已按
|
||||
/// 真 host 的契约发 Idle,见 `presenter/src/mock.rs`)。
|
||||
#[test]
|
||||
fn close_really_closes_the_login_panel_on_both_tracks() {
|
||||
// ① 微软轨:失败态
|
||||
let mut app = headless();
|
||||
seed(
|
||||
&mut app,
|
||||
CoreEvent::DeviceLoginChanged(DeviceLoginView {
|
||||
phase: DeviceLoginPhase::Failed,
|
||||
message: "微软拒绝了这次登录:AADSTS7000218(HTTP 403)".to_string(),
|
||||
failure: Some(LoginFailure::NotWhitelisted),
|
||||
..DeviceLoginView::new()
|
||||
}),
|
||||
);
|
||||
assert_eq!(app.account.panel, Panel::Microsoft, "失败态面板要显示");
|
||||
let _ = app.on_account(Account::Close);
|
||||
assert_eq!(app.account.panel, Panel::None, "Close 之后面板必须关掉");
|
||||
assert_eq!(app.state.device_login.phase, DeviceLoginPhase::Idle);
|
||||
|
||||
// ② 微软轨:成功态(同样不在 busy 里)
|
||||
let mut app = headless();
|
||||
seed(
|
||||
&mut app,
|
||||
CoreEvent::DeviceLoginChanged(DeviceLoginView::succeeded("Steve")),
|
||||
);
|
||||
assert_eq!(app.account.panel, Panel::Microsoft);
|
||||
let _ = app.on_account(Account::Close);
|
||||
assert_eq!(app.account.panel, Panel::None, "成功提示也要能关掉");
|
||||
|
||||
// ③ 第三方轨:失败态
|
||||
let mut app = headless();
|
||||
seed(
|
||||
&mut app,
|
||||
CoreEvent::AuthlibLoginChanged(AuthlibLoginView {
|
||||
phase: DeviceLoginPhase::Failed,
|
||||
server: "https://example.invalid/api/yggdrasil/".to_string(),
|
||||
server_name: String::new(),
|
||||
message: "服务器返回 HTTP 404".to_string(),
|
||||
failure: Some(LoginFailure::NotAuthlibServer),
|
||||
account_name: String::new(),
|
||||
}),
|
||||
);
|
||||
app.account_form.open(Track::Authlib);
|
||||
app.refresh_view();
|
||||
assert_eq!(
|
||||
app.account.panel,
|
||||
Panel::Authlib,
|
||||
"第三方轨失败态面板要显示"
|
||||
);
|
||||
let _ = app.on_account(Account::Close);
|
||||
assert_eq!(app.account.panel, Panel::None, "第三方轨也要能关掉");
|
||||
assert_eq!(app.state.authlib_login.phase, DeviceLoginPhase::Idle);
|
||||
}
|
||||
|
||||
/// 表单态是纯 UI 状态:打开/关闭面板不派命令,但会把面板切对。
|
||||
///
|
||||
|
||||
@@ -256,7 +256,7 @@ fn status_line<'a>(ui: &'a Ui, page: &'a Page, catalog: Catalog) -> Element<'a,
|
||||
"",
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
),
|
||||
AuthlibPanel::Running { .. } => frame::text_role(
|
||||
AuthlibPanel::Running => frame::text_role(
|
||||
ui,
|
||||
TypeRole::LabelMedium,
|
||||
false,
|
||||
@@ -271,7 +271,7 @@ fn status_line<'a>(ui: &'a Ui, page: &'a Page, catalog: Catalog) -> Element<'a,
|
||||
ui.color(Role::Primary),
|
||||
),
|
||||
// 失败只画**原因码对应的 i18n** + 一行 Code(原文是中文诊断串,上屏就是语言泄漏)
|
||||
AuthlibPanel::Failed { failure, .. } => column![
|
||||
AuthlibPanel::Failed { failure } => column![
|
||||
frame::text_role(
|
||||
ui,
|
||||
TypeRole::LabelMedium,
|
||||
|
||||
@@ -32,21 +32,19 @@ pub fn panel<'a>(
|
||||
) -> Element<'a, PageMessage> {
|
||||
match device {
|
||||
DevicePanel::Hidden => idle(ui, catalog),
|
||||
DevicePanel::Requesting { message } => notice(ui, message, catalog),
|
||||
DevicePanel::Requesting => requesting(ui, catalog),
|
||||
DevicePanel::Code {
|
||||
code,
|
||||
link,
|
||||
remaining,
|
||||
expired,
|
||||
..
|
||||
} => device_code(ui, code, link, *remaining, *expired, catalog),
|
||||
DevicePanel::Browser {
|
||||
link,
|
||||
remaining,
|
||||
expired,
|
||||
..
|
||||
} => browser(ui, link, *remaining, *expired, catalog),
|
||||
DevicePanel::Failed { failure, .. } => failed(ui, *failure, catalog),
|
||||
DevicePanel::Failed { failure } => failed(ui, *failure, catalog),
|
||||
DevicePanel::Succeeded { name } => succeeded(ui, name, catalog),
|
||||
}
|
||||
}
|
||||
@@ -82,10 +80,10 @@ fn idle<'a>(ui: &'a Ui, catalog: Catalog) -> Element<'a, PageMessage> {
|
||||
frame::card(ui, body)
|
||||
}
|
||||
|
||||
/// 「正在申请/正在校验」:一句**i18n**状态 + 取消。
|
||||
/// 「正在申请」:一句 i18n 状态 + 取消。
|
||||
///
|
||||
/// 不复读 presenter 的 `message`:那是后端诊断串(中文),上屏就是语言泄漏(复审 r1 P1-1)。
|
||||
fn notice<'a>(ui: &'a Ui, _message: &str, catalog: Catalog) -> Element<'a, PageMessage> {
|
||||
/// presenter 那边的进度串是中文诊断(只进日志),面板态里**根本没有**这个字段可画。
|
||||
fn requesting<'a>(ui: &'a Ui, catalog: Catalog) -> Element<'a, PageMessage> {
|
||||
let body = column![
|
||||
frame::text_role(
|
||||
ui,
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
//!
|
||||
//! 从 `model.rs` 拆出来守 400 行硬线:这些类型是「UI 要画什么」的枚举,
|
||||
//! 判定逻辑(倒计时 → 已过期、服务发现 → 可用)也在这里,页面与表单只消费结果。
|
||||
//!
|
||||
//! ## 为什么这里**没有** `message` / `detail` 字段(M114 r3 / 复审 r2 P2-2)
|
||||
//!
|
||||
//! 后端的原始诊断串是中文人话,只该进日志。r2 时它们还以 `message`/`detail` 存在于此,
|
||||
//! 「页面把它们画上屏」只能靠一条**平行实现**的取样断言去拦(复审用变异证明那条断言是哑的)。
|
||||
//! 现在直接把字段删掉:视图**没有材料可画**,想画原文是编译错误——比任何测试都硬。
|
||||
//! 原文仍在 presenter 的 `DeviceLoginView/AuthlibLoginView::message` 里(只进 `eprintln!`)。
|
||||
|
||||
use unlucky_presenter::dto::{AuthlibLoginView, DeviceLoginPhase, DeviceLoginView, LoginFailure};
|
||||
|
||||
@@ -10,11 +17,8 @@ use unlucky_presenter::dto::{AuthlibLoginView, DeviceLoginPhase, DeviceLoginView
|
||||
pub enum DevicePanel {
|
||||
/// 没有登录在跑
|
||||
Hidden,
|
||||
/// 正在申请设备码
|
||||
Requesting {
|
||||
/// 状态文案(presenter 给的,已脱敏)
|
||||
message: String,
|
||||
},
|
||||
/// 正在申请设备码(进度文案由页面按 i18n 出)
|
||||
Requesting,
|
||||
/// 设备码等待用户操作
|
||||
Code {
|
||||
/// 用户码
|
||||
@@ -25,8 +29,6 @@ pub enum DevicePanel {
|
||||
remaining: Option<u64>,
|
||||
/// 是否已过期
|
||||
expired: bool,
|
||||
/// 状态文案
|
||||
message: String,
|
||||
},
|
||||
/// 授权码流:等浏览器回调
|
||||
Browser {
|
||||
@@ -36,15 +38,11 @@ pub enum DevicePanel {
|
||||
remaining: Option<u64>,
|
||||
/// 是否已过期
|
||||
expired: bool,
|
||||
/// 状态文案
|
||||
message: String,
|
||||
},
|
||||
/// 失败(**原因码上屏,原文只进日志**)
|
||||
/// 失败(**只有原因码**;原文在 presenter 的日志里)
|
||||
Failed {
|
||||
/// 原因码(页面据此选 i18n)
|
||||
failure: LoginFailure,
|
||||
/// 原始诊断(不上屏)
|
||||
detail: String,
|
||||
},
|
||||
/// 成功
|
||||
Succeeded {
|
||||
@@ -59,23 +57,16 @@ pub enum AuthlibPanel {
|
||||
/// 没在跑
|
||||
Idle,
|
||||
/// 正在校验/登录
|
||||
Running {
|
||||
/// 状态文案
|
||||
message: String,
|
||||
},
|
||||
Running,
|
||||
/// 地址校验通过(服务发现拿到服务器名)
|
||||
Ready {
|
||||
/// 服务器名
|
||||
server_name: String,
|
||||
/// 状态文案
|
||||
message: String,
|
||||
},
|
||||
/// 失败(**原因码上屏,原文只进日志**)
|
||||
/// 失败(**只有原因码**;原文在 presenter 的日志里)
|
||||
Failed {
|
||||
/// 原因码(页面据此选 i18n)
|
||||
failure: LoginFailure,
|
||||
/// 原始诊断(不上屏)
|
||||
detail: String,
|
||||
},
|
||||
/// 成功
|
||||
Succeeded {
|
||||
@@ -89,16 +80,13 @@ pub(super) fn device_panel(view: &DeviceLoginView, remaining: Option<u64>) -> De
|
||||
let expired = remaining == Some(0);
|
||||
match view.phase {
|
||||
DeviceLoginPhase::Idle => DevicePanel::Hidden,
|
||||
DeviceLoginPhase::Requesting => DevicePanel::Requesting {
|
||||
message: view.message.clone(),
|
||||
},
|
||||
DeviceLoginPhase::Requesting => DevicePanel::Requesting,
|
||||
DeviceLoginPhase::Waiting => {
|
||||
if view.user_code.trim().is_empty() {
|
||||
DevicePanel::Browser {
|
||||
link: view.verification_uri.clone(),
|
||||
remaining,
|
||||
expired,
|
||||
message: view.message.clone(),
|
||||
}
|
||||
} else {
|
||||
DevicePanel::Code {
|
||||
@@ -106,13 +94,11 @@ pub(super) fn device_panel(view: &DeviceLoginView, remaining: Option<u64>) -> De
|
||||
link: view.qr_link.clone(),
|
||||
remaining,
|
||||
expired,
|
||||
message: view.message.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
DeviceLoginPhase::Failed => DevicePanel::Failed {
|
||||
failure: view.failure.unwrap_or(LoginFailure::Other),
|
||||
detail: view.message.clone(),
|
||||
},
|
||||
DeviceLoginPhase::Succeeded => DevicePanel::Succeeded {
|
||||
name: view.account_name.clone(),
|
||||
@@ -124,16 +110,10 @@ pub(super) fn device_panel(view: &DeviceLoginView, remaining: Option<u64>) -> De
|
||||
pub(super) fn authlib_panel(view: &AuthlibLoginView) -> (AuthlibPanel, String) {
|
||||
match view.phase {
|
||||
DeviceLoginPhase::Idle => (AuthlibPanel::Idle, String::new()),
|
||||
DeviceLoginPhase::Requesting => (
|
||||
AuthlibPanel::Running {
|
||||
message: view.message.clone(),
|
||||
},
|
||||
String::new(),
|
||||
),
|
||||
DeviceLoginPhase::Requesting => (AuthlibPanel::Running, String::new()),
|
||||
DeviceLoginPhase::Waiting => (
|
||||
AuthlibPanel::Ready {
|
||||
server_name: view.server_name.clone(),
|
||||
message: view.message.clone(),
|
||||
},
|
||||
view.server_name.clone(),
|
||||
),
|
||||
@@ -146,7 +126,6 @@ pub(super) fn authlib_panel(view: &AuthlibLoginView) -> (AuthlibPanel, String) {
|
||||
DeviceLoginPhase::Failed => (
|
||||
AuthlibPanel::Failed {
|
||||
failure: view.failure.unwrap_or(LoginFailure::Other),
|
||||
detail: view.message.clone(),
|
||||
},
|
||||
String::new(),
|
||||
),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! | 用例 | 抓得住什么 |
|
||||
//! |---|---|
|
||||
//! | `a_jwt_injected_into_events_never_reaches_the_page` | **凭据红线**:DTO → 状态 → 视图模型这一路漏了脱敏 |
|
||||
//! | `a_failure_with_a_provider_code_renders_english_and_the_code` | **语言泄漏(r1 p1)**:后端中文诊断串又直接上屏了 |
|
||||
//! | `a_failure_with_a_provider_code_renders_english_and_the_code` | **语言泄漏(r1 p1)**:模型 + i18n 查表这一路又出现中文(视图层由类型禁画原文,见 `tests_text` 的边界说明) |
|
||||
//! | `the_device_code_panel_shows_a_countdown_and_expires` | 设备码「只转圈」/ 倒计时不是人话格式 |
|
||||
|
||||
use unlucky_i18n::{Key, Lang};
|
||||
@@ -42,8 +42,8 @@ const JWT: &str =
|
||||
|
||||
/// **凭据红线(审核项)**:把 JWT 形状串注入事件 → 状态与视图模型里都不许出现。
|
||||
///
|
||||
/// 抓得住:presenter 状态层的脱敏被删(`AppState::apply_device_login` / `apply_authlib_login`),
|
||||
/// 或页面把 DTO 的错误原文直接搬上屏。
|
||||
/// 抓得住:presenter 状态层的脱敏被删(`AppState::apply_device_login` / `apply_authlib_login`)。
|
||||
/// 视图层不在此列:面板态里已无 `message`/`detail` 字段,想画原文是编译错误。
|
||||
#[test]
|
||||
fn a_jwt_injected_into_events_never_reaches_the_page() {
|
||||
let mut state = AppState::new();
|
||||
@@ -86,10 +86,12 @@ fn a_jwt_injected_into_events_never_reaches_the_page() {
|
||||
assert!(!text.contains("⟪redacted⟫"), "脱敏占位符也不该上屏:{text}");
|
||||
}
|
||||
|
||||
/// **语言泄漏(复审 r1 p1)**:带 AADSTS 码的失败事件 → 英文视图里不许有中文,且要带 code。
|
||||
/// **语言泄漏(复审 r1 p1)**:带 AADSTS 码的失败事件 → 英文取样里不许有中文,且要带 code。
|
||||
///
|
||||
/// 抓得住:有人又把 `view.message`(core 的中文诊断串)画回面板上;或者码→i18n 的映射被摘。
|
||||
/// 抓不住:catalog 里某条英文译文本身写得差(那只能人工审)。
|
||||
/// 抓得住:`failure_body_key` 的码→key 映射被摘/接错;DTO 或状态层把中文混进模型字段。
|
||||
/// 抓不住(复审 r2 P2-2 实测过):视图层把原文画上屏——这条断言是**平行实现**。
|
||||
/// 那道回归改由**类型**拦:`DevicePanel`/`AuthlibPanel` 已无 `message`/`detail` 字段。
|
||||
/// 也抓不住:catalog 里某条英文译文本身写得差(那只能人工审图)。
|
||||
#[test]
|
||||
fn a_failure_with_a_provider_code_renders_english_and_the_code() {
|
||||
let mut state = AppState::new();
|
||||
@@ -306,7 +308,7 @@ fn login_phases_map_to_distinct_panels() {
|
||||
},
|
||||
)));
|
||||
let page = build(&state, &Form::new());
|
||||
assert!(matches!(page.device, DevicePanel::Requesting { .. }));
|
||||
assert!(matches!(page.device, DevicePanel::Requesting));
|
||||
// 进行中也只有 i18n(不再复读 presenter 的中文状态串)
|
||||
assert!(!has_cjk(&on_screen(&page)));
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
//! 「**页面会画什么**」的取样器:把视图模型 + 视图层对码的 i18n 查表结果拼成一串。
|
||||
//! 「**模型这一层会给出什么**」的取样器:视图模型字段 + **按码做的 i18n 查表**,拼成一串。
|
||||
//!
|
||||
//! ## 边界(复审 r2 P2-2 订正,别把这条读成「视图覆盖」)
|
||||
//!
|
||||
//! 这是**平行实现**:它自己调 `catalog` 查表,**不调用** `login::panel` / `forms::status_line`,
|
||||
//! 也不遍历 iced 的元素树。所以它证明的是「模型 + i18n 表里没有中文」,**不证明**「视图真的照它画」。
|
||||
//!
|
||||
//! 视图层靠两道别的闸:
|
||||
//! 1. **类型**:`DevicePanel` / `AuthlibPanel` 里**没有** `message`/`detail` 字段(r3 删掉了),
|
||||
//! 想画后端原文是编译错误,不是测试问题;
|
||||
//! 2. **出图**:`DevDocs/artifacts/m114/after/*` 由人(tower/复审)逐张看。
|
||||
//!
|
||||
//! 为什么单独一个文件:`pages/account/tests.rs` 贴着 400 行硬线,而这段是**纯取样**
|
||||
//! (不给任何断言)——断言都在 `tests.rs` 里。语言泄漏(复审 r1 P1-1)与凭据红线两条判据
|
||||
//! 都依赖它:只有把「模型字段 + i18n 文案」一起取样,才能证明「英文界面里没有中文」。
|
||||
//! (不给任何断言)——断言都在 `tests.rs` 里。
|
||||
|
||||
use unlucky_i18n::{Key, Lang};
|
||||
use unlucky_presenter::dto::AccountNoticeCode;
|
||||
@@ -47,13 +56,12 @@ pub(super) fn on_screen(page: &Page) -> String {
|
||||
}
|
||||
match &page.device {
|
||||
DevicePanel::Hidden => {}
|
||||
DevicePanel::Requesting { .. } => out.push_str(catalog.text(Key::AccountDeviceRequesting)),
|
||||
DevicePanel::Requesting => out.push_str(catalog.text(Key::AccountDeviceRequesting)),
|
||||
DevicePanel::Code {
|
||||
code,
|
||||
link,
|
||||
remaining,
|
||||
expired,
|
||||
..
|
||||
} => {
|
||||
out.push_str(code);
|
||||
out.push_str(link);
|
||||
@@ -65,7 +73,7 @@ pub(super) fn on_screen(page: &Page) -> String {
|
||||
}
|
||||
}
|
||||
DevicePanel::Browser { link, .. } => out.push_str(link),
|
||||
DevicePanel::Failed { failure, .. } => {
|
||||
DevicePanel::Failed { failure } => {
|
||||
out.push_str(catalog.text(failure_body_key(*failure)));
|
||||
out.push_str(&catalog.render(Key::AccountFailureCode, &[("code", failure.code())]));
|
||||
}
|
||||
@@ -75,9 +83,9 @@ pub(super) fn on_screen(page: &Page) -> String {
|
||||
}
|
||||
match &page.authlib {
|
||||
AuthlibPanel::Idle => {}
|
||||
AuthlibPanel::Running { .. } => out.push_str(catalog.text(Key::AccountServerChecking)),
|
||||
AuthlibPanel::Ready { server_name, .. } => out.push_str(server_name),
|
||||
AuthlibPanel::Failed { failure, .. } => out.push_str(failure.code()),
|
||||
AuthlibPanel::Running => out.push_str(catalog.text(Key::AccountServerChecking)),
|
||||
AuthlibPanel::Ready { server_name } => out.push_str(server_name),
|
||||
AuthlibPanel::Failed { failure } => out.push_str(failure.code()),
|
||||
AuthlibPanel::Succeeded { name } => out.push_str(name),
|
||||
}
|
||||
out
|
||||
|
||||
@@ -123,23 +123,20 @@ pub fn alert<'a>(
|
||||
..container::Style::default()
|
||||
});
|
||||
|
||||
let mut lines = iced::widget::Column::new().spacing(ui.space(Space::X1));
|
||||
lines = lines.push(frame::text_role(
|
||||
ui,
|
||||
TypeRole::TitleSmall,
|
||||
true,
|
||||
title,
|
||||
ui.color(Role::OnSurface),
|
||||
));
|
||||
lines = lines.push(frame::text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
body,
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
));
|
||||
// 标题与 `Code: …` 同**一行**:`Code` 只是取证用的稳定 id,不值得占一整行高度
|
||||
// (1180×760 下多一行就把「All accounts」第三行挤到折线下)。
|
||||
let mut title_row = iced::widget::Row::new()
|
||||
.spacing(ui.space(Space::X2))
|
||||
.align_y(Alignment::Center)
|
||||
.push(frame::text_role(
|
||||
ui,
|
||||
TypeRole::TitleSmall,
|
||||
true,
|
||||
title,
|
||||
ui.color(Role::OnSurface),
|
||||
));
|
||||
if let Some(code) = code {
|
||||
lines = lines.push(frame::text_role_owned(
|
||||
title_row = title_row.push(frame::text_role_owned(
|
||||
ui,
|
||||
TypeRole::LabelSmall,
|
||||
false,
|
||||
@@ -148,6 +145,16 @@ pub fn alert<'a>(
|
||||
));
|
||||
}
|
||||
|
||||
let mut lines = iced::widget::Column::new().spacing(ui.space(Space::X1));
|
||||
lines = lines.push(title_row);
|
||||
lines = lines.push(frame::text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
body,
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
));
|
||||
|
||||
container(
|
||||
row![
|
||||
chip,
|
||||
|
||||
Reference in new issue
Block a user