diff --git a/lib/layout/nav_bar.dart b/lib/layout/nav_bar.dart index e5f484a..b8ea016 100644 --- a/lib/layout/nav_bar.dart +++ b/lib/layout/nav_bar.dart @@ -32,14 +32,11 @@ final List pageInfos = [ ), ]; -class NavBar extends StatelessWidget { - final int currentIndex; - - final Function(int) onTap; - - const NavBar({super.key, required this.currentIndex, required this.onTap}); - - List get items => +Widget buildBottomNavBar({ + required int currentIndex, + required Function(int) onTap, +}) { + final List items = pageInfos .map( (item) => BottomNavigationBarItem( @@ -50,37 +47,78 @@ class NavBar extends StatelessWidget { ) .toList(); - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - border: Border(top: BorderSide(color: Theme.of(context).primaryColor)), - ), - child: BottomNavigationBar( - currentIndex: currentIndex, - iconSize: 25, - type: BottomNavigationBarType.fixed, - backgroundColor: Colors.white, - items: items, - onTap: onTap, - ), - ); - } + return BottomNavigationBar( + currentIndex: currentIndex, + iconSize: 25, + type: BottomNavigationBarType.fixed, + backgroundColor: Colors.white, + items: items, + onTap: onTap, + ); } -List homeActions(BuildContext context) { - return [ - IconButton( - icon: Icon(Icons.search, color: Colors.white), - onPressed: () { - // 搜索功能 - }, +void buildBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - // IconButton( - // icon: Icon(Icons.add, color: Colors.white), - // onPressed: () { - // Navigator.pushNamed(context, '/recordForm'); - // }, - // ), - ]; + builder: (context) => buildBottomSheetBody(context), + ); +} + +Widget buildBottomSheetBody(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + const Text( + '请选择操作', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + ListTile( + leading: Icon(Icons.book, color: colors.primary), + title: const Text('新增菜谱'), + onTap: () { + Navigator.pop(context); + _handleAddRecipe(context); + }, + ), + ListTile( + leading: Icon(Icons.note_add, color: colors.primary), + title: const Text('新增记录'), + onTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, "/recordForm"); + }, + ), + ListTile( + leading: Icon(Icons.group, color: colors.primary), + title: const Text('发布朋友圈'), + onTap: () { + Navigator.pop(context); + _handleAddRecord(context); + }, + ), + ], + ); +} + +// 处理添加菜谱 +void _handleAddRecipe(BuildContext context) { + // 这里添加跳转或处理添加菜谱的逻辑 + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('添加菜谱功能'))); +} + +// 处理添加记录 +void _handleAddRecord(BuildContext context) { + // 这里添加跳转或处理添加记录的逻辑 + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('添加记录功能'))); } diff --git a/lib/views/home.dart b/lib/views/home.dart index 0562bc1..eebb220 100644 --- a/lib/views/home.dart +++ b/lib/views/home.dart @@ -14,79 +14,6 @@ class _HomePage extends State { List get tabPages => pageInfos.map((item) => item.page).toList(); - // 显示底部弹窗 - void _showBottomSheet() { - showModalBottomSheet( - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(16)), - ), - builder: - (context) => Container( - padding: const EdgeInsets.all(10), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - '请选择操作', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - ListTile( - leading: Icon( - Icons.book, - color: Theme.of(context).primaryColor, - ), - title: const Text('新增菜谱'), - onTap: () { - Navigator.pop(context); - _handleAddRecipe(); - }, - ), - ListTile( - leading: Icon( - Icons.note_add, - color: Theme.of(context).primaryColor, - ), - title: const Text('新增记录'), - onTap: () { - Navigator.pop(context); - Navigator.pushNamed(context, "/recordForm"); - }, - ), - ListTile( - leading: Icon( - Icons.group, - color: Theme.of(context).primaryColor, - ), - title: const Text('发布朋友圈'), - onTap: () { - Navigator.pop(context); - _handleAddRecord(); - }, - ), - ], - ), - ), - ); - } - - // 处理添加菜谱 - void _handleAddRecipe() { - // 这里添加跳转或处理添加菜谱的逻辑 - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('添加菜谱功能'))); - } - - // 处理添加记录 - void _handleAddRecord() { - // 这里添加跳转或处理添加记录的逻辑 - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('添加记录功能'))); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -103,23 +30,27 @@ class _HomePage extends State { ); }, ), - actions: homeActions(context), + actions: [ + IconButton( + icon: Icon(Icons.search, color: Colors.white), + onPressed: () { + // 搜索功能 + }, + ), + ], ), backgroundColor: Color(0xFFF5F5F5), drawer: SettingsDrawer(), - body: Padding( - padding: EdgeInsets.all(4), - child: tabPages[_currentIndex], - ), + body: Padding(padding: EdgeInsets.all(4), child: tabPages[_currentIndex]), floatingActionButton: FloatingActionButton( - backgroundColor: Theme.of(context).primaryColor, - onPressed: _showBottomSheet, + 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: NavBar( + bottomNavigationBar: buildBottomNavBar( currentIndex: _currentIndex, onTap: (index) { setState(() { diff --git a/lib/views/recipe_detail.dart b/lib/views/recipe_detail.dart index 3e583b3..0461de4 100644 --- a/lib/views/recipe_detail.dart +++ b/lib/views/recipe_detail.dart @@ -43,8 +43,7 @@ class _RecipeDetailState extends State { Future refreshRecipeDetail() async { // 获取参数 final args = ModalRoute.of(context)?.settings.arguments as Map; - final recipeId = args['id']; - final result = await queryRecipeByIdApi(recipeId); + final result = await queryRecipeByIdApi(args['id']); setState(() { recipe = result; @@ -71,13 +70,8 @@ class _RecipeDetailState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text('菜谱信息'), - leading: IconButton( - icon: Icon(Icons.arrow_back), - onPressed: () => Navigator.pop(context), - ), - ), + appBar: AppBar(title: Text('菜谱信息')), + backgroundColor: Color(0xFFF4F4F4), body: SingleChildScrollView( child: Padding(padding: EdgeInsets.all(5), child: _buildRecipeDetail()), ), @@ -89,12 +83,11 @@ class _RecipeDetailState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildTitleSection(), - SizedBox(height: 10), _buildInfoCard( context, icon: Icons.info, title: "基础信息", - content: _buildTag(context, title: recipe.category), + content: buildTag(context, recipe.category), ), _buildInfoCard( context, @@ -131,20 +124,6 @@ class _RecipeDetailState extends State { ); } - Widget _buildTag(BuildContext context, {required String title}) { - return Container( - padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - color: Color.lerp(Theme.of(context).primaryColor, Colors.white, 0.8), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - title, - style: TextStyle(color: Theme.of(context).primaryColor), - ), - ); - } - Widget _buildTitleSection() { return Text( recipe.name, @@ -165,16 +144,14 @@ class _RecipeDetailState extends State { child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)), ), if (materials.isEmpty) - _buildTag(context, title: '无') + buildTag(context, '无') else Wrap( spacing: 8.0, runSpacing: 8.0, children: materials - .map( - (m) => _buildTag(context, title: '${m.name} ${m.amount}'), - ) + .map((m) => buildTag(context, '${m.name} ${m.amount}')) .toList(), ), ], @@ -229,7 +206,7 @@ class _RecipeDetailState extends State { children: [ Row( children: [ - Icon(icon, color: Theme.of(context).primaryColor), + Icon(icon, color: Theme.of(context).colorScheme.primary), const SizedBox(width: 5), Text( title, @@ -240,7 +217,7 @@ class _RecipeDetailState extends State { SizedBox(height: 5), content, ], - ) + ), ); } } diff --git a/lib/widgets/common/image.dart b/lib/widgets/common/image.dart index f2f3049..8fe0794 100644 --- a/lib/widgets/common/image.dart +++ b/lib/widgets/common/image.dart @@ -19,6 +19,7 @@ class ImagePreviewPage extends StatefulWidget { class _ImagePreviewPageState extends State { // 声明 PageController 并初始化初始索引 late PageController _pageController; + // 记录当前显示的图片索引(用于更新页码) int _currentIndex = 0; @@ -68,17 +69,18 @@ class _ImagePreviewPageState extends State { centerTitle: true, ), body: PhotoViewGallery( - pageOptions: widget.images.map((url) { - return PhotoViewGalleryPageOptions( - imageProvider: NetworkImage(url), - minScale: PhotoViewComputedScale.contained, - maxScale: PhotoViewComputedScale.covered * 2, - // 点击空白处关闭预览 - onTapDown: (context, details, controllerValue) { - Navigator.pop(context); - }, - ); - }).toList(), + pageOptions: + widget.images.map((url) { + return PhotoViewGalleryPageOptions( + imageProvider: NetworkImage(url), + minScale: PhotoViewComputedScale.contained, + maxScale: PhotoViewComputedScale.covered * 2, + // 点击空白处关闭预览 + onTapDown: (context, details, controllerValue) { + Navigator.pop(context); + }, + ); + }).toList(), pageController: _pageController, scrollDirection: Axis.horizontal, ), @@ -86,30 +88,94 @@ class _ImagePreviewPageState extends State { } } -Widget buildNetworkImage(String url) { +Widget buildNetworkImage(BuildContext context, String url) { return ClipRRect( borderRadius: BorderRadius.circular(8), - child: Image.network( - url, - fit: BoxFit.cover, - loadingBuilder: (context, child, loadingProgress) { - // 加载中显示占位符 - if (loadingProgress == null) return child; - return Center( - child: CircularProgressIndicator( - value: loadingProgress.expectedTotalBytes != null - ? loadingProgress.cumulativeBytesLoaded / - loadingProgress.expectedTotalBytes! - : null, - ), - ); + child: GestureDetector( + onTap: () { + _showFullScreenImage(context, url); }, - errorBuilder: (context, error, stackTrace) { - return Container( - color: Colors.grey[200], - child: const Icon(Icons.image, color: Colors.grey, size: 30), + child: Image.network( + url, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return _buildLoadingIndicator(loadingProgress); + }, + errorBuilder: (context, error, stackTrace) => _buildErrorImage(), + ), + ), + ); +} + +Widget _buildLoadingIndicator(ImageChunkEvent? loadingProgress) { + return Center( + child: SizedBox( + width: 30, + height: 30, + child: CircularProgressIndicator( + value: + loadingProgress?.expectedTotalBytes != null + ? loadingProgress!.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ), + ); +} + +Widget _buildErrorImage() { + return Container( + color: Colors.grey[200], + child: const Icon(Icons.image, color: Colors.grey, size: 30), + ); +} + +Widget _buildPhotoView(String url) { + return PhotoView( + imageProvider: NetworkImage(url), + backgroundDecoration: const BoxDecoration(color: Colors.transparent), + minScale: PhotoViewComputedScale.contained, + maxScale: PhotoViewComputedScale.covered * 2, + initialScale: PhotoViewComputedScale.contained, + heroAttributes: PhotoViewHeroAttributes(tag: url), + loadingBuilder: (context, event) => _buildLoadingIndicator(event), + errorBuilder: (context, error, stackTrace) => _buildErrorImage(), + ); +} + +Widget _buildCloseImage(BuildContext context) { + return Positioned( + top: MediaQuery.of(context).padding.top + 10, + right: 20, + child: IconButton( + icon: Icon(Icons.close, color: Colors.white, size: 30), + onPressed: () => Navigator.of(context).pop(), + ), + ); +} + +void _showFullScreenImage(BuildContext context, String url) { + Navigator.of(context).push( + PageRouteBuilder( + opaque: false, + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return Scaffold( + backgroundColor: Colors.black.withAlpha(200), + body: Stack( + children: [ + // 可缩放图片 + Positioned.fill(child: _buildPhotoView(url)), + // 关闭按钮 + _buildCloseImage(context), + ], + ), ); }, ), ); -} \ No newline at end of file +} diff --git a/lib/widgets/common/index.dart b/lib/widgets/common/index.dart index d088d62..5c243b8 100644 --- a/lib/widgets/common/index.dart +++ b/lib/widgets/common/index.dart @@ -83,7 +83,7 @@ Widget circleIconButton({ required IconData icon, required VoidCallback onPressed, required BuildContext context, - bool? isSmall + bool? isSmall, }) { final double size = isSmall == true ? 24 : 36; @@ -101,6 +101,19 @@ Widget circleIconButton({ ); } +Widget buildTag(BuildContext context, String title) { + final colors = Theme.of(context).colorScheme; + + return Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: Color.lerp(colors.primary, Colors.white, 0.8), + borderRadius: BorderRadius.circular(10), + ), + child: Text(title, style: TextStyle(color: colors.primary)), + ); +} + /// 确认按钮样式 ButtonStyle primaryButtonStyle() { return ButtonStyle( diff --git a/lib/widgets/common/year_selector.dart b/lib/widgets/common/year_selector.dart index 09dfa2a..b2f2c95 100644 --- a/lib/widgets/common/year_selector.dart +++ b/lib/widgets/common/year_selector.dart @@ -94,7 +94,6 @@ class _YearSelectorState extends State isSmall: true, onPressed: () => _previousYear(), ), - SizedBox(width: 5), // 年份显示 AnimatedBuilder( animation: _scaleAnimation, @@ -110,7 +109,6 @@ class _YearSelectorState extends State ), ), ), - SizedBox(width: 5), circleIconButton( context: context, icon: Icons.chevron_right, diff --git a/lib/widgets/moment/card.dart b/lib/widgets/moment/card.dart index 3281d28..ac6a499 100644 --- a/lib/widgets/moment/card.dart +++ b/lib/widgets/moment/card.dart @@ -128,7 +128,7 @@ class MomentCard extends StatelessWidget { // 点击图片时,跳转到预览页面 onTap: () => imageTapClick(index), // 原图片组件 - child: buildNetworkImage(imageUrls[index]), + child: buildNetworkImage(context, imageUrls[index]), ); }), ); diff --git a/lib/widgets/recipe/card.dart b/lib/widgets/recipe/card.dart index bfc3268..7302c69 100644 --- a/lib/widgets/recipe/card.dart +++ b/lib/widgets/recipe/card.dart @@ -10,12 +10,23 @@ class RecipeCard extends StatelessWidget { const RecipeCard({super.key, required this.recipe}); + void navigatorToRecipeDetail(BuildContext context) { + Navigator.pushNamed(context, '/recipeDetail', arguments: {'id': recipe.id}); + } + @override Widget build(BuildContext context) { - final List imageUrls = - recipe.recordList - .map((item) => '${AppConfig.baseApiUrl}/${item.imageUrl}') - .toList(); + final String firstImageUrl = recipe.recordList.first.imageUrl; + + Widget buildRecipeImage() { + return AspectRatio( + aspectRatio: 1.5, + child: buildNetworkImage( + context, + '${AppConfig.baseApiUrl}/$firstImageUrl', + ), + ); + } Widget buildRecipeContent(BuildContext context) { return Column( @@ -32,26 +43,6 @@ class RecipeCard extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - Row( - children: [ - circleIconButton( - context: context, - icon: Icons.edit, - onPressed: () => {}, - ), - SizedBox(width: 4), - circleIconButton( - context: context, - icon: Icons.book, - onPressed: - () => Navigator.pushNamed( - context, - '/recipeDetail', - arguments: {'id': recipe.id}, - ), - ), - ], - ), ], ), Row( @@ -105,12 +96,12 @@ class RecipeCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - AspectRatio( - aspectRatio: 1.5, - child: buildNetworkImage(imageUrls.first), - ), + buildRecipeImage(), SizedBox(height: 8), - buildRecipeContent(context), + GestureDetector( + onTap: () => navigatorToRecipeDetail(context), + child: buildRecipeContent(context) + ) ], ), ); @@ -120,10 +111,10 @@ class RecipeCard extends StatelessWidget { Widget _buildIconText({ required IconData icon, required String text, - Color color = Colors.grey, // 默认灰色 + Color color = Colors.grey, double iconSize = 16, double textSize = 16, - double spacing = 2, // 图标与文本间距 + double spacing = 2, }) { return Row( children: [ diff --git a/lib/widgets/recipe/timeline.dart b/lib/widgets/recipe/timeline.dart index 4f84409..16e6d2f 100644 --- a/lib/widgets/recipe/timeline.dart +++ b/lib/widgets/recipe/timeline.dart @@ -48,19 +48,28 @@ class _RecipeTimeline extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - record.date, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + Row( + children: [ + Icon(Icons.date_range, color: Theme.of(context).colorScheme.primary), + SizedBox(width: 5), + Text( + record.date, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ], ), Column( children: [ - Text(record.name, style: TextStyle(fontSize: 16)), + Text( + record.name, + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), const SizedBox(height: 5), GestureDetector( onTap: () => imageTapClick(), child: AspectRatio( aspectRatio: 1.5, - child: buildNetworkImage(imageUrls.first), + child: buildNetworkImage(context, imageUrls.first), ), ), ],