Design §8.7:新增 ShakeKit + 4 项接口实现骨架
覆盖 §3.1 [7]startshake / [8]stopshake / [9]SwitchShake +
§3.2 [5]shakeEnd 反向 callback 共 4 项接口。
§8.7.1 ShakeKit module 骨架:
- canShake / canVoice 开关位(@MainActor 实例属性,取代 msext 全局变量)
- handleMotionEnded(.motionShake) 集中处理:判 canShake → 播音效 → 触发 callback
- shake_sound_male.mp3 复用 AudioKit.AudioPlayer.playOnce 统一路径
§8.7.1 接入 VC:
- WebContainerViewController.canBecomeFirstResponder = true
- viewDidAppear 调 becomeFirstResponder(必要,否则 motionEnded 不触发)
- motionEnded(_:with:) 转发到 shakeKit.handleMotionEnded
§8.7.2 H5 handler 实现:
- 【7】startshake : canshake=YES + cb "startshake from accreditlogin"
(字面字符串带 "from accreditlogin" 后缀沿用历史)
- 【8】stopshake : canshake=NO + cb 同上
- 【9】SwitchShake : data int → canvoice = (1==YES) + cb "SwitchShake"
- 反向 [5]shakeEnd: data=nil,由 motionEnded → onShakeEnded 触发
§8.7.3 与 msext 6 维度差异表(全局变量 → 实例属性 / 音效统一 /
motionEnded 集中处理 / canBecomeFirstResponder 基类统一 / responseCallback 字面保留)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
d48ecc0473
commit
a06a18c3aa
@@ -2839,6 +2839,141 @@ extension LocationInfo {
|
|||||||
| latitude 字段类型 | `stringWithFormat:@"%f"` 字符串 | `String(format: "%f", ...)`(等价) |
|
| latitude 字段类型 | `stringWithFormat:@"%f"` 字符串 | `String(format: "%f", ...)`(等价) |
|
||||||
| province 大小写 | 小写 p(与 sharelogin 大写 P 不一致)| 严格保持 |
|
| province 大小写 | 小写 p(与 sharelogin 大写 P 不一致)| 严格保持 |
|
||||||
|
|
||||||
|
### 8.7 ShakeKit
|
||||||
|
|
||||||
|
覆盖契约 §C §3.1 [7][8] [9] 摇一摇 3 项 + §3.2 [5]`shakeEnd` 反向 callback,共 4 项接口。
|
||||||
|
|
||||||
|
设备摇动的真正事件源是 `UIResponder.motionEnded(_:with:)`(系统检测晃动后回调),ShakeKit 把它包装为:开关位(canshake / canvoice)+ 摇一摇音效播放 + 触发 shakeEnd callback。
|
||||||
|
|
||||||
|
#### 8.7.1 Module 骨架
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Source/ShakeKit/ShakeKit.swift
|
||||||
|
@MainActor
|
||||||
|
public final class ShakeKit {
|
||||||
|
|
||||||
|
/// 摇一摇总开关(startshake / stopshake 控制)
|
||||||
|
public private(set) var canShake: Bool = false
|
||||||
|
/// 摇一摇音效开关(SwitchShake 控制)
|
||||||
|
public private(set) var canVoice: Bool = true
|
||||||
|
|
||||||
|
public var onShakeEnded: (() -> Void)?
|
||||||
|
|
||||||
|
private let audioPlayer: AudioPlayer
|
||||||
|
private let shakeSoundURL: URL // 拷贝自 docs/res/Res/shake_sound_male.mp3 → Bundle
|
||||||
|
|
||||||
|
public init(audioPlayer: AudioPlayer, shakeSoundURL: URL) {
|
||||||
|
self.audioPlayer = audioPlayer
|
||||||
|
self.shakeSoundURL = shakeSoundURL
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setShakeEnabled(_ on: Bool) { canShake = on }
|
||||||
|
public func setVoiceEnabled(_ on: Bool) { canVoice = on }
|
||||||
|
|
||||||
|
/// 由 WebContainerViewController.motionEnded(_:with:) 转发调用
|
||||||
|
public func handleMotionEnded(_ motion: UIEvent.EventSubtype) {
|
||||||
|
guard motion == .motionShake, canShake else { return }
|
||||||
|
if canVoice { audioPlayer.playOnce(shakeSoundURL) }
|
||||||
|
onShakeEnded?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**接入 VC 层**(在 §4.2.1「启动期容易遗漏的契约点」已声明 `UIApplication.shared.applicationSupportsShakeToEdit = true`,本节补 motionEnded 转发):
|
||||||
|
|
||||||
|
```swift
|
||||||
|
extension WebContainerViewController {
|
||||||
|
public override var canBecomeFirstResponder: Bool { true }
|
||||||
|
|
||||||
|
public override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
// 必须主动 becomeFirstResponder,否则 motionEnded: 不会触发
|
||||||
|
becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
||||||
|
shakeKit.handleMotionEnded(motion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 8.7.2 H5 shake handlers + 反向 callback 骨架
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Source/Bridge/Handlers/ShakeHandlers.swift
|
||||||
|
@MainActor
|
||||||
|
public struct ShakeHandlers {
|
||||||
|
|
||||||
|
let bridge: BridgeProtocol
|
||||||
|
let shakeKit: ShakeKit
|
||||||
|
|
||||||
|
public func register() {
|
||||||
|
bridge.register("startshake", handler: startShake)
|
||||||
|
bridge.register("stopshake", handler: stopShake)
|
||||||
|
bridge.register("SwitchShake", handler: switchShake)
|
||||||
|
bindReverseCallback()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 【7】 startshake
|
||||||
|
//
|
||||||
|
// 契约 §3.1 [7]:
|
||||||
|
// 入参 : 忽略 → canshake = YES
|
||||||
|
// responseCallback: "startshake from accreditlogin"
|
||||||
|
// (字面字符串带 from accreditlogin 后缀,沿用历史;
|
||||||
|
// H5 不依赖此值,但严格拷贝保留契约等价)
|
||||||
|
|
||||||
|
private func startShake(_ data: BridgeData?, _ cb: BridgeCallback?) async {
|
||||||
|
shakeKit.setShakeEnabled(true)
|
||||||
|
cb?(.string("startshake from accreditlogin"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 【8】 stopshake
|
||||||
|
//
|
||||||
|
// 契约 §3.1 [8]:
|
||||||
|
// 入参 : 忽略 → canshake = NO
|
||||||
|
// responseCallback: 同 startshake("startshake from accreditlogin")
|
||||||
|
|
||||||
|
private func stopShake(_ data: BridgeData?, _ cb: BridgeCallback?) async {
|
||||||
|
shakeKit.setShakeEnabled(false)
|
||||||
|
cb?(.string("startshake from accreditlogin"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 【9】 SwitchShake — 摇一摇音效开关
|
||||||
|
//
|
||||||
|
// 契约 §3.1 [9]:
|
||||||
|
// 入参 : int 1 → canvoice = YES,其他 → canvoice = NO
|
||||||
|
// responseCallback: "SwitchShake"
|
||||||
|
|
||||||
|
private func switchShake(_ data: BridgeData?, _ cb: BridgeCallback?) async {
|
||||||
|
defer { cb?(.string("SwitchShake")) }
|
||||||
|
let on = (data?.asInt ?? 0) == 1
|
||||||
|
shakeKit.setVoiceEnabled(on)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - §3.2 [5] shakeEnd 反向 callback
|
||||||
|
//
|
||||||
|
// 触发时机:UIResponder.motionEnded(.motionShake, with:) 且 canshake == YES
|
||||||
|
// 数据:nil(契约规定 data 为 nil,不传额外字段)
|
||||||
|
|
||||||
|
private func bindReverseCallback() {
|
||||||
|
shakeKit.onShakeEnded = { [weak bridge] in
|
||||||
|
bridge?.call("shakeEnd", data: nil, callback: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 8.7.3 与原 msext 的差异
|
||||||
|
|
||||||
|
| 维度 | msext 现状(不要照抄) | 新外壳决策 |
|
||||||
|
|------|--------------------|----------|
|
||||||
|
| 开关状态 | `static BOOL canshake` / `static BOOL canvoice` 全局变量 | `@MainActor ShakeKit` 实例属性,VC 注入 |
|
||||||
|
| 摇一摇音效 | `[FuncPublic playWithFileName:@"shake_sound_male.mp3"]` 散落 | 复用 AudioKit 的 `AudioPlayer.playOnce`,统一路径 |
|
||||||
|
| 摇动事件 | 大厅 / 子游戏 VC 各自实现 `motionEnded:` 各自判断 | ShakeKit 集中判断 + 单点 callback 触发 |
|
||||||
|
| canBecomeFirstResponder | 大厅 VC override 为 YES,子游戏沿用默认 NO(潜在漏点) | 统一在 WebContainerViewController 基类内 override = true |
|
||||||
|
| responseCallback 字面 | `"startshake from accreditlogin"` 字面奇怪但沿用 | 严格保留(H5 不读,仅保契约不破) |
|
||||||
|
| shakeEnd 数据 | `nil` | 严格保留(Bridge.call data: nil) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. 并发模型
|
## 9. 并发模型
|
||||||
|
|||||||
Reference in New Issue
Block a user