Files
web_tools/lib/utils/app_bridge.dart
2026-01-27 17:25:20 +08:00

454 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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'),
// 定向充值
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'), //分享完成
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);
}