diff --git a/client/ios/Runner/Info.plist b/client/ios/Runner/Info.plist index 5dd1c1c..9e028fb 100644 --- a/client/ios/Runner/Info.plist +++ b/client/ios/Runner/Info.plist @@ -4,6 +4,10 @@ CADisableMinimumFrameDurationOnPhone + NSCameraUsageDescription + 用于拍摄商品照片 + NSPhotoLibraryUsageDescription + 用于从相册选择商品照片 CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/client/lib/screens/products/product_detail_screen.dart b/client/lib/screens/products/product_detail_screen.dart index 2c4aa85..feec6ad 100644 --- a/client/lib/screens/products/product_detail_screen.dart +++ b/client/lib/screens/products/product_detail_screen.dart @@ -1,6 +1,7 @@ import '../../core/utils/dialog_util.dart'; import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -91,7 +92,56 @@ class _ProductDetailScreenState extends ConsumerState { } } + // 移动端(Android/iOS)支持拍照,用 image_picker;桌面/Web 用 file_picker 选文件。 + // 用 defaultTargetPlatform 而非 dart:io 的 Platform,避免在参与 Web 编译的文件里引入 dart:io。 + bool get _isMobile => + !kIsWeb && + (defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS); + Future _pickAndUpload() async { + if (_isMobile) { + await _pickFromCameraOrGallery(); + } else { + await _pickFromFiles(); + } + } + + /// 移动端:底部弹出「拍照 / 从相册选择」 + Future _pickFromCameraOrGallery() async { + final source = await showModalBottomSheet( + context: context, + builder: (ctx) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.camera_alt_outlined), + title: const Text('拍照'), + onTap: () => Navigator.pop(ctx, ImageSource.camera), + ), + ListTile( + leading: const Icon(Icons.photo_library_outlined), + title: const Text('从相册选择'), + onTap: () => Navigator.pop(ctx, ImageSource.gallery), + ), + ], + ), + ), + ); + if (source == null) return; + + final XFile? xfile = await ImagePicker().pickImage( + source: source, + maxWidth: 2000, + imageQuality: 85, + ); + if (xfile == null) return; + await _uploadImage(filePath: xfile.path, fileName: xfile.name); + } + + /// 桌面 / Web:文件选择器 + Future _pickFromFiles() async { final result = await FilePicker.platform.pickFiles( type: FileType.image, allowMultiple: false, @@ -102,13 +152,17 @@ class _ProductDetailScreenState extends ConsumerState { final String? filePath = kIsWeb ? null : file.path; final fileBytes = file.bytes; if (filePath == null && fileBytes == null) return; + await _uploadImage( + filePath: filePath, bytes: fileBytes, fileName: file.name); + } + Future _uploadImage( + {String? filePath, Uint8List? bytes, required String fileName}) async { setState(() => _uploading = true); try { - final img = await ref - .read(productRepositoryProvider) - .uploadImage(_product!.id, filePath, - bytes: fileBytes, fileName: file.name); + final img = await ref.read(productRepositoryProvider).uploadImage( + _product!.id, filePath, + bytes: bytes, fileName: fileName); _updateImages([..._product!.images, img]); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift index 5713c70..6ad9ca7 100644 --- a/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import file_picker +import file_selector_macos import package_info_plus import printing import shared_preferences_foundation @@ -13,6 +14,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/client/pubspec.lock b/client/pubspec.lock index 3131bc6..eabe1c6 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -161,6 +161,38 @@ packages: url: "https://pub.dev" source: hosted version: "8.3.7" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" flutter: dependency: "direct main" description: flutter @@ -253,6 +285,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.3.0" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a" + url: "https://pub.dev" + source: hosted + version: "0.8.13+19" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 + url: "https://pub.dev" + source: hosted + version: "0.8.13+6" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" intl: dependency: "direct main" description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index a8c0819..5bc9745 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: printing: ^5.13.0 pdf: ^3.10.8 web: ^1.1.0 + image_picker: ^1.2.2 dev_dependencies: flutter_test: diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc index ee76227..7126a31 100644 --- a/client/windows/flutter/generated_plugin_registrant.cc +++ b/client/windows/flutter/generated_plugin_registrant.cc @@ -6,10 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); PrintingPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PrintingPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/client/windows/flutter/generated_plugins.cmake b/client/windows/flutter/generated_plugins.cmake index 437b6b9..584fea7 100644 --- a/client/windows/flutter/generated_plugins.cmake +++ b/client/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows printing url_launcher_windows )