diff --git a/wgshare/android/app/src/main/AndroidManifest.xml b/wgshare/android/app/src/main/AndroidManifest.xml index 47432b3..870a059 100644 --- a/wgshare/android/app/src/main/AndroidManifest.xml +++ b/wgshare/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,8 @@ + + + + + + NSCameraUsageDescription + App需要您的同意,才能访问摄像头 + + NSMicrophoneUsageDescription + App需要您的同意,才能访问麦克风 + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/wgshare/lib/common/api/retrofit_client.dart b/wgshare/lib/common/api/retrofit_client.dart index b3a6e48..28897f2 100644 --- a/wgshare/lib/common/api/retrofit_client.dart +++ b/wgshare/lib/common/api/retrofit_client.dart @@ -53,4 +53,18 @@ abstract class RetrofitClient { Future> getMeetingToken( @Query("roomNum") String roomNum, ); + + /// 申请发言 + @GET("/room/apply-speak") + Future applySpeak( + @Query("roomNum") String roomNum, + ); + + /// 结束发言 + @DELETE("/room/manager") + Future cancelSpeak( + @Field("roomId") String roomId, + @Field("roomNum") String roomNum, + @Field("userId") String userId, + ); } diff --git a/wgshare/lib/common/request/rest_dio.dart b/wgshare/lib/common/request/rest_dio.dart index ea046a7..43c3cb7 100644 --- a/wgshare/lib/common/request/rest_dio.dart +++ b/wgshare/lib/common/request/rest_dio.dart @@ -148,7 +148,7 @@ class TheError extends Interceptor { break; case DioExceptionType.badResponse: if (err.response == null) { - debugPrint('请求进入异常但是请求response'); + debugPrint('wgs输出===:请求进入异常但是请求response'); } else { Response? response = err.response; if (response != null) { diff --git a/wgshare/lib/common/store/user_store.dart b/wgshare/lib/common/store/user_store.dart index 2d3744f..ca91200 100644 --- a/wgshare/lib/common/store/user_store.dart +++ b/wgshare/lib/common/store/user_store.dart @@ -24,7 +24,7 @@ class UserStore extends GetxController with RequestToolMixin { userInfoEntity.value = UserInfoEntity.fromJson(userDetail); } } catch (err) { - debugPrint('$err'); + debugPrint('wgs输出===:$err'); StorageService.to.remove(AppStorageKey.userInfo.value); } if ((token?.isNotEmpty ?? false) && userInfoEntity.value != null) { diff --git a/wgshare/lib/main.dart b/wgshare/lib/main.dart index f5bca2f..ba49f10 100644 --- a/wgshare/lib/main.dart +++ b/wgshare/lib/main.dart @@ -85,7 +85,7 @@ class MyApp extends StatelessWidget { getPages: AppPages.pages, routingCallback: (routing) { String? currentRouter = routing?.current; - debugPrint("当前路由:${routing?.current}"); + debugPrint("wgs输出===:当前路由:${routing?.current}"); }, builder: EasyLoading.init( builder: (context, child) { diff --git a/wgshare/lib/pages/metting/meeting_main_logic.dart b/wgshare/lib/pages/metting/meeting_main_logic.dart index 14b2474..873058c 100644 --- a/wgshare/lib/pages/metting/meeting_main_logic.dart +++ b/wgshare/lib/pages/metting/meeting_main_logic.dart @@ -1,17 +1,17 @@ import 'dart:async'; +import 'dart:convert'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:signalr_core/signalr_core.dart'; import 'package:wgshare/common/store/user_store.dart'; - import '../../common/config/request_config.dart'; import '../../common/mixins/request_tool_mixin.dart'; import '../../common/models/common/base_structure_result.dart'; import '../../common/models/meeting_room_info.dart'; import '../../common/models/meeting_room_user.dart'; +import '../../utils/permission_handler.dart'; import '../../utils/toast_utils.dart'; import 'meeting_main_state.dart'; @@ -34,10 +34,20 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{ super.onClose(); state.memberNameSearchController.dispose(); stopTime(); - leaveMeeting(); + leaveMeetingToRtc(); + leaveMeetingToSocket(); state.hubConnection.value?.stop(); } + /// 获取会议室Token + Future doHttpGetMeetingToken() async { + BaseStructureResult res = await getClient().getMeetingToken(state.roomNumber.value); + state.meetingToken.value = res.data!; + + initRtc(); + signalRSocket(); + } + /// 合并请求 /// 1.获取会议室信息 /// 2.获取会议室所有用户 @@ -54,26 +64,6 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{ ToastUtils.dismiss(); } - /// Socket长连接 - Future signalRSocket() async { - state.hubConnection.value = HubConnectionBuilder().withUrl('${RequestConfig().baseUrl}/session-manage', - HttpConnectionOptions( - transport: HttpTransportType.longPolling, - accessTokenFactory: () async => await Future.value(UserStore.to.token), - logging: (level, message) => debugPrint("SignalR Socket:$message"), - )).build(); - - await state.hubConnection.value?.start(); - - joinChannelToSocket(); - } - - /// 加入会议室 - Future joinChannelToSocket() async { - await state.hubConnection.value?.invoke("joinChannel", args: [state.roomNumber.value, false, false, false]); - mergeFetch(); - } - /// 改变会议信息浮层显示状态 void changeMeetingInfoState(bool isShow){ state.isShowMeetingInfoFloatingLayer.value = isShow; @@ -101,15 +91,6 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{ state.cacheUsers.value = meetingRoomUsers; } - /// 获取会议室Token - Future doHttpGetMeetingToken() async { - BaseStructureResult res = await getClient().getMeetingToken(state.roomNumber.value); - state.meetingToken.value = res.data!; - - initRtc(); - signalRSocket(); - } - /// 启动计时 void startTime(){ state.stopwatch.value.start(); @@ -136,8 +117,8 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{ void searchMember(String value){ if(value.isNotEmpty){ List memberSearchList = []; - for(var i = 0; i < state.cacheUsers.length; i++){ - if(state.cacheUsers[i].userName.contains(value)){ + for(var i = 0; i < state.cacheUsers.value.length; i++){ + if(state.cacheUsers.value[i].userName.contains(value)){ memberSearchList.add(state.cacheUsers.value[i]); } } @@ -147,12 +128,104 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{ } } + /// 申请发言 + Future doHttpApplySpeak() async { + BaseStructureResult res = await getClient().applySpeak(state.roomNumber.value); + } + + /// 结束发言 + Future doHttpCancelSpeak() async { + BaseStructureResult res = await getClient().cancelSpeak(state.meetingRoomInfo.value!.id, state.meetingRoomInfo.value!.roomNum, UserStore.to.userInfoEntity.value!.uid); + } + + /// --------------------------signalR Socket相关 + /// Socket长连接 + Future signalRSocket() async { + state.hubConnection.value = HubConnectionBuilder().withUrl('${RequestConfig().baseUrl}/session-manage', + HttpConnectionOptions( + transport: HttpTransportType.longPolling, + accessTokenFactory: () async => await Future.value(UserStore.to.token), + logging: (level, message) => debugPrint("wgs输出===:SignalR Socket-$message"), + )).build(); + + await state.hubConnection.value?.start(); + + joinMeetingToSocket(); + + /// 远端用户或主播加入会议室回调 + state.hubConnection.value?.on("UserJoined", (user){ + var jsonStr = json.encode(user); + var listDynamic = jsonDecode(jsonStr); + List meetingRoomUsers = (listDynamic as List).map((e) => MeetingRoomUser.fromJson((e as Map))).toList(); + state.cacheUsers.value.addAll(meetingRoomUsers); + state.users.value = state.cacheUsers.value; + debugPrint("wgs输出===:Socket-远端用户或主播加入会议室:$jsonStr"); + }); + + /// 远端用户或主播离开会议室回调 + state.hubConnection.value?.on("UserLeave", (uid){ + var jsonStr = json.encode(uid); + List listDynamic = jsonDecode(jsonStr); + for(String uidStr in listDynamic){ + for(var j = 0; j < state.cacheUsers.value.length; j++){ + if(state.cacheUsers.value[j].uid == uidStr){ + state.cacheUsers.value.removeAt(j); + } + } + } + state.users.value = state.cacheUsers.value; + debugPrint("wgs输出===:Socket-远端用户或主播离开会议室:$jsonStr"); + }); + + /// 申请/结束发言-管理员同意或拒绝回调 + state.hubConnection.value?.on("ManagerRefresh", (e){ + var jsonStr = json.encode(e?[0]); + var listDynamic = jsonDecode(jsonStr); + MeetingRoomUser meetingRoomUser = MeetingRoomUser.fromJson(listDynamic); + if(meetingRoomUser.isRoomManager == true){ + debugPrint("wgs输出===:Socket-申请发言:${e?[0]}--${e?[1]}"); + for(MeetingRoomUser mru in state.cacheUsers.value){ + if(mru.uid == meetingRoomUser.uid){ + mru.roleId = meetingRoomUser.roleId; + mru.enableMicr = true; + mru.isRoomManager = meetingRoomUser.isRoomManager; + } + } + state.isSpeak.value = true; + setEnableLocalAudio(true); + }else{ + debugPrint("wgs输出===:Socket-停止发言:${e?[0]}--${e?[1]}"); + for(MeetingRoomUser mru in state.cacheUsers.value){ + if(mru.uid == meetingRoomUser.uid){ + mru.roleId = meetingRoomUser.roleId; + mru.enableMicr = false; + mru.isRoomManager = meetingRoomUser.isRoomManager; + } + } + state.isSpeak.value = false; + setEnableLocalAudio(false); + } + + }); + } + + /// 加入会议室 + Future joinMeetingToSocket() async { + await state.hubConnection.value?.invoke("joinChannel", args: [state.roomNumber.value, false, false, false]); + mergeFetch(); + } + + /// 离开会议室 + Future leaveMeetingToSocket() async { + await state.hubConnection.value?.invoke("levelChannel", args: [state.roomNumber.value]); + } + + /// --------------------------声网SDK相关 /// 初始化声网SDK Future initRtc() async { - // 请求麦克风权限 - if (defaultTargetPlatform == TargetPlatform.android) { - await [Permission.microphone].request(); - } + // 请求麦克风、摄像头权限 + PermissionHandler.requestCameraPermission(); + PermissionHandler.requestMicrophonePermission(); // 创建 RtcEngine 对象 state.rctEngine.value = createAgoraRtcEngine(); @@ -163,29 +236,58 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{ channelProfile: ChannelProfileType.channelProfileLiveBroadcasting, )); - joinChannelToRtc(); + // 设置默认音频路由为听筒 + state.rctEngine.value?.setDefaultAudioRouteToSpeakerphone(false); + + joinMeetingToRtc(); // 回调 state.rctEngine.value?.registerEventHandler( RtcEngineEventHandler( - // 成功加入频道回调 - onJoinChannelSuccess: (RtcConnection connection, int elapsed) { - debugPrint("自己加入会议室,ID:${connection.localUid}"); - }, - // 远端用户或主播加入当前频道回调 - onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) { - debugPrint("远端用户或主播加入会议室,用户或主机的ID:$remoteUid"); - }, - // 远端用户或主播离开当前频道回调 - onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) { - debugPrint("远端用户或主播离开会议室,用户或主机的ID:$remoteUid"); - }, + + // 成功加入会议室回调 + onJoinChannelSuccess: (RtcConnection connection, int elapsed) { + 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"); + }, + + // 远端用户或主播离开当前会议室回调-主播角色才能接收该回调 + 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"); + } ), ); } /// 加入会议室 - Future joinChannelToRtc() async { + Future joinMeetingToRtc() async { await state.rctEngine.value?.joinChannel( token: state.meetingToken.value, channelId: state.roomNumber.value, @@ -199,16 +301,30 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{ publishCameraTrack: false, // 发布麦克风采集的音频 publishMicrophoneTrack: false, - // 设置用户角色为clientRoleAudience(观众) - clientRoleType: ClientRoleType.clientRoleAudience), + // 设置用户角色为 clientRoleBroadcaster(主播)或 clientRoleAudience(观众) + // 这里设置角色为clientRoleBroadcaster(主播) + // 主播:可以在频道内发布音视频,同时也可以订阅其他主播发布的音视频 + // 观众:可以在频道内订阅音视频,不具备发布音视频权限 + clientRoleType: ClientRoleType.clientRoleBroadcaster), ); } /// 离开会议室 - Future leaveMeeting() async { + Future leaveMeetingToRtc() async { // 离开 await state.rctEngine.value?.leaveChannel(); // 释放资源 await state.rctEngine.value?.release(); } + + /// 设置音频输出路由(没有外接设备时生效) + Future setEnableSpeakerphone(int mode) async { + state.communicationMode.value = mode; + await state.rctEngine.value?.setEnableSpeakerphone(mode == 1 ? false : true); + } + + /// 设置是否打开本地音频采集 + Future setEnableLocalAudio(bool enabled) async { + await state.rctEngine.value?.enableLocalAudio(enabled); + } } diff --git a/wgshare/lib/pages/metting/meeting_main_state.dart b/wgshare/lib/pages/metting/meeting_main_state.dart index 63b3ab2..dac8512 100644 --- a/wgshare/lib/pages/metting/meeting_main_state.dart +++ b/wgshare/lib/pages/metting/meeting_main_state.dart @@ -49,6 +49,9 @@ class MeetingMainState { /// 搜索用户时,缓存会议室所有用户原始数据 late RxList cacheUsers = RxList([]); + /// 是否被允许发言 + late RxBool isSpeak = false.obs; + /// signalR 长连接相关 late RxString serviceUrl = "http://192.168.2.9:5192/session-manage".obs; late Rx hubConnection = Rx(null); @@ -64,4 +67,6 @@ class MeetingMainState { late RxBool isPublishCameraTrack = false.obs; /// 是否发布麦克风采集的音频 late RxBool isPublishMicrophoneTrack = false.obs; + /// 当前音频路由,1:听筒,3:扬声器 + late RxInt communicationMode = 1.obs; } diff --git a/wgshare/lib/pages/metting/meeting_main_view.dart b/wgshare/lib/pages/metting/meeting_main_view.dart index 38dae84..84e454e 100644 --- a/wgshare/lib/pages/metting/meeting_main_view.dart +++ b/wgshare/lib/pages/metting/meeting_main_view.dart @@ -60,12 +60,14 @@ class MeetingMainPage extends StatelessWidget { SizedBox(width: 16.w), GestureDetector( child: Image.asset( - 'assets/images/meeting_main_audio.png', + state.communicationMode.value == 1 ? 'assets/images/index_copy.png' : state.communicationMode.value == 3 ? 'assets/images/meeting_main_camera_open.png' : 'assets/images/meeting_main_audio.png', width: 20.w, height: 20.h, ), onTap: (){ - logic.changeMeetingAudioState(true); + if(state.communicationMode.value == 1 || state.communicationMode.value == 3){ + logic.changeMeetingAudioState(true); + } }, ), Row( @@ -146,7 +148,7 @@ class MeetingMainPage extends StatelessWidget { // 语音 Visibility( visible: true, - child: MeetingMainVoiceComponent(users: state.cacheUsers.value) + child: MeetingMainVoiceComponent(users: state.cacheUsers) ), // 视频 @@ -226,7 +228,7 @@ class MeetingMainPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( - 'assets/images/meeting_main_microphone_default.png', + state.isSpeak.value == true ? 'assets/images/meeting_main_microphone_mute.png' : 'assets/images/meeting_main_microphone_default.png', width: 22.w, height: 22.h, ), @@ -235,16 +237,20 @@ class MeetingMainPage extends StatelessWidget { ' 静音 ', style: TextStyle( fontSize: 12.sp, - color: ColorUtil.Color_202_202_202), + color: state.isSpeak.value == true ? ColorUtil.Color_85_117_242 : ColorUtil.Color_202_202_202), ) ], ), onTap: () { - Get.bottomSheet( - isScrollControlled: true, - applySpeakPermissionBottomSheet( - context) - ); + if(state.isSpeak.value == false){ + Get.bottomSheet( + isScrollControlled: true, + applySpeakPermissionBottomSheet( + context) + ); + }else{ + logic.doHttpCancelSpeak(); + } }, ), @@ -631,11 +637,12 @@ class MeetingMainPage extends StatelessWidget { GestureDetector( child: Column(children: [ Text( - '蓝牙设备', + '听筒', style: TextStyle( fontSize: 14.sp, - fontWeight: FontWeight.w500, - color: ColorUtil.Color_85_117_242), + fontWeight: state.communicationMode.value == 1 ? FontWeight.w500 : FontWeight.w400, + color: state.communicationMode.value == 1 ? ColorUtil.Color_85_117_242 : ColorUtil.Color_134_134_134 + ), ), Container( width: double.infinity, @@ -645,31 +652,31 @@ class MeetingMainPage extends StatelessWidget { ) ]), onTap: (){ + logic.setEnableSpeakerphone(1); logic.changeMeetingAudioState(false); }, ) ); - audioList.add(Column(children: [ - Text( - '手机听筒', - style: TextStyle(fontSize: 14.sp, color: ColorUtil.Color_134_134_134), - ), - Container( - width: double.infinity, - height: 1.h, - color: ColorUtil.Color_49_47_47, - margin: const EdgeInsets.only(top: 14, bottom: 14), + audioList.add( + GestureDetector( + child: Column( + children: [ + Text( + '扬声器', + style: TextStyle( + fontSize: 14.sp, + fontWeight: state.communicationMode.value == 3 ? FontWeight.w500 : FontWeight.w400, + color: state.communicationMode.value == 3 ? ColorUtil.Color_85_117_242 : ColorUtil.Color_134_134_134 + ), + ), + ], + ), + onTap: (){ + logic.setEnableSpeakerphone(3); + logic.changeMeetingAudioState(false); + }, ) - ])); - audioList.add(Column( - children: [ - Text( - '扬声器', - style: - TextStyle(fontSize: 14.sp, color: ColorUtil.Color_134_134_134), - ) - ], - )); + ); return audioList; } @@ -754,21 +761,27 @@ class MeetingMainPage extends StatelessWidget { ), ), Expanded( - child: Container( - height: 44.h, - margin: const EdgeInsets.only(left: 6), - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - color: ColorUtil.Color_85_117_242, - ), - alignment: Alignment.center, - child: Text( - '申请', - style: TextStyle( - fontSize: 14.sp, - color: ColorUtil.Color_238_238_238, + child: GestureDetector( + child: Container( + height: 44.h, + margin: const EdgeInsets.only(left: 6), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: ColorUtil.Color_85_117_242, + ), + alignment: Alignment.center, + child: Text( + '申请', + style: TextStyle( + fontSize: 14.sp, + color: ColorUtil.Color_238_238_238, + ), ), ), + onTap: (){ + logic.doHttpApplySpeak(); + Get.back(); + }, ), ) ], @@ -901,8 +914,8 @@ class MeetingMainPage extends StatelessWidget { height: 36.h, alignment: Alignment.center, child: Text( - state.users[index].userName.length >= 3 - ? state.users[index].userName.substring(1,state.users[index].userName.length) + state.users[index].userName.length > 3 + ? state.users[index].userName.substring(state.users[index].userName.length - 2,state.users[index].userName.length) : state.users[index].userName, style: TextStyle( fontSize: 12.sp, @@ -919,6 +932,7 @@ class MeetingMainPage extends StatelessWidget { ), SizedBox(width: 8.w), Visibility( + visible: state.users[index].roleId == "1" || state.users[index].roleId == "3" ? true : false, child: Text( '主持人', style: TextStyle( @@ -926,7 +940,16 @@ class MeetingMainPage extends StatelessWidget { fontWeight: FontWeight.w500, color: ColorUtil.Color_2_177_136), ), - visible: state.users[index].isRoomManager, + ), + Visibility( + visible: state.users[index].roleId == "2" && state.users[index].isRoomManager == true ? true : false, + child: Text( + '发言人', + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: ColorUtil.Color_2_177_136), + ), ) ], ), diff --git a/wgshare/lib/pages/metting/video/meeting_main_video_view.dart b/wgshare/lib/pages/metting/video/meeting_main_video_view.dart index 58227cc..c41f545 100644 --- a/wgshare/lib/pages/metting/video/meeting_main_video_view.dart +++ b/wgshare/lib/pages/metting/video/meeting_main_video_view.dart @@ -29,7 +29,7 @@ class MeetingMainVideoComponent extends StatelessWidget { pageSnapping: true, onPageChanged: (index) { // 监听事件 - debugPrint('index=====$index'); + debugPrint('wgs输出===:$index'); }, children: [ Container( 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 002e564..436281c 100644 --- a/wgshare/lib/pages/metting/voice/meeting_main_voice_view.dart +++ b/wgshare/lib/pages/metting/voice/meeting_main_voice_view.dart @@ -9,7 +9,7 @@ import 'meeting_main_voice_logic.dart'; import 'meeting_main_voice_state.dart'; class MeetingMainVoiceComponent extends StatelessWidget { - MeetingMainVoiceComponent({Key? key, required this.users}) : super(key: key); + MeetingMainVoiceComponent({super.key, required this.users}); final List users; @@ -26,7 +26,7 @@ class MeetingMainVoiceComponent extends StatelessWidget { behavior: CusBehavior(), child: GridView.builder( gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( + const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, childAspectRatio: 0.7, crossAxisSpacing: 20), @@ -44,8 +44,8 @@ class MeetingMainVoiceComponent extends StatelessWidget { height: 76.h, alignment: Alignment.center, child: Text( - users[index].userName.length >= 3 - ? users[index].userName.substring(1,users[index].userName.length) + users[index].userName.length > 3 + ? users[index].userName.substring(users[index].userName.length - 2,users[index].userName.length) : users[index].userName, style: TextStyle( fontSize: 22.sp, diff --git a/wgshare/lib/utils/cached_network_img.dart b/wgshare/lib/utils/cached_network_img.dart index 3ace3d2..d4e2bcd 100644 --- a/wgshare/lib/utils/cached_network_img.dart +++ b/wgshare/lib/utils/cached_network_img.dart @@ -25,7 +25,7 @@ Widget $theCachedNetworkImage(ImageWidgetBuilder imageBuilder, {required String // placeholder: (context, url) => const CircularProgressIndicator(), placeholder: (context, url) => Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)), errorListener: (e) { - debugPrint('图片报错.............$e'); + debugPrint('wgs输出===:图片报错.............$e'); }, errorWidget: (context, url, error) { return GestureDetector( diff --git a/wgshare/lib/utils/device_info.dart b/wgshare/lib/utils/device_info.dart index 289943e..1e20f21 100644 --- a/wgshare/lib/utils/device_info.dart +++ b/wgshare/lib/utils/device_info.dart @@ -17,4 +17,10 @@ class DeviceInfo { return 'error'; } } + + static Future getAndroidSdkVersion() async { + DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); + AndroidDeviceInfo deviceInfo = await deviceInfoPlugin.androidInfo; + return deviceInfo.version.sdkInt; + } } \ No newline at end of file diff --git a/wgshare/lib/utils/permission/AndroidPermissionHandler.dart b/wgshare/lib/utils/permission/AndroidPermissionHandler.dart new file mode 100644 index 0000000..0b4e6cb --- /dev/null +++ b/wgshare/lib/utils/permission/AndroidPermissionHandler.dart @@ -0,0 +1,97 @@ +import 'dart:ffi'; + +import 'package:permission_handler/permission_handler.dart'; +import 'dart:io'; + +import '../device_info.dart'; + +class AndroidPermissionHandler { + Future requestAllPermissions() async { + await requestLocationPermission(); + await requestBluetoothPermission(); + await requestCameraPermission(); + await requestStoragePermission(); + await requestNotificationPermission(); + await requestPhonePermission(); + } + + Future requestLocationPermission() async { + PermissionStatus status; + if (Platform.isAndroid && await DeviceInfo.getAndroidSdkVersion() >= 29) { + status = await Permission.locationAlways.request(); + } else { + status = await Permission.locationWhenInUse.request(); + } + + if (status.isGranted) { + print("Android: 位置权限已授予"); + } else if (status.isPermanentlyDenied) { + print("Android: 位置权限被永久拒绝,请前往设置开启"); + } else { + print("Android: 位置权限被拒绝"); + } + } + + Future requestBluetoothPermission() async { + PermissionStatus status; + if (Platform.isAndroid && await DeviceInfo.getAndroidSdkVersion() >= 31) { + status = (await [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.bluetoothAdvertise, + ].request()) as PermissionStatus; + } else { + status = await Permission.bluetooth.request(); + } + + if (status.isGranted) { + print("Android: 蓝牙权限已授予"); + } else { + print("Android: 蓝牙权限被拒绝"); + } + } + + Future requestCameraPermission() async { + PermissionStatus status = await Permission.camera.request(); + if (status.isGranted) { + print("Android: 相机权限已授予"); + } else if (status.isPermanentlyDenied) { + print("Android: 相机权限被永久拒绝,请前往设置开启"); + } else { + print("Android: 相机权限被拒绝"); + } + } + + Future requestStoragePermission() async { + PermissionStatus status = await Permission.storage.request(); + if (status.isGranted) { + print("Android: 存储权限已授予"); + } else if (status.isPermanentlyDenied) { + print("Android: 存储权限被永久拒绝,请前往设置开启"); + } else { + print("Android: 存储权限被拒绝"); + } + } + + Future requestNotificationPermission() async { + if (Platform.isAndroid && await DeviceInfo.getAndroidSdkVersion() >= 33) { + PermissionStatus status = await Permission.notification.request(); + if (status.isGranted) { + print("Android: 通知权限已授予"); + } else { + print("Android: 通知权限被拒绝"); + } + } + } + + Future requestPhonePermission() async { + PermissionStatus status = await Permission.phone.request(); + if (status.isGranted) { + print("Android: 电话权限已授予"); + } else if (status.isPermanentlyDenied) { + print("Android: 电话权限被永久拒绝,请前往设置开启"); + } else { + print("Android: 电话权限被拒绝"); + } + } +} \ No newline at end of file diff --git a/wgshare/lib/utils/permission/IosPermissionHandler.dart b/wgshare/lib/utils/permission/IosPermissionHandler.dart new file mode 100644 index 0000000..508e261 --- /dev/null +++ b/wgshare/lib/utils/permission/IosPermissionHandler.dart @@ -0,0 +1,74 @@ +import 'package:permission_handler/permission_handler.dart'; + +class IosPermissionHandler { + Future requestAllPermissions() async { + await requestLocationPermission(); + await requestBluetoothPermission(); + await requestCameraPermission(); + await requestStoragePermission(); + await requestNotificationPermission(); + await requestPhonePermission(); + } + + Future requestLocationPermission() async { + PermissionStatus status = await Permission.location.request(); + if (status.isGranted) { + print("iOS: 位置权限已授予"); + } else if (status.isPermanentlyDenied) { + print("iOS: 位置权限被永久拒绝,请前往设置开启"); + } else { + print("iOS: 位置权限被拒绝"); + } + } + + Future requestBluetoothPermission() async { + PermissionStatus status = await Permission.bluetooth.request(); + if (status.isGranted) { + print("iOS: 蓝牙权限已授予"); + } else { + print("iOS: 蓝牙权限被拒绝"); + } + } + + Future requestCameraPermission() async { + PermissionStatus status = await Permission.camera.request(); + if (status.isGranted) { + print("iOS: 相机权限已授予"); + } else if (status.isPermanentlyDenied) { + print("iOS: 相机权限被永久拒绝,请前往设置开启"); + } else { + print("iOS: 相机权限被拒绝"); + } + } + + Future requestStoragePermission() async { + PermissionStatus status = await Permission.photos.request(); + if (status.isGranted) { + print("iOS: 存储权限已授予"); + } else if (status.isPermanentlyDenied) { + print("iOS: 存储权限被永久拒绝,请前往设置开启"); + } else { + print("iOS: 存储权限被拒绝"); + } + } + + Future requestNotificationPermission() async { + PermissionStatus status = await Permission.notification.request(); + if (status.isGranted) { + print("iOS: 通知权限已授予"); + } else { + print("iOS: 通知权限被拒绝"); + } + } + + Future requestPhonePermission() async { + PermissionStatus status = await Permission.phone.request(); + if (status.isGranted) { + print("iOS: 电话权限已授予"); + } else if (status.isPermanentlyDenied) { + print("iOS: 电话权限被永久拒绝,请前往设置开启"); + } else { + print("iOS: 电话权限被拒绝"); + } + } +} \ No newline at end of file diff --git a/wgshare/lib/utils/permission_handler.dart b/wgshare/lib/utils/permission_handler.dart new file mode 100644 index 0000000..7213418 --- /dev/null +++ b/wgshare/lib/utils/permission_handler.dart @@ -0,0 +1,36 @@ +import 'package:flutter/cupertino.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:wgshare/utils/toast_utils.dart'; + +class PermissionHandler { + + /// 摄像头权限 + static Future requestCameraPermission() async { + PermissionStatus status = await Permission.camera.request(); + if (status.isGranted) { + debugPrint("wgs输出===:权限-摄像头权限已授予"); + } else if (status.isPermanentlyDenied) { + debugPrint("wgs输出===:权限-摄像头权限被永久拒绝,请前往设置开启"); + ToastUtils.showError("权限被永久拒绝,请前往设置开启!"); + openAppSettings(); + } else { + debugPrint("wgs输出===:权限-摄像头权限被拒绝"); + ToastUtils.showError("权限被拒绝,可能会导致相关功能不可用!"); + } + } + + /// 麦克风权限 + static Future requestMicrophonePermission() async { + PermissionStatus status = await Permission.microphone.request(); + if (status.isGranted) { + debugPrint("wgs输出===:权限-麦克风权限已授予"); + } else if (status.isPermanentlyDenied) { + debugPrint("wgs输出===:权限-麦克风权限被永久拒绝,请前往设置开启"); + ToastUtils.showError("权限被永久拒绝,请前往设置开启!"); + openAppSettings(); + } else { + debugPrint("wgs输出===:权限-麦克风权限被拒绝"); + ToastUtils.showError("权限被拒绝,可能会导致相关功能不可用!"); + } + } +} \ No newline at end of file