feat:增加dio网络请求

This commit is contained in:
2025-07-15 19:54:32 +08:00
parent 3f12b03648
commit c8f93f15a1
16 changed files with 570 additions and 126 deletions

16
lib/api/recipe.dart Normal file
View File

@@ -0,0 +1,16 @@
import 'package:food_hub_app/models/recipe.dart';
import 'package:food_hub_app/utils/http_util.dart';
Future<Recipe> queryRecipeByIdApi(int id) {
return HttpUtil().get<Recipe>(
"/food/recipe/$id",
converter: (data) => Recipe.fromJson(data),
);
}
Future<List<Recipe>> queryRecipeByUserApi(int id) {
return HttpUtil().get<List<Recipe>>(
"/food/recipe/user/$id",
converter: (data) => data.map((item) => Recipe.fromJson(item)).toList()
);
}

View File

@@ -1,7 +1,7 @@
import 'package:food_hub_app/models/session.dart';
import 'package:food_hub_app/utils/HttpUtil.dart';
import 'package:food_hub_app/utils/http_util.dart';
Future<Session?> loginApi(String username, String password) {
Future<Session> loginApi(String username, String password) {
return HttpUtil().post<Session>(
"/session",
queryParameters: {"username": username, "password": password},

View File

@@ -1,12 +1,16 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.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/recordForm.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
FormBuilderLocalizations.delegate.load(const Locale('zh', 'CN'));
SPUtil.init();
runApp(const MyApp());
}

View File

@@ -1,30 +1,140 @@
class Recipe {
final String name;
final String category;
final String date;
final String imageUrl;
final int likeCount;
final int favoriteCount;
final int commentCount;
import 'package:json_annotation/json_annotation.dart';
Recipe(
this.name,
this.category,
this.date,
this.imageUrl,
this.likeCount,
this.favoriteCount,
this.commentCount,
);
part 'recipe.g.dart';
/// 食材信息
@JsonSerializable()
class Material {
String type;
String name;
String amount;
Material({
required this.type,
required this.name,
required this.amount,
});
factory Material.fromJson(Map<String, dynamic> json) =>
_$MaterialFromJson(json);
Map<String, dynamic> toJson() => _$MaterialToJson(this);
}
class Record {
final String name;
final String category;
final String date;
final String imageUrl;
/// 步骤信息
@JsonSerializable()
class Step {
int sort;
String content;
String imageUrl;
Record(this.name, this.category, this.date, this.imageUrl);
Step({
required this.sort,
required this.content,
required this.imageUrl,
});
factory Step.fromJson(Map<String, dynamic> json) =>
_$StepFromJson(json);
Map<String, dynamic> toJson() => _$StepToJson(this);
}
/// 评论信息
@JsonSerializable()
class Comment {
int id;
String username;
String avatar;
String content;
String date;
Comment({
required this.id,
required this.username,
required this.avatar,
required this.content,
required this.date,
});
factory Comment.fromJson(Map<String, dynamic> json) =>
_$CommentFromJson(json);
Map<String, dynamic> toJson() => _$CommentToJson(this);
}
/// 成果信息
@JsonSerializable()
class Record {
int id;
String name;
String? category;
int person;
String date;
String imageUrl;
Record({
required this.id,
required this.name,
this.category,
required this.person,
required this.date,
required this.imageUrl,
});
factory Record.fromJson(Map<String, dynamic> json) =>
_$RecordFromJson(json);
Map<String, dynamic> toJson() => _$RecordToJson(this);
}
/// 菜谱信息
@JsonSerializable()
class Recipe {
int? id;
String name;
String category;
double recommendRate;
String? remark;
bool isShare;
int? userId;
String? username;
String? avatar;
List<Material> materialList;
List<Step> stepList;
List<Record> recordList;
List<int>? likeList;
int? likeCount;
List<int>? favouriteList;
int? favouriteCount;
List<Comment>? commentList;
int? commentCount;
Recipe({
this.id,
required this.name,
required this.category,
required this.recommendRate,
this.remark,
required this.isShare,
this.userId,
this.username,
this.avatar,
required this.materialList,
required this.stepList,
required this.recordList,
this.likeList,
this.likeCount,
this.favouriteList,
this.favouriteCount,
this.commentList,
this.commentCount,
});
factory Recipe.fromJson(Map<String, dynamic> json) =>
_$RecipeFromJson(json);
Map<String, dynamic> toJson() => _$RecipeToJson(this);
}
enum ViewType { recipe, calendar, timeline }

125
lib/models/recipe.g.dart Normal file
View File

@@ -0,0 +1,125 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'recipe.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Material _$MaterialFromJson(Map<String, dynamic> json) => Material(
type: json['type'] as String,
name: json['name'] as String,
amount: json['amount'] as String,
);
Map<String, dynamic> _$MaterialToJson(Material instance) => <String, dynamic>{
'type': instance.type,
'name': instance.name,
'amount': instance.amount,
};
Step _$StepFromJson(Map<String, dynamic> json) => Step(
sort: (json['sort'] as num).toInt(),
content: json['content'] as String,
imageUrl: json['imageUrl'] as String,
);
Map<String, dynamic> _$StepToJson(Step instance) => <String, dynamic>{
'sort': instance.sort,
'content': instance.content,
'imageUrl': instance.imageUrl,
};
Comment _$CommentFromJson(Map<String, dynamic> json) => Comment(
id: (json['id'] as num).toInt(),
username: json['username'] as String,
avatar: json['avatar'] as String,
content: json['content'] as String,
date: json['date'] as String,
);
Map<String, dynamic> _$CommentToJson(Comment instance) => <String, dynamic>{
'id': instance.id,
'username': instance.username,
'avatar': instance.avatar,
'content': instance.content,
'date': instance.date,
};
Record _$RecordFromJson(Map<String, dynamic> json) => Record(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
category: json['category'] as String?,
person: (json['person'] as num).toInt(),
date: json['date'] as String,
imageUrl: json['imageUrl'] as String,
);
Map<String, dynamic> _$RecordToJson(Record instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'category': instance.category,
'person': instance.person,
'date': instance.date,
'imageUrl': instance.imageUrl,
};
Recipe _$RecipeFromJson(Map<String, dynamic> json) => Recipe(
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String,
category: json['category'] as String,
recommendRate: (json['recommendRate'] as num).toDouble(),
remark: json['remark'] as String?,
isShare: json['isShare'] as bool,
userId: (json['userId'] as num?)?.toInt(),
username: json['username'] as String?,
avatar: json['avatar'] as String?,
materialList:
(json['materialList'] as List<dynamic>)
.map((e) => Material.fromJson(e as Map<String, dynamic>))
.toList(),
stepList:
(json['stepList'] as List<dynamic>)
.map((e) => Step.fromJson(e as Map<String, dynamic>))
.toList(),
recordList:
(json['recordList'] as List<dynamic>)
.map((e) => Record.fromJson(e as Map<String, dynamic>))
.toList(),
likeList:
(json['likeList'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList(),
likeCount: (json['likeCount'] as num?)?.toInt(),
favouriteList:
(json['favouriteList'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList(),
favouriteCount: (json['favouriteCount'] as num?)?.toInt(),
commentList:
(json['commentList'] as List<dynamic>?)
?.map((e) => Comment.fromJson(e as Map<String, dynamic>))
.toList(),
commentCount: (json['commentCount'] as num?)?.toInt(),
);
Map<String, dynamic> _$RecipeToJson(Recipe instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'category': instance.category,
'recommendRate': instance.recommendRate,
'remark': instance.remark,
'isShare': instance.isShare,
'userId': instance.userId,
'username': instance.username,
'avatar': instance.avatar,
'materialList': instance.materialList,
'stepList': instance.stepList,
'recordList': instance.recordList,
'likeList': instance.likeList,
'likeCount': instance.likeCount,
'favouriteList': instance.favouriteList,
'favouriteCount': instance.favouriteCount,
'commentList': instance.commentList,
'commentCount': instance.commentCount,
};

View File

@@ -4,29 +4,29 @@ part 'session.g.dart';
@JsonSerializable()
class SaTokenInfo {
String? tokenName;
String? tokenValue;
bool? isLogin;
String tokenName;
String tokenValue;
bool isLogin;
dynamic loginId;
String? loginType;
int? tokenTimeout;
int? sessionTimeout;
int? tokenSessionTimeout;
int? tokenActiveTimeout;
String? loginDeviceType;
String loginType;
int tokenTimeout;
int sessionTimeout;
int tokenSessionTimeout;
int tokenActiveTimeout;
String loginDeviceType;
String? tag;
SaTokenInfo({
this.tokenName,
this.tokenValue,
this.isLogin,
this.loginId,
this.loginType,
this.tokenTimeout,
this.sessionTimeout,
this.tokenSessionTimeout,
this.tokenActiveTimeout,
this.loginDeviceType,
required this.tokenName,
required this.tokenValue,
required this.isLogin,
required this.loginId,
required this.loginType,
required this.tokenTimeout,
required this.sessionTimeout,
required this.tokenSessionTimeout,
required this.tokenActiveTimeout,
required this.loginDeviceType,
this.tag,
});
@@ -36,6 +36,7 @@ class SaTokenInfo {
@JsonSerializable()
class User {
int? id;
String? username;
int? gender;
String? phoneNumber;
@@ -47,7 +48,6 @@ class User {
String? job;
List<String>? tags;
String? description;
String? id;
User({
this.id,
@@ -70,10 +70,10 @@ class User {
@JsonSerializable()
class Session {
SaTokenInfo? saToken;
User? userInfo;
SaTokenInfo saToken;
User userInfo;
Session({this.saToken, this.userInfo});
Session({required this.saToken, required this.userInfo});
factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json);
Map<String, dynamic> toJson() => _$SessionToJson(this);

View File

@@ -7,16 +7,16 @@ part of 'session.dart';
// **************************************************************************
SaTokenInfo _$SaTokenInfoFromJson(Map<String, dynamic> json) => SaTokenInfo(
tokenName: json['tokenName'] as String?,
tokenValue: json['tokenValue'] as String?,
isLogin: json['isLogin'] as bool?,
tokenName: json['tokenName'] as String,
tokenValue: json['tokenValue'] as String,
isLogin: json['isLogin'] as bool,
loginId: json['loginId'],
loginType: json['loginType'] as String?,
tokenTimeout: (json['tokenTimeout'] as num?)?.toInt(),
sessionTimeout: (json['sessionTimeout'] as num?)?.toInt(),
tokenSessionTimeout: (json['tokenSessionTimeout'] as num?)?.toInt(),
tokenActiveTimeout: (json['tokenActiveTimeout'] as num?)?.toInt(),
loginDeviceType: json['loginDeviceType'] as String?,
loginType: json['loginType'] as String,
tokenTimeout: (json['tokenTimeout'] as num).toInt(),
sessionTimeout: (json['sessionTimeout'] as num).toInt(),
tokenSessionTimeout: (json['tokenSessionTimeout'] as num).toInt(),
tokenActiveTimeout: (json['tokenActiveTimeout'] as num).toInt(),
loginDeviceType: json['loginDeviceType'] as String,
tag: json['tag'] as String?,
);
@@ -36,7 +36,7 @@ Map<String, dynamic> _$SaTokenInfoToJson(SaTokenInfo instance) =>
};
User _$UserFromJson(Map<String, dynamic> json) => User(
id: json['id'] as String?,
id: (json['id'] as num?)?.toInt(),
username: json['username'] as String?,
gender: (json['gender'] as num?)?.toInt(),
phoneNumber: json['phoneNumber'] as String?,
@@ -54,6 +54,7 @@ User _$UserFromJson(Map<String, dynamic> json) => User(
);
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'id': instance.id,
'username': instance.username,
'gender': instance.gender,
'phoneNumber': instance.phoneNumber,
@@ -65,18 +66,11 @@ Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'job': instance.job,
'tags': instance.tags,
'description': instance.description,
'id': instance.id,
};
Session _$SessionFromJson(Map<String, dynamic> json) => Session(
saToken:
json['saToken'] == null
? null
: SaTokenInfo.fromJson(json['saToken'] as Map<String, dynamic>),
userInfo:
json['userInfo'] == null
? null
: User.fromJson(json['userInfo'] as Map<String, dynamic>),
saToken: SaTokenInfo.fromJson(json['saToken'] as Map<String, dynamic>),
userInfo: User.fromJson(json['userInfo'] as Map<String, dynamic>),
);
Map<String, dynamic> _$SessionToJson(Session instance) => <String, dynamic>{

View File

@@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:food_hub_app/utils/sp_util.dart';
import 'package:food_hub_app/widgets/common/index.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
class HttpUtil {
static final HttpUtil _instance = HttpUtil._internal();
@@ -38,8 +38,10 @@ class HttpUtil {
if (options.data != null) {
print("请求参数: ${options.data}");
}
// token等操作
// options.headers['Authorization'] = 'Bearer your_token';
if (SPUtil.getString('token').isNotEmpty) {
options.headers['satoken'] = SPUtil.getString('token');
}
return handler.next(options);
},
),
@@ -60,18 +62,7 @@ class HttpUtil {
_dio.interceptors.add(
InterceptorsWrapper(
onError: (DioException e, handler) {
if (e.response != null) {
final responseData = e.response?.data;
if (responseData is Map<String, dynamic>) {
// JSON错误格式
print(responseData['message']?.toString());
showErrorToast('错误');
} else {
// JSON格式错误
print('网络错误: ${e.message}');
}
}
_handleError(e);
return handler.next(e);
},
),
@@ -79,7 +70,7 @@ class HttpUtil {
}
//
Future<T?> _request<T>(
Future<T> _request<T>(
String path, {
required String method,
dynamic data,
@@ -99,21 +90,15 @@ class HttpUtil {
return converter(response.data);
}
// T是dynamic
if (T == dynamic) {
return response.data as T;
}
//
return response.data as T?;
return response.data;
} catch (e) {
_handleError(e);
rethrow;
}
}
// GET请求
Future<T?> get<T>(
Future<T> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
T Function(dynamic data)? converter,
@@ -125,7 +110,7 @@ class HttpUtil {
);
// POST请求
Future<T?> post<T>(
Future<T> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
@@ -139,7 +124,7 @@ class HttpUtil {
);
// PUT请求
Future<T?> put<T>(
Future<T> put<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
@@ -153,7 +138,7 @@ class HttpUtil {
);
// DELETE请求
Future<T?> delete<T>(
Future<T> delete<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
@@ -191,32 +176,68 @@ class HttpUtil {
//
void _handleError(dynamic error) {
String errorMessage = '未知错误';
if (error is DioException) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
print("连接超时");
errorMessage = '连接超时,请检查网络连接';
break;
case DioExceptionType.sendTimeout:
print("发送超时");
errorMessage = '发送超时,请检查网络连接';
break;
case DioExceptionType.receiveTimeout:
print("接收超时");
errorMessage = '接收超时,请检查网络连接';
break;
case DioExceptionType.cancel:
print("请求取消");
errorMessage = '请求取消';
break;
case DioExceptionType.badCertificate:
print("证书错误");
errorMessage = '证书验证失败';
break;
case DioExceptionType.badResponse:
print("错误响应");
// HTTP响应错误4xx, 5xx
final statusCode = error.response?.statusCode ?? 0;
final responseData = error.response?.data;
if (responseData is Map<String, dynamic> && responseData.containsKey('message')) {
//
errorMessage = responseData['message']?.toString() ?? '未知错误';
} else {
errorMessage = _getHttpErrorMessage(statusCode);
}
break;
case DioExceptionType.connectionError:
print("连接错误");
errorMessage = '网络连接错误,请检查网络设置';
break;
case DioExceptionType.unknown:
print("未知错误");
if (error.error != null) {
errorMessage = '未知错误: ${error.error.toString()}';
}
errorMessage = '发生未知错误';
break;
}
} else {
print("未知错误: $error");
errorMessage = '非Dio错误: $error';
}
print(errorMessage);
showErrorToast(errorMessage);
}
String _getHttpErrorMessage(int statusCode) {
// HTTP状态码返回对应的错误消息
switch (statusCode) {
case 400: return '错误请求,请检查参数';
case 401: return '未授权,请登录';
case 403: return '禁止访问,权限不足';
case 404: return '资源不存在';
case 405: return '方法不允许';
case 408: return '请求超时';
case 500: return '服务器内部错误';
case 502: return '网关错误';
case 503: return '服务不可用';
case 504: return '网关超时';
default: return 'HTTP错误: $statusCode';
}
}
}

61
lib/utils/sp_util.dart Normal file
View File

@@ -0,0 +1,61 @@
import 'package:shared_preferences/shared_preferences.dart';
class SPUtil {
static late SharedPreferences _prefs;
/// 初始化
static Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
/// 存储数据
static Future<bool> set(String key, dynamic value) {
if (value is String) return _prefs.setString(key, value);
if (value is int) return _prefs.setInt(key, value);
if (value is bool) return _prefs.setBool(key, value);
if (value is double) return _prefs.setDouble(key, value);
if (value is List<String>) return _prefs.setStringList(key, value);
throw ArgumentError('Unsupported value type: ${value.runtimeType}');
}
/// 获取数据
static dynamic get(String key, [dynamic defaultValue]) {
if (!_prefs.containsKey(key)) return defaultValue;
return _prefs.get(key) ?? defaultValue;
}
/// 获取字符串
static String getString(String key, [String defaultValue = '']) {
return _prefs.getString(key) ?? defaultValue;
}
/// 获取布尔值
static bool getBool(String key, [bool defaultValue = false]) {
return _prefs.getBool(key) ?? defaultValue;
}
/// 获取整数
static int getInt(String key, [int defaultValue = 0]) {
return _prefs.getInt(key) ?? defaultValue;
}
/// 获取浮点数
static double getDouble(String key, [double defaultValue = 0.0]) {
return _prefs.getDouble(key) ?? defaultValue;
}
/// 获取字符串列表
static List<String> getStringList(String key, [List<String> defaultValue = const []]) {
return _prefs.getStringList(key) ?? defaultValue;
}
/// 删除数据
static Future<bool> remove(String key) {
return _prefs.remove(key);
}
/// 清空所有数据
static Future<bool> clear() {
return _prefs.clear();
}
}

View File

@@ -1,8 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:food_hub_app/api/recipe.dart';
import 'package:food_hub_app/api/session.dart';
import 'package:food_hub_app/models/recipe.dart';
import 'package:food_hub_app/models/session.dart';
import 'package:food_hub_app/utils/sp_util.dart';
import 'package:food_hub_app/widgets/common/index.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
class LoginPage extends StatefulWidget {
@@ -25,7 +30,13 @@ class _LoginPage extends State<LoginPage> {
];
void loginClick(BuildContext context) async {
await loginApi("Cxx", "1232");
SPUtil.clear();
Session session = await loginApi("Cxx0822", "19940822Cxx");
SPUtil.set('token', session.saToken.tokenValue);
Recipe recipe = await queryRecipeByIdApi(614660173221957);
List<Recipe> recipeList = await queryRecipeByUserApi(691110779121733);
// SPUtil.set("token", session?.saToken?.tokenValue);
// Navigator.pushNamed(context, '/home');
return;

View File

@@ -15,15 +15,9 @@ class RecordPage extends StatefulWidget {
class _RecordPageState extends State<RecordPage>
with SingleTickerProviderStateMixin {
final List<Recipe> recipeList = [
Recipe("韭菜炒鸡蛋", "家常菜", "2025-05-04", "https://picsum.photos/200", 10, 2, 4),
Recipe("红烧肉", "家常菜", "2025-05-05", "https://picsum.photos/200", 12, 4, 7),
];
final List<Recipe> recipeList = [];
final List<Record> recordList = [
Record("韭菜炒鸡蛋", "家常菜", "2025-05-04", "https://picsum.photos/200"),
Record("红烧肉", "家常菜", "2025-05-05", "https://picsum.photos/200"),
];
final List<Record> recordList = [];
late final TabController _tabController = TabController(
length: 3,
@@ -68,7 +62,7 @@ class _RecordPageState extends State<RecordPage>
children: [
RecipeList(recipeList: recipeList),
RecipeCalendar(),
RecipeTimeline(recipeList: recipeList),
RecipeTimeline(recordList: recordList),
],
),
),

View File

@@ -18,7 +18,7 @@ class RecipeCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
recipe.imageUrl,
recipe.recordList[0].imageUrl,
height: 200,
width: double.infinity,
fit: BoxFit.contain,
@@ -106,7 +106,7 @@ class RecipeCard extends StatelessWidget {
const SizedBox(width: 12),
_buildIconText(
icon: Icons.star_outline,
text: recipe.favoriteCount.toString(),
text: recipe.favouriteCount.toString(),
color: Colors.blue,
),
const SizedBox(width: 12),
@@ -121,7 +121,7 @@ class RecipeCard extends StatelessWidget {
children: [
_buildIconText(
icon: Icons.date_range,
text: recipe.date,
text: recipe.recordList[0].date,
color: Colors.red,
),
],

View File

@@ -3,23 +3,23 @@ import 'package:food_hub_app/models/recipe.dart';
import 'package:timelines_plus/timelines_plus.dart';
class RecipeTimeline extends StatelessWidget {
final List<Recipe> recipeList;
final List<Record> recordList;
const RecipeTimeline({super.key, required this.recipeList});
const RecipeTimeline({super.key, required this.recordList});
@override
Widget build(BuildContext context) {
return Timeline.tileBuilder(
theme: TimelineThemeData(nodePosition: 0, indicatorPosition: 0),
builder: TimelineTileBuilder.connected(
itemCount: recipeList.length,
itemCount: recordList.length,
connectorBuilder:
(context, index, type) => Connector.solidLine(thickness: 2),
indicatorBuilder: (context, index) {
return Indicator.dot(size: 12.0);
},
contentsBuilder: (context, index) {
return TimelineCard(recipe: recipeList[index]);
return TimelineCard(record: recordList[index]);
},
),
);
@@ -27,9 +27,9 @@ class RecipeTimeline extends StatelessWidget {
}
class TimelineCard extends StatelessWidget {
final Recipe recipe;
final Record record;
const TimelineCard({super.key, required this.recipe});
const TimelineCard({super.key, required this.record});
@override
Widget build(BuildContext context) {
@@ -41,7 +41,7 @@ class TimelineCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
recipe.date,
record.date,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Card(
@@ -51,10 +51,10 @@ class TimelineCard extends StatelessWidget {
padding: EdgeInsets.all(10),
child: Column(
children: [
Text(recipe.name, style: TextStyle(fontSize: 16)),
Text(record.name, style: TextStyle(fontSize: 16)),
const SizedBox(height: 10),
Image.network(
recipe.imageUrl,
record.imageUrl,
width: double.infinity,
height: 250,
fit: BoxFit.contain,