2025-07-21 15:46:30 +08:00
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
2025-10-10 11:27:26 +08:00
|
|
|
import 'package:flutter/material.dart';
|
2025-07-21 15:46:30 +08:00
|
|
|
import 'package:get/get.dart';
|
2025-09-13 17:01:01 +08:00
|
|
|
import 'package:loopin/IM/im_service.dart';
|
|
|
|
|
import 'package:loopin/components/my_confirm.dart';
|
2025-07-21 15:46:30 +08:00
|
|
|
import 'package:permission_handler/permission_handler.dart';
|
2025-10-10 11:27:26 +08:00
|
|
|
import 'package:photo_manager/photo_manager.dart';
|
2025-07-21 15:46:30 +08:00
|
|
|
|
|
|
|
|
class Permissions {
|
2025-10-10 11:27:26 +08:00
|
|
|
/// 判断是否为华为设备
|
|
|
|
|
static Future<bool> isHuaweiDevice() async {
|
|
|
|
|
if (Platform.isAndroid) {
|
|
|
|
|
final deviceInfoPlugin = DeviceInfoPlugin();
|
|
|
|
|
final androidInfo = await deviceInfoPlugin.androidInfo;
|
|
|
|
|
// 判断品牌是否为华为
|
|
|
|
|
return androidInfo.brand.toLowerCase().contains('huawei') || androidInfo.manufacturer.toLowerCase().contains('huawei');
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-21 15:46:30 +08:00
|
|
|
// 请求视频访问权限
|
2025-10-10 11:27:26 +08:00
|
|
|
static Future<bool> requestVideoPermission({String? title, String? content}) async {
|
2025-07-21 15:46:30 +08:00
|
|
|
if (Platform.isAndroid) {
|
2025-10-10 11:27:26 +08:00
|
|
|
SnackbarController? ctl;
|
|
|
|
|
|
|
|
|
|
final isHw = await isHuaweiDevice();
|
|
|
|
|
if (isHw) {
|
|
|
|
|
ctl = Get.snackbar(
|
|
|
|
|
title ?? '相册权限使用说明:',
|
|
|
|
|
content ?? '我们需要读取您的手机相册,以便您从相册中选择文件',
|
|
|
|
|
duration: Duration(days: 1),
|
|
|
|
|
backgroundColor: Colors.red.withAlpha(230),
|
|
|
|
|
colorText: Colors.white,
|
|
|
|
|
icon: const Icon(Icons.error_outline, color: Colors.white),
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-21 15:46:30 +08:00
|
|
|
final deviceInfoPlugin = DeviceInfoPlugin();
|
|
|
|
|
final androidInfo = await deviceInfoPlugin.androidInfo;
|
|
|
|
|
final sdkInt = androidInfo.version.sdkInt;
|
|
|
|
|
|
|
|
|
|
if (sdkInt >= 33) {
|
2025-09-13 17:01:01 +08:00
|
|
|
// Android 13 及以上
|
2025-07-21 15:46:30 +08:00
|
|
|
final status = await Permission.videos.request();
|
2025-10-10 11:27:26 +08:00
|
|
|
if (ctl != null) {
|
|
|
|
|
ctl.close();
|
|
|
|
|
}
|
2025-07-21 15:46:30 +08:00
|
|
|
return status.isGranted;
|
|
|
|
|
} else {
|
2025-09-13 17:01:01 +08:00
|
|
|
// Android 12 及以下
|
2025-07-21 15:46:30 +08:00
|
|
|
final status = await Permission.storage.request();
|
2025-10-10 11:27:26 +08:00
|
|
|
if (ctl != null) {
|
|
|
|
|
ctl.close();
|
|
|
|
|
}
|
2025-07-21 15:46:30 +08:00
|
|
|
return status.isGranted;
|
|
|
|
|
}
|
|
|
|
|
} else if (Platform.isIOS) {
|
|
|
|
|
final status = await Permission.photos.request();
|
2025-10-10 11:27:26 +08:00
|
|
|
// return status.isGranted || status.isLimited;
|
|
|
|
|
return handleStatus(status, isAndroid: false);
|
2025-09-17 15:32:18 +08:00
|
|
|
} else {
|
|
|
|
|
return false;
|
2025-07-21 15:46:30 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-13 17:01:01 +08:00
|
|
|
// 请求相册权限
|
2025-10-10 11:27:26 +08:00
|
|
|
static Future<bool> requestPhotoPermission({String? title, String? content}) async {
|
2025-09-13 17:01:01 +08:00
|
|
|
if (Platform.isAndroid) {
|
2025-10-10 11:27:26 +08:00
|
|
|
final isHw = await isHuaweiDevice();
|
|
|
|
|
SnackbarController? ctl;
|
|
|
|
|
if (isHw) {
|
|
|
|
|
ctl = Get.snackbar(
|
|
|
|
|
title ?? '相册权限使用说明:',
|
|
|
|
|
content ?? '我们需要读取您的手机相册,以便您从相册中选择文件',
|
|
|
|
|
duration: Duration(days: 1),
|
|
|
|
|
backgroundColor: Colors.red.withAlpha(230),
|
|
|
|
|
colorText: Colors.white,
|
|
|
|
|
icon: const Icon(Icons.error_outline, color: Colors.white),
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-13 17:01:01 +08:00
|
|
|
final deviceInfoPlugin = DeviceInfoPlugin();
|
|
|
|
|
final androidInfo = await deviceInfoPlugin.androidInfo;
|
|
|
|
|
final sdkInt = androidInfo.version.sdkInt;
|
|
|
|
|
logger.w(sdkInt);
|
|
|
|
|
if (sdkInt >= 33) {
|
|
|
|
|
// Android 13 及以上
|
|
|
|
|
final status = await Permission.photos.request();
|
2025-10-10 11:27:26 +08:00
|
|
|
if (ctl != null) {
|
|
|
|
|
ctl.close();
|
|
|
|
|
}
|
2025-09-17 15:32:18 +08:00
|
|
|
// return status.isGranted;
|
|
|
|
|
return handleStatus(status, isAndroid: true);
|
2025-09-13 17:01:01 +08:00
|
|
|
} else {
|
|
|
|
|
// Android 12 及以下
|
|
|
|
|
final status = await Permission.storage.request();
|
2025-10-10 11:27:26 +08:00
|
|
|
if (ctl != null) {
|
|
|
|
|
ctl.close();
|
|
|
|
|
}
|
2025-09-17 15:32:18 +08:00
|
|
|
// return status.isGranted;
|
|
|
|
|
return handleStatus(status, isAndroid: true);
|
2025-09-13 17:01:01 +08:00
|
|
|
}
|
|
|
|
|
} else if (Platform.isIOS) {
|
|
|
|
|
final status = await Permission.photos.request();
|
2025-09-17 15:32:18 +08:00
|
|
|
logger.w('iOS photos = $status');
|
|
|
|
|
// return status.isGranted || status.isLimited;
|
|
|
|
|
return handleStatus(status, isAndroid: false);
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
2025-09-13 17:01:01 +08:00
|
|
|
}
|
2025-09-17 15:32:18 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-10 11:27:26 +08:00
|
|
|
// 请求相机
|
|
|
|
|
static Future<bool> requestCameraPermission({String? title, String? content}) async {
|
|
|
|
|
return await _checkAndRequest(Permission.camera, title: title, content: content);
|
2025-07-21 15:46:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 请求麦克风权限
|
2025-10-10 11:27:26 +08:00
|
|
|
static Future<bool> requestMicrophonePermission({String? title, String? content}) async {
|
|
|
|
|
return await _checkAndRequest(Permission.microphone, title: title, content: content);
|
2025-07-21 15:46:30 +08:00
|
|
|
}
|
|
|
|
|
|
2025-09-13 17:01:01 +08:00
|
|
|
// 请求本地存储权限
|
2025-10-10 11:27:26 +08:00
|
|
|
static Future<bool> requestStoragePermission({String? title, String? content}) async {
|
|
|
|
|
return await _checkAndRequest(Permission.storage, title: title, content: content);
|
2025-08-26 15:22:16 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-21 15:46:30 +08:00
|
|
|
// 封装公共权限处理逻辑
|
2025-10-10 11:27:26 +08:00
|
|
|
static Future<bool> _checkAndRequest(Permission permission, {String? title, String? content}) async {
|
|
|
|
|
if (Platform.isAndroid) {
|
|
|
|
|
final isHw = await isHuaweiDevice();
|
|
|
|
|
SnackbarController? ctl;
|
|
|
|
|
if (isHw) {
|
|
|
|
|
ctl = Get.snackbar(
|
|
|
|
|
title ?? '麦克风权限使用说明:',
|
|
|
|
|
content ?? '用于发送语音消息',
|
|
|
|
|
duration: Duration(days: 1),
|
|
|
|
|
backgroundColor: Colors.red.withAlpha(230),
|
|
|
|
|
colorText: Colors.white,
|
|
|
|
|
icon: const Icon(Icons.error_outline, color: Colors.white),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final result = await permission.request();
|
2025-07-21 15:46:30 +08:00
|
|
|
|
2025-10-10 11:27:26 +08:00
|
|
|
if (ctl != null) {
|
|
|
|
|
ctl.close();
|
|
|
|
|
}
|
|
|
|
|
return handleStatus(result, isAndroid: true);
|
2025-07-21 15:46:30 +08:00
|
|
|
} else {
|
2025-10-10 11:27:26 +08:00
|
|
|
final result = await permission.request();
|
|
|
|
|
|
|
|
|
|
return handleStatus(result, isAndroid: false);
|
2025-07-21 15:46:30 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-10 11:27:26 +08:00
|
|
|
// final status = await permission.status;
|
|
|
|
|
|
|
|
|
|
// if (status.isGranted) {
|
|
|
|
|
// // 有权限直接关闭
|
|
|
|
|
// if (ctl != null) {
|
|
|
|
|
// ctl.close();
|
|
|
|
|
// }
|
|
|
|
|
// return true;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// final result = await permission.request();
|
|
|
|
|
|
|
|
|
|
// if (result.isGranted) {
|
|
|
|
|
// if (ctl != null) {
|
|
|
|
|
// ctl.close();
|
|
|
|
|
// }
|
|
|
|
|
// return true;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if (result.isPermanentlyDenied) {
|
|
|
|
|
// // 永久拒绝 只能去设置
|
|
|
|
|
// if (ctl != null) {
|
|
|
|
|
// ctl.close();
|
|
|
|
|
// }
|
|
|
|
|
// return false;
|
|
|
|
|
// } else {
|
|
|
|
|
// if (ctl != null) {
|
|
|
|
|
// ctl.close();
|
|
|
|
|
// }
|
|
|
|
|
// // 临时拒绝 提示
|
|
|
|
|
// Get.snackbar('权限请求失败', '无法访问,请授权对应权限后重试');
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// return false;
|
2025-07-21 15:46:30 +08:00
|
|
|
}
|
|
|
|
|
|
2025-09-17 15:32:18 +08:00
|
|
|
/// 处理权限状态
|
2025-10-10 11:27:26 +08:00
|
|
|
static Future<bool> handleStatus(PermissionStatus status, {required bool isAndroid, SnackbarController? ctl}) async {
|
2025-09-17 15:32:18 +08:00
|
|
|
logger.w("当前权限状态 = $status");
|
|
|
|
|
logger.e(status.isPermanentlyDenied);
|
|
|
|
|
if (status.isGranted) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isAndroid) {
|
|
|
|
|
// iOS 独有的情况
|
|
|
|
|
if (status.isLimited) {
|
|
|
|
|
logger.w("iOS 相册权限 = 仅限部分照片 (Limited)");
|
|
|
|
|
return true; // Limited 状态下也能用,但受限制
|
|
|
|
|
}
|
|
|
|
|
if (status.isPermanentlyDenied) {
|
2025-10-10 11:27:26 +08:00
|
|
|
// ios 二次检测
|
|
|
|
|
// permission_handler在ios下只能检测出limited和granted;其余被拒或手动修改均为permanentlyDenied的问题
|
|
|
|
|
final result = await PhotoManager.requestPermissionExtend();
|
|
|
|
|
logger.e(result);
|
|
|
|
|
switch (result) {
|
|
|
|
|
case PermissionState.authorized:
|
|
|
|
|
return true; // 完全授权
|
|
|
|
|
case PermissionState.limited:
|
|
|
|
|
return true; // 部分授权,也可用
|
|
|
|
|
case PermissionState.denied:
|
|
|
|
|
return false;
|
|
|
|
|
case PermissionState.restricted:
|
|
|
|
|
return false;
|
|
|
|
|
default:
|
|
|
|
|
return false; // 用户拒绝,跳转设置页
|
|
|
|
|
}
|
2025-09-17 15:32:18 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (status.isDenied) {
|
|
|
|
|
logger.w("相册权限被拒绝(可再次请求)");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (status.isPermanentlyDenied || status.isRestricted) {
|
|
|
|
|
logger.w("相册权限被永久拒绝,需要跳转到设置页");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-21 15:46:30 +08:00
|
|
|
// 跳转设置的提示弹窗
|
2025-09-17 15:32:18 +08:00
|
|
|
static void showPermissionDialog(String name) async {
|
2025-09-13 17:01:01 +08:00
|
|
|
final confirmed = await ConfirmDialog.show(
|
2025-09-17 15:32:18 +08:00
|
|
|
title: '需要$name权限',
|
2025-09-13 17:01:01 +08:00
|
|
|
content: '请前往系统设置中手动开启权限',
|
|
|
|
|
confirmText: '去设置',
|
2025-07-21 15:46:30 +08:00
|
|
|
);
|
2025-09-13 17:01:01 +08:00
|
|
|
if (confirmed == true) {
|
|
|
|
|
await openAppSettings();
|
|
|
|
|
}
|
2025-07-21 15:46:30 +08:00
|
|
|
}
|
|
|
|
|
}
|