diff --git a/wgshare/assets/images/2.0x/meeting_main_share_currently_n.png b/wgshare/assets/images/2.0x/meeting_main_share_currently_n.png new file mode 100644 index 0000000..6d6d573 Binary files /dev/null and b/wgshare/assets/images/2.0x/meeting_main_share_currently_n.png differ diff --git a/wgshare/assets/images/3.0x/meeting_main_share_currently_n.png b/wgshare/assets/images/3.0x/meeting_main_share_currently_n.png new file mode 100644 index 0000000..239c409 Binary files /dev/null and b/wgshare/assets/images/3.0x/meeting_main_share_currently_n.png differ diff --git a/wgshare/assets/images/meeting_main_share_currently_n.png b/wgshare/assets/images/meeting_main_share_currently_n.png new file mode 100644 index 0000000..b7a8aea Binary files /dev/null and b/wgshare/assets/images/meeting_main_share_currently_n.png differ diff --git a/wgshare/lib/common/api/retrofit_client.dart b/wgshare/lib/common/api/retrofit_client.dart index 60f8f3d..c2ed57c 100644 --- a/wgshare/lib/common/api/retrofit_client.dart +++ b/wgshare/lib/common/api/retrofit_client.dart @@ -42,6 +42,12 @@ abstract class RetrofitClient { @Path("roomNum") String roomNum, ); + /// 检验房间是否存在 + @GET("/room/checkout") + Future> checkout( + @Query("roomNum") String roomNum, + ); + /// 获取会议室所有用户 @GET("/room/user") Future>> getMeetingRoomAllUser( diff --git a/wgshare/lib/common/config/app_config.dart b/wgshare/lib/common/config/app_config.dart index 29c6742..8032f78 100644 --- a/wgshare/lib/common/config/app_config.dart +++ b/wgshare/lib/common/config/app_config.dart @@ -21,4 +21,9 @@ class AppConfig { static const paddingLeft = 20.0; static const paddingRight = 20.0; static const paddingbottom = 20.0; + + // 正常登录 + static const NORMAL_LOGIN = "normal_login"; + // 匿名登录 + static const ANONYMOUS_LOGIN = "anonymous_login"; } diff --git a/wgshare/lib/common/models/meeting_room_msg.dart b/wgshare/lib/common/models/meeting_room_msg.dart index 9043751..1c85803 100644 --- a/wgshare/lib/common/models/meeting_room_msg.dart +++ b/wgshare/lib/common/models/meeting_room_msg.dart @@ -14,7 +14,9 @@ class MeetingRoomMsg extends Object{ // 0:别人,1:自己 int source; - MeetingRoomMsg(this.uid,this.userName,this.message,this.source); + String time; + + MeetingRoomMsg(this.uid,this.userName,this.message,this.source,this.time); factory MeetingRoomMsg.fromJson(Map srcJson) => _$MeetingRoomMsgFromJson(srcJson); diff --git a/wgshare/lib/common/models/meeting_room_user.dart b/wgshare/lib/common/models/meeting_room_user.dart index 5ec6707..b8948d5 100644 --- a/wgshare/lib/common/models/meeting_room_user.dart +++ b/wgshare/lib/common/models/meeting_room_user.dart @@ -39,7 +39,10 @@ class MeetingRoomUser extends Object{ @JsonKey(name: 'volume') double? volume = 0.0; - MeetingRoomUser(this.uid,this.connectId,this.account,this.enableMicr,this.enableCamera,this.screenShareId,this.userName,this.roleId,this.roleName,this.isRoomManager,this.volume); + @JsonKey(name: 'enableShare') + bool? enableShare = false; + + MeetingRoomUser(this.uid,this.connectId,this.account,this.enableMicr,this.enableCamera,this.screenShareId,this.userName,this.roleId,this.roleName,this.isRoomManager,this.volume,this.enableShare); factory MeetingRoomUser.fromJson(Map srcJson) => _$MeetingRoomUserFromJson(srcJson); diff --git a/wgshare/lib/common/store/app_storage_key.dart b/wgshare/lib/common/store/app_storage_key.dart index 2fc6818..c0c433f 100644 --- a/wgshare/lib/common/store/app_storage_key.dart +++ b/wgshare/lib/common/store/app_storage_key.dart @@ -4,7 +4,8 @@ enum AppStorageKey { token(value: 'TOKEN', label: "登录用户的token"), userInfo(value: 'USERINFO', label: "登录用户的基本信息"), account(value: 'ACCOUNT', label: "用户名"), - pwd(value: 'PWD', label: "密码"); + pwd(value: 'PWD', label: "密码"), + loginType(value: 'LOGINTYPE', label: "登录类型"); final String label; final String value; diff --git a/wgshare/lib/common/store/user_store.dart b/wgshare/lib/common/store/user_store.dart index 9596f38..294b7a6 100644 --- a/wgshare/lib/common/store/user_store.dart +++ b/wgshare/lib/common/store/user_store.dart @@ -14,10 +14,14 @@ class UserStore extends GetxController with RequestToolMixin { /// 是否登录 String? token; + /// 登录类型 + String? loginType; + /// 用户信息 Rx userInfoEntity = Rx(null); UserStore init() { + loginType = StorageService.to.read(AppStorageKey.loginType.value); token = StorageService.to.read(AppStorageKey.token.value); try { var userDetail = StorageService.to.read(AppStorageKey.userInfo.value); @@ -41,6 +45,12 @@ class UserStore extends GetxController with RequestToolMixin { StorageService.to.write(AppStorageKey.token.value, token); } + /// 保存登录类型 + void setLoginType(String loginType) { + this.loginType = loginType; + StorageService.to.write(AppStorageKey.loginType.value, loginType); + } + /// 保存用户信息 void setUserDetailInfo(UserInfoEntity info) { userInfoEntity.value = info; @@ -51,6 +61,7 @@ class UserStore extends GetxController with RequestToolMixin { void erase() { userInfoEntity.value = null; token = null; + loginType = null; StorageService.to.erase(); } diff --git a/wgshare/lib/main.dart b/wgshare/lib/main.dart index 36293bb..fb12d3b 100644 --- a/wgshare/lib/main.dart +++ b/wgshare/lib/main.dart @@ -10,6 +10,7 @@ import 'package:wgshare/utils/package_info_util.dart'; import 'package:wgshare/utils/storage.dart'; import 'package:wgshare/utils/utils.dart'; +import 'common/config/app_config.dart'; import 'common/config/colorUtils.dart'; import 'routes/app_pages.dart'; import 'routes/app_routes.dart'; @@ -79,7 +80,9 @@ class MyApp extends StatelessWidget { //默认专场动画 defaultTransition: Transition.fade, //初始化路由页面 - initialRoute: (UserStore.to.token?.isNotEmpty ?? false) && UserStore.to.userInfoEntity.value != null ? Routes.startPage : Routes.loginPage, + initialRoute: (UserStore.to.token?.isNotEmpty ?? false) && UserStore.to.userInfoEntity.value != null && UserStore.to.loginType != null && UserStore.to.loginType == AppConfig.NORMAL_LOGIN + ? Routes.startPage + : Routes.loginPage, /// 路由表 getPages: AppPages.pages, diff --git a/wgshare/lib/pages/homePage/home_logic.dart b/wgshare/lib/pages/homePage/home_logic.dart index 8600c8e..3179953 100644 --- a/wgshare/lib/pages/homePage/home_logic.dart +++ b/wgshare/lib/pages/homePage/home_logic.dart @@ -1,9 +1,12 @@ +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:signalr_core/signalr_core.dart'; import 'package:wgshare/common/models/common/base_structure_result.dart'; import 'package:wgshare/common/mixins/request_tool_mixin.dart'; +import 'package:wgshare/common/store/user_store.dart'; import '../../common/models/meeting_room_item.dart'; +import '../../utils/toast_utils.dart'; import 'home_state.dart'; class HomeLogic extends GetxController with RequestToolMixin { @@ -22,8 +25,9 @@ class HomeLogic extends GetxController with RequestToolMixin { state.refreshController.dispose(); } - /// 直接进入会议(匿名登录) + /// 获取会议列表 Future doHttpGetMeetingRoomList(int pageIndex, int pageSize) async { + debugPrint("wgs输出===:token:${UserStore.to.token}"); BaseStructureResult res = await getClient().getMeetingRoomList(pageIndex,pageSize); if(null != res.data){ if(state.pageIndex == 1){ @@ -54,4 +58,20 @@ class HomeLogic extends GetxController with RequestToolMixin { doHttpGetMeetingRoomList(state.pageIndex.value,state.pageSize.value); } + /// 返回键退出 + bool closeOnConfirm(BuildContext context) { + DateTime now = DateTime.now(); + // 物理键,两次间隔大于4秒, 退出请求无效 + if (state.currentBackPressTime == null || + now.difference(state.currentBackPressTime!) > const Duration(seconds: 4)) { + state.currentBackPressTime = now; + ToastUtils.showInfo("再按一次退出"); + return false; + } + // 退出请求有效 + state.currentBackPressTime = null; + return true; + } + + } diff --git a/wgshare/lib/pages/homePage/home_state.dart b/wgshare/lib/pages/homePage/home_state.dart index 00b7222..d8df1c6 100644 --- a/wgshare/lib/pages/homePage/home_state.dart +++ b/wgshare/lib/pages/homePage/home_state.dart @@ -25,4 +25,7 @@ class HomeState { /// 总页数 final RxInt totalPage = 0.obs; + /// 退出计时 + DateTime? currentBackPressTime; + } diff --git a/wgshare/lib/pages/homePage/home_view.dart b/wgshare/lib/pages/homePage/home_view.dart index fdd2e24..fe845a9 100644 --- a/wgshare/lib/pages/homePage/home_view.dart +++ b/wgshare/lib/pages/homePage/home_view.dart @@ -19,7 +19,6 @@ class HomePage extends StatefulWidget { } class HomePageState extends State with AutomaticKeepAliveClientMixin { - final logic = Get.put(HomeLogic()); final state = Get.find().state; @@ -36,155 +35,180 @@ class HomePageState extends State with AutomaticKeepAliveClientMixin { @override Widget build(BuildContext context) { super.build(context); - return Scaffold( - appBar: AppBar( - surfaceTintColor: Colors.white, - elevation: 0, - toolbarHeight: 0, - systemOverlayStyle: const SystemUiOverlayStyle( - statusBarColor: Colors.transparent, - systemNavigationBarColor: Color(0xFF000000), - systemNavigationBarIconBrightness: Brightness.light, - statusBarIconBrightness: Brightness.dark, - statusBarBrightness: Brightness.light, + return PopScope( + canPop: false, + onPopInvoked: (bool didPop) async { + if (didPop) { + return; + } + if (logic.closeOnConfirm(context)) { + // 系统级别导航栈 退出程序 + SystemNavigator.pop(); + } + }, + child: Scaffold( + appBar: AppBar( + surfaceTintColor: Colors.white, + elevation: 0, + toolbarHeight: 0, + systemOverlayStyle: const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarColor: Color(0xFF000000), + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + ), + backgroundColor: Colors.white, ), - backgroundColor: Colors.white, - ), - body: Obx(() => Column( - children: [ - Container( - width: double.infinity, - height: 44.h, - alignment: Alignment.center, - color: Colors.white, - child: Text( - '会议列表', - style: TextStyle( - fontSize: 16.sp, - color: ColorUtil.Color_51_51_51, - fontWeight: FontWeight.w500 + body: Obx(() => Column( + children: [ + Container( + width: double.infinity, + height: 44.h, + alignment: Alignment.center, + color: Colors.white, + child: Text( + '会议列表', + style: TextStyle( + fontSize: 16.sp, + color: ColorUtil.Color_51_51_51, + fontWeight: FontWeight.w500), ), ), - ), - Expanded( - child: Container( - color: ColorUtil.Color_244_244_244, - child: SmartRefresher( - enablePullUp: true, - controller: state.refreshController, - onRefresh: logic.onRefresh, - onLoading: logic.onLoading, - child: ListView.builder( - itemBuilder: (context, index) { - return Container( - width: double.infinity, - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(6)), - color: Colors.white, - ), - margin: EdgeInsets.only(top: index == 0 ? 20 : 12, bottom: index == 19 ? 20 : 0, left: 16, right: 16), - padding: const EdgeInsets.all(12), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - state.meetingRooms.value[index].roomName, - style: TextStyle( - fontSize: 14.sp, - color: ColorUtil.Color_89_88_88, - fontWeight: FontWeight.w500 + Expanded( + child: Container( + color: ColorUtil.Color_244_244_244, + child: SmartRefresher( + enablePullUp: true, + controller: state.refreshController, + onRefresh: logic.onRefresh, + onLoading: logic.onLoading, + child: ListView.builder( + itemBuilder: (context, index) { + return Container( + width: double.infinity, + decoration: const BoxDecoration( + borderRadius: + BorderRadius.all(Radius.circular(6)), + color: Colors.white, + ), + margin: EdgeInsets.only( + top: index == 0 ? 20 : 12, + bottom: index == 19 ? 20 : 0, + left: 16, + right: 16), + padding: const EdgeInsets.all(12), + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + state.meetingRooms.value[index].roomName, + style: TextStyle( + fontSize: 14.sp, + color: ColorUtil.Color_89_88_88, + fontWeight: FontWeight.w500), ), - ), - Row( - children: [ - Image.asset( - 'assets/images/index_persons.png', - width: 16.w, - height: 16.h, - ), - Text( - '${state.meetingRooms.value[index].onlineUserCount}人', - style: TextStyle( - fontSize: 12.sp, - color: ColorUtil.Color_177_177_177, - ), - ), - ], - ) - ], - ), - SizedBox(height: 20.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Text( - state.meetingRooms.value[index].roomNum, - style: TextStyle( - fontSize: 12.sp, - color: ColorUtil.Color_177_177_177, - ), - ), - SizedBox(width: 6.w), - GestureDetector( - child: Image.asset( - 'assets/images/index_copy.png', + Row( + children: [ + Image.asset( + 'assets/images/index_persons.png', width: 16.w, height: 16.h, ), - onTap: (){ - Clipboard.setData(ClipboardData(text: state.meetingRooms.value[index].roomNum)); - ToastUtils.showSuccess("复制成功"); - }, - ) - ], - ), - GestureDetector( - child: Container( - width: 78.w, - height: 30.h, - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(6)), - color: ColorUtil.Color_85_117_242, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '进入', - style: TextStyle( - fontSize: 12.sp, - color: Colors.white, - ), + Text( + '${state.meetingRooms.value[index].onlineUserCount}人', + style: TextStyle( + fontSize: 12.sp, + color: ColorUtil.Color_177_177_177, ), - Image.asset( - 'assets/images/index_right.png', + ), + ], + ) + ], + ), + SizedBox(height: 20.h), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + state.meetingRooms.value[index] + .roomNum, + style: TextStyle( + fontSize: 12.sp, + color: ColorUtil.Color_177_177_177, + ), + ), + SizedBox(width: 6.w), + GestureDetector( + child: Image.asset( + 'assets/images/index_copy.png', width: 16.w, height: 16.h, - ) - ], - ), + ), + onTap: () { + Clipboard.setData(ClipboardData( + text: state.meetingRooms + .value[index].roomNum)); + ToastUtils.showSuccess("复制成功"); + }, + ) + ], ), - onTap: (){ - Get.toNamed(Routes.meetingMainPage, arguments: {"roomNumber": state.meetingRooms.value[index].roomNum}); - }, - ) - ], - ) - ], - ), - ); - }, - itemCount: state.meetingRooms.value.length, + GestureDetector( + child: Container( + width: 78.w, + height: 30.h, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(6)), + color: ColorUtil.Color_85_117_242, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text( + '进入', + style: TextStyle( + fontSize: 12.sp, + color: Colors.white, + ), + ), + Image.asset( + 'assets/images/index_right.png', + width: 16.w, + height: 16.h, + ) + ], + ), + ), + onTap: () { + Get.toNamed(Routes.meetingMainPage, + arguments: { + "roomNumber": state.meetingRooms + .value[index].roomNum + }); + }, + ) + ], + ) + ], + ), + ); + }, + itemCount: state.meetingRooms.value.length, + ), ), ), - ), - ) - ], - )) + ) + ], + ))), ); } diff --git a/wgshare/lib/pages/loginPage/login_logic.dart b/wgshare/lib/pages/loginPage/login_logic.dart index 7d2aa93..01c50f7 100644 --- a/wgshare/lib/pages/loginPage/login_logic.dart +++ b/wgshare/lib/pages/loginPage/login_logic.dart @@ -7,6 +7,7 @@ import 'package:wgshare/common/mixins/request_tool_mixin.dart'; import 'package:wgshare/utils/device_info.dart'; import 'package:wgshare/utils/toast_utils.dart'; +import '../../common/config/app_config.dart'; import '../../common/models/common/base_structure_result.dart'; import '../../common/models/user_info_entity.dart'; import '../../common/store/app_storage_key.dart'; @@ -46,16 +47,17 @@ class LoginLogic extends GetxController with RequestToolMixin { }else if(state.checkAgreementBool.value != true){ ToastUtils.showError("请阅读并勾选相关协议"); }else{ - try{ - ToastUtils.showLoading(); - BaseStructureResult res = await getClient().login(state.userNameController.text, md5.convert(utf8.encode(state.passwordController.text)).toString()); - if (null != res.data) { - UserStore.to.setToken(res.data!.token); - UserStore.to.setUserDetailInfo(res.data!); - Get.toNamed(Routes.startPage); - } - }finally{ - ToastUtils.dismiss(); + BaseStructureResult res = await getClient().login(state.userNameController.text, md5.convert(utf8.encode(state.passwordController.text)).toString()); + if (null != res.data) { + UserStore.to.setToken(res.data!.token); + UserStore.to.setUserDetailInfo(res.data!); + UserStore.to.setLoginType(AppConfig.NORMAL_LOGIN); + Get.offAllNamed(Routes.startPage); + state.userNameController.text = ""; + state.passwordController.text = ""; + state.meetingCodeController.text = ""; + state.nickNameCodeController.text = ""; + state.checkAgreementBool.value = false; } } } @@ -71,16 +73,39 @@ class LoginLogic extends GetxController with RequestToolMixin { }else if(state.checkAgreementBool != true){ ToastUtils.showError("请阅读并勾选相关协议"); }else{ - try{ - ToastUtils.showLoading(); - BaseStructureResult res = await getClient().anonLogin(await DeviceInfo.getDeviceId(),state.nickNameCodeController.text, state.meetingCodeController.text); - if (null != res.data) { - UserStore.to.setToken(res.data!.token); - UserStore.to.setUserDetailInfo(res.data!); - Get.toNamed(Routes.meetingMainPage, arguments: {"roomNumber": state.meetingCodeController.text}); + BaseStructureResult res = await getClient().anonLogin(await DeviceInfo.getDeviceId(),state.nickNameCodeController.text, state.meetingCodeController.text); + if (null != res.data) { + UserStore.to.setToken(res.data!.token); + UserStore.to.setUserDetailInfo(res.data!); + UserStore.to.setLoginType(AppConfig.ANONYMOUS_LOGIN); + Get.toNamed(Routes.meetingMainPage, arguments: {"roomNumber": state.meetingCodeController.text}); + state.userNameController.text = ""; + state.passwordController.text = ""; + state.meetingCodeController.text = ""; + state.nickNameCodeController.text = ""; + state.checkAgreementBool.value = false; + } + } + } + + /// 检验房间 + Future doHttpCheckMeetingRoom() async { + if(state.meetingCodeController.text.isEmpty){ + ToastUtils.showError("请输入会议号"); + }else if(state.meetingCodeController.text.length != 8){ + ToastUtils.showError("请输入正确的会议号"); + }else if(state.nickNameCodeController.text.isEmpty){ + ToastUtils.showError("请输入昵称"); + }else if(state.checkAgreementBool != true){ + ToastUtils.showError("请阅读并勾选相关协议"); + }else{ + BaseStructureResult res = await getClient().checkout(state.meetingCodeController.text); + if (null != res.data) { + if(res.data == true){ + doHttpAnonymousLogin(); + }else{ + ToastUtils.showError("房间不存在"); } - }finally{ - ToastUtils.dismiss(); } } } diff --git a/wgshare/lib/pages/loginPage/login_view.dart b/wgshare/lib/pages/loginPage/login_view.dart index 87a579c..ffca5fc 100644 --- a/wgshare/lib/pages/loginPage/login_view.dart +++ b/wgshare/lib/pages/loginPage/login_view.dart @@ -378,7 +378,7 @@ class _LoginPageState extends State { if (state.pageState.value == 0) { logic.doHttpLogin(); } else { - logic.doHttpAnonymousLogin(); + logic.doHttpCheckMeetingRoom(); } }, ) diff --git a/wgshare/lib/pages/metting/meeting_main_logic.dart b/wgshare/lib/pages/metting/meeting_main_logic.dart index 3a54584..d5b6778 100644 --- a/wgshare/lib/pages/metting/meeting_main_logic.dart +++ b/wgshare/lib/pages/metting/meeting_main_logic.dart @@ -1,8 +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'; @@ -13,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'; @@ -50,17 +54,20 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { state.meetingToken.value = res.data!; if (isInit == true) { - initRtc(); signalRSocket(); + }else{ + initRtc(); } } /// 合并请求 /// 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), @@ -92,6 +99,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { /// 获取会议室信息 void getMeetingRoomInfo(MeetingRoomInfo meetingRoomInfo) async { state.meetingRoomInfo.value = meetingRoomInfo; + initRtc(); startTime(); } @@ -99,6 +107,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { void getMeetingRoomAllUser(List meetingRoomUsers) async { state.users.value = meetingRoomUsers; state.cacheUsers.value = meetingRoomUsers; + update(); doHttpGetTvAnchor(); } @@ -138,6 +147,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { } else { state.users.value = state.cacheUsers.value; } + update(); } /// ------------------------------------------------------------------------------发言权限 @@ -195,6 +205,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { if (res.data!.toString().length != 9) { // 摄像头 if (state.remoteUid.value == UserStore.to.userInfoEntity.value!.uid) { + debugPrint("wgs输出===:当前全员观看是自己"); // 全员观看主播是自己 // 设置主播ID为0(自己) state.remoteUid.value = "0"; @@ -205,10 +216,22 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { // 有发言权限且开启了摄像头时,切换页面到视频状态 changePageState(1); } else { - // 没有发言权限或没开启摄像头时,切换页面到语音状态 - changePageState(0); + // 没有发言权限或没开启摄像头时,查看有没有其他人开摄像头,如果没有切换页面到语音状态,如果有切换到视频状态 + var isOtherOpenCamera = false; + for (var i = 0; i < state.cacheUsers.value.length; i++) { + if (state.cacheUsers.value[i].enableCamera == true) { + isOtherOpenCamera = true; + } + } + if(isOtherOpenCamera == false){ + changePageState(0); + }else{ + state.remoteUid.value = ""; + changePageState(1); + } } } else { + debugPrint("wgs输出===:当前全员观看不是自己"); // 全员观看主播不是自己 // 判断当前会议室是否存在全员观看主播 var isCurrentUserIsCamera = false; @@ -222,7 +245,10 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { if (isCurrentUserIsCamera == true) { // 当前会议室存在全员观看主播时,切换页面到视频状态 debugPrint("wgs输出===:当前会议室存在全员观看主播时,切换页面到视频状态"); - changePageState(1); + changePageState(0); + Future.delayed(const Duration(milliseconds: 200), () { + changePageState(1); + }); } else { // 当前会议室不存在全员观看主播时,设置主播ID为空并切换页面到视频状态 debugPrint("wgs输出===:当前会议室不存在全员观看主播时,设置主播ID为空并切换页面到视频状态"); @@ -231,14 +257,20 @@ 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{ - // 如果是别人在共享,切换页面到视频状态 - changePageState(1); - }*/ + } else { + // 共享不是自己 + for (var i = 0; i < state.cacheUsers.value.length; i++) { + if (state.remoteUid.value == + state.cacheUsers.value[i].screenShareId) { + state.cacheUsers.value[i].enableShare = true; + } + } + } } + update(); } /// 设置当前全员观看主播 @@ -300,14 +332,52 @@ 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); /// ------------------------------------------------------------------------------发言权限相关回调 /// 开启关闭发言权限 @@ -321,13 +391,22 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { for (MeetingRoomUser mru in state.cacheUsers.value) { if (mru.uid == meetingRoomUser.uid) { mru.roleId = meetingRoomUser.roleId; - mru.enableMicr = true; + if(state.defaulOpenState.value == 1){ + mru.enableMicr = true; + } mru.isRoomManager = meetingRoomUser.isRoomManager; } } if (UserStore.to.userInfoEntity.value!.uid == meetingRoomUser.uid) { state.isSpeak.value = true; - state.isOpenMicrophone.value = true; + debugPrint("wgs输出===:Socket-状态:${state.defaulOpenState.value}"); + if (state.defaulOpenState.value == 1) { + debugPrint("wgs输出===:Socket-状态1111111111111"); + doHttpSetMicr(true); + } else if (state.defaulOpenState.value == 2) { + debugPrint("wgs输出===:Socket-状态2222222222222"); + doHttpSetCamer(true); + } // 设置声网SDK角色为主播 setClientRole("主播"); debugPrint("wgs输出===:Socket-开启发言权限:主播"); @@ -338,16 +417,20 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { if (mru.uid == meetingRoomUser.uid) { mru.roleId = meetingRoomUser.roleId; mru.enableMicr = false; + mru.enableCamera = false; mru.isRoomManager = meetingRoomUser.isRoomManager; } } if (UserStore.to.userInfoEntity.value!.uid == meetingRoomUser.uid) { state.isSpeak.value = false; + state.defaulOpenState.value = 0; state.isOpenMicrophone.value = false; state.isOpenCamera.value = false; state.isOpenShare.value = false; - state.remoteUid.value = ""; + // 关闭麦克风视频 + doHttpSetMicr(false); + doHttpSetCamer(false); // 设置声网SDK角色为观众 setClientRole("观众"); // 取消发布视频流 @@ -356,14 +439,18 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { stopPreview(); // 关闭本地预览悬浮窗 state.floating.value?.close(); - // 切换页面状态 - changePageState(0); + // 当前全员观看主播是自己时 + if (state.remoteUid.value == "0") { + state.remoteUid.value = ""; + changePageState(0); + } // 停止共享屏幕(此版本不做) // stopScreenCapture(); debugPrint("wgs输出===:Socket-关闭发言权限:观众"); } } + update(); }); /// ------------------------------------------------------------------------------麦克风相关回调 @@ -379,6 +466,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { } state.users.value = state.cacheUsers.value; state.isOpenMicrophone.value = e?[0]; + update(); }); /// 用户单独开闭麦回调 @@ -416,6 +504,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { muteLocalAudioStream(true); } } + update(); }); /// ------------------------------------------------------------------------------会议室进出相关回调 @@ -429,6 +518,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { .toList(); state.cacheUsers.value.addAll(meetingRoomUsers); state.users.value = state.cacheUsers.value; + update(); debugPrint("wgs输出===:Socket-远端用户或主播加入会议室:$jsonStr"); }); @@ -445,6 +535,8 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { } } state.users.value = state.cacheUsers.value; + update(); + doHttpGetTvAnchor(); debugPrint("wgs输出===:Socket-远端用户或主播离开会议室:$jsonStr"); }); @@ -453,15 +545,21 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { // var jsonStr = const Utf8Decoder().convert(json.encode(e).runes.toList()); var jsonStr = json.encode(e); debugPrint("wgs输出===:Socket-被移除会议:$jsonStr"); - Get.back(); + state.isNormaExit.value = true; ToastUtils.showSuccess("你已被移除会议"); + Future.delayed(const Duration(seconds: 1), () { + Get.back(); + }); }); /// 全员结束会议回调 state.hubConnection.value?.on("AllLeave", (e) { debugPrint("wgs输出===:Socket-全员结束会议"); - Get.back(); + state.isNormaExit.value = true; ToastUtils.showSuccess("会议已结束"); + Future.delayed(const Duration(seconds: 1), () { + Get.back(); + }); }); /// ------------------------------------------------------------------------------会议室聊天相关回调 @@ -470,8 +568,9 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { // var jsonStr = const Utf8Decoder().convert(json.encode(e).runes.toList()); var jsonStr = json.encode(e); List list = json.decode(jsonStr); - MeetingRoomMsg meetingRoomMsg = - MeetingRoomMsg(list[0], list[1], list[2], 0); + DateTime dateTime = DateTime.now(); + 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), () { @@ -533,7 +632,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { stopPreview(); // 当前全员观看主播是自己时 if (state.remoteUid.value == "0") { - changePageState(0); + state.remoteUid.value = ""; } // 关闭本地预览悬浮窗 if (state.floating.value?.isShowing == true) { @@ -541,6 +640,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { } } } + update(); }); /// 设置新的全员观看视频主播回调 @@ -554,10 +654,17 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { } /// 加入会议室 - Future joinMeetingToSocket() async { + /// isAgain:是否重新加入 + Future joinMeetingToSocket(bool isAgain) async { await state.hubConnection.value?.invoke("joinChannel", args: [state.roomNumber.value, false, false, false]); - mergeFetch(); + mergeFetch(isAgain); + /*if(isAgain == false){ + mergeFetch(isAgain); + initRtc(false); + }else{ + mergeFetch(true); + }*/ } /// 离开会议室 @@ -569,11 +676,13 @@ class MeetingMainLogic extends GetxController with RequestToolMixin { /// 会议室发送消息 Future sendMsg(String msg) async { + DateTime dateTime = DateTime.now(); MeetingRoomMsg meetingRoomMsg = MeetingRoomMsg( UserStore.to.userInfoEntity.value!.uid, UserStore.to.userInfoEntity.value!.userName, msg, - 1); + 1, + formatDate(dateTime, [HH, ':', nn, ':', ss])); state.meetingRoomMsgs.value.add(meetingRoomMsg); update(); Future.delayed(const Duration(milliseconds: 100), () { @@ -608,129 +717,186 @@ 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); joinMeetingToRtc(); // 回调 state.rctEngine.value?.registerEventHandler( RtcEngineEventHandler( - // 成功加入会议室回调 - onJoinChannelSuccess: (RtcConnection connection, int elapsed) { - debugPrint("wgs输出===:RTC-自己加入会议室,ID:${connection.localUid}"); - }, + // 成功加入会议室回调 + onJoinChannelSuccess: (RtcConnection connection, int elapsed) { + state.isJoinSuccess.value = true; + 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 { - 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-音频路由切换为扬声器"); - } 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 { - // 共享屏幕(此版本不做) + // 远端用户或主播离开当前会议室回调-主播角色才能接收该回调 + 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; } } - }, + } + update(); + debugPrint("wgs输出===:RTC-远端用户或主播离开会议室,用户或主机的ID:$remoteUid"); + }, - // 用户音量提示回调 - 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!)}"); + // 音频路由发生变化回调 + 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 { + // 共享屏幕(此版本不做) + } + } + }, + + // 用户音量提示回调 + 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){ // 获取共享屏幕出错(此版本不做) @@ -738,7 +904,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 0f0f671..f2e9a20 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; @@ -60,6 +64,8 @@ class MeetingMainState { /// 是否被允许发言 late RxBool isSpeak = false.obs; + /// 允许发言后默认开启麦克风还是摄像头,0都关闭,1麦克风,2摄像头 + late RxInt defaulOpenState = 0.obs; /// 是否打开麦克风 late RxBool isOpenMicrophone = false.obs; /// 麦克风音量 @@ -70,6 +76,8 @@ class MeetingMainState { late RxBool isOpenShare = false.obs; /// 当前视频主播ID late RxString remoteUid = "".obs; + /// 是否成功加入会议室 + late RxBool isJoinSuccess = false.obs; /// 当前谁在说话 late RxString spokesman = "".obs; diff --git a/wgshare/lib/pages/metting/meeting_main_view.dart b/wgshare/lib/pages/metting/meeting_main_view.dart index 73365ad..bc00413 100644 --- a/wgshare/lib/pages/metting/meeting_main_view.dart +++ b/wgshare/lib/pages/metting/meeting_main_view.dart @@ -3,12 +3,14 @@ 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'; import 'package:get/get.dart'; import 'package:liquid_progress_indicator_v2/liquid_progress_indicator.dart'; import 'package:preload_page_view/preload_page_view.dart'; +import 'package:wgshare/common/config/app_config.dart'; import 'package:wgshare/common/store/user_store.dart'; import 'package:wgshare/utils/toast_utils.dart'; @@ -46,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: [ @@ -164,22 +169,6 @@ class MeetingMainPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - /// 结束发言 - Visibility( - visible: state.isSpeak.value, - child: GestureDetector( - child: Image.asset( - 'assets/images/meeting_main_hang_up.png', - width: 22.w, - height: 22.h, - ), - onTap: () { - logic.cancelSpeak(); - }, - ), - ), - SizedBox(width: 16.w), - /// 退出会议 GestureDetector( child: Image.asset( @@ -214,126 +203,162 @@ class MeetingMainPageState extends State { // 视频-共享 Visibility( visible: state.pageState.value == 1, - child: null != state.rctEngine.value + child: state.isJoinSuccess.value == true && null != state.rctEngine.value && null != state.users.value && state.users.value.isNotEmpty ? 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(); - } - } - }, - ), - - /// 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()), - - GestureDetector( - child: Container( - width: 180.w, - height: 40.h, - margin: - const EdgeInsets.only(left: 20, bottom: 40), - padding: const EdgeInsets.only(left: 20), - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(56), - topRight: Radius.circular(50), - bottomRight: Radius.circular(50)), - color: ColorUtil.Color_35_35_35_07, - border: Border.all( - width: 1.w, - color: ColorUtil.Color_99_111_158), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, + alignment: Alignment.center, children: [ - Image.asset( - 'assets/images/meeting_main_chat.png', - width: 18.w, - height: 18.h, + 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), + ) + ], + ), ), - SizedBox(width: 6.w), - Text( - '说点什么...', - style: TextStyle( - fontSize: 14.sp, - color: ColorUtil.Color_156_156_156), - ) ], + ) + : Container( + color: ColorUtil.Color_0_0_0_0, + /*child: Text('加载反馈:是否成功加入会议室${state.isJoinSuccess.value}-会议室对象${state.rctEngine.value}-成员列表${state.users.value.length}-全员观看ID${state.remoteUid.value}'),*/ + )), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: Container( + width: 200.w, + height: 40.h, + margin: const EdgeInsets.only( + left: 20, bottom: 40), + padding: const EdgeInsets.only(left: 20), + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(56), + topRight: Radius.circular(50), + bottomRight: Radius.circular(50)), + color: ColorUtil.Color_35_35_35_07, + border: Border.all( + width: 1.w, + color: ColorUtil.Color_99_111_158), + ), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + 'assets/images/meeting_main_chat.png', + width: 18.w, + height: 18.h, + ), + SizedBox(width: 6.w), + Text( + '说点什么...', + style: TextStyle( + fontSize: 14.sp, + color: ColorUtil.Color_156_156_156), + ) + ], + ), + ), + onTap: () { + Get.bottomSheet( + isScrollControlled: true, + chatBottomSheet(context)); + Future.delayed( + const Duration(milliseconds: 100), () { + state.chatController.jumpTo(state + .chatController + .position + .maxScrollExtent); + }); + }, ), - ), - onTap: () { - Get.bottomSheet( - isScrollControlled: true, - chatBottomSheet(context)); - Future.delayed(const Duration(milliseconds: 100), - () { - state.chatController.jumpTo(state - .chatController.position.maxScrollExtent); - }); - }, + Visibility( + visible: state.isSpeak.value, + child: GestureDetector( + child: Container( + width: 82.w, + height: 40.h, + margin: const EdgeInsets.only( + left: 12, bottom: 40, right: 20), + alignment: Alignment.center, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(99)), + color: ColorUtil.Color_255_69_69), + child: Text( + '结束发言', + style: TextStyle( + fontSize: 14.sp, + color: ColorUtil.Color_255_255_255), + ), + ), + onTap: () { + logic.cancelSpeak(); + }, + ), + ) + ], ) ], ), @@ -355,44 +380,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), @@ -403,7 +428,8 @@ class MeetingMainPageState extends State { if (state.isSpeak.value == false) { Get.bottomSheet( isScrollControlled: true, - applySpeakPermissionBottomSheet(context)); + applySpeakPermissionBottomSheet( + context, 1)); } else { if (state.isOpenMicrophone.value == false) { logic.doHttpSetMicr(true); @@ -422,14 +448,17 @@ 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, ), SizedBox(height: 4.h), Text( + /*state.isSpeak.value == false + ? '申请发言' + : */ state.isOpenCamera.value == true ? "关闭视频" : "开启视频", @@ -440,7 +469,12 @@ class MeetingMainPageState extends State { ], ), onTap: () { - if (state.isSpeak.value == true) { + if (state.isSpeak.value == false) { + Get.bottomSheet( + isScrollControlled: true, + applySpeakPermissionBottomSheet( + context, 2)); + } else { if (state.isOpenCamera.value == true) { logic.doHttpSetCamer(false); } else { @@ -518,7 +552,9 @@ class MeetingMainPageState extends State { meetingInfoFloatingLayer(), meetingAudioFloatingLayer(), ], - ))); + ))), + ) + ); } /// 退出会议底部弹窗 @@ -546,6 +582,7 @@ class MeetingMainPageState extends State { ), ), onTap: () { + state.isNormaExit.value = true; Get.back(); Get.back(); }, @@ -742,8 +779,13 @@ class MeetingMainPageState extends State { ), ), Expanded( - child: Container( - color: ColorUtil.Color_57_57_57_08, + child: GestureDetector( + child: Container( + color: ColorUtil.Color_57_57_57_08, + ), + onTap: () { + logic.changeMeetingInfoState(false); + }, )) ], ), @@ -757,8 +799,13 @@ class MeetingMainPageState extends State { child: Column( children: [ Expanded( - child: Container( - color: ColorUtil.Color_57_57_57_08, + child: GestureDetector( + child: Container( + color: ColorUtil.Color_57_57_57_08, + ), + onTap: () { + logic.changeMeetingAudioState(false); + }, )), Container( color: ColorUtil.Color_57_57_57_08, @@ -777,8 +824,13 @@ class MeetingMainPageState extends State { ), ), Expanded( - child: Container( - color: ColorUtil.Color_57_57_57_08, + child: GestureDetector( + child: Container( + color: ColorUtil.Color_57_57_57_08, + ), + onTap: () { + logic.changeMeetingAudioState(false); + }, )) ], ), @@ -838,7 +890,8 @@ class MeetingMainPageState extends State { } /// 申请发言权限底部弹窗 - Widget applySpeakPermissionBottomSheet(BuildContext context) { + Widget applySpeakPermissionBottomSheet( + BuildContext context, int defaulOpenState) { return Container( height: 240.h, color: ColorUtil.Color_7_9_11, @@ -936,6 +989,7 @@ class MeetingMainPageState extends State { ), ), onTap: () { + state.defaulOpenState.value = defaulOpenState; logic.doHttpApplySpeak(); Get.back(); }, @@ -1033,13 +1087,15 @@ class MeetingMainPageState extends State { Container( margin: const EdgeInsets.only(top: 18, left: 16, right: 16, bottom: 16), - child: Text( - '会议中(${state.users.value.length})', - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w500, - color: ColorUtil.Color_85_117_242), - ), + child: GetBuilder(builder: (controll) { + return Text( + '会议中(${state.users.value.length})', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: ColorUtil.Color_85_117_242), + ); + }), ), Expanded( child: ScrollConfiguration( @@ -1090,12 +1146,18 @@ class MeetingMainPageState extends State { ColorUtil.Color_244_244_244), ), ), - Text( - state.users.value[index].userName, - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w600, - color: ColorUtil.Color_243_243_243), + Container( + width: 76, + margin: const EdgeInsets.only(left: 6), + child: Text( + state.users.value[index].userName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + color: ColorUtil.Color_243_243_243), + ), ), SizedBox(width: 8.w), Visibility( @@ -1138,7 +1200,10 @@ class MeetingMainPageState extends State { children: [ Container( child: Image.asset( - 'assets/images/meeting_main_share_currently.png', + state.users.value[index].enableShare == + true + ? 'assets/images/meeting_main_share_currently.png' + : 'assets/images/meeting_main_share_currently_n.png', width: 17.w, height: 17.h, ), @@ -1322,10 +1387,23 @@ class MeetingMainPageState extends State { children: [ Container( margin: const EdgeInsets.only(left: 6), - child: Text( - state.meetingRoomMsgs.value[index].userName, - style: TextStyle( - fontSize: 10.sp, color: ColorUtil.Color_202_202_202), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: state.meetingRoomMsgs.value[index].userName, + style: TextStyle( + fontSize: 12.sp, color: ColorUtil.Color_202_202_202), + ), + TextSpan( + text: " ${state.meetingRoomMsgs.value[index].time}", + style: TextStyle( + fontSize: 10.sp, color: ColorUtil.Color_202_202_202 + ), + ), + ], + ), + textAlign: TextAlign.left, ), ), Container( @@ -1334,9 +1412,9 @@ class MeetingMainPageState extends State { left: 18, right: 18, top: 10, bottom: 10), decoration: const BoxDecoration( borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(99), - topRight: Radius.circular(99), - bottomRight: Radius.circular(99)), + bottomLeft: Radius.circular(30), + topRight: Radius.circular(30), + bottomRight: Radius.circular(30)), color: ColorUtil.Color_53_53_53, ), child: Text( @@ -1368,10 +1446,23 @@ class MeetingMainPageState extends State { children: [ Container( margin: const EdgeInsets.only(right: 6), - child: Text( - state.meetingRoomMsgs.value[index].userName, - style: TextStyle( - fontSize: 10.sp, color: ColorUtil.Color_202_202_202), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: "${state.meetingRoomMsgs.value[index].time} ", + style: TextStyle( + fontSize: 10.sp, color: ColorUtil.Color_202_202_202 + ), + ), + TextSpan( + text: state.meetingRoomMsgs.value[index].userName, + style: TextStyle( + fontSize: 12.sp, color: ColorUtil.Color_202_202_202), + ), + ], + ), + textAlign: TextAlign.right, ), ), Container( @@ -1380,9 +1471,9 @@ class MeetingMainPageState extends State { left: 18, right: 18, top: 10, bottom: 10), decoration: const BoxDecoration( borderRadius: BorderRadius.only( - topLeft: Radius.circular(99), - bottomLeft: Radius.circular(99), - bottomRight: Radius.circular(99)), + topLeft: Radius.circular(30), + bottomLeft: Radius.circular(30), + bottomRight: Radius.circular(30)), color: ColorUtil.Color_85_117_242, ), child: Text( @@ -1470,208 +1561,225 @@ class MeetingMainPageState extends State { var pageList = []; /// 大屏 - pageList.add(Stack( - alignment: Alignment.center, - children: [ - state.remoteUid.value != "" - ? AgoraVideoView( - controller: VideoViewController( - rtcEngine: state.rctEngine.value!, - canvas: VideoCanvas( - uid: int.tryParse(state.remoteUid.value), - setupMode: VideoViewSetupMode.videoViewSetupAdd), - ), - ) - : Container( - width: double.infinity, - color: ColorUtil.Color_57_57_57, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - 'assets/images/metting_main_no_person.png', - width: 138.w, - height: 144.h, - ), - const SizedBox(height: 20), - Text( - '主持人正在赶来的路上...', - style: TextStyle( - color: ColorUtil.Color_255_255_255, fontSize: 14.sp), - ) - ], - ), - ), - Positioned( - top: 16, - right: 16, - child: Container( - height: 30, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: ColorUtil.Color_0_0_0_96), - padding: const EdgeInsets.only(left: 12, right: 12), - child: Row( + pageList.add(GetBuilder(builder: (controll) { + return Stack( + alignment: Alignment.center, + children: [ + state.remoteUid.value != "" + ? AgoraVideoView( + controller: VideoViewController( + rtcEngine: state.rctEngine.value!, + canvas: VideoCanvas( + uid: int.tryParse(state.remoteUid.value), + setupMode: VideoViewSetupMode.videoViewSetupAdd, + renderMode: state.remoteUid.value.length == 9 + ? RenderModeType.renderModeFit + : RenderModeType.renderModeHidden), + ), + ) + : Container( + width: double.infinity, + color: ColorUtil.Color_57_57_57, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - '正在讲话:', - style: TextStyle( - fontSize: 10.sp, color: ColorUtil.Color_185_184_184), - ), - SizedBox( - width: 20.w, - height: 20.h, - child: LiquidCustomProgressIndicator( - value: state.spokesmanVolume.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/metting_main_no_person.png', + width: 138.w, + height: 144.h, ), + const SizedBox(height: 20), Text( - state.spokesman.value, + '主持人正在赶来的路上...', style: TextStyle( - fontSize: 10.sp, color: ColorUtil.Color_185_184_184), + color: ColorUtil.Color_255_255_255, fontSize: 14.sp), ) ], ), ), - ), - ], - )); + Positioned( + top: 16, + right: 16, + child: Container( + height: 30, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: ColorUtil.Color_0_0_0_96), + padding: const EdgeInsets.only(left: 12, right: 12), + child: Row( + children: [ + Text( + '正在讲话:', + style: TextStyle( + fontSize: 10.sp, color: ColorUtil.Color_185_184_184), + ), + SizedBox( + width: 20.w, + height: 20.h, + child: LiquidCustomProgressIndicator( + value: state.spokesmanVolume.value, + valueColor: const AlwaysStoppedAnimation( + ColorUtil.Color_2_177_136), + backgroundColor: ColorUtil.Color_255_255_255, + direction: Axis.vertical, + shapePath: ViewSvgPath.getMicrpphonePath()), + ), + Text( + state.spokesman.value, + style: TextStyle( + fontSize: 10.sp, color: ColorUtil.Color_185_184_184), + ) + ], + ), + ), + ), + ], + ); + })); - /// gridview - pageList.add(Container( - color: ColorUtil.Color_57_57_57, - child: GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, childAspectRatio: 0.8, crossAxisSpacing: 0), - itemCount: state.cacheUsers.value.length, - itemBuilder: (BuildContext ctx, index) { - return Stack( - children: [ - state.cacheUsers.value[index].enableCamera == true - ? state.cacheUsers.value[index].uid == - UserStore.to.userInfoEntity.value!.uid - ? AgoraVideoView( - controller: VideoViewController( - rtcEngine: state.rctEngine.value!, - canvas: const VideoCanvas( - uid: 0, - setupMode: - VideoViewSetupMode.videoViewSetupAdd)), + /// gridview + pageList.add(GetBuilder(builder: (controll){ + return Container( + color: ColorUtil.Color_57_57_57, + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, childAspectRatio: 0.8, crossAxisSpacing: 0), + itemCount: state.cacheUsers.value.length, + itemBuilder: (BuildContext ctx, index) { + return Stack( + children: [ + state.cacheUsers.value[index].enableCamera == true + ? state.cacheUsers.value[index].uid == UserStore.to.userInfoEntity.value!.uid + ? AgoraVideoView( + controller: VideoViewController( + rtcEngine: state.rctEngine.value!, + canvas: const VideoCanvas( + uid: 0, + setupMode: + VideoViewSetupMode.videoViewSetupAdd)), + ) + : AgoraVideoView( + controller: VideoViewController.remote( + rtcEngine: state.rctEngine.value!, + canvas: VideoCanvas( + uid: int.tryParse( + state.cacheUsers.value[index].uid), + setupMode: + VideoViewSetupMode.videoViewSetupAdd), + connection: RtcConnection( + channelId: state.roomNumber.value), + ), + ) + : Container( + color: ColorUtil.Color_16_19_13, + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/metting_main_no_person.png', + width: 78.w, + height: 84.h, + ), + const SizedBox(height: 12), + Text( + '该用户未开启摄像头...', + style: TextStyle( + color: ColorUtil.Color_255_255_255, + fontSize: 10.sp), ) - : AgoraVideoView( - controller: VideoViewController.remote( - rtcEngine: state.rctEngine.value!, - canvas: VideoCanvas( - uid: int.tryParse( - state.cacheUsers.value[index].uid), - setupMode: - VideoViewSetupMode.videoViewSetupAdd), - connection: RtcConnection( - channelId: state.roomNumber.value), - ), - ) - : Container( - color: ColorUtil.Color_16_19_13, - child: SizedBox( - width: double.infinity, - child: Column( + ], + ), + ), + ), + Positioned( + left: 4, + bottom: 4, + child: Row( + children: [ + Visibility( + visible: state.cacheUsers.value[index].uid == + UserStore.to.userInfoEntity.value!.uid, + child: Image.asset( + 'assets/images/meeting_main_own.png', + width: 24.w, + height: 24.h, + ), + ), + Container( + height: 20, + margin: const EdgeInsets.only(left: 4), + padding: const EdgeInsets.only(left: 4, right: 4), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + color: ColorUtil.Color_0_0_0_96), + child: state.cacheUsers.value[index].enableMicr == true + ? Row( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 20.w, + height: 20.h, + child: LiquidCustomProgressIndicator( + value: state.cacheUsers.value[index] + .volume ?? + 0.0, + valueColor: + const AlwaysStoppedAnimation( + ColorUtil.Color_2_177_136), + backgroundColor: + ColorUtil.Color_255_255_255, + direction: Axis.vertical, + shapePath: + ViewSvgPath.getMicrpphonePath()), + ), + SizedBox( + width: 70, + child: Text( + state.cacheUsers.value[index].userName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: ColorUtil.Color_255_255_255), + ) + ) + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( - 'assets/images/metting_main_no_person.png', - width: 78.w, - height: 84.h, + 'assets/images/meeting_main_microphone_open.png', + width: 20.w, + height: 20.h, ), - const SizedBox(height: 12), - Text( - '该用户未开启摄像头...', - style: TextStyle( - color: ColorUtil.Color_255_255_255, - fontSize: 10.sp), + SizedBox( + width: 70, + child: Text( + state.cacheUsers.value[index].userName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: ColorUtil.Color_255_255_255), + ) ) ], ), - ), - ), - Positioned( - left: 4, - bottom: 4, - child: Row( - children: [ - Visibility( - visible: state.cacheUsers.value[index].uid == - UserStore.to.userInfoEntity.value!.uid, - child: Image.asset( - 'assets/images/meeting_main_own.png', - width: 24.w, - height: 24.h, - ), - ), - Container( - height: 20, - margin: const EdgeInsets.only(left: 4), - padding: const EdgeInsets.only(left: 4, right: 4), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(2), - color: ColorUtil.Color_0_0_0_96), - child: state.cacheUsers.value[index].enableMicr == true - ? Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: 20.w, - height: 20.h, - child: LiquidCustomProgressIndicator( - value: state.cacheUsers.value[index] - .volume ?? - 0.0, - valueColor: - const AlwaysStoppedAnimation( - ColorUtil.Color_2_177_136), - backgroundColor: - ColorUtil.Color_255_255_255, - direction: Axis.vertical, - shapePath: - ViewSvgPath.getMicrpphonePath()), - ), - Text( - state.cacheUsers.value[index].userName, - style: TextStyle( - fontSize: 12.sp, - color: ColorUtil.Color_255_255_255), - ) - ], - ) - : Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Image.asset( - 'assets/images/meeting_main_microphone_open.png', - width: 20.w, - height: 20.h, - ), - Text( - state.cacheUsers.value[index].userName, - style: TextStyle( - fontSize: 12.sp, - color: ColorUtil.Color_255_255_255), - ) - ], - ), - ) - ], - ), - ) - ], - ); - }), - )); + ) + ], + ), + ) + ], + ); + }), + ); + })); + return pageList[position]; } } diff --git a/wgshare/lib/pages/metting/voice/meeting_main_voice_view.dart b/wgshare/lib/pages/metting/voice/meeting_main_voice_view.dart index cf83781..2ffa27c 100644 --- a/wgshare/lib/pages/metting/voice/meeting_main_voice_view.dart +++ b/wgshare/lib/pages/metting/voice/meeting_main_voice_view.dart @@ -72,11 +72,16 @@ class MeetingMainVoiceComponent extends StatelessWidget { shapePath: ViewSvgPath.getMicrpphonePath() ), ), - Text( - users[index].userName, - style: TextStyle( - fontSize: 12.sp, - color: ColorUtil.Color_255_255_255), + SizedBox( + width: 70, + child: Text( + users[index].userName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: ColorUtil.Color_255_255_255), + ), ) ], ) @@ -89,11 +94,16 @@ class MeetingMainVoiceComponent extends StatelessWidget { width: 20.w, height: 20.h, ), - Text( - users[index].userName, - style: TextStyle( - fontSize: 12.sp, - color: ColorUtil.Color_255_255_255), + SizedBox( + width: 70, + child: Text( + users[index].userName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: ColorUtil.Color_255_255_255), + ), ) ], ), diff --git a/wgshare/lib/pages/userPage/user_logic.dart b/wgshare/lib/pages/userPage/user_logic.dart index a83a0f2..41b1b09 100644 --- a/wgshare/lib/pages/userPage/user_logic.dart +++ b/wgshare/lib/pages/userPage/user_logic.dart @@ -11,6 +11,6 @@ class UserLogic extends GetxController with RequestToolMixin { /// 退出登录 void logout(){ UserStore.to.erase(); - Get.offAllNamed(Routes.loginPage); + Get.toNamed(Routes.loginPage); } } diff --git a/wgshare/lib/pages/userPage/user_view.dart b/wgshare/lib/pages/userPage/user_view.dart index cf1c8f0..9d97033 100644 --- a/wgshare/lib/pages/userPage/user_view.dart +++ b/wgshare/lib/pages/userPage/user_view.dart @@ -77,7 +77,7 @@ class UserPageState extends State { alignment: Alignment.center, child: Text( UserStore.to.userInfoEntity.value!.userName.length >= 3 - ? UserStore.to.userInfoEntity.value!.userName.substring(1,UserStore.to.userInfoEntity.value!.userName.length) + ? UserStore.to.userInfoEntity.value!.userName.substring(UserStore.to.userInfoEntity.value!.userName.length - 2,UserStore.to.userInfoEntity.value!.userName.length) : UserStore.to.userInfoEntity.value!.userName, style: TextStyle( fontSize: 30.sp, 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/color_util.dart b/wgshare/lib/utils/color_util.dart index 9bc5baa..68d30c9 100644 --- a/wgshare/lib/utils/color_util.dart +++ b/wgshare/lib/utils/color_util.dart @@ -71,6 +71,8 @@ class ColorUtil { static const Color_16_19_13 = Color.fromRGBO(16, 19, 13, 1); + static const Color_255_69_69 = Color.fromRGBO(255, 69, 69, 1); + /// 十六进制颜色, /// hex, 十六进制值,例如:0xffffff, /// alpha, 透明度 [0.0,1.0] 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 a9d7c01..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: @@ -214,6 +238,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.3.7" + date_format: + dependency: "direct main" + description: + name: date_format + sha256: a48254e60bdb7f1d5a15cac7f86e37491808056c0a99dbdc850841def4754ddc + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.9" device_info_plus: dependency: "direct main" description: @@ -246,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: @@ -546,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: @@ -586,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: @@ -690,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 07324ee..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,9 +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