feat:增加加载中组件

This commit is contained in:
2025-11-19 16:35:20 +08:00
parent 625a872d2f
commit 8acad9c63a
15 changed files with 616 additions and 344 deletions

View File

@@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:food_hub_app/apis/recipe.dart';
import 'package:food_hub_app/models/recipe.dart';
import 'package:food_hub_app/provider/food_provider.dart';
import 'package:food_hub_app/utils/date_util.dart';
import 'package:food_hub_app/utils/index.dart';
import 'package:food_hub_app/widgets/common/index.dart';
import 'package:table_calendar/table_calendar.dart';
@@ -16,46 +14,7 @@ class RecipeCalendar extends StatefulWidget {
}
class _RecipeCalendarState extends State<RecipeCalendar> {
DateTime _selectedDay = DateTime.now();
DateTime _focusedDay = DateTime.now();
List<FoodRecord> recordList = [];
List<FoodRecord> selectRecordList = [];
@override
void initState() {
super.initState();
_refreshRecord();
}
Future<void> _refreshRecord() async {
final result = await queryRecordApi(
getFirstDayOfMonth(_focusedDay),
getLastDayOfMonth(_focusedDay),
);
setState(() {
recordList = result;
if (recordList.isNotEmpty) {
_selectedDay = DateTime.parse(recordList.last.date);
} else {
_selectedDay = _focusedDay;
}
_refreshSelectRecord();
});
}
void _refreshSelectRecord() {
setState(() {
selectRecordList =
recordList
.where((record) => record.date == formatDateTime(_selectedDay))
.toList();
});
}
TableCalendar _recipeCalendar() {
TableCalendar _recipeCalendar(FoodProvider provider) {
return TableCalendar(
locale: 'zh_CN',
headerStyle: const HeaderStyle(
@@ -64,47 +23,53 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
),
firstDay: DateTime.utc(2010, 1, 1),
lastDay: DateTime.utc(2100, 12, 31),
focusedDay: _focusedDay,
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
focusedDay: provider.focusedDay,
selectedDayPredicate: (day) => isSameDay(provider.selectedDay, day),
onDaySelected: (selectedDay, focusedDay) {
if (!isSameDay(_selectedDay, selectedDay)) {
if (!isSameDay(provider.selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
_refreshSelectRecord();
provider.selectedDay = selectedDay;
provider.focusedDay = focusedDay;
provider.refreshSelectRecord();
});
}
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
_refreshRecord();
provider.focusedDay = focusedDay;
provider.refreshRecordList(null, null);
},
// 自定义日期单元格构建器
calendarBuilders: CalendarBuilders(
defaultBuilder:
(context, day, focusedDay) => _buildDateWidget(day, false, false),
(context, day, focusedDay) =>
_buildDateItem(day, false, false, provider),
selectedBuilder:
(context, day, focusedDay) => _buildDateWidget(day, true, false),
(context, day, focusedDay) =>
_buildDateItem(day, true, false, provider),
todayBuilder:
(context, day, focusedDay) => _buildDateWidget(day, false, true),
(context, day, focusedDay) =>
_buildDateItem(day, false, true, provider),
),
);
}
Widget _dailyItem(BuildContext context) {
Widget _dailyItem(BuildContext context, FoodProvider provider) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(formatDateTime(_selectedDay, 'MM月dd日 EEEE')),
if (selectRecordList.isEmpty)
Text(formatDateTime(provider.selectedDay, 'MM月dd日 EEEE')),
if (provider.selectRecordList.isEmpty)
buildEmptyData()
else
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: selectRecordList.length,
itemCount: provider.selectRecordList.length,
itemBuilder: (context, index) {
return _buildRecordItem(context, selectRecordList[index]);
return _buildRecordItem(
context,
provider.selectRecordList[index],
);
},
),
],
@@ -170,9 +135,14 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
);
}
Widget _buildDateWidget(DateTime day, bool isSelected, bool isToday) {
Widget _buildDateItem(
DateTime day,
bool isSelected,
bool isToday,
FoodProvider provider,
) {
// 检查当前日期是否在需要显示红点的列表中
bool shouldShowRedDot = recordList.any(
bool shouldShowRedDot = provider.recordList.any(
(item) => item.date == formatDateTime(day),
);
@@ -214,15 +184,31 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
);
}
@override
Widget build(BuildContext context) {
Widget _buildContent(FoodProvider provider) {
return Column(
children: [
buildCard(context: context, child: _recipeCalendar()),
buildCard(context: context, child: _recipeCalendar(provider)),
Expanded(
child: buildCard(context: context, child: _dailyItem(context)),
child: buildCard(
context: context,
child: _dailyItem(context, provider),
),
),
],
);
}
@override
Widget build(BuildContext context) {
final provider = context.watch<FoodProvider>();
return Stack(
children: [
if (provider.isLoading)
buildLoadingIndicator(context)
else
_buildContent(provider),
],
);
}
}

View File

@@ -1,6 +1,4 @@
import 'package:flutter/material.dart';
import 'package:food_hub_app/config/app_config.dart';
import 'package:food_hub_app/models/recipe.dart';
import 'package:food_hub_app/widgets/common/image.dart';
import 'package:food_hub_app/widgets/common/index.dart';
@@ -86,10 +84,7 @@ class RecipeCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildNetworkImage(
context,
'${AppConfig.imageBaseUrl}/$firstImageUrl',
),
buildNetworkImage(context, firstImageUrl),
SizedBox(height: 8),
GestureDetector(
onTap: () => navigatorToRecipeDetail(context),

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:food_hub_app/apis/recipe.dart';
import 'package:food_hub_app/models/recipe.dart';
import 'package:food_hub_app/provider/food_provider.dart';
import 'package:food_hub_app/widgets/common/index.dart';
import 'package:food_hub_app/widgets/recipe/card.dart';
import 'package:provider/provider.dart';
class RecipeList extends StatefulWidget {
const RecipeList({super.key});
@@ -12,33 +12,40 @@ class RecipeList extends StatefulWidget {
}
class _RecipeListState extends State<RecipeList> {
List<RecipeSummary> recipeSummaryList = [];
@override
void initState() {
super.initState();
refreshRecipeList();
// 初始化加载数据
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<FoodProvider>().refreshRecipeList();
});
}
Future<void> refreshRecipeList() async {
final result = await queryRecipeApi(RecipeQuery(category: ""));
setState(() {
recipeSummaryList = result;
});
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]);
},
);
}
}
@override
Widget build(BuildContext context) {
if (recipeSummaryList.isEmpty) {
return buildEmptyData();
} else {
return ListView.builder(
itemCount: recipeSummaryList.length,
itemBuilder: (context, index) {
return RecipeCard(recipe: recipeSummaryList[index]);
}
);
}
final provider = context.watch<FoodProvider>();
return Stack(
children: [
if (provider.isLoading)
buildLoadingIndicator(context)
else
_buildContent(provider),
],
);
}
}

View File

@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:food_hub_app/apis/recipe.dart';
import 'package:food_hub_app/config/app_config.dart';
import 'package:food_hub_app/models/recipe.dart';
import 'package:food_hub_app/provider/food_provider.dart';
import 'package:food_hub_app/widgets/common/image.dart';
import 'package:food_hub_app/widgets/common/index.dart';
import 'package:food_hub_app/widgets/common/year_selector.dart';
import 'package:timelines_plus/timelines_plus.dart';
import 'package:provider/provider.dart';
class RecipeTimeline extends StatefulWidget {
const RecipeTimeline({super.key});
@@ -15,24 +15,7 @@ class RecipeTimeline extends StatefulWidget {
}
class _RecipeTimeline extends State<RecipeTimeline> {
List<FoodRecord> recordList = [];
@override
void initState() {
super.initState();
refreshRecord(DateTime.now().year);
}
Future<void> refreshRecord(int year) async {
final result = await queryRecordApi("$year-01-01", "$year-12-31");
setState(() {
recordList = result;
});
}
Widget buildTimelineCard(BuildContext context, FoodRecord record) {
final imageUrl = '${AppConfig.imageBaseUrl}${record.imageUrl}';
return buildCard(
context: context,
child: Column(
@@ -58,7 +41,7 @@ class _RecipeTimeline extends State<RecipeTimeline> {
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 5),
buildNetworkImage(context, imageUrl),
buildNetworkImage(context, record.imageUrl),
],
),
],
@@ -93,8 +76,7 @@ class _RecipeTimeline extends State<RecipeTimeline> {
}
}
@override
Widget build(BuildContext context) {
Widget _buildContent(FoodProvider provider) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -102,9 +84,25 @@ class _RecipeTimeline extends State<RecipeTimeline> {
initialYear: DateTime.now().year,
minYear: 2000,
maxYear: 2100,
onYearChanged: (year) => refreshRecord(year),
onYearChanged:
(year) =>
provider.refreshRecordList("$year-01-01", "$year-12-31"),
),
Expanded(child: buildTimeline(context, recordList)),
Expanded(child: buildTimeline(context, provider.recordList)),
],
);
}
@override
Widget build(BuildContext context) {
final provider = context.watch<FoodProvider>();
return Stack(
children: [
if (provider.isLoading)
buildLoadingIndicator(context)
else
_buildContent(provider),
],
);
}