//! 两条登录流的面板态(微软设备码/授权码 + 第三方站校验/登录)。 //! //! 从 `model.rs` 拆出来守 400 行硬线:这些类型是「UI 要画什么」的枚举, //! 判定逻辑(倒计时 → 已过期、服务发现 → 可用)也在这里,页面与表单只消费结果。 use unlucky_presenter::dto::{DeviceLoginPhase, DeviceLoginView}; /// 微软登录面板要画什么(每态都不同,**不允许只有一个转圈**)。 #[derive(Debug, Clone, PartialEq, Eq)] pub enum DevicePanel { /// 没有登录在跑 Hidden, /// 正在申请设备码 Requesting { /// 状态文案(presenter 给的,已脱敏) message: String, }, /// 设备码等待用户操作 Code { /// 用户码 code: String, /// 验证链接 link: String, /// 剩余秒数 remaining: Option, /// 是否已过期 expired: bool, /// 状态文案 message: String, }, /// 授权码流:等浏览器回调 Browser { /// 授权链接(可复制) link: String, /// 剩余秒数 remaining: Option, /// 是否已过期 expired: bool, /// 状态文案 message: String, }, /// 失败(原文已脱敏) Failed { /// 失败原因 message: String, }, /// 成功 Succeeded { /// 账户名 name: String, }, } /// 第三方轨面板的可见状态。 #[derive(Debug, Clone, PartialEq, Eq)] pub enum AuthlibPanel { /// 没在跑 Idle, /// 正在校验/登录 Running { /// 状态文案 message: String, }, /// 地址校验通过(服务发现拿到服务器名) Ready { /// 服务器名 server_name: String, /// 状态文案 message: String, }, /// 失败(原文已脱敏) Failed { /// 失败原因 message: String, }, /// 成功 Succeeded { /// 账户名 name: String, }, } /// 微软登录状态 → 面板态(倒计时与「已过期」都在这里定性,UI 只画)。 pub(super) fn device_panel(view: &DeviceLoginView, remaining: Option) -> DevicePanel { let expired = remaining == Some(0); match view.phase { DeviceLoginPhase::Idle => DevicePanel::Hidden, DeviceLoginPhase::Requesting => DevicePanel::Requesting { message: view.message.clone(), }, 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 { code: view.user_code.clone(), link: view.qr_link.clone(), remaining, expired, message: view.message.clone(), } } } DeviceLoginPhase::Failed => DevicePanel::Failed { message: view.message.clone(), }, DeviceLoginPhase::Succeeded => DevicePanel::Succeeded { name: view.account_name.clone(), }, } } /// 第三方轨状态 → 面板态(顺带取出「校验通过的服务器名」)。 pub(super) fn authlib_panel( view: &unlucky_presenter::dto::AuthlibLoginView, ) -> (AuthlibPanel, String) { match view.phase { DeviceLoginPhase::Idle => (AuthlibPanel::Idle, String::new()), DeviceLoginPhase::Requesting => ( AuthlibPanel::Running { message: view.message.clone(), }, String::new(), ), DeviceLoginPhase::Waiting => ( AuthlibPanel::Ready { server_name: view.server_name.clone(), message: view.message.clone(), }, view.server_name.clone(), ), DeviceLoginPhase::Succeeded => ( AuthlibPanel::Succeeded { name: view.account_name.clone(), }, view.server_name.clone(), ), DeviceLoginPhase::Failed => ( AuthlibPanel::Failed { message: view.message.clone(), }, String::new(), ), } } impl AuthlibPanel { /// 是否正在跑(此时表单字段禁用,避免改了一半的地址被提交)。 #[must_use] pub fn busy(&self) -> bool { matches!(self, Self::Running { .. }) } }