From fc9f72b88ef01ba18e8cd2ad1c632523504dfce5 Mon Sep 17 00:00:00 2001 From: fuenmao <980740792@qq.com> Date: Thu, 26 Dec 2024 11:35:57 +0800 Subject: [PATCH] =?UTF-8?q?1.=E5=A2=9E=E5=8A=A0socket=E5=92=8C=E5=A3=B0?= =?UTF-8?q?=E7=BD=91SDK=E6=96=AD=E7=BA=BF=E9=87=8D=E8=BF=9E=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=8F=90=E7=A4=BA=E5=BC=B9=E7=AA=97=202.=E6=8B=A6?= =?UTF-8?q?=E6=88=AA=E4=BC=9A=E8=AE=AE=E5=AE=A4=E8=BF=94=E5=9B=9E=E6=8C=89?= =?UTF-8?q?=E9=92=AE=EF=BC=8C=E5=8F=AA=E8=83=BD=E5=8F=B3=E4=B8=8A=E8=A7=92?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=E7=82=B9=E5=87=BB=E2=80=9C=E4=BB=85=E8=87=AA?= =?UTF-8?q?=E5=B7=B1=E7=A6=BB=E5=BC=80=E2=80=9D=E9=80=80=E5=87=BA=E4=BC=9A?= =?UTF-8?q?=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/pages/metting/meeting_main_logic.dart | 333 +++++++++++------- .../lib/pages/metting/meeting_main_state.dart | 4 + .../lib/pages/metting/meeting_main_view.dart | 243 ++++++------- wgshare/lib/utils/agora/AgoraUtil.dart | 110 ++++++ wgshare/lib/utils/toast_utils.dart | 8 + .../flutter/generated_plugin_registrant.cc | 4 + wgshare/linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 8 + wgshare/pubspec.lock | 64 ++++ wgshare/pubspec.yaml | 6 +- .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + 12 files changed, 540 insertions(+), 245 deletions(-) create mode 100644 wgshare/lib/utils/agora/AgoraUtil.dart diff --git a/wgshare/lib/pages/metting/meeting_main_logic.dart b/wgshare/lib/pages/metting/meeting_main_logic.dart index 6972b2c..770df6c 100644 --- a/wgshare/lib/pages/metting/meeting_main_logic.dart +++ b/wgshare/lib/pages/metting/meeting_main_logic.dart @@ -1,9 +1,11 @@ import 'dart:async'; import 'dart:convert'; +import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:date_format/date_format.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:get/get.dart'; import 'package:signalr_core/signalr_core.dart'; import 'package:wgshare/common/store/user_store.dart'; @@ -14,6 +16,7 @@ import '../../common/models/common/base_structure_result.dart'; import '../../common/models/meeting_room_info.dart'; import '../../common/models/meeting_room_msg.dart'; import '../../common/models/meeting_room_user.dart'; +import '../../utils/agora/AgoraUtil.dart'; import '../../utils/permission/PermissionService.dart'; import '../../utils/toast_utils.dart'; import 'meeting_main_state.dart'; @@ -59,9 +62,11 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { /// 合并请求 /// 1.获取会议室信息 /// 2.获取会议室所有用户 - Future mergeFetch() async { + Future mergeFetch(bool isAgain) async { try { - ToastUtils.showLoading(); + if(isAgain == false){ + ToastUtils.showLoading(); + } var results = await Future.wait([ getClient().getMeetingRoomInfo(state.roomNumber.value), @@ -238,12 +243,14 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { } } } else { - if(state.remoteUid.value == UserStore.to.userInfoEntity.value!.screenShareId){ + if (state.remoteUid.value == + UserStore.to.userInfoEntity.value!.screenShareId) { // 如果是自己在共享 - }else{ + } else { // 共享不是自己 for (var i = 0; i < state.cacheUsers.value.length; i++) { - if (state.remoteUid.value == state.cacheUsers.value[i].screenShareId) { + if (state.remoteUid.value == + state.cacheUsers.value[i].screenShareId) { state.cacheUsers.value[i].enableShare = true; } } @@ -311,14 +318,51 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { accessTokenFactory: () async => await Future.value(UserStore.to.token), logging: (level, message) => - debugPrint("wgs输出===:SignalR Socket-$message"), + debugPrint("wgs输出===:SignalR Socket-日志:$message"), )) - // 配置断线重连策略:默认重连4次,分别间隔10秒 4秒 10秒 10秒 - .withAutomaticReconnect([10000, 4000, 10000, 10000]).build(); + // 配置断线重连策略:默认重连5次,分别间隔2秒 4秒 4秒 5秒 10秒 + .withAutomaticReconnect([2000, 4000, 4000, 5000, 10000]).build(); await state.hubConnection.value?.start(); - joinMeetingToSocket(); + // 开始重新连接时回调 + state.hubConnection.value?.onreconnecting((error) { + debugPrint("wgs输出===:SignalR Socket-重连$error"); + if(EasyLoading.isShow == false) { + ToastUtils.showLoadingToMask("网络故障,正在重连...", EasyLoadingMaskType.black); + } + }); + + // 重新连接成功时回调,5次重连任意一次成功就回调 + state.hubConnection.value?.onreconnected((connectionId) { + debugPrint("wgs输出===:SignalR Socket-重连成功$connectionId"); + joinMeetingToSocket(true); + ToastUtils.dismiss(); + if(EasyLoading.isShow == false) { + ToastUtils.showSuccessToMask("重连成功!", EasyLoadingMaskType.black); + } + }); + + // 重新连接失败时回调,5次重连都失败才回调 + state.hubConnection.value?.onclose((error){ + debugPrint("wgs输出===:SignalR Socket-重连失败$error"); + ToastUtils.dismiss(); + + if(state.isNormaExit.value == false && state.isShowOkAlertDialog.value == false){ + showOkAlertDialog( + context: Get.context!, + title: "提示", + message: "网络错误,请重新加入会议室", + okLabel: "确定", + barrierDismissible: false, + ).then((OkCancelResult value){ + Get.back(); + Get.back(); + }); + } + }); + + joinMeetingToSocket(false); /// ------------------------------------------------------------------------------发言权限相关回调 /// 开启关闭发言权限 @@ -339,9 +383,9 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { if (UserStore.to.userInfoEntity.value!.uid == meetingRoomUser.uid) { state.isSpeak.value = true; state.defaulOpenState.value = 1; - if(state.defaulOpenState.value == 1){ + if (state.defaulOpenState.value == 1) { state.isOpenMicrophone.value = true; - }else if(state.defaulOpenState.value == 2){ + } else if (state.defaulOpenState.value == 2) { doHttpSetCamer(true); } // 设置声网SDK角色为主播 @@ -359,7 +403,6 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { } } if (UserStore.to.userInfoEntity.value!.uid == meetingRoomUser.uid) { - state.isSpeak.value = false; state.defaulOpenState.value = 0; state.isOpenMicrophone.value = false; @@ -375,7 +418,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { // 关闭本地预览悬浮窗 state.floating.value?.close(); // 当前全员观看主播是自己时 - if(state.remoteUid.value == "0"){ + if (state.remoteUid.value == "0") { state.remoteUid.value = ""; changePageState(0); } @@ -493,7 +536,8 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { var jsonStr = json.encode(e); List list = json.decode(jsonStr); DateTime dateTime = DateTime.now(); - MeetingRoomMsg meetingRoomMsg = MeetingRoomMsg(list[0], list[1], list[2], 0, formatDate(dateTime, [HH, ':', nn, ':', ss])); + MeetingRoomMsg meetingRoomMsg = MeetingRoomMsg(list[0], list[1], list[2], + 0, formatDate(dateTime, [HH, ':', nn, ':', ss])); state.meetingRoomMsgs.value.add(meetingRoomMsg); update(); Future.delayed(const Duration(milliseconds: 100), () { @@ -576,10 +620,10 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { } /// 加入会议室 - Future joinMeetingToSocket() async { + Future joinMeetingToSocket(bool isAgain) async { await state.hubConnection.value?.invoke("joinChannel", args: [state.roomNumber.value, false, false, false]); - mergeFetch(); + mergeFetch(isAgain); } /// 离开会议室 @@ -596,7 +640,8 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { UserStore.to.userInfoEntity.value!.uid, UserStore.to.userInfoEntity.value!.userName, msg, - 1, formatDate(dateTime, [HH, ':', nn, ':', ss])); + 1, + formatDate(dateTime, [HH, ':', nn, ':', ss])); state.meetingRoomMsgs.value.add(meetingRoomMsg); update(); Future.delayed(const Duration(milliseconds: 100), () { @@ -632,138 +677,178 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { await state.rctEngine.value?.enableAudioVolumeIndication( interval: 200, smooth: 3, reportVad: true); // 在发送端设置双流模式并设置视频小流 - await state.rctEngine.value?.setDualStreamMode(mode: SimulcastStreamMode.enableSimulcastStream); + await state.rctEngine.value + ?.setDualStreamMode(mode: SimulcastStreamMode.enableSimulcastStream); joinMeetingToRtc(); // 回调 state.rctEngine.value?.registerEventHandler( RtcEngineEventHandler( - // 成功加入会议室回调 - onJoinChannelSuccess: (RtcConnection connection, int elapsed) { - debugPrint("wgs输出===:RTC-自己加入会议室,ID:${connection.localUid}"); - }, + // 成功加入会议室回调 + onJoinChannelSuccess: (RtcConnection connection, int elapsed) { + debugPrint("wgs输出===:RTC-自己加入会议室,ID:${connection.localUid}"); + }, - // 成功离开会议室回调 - onLeaveChannel: (RtcConnection connection, RtcStats stats) { - debugPrint("wgs输出===:RTC-自己离开会议室,ID:${connection.localUid}"); - }, + // 成功离开会议室回调 + onLeaveChannel: (RtcConnection connection, RtcStats stats) { + debugPrint("wgs输出===:RTC-自己离开会议室,ID:${connection.localUid}"); + }, - // 远端用户或主播加入当前会议室回调-主播角色才能接收该回调 - onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) { - debugPrint("wgs输出===:RTC-远端用户或主播加入会议室,用户或主机的ID:$remoteUid"); - }, + // 远端用户或主播加入当前会议室回调-主播角色才能接收该回调 + onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) { + debugPrint("wgs输出===:RTC-远端用户或主播加入会议室,用户或主机的ID:$remoteUid"); + }, - // 远端用户或主播离开当前会议室回调-主播角色才能接收该回调 - onUserOffline: (RtcConnection connection, int remoteUid, - UserOfflineReasonType reason) async { - // 判断是否用户取消了共享 - if(remoteUid.toString().length == 9){ - for (var i = 0; i < state.cacheUsers.value.length; i++) { - if (remoteUid.toString() == state.cacheUsers.value[i].screenShareId) { - state.cacheUsers.value[i].enableShare = false; - } + // 远端用户或主播离开当前会议室回调-主播角色才能接收该回调 + onUserOffline: (RtcConnection connection, int remoteUid, + UserOfflineReasonType reason) async { + // 判断是否用户取消了共享 + if (remoteUid.toString().length == 9) { + for (var i = 0; i < state.cacheUsers.value.length; i++) { + if (remoteUid.toString() == + state.cacheUsers.value[i].screenShareId) { + state.cacheUsers.value[i].enableShare = false; } } - debugPrint("wgs输出===:RTC-远端用户或主播离开会议室,用户或主机的ID:$remoteUid"); - }, + } + debugPrint("wgs输出===:RTC-远端用户或主播离开会议室,用户或主机的ID:$remoteUid"); + }, - // 音频路由发生变化回调 - onAudioRoutingChanged: (int routing) { - debugPrint("wgs输出===:RTC-音频路由切换:$routing"); - state.communicationMode.value = routing; - if (routing == 1) { - debugPrint("wgs输出===:RTC-音频路由切换为听筒"); - } else if (routing == 3) { - debugPrint("wgs输出===:RTC-音频路由切换为扬声器"); + // 音频路由发生变化回调 + onAudioRoutingChanged: (int routing) { + debugPrint("wgs输出===:RTC-音频路由切换:$routing"); + state.communicationMode.value = routing; + if (routing == 1) { + debugPrint("wgs输出===:RTC-音频路由切换为听筒"); + } else if (routing == 3) { + debugPrint("wgs输出===:RTC-音频路由切换为扬声器"); + } else { + debugPrint("wgs输出===:RTC-音频路由切换为外接设备"); + } + }, + + // 音频采集开关回调 + onLocalAudioStateChanged: (RtcConnection connection, + LocalAudioStreamState state, LocalAudioStreamReason reason) { + debugPrint("wgs输出===:RTC-音频采集开关:$state"); + }, + + // 远端视频状态发生改变回调 + onRemoteVideoStateChanged: (RtcConnection connection, + int remoteUid, + RemoteVideoState remoteVideoState, + RemoteVideoStateReason remoteVideoStateReason, + int elapsed) { + debugPrint( + "wgs输出===:RTC-远端视频状态发生改变:ID-$remoteUid-状态-$remoteVideoStateReason"); + if (remoteVideoStateReason == + RemoteVideoStateReason.remoteVideoStateReasonRemoteMuted) { + // 远端用户停止发送视频流或远端用户禁用视频模块 + if (remoteUid.toString().length != 9) { + // 摄像头 + if (remoteUid.toString() == state.remoteUid.value) { + // 如果停止发送视频流或禁用视频模块的远端用户是当前全员观看主播 + doHttpGetTvAnchor(); + } } else { - debugPrint("wgs输出===:RTC-音频路由切换为外接设备"); + // 共享屏幕(此版本不做) } - }, + } + }, - // 音频采集开关回调 - onLocalAudioStateChanged: (RtcConnection connection, - LocalAudioStreamState state, LocalAudioStreamReason reason) { - debugPrint("wgs输出===:RTC-音频采集开关:$state"); - }, - - // 远端视频状态发生改变回调 - onRemoteVideoStateChanged: (RtcConnection connection, - int remoteUid, - RemoteVideoState remoteVideoState, - RemoteVideoStateReason remoteVideoStateReason, - int elapsed) { - debugPrint( - "wgs输出===:RTC-远端视频状态发生改变:ID-$remoteUid-状态-$remoteVideoStateReason"); - if (remoteVideoStateReason == - RemoteVideoStateReason.remoteVideoStateReasonRemoteMuted) { - // 远端用户停止发送视频流或远端用户禁用视频模块 - if (remoteUid.toString().length != 9) { - // 摄像头 - if (remoteUid.toString() == state.remoteUid.value) { - // 如果停止发送视频流或禁用视频模块的远端用户是当前全员观看主播 - doHttpGetTvAnchor(); - } - } else { - // 共享屏幕(此版本不做) - } - } - }, - - // 用户音量提示回调 - onAudioVolumeIndication: (RtcConnection connection, - List speakers, - int speakerNumber, - int totalVolume) { - if (speakers.isNotEmpty) { - for (AudioVolumeInfo avi in speakers) { - for (MeetingRoomUser mru in state.cacheUsers.value) { - // 用于更改语音布局里的用户列表麦克风 - if (avi.uid == 0) { - //debugPrint("wgs输出===:RTC-用户音量提示(自己):${CountMicrophoneVolume.getVolume(avi.volume!)}"); + // 用户音量提示回调 + onAudioVolumeIndication: (RtcConnection connection, + List speakers, + int speakerNumber, + int totalVolume) { + if (speakers.isNotEmpty) { + for (AudioVolumeInfo avi in speakers) { + for (MeetingRoomUser mru in state.cacheUsers.value) { + // 用于更改语音布局里的用户列表麦克风 + if (avi.uid == 0) { + //debugPrint("wgs输出===:RTC-用户音量提示(自己):${CountMicrophoneVolume.getVolume(avi.volume!)}"); + mru.volume = CountMicrophoneVolume.getVolume(avi.volume!); + state.microphoneVolume.value = + CountMicrophoneVolume.getVolume(avi.volume!); + } else { + if (avi.uid.toString() == mru.uid) { + //debugPrint("wgs输出===:RTC-用户音量提示(远端用户):${speakers[0].uid}--${speakers[0].volume}"); mru.volume = CountMicrophoneVolume.getVolume(avi.volume!); - state.microphoneVolume.value = - CountMicrophoneVolume.getVolume(avi.volume!); - } else { - if (avi.uid.toString() == mru.uid) { - //debugPrint("wgs输出===:RTC-用户音量提示(远端用户):${speakers[0].uid}--${speakers[0].volume}"); - mru.volume = CountMicrophoneVolume.getVolume(avi.volume!); - if (avi.volume != 0) { - state.spokesman.value = mru.userName; - state.spokesmanVolume.value = - CountMicrophoneVolume.getVolume(avi.volume!); - } else { - state.spokesman.value = ""; - state.spokesmanVolume.value = 0; - } + if (avi.volume != 0) { + state.spokesman.value = mru.userName; + state.spokesmanVolume.value = + CountMicrophoneVolume.getVolume(avi.volume!); + } else { + state.spokesman.value = ""; + state.spokesmanVolume.value = 0; } } } } } - }, + } + }, - // 切换用户角色回调 - onClientRoleChanged: (RtcConnection connection, ClientRoleType oldRole, - ClientRoleType newRole, ClientRoleOptions newRoleOptions) { - debugPrint( - "wgs输出===:RTC-切换用户角色为:${newRole == ClientRoleType.clientRoleBroadcaster ? "主播" : "观众"}"); - }, + // 切换用户角色回调 + onClientRoleChanged: (RtcConnection connection, + ClientRoleType oldRole, + ClientRoleType newRole, + ClientRoleOptions newRoleOptions) { + debugPrint( + "wgs输出===:RTC-切换用户角色为:${newRole == ClientRoleType.clientRoleBroadcaster ? "主播" : "观众"}"); + }, - // token即将在30秒内过期回调 - onTokenPrivilegeWillExpire: (RtcConnection connection, String token) { - doHttpGetMeetingToken(false); - }, + // token即将在30秒内过期回调 + onTokenPrivilegeWillExpire: (RtcConnection connection, String token) { + doHttpGetMeetingToken(false); + }, - // 本地视频状态发生改变回调 - onLocalVideoStateChanged: (VideoSourceType source, - LocalVideoStreamState state, LocalVideoStreamReason reason) { - debugPrint("wgs输出===:RTC-本地视频状态发生改变:$source--$state--$reason"); - }, + // 本地视频状态发生改变回调 + onLocalVideoStateChanged: (VideoSourceType source, + LocalVideoStreamState state, LocalVideoStreamReason reason) { + debugPrint("wgs输出===:RTC-本地视频状态发生改变:$source--$state--$reason"); + }, - // 获取设备权限出错回调 - /*onPermissionError: (PermissionType permissionType){ + // 网络连接状态回调 + onConnectionStateChanged: (RtcConnection connection, + ConnectionStateType stateType, ConnectionChangedReasonType reason) { + debugPrint("wgs输出===:RTC-网络连接状态发生改变:" + "会议室编号(${connection.channelId})," + "网络状态($stateType-${AgoraUtil.getConnectionStateChangedType(stateType)})," + "网络改变原因($reason-${AgoraUtil.getConnectionChangedReasonType(reason)})"); + if(stateType == ConnectionStateType.connectionStateReconnecting){ + if(EasyLoading.isShow == false){ + ToastUtils.showLoadingToMask("网络故障,正在重连...", EasyLoadingMaskType.black); + } + }else if(stateType == ConnectionStateType.connectionStateConnected && reason == ConnectionChangedReasonType.connectionChangedRejoinSuccess){ + ToastUtils.dismiss(); + if(EasyLoading.isShow == false){ + ToastUtils.showSuccessToMask("重连成功!", EasyLoadingMaskType.black); + } + }else if(reason == ConnectionChangedReasonType.connectionChangedLost){ + // 和服务器失去连接后,再延迟15秒(和signalR Socket一致),让SDK继续重连,如果重连不上则告知用户需要重新加入 + Future.delayed(const Duration(milliseconds: 15000), () { + ToastUtils.dismiss(); + if(state.isShowOkAlertDialog.value == false){ + showOkAlertDialog( + context: Get.context!, + title: "提示", + message: "网络错误,请重新加入会议室", + okLabel: "确定", + barrierDismissible: false, + ).then((OkCancelResult value){ + Get.back(); + Get.back(); + }); + } + }); + } + } + + // 获取设备权限出错回调 + /*onPermissionError: (PermissionType permissionType){ debugPrint("wgs输出===:RTC-获取设备权限出错:$permissionType"); if(permissionType == PermissionType.screenCapture){ // 获取共享屏幕出错(此版本不做) @@ -771,7 +856,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { stopScreenCapture(); } }*/ - ), + ), ); } diff --git a/wgshare/lib/pages/metting/meeting_main_state.dart b/wgshare/lib/pages/metting/meeting_main_state.dart index b2823e2..4a64d06 100644 --- a/wgshare/lib/pages/metting/meeting_main_state.dart +++ b/wgshare/lib/pages/metting/meeting_main_state.dart @@ -46,6 +46,10 @@ class MeetingMainState { /// 会议室信息 late Rx meetingRoomInfo = Rx(null); + /// 是否弹出showOkAlertDialog + late RxBool isShowOkAlertDialog = false.obs; + /// 是否正常退出(点击仅自己退出) + late RxBool isNormaExit = false.obs; /// 会议室计时相关 late RxString duration = "".obs; diff --git a/wgshare/lib/pages/metting/meeting_main_view.dart b/wgshare/lib/pages/metting/meeting_main_view.dart index 318131d..92d1c26 100644 --- a/wgshare/lib/pages/metting/meeting_main_view.dart +++ b/wgshare/lib/pages/metting/meeting_main_view.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_floating/floating/assist/floating_slide_type.dart'; import 'package:flutter_floating/floating/floating.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -47,21 +48,24 @@ class MeetingMainPageState extends State { @override Widget build(BuildContext context) { state.context.value = context; - return Scaffold( - appBar: AppBar( - surfaceTintColor: ColorUtil.Color_41_41_41, - elevation: 0, - toolbarHeight: 0, - systemOverlayStyle: const SystemUiOverlayStyle( - statusBarColor: Colors.transparent, - systemNavigationBarColor: ColorUtil.Color_41_41_41, - systemNavigationBarIconBrightness: Brightness.light, - statusBarIconBrightness: Brightness.light, - statusBarBrightness: Brightness.light, - ), - backgroundColor: ColorUtil.Color_41_41_41, - ), - body: Obx(() => Stack( + return PopScope( + canPop: false, + child: FlutterEasyLoading( + child: Scaffold( + appBar: AppBar( + surfaceTintColor: ColorUtil.Color_41_41_41, + elevation: 0, + toolbarHeight: 0, + systemOverlayStyle: const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarColor: ColorUtil.Color_41_41_41, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.light, + statusBarBrightness: Brightness.light, + ), + backgroundColor: ColorUtil.Color_41_41_41, + ), + body: Obx(() => Stack( children: [ Column( children: [ @@ -201,76 +205,76 @@ class MeetingMainPageState extends State { visible: state.pageState.value == 1, child: null != state.rctEngine.value ? Stack( - alignment: Alignment.center, - children: [ - PreloadPageView.builder( - preloadPagesCount: 2, - itemCount: 2, - itemBuilder: (BuildContext context, - int position) => - returnPage(position), - controller: PreloadPageController( - initialPage: 0), - onPageChanged: (int position) { - state.pageIndex.value = position; - if (state.isSpeak.value == true && - state.isOpenCamera.value == - true && - state.remoteUid.value != "0") { - if (position == 0) { - state.floating.value - ?.open(context); - } else { - state.floating.value?.close(); - } - } - }, - ), + alignment: Alignment.center, + children: [ + PreloadPageView.builder( + preloadPagesCount: 2, + itemCount: 2, + itemBuilder: (BuildContext context, + int position) => + returnPage(position), + controller: PreloadPageController( + initialPage: 0), + onPageChanged: (int position) { + state.pageIndex.value = position; + if (state.isSpeak.value == true && + state.isOpenCamera.value == + true && + state.remoteUid.value != "0") { + if (position == 0) { + state.floating.value + ?.open(context); + } else { + state.floating.value?.close(); + } + } + }, + ), - /// pageview 指示器 - Positioned( - bottom: 16, - child: Row( - children: [ - Container( - width: 8.w, - height: 8.h, - margin: const EdgeInsets.only( - right: 6), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular( - 8), - color: state.pageIndex - .value == - 0 - ? ColorUtil - .Color_255_255_255 - : ColorUtil - .Color_108_108_108), - ), - Container( - width: 8.w, - height: 8.h, - margin: const EdgeInsets.only( - left: 6), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular( - 8), - color: state.pageIndex - .value == - 1 - ? ColorUtil - .Color_255_255_255 - : ColorUtil - .Color_108_108_108), - ) - ], - ), + /// pageview 指示器 + Positioned( + bottom: 16, + child: Row( + children: [ + Container( + width: 8.w, + height: 8.h, + margin: const EdgeInsets.only( + right: 6), + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular( + 8), + color: state.pageIndex + .value == + 0 + ? ColorUtil + .Color_255_255_255 + : ColorUtil + .Color_108_108_108), ), + Container( + width: 8.w, + height: 8.h, + margin: const EdgeInsets.only( + left: 6), + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular( + 8), + color: state.pageIndex + .value == + 1 + ? ColorUtil + .Color_255_255_255 + : ColorUtil + .Color_108_108_108), + ) ], - ) + ), + ), + ], + ) : Container()), Row( @@ -295,7 +299,7 @@ class MeetingMainPageState extends State { ), child: Row( crossAxisAlignment: - CrossAxisAlignment.center, + CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start, children: [ Image.asset( @@ -373,44 +377,44 @@ class MeetingMainPageState extends State { children: [ state.isSpeak.value == false ? Image.asset( - state.isSpeak.value == false - ? 'assets/images/meeting_main_sqfy.png' - : state.isOpenMicrophone.value == - true - ? 'assets/images/meeting_main_microphone_default.png' - : 'assets/images/meeting_main_sqfy.png', - width: 20.w, - height: 20.h, - ) + state.isSpeak.value == false + ? 'assets/images/meeting_main_sqfy.png' + : state.isOpenMicrophone.value == + true + ? 'assets/images/meeting_main_microphone_default.png' + : 'assets/images/meeting_main_sqfy.png', + width: 20.w, + height: 20.h, + ) : state.isOpenMicrophone.value == true - ? Container( - width: 20.w, - height: 20.h, - child: LiquidCustomProgressIndicator( - value: state - .microphoneVolume.value, - valueColor: - const AlwaysStoppedAnimation( - ColorUtil - .Color_2_177_136), - backgroundColor: - ColorUtil.Color_255_255_255, - direction: Axis.vertical, - shapePath: ViewSvgPath - .getMicrpphonePath()), - ) - : Image.asset( - 'assets/images/meeting_main_sqfy.png', - width: 20.w, - height: 20.h, - ), + ? Container( + width: 20.w, + height: 20.h, + child: LiquidCustomProgressIndicator( + value: state + .microphoneVolume.value, + valueColor: + const AlwaysStoppedAnimation( + ColorUtil + .Color_2_177_136), + backgroundColor: + ColorUtil.Color_255_255_255, + direction: Axis.vertical, + shapePath: ViewSvgPath + .getMicrpphonePath()), + ) + : Image.asset( + 'assets/images/meeting_main_sqfy.png', + width: 20.w, + height: 20.h, + ), SizedBox(height: 4.h), Text( state.isSpeak.value == false ? '申请发言' : state.isOpenMicrophone.value == true - ? "手动静音" - : "解除静音", + ? "手动静音" + : "解除静音", style: TextStyle( fontSize: 12.sp, color: ColorUtil.Color_202_202_202), @@ -441,8 +445,8 @@ class MeetingMainPageState extends State { Image.asset( state.isSpeak.value == true ? state.isOpenCamera.value == true - ? 'assets/images/meeting_main_camera_open.png' - : 'assets/images/meeting_main_camera_default.png' + ? 'assets/images/meeting_main_camera_open.png' + : 'assets/images/meeting_main_camera_default.png' : 'assets/images/meeting_main_sp.png', width: 22.w, height: 22.h, @@ -545,7 +549,9 @@ class MeetingMainPageState extends State { meetingInfoFloatingLayer(), meetingAudioFloatingLayer(), ], - ))); + ))), + ) + ); } /// 退出会议底部弹窗 @@ -573,6 +579,7 @@ class MeetingMainPageState extends State { ), ), onTap: () { + state.isNormaExit.value = true; Get.back(); Get.back(); }, diff --git a/wgshare/lib/utils/agora/AgoraUtil.dart b/wgshare/lib/utils/agora/AgoraUtil.dart new file mode 100644 index 0000000..74bcd1b --- /dev/null +++ b/wgshare/lib/utils/agora/AgoraUtil.dart @@ -0,0 +1,110 @@ +import 'package:agora_rtc_engine/agora_rtc_engine.dart'; + +/// 声网SDK相关工具类 +class AgoraUtil{ + /// 获取网络连接状态 + static String getConnectionStateChangedType(ConnectionStateType type){ + var returnTypeStr = ""; + switch(type){ + case ConnectionStateType.connectionStateDisconnected: + returnTypeStr = "网络连接断开"; + break; + case ConnectionStateType.connectionStateConnecting: + returnTypeStr = "网络连接中"; + break; + case ConnectionStateType.connectionStateConnected: + returnTypeStr = "网络已连接"; + break; + case ConnectionStateType.connectionStateReconnecting: + returnTypeStr = "网络重新连接中"; + break; + case ConnectionStateType.connectionStateFailed: + returnTypeStr = "网络连接失败"; + break; + } + return returnTypeStr; + } + + /// 获取网络连接状态发生变化的原因 + static String getConnectionChangedReasonType(ConnectionChangedReasonType type){ + var returnTypeStr = ""; + switch(type){ + case ConnectionChangedReasonType.connectionChangedConnecting: + returnTypeStr = "建立网络连接中"; + break; + case ConnectionChangedReasonType.connectionChangedJoinSuccess: + returnTypeStr = "成功加入频道"; + break; + case ConnectionChangedReasonType.connectionChangedInterrupted: + returnTypeStr = "网络连接中断"; + break; + case ConnectionChangedReasonType.connectionChangedBannedByServer: + returnTypeStr = "网络连接被服务器禁止"; + break; + case ConnectionChangedReasonType.connectionChangedJoinFailed: + returnTypeStr = "加入频道失败"; + break; + case ConnectionChangedReasonType.connectionChangedLeaveChannel: + returnTypeStr = "离开频道"; + break; + case ConnectionChangedReasonType.connectionChangedInvalidAppId: + returnTypeStr = "App ID 无效"; + break; + case ConnectionChangedReasonType.connectionChangedInvalidChannelName: + returnTypeStr = "频道名无效"; + break; + case ConnectionChangedReasonType.connectionChangedInvalidToken: + returnTypeStr = "Token 无效"; + break; + case ConnectionChangedReasonType.connectionChangedTokenExpired: + returnTypeStr = "当前使用的 Token 已过期"; + break; + case ConnectionChangedReasonType.connectionChangedRejectedByServer: + returnTypeStr = "此用户被服务器禁止"; + break; + case ConnectionChangedReasonType.connectionChangedSettingProxyServer: + returnTypeStr = "设置了代理服务器"; + break; + case ConnectionChangedReasonType.connectionChangedRenewToken: + returnTypeStr = "更新 Token 引起网络连接状态改变"; + break; + case ConnectionChangedReasonType.connectionChangedClientIpAddressChanged: + returnTypeStr = "客户端 IP 地址变更"; + break; + case ConnectionChangedReasonType.connectionChangedKeepAliveTimeout: + returnTypeStr = "SDK 和服务器连接保活超时"; + break; + case ConnectionChangedReasonType.connectionChangedRejoinSuccess: + returnTypeStr = "重新加入频道成功"; + break; + case ConnectionChangedReasonType.connectionChangedLost: + returnTypeStr = "SDK 和服务器失去连接"; + break; + case ConnectionChangedReasonType.connectionChangedEchoTest: + returnTypeStr = "连接状态变化由回声测试引起"; + break; + case ConnectionChangedReasonType.connectionChangedClientIpAddressChangedByUser: + returnTypeStr = "本地 IP 地址被用户更改"; + break; + case ConnectionChangedReasonType.connectionChangedSameUidLogin: + returnTypeStr = "使用相同的 UID 从不同的设备加入同一频道"; + break; + case ConnectionChangedReasonType.connectionChangedTooManyBroadcasters: + returnTypeStr = "频道内主播人数已达上限"; + break; + case ConnectionChangedReasonType.connectionChangedLicenseValidationFailure: + returnTypeStr = "连接已更改许可证验证失败"; + break; + case ConnectionChangedReasonType.connectionChangedCertificationVeryfyFailure: + returnTypeStr = "连接已更改认证版本失败"; + break; + case ConnectionChangedReasonType.connectionChangedStreamChannelNotAvailable: + returnTypeStr = "连接已更改流通道不可用"; + break; + case ConnectionChangedReasonType.connectionChangedInconsistentAppid: + returnTypeStr = "连接已更改不一致Appid"; + break; + } + return returnTypeStr; + } +} \ No newline at end of file diff --git a/wgshare/lib/utils/toast_utils.dart b/wgshare/lib/utils/toast_utils.dart index ad1e7cf..ab9efde 100644 --- a/wgshare/lib/utils/toast_utils.dart +++ b/wgshare/lib/utils/toast_utils.dart @@ -51,6 +51,10 @@ class ToastUtils { EasyLoading.showError(showMsg, duration: duration); } + static showLoadingToMask(String str, EasyLoadingMaskType maskType) { + EasyLoading.show(status: str, maskType: maskType); + } + static showLoading() { EasyLoading.show(status: 'loading...'); } @@ -63,6 +67,10 @@ class ToastUtils { EasyLoading.showInfo(showMsg, duration: Duration(microseconds: microseconds)); } + static showSuccessToMask(String showMsg, EasyLoadingMaskType maskType, {Duration? duration}) { + EasyLoading.showSuccess(showMsg, maskType: maskType, duration: duration); + } + static showSuccess(String showMsg, {Duration? duration}) { EasyLoading.showSuccess(showMsg, duration: duration); } diff --git a/wgshare/linux/flutter/generated_plugin_registrant.cc b/wgshare/linux/flutter/generated_plugin_registrant.cc index e71a16d..675b719 100644 --- a/wgshare/linux/flutter/generated_plugin_registrant.cc +++ b/wgshare/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) dynamic_color_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); + dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); } diff --git a/wgshare/linux/flutter/generated_plugins.cmake b/wgshare/linux/flutter/generated_plugins.cmake index 2e1de87..3e303c1 100644 --- a/wgshare/linux/flutter/generated_plugins.cmake +++ b/wgshare/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + dynamic_color ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/wgshare/macos/Flutter/GeneratedPluginRegistrant.swift b/wgshare/macos/Flutter/GeneratedPluginRegistrant.swift index ac5210e..0210b80 100644 --- a/wgshare/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/wgshare/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,20 +6,28 @@ import FlutterMacOS import Foundation import agora_rtc_engine +import appkit_ui_element_colors import device_info_plus +import dynamic_color import flutter_inappwebview_macos import geolocator_apple import iris_method_channel +import macos_ui +import macos_window_utils import package_info_plus import path_provider_foundation import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AgoraRtcNgPlugin.register(with: registry.registrar(forPlugin: "AgoraRtcNgPlugin")) + AppkitUiElementColorsPlugin.register(with: registry.registrar(forPlugin: "AppkitUiElementColorsPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) IrisMethodChannelPlugin.register(with: registry.registrar(forPlugin: "IrisMethodChannelPlugin")) + MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) + MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) diff --git a/wgshare/pubspec.lock b/wgshare/pubspec.lock index 06f1bec..e85eb93 100644 --- a/wgshare/pubspec.lock +++ b/wgshare/pubspec.lock @@ -14,6 +14,14 @@ packages: description: dart source: sdk version: "0.3.2" + adaptive_dialog: + dependency: "direct main" + description: + name: adaptive_dialog + sha256: a87f9e13fdbe0b11d353733a90796129ee79fc0654a27855fa2c9c0a3633a362 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" agora_rtc_engine: dependency: "direct main" description: @@ -30,6 +38,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.7.0" + animations: + dependency: transitive + description: + name: animations + sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.11" + appkit_ui_element_colors: + dependency: transitive + description: + name: appkit_ui_element_colors + sha256: c3e50f900aae314d339de489535736238627071457c4a4a2dbbb1545b4f04f22 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" args: dependency: transitive description: @@ -254,6 +278,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" + dynamic_color: + dependency: transitive + description: + name: dynamic_color + sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.7.0" easy_debounce: dependency: "direct main" description: @@ -554,6 +586,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" + gradient_borders: + dependency: transitive + description: + name: gradient_borders + sha256: b1cd969552c83f458ff755aa68e13a0327d09f06c3f42f471b423b01427f21f8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" graphs: dependency: transitive description: @@ -594,6 +634,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.0.2" + intersperse: + dependency: transitive + description: + name: intersperse + sha256: "2f8a905c96f6cbba978644a3d5b31b8d86ddc44917662df7d27a61f3df66a576" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" intl: dependency: transitive description: @@ -698,6 +746,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" + macos_ui: + dependency: transitive + description: + name: macos_ui + sha256: "80f6539aba5a3a1182d5225a6c27969a780bcb1d2d8135b4ffb708570cf0c854" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.9" + macos_window_utils: + dependency: transitive + description: + name: macos_window_utils + sha256: "3534f2af024f2f24112ca28789a44e6750083f8c0065414546c6593ee48a5009" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.1" macros: dependency: transitive description: diff --git a/wgshare/pubspec.yaml b/wgshare/pubspec.yaml index e44c5cf..7665411 100644 --- a/wgshare/pubspec.yaml +++ b/wgshare/pubspec.yaml @@ -33,8 +33,6 @@ dependencies: flutter_localizations: sdk: flutter - - # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 @@ -86,12 +84,14 @@ dependencies: preload_page_view: ^0.2.0 # webview - #webview_flutter: ^3.0.4 flutter_inappwebview: ^6.1.5 # 时间组件 date_format: ^2.0.9 + # 弹窗 + adaptive_dialog: ^2.3.0 + dev_dependencies: flutter_test: sdk: flutter diff --git a/wgshare/windows/flutter/generated_plugin_registrant.cc b/wgshare/windows/flutter/generated_plugin_registrant.cc index 388caa0..2ccde88 100644 --- a/wgshare/windows/flutter/generated_plugin_registrant.cc +++ b/wgshare/windows/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { AgoraRtcEnginePluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("AgoraRtcEnginePlugin")); + DynamicColorPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); GeolocatorWindowsRegisterWithRegistrar( diff --git a/wgshare/windows/flutter/generated_plugins.cmake b/wgshare/windows/flutter/generated_plugins.cmake index 3f27852..94bdeb9 100644 --- a/wgshare/windows/flutter/generated_plugins.cmake +++ b/wgshare/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST agora_rtc_engine + dynamic_color flutter_inappwebview_windows geolocator_windows iris_method_channel