iOS 界面字体回退系统原生 + iPad 适配 + 登录列宽

- 移除自定义 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 双端、用户确认
This commit is contained in:
2026-07-07 20:49:06 +08:00
parent adfd2912a5
commit 7f6e35908c
18 changed files with 123 additions and 174 deletions
+5 -7
View File
@@ -77,14 +77,12 @@ desktop-doctor:
ios-devices:
xcrun devicectl list devices
# 拉取品牌字体(不入库——Orbit Gothic 专有;统一从 CDN / Google Fonts 拉,镜像 web 思路)。幂等:已存在
# 则跳过。ios-sim-build / ios-device 构建前已 chain 本步。前置:curl;造 Fredoka 静态实例需 python3 +
# fonttoolspython3 -m pip install --user fonttools)。见 ios/CDrop/Sources/Fonts/README.md。
# 拉取品牌字标字体 Fredokalogo / Wordmark 用,不入库——虽为开源 OFL 仍统一从 Google Fonts 拉,
# 保持仓库精简)。幂等:已存在则跳过。ios-sim-build / ios-device 构建前已 chain 本步。前置:curl;造
# Fredoka 静态实例需 python3 + fonttoolspython3 -m pip install --user fonttools)。UI 界面字体已改用
# 系统原生字体(不再拉 Orbit Gothic / Maple Mono);见 ios/CDrop/Shared/Fonts/README.md。
ios-fonts:
mkdir -p ios/CDrop/Sources/Fonts ios/CDrop/Shared/Fonts
[ -f ios/CDrop/Sources/Fonts/OrbitGothicCJKsc-Regular.woff2 ] || curl -fsSL "https://orbit-gothic.cdn.commilitia.net/WOFF2/CJKsc/OrbitGothicCJKsc-Regular.woff2" -o ios/CDrop/Sources/Fonts/OrbitGothicCJKsc-Regular.woff2
[ -f ios/CDrop/Sources/Fonts/OrbitGothicCJKsc-SemiBold.woff2 ] || curl -fsSL "https://orbit-gothic.cdn.commilitia.net/WOFF2/CJKsc/OrbitGothicCJKsc-SemiBold.woff2" -o ios/CDrop/Sources/Fonts/OrbitGothicCJKsc-SemiBold.woff2
[ -f ios/CDrop/Sources/Fonts/MapleMono-Regular.woff2 ] || curl -fsSL "https://maple-mono.cdn.commilitia.net/MapleMono-Regular.ttf.woff2" -o ios/CDrop/Sources/Fonts/MapleMono-Regular.woff2
mkdir -p ios/CDrop/Shared/Fonts
[ -f ios/CDrop/Shared/Fonts/Fredoka-SemiBold.ttf ] || just ios-fonts-fredoka
# Fredoka 字标:从 Google Fonts 变量字体实例化出静态 SemiBoldwght=600, wdth=100)。
+3 -5
View File
@@ -10,11 +10,9 @@ DerivedData/
# 原生数据面引擎(曾是 gomobile/pion → xcframework;现已改 libwebrtc SPM,本目录废弃但留忽略防残留)。
Frameworks/
# 品牌字体不入库:Orbit Gothic 为专有 / 定制字体(公开仓库不应分发);Maple Mono / Fredoka 虽开源,
# 也统一不入库、改由 `just ios-fonts` 从 CDN / Google Fonts 拉取(镜像 web 的 CDN 思路),保持仓库精简
# 见 Sources/Fonts/README.md。构建前 `just ios-fonts`(已 chain 进 ios-device / ios-sim-build)会自动备齐
Sources/Fonts/*.woff2
Sources/Fonts/*.ttf
# 品牌字标字体 Fredokalogo / Wordmark 用)不入库:虽为开源 OFL,仍统一从 Google Fonts 拉、保持仓库
# 精简。见 Shared/Fonts/README.md。构建前 `just ios-fonts`(已 chain 进 ios-device / ios-sim-build)自动备齐
# UI 界面字体已改用系统原生字体,不再打包 Orbit Gothic / Maple Mono
Shared/Fonts/*.ttf
Shared/Fonts/*.woff2
+30
View File
@@ -0,0 +1,30 @@
# 品牌字标字体 Fredoka(不入库,构建前拉取)
iOS App 的 **UI 界面字体已统一用系统原生字体**`.font(.body)` / `.headline` / … 语义样式随 Dynamic Type
缩放;日志等宽处用 `.system(…, design: .monospaced)` 原生等宽字体)。**唯一保留的自定义字体是品牌字标
Fredoka**——仅用于 logo 的 Wordmark(「Commilitia Drop」,见 `Shared/Brand.swift`),与 web 品牌资产一致。
| 角色 | 字体 | 落地 | 来源 |
|---|---|---|---|
| 字标(Wordmark,仅 logo | **Fredoka SemiBold** | `Shared/Fonts/Fredoka-SemiBold.ttf` | Google Fonts 变量字体实例化(OFL |
## 为什么不提交进仓库
Fredoka 虽为开源(OFL),仍统一走拉取以保持仓库精简;经 `.gitignore` 排除,本 README 入库作说明。
## 如何备齐
```sh
just ios-fonts
```
幂等(已存在则跳过)。`just ios-device` / `just ios-sim-build` 构建前已自动 chain 本步,正常无需手动跑。
**前置**`curl`;造 Fredoka 静态实例需 `python3` + `fonttools``python3 -m pip install --user fonttools`)。
## 接入方式(代码)
Fredoka 是 ttf,经 `UIAppFonts`(各 target 的 Info.plist)注册;`Wordmark``Shared/Brand.swift`)用
`.font(.custom("Fredoka-SemiBold", size:))`。主 app、Share 扩展、控件扩展均可用(字标体积小,无 CJK)。
> 历史:早先曾为 UI 引入 Orbit GothicSans+ Maple MonoMono)自定义字体栈,后统一回退系统原生字体
> 移除(仅保留 Fredoka 字标)。故不再有 `Sources/Fonts/` 与运行期 `CTFontManagerRegisterFontsForURL` 注册。
-2
View File
@@ -10,8 +10,6 @@ final class AppDelegate: NSObject, UIApplicationDelegate
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool
{
// UI + / Orbit Gothic
BrandFonts.register()
// BGContinuedProcessingTask launch handler launch
BackgroundTaskManager.shared.registerOnce()
return true
+7 -3
View File
@@ -35,7 +35,7 @@ struct LoginView: View
if !auth.statusText.isEmpty
{
Text(auth.statusText)
.font(.oCallout)
.font(.callout)
.foregroundStyle(auth.qrExpired ? Color.red : Color.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 40)
@@ -49,7 +49,7 @@ struct LoginView: View
qrArea
refreshButton
Text(t("ios.login.guide"))
.font(.oFootnote)
.font(.footnote)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
@@ -58,7 +58,7 @@ struct LoginView: View
label:
{
Text(t("ios.login.scanAlt"))
.font(.oSubheadline)
.font(.subheadline)
}
.padding(.horizontal, 40)
.onChange(of: showQR)
@@ -68,6 +68,10 @@ struct LoginView: View
Spacer()
}
// iPad /
//iPhone 420
.frame(maxWidth: 420)
.frame(maxWidth: .infinity)
.padding()
.tint(.cdropAccent)
}
-81
View File
@@ -1,81 +0,0 @@
import CoreText
import SwiftUI
import UIKit
// web --font-sans / --font-mono
// - Sans Orbit Gothic CJK SCRegular 400 + SemiBold 600
// - Mono Maple Mono /
// woff2 TTF 1/3iOS Core Text iOS 13 woff/woff2
// CTFontManagerRegisterFontsForURL UIAppFonts ttf/otfwoff2
// app CJK ~10MB/ Share / + Fredoka
// Orbit Gothic iOS 退
enum BrandFonts
{
static let sansRegular = "OrbitGothicCJKsc-Regular"
static let sansSemibold = "OrbitGothicCJKsc-SemiBold"
static let mono = "MapleMono-Regular"
// app AppDelegate didFinishLaunching + UIKit
static func register()
{
for name in [ sansRegular, sansSemibold, mono ]
{
guard let url = Bundle.main.url(forResource: name, withExtension: "woff2") else { continue }
CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil)
}
applyUIKitAppearance()
}
// SwiftUI / UIKit SwiftUI API appearance Orbit
private static func applyUIKitAppearance()
{
if let large = UIFont(name: sansSemibold, size: 32)
{
UINavigationBar.appearance().largeTitleTextAttributes = [ .font: large ]
}
if let inline = UIFont(name: sansSemibold, size: 17)
{
UINavigationBar.appearance().titleTextAttributes = [ .font: inline ]
}
if let tab = UIFont(name: sansRegular, size: 10)
{
let item = UITabBarItem.appearance()
item.setTitleTextAttributes([ .font: tab ], for: .normal)
item.setTitleTextAttributes([ .font: tab ], for: .selected)
}
}
}
// Orbit GothicSans+ Maple MonoMonorelativeTo
// 访weight bold SemiBold Regular
extension Font
{
static func orbit(_ size: CGFloat, bold: Bool = false, relativeTo style: Font.TextStyle = .body) -> Font
{
.custom(bold ? BrandFonts.sansSemibold : BrandFonts.sansRegular, size: size, relativeTo: style)
}
static func maple(_ size: CGFloat, relativeTo style: Font.TextStyle = .body) -> Font
{
.custom(BrandFonts.mono, size: size, relativeTo: style)
}
// SF Orbit / SF .font(.xxx)
// headline / title* SemiBold / Regular
static let oLargeTitle = orbit(34, bold: true, relativeTo: .largeTitle)
static let oTitle = orbit(28, bold: true, relativeTo: .title)
static let oTitle2 = orbit(22, bold: true, relativeTo: .title2)
static let oTitle3 = orbit(20, bold: true, relativeTo: .title3)
static let oHeadline = orbit(17, bold: true, relativeTo: .headline)
static let oBody = orbit(17, relativeTo: .body)
static let oCallout = orbit(16, relativeTo: .callout)
static let oSubheadline = orbit(15, relativeTo: .subheadline)
static let oFootnote = orbit(13, relativeTo: .footnote)
static let oCaption = orbit(12, relativeTo: .caption)
static let oCaption2 = orbit(11, relativeTo: .caption2)
// Mono / id /
static let mCaption = maple(12, relativeTo: .caption)
static let mFootnote = maple(13, relativeTo: .footnote)
static let mBody = maple(15, relativeTo: .body)
}
-3
View File
@@ -15,9 +15,6 @@ struct CDropApp: App
WindowGroup
{
AppRoot(auth: auth, engine: engine)
// Orbit Gothic .font Sans
// .font(.oXxx) BrandFonts
.environment(\.font, .oBody)
}
.onChange(of: scenePhase)
{ _, phase in
-40
View File
@@ -1,40 +0,0 @@
# 品牌字体(不入库,构建前拉取)
iOS App 的字体栈对齐 web(见 `web/src/styles/fonts.css`):
| 角色 | 字体 | 落地 | 来源 |
|---|---|---|---|
| Sans(正文 / 界面) | **Orbit Gothic CJK SC**Regular 400 + SemiBold 600 | `Sources/Fonts/OrbitGothicCJKsc-*.woff2` | 私有 CDN `orbit-gothic.cdn.commilitia.net` |
| Mono(等宽) | **Maple Mono**Regular | `Sources/Fonts/MapleMono-Regular.woff2` | 私有 CDN `maple-mono.cdn.commilitia.net`OFL |
| 字标(Wordmark | **Fredoka SemiBold** | `Shared/Fonts/Fredoka-SemiBold.ttf` | Google Fonts 变量字体实例化(OFL |
## 为什么不提交进仓库
- **Orbit Gothic 是专有 / 定制字体**——公开仓库(见根 README 的双 remote 发布)不应分发它。web 端同样不入库、
改从私有 CDN 拉。
- Maple Mono / Fredoka 虽开源(OFL),也统一走拉取,保持仓库精简(Orbit 两变体 woff2 合计 ~20MB)。
字体文件经 `.gitignore` 排除(`Sources/Fonts/*.woff2,*.ttf``Shared/Fonts/*`);本 README 入库作说明。
## 如何备齐
```sh
just ios-fonts
```
幂等(已存在则跳过)。`just ios-device` / `just ios-sim-build` 构建前已自动 chain 本步,正常无需手动跑。
**前置**`curl`;造 Fredoka 静态实例需 `python3` + `fonttools``python3 -m pip install --user fonttools`)。
CDN 不可达时构建会失败——离线则手动把上述文件放到对应目录即可。
## 接入方式(代码)
字体以 **woff2** 内嵌(体积约 TTF 的 1/3iOS 13+ Core Text 原生支持 woff/woff2)。`UIAppFonts`Info.plist
历史上只认 ttf/otf,故 woff2 走**运行期注册** `CTFontManagerRegisterFontsForURL`——见 `Sources/BrandFonts.swift`
`BrandFonts.register()``AppDelegate` 启动调用)。Fredoka 字标是 ttf,经 `UIAppFonts` 注册。
仅**主 app** 接入 Orbit / MapleCJK 字体大,不进 Share / 控件扩展——它们保持系统字 + 仅 Fredoka 字标)。
生僻字超出 Orbit Gothic 字汇时由 iOS 字体级联自动回退系统苹方,不出豆腐块。
字体助手见 `Sources/BrandFonts.swift``Font.oBody/.oHeadline/...`Orbit,对位 SF 语义样式)、`Font.maple(...)`
Maple Mono);导航 / 标签栏标题经 UIKit appearance 代理换 Orbit。
+13
View File
@@ -70,5 +70,18 @@
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
+2 -2
View File
@@ -109,10 +109,10 @@ struct LogView: View
HStack(spacing: 6)
{
Image(systemName: levelSymbol(entry.level))
.font(.oCaption)
.font(.caption)
.foregroundStyle(levelTint(entry.level))
Text(timeString(entry.at))
.font(.oCaption2)
.font(.caption2)
.foregroundStyle(.secondary)
.monospacedDigit()
}
+10 -7
View File
@@ -69,16 +69,16 @@ struct MessagesView: View
HStack(spacing: 12)
{
Image(systemName: deviceSymbol(convo.deviceType))
.font(.oTitle3)
.font(.title3)
.foregroundStyle(convo.online ? Color.green : Color.secondary)
.frame(width: 28)
VStack(alignment: .leading, spacing: 3)
{
Text(convo.peerName)
.font(.oBody)
.font(.body)
.lineLimit(1)
Text(previewText(convo))
.font(.oCaption)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -88,7 +88,7 @@ struct MessagesView: View
if convo.lastAt > 0
{
Text(relativeTime(convo.lastAt))
.font(.oCaption2)
.font(.caption2)
.foregroundStyle(.secondary)
}
// tab engine.unreadMessages
@@ -96,7 +96,7 @@ struct MessagesView: View
if unread > 0
{
Text("\(unread)")
.font(.oCaption2)
.font(.caption2)
.fontWeight(.bold)
.foregroundStyle(.white)
.padding(.horizontal, 7)
@@ -159,6 +159,9 @@ struct ConversationView: View
}
}
.padding()
// iPad iMessage
.frame(maxWidth: 680)
.frame(maxWidth: .infinity)
}
.onChange(of: thread.count) { scrollToLast(proxy) }
.onAppear { scrollToLast(proxy) }
@@ -213,7 +216,7 @@ struct ConversationView: View
.submitLabel(.send)
.onSubmit { send() }
Button { send() }
label: { Image(systemName: "paperplane.fill").font(.oTitle3) }
label: { Image(systemName: "paperplane.fill").font(.title3) }
.disabled(draft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
.padding()
@@ -222,7 +225,7 @@ struct ConversationView: View
else
{
Text(t("ios.messages.viewOnly"))
.font(.oCaption)
.font(.caption)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity)
.padding()
+2 -2
View File
@@ -35,10 +35,10 @@ private struct NoticeBanner: View
HStack(spacing: 10)
{
Image(systemName: symbol)
.font(.oHeadline)
.font(.headline)
.foregroundStyle(tint)
Text(notice.text)
.font(.oSubheadline)
.font(.subheadline)
.foregroundStyle(.primary)
.lineLimit(2)
Spacer(minLength: 8)
+13 -9
View File
@@ -60,6 +60,10 @@ struct RootView: View
}
}
}
// iPad iPadFiles / Music +
// iPhone TabView sidebarAdaptable iPad
// NavigationStack ios/PLAN.md §13
.tabViewStyle(.sidebarAdaptable)
.tabBarMinimizeBehavior(.onScrollDown)
.tint(.cdropAccent)
// #2
@@ -162,7 +166,7 @@ struct TransferListView: View
label:
{
Image(systemName: "paperplane.fill")
.font(.oTitle2)
.font(.title2)
.padding(18)
}
.buttonStyle(.glassProminent)
@@ -220,7 +224,7 @@ struct TransferListView: View
HStack
{
Text(title)
.font(.oSubheadline)
.font(.subheadline)
.foregroundStyle(.secondary)
Spacer()
}
@@ -342,7 +346,7 @@ struct TransferCardView: View
? "arrow.down.circle" : "arrow.up.circle")
.foregroundStyle(.secondary)
Text(item.fileName)
.font(.oHeadline)
.font(.headline)
.lineLimit(1)
Spacer()
}
@@ -351,7 +355,7 @@ struct TransferCardView: View
ProgressView(value: progressOf(item))
}
Text(detailLine)
.font(.oCaption)
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
@@ -625,14 +629,14 @@ struct DeviceListView: View
if isSelf(dev)
{
Text(t("ios.devices.thisDevice"))
.font(.oCaption2)
.font(.caption2)
.foregroundStyle(Color.cdropAccent)
}
}
Text(dev.online
? t("settings.online")
: t("settings.lastSeen", [ "time": relativeTime(dev.lastSeen) ]))
.font(.oCaption)
.font(.caption)
.foregroundStyle(.secondary)
}
}
@@ -664,7 +668,7 @@ struct SettingsView: View
if !appVersion.isEmpty
{
Text(appVersion)
.font(.oCaption2)
.font(.caption2)
.foregroundStyle(.secondary)
}
}
@@ -718,7 +722,7 @@ struct SettingsView: View
if !engine.lastEngineLog.isEmpty
{
Text(engine.lastEngineLog)
.font(.oCaption2)
.font(.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -861,7 +865,7 @@ struct ReceivedFilesView: View
Text(url.lastPathComponent)
.lineLimit(1)
Text(fileSizeString(url))
.font(.oCaption)
.font(.caption)
.foregroundStyle(.secondary)
}
}
+2 -2
View File
@@ -59,14 +59,14 @@ struct ScanSheet: View
if invalid
{
Text(t("ios.scan.invalid"))
.font(.oCallout)
.font(.callout)
.padding(.vertical, 8)
.padding(.horizontal, 14)
.background(.red.opacity(0.85), in: Capsule())
.foregroundStyle(.white)
}
Text(t("ios.scan.hint"))
.font(.oFootnote)
.font(.footnote)
.multilineTextAlignment(.center)
.padding(.vertical, 8)
.padding(.horizontal, 14)
+1 -1
View File
@@ -40,7 +40,7 @@ struct SendComposeSheet: View
.lineLimit(1)
Spacer()
Text(formatBytes(file.size))
.font(.oCaption)
.font(.caption)
.foregroundStyle(.secondary)
}
}
+12
View File
@@ -57,6 +57,18 @@ targets:
- zh-Hant
- en
UILaunchScreen: {}
# 界面方向:iPhone 竖屏 + 横屏(不含上下颠倒,遵 iPhone 惯例);iPad 支持全部 4 向——通用 App
# 若不锁全屏(UIRequiresFullScreen 未设),iPad 多任务(Split View / Slide Over)要求支持全部方向,
# 否则构建告警「All interface orientations must be supported...」。
UISupportedInterfaceOrientations:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
"UISupportedInterfaceOrientations~ipad":
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
# 后台续传:BGContinuedProcessingTask 的通配标识符须在此声明,系统才允许注册 / 提交
# (见 BackgroundTaskManager)。默认资源(CPU + 网络)无需额外 entitlement。
BGTaskSchedulerPermittedIdentifiers:
+3 -2
View File
@@ -30,8 +30,8 @@
### 4. 允许分叉登记(明确 OK 不一致)
- **视觉材质**:液态玻璃 vs Theme B。
- **导航外壳**:原生 `NavigationStack` / `TabView` vs web 布局。
- **字体 / 排版**2026-06-28 更新,字族已收敛):两端现**同用** Orbit GothicSans+ Maple MonoMono+ Fredoka(字标)——字族升为共享源(强约束③延伸)。**残余分叉**仅在落地机制(iOS woff2 运行期 `CTFontManagerRegisterFontsForURL` 注册 + UIKit appearance 代理 + Dynamic Type vs web CSS `@font-face`)与间距 / 圆角 / 字阶(各用平台母语);生僻字 iOS 回退苹方、web 回退 Noto。Orbit 为专有字体,两端均不入库、构建期从 CDN 拉。
- **导航外壳**:原生 `NavigationStack` / `TabView`iPad 经 `.tabViewStyle(.sidebarAdaptable)` 呈侧边栏、iPhone 底部标签栏)vs web 布局。
- **字体 / 排版**2026-07-07 更新,iOS 撤下自定义字体):**iOS 界面用系统原生字体**SF 语义样式 + Dynamic Type;日志等宽用原生 monospacedvs web Orbit Gothic / Noto——字体选择归**允许分叉**(各用平台母语,间距 / 圆角 / 字阶同)。**唯一共享**的自定义字体是**品牌字标 Fredoka**(仅 logo 的 Wordmark,两端一致);品牌 logo(mark + 字标)仍是共享源(强约束③)。(撤下 2026-06-28 曾引入的 Orbit Gothic + Maple Mono UI 字体栈。)
- **分享引导界面**(仅 iOS):Share Extension 内的吉祥物引导卡(`ShareGuideView`)引用户回主程序完成发送——平台专属流程,web 无对应。
- **平台专属**:控制中心控件、Share Extension(仅 iOS);Web Push 订阅 UI、桌面设置区块(非 iOS)。
- **控制中心控件标签**:运行时控件标签(用户点按的那个)经 `t()` 跟随设备语言;但「添加控件」库名 + Shortcuts 标题是 `LocalizedStringResource` 静态值、`CDropWidgets` target 无 String Catalog,故对 en / zh-TW 用户回退简体字面量(审查 L7,平台专属功能可接受分叉;要彻底本地化须为 Widgets target 加 `.xcstrings`)。
@@ -62,3 +62,4 @@
| 2026-06-24 | engine/main.tstransfers 推送节流 ~3Hz + toWire 加 ice 摘要(未提交,**待部署** | EngineController 手动容忍解析 + ICE 字段;RootView 详情页 ICE 行 + 收到文件视图 + 设备名编辑 + 设备计数;DownloadManager listFiles/delete;新 DeviceNameStore / QuickLookPreviewproject.yml 加文件共享键 + 品牌化权限串(未提交) | **首轮真机测试反馈修复**:① 速度数字 ~10Hz 闪 → 节流活跃传输推送到 3Hz(iOS-only,亦降 P2P 接收主线程抖动);② 设备列表空 → 原生改手动容忍解析(严格 JSONDecoder 遇 bool/int 失配整组失败的隐患),加「已知设备」计数诊断;③ 收到文件无处开 / Files 不可见 / 阻塞发送 → `UIFileSharingEnabled`+`LSSupportsOpeningDocumentsInPlace`Documents 已是落地目录)+ 应用内「收到的文件」视图(QuickLook 预览 / 系统分享 / 转发在线设备 / 删除);④ 设备名不可改 → `DeviceNameStore`UserDefaults)+ 设置页编辑框(后端令牌烤名、改名下次登录生效);⑤ P2P <100KB/srelay 1MB/s)→ 详情页暴露选中 ICE 候选对诊断是否 TURN 中继(不盲改共享 p2p.ts 水位)。i18n 加 `ios.files.*`/`ios.settings.deviceName|deviceNameNote|deviceCount`ICE 行复用 `transfer.debug.*`。web 双过 + xcodebuild 编过 + 模拟器设置 / 传输屏截图验。**已部署 prod**:`engine.html``engine-BC6mZ9zP.js`(含节流 + ice 摘要) |
| 2026-06-27 | source.ts(新 FileSource+ p2p/relay/transfer 改 src + engine/main.ts 加 registerPush/provisionWidgetSession + i18n 加 `ios.bg.*`/`ios.share.*`/`ios.control.*`(本提交) | 新 BackgroundTaskManager / AppDelegate / PushRegistry / Shared(AppGroup,I18n,CDropAPI,WidgetSession) / Share(ShareViewController) / Widgets(控件+intents+client)EngineController 加 Range handler / 后台 sync / 分享导入 / 控件代铸;project.yml 加两扩展 target + 真机签名 scaffold;包名改 `net.commilitia.Commilitia-Drop`(本提交) | **计划完成(I3I7**:发送端流式(FileSource + 原生 Range,新增**分享发送 + 控件**两条非引擎流程,对应残余项 1/2)/ 后台续传 / APNs(后端 internal/apns + 原生注册,新后端字段 `push_subscriptions.platform` + 端点 `POST /api/push/apns/register` 对应残余项 3/ Share Extension / 控制中心两控件(新允许分叉:控件标签见上)。经 ultracode 多 agent 审查(0 HIGH,修 2 MED + 7 LOW)。web tsc/build + go build/test + xcodebuild 全过;模拟器验登录 / 分享弹窗 / 控件嵌入 / App Group 容器。**真机 + 真发推送 + 分发账号门控**(见 REALDEVICE.md)。**待部署 prod**engine.html(新桥)+ cdrop binaryAPNs 端点 + platform 列) |
| 2026-06-28 | `p2p.ts``IOS_NATIVE=true` + 新 `p2pIos.ts` + `p2pNative.ts`/`net/ios.ts` 微调;平均速度 `store/slices/transfer.ts` + `TransferRow.tsx`i18n 加 `transfer.avgRate`native-transfer C1/C2 | 新 `Engine/LibWebRtcEngine.swift` + 改 `NativeTransfer.swift`/`EngineController.swift`(原生数据面);新 `SendComposeSheet.swift` + 重写 `MessagesView.swift`(发送预览 / 按设备会话 + 每设备未读);新 `Shared/Brand.swift`/`Sources/BrandFonts.swift`/`Share/ShareGuideView.swift` + 品牌资产;i18n 加 `ios.send.*`/`ios.messages.*`/`ios.share.guide.*`native-transfer C1/C2 | **iOS 原生数据面 libwebrtc**(P2P 数据面下沉原生、线协议逐字节对齐桌面 pion,PLAN 决策 F;编排留无头 WebView+ **UX 改造**(发送预览直入 / 分享直入 / 消息按设备会话 + 每设备未读红点 / 删除粒度——对应残余项 1/2)+ **品牌 + 字体**logo + Orbit Gothic / Maple Mono / Fredoka,字族升共享源、字体分叉条目已更新)+ **全平台平均速度**(残余项 2,三端一致)。环回端到端 20MB 逐字节 + 模拟器全 app 编译 + web tsc/build 过;真机已装、用户反馈“传输总体良好”,真网吞吐互通为剩余闸。**待部署 prod**engine.html`IOS_NATIVE=true` 引擎);桌面平均速度待 `desktop-build-{mac,win}` 重建分发 |
| 2026-07-07 | 无(纯 iOS 端改动,web 字体栈不变) | 移除 Orbit / Maple 自定义 UI 字体(删 `BrandFonts.swift` + `Sources/Fonts/`),9 视图 `.o语义`→系统语义 + 日志原生等宽,仅留 Fredoka 字标;`RootView``.tabViewStyle(.sidebarAdaptable)` + `ConversationView` 可读宽度(native-transfer,待 commit | **iOS UI 字体回退系统原生**(撤 2026-06-28 的 Orbit/Maple;字体条目已改回允许分叉、仅 Fredoka 字标共享)+ **iPad 适配**(TabView 侧边栏自适应 + 消息线程可读宽度;`TARGETED_DEVICE_FAMILY` 本就 1,2)。`just ios-sim-build` 编译过(BUILD SUCCEEDED);iPad 观感待肉眼验 |
+20 -8
View File
@@ -272,22 +272,24 @@ Share Extension 落法(据上):**抓文件 → 写 App Group 容器 →
---
## 11. 品牌与字体系统(2026-06-28,对齐 web
## 11. 品牌 logo 与界面字体(2026-06-28 引入 logo;字体 2026-07-07 改系统原生
iOS 界面引入与 web 一致的品牌资产与字体栈——这是强约束③§5,品牌资产 + 主色单一源)在视觉层的延伸
iOS 引入与 web 一致的**品牌资产(吉祥物 mark + Fredoka 字标)**作共享源(强约束③§5);**界面字体则用系统原生**(见 §11.2,归允许分叉)
### 11.1 品牌 logo
- `Shared/Brand.swift``BrandMark`(吉祥物,资产 `Shared/Brand.xcassets/LogoMark`,源 `web/public/logo-mark.png`)、`Wordmark`(“Commilitia” 主色 + “Drop” `cdropAmber`Fredoka SemiBold)、`BrandLockup``BrandHero(tagline:)`
- 接入点:登录页(`BrandMark` + `Wordmark` 取代纯文字标题)、传输空态(`BrandHero`)、设置页顶部横向品牌头、控制中心控件重设计(吉祥物 + 品牌渐变 + 全宽按钮,最大化空间)。`Theme.swift``Color.cdropAmber`#DB9410 / #F8C637)。
### 11.2 字体栈(Orbit Gothic + Maple Mono + Fredoka
### 11.2 界面字体=系统原生(2026-07-07 定案,撤下自定义字体栈
- 与 web `fonts.css` 同源:Sans = **Orbit Gothic CJK SC**Regular + SemiBold)、Mono = **Maple Mono**、字标 = **Fredoka SemiBold**
- **woff2 运行期注册**Orbit / Maple 以 woff2 内嵌(约 TTF 的 1/3 体积),`UIAppFonts` 历史只认 ttf/otf,故经 `CTFontManagerRegisterFontsForURL` 运行期注册(iOS 13+ Core Text 原生支持 woff2,实测 register→true)——见 `Sources/BrandFonts.swift``BrandFonts.register()``AppDelegate` 启动调)。Fredoka 是 ttf,走 `UIAppFonts`
- **助手 + sweep**`BrandFonts` 暴露 `Font.oBody/.oHeadline/…`Orbit,对位 SF 语义样式)、`Font.maple(…)`Maple Mono);导航 / 标签栏标题经 UIKit appearance 代理换 Orbit7 个视图的 `.font(.语义)``.font(.o语义)` sweep
- **仅主 app**CJK 字体大,不进 Share / 控件扩展(它们保持系统字 + 仅 Fredoka 字标);生僻字超 Orbit 字汇时 iOS 字体级联自动回退苹方,不出豆腐块
- **字体不入库**Orbit Gothic 为专有 / 定制字体(公开仓库不分发),Maple / Fredoka 虽开源也统一不入库保持精简;经 `.gitignore` 排除,构建前 `just ios-fonts`(已 chain 进 `ios-device` / `ios-sim-build`)从私有 CDN / Google Fonts 幂等拉取。详见 `Sources/Fonts/README.md`
> **决策更新(2026-07-07**:移除早先(2026-06-28)引入的 Orbit GothicSans+ Maple MonoMono)自定义 UI 字体栈,**界面统一回退系统原生字体**。**唯一保留的自定义字体是品牌字标 Fredoka**(仅 logo 的 `Wordmark`,见 §11.1
- **系统语义样式**UI 用 `.font(.body)` / `.headline` / `.caption` / … SF 语义样式(随 Dynamic Type 缩放);日志等宽处用 `.system(…, design: .monospaced)` **原生等宽字体**`LogView` 消息行)
- **缘由**CJK 自定义字体体积大(Orbit 两变体 woff2 合计 ~20MB)、须运行期注册,收益不抵成本;系统字体免维护,且 Dynamic Type / 无障碍对比 / 生僻字级联回退最稳
- **落地**:删 `Sources/BrandFonts.swift`(含 woff2 运行期 `CTFontManagerRegisterFontsForURL` 注册 + UIKit 导航 / 标签栏外观代理)+ `AppDelegate``BrandFonts.register()` 调用 + `CDropApp` 全局 `.environment(\.font, .oBody)`9 个视图的 `.font(.o语义)``.font(.语义)` 回退系统;删 `Sources/Fonts/`Orbit / Maple woff2
- **Fredoka 字标(保留)**`Shared/Fonts/Fredoka-SemiBold.ttf`ttf,经各 target `UIAppFonts` 注册),`Wordmark``.font(.custom("Fredoka-SemiBold", size:))`;不入库、`just ios-fonts` 构建前从 Google Fonts 拉。详见 `Shared/Fonts/README.md`
- **对 PARITY 的影响**:字体从「两端同用 Orbit / Maple」回退为「iOS 系统原生 vs web Orbit Gothic」的**允许分叉**;仅品牌 logomark + Fredoka 字标)仍是共享源(强约束③)。
### 11.3 分享引导界面
@@ -301,6 +303,16 @@ iOS 界面引入与 web 一致的品牌资产与字体栈——这是强约束
---
## 13. iPad 适配(2026-07-07
iOS 目标本就 `TARGETED_DEVICE_FAMILY = 1,2`iPhone + iPad 通用),本轮补齐 iPad 的 UI 适配。
- **导航:`TabView``.tabViewStyle(.sidebarAdaptable)` 双形态**`RootView`)——iPad 常规宽度自适应为**侧边栏**Files / Music 式,左标签栏 + 右内容区),iPhone 紧凑宽度仍**底部标签栏**(浮动液态玻璃)。同一 TabView、同一 5 标签,无需为 iPad 另写导航壳;各标签页的 `NavigationStack` 在右侧内容区内推详情。未读红点 `.badge()` 在侧边栏同样呈现。
- **可读宽度**:消息线程(`ConversationView`)气泡列约束 `maxWidth: 680` 并居中,避免在宽内容区横向拉满(呼应 iMessage)。列表类(传输 / 文件 / 设备 / 设置)保留内容区满宽,符合 iPad 原生列表观感(Settings / Mail 式)。sheet(发送预览 / 扫码 / 预览)在 iPad 自动呈 form sheet 居中。
- **验证**`just ios-sim-build`iphonesimulator,通用二进制)编译过(BUILD SUCCEEDED)。iPad 模拟器 / 真机观感待肉眼验。
---
## 附:与桌面端策略对照(修订)
| 维度 | 桌面(Wails | iOSarch A |