diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 89176ef..5ff3d60 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,5 +1,13 @@ allprojects { repositories { + // 优先添加国内镜像 + maven { url = uri("https://maven.aliyun.com/repository/central") } + maven { url = uri("https://maven.aliyun.com/repository/jcenter") } + maven { url = uri("https://maven.aliyun.com/repository/google") } + maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") } + maven { url = uri("https://maven.aliyun.com/repository/public") } + maven { url = uri("https://jitpack.io") } + google() mavenCentral() } diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index a439442..7aeb05c 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -10,6 +10,14 @@ pluginManagement { includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { + // 国内镜像加速 + maven { url = uri("https://maven.aliyun.com/repository/central") } + maven { url = uri("https://maven.aliyun.com/repository/jcenter") } + maven { url = uri("https://maven.aliyun.com/repository/google") } + maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") } + maven { url = uri("https://maven.aliyun.com/repository/public") } + maven { url = uri("https://jitpack.io") } + google() mavenCentral() gradlePluginPortal() diff --git a/lib/views/recordForm.dart b/lib/views/recordForm.dart index e5d06cc..35e4dd5 100644 --- a/lib/views/recordForm.dart +++ b/lib/views/recordForm.dart @@ -1,32 +1,15 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:food_hub_app/widgets/common/index.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; class RecordFormPage extends StatefulWidget { const RecordFormPage({super.key}); - // @override - // Widget build(BuildContext context) { - // return Scaffold( - // appBar: AppBar(title: Text('新页面'), backgroundColor: Colors.white), - // backgroundColor: Color(0xFFF5F5F5), - // body: Center( - // child: Column( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // Text('这是新页面'), - // ElevatedButton( - // onPressed: () => Navigator.pop(context), - // child: Text('返回'), - // ), - // ], - // ), - // ), - // ); - // } - @override State createState() => _RecordFormPage(); } @@ -34,6 +17,27 @@ class RecordFormPage extends StatefulWidget { class _RecordFormPage extends State { final _formKey = GlobalKey(); + final ImagePicker _picker = ImagePicker(); + XFile? _selectedImage; + + // 选择图片的方法 + Future _pickImage(ImageSource source) async { + try { + final XFile? image = await _picker.pickImage(source: source); + if (image != null) { + setState(() { + _selectedImage = image; + // 更新表单字段的值 + _formKey.currentState?.fields['image']?.didChange(image.path); + }); + } + } catch (e) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('选择图片失败: $e'))); + } + } + void confirmClick(BuildContext context) { if (_formKey.currentState?.saveAndValidate() ?? false) { showSuccessToast("新增记录成功"); @@ -42,6 +46,87 @@ class _RecordFormPage extends State { } } + // 显示图片操作弹窗(查看/删除) + Future _showImageActionDialog(BuildContext context) async { + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('图片操作'), + actions: [ + // 查看图片 + TextButton( + onPressed: () { + Navigator.pop(context); + _previewImage(context); + }, + child: const Text('查看'), + ), + // 删除图片 + TextButton( + onPressed: () { + setState(() { + _selectedImage = null; // 清空选中的图片 + }); + Navigator.pop(context); + }, + child: const Text( + '删除', + style: TextStyle(color: Colors.red), + ), + ), + ], + ), + ); + } + +// 显示选择图片来源的弹窗(相册/相机) + Future _showImageSourceDialog(BuildContext context) async { + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('选择图片来源'), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + _pickImage(ImageSource.gallery); + }, + child: const Text('相册'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + _pickImage(ImageSource.camera); + }, + child: const Text('相机'), + ), + ], + ), + ); + } + +// 预览图片(全屏查看) + void _previewImage(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Scaffold( + backgroundColor: Colors.black, + body: Center( + child: Image.file( + File(_selectedImage!.path), + fit: BoxFit.contain, + ), + ), + floatingActionButton: IconButton( + icon: const Icon(Icons.close, color: Colors.white), + onPressed: () => Navigator.pop(context), + ), + ), + ), + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -59,7 +144,7 @@ class _RecordFormPage extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - formLabelText(labelText: '菜谱名称'), + formLabelText(labelText: '菜谱名称', isRequired: true), const SizedBox(height: 5), FormBuilderTextField( name: 'name', @@ -73,7 +158,7 @@ class _RecordFormPage extends State { keyboardType: TextInputType.name, ), const SizedBox(height: 10), - formLabelText(labelText: '完成时间'), + formLabelText(labelText: '完成时间', isRequired: true), const SizedBox(height: 5), FormBuilderDateTimePicker( name: 'date', @@ -87,6 +172,69 @@ class _RecordFormPage extends State { format: DateFormat('yyyy-MM-dd'), // enabled: _formKey.currentState?.fields['date']?.enabled ?? true, ), + const SizedBox(height: 10), + formLabelText(labelText: '美食图片'), + const SizedBox(height: 5), + + // 图片选择区域(核心优化部分) + GestureDetector( + onTap: () { + // 已选择图片时,显示操作弹窗;未选择时直接打开选择器 + if (_selectedImage != null) { + _showImageActionDialog(context); + } else { + _showImageSourceDialog(context); // 弹出选择相册/相机的对话框 + } + }, + child: Container( + height: 200, + width: 200, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.grey[300]!, + width: 1, + style: BorderStyle.solid, + ), + // 有图片时显示图片,无图片时显示背景色 + color: + _selectedImage == null ? Colors.grey[50] : null, + image: + _selectedImage != null + ? DecorationImage( + image: FileImage( + File(_selectedImage!.path), + ), + fit: BoxFit.contain, + ) + : null, + ), + // 初始状态显示+号;有图片时隐藏 + child: + _selectedImage == null + ? Center( + child: Icon( + Icons.add, + color: Colors.grey[400], + size: 48, // 大号+号,视觉更清晰 + ), + ) + : null, + ), + ), + + // 隐藏的表单字段(保留原功能) + FormBuilderTextField( + name: 'image', + initialValue: _selectedImage?.path ?? '', + enabled: false, + validator: (value) { + if (value == null || value.isEmpty) { + return '请选择一张图片'; + } + return null; + }, + ), ], ), ), diff --git a/lib/widgets/common/index.dart b/lib/widgets/common/index.dart index de8ba47..2128d24 100644 --- a/lib/widgets/common/index.dart +++ b/lib/widgets/common/index.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; -Widget formLabelText({required String labelText}) { +Widget formLabelText({required String labelText, bool isRequired = false}) { return Text.rich( TextSpan( children: [ - TextSpan(text: '* ', style: TextStyle(color: Colors.red)), + if (isRequired) + const TextSpan(text: '* ', style: TextStyle(color: Colors.red)), TextSpan(text: labelText), ], ), diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..64a0ece 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..2db3c22 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..14b5f7c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import file_selector_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 8df39fe..bf01375 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.4+2" cupertino_icons: dependency: "direct main" description: @@ -65,6 +73,38 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.2" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.4+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.3+4" flutter: dependency: "direct main" description: flutter @@ -107,6 +147,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.28" flutter_test: dependency: "direct dev" description: flutter @@ -133,6 +181,86 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "11.1.2" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.12+23" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1+2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1+1" intl: dependency: "direct main" description: @@ -197,6 +325,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" path: dependency: transitive description: @@ -205,6 +341,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" simple_gesture_detector: dependency: transitive description: @@ -282,6 +426,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.7" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d2748a5..eb0cdd4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: flutter_form_builder: ^10.0.0 form_builder_validators: ^11.1.2 fluttertoast: ^8.2.2 + image_picker: ^1.1.2 intl: ^0.19.0 dev_dependencies: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..77ab7a0 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..a423a02 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST