diff --git a/Justfile b/Justfile index 4588fd2..a85057e 100644 --- a/Justfile +++ b/Justfile @@ -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 + -# fonttools(python3 -m pip install --user fonttools)。见 ios/CDrop/Sources/Fonts/README.md。 +# 拉取品牌字标字体 Fredoka(logo / Wordmark 用,不入库——虽为开源 OFL 仍统一从 Google Fonts 拉, +# 保持仓库精简)。幂等:已存在则跳过。ios-sim-build / ios-device 构建前已 chain 本步。前置:curl;造 +# Fredoka 静态实例需 python3 + fonttools(python3 -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 变量字体实例化出静态 SemiBold(wght=600, wdth=100)。 diff --git a/ios/CDrop/.gitignore b/ios/CDrop/.gitignore index 0041bb3..ee6c161 100644 --- a/ios/CDrop/.gitignore +++ b/ios/CDrop/.gitignore @@ -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 +# 品牌字标字体 Fredoka(logo / 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 diff --git a/ios/CDrop/Shared/Fonts/README.md b/ios/CDrop/Shared/Fonts/README.md new file mode 100644 index 0000000..f5da22d --- /dev/null +++ b/ios/CDrop/Shared/Fonts/README.md @@ -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 Gothic(Sans)+ Maple Mono(Mono)自定义字体栈,后统一回退系统原生字体 +> 移除(仅保留 Fredoka 字标)。故不再有 `Sources/Fonts/` 与运行期 `CTFontManagerRegisterFontsForURL` 注册。 diff --git a/ios/CDrop/Sources/AppDelegate.swift b/ios/CDrop/Sources/AppDelegate.swift index 63698a0..f478c07 100644 --- a/ios/CDrop/Sources/AppDelegate.swift +++ b/ios/CDrop/Sources/AppDelegate.swift @@ -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 diff --git a/ios/CDrop/Sources/Auth/LoginView.swift b/ios/CDrop/Sources/Auth/LoginView.swift index c1ef588..87bc5d2 100644 --- a/ios/CDrop/Sources/Auth/LoginView.swift +++ b/ios/CDrop/Sources/Auth/LoginView.swift @@ -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) } diff --git a/ios/CDrop/Sources/BrandFonts.swift b/ios/CDrop/Sources/BrandFonts.swift deleted file mode 100644 index 6216729..0000000 --- a/ios/CDrop/Sources/BrandFonts.swift +++ /dev/null @@ -1,81 +0,0 @@ -import CoreText -import SwiftUI -import UIKit - -// 品牌字体接入(对齐 web 字体栈 --font-sans / --font-mono): -// - Sans 正文 = Orbit Gothic CJK SC(Regular 400 + SemiBold 600 两变体) -// - Mono = Maple Mono(拉丁 / 符号) -// 字体以 woff2 内嵌(体积约 TTF 的 1/3;iOS Core Text 自 iOS 13 起原生支持 woff/woff2),故经 -// CTFontManagerRegisterFontsForURL 运行期注册(UIAppFonts 历史上只认 ttf/otf,woff2 走运行期最稳)。 -// 仅主 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 Gothic(Sans)+ Maple Mono(Mono)。relativeTo 让自定义字体随动态字号缩放 -// (不丢可访问性)。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) -} diff --git a/ios/CDrop/Sources/CDropApp.swift b/ios/CDrop/Sources/CDropApp.swift index 6a4724c..941b5e0 100644 --- a/ios/CDrop/Sources/CDropApp.swift +++ b/ios/CDrop/Sources/CDropApp.swift @@ -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 diff --git a/ios/CDrop/Sources/Fonts/README.md b/ios/CDrop/Sources/Fonts/README.md deleted file mode 100644 index 97bbe8d..0000000 --- a/ios/CDrop/Sources/Fonts/README.md +++ /dev/null @@ -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/3;iOS 13+ Core Text 原生支持 woff/woff2)。`UIAppFonts`(Info.plist) -历史上只认 ttf/otf,故 woff2 走**运行期注册** `CTFontManagerRegisterFontsForURL`——见 `Sources/BrandFonts.swift` -的 `BrandFonts.register()`(`AppDelegate` 启动调用)。Fredoka 字标是 ttf,经 `UIAppFonts` 注册。 - -仅**主 app** 接入 Orbit / Maple(CJK 字体大,不进 Share / 控件扩展——它们保持系统字 + 仅 Fredoka 字标)。 -生僻字超出 Orbit Gothic 字汇时由 iOS 字体级联自动回退系统苹方,不出豆腐块。 - -字体助手见 `Sources/BrandFonts.swift`:`Font.oBody/.oHeadline/...`(Orbit,对位 SF 语义样式)、`Font.maple(...)` -(Maple Mono);导航 / 标签栏标题经 UIKit appearance 代理换 Orbit。 diff --git a/ios/CDrop/Sources/Info.plist b/ios/CDrop/Sources/Info.plist index 887a9f3..431a915 100644 --- a/ios/CDrop/Sources/Info.plist +++ b/ios/CDrop/Sources/Info.plist @@ -70,5 +70,18 @@ UILaunchScreen + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + diff --git a/ios/CDrop/Sources/LogView.swift b/ios/CDrop/Sources/LogView.swift index 92c852f..b173800 100644 --- a/ios/CDrop/Sources/LogView.swift +++ b/ios/CDrop/Sources/LogView.swift @@ -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() } diff --git a/ios/CDrop/Sources/MessagesView.swift b/ios/CDrop/Sources/MessagesView.swift index b7e022c..279876d 100644 --- a/ios/CDrop/Sources/MessagesView.swift +++ b/ios/CDrop/Sources/MessagesView.swift @@ -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() diff --git a/ios/CDrop/Sources/NotificationBanner.swift b/ios/CDrop/Sources/NotificationBanner.swift index 5bc775c..ddd975b 100644 --- a/ios/CDrop/Sources/NotificationBanner.swift +++ b/ios/CDrop/Sources/NotificationBanner.swift @@ -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) diff --git a/ios/CDrop/Sources/RootView.swift b/ios/CDrop/Sources/RootView.swift index fa82e24..37f641a 100644 --- a/ios/CDrop/Sources/RootView.swift +++ b/ios/CDrop/Sources/RootView.swift @@ -60,6 +60,10 @@ struct RootView: View } } } + // iPad 适配:常规宽度(iPad)自适应为侧边栏(Files / 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) } } diff --git a/ios/CDrop/Sources/ScannerView.swift b/ios/CDrop/Sources/ScannerView.swift index ccbd7a1..8f16852 100644 --- a/ios/CDrop/Sources/ScannerView.swift +++ b/ios/CDrop/Sources/ScannerView.swift @@ -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) diff --git a/ios/CDrop/Sources/SendComposeSheet.swift b/ios/CDrop/Sources/SendComposeSheet.swift index 4e28eba..e987134 100644 --- a/ios/CDrop/Sources/SendComposeSheet.swift +++ b/ios/CDrop/Sources/SendComposeSheet.swift @@ -40,7 +40,7 @@ struct SendComposeSheet: View .lineLimit(1) Spacer() Text(formatBytes(file.size)) - .font(.oCaption) + .font(.caption) .foregroundStyle(.secondary) } } diff --git a/ios/CDrop/project.yml b/ios/CDrop/project.yml index a425197..9b76237 100644 --- a/ios/CDrop/project.yml +++ b/ios/CDrop/project.yml @@ -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: diff --git a/ios/PARITY.md b/ios/PARITY.md index 13fb6a1..3f96e9d 100644 --- a/ios/PARITY.md +++ b/ios/PARITY.md @@ -30,8 +30,8 @@ ### 4. 允许分叉登记(明确 OK 不一致) - **视觉材质**:液态玻璃 vs Theme B。 -- **导航外壳**:原生 `NavigationStack` / `TabView` vs web 布局。 -- **字体 / 排版**(2026-06-28 更新,字族已收敛):两端现**同用** Orbit Gothic(Sans)+ Maple Mono(Mono)+ 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;日志等宽用原生 monospaced)vs 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.ts:transfers 推送节流 ~3Hz + toWire 加 ice 摘要(未提交,**待部署**) | EngineController 手动容忍解析 + ICE 字段;RootView 详情页 ICE 行 + 收到文件视图 + 设备名编辑 + 设备计数;DownloadManager listFiles/delete;新 DeviceNameStore / QuickLookPreview;project.yml 加文件共享键 + 品牌化权限串(未提交) | **首轮真机测试反馈修复**:① 速度数字 ~10Hz 闪 → 节流活跃传输推送到 3Hz(iOS-only,亦降 P2P 接收主线程抖动);② 设备列表空 → 原生改手动容忍解析(严格 JSONDecoder 遇 bool/int 失配整组失败的隐患),加「已知设备」计数诊断;③ 收到文件无处开 / Files 不可见 / 阻塞发送 → `UIFileSharingEnabled`+`LSSupportsOpeningDocumentsInPlace`(Documents 已是落地目录)+ 应用内「收到的文件」视图(QuickLook 预览 / 系统分享 / 转发在线设备 / 删除);④ 设备名不可改 → `DeviceNameStore`(UserDefaults)+ 设置页编辑框(后端令牌烤名、改名下次登录生效);⑤ P2P <100KB/s(relay 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`(本提交) | **计划完成(I3–I7)**:发送端流式(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 binary(APNs 端点 + 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 观感待肉眼验 | diff --git a/ios/PLAN.md b/ios/PLAN.md index e3f7a68..89921ce 100644 --- a/ios/PLAN.md +++ b/ios/PLAN.md @@ -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 代理换 Orbit;7 个视图的 `.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 Gothic(Sans)+ Maple Mono(Mono)自定义 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」的**允许分叉**;仅品牌 logo(mark + 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) | iOS(arch A) |