新增详情页显示样式

This commit is contained in:
DelLevin-Home
2026-08-12 22:39:36 +08:00
parent ad6ab51afd
commit 509d1720bc
4 changed files with 802 additions and 20 deletions

View File

@@ -148,9 +148,11 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
if (Breakpoint.isDesktop(context)) {
return _buildDesktopStyle(movie, colors);
}
return _detailStyle == 1
? _buildOverlayStyle(movie, colors)
: _buildStandardStyle(movie, colors);
return switch (_detailStyle) {
1 => _buildOverlayStyle(movie, colors),
2 => _buildMinimalLayeredStyle(movie, colors),
_ => _buildStandardStyle(movie, colors),
};
}
/// 桌面端左右分栏布局
@@ -1128,6 +1130,274 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
);
}
/// 浅色极简层叠样式:海报卡片 + 浅色信息卡片层叠(无毛玻璃)
Widget _buildMinimalLayeredStyle(Movie movie, ColorScheme colors) {
final safeTop = MediaQuery.of(context).padding.top;
return Scaffold(
backgroundColor: colors.surface,
body: Stack(
children: [
// 整体可滚动(海报卡片 + 内容一起滑动)
Padding(
padding: EdgeInsets.only(top: safeTop + 48),
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 120),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 居中海报卡片
Center(child: _buildLayeredPoster(movie)),
const SizedBox(height: 20),
_buildLayeredHeader(movie),
const SizedBox(height: 20),
if (movie.directors.isNotEmpty)
_buildLayeredInfoRow('导演', movie.directors.join(''), colors),
if (movie.writers.isNotEmpty)
_buildLayeredInfoRow('编剧', movie.writers.join(''), colors),
if (movie.actors.isNotEmpty)
_buildLayeredInfoRow('主演', movie.actors.join(''), colors),
if (movie.genres.isNotEmpty)
_buildLayeredGenres(movie),
CharacterPreviewSection(
characters: _characters,
onTap: _openCharacterSheet,
),
WorkPeopleSection(workId: movie.id, workType: 'movie'),
if (movie.summary != null && movie.summary!.isNotEmpty)
_buildLayeredSummary(movie),
const SizedBox(height: 20),
_buildLayeredExtraSections(movie),
],
),
),
),
// 顶部导航栏
Positioned(
top: 0, left: 0, right: 0,
child: Container(
padding: EdgeInsets.only(top: safeTop),
color: colors.surface,
child: SizedBox(
height: 48,
child: Row(children: [
const SizedBox(width: 4),
IconButton(
icon: widget.embedded
? Icon(Icons.arrow_back, color: colors.onSurface, size: 18)
: Icon(Icons.arrow_back_ios_new, color: colors.onSurface, size: 18),
onPressed: widget.embedded
? () => context.read<AppProvider>().selectMovie(null)
: () => Navigator.pop(context),
),
const SizedBox(width: 4),
Expanded(
child: Text(movie.title,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface),
maxLines: 1, overflow: TextOverflow.ellipsis),
),
_buildStyleButton(color: colors.onSurface),
]),
),
),
),
Positioned(right: 16, bottom: 24, child: _buildFloatingActionButtons(movie)),
],
),
);
}
/// 浅色极简:居中海报卡片
Widget _buildLayeredPoster(Movie movie) {
final colors = Theme.of(context).colorScheme;
final hasPoster = movie.posterPath != null && movie.posterPath!.isNotEmpty;
return Container(
width: 160, height: 230,
decoration: BoxDecoration(
color: colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
boxShadow: hasPoster
? [BoxShadow(color: Colors.black.withValues(alpha: 0.12), blurRadius: 20, offset: const Offset(0, 8))]
: null,
),
clipBehavior: Clip.antiAlias,
child: hasPoster
? FadeInLocalImage(path: movie.posterPath, fit: BoxFit.cover)
: Center(child: Icon(Icons.movie_outlined, size: 48, color: colors.onSurface.withValues(alpha: 0.25))),
);
}
/// 浅色极简:居中标题 + 评分/状态/分类
Widget _buildLayeredHeader(Movie movie) {
final colors = Theme.of(context).colorScheme;
return Column(
children: [
Text(movie.title,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: colors.onSurface, height: 1.3)),
if (movie.alternateTitles.isNotEmpty) ...[
const SizedBox(height: 6),
Text(movie.alternateTitles.join(' / '),
textAlign: TextAlign.center,
style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4), height: 1.4)),
],
const SizedBox(height: 12),
Wrap(spacing: 8, runSpacing: 8, alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [
if (movie.rating != null) ...[
Icon(Icons.star, size: 20, color: colors.onSurface),
const SizedBox(width: 4),
Text(movie.rating!.toStringAsFixed(1),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)),
],
_buildStatusTag(movie),
_buildCategoryTag(movie),
]),
if (movie.releaseDate != null) ...[
const SizedBox(height: 8),
GestureDetector(
onTap: _toggleDateDisplay,
child: Row(mainAxisSize: MainAxisSize.min, children: [
Text(
_showExactDate
? '${movie.releaseDate!.year}${movie.releaseDate!.month.toString().padLeft(2, '0')}${movie.releaseDate!.day.toString().padLeft(2, '0')}日上映'
: '${movie.releaseDate!.year}${movie.releaseDate!.month.toString().padLeft(2, '0')}月上映',
style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.4)),
),
const SizedBox(width: 4),
Icon(Icons.tune, size: 14, color: colors.onSurface.withValues(alpha: 0.2)),
]),
),
],
if (movie.watchDate != null) ...[
const SizedBox(height: 4),
Text('观看于 ${_formatDate(movie.watchDate!)}',
style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.4))),
],
if (movie.watchCount > 0) ...[
const SizedBox(height: 4),
Text('已观看 ${movie.watchCount}',
style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.4))),
],
if (movie.duration > 0) ...[
const SizedBox(height: 4),
Text(_formatDuration(movie.duration),
style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.4))),
],
],
);
}
/// 浅色极简:信息卡片(导演/编剧/主演)
Widget _buildLayeredInfoRow(String label, String value, ColorScheme colors) {
return Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: colors.outlineVariant, width: 0.5),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(width: 48, child: Text(label, style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)))),
Expanded(child: Text(value, style: TextStyle(fontSize: 15, color: colors.onSurface, height: 1.5))),
],
),
);
}
/// 浅色极简:类型卡片
Widget _buildLayeredGenres(Movie movie) {
final colors = Theme.of(context).colorScheme;
return Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: colors.outlineVariant, width: 0.5),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(width: 48, child: Text('类型', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)))),
Expanded(child: Wrap(spacing: 8, runSpacing: 8,
children: movie.genres.map((g) => Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(color: colors.surface, borderRadius: BorderRadius.circular(16)),
child: Text(g, style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.6))),
)).toList(),
)),
],
),
);
}
/// 浅色极简:简介卡片
Widget _buildLayeredSummary(Movie movie) {
final colors = Theme.of(context).colorScheme;
return Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: colors.outlineVariant, width: 0.5),
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Container(width: 4, height: 14, decoration: BoxDecoration(color: colors.onSurface, borderRadius: BorderRadius.circular(2))),
const SizedBox(width: 8),
Text('简介', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface)),
]),
const SizedBox(height: 12),
Text(movie.summary!, style: TextStyle(fontSize: 15, color: colors.onSurface, height: 1.8)),
]),
);
}
/// 浅色极简:更多(影评/海报墙/角色)卡片标题
Widget _buildLayeredExtraSections(Movie movie) {
final colors = Theme.of(context).colorScheme;
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Container(width: 4, height: 16, decoration: BoxDecoration(color: colors.onSurface, borderRadius: BorderRadius.circular(2))),
const SizedBox(width: 8),
Text('更多', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface)),
]),
const SizedBox(height: 12),
_buildExtraSectionItem(
icon: Icons.rate_review_outlined,
title: '影评',
subtitleFuture: context.read<AppProvider>().getMovieReviewCount(movie.id),
emptyText: '暂无影评',
unit: '条影评',
onTap: () => _navigateToReviews(movie),
),
const SizedBox(height: 10),
_buildExtraSectionItem(
icon: Icons.photo_library_outlined,
title: '海报墙',
subtitleFuture: context.read<AppProvider>().getMoviePosterCount(movie.id),
emptyText: '暂无海报',
unit: '张海报',
onTap: () => _navigateToPosters(movie),
),
const SizedBox(height: 10),
_buildExtraSectionItem(
icon: Icons.people_outline,
title: '角色',
subtitleFuture: context.read<AppProvider>().getMovieCharacterCount(movie.id),
emptyText: '暂无角色',
unit: '个角色',
onTap: () => _navigateToCharacters(movie),
),
]);
}
/// 叠层模式:封面小图 + 标题/评分
Widget _buildOverlayHeader(Movie movie) {
final hasPoster = movie.posterPath != null && movie.posterPath!.isNotEmpty;
@@ -1195,9 +1465,9 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
void _showStylePicker() {
final colors = Theme.of(context).colorScheme;
const names = ['默认样式', '毛玻璃层叠'];
const icons = [Icons.article_outlined, Icons.blur_on_outlined];
const subtitles = ['标准封面顶部布局', '封面背景 + 毛玻璃卡片'];
const names = ['默认样式', '毛玻璃层叠', '浅色极简'];
const icons = [Icons.article_outlined, Icons.blur_on_outlined, Icons.layers_outlined];
const subtitles = ['标准封面顶部布局', '封面背景 + 毛玻璃卡片', '海报卡片 + 浅色信息卡片层叠'];
appModalBottomSheet(
context: context,
backgroundColor: colors.surface,