启动文字提示,h5调试加入
This commit is contained in:
@@ -231,7 +231,7 @@ H5 → Native 时 data 字典中的 key,以及 Native → H5 callback payload
|
|||||||
- `sharelogin` 回包中 `Province` 是**大写 P**(不是 `province`)
|
- `sharelogin` 回包中 `Province` 是**大写 P**(不是 `province`)
|
||||||
- `OpenurlTitleData` 入参中 `"title "` 末尾**有一个空格**(不是 `"title"`)
|
- `OpenurlTitleData` 入参中 `"title "` 末尾**有一个空格**(不是 `"title"`)
|
||||||
- `getphoneinfo` 回包用**小写 i**(H5 调用方是 `getphoneInfo` 大写 I)
|
- `getphoneinfo` 回包用**小写 i**(H5 调用方是 `getphoneInfo` 大写 I)
|
||||||
- `appservice` 用字符串 `"1"`(后台)/ `"2"`(前台),不是数字
|
- `appservice` 用字符串 `"2"`(后台)/ `"1"`(前台),不是数字 ⚠️ 值错位沿用 msext WKWebView 路径历史,参 `docs/H5-Native-Contract.md` §3.2 `appservice` 值错位说明
|
||||||
- `getlocationinfo` 中 `latitude` / `longitude` 是 **string** 而不是 number(`stringWithFormat:@"%f"` 产生)
|
- `getlocationinfo` 中 `latitude` / `longitude` 是 **string** 而不是 number(`stringWithFormat:@"%f"` 产生)
|
||||||
|
|
||||||
### 3. 参数数据结构
|
### 3. 参数数据结构
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ Contract Design Plan(本文档)
|
|||||||
- [x] **2.10** `Source/Resource/NetworkMonitor.swift`:NWPathMonitor 包装(Phase 1 已建)
|
- [x] **2.10** `Source/Resource/NetworkMonitor.swift`:NWPathMonitor 包装(Phase 1 已建)
|
||||||
- 状态变化 → 重写 `app_network.js` + `bridge.call("getnetwork", data: .string("1"/"2"/"3"))`
|
- 状态变化 → 重写 `app_network.js` + `bridge.call("getnetwork", data: .string("1"/"2"/"3"))`
|
||||||
- [x] **2.11** `Source/Resource/AppLifecycleObserver.swift`:监听 `UIApplication.didEnterBackgroundNotification` / `willEnterForegroundNotification`
|
- [x] **2.11** `Source/Resource/AppLifecycleObserver.swift`:监听 `UIApplication.didEnterBackgroundNotification` / `willEnterForegroundNotification`
|
||||||
- 后台 → `bridge.call("appservice", data: .string("1"))`;前台 → `"2"`
|
- 后台 → `bridge.call("appservice", data: .string("2"))`;前台 → `"1"`(值错位沿用 msext WKWebView 路径历史,参 Contract.md §3.2 `appservice` 值错位说明)
|
||||||
- [x] **2.12** ShakeDetector 集成在 `Source/Bridge/Handlers/ShakeHandler.swift`(Phase 2.3 一并完成)
|
- [x] **2.12** ShakeDetector 集成在 `Source/Bridge/Handlers/ShakeHandler.swift`(Phase 2.3 一并完成)
|
||||||
- `WebContainerViewController.motionEnded(_:with:)` 转发 → `bridge.call("shakeEnd", data: nil)`(仅 `canShake=YES` 时触发)
|
- `WebContainerViewController.motionEnded(_:with:)` 转发 → `bridge.call("shakeEnd", data: nil)`(仅 `canShake=YES` 时触发)
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
新外壳与现网外壳之间,**只有以下三类内容是契约,必须 1:1 一致**:
|
新外壳与现网外壳之间,**只有以下三类内容是契约,必须 1:1 一致**:
|
||||||
|
|
||||||
1. **桥接接口名** — H5 调用的 handler 名字符串(如 `mediaTypeAudio`、`friendsSharetypeUrlToptitleDescript`、`OpenurlTitleData`),少一个字符 H5 就找不到。
|
1. **桥接接口名** — H5 调用的 handler 名字符串(如 `mediaTypeAudio`、`friendsSharetypeUrlToptitleDescript`、`OpenurlTitleData`),少一个字符 H5 就找不到。
|
||||||
2. **参数字段名 & 数据结构** — H5 传入的 `data` 字段名、Native 回调时的 payload 结构。**含历史包袱**:`sharelogin.Province`(大写 P)、`OpenurlTitleData` 的入参键名 `"title "`(末尾有空格)、`appservice` 用 `"1"`/`"2"` 字符串而不是数字、字段值约定(如 `isloop: -1/0/1` 三态),**必须照抄**。
|
2. **参数字段名 & 数据结构** — H5 传入的 `data` 字段名、Native 回调时的 payload 结构。**含历史包袱**:`sharelogin.Province`(大写 P)、`OpenurlTitleData` 的入参键名 `"title "`(末尾有空格)、`appservice` 用字符串 `"2"`=进入后台 / `"1"`=回到前台(值错位,沿用 msext WKWebView 路径历史,参 §3.2 第 4 行)、字段值约定(如 `isloop: -1/0/1` 三态),**必须照抄**。
|
||||||
3. **空实现接口也必须注册** — `opensaoma`、`getGameplay` 等当前为空实现的 handler,新外壳必须以同名空 handler 注册(响应 callback 但不做任何事),否则 H5 调用时会触发"bridge not found"错误分支。
|
3. **空实现接口也必须注册** — `opensaoma`、`getGameplay` 等当前为空实现的 handler,新外壳必须以同名空 handler 注册(响应 callback 但不做任何事),否则 H5 调用时会触发"bridge not found"错误分支。
|
||||||
|
|
||||||
**除以上三类之外,原生内部一切都可以重写**:技术栈(Swift/SwiftUI/Combine 都行)、WebView 接入方式(直接用 WKScriptMessageHandler 或继续用 WebViewJavascriptBridge)、网络层(URLSession/Alamofire)、解压库(ZIPFoundation)、状态机、控制器层级、命名风格——只要外部行为不变,怎么实现都行。本文档后续章节中描述的"现有实现细节"只是为了帮助你理解**为什么 H5 期待某种行为**,不是要求你照抄实现方式。
|
**除以上三类之外,原生内部一切都可以重写**:技术栈(Swift/SwiftUI/Combine 都行)、WebView 接入方式(直接用 WKScriptMessageHandler 或继续用 WebViewJavascriptBridge)、网络层(URLSession/Alamofire)、解压库(ZIPFoundation)、状态机、控制器层级、命名风格——只要外部行为不变,怎么实现都行。本文档后续章节中描述的"现有实现细节"只是为了帮助你理解**为什么 H5 期待某种行为**,不是要求你照抄实现方式。
|
||||||
@@ -558,7 +558,7 @@ window.rootViewController = NavgationController
|
|||||||
| 1 | `getphoneinfo` | 注册 `getphoneInfo` handler 收到调用后 | 见下方表 A | ✓ | ✓ |
|
| 1 | `getphoneinfo` | 注册 `getphoneInfo` handler 收到调用后 | 见下方表 A | ✓ | ✓ |
|
||||||
| 2 | `getBattery` | UIDeviceBatteryLevelDidChangeNotification | 字符串 `"%.2f"` 形式的小数电量(0~1)| ✓ | ✓ |
|
| 2 | `getBattery` | UIDeviceBatteryLevelDidChangeNotification | 字符串 `"%.2f"` 形式的小数电量(0~1)| ✓ | ✓ |
|
||||||
| 3 | `getnetwork` | AFNetworkReachability 变化 | 字符串 `"1"`/`"2"`/`"3"` (1=无网,2=WiFi,3=蜂窝)| ✓ | ✓ |
|
| 3 | `getnetwork` | AFNetworkReachability 变化 | 字符串 `"1"`/`"2"`/`"3"` (1=无网,2=WiFi,3=蜂窝)| ✓ | ✓ |
|
||||||
| 4 | `appservice` | App 前后台切换 | 字符串 `"1"`=进入后台,`"2"`=回到前台(命名错位,沿用历史)| ✓ | ✓ |
|
| 4 | `appservice` | App 前后台切换 | 字符串 `"2"`=进入后台,`"1"`=回到前台(值错位,沿用 msext WKWebView 路径历史。详见下方注释)| ✓ | ✓ |
|
||||||
| 5 | `shakeEnd` | motionEnded 摇一摇结束 | `nil` | ✓ | ✓ |
|
| 5 | `shakeEnd` | motionEnded 摇一摇结束 | `nil` | ✓ | ✓ |
|
||||||
| 6 | `getlocationinfo` | 定位成功/失败 | 见下方表 B | ✓ | ✓ |
|
| 6 | `getlocationinfo` | 定位成功/失败 | 见下方表 B | ✓ | ✓ |
|
||||||
| 7 | `gameui_play_voice` | mediaTypeAudio 开始播放(且 voicePlaying=1 时)| 字符串 user_id | ✓ | ✓ |
|
| 7 | `gameui_play_voice` | mediaTypeAudio 开始播放(且 voicePlaying=1 时)| 字符串 user_id | ✓ | ✓ |
|
||||||
@@ -575,6 +575,16 @@ window.rootViewController = NavgationController
|
|||||||
- `phonestate`:H5 子游戏会在收到 `"2"` 时主动哑麦/暂停,收到 `"0"` 恢复,跟视频房间体验强相关
|
- `phonestate`:H5 子游戏会在收到 `"2"` 时主动哑麦/暂停,收到 `"0"` 恢复,跟视频房间体验强相关
|
||||||
- `recordSuccess`:H5 收到后会拿 `fileUrl` 而非 `audiourl` 走业务(早期接口与新接口并存的兼容产物);新外壳两个 handler 都得发,data 字段不同
|
- `recordSuccess`:H5 收到后会拿 `fileUrl` 而非 `audiourl` 走业务(早期接口与新接口并存的兼容产物);新外壳两个 handler 都得发,data 字段不同
|
||||||
|
|
||||||
|
**`appservice` 值错位说明**(极容易看反,新人务必照抄不要"修正"):
|
||||||
|
|
||||||
|
原 msext WKWebView 路径(`NewRootVC.m:1769-1786` / `gameController.m:1319-1336`)的真实代码是:
|
||||||
|
- iOS `applicationDidEnterBackground`(**真·进入后台**)→ `AppDelegate` post 一个**名为 `enterForeground` 的 NSNotification**(NSNotification 名错位,沿用历史)→ VC 的 `-enterForeground` 方法触发 → `[_bridge callHandler:@"appservice" data:@"2"]`
|
||||||
|
- iOS `applicationDidBecomeActive`(**真·回到前台**)→ `AppDelegate` post 一个**名为 `applicationWillResignActive` 的 NSNotification**(NSNotification 名错位)→ VC 的 `-applicationWillResignActive` 方法触发 → `[_bridge callHandler:@"appservice" data:@"1"]`
|
||||||
|
|
||||||
|
H5 端 N 年来收到的实际语义就是:**`"2"` = 进入后台、`"1"` = 回到前台**。新外壳不论 NSNotification 内部命名怎么改、用 `willEnterForegroundNotification` 还是 `didBecomeActiveNotification`,桥发出的字符串值必须按上面这套对应关系发,**不要被 NSNotification 名误导而把值发反**。
|
||||||
|
|
||||||
|
(顺带:原 msext 还有一条 iOS<9 UIWebView 路径 `RootVC.m:516-543` / `fourviewVC.m:1254-1282`,发的值反过来:进入后台 → `"1"` / 回到前台 → `"2"`。该路径在新外壳最低 iOS 15.6 下完全不可达,**不能作为对齐依据**,请忽略。)
|
||||||
|
|
||||||
**表 A — `getphoneinfo`**:
|
**表 A — `getphoneinfo`**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -880,18 +890,18 @@ zip 包下载 + 解压由 `ASIHTTPRequest`(旧)/ `AFNetworking`(新)+ `Z
|
|||||||
|
|
||||||
| 通知名 | 发送方 | 接收方 | 用途 |
|
| 通知名 | 发送方 | 接收方 | 用途 |
|
||||||
|--------|--------|--------|------|
|
|--------|--------|--------|------|
|
||||||
| `enterForeground` | AppDelegate `applicationDidEnterBackground` | Root/NewRootVC/fourview/game/three | 转 `appservice("2")` |
|
| `enterForeground` | AppDelegate `applicationDidEnterBackground`(真·进入后台)| Root/NewRootVC/fourview/game/three | 转 `appservice("2")`(即 H5 收到 "2" 才是真·进入后台)|
|
||||||
| `applicationWillResignActive` | AppDelegate `applicationDidBecomeActive`(节流 0.3s) | 同上 | 转 `appservice("1")` |
|
| `applicationWillResignActive` | AppDelegate `applicationDidBecomeActive`(真·回到前台,节流 0.3s) | 同上 | 转 `appservice("1")`(即 H5 收到 "1" 才是真·回到前台)|
|
||||||
| `backgameDatatwo` | threeView / 子游戏 / RootVC.backgameData: | 父大厅 | 转 `getWebdata(data)` |
|
| `backgameDatatwo` | threeView / 子游戏 / RootVC.backgameData: | 父大厅 | 转 `getWebdata(data)` |
|
||||||
| `stopradio` | 任意 | 主 VC | 暂停 backgroundPlayer |
|
| `stopradio` | 任意 | 主 VC | 暂停 backgroundPlayer |
|
||||||
| `canbackye` | 任意(防抖恢复)| 主 VC | 允许返回手势 |
|
| `canbackye` | 任意(防抖恢复)| 主 VC | 允许返回手势 |
|
||||||
| `UIDeviceBatteryLevelDidChangeNotification` | 系统 | 主 VC | 转 `getBattery(level)` |
|
| `UIDeviceBatteryLevelDidChangeNotification` | 系统 | 主 VC | 转 `getBattery(level)` |
|
||||||
|
|
||||||
**命名注意**:旧 msext 中 `enterForeground` 实际表示"进入后台",`applicationWillResignActive` 实际表示"回到前台"(命名被换反,历史包袱)。
|
**命名注意**:旧 msext 中 `enterForeground` 实际表示"进入后台",`applicationWillResignActive` 实际表示"回到前台"(命名被换反,历史包袱)。**这个命名错位顺势把桥的字符串值也带反了**——H5 收到 `appservice("2")` 才表示进入后台、收到 `appservice("1")` 才表示回到前台。
|
||||||
|
|
||||||
这些 NSNotification 是**模块内通讯**,**不是 H5 可见的契约边界** —— H5 只能看到桥事件 `appservice("1")`/`appservice("2")`。
|
这些 NSNotification 是**模块内通讯**,**不是 H5 可见的契约边界** —— H5 只能看到桥事件 `appservice("2")`(后台)/`appservice("1")`(前台)。
|
||||||
|
|
||||||
新项目自由命名(推荐用 `.appDidEnterBackground` / `.appDidBecomeActive` 等清晰命名),只要最终把 `"1"`/`"2"` 正确转发给 H5 桥即可。详见 `H5-Native-Implementation-Design.md` §4.4。
|
新项目自由命名(推荐用 `.appDidEnterBackground` / `.appDidBecomeActive` 等清晰命名),但桥发出的值**必须按上面错位关系**对应:`didEnterBackground → "2"` / `willEnterForeground → "1"`,**不要按 NSNotification 字面语义改回去**。详见 `H5-Native-Implementation-Design.md` §4.4 与 §3.2 `appservice` 值错位说明。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1049,7 +1059,7 @@ Library/Caches/
|
|||||||
- [ ] **H5 调 `confirm("是否退出")`** → 弹"取消"/"确定"两按钮 → 取消返回 false / 确定返回 true,H5 业务正常分支
|
- [ ] **H5 调 `confirm("是否退出")`** → 弹"取消"/"确定"两按钮 → 取消返回 false / 确定返回 true,H5 业务正常分支
|
||||||
|
|
||||||
### C. 系统事件
|
### C. 系统事件
|
||||||
- [ ] 切后台 → H5 收到 `appservice("1")`;回前台 → 收到 `appservice("2")`
|
- [ ] 切后台 → H5 收到 `appservice("2")`;回前台 → 收到 `appservice("1")` ⚠️ 值错位沿用历史,参 §3.2 `appservice` 值错位说明
|
||||||
- [ ] 电量变化 → H5 收到 `getBattery("0.XX")`
|
- [ ] 电量变化 → H5 收到 `getBattery("0.XX")`
|
||||||
- [ ] 飞行模式开关 → H5 收到 `getnetwork("1")` / `getnetwork("2"|"3")`
|
- [ ] 飞行模式开关 → H5 收到 `getnetwork("1")` / `getnetwork("2"|"3")`
|
||||||
- [ ] 摇一摇(startshake==YES 时)→ H5 收到 `shakeEnd`
|
- [ ] 摇一摇(startshake==YES 时)→ H5 收到 `shakeEnd`
|
||||||
|
|||||||
@@ -267,7 +267,8 @@ final class ExternalSubscriptions {
|
|||||||
bridge?.call("getnetwork", data: .string(state.rawValue), callback: nil)
|
bridge?.call("getnetwork", data: .string(state.rawValue), callback: nil)
|
||||||
}
|
}
|
||||||
lifecycle.onChange = { [weak bridge] state in
|
lifecycle.onChange = { [weak bridge] state in
|
||||||
bridge?.call("appservice", data: .string(state == .background ? "1" : "2"), callback: nil)
|
// 值错位沿用历史 msext WKWebView 路径:背景="2" / 前台="1"
|
||||||
|
bridge?.call("appservice", data: .string(state == .background ? "2" : "1"), callback: nil)
|
||||||
}
|
}
|
||||||
battery.start(); network.start(); lifecycle.start()
|
battery.start(); network.start(); lifecycle.start()
|
||||||
// 立即派发一次,补齐暂停期间的状态
|
// 立即派发一次,补齐暂停期间的状态
|
||||||
@@ -1069,18 +1070,20 @@ func sceneDidBecomeActive(_ scene: UIScene) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LobbyHandlers / SubGameHandlers 内监听并转桥
|
// LobbyHandlers / SubGameHandlers 内监听并转桥
|
||||||
|
// 注:契约值与 NSNotification 字面语义相反,沿用 msext WKWebView 路径历史。
|
||||||
|
// 真·进入后台 → "2";真·回到前台 → "1"。详见 Contract.md §3.2 `appservice` 值错位说明。
|
||||||
notifications.observe(.appDidEnterBackground) { [bridge] _ in
|
notifications.observe(.appDidEnterBackground) { [bridge] _ in
|
||||||
bridge.call("appservice", data: .string("1"), callback: nil) // 契约:"1"=后台
|
bridge.call("appservice", data: .string("2"), callback: nil) // 契约:进入后台="2"
|
||||||
}
|
}
|
||||||
notifications.observe(.appDidBecomeActive) { [bridge] _ in
|
notifications.observe(.appDidBecomeActive) { [bridge] _ in
|
||||||
bridge.call("appservice", data: .string("2"), callback: nil) // 契约:"2"=前台
|
bridge.call("appservice", data: .string("1"), callback: nil) // 契约:回到前台="1"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**0.3s 节流决策**:旧外壳在 `applicationDidBecomeActive` 里设了 `flag=NO` + 0.3s 后恢复,防止双重触发。新外壳**默认不要这个节流**,理由:
|
**0.3s 节流决策**:旧外壳在 `applicationDidBecomeActive` 里设了 `flag=NO` + 0.3s 后恢复,防止双重触发。新外壳**默认不要这个节流**,理由:
|
||||||
1. 节流的根因是旧代码在 `applicationDidBecomeActive` 内被多处触发,新外壳只有 SceneDelegate 一处发通知
|
1. 节流的根因是旧代码在 `applicationDidBecomeActive` 内被多处触发,新外壳只有 SceneDelegate 一处发通知
|
||||||
2. 测试如果证实双触发,加节流是 1 行代码的事(`DispatchQueue.asyncAfter`),不是架构改动
|
2. 测试如果证实双触发,加节流是 1 行代码的事(`DispatchQueue.asyncAfter`),不是架构改动
|
||||||
3. H5 端通常对重复 `appservice("2")` 是幂等的(就是刷新 UI 状态)
|
3. H5 端通常对重复 `appservice("1")`(回前台)是幂等的(就是刷新 UI 状态)
|
||||||
|
|
||||||
如真机回归发现 H5 双触发产生问题,在 LobbyHandlers 内加 throttle 即可,不污染 SceneDelegate。
|
如真机回归发现 H5 双触发产生问题,在 LobbyHandlers 内加 throttle 即可,不污染 SceneDelegate。
|
||||||
|
|
||||||
@@ -1253,7 +1256,8 @@ extension BridgeProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func callAppService(_ state: AppState) {
|
func callAppService(_ state: AppState) {
|
||||||
call("appservice", data: .string(state == .background ? "1" : "2"), callback: nil)
|
// 值错位沿用历史 msext WKWebView 路径:背景="2" / 前台="1"
|
||||||
|
call("appservice", data: .string(state == .background ? "2" : "1"), callback: nil)
|
||||||
}
|
}
|
||||||
// ... 15 个全列出
|
// ... 15 个全列出
|
||||||
}
|
}
|
||||||
@@ -2231,11 +2235,12 @@ private func runBootPipelineSteps() async throws {
|
|||||||
bridge.call("getnetwork", data: .string("\(code)"), callback: nil)
|
bridge.call("getnetwork", data: .string("\(code)"), callback: nil)
|
||||||
}
|
}
|
||||||
// appservice 无对应 app_* 全局变量,只走 bridge.call
|
// appservice 无对应 app_* 全局变量,只走 bridge.call
|
||||||
|
// 值错位沿用历史 msext WKWebView 路径:背景="2" / 前台="1"
|
||||||
AppLifecycleObserver.shared.onBackground = {
|
AppLifecycleObserver.shared.onBackground = {
|
||||||
bridge.call("appservice", data: .string("1"), callback: nil)
|
bridge.call("appservice", data: .string("2"), callback: nil)
|
||||||
}
|
}
|
||||||
AppLifecycleObserver.shared.onForeground = {
|
AppLifecycleObserver.shared.onForeground = {
|
||||||
bridge.call("appservice", data: .string("2"), callback: nil)
|
bridge.call("appservice", data: .string("1"), callback: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -2877,12 +2882,13 @@ public struct DeviceHandlers {
|
|||||||
networkMonitor.start()
|
networkMonitor.start()
|
||||||
|
|
||||||
// 【4】 appservice — App 前后台切换
|
// 【4】 appservice — App 前后台切换
|
||||||
// 数据:字符串 "1"=进入后台 / "2"=回到前台(命名错位,沿用历史)
|
// 数据:字符串 "2"=进入后台 / "1"=回到前台(值错位沿用 msext WKWebView 路径历史,
|
||||||
|
// 参 Contract.md §3.2 `appservice` 值错位说明,以及原 NewRootVC.m:1773/1782)
|
||||||
appLifecycle.onBackground = { [weak bridge] in
|
appLifecycle.onBackground = { [weak bridge] in
|
||||||
bridge?.call("appservice", data: .string("1"), callback: nil)
|
bridge?.call("appservice", data: .string("2"), callback: nil)
|
||||||
}
|
}
|
||||||
appLifecycle.onForeground = { [weak bridge] in
|
appLifecycle.onForeground = { [weak bridge] in
|
||||||
bridge?.call("appservice", data: .string("2"), callback: nil)
|
bridge?.call("appservice", data: .string("1"), callback: nil)
|
||||||
}
|
}
|
||||||
appLifecycle.start()
|
appLifecycle.start()
|
||||||
}
|
}
|
||||||
@@ -4263,7 +4269,7 @@ extension BridgeProtocol {
|
|||||||
func callGetPhoneInfo(_ info: [String: String]) { call("getphoneinfo", data: .object(info.mapValues(BridgeData.string)), callback: nil) }
|
func callGetPhoneInfo(_ info: [String: String]) { call("getphoneinfo", data: .object(info.mapValues(BridgeData.string)), callback: nil) }
|
||||||
func callGetBattery(_ level: Float) { call("getBattery", data: .string(String(format: "%.2f", level)), callback: nil) }
|
func callGetBattery(_ level: Float) { call("getBattery", data: .string(String(format: "%.2f", level)), callback: nil) }
|
||||||
func callGetNetwork(_ state: NetworkState) { call("getnetwork", data: .string(state.rawValue), callback: nil) } // "1"/"2"/"3"
|
func callGetNetwork(_ state: NetworkState) { call("getnetwork", data: .string(state.rawValue), callback: nil) } // "1"/"2"/"3"
|
||||||
func callAppService(_ s: AppLifecycleState) { call("appservice", data: .string(s == .background ? "1" : "2"), callback: nil) }
|
func callAppService(_ s: AppLifecycleState) { call("appservice", data: .string(s == .background ? "2" : "1"), callback: nil) } // 值错位沿用 msext WKWebView 路径
|
||||||
func callShakeEnd() { call("shakeEnd", data: nil, callback: nil) }
|
func callShakeEnd() { call("shakeEnd", data: nil, callback: nil) }
|
||||||
func callGetLocationInfo(_ d: LocationPayload) { call("getlocationinfo", data: .object(d.dict), callback: nil) } // 字段见契约 §3.2 表B
|
func callGetLocationInfo(_ d: LocationPayload) { call("getlocationinfo", data: .object(d.dict), callback: nil) } // 字段见契约 §3.2 表B
|
||||||
func callGameUIPlayVoice(_ user: String) { call("gameui_play_voice", data: .string(user), callback: nil) }
|
func callGameUIPlayVoice(_ user: String) { call("gameui_play_voice", data: .string(user), callback: nil) }
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ H5 需先用 `bridge.registerHandler('callbackName', fn)` 注册:
|
|||||||
|
|
||||||
- [ ] **F.1 模拟器 Features → Battery State 切换** → H5 收到 `getBattery` "0.XX" + `window.app_getbattery` 实时跟随
|
- [ ] **F.1 模拟器 Features → Battery State 切换** → H5 收到 `getBattery` "0.XX" + `window.app_getbattery` 实时跟随
|
||||||
- [ ] **F.2 模拟器 Features → Network Link Conditioner 切换 100% Loss** → H5 收到 `getnetwork` "1" + `window.app_getnetwork === 1`;切回 WiFi → 收 "2";切 3G → 收 "3"
|
- [ ] **F.2 模拟器 Features → Network Link Conditioner 切换 100% Loss** → H5 收到 `getnetwork` "1" + `window.app_getnetwork === 1`;切回 WiFi → 收 "2";切 3G → 收 "3"
|
||||||
- [ ] **F.3 App 切后台**(Home 键 / 上滑)→ H5 收到 `appservice` "1";回前台 → 收 "2"
|
- [ ] **F.3 App 切后台**(Home 键 / 上滑)→ H5 收到 `appservice` "2";回前台 → 收 "1" ⚠️ 值错位沿用 msext WKWebView 路径历史,参 Contract.md §3.2
|
||||||
- [ ] **F.4 摇一摇** → shakeEnd(见 E.7)
|
- [ ] **F.4 摇一摇** → shakeEnd(见 E.7)
|
||||||
- [ ] **F.5 getphoneInfo 触发 getphoneinfo**(见 E.10)
|
- [ ] **F.5 getphoneInfo 触发 getphoneinfo**(见 E.10)
|
||||||
|
|
||||||
|
|||||||
@@ -222,6 +222,7 @@
|
|||||||
"$(PROJECT_DIR)/ylgamehall/Source/Audio/AMRFileCodec",
|
"$(PROJECT_DIR)/ylgamehall/Source/Audio/AMRFileCodec",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = ylgamehall/Info.plist;
|
INFOPLIST_FILE = ylgamehall/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = "进贤聚友棋牌";
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UIMainStoryboardFile = "";
|
INFOPLIST_KEY_UIMainStoryboardFile = "";
|
||||||
@@ -271,6 +272,7 @@
|
|||||||
"$(PROJECT_DIR)/ylgamehall/Source/Audio/AMRFileCodec",
|
"$(PROJECT_DIR)/ylgamehall/Source/Audio/AMRFileCodec",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = ylgamehall/Info.plist;
|
INFOPLIST_FILE = ylgamehall/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = "进贤聚友棋牌";
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UIMainStoryboardFile = "";
|
INFOPLIST_KEY_UIMainStoryboardFile = "";
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
// 监听 UIApplication 前/后台切换通知,提供 onBackground / onForeground 钩子。
|
// 监听 UIApplication 前/后台切换通知,提供 onBackground / onForeground 钩子。
|
||||||
//
|
//
|
||||||
// 契约:docs/H5-Native-Contract.md §3.2 [4]appservice 反向 callback
|
// 契约:docs/H5-Native-Contract.md §3.2 [4]appservice 反向 callback
|
||||||
// "1" = 进入后台 / "2" = 回到前台(命名错位沿用历史)
|
// "2" = 进入后台 / "1" = 回到前台(值错位沿用历史 msext WKWebView 路径,
|
||||||
|
// 参 daoqi/msext NewRootVC.m:1773/1782、gameController.m:1323/1332)
|
||||||
// Phase 2.11
|
// Phase 2.11
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ public final class BridgedWebView: UIView {
|
|||||||
|
|
||||||
// ── 创建 WKWebView + BridgeBus ─────────────────────────
|
// ── 创建 WKWebView + BridgeBus ─────────────────────────
|
||||||
let webView = WKWebView(frame: .zero, configuration: configuration)
|
let webView = WKWebView(frame: .zero, configuration: configuration)
|
||||||
|
#if DEBUG
|
||||||
|
if #available(iOS 16.4, *) {
|
||||||
|
webView.isInspectable = true
|
||||||
|
}
|
||||||
|
#endif
|
||||||
let bridge = BridgeBus(webView: webView,
|
let bridge = BridgeBus(webView: webView,
|
||||||
controller: configuration.userContentController)
|
controller: configuration.userContentController)
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,11 @@ public final class OverlayViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
webView = WKWebView(frame: .zero, configuration: config)
|
webView = WKWebView(frame: .zero, configuration: config)
|
||||||
|
#if DEBUG
|
||||||
|
if #available(iOS 16.4, *) {
|
||||||
|
webView.isInspectable = true
|
||||||
|
}
|
||||||
|
#endif
|
||||||
webView.scrollView.bounces = false
|
webView.scrollView.bounces = false
|
||||||
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
webView.translatesAutoresizingMaskIntoConstraints = false
|
webView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ final class SplashOverlay: UIView {
|
|||||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
addSubview(imageView)
|
addSubview(imageView)
|
||||||
|
|
||||||
label.textColor = .white
|
// SplashImage 底部是白色留白,白字会完全融进去 → 用黑色。
|
||||||
|
label.textColor = .black
|
||||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
label.numberOfLines = 1
|
label.numberOfLines = 1
|
||||||
|
|||||||
@@ -336,11 +336,13 @@ public final class SubGameViewController: UIViewController {
|
|||||||
bridge.call("getnetwork", data: .string("\(code)"), callback: nil)
|
bridge.call("getnetwork", data: .string("\(code)"), callback: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// §3.2 [4] appservice — 值错位沿用历史 msext WKWebView 路径:
|
||||||
|
// 真·进入后台 → "2",真·回到前台 → "1"。参 WebContainerViewController 同位置注释。
|
||||||
AppLifecycleObserver.shared.onBackground = {
|
AppLifecycleObserver.shared.onBackground = {
|
||||||
bridge.call("appservice", data: .string("1"), callback: nil)
|
bridge.call("appservice", data: .string("2"), callback: nil)
|
||||||
}
|
}
|
||||||
AppLifecycleObserver.shared.onForeground = {
|
AppLifecycleObserver.shared.onForeground = {
|
||||||
bridge.call("appservice", data: .string("2"), callback: nil)
|
bridge.call("appservice", data: .string("1"), callback: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 电话状态 — 契约 §3.1 [14]:来电 "2" / 挂断 "0"
|
// 电话状态 — 契约 §3.1 [14]:来电 "2" / 挂断 "0"
|
||||||
|
|||||||
@@ -359,6 +359,12 @@ public final class WebContainerViewController: UIViewController {
|
|||||||
throw BootError.ipaUpgradeRequired(downloadURL: dl)
|
throw BootError.ipaUpgradeRequired(downloadURL: dl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 过渡文案:config 已拉到、进入 zip 升级前的"建连等首字节"静默阶段。
|
||||||
|
// 不补这一步,用户会看到"拉取配置中..."一直显示到 zip CDN 冷 DNS+TLS 握完
|
||||||
|
// 才跳到"下载更新中 1%",慢网络下感知像卡死。这里把 UI 状态机补齐到与
|
||||||
|
// 实际工作阶段对应。
|
||||||
|
splash.update(text: "校验版本中...", progress: nil)
|
||||||
|
|
||||||
// H5 zip 升级失败仍致命(用户体验 ≠ 静默用旧 zip)—— 抛出由外层 splash.showError
|
// H5 zip 升级失败仍致命(用户体验 ≠ 静默用旧 zip)—— 抛出由外层 splash.showError
|
||||||
// LobbyZipUpgrader.onProgress 由 URLSession delegate 后台队列调度,
|
// LobbyZipUpgrader.onProgress 由 URLSession delegate 后台队列调度,
|
||||||
// 必须显式 hop 到 MainActor 才能动 splash UI。
|
// 必须显式 hop 到 MainActor 才能动 splash UI。
|
||||||
@@ -450,12 +456,15 @@ public final class WebContainerViewController: UIViewController {
|
|||||||
bridge.call("getnetwork", data: .string("\(code)"), callback: nil)
|
bridge.call("getnetwork", data: .string("\(code)"), callback: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// §3.2 [4] appservice — App 前后台切换(命名错位沿用历史)
|
// §3.2 [4] appservice — App 前后台切换(值错位沿用历史 msext WKWebView 路径)
|
||||||
|
// 真·进入后台 → "2",真·回到前台 → "1"。参 daoqi/msext NewRootVC.m:1773/1782、
|
||||||
|
// gameController.m:1323/1332(AppDelegate post 的 NSNotification 名也是错位的,
|
||||||
|
// 净结果是 H5 收到的桥值与"自然语义"相反,必须照抄)。
|
||||||
AppLifecycleObserver.shared.onBackground = {
|
AppLifecycleObserver.shared.onBackground = {
|
||||||
bridge.call("appservice", data: .string("1"), callback: nil)
|
bridge.call("appservice", data: .string("2"), callback: nil)
|
||||||
}
|
}
|
||||||
AppLifecycleObserver.shared.onForeground = {
|
AppLifecycleObserver.shared.onForeground = {
|
||||||
bridge.call("appservice", data: .string("2"), callback: nil)
|
bridge.call("appservice", data: .string("1"), callback: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// §3.2 [12] getWebdata — 反向把 backgameData / 弹层 data 入参透传给大厅 H5。
|
// §3.2 [12] getWebdata — 反向把 backgameData / 弹层 data 入参透传给大厅 H5。
|
||||||
|
|||||||
Reference in New Issue
Block a user