feat:更新组件UI

This commit is contained in:
2025-11-19 19:53:05 +08:00
parent 8acad9c63a
commit 07180965cc
13 changed files with 360 additions and 237 deletions

View File

@@ -5,10 +5,11 @@ class AppConfig {
// http://14.103.235.151:81/food-service
// static const String baseApiUrl = "http://14.103.235.151:81/food-service";
// static const String baseApiUrl = "http://192.168.1.3:8100";
// static const String baseApiUrl = "https://cxx0822.iepose.cn/food-api";
static const String baseApiUrl = "http://127.0.0.1:8083";
static const String baseApiUrl = "https://cxx0822.iepose.cn/food-api";
// static const String baseApiUrl = "http://192.168.1.103:8083";
static const String rustfsIp = '14.103.235.151';
static const String rustfsFileUrl = 'http://14.103.235.151:9100';
static const String bucketName = 'food';
static const String imageBaseUrl = '$rustfsFileUrl/$bucketName/';
// static const String imageBaseUrl = '$rustfsFileUrl/$bucketName/';
static const String imageBaseUrl = '$baseApiUrl/';
}

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:food_hub_app/apis/moment.dart';
import 'package:food_hub_app/apis/recipe.dart';
import 'package:food_hub_app/apis/stats.dart';
import 'package:food_hub_app/models/moment.dart';
import 'package:food_hub_app/models/recipe.dart';
import 'package:food_hub_app/models/stats.dart';
import 'package:food_hub_app/utils/date_util.dart';
@@ -13,6 +15,8 @@ class FoodProvider with ChangeNotifier {
bool isLoading = false;
String? error;
String queryCategory = '全部菜系';
late List<String> categoryList = [];
late List<RecipeSummary> recipeSummaryList = [];
DateTime selectedDay = DateTime.now();
@@ -31,6 +35,11 @@ class FoodProvider with ChangeNotifier {
late List<ChartData> categoryStats = [];
late List<ChartData> rankStats = [];
List<Moment> momentList = [];
int currentPage = 1;
final int pageSize = 3;
bool hasMore = true;
void resetRecordForm() {
recordFormItem = FoodRecord.getEmpty();
notifyListeners();
@@ -41,6 +50,23 @@ class FoodProvider with ChangeNotifier {
notifyListeners();
}
Future<void> queryCategoryList() async {
try {
error = null;
notifyListeners();
final result = await queryCategoryApi();
categoryList.clear();
categoryList.add('全部菜系');
categoryList.addAll(result);
} catch (e) {
error = '加载数据失败: $e';
debugPrint('加载数据失败: $e');
} finally {
notifyListeners();
}
}
Future<void> refreshRecipeList() async {
if (isLoading) return;
@@ -49,7 +75,8 @@ class FoodProvider with ChangeNotifier {
error = null;
notifyListeners();
final result = await queryRecipeApi(RecipeQuery(category: ""));
final category = queryCategory == '全部菜系' ? '' : queryCategory;
final result = await queryRecipeApi(RecipeQuery(category: category));
recipeSummaryList = result;
} catch (e) {
error = '加载数据失败: $e';
@@ -131,4 +158,50 @@ class FoodProvider with ChangeNotifier {
notifyListeners();
}
}
Future<void> queryMomentByPage({bool isRefresh = true}) async {
if (isLoading) return;
try {
isLoading = true;
error = null;
notifyListeners();
// 如果是刷新,重置页码
if (isRefresh) {
currentPage = 1;
}
final result = await queryMomentByPageApi(currentPage, pageSize);
// 更新数据
if (isRefresh) {
momentList = result.records;
} else {
momentList.addAll(result.records);
}
// 判断是否还有更多数据
hasMore = result.current < result.pages;
if (hasMore) {
currentPage++;
}
} catch (e) {
error = '加载数据失败: $e';
debugPrint('加载数据失败: $e');
} finally {
isLoading = false;
notifyListeners();
}
}
Future<void> loadMoreMomentList() async {
if (hasMore && !isLoading) {
await queryMomentByPage(isRefresh: false);
}
}
Future<void> refreshMomentList() async {
await queryMomentByPage(isRefresh: true);
}
}

View File

@@ -31,25 +31,35 @@ class _HomePage extends State<HomePage> {
},
),
actions: [
IconButton(
icon: Icon(Icons.search, color: Colors.white),
onPressed: () {
// 搜索功能
},
),
Row(
children: [
IconButton(
icon: Icon(Icons.search, color: Colors.white),
onPressed: () {
// 搜索功能
},
),
IconButton(
icon: Icon(Icons.add, color: Colors.white),
onPressed: () {
buildBottomSheet(context);
},
)
],
)
],
),
backgroundColor: Color(0xFFF5F5F5),
drawer: SettingsDrawer(),
body: Padding(padding: EdgeInsets.all(4), child: tabPages[_currentIndex]),
floatingActionButton: FloatingActionButton(
backgroundColor: Theme.of(context).colorScheme.primary,
onPressed: () => buildBottomSheet(context),
shape: const CircleBorder(),
mini: true,
child: const Icon(Icons.add, color: Colors.white, size: 30),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
// floatingActionButton: FloatingActionButton(
// backgroundColor: Theme.of(context).colorScheme.primary,
// onPressed: () => buildBottomSheet(context),
// shape: const CircleBorder(),
// mini: true,
// child: const Icon(Icons.add, color: Colors.white, size: 30),
// ),
// floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: buildBottomNavBar(
currentIndex: _currentIndex,
onTap: (index) {

View File

@@ -1,9 +1,10 @@
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:food_hub_app/apis/moment.dart';
import 'package:food_hub_app/models/moment.dart';
import 'package:food_hub_app/provider/food_provider.dart';
import 'package:food_hub_app/widgets/common/easy_refresh.dart';
import 'package:food_hub_app/widgets/common/index.dart';
import 'package:food_hub_app/widgets/moment/card.dart';
import 'package:provider/provider.dart';
class MomentPage extends StatefulWidget {
const MomentPage({super.key});
@@ -13,26 +14,23 @@ class MomentPage extends StatefulWidget {
}
class _MomentPageState extends State<MomentPage> {
List<Moment> momentList = [];
int _currentPage = 1;
final int _pageSize = 3;
bool _hasMore = true;
// 初始化EasyRefresh控制器
final EasyRefreshController _freshController = EasyRefreshController(
controlFinishRefresh: true,
controlFinishLoad: true,
);
bool _showScrollToTop = false;
final ScrollController _scrollController = ScrollController();
bool _showScrollToTop = false;
@override
void initState() {
super.initState();
// 初始加载数据
_loadData(isRefresh: true);
_scrollController.addListener(_onScroll);
// 初始化加载数据
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<FoodProvider>().refreshMomentList();
});
}
@override
@@ -43,50 +41,18 @@ class _MomentPageState extends State<MomentPage> {
super.dispose();
}
// 统一的数据加载方法
Future<void> _loadData({required bool isRefresh}) async {
try {
// 如果是刷新,重置页码
if (isRefresh) {
_currentPage = 1;
}
final result = await queryMomentByPageApi(_currentPage, _pageSize);
setState(() {
if (isRefresh) {
// 刷新时直接替换数据
momentList = result.records;
} else {
// 加载更多时追加数据
momentList.addAll(result.records);
}
// 判断是否还有更多数据
_hasMore = result.current < result.pages;
// 如果有更多数据,准备加载下一页
if (_hasMore) {
_currentPage++;
}
});
} catch (e) {
// 处理错误
debugPrint('加载数据失败: $e');
} finally {
_freshController.finishRefresh();
_freshController.resetFooter();
}
}
// 下拉刷新
Future<void> _onRefresh() async {
await _loadData(isRefresh: true);
await context.read<FoodProvider>().refreshMomentList();
_freshController.finishRefresh();
}
// 上拉加载
Future<void> _onLoad() async {
if (_hasMore) {
await _loadData(isRefresh: false);
final provider = context.read<FoodProvider>();
if (provider.hasMore) {
await provider.loadMoreMomentList();
_freshController.finishLoad(IndicatorResult.success);
} else {
_freshController.finishLoad(IndicatorResult.noMore);
@@ -111,78 +77,47 @@ class _MomentPageState extends State<MomentPage> {
}
// 滚动到顶部
void _scrollToTop() {
_scrollController.animateTo(
0,
duration: const Duration(milliseconds: 500), // 滚动动画时长
curve: Curves.easeInOut, // 滚动动画曲线
void _scrollToTop() => scrollToTopAnimateTo(_scrollController);
Widget _buildMomentList(FoodProvider provider) {
return ListView.builder(
controller: _scrollController,
itemCount: provider.momentList.length,
itemBuilder: (context, index) {
return MomentCard(moment: provider.momentList[index]);
},
);
}
@override
Widget build(BuildContext context) {
final provider = context.watch<FoodProvider>();
// 空状态显示
if (momentList.isEmpty) {
return EasyRefresh(
controller: _freshController,
onRefresh: _onRefresh,
child: buildEmptyData(),
);
if (provider.momentList.isEmpty) {
return buildEmptyData();
}
// 有数据时显示列表
return Stack(
children: [
EasyRefresh(
controller: _freshController,
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '准备刷新',
processingText: '刷新中...',
processedText: '刷新完成',
failedText: '刷新失败',
noMoreText: '没有更多数据',
showText: true,
messageText: '更新于 %T',
showMessage: true,
),
footer: ClassicFooter(
dragText: '上拉加载',
armedText: '释放加载',
readyText: '准备加载',
processingText: '加载中...',
processedText: '加载完成',
failedText: '加载失败',
noMoreText: '没有更多数据',
showText: true,
messageText: '更新于 %T',
showMessage: true,
),
buildEasyRefresh(
freshController: _freshController,
onRefresh: _onRefresh,
onLoad: _onLoad,
child: ListView.builder(
controller: _scrollController,
itemCount: momentList.length,
itemBuilder: (context, index) {
return MomentCard(moment: momentList[index]);
},
body: Stack(
children: [
if (provider.isLoading)
buildLoadingIndicator(context)
else
_buildMomentList(provider),
],
),
),
// 返回顶部按钮
if (_showScrollToTop)
Positioned(
right: 0,
bottom: 0,
child: FloatingActionButton(
onPressed: _scrollToTop,
backgroundColor: Theme.of(context).colorScheme.primary,
elevation: 5,
mini: true,
child: const Icon(Icons.arrow_upward, color: Colors.white),
),
),
buildScrollToTop(context: context, scrollToTop: _scrollToTop),
],
);
}

View File

@@ -86,7 +86,8 @@ class _RecordFormPageState extends State<RecordFormPage> {
else
buildImagePreviewItem(
context: context,
imageUrl: provider.recordFormItem.imageUrl,
imageUrls: [provider.recordFormItem.imageUrl],
index: 0,
onRemoveImage: () => _removeImage(provider),
),
],
@@ -110,7 +111,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
return const Iterable<String>.empty();
}
return _foodNameList.where(
(option) => option.toLowerCase().contains(
(option) => option.toLowerCase().contains(
textEditingValue.text.toLowerCase(),
),
);
@@ -123,10 +124,10 @@ class _RecordFormPageState extends State<RecordFormPage> {
},
optionsViewBuilder: (
BuildContext context,
AutocompleteOnSelected<String> onSelected,
Iterable<String> options,
) {
BuildContext context,
AutocompleteOnSelected<String> onSelected,
Iterable<String> options,
) {
Widget buildOptionItem(String option) {
return InkWell(
onTap: () => onSelected(option),
@@ -164,11 +165,11 @@ class _RecordFormPageState extends State<RecordFormPage> {
},
fieldViewBuilder: (
BuildContext context,
TextEditingController controller,
FocusNode focusNode,
VoidCallback onFieldSubmitted,
) {
BuildContext context,
TextEditingController controller,
FocusNode focusNode,
VoidCallback onFieldSubmitted,
) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (provider.recordFormItem.name.isNotEmpty &&
controller.text.isEmpty) {
@@ -184,11 +185,13 @@ class _RecordFormPageState extends State<RecordFormPage> {
}
Widget? buildSuffixIcon() {
return controller.text.isNotEmpty
final isShow = controller.text.isNotEmpty && !provider.isEditing;
return isShow
? IconButton(
icon: Icon(Icons.clear, size: 18),
onPressed: onClearRecipeName,
)
icon: Icon(Icons.clear, size: 18),
onPressed: onClearRecipeName,
)
: null;
}

View File

@@ -0,0 +1,65 @@
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
Widget buildEasyRefresh({
required EasyRefreshController freshController,
required Future<void> Function()? onRefresh,
required Future<void> Function()? onLoad,
required Widget body,
}) {
return EasyRefresh(
controller: freshController,
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '准备刷新',
processingText: '刷新中...',
processedText: '刷新完成',
failedText: '刷新失败',
noMoreText: '没有更多数据',
showText: true,
messageText: '更新于 %T',
showMessage: true,
),
footer: ClassicFooter(
dragText: '上拉加载',
armedText: '释放加载',
readyText: '准备加载',
processingText: '加载中...',
processedText: '加载完成',
failedText: '加载失败',
noMoreText: '没有更多数据',
showText: true,
messageText: '更新于 %T',
showMessage: true,
),
onRefresh: onRefresh,
onLoad: onLoad,
child: body,
);
}
Widget buildScrollToTop({
required BuildContext context,
required VoidCallback scrollToTop,
}) {
return Positioned(
right: 0,
bottom: 20,
child: FloatingActionButton(
onPressed: scrollToTop,
backgroundColor: Theme.of(context).colorScheme.primary,
elevation: 2,
mini: true,
child: const Icon(Icons.arrow_upward, color: Colors.white),
),
);
}
void scrollToTopAnimateTo(ScrollController controller) {
controller.animateTo(
0,
duration: const Duration(milliseconds: 500), // 滚动动画时长
curve: Curves.easeInOut, // 滚动动画曲线
);
}

View File

@@ -75,7 +75,7 @@ class _ImagePreviewPageState extends State<ImagePreviewPage> {
pageOptions:
widget.images.map((url) {
return PhotoViewGalleryPageOptions(
imageProvider: NetworkImage(url),
imageProvider: NetworkImage('${AppConfig.imageBaseUrl}$url'),
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 2,
// 点击空白处关闭预览
@@ -91,42 +91,27 @@ 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) {
Widget buildNetworkImage(
BuildContext context,
List<String> imageUrls,
int index,
) {
return AspectRatio(
aspectRatio: 1.5,
aspectRatio: 4 / 3,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: GestureDetector(
onTap: () {
showFullScreenImage(
context,
NetworkImage('${AppConfig.imageBaseUrl}$url'),
);
showFullScreenImage(context, imageUrls, index);
},
child: Image.network(
'${AppConfig.imageBaseUrl}$url',
'${AppConfig.imageBaseUrl}${imageUrls[index]}',
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return buildImageLoadingIndicator(loadingProgress);
},
errorBuilder: (context, error, stackTrace) => _buildErrorImage(),
errorBuilder: (context, error, stackTrace) => buildErrorImage(),
),
),
),
@@ -135,7 +120,8 @@ Widget buildNetworkImage(BuildContext context, String url) {
Widget buildImagePreviewItem({
required BuildContext context,
required String imageUrl,
required List<String> imageUrls,
required int index,
required VoidCallback onRemoveImage,
}) {
return Container(
@@ -144,7 +130,7 @@ Widget buildImagePreviewItem({
borderRadius: BorderRadius.circular(8),
child: Stack(
children: [
buildNetworkImage(context, imageUrl),
buildNetworkImage(context, imageUrls, index),
buildDeleteImage(onRemoveImage: onRemoveImage),
],
),
@@ -168,7 +154,7 @@ Widget buildImageLoadingIndicator(ImageChunkEvent? loadingProgress) {
);
}
Widget _buildErrorImage() {
Widget buildErrorImage() {
return Container(
color: Colors.grey[200],
child: const Icon(Icons.image, color: Colors.grey, size: 30),
@@ -183,7 +169,7 @@ Widget _buildPhotoView(ImageProvider imageProvider) {
maxScale: PhotoViewComputedScale.covered * 2,
initialScale: PhotoViewComputedScale.contained,
loadingBuilder: (context, event) => buildImageLoadingIndicator(event),
errorBuilder: (context, error, stackTrace) => _buildErrorImage(),
errorBuilder: (context, error, stackTrace) => buildErrorImage(),
);
}
@@ -198,29 +184,40 @@ Widget _buildCloseImage(BuildContext context) {
);
}
void showFullScreenImage(BuildContext context, ImageProvider imageProvider) {
Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return Scaffold(
backgroundColor: Colors.black.withAlpha(200),
body: Stack(
children: [
// 可缩放图片
Positioned.fill(child: _buildPhotoView(imageProvider)),
// 关闭按钮
_buildCloseImage(context),
],
),
);
},
void showFullScreenImage(
BuildContext context,
List<String> imageUrls,
int index,
) {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => ImagePreviewPage(images: imageUrls, initialIndex: index),
),
);
// Navigator.of(context).push(
// PageRouteBuilder(
// opaque: false,
// pageBuilder: (
// BuildContext context,
// Animation<double> animation,
// Animation<double> secondaryAnimation,
// ) {
// return Scaffold(
// backgroundColor: Colors.black.withAlpha(200),
// body: Stack(
// children: [
// // 可缩放图片
// Positioned.fill(child: _buildPhotoView(imageProvider)),
// // 关闭按钮
// _buildCloseImage(context),
// ],
// ),
// );
// },
// ),
// );
}
Widget buildImageUploadButton({required VoidCallback onPickImage}) {

View File

@@ -29,20 +29,18 @@ InputDecoration formInputDecoration({
}
Widget buildCard({required BuildContext context, required Widget child}) {
final colors = Theme
.of(context)
.colorScheme;
final colors = Theme.of(context).colorScheme;
return Card(
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: colors.surface,
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: colors.outline.withAlpha(50), width: 1),
border: Border.all(color: colors.outline.withAlpha(50)),
),
child: Padding(padding: EdgeInsets.all(8), child: child),
child: Padding(padding: EdgeInsets.all(10 ), child: child),
),
);
}
@@ -66,11 +64,7 @@ Widget buildToggleSwitch<T extends Enum>({
initialLabelIndex: tabValues.indexOf(currentTab),
totalSwitches: tabValues.length,
labels: labels,
activeBgColor: [Theme
.of(context)
.colorScheme
.primary
],
activeBgColor: [Theme.of(context).colorScheme.primary],
activeFgColor: Colors.white,
inactiveBgColor: Colors.grey.shade200,
inactiveFgColor: Colors.grey.shade700,
@@ -99,10 +93,7 @@ Widget circleIconButton({
fixedSize: Size(size, size),
shape: CircleBorder(),
elevation: 0,
backgroundColor: Theme
.of(context)
.colorScheme
.primary,
backgroundColor: Theme.of(context).colorScheme.primary,
padding: EdgeInsets.zero,
minimumSize: const Size(0, 0),
),
@@ -111,9 +102,7 @@ Widget circleIconButton({
}
Widget buildTag(BuildContext context, String title) {
final colors = Theme
.of(context)
.colorScheme;
final colors = Theme.of(context).colorScheme;
return Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
@@ -195,11 +184,7 @@ Widget buildLoadingIndicator(BuildContext context) {
color: colors.surface,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 8,
offset: Offset(0, 2),
),
BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 2)),
],
),
child: Column(

View File

@@ -102,17 +102,6 @@ class MomentCard extends StatelessWidget {
final List<String> imageUrls =
moment.imageList.map((path) => path).toList();
void imageTapClick(int index) {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
ImagePreviewPage(images: imageUrls, initialIndex: index),
),
);
}
return GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
@@ -121,13 +110,7 @@ class MomentCard extends StatelessWidget {
mainAxisSpacing: 4,
childAspectRatio: itemAspectRatio,
children: List.generate(imageCount, (index) {
// 单个图片项:添加点击事件
return GestureDetector(
// 点击图片时,跳转到预览页面
onTap: () => imageTapClick(index),
// 原图片组件
child: buildNetworkImage(context, imageUrls[index]),
);
return buildNetworkImage(context, imageUrls, index);
}),
);
}

View File

@@ -116,7 +116,7 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
return Card(
elevation: 0,
color: colors.onSecondary,
color: colors.primary.withAlpha(50),
child: Padding(
padding: EdgeInsets.all(10),
child: Row(

View File

@@ -14,7 +14,8 @@ class RecipeCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String firstImageUrl = recipe.recordList.first.imageUrl;
final List<String> imageUrls =
recipe.recordList.reversed.map((record) => record.imageUrl).toList();
Widget buildRecipeContent(BuildContext context) {
return Column(
@@ -33,6 +34,7 @@ class RecipeCard extends StatelessWidget {
),
],
),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
@@ -84,7 +86,7 @@ class RecipeCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildNetworkImage(context, firstImageUrl),
buildNetworkImage(context, imageUrls, 0),
SizedBox(height: 8),
GestureDetector(
onTap: () => navigatorToRecipeDetail(context),

View File

@@ -19,18 +19,82 @@ class _RecipeListState extends State<RecipeList> {
// 初始化加载数据
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<FoodProvider>().refreshRecipeList();
context.read<FoodProvider>().queryCategoryList();
});
}
Widget _buildSelectCategory(FoodProvider provider) {
final colors = Theme.of(context).colorScheme;
return Container(
width: 150,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[200]!),
),
padding: EdgeInsets.symmetric(horizontal: 16),
child: DropdownButton<String>(
value: provider.queryCategory,
hint: Text(
'请选择菜谱类别',
style: TextStyle(color: Colors.grey[500]),
),
isExpanded: true,
borderRadius: BorderRadius.circular(12),
dropdownColor: Colors.white,
elevation: 6,
underline: Container(),
items: provider.categoryList.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: colors.primary,
shape: BoxShape.circle,
),
),
SizedBox(width: 8),
Expanded(
child: Text(
value,
style: TextStyle(fontSize: 14),
),
)
],
),
);
}).toList(),
onChanged: (value) {
setState(() {
provider.queryCategory = value!;
provider.refreshRecipeList();
});
},
),
);
}
Widget _buildContent(FoodProvider provider) {
if (provider.recipeSummaryList.isEmpty) {
return buildEmptyData();
} else {
return ListView.builder(
itemCount: provider.recipeSummaryList.length,
itemBuilder: (context, index) {
return RecipeCard(recipe: provider.recipeSummaryList[index]);
},
return Column(
children: [
_buildSelectCategory(provider),
Expanded(
child: ListView.builder(
itemCount: provider.recipeSummaryList.length,
itemBuilder: (context, index) {
return RecipeCard(recipe: provider.recipeSummaryList[index]);
},
),
),
],
);
}
}

View File

@@ -41,7 +41,7 @@ class _RecipeTimeline extends State<RecipeTimeline> {
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 5),
buildNetworkImage(context, record.imageUrl),
buildNetworkImage(context, [record.imageUrl], 0),
],
),
],
@@ -88,7 +88,12 @@ class _RecipeTimeline extends State<RecipeTimeline> {
(year) =>
provider.refreshRecordList("$year-01-01", "$year-12-31"),
),
Expanded(child: buildTimeline(context, provider.recordList)),
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 10),
child: buildTimeline(context, provider.recordList),
),
),
],
);
}