image_picker_util.dart 6.7 KB

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