Files
ReelShort/lib/pages/drama_detail/widgets/drama_detail_header.dart
T

207 lines
5.9 KiB
Dart

// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/data/models/drama.dart';
import '../../../core/formatting/localized_content.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/widgets/remote_image.dart';
import '../../../core/widgets/subpage_header.dart';
import '../../../l10n/app_localizations.dart';
const double _horizontal = 14;
const double _topGap = 8;
const double _posterWidth = 96;
const double _posterHeight = 128;
const double _posterRadius = 12;
const double _infoGap = 14;
const double _favoriteHeight = 34;
const double _favoriteRadius = 17;
const double _favoriteHorizontal = 12;
/// 详情页顶部操作与短剧主信息所需的交互回调。
class DramaDetailHeaderActions {
const DramaDetailHeaderActions({
required this.onBack,
required this.onShare,
required this.onFavorite,
required this.moreMenu,
});
final VoidCallback onBack;
final VoidCallback onShare;
final VoidCallback onFavorite;
final Widget moreMenu;
}
/// 详情页暖色渐变头部:操作行、封面、标题、集数与收藏按钮。
class DramaDetailHeader extends StatelessWidget {
const DramaDetailHeader({
super.key,
required this.l10n,
required this.drama,
required this.saved,
required this.actions,
});
final AppLocalizations l10n;
final Drama drama;
final bool saved;
final DramaDetailHeaderActions actions;
@override
Widget build(BuildContext context) {
return Container(
key: const Key('detail-hero'),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.followingGlow,
AppColors.followingGlowMid,
AppColors.appBackground,
],
stops: [0, 0.14, 0.3],
),
),
child: SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.fromLTRB(_horizontal, 8, _horizontal, 10),
child: Column(
children: [
_actions(context),
const SizedBox(height: _topGap),
_identity(context),
],
),
),
),
);
}
Widget _actions(BuildContext context) {
return Row(
children: [
AppSubpageBackButton(
backKey: const Key('detail-back'),
dark: true,
onBack: actions.onBack,
),
const Spacer(),
AppSubpageCircleButton(
buttonKey: const Key('detail-share'),
tooltip: l10n.share,
icon: Icons.share_outlined,
dark: true,
onPressed: actions.onShare,
),
const SizedBox(width: 8),
actions.moreMenu,
],
);
}
Widget _identity(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
key: const Key('detail-poster'),
width: _posterWidth,
height: _posterHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_posterRadius),
boxShadow: const [
BoxShadow(
color: AppColors.cardShadow,
blurRadius: 16,
offset: Offset(0, 5),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(_posterRadius),
child: RemoteImage(
drama.posterUrl,
fit: BoxFit.cover,
fallbackAspectRatio: 3 / 4,
),
),
),
const SizedBox(width: _infoGap),
Expanded(
child: SizedBox(
height: _posterHeight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
localizedDramaTitle(l10n, drama),
key: const Key('detail-title'),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: 20,
fontWeight: FontWeight.w800,
height: 1.2,
),
),
const SizedBox(height: 6),
Text(
l10n.episodeCountLabel(drama.episodeCount),
key: const Key('detail-episode-count'),
style: const TextStyle(
color: AppColors.muted,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
const Spacer(),
_favoriteButton(),
],
),
),
),
],
);
}
Widget _favoriteButton() {
return GestureDetector(
onTap: actions.onFavorite,
child: Container(
key: const Key('detail-favorite'),
height: _favoriteHeight,
padding: const EdgeInsets.symmetric(horizontal: _favoriteHorizontal),
decoration: BoxDecoration(
color: saved ? AppColors.blue : AppColors.card,
borderRadius: BorderRadius.circular(_favoriteRadius),
border: Border.all(color: saved ? AppColors.blue : AppColors.blue),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
saved ? Icons.star_rounded : Icons.star_border_rounded,
size: 16,
color: saved ? AppColors.onPrimary : AppColors.blue,
),
const SizedBox(width: 4),
Text(
l10n.favorite,
style: TextStyle(
color: saved ? AppColors.onPrimary : AppColors.blue,
fontSize: 12,
fontWeight: FontWeight.w700,
),
),
],
),
),
);
}
}