权限和app内前台升级

This commit is contained in:
fuenmao 2024-12-30 14:56:43 +08:00
parent 8df34b0fa7
commit 8f08223a18
19 changed files with 1433 additions and 650 deletions

View File

@ -1,8 +1,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 摄像头 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 麦克风 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 网络 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 安装 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- 写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 读权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 屏幕常亮 -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- 高android版本读写权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<application
android:label="智汇享"
@ -36,47 +49,19 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- 访问电话状态 -->
<uses-permission
android:name="android.permission.READ_PHONE_STATE"/>
<!-- 允许全部网络访问 -->
<uses-permission
android:name="android.permission.INTERNET"/>
<!-- 获取网络信息状态 -->
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- 获取当前WiFi接入的状态以及WLAN热点的信息 -->
<uses-permission
android:name="android.permission.ACCESS_WIFI_STATE"/>
<!-- 获取当前设备存储权限 -->
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.ACTION_MANAGE_UNKNOWN_APP_SOURCES"/>
<!-- 这个权限用于app安装 -->
<uses-permission
android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<!-- 屏幕常亮权限 -->
<uses-permission
android:name="android.permission.WAKE_LOCK"/>
<uses-permission
android:name="android.permission.CAMERA"/>
<!-- Permissions options for the `access notification policy` group -->
<uses-permission
android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
<!-- Permissions options for the `notification` group -->
<uses-permission
android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
<!-- ${applicationId}会被替换为你的应用ID -->
<!-- android:resource="@xml/file_paths"指定了存放共享文件路径的资源文件。 -->
</application>
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<root-path name="root" path="" />
<files-path name="files" path="" />
<cache-path name="cache" path="" />
<external-path name="external" path="" />
<external-files-path name="external_files" path="" />
<external-cache-path name="external_cache" path="" />
</paths>
</resources>

View File

@ -1,12 +1,16 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:signalr_core/signalr_core.dart';
import 'package:permission_handler/permission_handler.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 '../../routes/app_routes.dart';
import '../../utils/permission/PermissionService.dart';
import '../../utils/toast_utils.dart';
import '../../view/public_dialog.dart';
import '../../view/upgrade/loadJson/load_network_json.dart';
import 'home_state.dart';
class HomeLogic extends GetxController with RequestToolMixin {
@ -16,6 +20,7 @@ class HomeLogic extends GetxController with RequestToolMixin {
@override
void onInit() {
super.onInit();
doHttpCheckVersion(Get.context!);
doHttpGetMeetingRoomList(state.pageIndex.value,state.pageSize.value);
}
@ -73,5 +78,41 @@ class HomeLogic extends GetxController with RequestToolMixin {
return true;
}
///
void gotoMeetingRoom(BuildContext context, int index){
PermissionService.checkPermission(permissionList: [Permission.microphone,Permission.camera]).then((value){
if(value == true){
Get.back();
Get.toNamed(Routes.meetingMainPage,
arguments: {
"roomNumber": state.meetingRooms.value[index].roomNum
});
}else{
publicDialog(
context,
hideCancelBtn: true,
title: '请求权限说明',
describe: '进入会议室需要获取您的麦克风以及摄像头权限,用以确保能够正常参会发言。',
leftBtnStr: '拒绝',
rightBtnStr: '同意',
leftBtnCallback: (){
Get.toNamed(Routes.meetingMainPage,
arguments: {
"roomNumber": state.meetingRooms.value[index].roomNum
});
},
rightBtnCallback: (){
PermissionService.requestPermissions().then((value){
if(value == true){
Get.toNamed(Routes.meetingMainPage,
arguments: {
"roomNumber": state.meetingRooms.value[index].roomNum
});
}
});
}
);
}
});
}
}

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -189,11 +190,7 @@ class HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin {
),
),
onTap: () {
Get.toNamed(Routes.meetingMainPage,
arguments: {
"roomNumber": state.meetingRooms
.value[index].roomNum
});
logic.gotoMeetingRoom(context, index);
},
)
],

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:wgshare/common/mixins/request_tool_mixin.dart';
import 'package:wgshare/utils/device_info.dart';
import 'package:wgshare/utils/toast_utils.dart';
@ -10,15 +11,21 @@ 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';
import '../../common/store/user_store.dart';
import '../../routes/app_routes.dart';
import '../../utils/storage.dart';
import '../../utils/permission/PermissionService.dart';
import '../../view/public_dialog.dart';
import '../../view/upgrade/loadJson/load_network_json.dart';
import 'login_state.dart';
class LoginLogic extends GetxController with RequestToolMixin {
final LoginState state = LoginState();
@override
void onInit() {
super.onInit();
}
@override
void onClose() {
super.onClose();

View File

@ -39,7 +39,7 @@ class _LoginPageState extends State<LoginPage> {
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/login_bg.png'),
fit: BoxFit.fill,
fit: BoxFit.cover,
),
),
child: Column(

View File

@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_easyloading/flutter_easyloading.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 'package:wgshare/utils/count_microphone_volume.dart';
@ -696,9 +697,6 @@ class MeetingMainLogic extends GetxController with RequestToolMixin {
/// ------------------------------------------------------------------------------SDK相关
/// SDK
Future<void> initRtc() async {
//
PermissionService.requestPermissions();
// RtcEngine
state.rctEngine.value = createAgoraRtcEngineEx();

View File

@ -44,6 +44,9 @@ class MeetingMainState {
/// token
late RxString meetingToken = "".obs;
///
late RxBool isCameraToMicrophonePermissions = false.obs;
///
late Rx<MeetingRoomInfo?> meetingRoomInfo = Rx(null);
/// showOkAlertDialog

View File

@ -9,6 +9,7 @@ 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:permission_handler/permission_handler.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';
@ -16,6 +17,8 @@ import 'package:wgshare/utils/toast_utils.dart';
import '../../utils/color_util.dart';
import '../../utils/cus_behavior.dart';
import '../../utils/permission/PermissionService.dart';
import '../../view/public_dialog.dart';
import '../../view/view_svg_path.dart';
import 'meeting_main_logic.dart';
import 'meeting_main_state.dart';
@ -101,8 +104,10 @@ class MeetingMainPageState extends State<MeetingMainPage> {
height: 20.h,
),
onTap: () {
if (state.communicationMode.value == 1 ||
state.communicationMode.value == 3) {
if (state.communicationMode.value ==
1 ||
state.communicationMode.value ==
3) {
logic.changeMeetingAudioState(true);
}
},
@ -135,7 +140,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
Row(
children: [
Text(
state.meetingRoomInfo.value?.roomName ??
state.meetingRoomInfo.value
?.roomName ??
'',
style: TextStyle(
color: Colors.white,
@ -203,29 +209,39 @@ class MeetingMainPageState extends State<MeetingMainPage> {
// -
Visibility(
visible: state.pageState.value == 1,
child: state.isJoinSuccess.value == true && null != state.rctEngine.value && null != state.users.value && state.users.value.isNotEmpty
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,
itemBuilder:
(BuildContext context,
int position) =>
returnPage(position),
controller: PreloadPageController(
controller:
PreloadPageController(
initialPage: 0),
onPageChanged: (int position) {
state.pageIndex.value = position;
if (state.isSpeak.value == true &&
state.isOpenCamera.value ==
state.pageIndex.value =
position;
if (state.isSpeak.value ==
true &&
state.remoteUid.value != "0") {
state.isOpenCamera
.value ==
true &&
state.remoteUid.value !=
"0") {
if (position == 0) {
state.floating.value
?.open(context);
} else {
state.floating.value?.close();
state.floating.value
?.close();
}
}
},
@ -239,12 +255,13 @@ class MeetingMainPageState extends State<MeetingMainPage> {
Container(
width: 8.w,
height: 8.h,
margin: const EdgeInsets.only(
margin:
const EdgeInsets.only(
right: 6),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(
8),
BorderRadius
.circular(8),
color: state.pageIndex
.value ==
0
@ -256,12 +273,13 @@ class MeetingMainPageState extends State<MeetingMainPage> {
Container(
width: 8.w,
height: 8.h,
margin: const EdgeInsets.only(
margin:
const EdgeInsets.only(
left: 6),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(
8),
BorderRadius
.circular(8),
color: state.pageIndex
.value ==
1
@ -281,7 +299,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: Container(
@ -289,7 +308,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
height: 40.h,
margin: const EdgeInsets.only(
left: 20, bottom: 40),
padding: const EdgeInsets.only(left: 20),
padding:
const EdgeInsets.only(left: 20),
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(56),
@ -298,12 +318,14 @@ class MeetingMainPageState extends State<MeetingMainPage> {
color: ColorUtil.Color_35_35_35_07,
border: Border.all(
width: 1.w,
color: ColorUtil.Color_99_111_158),
color:
ColorUtil.Color_99_111_158),
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Image.asset(
'assets/images/meeting_main_chat.png',
@ -315,7 +337,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
'说点什么...',
style: TextStyle(
fontSize: 14.sp,
color: ColorUtil.Color_156_156_156),
color: ColorUtil
.Color_156_156_156),
)
],
),
@ -325,7 +348,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
isScrollControlled: true,
chatBottomSheet(context));
Future.delayed(
const Duration(milliseconds: 100), () {
const Duration(milliseconds: 100),
() {
state.chatController.jumpTo(state
.chatController
.position
@ -350,7 +374,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
'结束发言',
style: TextStyle(
fontSize: 14.sp,
color: ColorUtil.Color_255_255_255),
color: ColorUtil
.Color_255_255_255),
),
),
onTap: () {
@ -382,7 +407,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
? Image.asset(
state.isSpeak.value == false
? 'assets/images/meeting_main_sqfy.png'
: state.isOpenMicrophone.value ==
: state.isOpenMicrophone
.value ==
true
? 'assets/images/meeting_main_microphone_default.png'
: 'assets/images/meeting_main_sqfy.png',
@ -395,13 +421,14 @@ class MeetingMainPageState extends State<MeetingMainPage> {
height: 20.h,
child: LiquidCustomProgressIndicator(
value: state
.microphoneVolume.value,
.microphoneVolume
.value,
valueColor:
const AlwaysStoppedAnimation(
ColorUtil
.Color_2_177_136),
backgroundColor:
ColorUtil.Color_255_255_255,
backgroundColor: ColorUtil
.Color_255_255_255,
direction: Axis.vertical,
shapePath: ViewSvgPath
.getMicrpphonePath()),
@ -415,7 +442,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
Text(
state.isSpeak.value == false
? '申请发言'
: state.isOpenMicrophone.value == true
: state.isOpenMicrophone.value ==
true
? "手动静音"
: "解除静音",
style: TextStyle(
@ -431,7 +459,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
applySpeakPermissionBottomSheet(
context, 1));
} else {
if (state.isOpenMicrophone.value == false) {
if (state.isOpenMicrophone.value ==
false) {
logic.doHttpSetMicr(true);
} else {
logic.doHttpSetMicr(false);
@ -553,8 +582,7 @@ class MeetingMainPageState extends State<MeetingMainPage> {
meetingAudioFloatingLayer(),
],
))),
)
);
));
}
/// 退
@ -989,9 +1017,32 @@ class MeetingMainPageState extends State<MeetingMainPage> {
),
),
onTap: () {
PermissionService.checkPermission(permissionList: [Permission.microphone,Permission.camera]).then((value){
if(value == true){
state.defaulOpenState.value = defaulOpenState;
logic.doHttpApplySpeak();
Get.back();
}else{
publicDialog(context,
hideCancelBtn: true,
title: '请求权限说明',
describe:
'申请发言需要获取您的麦克风以及摄像头权限,用以确保能够正常进行会议。',
leftBtnStr: '拒绝',
rightBtnStr: '同意',
leftBtnCallback: () {
}, rightBtnCallback: () {
PermissionService.requestPermissions();
/*PermissionService.requestPermissions().then((value){
if(value == true){
state.defaulOpenState.value = defaulOpenState;
logic.doHttpApplySpeak();
Get.back();
}
});*/
});
}
});
},
),
)
@ -1156,7 +1207,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: ColorUtil.Color_243_243_243),
color:
ColorUtil.Color_243_243_243),
),
),
SizedBox(width: 8.w),
@ -1200,7 +1252,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
children: [
Container(
child: Image.asset(
state.users.value[index].enableShare ==
state.users.value[index]
.enableShare ==
true
? 'assets/images/meeting_main_share_currently.png'
: 'assets/images/meeting_main_share_currently_n.png',
@ -1393,13 +1446,14 @@ class MeetingMainPageState extends State<MeetingMainPage> {
TextSpan(
text: state.meetingRoomMsgs.value[index].userName,
style: TextStyle(
fontSize: 12.sp, color: ColorUtil.Color_202_202_202),
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
),
fontSize: 10.sp,
color: ColorUtil.Color_202_202_202),
),
],
),
@ -1452,13 +1506,14 @@ class MeetingMainPageState extends State<MeetingMainPage> {
TextSpan(
text: "${state.meetingRoomMsgs.value[index].time} ",
style: TextStyle(
fontSize: 10.sp, color: ColorUtil.Color_202_202_202
),
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),
fontSize: 12.sp,
color: ColorUtil.Color_202_202_202),
),
],
),
@ -1592,7 +1647,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
Text(
'主持人正在赶来的路上...',
style: TextStyle(
color: ColorUtil.Color_255_255_255, fontSize: 14.sp),
color: ColorUtil.Color_255_255_255,
fontSize: 14.sp),
)
],
),
@ -1649,14 +1705,15 @@ class MeetingMainPageState extends State<MeetingMainPage> {
return Stack(
children: [
state.cacheUsers.value[index].enableCamera == true
? state.cacheUsers.value[index].uid == UserStore.to.userInfoEntity.value!.uid
? 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)),
setupMode: VideoViewSetupMode
.videoViewSetupAdd)),
)
: AgoraVideoView(
controller: VideoViewController.remote(
@ -1714,7 +1771,8 @@ class MeetingMainPageState extends State<MeetingMainPage> {
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2),
color: ColorUtil.Color_0_0_0_96),
child: state.cacheUsers.value[index].enableMicr == true
child: state.cacheUsers.value[index].enableMicr ==
true
? Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
@ -1738,14 +1796,15 @@ class MeetingMainPageState extends State<MeetingMainPage> {
SizedBox(
width: 70,
child: Text(
state.cacheUsers.value[index].userName,
state
.cacheUsers.value[index].userName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12.sp,
color: ColorUtil.Color_255_255_255),
)
)
color:
ColorUtil.Color_255_255_255),
))
],
)
: Row(
@ -1760,14 +1819,15 @@ class MeetingMainPageState extends State<MeetingMainPage> {
SizedBox(
width: 70,
child: Text(
state.cacheUsers.value[index].userName,
state
.cacheUsers.value[index].userName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12.sp,
color: ColorUtil.Color_255_255_255),
)
)
color:
ColorUtil.Color_255_255_255),
))
],
),
)

View File

@ -17,9 +17,16 @@ class AndroidPermissionHandler {
await requestMicrophonePermission();
}
Future<void> requestCameraToMicrophonePermissions() async {
await requestCameraPermission();
await requestMicrophonePermission();
Future<bool> requestCameraToMicrophonePermissions() async {
var isCameraPermission = false;
var isMicrophonePermissio = false;
if(await requestCameraPermission() == true){
isCameraPermission = true;
}
if(await requestMicrophonePermission() == true){
isMicrophonePermissio = true;
}
return isCameraPermission == true && isMicrophonePermissio == true;
}
Future<void> requestLocationPermission() async {
@ -58,10 +65,12 @@ class AndroidPermissionHandler {
}
}
Future<void> requestCameraPermission() async {
Future<bool> requestCameraPermission() async {
var isCameraPermission = false;
PermissionStatus status = await Permission.camera.request();
if (status.isGranted) {
print("Android: 摄像头权限已授予");
isCameraPermission = true;
} else if (status.isPermanentlyDenied) {
print("Android: 摄像头权限被永久拒绝,请前往设置开启");
ToastUtils.showError("权限被永久拒绝,请前往设置开启!");
@ -70,10 +79,11 @@ class AndroidPermissionHandler {
print("Android: 摄像头权限被拒绝");
ToastUtils.showError("权限被拒绝,可能会导致相关功能不可用!");
}
return isCameraPermission;
}
Future<void> requestStoragePermission() async {
PermissionStatus status = await Permission.storage.request();
PermissionStatus status = await Permission.manageExternalStorage.request();
if (status.isGranted) {
print("Android: 存储权限已授予");
} else if (status.isPermanentlyDenied) {
@ -105,10 +115,12 @@ class AndroidPermissionHandler {
}
}
static Future<void> requestMicrophonePermission() async {
static Future<bool> requestMicrophonePermission() async {
var isMicrophonePermission = false;
PermissionStatus status = await Permission.microphone.request();
if (status.isGranted) {
print("Android: 麦克风权限已授予");
isMicrophonePermission = true;
} else if (status.isPermanentlyDenied) {
print("Android: 麦克风权限被永久拒绝,请前往设置开启");
ToastUtils.showError("权限被永久拒绝,请前往设置开启!");
@ -117,5 +129,6 @@ class AndroidPermissionHandler {
print("Android: 麦克风限被拒绝");
ToastUtils.showError("权限被拒绝,可能会导致相关功能不可用!");
}
return isMicrophonePermission;
}
}

View File

@ -13,9 +13,16 @@ class IosPermissionHandler {
await requestMicrophonePermission();
}
Future<void> requestCameraToMicrophonePermissions() async {
await requestCameraPermission();
await requestMicrophonePermission();
Future<bool> requestCameraToMicrophonePermissions() async {
var isCameraPermission = false;
var isMicrophonePermissio = false;
if(await requestCameraPermission() == true){
isCameraPermission = true;
}
if(await requestMicrophonePermission() == true){
isMicrophonePermissio = true;
}
return isCameraPermission == true && isMicrophonePermissio == true;
}
Future<void> requestLocationPermission() async {
@ -38,10 +45,12 @@ class IosPermissionHandler {
}
}
Future<void> requestCameraPermission() async {
Future<bool> requestCameraPermission() async {
var isCameraPermission = false;
PermissionStatus status = await Permission.camera.request();
if (status.isGranted) {
print("iOS: 摄像头权限已授予");
isCameraPermission = true;
} else if (status.isPermanentlyDenied) {
print("iOS: 摄像头权限被永久拒绝,请前往设置开启");
ToastUtils.showError("权限被永久拒绝,请前往设置开启!");
@ -50,6 +59,7 @@ class IosPermissionHandler {
print("iOS: 摄像头权限被拒绝");
ToastUtils.showError("权限被拒绝,可能会导致相关功能不可用!");
}
return isCameraPermission;
}
Future<void> requestStoragePermission() async {
@ -83,10 +93,12 @@ class IosPermissionHandler {
}
}
static Future<void> requestMicrophonePermission() async {
static Future<bool> requestMicrophonePermission() async {
var isMicrophonePermission = false;
PermissionStatus status = await Permission.microphone.request();
if (status.isGranted) {
print("iOS: 麦克风权限已授予");
isMicrophonePermission = true;
} else if (status.isPermanentlyDenied) {
print("iOS: 麦克风权限被永久拒绝,请前往设置开启");
ToastUtils.showError("权限被永久拒绝,请前往设置开启!");
@ -95,5 +107,6 @@ class IosPermissionHandler {
print("iOS: 麦克风限被拒绝");
ToastUtils.showError("权限被拒绝,可能会导致相关功能不可用!");
}
return isMicrophonePermission;
}
}

View File

@ -1,16 +1,45 @@
import 'dart:io';
import 'dart:ui';
import 'package:permission_handler/permission_handler.dart';
import 'AndroidPermissionHandler.dart';
import 'IosPermissionHandler.dart';
class PermissionService {
static Future<void> requestPermissions() async {
static Future<bool> requestPermissions() async {
var isRequestPermissions = false;
if (Platform.isIOS) {
// iOS权限处理逻辑
await IosPermissionHandler().requestCameraToMicrophonePermissions();
isRequestPermissions = await IosPermissionHandler().requestCameraToMicrophonePermissions();
} else if (Platform.isAndroid) {
// Android权限处理逻辑
await AndroidPermissionHandler().requestCameraToMicrophonePermissions();
isRequestPermissions = await AndroidPermissionHandler().requestCameraToMicrophonePermissions();
}
return isRequestPermissions;
}
static Future<void> requestStoragePermissions() async {
if (Platform.isIOS) {
// iOS权限处理逻辑
IosPermissionHandler().requestStoragePermission();
} else if (Platform.isAndroid) {
// Android权限处理逻辑
AndroidPermissionHandler().requestStoragePermission();
}
}
///
static Future<bool> checkPermission({required List<Permission> permissionList}) async {
var isRequestPermissions = true;
///
for (Permission permission in permissionList) {
PermissionStatus status = await permission.status;
///
if (!status.isGranted) {
isRequestPermissions = false;
}
}
return isRequestPermissions;
}
}

View File

@ -0,0 +1,175 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/src/size_extension.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:install_plugin/install_plugin.dart';
import 'package:path_provider/path_provider.dart';
import 'package:wgshare/utils/color_util.dart';
import 'package:wgshare/utils/toast_utils.dart';
import 'package:wgshare/view/upgrade/util/load_file_cancel_request.dart';
publicDialog(BuildContext context,
{String title = "提示",
required String describe,
btnWidth = 116.0,
btnHeight = 42.0,
required String leftBtnStr,
required String rightBtnStr,
bool hideCancelBtn = false,
required Function leftBtnCallback,
required Function rightBtnCallback}) {
showDialog(
context: context,
barrierDismissible: hideCancelBtn,
builder: (_) => PopScope(
canPop: hideCancelBtn,
child: HintDialog(title, describe, btnWidth, btnHeight, leftBtnStr,
rightBtnStr, hideCancelBtn,leftBtnCallback,rightBtnCallback),
));
}
class HintDialog extends StatefulWidget {
final String title; //
final String describe; //
final double btnWidth; //
final double btnHeight; //
final String leftBtnStr; //
final String rightBtnStr; //
final bool hideCancelBtn; //btn
final Function leftBtnCallback;
final Function rightBtnCallback;
HintDialog(
this.title,
this.describe,
this.btnWidth,
this.btnHeight,
this.leftBtnStr,
this.rightBtnStr,
this.hideCancelBtn,
this.leftBtnCallback,
this.rightBtnCallback);
@override
State<StatefulWidget> createState() {
return HintDialogState();
}
}
class HintDialogState extends State<HintDialog> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 250.h,
margin: const EdgeInsets.only(left: 40, right: 40),
padding:
const EdgeInsets.only(top: 20, bottom: 20, left: 12, right: 12),
decoration: BoxDecoration(
color: const Color.fromRGBO(255, 255, 255, 1),
borderRadius: BorderRadius.circular(8),
),
child: _contentStyle(),
),
);
}
///
_contentStyle() {
return Column(
children: <Widget>[
_title(),
Expanded(
child: _hintMsg(),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: Container(
width: widget.btnWidth,
height: widget.btnHeight,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(99)),
color: ColorUtil.Color_230_230_230,
),
margin: const EdgeInsets.only(top: 12, left: 12),
alignment: Alignment.center,
child: Text(
widget.leftBtnStr,
style: TextStyle(
fontSize: 14.sp, color: ColorUtil.Color_255_255_255),
),
),
onTap: () {
Get.back();
widget.leftBtnCallback();
},
),
GestureDetector(
child: Container(
width: widget.btnWidth,
height: widget.btnHeight,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(99)),
color: ColorUtil.Color_85_117_242,
),
margin: const EdgeInsets.only(top: 12, right: 12),
alignment: Alignment.center,
child: Text(
widget.rightBtnStr,
style: TextStyle(
fontSize: 14.sp, color: ColorUtil.Color_255_255_255),
),
),
onTap: () {
Get.back();
widget.rightBtnCallback();
},
)
],
)
],
);
}
///
_title() {
return Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(bottom: 12),
child: Text(widget.title,
style: TextStyle(
fontSize: 16.sp,
color: ColorUtil.Color_85_117_242,
fontWeight: FontWeight.w600,
decoration: TextDecoration.none)),
);
}
///
_hintMsg() {
return SingleChildScrollView(
child: Container(
alignment: Alignment.topLeft,
margin: const EdgeInsets.only(left: 8, right: 8),
child: Text(widget.describe,
style: const TextStyle(
fontSize: 14,
color: ColorUtil.Color_89_88_88,
decoration: TextDecoration.none,
height: 1.4)),
),
);
}
}

View File

@ -0,0 +1,57 @@
import 'package:json_annotation/json_annotation.dart';
part 'upgrade_entity.g.dart';
@JsonSerializable()
class UpgradeEntity extends Object {
@JsonKey(name: 'code')
int code;
@JsonKey(name: 'message')
String message;
@JsonKey(name: 'success')
bool success;
@JsonKey(name: 'data')
Data data;
UpgradeEntity(this.code,this.message,this.success,this.data,);
factory UpgradeEntity.fromJson(Map<String, dynamic> srcJson) => _$UpgradeEntityFromJson(srcJson);
Map<String, dynamic> toJson() => _$UpgradeEntityToJson(this);
}
@JsonSerializable()
class Data extends Object {
@JsonKey(name: 'versionCode')
int versionCode;
@JsonKey(name: 'versionName')
String versionName;
@JsonKey(name: 'updateType')
int updateType;
@JsonKey(name: 'versionDescribe')
String versionDescribe;
@JsonKey(name: 'androidurl')
String androidurl;
@JsonKey(name: 'appStoreUrl')
String appStoreUrl;
Data(this.versionCode,this.versionName,this.updateType,this.versionDescribe,this.androidurl,this.appStoreUrl);
factory Data.fromJson(Map<String, dynamic> srcJson) => _$DataFromJson(srcJson);
Map<String, dynamic> toJson() => _$DataToJson(this);
}

View File

@ -0,0 +1,42 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../upgrade_dialog.dart';
import 'entity/upgrade_entity.dart';
Future<Map> request(String url) async{
Response response = await Dio().get(url);
return response.data;
}
void doHttpCheckVersion(BuildContext context){
Future<Map> map = request("https://meeting-api.23544.com/meeting/mobile/latest.json");
map.then((result) async {
var jsonStr = json.encode(result);
var listDynamic = jsonDecode(jsonStr);
UpgradeEntity upgradeEntity = UpgradeEntity.fromJson(listDynamic);
if(upgradeEntity.code == 200){
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String versionCode = packageInfo.buildNumber;
if(int.tryParse(versionCode)! < 2){
debugPrint("检查更新-服务器版本信息:$jsonStr");
debugPrint("检查更新-当前版本号:$versionCode");
upgradeDialog(
context,
upgradeEntity.data.versionDescribe,
hideCancelBtn: /*upgradeEntity.data.updateType == 1 ? false : true*/true,
title: '版本更新 v${upgradeEntity.data.versionName}',
/*upgradeEntity.data.url*/'https://s3.cn-north-1.amazonaws.com.cn/mtab.kezaihui.com/apk/takeaway_phone_release_1.apk',
upgradeEntity.data.appStoreUrl,
upgradeEntity.data.versionName
);
}else{
return;
}
}else{
return;
}
});
}

View File

@ -0,0 +1,302 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/src/size_extension.dart';
import 'package:install_plugin/install_plugin.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:wgshare/utils/color_util.dart';
import 'package:wgshare/utils/toast_utils.dart';
import 'package:wgshare/view/upgrade/util/load_file_cancel_request.dart';
import '../../utils/permission/PermissionService.dart';
import '../public_dialog.dart';
upgradeDialog(
BuildContext context,
String describe,
String url,
String appStoreUrl,
String versionName, {
String title = "提示",
btnWidth = 116.0,
btnHeight = 42.0,
bool hideCancelBtn = false,
}) {
showDialog(
context: context,
barrierDismissible: hideCancelBtn,
builder: (_) => PopScope(
canPop: hideCancelBtn,
child: HintDialog(title, describe, url, appStoreUrl, versionName,
btnWidth, btnHeight, hideCancelBtn),
));
}
class HintDialog extends StatefulWidget {
final String title; //
final String describe; //
final String url;
final String appStoreUrl;
final String versionName;
final double btnWidth; //
final double btnHeight; //
final bool hideCancelBtn; //btn
HintDialog(this.title, this.describe, this.url, this.appStoreUrl,
this.versionName, this.btnWidth, this.btnHeight, this.hideCancelBtn);
@override
State<StatefulWidget> createState() {
return HintDialogState();
}
}
class HintDialogState extends State<HintDialog> {
// 0123
int confirmBtnType = 0;
//
String btnStr = "更新";
//
double progressValue = 0.0;
// apk路径
late String apkPath;
late loadFileCancelRequest loadRequest;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 400.h,
margin: const EdgeInsets.only(left: 40, right: 40),
padding:
const EdgeInsets.only(top: 20, bottom: 20, left: 12, right: 12),
decoration: BoxDecoration(
color: const Color.fromRGBO(255, 255, 255, 1),
borderRadius: BorderRadius.circular(8),
),
child: _contentStyle(),
),
);
}
///
_contentStyle() {
return Column(
children: <Widget>[
_title(),
Expanded(
child: _hintMsg(),
),
Visibility(
visible: confirmBtnType == 1 ? true : false,
child: Container(
width: double.infinity,
height: 4.h,
color: ColorUtil.Color_85_117_242,
margin: const EdgeInsets.only(top: 12, left: 2, right: 2),
child: LinearProgressIndicator(
value: progressValue,
valueColor:
const AlwaysStoppedAnimation(ColorUtil.Color_85_117_242),
backgroundColor: ColorUtil.Color_185_184_184,
),
),
),
GestureDetector(
child: Container(
width: double.infinity,
height: 42.h,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(99)),
color: ColorUtil.Color_85_117_242,
),
margin: const EdgeInsets.only(top: 12),
alignment: Alignment.center,
child: Text(
btnStr,
style: TextStyle(
fontSize: 14.sp, color: ColorUtil.Color_255_255_255),
),
),
onTap: () {
if (Platform.isIOS) {
_gotoAppStore();
} else {
if (confirmBtnType == 0) {
_downloadApk();
}
if (confirmBtnType == 1) {
Navigator.of(context).pop();
}
if (confirmBtnType == 2) {
PermissionService.checkPermission(permissionList: [Permission.manageExternalStorage]).then((value){
if(value == true){
// APK函数
_installApk(apkPath);
}else{
publicDialog(context,
hideCancelBtn: true,
title: '请求权限说明',
describe:
'APP需要获取您的文件访问权限用以确保新版本下载后可以正常安装。',
leftBtnStr: '拒绝',
rightBtnStr: '同意',
leftBtnCallback: () {
}, rightBtnCallback: () {
PermissionService.requestStoragePermissions();
});
}
});
}
if (confirmBtnType == 3) {
SystemNavigator.pop();
}
if (confirmBtnType == 4) {
Navigator.of(context).pop();
}
}
},
)
],
);
}
///
_title() {
return Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(bottom: 12),
child: Text(widget.title,
style: TextStyle(
fontSize: 16.sp,
color: ColorUtil.Color_85_117_242,
fontWeight: FontWeight.w600,
decoration: TextDecoration.none)),
);
}
///
_hintMsg() {
return SingleChildScrollView(
child: Container(
alignment: Alignment.topLeft,
margin: const EdgeInsets.only(left: 8, right: 8),
child: Text(widget.describe,
style: const TextStyle(
fontSize: 14,
color: ColorUtil.Color_89_88_88,
decoration: TextDecoration.none,
height: 1.4)),
),
);
}
/// APK
_downloadApk() async {
loadRequest = loadFileCancelRequest();
try {
// apk下载路径
var appDocDir = await getTemporaryDirectory();
apkPath = "${appDocDir.path}/wgshare_${widget.versionName}.apk";
loadRequest.load(widget.url, apkPath);
loadRequest.setProgressCallbackListener((progress) {
setState(() {
progressValue = progress;
});
});
loadRequest.setSuccessCallbackListener(() {
setState(() {
confirmBtnType = 2;
btnStr = "安装";
});
});
setState(() {
confirmBtnType = 1;
btnStr = "取消";
});
} catch (e) {
debugPrint("检查更新-安卓下载失败:$e");
setState(() {
confirmBtnType = 0;
btnStr = "更新";
});
ToastUtils.showError("下载失败");
}
/*Dio dio = Dio();
try {
// apk下载路径
var appDocDir = await getTemporaryDirectory();
apkPath = "${appDocDir.path}/wgshare_${widget.versionName}.apk";
setState(() {
confirmBtnType = 1;
btnStr = "取消";
});
//
await dio.download(widget.url, apkPath, onReceiveProgress: (received, total) {
if (total != -1) {
//
setState(() {
progressValue = (received / total * 100);
});
}
}).then((onValue){
setState(() {
confirmBtnType = 2;
btnStr = "安装";
});
});
} catch (e) {
debugPrint("检查更新-下载失败:$e");
setState(() {
confirmBtnType = 0;
btnStr = "更新";
});
ToastUtils.showError("下载失败");
}*/
}
/// APK
_installApk(String path) async {
await InstallPlugin.installApk(path).then((result) {
debugPrint("检查更新-安卓安装成功:$result");
setState(() {
confirmBtnType = 3;
btnStr = "关闭APP重新打开";
});
}).catchError((error) {
debugPrint("检查更新-安卓安装失败:$error");
setState(() {
confirmBtnType = 4;
btnStr = "跳过";
});
});
}
///
_gotoAppStore() async {
await InstallPlugin.install(widget.appStoreUrl).then((result){
debugPrint("检查更新-苹果安装成功:$result");
}).catchError((error){
debugPrint("检查更新-苹果安装失败:$error");
});
}
}

View File

@ -0,0 +1,36 @@
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
class loadFileCancelRequest{
late Function progressCallback;
late Function successCallback;
CancelToken token = CancelToken();
load(String url, String path) async {
Dio dio = Dio();
await dio.download(url, path, cancelToken: token, onReceiveProgress: (received, total) {
if (total != -1) {
//
progressCallback((received / total));
debugPrint("检查更新-下载进度:${(received / total)}");
}
}).then((onValue){
//
successCallback();
});
}
cancel() {
token.cancel('取消请求');
}
setProgressCallbackListener(Function progressCallback){
this.progressCallback = progressCallback;
}
setSuccessCallbackListener(Function successCallback){
this.successCallback = successCallback;
}
}

View File

@ -634,6 +634,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.2"
install_plugin:
dependency: "direct main"
description:
name: install_plugin
sha256: "6fb67ba0781e75de4f2f2266ed25e835bfd277c5bfc2ed034af52774355857c6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
intersperse:
dependency: transitive
description:
@ -843,7 +851,7 @@ packages:
source: hosted
version: "1.9.0"
path_provider:
dependency: transitive
dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"

View File

@ -92,6 +92,12 @@ dependencies:
# 弹窗
adaptive_dialog: ^2.3.0
# 安装apk
install_plugin: ^2.1.0
# 文件目录
path_provider: ^2.0.2
dev_dependencies:
flutter_test:
sdk: flutter