7f6e35908c
- 移除自定义 UI 字体,界面统一系统原生:删 BrandFonts.swift(Orbit/Maple woff2 运行期 CTFontManager 注册 + UIKit 外观代理)+ AppDelegate 的 register 调用 + CDropApp 全局 .environment(\.font, .oBody);9 视图 .font(.o语义) → .font(.语义) 系统语义(日志 LogView 消息行本就 .caption.monospaced() 原生等宽);删 Sources/Fonts/(Orbit/Maple woff2 ~20MB)。仅留品牌字标 Fredoka(logo Wordmark),Justfile ios-fonts 改 Fredoka-only、.gitignore 收窄、新增 Shared/Fonts/README.md - iPad 适配:RootView TabView 加 .tabViewStyle(.sidebarAdaptable)(iPad 常规宽度侧边栏 / iPhone 底部标签栏)+ ConversationView 消息线程 maxWidth 680 居中;project.yml 补方向键 UISupportedInterfaceOrientations(iPhone 竖 + 横)/ ~ipad(全 4 向),消除 iPad 多任务方向构建告警 - 登录页内容约束 maxWidth 420 居中,避免 iPad 宽屏主登录按钮 / 二维码区横向拉满(iPhone 不受影响) - 文档:ios/PLAN.md §11.2 改系统原生字体 + 新增 §13 iPad;ios/PARITY.md 字体分叉条目回退 + 导航条目加 iPad + 2026-07-07 漂移行 - 已装 iPhone 15 Pro C + iPad Pro 13" M4 双端、用户确认
81 lines
2.5 KiB
Swift
81 lines
2.5 KiB
Swift
import SwiftUI
|
||
|
||
// 瞬时通知横幅(#2):覆盖在标签内容顶部,醒目、状态分明(成功绿 / 失败红 / 信息中性)、自动消失
|
||
// (EngineController.notify 安排 4s 后移除)、可点叉手动清除。取代原先散落在设备页 / 设置页、串页显示
|
||
// 且永不清除的灰色小字(deviceActionStatus / clipboardStatus)。由 EngineController.notices 驱动。
|
||
struct NoticeOverlay: View
|
||
{
|
||
@Environment(EngineController.self) private var engine
|
||
|
||
var body: some View
|
||
{
|
||
VStack(spacing: 8)
|
||
{
|
||
ForEach(engine.notices)
|
||
{ notice in
|
||
NoticeBanner(notice: notice) { engine.dismissNotice(notice.id) }
|
||
.transition(.move(edge: .top).combined(with: .opacity))
|
||
}
|
||
}
|
||
.padding(.horizontal, 16)
|
||
.padding(.top, 8)
|
||
.animation(.spring(duration: 0.3), value: engine.notices)
|
||
// 无通知时不拦截下方内容的点击;有通知时仅横幅自身(含叉按钮)可点。
|
||
.allowsHitTesting(!engine.notices.isEmpty)
|
||
}
|
||
}
|
||
|
||
private struct NoticeBanner: View
|
||
{
|
||
let notice: Notice
|
||
let onDismiss: () -> Void
|
||
|
||
var body: some View
|
||
{
|
||
HStack(spacing: 10)
|
||
{
|
||
Image(systemName: symbol)
|
||
.font(.headline)
|
||
.foregroundStyle(tint)
|
||
Text(notice.text)
|
||
.font(.subheadline)
|
||
.foregroundStyle(.primary)
|
||
.lineLimit(2)
|
||
Spacer(minLength: 8)
|
||
Button { onDismiss() }
|
||
label:
|
||
{
|
||
Image(systemName: "xmark")
|
||
.font(.caption.weight(.bold))
|
||
.foregroundStyle(.secondary)
|
||
}
|
||
.buttonStyle(.plain)
|
||
}
|
||
.padding(.vertical, 12)
|
||
.padding(.horizontal, 14)
|
||
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 14))
|
||
.overlay(RoundedRectangle(cornerRadius: 14).strokeBorder(tint.opacity(0.35), lineWidth: 1))
|
||
.shadow(color: .black.opacity(0.12), radius: 8, y: 2)
|
||
}
|
||
|
||
private var symbol: String
|
||
{
|
||
switch notice.kind
|
||
{
|
||
case .success: return "checkmark.circle.fill"
|
||
case .error: return "exclamationmark.triangle.fill"
|
||
case .info: return "info.circle.fill"
|
||
}
|
||
}
|
||
|
||
private var tint: Color
|
||
{
|
||
switch notice.kind
|
||
{
|
||
case .success: return .green
|
||
case .error: return .red
|
||
case .info: return .cdropAccent
|
||
}
|
||
}
|
||
}
|