From 5060cae6267075879825c9398ff2de9499d6b34e Mon Sep 17 00:00:00 2001 From: DelLevin-Home Date: Mon, 10 Aug 2026 21:09:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9B=BE=E5=BA=93=E7=95=8C?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/explore/gallery_page.dart | 156 +++++++++++++++++++------- lib/pages/profile/profile_page.dart | 30 +++-- lib/pages/profile/watchlist_page.dart | 47 ++------ lib/utils/theme/app_theme.dart | 6 + 4 files changed, 153 insertions(+), 86 deletions(-) diff --git a/lib/pages/explore/gallery_page.dart b/lib/pages/explore/gallery_page.dart index c51255b..362182c 100644 --- a/lib/pages/explore/gallery_page.dart +++ b/lib/pages/explore/gallery_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import '../../data/gallery/gallery_dao.dart'; import '../../models/data_models.dart'; import '../../widgets/fade_in_local_image.dart'; @@ -119,58 +120,49 @@ class _GalleryPageState extends State { ], ), ) - : Column( - children: [ + : CustomScrollView( + slivers: [ // 类别筛选条 if (_availableCategories.length > 1) - SizedBox( - height: 44, - child: ListView( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - children: [ - _buildChip(null, '全部', colors), - ..._availableCategories.map((c) => _buildChip(c, _categoryLabels[c] ?? c, colors)), - ], + SliverToBoxAdapter( + child: SizedBox( + height: 44, + child: ListView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + children: [ + _buildChip(null, '全部', colors), + ..._availableCategories.map((c) => _buildChip(c, _categoryLabels[c] ?? c, colors)), + ], + ), ), ), - // 网格 - Expanded( - child: GridView.builder( - padding: const EdgeInsets.all(4), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 4, - mainAxisSpacing: 4, - ), - itemCount: _filteredItems.length, + // 瀑布流网格 + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + sliver: SliverMasonryGrid.count( + crossAxisCount: 2, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + childCount: _filteredItems.length, itemBuilder: (context, index) { final item = _filteredItems[index]; - return GestureDetector( - onTap: () => _openPreview(index), - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: FadeInLocalImage( - path: item.path, - fit: BoxFit.cover, - errorWidget: Container( - color: colors.surfaceContainerHighest, - child: Icon(Icons.broken_image_outlined, color: colors.onSurface.withValues(alpha: 0.3)), - ), - ), - ), - ); + return _buildCard(item, index, colors); }, ), ), // 底部计数 - SafeArea( - top: false, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Text( - '共 ${_filteredItems.length} 张图片', - style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.5)), + SliverToBoxAdapter( + child: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Center( + child: Text( + '共 ${_filteredItems.length} 张图片', + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.5)), + ), + ), ), ), ), @@ -179,6 +171,86 @@ class _GalleryPageState extends State { ); } + Widget _buildCard(GalleryItem item, int index, ColorScheme colors) { + final categoryLabel = _categoryLabels[item.category] ?? item.category; + final title = item.entityTitle.isNotEmpty ? item.entityTitle : '未命名'; + + return GestureDetector( + onTap: () => _openPreview(index), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 图片 + AspectRatio( + aspectRatio: _aspectRatioFor(item.category), + child: FadeInLocalImage( + path: item.path, + fit: BoxFit.cover, + errorWidget: Container( + color: colors.surfaceContainerHighest, + child: Icon(Icons.broken_image_outlined, color: colors.onSurface.withValues(alpha: 0.3)), + ), + ), + ), + const SizedBox(height: 8), + // 类别标签 + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, + borderRadius: BorderRadius.circular(2), + ), + child: Text( + categoryLabel, + style: TextStyle(fontSize: 10, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.6)), + ), + ), + const SizedBox(height: 4), + // 标题 + Text( + title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface), + ), + // 角色图片显示父作品 + if (item.parentTitle != null && item.parentTitle!.isNotEmpty) ...[ + const SizedBox(height: 2), + Text( + item.parentTitle!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.45)), + ), + ], + ], + ), + ); + } + + /// 不同类别用不同宽高比,让瀑布流有错落感 + double _aspectRatioFor(String category) { + switch (category) { + case 'movie_poster': + case 'book_cover': + case 'game_cover': + return 2 / 3; // 竖版海报/封面 + case 'movie_posters': + return 2 / 3; + case 'person_photo': + case 'movie_character': + case 'book_character': + case 'game_character': + return 3 / 4; // 人物照 + case 'game_screenshot': + return 16 / 9; // 横版截图 + case 'note_image': + return 1 / 1; // 笔记图片方形 + default: + return 3 / 4; + } + } + Widget _buildChip(String? category, String label, ColorScheme colors) { final selected = _selectedCategory == category; return Padding( diff --git a/lib/pages/profile/profile_page.dart b/lib/pages/profile/profile_page.dart index 06a3dbd..5d4c82f 100644 --- a/lib/pages/profile/profile_page.dart +++ b/lib/pages/profile/profile_page.dart @@ -41,6 +41,9 @@ class _ProfilePageState extends State with RouteAware { // 我的模块切换索引 (0=影视, 1=阅读, 2=游戏, 3=笔记) int _myModuleIndex = 0; + // Hero 背景封面路径缓存,避免每次 build 都 shuffle 换图 + List _heroCoverPaths = const []; + @override void initState() { super.initState(); @@ -61,8 +64,13 @@ class _ProfilePageState extends State with RouteAware { @override void didPopNext() { - // 从其他页面返回时刷新用户数据(头像、昵称等) + // 从其他页面返回时刷新用户数据(头像、昵称等)+ 换一次背景图 _loadUserData(); + _invalidateHeroCache(); + } + + void _invalidateHeroCache() { + setState(() => _heroCoverPaths = const []); } Future _loadUserData() async { @@ -129,14 +137,18 @@ class _ProfilePageState extends State with RouteAware { Widget _buildHero(List movies, List books, List notes, List games) { final colors = Theme.of(context).colorScheme; - final coverPaths = [ - ...movies - .where((m) => m.posterPath != null && m.posterPath!.isNotEmpty) - .map((m) => m.posterPath!), - ...books - .where((b) => b.coverPath != null && b.coverPath!.isNotEmpty) - .map((b) => b.coverPath!), - ]..shuffle(); + // 仅在缓存为空时重新生成(进入页面 / 从其他页面返回),避免每次 build 都 shuffle 换图 + if (_heroCoverPaths.isEmpty) { + _heroCoverPaths = [ + ...movies + .where((m) => m.posterPath != null && m.posterPath!.isNotEmpty) + .map((m) => m.posterPath!), + ...books + .where((b) => b.coverPath != null && b.coverPath!.isNotEmpty) + .map((b) => b.coverPath!), + ]..shuffle(); + } + final coverPaths = _heroCoverPaths; final hasData = coverPaths.length >= 4; diff --git a/lib/pages/profile/watchlist_page.dart b/lib/pages/profile/watchlist_page.dart index 7deb19d..81183a0 100644 --- a/lib/pages/profile/watchlist_page.dart +++ b/lib/pages/profile/watchlist_page.dart @@ -218,25 +218,18 @@ class _WatchlistPageState extends State { padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: colors.surfaceContainerHigh, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(6), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 封面 Container( - width: 52, - height: 74, + width: 56, + height: 80, decoration: BoxDecoration( color: colors.surfaceContainerHighest, - borderRadius: BorderRadius.circular(6), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.08), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], + borderRadius: BorderRadius.circular(4), ), clipBehavior: Clip.antiAlias, child: item.imagePath != null && item.imagePath!.isNotEmpty @@ -248,23 +241,12 @@ class _WatchlistPageState extends State { : Icon(Icons.image_outlined, size: 18, color: colors.onSurface.withValues(alpha: 0.2)), ), - const SizedBox(width: 10), - // 左侧竖色条 - Container( - width: 3, - height: 50, - decoration: BoxDecoration( - color: _typeColor[item.type]!.withValues(alpha: 0.6), - borderRadius: BorderRadius.circular(2), - ), - ), - const SizedBox(width: 10), - // 标题 + 类型 + const SizedBox(width: 12), + // 标题 + 底部信息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 2), Text(item.title, maxLines: 2, overflow: TextOverflow.ellipsis, @@ -273,7 +255,7 @@ class _WatchlistPageState extends State { fontWeight: FontWeight.w500, color: colors.onSurface, height: 1.3)), - const SizedBox(height: 6), + const SizedBox(height: 8), Row( children: [ Icon(_typeIcon[item.type], @@ -284,21 +266,16 @@ class _WatchlistPageState extends State { fontSize: 11, fontWeight: FontWeight.w500, color: _typeColor[item.type])), + const Spacer(), + Text(_formatRelative(item.createdAt), + style: TextStyle( + fontSize: 11, + color: colors.onSurface.withValues(alpha: 0.4))), ], ), ], ), ), - // 右侧加入时间 - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text(_formatRelative(item.createdAt), - style: TextStyle( - fontSize: 11, - color: colors.onSurface.withValues(alpha: 0.4))), - ], - ), ], ), ), diff --git a/lib/utils/theme/app_theme.dart b/lib/utils/theme/app_theme.dart index 634ac07..173e6b1 100644 --- a/lib/utils/theme/app_theme.dart +++ b/lib/utils/theme/app_theme.dart @@ -75,6 +75,8 @@ class AppTheme { backgroundColor: scheme.surface, foregroundColor: scheme.onSurface, elevation: 0, + scrolledUnderElevation: 0, + surfaceTintColor: Colors.transparent, centerTitle: false, titleSpacing: 24, titleTextStyle: TextStyle( @@ -207,6 +209,8 @@ class AppTheme { backgroundColor: _white, foregroundColor: _black, elevation: 0, + scrolledUnderElevation: 0, + surfaceTintColor: Colors.transparent, centerTitle: false, titleSpacing: 24, titleTextStyle: TextStyle( @@ -431,6 +435,8 @@ class AppTheme { backgroundColor: _black, foregroundColor: _white, elevation: 0, + scrolledUnderElevation: 0, + surfaceTintColor: Colors.transparent, centerTitle: false, titleSpacing: 24, titleTextStyle: TextStyle(