feat:更新组件UI
This commit is contained in:
65
lib/widgets/common/easy_refresh.dart
Normal file
65
lib/widgets/common/easy_refresh.dart
Normal 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, // 滚动动画曲线
|
||||
);
|
||||
}
|
||||
@@ -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}) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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]);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user