image_picker_util.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import 'dart:io';
  2. import 'package:device_info_plus/device_info_plus.dart';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:keyboard/utils/app_setting_util.dart';
  5. import 'package:keyboard/utils/toast_util.dart';
  6. import 'package:wechat_assets_picker/wechat_assets_picker.dart';
  7. import '../dialog/permission_dialog.dart';
  8. import '../resource/colors.gen.dart';
  9. import '../resource/string.gen.dart';
  10. import 'package:permission_handler/permission_handler.dart';
  11. /// 本地选择图片工具类
  12. class ImagePickerUtil {
  13. static final Color _themeColor = ColorName.colorBrand;
  14. /// 请求图片权限(适配 iOS / Android)
  15. static Future<bool> requestPermissionExtend() async {
  16. if (Platform.isAndroid) {
  17. final androidInfo = await DeviceInfoPlugin().androidInfo;
  18. if (androidInfo.version.sdkInt <= 32) {
  19. // Android 版本 <= 32,使用 Permission.storage
  20. final status = await Permission.storage.request();
  21. return status.isGranted;
  22. } else {
  23. // Android 版本 > 32,使用 Permission.photos
  24. final status = await Permission.photos.request();
  25. return status.isGranted;
  26. }
  27. } else {
  28. // iOS 请求照片权限
  29. final status = await Permission.photos.request();
  30. return status.isGranted;
  31. }
  32. }
  33. /// 判断是否已有权限
  34. static Future<bool> hasPermission() async {
  35. if (Platform.isAndroid) {
  36. final androidInfo = await DeviceInfoPlugin().androidInfo;
  37. if (androidInfo.version.sdkInt <= 32) {
  38. // Android 版本 <= 32,使用 Permission.storage
  39. final status = await Permission.storage.status;
  40. return status.isGranted;
  41. } else {
  42. // Android 版本 > 32,使用 Permission.photos
  43. final status = await Permission.photos.status;
  44. return status.isGranted;
  45. }
  46. } else {
  47. // iOS 判断照片权限
  48. final status = await Permission.photos.status;
  49. return status.isGranted;
  50. }
  51. }
  52. /// 判断是否为“永久拒绝权限”
  53. static Future<bool> isPermissionPermanentlyDenied() async {
  54. if (Platform.isAndroid) {
  55. final androidInfo = await DeviceInfoPlugin().androidInfo;
  56. if (androidInfo.version.sdkInt <= 32) {
  57. // Android 版本 <= 32,使用 Permission.storage
  58. final status = await Permission.storage.status;
  59. return status.isPermanentlyDenied;
  60. } else {
  61. // Android 版本 > 32,使用 Permission.photos
  62. final status = await Permission.photos.status;
  63. return status.isPermanentlyDenied;
  64. }
  65. } else {
  66. // iOS 判断照片权限是否永久拒绝
  67. final status = await Permission.photos.status;
  68. return status.isPermanentlyDenied;
  69. }
  70. }
  71. /// 选择图片
  72. static Future<List<AssetEntity>?> pickImage(
  73. BuildContext context, {
  74. required int maxAssetsCount,
  75. List<AssetEntity> selectedAssets = const [],
  76. }) async {
  77. bool isAllow = await ImagePickerUtil.hasPermission();
  78. if (!isAllow) {
  79. bool shouldProceed = await PermissionDialog.showRequestDialog(
  80. StringName.permissionDialogTitle,
  81. PermissionDialog.buildStorageView(),
  82. StringName.permissionDialogAuthorize,
  83. "使用该功能App需要访问您设备“照片和媒体”权限,开启权限后,您可以上传图片使用该功能使用",
  84. sureClick: () async {
  85. bool granted = await ImagePickerUtil.requestPermissionExtend();
  86. if (!granted &&
  87. await ImagePickerUtil.isPermissionPermanentlyDenied()) {
  88. Future.delayed(const Duration(milliseconds: 400), () {
  89. AppSettingUtil.jumpSystemAppSetting();
  90. });
  91. }
  92. return granted;
  93. },
  94. );
  95. if (shouldProceed) {
  96. return await pickImage(
  97. context,
  98. maxAssetsCount: maxAssetsCount,
  99. selectedAssets: selectedAssets,
  100. );
  101. } else {
  102. ToastUtil.show(StringName.pickerImagePermissionDeniedTip);
  103. return null;
  104. }
  105. }
  106. // 2. 权限已授权,打开选择器
  107. try {
  108. return await AssetPicker.pickAssets(
  109. context,
  110. pickerConfig: AssetPickerConfig(
  111. // 最大选择数量
  112. maxAssets: maxAssetsCount,
  113. // 已选择的图片列表
  114. selectedAssets: selectedAssets,
  115. // 自定义按钮文字
  116. textDelegate: const CustomChineseDelegate(),
  117. // 主题
  118. pickerTheme: AssetPicker.themeData(
  119. // 主题色
  120. _themeColor,
  121. // 深色默认
  122. light: false,
  123. ),
  124. // 是否可以预览
  125. specialPickerType: SpecialPickerType.noPreview,
  126. // 只能选取图片类型
  127. requestType: RequestType.image,
  128. // 关闭拽托选择
  129. dragToSelect: false,
  130. // 实现最近相册的名字显示
  131. pathNameBuilder:
  132. (AssetPathEntity path) => switch (path) {
  133. final p when p.isAll => StringName.recently,
  134. _ => path.name,
  135. },
  136. ),
  137. ) ??
  138. [];
  139. } catch (e) {
  140. if (e is StateError) {
  141. ToastUtil.show(StringName.pickerImagePermissionDeniedTip);
  142. Future.delayed(const Duration(milliseconds: 400), () {
  143. AppSettingUtil.jumpSystemAppSetting();
  144. });
  145. } else {
  146. rethrow;
  147. }
  148. }
  149. return null;
  150. }
  151. /// 跳转到图片预览
  152. static Future<List<AssetEntity>> goImagePreview(
  153. BuildContext context,
  154. List<AssetEntity> previewAssets,
  155. int index,
  156. ) async {
  157. final List<AssetEntity> result =
  158. await AssetPickerViewer.pushToViewer(
  159. context,
  160. // 当前预览的索引位置
  161. currentIndex: index,
  162. // 资源列表
  163. previewAssets: previewAssets,
  164. // 主题色
  165. themeData: AssetPicker.themeData(_themeColor),
  166. ) ??
  167. [];
  168. return result;
  169. }
  170. /// AssetEntity实体,转换为File列表
  171. /// [assetsList] 图片、视频选择后的资源列表
  172. static Future<List<File>> convertAssetToFile(
  173. List<AssetEntity> assetsList,
  174. ) async {
  175. List<File> files = [];
  176. for (var asset in assetsList) {
  177. final file = await asset.file;
  178. if (file != null) {
  179. files.add(file);
  180. }
  181. }
  182. return files;
  183. }
  184. }
  185. /// 自定义按钮文字
  186. class CustomChineseDelegate extends AssetPickerTextDelegate {
  187. const CustomChineseDelegate();
  188. @override
  189. String get confirm => StringName.nextStep;
  190. }