460 lines
16 KiB
Dart
460 lines
16 KiB
Dart
import 'dart:convert';
|
||
// import 'dart:html';
|
||
import 'dart:html' as html;
|
||
|
||
import 'package:js/js.dart';
|
||
import 'package:web_tools/utils/model/model.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
|
||
// h5交互通知 原生 {type:'enum',data:'所需参数,可无'}
|
||
enum ToFlutterAppEnum {
|
||
close('close'),
|
||
gameExit('gameExit'),
|
||
createGame('createGame'),
|
||
wantToPlay('wantToPlay'),
|
||
gameOver('gameOver'),
|
||
uploadImage('upload_image'),
|
||
soundRecord('sound_recording'),
|
||
toRecharge('toRecharge'),
|
||
playing('playing'),
|
||
toRedDiamond('toRedDiamond'),
|
||
gameType('gameType'),
|
||
jumpToH5('jumpToH5'),
|
||
toHomepage('toHomepage'),
|
||
toMonthCardPay('toMonthCardPay'),
|
||
|
||
checkGameState('checkGameState'),
|
||
closeObserving('closeObserving'),
|
||
translateRequest('translateRequest'),
|
||
share('share'),
|
||
//开播检测
|
||
checkStartBroadcaster('checkStartBroadcaster'),
|
||
//直播间发言
|
||
taskLiveRoomChat('taskLiveRoomChat'),
|
||
//直播间送礼
|
||
taskLiveRoomGift('taskLiveRoomGift'),
|
||
//直播间其他任务 close
|
||
taskLiveRoomOther('taskLiveRoomOther'),
|
||
//跳转从业者申请
|
||
toApplyAdmissionPage('toApplyAdmissionPage'),
|
||
//直播预约设置
|
||
shouLiveBookingPicker('shouLiveBookingPicker'),
|
||
//完善个人信息
|
||
shouldCompleteProfile('shouldCompleteProfile'),
|
||
//在直播间或聊天室停留观看n分钟
|
||
shouldWatchDuration('shouldWatchDuration'),
|
||
//在直播间或聊天室发送n条公屏消息
|
||
shouldSendPublicMessage('shouldSendPublicMessage'),
|
||
//在直播间或聊天室上麦互动n分钟
|
||
shouldMicInteraction('shouldMicInteraction'),
|
||
//向任意用户,发送n条信息
|
||
shouldSendPrivateMessage('shouldSendPrivateMessage'),
|
||
//发布n条动态
|
||
shouldPostFeed('shouldPostFeed'),
|
||
//分享n次直播间或聊天室至任意平台
|
||
shouldShareRoom('shouldShareRoom'),
|
||
//佩戴任意装扮
|
||
shouldWearDecoration('shouldWearDecoration'),
|
||
//前往语音房
|
||
shouldGoToVoiceRoom('shouldGoToVoiceRoom'),
|
||
|
||
// 恩爱节活动跳转选择好友
|
||
lovingDayChooseFriend('lovingDayChooseFriend'),
|
||
// 恩爱节我的邀请页面
|
||
lovingDayMyInvite('lovingDayMyInvite'),
|
||
|
||
// 定向充值
|
||
rechargeItem('rechargeItem'),
|
||
//专属见面礼
|
||
shouldInviteCodeGift('InviteCodeGiftPackageDialogPage'),
|
||
//解析 URL 并跳转
|
||
showCommandJump('commandJump'),
|
||
//跳转至网页
|
||
showToWebViewPage('toWebViewPage'),
|
||
|
||
///通用交互 别往这下面加,👆🏻加
|
||
commonInteraction('commonInteraction'),
|
||
defaultCode('');
|
||
|
||
const ToFlutterAppEnum(this.code);
|
||
final String code;
|
||
|
||
static ToFlutterAppEnum? fromCode(String code) {
|
||
return ToFlutterAppEnum.values.firstWhere(
|
||
(e) => e.code == code,
|
||
orElse: () => ToFlutterAppEnum.defaultCode,
|
||
);
|
||
}
|
||
}
|
||
|
||
//通用交互
|
||
enum WebInteractionType {
|
||
taskKeyWatchLive('TaskKeyWatchLive'), // 观看直播
|
||
taskKeyCollectRoom('TaskKeyCollectRoom'), // 收藏房间
|
||
taskKeyFollowUser('TaskKeyFollowUser'), // 关注主播
|
||
taskKeySendRoomMessage('TaskKeySendRoomMessage'), // 发送房间消息
|
||
|
||
taskKeySendGift('TaskKeySendGift'), // 赠送礼物
|
||
taskKeySendGiftId('TaskKeySendGiftId'), // 赠送指定礼物
|
||
|
||
taskKeySendBackpackGift('TaskKeySendBackpackGift'), // 赠送礼物到背包
|
||
taskKeyPlayGame('TaskKeyPlayGame'), // 玩游戏
|
||
|
||
taskKeySendPrivateMessage('TaskKeySendPrivateMessage'), // 发送私聊消息
|
||
taskKeyShareActivity('TaskKeyShareActivity'), // 分享活动
|
||
|
||
taskKeyTimelineTopic('TaskKeyTimelineTopic'), // 参与动态话题
|
||
taskKeyLikeTimeline('TaskKeyLikeTimeline'), // 点赞动态
|
||
taskKeyReplayTimeline('TaskKeyReplayTimeline'), // 评论动态
|
||
|
||
taskKeySignIn('TaskKeySignIn'), // 签到
|
||
taskKeyOtherPage('TaskKeyOtherPage'), // 其他页面
|
||
taskKeyMineBackpack('TaskKeyMineBackpack'), // 我的装扮背包页面(或者是称号)
|
||
taskKeyMineWallet('TaskKeyMineWallet'), // 我的钱包页面
|
||
unknown('unknown');
|
||
|
||
const WebInteractionType(this.code);
|
||
final String code; //服务端 key
|
||
|
||
factory WebInteractionType.fromCode(String? code) => values.firstWhere(
|
||
(element) => element.code == code,
|
||
orElse: () => WebInteractionType.unknown,
|
||
);
|
||
}
|
||
|
||
// 原生交互通知 h5 {type:'enum',data:'所需参数 '}
|
||
enum FromFlutterAppEnum {
|
||
translateResult('translateResult'), //翻译
|
||
redDiamondRecharge('diamond_recharge'), //钻石充值
|
||
shareFinished('share_finished'), //分享完成
|
||
cpSendRingCallBack('cp_send_ring_callback'), //送戒指后回调
|
||
defaultCode('');
|
||
|
||
const FromFlutterAppEnum(this.code);
|
||
final String code;
|
||
}
|
||
|
||
@JS()
|
||
external void sendMessageToNative(String data);
|
||
|
||
/// 🆕 [新增] 通用请求任务模型
|
||
class _RequestTask {
|
||
final String sendType; // 发送给 Native 的类型
|
||
final Map<String, dynamic> params; // 发送的参数
|
||
final Function(Map<String, dynamic>) callback; // 成功回调
|
||
|
||
_RequestTask(this.sendType, this.params, this.callback);
|
||
}
|
||
|
||
class FlutterBridge {
|
||
static final FlutterBridge instance = FlutterBridge._internal();
|
||
|
||
// final _messageListeners = <String, Function(Map<String, dynamic>)>{};
|
||
// 1. 修改这里:Value 从 Function 变成 List<Function>
|
||
final _messageListeners = <String, List<Function(Map<String, dynamic>)>>{};
|
||
var textMap = <String, String>{}; //多语言翻译
|
||
|
||
/// 🆕 [新增] 通用请求队列池 (Key: listenType, Value: 任务列表)
|
||
final Map<String, List<_RequestTask>> _requestQueues = {};
|
||
|
||
/// 🆕 [新增] 忙碌状态池 (记录哪些 listenType 当前正在等待 Native 回复)
|
||
final Set<String> _activeResponseTypes = {};
|
||
|
||
FlutterBridge._internal() {
|
||
_initListener();
|
||
}
|
||
|
||
// 只初始化一次,监听 WebView 消息
|
||
void _initListener() {
|
||
html.window.onMessage.listen((event) {
|
||
if (event.data is String) {
|
||
try {
|
||
final Map<String, dynamic> data = jsonDecode(event.data);
|
||
final String? type = data['type'];
|
||
// 找到监听列表,遍历调用
|
||
if (type != null && _messageListeners.containsKey(type)) {
|
||
final listeners = _messageListeners[type];
|
||
if (listeners != null && listeners.isNotEmpty) {
|
||
// 使用 List.from 浅拷贝一份进行遍历,防止在回调中移除监听导致并发修改异常
|
||
for (final callback in List.from(listeners)) {
|
||
try {
|
||
callback(data); // 这里的 data 是已经解析好的 Map
|
||
} catch (e) {
|
||
print("Error in listener callback: $e");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
print('Invalid message format from Flutter: $type');
|
||
} catch (e) {
|
||
print('Invalid message format from Flutter: ${event.data}');
|
||
}
|
||
} else {
|
||
try {
|
||
final String? type = event.data['type'];
|
||
// 找到监听列表,遍历调用
|
||
if (type != null && _messageListeners.containsKey(type)) {
|
||
final listeners = _messageListeners[type];
|
||
if (listeners != null && listeners.isNotEmpty) {
|
||
// 使用 List.from 浅拷贝一份进行遍历,防止在回调中移除监听导致并发修改异常
|
||
for (final callback in List.from(listeners)) {
|
||
try {
|
||
callback(event.data); // 这里的 data 是已经解析好的 Map
|
||
} catch (e) {
|
||
print("Error in listener callback: $e");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
print('Invalid message format from Flutter: $type');
|
||
} catch (e) {
|
||
print('Invalid message format from Flutter: ${event.data}');
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/// 3. 核心修改:on 方法
|
||
/// 返回一个 VoidCallback,调用它即可取消本次监听
|
||
VoidCallback on(String type, Function(Map<String, dynamic>) callback) {
|
||
if (!_messageListeners.containsKey(type)) {
|
||
_messageListeners[type] = [];
|
||
}
|
||
|
||
_messageListeners[type]!.add(callback);
|
||
print(
|
||
"Listener added for '$type'. Total listeners: ${_messageListeners[type]!.length}",
|
||
);
|
||
|
||
// 返回一个闭包,用于精确移除当前的 callback
|
||
return () {
|
||
if (_messageListeners.containsKey(type)) {
|
||
_messageListeners[type]?.remove(callback);
|
||
print(
|
||
"One listener removed for '$type'. Remaining: ${_messageListeners[type]?.length}",
|
||
);
|
||
|
||
// 如果该类型没有监听者了,可以选择清理 key
|
||
if (_messageListeners[type]!.isEmpty) {
|
||
_messageListeners.remove(type);
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
/// 警告:这会移除该类型下的【所有】监听器 (慎用,通常用于全局重置,一旦调用,该 type 别的地方监听器将失效)
|
||
void off(String type) {
|
||
if (_messageListeners.containsKey(type)) {
|
||
_messageListeners.remove(type);
|
||
print("All listeners for '$type' have been removed.");
|
||
}
|
||
}
|
||
// =========================================================
|
||
// 👇 🆕 [新增] 核心通用请求方法 (替代了旧的 requestTranslate)
|
||
// =========================================================
|
||
|
||
/// 发送请求并等待回调 (自动排队,防并发)
|
||
/// [sendType] 发送给 Native 的类型
|
||
/// [listenType] 等待 Native 回复的类型
|
||
/// [params] 参数
|
||
/// [onSuccess] 成功回调
|
||
void sendRequest({
|
||
required ToFlutterAppEnum sendType,
|
||
required FromFlutterAppEnum listenType,
|
||
required Map<String, dynamic> params,
|
||
required Function(Map<String, dynamic>) onSuccess,
|
||
}) {
|
||
final responseKey = listenType.code;
|
||
|
||
// 1. 初始化该类型的队列
|
||
if (!_requestQueues.containsKey(responseKey)) {
|
||
_requestQueues[responseKey] = [];
|
||
}
|
||
|
||
// 2. 入队
|
||
_requestQueues[responseKey]!.add(_RequestTask(
|
||
sendType.code,
|
||
params,
|
||
onSuccess,
|
||
));
|
||
|
||
// 3. 调度执行
|
||
_processRequestQueue(responseKey);
|
||
}
|
||
|
||
/// 🆕 [新增] 内部队列调度方法
|
||
void _processRequestQueue(String responseKey) {
|
||
// 如果该类型正在忙,或者队列空了,直接返回
|
||
if (_activeResponseTypes.contains(responseKey) ||
|
||
(_requestQueues[responseKey]?.isEmpty ?? true)) {
|
||
return;
|
||
}
|
||
|
||
// 1. 标记忙碌
|
||
_activeResponseTypes.add(responseKey);
|
||
|
||
// 2. 取出队首任务
|
||
final task = _requestQueues[responseKey]!.removeAt(0);
|
||
|
||
// 3. 注册临时监听 (使用 on 的多播特性)
|
||
VoidCallback? cancelRef;
|
||
|
||
cancelRef = on(responseKey, (data) {
|
||
try {
|
||
final innerData = data['data'] ?? {};
|
||
// 执行业务回调
|
||
task.callback(innerData);
|
||
} catch (e) {
|
||
print("Queue processing error: $e");
|
||
} finally {
|
||
// --- 任务闭环 ---
|
||
// A. 移除当前临时监听 (不影响全局监听)
|
||
cancelRef?.call();
|
||
|
||
// B. 解除忙碌状态
|
||
_activeResponseTypes.remove(responseKey);
|
||
|
||
// C. 递归处理下一个任务
|
||
_processRequestQueue(responseKey);
|
||
}
|
||
});
|
||
|
||
// 4. 发送 Native 消息
|
||
sendToFlutter(task.sendType, task.params);
|
||
}
|
||
|
||
// 发送消息给 App(通过 WebView 调用 JS 方法)
|
||
void sendToFlutter(String type, Map<String, dynamic> data) {
|
||
final dataStr = jsonEncode({'type': type, 'data': data});
|
||
print('$dataStr');
|
||
sendMessageToNative(dataStr);
|
||
}
|
||
|
||
void sendToFlutterTest() {
|
||
// final message = jsonEncode({
|
||
// 'type': type,
|
||
// 'data': data,
|
||
// });
|
||
// _runJs("receiveMessageFromFlutter($message);");
|
||
final dataStr = jsonEncode({'type': close, 'data': {}});
|
||
sendMessageToNative(dataStr);
|
||
}
|
||
// 执行 JS 代码
|
||
// void _runJs(String js) {
|
||
// final script = ScriptElement()
|
||
// ..innerHtml = js
|
||
// ..type = 'application/javascript';
|
||
// document.body?.append(script);
|
||
// script.remove();
|
||
// }
|
||
|
||
// 具体封装的常用方法,直接发送消息给 Flutter Web
|
||
void close() => sendToFlutter(ToFlutterAppEnum.close.code, {});
|
||
void gameOver() => sendToFlutter(ToFlutterAppEnum.gameOver.code, {});
|
||
void createGame(String gameId) =>
|
||
sendToFlutter(ToFlutterAppEnum.createGame.code, {'gameId': gameId});
|
||
void wantToPlay(String gameId) =>
|
||
sendToFlutter(ToFlutterAppEnum.wantToPlay.code, {'gameId': gameId});
|
||
// void translateRequest(Map<String, String> data) =>
|
||
// sendToFlutter(ToFlutterAppEnum.translateRequest.code, data);//移除,建议使用sendRequest方法
|
||
void toRecharge() => sendToFlutter(ToFlutterAppEnum.toRecharge.code, {});
|
||
void toRedDiamond() => sendToFlutter(ToFlutterAppEnum.toRedDiamond.code, {});
|
||
void jumpToH5(String path, String title) => sendToFlutter(
|
||
ToFlutterAppEnum.jumpToH5.code,
|
||
{'path': path, 'title': title},
|
||
);
|
||
void toHomepage(String userId) =>
|
||
sendToFlutter(ToFlutterAppEnum.toHomepage.code, {'userId': userId});
|
||
void toMonthCardPay(
|
||
String googleProductId,
|
||
String iosProductId, {
|
||
otherUserId = '',
|
||
}) =>
|
||
sendToFlutter(ToFlutterAppEnum.toMonthCardPay.code, {
|
||
'googleProductId': googleProductId,
|
||
'iosProductId': iosProductId,
|
||
'otherUserId': otherUserId,
|
||
});
|
||
void checkGameState(String gameCode) => sendToFlutter(
|
||
ToFlutterAppEnum.checkGameState.code,
|
||
{'gameCode': gameCode},
|
||
);
|
||
|
||
void share({
|
||
required String activityId, // 活动id
|
||
required bool needShareReport, // 是否需要上报分享数据
|
||
ShareCardModel? shareCardModel, // 分享上方卡片 数据,参考客户端参数
|
||
}) =>
|
||
sendToFlutter(ToFlutterAppEnum.share.code, {
|
||
"activityId": activityId,
|
||
"shareReportKey": needShareReport ? "ActivityShared" : "",
|
||
...shareCardModel?.toJson() ?? {},
|
||
});
|
||
|
||
//专属见面礼
|
||
void taskInviteCodeGift() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouldInviteCodeGift.code, {});
|
||
|
||
//解析 URL 并跳转
|
||
void taskCommandJump({required String scheme, required String activityId}) =>
|
||
sendToFlutter(ToFlutterAppEnum.showCommandJump.code, {
|
||
"scheme": scheme,
|
||
"activityId": activityId,
|
||
});
|
||
|
||
//跳转至网页
|
||
void taskToWebViewPage({
|
||
required String linkUrl,
|
||
required String activityId,
|
||
}) =>
|
||
sendToFlutter(ToFlutterAppEnum.showToWebViewPage.code, {
|
||
"linkUrl": linkUrl,
|
||
"activityId": activityId,
|
||
});
|
||
|
||
void checkStartBroadcaster() =>
|
||
sendToFlutter(ToFlutterAppEnum.checkStartBroadcaster.code, {});
|
||
|
||
void taskLiveRoomChat() =>
|
||
sendToFlutter(ToFlutterAppEnum.taskLiveRoomChat.code, {});
|
||
void taskLiveRoomGift() =>
|
||
sendToFlutter(ToFlutterAppEnum.taskLiveRoomGift.code, {});
|
||
void taskLiveRoomOther() =>
|
||
sendToFlutter(ToFlutterAppEnum.taskLiveRoomOther.code, {});
|
||
void toApplyAdmissionPage() =>
|
||
sendToFlutter(ToFlutterAppEnum.toApplyAdmissionPage.code, {});
|
||
void shouLiveBookingPicker() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouLiveBookingPicker.code, {});
|
||
/** 完善个人信息 */
|
||
void shouldCompleteProfile() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouldCompleteProfile.code, {});
|
||
/** 在直播间或聊天室停留观看n分钟 */
|
||
void shouldWatchDuration() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouldWatchDuration.code, {});
|
||
/** 在直播间或聊天室发送n条公屏消息 */
|
||
void shouldSendPublicMessage() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouldSendPublicMessage.code, {});
|
||
/** 在直播间或聊天室上麦互动n分钟 */
|
||
void shouldMicInteraction() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouldMicInteraction.code, {});
|
||
/** 向任意用户,发送n条信息*/
|
||
void shouldSendPrivateMessage() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouldSendPrivateMessage.code, {});
|
||
/** 发布n条动态*/
|
||
void shouldPostFeed() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouldPostFeed.code, {});
|
||
/** 分享n次直播间或聊天室至任意平台 */
|
||
void shouldShareRoom() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouldShareRoom.code, {});
|
||
/** 佩戴任意装扮 */
|
||
void shouldWearDecoration() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouldWearDecoration.code, {});
|
||
/** 前往语音房 */
|
||
void shouldGoToVoiceRoom() =>
|
||
sendToFlutter(ToFlutterAppEnum.shouldGoToVoiceRoom.code, {});
|
||
|
||
/** 通用封装方法 无需传参可直接调用,需要传参需要调用sendToFlutter*/
|
||
void commonInteraction(Map<String, dynamic> data) =>
|
||
sendToFlutter(ToFlutterAppEnum.commonInteraction.code, data);
|
||
}
|