From 349d94aa8f8ba7da17b8a307586df8a607cbf8a8 Mon Sep 17 00:00:00 2001 From: commilitia Date: Sun, 28 Jun 2026 19:33:08 +0800 Subject: [PATCH] =?UTF-8?q?iOS=20=E5=8E=9F=E7=94=9F=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=EF=BC=9Alibwebrtc=20=E6=95=B0=E6=8D=AE=E9=9D=A2=20+?= =?UTF-8?q?=20UX=20=E6=94=B9=E9=80=A0=20+=20=E5=93=81=E7=89=8C/=E5=AD=97?= =?UTF-8?q?=E4=BD=93=20+=20=E5=85=A8=E5=B9=B3=E5=8F=B0=E5=B9=B3=E5=9D=87?= =?UTF-8?q?=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 原生数据面 libwebrtc(PLAN 决策 F):新增 Engine/LibWebRtcEngine.swift(RTCPeerConnection,端口 engine.go/session.go 线协议 + 水位背压 + ack 完成 + 不冲突落盘);NativeTransfer.swift 的 P2PEventsBridge 改 conform P2PEngineDelegate(去 gomobile);EngineController 4 个 p2p RPC 改调新引擎(completion-based + resolveOnMain)。stasel/WebRTC M149 经 SPM(exactVersion 149.0.0)。环回端到端 20MB 逐字节测过(Tests/) - UX 改造(PARITY 残余项 1/2):新增 SendComposeSheet(发送预览=文件名 + 选设备,手动多选与分享共用);分享直入预览 + scenePhase .active 兜底(修“分享后必须重启 app”);重写 MessagesView(按设备会话 + 每设备未读红点 + 删单条 / 删整设备历史);EngineController 加 conversations / unread 模型 - 品牌 logo:新增 Shared/Brand.swift(BrandMark / Wordmark / BrandLockup / BrandHero)+ Brand.xcassets/LogoMark;登录页 / 传输空态 / 设置头 / 控制中心控件接 logo;Theme 加 cdropAmber - 字体(对齐 web,仅主 app):新增 BrandFonts.swift(Orbit Gothic + Maple Mono woff2 运行期 CTFontManager 注册 + UIKit 外观代理 + .oXxx / .maple 助手);Fredoka SemiBold 字标;7 视图 .font sweep。字体不入库——.gitignore 排除 + just ios-fonts 构建前从 CDN / Google Fonts 拉取,见 Sources/Fonts/README.md - 分享引导:新增 Share/ShareGuideView(吉祥物卡 + “打开 Commilitia Drop”);ShareViewController 健壮 staging(文件 / 数据双路)+ 响应链 IMP 唤起宿主 - 全平台平均速度:传输完成态显平均速度(总字节 / 总耗时)而非末次瞬时——web store/slices/transfer.ts + TransferRow.tsx + iOS RootView 卡片 / 详情;i18n 加 transfer.avgRate - 工程:project.yml 接 SPM WebRTC + CDropTests + Fredoka UIAppFonts + Share i18n;Justfile 加 ios-sim-build + ios-fonts / ios-fonts-fredoka 配方 - 文档:ios/PLAN.md(决策 F + §10 UX + §11 品牌/字体 + §12 平均速度)、ios/PARITY.md(字体分叉条目更新 + 2026-06-28 漂移行) --- Justfile | 27 +- ios/CDrop/.gitignore | 11 + ios/CDrop/Share/Info.plist | 4 + ios/CDrop/Share/ShareGuideView.swift | 70 ++ ios/CDrop/Share/ShareViewController.swift | 159 ++- ios/CDrop/Shared/Brand.swift | 76 ++ ios/CDrop/Shared/Brand.xcassets/Contents.json | 6 + .../LogoMark.imageset/Contents.json | 12 + .../LogoMark.imageset/logo-mark.png | Bin 0 -> 268962 bytes ios/CDrop/Shared/Theme.swift | 8 + ios/CDrop/Sources/AppDelegate.swift | 2 + ios/CDrop/Sources/Auth/LoginView.swift | 16 +- ios/CDrop/Sources/BrandFonts.swift | 81 ++ ios/CDrop/Sources/CDropApp.swift | 11 +- .../Sources/Engine/EngineController.swift | 237 ++++- .../Sources/Engine/LibWebRtcEngine.swift | 905 ++++++++++++++++++ ios/CDrop/Sources/Engine/NativeTransfer.swift | 74 ++ ios/CDrop/Sources/Fonts/README.md | 40 + ios/CDrop/Sources/Info.plist | 4 + ios/CDrop/Sources/LogView.swift | 78 +- ios/CDrop/Sources/MessagesView.swift | 262 +++-- ios/CDrop/Sources/NotificationBanner.swift | 4 +- ios/CDrop/Sources/Resources/i18n/en-US.json | 17 +- ios/CDrop/Sources/Resources/i18n/zh-CN.json | 17 +- ios/CDrop/Sources/Resources/i18n/zh-TW.json | 17 +- ios/CDrop/Sources/RootView.swift | 169 ++-- ios/CDrop/Sources/ScannerView.swift | 4 +- ios/CDrop/Sources/SendComposeSheet.swift | 108 +++ ios/CDrop/Tests/LibWebRtcEngineTests.swift | 140 +++ ios/CDrop/Widgets/CloudClipboardWidget.swift | 66 +- ios/CDrop/project.yml | 41 + ios/PARITY.md | 4 +- ios/PLAN.md | 66 ++ web/src/features/transfer/TransferRow.tsx | 11 +- web/src/features/transfer/transfer.ts | 54 +- web/src/i18n/locales/en-US.ts | 5 +- web/src/i18n/locales/zh-CN.ts | 5 +- web/src/i18n/locales/zh-TW.ts | 5 +- web/src/store/slices/transfer.ts | 14 +- 39 files changed, 2535 insertions(+), 295 deletions(-) create mode 100644 ios/CDrop/Share/ShareGuideView.swift create mode 100644 ios/CDrop/Shared/Brand.swift create mode 100644 ios/CDrop/Shared/Brand.xcassets/Contents.json create mode 100644 ios/CDrop/Shared/Brand.xcassets/LogoMark.imageset/Contents.json create mode 100644 ios/CDrop/Shared/Brand.xcassets/LogoMark.imageset/logo-mark.png create mode 100644 ios/CDrop/Sources/BrandFonts.swift create mode 100644 ios/CDrop/Sources/Engine/LibWebRtcEngine.swift create mode 100644 ios/CDrop/Sources/Engine/NativeTransfer.swift create mode 100644 ios/CDrop/Sources/Fonts/README.md create mode 100644 ios/CDrop/Sources/SendComposeSheet.swift create mode 100644 ios/CDrop/Tests/LibWebRtcEngineTests.swift diff --git a/Justfile b/Justfile index e62d26b..4588fd2 100644 --- a/Justfile +++ b/Justfile @@ -77,13 +77,38 @@ 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。 +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 + [ -f ios/CDrop/Shared/Fonts/Fredoka-SemiBold.ttf ] || just ios-fonts-fredoka + +# Fredoka 字标:从 Google Fonts 变量字体实例化出静态 SemiBold(wght=600, wdth=100)。 +ios-fonts-fredoka: + curl -fsSL "https://raw.githubusercontent.com/google/fonts/main/ofl/fredoka/Fredoka%5Bwdth%2Cwght%5D.ttf" -o /tmp/Fredoka-VF.ttf + python3 -m fontTools.varLib.instancer /tmp/Fredoka-VF.ttf wght=600 wdth=100 -o ios/CDrop/Shared/Fonts/Fredoka-SemiBold.ttf --update-name-table + rm -f /tmp/Fredoka-VF.ttf + +# 模拟器编译验证(无需签名 / Apple 账号):拉字体 + xcodegen + xcodebuild iphonesimulator。原生数据面 +# 引擎走 SPM 的 libwebrtc(首次构建自动拉取 WebRTC.xcframework),无需预构建步骤。用于改动自检。 +ios-sim-build: + just ios-fonts + cd ios/CDrop && xcodegen generate + cd ios/CDrop && xcodebuild -project CDrop.xcodeproj -scheme CDrop -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build/DD build + # 真机构建 + 装机(自动 provisioning,经 ASC API Key)。前置:付费 ADP;App ID 的 App Groups / # Push 能力(门户建好或自动建);根目录 gitignore 的 .env 配齐:CDROP_TEAM_ID / CDROP_ASC_KEY_PATH # (.p8 路径)/ CDROP_ASC_KEY_ID / CDROP_ASC_ISSUER_ID。-allowProvisioningUpdates 自动登记连接的 -# 设备 + 建 profile(绕过手动 profile 的设备选择坑)。模拟器构建不需这些(base ad-hoc)。 +# 设备 + 建 profile(绕过手动 profile 的设备选择坑)。模拟器构建不需这些(base ad-hoc)。原生数据面 +# 经 SPM 拉 libwebrtc,无 gomobile 预构建步骤(早先 ios-engine recipe 已随 pion-on-iOS 废弃移除)。 # 用法:just ios-device <设备UDID>(UDID 见 just ios-devices)。 ios-device udid: [ -n "$CDROP_TEAM_ID" ] && [ -n "$CDROP_ASC_KEY_PATH" ] && [ -n "$CDROP_ASC_KEY_ID" ] && [ -n "$CDROP_ASC_ISSUER_ID" ] || { echo "缺 .env(CDROP_TEAM_ID / CDROP_ASC_KEY_PATH / CDROP_ASC_KEY_ID / CDROP_ASC_ISSUER_ID)——见 ios/CDrop/REALDEVICE.md"; exit 1; } + just ios-fonts cd ios/CDrop && xcodegen generate cd ios/CDrop && xcodebuild -project CDrop.xcodeproj -scheme CDrop -configuration Debug -destination "platform=iOS,id={{udid}}" -derivedDataPath build/DD-device -allowProvisioningUpdates -authenticationKeyPath "$CDROP_ASC_KEY_PATH" -authenticationKeyID "$CDROP_ASC_KEY_ID" -authenticationKeyIssuerID "$CDROP_ASC_ISSUER_ID" DEVELOPMENT_TEAM="$CDROP_TEAM_ID" CODE_SIGN_STYLE=Automatic CODE_SIGN_IDENTITY="Apple Development" build xcrun devicectl device install app --device {{udid}} ios/CDrop/build/DD-device/Build/Products/Debug-iphoneos/CDrop.app diff --git a/ios/CDrop/.gitignore b/ios/CDrop/.gitignore index 13effee..0041bb3 100644 --- a/ios/CDrop/.gitignore +++ b/ios/CDrop/.gitignore @@ -7,6 +7,17 @@ DerivedData/ *.xcuserstate .DS_Store +# 原生数据面引擎(曾是 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 +Shared/Fonts/*.ttf +Shared/Fonts/*.woff2 + # 真机签名的账号凭据——公开仓库不留账号标识。ASC API Key 的 .p8 + Team/Key/Issuer ID # 走根目录 gitignore 的 .env(见 REALDEVICE.md);.p8 若落本目录一并忽略。 *.p8 diff --git a/ios/CDrop/Share/Info.plist b/ios/CDrop/Share/Info.plist index 88e8e68..6e73c41 100644 --- a/ios/CDrop/Share/Info.plist +++ b/ios/CDrop/Share/Info.plist @@ -39,5 +39,9 @@ NSExtensionPrincipalClass $(PRODUCT_MODULE_NAME).ShareViewController + UIAppFonts + + Fredoka-SemiBold.ttf + diff --git a/ios/CDrop/Share/ShareGuideView.swift b/ios/CDrop/Share/ShareGuideView.swift new file mode 100644 index 0000000..4290e17 --- /dev/null +++ b/ios/CDrop/Share/ShareGuideView.swift @@ -0,0 +1,70 @@ +import SwiftUI + +// 分享引导界面的状态:文件数(nil=仍在准备)。@MainActor,UI 线程更新。 +@MainActor +final class ShareModel: ObservableObject +{ + @Published var fileCount: Int? +} + +// 分享扩展引导界面(取代「白窗一闪即关」):吉祥物 + 字标 + 已就绪文件数 + 醒目「打开 Commilitia +// Drop」按钮——iOS 不可靠地允许扩展自动拉起宿主 app,故改为明确引导用户回到 app(点按尝试唤起;即便 +// 唤起失败,文件已落 App Group,回到 app 即弹发送预览)。吉祥物抱着飞散的纸恰好呼应「文件已接住、待 drop」。 +struct ShareGuideView: View +{ + @ObservedObject var model: ShareModel + let onOpen: () -> Void + let onCancel: () -> Void + + var body: some View + { + VStack(spacing: 18) + { + Spacer() + ZStack + { + Circle() + .fill(Color.cdropAccent.opacity(0.10)) + .frame(width: 168, height: 168) + BrandMark(size: 112) + } + Wordmark(size: 24) + VStack(spacing: 6) + { + Text(titleText) + .font(.headline) + Text(t("ios.share.guide.hint")) + .font(.subheadline) + .foregroundStyle(.secondary) + } + .multilineTextAlignment(.center) + Spacer() + VStack(spacing: 12) + { + Button(action: onOpen) + { + Text(t("ios.share.guide.open")) + .fontWeight(.semibold) + .frame(maxWidth: .infinity) + } + .buttonStyle(.borderedProminent) + .controlSize(.large) + .tint(.cdropAccent) + .disabled((model.fileCount ?? 0) == 0) + + Button(t("common.cancel"), role: .cancel, action: onCancel) + .controlSize(.large) + } + } + .padding(28) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(.systemBackground)) + } + + private var titleText: String + { + guard let n = model.fileCount else { return t("ios.share.guide.preparing") } + if n == 0 { return t("ios.share.guide.empty") } + return t("ios.share.guide.ready", [ "n": String(n) ]) + } +} diff --git a/ios/CDrop/Share/ShareViewController.swift b/ios/CDrop/Share/ShareViewController.swift index 13a4b80..66fdd08 100644 --- a/ios/CDrop/Share/ShareViewController.swift +++ b/ios/CDrop/Share/ShareViewController.swift @@ -1,76 +1,173 @@ +import SwiftUI import UIKit import UniformTypeIdentifiers -// Share Extension 主控制器:把 share sheet 传入的文件拷进 App Group 收件箱,再深链唤起主 app -// 选设备发送(扩展只交接、不跑引擎,避 120MB jetsam,见 PLAN §I5)。无 compose UI——即起即办、 -// 处理完即返回。 +// Share Extension 主控制器:把 share sheet 传入的文件拷进 App Group 收件箱,再以**品牌引导界面** +// (ShareGuideView:吉祥物 + 字标 + 已就绪文件数 + 「打开 Commilitia Drop」按钮)引导用户回到主 app +// 选设备发送(扩展只交接、不跑引擎,避 120MB jetsam,见 PLAN §I5)。 +// +// 为何是「引导」而非自动跳:iOS 不官方支持、也不可靠地允许 share 扩展自动拉起宿主 app(旧单参 +// openURL: 自 iOS 10 弃用、iOS 18+ 已成空壳)。故改为明确的用户操作:点「打开」尝试唤起(响应链经 +// IMP 调现代 open,最大努力)。无论唤起成败,文件已落 App Group 收件箱,用户回到 app 时 +// CDropApp.scenePhase(.active)→loadPendingShares 即弹发送预览,故分享始终能用、不丢文件。 final class ShareViewController: UIViewController { + private let model = ShareModel() + private var started = false + // 已暂存文件的目标 URL(取消时据此删掉收件箱里的它们,不残留)。 + private var stagedURLs: [URL] = [] + + override func viewDidLoad() + { + super.viewDidLoad() + let host = UIHostingController( + rootView: ShareGuideView( + model: model, + onOpen: { [weak self] in self?.openAndFinish() }, + onCancel: { [weak self] in self?.cancelAndFinish() })) + addChild(host) + host.view.frame = view.bounds + host.view.autoresizingMask = [ .flexibleWidth, .flexibleHeight ] + view.addSubview(host.view) + host.didMove(toParent: self) + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - Task { await processAndClose() } + guard !started else { return } // viewDidAppear 可能多次;只处理一次 + started = true + Task { await stageAll() } } - private func processAndClose() async + private func stageAll() async { let items = (extensionContext?.inputItems as? [NSExtensionItem]) ?? [] - var staged = 0 + var urls: [URL] = [] for item in items { for provider in item.attachments ?? [] { - if await stage(provider) { staged += 1 } + if let dest = await stage(provider) { urls.append(dest) } } } - let ctx = extensionContext - guard staged > 0, let url = URL(string: "cdrop://share") else - { - ctx?.completeRequest(returningItems: nil) - return - } - // 先唤起主 app,等 open 回调拿到结果再 completeRequest——否则立即 completeRequest 会拆掉 - // 扩展、self 被释放,open 失败时的 openViaResponder 兜底永不触发(审查 L8)。捕获 ctx 使 - // 扩展存活到回调。 - ctx?.open(url) - { [weak self] ok in - if !ok { self?.openViaResponder(url) } - ctx?.completeRequest(returningItems: nil) + stagedURLs = urls + await MainActor.run { model.fileCount = urls.count } + } + + // 「打开 Commilitia Drop」:尝试唤起主 app(最大努力),延后 completeRequest 给系统留出拉起窗口 + // (立即 completeRequest 会拆掉扩展进程、可能取消尚未真正开始的唤起)。文件留在收件箱供主 app 接手。 + private func openAndFinish() + { + if let url = URL(string: "cdrop://share") { openViaResponder(url) } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) + { [weak self] in + self?.extensionContext?.completeRequest(returningItems: nil) } } - // 把单个附件落到 App Group 收件箱。用 loadFileRepresentation 取系统给的临时文件表示 - // (文件 / 图片 / 文档统一走此路,且大文件不整体进内存),在回调内同步拷到收件箱 - // (系统会在回调返回后删临时文件)。 - private func stage(_ provider: NSItemProvider) async -> Bool + // 「取消」:删掉刚暂存进收件箱的文件(不残留、避免下次开 app 误弹预览),返回来源 app。 + private func cancelAndFinish() { + for url in stagedURLs + { + try? FileManager.default.removeItem(at: url.deletingLastPathComponent()) + } + extensionContext?.completeRequest(returningItems: nil) + } + + // 把单个附件落到 App Group 收件箱,返回落地 URL。优先文件表示(大文件不整体进内存),失败退数据表示 + // (仅提供数据、无文件表示的提供者)。两路都失败返回 nil。 + private func stage(_ provider: NSItemProvider) async -> URL? + { + if let u = await stageFile(provider) { return u } + return await stageData(provider) + } + + private func stageFile(_ provider: NSItemProvider) async -> URL? + { + let typeId = provider.registeredTypeIdentifiers.first ?? UTType.item.identifier return await withCheckedContinuation { cont in - provider.loadFileRepresentation(forTypeIdentifier: UTType.item.identifier) + provider.loadFileRepresentation(forTypeIdentifier: typeId) { url, _ in guard let url, let dest = AppGroup.stagedDestination(originalName: url.lastPathComponent) - else { cont.resume(returning: false); return } + else { cont.resume(returning: nil); return } do { try FileManager.default.copyItem(at: url, to: dest) - cont.resume(returning: true) + cont.resume(returning: dest) } catch { - cont.resume(returning: false) + cont.resume(returning: nil) } } } } - // open 失败时的兜底:沿响应链找 UIApplication 唤起主 app(个别系统版本扩展直接 open 不生效)。 + private func stageData(_ provider: NSItemProvider) async -> URL? + { + let typeId = provider.registeredTypeIdentifiers.first ?? UTType.data.identifier + let name = suggestedName(provider, typeId: typeId) + return await withCheckedContinuation + { cont in + provider.loadDataRepresentation(forTypeIdentifier: typeId) + { data, _ in + guard let data, let dest = AppGroup.stagedDestination(originalName: name) + else { cont.resume(returning: nil); return } + do + { + try data.write(to: dest) + cont.resume(returning: dest) + } + catch + { + cont.resume(returning: nil) + } + } + } + } + + // 数据兜底路径的文件名:优先提供者建议名,否则按类型首选扩展名拼一个。 + private func suggestedName(_ provider: NSItemProvider, typeId: String) -> String + { + if let s = provider.suggestedName, !s.isEmpty + { + if (s as NSString).pathExtension.isEmpty, + let ext = UTType(typeId)?.preferredFilenameExtension + { + return "\(s).\(ext)" + } + return s + } + let ext = UTType(typeId)?.preferredFilenameExtension ?? "dat" + return "shared-\(UUID().uuidString.prefix(8)).\(ext)" + } + + // 沿响应链找 UIApplication 实例,经 IMP 调现代 open(_:options:completionHandler:)(绕过扩展对 + // UIApplication 的编译期可用性限制)。找不到则退旧单参 openURL:(iOS 18+ 多为空壳,仅历史兜底)。 private func openViaResponder(_ url: URL) { + let modern = NSSelectorFromString("openURL:options:completionHandler:") var responder: UIResponder? = self - let sel = NSSelectorFromString("openURL:") while let r = responder { - if r.responds(to: sel) { _ = r.perform(sel, with: url); return } + if r.responds(to: modern), let app = r as? NSObject, + let method = class_getInstanceMethod(type(of: app), modern) + { + typealias OpenFn = @convention(c) (NSObject, Selector, NSURL, NSDictionary, Any?) -> Void + let fn = unsafeBitCast(method_getImplementation(method), to: OpenFn.self) + fn(app, modern, url as NSURL, NSDictionary(), nil) + return + } + responder = r.next + } + let legacy = NSSelectorFromString("openURL:") + responder = self + while let r = responder + { + if r.responds(to: legacy) { _ = r.perform(legacy, with: url); return } responder = r.next } } diff --git a/ios/CDrop/Shared/Brand.swift b/ios/CDrop/Shared/Brand.swift new file mode 100644 index 0000000..fe7154d --- /dev/null +++ b/ios/CDrop/Shared/Brand.swift @@ -0,0 +1,76 @@ +import SwiftUI + +// 品牌视觉单一真源(吉祥物 mark + 文字字标),供主 app / 分享扩展 / 小组件共用(三者 sources 均含 +// Shared)。资产 LogoMark 在 Shared/Brand.xcassets,随各 target bundle 编译,故 Image("LogoMark") 在 +// 各自 bundle 内解析得到。字标双色对齐 web 品牌资产:Commilitia=主前景、Drop=品牌琥珀(cdropAmber); +// 品牌名是专有名词、不本地化。圆体(system rounded)呼应 web 的 Fredoka。 + +// 吉祥物 mark(透明背景,抱着飞散的纸=「drop」文件)。size 控制边长。 +struct BrandMark: View +{ + var size: CGFloat = 96 + + var body: some View + { + Image("LogoMark") + .resizable() + .scaledToFit() + .frame(width: size, height: size) + .accessibilityHidden(true) + } +} + +// 文字字标「Commilitia Drop」:Drop 用品牌琥珀,与 mark 成对呈现(品牌规范:不以纯文字呈现品牌)。 +struct Wordmark: View +{ + var size: CGFloat = 22 + + var body: some View + { + ( + Text("Commilitia ").foregroundColor(.primary) + + Text("Drop").foregroundColor(.cdropAmber) + ) + // 品牌字标用 Fredoka SemiBold(与 web 品牌资产一致,font 经 UIAppFonts 注册;见 Shared/Fonts)。 + .font(.custom("Fredoka-SemiBold", size: size)) + .accessibilityLabel("Commilitia Drop") + } +} + +// 横排锁版:mark + 字标(设置页头等)。 +struct BrandLockup: View +{ + var markSize: CGFloat = 30 + var wordSize: CGFloat = 20 + + var body: some View + { + HStack(spacing: 10) + { + BrandMark(size: markSize) + Wordmark(size: wordSize) + } + } +} + +// 品牌空态 hero(主页 / 列表空时):大 mark + 字标 + 一句副文案(i18n 由调用方传入)。取代单调的 +// SF Symbol 空态——首屏即见吉祥物,强化品牌;mark 抱纸的姿态恰好呼应「等你来 drop 文件」。 +struct BrandHero: View +{ + var tagline: String + + var body: some View + { + VStack(spacing: 16) + { + BrandMark(size: 132) + Wordmark(size: 26) + Text(tagline) + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding() + } +} diff --git a/ios/CDrop/Shared/Brand.xcassets/Contents.json b/ios/CDrop/Shared/Brand.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios/CDrop/Shared/Brand.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/CDrop/Shared/Brand.xcassets/LogoMark.imageset/Contents.json b/ios/CDrop/Shared/Brand.xcassets/LogoMark.imageset/Contents.json new file mode 100644 index 0000000..88c8e0f --- /dev/null +++ b/ios/CDrop/Shared/Brand.xcassets/LogoMark.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "logo-mark.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/CDrop/Shared/Brand.xcassets/LogoMark.imageset/logo-mark.png b/ios/CDrop/Shared/Brand.xcassets/LogoMark.imageset/logo-mark.png new file mode 100644 index 0000000000000000000000000000000000000000..c6093ce6c44e0e816287a774eef51a08b2789718 GIT binary patch literal 268962 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 z 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 }ftQphg{eRNsaRS(7S6*rVeDMWa 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 z zq610a9H)NX0CaIBP?Q)?c?ogoXPMw7+K)vEet=&6yMnRmU14-%ZtxDemIz}`tjZKw zs;VohgM6_%!2wNvu(~xMuniqL6-C9Rm4fS*jnwL_Qd z!eCJh&3tS+z4PXKln1*J+K?3&W{er|b9wlQ%}W4!KKVI9!ExTDVje~5Ag?;=l?t)>Om1mW+Oz|KKb?twA#gxrKMSze>?BF zKm8Oe>oAvcm@{XNL2*&xb9SZ%T0=WKLw!jjS$nxb*MJ#)_2UUT5_*j+t*zI-@!m)4 z)n)!QzF_Df_~QLVZx!X`Jo&)L{xtXbN1)6MmK{M7zaxj}0FYFM)BmphC(%6^E7|DI z?dHSMSKeNZ3x{``LGe1e}}hbiWkR?8qSTmQxSGc^6( zeRMVw*dqr2sc~JXi=Pvy0EN>IYzY|KQZ=ywHN>Q*uC#P^8K!S)vKE>LYZ>!DdH4i9 zH}zSXKKU6sdgwQhh>%(rOrToK;m+HFJiA1u@GCoo@QA=W_@gA|=dGL)N3(n-4TmW| zCG1Z=kzFI08qMtO!7K$k3&@Vie7RR5s07pi1qoo_s>RnJP!;^hlVj$V;;?q<&OL{}QD@ z0{#q?ngB2E15sJc^namWs+zh0l&uQldT9RCvHrYI$ydoqysJt^CD1E)l|q#8=1)1X zW-$BlV*n3UcN`*&r4HrHR1V|JNHyk`r0Q7tCMQNbu8we`5>hxb>~`(ektRJfmZnS@ zM-M(QlzMdUNLFCr+pzX$sz-S>*uy&cwV~OFQ;jly+<%mYb@L)G=zlK)e@{V93CPt} zqzg5)ZUN39EC5kd4Y+1KP`c7b_^x~rVca+7rnLUoFj~Fo6d9WsrdvAMKC}DC*&E3B zA%7ei41FX02X}EI4?9CL#^K7$)q!?_)V%VS6J%&=a>lEJ&uf1**FPVMwtBd|nwOpR zMqrnA^wGO>NKXrt6qU@s+~4Y+mpecXSNeiq(I3X9#?W_g8TwVOlZ&%GSwJ}&Bk!s{ z6Y=D<&Hd<2rr?;F>QguH{hYg$1S@@*D~!C0v5-e!^t2svrFWJer*dF%AI*CJcRYHAnVA{i^bGLr+*&!8qN8IT%glb-#MWVq zj)Bo{tZ|KxC4cNYLeD+?JUun(Svqv!531LKw15E^m~@y-$x}?eBjiacW}NubtQgf+ z(JZ{B*XD7X=hy7Ld305l98WW@re77Q6^~|I6ae6>k4@Xuo>ldNOv{@cHuKDgi&O=U zZgE=XocPp?dwn)2^wRtp^eRqUp_h|s_FLPaeG5nc_o=9_M&00LeF=kmx|6SmJ$M1a zut1SNFMO3_KYr+A*4XF(FK4%bR5mpe=DEhR|$hH%l5@>9f?;<9m@MphOD4sR)h$3%9VhROR({#T7X z$6_bP7UA$?K$TOS_y$H@Obq<6Y4a1~dicn3ItML)Y*dQPa=$ov9JBzM)9#Rqn2Wje z_+Z!|Xsn}aAT_(VSc1Vzk6gj@X8;92g6WToWgsGc;zeCqQE8sIJfkE;K%0Tz>Ff0V zSNoVsR&4KRKl9wB*vp(!(=oX{Kd)$xnURj}L;XC-$l8YNe7#|9FooX!axeB5?R0k^ zujdXNIGL-N?q2(sEn8-=ewFiwSs9s&JNWzPezjx)*;pEY8z009F}BT1ZJ8vBvzehKJN=z%Dl+0ow4k$j0 zBIOM5P^}GleDDypQ-ghVpg?o9?=1cZOkn!P8HovB zJ~nBRhw9^h$OC}X5FQ`D)5X(!go%aaVyOAgTfH47c;~L8=-706d~_E+UAX3D=WgiT zp+mdYsu|4rT)%$U`{75%>zG+i(b6|OrH>7ZH-wXikJAf}&8DX&KSu|F6jaxNk;sq{ z0j3Bjj%SKITa+Nxv)OD{h2PB6T3&J*RhEi}pUO`&U*)@I*lqenQq%);-@?UJB6FgR ze5cMF+nEs;fVpGi7ORINV+lfukPa+|y%{`@=FWYF4xf#q7eNWgEPyR;FxYTZsYblP zf&&*626HfRJ~E~oor4bcn7B;b*Fr_JZ~)0le0ca@Elvt7k?_R0KJ@17@nmHUk}?$f z=wc!7UbmU1j-5hW%E@n$ODmb*qhKyvIH_`sPzlf{Kb5cQcHo2f#_`l!A~Wr2J2i6uSVa zm^832^=;=%Ifccv8mIpeumIWtOscW5IY1t@$;!rz96%{Fv0_W2k~9(LEN%SAQ-J^+ z*u+U4qXmxtnCrRp>ifUq%CWZA+QxqV`RMqAO`^&#-+%e#@RHKf;lnyRQ70(Dvh-~W zKvzQlhO|IlVMT?#qw|~-p<$6NrM*{S!}|5}{__1r>+`d-7xeDYQRlm5Z;^+yB^bPA z2}RX-->gI)7N>;j0AIAWJFKf0aCx5a`^;*mY>{ z4az|yBsH>tECPu~|EmD|P3zY5BO_<*_(rJ|06n*$9MeM!JVk~XR<5qu*u%23VFakK z6#JEOT_NTh#1gT0@WH16`xDH^i+r%pHMzk9+zW%!UT1v_`|K33(b55fJm4CyVAp)STp+{+rc6`6gzg@*+Q53e$|z zynN-`otCYn<0qC9+HMSF(7?h{0Bvp~O9E8nWKm|!WqDIZ*#fZQD}cJ5Ry{U_*J;)Z zPtk$XSLubfw}UIXl5~WD6JXq;2rO-oXd6K_m^h*x7N-GiJ#Y^9v_Jq<8wX;l{8D}R zN`Or<4}dkfx1Jvdi-I1wqJhk=jvf_f<J^m+7(%iia^SW8txvyIrYU|B-pc7g7wWoYoIej_!7rGLk)nH|7voI{? z+Ro@*G_(5s00wj7~SYNkOvPB%^zNBfyBEy*^#Y{HC>2E1wzMs zcLLqSLy0Wss2U57f^Ynpo#qqayoL&jD!>HD{6)IJEgCX>AAPhKqJH2Dtbu|t4wvo# zM9na#>Z9OJ8ch7FWhJG39G4_ z{qid`V*CUu&dVJge&YD&TefUbSa+L7{13c^MMg%(WMs~EaP;m62EX|^XC4H;okRhY>t!ySX8W4h@%FHw$)h?!-$DfR0_1^oD^melY7*MX?s) z(y1MOg;X!%Fk*%N6a&pEir zRyttXFn{Vfye~mEfnIxOJ%yZ)BO80WkE0V)zX3Iq3%F|+;N#P`t&7W-oqT*o_3hNj zFEAh=(BIYdF)v%2HBlEsLh|x*KWp#j<@xQ>h4j%o&ygj>cgrE}r{;OMy`tG3iiMR0 zs45lo;_IK$hHVGP+TJ!6m9KYlv?YIE53x2yV^l5TLE@jL(|l)@yWlqhMa9eAiK1fC zp>7@XLd+-4G@POIYnnfTMbGnGRTV1cOlnRkl>^vA*;u}{m0gbWbD8zvWlFEXHF0`= z84v*ZK!WOUjo3Ct9RNSCKmGxUXoM!mGR<1F(%iUJDKm{(9%movy|CIlg+BxjGhB z$M2*jpD3=*xCi<>ynD~({rE<>E=Km2R~FFyME%kSu^*EZ7f;7QcM-vL@0 zpcRfWY`y@*)xq@1UFjFdY;W3ohTfVzRxCc=kOd%U(lIC$|9Ru8$EM-s4c!FryV;o2 zwX{4s6`n|0xkb2&Xrz#XztP!KA@sn6arDyMm#9PMju;!9PC(I9IESUIZ>LW*`{#E3 zH^~bjs_ctID!DWN=@c|7YClPgF>hE2`DJe$p4x246u@MFY)yZzqLZN+f%Go9~B> zpZvzxt*GlAIdY^^SV-s_U0{>or5DCfwSgJE{q~Qv4>~g}tgTi~efrr&*t5_}f6i%Z z)!TwGc3x>&>Ace1oI(IL12DcBv$f5D0o`ci&_2-c2PR1v+^Vc7mzebKph!me4TTt| zHtjlehCW@kmf{lA8f|Q>m$*7P#YN}juj~qjLK|q!EiK5G22bR#^lvML$)9;ju$f-A zLb(6nbx0G{kUfx%#s*w7%5#F}6i1ilCFiFxW{f{6uObpYqt0w)koAfHiOh2H_shJE!w4yr&25Vi?Os{*`6lKprtvK4n9K4hso4X~2 z5AhUUFs@yJeuL#t&ZM@3`qPZ1i^<;2g|41CPwQTPi*i#^>7^gOAtS`&eiv!EZk+gD zIk`TnilGoboQgYr2KrnW$Kf9XVT1{CJpjL8{6e@Epfc{w1n?JFBBv8bfRefV6k(i~ z(g7UKKiR=6>1yD28PVX+TrQ_&KMKnz_C#$jXXkgc>#Kdi{u=g+=h?)1}Dkv}(<-Q22F*XF*^)?to5m+b-)X>w2}a;z!VDcK#`G1CH!1-<_9S%t(GVmIyxn}=#`M*j2N zG4u^Kaz~*p3rc`)=6u_=qoqHt0j0o0D>p6Wou2LdKi(1@{aQ|GK zL$77!JZtReJV?jH^cy`R!(3xvTMz6g1CY?V6MqphiuQ3EO8cPoF zh9yrib@@E$#Y|NP!N(zY?szIIzviixGH#W#5|7hsE-dzI!B~k0))e2FslPE|b>ppgc2@T8~4^a5$tOl)9S z{8R~LGCzFrB~2aqAiekIyHGot0>v~K8-3hOHyIms+eG-Sx=dBCyYuj7#RD#?fDJb* z_6iMuW*W`;X(jn|?MP;p<`g(#EWPlsDT zNwD&Et|G~D;R+B|MMfK#qZ)Hs#6um?9tI-f$MHEF-#HG4iFXmke$G$%A)fp$au;dj zIGBGTE#fwU|3=Hon)dG8ODB&U1xGrr;kcP%PG|!$Aab)ar3bpWQA%nS_2`XjfwnG! zIZX7s(tk=xV*SSe06+jqL_t)4D1rPg@{yl-XhYdp8NmE&AMK&NN23_{Z}scmb2fm# z%t6PAn!3*ZZ6_7x<;|EeDUiDN=|V5PvW7P8ISp0CR^RpR)cr-iYHXU~?%%XfnNXR|GYHwEdfASlv_RP-Bg>*^jL9ZXz?7`hLXkJ>bV1%)*DzBu= z7b0lyj^F6w#Yh;HFd|oXSDZ|6&&ibrQyn|?5>R~nR2Ghi@+baCc}W>4`QJ`>IHi)Aoalri>t!-HvnAHNX>!L2Lwb5CaXh)ATDn1|$G0 z&>HH2VPr+9GQF0ZK~4_VGD*#{eBMu`6K-{z~tg+TE(ZA=sOl2h{!k`y#?4TOJ_Dh3S zB`5;VkLo}U;74f#vcFg7HcS;pz41eds=vx~7a^K|q?$4CFRgB**FN4&zaG9!wg7EF zlE1drzxoaB_3X|~JAv`G?E3fW)jlOLd0meHXY1KdkE8`3ZlS{=(PZOb_i50u(XX%F zzWtVrWi97?^U(C{?4obW%66UoesySOPQFD*=w*++2Ttmqy%343u|lfl{RfMM`-xBY z%-_QX(CX0f<<<4zm@frmVmci;5k^06+(XO1{e`yeI7A7lnd!QEhO2E|9AAcdjaD;~#!-Amc2dz$Tn6orsk!C!{T4_op~)r|7E3ep3Sv1Ps4{KKOUtI% zLc!aEy*xq41=T{M&L-x@bSfs3jzcMtzJXrspwM&Q9b2?W#AvM=_r(=J(fRYbuy%*5 zTRkvjn6}o|CgGjBcB2&=HqxuJo~4Me^UodJ{EPdTF=Jlbw{KrUlZf}@3uvQZL|4(Y z;6Z_bD|556$LZ8m&w!k1&&KL%t>1SYqGJb-!+=c(n*7*!8vWpKvW4;w7`z8*s|vG~ z$>qwtQYNyrFqAjxs}y$gE>u!z#?lOHUXErw#jnI^`JGP+7H%F?S&%Jit;!vn2LsUR z;sQzwzd#;=LD*QZBm}7un>8+inbc3!B{X{6U~>0dLks`)HZA+FOP)tZC!{YTFrAIad8QlsWiRsWzYZ7}rsw)dgq$Iep*WPhhk97JZ2 z@Aetrm(n0L@W+K%IvXA@iGMm%V_(CO*C@s1N7*$$Ekadu6$Q0WXf3sp#Z*U zTk~FNUIz)7+&b0`>68vcQjwaGEVQ=~a_WMq2|!SZf{R-k^z`GDo}2=WWqfc{eUY-& ze7%dRa3-8gGaC*QCwkONm8F2-92p=T8#Zs8eBzfl_!$#q0K?zqAHM)12%ZakV&KDf zxgwGTDez6RRE{sbi?aDud)v)L z`Gpfo%PUQ~w6Ue0u=Ho^WJnB%G0xYNt7boh#^->(_jCwG`Utw82{Tsl4 z52T9h>}@u+9o+MUoj{73g6ZFlABg&$yRz->U!cC=veHUQPk3Ojqdx1ti$zCylS ziNDNEm9AnBZQ0Uz3`4NXfc*+5_alDU=5L2D=5Bgf8MN~Ua#mU<3ol{-3*H_*{B7&W|) zP>(Kddv3dB=y)*~qy&6zV+Du_U0iC?)=dCO0li)9C)ynlWIVqPGJ_~OC z2ilXZqdC3v;xKv=41+&zJ4y4N8crpwqg!L6XsHn>1q&Ad6cPk+nFTbcS9_>=g@O~@ z0RujfCv?0&GqE3;!(hwFuvjVpJ(sVk%L@u=^Xi{zFMvt!f&FO;_*w=J9)uHvnNV!h zC{E$cN>qkLHCXWQE^>=HHp>O^n};w!l>?PBa=9T- zW^m6#M_yd3E zemHfi;K{;;edF0FQ>PkUi@NAjQBu{eyso}ONmh|hc}0d1+Gzkp4t%tYHM)9wRRETm z;Of0%ZEhQDYiD=$=(%&*qRz=_=p4E4U^ zIW}{?f4Kwa=?E~n$rAz!$u3z8QwT;g=)22=QbnV(5Ad&xXxUE~a^5(D` z3uqiegHj(=D?TPgU;x0JTS$s}S z6=-P@ahQ71G=00B{%tAG+es(IVN$vtX*3DkCck16us$Tjc8INbkSExLhDk&&K&<c$`Wog?B0uLM*%BJ$yPcFHGs#Qe|*7jYQjz?fAJVcRPO|FdW1TTY$5y;*}GzZ-N=OUBI+S=-iUPDH_0pJhPUCVClfDv8^ z2~po;t_Q>jS_bTJ+Ss$S zaBykhA#4P@n}no*@f}hq%KB-ka?#<@6B}x3+fIIHxS+I^rLuUWk6d>&0+jq z>37T|tSv*3?(Kj8K1V%MkPg9D+n z+y;CGt~3=A2Zv8Z(vedUl$BK|VAfTLkiB>DBK@%ZTN*lg6pf!eiMn;~N_qeaV1U36 zBAx;f;0;H!B8p$tT&1?slRw{V$TX7Q7>^So!tJJ4N|uwgnog9dQ8C09uvN- zI*M&qq~Qao+qE*6t@J4MbqmLq@o=m85{qDz-(sIWxZLH|&u^q{) zy*uuLp*w*QH&ME}Oi?iYi1wgP0&a<-fz*ImE<7rW=0d~YwWQnzoP^(hZ^_d4Z>FQa zyT5qQ-)C9r&M)P!EaSa%N`&M zrWbxY{Ja;;7>^p+8+ZR;{zGE^cejq$6f2Fg#1%kdd?l4Z6wd}s!Jv;e%~GC2(tP46 zB_RX6vv3$6UK3MFC@!UdO+Yv5=$RaCngQNWSH7WSXvh3RN(rnnFukxG%*v(ACsBl} z62UicL;7X`cLy<>W%8eXHSn>3qFe1+HUz{b8t>K(9x;N}?b<^#=lu(0*2XTGao0A^ z^KjiUtapz=TX4m2xB31f(_yilh=@pxN=jMQcKpO)#@4o@4b05efs?j?<<~Eq2&LCw zc%3Eb_kT6itR(oFGZ{a9kam<^$3 z*WB4%-dK^Rs#wV*M7IzxGb(~=AajgWEf=gr#5=Yi)&pRI#olk&AR5=VJ^k?h)8y)4 zOA9{TOV7XY3w4EU_WZnJTJ+^MSPQHY*4%h=Rh(QJUit1^m%_rXQkMb!$Q?S?nP0)L zUte(l0+66Cm#!oVo8!ZV_9A;bs6PclPy>X*&B216eRv=(3x1mBJv)~C{M?a7ALKt> z%EjICx^Gv}vCmD2?9Kowuw7#-k7+8nw3;4t2~u1(0sCA zdFOaHOV8h%$LBENG&5;ENENk7booVoNOOy~R1F6tfH(Ojm1SVf$N^W10RTVv9pQYE z7QkEzl>9swRN-=3zBT}$hP)X;J`UO&ttc7V`c{9xjvk%z1k`XpjBA=7sVqMqQ9$n3 z(<{NQzTVKnbZB&X`poF)_&A8bjVUQAdep>7hyFb+h&puffe?-!hMs}H+=?cR;dUWF z3B%Yi^-2qxBY!&u=UquoeeY(#pK~fMC>UN@UD*!6D$UTsY>`JB zzabIvNk9MB0e`OSJ#g`tFF#sYUDbW+!=uR#Iyj(c?Jjeb=MJalF~Iz=fN`aAoWje% z5G>6%W&AY8OD;_rz9xkO;Sal#PQ{=aLINPm(b=lWDISKTFtH_81~HvglK_M!AP;fr z#V8vG6dmmp$2N8;P4aH#g!wyvfAh9=&=0YnF}W{xO_DHpL6I3Ew)WiURpe*uDa?G$msglK^HcQomkjf zQ}6!0Y0|^vsPDkuLhBYTd~qL*D*~4NzQHL=KywO8+qd%Nh9q|#z-1tBXr!wsDW>F* zBLJ?b6R#qT!lY9b4*me});_-E(y^NW!iXrBERK@E0gx8}cXE6ttyu65&3tMU)dD5| z?Pvt8{q+=O)9w%Vyz-ynKm;yA32Aaa1pS=2zf!hy)679T56DgG;MUQN0Np||0b zx;2R^a#V*XT`58|U*yEiqHB(iY8X_xDd{x}R>FAHG{cbyoyVsxeu@oL`OL>DcKk`@ zK^&#CIS^wm9-L4Beq4W{-0`0o@3)+zk5?V0n)=2pD@TXtBd%WGt(2om*McX&)6=n{vMkWS$u;s!cz99^QSO_d zDWgZ*T{v_0q>H_k-^L9e!RQ*)twTLG&lLWrcA9>5Xv;nZ{?O!UYDqI*TuhMOam=h6On*^N7p9!1DefPc%78wIB|9T%!+{8hLYl9QPRpK-NiL>Q%k`E* z?~5ydcSi9wwgjqcxnMP_>Xh}z2XX}YyF1FOr*4G!%(NJsYn zPCouVGy-N$M~xdn9Xj|!ZXVOD798NvoX7X^Ejek)@n31vyXDMnDU(*iq}-Sp(AYwv z0u-kt2mmbBWV6E9WNS%!8g#C=C4J`HR((hPr2pU%s3rHduH@|FN2gCm(7>VHY0{XU z(DmP*4jzl7gV30kkeNqkE?%dUjC}I*c7T~zjka?>*g4Lgjix~p$Kvp40Y*b?Mp735 z0r94*iCJV1jc9`gbeC=;Kz(4KCaNRXiM55*fxOT6enDNls8?riN=(m(y4GtD2a2ZD zlx&e@ab6x627jdkpm4dky8#L4i)(_B)GMeL*|CHN12$Z5XrWyUfTT@}Df;HpupZYES_k?Xi$iKZh%z5NK?S4iBds0DYbl4D^kF{K05+X@joL zY3OU%WMXA?ASx;oeMBcEF%$5f#BAK1-_H(* zmn)v0^Qx;V{GNVl8rfSLQ+Z(t#+%1O8k4(fKE{R(u*IjO(}l}dFqdGMhXKDl&SHu1 z_{l|x)vGz4Vl08k<3c|zyPU$JGlWEqu7M#lqHAmzxM6Lmg&_l`8bZ+#rlgV$BM%k0 zd5uYg?f^az7#aR9Ci87H=)OPzg3(c3JYBQEB}#cYIk(a5+l!(P?0U?E2{d@ba5{SE zFm3vIjR7$H5h)4RN91It$8>G$ebA;?uN@CO{BYR(mtO|A*R3(G+bj9MKh}Yh!u;ok zqeqW!e(i-9dX|@#KB-evH38&t$9UM+UKVkc)~s4j-FkJU@ee&fgNF|Qps~d|z~W%4 zfVf}hc518+`R(4co>!c*M5Og6Ej}%>_G(L|+36B5054QIswxG->cP)YUQ|f>AeoC} z9wMo0$}yQ#tVILIkD`r>KcjAe?XVF*+|bN`9-r8oMh)pg7r=y71*s%2H+x~ToKq=| zjd{df2U#9u0OasC+#9E6(hKj-g=niDRX`z-7JxigLRC-2hXA%l zR$0MQP>Z~rEXa3Ce|mI6f7-bFH2t`4KSYWQ0hE{`07jw2MB0)NPdhekCKnG63hdRJ z296j;-Fx;RS63(8MMA_1QUqGe+{xpFZAj`#xdoaHQ5vK?Rh5%cNVQcy-uP5!B_~t| zh}PYhLJHA{)>;(QSwRStx*)1tj4F|m5&8}1F!UdOd=GR1zgTDx)<;}JAPDt(`h1~$u})8Kua1$)!N;dhj|yh3p6ThT;@HEK zwDz8E^9%9|pBT}<9lbqw1XMu7%A`P!QMRbAgp9}?uS(B542!O6fv@9AY(6dgY@bjg zZE9|^&!?^bi+`Lxove!d{~lEj4C>Xbdt5>OOFg@{qe4ECS>*em9l%5qi z?b>$|))7m|!bBfB0eFx&SB-fm3Q@n`P%4ndvyn-Z4K05LhWaJu<|fBP5{*Bl#bu^^ zHDrWqCw-X&L59raJ%pIKRYGeX+ z?cE+}VAQ3yp3Dq&X+&RtY%tI>fOxDej5Fi?hBuvBT$@}9kE5xt%mW!8i@BP?Grr*j znFIuC4^%Ywdte~-9{(Vf=VVh_*g2{LAQEC_8hECt9@iWHxrV%{RpUCKny+P`O4Z%T z8cc>&klc8T+IQ?ir%xQCE8ua+&P*4&`cnbOQm+1q4tpToD9dSMWX_7m6lSWyi5X~G> z6qhEtilZoi9`)zz91H>iVcZ1>TVY`l7|D|bzz@giJ~|=_2tX2*Lt|t;`T*8BfE(zN zF(gYGw6x1`=Xz0FPw%jvfze?LC#Q(e&`@w(<`RHE^1RV?^=mUeCo5-B*Y?iz#ak1| zOdr<-(BrNQE4M?l6-o{qCP@B}@!xy&D!sGx0Hx&=!788C7hU`G50)i)l<5EelzZUG zC!aJq^V@-sVPVzc<(D3Xtq;i3OsD~d?f?t3vm;N3C4{!RE}LYDtE&6NVPPZyGj2>tNN8#8 z+>=(axK_`gAo0t5c|JDN<8*lMUcMd(%*)OSEX>Y&B`+;G`AUdebYEZJa~8I?8}^?* zoqSIU|1Zx3K#k{OB7VzZMs)SDef#=WmzO=JqhIv^u4f!Fpu+a&ab@r`56m9I%{)-n-8V(-u%f$-^|JJ#RG9sh7HL3<#{<&RREpa ze5Wgx5(w_%5{4={G-lcGK*wsz#!vk#?* zorg}MLR?+o1Sr>64Ju@xTn6_eUk_BlrT`yE%)z8Kbi{)c)OQdSL-S$m)fhS#dYZz4 zSY%~D?ih$m9+(GDq^8sH-wu;8?i8Ke+@SlQ6Lo>2q0W#dXxp|85CWLLWl98-1;FDV zen6H~S33fU;zn~Xv6DNEy)6W3269a9YbA+>ms6`9KFBzaIt)JU6KP8ny@>$iDEb5S z#gpsJ{1t%qA|30QFD@>j)YLSJy>gABqhcsF<_b>tuojt>35p6|TQz{Jsso^}3od?e z=EFQIt_+eJ^>ojIWBw0g1A}w60|&+)`{^f1%q}TOXvO*%o{UbK$;00Q7Y96R|Otq$Vd?RDgqN^haPsBJq>s4Zop2mlVMAOR$iL{y2n zg-c5+=z3xfJ-_%xq=)z<*Olvt4W$@NfftXTrOv&&lcSd>s0@HB_-t%xMIOETQU(kd zm1SihD%Y0*os<@TMLdxYhjV`T!g*BU*8`iox?kb&#huF|FR z=jigKFleX4wFMNTRF;-g0)SyWfZ}dk6&OLorh|(Uwej->QqTd^gaB&e+m@W19KnEO zic<^j$3;mfjoS|dNQ|FApd{ENP(;Mx`Un6nD$Nx|nABOQvj`M%80aa;mx5O)8@GZ? z2@WjuDWE&=$P$G_bOPs#vBb4t4FGIOX(=Q#@+mVT6YDjJuE)gz(8p0Kq$BdPbHE&0 zA}AueRW>mZW!>5h3k>v* zh>2O_YH#AW>cfZ0ALgVR(ANTW6AhHIHt&El!Cu8@&76DK?l^363`748)Q71zHIvR>EA_#p6^~&UOZK|x^i4YU7crPZa(eVvIkli50aawEA<`R zhenMZN!~Jy8V7}GP0gd>ZTDr<<))d8RpuH(bjrPFKgy54Mx_9%4GO6P|*}=+MV`VAwXJ9U6t^>ehW^P7l$yo%ZQ=wjzff$#g4;gZ|9zm3SC5noY;*bQJ z3_~n9feNr!qg*B{>f^qUgR6l6FjatmP@4^&Oi5J97)*|Vz4}nE-u<9TwG2v!QgB5O z1+sTI#lyf4C|l5DPz3;4N(lgx@iABFx1D>05-4*pRNCR%z{TC2yuEo1c*E~Xj`_`N0)eEleFT=O5XcHBD9ry* zj63-}to^*K98fhf0qoQ9%fM+qn|MzsEiQ)5g)-n=)gmo3Pw@$g`<&0u1~QYMpg{qd zFfg~+U}#|Uou$2f+@VvavM`@jmA+G;J9_kJ*ND?08*I$=+Am);nY#MhLn|c%f2nN> zqR8W;I7Ui!uzdi>q_rE)(bwybQFd`fou!TCSB^gZ9~?V&tVoIb|IZtHU}(>NzSm+S zzp=G4Gg{)>ioABL#{2HwNVGbJT9XfvYa#Hj8HEY%^BWPW9XX1jp zJ**7^v1V$c7Q}{kg~WlYd4-PQ_Q-C2F_}2%fkAZc zz#*I{Gr{lR1V$j->7ouI8b?JE7RxKhFQzmw>`a+3Mj$u*NjN?wAdMLm2hDhU9(}v; zL;CsSW%S&lR|$H}#YvXy#1ab59lDSat|xM?Mp7M4^}G?Vlh31KW8pvC1$;lqm8fDp z{M;T6#FOiWH|P|{RRykJ5GwNaY2!uqjr9cLG0LVN^j;5?gC40TNOM)3~hnoi}o}4_{nVM5U!A zIN2c%Q#gRE@Hp~M08?(VG755=S@p9aBvY6%(#*sF<4^;Yus+n$-<>jH^>NiNxX(qJ zTG$tPC^q&~FaZCbS;G%L_@K*00Dnk%_5K=n{z08x#K|9BCOH}CE5pMlMpgpF`US3B z%c76J{hbblS_W+JC(7!uUQ-iGhKK>&h-wHiA6dY>)L&wKd7nbS0A*zntE{=JV< zhmGg>?}r50&cxE{{}*cWF2=_78&M{LH)Dg|IwUZ#ZBc&Ch?>gki8WvxgcO0%waYOS zeKCrDUb&WhJNVJS;RA7hJ&4)|w8e=Ml$}N(1I#?gq-J^Z)Lht7k@BLup?u}5yttV} z4V_&Kh_nFwg|eXJ<`8v~8l!pY=3rJYTE|w* z_=hrBc}=?xov1^nt^%0WK&@~IHqor?Oj@?|6Tv)aD2%f3X$O79#}mx|3O39Fx{JUl?&fpa`hC^ItHkG;t^zfE)B_*`qhEj!TQyiE1rS40E~z#Jg6u$P2uc=e^MGL zZw8vWLY|s!E3jyv4tOmLfsmL2$Xi3dy(6qFIyqXB3!aYlmShh_Fl@wV*QNtBYD5q6 z^>>3}7^0M{QaW(>Ea&ymW>3#oQ0@{`PV^jC<4)YLVZ+=nhJ>s)F=*`n^_$~qXkQ-y ze@Mab1QL^pXB^tX?LvdJah0KEjJy7-23q@bD6QOdk}_e{$K2d}wUe{k2dBcqQvM7w zawm1a7jZFuTJv6b@h#Auioag@-LiX;=dJRXHgTfOsgu92t|~7HdiV8bXe@MSFtW;B zu0jS|%%u4cYrgYr!}tJEHNy4sm)~up^;-{uT4x^Z*RI1031MN3Oxu`3I>QtUz%A)M z;W3wTkqTGV!_zve${Ey zx4R;#l28A(Hp?La!qnC~a`z1az^&FZ);os*stG$2s?UI!@ACd`mz0n|F%c24{b88o z?BX6u5z&8l75*{hVIyN`c8!0*!NIEzZQa_r6sm_|nec(S+S)E)Ow+l1Hk>YnhSBO} ztH}?@K>s0qXu!~Z)S;s<85&z4DYC#xQGHFoix$gFff_fiVSJbNUa=VQou2~G@D3rI z<}&2~1ytg@5`}}=FC`WmL+?>TXx*YuaF3o#&aM#s0~4MUflt?3l%17Fg*DpLf6P$O z-4P~;=Pn7R`8g#5xK`k<+0M}x-0Uf;I?0vCyX2%+OMiK@f@)3t zK*^9B4cjYZX>E86+dKP7Ye{>-T?sK-o}h9VJ6iSr+oulhp_S`2DZ%WQP1TA z0dV(nql;k5bO%E!S8UGo;S`UPRD41v)t8no0OGknhUktLa<493+D4y0x8BlJx92x+ zPoyEey{H~n6%qxDInYrN{1^SpNE=RrVDqQr=aOjYk4NcTL^5>z=wEemvVZ&X_2ggW z^@8;O90!JA$GY0;xfmDS-~|hI2Y>u=(qCEoNw{h`b@Jqz%JPz7|DN*%&3NoV!Qa7# z%h0)ZSF|)G0Z(wc2lZ;zPrK;zmA{aoiE+B4lf(1_$Bw1-4Gatb-sx-w20eiQKnh3m z#Lx3x%wzr|h8+xiIKf{@DxyEmB=Fp9G&VI`@%7hVSGQEj>dNwNfN9!(9#&)nQDauz zwdTlGIuFf(#wNyL&h0yWa|u+`J9U3QguK%l-a-5hU4o(u(y|gE=H!=_l1!f7K6jA% z-b9VK7)Hf;xnybYaBR!|{dxZ+Z2jK!z}@8n1OR6**dUNlhx4IVZC*988!CXjgsAipjc2(iKQLX-Y5 z)3t z%T84p`K{?YBT2OoS1YM2p`Kt!95Zeb9oxT~rVblQ-e3kS!x*u)LO#JWpFuT%KHp7N zfQ(-bM4%D~K^2fSsA7duq7hN%fPsE5?V zrtJ#o+?Ia*4^&8psVHbiY(gP@yFP?=9*LmJ+PX3eYm4t)-5o#w zBlL3mKXo6`tFP|})Xzp&&rqwTx_TlS|Am-bf5D%A9lLeEa5{8VeO-0;h4ZJ=v(v{@ zWmyRjPw6`DPKy+)0)Ke6=!4mgsf5c`ZKWkEH}O?hhMT+F6KBGsgsHyj@{%?UH8mzK zP|?lefVH6Da7r=Le>!!h#48cN@J6_^{i)rR~`SFZv0{`a**)g zsp*FNOFKB;afZXRZ6?X%2+v3l zOF|d{;9Y?(f{ni&0&v!*>9gn1#77{C!C zm=`%r5BIMuw#PcrYcWsM>A4=<4$ZbQLu3P(7OlarV@{EmuhXERJ;5(gO+8_aZ^WQ( zv}^wvP$()V_wLeRB@&;KO1$~sp8ozLg;PzbpZa1urbsZmkD_4 zFohBbO%6==uG?{v*6)BwWsL^a`k9+sXxZ4< z)utpTO$JZYa@Z$eWdEMIqfiAu$IEMGT>RBwGgCu{;CJWI~% zi7^TJwChL=kPCfWC0H!~?z`_=WETIXcW-a-xb(HMFa)*DgTfOF=mT62RRPhru(AH+ zbaeC|cZ(n@JAl%TfA$lL#njSjSEII0-H`)(#ismsHi$*6E`tI9`{${y?p~pPXW92< zSs2elr%&fZ#wG2#mY(sfcU%7;7$q97YhwJpu90CJ1T3Ippa%Q`k+f>*N_u9>b2Mqh zW3=#J!E|WfQOYX-9-$B8H-?a1*Ebd?VbM>#Dg|`mr@TNF*r2Rd5l89OCND~$##2rV zvRsz|eE@KEDI)9|vX<`rnE5X%DxL;SngA#gj!D@v8tCy%PNbo)N0YbpkX0v0ekFbQplSkf{Sq^5r3xMwhoav_@j! zkeVR=a+t)_70|((u3bq6)qs&RseO-+?@KmN#SF`S2BrB0^U$W7?w7&%-r2kD(+SBb z8+&$ec3$`CVW*;%ii9R{MvWW1A^8i^5Vsg8UygByciFyJ^@_!sZ-i+w`djSCun5me2qJ2%L9cYHw51>ziR&EW0_P}yx<~!T2SD)>z zbw+V+_Cw(3u^s~8k2@@Sb=h&s$SoxUGn2Lb9-Q#$pP4836$OAhuuX@~XYw*LuAL7F z4M>QKBOhPi*826Hho>YZQd)dG$kC=}w(Q%-a>Dn#jQ`mT85tmAJfEyvCV>edaXUss4#;hvS% z5#PW>hy}|lwfT_oI@S!+@UvGrE5Iw5^xOfgJ=*(HV$2oX`R9|XiydV^NeGk*Q0G3~ zB)NW^g4NkHFM!_#ToQl990EDr9!!6eXFpG$zW53)p7n2Xgy>yIW)}Q#DwqzPQAgy6 zyY_}E02_gL0QoR9Aqy8*l%)+Zz$zdBX`Hn{2t2$!L?PMW7vK}Qrc>8V9m>E>XLGOe4ur_C=4egCQ9y~k{H{=sPt}lv8YiRd@a9X?lEM1Au0<}ap z!rIREEAOseTX*jcDTT;a^X&hT@3FBTI^sI3qm8XS*+W|5$->tPcR7`S}Ljve`$@tGw4al3Y1+B!JSDk&)0>^Gas~t(7H_r>GS0qDGU5G);6|lygPU=ICSWA6Mgd| z`t@_HudQouX=X@mJzyFg$gVWcq%zgH%Lf8u13Gm%llC9Gis_)&U}9mmWZk-TAQ9c@ zMoyVx9ed>Wr!343sY@p>n!of9X!&LFVDsI+fkE?H;~BUyrZ zXJrH&*u9HhdiCEexBM>%y&4@&We`hp@Nz$njaw#A{sjg7k7tR#<&~9--$5+c-^Ps_ zZyFyR-Knm!awM(^hHKZ>b%Jhn6EWI zSbqUK&Q;1A1&J==&k%>h2U8Pc%9arTj?2K(!^cBV0dlkRQMaZ@0LN+3HR&I)DuC+s z@Z(S8loU(zSFEJ1j=_Y@TZr&+ZL|TP8Tco_V%}QZ_5XU}B=jaY&>Qc5L<0s77nFiZ zi2XI|_!f(n0#N}KVhxLXxH5=|ih?*`A%+fi9w1#|fGYr2hs@VB;S*ty4${rAb#xAX?~_kfw`5+qBrh)z zS|we5yzR-|(E|J|OhwXkoIie8liFZjEyo`41xWa8G|0lr{D9Y^Pwl=C9ITl@@&ge{ z^YVBWD#`tAY|UuR_DhtSQ2_B;^KAirI?vj=b?2Yf199IW0Nity&i0#23-adf+P2x^ z$!DJ<3kz(Ne}fB$C`cU?+uPbX0K<*@8%q5DB6wBp-c77cCxm}fro8-7T*R(`n(~?< zb#*l(bnEJHH&<^}1}^o}M@~`5@2AKF3Tiw7`~v&*fCj=|)CtUc;Izhu%*z3Qg8?eY z)+&TRwQn@tJfv--*%*K{-o954zMG?z#0*MI0K=WBJp}}I0ib4;uQ(Blzfw8?6nRhL z+~k5#4=n(;EZpxO-M)t|9Xml*V6vMs?*-}#=>S7h14;4$JOb$IInU9HpMOa| zE_jW+9UZ{%S4F8gd9>sB3EK4g@6>>JQ>V?OvEv^iD;qnZn}F}_Z^&3(L<>Ik7)ZWG z3cq+>@LFKMBq)6moc#pw!gzwJ8#pI*QaN9_K^Ox7b@FZRLFZ0g1~sA@T-iE;Kfo3U z#iUVz^wkghpi!`Ta^J39ze3`0&VWydbpx!Fr%k`iF#Pk&8svYE002M$NklUuyUnZp0DUlu&ax!it|+EM^8cpUB(% zqg9i3?X2^!BQGCUcu@cj8DX4#y0`#L2r$Ahb{sRf2d&u_3mSwKXnD!~DEz6Z)23N> zadCSQ5dmjS7#d_U_o;E@<7FW+X0QU!%&nxohtp}({v`3n zQ>#`E;}E@r;OQX%%b3}Kp)9w94OTfZgQ*Kog45@U==0@A#C!l9{+IjW;x8JydGdTp1KG&eEllTRx0F);uQJ|(=%{eX> zZDOo&W;d*D?Ww8&c|eB0q(~UnSy+HjX(eU|I9ekU)Leuh=E>@EiSux{Y+gMTg0VOW zZ-4;*0Q&07?`Zk*AL&q9I{fo3>5dtX&N)CgDs_{WK$P^4=9A-X#n+rHGI1# zjqOJtEnNpDqxzPx9z9O%%*uSs*~&QLjn{9bX}9*l`+zXZupc-Sn?YL+BqKI_7OeX# znZTU2G&BCg)yrjN?3pu++usnIlHRKJ|K8zAd}j!X)x*izO)#9m^*{^?bAoh~(lgS= z#m2_I8y+5x59!h=At51a^5m$yV)uV}XT3`K48ra9`eMni6m#SRjTzOSB0{^7E3hrh z7#{-!pAA3j@y9A1!Ih$DmTvL;&3b(n@#6pnsSQ+IT0u!CQs_YR3EF$$IHjf|I%a*n z%Gknur-Pf@SA7N!-1IhNs7tQ?bV;t^Iu^O`UOH+umQnGx>-f#__=)pZy|jTesFMHR;TIJ-t18 zOc*&bYU7R_X;-kS>$)fqVx5)v45$3dj1fL${uox1f1g29+CIYY#40vvrN>JqWh zFa?knFjL^&ts9wGThsVyQ=rBN=!-@qjm#o7lO(KEVLcxoeT2S$7eHzHMl|i&Srk4b z5*`C68|sKgOe&nD$*B2GaZ>w7o^M1~aRHMeBa;F9Mliq?zyc_^kOyO|mJ9$F!DYL9 zy9x36dEf-_0_tLO4Ns=*d(V=Dl+D#tOmL0s4R5htXx3_TokV82lD zxx2el>e(bb7i+XlyDY7Hs?uEw2o_boHQ<@UTz;zd#D41V~`Ccl^*^^!vtW zO3TUjR8%X!9TM&b-ScQNgVEs^-$#S+pQHG*=PAFa5>h~&Y>1TiI=VQn_Vn=C&-#6W zisf>|q(x({^8;l?#Z8EmXT%Hu6J~yxy!v(nRC-!ka`&wpR(Hk8_{(*3Xt9QI2wfz=VwwbufmOj z_5W{fE_bO_wYr1*_>hg25vdU$p!r=E>^{CfeB)rm+2dp1Q3P@_Hda6d_WX})b{1c< z|Cp~*iI$=)Ac*NSo{WmRWY&Lj;Xlg&Fo7A^xA%v5Va9y(*{7zXC)@##6uk18MR53+YJozUeCE@j($?yMEMf^yu$ku?alN8!Lg+H`x5gX20^X zqEMwu(c`Iy9}UK;$ZYmA&%E{iqD4Ed)o3txkGgpRiU(8jElUUT^7BJfHHt`x@go6X z5{lJ?GvGIM&!;m(kWM?-Z5L>5jA`%U;R0hZZ}RWzj|h4Gz?|?R2ShWpv@jzBpt-5x zrZ!;YFzV44FuH<(Yls$iSkNbly6NyL_RvxK^yPWf|E8Piwny#;h6G@Ov083aVI?D| zuypT5$RU^3GRax_Y-1e=zma$^c?G5m_p{85ba=-2x4ft6NCyB$F2emOGc8-_)dgS) zuCs5wA?|0K{{6B0Xd?iCP8^Fx1hXOu>YbgMlde^k0DN-8GrUR$}z_@jBrG2}Pf;f~yT>&vOIH97FN?N<+ z2#^)aM2rAy;IXSoVqLnAdlUlLx)PRzYMG3Uw{AM zhi>)in&IWuior3*6T=S1#5+TU1*{%J9aDJu7y(;>Ra*Rdso`2G;z{5etc5`=zUf*R zrWWDvl)kY^jJ=)xUQ1WkxI+gIm6A?gqdGmUem6VM8e#z2N@LvxxGd8iYF}D;tyILT@ zVFd{8wqqoosL>LZ4(#GT=IjrIhr1V)6?X&Bz`#0HZEqM8LZsQ?jMcp!tih=iYJ<~& zDdrpXfyQTMVGcw(D`7ie3g8eGtdVb;I9%u$VG@sAJR@i&%^_|9ERg{2m`cQCiAccg4(4lQx z$jdK?0=tF43&M+f_3TOe_wJ_U%YLNv^i+Co_A5e)!0`#%BuyWhyVB-_xP{dZCyyOP zDBpYv2@R)ZKYR@j0CTbh?61(u@NL?|)21T%L~}+4avn8QM3w%We2?Z8OTt zBk;}l;MKk!)L{(( z>HPV054EDk5taDV>FD}al~>z9nq!LRS&w(23YPs9hQT1QfWnX>eK4R=R_=ESmDkGYa znt~N!0K|s+B1#$SQ*WPo5B2O7POE3Nny-_K2>Ek zUos)j8Rj(5mAn=#M{sqkHbYpZtTmL0!^K=waDC(Zs}_yujQHA2Aj# zR#CL<`)>tc(DN_O70|vMRZt|YE^6&ww5coz;J@fy+sVb#gY29fV7Qk@4yL-acjqx0 zF)BjneK{yli`43{`5G4}jW$OpboOwB9l&|Gi-w9ZynD(px_9a*a)5on*T3w9mH1Y& zv@~w;?b-FCEnD_va5*QElIUkG2}N_+H{M>2vier6@(tYFy(RHK##Tz~W3MN?0kSgF zde5Ib*9Yez>Hd33^PRE_1^-D&SbxIImbXZP(3Y05IH~FBC*ZT*brFkxO$qJVOdK)7 z6`)bm_*=hYETFjcfsUz^zHw&$xNI zW&n8nHg4RJ)+Zz+DnBFr>v!hPr5{$WCUc;0UFnp6;R1z43=~{0PrNMdYEafgrx--f z&7Dtiu}5js+TSSt#Ic^m73X_alorj-ad$fy=I_4|y5a+2{raVR`pGA36pa-Vq>K7{XLIs{I@--F-rH)*hAJA($f6vYifcT8tVEYa8Y+G7lVL5V9UDB zdc8UjUp-hVWAy{wGOxyE0xj=xT{^HH4(=IDyZ=}N#!-QH69!Ehohe_J?veH`|EvA# z@Dv1CUaXYu9qgc^FD6eA;*BTbAUZEcT!24!jZKVCAhw;^zik)H5)5e6m~q0bFEc%b z3d`Xp2apsKFuPXpTCjpmz)#x$nWp1f#$Kf+TI}rF$r5bIoOas2B1s+zetXNjKo=T?AWwiQ7tN91Qx0&Y z1C`l(-$m25!{-o;%BaN7&GC_KM~}@q-Y`_ zD<_tf6qaqz&P)thx9)&Jw{E-L2M*l3{t~#*SG3{(u&{5x{njDv+}Q_Doj&y-XpJuj zOe0pAweFr?I#XLwq9J5-y_Yz6;JRI6e!Z?Cv zTOXKO^x+<~ci%2Dz^dBc!a*4E)j_^mgL@1Ra7YivPw&1t z7eP&|fxzeqV=oW#@$sd6008aXxt-Ld<>ZHeMLrgGlwMU#zx?=}0EQVeZlbU&kQQuh zI?&V;d56OZvn@hw^g;Hxp#j52(q`BrFtHEq;Y$IY4z%)@U9@RkG>w}GSY}oQH!)u* zr418+)f&D}jta;tH;9jgJ0$$i0Wn-77%~w|2DjEjSm)|sMdL^Jq3@S%AtQz2zJ8&h zUmYgOklS~oZ&kS!lfmrzd3gB>gHI;-Jm})dV(fWg#JB*~*0xk$Udrx(%dWRE8#QEz zUusqPqx>p8czZZmg3(t&ye|xbagq&P^w$Huy;m0fK zizU~pxmUMdtS~+WaNLZj8JU@%CK}hWuG8F@`h^DyBE_qJ{>KxB^sChz7%6$en9u}L zsg#7X2>!zcdK@W`^YXOGdw(b4ZrrW|2wHa-EN)r9O$0SDF*T*wm?JG102K&b3<&Dl z-`4=WFdotYO8_Jdh6`k0tkj)R2B3tIhXVk)!nH35L|g@R1qc1%*6|dthWA4`fR4&w z4_A?r2|$%|bgX_Wsc^ml@4XEI_OR7{b`AJHcw@o1l9n$16i~??6co}^VDPu6x9o+$ z_6&#j?V+B%BfyvdUG8imIRF*W(auIh6`XL(K-#t`27QjEu{ZVCk_2#T@dqIr_lctn z@>Zc#Q#RZm)6QkUdm$Urk}5DVmO^^M5)APK1O;QK3>DonBAhmEJ3`gvmCn`0g}?=% z*O5t+&)67Ego`uh?yi}HJ=t7|}d_{M%(cZ%NsaleotncG@N zjlUyh*^wymCUvMD-4F}_kNDbc+p>rC=`$on9T2Ii!sKygeR zMpq<{5++35d9S|mY(d_I*NqH~&HD`)sO}ch1JW)-W9YLF+@5sn^Hv-$d&aQdL<6S3fU`IT%ed9)1YKOijDC@1RY8 zY=FUEIy@QzX!6v1$rGzLwgF(`$5H_%x=gi|FlG#~bE0q$S2~Pv+f3Z`K|opCYKT0J zh&|wF4A)2ySSFhsf|r8R5dNq~97_0(JLP1S68NqkEcSK+M<%jR+(Wjn+0j(PX z{7B!1)I%OMxeuDJiR;0t|LD;e7$shyJMMoFE?!DXPD!DV08fPRH35oW6*BA4=m~vk z*XAR%dEEgTd-EXS(#PeA=Yf84K%oog^Fj18DLXSq1RJupwi1yg_s6BtEfWU7T?=(U+DisC${AzEj`?CMZEe;=OIdHY->Q_tZiv*`-QLSl zSpRcaXw9%Sj|Cnp8v`;da6){XXF*n4FI?Q&oS|dytqTbnRb5dr)dKM*p1P|q8AH{l zQnLyR?-uO7U}Q)?Z-}RPU+xwiF|)Q>^vYZBzdL10$F%k0FZ+f`yJBLt*||BrmYb15 zFVC6@c*QHjc=GCqk;n|d*@|VC7|}MbzGj?32s9Hb-&Y6dSaSJ;*4Dvv;%!sZq2Yb8kkaQ!UpjTQ^~ycFw%706vDFy8 zSPJXxNpo%x;*W=_Q>Rc2*K2Gvf*k-24EGn-8nq^gt*?F z-hkWc`-?xJ8IR6@TO(FAAofh`I5B}|Mx_Cf_w>+Kca{*qy59!xJnQe7!{P3*%N(0s z*Mrez;?GGRlcE{04P1;YDbUQ3dfR%ycu<#oyZF_fxFzOfLM759}F>B9ae~tt4?V6MH+UGmOcVTX8 z_2F$Z9(qY*L)0NVctbG&Y#6fevn8K8xVU|JZ2vx*1Bg}*Gx|>xuab-q?1HU6<@fxe zW%6tyW)yixnqHVAuE%OB?${9m>?MaZja&5Lhb&j>P-(^2U)xqzR6J*9YNj`4;w^Pf z&dvsy03q?$Aw^Y{a^u=n`X^$KG(dd%;wN8x`NZ`k+WV&i9kpkV9<1KSvd+^6c=UaG z0$vv(hz0tXT)6bOzrjZXg`H<`qWClbtrs$6v5*}Z~eIMFCglb?YsH&)Vc=EV$4%+R$5%(8<{Bf44nfWKo z%<){t=zE-)foyu6#XHde5G^yZ2S}7ssT{&#gGI&;{DVcCUfi*x3#-bjdJXCANsmnl zqk1;*MSnOT6$kD5;HMaRW65s3?*QJiv3%#Ydmnl+>Jm8r9i^)`6a&D6J`5;N;iE=9 zkBDSzw!+H!^%q|dy3K#`K=}P;t7qN{j<=~W`OW8Tb84E@G^d$>a%}UnfHh^kq`8H~ zUdVy*8g;Ccl$tsiI?#Z?;E*~yTU!IH#<0NEZ!9e>R;>B`SL3wQ zlg7WRWA))`wPx2d&-g%^)2kLna)JKt+7$g8KiJ+XX{cUe9n;~lL?XAh%+Y@lT$KitIK_4ytjK2KoTYCQ07X^?}QDHHN zXY=D?lIQN}O1=SJqWNs!!uBghkZ`HMtgx{Yi}nq%x({)3q(~V3dBCf{0LPpcms&Jg z8Uc27)S+T!H5q_;=+ke2kTS4A*j+R3r}I!Ltowb1U@~|$#p8tRO^NU2nRV}uZLk~g zq@JM>ARY=j7JG!;o$bie4XZIo)0%pY{KEJT&tu&9NDArRm8xnQY3!sC6o`EXFaRD1 zqs#zD%(O96^XvM(Gc=OqW({MRW6t159Fq@tWj|~_>HoSClg_NbfzFX>!km*0CPJv-BJDb90qzR{UyOR8*Yf zz&rua_shuy1UU~- zwM_K1lM^B&`oSxKS8}|1XUz2~=o*F>`Ue_=>^@4W4dg@33e2mtWB9FT5c1R)6O?nRB2aywI!);MLRRQtKQB_&_o5eu?;~WnTAGKnL{Ut`$Q(tfqD9a8R!A&fIC2lEFd{B0E1jzr=lX2 z0+<351d(Ur%w1Q32?z@APPa_HL%0k^@83g)EK$>ZD7*yE;de4aRG2;!fFkj*PM9;Q3~Hv|$m zW_Iw*xRcr)73FDuz~4%C26a#u^cHpowJLRweaDWB*UomM?$4b+Ki$&S@&UD4RS=Ch z5$q)eJ0UTg(h~vm2nuQmm|#l_YaNwJIjBR96~61f!Ci09&COYi*TVFznWJe0eE#{x zti$*H^_o*O{k64p=*)S<*D#By)arE#*KoLg=I(hJ@&?z?O`TKjKez@!f zLF^40EWmRvR+^nXyU^9a9j~qFi4A)xAlQnHEg}j+?V&1-&#v0kiX6o?PR z07iso5>AP4WW=}$(CM3_rbaq^_yAd&8&VJ8EwD!eH%I>CvZdxpzY@W2ph<8MGy`Pt zk&_uz0KETNFb+$A?AN`U7X`!ck6j%vo#dw}m8pW-q&x2E73_odt97f(%OAxG*znT# z-#AwmEd1aApr@ZMFD+Id+`n7cd9cf+{LZjch;2^n0h#1#oF*Kq%}P z){`nh^weN<1OkAjZ=y@QOxGlCm8>3MhQQDO5de|*WI+zyJn1$BO>zPbKs|`2O1Say zZL%(Y@W_$WXTU&Ox$JvJkc2CqISlQTv}5xIxa|hQa4}r09xH+T7aMz+!h(It1tEa( zE^2C*HqEVRKFxqWyuzPx*C=X)v|fW?+1Fk=-LV?`@3*G3q$IGhzRsLQ&3^EK)`zQ{zM-*@?EIzH zG}KY=A(51aaKu?DU_yoN^io7#euwzF{CA1kgk-{sLI62@>iBUgFKa96=R28^Wn z8^eeSD3OAIGLyhq8DnqxAHmQRu6kcl5ho# zedBth%DGnguWdKj*wMw&nr2KJMVoe?pw$TR8xa;j{=Uw_x}O=LmOX1*hV~hbG#82c z{d`?;f31FHY5Dz8SUqazxsmt7`wx8E$k=H4xihCJ6_C*EDJ3WNFkzr6BLhR*^3qZ) zudi)-_~D05yZiV&d?Ge>H`JO>4Cv*d`_sF3kq^)%=Y6z|?tJc7`gZj(s#Z6YS=!mY z@9pn7@>ELt?@`x@zW-vQ+xenuMg^if#jM1{7u>t}J_?+G;>V}nMIXNX79u#c4~r*q z;2^}BJ|`@b*522-#WRtx>7x`@yoFy=NRXImgK7jAl4p^~Zvb<&WAu<7RExGl3NUVGSXf}2YH!dJtj@G{v3eE^+VMC5IF$l>g~6CO3A+(X z*a28t>(n-=BCo{|jT<-4a!`19pPqhxckf>J$L5OS;wAW?yN(#(Ph$rL(9+)y)2&Z@ zM@z6RDpyrn*jRpVra?MvpoN z%VySzT05G=h&EiTf@^rLF1OkapdPkB|1$(_OG1Rd$*;Wj+L>$Eq$^T26Y+WQ-`_KD zo;j$9b75|-tFeiRGc40>Dl5v&4V)ZoyBiz3*48#y)Yhu?8|&-!*!zL~-hcSy6IS;$ z0{gfYI?Qm43*6)k$kmDApc#XoB|%JjC0f)TQqAILb}Q;PVhA1DzK15=f0rOXj>Xd5oTSTSEjCmhr-m|X1< zVj2N}{+u7-7B#n#nKf3|JTP@6#huNh6&nuIs6jo*)72J8oy_>iJ>b?!srhdCjOIKp z)IbU_Br=$OSsM*TzRDUgKBnR7oR&Fn+@m>j-pY@9;e|&k$}83%i#g;xWaJp(?#T>* zAsA&lJ4Y%iDeT|4yHQ_fm`+Pw&Q%rIGzi?Nv)mOIYKU=C^*_y7G(y(CK936dl z(D0EH96dZ19FIOw{lKmN175(3u+-1^(_Y+t-~DtD3_x*lA@rWSqG%Q;wwO}n1H42$ zAtymR24d1+Y;N8m4e9L6%!(Uzb#p-;lI`s6?|3CD>iQ%8Aoeq7&NLd{e|VQbAD_{I zULLapU0r`%HhJ>a^QYrtvd$(%SCtlTE6UCOxuPilGf;weYL(S5RFo9ot*k7&rCz0& z&{(G$tJ_dF2GnCrL#=8IK%2%YDk~?}s%l&?mysz}g0{96tkVQ8i0sCuO?q0tKCQ_L zjv-zTA3lJ}3W_NC^jTovvNr&eB-w#VetD#`;$jPe)w)bgK9n02)CKx?dl=Xq6U3B5 z1T!&goh*QGSEE!RkWXQYOL+E(@McSyIua02hs?Nq5{$PR{NUP!Mb@_U_(J zMX>&#fEB#Z+qdomw?qEZuApgOvO>4EFrmk$kAcdgik2?liL2<5nAo*C(sYmZj^-I? zhyX6HFabn_M^ZW0@*V)hjzR55w;)#-{6z!>-4|w&@#!SEe{4GFC)P){h0^dHD zmR5d|=3LWLTleg#88&$EUg(Fvf9w7CL6xqL!~`Gkyunt+#<$qKz3}mr)2Ci2E6SOz zR#ZPySEC$Vude91o&DP6Dx8xwI(+cYjQWY zB|BqFfoHFRzMdz3)?xE1n)e;-ENK!j=oymM%)yyV5&9N-0`dH%r?346@V@r3G^VV} zwy?9Nv&RzvahpfIhYo#~jY%GN#BwOaQ?w-r6uCAm-?=K)XdsBMw zt`WkRkm5$~xh1r1UmS(Pn%~pSUZ@%5TR30aq+KxY z;~8VT>};)Q=Yi8~z-d!kQ*}14r2K|9j%__|`wt&Jvu*PxyP|@EelVUBQ33gVZv<|0 z>bbL~P`2%gK71s(^?g_CI3puN;nv4@wP}OFMp*5vGcYq=Ze(h)7)Wd%1@#L3;>d}( zjYZEqb1o|{PjR){aIMSjlmPt66NrLw$Cksfd+vVvxrtuBJ`YwDmn3|);0?NC%t%@= z_f@ziwT*Ez3GvAX4jW9LefA;c=4Hc*83s<6n52n>x6(hJg4iJDLN20@l@wu;H9j}} z;fM47q&e60(!xcH65d&~DD9dyq8*ikAnEq+)hi;{!|RQ$zb@ZZo|C;_U0$-JuBP&S zwW_)=@ZB8^F<;y08Iy;xEd^RQQ>e8EMc8^#gpC*VvI3Zmr89LgcOXwwTXF~CXB>NH zBP(Fo!(KyC3@BUHk*jFH&`5f1{xb+|YAlF`8GPxD*!8c82sQ1rqvf0hZwaqNKuiWr z97U(1k5DPZ;e5=>1WBRL&Z1gnV?xbpiSYgrgh-8a4#st6=4N0#>qIE)R@pH*ON2i? zB^!hi*6~Q}#?`Y&PfCHkN(us4nZe+dvHs6nPX2E3oC0fHMh{MQPzaCxZz(oNZ6B5bt*X{=4@{6 z`ozqfjb}14Vp3C6vo~+v3^Xow(*AF!P6nVI&-eM~pRe8*pZHDhL4$|8`S?9sURoCa z^`iG_+PE?F==5o{aqU{6r<9Aj``&wL`h7F#+wZ=n^&8d-%VuT*v=bk(HFI`Gx*#To z#>S_nOrBh;ou_mEuZ%#WPM@%_LBSqwtBSHRcdN>a=hoFI`q$T~E%opjTN;>>x0yW% zya)BQ@u6^QPlUB~Bo7lCvISAaQ%-pi zTt2PowYOfOpH}^Zu)omtgFv$Nx|x{~R=1MN8~dFz36aQ9`{xlRrl2O&9ma7ckS-kF zz8gz#Bh5gxznm5$$M2rFwVuMldk72sV=*xzi?y{iDXNvikWTv^wgs?rumLz#6;uJu zcBx|~OdvxGbNVJRmbPT4Pzn6^nK3Yc6omEtyfp?BRD!U;za_?zm8%;)^}_RrN8s92 zJV*Vjge0xC^q?t&W(UTmR04c{Ca(5QwuqW&M5|W+LVm#E9~|Ki%mNk4;1wip5T6%z zwC8NuFp#f_Km4@%4!P{005=Nk>PgAz7pO)^8d{HYQvq^Cnigs{D3k3pAn>3u!$Kk2 z14B@w>bGgdioPu~b{-)#d%3NZ)zb)nUV7x<0WoeI7r@dAI27vIYwihfC7#gBvuF2x z;i;!?Ua?{Y8v=Fc)X4z+g~#*Lnl%N{2?>kB$BY~5*~M=LRv$aI{jo;%%)Qg-j#0yC z9wxdy+qc6=sfdP;96?_IT*%7KiYgHuOCQK?f(hWc1WBysFwbPC^P;*DC~GDsZUl}~ zbpA^JK))a8?)^kbX8HzoMaBQrwKZl;w9O&P_cL>%-ZtJK_#Pq}ofAa+roda^)a0v< z$y*Q+p7)t(h(F$Ex}ie_>MA->o=IiZ6*Oq@09w9o89njbEK*b!QyK#7I5^k}2EY`e zbT$y=8#&&6QxfHBj(Go~N%LZ$KGtiS0zq%k_>pvQ!&d0d0dfO^%*uqnotW?e4}kUS zwhnf5IQoDniWwG5)CB`A^nsa{=EPC}BOvAF!%tpOElEMc`}QSYUtcPyE~j;wXXw3? z`{|4LBeXj04DHU%q=Wf6v@t80K8-s_X|VpEyWlOT!$O4=p}4phtM@b*y`F=e%%4Z% zWYt7*QHcO1G5|wi2it>Re)^sg;}gM9JV|~L{m9hb8Ag*Fei(TE02E>m0`aGLn9?`a zdcbjv|NQJ_a3EGORD%h!Ll7rmxi>K;eCNf-yybJ?BWCq#fNK!Z!v|^wConB)gUX7c zJHX_$%-lS@H|*Yv$ z)oTy$-Mf6zf(8GoUBh1;yG}2FzdB;b|KpE8^6d4)lGxbgzdiO?L}@|6Z5iol-+LWRyPcq0G0pX#B;C)IN(cU0fWf zd#~<@iWf^|r6ocS%f@z0yehCNw}$RrPNrri0wTDoLO~aD3aCqfH(UshQ5vLHqg}hu z0A~+64l)19k~~VxE)W5N-~a(t2_fi@g6@CvNt$-|-5}x$%FfJzdtsH}t=QV%)y+j> zh~>&OTUIx)bbtvmKMfAHV*v&w34Z^dee^Mcc}3D4Gp4|8uaPWVJ;2jI+>Y{9V6xTK zRiwgx9XxRA;i||I1eVUQl%R>j!}H;1q>K0)dN8m=yms1uG?CIXF3`;njK}-J5+EMe z%cbT#{BChu*_h12!jwkBqhRTeThNBONuvi2d;^n!Ej1@cXIFCf_9YLW zF64s9X^u|LFe0?YDyV?cGt&VYV^m?-sMk43{EcV9I{zSN=O^J#VZ^KCKo;v8TM6RL zVtIL0E7ABSO0QKDCUSW$$7SAS`}PJ9iLzP+ovb)dl^|x1&U}boUGM_L+{ghudlhKQ zxl<=-Xm@8!{L;#qBRPtqK)^Ymk3@9j!nLYwv#XUy?1G5^F|l`Zrar@m()M3g34}nH z0$|nJ=H_;vU}GyLdVL4<1<+MA#h*Gw1BVP1_p|F?NpY!A1u!5GCsqTPS(*uATwYp9 z%m{q<&DUfE(fdgEE@TI=BX@5r8s+In-z6O<3tdC9M_BBxHV(A- zISqv?UoArYwm-FfpH#q47&*8Ht=@2u`|MhLA!iE8`>6e8-WWNf?u)8_P6r zUsgx!S0@8-C7uY3Mb==^gQ7NJ(%(FF+BEB|q@-YFW%)prLK%^jnI4p$lI)HZzXeaK zEb`*OD(o_(z(i(hYL@Qc?_UUT^DEJs8>$S1cXq@i-;V(tJYli#jg>zKrxB}t%}DSH zt!>gGx`LE|IDqK0&Og7liVD@$5dD`^siKM=cxbvr{B=P@@XT~TL{k!z$xy8zbNI!J z(z!Uz3LnM56-*&S`Hm#wSpTA_TuACP<}xnBmf}(kdAl*Cxe6Wv`)No zW)*~@N&)W!Jz@RNSOMH7PODe^Myr2YLC-z^5cL}bC}0?23RVqm6l2dS93dHzo-@bR zFff8W0;ha^jLp|PRTEj1l-xtBmsZ*O}}m) z#x-jwn1SWXmzN$mcI?2hb1Cnf&OSeR{LPd4d;51C2&myn7IqGE8g+EORVr(?)z;LU zHZe9iY;JD$a_0o`_nuf?MTHymxz>EQBSil^5eqV)`OzF{70!f>*N)P!E+%_D{MEp% zFI`ze@l_W@)Vew_5hbuv9uGZy)VnWZb;8771t20p`AG`llh@+$|ybXXXIy5g;$D4!|Eo1R(x~bn0jv zefR12U`osZ=j(^@MW5O}u;wBBlRd6oN|K6i^iV()L&{LER&`H`jTt3h?cBB;0VnS+ zUyxqo<};^G8?&URUaj_NndAC7sw=4uhxEzQ6>j4EEwO5C>OJ>TuZVD}Q7HRYLK5)7 z{P`XosYjg*z*T-4&^I=G^!@i`JC7efbtv)NA93j!ZztvzJ-9fpV1jq=h{!&p0*A#U zCU3vW&G^q2Ur|+M4MKv2CnPH%UYPGMl4JtWJWXSrD*jd0t0|#6kJ74($P{jVJ=}%) z%K!jC07*naR4iQJ8DK>PHKpX`?Llw8{~DQ@7*Zp2&8$0SDpvz>c41~JxnQNo6yFG| zO|Di6_b*mEaJG7=0Dw2p4uaC$rWOr{BtRInLu@~0>LkicN~1GasgfbvNVUF)k6WK> zQCjnLthVhOZKzMba7usyV^LwVg+Je6j+Hmuu|Nz#B#}5=F2@Hbh3nt8t(z&r$rB1r zb7)*x7lAgS+H(kFoel9k2MJ;V%7op;%nGP}`NAO92kw7XRxMZl90Is7zkte1%0y^j z4m)gvP{M5RCnt^&_{ww7QYk=x7QFvDSp#PQxDBWi(-v3Ul(hHxWb-lai6gX$+a>m4 zkXv0|O`pvBfJzF>NTIByocvP3s5BMXt{tg#{%wBk05y!hb?@p%{(f%k!JrEqkr`18 zFV~sci~u5OD$Olzo>$c<3LBxW!D9_*lj%A+QLj{u2MoM!Ku|LhG&2ErPM=O8y?TKO zQ1s8w&--ouoH1&Do*#Nc-M86=fP^`FZ>} zRJay?&eB#Qzs)3=%it?=lEzAN2GSeIZ0M&CzoK*iA{ipeVI7`TaS0?C{H$6q7IF!f z-)h&#D_IjTg%gH{A#Oav9jlZhcZ7v?zkDe-W{x)(E=+)g=|f#ijX>4xx*{RL@Om4G zZ@&4)0_vYZX0ZA4@$+xdd^QN2He&|$4DZeEp^>HKWh>r}ddaUvrfYQ2$pBoVf$yyL z%8Y=4fpG%}0q7c5co0iVFEmf&C||STnwaqOn!f~u|9E*0#aHH$IrQ3Lmafzl_y8>OraC|qXC`|BR$a`(Zv?T zPA=^tCF7FjY(ouVTZBT_Z-Cep`{Y}iwk(RT#Y$V20>C7awg8ntYQJn-d!-C{&(wWdU)pjkif_)^yZ$Ss^-K8_~WZ`4tdL&m}O!kQA8Y~KYmzA2Z68f z`bY0#Wfp>GR!8Mv-sI;dl_4`2moAgbl~Z$Dq`MgmXegk8-CgZDu##D2dEs3c9PK8_ zw{~tZ0v3+WU)9#uC&7)=qx#4Z)9Y2*^IynF zCnpCB==eEuoh~^#0J0X6gK{tc6_pAw1z0*WWSTwmN%97G(0qvgaSL=Rtdc#Dr zierA(f?PLsRqAJR*&+iNClflfWe@%S^-nZo_7gOC`~>QSg?Sw=mzDEtBo%)}~oMk5FIA{7`6l|psLhAe@d8_6}MTK9>zIHMI*K+hb z>;A_hps1-S0>PF9r<$k?OEK8pU#eFkEV#%77yX!U&+SxFR8EJs?Ple? zr~?;TG*Lc~?&YKAJ?R>kSC6;=CNy~1KuS1$n$l8I2MV)w z!RjA`SW_goHXp`+00bsSV{>5;$d~vww#P6;zYFs7VN|F9vs6!Z_I703O3yDU(E2Ga zFQ;c7e-yR>h4j%^@4|J~6{~+9kETJA>gS3y?PzgLl%;(U`+O^~I8GlrK}+BMm?l3s zord3f6HY1tPwXk~sDuXsmnV1|oZ-GT6)$o$|8Q|ytd?H_@Li{dOrNF&BTSTi?(@j+ZQ1Vv2~MV@$Bh@7k@~pSFsP z11BTY^~ee1nQ`?m%)hWAD(dBHX7|v^0Q~9W>b%&|N5J07s$54;PsoR>>LkL+I(-IQ zN`_${i~HGizo(Tm`GD|?sPFjltg*uFVrxfXZf?}y(~Aard&2+?B5e@up(6*86~uUq z=GTG@WjW_Dgc>5$qA;zI z)m+xWia0wvk5>PL#$?4VdVBZmre3yAaQ#E<0E{cw#wryBbm!H8&Si-Qi~Ly~!q=E| z>LUj;R>g1w6i9v`{>)VTS>n3pK0lid?Ab-{ee?!J4(9bcC(3snArd#ftDtQwC^2gEDne>sT5-I?6t+_x=A(vtUTGk`i zD7QvBW|(1bcRLF2t>Frmtvhy!i4^E~0aL-Hkw2R$4fsg9f``E^%^pxorE{ z&SvNsxv#=v4aOZA_;Mm>Og(5w?DZ;~xf*hU51^()$Ahv8D9$%CvqB#y3(I4f2 z8ZiCPbY5)10OV$6leLu*h(!Z{ZD3UZV((;c1(kp$jL915Y)Y=UNeAs=6T#H<$T(ahvDGDSMr(#Z%jSajDP_9H|qY$K47o=$NwM=8?HSF}f}sZppn%2-kX zp-UYWY{ppebIGjb=8_l^*#PL5h;j*!J1{eBV0W4H;oA%7$8Wx-x$|bzlmXYys8Hp}VUg;eat| z)AN?=0*wC5;QOyfnOOz2YW*QW*n|AtMW2(>5KyM3P7pvQKn+;= zm1boMHGnpCVIsyXfgonm0X3-WDGQeAJ^S_qRRx9sH>@ck6WVJEbiDrom}vK^^^|os z6`(}S1YGPh7A1?joA>xos*DK}2m1*N??o}u(R2YG0T)X*MWzd0}udN4I_)txgJ)9D4;TFPx^kzm-O!ZH|debr_+C>0qq*1qyr#ognKsrqLS&bpatbrH-k%5nRwJvP*L{-`) zO11GB`LA{T^09PXQbCDx;D%}d+!Pw@N!JWKW1@?3|?&u0P?0IqWEzs{)zebE#SVp(2X0)##lIl4Qe zekM|UjZ^cs5!U75kWh3D?LohN_O#3Wq16e_3!U&6^DHsq|Z?NQqd*o(eNgfF4%Fz==LfL%YkR>4PU;ut! zzKmXe?isp$%1!k8J98kC2QjW^&?ULwQnB){c2D@6l#9>e5B~*D0E`k3Z{0~t-*}g9 zduR&X`uN?X+W@AD836G>rP4U!B8z0XKbMaF+Oo!$M^ZVhf8{(Z1u%dP>h=kdkdDAG zw7P0Y+@77wZXPkhsr5ab$JgHoWS5rqtyR|sxnYsv?Bd$)4%kA)XbKM)1lbz+`RAYG zgD3j<guTc{DS>2%$oJa!lO}j{N>&_5$^(3^XqTRD zBTbq40G)@l;K0Tm!nIB6lSFo;`=r`i{%XjQAb=;#25VKE|8G|_HYxno>SzyNq7iklDqcp_n=NJ#|1t%dgm#z^)gXjOF6;ab4_KK;le zGKi0^yT){I;5Y{q#$EV9rb$ zJ?&0hP(mIaw&Vi0 zz(9XD+8>ih2aYDu?Kkxo#7iPBdQ_I3K~}ErWM=CKQ9^5dJIcpuJUcm+yaRj$5#<(e z64f{DNoN_A(5;IHjlcU&+W76y6wF?S_f*^_N{~yF&WP^eJg>67ecdP= zYJfdp09Y?+CkNF**60HVsIIb-e8EuFu|At`M}wF+r&W~zFQJ46x^!W41rd9pxvioL zLbAe!eV0n>SN%>;Joq5>8xTq#Em;W93arFcuq)si1SQto$pxeGR=sFdEP4$ls#e`d zD?eLG+kag}T>`sOURoY)&iDmz#yr)racDgVaxEeg!ez6xoU*en0DMLT)c^pB;HJpP zhx%X?Bz1-~l2W~L?b^L;)dW$4Ry(DaPe>OUA=P;7u3=;a?~8AL*-px;sp561Xcmz6KsxnaZPIdfiobHRdz`{ett#1@}!XX+e*>th7Q4jt+p z?B@1OS$^)yDn(UyTezOx8-nn#0lmlyt3F1h(~zka9co_C3F;+?7-#{Ke5_0P!2Q<; zdYh{kOXD=+cX3&o_!|H>w0_y|G=5|#h4t>jh+gF4Y)6^d#bVKG04c!Wet|;ootOfk zp`>i#^Df8%%3&-1yo6J5hf@n;FEv7LCWtXG1@d$Afc@`{>Lmt-^9VJ#CY#g}e6rYK zCSd68HvvV@k=B0oJvAb{Ei4joiFl2lwqG zPg8TUgE+lT%iWMM6SCnhsRRST(v_x!s^uD)4dG{@O~;$^VQ0;+%jvQEW>7?A4_fl$ z$1r>}(;zO2LD$aOj(t=NAM1j=Qu=WA>lALi7tTe03uzW~PMzs`8-bF_%8`io;pGu&3c^ty9FVtWy8p-g0f97 zKK0a>Z@&4a&y^ahP6@!38p+ObuG$E&u75yq;LWM0Pk*3PD!MQL&0t>jdj*mWbkpqX z-;`t`q?xyQLe@;hB8%p9Q+9UQyHJ6EG*y*kYHWxVFbD=#yCTVRJtlA!z;~QI+$kd1 z0V~K#5C%OM3?i2NK^2U<)FAeH)W5eMxx3iG4xotE?Knm~gS}CVR3m!})BqD8KPjGE zgTruMi*TYpDyR;sU=?fw@t-6i(6kyG(Ff)PH2b1$U=oa=zrO9sN9dE;bLi-CX44@b1M+w{Fb^2lVDp9zG;YEKaajSi#mN)Lshgb>8Nfb);c_`K zL1SIN8pl>Z|1*H~Ketd;Qix3WVRe8S44EO|l3S;rzx|q?pEZkyjqFFCeEk90F{&c3 z{86^l!q)9(vLq3n~ZlpA|JpeKlPJH$w0 zE0=Fbu`ws0+Cg8?e-4CHQ>B6{Xc1IBu=T*oj>|EFA3uwC*-n5Z1I^`VioWDVu9Gvs z1_fRsYDE8Jh~Pufr>GR~OGq`-NX9j2I7zD5~i?_g=zb z;Bdh}YHYf~JDLtr*>AZzB;(rt9@%eKQc*qpq7l<1Gn+}4crjh?JR?=S$wtz22C}(5dc?z8lCnjQ+DXp^|rO=FL} zI!Gdz;py4UlV;vCmWK2Vrplu7VaaC_)(!{_y{4#eZ5rQs;*uk9?##SUNCAi1!I&=q zMEYtbLof)D0|(F(&pu6oJ-Rb?fN#CJfsw5Kaq46M{_zlY7I3vj;QssXw|Hyz?4^~Z z#jmT>YLf_OFM6WK015^uO)UsN6Cr65)^34jLT1__pKjWb&I!XZ%-~5Nc4vT8#{pZ| z!d)NYa3kScuelLzo-|;iRi1pr6XbkKw6<`3va}@V#|O)3atLC05R<3#3~gOrBu?K zXP%~cue?l;&$^!$eX)=%%``fH9v3k-n!@Bhw`eO05tZ;3V`^+d=hM#92hY4laeMcH z@V`P6W=zItLs7mh5obrG&Cujk|nBY6V|z1Zt!%V2+)jE^4GV zpTCQ48Pf+?5mogY4sJ8w|FPly8y*H9j0-<*iPyu}k)+hGv)8|v{8brZ@Vpk9p zD+-8Gm5%h@LLej%(tF=cHa*|}+}YU@5^BKs{q{n3X71d3Zkd^T&OJ3QFZ;}jgsAXa zP)J`*3)yt&UNg{8r@kElzwG0C_dytIDHh0;?xB-2JdQ%~mSc3l`58ikUVbtW0GB_9 zCJ0wx2Cz_?AKbXUk=HSE-fn}AFgh#@rClL`td zL6Crmz@M*&6Ww$}l0Ygu_odN>ttn)u~a`G@xDi2xvMRkex~4&Xj8^5(>i zXivEt0wwUZHjhsj@w73U9pZ=kUVIvU0Z!4Mi{M3&HQYsgX^t3N#|gkVN-eAbMvof` zMMhUzzVug7^mqy!5?R^8{EwX;aSwD2qYhEDvnP4O@`9g$m=EzIHOm_HRdB+@u9;(F zdr);%6+JTb-}KGgFKE{LuhZ+Zo+n#N5O0_Vo_~ofYS%+SQoLTbZAAQMwOer}&< zOm0y$D>?dy*yZErMX(o?>(v#|rzpVVH zsja=4IfjWz{jXXV3*c2WE%g|$Lm6H!E*ra|VG{w+6|-o9(d9Fc7$0+;s=D%_1}K`1 z3yz`jfl&h4u^qo*;zd@Uc)2xNa`m^gXS_J4mz7XvCE~xsQyu#UD6Ut7sAv}YbF%+? zT}9CV5C(Q_N_gnwU_XptII*3)ho=$?z9mBIR*QunK|W~0nBG8qflxYYp@oY#QC=}( z-fR5=i0+|&3)s;4hjvpe|MWTHc-6sQFvz#FuIQ?>>O1fm-`dI)Xbu#vK}vM3uO03Rp-T5Gu%jDB@fi#p6P z5nK&}>6W0f`5$)H2olr+t^9(#T)O+FNpxW6HfXsb*)x z6o?;4-weu#B~Z}&?8RBMap`Y#*Z&@)yI*_~VjDOi!n|{mi3zz|e3Z|nb{ILMJrHvU z;6f=!(-6$bnY@AQ**S|0gNXok)WULqe%`hSiR=cX9RWh1ClnVGl47AS>Ls{BXHMnN zv4d%J?C>efTLD#8s*&F!-UlWc%|RH*iTlF70o>diAyTLn774BJPjLSoqv+X3CLyp= zn_X3L$=k0727JUEFu9)T*nMW8L{%}Nxv3#2JSK*G{rs+4$F^_X3cmqrva+@Q;o0Qm zvaV>@L;!TfESg|+=?w7hUsI{Nx3Q_wA__`qV_@Rfgxw^E>urxK^ox7vUEnf|M0mG( zy1blD1NpO?UiMU12!%dNp`;b*OeA00(Z+o^LX zAbw_d$PHl5%%I`V)`S~=`kmf;;sr1)G&JGCd#K0YB)|qiXaQHl*o~P%AykA8#8EoO z5q0?wTAG*-YjAtasTM(-3bG66w?)h8#EA@wjtnFv^f_9vGuO4tV?#H1Ok#NGGi0xV z%fmNV0n^jR9U9ufi1Qvru?eB%4K4!M!jyfckoLnAbmz|Fw10m(lr@IB3Z4m$X$CHP1||)eq=2kFj*;SE z-(aEW-GIf{XfxXh?r7gQ7vlw-1HRD( ze*GSr_3<(&k>*|7+}yN!K(Ag8?>>BZi`0_o_wSs6oO<ps0Cd$%n&5QV45VbF*taw`INC!YFF@%cWV1%d->4;dp*GBv z7Ht_hab;hhAJH}uJ@8Fc$ylVFty#Yu1|3N1|r&EIavAi z0=e^?H#WD?+TT}D|H1vC80kgk;KJA-_JcJz0Cx6vuu4!$TK;&qKAyXT9I%t`g!;-`B0$^(`}$wE-ardMIH+oBsn)K6_9ObC56t5H9i7PmyIiiw{br(w z;c=fB@z;Z`{d?vKTAGnUeFi7e%=ccSo{2Hw{?|(5l!+?2!|jg@3v5*gIH|m#gx3DN zkWzQ=r(VMc((O+@h%vbeB7)8OL;@Ogc6EZH9{e^yheC`^v|&N%M^b1cBL69HUra7{ zR{moojc2}X^By{c$cwSzeiRz$DJ(b`E)VPc$#5bKOB>dfh(mUn%!z9l-erPGgpdYT zLjOLa4*FRKPLcz3I9_^cGL!{9Y0hMM%0w$CaDeE~)vLh4!aJdG{P>wy zUV+k3*Cmq4WTVb)z&*Sfl>0&gZMH;&1u=xs#iKKiE_Ja?V z6F0-MVGr!?y?~@r4rB`Vp5{zwfCzARR?u^gO{5?ond)jaz4O^JTE88Z0pL2Y37LR_ zb{wE&#@1hrnp!GOIY8Nac9I&z2wNpcmI>S=?uo89IOo>217$$kYxWWt7#l%%{O?g( z@zwW~y7K_!;Jlz1)ECvH`VvH(V=;=23!@RE29bvS@dHU|K;&eAbFvE&4UZNTQz3%; zs8Fr}mDqJMb5xF3mC@UKH`DUM)AZC!|Dm6keMLR{L}*$5=#~61L?7iJgMRZ|+Cc7I zsiEax{X`$V@CN3tk)C|#HJbeReejG1M?p+T2nA57&tj~+sm9#1HG#1C$1bYyOb*#$ zw{MS~KHL8jD+jVa0Q8x;Dqw9< zXtG-XnKIJF{OO+$pUHpPb{X-*n_yLGHEmkDntosK11_y-@}pD1?uQa3Yxl##0hk{Z zm{>A?_F5Y>E2L7WyzJ7Gm#334(g1qw=8_XTE*>!dtnpk|qwo zh0T+x)K#1TCnu){1Z2@Pf@RNtG8&P)z{{kxL`r9JzI+tRC{}EIOF&p^ss%FQ9eGt# zJ*ly)@&odZ^z=c0+`mEzEF##0rayNZ#YP80&a0tM7ObX^zFq@efo3SK zvy9pjJ3?DwLdL9c@dw4j>Rg!Q?b=3p2li4;agi`Rgpkm)gfIQ1r?)c${A9^*^ z)jiAW#K>#M!Uxr*?f#j^REOPtB@~exAp(?M3H^(~95)3#k=jC6h0Ct|{Wq#a^if!x z%%Ab@2kWoI2u&P-D>17kn3pvJvu4er9h)}Y(b(AF(-)y=9l_ES_W!&#GGuYoe>mDK z(WcBoCU?*gT;p( zvX(8UZ52gFKX5V3@*n+3}WnaUA~kMf^XkNd=8ji|oyR8d$0g}NmaHzz!lvBL51fC?LNv%%cKo+}k2MxoH-^m4Rd%(cp(Vn#9=XNDD z%x4lzo>9)#CBA^JNPcE6K zL%l?>$Yu4l_^fM^QGWrFpDVu;#f4?W5gC~a#AT#0;+J1wZ8=5;ylYfIX>iEE9>RPz z_1I}UO|9m-9bbO#-)F#p-KS5VhI0i|x)L+cH@?R#xPiVdsz)r{F!4rV?t8KAG~&Hh zRjCkU%6Qk$pD62t*np^D*}pB6Qu!U&yL&gSLu?Mj2}pJI^?zX4uwhq3@54j@TAMmTixf(xwgKcJ|@P+lYEsf(L}1tvv3P3l@iNMO>dWx4WmmvK z4-it0rjk5_!k-U?xVZ^rE$U0_tWadDfPeUX1!w4nNjJcz+Z{@N>|91$Ks10WxF2?M zbEd6-{zV~y9{6~HsQ4@9grlt;%+avJJ#sLeqM%)_yNBCMEGQ5=U{JqU5GQa*gs6eJ zuo2(3_XKp?nh;JnfKJ0|AUeulhz=wK#(+7Y18AmMQ%ALMBU}v!LYkTy0p0@aLJKy5 zW1Rvm;$T}gMjYC1Qw)6l=bg!=HQz0!gaL`9K#&`BNCGX7QECOh@KhHRK+Bh}6;=`c z3K!DAbnc|Ol=hUKrdsP(dhDr(XwFA(QC!bR$l7ZJ(w27Ea*Nz!oHMrq^WKCIyd1D+ z#n(U6{w=%UYByXU{sCi$lA|+Z{4(O#5)ayr86D)O&Mg=vmcJkxx3C?=5DF&gNAG-S*!8Ks{zbGanU8$yARUFT1Q(Hzl@bf=+ ziH`2Fcql)&gn~kRr1wOAc5?fCE&fX*XDx9iN)vlWP@umjr5wvd2;=JBs+x+C5uxEJ z`NhR~M%^-9T=oo1fBA)*>QuF}ED-hT_Pg(an9u#9U^YQNMa^7Dh|7gL8S61@hyy^Hf;{sbG`Oh#lCD621K?+|2{$j2iXD zxt{DkcJrB&6yGb3`t5JW_&FL1?;zCPYnB^8y2r{nuEY}<5xX*2Lva&jNY>c4HM z*SHQIJ_6o7Ymq>#j`%K-bNs2~ro%fUm2YYkj@uR$FrmvwmKI z*fFyoKzUs=UZlBM{=h`S3Cq&zA%{*cE<=<*$kCf?sjrtmMQdYrGZeYJd}P?H^tLlU zi_DcaH0j3cpa=*8Kr|_r(l!A(xFaa{O{+FiWQ0HCTy2sF4@S@)P`KNH`KJd7OOGibbgb@_Kb~0o4@b2!f!tyae&)Yrrw67dj10NHCn9g9lIrtP7UGu})Y_ z1iAQni`_dz7Ygv)3Y(e(wy&=*E&X*B!IMUpwYKXpfQuLC;!MGvI4Qh1dM1<3`9U-NZKSn zXZcz>8kXWWKz<+C3*hbz+c4)o@Y^3DY5numpqhA7Y#nInk1HTUe_wbhw1L89Rb?HW z$tw}_Z*2vFN}6C%LvGcuy^edtC8bv6IvpJi;ppyT-29&8WfzjKzn37gv^1iwi2g*| zh5#f%{q z!7*5HSX3lkJ08LJ@DelunX@x5M)1N_G#ZdL57KjCE)6&a5TPOBB))MD^~(s~9y@$S z_8~|ppbhCm$YKRm6qN1;N|s^Woo5RoXV@Jp-T9j4|8#)-mu?ic0DiT z#-E*Z%}Q%C($t~?y7ksc(8h&1pg;_A4J{hLO09HO(wU>F@Ci@_E&ycqVDC%c zGY}K%C?Wsn-TDF8nny+lOUSxi-+8fcRM-Fs#R&F)IfBZRKsl`xIs$tRq|^5OC#bZ% z20H;LD1uGSrh`^m^Fy8sLxdrj3j*Dt?G|tX8Xz91Eh{BeQ9f1Y=TKFCHr3!*TUJQn z5H-}pRqx`j7n2KiBEG?aWDc=~fT87NFmiYI_N3F0*Jm8hpr4`SH){Myf`2_AZ^@s6 z8T!IH5oVS)(2HoGQz>b*X~}Q2^7|!}o0&yDz-72$>J&vWNcr$V+h0RL6c8 z8Z9iWYAwv#Y;r(!)z&paBm;j1SgiH)DYe$V&Gy%FJ7>R9a_^+0zMecrDW7+Qx!?eK z!KJV@h!TDK#`Qb8;<2%XfDK|y48r^40;fYqkDW1VsMRE?s>%n1g-0IDFGQ?kQ@W@b z_~x5A&T)}(FJkBZVN+wXcSLj)O}z0I1oT3PX}lvM;TvO}4B=g}z8>+ZtOPrs?+HR1 z956dZ`miu!g+UJI%j|w3E|eg2`1|{>ztWD)n?WGhq<9C1-1XdZ&sB6-unRLy1V9%~ zrU_Kn&A@{{{7`dr+0q*{4Vr*xg#Y!lbATzD_5(ySyr_yl#=7E9I>?EjpcB81s37D$&B8KwGqkSspneg*rx(}+PmC(14;iut*!(M&tfunpEcl;?!Xl8fr0tKz3ZCj%rywjmm=^!=JGnqXZ{VWA{y?GH`@ zYRNY(KtSc?6u^bB3%EoOhv?cqA6)%?yk>ZAEif|;q$qG^Qcq?h%3xJ+OGEucKY#zc z;iG<~|*zwncm`A`bd<(pDx52w& z3qqVNIP8Gn?O{p8CVTR3o{>DW^!X^~b<(6!_S!XTX#JYi3@cyZ=ydyQ)2F8y6}Und zCIa9JO{@v*<;}pBZ@+Eza&ZpU)YlJlfyr18S8owq#%K}7I;d6O(xDXo=;}b7pDZdB zI&uB{{1KeT4jXy7ro4C?^bc^v?j1aEC&;e18`FZ}E@h+P&( z275y`9Y-v*Mu@?AqC% z0RLG6Ny~0UIUaxot{LI5Zh%;vu#544H^bQIFgmn9l@9MeNJ=v}A%TKnWo|Aw1!dG! zrzRihIFy!E!gTI?3JLb3>Z~)gZt+T5@!gNK?YHHWlX{e#6=u{6^60)v@j^Kb%IX*s z!tQ_);-E{ciy$qA`q|-WGQQ#*DG4S7-_$=co_u7;f7)`GpTaCyB<$QzV{g4l_-_dtz^Aza+B%z-QNP-kcE@9(q_)rx}lJo#F^QFnYimAr|)b z%v#q%*B#}48nE%zR0=>GI>Rc15lTm52%`!-ei^m^EWJfMP5gcZ z`n%D9zR^?wQBu~Ke8<*?hKYV(eyOvP~PX5(KhqVH(BH*eTLtCk^*w}nNe z(%I$TZ@u%*wvNhOiM(^YpI4$i6HL<#T<{F^ii#RsT9~&fMCoGvzh1+HB>?LJ@G7WX z|Fp2Q*SKONnuTb8M{^4(9`fyAR~J~k@e*nLsvBM5g0Ia$0NCTB{_NOpI!PMZx@`sd z`8raYR-;^QML<3#2GZduh&)ftth@+Bt-#`T>}UpM zWadzR=p;l0`y#s)+y*EtqW{%(P4vVo^U!uR4T_eO&mv>O$-6U}q97=&5-2^`kzx=j? ze*SH%aNuOEa9H6H?)T*O?FVx76-{R^zx;Agy-M{|b7SMbAx?4k@eiP}*N>;(eG&y? zzZx_2*QU+%`{G}?sakub^1hj~XD_}Q?aFP)Z<>5i^Xlq3Hgo1Y1I@O7 zMw;-@L-xmhU9#J(sVV;1#F6BU`1JM6rbQBqv+nKHqGrCvO1U#Do8U!|`uO{bveM$I zD=hNFk2XsjP>7GVAOKQ;K;GK3nckfoTIW1S{SOyLIhskY zfABN4qVI$I$I&g<52YTFegyqFIMQLawaj|Q9O{rYYVsgK6z~l;Q)Gg`$^ni}oDjXw z(Mk9lumpF9{hxC`1pwV-;Dogj8K_9Q6K+I^Dpr2C1ik|tY2e5Kh+bGl&DE7uSyV_1 zf7uBC`CCA^K?w^)kCmP6=BW5ycP?AIwory99}}WNZmBD&{2E9==>TG8`m?uE@0b7t z>5(`;+BZusE*%ZJ!WrVRo%Jx!uF}4UB{($)_oaehbE1&&07#pdc;r&tfN^Q#D?`}0 zqq`}cFqem;6K>y^PM^+S4aL%GvU9ReaqADwUAshny41dP%jbtAgKpQsx zN(vibU*Qu#x+jeZ>l=`1RY5)#9o$7H;n1hBq=H6`?g!;8Rw%=L{4wjj0c_<}tG5!f z&=dPa0hzNsw|-}7u!3t(^=qfhN#GqXv+|j}&n9;bFa@iG$zX9=H5C?B3G0DUC=ixI z@v$17206+cdmv>02`qNt#2f?uV^M)UcExrq&jj+XZ)~9==ogg3#cv(ps=?WC1lM3h zQV$w8tQV{me8?SKh2Pffp-&h6Dg4yiVbM%L+<@ywCsI;E7`P1_-@X}tO~Co+KTj&p z0glrA@_gVw-{Ztw2*^%lH^)o}SU~(@0b<7-NZZUDY+{JC-? zSv1wr;32()eYF)D$;c|Ed5bsGoUm8K4fY-$PV@#mHb%qDjsrGv@1_1rzq;B6mpsgiPb{{vqN4{-2-$KBTq(1;SV!sn z;I$h#3Ys%_6&*TynyeId1rDxm52Tzv{fALIP+plkZ=U7vixzp6mzMTW z*VV<>s_UXKA5pL|@oi~ra>jdThwlr_BfN=Bt1-dGGh+@#7;|$_y;W0}q;PToia>S0e(z{@?(@JR zhRem@(5nk;azL*hgYz@9H~QH**gVr`gg~q<*jaY;;_|LGy{wenp&;i9(=&mHjGAdz zOM59DFdg{M%RP$$Hsxp1m&f+e!tXw%DYs96vzsb$PX|sQH?YxxkWT-#0o!0TZQFK~ z6kx^o!w$I#foLqSNNRvkSFYGh!C?7zPm;c=ULLe;(_T`+!U7vW5FyQgV@Y8lzR+8Ur=k6OfRo?}jdr*T z23G)))4-JgaCunVX>`(v^x;km%0OF8Obh?uNr-dpkWtJW#8g|WIj!BW3jutrXw0?! z1%XuwHvfu^2Wj!@UBZ*1ogg3(Zda-JGPGClv=u9MAjb1K61o%<9CTlks`Asu`X*Z+ zUssy`zgsCX#7huT(wAb`8NibJiT=yOJ4Xv0-eLmi*r7B**!0HSHR2hxYem#MKl~0# zAd3Syvj)1Rp@rssyOvh0-%l2{HkC?Gw<$NDJiRG($`t1_$Brdd*QkfX&;1ZMo(YE{ zsIQHcm6;2CU3hzYAsV4C`3DA&Z=fG}dU_%PqYF4%N)Vz}c(otzyVOXH$LANCFQ+cx*773JNLBk=P7o*z=LQ7y)M>J!(yH~ss*d)WN0 z!)Mi1nXF^NyZ*m6f`BzQueNhkJo@(gA1>-D_!kSOqxbD%VVKHy^BEX5sCQ6SdiLJd z=0;DLL7j^4)wlnWwQH}qiAh&X%f}x-Y4gdOKmFCHsq6n({7{N?_Mm#P8$TO5_J`B} z5rG1`NIuuE%$>aE$kyEvYpA4B$U%b|;rZ^hT^q?aA(H-B`x64kV4)Z7N;McMa1}Ek z(}I-4RFQF9>{vIh-zz-3CG?7f>>r3}`zHp;G4mc7Ilm;o8dS_Ck!+6=k0Fk zF9k$Vj(aA9W-WLjwObCrKY}xj#?HU2s!mu)EM2pc(jgkLMBlC0 zdco47*hb;_dh+bq^QTOi!h-elC+vb_gM;sd%jM5NyxFpYr{w2A1Ozb=0IrkVCGKA* zNmrzk`|Yz?1aS1wNpNbKC~07VurY66rnBpg+u~fge>ms=c#zx=ceGL*wFRtnG$t zPVx&cj8NC8e}vBw-=2LF>CUNBq40NQH~}j7KrtVyHvvGlePgPZx zemKM@gzk(?q}nFL&epHUdU$x7WyGw{Joj1q4?y0X+CzM>9@YSJkL{<=zId1Jf9OtZ zlgqWhq*{zH*(47B3MgyaM#!?E7>Hf*){O@cj@C}N`HhYWphWopXZAQFH&Ii@(r0z8 z<7K_WVn!X;UI@f#aDz`7B>zd<zlfgL-ALMP8kVjO4Xo}IyB9Tl7=@q6Ut=peWy+Q+1u0lAjJQx#bk z)&@#R|HX4;@yc^=d{6rhrIW3-1#~(3L4RTt^@7Q3XjC}4yStG&0%aj+1OA%eRH~4& z4yVwWBZsIcBLhK+R8Zu!Cl?=xgaQM}Ex@0g5m8g=;w((!7;#!b?0|$ZQ3-*Bq0p_e}m59ooG;J!#yC0YCk;T2^oGuHBdVg)xNt`FyIXta|K@ z$lf$EFalfvxZ1>`BVJ(s;RVR>ZPqS&MtKZP&gXM&d3o2RyQwXs0HLv~AeZJG-a$`1 z{Sd4IUL`o-2gaBA4_LYx1|kWTXP%;hqx&JJmKJm-1Xu>!%yH%o+7wNSO5B0Yl0gNd zB8CGrx}m@5pepaU*{47)909!qE2Dyf0vg%JhhjsV$PI3fnQLBK+eDiVm(cE$weIIqeQ=P^+n;q#U00pd<%Eh=;?X1SSSJ0wXIY<`!2` zdTyC;aa>ZVqB{69VCBa~h-i2eY+J2>bp)F)Iy%~u11u2i*~S{&2u6ymET~3A!n`6x zXe=lbB8vuy062o4Jxmkrz{T*gam1gJTtEP*A)eV)p2cj7Ru^yYNe9z1R_Q0@BF{hv zMn{I;)L33Vx22)c-5CmyFFkb&4S=#^tu~sQ1o7O@qk~7$K}Y0L{N9|M^mq05idf+Id~V zhYuvBCy*e^ikLv)GI%cH!()KrJw~q~UVu#_sq%{`^^ZR&ZR>VYB5ZczO*fHUU@+Y< z?i%>3sQV&4tMKveyy=*goE%zNR`xYwkPLV6bf+nI-bFDz;&`N&lby6;=wM(l6lFgLG3k-0S}lV~4_`A~tq@t*Ykk9r>pn2KWYJorl*x-hptE7eM(aFvV?oPhc8@uLua1tu8i!X<~e#ick2$$>y_q%nlTBv|u0TmR;s^GY$ zTyPv3AUo^vggatS5CaAE zRpxNxdNT;$)t8KHK!2oB=Cb&h$QpHV$%5(%b>Q^bztDdlo=9T`#}OO|fk5GRPo89X z(hVjYr-r$5N#hcHr7tCyk#pqh_I3Eak)))3fwQhf4Z%_IhLr=%CMY33fqqU+r9W1# zrKt~2mEtGB3L$!M5=9Rg08f{gaTZ%OXmmXkULqMbKOY7Okpg63RCX$hj{d%q&g|b$ zUf>jrnf46%#l_;DJtaT?Eal}DsC)EE`2Lcg$64dKG8r!>Cuh!_IrC<9Ny+S*@``&F ze(^btxnTm08gnfO`-}6LmvJ(UmM>XK+0d5&%RkrAQTfta@4fe9cbfwf0dQsBrEUZD zdLXeUK{W+cec)Sn=x=lZc0r(ve46WNuH|m-Xq1i z)DJ{}rj2)?oph5IC|!~Z8D;A3c6frKtijG~^GV@7=)~U5l>G86>e)MvygU_%VyERW zGf0$U*{ISdfGRSf49L4!u|jG&1p=X=+yyl+_qBbmP+6`o7h$-ZwBxRCK;WeO8rpcMgl-($pMw2d#7^4=JHmZnw=ex;7d`X9 zL<)hsVhxZtBLLo|J3<*y2@3&sV8cOD*f%lr_1BI}8tH`_MTS#yvF9l#(l^9}2i??Esrs(I zw!X*PA1?*LQcbr`7>EtAG)@_&Jl*nj`y=1)=rOe~Ctu5DJA^9(laF$x^Bj5eu7qw1 z6IIS&<%fkv(7Hd@(bW6?E#56AX2ndlH9_BnfiZVS-vhqK@_ooz#J$4fX|#Xwuapl$ zBB)Pqn(*dp+2w_Z>vV3kTmo7S$S?eOUje+lq)VYAB7!|S=^o3is&%SyW=4U`~*h%RUU^@E;QdLfdAP6LwTDC5Osz?UbMwcTCO2PaEgs%~lUDJjbw3NT9qu*>*4aVq=ZkbeCp7H4HH zXlra7G55Pasi3%m9=&@kOvbE*=^np>It9;jv?b=|Joowbxt^q`E*FrG=YT2S;-dUK zn4lHrpV-m%i0=VJwVqT6S*wKK2O-bra@tDbmE{XQ;TwL{b0Ce~Y{Qt;g?|B8@Eb`{oW*QwF zLC4FB>4zT|Q+&@Ddi?1JKsrH%LfSTA?Q14`c(}871B<(~z<7~p->yMFMyu$UY*ZTOx)7mXLB=mCwuze)X{ME11)a^9b&%$c0goK3fA<8dtI%!unI!{qeF(#Fu!E{5ZK6Z4eNFKvWf#(CHm}D1F-w5t=*tnvrzv|DGUw zS67yINumOjgK`HgU%ZT-fBG4!Z))1wf8?NtX3w4tx!6B1y)x`t%`X&vaMGL-CmRD4=9>70_0GN)>B5Z2C z28(S$Z4E`bc!*60FPI`3uA1^HDe`#5LkT(0pt$x_cU$6kqOd57B@m~qyrzKp+6}?6 zRCHnwy)}I{g@%UE_=zL2@IfCyw1MRxN^mX#AyiiikF)LCI;(#j^g-^+#U0U*5D3e) zc%c_dx4s2kU)cXY6ch1h1!YBdu!9-V@*>7gU7+G6U@<|kWp4(|><-eK|GN#`0Vm-f zUz!^(!^I2`inb-5WVvOzNx?S81xGz}b5qjE%mS(a+drvKIQ57M#Eukw0@1;A2iFlu z6od#946D}efu(>mjT+Ju^@?c;5HDLEc-bnYPKX*f)jnI+1@N$OpiB)M5jEA19ye?l zt8wFDTz=ZSH?91?lP6F8bLaMxn6(!V9z137+>CGOsRt*}z+U0-8dxWYDIR}EGt*fQ zxa)F~=Zvon^ZDBF9$#Nl!ZWJ}5!eX53|k;-HxEzYg8=sW6lxwQwJC^7w8!!SyeF)D zvG4nzD0TIp0Eky`V4BbFk3%p z(6D=cUb5n{#4wjM&MvG3-hBxs|LGa8zPMghQ~lPVy?YYQoH|9?XNXqucund5>kMQcI~-?iX&EqZ*l?pEM#sgGgQFug z*3}IGwcmcNF)G`gE|&L+i*8WYj0>8 zWw{=k9Y%P1&`7TkEP9Rf^b^k`%HLr)KT)C>*Tyew+m&8EWaH$5b9pyq*nl(sZ_Xgu{z2*w>&9K`NX>aqaGaSAxC z0P=^^DjGjJ3Az|<^!1X>h=ou|E(r0f1e;$dfXe+5HKeDYv_Rk^hzAhd#jCs|S5kZt z_b>lv&XOgXTOTZV-A3tjkBz-uaZzCzz4p!z^yBjF5QEra^Q#SXbe^Vo?&Lo%%ELF9 z0?sl@=elQ9u!9T3xn<(18s?cA*nF}MfE^S+S7EZS)UtG^9;9j)NX zxP=ANwaKs04Kt@x*wBF@u0$K;+srW#;{b82!8bAErRg-|;gxWs4Tk5A)~I@suOYaDlwm)y?hZ0V79FeDm$M z|1o-1{2)vOz(4PGyx<;Jmsbw)@bb1wKy)j^6c7{)8&_Xe+)8-zlTV<^`wvJUR5YcP zk3jj##minvp^k2BS|d7cER*9UNlKk9taASO7goT+e=f&J$P&BeevtDIfQEZ{Nf|wK z-+$@Eu``GuB=1OB9&SZWfuW+j=!M)JKFa4(_jJFVWb5u5VGGXfsDfDOr4jM3*ok6M z=hy*uK%}aH13)tp?$prkq}pH z-cNx}ZscyIpu?4U@CU%i2aO2oLTaDkSO9@JS=s?^t64*R!!VradLos&yx-(xv({rd zIX}4i`d??GQ0!=d%jGY>`Gem0a50rsX|O9)V2&@eNt6@BlGG=z)xM@QGndzAX?`L9 zha5i-cK#4h@v29B334&ZyCg9eaqJhb`|6|)VBo>HHN03BOv+EpBVGc+tgqR6l&Y?HyV; zIU)L3L?paYrHALPS>}U2WnHa)U1~em4j&$TMpJhiBmG3rK)Cz0A`OS>HCR9mVM~h? zau>pP+?{KBEX| zuQNN8cm7U3{#2fEf?DhA&^_K+b)K}%>K1;vtjL#d%GC|8`CKln!*NmuPYK5^uhTTq zY1rbIRl#>Z{&|80ALQc%yS^eWgnBawZ~JiHEa| zJ@tbHKqFZ49m}DCs(HQR)cFc3-Pithww%h*$8D1IQ09iCHHByDXZ!Xbl}o%c21w`dnb^Ux9ZyNa2D`nNhl`l%kZLdT|T4gmxSu zBH(xqt&L65@4Wb;KeDs%*X3#T>eZFtVm#YBB4n$1WzCy~h2?S6-&;gCT$@Dq+&U6m z41`y%gJPo)2cctUrH=B>D%42^*S&yef+9>DF;cGwTmN9nX=wlVAtxxT=I3U^HwbhA z+}xPOCH*l-h=Fv3D~n{rGvsanuUpwb-fvB5M^ovO_dldHum-TSv|1k<-|LxmJ9i#C zs4L6mO{u%jfQbOOgeUa;^G^jf*R@V=wY2L!`Vh)ur8+w)pB$?J)D0IrDGRj2hR*S?EqEvXl{PF_ecF2i~U zO6;x!JzPms2*o)!KRS3UjqaTC7|r|oUFtiaw_uezLkv)rlZlx0aBkC%C9L1A>U)1y zzi~^Kr#mssG_0#HU=43?>vtTd4;L(_iNgXYE<6&R^Vlx|9KP1Wr0zsHz4OIyH2a0S z$lu#pAZ7Ltms3p%1iXOUiEDh-w13wAykpNN31tDyMd6j2d|8(V>hIh2*av{FtbGeANiZwW9 zsTsMHmR&?Q-#zF|FefvYe)#%3`uY1G zsIj8``(G$n{T-!%;9wEJqp7|rfz1UwD$`x^wu~I=$(1L!>mt9y};^c+{CPC zJ7}6WfcwK9<$E~#(5c!|s)v~-lPIDMv}1+$#13+9C<>Z4HP-h+!xuM|XN~6k83zs= zD7?L}_;EWIm&tYxio?0tW%T;Hi|FmoeuHj69VwI!Lduz&lFb9MKCtz#AJhAw<)=H7Dp)YN`vu$--3KntbgY^)7%7 z1}4@!gKOQXSj==+=RfHbkW`g#^I~ZY*0nSI*q6ff?@+&BniLj8m9P!J|DI=P{ukc~ zlPLG_C`4DYkrraZei)uJz)sRRZndkSFUc;5#|(}IQn#V`%TH5APAyf{w!r4T725p` zw0vtWBIW_9LIi*fHDUy?(%&kv>i-w^{>)_&GGBpC4LjPd$#Yz`uO(kI5^o0b9zSLv zO}K6#-Fov-8ag0Ga46W5Amb|qxI24~oTAr1Sxkx+OX}|yD8l>-g+X97-r2W+70n@` zT`cU$$JU8X)|CiZHp_mvEejm&L74G#YS1+H(9g~}I^6}gk|Fy}$;w^s5EFN;t&7u4 z*w0t5Ublyye&s9rZS6khxRJfWPAFSyF|-a}ch>xQ`_2XA2{r;E)ntwcdvr`m&!s=N z9;ScYd<%k9`O}`=JAl;lVaj?vYJtbYlg|iW00Tf!0cId@3xW7Qp7jAdL{6l)XUwGH zl9D1Xf4`?@eLi>Go})*1quhDBXw)s!MYoy(69I4ukArvE99+fO#jT~juI{xrUVU}& zC4jkNRf_Ynhd8=8`;EGGT&HcgxVVrXymz&>H2ZGZvc<8}g5BVTxkZZ=7JYLYETA@! zsk69Jm$c^{u7SEj7kDDW?F5*7o1yV}E7m^VeR|kB(2b$7^zVoSvT1Ck=bm|!UVip1 zYD8F7&+u4@3(&z*FW`03)&=(XtYR!X5Ak!Qcb>nU+&x2S^O>MS8 zl>DDt$O{UA92WSSrCVu#3J~s-{}MrVbWs2SnY$)gSe)HSaZg+3tWI>0!H9_eG27oA zQT;*#-C=?R1P!fn)(v18HOH#hy7wf#^uZ#sZ7`?7p21`TLQB*XU4SM#tQ1(H(f}`v ztRxs>>q<>H7Btk5B_N7^;YSHE04hT4&=L#n?5oP9JG5tbaS+Af))@N zt7dmv^XlJdZLx1OH;2zZEcDIbyx3r|Vk0oIX+d}N%bnCWUMO{WeA3~{DV5dz$`w%7 zsEW~rR9Y~Uk#^ssTG+1;;x~G%48*AK`b|dlDmt4Leim z$KSUKXFoxHZj!SfBe0!!Nba&xaa?p9mpTB}! z$(9m55EB6JD03s^#Ajeg3ywfNdw{f*?nOFy(y^KXVIl~BhC*DxA){Fw0)og7A^hO3 z?(*US$bGorBX-6|i1tpNJo%R6`}W-4s8&rw7~|N~v>Y0Zkji&J?7?%+mI5Mh6y76g z^5hwmkJ8-9?TUGnFXW?sijZS)0RqDo#KwZfn`!saQ#AA456C~jA93lwqeBM{&?oag zB_|hWYOPa?rsWnT)5V;#c)mOpEd&~0^ky!n3;uzx*0ncg~HJyY4A|mxMo%FoPxX$7N@4(JEcbt zt$o!c0Pcb+6DExIZER>7J!;%|$av4a@$m%*0Gk74bzv3kspw=EG=D`v^cmQv_{g@u zifWr0ovUGL=K~L1BFwIC#p?QI#WISY&YEZeVk(FCH}-??WMc=`HX`BSU$na?J#5#H zR-HOQ+c)l_+pu$b{=rE!3Nmh11Y;JvVG{892W=TF-G)Uj;M}m6426ULw4m?s_=Z@& z_Ac<+#zjB{u=yYR1UPgen`V9b8{Pe{VKlg31f0Q$=r0%0*13zwV=(NITtf^~6u?X| zix~*s7IUYrZ=z2jhFH3K2Zh+VQml&?_VUKaUk3vOjtwjLvC zr343ll_gODo3KIf?uY;_p2+-LJkl*$vV_-zZ~OP}|EDTH`;kVC=8-k)_Id9;kVe;! zNuo((`jH314YR)m_K0YlXN6<3D0IKQ()K6%ASVrxde<|TTgrKB|(5$3NaQ6L7zp9P|!t1&>uK_)Ozje zKVlH-Hm2yz={u6*;_lmf?AYoKdTPq*#xr0N0bIhvadcKxz=Kx3T2&2~uo0Ay*pC`& z>+U!(fBv(V0Io}`s(XH~jXV~+O@(k~v_uOa^MQH(0f-1vi#%%+%`^)NT$r&zHZP6d>-^3rgS#<>$ z;PxJU==R72YAUXwX>Tv2Pk&ku=QpsqwzC6F8B)967`EN+)`eb5GF<+wS*4Cm|G)-^ z$({HE0bq;Wc4mGVz46KKFbiv>5rcbZiIIW0YDL!T{mNpAqWjz0mfWOlL|f>nkl!w5<(*j zc&D|rP&d||n-Vqvf2B_M?AcR%Jg@K#XTP8k_AX9yYijCM-!0lePrmvU{j_4ch|OT{ zU=MBxx{CSfkR0SGk^i(aB)Cz!PV-GBtSra@w*5N~Wzeg07SZ5gBk0Me{!3*g#q`E& zuYfKxqZ#kL4f}sMOk(kiuD)QFS73uJp|L)KVb$;1i&RcC5wjEV))9gsfW!s%P)KO?n_}2F{G}neC3sC z)Bbe{AiJar*#G9Ls>+-D_8&kY;SuMBsi|)eY*Iuu>(&GS5M~=>rfYdemr`CRl)Gf< zsl0gcQ9eKKz&k1+SC)@P@OWX>CfsMaI37M#;?D!fo=v4W6pWl0A+bx_ksuE5ita-p zR?ajZi~Zv>zNRDTd8B|=wFQUT3x0&d=*3o^)yI<~JbHGy3*kbgm$ijqHE z2FER>VjL>$`~}gV@4i7>GNcY~8k93Wm22s9wK+JL1HmaA96@}v!Nz|*{pa=hRB)<< zhWmz7fCJ1DwTMk$SAWL6V02{*Q$W@M;E8I3yqf3U5=b+^&V>#`6tJ@@ON0gHahY3K z=7dHCW$B^(oo8lk`;Q(yc_OdqQKh%XC>y22!ouPz)n^ED^dArci0ADRwFJ*ui2dU@J&lmXMf z*&lyEalLwC_pjk86iFhuWVp!U8%&5W7llm#nF|0h4dUDKq(kAgm^LHq?Xtzcm^FeJ zwy?53JXxZFC4i>if5Z%!L;#oYm;!@?Yl>c$KCiZA>%z4F!Y^PE%^oCwnZ~Mv32`^Kt8@v8&x$m z1(`MFzD1B(xuyO<8C-d79drO1$s3EU zkePCQB)KWpK@J`W;Ns;TdQ zd?v#az%SxQ>Z!g%>>02^rGdHMQc6wFAqP7fa4}#VP@}=SqAQIGa?8f27tyqK4y5lw zTrf!NN?GxZbrd%4Oo3c_BV}e5QH+x(g(BQC6IIRHZ>fD-a*rg4%-*-4npSvSWQs{l z5V!!~2zc8%(kXQ*Re`O|RvJ(;tZ;O4dj7lbeuMwp?vu2>1NLL>xUW}K)EAnn%ExjG zDsKAX>otxm*X~99gG3rLtQUF1Z8D2&n(&;M%n?cZLbQJ`gg6DyxDnp@zQ){s0k;2; zk)!FA*OTG!CzsxN>kT0Pbo%hK&*;Wm{|!*^Pa#}bOK=4dC_j59D4^U5T_>tnYp|}V z5HYZf^7C>jBP|_bgcM3kJwe&oIs6S4x_Nqi8yM5$9~JVq`@9W*Z%<4Fz$MJT-Q$l} z(S7$JAYDxu695PSDU@x7j2J=dSFiRdDK1&?=9_O$c;%H>vM&MrC0EJI&Y9>N;O{cx znla~vvEtXIba?MBQo6YQcK_gM+xDhDeBL^LXMy$G*A+(j`7Tw}RQ=~bNghoMi4g&M zI%5^LUm%V8&NpSOVsxYP3#p3q%RBr^cmiz1?$bk|fSlA|{|~q_XkJHKP$tY1o&(_# z&c#kanRONP{o+mZ*Pax*YvM2(JunU&0b9s`C9QTn3DH4cF0$X;4<*_5O>{2TVsk{;jVA$n}vK=&Ol*R3Hk_RYVwfof;!hzdnH@`M)iVC^?(UUp! z?XR0@=Ye$cgirqw-eKfujle#bPoBYZf)%acTLWAs09YI(&y{EqKUN@QeC-_RWKA)e zvo7%R^SNbb%JJRj>2SOLU%g8$A+Q%~$i9T|@J||5)ejbwR8L;;!#da1>-W*AKr>(?w_N#8D* zOXX#ye+YpZMs+(%gcrZdNw-(-KDm5ts=g zfV<8}!0=@jNI{z?pL02*WLUxIhW^5^2%obV&OiwxKT~p3lYTSuCesUZKHtzYDJTVW02=f z$}_wp56-Z8ls2fqLgdUpL0o8 z4y*0kgg_F>&>( z>ig@}>N{HC`r6e+NrU^x(zsEH6dvrw;jqzlh&C|4%w?+nzxLh(K(eA*A3r^vb5G72 zX7jRn$q0%_5=4}!^5E|?Afh})R1}oO^z>uv<~Iq{PeP% zEZWoDB4X7alQorEAcXhqZ>C?~wvvvTTS^z4c^rKjp<}&nEN`gW!%`LwjdA}@`i+mP z84pCNz>=++`h>_zaIYI+jK`o4)c|0V<%Tx>;4Fj&Zi|O@&*VVuL)qh=i<&{`SKXTGB^JbmWT;KRg zrrBn_Zuw`)hM7w72#gxfRw&9x2Gfc?}gxcJ4&Lo*bC@*qy_gBN?*v;z7f)_^~E;sTuB=K=2| zOusmr9aaG`y!0DCuo#fWTikO!Fkz*1Uv(2^{*U97y;@qHQ%X~8nL^m}Lov5En#yr1 zWFg)Cn{QGNd=XeEYe~ z2QEy46aS#|51|jiUz!@~EWuy}yYr5#0&w2xCzm3k+855d=%QhT|GIbIrT_Pf8?g7< zx}$j3wCj#E;m-h{vt!TO|0*eZqBY>ZY)#D`I)COu;Q$6H!2gwm2}@dRsAakf3lxo1 z6zgvK*J+&i$^tA5VcZ#wBNS~n0_#}C4uAwqv7$?ruO_erZ?%l`9GGTqUM>Y|VibV| zK{J1Iy!+f-7x@zrs%U7XJO26tJ^ajD^yy>g)9F|To;RZyp4~RYK7f@8?bvBhFZ9yG zN01zth%MNf$m{IC|I5D-y0wFj&z(j^R)kQ-Q)38XtUQa(!pVv?jV;s>>>{rNUfysv z)1<9p6HuOj#dZ3bwBFR&zOaB8aG+1KF!BHAoh_JsHc__8LCdkNk4^nN<7WpyesN-6 z{L(manLefaQc5*C?W#rmV}7jaYUNDLWAz{~6ED>yx)alGzWL_ix>7p$1ix?Hx{s&c z53X3T;*m%G`qu^cJ3rQLHs&Iva*festb!WKUfd1Z+};cRl~g zd2$`4*f&GLgY0HR&B}M~y&$isF#n8mzAz+x#s2+t{rA2@;ZU%(XnNVVUR=Abc1Yq+ zaK#G5TR&m>^54cH;j`YXE6-V)S4<_?J{IA<%6&w^!9Rrf$;4Y)uqZ#Ge5xO>|0l3O z8pbxX0ti)6lO`OpQzqeAo6}~Yx%oM;1T?08o3)vDCeE?XLhSoccZ_PfI_S9<*3;@& zH_#j?0H+u3I?i`XVU1n0JPkOXZmd7H{-j_-u$gN#&^rk z-H0f-l|ID!bBDhRCp9|g)clzc!cZvCh!tX?crYI%=5r{C1w1QK{OW<{zrmn{{MDX| z`LoXj|2@KbVqUT0Bk>U(N}r#Ewom|ghOt?F4Kbtu5w_>8zvdHnJjoE_mBk-70#X4u z$WP#6gQ4g9-}=TItu0NT4P#3kn_M|2fcEQYffWEMfh%cRZzGufXwpG6>G%9fokjt+AZo>D&3&-!pHM0Q06+jqL_t&; znvW$$JP2K@G_CeM5S2O-b{5RX>1)$-Vzj@tT{v@z0i!9CF>ja2Li4d+UgpW6j(CI` zD*SZsj+f{m%+iaCvT06PDa|b_L@3!@%EdAP%)b=Ge*Wz{tLeG5A7Jghk_vHX(&F4A za&wqsRrvFlQ{aPsSRu%^ILV0MM7%BUgk^IOoCg7eVBz4zOBw9Q$BtsyOE0zx#&Aq; zbDJOA2y0?eO)2*>fdX|cPI_IcXH8h<7{`#Eg0cp#OVcB4Z`)Ew}Q8|d(3 zdQAb~KDb)psSvW2hB3oq!O~@`@9kZgWW_}$iGW04AOcbW7>LP;OKeX2YyS4Y4ULWU zmYF#D4F`{i*+2hYSz(67kF7J*?jKhLVC$9-&cXpX3(mXnl0nDK+k1ZYgYVOZx89(v zg2G?C_tD-*kN6)%I3>-!=%TwfzqR(9o&M%iUa8zk=gwFN1&s06!N7U^_A8=6SFRNI zEf};cEjSo6K8Civi}#-s#nD$5!q`I0sVPmX4Ix#cWz-6`7|qDZrV!T4o1swfK76eW zGvhpOEo3LXLadz^+Ob`$qlP+g@?c9<2kqHYM^AyqVliS1A(kQFj2H$lKE~GrC%EO= zSns#{uw;<|LC+&h4FA=W z+7{Y@v*fqJ+q}H8f&4h$whK;*g$_5Jl3PYTST0z}*cnMs4wQ;+bxA~fChjx6h@h3C za3m>3&KYI&tehy%#^H`RDepK1fI1PIp$c0qjj&Q!oz7?RxurM=Qu!tkkO-s@kP1Kw ziqU5lE<1MfJFmaGv#N6cVpafzU}Xt}|2CLHF*`J4QaWx5z?wB{Ojlia{x^K^q{a(y;`9!)ACuYu_;KgR@c?EgYSzQC3 zY|2)+Xe2D;n3QMgytq^VMIacD(b3scMHD<7MnRo;)+mA@CE;0@4I!L~(5`IO=g`DG z5W;EGJ9APJ=eRGiR0W#Z*b*3QkK(kr7wD;1-lyV%9J0fD#XDO3@C|5b4?^H~QZK&9 zytbYXq3^?iLUD|1iKl?D(NB!C6VO;wFgX# zKdpqXs>BuIOSxk@jaIAGC|SU?Az%t00W5qyun5nldf}Td8sSYZ#Z_KsyV}{E@o@d<#RxMKU|SiVsFw>RXBLi*_MktP$%^;^hzbWw#3FaDP-55- z59jWQ@*EK+Hr=znpT19)rWsL=pAlvkaZwwX09I68WmjEojJ;egaB;Wd?(Qyy;_gt~ zo#O6Jf#U83%8R?ZyB2qMcO5z(X07=LXXPYMc6N4>Kp()R0@%->PGFJP2$U+DjFqzT z()+hmgD4pXpUe|liX8P3MB3FM#!3JLf@`XIG2p(H(mjwpHaSibE~9 z1ELpICwFiCIpH$}t(;2W34bgwW0r|*G%E$23PrC;@U1ez!PGDF`m-%?Fk;&% z%gI53)fs8FpFRs0!XShizYNo5Q=cOsV~Xbr=Z;A!oC|A?{!s^oKivGn26wX{?y28c zl)%i?>#)adkRnOAvb9LNw(YO*`}MG7fR_lW-{#?d>fQ%;2wx5 z8S6NI@fink#zrFs%L5hs1}9zPZy%Xn~2$Y z36*e=QC|zP@bnf6w|28{NQMUzSKzzLvVgt^<>Uw?(ZRsJ%l;@(kA~{MuJvc6J{s#-6Yps)fvjv|i(XG5JvpTLj@ft(l~|y$s-Zbk_5d!T7Td=lnXR&+vZ*{gE8WK=gfJ{UzCnlaCSHqeTb+JmA8~+_2A5 z(z0NlPWA0*rrdqszV{lH4x+80d@h%zalu`va4@5*qQIcpz5qUcfueUj`(PX-MCDJpBP|be8{QTzh^{}M~E^eXImCAu7N>V#;iS1_o6@EiIS_wf~Y_}Ju7OwBKGo|xBkYDEowAt5$c6Y!~7 z89`c)#H#t>59hT&lu%Y+Qc)+`RHK%*o3P*NY}Jm5S4+C4mQmqv{u$_6PiVPXvS8+8 z_wSap{j6x`QsJfuDRGlxIQ=o;@>-J0H^Ofc#g7csLb~d$XJ@9x-uu=}6}md5WN}*m zupsE4+kl9pgURXGZXhyu)jNIQ16{TnNBx)5Cl7Xr#N@Dpx(whQEs5kQQa z^&e00STk=AtKu6^$Kt9gN~pBdG*9O+FaP=akcU1JpgVN!9xdpt#1i04Ua2=>Wy1Sy zG`LyH{nZ1Fij+Mk>I-ZCv^4tGdY_VL^hP9~WO(Yeuv?xFAO@p+`O&Jnq9gzsAP$Wq zgolZz4H@*IiLo>-79`nK+n4PxdXP;ZkO9C_lLUcvq6>Lr;$mgeme>R)uUo5Tb9v`Q zs1(w^>1Q_k*PwWyWJMid8Uqny!z@>R#00EZVDbUy?D|c2eTsEh6ntrDvSy_*vZb53 z5s{Z9-5d!Tf=NNyvS<(yT^9dfgas;I;CnHh`W!EQ%!@Vcy^5_%QWenMN9$EZYZ28# z%IgSm+Lyea&dV!0V1NF^(aFiiy!+MfjYFe*$DJ2#LYWgm=oiP-)$vJ&NF&JOHHwx43p-p?oLj_ z=MF}6`akvltjag~RfXzRtVU-6*LMBch?o#Bw^+&$EIu@;YoBryKHvxPX`qNdYyUU;s@#5Y!drEBw8?yB0duy!+g^HSlLHftIbvk-o5(! z&YX&Z+5`!quV%u3_Z?mr(W&M0_4HjWByCUMq4njFnhoN%yy*g$_|@J?)>@7@wADJ2 zj(LMjLbcXHn-IT&+42yCNjA~%U%?-EVg9>)BM|VSs@({aqLn#)=we5A&eMkgdmrvY zJNFNticu-2ytb+-bJL(s;Z-Vo5tDJ^Ky+A`H|1(_c`0MF|BTCG!d9+`{AAc=)lh{P znCSCsi^h9yoMZuc%Lo<&K5o4bGZ2|rSXe6bWF+2b7*HX&$fsd4^>c1z%T2eosrgJv z^PDc2lc9X!3?6^91wB@?B1Qs|MP)e$>3(gtuch8m@tE<0 zq9F6q1yKGlYMPyf&n-ewb$@IU=Ls2FZg$vSOus~Pgp;RkYEg`>dIJgXn|fb!Fj_K5 zQ@Nk<>EO-O0m`Bc7t~0^J<5IQ3hk6|A*Zt(`@|xJ4&-}F4VqpWI@coH_@Su^?QC!? zF>uildfCS}`3!6tq6aled1!HO+*MN%6e>qe+sdfcfI&Ef>?u`C22C(gaFJwq4a&Pl zglX5fa9(uKg{oeOt1Rh^VwS~SkpA5(8a_OPG)l6?BHzs>4$^uweG*vq%RkpaBD$;m zxC}e6Q6K%vC$t=izD&yfXPsGKak0o|QE2ylH+D^un4W=A&zw2^Kd;TGX+#yF%B|Y2 z{VopIl#Er)QCIVWxX&KtpN;wNGRQu4F?RjeT|sLDZ-14-QAUY#z}_(T_rp* zehseeZB~f~BV_xs7#Ys)e=e;2rY-8#RpI6md4CP9@q$a}H8jqvBrnmtlFz&yfnAoI+y6tU5*J6PV?y1qtPHSi ziXeU6aX;U;6Kf0=HDlwOJJfl`VrBZ|PJV(uIo+S2!l2yd`vJTYVuOk(aWWRpje(u& z+Ru#&>n!kKXOyq$-{lOjCaRWlq_&r%jpLH;p6j|$dDKC~;bRV-PVPRt!y?@4ncj|G zwZS(AD3tfh4tPEo|4Fnx(Q=P$u?q(Gxs*&pA%hMTdmjDI<_xV^<1w+;aUC!+fmv08 zRaFh7_Z^6%C?tJb=H}viIkW~Y1k7}Ll!w6-zwwpcxP=fU;3osr0^Gis5SZ0UDr6X>P{5XO&e3V#pw3g%uBvu0Z3=EKRqEeav4>o&KkGMBRa3QR_Aq zmZZISb%xCQmD*?%Aljhu{-YGXbMYB*=A!*NZmK$$`*Zw8{uDto{O$47uS@4j#V|l^ zE7hp7W=Bg%NW+#~7?FFR5fqqX{;q;*W8upfEJPUi^!vAWWPk-{75?cXMqi`>9_jaO zZex7pvPS$rUp1NzA03Ye|8E0&n|xC#E1_ zs2@*^s5l(|fIEjtfwiJl?Hke{NC0K!P#;#(5!UFGFwEVH~eSW z2xgXz@?t?|saU=C@QoasD?$@7y#TlEbmUp)DL3|3xxGV|F`}>vaK;$Fi$^ykQyreO z#G45ChKOD&^?BTg6MY3Ywr4Dh$DPEV$BHNi>5qdd<10S?993|6U@PAWi#+7GvuQ+O zl#$I{MnVc5VRDGyaq~1~(wyjhIeO|J%*@HIh8G5gyKQ%a^?dH{%1ghcoF{){)@)+; z=+jB+PJap1pDU2PjUHS@w=1k5d^E-RNw5?G<+iy6y_If6%JvU_GXFzZI1U5($IC1B zv5#T^dIEOMxnC*)on*f4qa{u z@dt5k=MtJ1#UiA7z>D-Ps*ydW<4^kwD49HUDStNUR-a?>(#38=tRC7y zl>o0GhU)`JV+NT9g4$S1!@~R)748<#HIHul)jWuFLH1E^|mSXfR(BrF6|{j{jC0LJ8ySK>;PsQUaXx3-6 z!W|3`Z<4K4>q7jt?WlJB8-&t_d<|wopb`l_2th;bt^n#qnGKf>nV;#lE?CXPmcULs zN2JT#%~qQIaxDJ?BkwROkS;X9};@3b06W}?9(VN_C zR2jA(ktQ_u{n60ZzVhv|`Q3Dqk9 zZCagm8(N3|g74@j-UmV=ha|K$?dU(g2a=e1_hJTOdDtqV3pJ=~Tg!9yV6Qpt#og>9TjrBx}(mX$kwv5u)l-!zi?+TOgpOv zWwRW2HueY{OaZNPt0jv!j5}U|W-SbQNbHE5@qc$4mG*h_{sV#G5q?Y8jQu?V?1LE@ zNI1U{?};k{-Ev%y;;Kc_d(;PY^USO+j>v1b7oUok2D2S{P594hkY%qwDOYIlWeGK_ z6o`@?m{$9_O6BiXX9pjC+A)2N5}{eow?5TyV>0`5u`98VZz=eeVhi`keI+owD)Eg0so{6drE2x9WN&GaAHti z+WmA^4(ZHFev($z`=S)76B#@5t$pEJv<|c;`CG3wMVZAFm0=eT=m$5G>!x?0>oMnT z7wYVsT@9aKbwR}zQ!cI%|c!AbA`m^kA$B7cHWN|s&zH)k% zcq^>c(X!#FU~&@YM_^u`$6a>T>UShQwZC&a{4l9YB%rnMk(6MmxAJ4hOmviD88fCq zLvHm*g9s6ph+2c$;7lZ*_O{sr7jjZQNfpK~Q=pJbx;)Y1&ztT--5a=tCGt5&%>WK2 zH4~#@BPFW^Kw`$b&(Vca$;DUu?+1?X@`|g$k`XZondSb(Vp14p%!3%S9j|w7>=SU7 zEun5S%)9x7N%&^m;kbU*W$4v#z@A2u-qg4{%~PfoZyvga)1_KH?{CjPM%(^XGgDxT z`N<$NVmxV(;1#&b`E-2ZiK+nvt&{$RCek**2FW}wDPG?l9H$`*Fe(}WuKo0RDC~a< z9*XJNovgJX>VfsDQgbji#$1bF?rB}o*)+88rq|+2Wn|b@USxJUOQKa}dltE^JRwW| zlu04RGQl#K`KlAvQ=s6$V6L(;>ATygYpbe~#~_{a-CT?s3%dnjR88##C2(>rL=kxK zRTFkH&5<@>H&Bp@Dt>*-BWI_CWZq=MNW{)52C!6iEW+hcYmGDvxZ5{^X}%+$^v5U? z*dG4*jf`TS$2ub#x1OCkY~;0cJkoYax6!`ujrAUcVC#r=)l6#a&@i+MQV^^s3=r`x zx&8YIKvHqQWrt_u?se23MAHU`*IMugDpFDY z)!!|CtTT)qO+o)*+obZ7;M1CVtW`Ofu`F~#`LYn)e%wYrrQdGw{ucjUX|G8kY*|co zfYyP1vdew388dz{1yqQ`iGm*)Q3m^k^_Qg{yR`ApWh)yO>86n=f20Yi+%$t%Rx%Dd z&Msg(!KiWpJ`{HrcKyAB;sM6WEUIkH0L5ii79gE^SylUGA3D`HU;tZylK1{k+(!PZ z<5Gx-7_m(Q)We4ZzbYFOzKAX=q5#vIgPc`8oj0PeQA=IV)#_6SfRkY6qvqz#4$)htTm)@RL` z2~D?npp>f{ovMV*3vMby$O(x{Pqr;D1GGqCrm7QErtlw`sY5bks}wrK+|?nXD(Il( zG2~R5#cp7bXP%oWPP}^9j#9zj7FsGk`?U-_F3$W7-vU&6sx>_3Pu=_;)4};_^~5?TU${xpxU*5h4SsW!1c+J9imhK7oLH{oum$BIc-Ebs$?89cOaQ_ zOQDNRTY-}F%upQ*p>CEh`o)3{X_TkJf!F6hcW6xR)gFu-|AJgI)T!sG;qT?GhLvzW z&sl^0D{p2VPSokI;ep}tDH2E|ZAny8pF4Oaq7Ibrz~VYnh~c$1Pwv3^w{%L8r zcUtg1qr-L!4a_+{mv4fLj652}))zg{ccftuI_KU}xmr)>P;GuGcv{f+E5vgcF$J7` zR$>LB!nA;bKp8ZmBe3^Py0u>G+kS@L2^?tB#?mfN6u*j0c8|B4Ymv>d9>p+qtEOsn za+s1R@T9J;K}E(3rr$t{c`LZ1Y*o~e_?ghm_BQ7@>(+P2S36!sbPzjv+F8G0F&{gO z2%<6s9%5Yf$;JO&B(JCg6}tJ?SB!(jOx56UKfKKnp@kI4_pfYoE$EX4)n`rx7&XH7 zQR0U|McR(d-`xfA-{_3UETWw?>iN`>WVR$l+V(k78S0|)&5Q{on}gGJxOvAp!Ols;m`HVE2WD&bT) zdr73P!x;XPx{|~{>A19N8_q)Er^i4#56(!W+lvI3>}}>D~EhjfmwvGmy$PX#;HeNRJxo(@r2pUQ zgC%4ckGnFmtC7L|#*TB7z2DUY-w!q(G^VctzVlihh#VX@1=EKtH}LR6n9-prt`f%L z>aEcuW|R2}Ilh!xcl|dO-P`?D+*&YDX6rp6w-|T4kO4zBD4#`uVxq}bz`w3x080EF zI#K|U?aKXgG!)dNi|b?OT=?DVcD^Xs=zdj0JPD<1=wxjEnO~*4YiEi1BCOGHbnP~t zly+n^Z$8Gq3LH$vQezz3K@bM+XIVLe3$ftpIXv%~5oRM&_#Til=Mv z4vHaxQx51{r)Du%s4J_)=Ue+#J=F7C7qnG&8QU~>?IWqp&gHD3Rfw| z8+ddlOx+KPyucV}VJSM4Us!-A@zg}H&2qKzTU-1#Rm?3($2o}V-T_(cS-6h2=hXe8#bo2&j8K_rs#X1-`+pXqzwTzT5!1nf z!hk(I{L3NJ2)Uk^Tj7mybDP%`%H73{e$*Hg*>`V=P5efNh^SISffK>l3Uza4pKVBn z)CQ48%(j2TFwrJZ1k-~#O+>l~3cyXh>M(k|783Y@XYVKz;a(O}dawk#iqeIQOs|jz4F35&Fi_PY0rdcNdu}757qlwUtH*OWjAJ=P z!U?R;voMM!%)fJ5E*?Tkp?W*|w<}TTLXN45VaL& zr+m?Tyr}`vF~CkEVSp&&dbiyu2k*yUUZS&sHlG6l*cc;428@gyUj@cVSCZN=zvMvV4?shUqBTK_Y~0! z#brzoA;(O%BFd)tWRE~Vz3=%N7)Aiq6T4^Q*cl~9duXRlq@=1#WEz%Ej2E{BKhn+=Nk_ zZK0WYoTB#YB?B|TV*Gi6Prq*SM*F*}vgoY&7(a>$Sp`!TD`H(cr~A(;AaR12!Q4M4 zYwp)DG&*cR;nowNC;Z=BzTse zbDWM$SSNcaES)UTko6ZIl|^^l^e_1cGU4bsh_+x6{ z=B>k$?7u&z)0W6zHStr8HceUDEMaMC35)&3JP* z=v!M`305Np_&CCIz%3j0#z+fx`dhb0x)8ti18;dtQ#G{@(8o>Z@Xf>iap7#ZX|3phm8q`~} z$v(dxhw!AA!`7zhF45@W=$1gK40s_EFJSvrfKrLk#v_=ORY!wCD(x{iEb zuFb|;j3vf*Yt*^sxR^pP2i;za<&R<~NY=11Q+{ENN<4z1E=jJj4Toa4e(}afQ_%yD z^QI?t&z;X_I`5jDm z4;t-Y=s;q*lrKMXmB?umELve4z4g-h+7n~aIkMocYeK5b!~^^`LQDojj@UFI3=9z= zM(06ssl}V4Zjp=fu=Ywlcf?&$<)AX|a0-~q84S4cY#xW1Mui*oM6IEpXJ?NDh*5$&u~97%^ZUNOH9G0wgjhr+H`<7 zv6Zh#(@ysQC&YNvi$;e$62L3Q7z3xFEPP`wTbS=n4W!(=`Yc4w$t;0QS?4#mG0n8G05+laIZC~p}uTlCSeVDjI8 zp_sYwFAr>0IYg`ou=cz()WM|W%_#e?MKV2^jFA}Cm20JLxnhOOl;t?Ld);@dNLZe! z?hLAM2YpMXD#JUj|3!w7-p;D~-vUw-?q{U$9^O1>Btx5N%T zNTk;sUVCw25qT(7>@|NJ!UU$&~XDaHhf!`vyNU|FbJP8y+)HH&Wxc9y2pBAL-OOfncgEx zNxeOg1F~aInuYV!ER^}1q<2E-4(h!oSO|2`@m=YX6tJf6%D>at1Eu{r5UDV z12LjD8k=iLgyxI%UL36`G=Y()Jyi7JZvLy0?w+@;Bd4?3ZfzZ7oP4CNku%$}Ce=3Q z`P9<*eU4JIq-GNkNmgF#CUBSNG{*M}fr=kU_5zi5$vgy;2&M+(`}8jmiM+lqtbEBR zX)Y8mi*}0sWo;Qz94?Z4{XHxSCHlBMTN5m+#3ZR^loYotUv4OWWYdWc@3Vf)Rp0cB z)o0|Y#~Y!Vbc`cz;_UM*231pFaLG))c7`uO^rN~1LFSitIMhCL=AO728#h7MH~Uc{ z!b}IFl#1`oGidiOlk{1vNmvXzf{e%+uuuS0(lV@KOXup~K~whlqqxw|hEYtNf^3Z2 zcT{(S{zwb6AH#@?eAs16`T>e3Ymhz=?x6Ilg$PxaUWsOu8u!`H!}j>ZOd%t`e6f-= z$#zQT!oW|bV|P~3gJX#d(&U9!VJI^vrB(#{j*@i zFv*zd@z#%x_jtM^l|tD1a=w(6l{Ff}x5t(kGS$8l=bfg7?=mx|x?n|w{2qVM_wDnU zM1Q$SQgA~7w{=?1@@7mv_+VRC}wK-TH*^kDkv(SvvH_f!ITYEeoy5 z9{<=|ecj7vMtchfPC4|LWQ}rHQ*=ezdW%1K|701U2v2GWxX>BQjI-MM_>0wKw`lZ#(><1W@~``e0vqHC^#V=uNE@ zZ=aXE*-qAKq3xW=ujeMle@tsCJiNrhJM(4B z)hq^%+lf?Ies;TmW!v5hmt^+evD`6nY1ZSv8msjijGQyrO~^Y8K&1@Ha&)^Y%wcl!3L&+YQG0Nom}qv!JLT?|Xn^>cA?Bn2%obTjA)&7Wr6KcLOALO{_BSOie8nY}~9+|12?9s)4LU+-mke!@-( z(bERjFsnk5V$;=ck67P-AH)K6IAS)MekqUsgI-8&{za*HYSV10?@UgV1h>vp7A`3c z-ygH`g>25A6oa_n?@YXg@~iE-yobL!ddsuU#8b_^=_SnxfIjyLDhgoE%~UZG zjdCts;=XV!j;BU5l?aFyK7?^c_r@?IWm?{pQ$kX;nsa*8V}VOj-(geNIOHiiL5SO2 zhu+$VHV|U*d6!Sp;>HIL{h{{N+ocCt{Z-5j{KF8^UHYsSf`@XOGYC>wtkT0hrj8;Rje4?_<9VZ7PGm8qjB>)ap+o!yCL)RY*@t>(R(e z{|VGWzojVzfCBYO51>Eq6^uTqV_1TYtf6BrOqIrb0gy$<`Em1_ZsO`W80*hyx_W2Q ze<7VQSIwEkUu_dlrEZr6&h>4V8ZWYkPj8C@YOnIbCQcEcW1qGu^z$-T`ODo`E5d<) z9{tD%%HfCIfn>y({;;oGBNBLgp!L-{%M3QYlUUt6#{bIx2-^TiJVPAz8xzrpgBvMd z|JM772N#3djVKgBttj_6l<=zRaPCn#`{=pU@UIAwT%-;Qoh6D9irHupUK-&@=&4n}Mxm*^7v}uiij^imo7zqp==V z^tY#=(|#;jBmt67*sl7|f~pQu+rvM*u2lq`QpS(G8&SIhmUZ5?sdWdwCgjmj2s6wE z+Z>N{&b9c7J{GVtEY2QU*MC%DzI=mtMBP)?`JjS&9Tg>4L={?yH|{uf6v|-E<4L_vxJN*CY0#d0joF z746AI@)^F1*-ZuQ1RFs=&jZ`Omq87E<~D+Vxs5 zvtXi(C&a-j}uJ^+|9|Il|Yu`0g zuFe#4e2{eS@9dFa58PGj>N(j~QkM+iV_^J-os9m)NW|OINHHD^Nbi#tHq-aLM~o12 zbK6_@5!IwSxuU1ka<3)rvcAhy33>XZA;+T#4i@zyGM#Zd;_E zEcHuk*K*FuGsayP3A5Y)b&U<%^ad&h1Nomf80V``EqaLmMf8o* z2n%*w__wjX+vg__M}NK8#J3b#`cs!0<4aaj%Zj$QYTYS;D*;*fj0?9WHOxNcfZ6&*mA#|g!uQx&*8PC82A14vOrZ;RhC6l5=kR+%jv4I zF>cz#viZD4XoMRO2LA{<7yFs-row25_AW{Mwd`42okuP<1O85+0*zCnt3geP5FtM|nAI$Rf>owZzR4%-LnxA$h5ZO%s_tz0(lOB>7X z=P8V>KhHf*f6%6}s_}cXqZJxR_;X9nxED*3i<01X1uF_8a&7!{7yeWle2e%ehJsBc zPXSwi+wR_9_*lr^bwDs%bARWZ_+7zzFI%d8LG>PCt9ZGyKa6$TkV)&1 zW%-TJx97Iyz7E3pEvy5B0U>lH=;NA=A3HZa|4lcGq-NO$k!?A)JjdL;GAQ5&hCZNBpJXgfjUGbOm9_;iatqW~0%7Eh25OL- zh!LV`flWIcBcyZd&9zH=wI;XRjk`B$X2-@nG~Mzf?7|+2NV*v4wRpO2bLNLw27gEu;&_-+bhRY0X$u^M7s!CQvU$mq6 zT#fg(Vye`cc-u>UM1;A0*MWyTQCR^eaC7w99_Q2+zTc=C@4dsY zv3efp{kVHL+&aiER#6Z#9vW;|vNfq+>DNM)P5fz$TgWEceUEf(VOf}~Uyp!qblH*q zAFcE)Ik6MWd2diDb z@j_=HTrnKPmL$P!AB=tX-O7PWX9WnE7K1l3oVHZPh7DkLuPAuJqV$0PSt`llG^(8M zP2SIS8?I2ij7Rc)1XNkBUvyp8VvkgE-p9b%Q^N9GOi*3gfeh&g9W`HrQq5=AF{mRoG4^_Y4bw(+{Q^tEcm0|gf{W-HLP>pU zZq8sUOV*8>V1(-?5`O)4^1Jl2p3uPBOea1ly`FjK6YXfl3ruZqGn}^%Q_+$j4)~&o8<=`xIO<)b<>!UtnBk%Wzj*s{K5fL1@)cpCD|L9s?%k?Me zXq{i5E4&Wv={^KRw@a(TP!M3DatrID0~J|=6(wLq zr~Un?sqs0txK*Wk(42%aSizyp;!L=!@U$LE>rz8D7mXN>WZ}+mf zy>}1Z^Jf2T+=R)2@+H$_aKJhdeqnBizv6);Rte_7fO<~}HrE5diDtIH@zBgl9ZOMG zzxiJvwr99r=&@P92eQe@FXSY&hgNXYgR@Xhvr-L1zmi0DD+r&SAcxVw`9WYk>xNTe z51tA}X!)W^iy+^>Q1xVCSljz^+ZxzGaQ{$=;LTyjftAdGNVLc)g#^jP(lVlm3OD0cxO z9i-@GE&BnsGQL-z4ok*Cnn1?WPO9lmGGBIdttvK~vq+j%{`yu*HntDhV%TINGezxO zm3Eol7>)|M4Aztv-j~X&{kWI@dgTFnacOTTu|-*fl3x|_~d3oB-H?whViOeKBa?h7(q z-H6V$JM(~-ZvKkJFJOKi1C@{8FVfURb=qAcdic@?Lqzy9XqVm3E*R0`URin%yxuTP zg|cV?O8@*{=_@6kBnX=Hvi+i6$-Y6Wk^HikAhft`5s|o0$U>`Xr%(&py__rN?kSN* zZx$<6^#kn;`kq2sc<58(_l3dLX&*h<)Ku4~v@cn}YkmFiqI+{|?j?EH!h>z;=nuEh z{#((?=_aNKSI&&2Ohzh9EX-*PZYHeVT}5AYq(p!Gk>N2?CZqSTJZ|Sp4;V_Bu9TdI z*%kXvf&3FQQFFiBt@)I-rKQQv`_HGo>lYxY3v(0BkjfYj)N%$}HR>;`u|DijCra0yT? z(PltlU zHY0d|MNA*P{PEv%j9IGe$JUWE9r@NBy7fd(+2H zNH!B7z^Z%1#A8p@c9TfDBd7Y!2Weaxp(-(Y`GowMz&}0uCiSFek-T`pogoW}-6?*L zJc51-q+3l-`x(geP|V-zJ`Z-B@pTam!jMzX?iJyNKqZFR26>ue%(8F@Tu70G zzl%ZC33<#vVXx9;qZ+5l9?iL}FYS(wzYi9%rghY zT$HJj`^OOTrHPYQv#~k>!u4Y055PLKKYQZszN1o@Yg_$T(4C2RoHJ*!g3^hBokHRm3s_ zxeVGry^T7{{h>^cm1N!xYWe6cTHmn#D_iuNLLO5B3h3b%{XRpR7F-eGzIN*enFj1Y z9;$G4XfW~DM`A|Aw9V9{+_`)T2yqyIWbe~B48~HQVnQ2m+x>YfqFIy+dgYp{OE)qA zs6F;}Wz87udYn$4GAoj%hhG6{m@5vN+et!urQU5QV>dB<3RX0DuInYj%4{_ZdJLAO=0)Gf%qbhuv>E_l}r>p4ieh8k^ZV8$R=H1m2ZI z*r?*t=XC#qmt&2eijJC`+lFh@qik5dfFcjPGq&IZ35TE=U~5u7h>_aMhGwQ1nX95W z`l3T7du%Kv$7e6e=b1Mx1^g2$pkt$Ax*_@YQxz)=2LXO1$MENJSHBoc7E}k+1#dXN z>te$?s@ohniNX#IkCRf}N8TU{!h4G2O41>GKX>L0xop%P3k*$TW`IXEgkm;nilL=u zrdvgRjp1ysC~JH1M8FLQO~x9#%>x_wv=H#&+;7ST@!H6-^WZb>j;A_ZO+-fi2kDgg zh(Yz;XIv;tZ^L@>_2h@=#aScLQ2>u>6<#ZSpLvWul%GtUJ)B7mnNx!OUs6T1lM|iFvDUF({Di?2#l*r zIM>x1A*_2Nvz(*~MwV!7S&JgF6vo)MM9LOf zB1*Q%I>XFZLnLcR6hewD*=5U8j3s-FMwERCV;M8N*Y|z@i1+?>f4FD6&vVZCJkR-j z&W5n;x?d$JVZ*>j;FK+T^WWMsgS%|?bH#SRq^$&!C(Ic#qqCkMW&7C;qm^&e=RNABVRT} z)DsDb16HTEsG7e!jn!6VUS{URZnlQ9^Zo9x@jObh4_W84=D=*&ZI)^@g&9DT62u`W zN1x|hObP!>O+@(b3{A~EeE4&cd)cmUinh90$s2xly4vRTAmi%XIx(Ix`w_(U6G2a9w!!6Xfv4W3a+QZ1SbQh+3PrlF#*Z~^$=?lL z(sl7`%srk-DQ$W3XGniL!w5)NiHHbifY_Ljlh)_tIKXFX+?*tMJs)`%3I;7!Y`Wo* zq(VE;Z5VFvgMGxUjAB24 z<<91Bo;Bdl%37|dz5cEP-wLQMaPG|iIjSfrDcKX+n^r8l`-NfnI09Rrt%m&7MK3Aa z5*6t4>`_eeLBW6N^X0j8c61#_k4$FePZuRCy2p#`s?gstwofibUh}0&_qW^i#;RHr zS2>PLCRh&6>CLt=qmlfkEjO8yRSxwnjz8u*gzFkPY>SJ|2{k&AlRXG9;fy@o5t6TK#SKPA^&Ogu&cI8>2AC_XcBGj6nnwRwHXG&+{Mzw^M)SjGfWspGU9`Q=h}+l=Q_AMzW!RU4DMK+O-$Atb@>K+eZVZ*)=de z*BKs?7i_%?u}5Ut)U@m>$|;BD-yz5}9}amNV7uZ1Kn|ZC4kHFmya)%b!}6$aGLT;; z{pW6jUMDXIPO}aIv?yA~Q^A_~%$PS)pUiQ3CA_~n7;(^UlA{m;Hgao!OmV!Y)t-hI zSU8LiqcvLPTpDOtuvFJHQPG|?8;0)97-K2K^8JDq)@@TY%6o2UOT?X?Hf@0<4~;h` zVT#D3pg$d<(=f-6bz`%>#Me*7iz{kws6hFv>p*Ahj5b%c?qo`=9`HqK<>k-H-@sV2 zykdRqg{f8MiJwe0KM1SiAU(y*G&M11HqNMJFlJ202FKgy(mg}qjpCh4K3$X!_HTSneD8E!|) z$BT@s5|#M{9uzBko5PujtK0|)gp0Di;K_g9LMmMSEy{u~%C%pqJycWPYjyo)Z4r?l z_(HfYDsMsAq{lCPMWy1&g72TQfktz2{y1L+w#211!v#y=RjRXXLq?d%ogiO#7aU>R#%qXN=(;t z0sPsp!$Y^efQ7jnfiU++UJ_6YSVt=eBr>lUoLFiNK^00Zl)e)#wkm_qUysiL@l-|G zWJcV@%lG#@c?rjr$Ed3MS*Rdlo0}+^@Ib)?y(KG+V?NsB&IBNOqr^qX2_DK~wMFt@ zh8jx2Rdi0w>nZ5_-xDO~^RJV4Z~xu(1Kl4UWu7G^ZytzcSwgInBADX+MSdPcZ&-lK z=SS}(q)Y2c8-&S#eLQ9OvcvVZaIy@maz)M>p+9-5Ix5x@tQ@#!bJ<5i1-?L!w-d^T ze{Iy|sM&8A8z%zDD=Sbb5oyN%$r_5|qvx4%Hy%9V0fs*2UVF;%Z7ms4B=j#46z#H6 zohE(Xr=06bjH2@*VT&@>dCuu)edwk*)z#xPbuB@8&>{nyu$!^L>XMOuMB?QhwfYrX zMbEZkcHfN?%nfjLd&uewsPxjD~5b)tB(;kA_hHwaKAz$l%b7CVPAD z@sFy3w`eb#D3G_Dk$QzZ+!k7uIj&;a%GWph%dQLoRNiOx>~@ux;3yB=*y#+J`uBwm zzdQeCEqlZhZdghq4oHLDhNq9>@)jUBKYUs8O1e*Ob?hNE&v6-lWnQ_q$BpyJ{SwtD zBuLj+_JP6Un@6#fY;G(RQ(V)KWgq6HsD98RT78z0f$w5EOFYVBEm2N+D z=d>S!M2G4jIaMM*MybaN`h~_Ex2dCddv47~>t44sq7#GoGNHm4#UQyLIwh~^2KB~* zecM<;<||efW>=yO@XNlfjvd2KAcV*a{9wClDQjC)!#2OP^aA^sUb-RhIVbo8g$(A4 z0P*wZa6H)-8A5!KLZhi@Q53B#&D7w$_;BlqBKsa#2kt$%n3s#cCp-sKd?oN;N`rhE z8NGT3=#7fj$*C&%mgYC78yLEYn=CIN$4fom4=k#swM<4;t|kT2KV8HBv~%dttOt%; zA}ucQ#G=3k=I7uJH4s&nD@MU5lyfmigorR!j6qu$Xc9kH|3zANDQWQTgy zrPj8jkhrl*Fi^9d`T<$fJkhWOG(=Q@kvfCmwYdP_w}xA{gxPw`{J8I}M-wOtq^AZG z<^Ba$S0}Z6OxqWJlhl$!C78^6yPqo>LP^TJ(vNq&k*~6N!F?q`qUy2nE#bN&=TO*W zJy7gstR5;Fa-@S*^YuyL!z%+S3Jh)x)IdK^vfmmjIT%?KUr7vz>*RF#E|=7sZAc${ z^>$pwSvFkG3#rl1H7Rg?sQ%lbPDEy62A70F^wuPRTJZnvF$O1k_+wkU6G6FrK07!4 z%DhUwgU+O5r()xIlDnOF!$1UK+1Ck^@ipVbUxAPbA64Vm8zecIqxu&YnO}6=E8ImTU0BX~5a_RN zq38>96218PrjdBPO?7i6n<}`WM3VEc`!HKsfB2Ql4ZvgoWlDGw5AF(&yd-?zt4-B9Ckom%Nn?Yp;`{94ABgsUcX zOxD05ep7-W-rn?t9CIykYsjLZM4wVoFA_4AtaIX=1+X4*oFS#Hw;Q>|F;U^Hzt_2t zvzNQvK#B4v5USqWUX#L~0TGFY-*@XDE^Rx=OJE}Z< z)Fpuw44Qg7hlDPGPGtPR|Ct}qE;*RplOg8bXK%w-nC%jyO9asymC4gH&PJy~iVp3X zZ-uUtDyqKs=g6^33!QZ23@=?O?)O~#9r+Xg>f*)QcO(ZJyVWRK*k?V}UAQIxuwvqa zXT44EbI%L14)F?fE-bizG>}-}|5XSiV*Z;V{-3_Kn!%{YW0B@#fBa@^LBM0Edqby8 I%Q5_a02g-dzW@LL literal 0 HcmV?d00001 diff --git a/ios/CDrop/Shared/Theme.swift b/ios/CDrop/Shared/Theme.swift index 7569585..415fd5d 100644 --- a/ios/CDrop/Shared/Theme.swift +++ b/ios/CDrop/Shared/Theme.swift @@ -11,4 +11,12 @@ extension Color ? UIColor(red: 0.584, green: 0.502, blue: 1.0, alpha: 1) : UIColor(red: 0.392, green: 0.290, blue: 0.788, alpha: 1) }) + + // 品牌琥珀色(字标里「Drop」用色,对齐 web 品牌资产):浅色 #DB9410 / 深色 #F8C637。 + static let cdropAmber = Color(uiColor: UIColor + { trait in + trait.userInterfaceStyle == .dark + ? UIColor(red: 0.973, green: 0.776, blue: 0.216, alpha: 1) + : UIColor(red: 0.859, green: 0.580, blue: 0.063, alpha: 1) + }) } diff --git a/ios/CDrop/Sources/AppDelegate.swift b/ios/CDrop/Sources/AppDelegate.swift index f478c07..63698a0 100644 --- a/ios/CDrop/Sources/AppDelegate.swift +++ b/ios/CDrop/Sources/AppDelegate.swift @@ -10,6 +10,8 @@ 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 cd5aeeb..c1ef588 100644 --- a/ios/CDrop/Sources/Auth/LoginView.swift +++ b/ios/CDrop/Sources/Auth/LoginView.swift @@ -14,9 +14,13 @@ struct LoginView: View VStack(spacing: 20) { Spacer() - Text(t("app.brand")) - .font(.largeTitle) - .bold() + // 品牌(登录页):吉祥物 + 字标(Fredoka),取代原纯文字标题——首屏即见完整品牌。 + VStack(spacing: 14) + { + BrandMark(size: 124) + Wordmark(size: 30) + } + .padding(.bottom, 8) // 主登录:应用内 broker 账号登录。 Button { Task { await auth.startBrokerLogin() } } @@ -31,7 +35,7 @@ struct LoginView: View if !auth.statusText.isEmpty { Text(auth.statusText) - .font(.callout) + .font(.oCallout) .foregroundStyle(auth.qrExpired ? Color.red : Color.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 40) @@ -45,7 +49,7 @@ struct LoginView: View qrArea refreshButton Text(t("ios.login.guide")) - .font(.footnote) + .font(.oFootnote) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } @@ -54,7 +58,7 @@ struct LoginView: View label: { Text(t("ios.login.scanAlt")) - .font(.subheadline) + .font(.oSubheadline) } .padding(.horizontal, 40) .onChange(of: showQR) diff --git a/ios/CDrop/Sources/BrandFonts.swift b/ios/CDrop/Sources/BrandFonts.swift new file mode 100644 index 0000000..6216729 --- /dev/null +++ b/ios/CDrop/Sources/BrandFonts.swift @@ -0,0 +1,81 @@ +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 647289a..6a4724c 100644 --- a/ios/CDrop/Sources/CDropApp.swift +++ b/ios/CDrop/Sources/CDropApp.swift @@ -15,12 +15,21 @@ struct CDropApp: App WindowGroup { AppRoot(auth: auth, engine: engine) + // 全局默认字体=Orbit Gothic 正文(无显式 .font 的文本随之走品牌 Sans)。显式语义样式 + // 由各视图改用 .font(.oXxx)(见 BrandFonts)覆盖到品牌字。 + .environment(\.font, .oBody) } .onChange(of: scenePhase) { _, phase in + guard phase == .active else { return } + // 回前台即重扫 App Group 收件箱:iOS 对 share 扩展唤起宿主 app 有限制、未必生效,故无论扩展 + // 是否成功把 app 拉前台,只要回到前台(手动切回或被拉起)都重扫一次——有待发分享文件即弹 + // 发送预览,免去「必须重启 app 分享才出现」(onAppear 仅冷启触发、回前台不触发,是要重启的根因)。 + // 空收件箱无副作用。 + engine.loadPendingShares() // 回到前台即确保控件 / 剪贴板会话已铸(修「关闭态剪贴板不可用」:引擎冷启 ready 时铸一次, // 这里补暖前台场景)。引擎持完整主会话铸独立控件会话(隔离于主会话),仅缺失时铸、不重复轮换。 - if phase == .active, auth.session != nil + if auth.session != nil { engine.provisionWidgetSessionIfNeeded() } diff --git a/ios/CDrop/Sources/Engine/EngineController.swift b/ios/CDrop/Sources/Engine/EngineController.swift index b2a2f10..d207e47 100644 --- a/ios/CDrop/Sources/Engine/EngineController.swift +++ b/ios/CDrop/Sources/Engine/EngineController.swift @@ -30,6 +30,23 @@ struct MessageItem: Identifiable, Equatable, Codable let sentAt: Double } +// 会话摘要(消息按设备聚合,社交软件式):每个对端设备一条,驱动消息 tab 的会话列表。由 +// conversations() 从 messages(取每设备最新一条)∪ 当前可达设备(在线 / 可唤醒,供发起新会话)合成。 +// reachable=在线或可经 APNs 唤醒(离线 iOS)——决定 ConversationView 是否可发:不可达的离线设备 +// (离线桌面 / 浏览器)仅可查看历史、不可发(对齐 sendableDevices 语义)。 +struct ConversationItem: Identifiable, Equatable +{ + let peerName: String + let deviceID: String // 匹配到的当前设备 id(用于图标 / 判型);无匹配(历史残留设备)为空 + let deviceType: String + let online: Bool + let reachable: Bool // 在线或可唤醒 → 可发;否则仅查看 + let lastText: String + let lastAt: Double // 最近一条消息时间(epoch 秒);无历史(仅可达)为 0,排在有历史之后 + let lastOutgoing: Bool + var id: String { peerName } +} + // 引擎过桥推来的传输记录(对应 web engine/main.ts 的 toWire 精简视图)。ice* 为选中候选 // 对诊断(揭示 P2P 是否实为 TURN 中继,解释慢速)。同样手动容忍解析。 struct TransferItem: Identifiable, Equatable, Codable @@ -68,6 +85,24 @@ struct LogEntry: Identifiable, Equatable let level: String // warn | error | info let message: String let at: Date + // 从 "session ..." 形式的诊断行解析出的传输会话 id(引擎诊断行 + pion 内部日志统一此格式), + // 供日志界面按会话聚合 / 整体复制;非会话日志(SSE / 鉴权等)为 nil。 + let sessionId: String? + + init(level: String, message: String, at: Date) + { + self.level = level + self.message = message + self.at = at + self.sessionId = LogEntry.parseSession(message) + } + + static func parseSession(_ msg: String) -> String? + { + guard msg.hasPrefix("session ") else { return nil } + let id = msg.dropFirst("session ".count).prefix { !$0.isWhitespace } + return id.isEmpty ? nil : String(id) + } } // 待批准的他端登录请求(#8):本机(已登录)扫到另一台设备显示的登录二维码(/link?r=&c=)后,经引擎 @@ -117,10 +152,13 @@ final class EngineController: NSObject // 待确认的他端登录请求(#8):扫码拿到信息后置位,驱动设备页弹批准确认;批准 / 拒绝 / 取消后清空。 var pendingLoginRequest: LoginRequestItem? - // 未读红点(#5):消息 / 传输各自的未读计数,对应 tab 激活时清零。activeTab 记录当前所在 - // 标签,使「正在该页时到达的事件」不计未读。seenIncomingTransfers 去重,避免同一传入传输在 - // 活跃→完成两阶段被重复计数。 - var unreadMessages = 0 + // 未读红点(#5):传输未读对应 tab 激活时清零(activeTab 记录当前页,使「正在该页时到达的事件」不 + // 计未读);seenIncomingTransfers 去重,避免同一传入传输在活跃→完成两阶段被重复计数。 + // 消息未读按设备分桶(unreadByPeer):每设备各自未读计数(会话列表逐行红点),消息 tab 徽标=总和 + // (unreadMessages 计算属性);activeConversation 记录当前打开的会话,使「正在该会话时到达的消息」不计未读。 + var unreadByPeer: [String: Int] = [:] + var activeConversation: String? + var unreadMessages: Int { unreadByPeer.values.reduce(0, +) } var unreadTransfers = 0 var activeTab = "transfer" private var seenIncomingTransfers: Set = [] @@ -155,6 +193,14 @@ final class EngineController: NSObject // 回取字节(下方 WKURLSchemeHandler 供给)。 private var outgoing: [String: URL] = [:] + // 原生数据面引擎(libwebrtc,Safari 同款 RTCPeerConnection):逃离 WKWebView 写死的 256KB SCTP + // 接收窗 + 渲染器节流(见 desktop/NATIVE-TRANSFER.md §7)。取代早先 gomobile/pion——pion 的 Go raw + // socket 不与 iOS Network framework 集成,真机确证不可靠。懒构造(首次 P2P RPC 时建),落盘= + // Documents(与 DownloadManager 同源,故收到的文件经「收到的文件」视图可见)。p2pEvents 强持引用 + // (引擎只弱引它),避免回调宿主被释放。libwebrtc 自管线程(信令 / 网络线程),无需宿主串行队列。 + private var transferEngine: LibWebRtcEngine? + private var p2pEvents: P2PEventsBridge? + // 历史 / 消息限长(原生持久化,跨重启存活)。 private static let historyCap = 50 private static let messagesCap = 200 @@ -276,7 +322,8 @@ final class EngineController: NSObject // 登出清空瞬时态:通知 / 日志 / 未读 / 已读传输集 / 待批准登录,不跨账号残留。 notices = [] logs = [] - unreadMessages = 0 + unreadByPeer = [:] + activeConversation = nil unreadTransfers = 0 seenIncomingTransfers = [] activeTab = "transfer" @@ -307,7 +354,11 @@ final class EngineController: NSObject func clearHistory() { history.removeAll() - RecordsStore.clear(recordKey("history")) + // 落空数组到分桶键(而非删文件):activateRecords 在分桶键缺失时回退读 legacy 未分桶文件, + // 直接删文件会让旧记录重启复活(清空按钮失效的根因);写 [] 使 load 返回空数组、阻断回退。 + // 顺手清掉 legacy 未分桶文件。 + persistHistory() + RecordsStore.clear("history") } // 删除一条消息(H)/ 清空全部(持久化同步)。 @@ -320,7 +371,18 @@ final class EngineController: NSObject func clearMessages() { messages.removeAll() - RecordsStore.clear(recordKey("messages")) + // 同 clearHistory:落空数组阻断 legacy 回退(删文件会令清空后的消息重启复活),再清 legacy 文件。 + persistMessages() + RecordsStore.clear("messages") + } + + // 删除与某设备的整段会话历史(按对端设备名):清掉该设备的全部消息,避免废会话 / 历史残留堆积。 + // 仅删本地展示记录(持久化同步),不影响对方。删空后该会话从列表消失(除非该设备当前可达, + // 仍作「可发起新会话」项保留)。 + func deleteConversation(_ peerName: String) + { + messages.removeAll { $0.peerName == peerName } + persistMessages() } // ---- 瞬时通知(#2) ---- @@ -350,10 +412,28 @@ final class EngineController: NSObject func setActiveTab(_ tab: String) { activeTab = tab - if tab == "messages" { unreadMessages = 0 } + // 消息未读不在进入 tab 时清零(那只是看到会话列表、尚未读各设备消息)——改为打开某会话时清该 + // 设备未读(见 markConversationRead)。传输未读仍按 tab 激活清零。 if tab == "transfer" { unreadTransfers = 0 } } + // 打开某设备会话(ConversationView.onAppear):记录当前会话 + 清该设备未读;关闭(onDisappear)置 nil。 + func setActiveConversation(_ peerName: String?) + { + activeConversation = peerName + } + + func markConversationRead(_ peerName: String) + { + if unreadByPeer[peerName] != nil { unreadByPeer[peerName] = 0 } + } + + // 某设备的未读消息数(会话列表行红点用)。 + func unread(for peerName: String) -> Int + { + return unreadByPeer[peerName] ?? 0 + } + // noteIncomingTransfer:传入传输首次出现(活跃或完成)时计一次未读,按 sessionId 去重,且仅当 // 不在传输页时计。本机发出的传输不计。 private func noteIncomingTransfer(_ item: TransferItem) @@ -405,6 +485,57 @@ final class EngineController: NSObject return Self.isWakeable(dev) ? "\(dev.name) · \(t("home.device.wakeable"))" : dev.name } + // ---- 消息按设备聚合(社交软件式会话) ---- + + // 会话列表:每个对端设备一条。取「有消息历史的设备」∪「当前可达设备(在线 / 可唤醒,供发起新 + // 会话)」,匹配当前 presence 取设备型号 / 在线态。按最近消息时间降序(无历史的可达设备 lastAt=0 + // 排在最后),最近活跃的会话在最前。 + func conversations() -> [ConversationItem] + { + // 每个对端的最新一条消息(messages 最新在前,故首次见到即最新)。 + var latest: [String: MessageItem] = [:] + for m in messages where latest[m.peerName] == nil { latest[m.peerName] = m } + + let reachable = sendableDevices() + let reachableNames = Set(reachable.map { $0.name }) + + // 候选对端:有历史的 ∪ 当前可达的(去重)。 + var peers = Set(latest.keys) + peers.formUnion(reachableNames) + peers.remove(deviceName) // 不与本机会话 + + var items: [ConversationItem] = [] + for peer in peers + { + let dev = devices.first { $0.name == peer } + let last = latest[peer] + items.append(ConversationItem( + peerName: peer, + deviceID: dev?.deviceID ?? "", + deviceType: dev?.type ?? "", + online: dev?.online ?? false, + reachable: reachableNames.contains(peer), + lastText: last?.text ?? "", + lastAt: last?.sentAt ?? 0, + lastOutgoing: last?.direction == "outgoing")) + } + // 最近消息在前;时间相同(多为均无历史)按名稳定排序。 + return items.sorted { a, b in a.lastAt != b.lastAt ? a.lastAt > b.lastAt : a.peerName < b.peerName } + } + + // 与某设备的消息线程(最旧→最新,供聊天视图自上而下渲染)。 + func thread(with peerName: String) -> [MessageItem] + { + return messages.filter { $0.peerName == peerName }.sorted { $0.sentAt < $1.sentAt } + } + + // 某对端当前是否可发消息:在线或可经 APNs 唤醒(=在 sendableDevices 中)。不可达的离线设备 + // (离线桌面 / 浏览器)会话仅可查看历史、撰写栏禁用。 + func canMessage(_ peerName: String) -> Bool + { + return sendableDevices().contains { $0.name == peerName } + } + // 剪贴板上行:读本机 UIPasteboard 文本交引擎上传云剪贴板(用户手势触发,前台读取合规)。 func uploadClipboard() { @@ -617,6 +748,54 @@ final class EngineController: NSObject let data = (try handle.read(upToCount: count)) ?? Data() return data.base64EncodedString() } + + // ---- 原生数据面(gomobile/pion)---- + + // 懒构造原生引擎:落盘目录=Documents(与 DownloadManager 同源),事件经 P2PEventsBridge + // 跳回主线程过桥给 JS。libwebrtc 工厂构造不会失败(连接级失败按会话经 RPC error 上报)。 + private func nativeTransferEngine() -> LibWebRtcEngine + { + if let e = transferEngine { return e } + let events = P2PEventsBridge() + events.controller = self + let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].path + let e = LibWebRtcEngine(downloadDir: dir, delegate: events) + p2pEvents = events + transferEngine = e + return e + } + + // 把 cdrop-file:// 引用解析回暂存文件的沙盒绝对路径(Go 据此直接读盘发送)。安全作用域 + // 访问已在 stageOutgoingFile 开启、持续到 reset,故同进程的 Go 可直接 os.Open 该路径。无对应 + // 暂存项返回空串。 + private func resolveOutgoingPath(_ ref: String) -> String + { + guard let id = URL(string: ref)?.host, let url = outgoing[id] else { return "" } + return url.path + } + + // resolveOnMain 把引擎完成回调(在 libwebrtc 信令线程触发,非主线程)跳回主线程 resolve 该 RPC。 + // nonisolated 使其可从任意线程调用;内部经 DispatchQueue.main + assumeIsolated 进 @MainActor 调 + // resolve(evaluateJavaScript 仅限主线程)。error≠nil → ok=false(该会话回退中继 / 报错)。 + nonisolated private func resolveOnMain(id: Int, error: Error?) + { + DispatchQueue.main.async + { + MainActor.assumeIsolated + { + self.resolve(id: id, ok: error == nil, value: error?.localizedDescription) + } + } + } + + // 原生引擎诊断日志入环形缓冲(#3 日志界面)。由 P2PEventsBridge.onLog 在主线程调用。 + func appendNativeLog(_ msg: String) + { + guard !msg.isEmpty else { return } + lastEngineLog = msg + logs.insert(LogEntry(level: "info", message: msg, at: Date()), at: 0) + if logs.count > Self.logsCap { logs.removeLast(logs.count - Self.logsCap) } + } } // MARK: - JS → 原生(RPC + 通知) @@ -669,6 +848,40 @@ extension EngineController: WKScriptMessageHandler start: Self.intOf(payload["start"]), end: Self.intOf(payload["end"])) resolve(id: id, ok: true, value: b64) + case "p2pStartOutgoing": + // 发起原生发送:filePath 为 cdrop-file://,解析回沙盒绝对路径交引擎直接读盘。引擎 + // 调用立即返回(libwebrtc 异步),completion 在 offer 已铸+发出后回,经 resolveOnMain 回交。 + let engine = nativeTransferEngine() + let path = resolveOutgoingPath(payload["filePath"] as? String ?? "") + guard !path.isEmpty + else { resolve(id: id, ok: false, value: "no staged file"); return } + let sid = payload["sessionId"] as? String ?? "" + let peer = payload["peerName"] as? String ?? "" + let ice = payload["iceServersJSON"] as? String ?? "" + engine.startOutgoing(sessionId: sid, peerName: peer, filePath: path, iceServersJSON: ice) + { [weak self] err in self?.resolveOnMain(id: id, error: err) } + case "p2pStartIncoming": + // 预备原生接收:建 PeerConnection 等对端 offer,收到即直接写盘到 Documents。 + let engine = nativeTransferEngine() + let sid = payload["sessionId"] as? String ?? "" + let peer = payload["peerName"] as? String ?? "" + let ice = payload["iceServersJSON"] as? String ?? "" + engine.startIncoming(sessionId: sid, peerName: peer, iceServersJSON: ice) + { [weak self] err in self?.resolveOnMain(id: id, error: err) } + case "p2pHandleSignal": + // 把 JS hub 从 SSE 收到的入站信令喂给原生引擎(按对端设备名路由)。completion 在信令应用 + // 完成后回——JS 侧信令链据此按序投递(offer/answer 先于其后 trickle 候选,见 p2pIos.ts chain)。 + guard let engine = transferEngine + else { resolve(id: id, ok: false, value: "no native session"); return } + let from = payload["fromPeer"] as? String ?? "" + let pj = payload["payloadJSON"] as? String ?? "" + engine.handleSignal(fromPeer: from, payloadJSON: pj) + { [weak self] err in self?.resolveOnMain(id: id, error: err) } + case "p2pCancel": + // 取消 / 中继回退:拆除原生会话(静默,不回 OnState)。libwebrtc close 快,直接调,立即 resolve。 + let sid = payload["sessionId"] as? String ?? "" + transferEngine?.cancel(sessionId: sid) + resolve(id: id, ok: true, value: nil) default: resolve(id: id, ok: false, value: "unknown method: \(method)") } @@ -729,8 +942,12 @@ extension EngineController: WKScriptMessageHandler messages.removeLast(messages.count - Self.messagesCap) } persistMessages() - // 新到的对端消息:不在消息页时计未读红点(#5)。本机发出的乐观回显(outgoing)不计。 - if isNew, item.direction == "incoming", activeTab != "messages" { unreadMessages += 1 } + // 新到的对端消息:非「正在该设备会话中」即给该设备计未读(按设备分桶)。本机发出的 + // 乐观回显(outgoing)不计。 + if isNew, item.direction == "incoming", item.peerName != activeConversation + { + unreadByPeer[item.peerName, default: 0] += 1 + } } case "transferDone": // 完成项移出活跃、前插历史;按 sessionId 去重避免重复事件叠加。 diff --git a/ios/CDrop/Sources/Engine/LibWebRtcEngine.swift b/ios/CDrop/Sources/Engine/LibWebRtcEngine.swift new file mode 100644 index 0000000..2cf8598 --- /dev/null +++ b/ios/CDrop/Sources/Engine/LibWebRtcEngine.swift @@ -0,0 +1,905 @@ +import Foundation +import WebRTC + +// LibWebRtcEngine —— iOS 原生数据面引擎,用 libwebrtc(Safari 同款 RTCPeerConnection)在原生进程内 +// 完成 P2P 文件收发。取代早先 gomobile/pion 路径:pion 的 Go raw socket 不与 iOS Network framework +// 集成(WebKit/libwebrtc 才集成),真机确证架构性不可靠(见 desktop/NATIVE-TRANSFER.md §7)。 +// +// 线协议与 desktop/engine(pion)及 web/src/features/transfer/p2p.ts 逐字节一致,故可与桌面 pion +// 引擎、浏览器 / 旧 iOS 的 JS 引擎互通:DataChannel "cdrop-file"(ordered),控制帧 meta/done/ack 走 +// 文本帧、文件分片走二进制帧(64KB),信令 payload 形如 {type, sdp?, candidate?}。本类是 session.go + +// engine.go 的忠实 Swift 端口——逻辑、水位、ack 语义、收尾顺序一一对应。 +// +// 分工同桌面:本引擎只跑纯 WebRTC + 文件 I/O,一切 HTTP(出站信令 POST / 状态机 POST)留 JS +// (见 web/src/features/transfer/p2pIos.ts)。事件经 P2PEngineDelegate 回灌——宿主即 EngineController +// 经 cdropEngine 桥(P2PEventsBridge)转给无头 JS 引擎。 +// +// 宿主契约(EngineController 的 4 个 p2p RPC + net/ios.ts 的 nativeP2P*)一字不改;只把 Swift 侧引擎 +// 从 gomobile EngineEngine 换成本类。翻 web 侧 IOS_NATIVE=true 即启用。 + +// MARK: - 事件回调(宿主实现,等价 desktop/engine 的 Events 接口) + +protocol P2PEngineDelegate: AnyObject +{ + // 已传字节:发送端=接收端 ack 追平值(无 ack 时回退本地已交付估计),接收端=已写盘字节。 + func engineDidProgress(sessionId: String, bytes: Int64) + // 会话状态:connected / completed / failed / closed。 + func engineDidChangeState(sessionId: String, state: String) + // 出站信令(offer/answer/ice):payloadJSON 为信令 JSON 串,宿主 POST /api/hub/signal。 + func engineDidEmitSignal(sessionId: String, toPeer: String, payloadJSON: String) + // 接收端落盘完成的绝对路径(沙盒 Documents)。 + func engineDidSave(sessionId: String, path: String) + // 选中候选对(形如 "host/udp"),供调试面板展示实际连通路径。 + func engineDidSelectIcePair(sessionId: String, local: String, remote: String) + // 诊断日志(候选 / ICE 态 / 选中对 / 发送停滞),形如 "session ...",供日志界面按会话聚合。 + func engineDidLog(_ line: String) +} + +// MARK: - 线协议常量(与 desktop/engine wire.go、web p2p.ts 一致) + +private enum Wire +{ + static let channelName = "cdrop-file" + static let chunkSize = 64 * 1024 + static let highWatermark: UInt64 = 16 * 1024 * 1024 + static let lowWatermark: UInt64 = 4 * 1024 * 1024 + static let ackIntervalMs = 200 + static let progressThrottleNs: Int64 = 100 * 1_000_000 // ~10Hz,护宿主主线程(每片过桥 evaluateJavaScript) + static let ackCompleteTimeout: TimeInterval = 30 + // ICE 候选预热池,与 web p2p.ts ICE_CANDIDATE_POOL 对齐。 + static let iceCandidatePool: Int32 = 4 +} + +struct EngineError: LocalizedError +{ + let msg: String + init(_ m: String) { msg = m } + var errorDescription: String? { msg } +} + +// MARK: - 引擎(持有所有活跃会话,按对端设备名索引,等价 engine.go 的 Engine) + +final class LibWebRtcEngine +{ + // 单次 SSL 初始化(libwebrtc 要求)。全局 let 惰性、线程安全地执行一次。 + private static let sslReady: Bool = RTCInitializeSSL() + + private let factory: RTCPeerConnectionFactory + // 内部可设:生产由 init 注入(EngineController 的 P2PEventsBridge);测试可重定向到环回宿主。 + weak var delegate: P2PEngineDelegate? + + private let lock = NSLock() + private var byPeer: [String: P2PTransferSession] = [:] + private var downloadDirValue: String + + init(downloadDir: String, delegate: P2PEngineDelegate) + { + _ = LibWebRtcEngine.sslReady + factory = RTCPeerConnectionFactory() + downloadDirValue = downloadDir + self.delegate = delegate + } + + var events: P2PEngineDelegate? { delegate } + + func downloadDir() -> String + { + lock.lock(); defer { lock.unlock() } + return downloadDirValue + } + + func setDownloadDir(_ dir: String) + { + lock.lock(); downloadDirValue = dir; lock.unlock() + } + + // StartOutgoing:建 PeerConnection + DataChannel,发 offer,通道就绪后流式发出 filePath。 + // filePath 为本机绝对路径(原生从 cdrop-file:// 解析得到)。completion 在 offer 已铸并 + // setLocalDescription 完成后回(或出错时回错误),对齐 JS 侧信令链 seed 语义(见 p2pIos.ts chain)。 + func startOutgoing(sessionId: String, peerName: String, filePath: String, + iceServersJSON: String, completion: @escaping (Error?) -> Void) + { + let fm = FileManager.default + var isDir: ObjCBool = false + guard fm.fileExists(atPath: filePath, isDirectory: &isDir), !isDir.boolValue + else { completion(EngineError("not a file: \(filePath)")); return } + let size = ((try? fm.attributesOfItem(atPath: filePath))?[.size] as? NSNumber)?.int64Value ?? 0 + + let s = P2PTransferSession(engine: self, factory: factory, + sessionId: sessionId, peerName: peerName, role: .sender) + s.filePath = filePath + s.fileSize = size + do { try s.createPeerConnection(iceServersJSON) } + catch { completion(error); return } + put(peerName, s) + do { try s.createSenderDataChannel() } + catch { s.fail("create datachannel: \(error.localizedDescription)"); completion(error); return } + s.createOfferAndEmit(completion: completion) + } + + // StartIncoming:建 PeerConnection 等对端 offer,收到 DataChannel 后按线协议落盘。须在对端 + // offer 到达前调用。completion 立即回(会话已登记,后续入站 offer/ice 可路由)。 + func startIncoming(sessionId: String, peerName: String, + iceServersJSON: String, completion: @escaping (Error?) -> Void) + { + let s = P2PTransferSession(engine: self, factory: factory, + sessionId: sessionId, peerName: peerName, role: .receiver) + do { try s.createPeerConnection(iceServersJSON) } + catch { completion(error); return } + put(peerName, s) + completion(nil) + } + + // HandleSignal:把宿主从 SSE 收到的入站信令喂给对应会话(按对端设备名路由)。completion 在信令 + // 应用完成后回——对 offer 是 answer 已铸并发出后,对 answer 是 setRemoteDescription 完成后,对 + // ice 是 addIceCandidate 完成后。这保证 JS 侧信令链按序投递(offer/answer 先于其后 trickle 候选)。 + func handleSignal(fromPeer: String, payloadJSON: String, completion: @escaping (Error?) -> Void) + { + lock.lock(); let s = byPeer[fromPeer]; lock.unlock() + guard let s + else { completion(EngineError("no active session for peer \(fromPeer)")); return } + s.handleSignal(payloadJSON, completion: completion) + } + + // Cancel:拆除指定会话(取消 / 中继回退时由宿主调用)。静默拆除,不回 OnState。 + func cancel(sessionId: String) + { + lock.lock() + var target: P2PTransferSession? + for (_, s) in byPeer where s.sessionId == sessionId { target = s; break } + lock.unlock() + target?.cleanup() + } + + fileprivate func put(_ peer: String, _ s: P2PTransferSession) + { + lock.lock() + let old = byPeer[peer] + byPeer[peer] = s + lock.unlock() + // 同一对端若已有会话,先拆旧的(沿用 web / 桌面按对端覆盖的语义)。异步拆,避免在 RPC / 主线程 + // 上同步 close(对齐 engine.go 的 `go old.cleanup()`)。 + if let old, old !== s { DispatchQueue.global(qos: .utility).async { old.cleanup() } } + } + + fileprivate func remove(_ peer: String, _ s: P2PTransferSession) + { + lock.lock() + if byPeer[peer] === s { byPeer.removeValue(forKey: peer) } + lock.unlock() + } +} + +// MARK: - 单条传输会话(pc + dc + 角色相关收发循环,等价 session.go 的 session) + +final class P2PTransferSession: NSObject +{ + enum Role { case sender, receiver } + + let sessionId: String + let peerName: String + let role: Role + private weak var engine: LibWebRtcEngine? + private let factory: RTCPeerConnectionFactory + + private var pc: RTCPeerConnection? + private var dc: RTCDataChannel? + + // 发送端 + var filePath = "" + var fileSize: Int64 = 0 + + // 计数器(跨线程:发送循环 / pion 回调 / dc 委托)。cLock 守护。 + private let cLock = NSLock() + private var bytesSent: Int64 = 0 // 已塞进本地 SCTP 缓冲的字节 + private var receivedBytes: Int64 = 0 // 接收端已写盘字节 + private var ackedBytes: Int64 = 0 // 接收端经 ack 回传的已收字节 + private var lastEmitNs: Int64 = 0 // 进度节流时间戳 + + // 接收端落盘。outLock 守护 outHandle 的读/写/关——writeChunk 在 libwebrtc 信令线程跑,cleanup 可能 + // 在主线程(JS p2pCancel / 中继回退 / SSE 终态)并发关句柄;无锁则 ARC retain/release 与 close/write + // 同体竞争 → 可能崩溃(Go 的 *os.File 有内建 fdMutex 故安全,Swift FileHandle 无此保证)。 + private var outHandle: FileHandle? + private var outName = "" + private var tmpPath = "" + private let outLock = NSLock() + + // 发送流 / 背压 + private let streamQueue: DispatchQueue + private let bufSem = DispatchSemaphore(value: 0) // didChangeBufferedAmount / ack 唤醒等待循环 + private var ackTimer: DispatchSourceTimer? + private var senderStarted = false + + // 生命周期 + private let stateLock = NSLock() + private var state = "" + private var doneFlag = false + private var cleanedUp = false + + init(engine: LibWebRtcEngine, factory: RTCPeerConnectionFactory, + sessionId: String, peerName: String, role: Role) + { + self.engine = engine + self.factory = factory + self.sessionId = sessionId + self.peerName = peerName + self.role = role + streamQueue = DispatchQueue(label: "net.commilitia.cdrop.p2p.\(sessionId)") + super.init() + } + + // MARK: 建连 + + func createPeerConnection(_ iceServersJSON: String) throws + { + let config = RTCConfiguration() + config.iceServers = Self.parseICEServers(iceServersJSON) + config.sdpSemantics = .unifiedPlan + config.continualGatheringPolicy = .gatherOnce + config.iceCandidatePoolSize = Wire.iceCandidatePool + // 默认网络处理(不禁链路本地、不强 IPv4、不限接口):libwebrtc 与 iOS Network framework 原生 + // 集成,同内网经 host↔host 直连。绝不在此加 iOS 定向过滤——曾把 IPv4-only 漏进桌面共享引擎致 + // 桌面↔iOS-WebKit 的 IPv6 host 对被裁、退中继(重大回归)。 + let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) + guard let pc = factory.peerConnection(with: config, constraints: constraints, delegate: self) + else { throw EngineError("failed to create peer connection") } + self.pc = pc + } + + func createSenderDataChannel() throws + { + guard let pc else { throw EngineError("no pc") } + let dcConfig = RTCDataChannelConfiguration() + dcConfig.isOrdered = true + guard let dc = pc.dataChannel(forLabel: Wire.channelName, configuration: dcConfig) + else { throw EngineError("failed to create data channel") } + dc.delegate = self + self.dc = dc + } + + func createOfferAndEmit(completion: @escaping (Error?) -> Void) + { + guard let pc else { completion(EngineError("no pc")); return } + let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) + pc.offer(for: constraints) + { [weak self] sdp, err in + guard let self else { completion(EngineError("session gone")); return } + if let err { self.fail("create offer: \(err.localizedDescription)"); completion(err); return } + guard let sdp else { self.fail("create offer: nil"); completion(EngineError("nil offer")); return } + pc.setLocalDescription(sdp) + { [weak self] err2 in + guard let self else { completion(EngineError("session gone")); return } + if let err2 + { + self.fail("set local (offer): \(err2.localizedDescription)"); completion(err2); return + } + self.emitSignal(sdp: sdp, candidate: nil) + completion(nil) + } + } + } + + // MARK: 入站信令 + + func handleSignal(_ payloadJSON: String, completion: @escaping (Error?) -> Void) + { + guard let pc else { completion(EngineError("no pc")); return } + guard let data = payloadJSON.data(using: .utf8), + let obj = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any], + let type = obj["type"] as? String + else { completion(EngineError("decode signal")); return } + log("signal in: \(type)") + + switch type + { + case "offer": + guard let sdpObj = obj["sdp"] as? [String: Any], let sdpStr = sdpObj["sdp"] as? String + else { completion(EngineError("offer missing sdp")); return } + let desc = RTCSessionDescription(type: .offer, sdp: sdpStr) + pc.setRemoteDescription(desc) + { [weak self] err in + guard let self else { completion(EngineError("gone")); return } + if let err { completion(err); return } + let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) + pc.answer(for: constraints) + { [weak self] ans, aerr in + guard let self else { completion(EngineError("gone")); return } + if let aerr { completion(aerr); return } + guard let ans else { completion(EngineError("nil answer")); return } + pc.setLocalDescription(ans) + { [weak self] lerr in + guard let self else { completion(EngineError("gone")); return } + if let lerr { completion(lerr); return } + self.emitSignal(sdp: ans, candidate: nil) + completion(nil) + } + } + } + case "answer": + guard let sdpObj = obj["sdp"] as? [String: Any], let sdpStr = sdpObj["sdp"] as? String + else { completion(EngineError("answer missing sdp")); return } + let desc = RTCSessionDescription(type: .answer, sdp: sdpStr) + pc.setRemoteDescription(desc) { err in completion(err) } + case "ice": + // 空候选 / end-of-candidates:忽略(pion 同样跳过 nil candidate)。 + guard let candObj = obj["candidate"] as? [String: Any], + let candStr = candObj["candidate"] as? String, !candStr.isEmpty + else { completion(nil); return } + let mid = candObj["sdpMid"] as? String + let mline = (candObj["sdpMLineIndex"] as? NSNumber)?.int32Value ?? 0 + let cand = RTCIceCandidate(sdp: candStr, sdpMLineIndex: mline, sdpMid: mid) + pc.add(cand) { err in completion(err) } + default: + completion(nil) + } + } + + // MARK: 出站信令 + + // 与 session.go emitSignal 同形:有 candidate 即 ice,否则取 sdp 自身类型(offer/answer)。 + // JSON 形状与 web p2p.ts 的 SignalPayload / pion 的 signalPayload 逐字段对齐(键序无关,两侧皆 parse)。 + private func emitSignal(sdp: RTCSessionDescription?, candidate: RTCIceCandidate?) + { + var payload: [String: Any] = [:] + if let candidate + { + payload["type"] = "ice" + // RTCIceCandidate(ObjC)不暴露 usernameFragment,故 candidate JSON 仅含 candidate/sdpMid/ + // sdpMLineIndex。ufrag 由 m-line 的 ice-ufrag 决定(offer/answer 已带),pion 与浏览器的 + // addIceCandidate 对缺省 usernameFragment 皆容忍,互通不受影响。 + var c: [String: Any] = [ + "candidate": candidate.sdp, + "sdpMLineIndex": Int(candidate.sdpMLineIndex), + ] + c["sdpMid"] = candidate.sdpMid ?? NSNull() + payload["candidate"] = c + } + else if let sdp + { + let t = Self.sdpTypeString(sdp.type) + payload["type"] = t + payload["sdp"] = [ "type": t, "sdp": sdp.sdp ] + } + else { return } + + guard let data = try? JSONSerialization.data(withJSONObject: payload), + let json = String(data: data, encoding: .utf8) + else { return } + engine?.events?.engineDidEmitSignal(sessionId: sessionId, toPeer: peerName, payloadJSON: json) + } + + // MARK: 发送端 + + private func startSenderStream() + { + cLock.lock() + if senderStarted { cLock.unlock(); return } + senderStarted = true + cLock.unlock() + streamQueue.async { [weak self] in self?.streamFile() } + } + + // streamFile 把文件按 64KB 分片经数据通道发出,水位回压与桌面 pion / web 引擎一致:缓冲将越 + // HIGH 前等抽干到 LOW。发完 done 帧后先抽干本地缓冲、再等接收端 ack 追平总量才宣告 completed。 + private func streamFile() + { + guard let dc else { fail("no data channel"); return } + let fh: FileHandle + do { fh = try FileHandle(forReadingFrom: URL(fileURLWithPath: filePath)) } + catch { fail("open \(filePath): \(error.localizedDescription)"); return } + defer { try? fh.close() } + + if !sendText([ "type": "meta", "name": (filePath as NSString).lastPathComponent, "size": fileSize ]) + { + fail("send meta"); return + } + + while true + { + if isDone() { return } + let chunk: Data? + do { chunk = try fh.read(upToCount: Wire.chunkSize) } + catch { fail("read \(filePath): \(error.localizedDescription)"); return } + guard let chunk, !chunk.isEmpty else { break } // EOF + + if dc.bufferedAmount + UInt64(chunk.count) > Wire.highWatermark + { + waitBufferLow(Wire.lowWatermark) + } + if isDone() { return } + if !dc.sendData(RTCDataBuffer(data: chunk, isBinary: true)) + { + fail("send chunk failed (channel closed?)"); return + } + addSent(Int64(chunk.count)) + emitProgress() + } + + _ = sendText([ "type": "done" ]) + waitDrained() + waitAck(fileSize) + emitProgressNow() + setState("completed") + } + + @discardableResult + private func sendText(_ dict: [String: Any]) -> Bool + { + guard let dc, let data = try? JSONSerialization.data(withJSONObject: dict) else { return false } + return dc.sendData(RTCDataBuffer(data: data, isBinary: false)) + } + + // 等本地缓冲落到 threshold 以下:靠 didChangeBufferedAmount 唤醒 bufSem,兼 50ms 轮询兜底。 + private func waitBufferLow(_ threshold: UInt64) + { + let start = Date() + var warned = false + while true + { + guard let dc else { return } + if dc.bufferedAmount <= threshold { return } + if isDone() { return } + _ = bufSem.wait(timeout: .now() + 0.05) + if !warned, Date().timeIntervalSince(start) > 5 + { + warned = true + log("send stalled >5s: buffered=\(dc.bufferedAmount) sent=\(getSent()) acked=\(getAcked())") + } + } + } + + // 等本地缓冲彻底抽干(done 帧也已离开本端 SCTP)。 + private func waitDrained() + { + while true + { + guard let dc else { return } + if dc.bufferedAmount == 0 { return } + if isDone() { return } + _ = bufSem.wait(timeout: .now() + 0.02) + } + } + + // 等接收端 ack 追平 total(确认已全收)。超时兜底;无任何 ack 则不空等(对齐 session.go waitAck)。 + private func waitAck(_ total: Int64) + { + if getAcked() == 0 { return } + let deadline = Date().addingTimeInterval(Wire.ackCompleteTimeout) + while getAcked() < total + { + if Date() > deadline { return } + if isDone() { return } + _ = bufSem.wait(timeout: .now() + 0.05) + } + } + + // MARK: 接收端 + + private func bindReceiverDataChannel(_ channel: RTCDataChannel) + { + dc = channel + channel.delegate = self + startAckLoop() + } + + // 每 200ms 把最新已收字节回传发送端(节流,避免每片都回与下行争用通道)。 + private func startAckLoop() + { + let timer = DispatchSource.makeTimerSource(queue: streamQueue) + timer.schedule(deadline: .now() + .milliseconds(Wire.ackIntervalMs), + repeating: .milliseconds(Wire.ackIntervalMs)) + var lastSent: Int64 = -1 + timer.setEventHandler + { [weak self] in + guard let self else { return } + let r = self.getReceived() + if r != lastSent { self.sendAck(r); lastSent = r } + } + // 若 cleanup 已先发生(极少见:didOpen 与 cancel 同刻),不再挂表,避免泄漏一个活计时器。 + stateLock.lock() + if cleanedUp { stateLock.unlock(); timer.cancel(); return } + ackTimer = timer + stateLock.unlock() + timer.resume() + } + + private func sendAck(_ bytes: Int64) + { + guard let dc, dc.readyState == .open, + let data = try? JSONSerialization.data(withJSONObject: [ "type": "ack", "bytes": bytes ]) + else { return } + _ = dc.sendData(RTCDataBuffer(data: data, isBinary: false)) + } + + // 文本控制帧:发送端处理 ack,接收端处理 meta / done。角色守护避免误处理对端不该发的帧。 + private func handleControl(_ data: Data) + { + guard let obj = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any], + let type = obj["type"] as? String + else { return } + switch type + { + case "meta": + if role == .receiver { openOutput(name: obj["name"] as? String ?? "download") } + case "done": + if role == .receiver { finalizeReceive() } + case "ack": + if role == .sender, let b = (obj["bytes"] as? NSNumber)?.int64Value + { + setAcked(b) + bufSem.signal() // 唤醒 waitAck / waitDrained + emitProgress() + } + default: + break + } + } + + private func openOutput(name: String) + { + outName = name + let dir = engine?.downloadDir() ?? NSTemporaryDirectory() + let tmp = (dir as NSString).appendingPathComponent(".\(sessionId).part") + tmpPath = tmp + FileManager.default.createFile(atPath: tmp, contents: nil) + let handle = FileHandle(forWritingAtPath: tmp) + outLock.lock(); outHandle = handle; outLock.unlock() + if handle == nil { fail("create \(tmp)") } + } + + // 直接写盘——无 base64 桥、无 OPFS、无 WebView 内存。dc 委托串行投递,写入天然有序。 + private func writeChunk(_ data: Data) + { + // 持 outLock 整段写:与 cleanup 的关句柄互斥,杜绝写到已关闭 / 已释放的句柄(见 outHandle 注释)。 + outLock.lock() + if let h = outHandle + { + do { try h.write(contentsOf: data) } + catch { outLock.unlock(); fail("write chunk: \(error.localizedDescription)"); return } + } + outLock.unlock() + _ = addReceived(Int64(data.count)) + emitProgress() + } + + // 收到 done=所有分片已到(ordered),receivedBytes 已等于总量。回最终 ack、关文件、改名落到 + // 不冲突路径,报告路径与 completed。命名避开 NSObject 已弃用的 finalize()(否则被判为 override)。 + private func finalizeReceive() + { + sendAck(getReceived()) + outLock.lock(); let h = outHandle; outHandle = nil; outLock.unlock() + if let h + { + try? h.close() + let dir = engine?.downloadDir() ?? NSTemporaryDirectory() + let final = Self.nonCollidingPath(dir: dir, name: outName) + do { try FileManager.default.moveItem(atPath: tmpPath, toPath: final) } + catch { fail("rename to \(final): \(error.localizedDescription)"); return } + engine?.events?.engineDidSave(sessionId: sessionId, path: final) + } + emitProgressNow() + setState("completed") + } + + // MARK: 进度 / 状态 / 收尾 + + // 当前应上报的已传字节:发送端优先用接收端 ack 追平值(无 ack 时回退本地已交付估计), + // 接收端用已写盘字节(对齐 session.go progressBytes)。 + private func progressBytes() -> Int64 + { + if role == .sender + { + let a = getAcked() + if a > 0 { return min(fileSize, a) } + let buffered = Int64(dc?.bufferedAmount ?? 0) + let b = getSent() - buffered + return b < 0 ? 0 : b + } + return getReceived() + } + + // 节流上报(~10Hz):高吞吐下每片一回调把宿主主线程打满(每片过桥 evaluateJavaScript)。 + private func emitProgress() + { + let now = Int64(DispatchTime.now().uptimeNanoseconds) + cLock.lock() + if now - lastEmitNs < Wire.progressThrottleNs { cLock.unlock(); return } + lastEmitNs = now + cLock.unlock() + engine?.events?.engineDidProgress(sessionId: sessionId, bytes: progressBytes()) + } + + // 无视节流强发一次:收尾确保最终字节数到达宿主(否则末次进度被节流吞掉、文案停在 99%)。 + private func emitProgressNow() + { + cLock.lock(); lastEmitNs = Int64(DispatchTime.now().uptimeNanoseconds); cLock.unlock() + engine?.events?.engineDidProgress(sessionId: sessionId, bytes: progressBytes()) + } + + private func setState(_ st: String) + { + stateLock.lock() + // 终态不可被覆盖:completed/failed 之后即便连接随后 closed/failed 也不回退。 + if state == st || state == "completed" || state == "failed" + { + stateLock.unlock(); return + } + state = st + stateLock.unlock() + + engine?.events?.engineDidChangeState(sessionId: sessionId, state: st) + switch st + { + case "failed", "closed": + cleanup() + case "completed": + // 不立即拆连接:接收端刚发最终 ack;高吞吐下反向 ack 可能未达发送端,此刻关通道会让发送端 + // 误判失败。留连接,ackLoop 续发;60s 兜底防泄漏。真正拆除交宿主 SSE 终态驱动的 cancel。 + streamQueue.asyncAfter(deadline: .now() + 60) { [weak self] in self?.cleanup() } + default: + break + } + } + + func fail(_ msg: String) + { + log("fail: \(msg)") + setState("failed") + } + + func cleanup() + { + stateLock.lock() + if cleanedUp { stateLock.unlock(); return } + cleanedUp = true + doneFlag = true + let timer = ackTimer // 与 startAckLoop 的赋值经 stateLock 互斥,避免 ackTimer ARC 竞争 + ackTimer = nil + stateLock.unlock() + + bufSem.signal() // 唤醒任何在等缓冲 / ack 的发送循环 + timer?.cancel() + // outHandle 经 outLock 取出再关:与 writeChunk 互斥(writeChunk 持锁期间 cleanup 在此阻塞, + // 待其写完再关),杜绝并发 close/write 与 ARC 竞争。 + outLock.lock(); let h = outHandle; outHandle = nil; outLock.unlock() + if let h { try? h.close() } + dc?.close() + pc?.close() + engine?.remove(peerName, self) + } + + // MARK: 计数器(cLock 守护) + + private func addSent(_ n: Int64) { cLock.lock(); bytesSent += n; cLock.unlock() } + private func getSent() -> Int64 { cLock.lock(); defer { cLock.unlock() }; return bytesSent } + private func addReceived(_ n: Int64) -> Int64 + { + cLock.lock(); receivedBytes += n; let r = receivedBytes; cLock.unlock(); return r + } + private func getReceived() -> Int64 { cLock.lock(); defer { cLock.unlock() }; return receivedBytes } + private func setAcked(_ v: Int64) { cLock.lock(); if v > ackedBytes { ackedBytes = v }; cLock.unlock() } + private func getAcked() -> Int64 { cLock.lock(); defer { cLock.unlock() }; return ackedBytes } + private func isDone() -> Bool { stateLock.lock(); defer { stateLock.unlock() }; return doneFlag } + + // MARK: 选中候选对(连接建立后异步取,best-effort,仅供调试面板) + + private func logSelectedPair() + { + // 不用 streamQueue:发送端的 streamFile 会长期占用它(背压等待中),用全局队列才能在传输期间 + // 实时取到选中候选对(调试面板用),而非等传输结束。 + DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 0.3) + { [weak self] in self?.querySelectedPair(attempt: 0) } + } + + private func querySelectedPair(attempt: Int) + { + guard attempt < 12, !isDone(), let pc else { return } + pc.statistics + { [weak self] report in + guard let self else { return } + if let pair = Self.extractSelectedPair(report) + { + self.log("ice pair: local=\(pair.0) remote=\(pair.1)") + self.engine?.events?.engineDidSelectIcePair(sessionId: self.sessionId, + local: pair.0, remote: pair.1) + } + else + { + DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 0.3) + { [weak self] in self?.querySelectedPair(attempt: attempt + 1) } + } + } + } + + // MARK: 静态助手 + + private static func parseICEServers(_ raw: String) -> [RTCIceServer] + { + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty, let data = trimmed.data(using: .utf8), + let arr = (try? JSONSerialization.jsonObject(with: data)) as? [[String: Any]] + else { return [] } + var out: [RTCIceServer] = [] + for s in arr + { + var urls: [String] = [] + if let one = s["urls"] as? String { urls = [one] } + else if let many = s["urls"] as? [String] { urls = many } + if urls.isEmpty { continue } + let username = s["username"] as? String + let credential = s["credential"] as? String + if let username, let credential, !username.isEmpty + { + out.append(RTCIceServer(urlStrings: urls, username: username, credential: credential)) + } + else + { + out.append(RTCIceServer(urlStrings: urls)) + } + } + return out + } + + // 返回 dir 下不与现有文件冲突的目标路径:name、name (1)、name (2)……(对齐 wire.go nonCollidingPath)。 + private static func nonCollidingPath(dir: String, name: String) -> String + { + let fm = FileManager.default + let base = (dir as NSString).appendingPathComponent(name) + if !fm.fileExists(atPath: base) { return base } + let ext = (name as NSString).pathExtension + let stem = (name as NSString).deletingPathExtension + var i = 1 + while true + { + let candidate = ext.isEmpty ? "\(stem) (\(i))" : "\(stem) (\(i)).\(ext)" + let p = (dir as NSString).appendingPathComponent(candidate) + if !fm.fileExists(atPath: p) { return p } + i += 1 + } + } + + private static func extractSelectedPair(_ report: RTCStatisticsReport) -> (String, String)? + { + let stats = report.statistics + var localId: String? + var remoteId: String? + for (_, s) in stats where s.type == "candidate-pair" + { + let v = s.values + let succeeded = (v["state"] as? String) == "succeeded" + let nominated = (v["nominated"] as? NSNumber)?.boolValue ?? false + if succeeded, nominated + { + localId = v["localCandidateId"] as? String + remoteId = v["remoteCandidateId"] as? String + break + } + } + guard let localId, let remoteId else { return nil } + func label(_ id: String) -> String + { + guard let c = stats[id] else { return "?" } + let t = (c.values["candidateType"] as? String) ?? "?" + let proto = (c.values["protocol"] as? String) ?? "?" + return "\(t)/\(proto)" + } + return (label(localId), label(remoteId)) + } + + private static func sdpTypeString(_ t: RTCSdpType) -> String + { + switch t + { + case .offer: return "offer" + case .answer: return "answer" + case .prAnswer: return "pranswer" + case .rollback: return "rollback" + @unknown default: return "offer" + } + } + + private static func iceConnString(_ s: RTCIceConnectionState) -> String + { + switch s + { + case .new: return "new" + case .checking: return "checking" + case .connected: return "connected" + case .completed: return "completed" + case .failed: return "failed" + case .disconnected: return "disconnected" + case .closed: return "closed" + case .count: return "count" + @unknown default: return "?" + } + } + + // 从候选 SDP 串提取 "type/proto"(仅日志,best-effort): + // candidate: typ ... + private static func candTypeString(_ sdp: String) -> String + { + let parts = sdp.split(separator: " ").map(String.init) + var typ = "?" + var proto = "?" + if parts.count > 2 { proto = parts[2].lowercased() } + if let i = parts.firstIndex(of: "typ"), i + 1 < parts.count { typ = parts[i + 1] } + return "\(typ)/\(proto)" + } + + private func log(_ msg: String) + { + engine?.events?.engineDidLog("session \(sessionId) \(msg)") + } +} + +// MARK: - RTCPeerConnectionDelegate(回调在 libwebrtc 信令线程,非主线程) + +extension P2PTransferSession: RTCPeerConnectionDelegate +{ + func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {} + func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {} + func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {} + func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {} + + func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) + { + log("ice: \(Self.iceConnString(newState))") + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) + { + if newState == .complete { log("local gathering done") } + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) + { + log("cand local: \(Self.candTypeString(candidate.sdp))") + emitSignal(sdp: nil, candidate: candidate) + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {} + + func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) + { + if role == .receiver { bindReceiverDataChannel(dataChannel) } + } + + // 组合 ice+dtls 连接态(等价 session.go OnConnectionStateChange):驱动 connected/failed/closed。 + func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCPeerConnectionState) + { + switch newState + { + case .connected: + setState("connected") + logSelectedPair() + case .failed: + fail("peerconnection failed") + case .closed: + setState("closed") + default: + break + } + } +} + +// MARK: - RTCDataChannelDelegate(回调在 libwebrtc 信令线程) + +extension P2PTransferSession: RTCDataChannelDelegate +{ + func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) + { + // 发送端:通道就绪即开始 streamFile(等价 session.go dc.OnOpen → go streamFile)。 + if role == .sender, dataChannel.readyState == .open { startSenderStream() } + } + + func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) + { + if buffer.isBinary { writeChunk(buffer.data) } + else { handleControl(buffer.data) } + } + + func dataChannel(_ dataChannel: RTCDataChannel, didChangeBufferedAmount amount: UInt64) + { + if amount <= Wire.lowWatermark { bufSem.signal() } + } +} diff --git a/ios/CDrop/Sources/Engine/NativeTransfer.swift b/ios/CDrop/Sources/Engine/NativeTransfer.swift new file mode 100644 index 0000000..fdbf69e --- /dev/null +++ b/ios/CDrop/Sources/Engine/NativeTransfer.swift @@ -0,0 +1,74 @@ +import Foundation + +// P2PEventsBridge:libwebrtc 原生数据面引擎(LibWebRtcEngine)的事件宿主。引擎在 libwebrtc 信令 +// 线程(非主线程)触发这些回调,每条都用 DispatchQueue.main.async 跳回主线程再交 EngineController +// 过桥给无头 JS 引擎(evaluateJavaScript 仅限主线程)。 +// +// 用 DispatchQueue.main.async 而非 Task { @MainActor }:前者对同源提交保持 FIFO,使 +// connected → completed 等有序状态不被乱序处理(Task 无顺序保证,completed 抢在 connected 前 +// 处理会把终态回退)。assumeIsolated 把主队列闭包桥进 EngineController 的 @MainActor 隔离域。 +// +// 与桌面 desktop/app.go 的 transferEvents 等价——桌面经 Wails EventsEmit,iOS 经 cdropEngine 桥。 +// 事件契约见 LibWebRtcEngine.swift 的 P2PEngineDelegate;JS 侧消费见 +// web/src/features/transfer/p2pIos.ts(事件名 / payload 形状与桌面 NativeP2PHandlers 同形)。 +final class P2PEventsBridge: P2PEngineDelegate +{ + weak var controller: EngineController? + + // 已传字节(发送端=接收端 ack 追平、接收端=已写盘)。 + func engineDidProgress(sessionId: String, bytes: Int64) + { + emit("p2pProgress", [ "sessionId": sessionId, "bytes": bytes ]) + } + + // 会话状态:connected / completed / failed / closed。 + func engineDidChangeState(sessionId: String, state: String) + { + emit("p2pState", [ "sessionId": sessionId, "state": state ]) + } + + // 出站信令(offer/answer/ice):payloadJSON 为信令 JSON 串,JS 侧 POST /api/hub/signal 前 + // 解析(payload 透传字符串再 JSON.parse,与桌面 p2pNative onSignal 同形)。 + func engineDidEmitSignal(sessionId: String, toPeer: String, payloadJSON: String) + { + emit("p2pSignal", [ + "sessionId": sessionId, + "to": toPeer, + "payload": payloadJSON, + ]) + } + + // 接收端落盘完成的绝对路径(沙盒 Documents)。落盘提示由原生 transferDone 链路负责,JS 侧 + // p2pIos onSaved 当前为 no-op;事件仍过桥以备用。 + func engineDidSave(sessionId: String, path: String) + { + emit("p2pSaved", [ "sessionId": sessionId, "path": path ]) + } + + // 选中候选对(形如 "host/udp"):填进传输记录的 iceStats 供调试面板展示实际连通路径。 + func engineDidSelectIcePair(sessionId: String, local: String, remote: String) + { + emit("p2pIcePair", [ + "sessionId": sessionId, + "local": local, + "remote": remote, + ]) + } + + // 引擎诊断日志(候选 / ICE 态 / 选中对 / 发送停滞):直接进原生日志环形缓冲(#3 日志界面), + // 不过 JS——诊断由原生侧呈现,便于真机现场排查同内网 mDNS/ICE 时间线。 + func engineDidLog(_ line: String) + { + let msg = line + DispatchQueue.main.async { [weak controller] in + MainActor.assumeIsolated { controller?.appendNativeLog(msg) } + } + } + + private func emit(_ name: String, _ payload: [String: Any]) + { + DispatchQueue.main.async { [weak controller] in + MainActor.assumeIsolated { controller?.sendCommand(name, payload: payload) } + } + } +} diff --git a/ios/CDrop/Sources/Fonts/README.md b/ios/CDrop/Sources/Fonts/README.md new file mode 100644 index 0000000..97bbe8d --- /dev/null +++ b/ios/CDrop/Sources/Fonts/README.md @@ -0,0 +1,40 @@ +# 品牌字体(不入库,构建前拉取) + +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 97c4327..887a9f3 100644 --- a/ios/CDrop/Sources/Info.plist +++ b/ios/CDrop/Sources/Info.plist @@ -58,6 +58,10 @@ Commilitia Drop 需要访问本地网络以发现同内网设备并建立直连传输。 NSPhotoLibraryUsageDescription Commilitia Drop 需要访问照片库,以便从相册选取图片或视频发送给其他设备。 + UIAppFonts + + Fredoka-SemiBold.ttf + UIBackgroundModes remote-notification diff --git a/ios/CDrop/Sources/LogView.swift b/ios/CDrop/Sources/LogView.swift index 5e7ff23..92c852f 100644 --- a/ios/CDrop/Sources/LogView.swift +++ b/ios/CDrop/Sources/LogView.swift @@ -1,12 +1,37 @@ import SwiftUI +import UIKit -// 引擎日志独立界面(#3):展示 EngineController.logs 环形缓冲里最近 N 条 console.warn/error,分级配色、 -// 可选中复制、可一键清空。从设置页「查看日志」进入。便于在真机上(无 Web Inspector)定位 SSE / 鉴权 / -// 传输失败原因——此前只能在设置页看到孤零零一行 lastEngineLog。 +// 引擎日志独立界面(#3):展示 EngineController.logs 环形缓冲里最近 N 条诊断(含 pion 内部 ICE/mDNS +// 日志)。按传输会话(session id)聚合分组,每组可一键整体复制——便于真机调试时把某次传输的完整 +// 日志一并贴出(无 Web Inspector)。另有「复制全部」「清空」。 struct LogView: View { @Environment(EngineController.self) private var engine + private struct LogGroup: Identifiable + { + let id: String + let session: String? + let entries: [LogEntry] + } + + // 按 sessionId 聚合:保持组内最新在前;组按首次出现(即最新条目)顺序。非会话日志归入「其他」组。 + private var groups: [LogGroup] + { + var order: [String] = [] + var map: [String: [LogEntry]] = [:] + for e in engine.logs + { + let key = e.sessionId ?? "" + if map[key] == nil { order.append(key) } + map[key, default: []].append(e) + } + return order.map + { + LogGroup(id: $0.isEmpty ? "—" : $0, session: $0.isEmpty ? nil : $0, entries: map[$0] ?? []) + } + } + var body: some View { Group @@ -19,9 +44,23 @@ struct LogView: View { List { - ForEach(engine.logs) - { entry in - logRow(entry) + ForEach(groups) + { group in + Section + { + ForEach(group.entries) { entry in logRow(entry) } + } + header: + { + HStack + { + Text(sessionLabel(group)) + Spacer() + Button { copy(group.entries) } + label: { Image(systemName: "doc.on.doc") } + .buttonStyle(.borderless) + } + } } } .listStyle(.plain) @@ -33,15 +72,36 @@ struct LogView: View { if !engine.logs.isEmpty { + ToolbarItem(placement: .topBarTrailing) + { + Button { copy(engine.logs) } + label: { Label(t("ios.logs.copy"), systemImage: "doc.on.doc") } + } ToolbarItem(placement: .topBarTrailing) { Button(role: .destructive) { engine.clearLogs() } - label: { Label(t("ios.logs.clear"), systemImage: "trash") } + label: { Label(t("ios.logs.clear"), systemImage: "trash") } } } } } + // 复制为纯文本进剪贴板:存储为最新在前,复制时反转成时间正序便于阅读。 + private func copy(_ entries: [LogEntry]) + { + let text = entries.reversed() + .map { "\(timeString($0.at)) \($0.message)" } + .joined(separator: "\n") + UIPasteboard.general.string = text + } + + private func sessionLabel(_ group: LogGroup) -> String + { + guard let s = group.session + else { return "\(t("ios.logs.ungrouped")) · \(group.entries.count)" } + return "\(t("ios.logs.session")) \(String(s.prefix(8))) · \(group.entries.count)" + } + private func logRow(_ entry: LogEntry) -> some View { VStack(alignment: .leading, spacing: 4) @@ -49,10 +109,10 @@ struct LogView: View HStack(spacing: 6) { Image(systemName: levelSymbol(entry.level)) - .font(.caption) + .font(.oCaption) .foregroundStyle(levelTint(entry.level)) Text(timeString(entry.at)) - .font(.caption2) + .font(.oCaption2) .foregroundStyle(.secondary) .monospacedDigit() } diff --git a/ios/CDrop/Sources/MessagesView.swift b/ios/CDrop/Sources/MessagesView.swift index 6db42e6..b7e022c 100644 --- a/ios/CDrop/Sources/MessagesView.swift +++ b/ios/CDrop/Sources/MessagesView.swift @@ -1,66 +1,51 @@ import SwiftUI -// 设备间文本消息(#13):聊天式列表 + 底部撰写栏。消息由引擎经桥推来(in-memory,关 app 即清, -// 对齐 web 语义),收发都经 POST /api/message(hub SendTo 实时转发;对端离线则后端发推送)。 -// 可发给任意非本机设备(在线即时达、离线走推送),故目标取全部非本机设备而非仅在线。 +// 消息(按设备聚合,社交软件式):会话列表(每个对端设备一行)→ 点进 ConversationView 聊天线程。 +// 会话由引擎 conversations() 合成——有消息历史的设备 ∪ 当前可达设备(在线 / 可唤醒,供发起新会话)。 +// 可达设备可发(在线即时达、离线 iOS 走 APNs 唤醒);不可达的离线设备(离线桌面 / 浏览器)仅可查看 +// 历史、撰写栏禁用。删除:单条消息(线程内长按)+ 整段会话(列表滑动 / 长按)——避免废会话堆积。 struct MessagesView: View { @Environment(EngineController.self) private var engine - @State private var draft = "" - @State private var target = "" @State private var showClearConfirm = false - // 可发消息的目标:全部非本机设备(按 device_id 判本机,与设备名解耦)。 - private var targets: [DeviceItem] - { - let selfID = engine.selfDeviceID - return engine.devices.filter - { d in - if !selfID.isEmpty, !d.deviceID.isEmpty { return d.deviceID != selfID } - return d.name != engine.deviceName - } - } - - // store 是最新在前;聊天式从上到下需旧→新。 - private var ordered: [MessageItem] { engine.messages.reversed() } - var body: some View { - VStack(spacing: 0) + Group { - if engine.messages.isEmpty + let convos = engine.conversations() + if convos.isEmpty { ContentUnavailableView(t("ios.messages.empty"), systemImage: "message") - .frame(maxWidth: .infinity, maxHeight: .infinity) } else { - ScrollViewReader - { proxy in - ScrollView - { - LazyVStack(alignment: .leading, spacing: 10) + List + { + ForEach(convos) + { convo in + NavigationLink(value: convo.peerName) { - ForEach(ordered) - { msg in - messageRow(msg) - .id(msg.id) - .contextMenu - { - Button(role: .destructive) { engine.deleteMessage(msg.id) } - label: { Label(t("transfer.action.delete"), systemImage: "trash") } - } - } + conversationRow(convo) + } + // 长条行统一:滑动 + 长按都能删整段会话(清掉该设备全部消息记录)。 + .swipeActions(edge: .trailing) + { + Button(role: .destructive) { engine.deleteConversation(convo.peerName) } + label: { Label(t("ios.messages.deleteConversation"), systemImage: "trash") } + } + .contextMenu + { + Button(role: .destructive) { engine.deleteConversation(convo.peerName) } + label: { Label(t("ios.messages.deleteConversation"), systemImage: "trash") } } - .padding() } - .onChange(of: engine.messages.count) { scrollToLast(proxy) } - .onAppear { scrollToLast(proxy) } } + .listStyle(.plain) } - composeBar } .navigationTitle(t("ios.tab.messages")) + .navigationDestination(for: String.self) { peer in ConversationView(peerName: peer) } .toolbar { if !engine.messages.isEmpty @@ -77,12 +62,124 @@ struct MessagesView: View Button(t("ios.records.clear"), role: .destructive) { engine.clearMessages() } Button(t("common.cancel"), role: .cancel) { } } - .onAppear { if target.isEmpty { target = targets.first?.name ?? "" } } + } + + private func conversationRow(_ convo: ConversationItem) -> some View + { + HStack(spacing: 12) + { + Image(systemName: deviceSymbol(convo.deviceType)) + .font(.oTitle3) + .foregroundStyle(convo.online ? Color.green : Color.secondary) + .frame(width: 28) + VStack(alignment: .leading, spacing: 3) + { + Text(convo.peerName) + .font(.oBody) + .lineLimit(1) + Text(previewText(convo)) + .font(.oCaption) + .foregroundStyle(.secondary) + .lineLimit(1) + } + Spacer() + VStack(alignment: .trailing, spacing: 5) + { + if convo.lastAt > 0 + { + Text(relativeTime(convo.lastAt)) + .font(.oCaption2) + .foregroundStyle(.secondary) + } + // 该设备未读红点(社交软件式逐设备计数);消息 tab 徽标=各设备未读总和(engine.unreadMessages)。 + let unread = engine.unread(for: convo.peerName) + if unread > 0 + { + Text("\(unread)") + .font(.oCaption2) + .fontWeight(.bold) + .foregroundStyle(.white) + .padding(.horizontal, 7) + .padding(.vertical, 2) + .background(Color.cdropAccent, in: Capsule()) + } + } + } + .padding(.vertical, 4) + } + + // 会话副行预览:有历史显示最近一条(本端发出的加「你:」前缀);无历史(仅可达)显示发起提示。 + private func previewText(_ convo: ConversationItem) -> String + { + if convo.lastText.isEmpty + { + return convo.reachable ? t("ios.messages.startHint") : t("ios.messages.viewOnly") + } + return convo.lastOutgoing ? "\(t("ios.messages.youPrefix"))\(convo.lastText)" : convo.lastText + } +} + +// 单个设备的聊天线程:自上而下旧→新 + 底部撰写栏。可达设备(在线 / 可唤醒)才显示撰写栏; +// 不可达离线设备仅查看历史、底部显示「仅查看」提示。长按消息可删单条。 +struct ConversationView: View +{ + @Environment(EngineController.self) private var engine + let peerName: String + @State private var draft = "" + + private var thread: [MessageItem] { engine.thread(with: peerName) } + private var reachable: Bool { engine.canMessage(peerName) } + + var body: some View + { + VStack(spacing: 0) + { + if thread.isEmpty + { + ContentUnavailableView(t("ios.messages.empty"), systemImage: "message") + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + else + { + ScrollViewReader + { proxy in + ScrollView + { + LazyVStack(alignment: .leading, spacing: 10) + { + ForEach(thread) + { msg in + messageRow(msg) + .id(msg.id) + .contextMenu + { + Button(role: .destructive) { engine.deleteMessage(msg.id) } + label: { Label(t("transfer.action.delete"), systemImage: "trash") } + } + } + } + .padding() + } + .onChange(of: thread.count) { scrollToLast(proxy) } + .onAppear { scrollToLast(proxy) } + } + } + composeBar + } + .navigationTitle(peerName) + .navigationBarTitleDisplayMode(.inline) + // 进入会话:记为当前会话(其后到达的消息不计未读)+ 清该设备已积累未读;离开置空。 + .onAppear + { + engine.setActiveConversation(peerName) + engine.markConversationRead(peerName) + } + .onDisappear { engine.setActiveConversation(nil) } } private func scrollToLast(_ proxy: ScrollViewProxy) { - guard let last = ordered.last else { return } + guard let last = thread.last else { return } withAnimation { proxy.scrollTo(last.id, anchor: .bottom) } } @@ -92,18 +189,12 @@ struct MessagesView: View return HStack { if outgoing { Spacer(minLength: 48) } - VStack(alignment: outgoing ? .trailing : .leading, spacing: 2) - { - Text(msg.peerName) - .font(.caption2) - .foregroundStyle(.secondary) - Text(msg.text) - .padding(.horizontal, 12) - .padding(.vertical, 8) - .background(outgoing ? Color.cdropAccent.opacity(0.18) : Color(.secondarySystemBackground), - in: RoundedRectangle(cornerRadius: 14)) - .textSelection(.enabled) - } + Text(msg.text) + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(outgoing ? Color.cdropAccent.opacity(0.18) : Color(.secondarySystemBackground), + in: RoundedRectangle(cornerRadius: 14)) + .textSelection(.enabled) if !outgoing { Spacer(minLength: 48) } } .frame(maxWidth: .infinity, alignment: outgoing ? .trailing : .leading) @@ -112,53 +203,38 @@ struct MessagesView: View @ViewBuilder private var composeBar: some View { - VStack(spacing: 6) + if reachable { - if targets.isEmpty + HStack(spacing: 10) { - Text(t("ios.messages.noPeers")) - .font(.caption) - .foregroundStyle(.secondary) - .frame(maxWidth: .infinity, alignment: .leading) - } - else - { - HStack(spacing: 10) - { - Menu - { - ForEach(targets) - { dev in - Button(dev.name) { target = dev.name } - } - } - label: - { - Label(target.isEmpty ? t("ios.send.pickDevice") : target, - systemImage: "chevron.up.chevron.down") - .font(.caption) - .lineLimit(1) - } - TextField(t("ios.messages.placeholder"), text: $draft, axis: .vertical) - .textFieldStyle(.roundedBorder) - .lineLimit(1...4) - .submitLabel(.send) - .onSubmit { send() } - Button { send() } - label: { Image(systemName: "paperplane.fill").font(.title3) } - .disabled(draft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || target.isEmpty) - } + TextField(t("ios.messages.placeholder"), text: $draft, axis: .vertical) + .textFieldStyle(.roundedBorder) + .lineLimit(1 ... 4) + .submitLabel(.send) + .onSubmit { send() } + Button { send() } + label: { Image(systemName: "paperplane.fill").font(.oTitle3) } + .disabled(draft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) } + .padding() + .background(.bar) + } + else + { + Text(t("ios.messages.viewOnly")) + .font(.oCaption) + .foregroundStyle(.secondary) + .frame(maxWidth: .infinity) + .padding() + .background(.bar) } - .padding() - .background(.bar) } private func send() { let text = draft.trimmingCharacters(in: .whitespacesAndNewlines) - guard !text.isEmpty, !target.isEmpty else { return } - engine.sendMessage(to: target, text: text) + guard !text.isEmpty else { return } + engine.sendMessage(to: peerName, text: text) draft = "" } } diff --git a/ios/CDrop/Sources/NotificationBanner.swift b/ios/CDrop/Sources/NotificationBanner.swift index ddd975b..5bc775c 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(.headline) + .font(.oHeadline) .foregroundStyle(tint) Text(notice.text) - .font(.subheadline) + .font(.oSubheadline) .foregroundStyle(.primary) .lineLimit(2) Spacer(minLength: 8) diff --git a/ios/CDrop/Sources/Resources/i18n/en-US.json b/ios/CDrop/Sources/Resources/i18n/en-US.json index dc08aac..4e09af4 100644 --- a/ios/CDrop/Sources/Resources/i18n/en-US.json +++ b/ios/CDrop/Sources/Resources/i18n/en-US.json @@ -271,6 +271,7 @@ "transfer.stalled": "Looks stalled ({{seconds}}s without progress)", "transfer.bytesProgress": "{{sent}} / {{total}}", "transfer.bytesRate": "{{rate}}/s", + "transfer.avgRate": "Avg {{rate}}/s", "transfer.debug.toggle": "Debug", "transfer.debug.iceGathering": "Gathering", "transfer.debug.iceConnection": "Connection", @@ -354,13 +355,20 @@ "ios.tab.messages": "Messages", "ios.send.fromFiles": "From Files", "ios.send.fromPhotos": "From Photos", + "ios.send.previewTitle": "Send Files", + "ios.send.files": "Files", + "ios.send.send": "Send", "ios.messages.empty": "No messages yet", "ios.messages.noPeers": "No device to message", "ios.messages.placeholder": "Type a message…", + "ios.messages.viewOnly": "Offline — viewing only", + "ios.messages.deleteConversation": "Delete Conversation", + "ios.messages.startHint": "Tap to start a conversation", + "ios.messages.youPrefix": "You: ", "ios.records.clear": "Clear", "ios.records.clearConfirm": "Clear all records? This cannot be undone.", "ios.transfer.cancel": "Cancel transfer", - "ios.transfer.forceRelay": "Relay now", + "ios.transfer.forceRelay": "Relay", "ios.settings.deviceCount": "Known Devices", "ios.settings.signaling": "Signaling", "ios.settings.presenceEvents": "Presence Events", @@ -375,6 +383,11 @@ "ios.bg.title": "Transferring files", "ios.bg.subtitle": "Continues in the background", "ios.share.title": "Send {{n}} file(s) to a device", + "ios.share.guide.preparing": "Preparing…", + "ios.share.guide.ready": "{{n}} file(s) ready", + "ios.share.guide.empty": "Couldn't add files", + "ios.share.guide.hint": "Open Commilitia Drop to pick a device", + "ios.share.guide.open": "Open Commilitia Drop", "ios.control.upload": "Upload Clipboard", "ios.control.pull": "Pull Clipboard", "ios.control.sessionLabel": "{{name}} · Control", @@ -383,6 +396,8 @@ "ios.logs.empty": "No logs yet", "ios.logs.clear": "Clear Logs", "ios.logs.copy": "Copy All", + "ios.logs.session": "Session", + "ios.logs.ungrouped": "Other", "ios.scan.entry": "Scan to Sign In", "ios.scan.title": "Scan QR Code", "ios.scan.hint": "Point at the sign-in QR code on another device", diff --git a/ios/CDrop/Sources/Resources/i18n/zh-CN.json b/ios/CDrop/Sources/Resources/i18n/zh-CN.json index a631759..a28c048 100644 --- a/ios/CDrop/Sources/Resources/i18n/zh-CN.json +++ b/ios/CDrop/Sources/Resources/i18n/zh-CN.json @@ -271,6 +271,7 @@ "transfer.stalled": "似乎卡住了({{seconds}} 秒无进度)", "transfer.bytesProgress": "{{sent}} / {{total}}", "transfer.bytesRate": "{{rate}}/秒", + "transfer.avgRate": "平均 {{rate}}/秒", "transfer.debug.toggle": "调试信息", "transfer.debug.iceGathering": "候选收集", "transfer.debug.iceConnection": "连接状态", @@ -354,13 +355,20 @@ "ios.tab.messages": "消息", "ios.send.fromFiles": "从文件", "ios.send.fromPhotos": "从图库", + "ios.send.previewTitle": "发送文件", + "ios.send.files": "文件", + "ios.send.send": "发送", "ios.messages.empty": "暂无消息", "ios.messages.noPeers": "没有可发送消息的设备", "ios.messages.placeholder": "输入消息…", + "ios.messages.viewOnly": "对方当前离线,仅可查看记录", + "ios.messages.deleteConversation": "删除会话", + "ios.messages.startHint": "点按开始会话", + "ios.messages.youPrefix": "你:", "ios.records.clear": "清空", "ios.records.clearConfirm": "确定清空全部记录?此操作不可撤销。", "ios.transfer.cancel": "取消传输", - "ios.transfer.forceRelay": "立即切到中继", + "ios.transfer.forceRelay": "中继传输", "ios.settings.deviceCount": "已知设备", "ios.settings.signaling": "信令连接", "ios.settings.presenceEvents": "在线事件", @@ -375,6 +383,11 @@ "ios.bg.title": "正在传输文件", "ios.bg.subtitle": "切到后台仍继续传输", "ios.share.title": "发送 {{n}} 个文件到设备", + "ios.share.guide.preparing": "正在准备…", + "ios.share.guide.ready": "已就绪 {{n}} 个文件", + "ios.share.guide.empty": "未能添加文件", + "ios.share.guide.hint": "打开 Commilitia Drop,选择设备发送", + "ios.share.guide.open": "打开 Commilitia Drop", "ios.control.upload": "上传剪贴板", "ios.control.pull": "拉取剪贴板", "ios.control.sessionLabel": "{{name}} · 控件", @@ -383,6 +396,8 @@ "ios.logs.empty": "暂无日志", "ios.logs.clear": "清空日志", "ios.logs.copy": "复制全部", + "ios.logs.session": "会话", + "ios.logs.ungrouped": "其他", "ios.scan.entry": "扫码登录设备", "ios.scan.title": "扫码登录", "ios.scan.hint": "对准另一台设备上的登录二维码", diff --git a/ios/CDrop/Sources/Resources/i18n/zh-TW.json b/ios/CDrop/Sources/Resources/i18n/zh-TW.json index a29a323..9a82128 100644 --- a/ios/CDrop/Sources/Resources/i18n/zh-TW.json +++ b/ios/CDrop/Sources/Resources/i18n/zh-TW.json @@ -271,6 +271,7 @@ "transfer.stalled": "似乎卡住了({{seconds}} 秒無進度)", "transfer.bytesProgress": "{{sent}} / {{total}}", "transfer.bytesRate": "{{rate}}/秒", + "transfer.avgRate": "平均 {{rate}}/秒", "transfer.debug.toggle": "偵錯資訊", "transfer.debug.iceGathering": "候選收集", "transfer.debug.iceConnection": "連線狀態", @@ -354,13 +355,20 @@ "ios.tab.messages": "訊息", "ios.send.fromFiles": "從檔案", "ios.send.fromPhotos": "從相簿", + "ios.send.previewTitle": "傳送檔案", + "ios.send.files": "檔案", + "ios.send.send": "傳送", "ios.messages.empty": "尚無訊息", "ios.messages.noPeers": "沒有可傳送訊息的裝置", "ios.messages.placeholder": "輸入訊息…", + "ios.messages.viewOnly": "對方目前離線,僅可檢視記錄", + "ios.messages.deleteConversation": "刪除會話", + "ios.messages.startHint": "點按開始會話", + "ios.messages.youPrefix": "你:", "ios.records.clear": "清空", "ios.records.clearConfirm": "確定清空全部記錄?此操作無法復原。", "ios.transfer.cancel": "取消傳輸", - "ios.transfer.forceRelay": "立即切到中繼", + "ios.transfer.forceRelay": "中繼傳輸", "ios.settings.deviceCount": "已知裝置", "ios.settings.signaling": "信令連線", "ios.settings.presenceEvents": "上線事件", @@ -375,6 +383,11 @@ "ios.bg.title": "正在傳輸檔案", "ios.bg.subtitle": "切到背景仍繼續傳輸", "ios.share.title": "傳送 {{n}} 個檔案到裝置", + "ios.share.guide.preparing": "正在準備…", + "ios.share.guide.ready": "已就緒 {{n}} 個檔案", + "ios.share.guide.empty": "未能加入檔案", + "ios.share.guide.hint": "開啟 Commilitia Drop,選擇裝置傳送", + "ios.share.guide.open": "開啟 Commilitia Drop", "ios.control.upload": "上傳剪貼簿", "ios.control.pull": "拉取剪貼簿", "ios.control.sessionLabel": "{{name}} · 控制項", @@ -383,6 +396,8 @@ "ios.logs.empty": "尚無日誌", "ios.logs.clear": "清空日誌", "ios.logs.copy": "複製全部", + "ios.logs.session": "會話", + "ios.logs.ungrouped": "其他", "ios.scan.entry": "掃碼登入裝置", "ios.scan.title": "掃碼登入", "ios.scan.hint": "對準另一台裝置上的登入 QR 碼", diff --git a/ios/CDrop/Sources/RootView.swift b/ios/CDrop/Sources/RootView.swift index 086c1b2..fa82e24 100644 --- a/ios/CDrop/Sources/RootView.swift +++ b/ios/CDrop/Sources/RootView.swift @@ -76,22 +76,20 @@ struct RootView: View .opacity(0) .allowsHitTesting(false) } - // Share Extension 交接:cdrop://share 唤起后导入收件箱待发文件,弹出选设备发送。 + // Share Extension 交接:cdrop://share 唤起 / 前台扫到收件箱待发文件 → 直入发送预览(与手动 + // 选文件同一表单 SendComposeSheet),预填分享的文件,选目标设备发送。onSend 走 sendShares + // (把收件箱文件 move 到私有暂存再发),完成后 pendingShareFiles 清空 → 表单自动收起。 .onAppear { engine.loadPendingShares() } - .confirmationDialog( - t("ios.share.title", [ "n": String(engine.pendingShareFiles.count) ]), - isPresented: Binding( - get: { !engine.pendingShareFiles.isEmpty }, - set: { if !$0 { engine.dismissShares() } } - ), - titleVisibility: .visible - ) + .sheet(isPresented: Binding( + get: { !engine.pendingShareFiles.isEmpty }, + set: { if !$0 { engine.dismissShares() } })) { - ForEach(engine.sendableDevices()) - { dev in - Button(engine.sendLabel(dev)) { engine.sendShares(to: dev.name) } - } - Button(t("common.cancel"), role: .cancel) { engine.dismissShares() } + SendComposeSheet( + files: engine.pendingShareFiles.map { PreviewFile(name: $0.lastPathComponent, size: fileByteSize($0)) }, + devices: engine.sendableDevices(), + sendLabel: engine.sendLabel, + onSend: { device in engine.sendShares(to: device) }, + onCancel: { engine.dismissShares() }) } } } @@ -101,9 +99,11 @@ struct TransferListView: View @Environment(EngineController.self) private var engine @State private var showImporter = false @State private var showPhotoPicker = false - @State private var photoItem: PhotosPickerItem? - @State private var pickedURL: URL? - @State private var showDevicePicker = false + @State private var photoItems: [PhotosPickerItem] = [] + // 选中的待发文件(手动选文件 / 图库临时文件统一收口,支持多文件):非空即弹发送预览 + // (SendComposeSheet)。 + @State private var pickedURLs: [URL] = [] + @State private var showCompose = false @State private var showNoDevices = false @State private var showClearConfirm = false // 点按传输卡片进详情:不在 List 内用 NavigationLink(会出现 chevron 改变卡片观感),改经 @@ -122,7 +122,8 @@ struct TransferListView: View { if engine.transfers.isEmpty && engine.history.isEmpty { - ContentUnavailableView(t("ios.transfer.empty"), systemImage: "tray") + // 主页空态=品牌 hero(吉祥物 + 字标 + 文案):首屏即见品牌,吉祥物抱纸恰呼应「等你 drop」。 + BrandHero(tagline: t("ios.transfer.empty")) } else { @@ -161,31 +162,29 @@ struct TransferListView: View label: { Image(systemName: "paperplane.fill") - .font(.title2) + .font(.oTitle2) .padding(18) } .buttonStyle(.glassProminent) .padding(24) } - .fileImporter(isPresented: $showImporter, allowedContentTypes: [ .item ]) + // 文件 / 图库均支持一次多选;选完统一进发送预览(SendComposeSheet)选目标设备发送。 + .fileImporter(isPresented: $showImporter, allowedContentTypes: [ .item ], + allowsMultipleSelection: true) { result in - if case .success(let url) = result - { - pickedURL = url - if sendableDevices.isEmpty { showNoDevices = true } - else { showDevicePicker = true } - } + if case .success(let urls) = result, !urls.isEmpty { present(urls) } } - .photosPicker(isPresented: $showPhotoPicker, selection: $photoItem, + .photosPicker(isPresented: $showPhotoPicker, selection: $photoItems, matching: .any(of: [ .images, .videos ])) - .onChange(of: photoItem) { loadPhoto() } - .confirmationDialog(t("ios.send.pickDevice"), isPresented: $showDevicePicker, titleVisibility: .visible) + .onChange(of: photoItems) { loadPhotos() } + .sheet(isPresented: $showCompose, onDismiss: clearPicked) { - ForEach(sendableDevices) - { dev in - Button(engine.sendLabel(dev)) { startSend(to: dev.name) } - } - Button(t("common.cancel"), role: .cancel) { } + SendComposeSheet( + files: pickedURLs.map { PreviewFile(name: $0.lastPathComponent, size: fileByteSize($0)) }, + devices: sendableDevices, + sendLabel: engine.sendLabel, + onSend: { device in sendPicked(to: device) }, + onCancel: { showCompose = false }) } .alert(t("ios.send.noDevices"), isPresented: $showNoDevices) { } } @@ -221,7 +220,7 @@ struct TransferListView: View HStack { Text(title) - .font(.subheadline) + .font(.oSubheadline) .foregroundStyle(.secondary) Spacer() } @@ -276,29 +275,46 @@ struct TransferListView: View } } - private func startSend(to device: String) + // 选完文件 / 图库后统一入口:非空即弹发送预览(无可达设备则提示)。 + private func present(_ urls: [URL]) { - guard let url = pickedURL else { return } - engine.sendFile(to: device, fileURL: url) - pickedURL = nil + pickedURLs = urls + if sendableDevices.isEmpty { showNoDevices = true } + else { showCompose = true } } - // 图库选中后:异步取字节 → 落临时文件 → 走与文件相同的「选设备发送」流程(#14)。 - private func loadPhoto() + // 发送预览确认:把全部待发文件发往选中设备(多文件逐个走既有发送链路)。随后表单收起、清空选中。 + private func sendPicked(to device: String) { - guard let item = photoItem else { return } + for url in pickedURLs { engine.sendFile(to: device, fileURL: url) } + showCompose = false + } + + // 表单收起(发送 / 取消)后清空选中态,使下次选择从零开始。 + private func clearPicked() + { + pickedURLs = [] + photoItems = [] + } + + // 图库选中(支持多选):异步逐个取字节 → 落临时文件 → 走与文件相同的发送预览流程(#14)。 + private func loadPhotos() + { + let items = photoItems + guard !items.isEmpty else { return } Task { - guard let data = try? await item.loadTransferable(type: Data.self), - let url = engine.stagePhotoData(data, suggestedName: suggestedPhotoName(item)) - else { return } - await MainActor.run + var urls: [URL] = [] + for item in items { - pickedURL = url - photoItem = nil - if sendableDevices.isEmpty { showNoDevices = true } - else { showDevicePicker = true } + if let data = try? await item.loadTransferable(type: Data.self), + let url = engine.stagePhotoData(data, suggestedName: suggestedPhotoName(item)) + { + urls.append(url) + } } + guard !urls.isEmpty else { return } + await MainActor.run { present(urls) } } } @@ -326,7 +342,7 @@ struct TransferCardView: View ? "arrow.down.circle" : "arrow.up.circle") .foregroundStyle(.secondary) Text(item.fileName) - .font(.headline) + .font(.oHeadline) .lineLimit(1) Spacer() } @@ -335,7 +351,7 @@ struct TransferCardView: View ProgressView(value: progressOf(item)) } Text(detailLine) - .font(.caption) + .font(.oCaption) .foregroundStyle(.secondary) } .padding() @@ -360,6 +376,11 @@ struct TransferCardView: View { parts.append(t("transfer.bytesRate", [ "rate": formatBytes(Int(rate)) ])) } + else if item.state == "DONE", let rate = item.bytesPerSec, rate > 0 + { + // 完成后显示平均速度(总字节 / 总耗时;引擎在 store completeTransfer 已置 bytesPerSec 为均速)。 + parts.append(t("transfer.avgRate", [ "rate": formatBytes(Int(rate)) ])) + } return parts.joined(separator: " · ") } } @@ -427,8 +448,10 @@ struct TransferDetailView: View } if let rate = item.bytesPerSec, rate > 0 { + // 完成显示平均速度、进行中显示瞬时速度(值文案不同;store 已据状态置 bytesPerSec)。 LabeledContent(t("ios.detail.speed"), - value: t("transfer.bytesRate", [ "rate": formatBytes(Int(rate)) ])) + value: t(item.state == "DONE" ? "transfer.avgRate" : "transfer.bytesRate", + [ "rate": formatBytes(Int(rate)) ])) } ProgressView(value: progressOf(item)) } @@ -602,14 +625,14 @@ struct DeviceListView: View if isSelf(dev) { Text(t("ios.devices.thisDevice")) - .font(.caption2) + .font(.oCaption2) .foregroundStyle(Color.cdropAccent) } } Text(dev.online ? t("settings.online") : t("settings.lastSeen", [ "time": relativeTime(dev.lastSeen) ])) - .font(.caption) + .font(.oCaption) .foregroundStyle(.secondary) } } @@ -632,6 +655,23 @@ struct SettingsView: View { List { + // 品牌头:字标 + 版本号,置于设置页顶(「关于」自然归属处),主程序持久可见 logo。 + Section + { + VStack(spacing: 6) + { + BrandLockup(markSize: 40, wordSize: 24) + if !appVersion.isEmpty + { + Text(appVersion) + .font(.oCaption2) + .foregroundStyle(.secondary) + } + } + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + .listRowBackground(Color.clear) + } if let user = auth.session?.user { Section @@ -678,7 +718,7 @@ struct SettingsView: View if !engine.lastEngineLog.isEmpty { Text(engine.lastEngineLog) - .font(.caption2) + .font(.oCaption2) .foregroundStyle(.secondary) .lineLimit(1) } @@ -712,6 +752,15 @@ struct SettingsView: View return t("ios.engine.disconnected") } + // 应用版本(品牌头副标):vX.Y (build)。 + private var appVersion: String + { + let v = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" + guard !v.isEmpty else { return "" } + let b = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "" + return b.isEmpty ? "v\(v)" : "v\(v) (\(b))" + } + // 即时改名:提交时把草稿交引擎改名(PATCH /api/devices/{device_id})。空 / 未变则忽略。 // 成功后引擎经 renamed 事件落本地名 + Keychain 会话名 + 刷新 engine.deviceName。 private func commitRename() @@ -812,7 +861,7 @@ struct ReceivedFilesView: View Text(url.lastPathComponent) .lineLimit(1) Text(fileSizeString(url)) - .font(.caption) + .font(.oCaption) .foregroundStyle(.secondary) } } @@ -861,7 +910,7 @@ private func progressOf(_ item: TransferItem) -> Double return min(1.0, Double(sent) / Double(item.fileSize)) } -private func formatBytes(_ bytes: Int) -> String +func formatBytes(_ bytes: Int) -> String { let f = ByteCountFormatter() f.countStyle = .file @@ -876,7 +925,7 @@ private func fileSizeString(_ url: URL) -> String // lastSeen 是 epoch 秒(后端 devices.last_seen=now.Unix(),web formatRelative 也按秒); // 转相对时间填进共享 settings.lastSeen 文案的 {{time}}。 -private func relativeTime(_ epochSec: Double) -> String +func relativeTime(_ epochSec: Double) -> String { let date = Date(timeIntervalSince1970: epochSec) let f = RelativeDateTimeFormatter() @@ -885,7 +934,7 @@ private func relativeTime(_ epochSec: Double) -> String } // 设备类型 → SF Symbol(对齐 web mapKind 的语义)。 -private func deviceSymbol(_ type: String) -> String +func deviceSymbol(_ type: String) -> String { let s = type.lowercased() if s.contains("mac") || s == "darwin" { return "laptopcomputer" } diff --git a/ios/CDrop/Sources/ScannerView.swift b/ios/CDrop/Sources/ScannerView.swift index 8f16852..ccbd7a1 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(.callout) + .font(.oCallout) .padding(.vertical, 8) .padding(.horizontal, 14) .background(.red.opacity(0.85), in: Capsule()) .foregroundStyle(.white) } Text(t("ios.scan.hint")) - .font(.footnote) + .font(.oFootnote) .multilineTextAlignment(.center) .padding(.vertical, 8) .padding(.horizontal, 14) diff --git a/ios/CDrop/Sources/SendComposeSheet.swift b/ios/CDrop/Sources/SendComposeSheet.swift new file mode 100644 index 0000000..4e28eba --- /dev/null +++ b/ios/CDrop/Sources/SendComposeSheet.swift @@ -0,0 +1,108 @@ +import SwiftUI + +// 发送预览表单:列出待发文件(名 + 大小)+ 选择目标设备 + 发送。手动选文件(TransferListView)与 +// 分享交接(RootView)共用此一表单——即「选文件 → 预览 → 选设备 → 发送」。纯 UI:不感知文件来源 +// (手动安全作用域 URL / 图库临时文件 / 分享 App Group),实际发送由调用方经 onSend(设备名) 实现 +// (手动 loop engine.sendFile;分享 engine.sendShares),故同一表单复用零分叉。 +struct PreviewFile: Identifiable +{ + let id = UUID() + let name: String + let size: Int +} + +struct SendComposeSheet: View +{ + let files: [PreviewFile] + let devices: [DeviceItem] + let sendLabel: (DeviceItem) -> String + let onSend: (String) -> Void // 目标设备名 + let onCancel: () -> Void + + // 选中的目标设备名。onAppear 默认选第一个可达设备,减少一次点击。 + @State private var selected = "" + + var body: some View + { + NavigationStack + { + Form + { + Section(t("ios.send.files")) + { + ForEach(files) + { file in + HStack(spacing: 10) + { + Image(systemName: "doc") + .foregroundStyle(.secondary) + Text(file.name) + .lineLimit(1) + Spacer() + Text(formatBytes(file.size)) + .font(.oCaption) + .foregroundStyle(.secondary) + } + } + } + Section(t("ios.send.pickDevice")) + { + if devices.isEmpty + { + Text(t("ios.send.noDevices")) + .foregroundStyle(.secondary) + } + else + { + ForEach(devices) + { dev in + Button { selected = dev.name } + label: + { + HStack(spacing: 10) + { + Image(systemName: deviceSymbol(dev.type)) + .foregroundStyle(dev.online ? Color.green : Color.secondary) + Text(sendLabel(dev)) + .foregroundStyle(.primary) + Spacer() + if selected == dev.name + { + Image(systemName: "checkmark") + .foregroundStyle(Color.cdropAccent) + } + } + } + .buttonStyle(.plain) + } + } + } + } + .navigationTitle(t("ios.send.previewTitle")) + .navigationBarTitleDisplayMode(.inline) + .toolbar + { + ToolbarItem(placement: .topBarLeading) + { + Button(t("common.cancel")) { onCancel() } + } + ToolbarItem(placement: .topBarTrailing) + { + Button(t("ios.send.send")) { onSend(selected) } + .fontWeight(.semibold) + .disabled(selected.isEmpty || files.isEmpty) + } + } + .onAppear { if selected.isEmpty { selected = devices.first?.name ?? "" } } + } + } +} + +// 读文件字节大小(供发送预览展示)。安全作用域 URL(文件选择器)须临时声明访问才能读属性; +// 临时文件 / App Group 文件无须声明(startAccessing 返回 false,仍可正常读),故统一走此函数。 +func fileByteSize(_ url: URL) -> Int +{ + let access = url.startAccessingSecurityScopedResource() + defer { if access { url.stopAccessingSecurityScopedResource() } } + return (try? url.resourceValues(forKeys: [ .fileSizeKey ]))?.fileSize ?? 0 +} diff --git a/ios/CDrop/Tests/LibWebRtcEngineTests.swift b/ios/CDrop/Tests/LibWebRtcEngineTests.swift new file mode 100644 index 0000000..a9d53dc --- /dev/null +++ b/ios/CDrop/Tests/LibWebRtcEngineTests.swift @@ -0,0 +1,140 @@ +import XCTest +@testable import CDrop + +// LibWebRtcEngine 环回端到端测试:两个引擎在同进程内交叉连,跑完整线协议 +// (offer/answer/ICE + meta/分片/done + ack + 收尾),验证收到的文件字节与源逐字节相等。 +// 这是真机部署前最强的非设备验证——证实线协议端口(分片 / 背压 / ack / finalize)在 libwebrtc 上 +// 正确闭环;不覆盖真实网络 / 吞吐(须真机)。host 候选环回直连,无需 STUN/TURN / 本地网络权限。 +final class LibWebRtcEngineTests: XCTestCase +{ + // 占位 delegate:仅供 LibWebRtcEngine 构造(随后重定向到 Peer)。 + final class NullDelegate: P2PEngineDelegate + { + static let shared = NullDelegate() + func engineDidProgress(sessionId: String, bytes: Int64) {} + func engineDidChangeState(sessionId: String, state: String) {} + func engineDidEmitSignal(sessionId: String, toPeer: String, payloadJSON: String) {} + func engineDidSave(sessionId: String, path: String) {} + func engineDidSelectIcePair(sessionId: String, local: String, remote: String) {} + func engineDidLog(_ line: String) {} + } + + // 环回事件宿主:捕获状态 / 落盘,并把出站信令「串行」转发给对端引擎——每条等对端 handleSignal + // 完成再投下一条,复刻 p2pIos.ts 的信令链语义(offer/answer 必先于其后 trickle 的 ICE 候选, + // 否则 addIceCandidate 在 remote description 未设时报错)。 + final class Peer: P2PEngineDelegate + { + let name: String // 对端索引「我」用的设备名(转发 handleSignal 时填 fromPeer) + let engine: LibWebRtcEngine + weak var other: Peer? + private let signalQueue: DispatchQueue + + var onState: ((String) -> Void)? + var onSaved: ((String) -> Void)? + + init(name: String, downloadDir: String) + { + self.name = name + signalQueue = DispatchQueue(label: "test.signal.\(name)") + engine = LibWebRtcEngine(downloadDir: downloadDir, delegate: NullDelegate.shared) + engine.delegate = self // 重定向到自己(self 此刻全属性已初始化,可用) + } + + func engineDidProgress(sessionId: String, bytes: Int64) {} + + func engineDidChangeState(sessionId: String, state: String) + { + onState?(state) + } + + // 出站信令 → 对端引擎,按对端串行队列逐条投递(等对端处理完再投下一条)。 + func engineDidEmitSignal(sessionId: String, toPeer: String, payloadJSON: String) + { + guard let other else { return } + let fromPeer = name + other.signalQueue.async { [weak other] in + guard let other else { return } + let sem = DispatchSemaphore(value: 0) + other.engine.handleSignal(fromPeer: fromPeer, payloadJSON: payloadJSON) { _ in sem.signal() } + sem.wait() + } + } + + func engineDidSave(sessionId: String, path: String) + { + onSaved?(path) + } + + func engineDidSelectIcePair(sessionId: String, local: String, remote: String) {} + func engineDidLog(_ line: String) {} + } + + // 端到端:20 MB 伪随机源(跨数百个 64KB 分片 + 触发 HIGH/LOW 背压),环回传输后逐字节比对。 + func testLoopbackTransferIntegrity() throws + { + let fm = FileManager.default + let dirA = fm.temporaryDirectory.appendingPathComponent("cdropTestA-\(UUID().uuidString)") + let dirB = fm.temporaryDirectory.appendingPathComponent("cdropTestB-\(UUID().uuidString)") + try fm.createDirectory(at: dirA, withIntermediateDirectories: true) + try fm.createDirectory(at: dirB, withIntermediateDirectories: true) + defer { try? fm.removeItem(at: dirA); try? fm.removeItem(at: dirB) } + + // 确定可重现的伪随机字节(线性同余),便于失败诊断。 + let size = 20 * 1024 * 1024 + var payload = Data(count: size) + payload.withUnsafeMutableBytes + { raw in + let p = raw.bindMemory(to: UInt8.self) + var s: UInt64 = 0x9E37_79B9_7F4A_7C15 + for i in 0 ..< size + { + s = s &* 6_364_136_223_846_793_005 &+ 1_442_695_040_888_963_407 + p[i] = UInt8(truncatingIfNeeded: s >> 33) + } + } + let srcURL = dirA.appendingPathComponent("payload.bin") + try payload.write(to: srcURL) + + let senderName = "DeviceA" + let receiverName = "DeviceB" + let peerA = Peer(name: senderName, downloadDir: dirA.path) // 发送端(engineB 据 senderName 索引) + let peerB = Peer(name: receiverName, downloadDir: dirB.path) // 接收端(engineA 据 receiverName 索引) + peerA.other = peerB + peerB.other = peerA + + let senderDone = expectation(description: "sender completed") + let receiverSaved = expectation(description: "receiver saved") + var savedPath: String? + + peerA.onState = + { state in + if state == "completed" { senderDone.fulfill() } + else if state == "failed" { XCTFail("sender entered failed state") } + } + peerB.onSaved = + { path in + savedPath = path + receiverSaved.fulfill() + } + peerB.onState = + { state in + if state == "failed" { XCTFail("receiver entered failed state") } + } + + let sid = "loopback-session-1" + let ice = "[]" // host 候选环回直连,无 STUN/TURN + // 接收端先建 PeerConnection 等 offer(生产中 transfer:incoming 先于 offer 到达)。 + peerB.engine.startIncoming(sessionId: sid, peerName: senderName, iceServersJSON: ice) + { err in XCTAssertNil(err, "startIncoming error: \(String(describing: err))") } + peerA.engine.startOutgoing(sessionId: sid, peerName: receiverName, + filePath: srcURL.path, iceServersJSON: ice) + { err in XCTAssertNil(err, "startOutgoing error: \(String(describing: err))") } + + wait(for: [senderDone, receiverSaved], timeout: 120) + + let saved = try XCTUnwrap(savedPath, "receiver never reported a saved path") + let got = try Data(contentsOf: URL(fileURLWithPath: saved)) + XCTAssertEqual(got.count, size, "received size mismatch") + XCTAssertEqual(got, payload, "received bytes differ from source") + } +} diff --git a/ios/CDrop/Widgets/CloudClipboardWidget.swift b/ios/CDrop/Widgets/CloudClipboardWidget.swift index 1f5c32d..be53d92 100644 --- a/ios/CDrop/Widgets/CloudClipboardWidget.swift +++ b/ios/CDrop/Widgets/CloudClipboardWidget.swift @@ -33,34 +33,59 @@ struct ClipboardWidgetView: View var body: some View { - VStack(alignment: .leading, spacing: 10) + Group { - HStack(spacing: 6) + if family == .systemMedium { mediumLayout } else { smallLayout } + } + .containerBackground(for: .widget) { brandBackground } + } + + // 小尺寸:slim 品牌 header(吉祥物 + 名)+ 两个全宽按钮填满余下高度(最大化利用空间、大点按区)。 + private var smallLayout: some View + { + VStack(spacing: 10) + { + HStack(spacing: 8) { - Image(systemName: "doc.on.clipboard") - .foregroundStyle(.tint) + BrandMark(size: 30) Text(t("ios.widget.clipboardName")) - .font(.caption).bold() + .font(.subheadline).bold() .lineLimit(1) + Spacer(minLength: 0) } - // 小尺寸纵向堆叠两按钮;中尺寸并排。 - if family == .systemSmall + actionButton(t("ios.control.upload"), "square.and.arrow.up", UploadClipboardIntent()) + actionButton(t("ios.control.pull"), "square.and.arrow.down", PullClipboardIntent()) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(14) + } + + // 中尺寸:左侧大吉祥物作视觉锚,右侧名 + 两按钮竖排,吃满横向空间。 + private var mediumLayout: some View + { + HStack(spacing: 18) + { + BrandMark(size: 72) + VStack(alignment: .leading, spacing: 9) { + Text(t("ios.widget.clipboardName")) + .font(.headline) + .lineLimit(1) actionButton(t("ios.control.upload"), "square.and.arrow.up", UploadClipboardIntent()) actionButton(t("ios.control.pull"), "square.and.arrow.down", PullClipboardIntent()) } - else - { - HStack(spacing: 10) - { - actionButton(t("ios.control.upload"), "square.and.arrow.up", UploadClipboardIntent()) - actionButton(t("ios.control.pull"), "square.and.arrow.down", PullClipboardIntent()) - } - } + .frame(maxWidth: .infinity, alignment: .leading) } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) - .padding() - .containerBackground(.fill.tertiary, for: .widget) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(16) + } + + // 品牌底:淡品牌色斜向渐变(取代原 .fill.tertiary 灰底),与吉祥物 / 按钮同色系。 + private var brandBackground: some View + { + LinearGradient( + colors: [ Color.cdropAccent.opacity(0.18), Color.cdropAccent.opacity(0.05) ], + startPoint: .topLeading, endPoint: .bottomTrailing) } private func actionButton(_ title: String, _ symbol: String, _ intent: some AppIntent) -> some View @@ -68,11 +93,12 @@ struct ClipboardWidgetView: View Button(intent: intent) { Label(title, systemImage: symbol) - .font(.caption2) + .font(.caption).fontWeight(.semibold) .lineLimit(1) .frame(maxWidth: .infinity) + .padding(.vertical, 3) } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) .tint(.cdropAccent) } } diff --git a/ios/CDrop/project.yml b/ios/CDrop/project.yml index d9475e6..a425197 100644 --- a/ios/CDrop/project.yml +++ b/ios/CDrop/project.yml @@ -4,6 +4,14 @@ options: deploymentTarget: iOS: "26.0" createIntermediateGroups: true +# 原生数据面:iOS 走 libwebrtc(Safari 同款,与 iOS 网络栈原生集成)。预编译 xcframework 经 +# stasel/WebRTC 以 SPM 分发,仅二进制 target,pin 精确版本避免上游 M 版跳变改 API。替换早先 +# gomobile/pion 路径——pion 的 Go raw socket 不与 iOS Network framework 集成,真机确证不可靠 +# (见 desktop/NATIVE-TRANSFER.md §7 / cdrop-native-data-plane 记忆)。 +packages: + WebRTC: + url: https://github.com/stasel/WebRTC + exactVersion: 149.0.0 settings: base: SWIFT_VERSION: "5.0" @@ -26,10 +34,16 @@ targets: embed: true - target: CDropWidgets embed: true + # 原生数据面引擎(libwebrtc):SPM 预编 xcframework,Xcode 自动嵌入 + 签名。仅主 app 链接—— + # Share / 控件扩展不碰数据面,避免超扩展 120MB 内存限。见 LibWebRtcEngine.swift。 + - package: WebRTC info: path: Sources/Info.plist properties: CFBundleDisplayName: Commilitia Drop + # 品牌字标字体 Fredoka SemiBold(Shared/Fonts/,随 bundle 打包),用于 Wordmark(与 web 一致)。 + UIAppFonts: + - Fredoka-SemiBold.ttf # cdrop:// URL scheme:Share Extension 交接后经 cdrop://share 深链唤起主 app 选设备发送。 CFBundleURLTypes: - CFBundleURLName: net.commilitia.Commilitia-Drop @@ -79,10 +93,15 @@ targets: sources: - path: Share - path: Shared + # i18n JSON 随扩展 bundle 一并打包,使分享引导界面的 t() 文案随设备语言(同控件扩展)。 + - path: Sources/Resources/i18n info: path: Share/Info.plist properties: CFBundleDisplayName: Commilitia Drop + # 品牌字标字体(分享引导界面的 Wordmark 用),随扩展 bundle 打包 + 注册。 + UIAppFonts: + - Fredoka-SemiBold.ttf NSExtension: NSExtensionPointIdentifier: com.apple.share-services NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).ShareViewController @@ -117,10 +136,32 @@ targets: PRODUCT_BUNDLE_IDENTIFIER: net.commilitia.Commilitia-Drop.widgets TARGETED_DEVICE_FAMILY: "1,2" CODE_SIGN_ENTITLEMENTS: Widgets/CDropWidgets.entitlements + # 单元 / 集成测试(模拟器,无需签名):原生数据面 libwebrtc 引擎的环回端到端测试——两个 + # LibWebRtcEngine 在同进程内交叉连,跑完整线协议(offer/answer/ICE + meta/分片/done + ack + + # 收尾)验证文件字节完整性。宿主即 CDrop app(@testable import 取内部类型 + 经 app 链接 WebRTC)。 + CDropTests: + type: bundle.unit-test + platform: iOS + deploymentTarget: "26.0" + sources: + - path: Tests + dependencies: + - target: CDrop + - package: WebRTC + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: net.commilitia.Commilitia-Drop.tests + TEST_HOST: "$(BUILT_PRODUCTS_DIR)/CDrop.app/CDrop" + BUNDLE_LOADER: "$(TEST_HOST)" + GENERATE_INFOPLIST_FILE: "YES" schemes: CDrop: build: targets: CDrop: all + test: + targets: + - CDropTests + gatherCoverageData: false run: config: Debug diff --git a/ios/PARITY.md b/ios/PARITY.md index 2fccf45..13fb6a1 100644 --- a/ios/PARITY.md +++ b/ios/PARITY.md @@ -31,7 +31,8 @@ - **视觉材质**:液态玻璃 vs Theme B。 - **导航外壳**:原生 `NavigationStack` / `TabView` vs web 布局。 -- **字体 / 排版**:系统字体 + Dynamic Type vs Orbit Gothic / Noto;间距 / 圆角 / 字阶各用平台母语。 +- **字体 / 排版**(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 拉。 +- **分享引导界面**(仅 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`)。 @@ -60,3 +61,4 @@ | 2026-06-24 | engine/main.ts 加 presence 推送 + 初始快照(未提交) | RootView / EngineController / AuthManager 真数据绑定(未提交) | **A 任务**:引擎订阅 `store.devices` 推 `presence` 事件 + boot 后补发 presence/transfers 初始快照;原生 `EngineController` 解码进 `@Observable` devices/transfers/history;`RootView` 三屏全替 demo→真数据 + 新增 `TransferDetailView`(TODO #5)。i18n 加 `ios.detail.*` / `ios.settings.account|user|logout` / `ios.transfer.empty|incoming|outgoing` / `ios.send.noDevices` / `ios.devices.empty`,传输态文案复用共享 `transfer.state|phase|mode|bytesRate`。iOS `t()` 加 `{{name}}` 插值变体对齐 web。Keychain 会话持久化 + 设置页登出(闭合 TODO #4)。web tsc/build + xcodebuild iphonesimulator26.5 双过,模拟器三屏空态截图验。**已部署 prod**:`engine.html` → `engine-BB8iav7K.js`(含 presence 推送),真会话下即生效 | | 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}` 重建分发 | diff --git a/ios/PLAN.md b/ios/PLAN.md index 79d4d94..10d6267 100644 --- a/ios/PLAN.md +++ b/ios/PLAN.md @@ -53,6 +53,17 @@ - **缺失 / 拒绝时按特性二选一**:① **有兜底则降级**——本地网络→回退中继并提示“仅中继、无法同内网直连”;通知→无后台推送但前台仍可用。② **无兜底则提示“无权限”并阻塞该特性**——相机→扫码不可用,提示 + 跳系统设置;阻塞的是**该特性**、非整个 app。 - 不拿任一权限当**整个 app** 的门槛(HIG 5.1.2)。详见 §9.2 逐权限映射。 +### 决策 F(新,2026-06-28):iOS P2P 数据面下沉原生 libwebrtc(与桌面 pion 对称) + +修订决策 A 的“数据面亦跑在无头 WebView JS 里”。**P2P DataChannel 的字节收发(数据面)已从无头 WebView 的 JS 引擎移到原生 libwebrtc**(`Sources/Engine/LibWebRtcEngine.swift`,`RTCPeerConnection`),与桌面把数据面移到 Go/pion **完全对称**。 + +- **无头 WebView 此后只跑编排**:信令转发 / 会话 / presence / 消息 / 状态机 / relay(`hub.ts` + `transfer.ts`),**不再承载 P2P 数据面**。决策 A 的“原生壳 + 无头引擎”仍成立,只是引擎的职责收窄到编排层。 +- **缘由(U1→U2 翻案)**:先试 gomobile+pion(与桌面同引擎,U1),真机 POC **证伪**——Go raw BSD socket 不与 iOS Network framework 集成(连接时好时坏 + 中途断流)。按闸退到 **libwebrtc.framework**(U2,stasel/WebRTC M149 经 SPM、pin `exactVersion 149.0.0`)。完整决策依据与实测证据见 `desktop/NATIVE-TRANSFER.md` §7/§8。 +- **收益**:libwebrtc 原生集成 iOS 网络栈 + DcSCTP 默认 ~5MB rwnd——摆脱 WebKit 写死的 256KB 接收窗(R-iOS-3 的吞吐残差)对数据面的钳制,连通性与吞吐一并根治。 +- **互通靠线协议逐字节对齐**:`LibWebRtcEngine` 是 `desktop/engine` 的 `engine.go`/`session.go` 线协议的忠实 Swift 端口——DataChannel `cdrop-file`(ordered)、`meta`/二进制分片(64KB)/`done`/`ack` 帧、16MB/4MB 水位背压、ack 追平完成、不冲突落盘。故 iOS↔桌面(pion)、iOS↔浏览器(JS)三端互通。 +- **桥与 JS 路由零改动**:`IOS_NATIVE` 开关、`p2pIos.ts`/`net/ios.ts` 桥协议原样;只在 Swift 侧新增一条原生传输 RPC(`startOutgoing`/`startIncoming`/`handleSignal`/`cancel`,completion-based,经 `resolveOnMain` 跳主线程 resolve)。**iOS 定向的引擎配置绝不回流 `desktop/engine` 共享包**。 +- **验证**:环回端到端(两个引擎同进程交叉连、20MB 经完整线协议传输后逐字节比对)✅;模拟器全 app 编译 ✅;web typecheck/build ✅。真机 / 真网吞吐互通为剩余闸(须 deploy prod 让设备拿到 `IOS_NATIVE=true` 引擎 + 真机装机,二者 Touch ID 门控)。 + --- ## 1. 后端现状(决定 iOS 能做什么,2026-06-24 扫描) @@ -72,6 +83,8 @@ Share Extension 落法(据上):**抓文件 → 写 App Group 容器 → ## 2. 桥协议(arch A 的接口契约,账号无关、可先冻结) +> **状态(2026-06-28,决策 F 后)**:本节描述的桥是**编排与控制面**(信令 / 会话 / presence / 消息 / 接收回退字节通道)。**P2P 数据面(含其磁盘 I/O)现走原生 libwebrtc**(决策 F),不再经此桥的 `sendFile` / `Begin`-`Append`-`Finalize` 路径;那条接收字节通道仅保留给 relay / 非原生回退。下文桥语义对编排层仍准确。 + 原生 ↔ 无头 JS 引擎的 RPC:`WKScriptMessageHandler`(JS→Swift)+ `evaluateJavaScript`(Swift→JS),类比 Wails 的 `EventsEmit` / bound methods,但**方向相反**(原生在顶层)。 - **平台判定**:`isIOSShell()`(`web/src/net/ios.ts`)查 `window.webkit.messageHandlers.cdropEngine` 在场(类比 `isDesktop()` 查 `window.go.main.App`)。无头入口(`web/src/engine/main.ts`)据此装配桥。 @@ -113,6 +126,8 @@ Share Extension 落法(据上):**抓文件 → 写 App Group 容器 → > **实现状态(2026-06-27)**:I1/I2/I3/I4/I5/I6 的**代码**全部落地——发送端流式(R-iOS-4,FileSource + 原生 Range,整文件不进 WebView 内存);后台续传(BGContinuedProcessingTask);APNs(后端 `internal/apns` ES256 + 原生注册经引擎桥);Share Extension(App Group 收件箱 + `cdrop://share` 深链);控制中心两控件(**专用 broker 设备会话**,纯原生 REST,不与引擎抢 refresh 轮换)。模拟器验 + ultracode 多 agent 审查修讫(0 HIGH,修 2 MED + 7 LOW)。**I0 账号 / 证书 + 真机签名 / 真发推送 / 旁加载分发**仍账号门控——手册 `ios/CDrop/REALDEVICE.md`(ASC API Key 自动 provisioning + `just ios-device` 全 CLI 装机,`-allowProvisioningUpdates` 自动登记设备 / 建 profile)。包名 `net.commilitia.Commilitia-Drop`,App Group `group.net.commilitia.Commilitia-Drop`。 +> **更新(2026-06-28)**:① **P2P 数据面改原生 libwebrtc**(决策 F;完整依据 `desktop/NATIVE-TRANSFER.md` §7/§8)——已真机装机、用户反馈“传输总体良好”,真网吞吐互通为剩余闸;② **UX 改造**(发送预览直入 / 消息按设备会话 / 删除粒度,§10);③ **品牌与字体系统**(logo + Orbit Gothic / Maple Mono / Fredoka,§11)。 + --- ## 5. 一致性:强约束同步 vs 检查单(决策 D 展开) @@ -235,6 +250,57 @@ Share Extension 落法(据上):**抓文件 → 写 App Group 容器 → --- +## 10. UX 改造(2026-06-28,真机反馈驱动) + +真机使用后落地的三块结构性改造,均属决策 D 的“非引擎驱动 UI 流程”(进 `ios/PARITY.md` 残余项 1/2,已登记)。 + +### 10.1 发送预览界面(取代即弹的设备选择框) + +- 选文件 / 选图后**不再**直接弹设备选择,而是进入**发送预览**(`Sources/SendComposeSheet.swift`):列出待发文件名 + 字节大小,下方列表选目标设备。手动发送与分享发送**共用**此界面。 +- 手动入口支持**多选**:`fileImporter(allowsMultipleSelection: true)` + `photosPicker(selection:)` 数组;`RootView` 的 `present`/`sendPicked`/`clearPicked`/`loadPhotos` 串起选取→预览→发送。 + +### 10.2 分享直入预览(修“必须重启 app”) + +- Share Extension 抓取 → 暂存 App Group → 唤起主程序,主程序**直入发送预览**(同 `SendComposeSheet`)。 +- 修复“分享后必须重启 app 才出现传输”:唤起宿主 app 经响应链 IMP 调现代 `openURL:options:completionHandler:`(旧单参 `openURL:` 在 iOS 18+ 已成空壳),**并以 `scenePhase` 回 `.active` 时 `loadPendingShares()` 兜底**——回前台即扫 App Group 暂存、补出预览,不依赖那条 iOS 灰区的扩展→宿主唤起是否可靠。分享引导界面见 §11.3。 + +### 10.3 消息按设备会话聚合 + 删除粒度 + +- 消息从扁平流改为**按设备聚合的会话列表**(社交 app 模型,`Sources/MessagesView.swift`):会话列表项 = 有历史**或**当前可达的设备;点进 `ConversationView` 看与该设备的线程。非 APNs 离线设备**不能**作为发送对象,但历史**可查看**(`canMessage()` 判定)。 +- **每设备未读分桶**:`EngineController` 的 `unreadByPeer: [String: Int]` + `activeConversation`;每设备会话显示各自未读红点,“消息”标签显示总未读(`unreadMessages = unreadByPeer.values.reduce(0,+)`)。进会话即 `markConversationRead` + `setActiveConversation`。 +- **任意删除**:可删**单条消息**与**整个设备的历史**(`deleteConversation()`),避免废 session 堆积。 + +--- + +## 11. 品牌与字体系统(2026-06-28,对齐 web) + +iOS 界面引入与 web 一致的品牌资产与字体栈——这是强约束③(§5,品牌资产 + 主色单一源)在视觉层的延伸。 + +### 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) + +- 与 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`。 + +### 11.3 分享引导界面 + +- `Share/ShareGuideView.swift`(`ShareModel: ObservableObject`):扩展内呈现吉祥物 + Wordmark + “已就绪 N 个文件” + “打开 Commilitia Drop” 主按钮 + 取消,引导用户回到主程序完成发送(扩展内不跑传输引擎,§8.4)。取消则删暂存;`started` 标志防重入。 + +--- + +## 12. 全平台平均速度(2026-06-28) + +传输完成后,速度列**不再显示最后一次瞬时速率,而显示平均速度**(总字节 / 总耗时)。三端一致:web `store/slices/transfer.ts` 的 `completeTransfer` 在 DONE 时置 `bytesPerSec = total / (elapsedMs/1000)`、`TransferRow.tsx` 在 DONE 读 `avgRate`;iOS `RootView` 卡片 / 详情完成态显平均速度;i18n 加 `transfer.avgRate`(web 3 locale + ios 3 JSON)。桌面(Wails)共享 web store,逻辑自动生效,但需 `just desktop-build-{mac,win}` 重建 + 重分发方落到已装客户端。 + +--- + ## 附:与桌面端策略对照(修订) | 维度 | 桌面(Wails) | iOS(arch A) | diff --git a/web/src/features/transfer/TransferRow.tsx b/web/src/features/transfer/TransferRow.tsx index 928ce76..58d047e 100644 --- a/web/src/features/transfer/TransferRow.tsx +++ b/web/src/features/transfer/TransferRow.tsx @@ -71,8 +71,8 @@ export function TransferRow(props: TransferRowProps) // 终态记录可删除(从列表移除);非终态记录可取消(驱动后端 /cancel)。 const terminal = done || failed || cancelled; - // 发送方仍在试 P2P(已接受 / P2P 中、尚未切中继)时,给一个「立即中继」入口, - // 跳过 30s ICE 等待——只对 outgoing 有意义(驱动后端 /fallback)。 + // 发送方仍在试 P2P(已接受 / P2P 中、尚未切中继)时,给一个「中继传输」入口手动切中继—— + // 无自动回退后这是切中继的唯一途径,只对 outgoing 有意义(驱动后端 /fallback)。 const canRelayNow = tr.direction === "outgoing" && (tr.state === "ACCEPTED" || tr.state === "P2P_ACTIVE") && tr.phase !== "fallback_pending" @@ -192,10 +192,13 @@ function bytesText(tr: TransferRecord): string // 卡住了」状态已表达停滞,再显示一个陈旧的非零速度会误导。速率由 store 做 EMA 平滑。 function speedText(tr: TransferRecord, stalled: boolean): string { - if (stalled) { return ""; } - if (!FLOW_PHASES.has(tr.phase ?? "initializing")) { return ""; } const rate = tr.bytesPerSec; if (!rate || !Number.isFinite(rate) || rate <= 0) { return ""; } + // 完成后显示平均速度(store completeTransfer 已把 bytesPerSec 置为总字节 / 总耗时的均速)。 + if (tr.state === "DONE") { return t("transfer.avgRate", { rate: formatBytes(rate) }); } + // 进行中:瞬时速度(EMA 平滑)。卡死 / 非流动阶段不显示陈旧速度,避免误导。 + if (stalled) { return ""; } + if (!FLOW_PHASES.has(tr.phase ?? "initializing")) { return ""; } return t("transfer.bytesRate", { rate: formatBytes(rate) }); } diff --git a/web/src/features/transfer/transfer.ts b/web/src/features/transfer/transfer.ts index c40f291..5a51390 100644 --- a/web/src/features/transfer/transfer.ts +++ b/web/src/features/transfer/transfer.ts @@ -4,10 +4,10 @@ import { relayReceiveFile, relaySendFile } from "./relay"; import type { FileSource } from "./source"; import { useAppStore, type TransferRecord } from "../../store"; -// Orchestrator that picks P2P first, falls back to Relay on ICE timeout -// (plan §M9: 30s after start, if iceConnectionState != connected → /fallback). - -const ICE_TIMEOUT_MS = 30_000; +// Orchestrator that starts P2P and leaves relay to a manual user action. +// 不再有 30s ICE 超时自动回退中继:P2P 无时限尝试,中继仅由用户手动触发(TransferRow 的 +// 「中继传输」按钮 → skipWaitRelay)。自动回退会在弱网/慢 ICE 下抢先切走、且掩盖 P2P 是否 +// 终将连通,故按产品要求移除——所有中继改手动。 interface OutgoingState { @@ -15,7 +15,6 @@ interface OutgoingState peerName: string; src: FileSource; p2p: P2PSession | null; - fallbackTimer: number | null; // waitTimer / waitUnsub back the "wait for an offline receiver to wake" path: the timeout that // fails the transfer if the peer never comes online, and the presence subscription that fires // P2P once it does. @@ -73,7 +72,6 @@ export async function startOutgoingTransfer( peerName: receiverName, src, p2p: null, - fallbackTimer: null, waitTimer: null, waitUnsub: null, abort, @@ -86,8 +84,9 @@ export async function startOutgoingTransfer( return sessionId; } -// beginP2P arms the relay-fallback watchdog and starts the P2P offer. Extracted from -// startOutgoingTransfer so the offline-receiver path can defer it until the peer wakes. +// beginP2P starts the P2P offer. Extracted from startOutgoingTransfer so the +// offline-receiver path can defer it until the peer wakes. No auto-fallback watchdog: +// relay is manual only (skipWaitRelay). function beginP2P(state: OutgoingState): void { const { sessionId, peerName, src } = state; @@ -95,35 +94,8 @@ function beginP2P(state: OutgoingState): void const r0 = store0.activeTransfers[sessionId]; if (r0 && r0.phase === "waiting_peer") { store0.upsertTransfer({ ...r0, phase: "waiting_accept" }); } - // Arm the fallback watchdog *before* any P2P work. p2pStartOutgoing is - // intentionally synchronous: if createOffer / setLocalDescription hang - // (e.g. injected stub during fallback testing) the timer must still fire. - state.fallbackTimer = window.setTimeout(() => - { - const cur = outgoing.get(sessionId); - if (!cur) { return; } - const p2pState = cur.p2p?.state; - if (p2pState !== "connected" && p2pState !== "completed") - { - // 把"已经放弃 P2P,等服务端 RELAY_ACTIVE"这个状态显式落到 store, - // UI 据此告诉用户「还在通,正在切到中继」而不是干等。 - const store = useAppStore.getState(); - const r = store.activeTransfers[sessionId]; - if (r) { store.upsertTransfer({ ...r, phase: "fallback_pending" }); } - void triggerFallback(sessionId); - } - }, ICE_TIMEOUT_MS); - const p2p = p2pStartOutgoing(sessionId, peerName, src); state.p2p = p2p; - - p2p.onState((s) => - { - if (s === "completed" || s === "connected") - { - if (state.fallbackTimer) { clearTimeout(state.fallbackTimer); state.fallbackTimer = null; } - } - }); } // PEER_WAIT_MS bounds how long the sender waits for an offline receiver to wake + reconnect. @@ -311,7 +283,6 @@ function cleanupOutgoing(sessionId: string): void { const s = outgoing.get(sessionId); if (!s) { return; } - if (s.fallbackTimer) { clearTimeout(s.fallbackTimer); } if (s.waitTimer !== null) { clearTimeout(s.waitTimer); } if (s.waitUnsub) { s.waitUnsub(); } s.abort.abort(); @@ -325,17 +296,10 @@ export function cleanupTransfer(sessionId: string): void incomingFiles.delete(sessionId); } -// UI-triggered (发送方「跳过等待、立即中继」):发送方不等 30s ICE 超时,直接切到 -// 中继。复用 watchdog 的逻辑——先停掉那个定时器以免重复 fire,把记录标成 -// fallback_pending 让 UI 显示「正在切到中继」,再请求服务端转 RELAY_ACTIVE。 +// UI-triggered (发送方「中继传输」):手动把传输切到中继(无自动回退后,这是切中继的唯一途径)。 +// 把记录标成 fallback_pending 让 UI 显示「正在切到中继」,再请求服务端转 RELAY_ACTIVE。 export async function skipWaitRelay(sessionId: string): Promise { - const cur = outgoing.get(sessionId); - if (cur?.fallbackTimer) - { - clearTimeout(cur.fallbackTimer); - cur.fallbackTimer = null; - } const store = useAppStore.getState(); const r = store.activeTransfers[sessionId]; if (r) { store.upsertTransfer({ ...r, phase: "fallback_pending" }); } diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index e8e36d5..1790a61 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -284,7 +284,7 @@ export const enUS: Partial = { "transfer.saveFailed": "Failed to save file: {{error}}", "transfer.action.cancel": "Cancel", "transfer.action.delete": "Delete", - "transfer.action.relayNow": "Relay now", + "transfer.action.relayNow": "Relay", "transfer.action.details": "Details", "transfer.state.PENDING": "Pending", "transfer.state.ACCEPTED": "Accepted", @@ -313,6 +313,7 @@ export const enUS: Partial = { "transfer.stalled": "Looks stalled ({{seconds}}s without progress)", "transfer.bytesProgress": "{{sent}} / {{total}}", "transfer.bytesRate": "{{rate}}/s", + "transfer.avgRate": "Avg {{rate}}/s", // ---- debug expansion ------------------------------------------------ "transfer.debug.toggle": "Debug", @@ -414,7 +415,7 @@ export const enUS: Partial = { "ios.records.clear": "Clear", "ios.records.clearConfirm": "Clear all records? This cannot be undone.", "ios.transfer.cancel": "Cancel transfer", - "ios.transfer.forceRelay": "Relay now", + "ios.transfer.forceRelay": "Relay", "ios.settings.deviceCount": "Known Devices", "ios.settings.signaling": "Signaling", "ios.settings.presenceEvents": "Presence Events", diff --git a/web/src/i18n/locales/zh-CN.ts b/web/src/i18n/locales/zh-CN.ts index 456776e..8540905 100644 --- a/web/src/i18n/locales/zh-CN.ts +++ b/web/src/i18n/locales/zh-CN.ts @@ -280,7 +280,7 @@ export const zhCN = { "transfer.saveFailed": "保存文件失败:{{error}}", "transfer.action.cancel": "取消", "transfer.action.delete": "删除", - "transfer.action.relayNow": "立即中继", + "transfer.action.relayNow": "中继传输", "transfer.action.details": "详情", "transfer.state.PENDING": "等待中", "transfer.state.ACCEPTED": "已接受", @@ -309,6 +309,7 @@ export const zhCN = { "transfer.stalled": "似乎卡住了({{seconds}} 秒无进度)", "transfer.bytesProgress": "{{sent}} / {{total}}", "transfer.bytesRate": "{{rate}}/秒", + "transfer.avgRate": "平均 {{rate}}/秒", // ---- 调试展开区 ----------------------------------------------------- "transfer.debug.toggle": "调试信息", @@ -410,7 +411,7 @@ export const zhCN = { "ios.records.clear": "清空", "ios.records.clearConfirm": "确定清空全部记录?此操作不可撤销。", "ios.transfer.cancel": "取消传输", - "ios.transfer.forceRelay": "立即切到中继", + "ios.transfer.forceRelay": "中继传输", "ios.settings.deviceCount": "已知设备", "ios.settings.signaling": "信令连接", "ios.settings.presenceEvents": "在线事件", diff --git a/web/src/i18n/locales/zh-TW.ts b/web/src/i18n/locales/zh-TW.ts index c6304d6..089f5dd 100644 --- a/web/src/i18n/locales/zh-TW.ts +++ b/web/src/i18n/locales/zh-TW.ts @@ -284,7 +284,7 @@ export const zhTW: Partial = { "transfer.saveFailed": "儲存檔案失敗:{{error}}", "transfer.action.cancel": "取消", "transfer.action.delete": "刪除", - "transfer.action.relayNow": "立即中繼", + "transfer.action.relayNow": "中繼傳輸", "transfer.action.details": "詳情", "transfer.state.PENDING": "等待中", "transfer.state.ACCEPTED": "已接受", @@ -313,6 +313,7 @@ export const zhTW: Partial = { "transfer.stalled": "似乎卡住了({{seconds}} 秒無進度)", "transfer.bytesProgress": "{{sent}} / {{total}}", "transfer.bytesRate": "{{rate}}/秒", + "transfer.avgRate": "平均 {{rate}}/秒", // ---- 偵錯展開區 ----------------------------------------------------- "transfer.debug.toggle": "偵錯資訊", @@ -414,7 +415,7 @@ export const zhTW: Partial = { "ios.records.clear": "清空", "ios.records.clearConfirm": "確定清空全部記錄?此操作無法復原。", "ios.transfer.cancel": "取消傳輸", - "ios.transfer.forceRelay": "立即切到中繼", + "ios.transfer.forceRelay": "中繼傳輸", "ios.settings.deviceCount": "已知裝置", "ios.settings.signaling": "信令連線", "ios.settings.presenceEvents": "上線事件", diff --git a/web/src/store/slices/transfer.ts b/web/src/store/slices/transfer.ts index 4053e99..74e334c 100644 --- a/web/src/store/slices/transfer.ts +++ b/web/src/store/slices/transfer.ts @@ -46,7 +46,19 @@ export const createTransferSlice: StateCreator { const cur = s.activeTransfers[sessionId]; if (!cur) { return s; } - const finalRec = normalizeTerminal(cur, finalState); + let finalRec = normalizeTerminal(cur, finalState); + // 完成后速度显示「平均速度」(总字节 / 总耗时),取代最后的瞬时 EMA 速率——后者只反映末段 + // 抽样、对整体传输无代表性。仅 DONE 有意义(FAILED/CANCELLED 为部分传输,不覆写)。全平台 + // 一处生效:web/desktop 读此记录、iOS 经 toWire→transferDone 拿到同一 bytesPerSec。 + if (finalState === "DONE") + { + const elapsedMs = Date.now() - (cur.startedAt ?? Date.now()); + const total = cur.fileSize > 0 ? cur.fileSize : (cur.bytesTransferred ?? 0); + if (elapsedMs > 0 && total > 0) + { + finalRec = { ...finalRec, bytesPerSec: total / (elapsedMs / 1000) }; + } + } const { [sessionId]: _, ...rest } = s.activeTransfers; return ({ activeTransfers: rest,