picker_service.dart 7.1 KB

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