feat:更新组件UI
This commit is contained in:
@@ -5,10 +5,11 @@ class AppConfig {
|
|||||||
// http://14.103.235.151:81/food-service
|
// 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://14.103.235.151:81/food-service";
|
||||||
// static const String baseApiUrl = "http://192.168.1.3:8100";
|
// static const String baseApiUrl = "http://192.168.1.3:8100";
|
||||||
// static const String baseApiUrl = "https://cxx0822.iepose.cn/food-api";
|
static const String baseApiUrl = "https://cxx0822.iepose.cn/food-api";
|
||||||
static const String baseApiUrl = "http://127.0.0.1:8083";
|
// static const String baseApiUrl = "http://192.168.1.103:8083";
|
||||||
static const String rustfsIp = '14.103.235.151';
|
static const String rustfsIp = '14.103.235.151';
|
||||||
static const String rustfsFileUrl = 'http://14.103.235.151:9100';
|
static const String rustfsFileUrl = 'http://14.103.235.151:9100';
|
||||||
static const String bucketName = 'food';
|
static const String bucketName = 'food';
|
||||||
static const String imageBaseUrl = '$rustfsFileUrl/$bucketName/';
|
// static const String imageBaseUrl = '$rustfsFileUrl/$bucketName/';
|
||||||
|
static const String imageBaseUrl = '$baseApiUrl/';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
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/recipe.dart';
|
||||||
import 'package:food_hub_app/apis/stats.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/recipe.dart';
|
||||||
import 'package:food_hub_app/models/stats.dart';
|
import 'package:food_hub_app/models/stats.dart';
|
||||||
import 'package:food_hub_app/utils/date_util.dart';
|
import 'package:food_hub_app/utils/date_util.dart';
|
||||||
@@ -13,6 +15,8 @@ class FoodProvider with ChangeNotifier {
|
|||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
String? error;
|
String? error;
|
||||||
|
|
||||||
|
String queryCategory = '全部菜系';
|
||||||
|
late List<String> categoryList = [];
|
||||||
late List<RecipeSummary> recipeSummaryList = [];
|
late List<RecipeSummary> recipeSummaryList = [];
|
||||||
|
|
||||||
DateTime selectedDay = DateTime.now();
|
DateTime selectedDay = DateTime.now();
|
||||||
@@ -31,6 +35,11 @@ class FoodProvider with ChangeNotifier {
|
|||||||
late List<ChartData> categoryStats = [];
|
late List<ChartData> categoryStats = [];
|
||||||
late List<ChartData> rankStats = [];
|
late List<ChartData> rankStats = [];
|
||||||
|
|
||||||
|
List<Moment> momentList = [];
|
||||||
|
int currentPage = 1;
|
||||||
|
final int pageSize = 3;
|
||||||
|
bool hasMore = true;
|
||||||
|
|
||||||
void resetRecordForm() {
|
void resetRecordForm() {
|
||||||
recordFormItem = FoodRecord.getEmpty();
|
recordFormItem = FoodRecord.getEmpty();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -41,6 +50,23 @@ class FoodProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
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 {
|
Future<void> refreshRecipeList() async {
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
|
|
||||||
@@ -49,7 +75,8 @@ class FoodProvider with ChangeNotifier {
|
|||||||
error = null;
|
error = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
final result = await queryRecipeApi(RecipeQuery(category: ""));
|
final category = queryCategory == '全部菜系' ? '' : queryCategory;
|
||||||
|
final result = await queryRecipeApi(RecipeQuery(category: category));
|
||||||
recipeSummaryList = result;
|
recipeSummaryList = result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = '加载数据失败: $e';
|
error = '加载数据失败: $e';
|
||||||
@@ -131,4 +158,50 @@ class FoodProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,25 +31,35 @@ class _HomePage extends State<HomePage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.search, color: Colors.white),
|
icon: Icon(Icons.search, color: Colors.white),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// 搜索功能
|
// 搜索功能
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.add, color: Colors.white),
|
||||||
|
onPressed: () {
|
||||||
|
buildBottomSheet(context);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Color(0xFFF5F5F5),
|
backgroundColor: Color(0xFFF5F5F5),
|
||||||
drawer: SettingsDrawer(),
|
drawer: SettingsDrawer(),
|
||||||
body: Padding(padding: EdgeInsets.all(4), child: tabPages[_currentIndex]),
|
body: Padding(padding: EdgeInsets.all(4), child: tabPages[_currentIndex]),
|
||||||
floatingActionButton: FloatingActionButton(
|
// floatingActionButton: FloatingActionButton(
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
onPressed: () => buildBottomSheet(context),
|
// onPressed: () => buildBottomSheet(context),
|
||||||
shape: const CircleBorder(),
|
// shape: const CircleBorder(),
|
||||||
mini: true,
|
// mini: true,
|
||||||
child: const Icon(Icons.add, color: Colors.white, size: 30),
|
// child: const Icon(Icons.add, color: Colors.white, size: 30),
|
||||||
),
|
// ),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
// floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||||
bottomNavigationBar: buildBottomNavBar(
|
bottomNavigationBar: buildBottomNavBar(
|
||||||
currentIndex: _currentIndex,
|
currentIndex: _currentIndex,
|
||||||
onTap: (index) {
|
onTap: (index) {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import 'package:easy_refresh/easy_refresh.dart';
|
import 'package:easy_refresh/easy_refresh.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/apis/moment.dart';
|
import 'package:food_hub_app/provider/food_provider.dart';
|
||||||
import 'package:food_hub_app/models/moment.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/common/index.dart';
|
||||||
import 'package:food_hub_app/widgets/moment/card.dart';
|
import 'package:food_hub_app/widgets/moment/card.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class MomentPage extends StatefulWidget {
|
class MomentPage extends StatefulWidget {
|
||||||
const MomentPage({super.key});
|
const MomentPage({super.key});
|
||||||
@@ -13,26 +14,23 @@ class MomentPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MomentPageState extends State<MomentPage> {
|
class _MomentPageState extends State<MomentPage> {
|
||||||
List<Moment> momentList = [];
|
|
||||||
int _currentPage = 1;
|
|
||||||
final int _pageSize = 3;
|
|
||||||
bool _hasMore = true;
|
|
||||||
|
|
||||||
// 初始化EasyRefresh控制器
|
// 初始化EasyRefresh控制器
|
||||||
final EasyRefreshController _freshController = EasyRefreshController(
|
final EasyRefreshController _freshController = EasyRefreshController(
|
||||||
controlFinishRefresh: true,
|
controlFinishRefresh: true,
|
||||||
controlFinishLoad: true,
|
controlFinishLoad: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
bool _showScrollToTop = false;
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
bool _showScrollToTop = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// 初始加载数据
|
|
||||||
_loadData(isRefresh: true);
|
|
||||||
_scrollController.addListener(_onScroll);
|
_scrollController.addListener(_onScroll);
|
||||||
|
|
||||||
|
// 初始化加载数据
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context.read<FoodProvider>().refreshMomentList();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -43,50 +41,18 @@ class _MomentPageState extends State<MomentPage> {
|
|||||||
super.dispose();
|
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 {
|
Future<void> _onRefresh() async {
|
||||||
await _loadData(isRefresh: true);
|
await context.read<FoodProvider>().refreshMomentList();
|
||||||
|
_freshController.finishRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上拉加载
|
// 上拉加载
|
||||||
Future<void> _onLoad() async {
|
Future<void> _onLoad() async {
|
||||||
if (_hasMore) {
|
final provider = context.read<FoodProvider>();
|
||||||
await _loadData(isRefresh: false);
|
|
||||||
|
if (provider.hasMore) {
|
||||||
|
await provider.loadMoreMomentList();
|
||||||
_freshController.finishLoad(IndicatorResult.success);
|
_freshController.finishLoad(IndicatorResult.success);
|
||||||
} else {
|
} else {
|
||||||
_freshController.finishLoad(IndicatorResult.noMore);
|
_freshController.finishLoad(IndicatorResult.noMore);
|
||||||
@@ -111,78 +77,47 @@ class _MomentPageState extends State<MomentPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 滚动到顶部
|
// 滚动到顶部
|
||||||
void _scrollToTop() {
|
void _scrollToTop() => scrollToTopAnimateTo(_scrollController);
|
||||||
_scrollController.animateTo(
|
|
||||||
0,
|
Widget _buildMomentList(FoodProvider provider) {
|
||||||
duration: const Duration(milliseconds: 500), // 滚动动画时长
|
return ListView.builder(
|
||||||
curve: Curves.easeInOut, // 滚动动画曲线
|
controller: _scrollController,
|
||||||
|
itemCount: provider.momentList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return MomentCard(moment: provider.momentList[index]);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final provider = context.watch<FoodProvider>();
|
||||||
|
|
||||||
// 空状态显示
|
// 空状态显示
|
||||||
if (momentList.isEmpty) {
|
if (provider.momentList.isEmpty) {
|
||||||
return EasyRefresh(
|
return buildEmptyData();
|
||||||
controller: _freshController,
|
|
||||||
onRefresh: _onRefresh,
|
|
||||||
child: buildEmptyData(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 有数据时显示列表
|
// 有数据时显示列表
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
EasyRefresh(
|
buildEasyRefresh(
|
||||||
controller: _freshController,
|
freshController: _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,
|
onRefresh: _onRefresh,
|
||||||
onLoad: _onLoad,
|
onLoad: _onLoad,
|
||||||
child: ListView.builder(
|
body: Stack(
|
||||||
controller: _scrollController,
|
children: [
|
||||||
itemCount: momentList.length,
|
if (provider.isLoading)
|
||||||
itemBuilder: (context, index) {
|
buildLoadingIndicator(context)
|
||||||
return MomentCard(moment: momentList[index]);
|
else
|
||||||
},
|
_buildMomentList(provider),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// 返回顶部按钮
|
// 返回顶部按钮
|
||||||
if (_showScrollToTop)
|
if (_showScrollToTop)
|
||||||
Positioned(
|
buildScrollToTop(context: context, scrollToTop: _scrollToTop),
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ class _RecordFormPageState extends State<RecordFormPage> {
|
|||||||
else
|
else
|
||||||
buildImagePreviewItem(
|
buildImagePreviewItem(
|
||||||
context: context,
|
context: context,
|
||||||
imageUrl: provider.recordFormItem.imageUrl,
|
imageUrls: [provider.recordFormItem.imageUrl],
|
||||||
|
index: 0,
|
||||||
onRemoveImage: () => _removeImage(provider),
|
onRemoveImage: () => _removeImage(provider),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -184,7 +185,9 @@ class _RecordFormPageState extends State<RecordFormPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget? buildSuffixIcon() {
|
Widget? buildSuffixIcon() {
|
||||||
return controller.text.isNotEmpty
|
final isShow = controller.text.isNotEmpty && !provider.isEditing;
|
||||||
|
|
||||||
|
return isShow
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: Icon(Icons.clear, size: 18),
|
icon: Icon(Icons.clear, size: 18),
|
||||||
onPressed: onClearRecipeName,
|
onPressed: onClearRecipeName,
|
||||||
|
|||||||
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:
|
pageOptions:
|
||||||
widget.images.map((url) {
|
widget.images.map((url) {
|
||||||
return PhotoViewGalleryPageOptions(
|
return PhotoViewGalleryPageOptions(
|
||||||
imageProvider: NetworkImage(url),
|
imageProvider: NetworkImage('${AppConfig.imageBaseUrl}$url'),
|
||||||
minScale: PhotoViewComputedScale.contained,
|
minScale: PhotoViewComputedScale.contained,
|
||||||
maxScale: PhotoViewComputedScale.covered * 2,
|
maxScale: PhotoViewComputedScale.covered * 2,
|
||||||
// 点击空白处关闭预览
|
// 点击空白处关闭预览
|
||||||
@@ -91,42 +91,27 @@ class _ImagePreviewPageState extends State<ImagePreviewPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildFileImage(BuildContext context, File imageFile) {
|
Widget buildNetworkImage(
|
||||||
return GestureDetector(
|
BuildContext context,
|
||||||
onTap: () => showFullScreenImage(context, FileImage(imageFile)),
|
List<String> imageUrls,
|
||||||
child: Image(
|
int index,
|
||||||
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 AspectRatio(
|
return AspectRatio(
|
||||||
aspectRatio: 1.5,
|
aspectRatio: 4 / 3,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showFullScreenImage(
|
showFullScreenImage(context, imageUrls, index);
|
||||||
context,
|
|
||||||
NetworkImage('${AppConfig.imageBaseUrl}$url'),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Image.network(
|
child: Image.network(
|
||||||
'${AppConfig.imageBaseUrl}$url',
|
'${AppConfig.imageBaseUrl}${imageUrls[index]}',
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
loadingBuilder: (context, child, loadingProgress) {
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
if (loadingProgress == null) return child;
|
if (loadingProgress == null) return child;
|
||||||
return buildImageLoadingIndicator(loadingProgress);
|
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({
|
Widget buildImagePreviewItem({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required String imageUrl,
|
required List<String> imageUrls,
|
||||||
|
required int index,
|
||||||
required VoidCallback onRemoveImage,
|
required VoidCallback onRemoveImage,
|
||||||
}) {
|
}) {
|
||||||
return Container(
|
return Container(
|
||||||
@@ -144,7 +130,7 @@ Widget buildImagePreviewItem({
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
buildNetworkImage(context, imageUrl),
|
buildNetworkImage(context, imageUrls, index),
|
||||||
buildDeleteImage(onRemoveImage: onRemoveImage),
|
buildDeleteImage(onRemoveImage: onRemoveImage),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -168,7 +154,7 @@ Widget buildImageLoadingIndicator(ImageChunkEvent? loadingProgress) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildErrorImage() {
|
Widget buildErrorImage() {
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
child: const Icon(Icons.image, color: Colors.grey, size: 30),
|
child: const Icon(Icons.image, color: Colors.grey, size: 30),
|
||||||
@@ -183,7 +169,7 @@ Widget _buildPhotoView(ImageProvider imageProvider) {
|
|||||||
maxScale: PhotoViewComputedScale.covered * 2,
|
maxScale: PhotoViewComputedScale.covered * 2,
|
||||||
initialScale: PhotoViewComputedScale.contained,
|
initialScale: PhotoViewComputedScale.contained,
|
||||||
loadingBuilder: (context, event) => buildImageLoadingIndicator(event),
|
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) {
|
void showFullScreenImage(
|
||||||
Navigator.of(context).push(
|
|
||||||
PageRouteBuilder(
|
|
||||||
opaque: false,
|
|
||||||
pageBuilder: (
|
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Animation<double> animation,
|
List<String> imageUrls,
|
||||||
Animation<double> secondaryAnimation,
|
int index,
|
||||||
) {
|
) {
|
||||||
return Scaffold(
|
Navigator.push(
|
||||||
backgroundColor: Colors.black.withAlpha(200),
|
context,
|
||||||
body: Stack(
|
MaterialPageRoute(
|
||||||
children: [
|
builder:
|
||||||
// 可缩放图片
|
(context) => ImagePreviewPage(images: imageUrls, initialIndex: index),
|
||||||
Positioned.fill(child: _buildPhotoView(imageProvider)),
|
|
||||||
// 关闭按钮
|
|
||||||
_buildCloseImage(context),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
// 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}) {
|
Widget buildImageUploadButton({required VoidCallback onPickImage}) {
|
||||||
|
|||||||
@@ -29,20 +29,18 @@ InputDecoration formInputDecoration({
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildCard({required BuildContext context, required Widget child}) {
|
Widget buildCard({required BuildContext context, required Widget child}) {
|
||||||
final colors = Theme
|
final colors = Theme.of(context).colorScheme;
|
||||||
.of(context)
|
|
||||||
.colorScheme;
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colors.surface,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(12),
|
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),
|
initialLabelIndex: tabValues.indexOf(currentTab),
|
||||||
totalSwitches: tabValues.length,
|
totalSwitches: tabValues.length,
|
||||||
labels: labels,
|
labels: labels,
|
||||||
activeBgColor: [Theme
|
activeBgColor: [Theme.of(context).colorScheme.primary],
|
||||||
.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primary
|
|
||||||
],
|
|
||||||
activeFgColor: Colors.white,
|
activeFgColor: Colors.white,
|
||||||
inactiveBgColor: Colors.grey.shade200,
|
inactiveBgColor: Colors.grey.shade200,
|
||||||
inactiveFgColor: Colors.grey.shade700,
|
inactiveFgColor: Colors.grey.shade700,
|
||||||
@@ -99,10 +93,7 @@ Widget circleIconButton({
|
|||||||
fixedSize: Size(size, size),
|
fixedSize: Size(size, size),
|
||||||
shape: CircleBorder(),
|
shape: CircleBorder(),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
backgroundColor: Theme
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primary,
|
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
minimumSize: const Size(0, 0),
|
minimumSize: const Size(0, 0),
|
||||||
),
|
),
|
||||||
@@ -111,9 +102,7 @@ Widget circleIconButton({
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildTag(BuildContext context, String title) {
|
Widget buildTag(BuildContext context, String title) {
|
||||||
final colors = Theme
|
final colors = Theme.of(context).colorScheme;
|
||||||
.of(context)
|
|
||||||
.colorScheme;
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
@@ -195,11 +184,7 @@ Widget buildLoadingIndicator(BuildContext context) {
|
|||||||
color: colors.surface,
|
color: colors.surface,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 2)),
|
||||||
color: Colors.black12,
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -102,17 +102,6 @@ class MomentCard extends StatelessWidget {
|
|||||||
final List<String> imageUrls =
|
final List<String> imageUrls =
|
||||||
moment.imageList.map((path) => path).toList();
|
moment.imageList.map((path) => path).toList();
|
||||||
|
|
||||||
void imageTapClick(int index) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder:
|
|
||||||
(context) =>
|
|
||||||
ImagePreviewPage(images: imageUrls, initialIndex: index),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GridView.count(
|
return GridView.count(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
@@ -121,13 +110,7 @@ class MomentCard extends StatelessWidget {
|
|||||||
mainAxisSpacing: 4,
|
mainAxisSpacing: 4,
|
||||||
childAspectRatio: itemAspectRatio,
|
childAspectRatio: itemAspectRatio,
|
||||||
children: List.generate(imageCount, (index) {
|
children: List.generate(imageCount, (index) {
|
||||||
// 单个图片项:添加点击事件
|
return buildNetworkImage(context, imageUrls, index);
|
||||||
return GestureDetector(
|
|
||||||
// 点击图片时,跳转到预览页面
|
|
||||||
onTap: () => imageTapClick(index),
|
|
||||||
// 原图片组件
|
|
||||||
child: buildNetworkImage(context, imageUrls[index]),
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
|
|||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
color: colors.onSecondary,
|
color: colors.primary.withAlpha(50),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(10),
|
padding: EdgeInsets.all(10),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ class RecipeCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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) {
|
Widget buildRecipeContent(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
@@ -33,6 +34,7 @@ class RecipeCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@@ -84,7 +86,7 @@ class RecipeCard extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
buildNetworkImage(context, firstImageUrl),
|
buildNetworkImage(context, imageUrls, 0),
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => navigatorToRecipeDetail(context),
|
onTap: () => navigatorToRecipeDetail(context),
|
||||||
|
|||||||
@@ -19,18 +19,82 @@ class _RecipeListState extends State<RecipeList> {
|
|||||||
// 初始化加载数据
|
// 初始化加载数据
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
context.read<FoodProvider>().refreshRecipeList();
|
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) {
|
Widget _buildContent(FoodProvider provider) {
|
||||||
if (provider.recipeSummaryList.isEmpty) {
|
if (provider.recipeSummaryList.isEmpty) {
|
||||||
return buildEmptyData();
|
return buildEmptyData();
|
||||||
} else {
|
} else {
|
||||||
return ListView.builder(
|
return Column(
|
||||||
|
children: [
|
||||||
|
_buildSelectCategory(provider),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
itemCount: provider.recipeSummaryList.length,
|
itemCount: provider.recipeSummaryList.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return RecipeCard(recipe: provider.recipeSummaryList[index]);
|
return RecipeCard(recipe: provider.recipeSummaryList[index]);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class _RecipeTimeline extends State<RecipeTimeline> {
|
|||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
buildNetworkImage(context, record.imageUrl),
|
buildNetworkImage(context, [record.imageUrl], 0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -88,7 +88,12 @@ class _RecipeTimeline extends State<RecipeTimeline> {
|
|||||||
(year) =>
|
(year) =>
|
||||||
provider.refreshRecordList("$year-01-01", "$year-12-31"),
|
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