image_picker_util.dart 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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. PermissionDialog.showRequestDialog(
  80. StringName.permissionDialogTitle,
  81. PermissionDialog.buildStorageView(),
  82. StringName.permissionDialogAuthorize,
  83. "使用该功能App需要访问您设备“照片和媒体”权限,开启权限后,您可以上传图片使用该功能使用",
  84. sureClick: () async {
  85. bool isGranted = await ImagePickerUtil.requestPermissionExtend();
  86. if (isGranted) {
  87. await pickImage(context, maxAssetsCount: maxAssetsCount, selectedAssets: selectedAssets);
  88. return true;
  89. } else {
  90. if (await ImagePickerUtil.isPermissionPermanentlyDenied()) {
  91. Future.delayed(const Duration(milliseconds: 400), () {
  92. AppSettingUtil.jumpSystemAppSetting();
  93. });
  94. }
  95. ToastUtil.show(StringName.pickerImagePermissionDeniedTip);
  96. return false;
  97. }
  98. },
  99. );
  100. return null;
  101. }
  102. // 2. 权限已授权,打开选择器
  103. try {
  104. return await AssetPicker.pickAssets(
  105. context,
  106. pickerConfig: AssetPickerConfig(
  107. maxAssets: maxAssetsCount,
  108. selectedAssets: selectedAssets,
  109. textDelegate: const CustomChineseDelegate(),
  110. pickerTheme: AssetPicker.themeData(
  111. _themeColor,
  112. light: false,
  113. ),
  114. specialPickerType: SpecialPickerType.noPreview,
  115. requestType: RequestType.image,
  116. dragToSelect: false,
  117. pathNameBuilder: (AssetPathEntity path) => switch (path) {
  118. final p when p.isAll => StringName.recently,
  119. _ => path.name,
  120. },
  121. ),
  122. ) ??
  123. [];
  124. } catch (e) {
  125. if (e is StateError) {
  126. ToastUtil.show(StringName.pickerImagePermissionDeniedTip);
  127. Future.delayed(const Duration(milliseconds: 400), () {
  128. AppSettingUtil.jumpSystemAppSetting();
  129. });
  130. } else {
  131. rethrow;
  132. }
  133. }
  134. return null;
  135. }
  136. /// 跳转到图片预览
  137. static Future<List<AssetEntity>> goImagePreview(
  138. BuildContext context,
  139. List<AssetEntity> previewAssets,
  140. int index,
  141. ) async {
  142. final List<AssetEntity> result =
  143. await AssetPickerViewer.pushToViewer(
  144. context,
  145. // 当前预览的索引位置
  146. currentIndex: index,
  147. // 资源列表
  148. previewAssets: previewAssets,
  149. // 主题色
  150. themeData: AssetPicker.themeData(_themeColor),
  151. ) ??
  152. [];
  153. return result;
  154. }
  155. /// AssetEntity实体,转换为File列表
  156. /// [assetsList] 图片、视频选择后的资源列表
  157. static Future<List<File>> convertAssetToFile(
  158. List<AssetEntity> assetsList,
  159. ) async {
  160. List<File> files = [];
  161. for (var asset in assetsList) {
  162. final file = await asset.file;
  163. if (file != null) {
  164. files.add(file);
  165. }
  166. }
  167. return files;
  168. }
  169. }
  170. /// 自定义按钮文字
  171. class CustomChineseDelegate extends AssetPickerTextDelegate {
  172. const CustomChineseDelegate();
  173. @override
  174. String get confirm => StringName.nextStep;
  175. }