feat:增加Minio工具类

This commit is contained in:
2025-11-18 20:03:25 +08:00
parent 9a8e1a7419
commit b6995d8dd1
15 changed files with 332 additions and 194 deletions

View File

@@ -51,11 +51,11 @@ Future<List<String>> queryFoodNameListApi() {
);
}
Future<bool> addRecordApi(Record record) {
Future<bool> addRecordApi(FoodRecord record) {
return HttpUtil().post<bool>("/food/record", data: record);
}
Future<bool> updateRecordApi(int id, Record record) {
Future<bool> updateRecordApi(int id, FoodRecord record) {
return HttpUtil().put<bool>("/food/record/$id", data: record);
}
@@ -83,11 +83,11 @@ Future<bool> deleteRecipeFavouriteApi(int id) {
return HttpUtil().delete<bool>("/food/recipe/$id/like");
}
Future<List<Record>> queryRecordApi(String startDate, String endDate) {
return HttpUtil().get<List<Record>>(
Future<List<FoodRecord>> queryRecordApi(String startDate, String endDate) {
return HttpUtil().get<List<FoodRecord>>(
"/food/record",
queryParameters: {"startDate": startDate, "endDate": endDate},
converter: (data) => convertListResponse<Record>(data, Record.fromJson),
converter: (data) => convertListResponse<FoodRecord>(data, FoodRecord.fromJson),
);
}

View File

@@ -1,18 +1,26 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:food_hub_app/provider/food_provider.dart';
import 'package:food_hub_app/utils/sp_util.dart';
import 'package:food_hub_app/views/home.dart';
import 'package:food_hub_app/views/login.dart';
import 'package:food_hub_app/views/record_form.dart';
import 'package:food_hub_app/views/recipe_detail.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:provider/provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
FormBuilderLocalizations.delegate.load(const Locale('zh', 'CN'));
await SPUtil.init();
runApp(const MyApp());
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => FoodProvider()),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {

View File

@@ -9,7 +9,11 @@ class RecipeMaterial {
String name;
String amount;
RecipeMaterial({required this.type, required this.name, required this.amount});
RecipeMaterial({
required this.type,
required this.name,
required this.amount,
});
factory RecipeMaterial.fromJson(Map<String, dynamic> json) =>
_$RecipeMaterialFromJson(json);
@@ -24,9 +28,14 @@ class RecipeStep {
String content;
String imageUrl;
RecipeStep({required this.sort, required this.content, required this.imageUrl});
RecipeStep({
required this.sort,
required this.content,
required this.imageUrl,
});
factory RecipeStep.fromJson(Map<String, dynamic> json) => _$RecipeStepFromJson(json);
factory RecipeStep.fromJson(Map<String, dynamic> json) =>
_$RecipeStepFromJson(json);
Map<String, dynamic> toJson() => _$RecipeStepToJson(this);
}
@@ -56,7 +65,7 @@ class RecipeComment {
/// 成果信息
@JsonSerializable()
class Record {
class FoodRecord {
int? id;
String name;
String category;
@@ -64,7 +73,7 @@ class Record {
String date;
String imageUrl;
Record({
FoodRecord({
this.id,
required this.name,
required this.category,
@@ -73,20 +82,31 @@ class Record {
required this.imageUrl,
});
factory Record.fromJson(Map<String, dynamic> json) => _$RecordFromJson(json);
factory FoodRecord.fromJson(Map<String, dynamic> json) =>
_$RecordFromJson(json);
Map<String, dynamic> toJson() => _$RecordToJson(this);
static FoodRecord getEmpty() {
return FoodRecord(
id: 0,
name: '',
category: '',
person: 0,
date: '',
imageUrl: '',
);
}
}
@JsonSerializable()
class RecipeQuery {
String category;
RecipeQuery({
required this.category
});
RecipeQuery({required this.category});
factory RecipeQuery.fromJson(Map<String, dynamic> json) => _$RecipeQueryFromJson(json);
factory RecipeQuery.fromJson(Map<String, dynamic> json) =>
_$RecipeQueryFromJson(json);
Map<String, dynamic> toJson() => _$RecipeQueryToJson(this);
}
@@ -105,7 +125,7 @@ class Recipe {
String avatar;
List<RecipeMaterial> materialList;
List<RecipeStep> stepList;
List<Record> recordList;
List<FoodRecord> recordList;
List<int> likeList;
int likeCount;
List<int> favouriteList;
@@ -146,7 +166,7 @@ class RecipeSummary {
String category;
double recommendRate;
bool isShare;
List<Record> recordList;
List<FoodRecord> recordList;
int likeCount;
int favouriteCount;
int commentCount;
@@ -169,7 +189,8 @@ class RecipeSummary {
required this.commentCount,
});
factory RecipeSummary.fromJson(Map<String, dynamic> json) => _$RecipeSummaryFromJson(json);
factory RecipeSummary.fromJson(Map<String, dynamic> json) =>
_$RecipeSummaryFromJson(json);
Map<String, dynamic> toJson() => _$RecipeSummaryToJson(this);
}
@@ -187,7 +208,7 @@ class RecipeDetail {
String avatar;
List<RecipeMaterial> materialList;
List<RecipeStep> stepList;
List<Record> recordList;
List<FoodRecord> recordList;
List<int> likeList;
List<int> favouriteList;
List<RecipeComment> commentList;
@@ -210,7 +231,8 @@ class RecipeDetail {
required this.commentList,
});
factory RecipeDetail.fromJson(Map<String, dynamic> json) => _$RecipeDetailFromJson(json);
factory RecipeDetail.fromJson(Map<String, dynamic> json) =>
_$RecipeDetailFromJson(json);
Map<String, dynamic> toJson() => _$RecipeDetailToJson(this);
}

View File

@@ -51,7 +51,7 @@ Map<String, dynamic> _$RecipeCommentToJson(RecipeComment instance) =>
'date': instance.date,
};
Record _$RecordFromJson(Map<String, dynamic> json) => Record(
FoodRecord _$RecordFromJson(Map<String, dynamic> json) => FoodRecord(
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String,
category: json['category'] as String,
@@ -60,7 +60,7 @@ Record _$RecordFromJson(Map<String, dynamic> json) => Record(
imageUrl: json['imageUrl'] as String,
);
Map<String, dynamic> _$RecordToJson(Record instance) => <String, dynamic>{
Map<String, dynamic> _$RecordToJson(FoodRecord instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'category': instance.category,
@@ -95,7 +95,7 @@ Recipe _$RecipeFromJson(Map<String, dynamic> json) => Recipe(
.toList(),
recordList:
(json['recordList'] as List<dynamic>)
.map((e) => Record.fromJson(e as Map<String, dynamic>))
.map((e) => FoodRecord.fromJson(e as Map<String, dynamic>))
.toList(),
likeList:
(json['likeList'] as List<dynamic>)
@@ -147,7 +147,7 @@ RecipeSummary _$RecipeSummaryFromJson(Map<String, dynamic> json) =>
avatar: json['avatar'] as String,
recordList:
(json['recordList'] as List<dynamic>)
.map((e) => Record.fromJson(e as Map<String, dynamic>))
.map((e) => FoodRecord.fromJson(e as Map<String, dynamic>))
.toList(),
likeCount: (json['likeCount'] as num).toInt(),
favouriteCount: (json['favouriteCount'] as num).toInt(),
@@ -190,7 +190,7 @@ RecipeDetail _$RecipeDetailFromJson(Map<String, dynamic> json) => RecipeDetail(
.toList(),
recordList:
(json['recordList'] as List<dynamic>)
.map((e) => Record.fromJson(e as Map<String, dynamic>))
.map((e) => FoodRecord.fromJson(e as Map<String, dynamic>))
.toList(),
likeList:
(json['likeList'] as List<dynamic>)

View File

@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:food_hub_app/models/recipe.dart';
class FoodProvider with ChangeNotifier {
late FoodRecord _recordFormItem;
FoodRecord get recordFormItem => _recordFormItem;
void resetRecordForm() {
_recordFormItem = FoodRecord.getEmpty();
notifyListeners();
}
void initRecordForm(FoodRecord record) {
_recordFormItem = record;
notifyListeners();
}
}

View File

@@ -0,0 +1,57 @@
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:file_picker/file_picker.dart';
import 'package:minio/io.dart';
import 'package:minio/minio.dart';
class MinIOHelper {
static final MinIOHelper _instance = MinIOHelper._internal();
factory MinIOHelper() => _instance;
final String rustfsIp = '14.103.235.151';
final String rustfsFileUrl = 'http://14.103.235.151:9100';
final String bucketName = 'flisp';
MinIOHelper._internal() {
_minio = Minio(
endPoint: rustfsIp,
port: 9100,
accessKey: "tHSFfcDW8qpCzKa2Xg6Y",
secretKey: "oq79EeYJ4jdczRp2IHUMCnbKtSw58NgDlG3sOkvX",
useSSL: false,
);
}
late Minio _minio;
Future<String> uploadFile({
required PlatformFile file,
Function(double)? onProgress,
}) async {
try {
String hashName = await _generateMD5HashName(file.path!);
String fileName = '$hashName${_getFileExtension(file.name)}';
await _minio.fPutObject(bucketName, fileName, file.path!);
return fileName;
} catch (e) {
throw Exception('文件上传失败: $e');
}
}
String _getFileExtension(String fileName) {
if (fileName.contains('.')) {
return '.${fileName.split('.').last.toLowerCase()}';
}
return '';
}
Future<String> _generateMD5HashName(String filePath) async {
final file = File(filePath);
final bytes = await file.readAsBytes();
final hash = md5.convert(bytes);
return hash.toString();
}
}

View File

@@ -71,7 +71,7 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('菜谱信息')),
backgroundColor: Color(0xFFF4F4F4),
backgroundColor: Color(0xFFF5F5F5),
body: SingleChildScrollView(
child: Padding(padding: EdgeInsets.all(5), child: _buildRecipeDetail()),
),

View File

@@ -1,10 +1,17 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:food_hub_app/provider/food_provider.dart';
import 'package:food_hub_app/utils/minio_utils.dart';
import 'package:food_hub_app/widgets/common/form.dart';
import 'package:image_picker/image_picker.dart';
import 'package:food_hub_app/widgets/common/image.dart';
import 'package:food_hub_app/widgets/common/index.dart';
import 'package:food_hub_app/models/recipe.dart';
import 'package:intl/intl.dart';
import 'dart:io';
import 'package:provider/provider.dart';
class RecordFormPage extends StatefulWidget {
const RecordFormPage({super.key});
@@ -17,9 +24,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
final _focusNode = FocusNode();
static const String _nameField = 'name';
static const String _dateField = 'date';
final ImagePicker _picker = ImagePicker();
File? imageUrl; // 改为单张图片变量
File? imageFile;
@override
void dispose() {
@@ -29,12 +34,12 @@ class _RecordFormPageState extends State<RecordFormPage> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colors = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('新增记录', style: TextStyle(color: Colors.white)),
backgroundColor: theme.primaryColor,
backgroundColor: colors.primary,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
@@ -43,24 +48,16 @@ class _RecordFormPageState extends State<RecordFormPage> {
backgroundColor: const Color(0xFFF5F5F5),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(5),
child: Card(
elevation: 0,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.all(10),
child: _buildFormBuilder(),
),
),
padding: EdgeInsets.all(5),
child: buildCard(context: context, child: _buildFormBuilder()),
),
),
);
}
Widget _buildFormBuilder() {
final foodProvider = context.watch<FoodProvider>();
return FormBuilder(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
@@ -82,7 +79,10 @@ class _RecordFormPageState extends State<RecordFormPage> {
_buildImageUploadArea(),
const SizedBox(height: 10),
buildFormButtonGroup(context: context, onConfirm: () => _submitForm),
buildFormButtonGroup(
context: context,
onConfirm: () => _submitForm(),
),
],
),
);
@@ -93,7 +93,11 @@ class _RecordFormPageState extends State<RecordFormPage> {
return FormBuilderTextField(
name: _nameField,
focusNode: _focusNode,
decoration: buildInputDecoration(context: context, hintText: '请输入菜谱名称'),
decoration: buildInputDecoration(
context: context,
hintText: '请输入菜谱名称',
prefixIcon: const Icon(Icons.title, size: 20, color: Color(0xFF86909C)),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入菜谱名称';
@@ -132,10 +136,10 @@ class _RecordFormPageState extends State<RecordFormPage> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (imageUrl != null)
if (imageFile != null)
_buildImagePreviewItem()
else
_buildImageUploadButton(),
buildImageUploadButton(onPickImage: _pickImage),
],
);
}
@@ -147,109 +151,8 @@ class _RecordFormPageState extends State<RecordFormPage> {
borderRadius: BorderRadius.circular(8),
child: Stack(
children: [
GestureDetector(
onTap: () => _showImagePreview(),
child: Image(
image: FileImage(imageUrl!),
width: 120,
height: 120,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
width: 120,
height: 120,
color: Colors.grey[100],
child: const Center(
child: CircularProgressIndicator(strokeWidth: 2),
),
);
},
),
),
Positioned(
top: 6,
right: 6,
child: GestureDetector(
onTap: _removeImage,
child: Container(
width: 24,
height: 24,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 2,
spreadRadius: 0,
),
],
),
child: const Icon(Icons.close, color: Colors.white, size: 16),
),
),
),
],
),
),
);
}
void _showImagePreview() {
showDialog(
context: context,
barrierColor: Colors.black87, // 半透明黑色背景
builder:
(context) => Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
insetPadding: const EdgeInsets.all(16),
child: GestureDetector(
onTap: () => Navigator.pop(context), // 点击空白处关闭
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
// 预览图添加轻微阴影
boxShadow: [BoxShadow(color: Colors.black38, blurRadius: 10)],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.file(
imageUrl!,
fit: BoxFit.contain, // 保持图片比例
height: MediaQuery.of(context).size.height * 0.7, // 限制最大高度
),
),
),
),
),
);
}
/// 图片上传按钮
Widget _buildImageUploadButton() {
return InkWell(
onTap: _pickImage,
borderRadius: BorderRadius.circular(8),
child: Container(
width: 96,
height: 96,
decoration: BoxDecoration(
color: const Color(0xFFF2F3F5),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFDCDFE6), width: 1),
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.add, color: Color(0xFF86909C), size: 24),
SizedBox(height: 6),
Text(
'添加图片',
style: TextStyle(color: Color(0xFF86909C), fontSize: 13),
),
buildFileImage(context, imageFile!),
buildDeleteImage(onRemoveImage: _removeImage),
],
),
),
@@ -263,19 +166,6 @@ class _RecordFormPageState extends State<RecordFormPage> {
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
builder:
(context, child) => Theme(
data: ThemeData.light().copyWith(
primaryColor: Theme.of(context).primaryColor,
colorScheme: ColorScheme.light(
primary: Theme.of(context).primaryColor,
),
buttonTheme: const ButtonThemeData(
textTheme: ButtonTextTheme.primary,
),
),
child: child!,
),
);
if (picked != null) {
@@ -287,10 +177,17 @@ class _RecordFormPageState extends State<RecordFormPage> {
/// 选择图片(限制单张)
Future<void> _pickImage() async {
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpg', 'jpeg', 'png'],
allowMultiple: false,
);
if (result != null) {
PlatformFile file = result.files.first;
final fileName = await MinIOHelper().uploadFile(file: file);
setState(() {
imageUrl = File(image.path); // 直接覆盖现有图片
// imageFile = File(image.path);
});
}
}
@@ -298,14 +195,14 @@ class _RecordFormPageState extends State<RecordFormPage> {
/// 移除图片
void _removeImage() {
setState(() {
imageUrl = null;
imageFile = null;
});
}
/// 提交表单
void _submitForm() {
if (_formKey.currentState?.saveAndValidate() ?? false) {
if (imageUrl == null) {
if (imageFile == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请上传图片'),
@@ -318,7 +215,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
final formData = {
..._formKey.currentState!.value,
'imageUrl': imageUrl?.path, // 单张图片路径
'imageUrl': imageFile?.path,
};
ScaffoldMessenger.of(context).showSnackBar(

View File

@@ -79,7 +79,6 @@ Widget _buildCancelButton(BuildContext context) {
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF4E5969),
side: const BorderSide(color: Color(0xFFDCDFE6)),
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0,
),
@@ -90,10 +89,9 @@ Widget _buildCancelButton(BuildContext context) {
/// 提交按钮
Widget _buildSubmitButton(BuildContext context, VoidCallback onConfirm) {
return ElevatedButton(
onPressed: onConfirm,
onPressed: () => onConfirm(),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor, // 使用传入的context获取主题
padding: const EdgeInsets.symmetric(vertical: 15),
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0,
),

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
@@ -88,19 +90,35 @@ class _ImagePreviewPageState extends State<ImagePreviewPage> {
}
}
Widget buildFileImage(BuildContext context, File imageFile) {
return GestureDetector(
onTap: () => showFullScreenImage(context, FileImage(imageFile)),
child: Image(
image: FileImage(imageFile),
width: 120,
height: 120,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return buildImageLoadingIndicator(loadingProgress);
},
),
);
}
Widget buildNetworkImage(BuildContext context, String url) {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: GestureDetector(
onTap: () {
_showFullScreenImage(context, url);
showFullScreenImage(context, NetworkImage(url));
},
child: Image.network(
url,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return _buildLoadingIndicator(loadingProgress);
return buildImageLoadingIndicator(loadingProgress);
},
errorBuilder: (context, error, stackTrace) => _buildErrorImage(),
),
@@ -108,7 +126,7 @@ Widget buildNetworkImage(BuildContext context, String url) {
);
}
Widget _buildLoadingIndicator(ImageChunkEvent? loadingProgress) {
Widget buildImageLoadingIndicator(ImageChunkEvent? loadingProgress) {
return Center(
child: SizedBox(
width: 30,
@@ -131,15 +149,14 @@ Widget _buildErrorImage() {
);
}
Widget _buildPhotoView(String url) {
Widget _buildPhotoView(ImageProvider imageProvider) {
return PhotoView(
imageProvider: NetworkImage(url),
imageProvider: imageProvider,
backgroundDecoration: const BoxDecoration(color: Colors.transparent),
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 2,
initialScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(tag: url),
loadingBuilder: (context, event) => _buildLoadingIndicator(event),
loadingBuilder: (context, event) => buildImageLoadingIndicator(event),
errorBuilder: (context, error, stackTrace) => _buildErrorImage(),
);
}
@@ -155,7 +172,7 @@ Widget _buildCloseImage(BuildContext context) {
);
}
void _showFullScreenImage(BuildContext context, String url) {
void showFullScreenImage(BuildContext context, ImageProvider imageProvider) {
Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
@@ -169,7 +186,7 @@ void _showFullScreenImage(BuildContext context, String url) {
body: Stack(
children: [
// 可缩放图片
Positioned.fill(child: _buildPhotoView(url)),
Positioned.fill(child: _buildPhotoView(imageProvider)),
// 关闭按钮
_buildCloseImage(context),
],
@@ -179,3 +196,46 @@ void _showFullScreenImage(BuildContext context, String url) {
),
);
}
Widget buildImageUploadButton({required VoidCallback onPickImage}) {
return InkWell(
onTap: onPickImage,
borderRadius: BorderRadius.circular(8),
child: Container(
width: 96,
height: 96,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.black),
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.add, color: Color(0xFF86909C)),
SizedBox(height: 6),
Text('添加图片', style: TextStyle(color: Color(0xFF86909C))),
],
),
),
);
}
Widget buildDeleteImage({required VoidCallback onRemoveImage}) {
return Positioned(
top: 0,
right: 0,
child: GestureDetector(
onTap: onRemoveImage,
child: Container(
width: 24,
height: 24,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: const Icon(Icons.close, color: Colors.white, size: 16),
),
),
);
}

View File

@@ -17,8 +17,8 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
DateTime _selectedDay = DateTime.now();
DateTime _focusedDay = DateTime.now();
List<Record> recordList = [];
List<Record> selectRecordList = [];
List<FoodRecord> recordList = [];
List<FoodRecord> selectRecordList = [];
@override
void initState() {
@@ -109,7 +109,7 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
);
}
Widget recipeRecordItem(BuildContext context, Record record) {
Widget recipeRecordItem(BuildContext context, FoodRecord record) {
final colors = Theme.of(context).colorScheme;
return Card(

View File

@@ -15,7 +15,7 @@ class RecipeTimeline extends StatefulWidget {
}
class _RecipeTimeline extends State<RecipeTimeline> {
List<Record> recordList = [];
List<FoodRecord> recordList = [];
@override
void initState() {
@@ -30,7 +30,7 @@ class _RecipeTimeline extends State<RecipeTimeline> {
});
}
Widget buildTimelineCard(BuildContext context, Record record) {
Widget buildTimelineCard(BuildContext context, FoodRecord record) {
final imageUrls = ['${AppConfig.baseApiUrl}/${record.imageUrl}'];
void imageTapClick() {
@@ -79,7 +79,7 @@ class _RecipeTimeline extends State<RecipeTimeline> {
);
}
Widget buildTimeline(BuildContext context, List<Record> recordList) {
Widget buildTimeline(BuildContext context, List<FoodRecord> recordList) {
if (recordList.isEmpty) {
return buildEmptyData();
} else {

View File

@@ -5,10 +5,12 @@
import FlutterMacOS
import Foundation
import file_picker
import file_selector_macos
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@@ -41,6 +41,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
buffer:
dependency: transitive
description:
name: buffer
sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.3"
build:
dependency: transitive
description:
@@ -162,13 +170,13 @@ packages:
source: hosted
version: "0.3.4+2"
crypto:
dependency: transitive
dependency: "direct main"
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.6"
version: "3.0.7"
cupertino_icons:
dependency: "direct main"
description:
@@ -185,6 +193,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.0"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.7.11"
dio:
dependency: "direct main"
description:
@@ -233,6 +249,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.1"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: f8f4ea435f791ab1f817b4e338ed958cb3d04ba43d6736ffc39958d950754967
url: "https://pub.flutter-io.cn"
source: hosted
version: "10.3.6"
file_selector_linux:
dependency: transitive
description:
@@ -589,6 +613,22 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
minio:
dependency: "direct main"
description:
name: minio
sha256: ee2ce47766e46c7d164f960f2f5ed6a9a82844d877f6b82574f6876ec50c56d1
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.5.8"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
package_config:
dependency: transitive
description:
@@ -645,6 +685,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.1.0"
photo_view:
dependency: "direct main"
description:
@@ -677,6 +725,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.5.1"
provider:
dependency: "direct main"
description:
name: provider
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
@@ -970,6 +1026,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.3"
win32:
dependency: transitive
description:
name: win32
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.13.0"
xdg_directories:
dependency: transitive
description:
@@ -978,6 +1042,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:

View File

@@ -36,6 +36,7 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dio: ^5.7.0
provider: ^6.1.1
timelines_plus: ^1.0.7
table_calendar: ^3.1.3
toggle_switch: ^2.3.0
@@ -51,6 +52,9 @@ dependencies:
flutter_carousel_widget: ^3.1.0
easy_refresh: ^3.4.0
syncfusion_flutter_charts: ^30.1.41
minio: ^3.5.8
crypto: ^3.0.7
file_picker: ^10.3.3
dependency_overrides:
tdesign_flutter_adaptation: 3.16.0