import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; /// Flutter → H5 桥接层。 /// /// 把 [window.avatar] 暴露的 JS API 包装成 Dart 方法。 /// 内部只通过 [evaluateJavascript] 调用,未来切到 Cubism Native 时 /// 实现这一层的另一个版本即可,UI 完全无需改动。 class AvatarBridge { final InAppWebViewController _ctrl; AvatarBridge(this._ctrl); /// 等待 H5 端的 window.avatar 注册完成且模型加载到 expression/motion 可用 Future waitReady({Duration timeout = const Duration(seconds: 15)}) async { final deadline = DateTime.now().add(timeout); while (DateTime.now().isBefore(deadline)) { final result = await _ctrl.evaluateJavascript(source: ''' (function() { if (!window.avatar) return false; const exps = window.avatar.listExpressions(); const mots = window.avatar.listMotions(); return exps.length > 0 || Object.keys(mots).length > 0; })() '''); if (result == true) return; await Future.delayed(const Duration(milliseconds: 200)); } if (kDebugMode) { debugPrint('[AvatarBridge] waitReady timed out'); } } Future setState(String state) => _eval('avatar.setState("$state")'); Future setExpression(String name) => _eval('avatar.setExpression("$name")'); Future playMotion(String group, int index) => _eval('avatar.playMotion("$group", $index)'); Future setMouthOpen(double value) => _eval('avatar.setMouthOpen($value)'); Future playAction(String name) => _eval('avatar.playAction("$name")'); Future startDance() => _eval('avatar.startDance()'); Future stopDance() => _eval('avatar.stopDance()'); Future playMockConversation() => _eval('avatar.playMockConversation()'); /// 拉一次资产清单(表情名、动作分组、语义动作列表) Future fetchManifest() async { final raw = await _ctrl.evaluateJavascript(source: ''' JSON.stringify({ expressions: avatar.listExpressions(), motions: avatar.listMotions(), actions: avatar.listActions(), }) '''); if (raw is! String) { return const AvatarManifest(expressions: [], motions: {}, actions: []); } final json = jsonDecode(raw) as Map; final expressions = (json['expressions'] as List).map((e) => e as String).toList(); final motions = (json['motions'] as Map).map( (k, v) => MapEntry(k, v as int), ); final actions = (json['actions'] as List).map((e) => e as String).toList(); return AvatarManifest( expressions: expressions, motions: motions, actions: actions, ); } Future _eval(String source) => _ctrl.evaluateJavascript(source: source); } /// 当前角色资产清单(表情名 + motion 分组 + 语义动作) class AvatarManifest { final List expressions; final Map motions; final List actions; const AvatarManifest({ required this.expressions, required this.motions, required this.actions, }); }