picker_service.dart 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import 'dart:io';
  2. import 'package:image_picker/image_picker.dart';
  3. import 'package:sino_med_cloud/core/utils/logger.dart';
  4. import 'package:sino_med_cloud/core/utils/toast_utils.dart';
  5. import 'package:sino_med_cloud/core/constants/app_enum.dart';
  6. import 'package:sino_med_cloud/permission/permission_service.dart';
  7. import 'media_exception.dart';
  8. /// 相册和快速拍照服务
  9. ///
  10. /// 适合普通拍照、表单上传、医疗资料拍照(非实时)
  11. ///
  12. /// 使用示例:
  13. /// ```dart
  14. /// // 从相册选择
  15. /// final file = await PickerService.pickFromGallery();
  16. ///
  17. /// // 使用系统相机拍照
  18. /// final photo = await PickerService.pickFromCamera();
  19. /// ```
  20. class PickerService {
  21. static final ImagePicker _picker = ImagePicker();
  22. /// 默认图片质量(1-100)
  23. static const int defaultImageQuality = 90;
  24. /// 从相册选择图片
  25. ///
  26. /// - [imageQuality] 图片质量(1-100),默认 90
  27. /// - [maxWidth] 最大宽度,null 表示不限制
  28. /// - [maxHeight] 最大高度,null 表示不限制
  29. ///
  30. /// 返回选择的图片文件,如果用户取消选择返回 null
  31. ///
  32. /// 抛出 [MediaException] 如果选择失败
  33. static Future<File?> pickFromGallery({
  34. int imageQuality = defaultImageQuality,
  35. double? maxWidth,
  36. double? maxHeight,
  37. }) async {
  38. try {
  39. // 检查相册权限
  40. final permissionResult = await PermissionService.request(PermissionType.gallery);
  41. if (permissionResult != PermissionResult.granted) {
  42. if (permissionResult == PermissionResult.permanentlyDenied) {
  43. ToastUtils.showError('相册权限被永久拒绝,请在设置中开启相册权限');
  44. // 自动打开设置页面
  45. await PermissionService.openSetting();
  46. throw MediaPermissionException('相册权限被永久拒绝,已引导用户前往设置');
  47. } else {
  48. ToastUtils.showError('需要相册权限才能选择图片');
  49. throw MediaPermissionException('相册权限被拒绝');
  50. }
  51. }
  52. AppLogger.d('开始从相册选择图片...');
  53. final XFile? file = await _picker.pickImage(
  54. source: ImageSource.gallery,
  55. imageQuality: imageQuality,
  56. maxWidth: maxWidth,
  57. maxHeight: maxHeight,
  58. );
  59. if (file == null) {
  60. AppLogger.d('用户取消了相册选择');
  61. return null;
  62. }
  63. final imageFile = File(file.path);
  64. if (!await imageFile.exists()) {
  65. throw MediaFileException('选择的图片文件不存在: ${file.path}');
  66. }
  67. AppLogger.d('相册选择成功: ${file.path}');
  68. return imageFile;
  69. } on MediaException {
  70. rethrow;
  71. } catch (e) {
  72. AppLogger.e('相册选择失败', e);
  73. if (e.toString().contains('permission') || e.toString().contains('权限')) {
  74. throw MediaPermissionException(
  75. '相册访问权限被拒绝,请在设置中开启相册权限',
  76. originalError: e,
  77. );
  78. }
  79. throw MediaException(
  80. '相册选择失败: ${e.toString()}',
  81. originalError: e,
  82. );
  83. }
  84. }
  85. /// 使用系统相机拍照(轻量级)
  86. ///
  87. /// - [imageQuality] 图片质量(1-100),默认 90
  88. /// - [maxWidth] 最大宽度,null 表示不限制
  89. /// - [maxHeight] 最大高度,null 表示不限制
  90. ///
  91. /// 返回拍摄的照片文件,如果用户取消拍照返回 null
  92. ///
  93. /// 抛出 [MediaException] 如果拍照失败
  94. static Future<File?> pickFromCamera({
  95. int imageQuality = defaultImageQuality,
  96. double? maxWidth,
  97. double? maxHeight,
  98. }) async {
  99. try {
  100. // 检查相机权限
  101. final permissionResult = await PermissionService.request(PermissionType.camera);
  102. if (permissionResult != PermissionResult.granted) {
  103. if (permissionResult == PermissionResult.permanentlyDenied) {
  104. ToastUtils.showError('相机权限被永久拒绝,请在设置中开启相机权限');
  105. // 自动打开设置页面
  106. await PermissionService.openSetting();
  107. throw MediaPermissionException('相机权限被永久拒绝,已引导用户前往设置');
  108. } else {
  109. ToastUtils.showError('需要相机权限才能拍照');
  110. throw MediaPermissionException('相机权限被拒绝');
  111. }
  112. }
  113. AppLogger.d('开始使用系统相机拍照...');
  114. final XFile? file = await _picker.pickImage(
  115. source: ImageSource.camera,
  116. imageQuality: imageQuality,
  117. maxWidth: maxWidth,
  118. maxHeight: maxHeight,
  119. );
  120. if (file == null) {
  121. AppLogger.d('用户取消了拍照');
  122. return null;
  123. }
  124. final photoFile = File(file.path);
  125. if (!await photoFile.exists()) {
  126. throw MediaFileException('拍摄的照片文件不存在: ${file.path}');
  127. }
  128. AppLogger.d('拍照成功: ${file.path}');
  129. return photoFile;
  130. } on MediaException {
  131. rethrow;
  132. } catch (e) {
  133. AppLogger.e('相机拍照失败', e);
  134. if (e.toString().contains('permission') || e.toString().contains('权限')) {
  135. throw MediaPermissionException(
  136. '相机权限被拒绝,请在设置中开启相机权限',
  137. originalError: e,
  138. );
  139. }
  140. throw MediaException(
  141. '相机拍照失败: ${e.toString()}',
  142. originalError: e,
  143. );
  144. }
  145. }
  146. /// 从相册选择视频
  147. ///
  148. /// 返回选择的视频文件,如果用户取消选择返回 null
  149. ///
  150. /// 抛出 [MediaException] 如果选择失败
  151. static Future<File?> pickVideoFromGallery() async {
  152. try {
  153. // 检查相册权限
  154. final permissionResult = await PermissionService.request(PermissionType.gallery);
  155. if (permissionResult != PermissionResult.granted) {
  156. if (permissionResult == PermissionResult.permanentlyDenied) {
  157. ToastUtils.showError('相册权限被永久拒绝,请在设置中开启相册权限');
  158. // 自动打开设置页面
  159. await PermissionService.openSetting();
  160. throw MediaPermissionException('相册权限被永久拒绝,已引导用户前往设置');
  161. } else {
  162. ToastUtils.showError('需要相册权限才能选择视频');
  163. throw MediaPermissionException('相册权限被拒绝');
  164. }
  165. }
  166. AppLogger.d('开始从相册选择视频...');
  167. final XFile? file = await _picker.pickVideo(
  168. source: ImageSource.gallery,
  169. );
  170. if (file == null) {
  171. AppLogger.d('用户取消了视频选择');
  172. return null;
  173. }
  174. final videoFile = File(file.path);
  175. if (!await videoFile.exists()) {
  176. throw MediaFileException('选择的视频文件不存在: ${file.path}');
  177. }
  178. AppLogger.d('视频选择成功: ${file.path}');
  179. return videoFile;
  180. } on MediaException {
  181. rethrow;
  182. } catch (e) {
  183. AppLogger.e('视频选择失败', e);
  184. if (e.toString().contains('permission') || e.toString().contains('权限')) {
  185. throw MediaPermissionException(
  186. '相册访问权限被拒绝,请在设置中开启相册权限',
  187. originalError: e,
  188. );
  189. }
  190. throw MediaException(
  191. '视频选择失败: ${e.toString()}',
  192. originalError: e,
  193. );
  194. }
  195. }
  196. }