From 2280462a222a4ade84b45016a8a5202b0c477732 Mon Sep 17 00:00:00 2001 From: joywayer Date: Mon, 22 Jun 2026 02:35:37 +0800 Subject: [PATCH] =?UTF-8?q?docs=EF=BC=9APlan=20+=20Design=20=E6=89=A9?= =?UTF-8?q?=E5=B1=95=20Phase=201.14=20=E4=B8=BA=205=20=E5=AD=90=E9=A1=B9?= =?UTF-8?q?=EF=BC=88=E5=90=AB=20AppIcon=20/=20LaunchScreen=20/=20progress?= =?UTF-8?q?=20/=20splash=20UI=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原 Phase 1.14 仅含"WebContainer 16:9 letterbox + 串接整链路", 漏掉了用户实际能看到的三件事:AppIcon / LaunchScreen 启动图 / zip 下载进度条。msext 上线时这三件都有,新外壳上线必须对齐。按用户偏好 方案 A 合并到 Phase 1.14,避免做两遍 WebContainer。 - Plan §5 Phase 1.14 拆为 1.14.a–e 五个子项 - 1.14.a AppIcon:拷贝 docs/res/Images.xcassets/AppIcon-1.appiconset (9 个 png + Contents.json) 到 ylgamehall/Assets.xcassets - 1.14.b LaunchScreen:Default-568h@2x~iphone.png → SplashImage imageset;改写 LaunchScreen.storyboard 横屏铺满 UIImageView - 1.14.c LobbyZipUpgrader 加 onProgress 回调(@Sendable (Double) -> Void) - 1.14.d WebContainerViewController:splash 覆盖层(启动图 + UIProgressView + 状态文字)+ 16:9 letterbox + 启动流水线串接 - 1.14.e webView didFinish 淡出 splash(0.3s 动画 + removeFromSuperview) - Plan §8 进度追踪同步:5 个子项独立勾选条目 - Design §6.3.5 WebContainer 调用串完整重写: - Splash UI 6 状态切换表(拼命启动中... / 拉取配置中... / 下载更新中 XX% / 加载大厅... / 淡出) - runBootPipeline 完整 swift 骨架:含 .shortText 短文本响应处理 / showmessage 阻塞处理 / IPA 升级 alert / zip 升级 onProgress 回调 - 所有失败 / 阻塞终态都用 UIAlertController(非 splash 文字): 短文本 / showmessage / IPA 升级 / 网络错误 / zip 下载失败 各场景的按钮文案 + tap 行为表 --- docs/Development-Plan.md | 19 +++- docs/H5-Native-Implementation-Design.md | 116 ++++++++++++++++++++---- 2 files changed, 115 insertions(+), 20 deletions(-) diff --git a/docs/Development-Plan.md b/docs/Development-Plan.md index a596845..a9d3dad 100644 --- a/docs/Development-Plan.md +++ b/docs/Development-Plan.md @@ -287,12 +287,21 @@ Contract Design Plan(本文档) ##### 1.E 集成 + 烟雾测试 -- [ ] **1.14** 实现 `Source/WebView/WebContainerViewController.swift`(基类) +- [ ] **1.14.a** AppIcon:从 `docs/res/Images.xcassets/AppIcon-1.appiconset` 拷贝 9 个 png + Contents.json 到 `ylgamehall/Assets.xcassets/AppIcon.appiconset`,替换 Xcode 默认空模板。验收:Home Screen 显示真实图标 +- [ ] **1.14.b** LaunchScreen:把 `docs/res/Res/Default-568h@2x~iphone.png` 拷到 `ylgamehall/Assets.xcassets/SplashImage.imageset`;改写 `LaunchScreen.storyboard` 为单一 UIImageView 横屏铺满(contentMode = .scaleAspectFill,背景 .black)。验收:启动瞬间看到启动图而非黑屏 +- [ ] **1.14.c** `LobbyZipUpgrader` 加 progress 回调 + - `upgradeIfNeeded(resolved:onProgress:) async throws -> Outcome`,`onProgress: @Sendable (Double) -> Void` 报告 0.0…1.0 + - 用 `URLSession.download` 配 `URLSessionTaskDelegate.urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)` 上报 +- [ ] **1.14.d** 实现 `Source/WebView/WebContainerViewController.swift`(基类) - 持有 `BridgedWebView` + `BridgeBus` - 16:9 比例布局:屏幕比 < 16:9 用宽度撑满上下黑边;屏幕比 > 16:9 用高度撑满左右黑边 - - `viewDidLoad` 内串:`ResourceUnzipper.ensureReady() → RemoteConfigClient.fetch → VersionResolver.resolve → LobbyZipUpgrader.upgradeIfNeeded → loadFileURL(lobbyIndex)` + - **Splash 状态层**:与 LaunchScreen 同款启动图 + UIProgressView + 状态文字标签("拼命启动中..." / "下载更新中 XX%" / "网络异常重试中") + - `viewDidLoad` 内串:`ResourceUnzipper.ensureReady() → RemoteConfigClient.fetch → VersionResolver.resolve → LobbyZipUpgrader.upgradeIfNeeded(onProgress: 更新 UIProgressView) → loadFileURL(lobbyIndex)` - 无升级路径走快路(< 200 ms),有升级路径显示 progress UI - 实现 `webViewWebContentProcessDidTerminate:` 退避 reload(Design §3.6) +- [ ] **1.14.e** WebView 加载完成后淡出 splash + - `webView(_:didFinish:)` 内 `UIView.animate(duration: 0.3)` splash.alpha = 0 → `removeFromSuperview` + - 期间 webView 已渲染好 H5 大厅,无白屏闪烁 - [ ] **1.15** 实现 `Source/Bridge/Handlers/VibratorHandler.swift` - `register(_ bridge)` 注册 `vibrator` handler → `AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)` + responseCallback `"vibrator"` - [ ] **1.16** `SceneDelegate` → `WebContainerViewController` 作 rootViewController @@ -785,7 +794,11 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin - [x] 1.11 VersionResolver(chulishengji 双子树合并算法,纯函数) - [x] 1.12 LocalVersionReader(version.xml 解析 + ATS 全局放行) - [x] 1.13 LobbyZipUpgrader(actor,原子 rename 升级;端到端实测 260→261) -- [ ] 1.14 WebContainerViewController(16:9 letterbox + 串接整链路) +- [ ] 1.14.a AppIcon 替换 Xcode 默认空模板 +- [ ] 1.14.b LaunchScreen 改启动图(Default-568h@2x 横屏铺满) +- [ ] 1.14.c LobbyZipUpgrader 加 progress 回调 +- [ ] 1.14.d WebContainerViewController(含 splash UI 状态层 + 16:9 letterbox + 启动流水线串接) +- [ ] 1.14.e WebView didFinish 后淡出 splash - [ ] 1.15 VibratorHandler - [ ] 1.16 SceneDelegate → WebContainerViewController - [ ] 1.17 Demo H5 联调 + 真机验证升级路径 diff --git a/docs/H5-Native-Implementation-Design.md b/docs/H5-Native-Implementation-Design.md index a1c63dd..1baa5d2 100644 --- a/docs/H5-Native-Implementation-Design.md +++ b/docs/H5-Native-Implementation-Design.md @@ -1236,36 +1236,118 @@ public actor LobbyZipUpgrader { } ``` -#### 6.3.5 WebContainer 调用串 +#### 6.3.5 WebContainer 调用串 + Splash UI 状态机(Phase 1.14) + +WebContainerViewController 同时承担两层: +- **下层**:BridgedWebView 嵌入 + 16:9 letterbox(屏幕比 < 16:9 上下黑边;> 16:9 左右黑边) +- **上层(Splash 覆盖)**:与 LaunchScreen.storyboard 同款启动图(横屏铺满)+ UIProgressView + 状态文字标签 + - 启动瞬间:LaunchScreen 静态图(iOS 系统级,0 延迟) + - viewDidLoad:WebContainer 把自己的 Splash 覆盖在 BridgedWebView 之上(两者 alpha 0/1 由状态切换) + - 启动流水线推进时:updateSplash(text: ..., progress: ...) 更新文字 + 进度条 + - WKWebView didFinish 后:UIView.animate(duration: 0.3) splash.alpha = 0 → removeFromSuperview ```swift // Source/WebView/WebContainerViewController.swift(Phase 1.14) override func viewDidLoad() { super.viewDidLoad() + view.backgroundColor = .black + + setupBridgedWebView() // 16:9 letterbox 嵌入 + setupSplashOverlay() // 启动图 + 进度条 + 文字标签 + + bridgedWebView.webView.navigationDelegate = self + Task { @MainActor in - try await ResourceUnzipper.shared.ensureReady() // 首装解压 Bundle 内 zip - let config = try await RemoteConfigClient.shared.fetch() - let resolved = VersionResolver.resolve( - config: config, - agentId: BundleConfig.shared.agent, - channelId: BundleConfig.shared.channel, - marketId: BundleConfig.shared.market, - gameId: BundleConfig.shared.gameId - ) - if resolved.appVersion > LocalVersionReader.localAppVersion { - // 弹窗 + Safari 外链 → resolved.appDownload - return showAppUpgradeAlert(resolved.appDownload) + await runBootPipeline() + } +} + +private func runBootPipeline() async { + do { + updateSplash(text: "拼命启动中...", progress: nil) + try await ResourceUnzipper.shared.ensureReady() + + updateSplash(text: "拉取配置中...", progress: nil) + let outcome = try await RemoteConfigClient.shared.fetch() + + switch outcome { + case .shortText(let msg): + // 运营杀手锏 #1:弹 alert,App 永停(msext NewRootVC.m:1239-1243 契约) + showBlockingAlert(msg) + return + case .parsed(let cfg): + let bc = BundleConfig.shared + let resolved = VersionResolver.resolve( + config: cfg, + agentId: bc.agent, + channelId: bc.channel, + marketId: bc.market, + gameId: bc.gameId + ) + + // 运营杀手锏 #2:showmessage 非空 → 弹 alert + 完全阻塞 + if let msg = resolved.showmessage, !msg.isEmpty { + showBlockingAlert(msg) + return + } + + // IPA 升级:弹窗 + Safari 外链(msext NewRootVC.m:1510-1520) + if resolved.appVersion > LocalVersionReader.localAppVersion, + let dl = resolved.appDownload { + showIPAUpgradeAlert(dl) + return + } + + // H5 zip 升级:进度条实时更新 + _ = try await LobbyZipUpgrader.shared.upgradeIfNeeded( + resolved: resolved, + onProgress: { @MainActor [weak self] p in + self?.updateSplash( + text: String(format: "下载更新中 %d%%", Int(p * 100)), + progress: p + ) + } + ) } - _ = try await LobbyZipUpgrader.shared.upgradeIfNeeded( - remoteGameVersion: resolved.gameVersion, - remoteGameZip: resolved.gameZip - ) + + updateSplash(text: "加载大厅...", progress: nil) bridgedWebView.webView.loadFileURL( SandboxPaths.lobbyIndex, allowingReadAccessTo: SandboxPaths.lobbyRoot ) + // didFinish 回调里淡出 splash(见 WKNavigationDelegate 扩展) + } catch { + showFatalAlert(error) } } + +// MARK: - WKNavigationDelegate +func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + UIView.animate(withDuration: 0.3, animations: { [weak self] in + self?.splash.alpha = 0 + }, completion: { [weak self] _ in + self?.splash.removeFromSuperview() + }) +} +``` + +**Splash UI 状态切换表**: + +| 阶段 | 文字 | progress | 持续时长(典型) | +|------|------|----------|---------------| +| viewDidLoad 起 | "拼命启动中..." | 隐藏 | ~50 ms | +| 首装解压 zip 中 | "拼命启动中..." | 隐藏 | ~50-500 ms | +| 拉远端配置中 | "拉取配置中..." | 隐藏 | ~500-2000 ms | +| H5 zip 下载中 | "下载更新中 XX%" | 0.0-1.0 | ~1000-3000 ms | +| WebView 加载 H5 中 | "加载大厅..." | 隐藏 | ~500-1000 ms | +| WebView didFinish | (淡出消失)| - | 300 ms 动画 | + +**所有失败 / 阻塞终态都用 UIAlertController(非 splash 文字)**: +- 短文本响应:弹 alert,标题 `gamehallname + 提醒`,单按钮"确定",无 tap 处理(永停) +- showmessage 非空:同上 +- IPA 升级:弹 alert,单按钮"确定"→ openURL(appDownload),永停 +- 网络错误 / 解析失败:弹 alert,单按钮"重试",点击重新 runBootPipeline() +- zip 下载失败:弹 alert,单按钮"重试" + 单按钮"使用旧版本"(回退用 IPA 内 Bundle zip) ``` #### 6.3.6 与 msext 历史实现的差异(不要照抄的部分)