feat(ui): 校正首页发现详情播放器预览
This commit is contained in:
1 parent
87db548591
commit
21d457fd98
13 files changed
+1218
-1684
No files matched your search
@@ -13,7 +13,6 @@ import '../../core/theme/app_dimens.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/app_bottom_nav.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../core/widgets/glass_panel.dart';
|
||||
import '../../core/widgets/remote_image.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import 'discover_binding.dart';
|
||||
@@ -165,53 +164,41 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 2, 16, 12),
|
||||
padding: const EdgeInsets.fromLTRB(14, 2, 14, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 3,
|
||||
height: 18,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.blue,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.trendingMicroDramas,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Outfit',
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
l10n.trendingSubtitle,
|
||||
style: const TextStyle(
|
||||
color: AppColors.subtle,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Text(
|
||||
l10n.trendingMicroDramas,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onSurface,
|
||||
fontSize: AppDimens.previewSectionTitle,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
categories.selectCode(CategoryController.hotCode),
|
||||
categories.selectCode(CategoryController.allCode),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AppColors.blue,
|
||||
foregroundColor: AppColors.accent,
|
||||
minimumSize: const Size(44, 44),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
l10n.seeAll,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right_rounded, size: 16),
|
||||
],
|
||||
child: Text(
|
||||
'${l10n.all} ›',
|
||||
style: const TextStyle(fontSize: 10),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -236,55 +223,41 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
|
||||
Widget _header(AppLocalizations l10n) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 6, 16, 12),
|
||||
padding: const EdgeInsets.fromLTRB(14, 2, 14, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.blue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
l10n.appName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Outfit',
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 21,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.05,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
l10n.verticalMiniSeries,
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.6,
|
||||
),
|
||||
),
|
||||
],
|
||||
Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.blue,
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.play_arrow_rounded,
|
||||
color: AppColors.onPrimary,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.discoverTab,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Outfit',
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
_HeaderIcon(
|
||||
key: const Key('discover-search-button'),
|
||||
icon: Icons.search_rounded,
|
||||
tooltip: l10n.search,
|
||||
onTap: logic.openSearch,
|
||||
),
|
||||
const SizedBox(width: 7),
|
||||
Semantics(
|
||||
button: true,
|
||||
label: widget.signedIn ? l10n.myTab : l10n.logIn,
|
||||
@@ -293,20 +266,20 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: widget.signedIn ? widget.onCoins : widget.onSignIn,
|
||||
child: Container(
|
||||
width: 38,
|
||||
height: 38,
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.raised,
|
||||
border: Border.all(
|
||||
color: AppColors.secondary.withValues(alpha: 0.7),
|
||||
width: 2,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF6A5CFF), AppColors.blue],
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.person_rounded,
|
||||
color: AppColors.onSurface,
|
||||
size: 21,
|
||||
size: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -318,9 +291,9 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
|
||||
Widget _search() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
child: SizedBox(
|
||||
height: AppDimens.searchHeight,
|
||||
height: 36,
|
||||
child: TextField(
|
||||
key: const Key('discover-search'),
|
||||
controller: logic.search,
|
||||
@@ -331,27 +304,22 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: AppColors.card,
|
||||
fillColor: AppColors.input,
|
||||
hintText: AppLocalizations.of(context)!.searchDiscover,
|
||||
hintStyle: const TextStyle(color: AppColors.subtle, fontSize: 12.5),
|
||||
hintStyle: const TextStyle(color: AppColors.cream, fontSize: 11),
|
||||
prefixIcon: const Icon(
|
||||
Icons.search_rounded,
|
||||
color: AppColors.subtle,
|
||||
size: 20,
|
||||
),
|
||||
suffixIcon: const Icon(
|
||||
Icons.mic_none_rounded,
|
||||
color: AppColors.muted,
|
||||
size: 19,
|
||||
size: 17,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppDimens.glassSmallRadius),
|
||||
borderSide: const BorderSide(color: AppColors.border),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppDimens.glassSmallRadius),
|
||||
borderSide: const BorderSide(color: AppColors.blue, width: 1.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderSide: const BorderSide(color: AppColors.blue, width: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -361,9 +329,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
|
||||
Widget _categories(AppLocalizations l10n, CategoryController categories) {
|
||||
final choices = <({String code, String label})>[
|
||||
(code: CategoryController.allCode, label: l10n.all),
|
||||
(code: CategoryController.hotCode, label: l10n.hot),
|
||||
(code: CategoryController.newCode, label: l10n.newest),
|
||||
(code: CategoryController.allCode, label: l10n.recommended),
|
||||
for (final category in categories.categories)
|
||||
(code: 'category-${category.id}', label: category.name),
|
||||
];
|
||||
@@ -371,7 +337,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
key: const Key('home-category-chips'),
|
||||
height: AppDimens.tapTarget,
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: choices.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 8),
|
||||
@@ -390,12 +356,10 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? AppColors.blue : AppColors.card,
|
||||
color: selected ? AppColors.blue : const Color(0xFF29272B),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(
|
||||
color: selected
|
||||
? AppColors.blue
|
||||
: Colors.white.withValues(alpha: 0.08),
|
||||
color: selected ? AppColors.blue : Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
@@ -403,7 +367,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: selected ? AppColors.onPrimary : AppColors.muted,
|
||||
fontSize: 12,
|
||||
fontSize: AppDimens.chipFont,
|
||||
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
|
||||
height: 1.1,
|
||||
),
|
||||
@@ -423,23 +387,20 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
WatchProgress record,
|
||||
) {
|
||||
final progress = record.fraction;
|
||||
return GlassPanel(
|
||||
return Container(
|
||||
key: const Key('discover-continue-watching'),
|
||||
radius: AppDimens.glassSmallRadius,
|
||||
blur: 18,
|
||||
padding: const EdgeInsets.all(12),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [Color(0x1FFFFFFF), Color(0x0AFFFFFF)],
|
||||
padding: const EdgeInsets.all(9),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.cardAlt,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: SizedBox(
|
||||
width: 64,
|
||||
height: 88,
|
||||
width: 52,
|
||||
height: 66,
|
||||
child: RemoteImage(
|
||||
drama.posterUrl,
|
||||
fit: BoxFit.cover,
|
||||
@@ -447,57 +408,38 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const SizedBox(width: 9),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.blue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Flexible(
|
||||
child: Text(
|
||||
l10n.continueWatching,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.blue,
|
||||
fontSize: 9.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
l10n.continueWatching,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.accent,
|
||||
fontSize: 8,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
drama.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
_resumeMeta(drama, record),
|
||||
_resumeShort(drama, record),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontSize: 10.5,
|
||||
),
|
||||
style: const TextStyle(color: Color(0xFFBBBBBB), fontSize: 9),
|
||||
),
|
||||
const SizedBox(height: 7),
|
||||
ClipRRect(
|
||||
@@ -505,7 +447,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
child: LinearProgressIndicator(
|
||||
value: progress <= 0 ? null : progress,
|
||||
minHeight: 3,
|
||||
backgroundColor: Colors.white.withValues(alpha: 0.12),
|
||||
backgroundColor: const Color(0xFF474248),
|
||||
valueColor: const AlwaysStoppedAnimation(AppColors.blue),
|
||||
),
|
||||
),
|
||||
@@ -525,29 +467,24 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: AppColors.blue,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
minimumSize: const Size(64, 34),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
shape: const StadiumBorder(),
|
||||
minimumSize: const Size(56, 28),
|
||||
padding: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(l10n.play),
|
||||
const SizedBox(width: 2),
|
||||
const Icon(Icons.play_arrow_rounded, size: 14),
|
||||
],
|
||||
),
|
||||
child: Text(l10n.resume),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _resumeMeta(Drama drama, WatchProgress record) {
|
||||
String _resumeShort(Drama drama, WatchProgress record) {
|
||||
final label = AppLocalizations.of(context)!;
|
||||
Episode? episode;
|
||||
for (final item in drama.episodes) {
|
||||
@@ -556,10 +493,8 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
final head = episode == null
|
||||
? ''
|
||||
: '${label.episodeLabel(episode.number)} / ${drama.episodeCount} · ';
|
||||
return '$head${_duration(record.position)} / ${_duration(record.duration)}';
|
||||
if (episode == null) return _duration(record.position);
|
||||
return label.episodeOfTotal(episode.number, drama.episodeCount);
|
||||
}
|
||||
|
||||
Widget _quickActions(AppLocalizations l10n, CategoryController categories) {
|
||||
@@ -574,7 +509,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
onTap: logic.openSearch,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(width: 7),
|
||||
Expanded(
|
||||
child: _QuickAction(
|
||||
key: const Key('discover-quick-charts'),
|
||||
@@ -584,17 +519,17 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
onTap: () => categories.selectCode(CategoryController.hotCode),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(width: 7),
|
||||
Expanded(
|
||||
child: _QuickAction(
|
||||
key: const Key('discover-quick-new'),
|
||||
icon: Icons.auto_awesome_rounded,
|
||||
label: l10n.newest,
|
||||
label: l10n.newReleases,
|
||||
color: AppColors.gold,
|
||||
onTap: () => categories.selectCode(CategoryController.newCode),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(width: 7),
|
||||
Expanded(
|
||||
child: _QuickAction(
|
||||
key: const Key('discover-quick-watchlist'),
|
||||
@@ -617,8 +552,8 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
items: [
|
||||
AppBottomNavItem(
|
||||
tabKey: const Key('discover-nav-home'),
|
||||
icon: Icons.home_outlined,
|
||||
selectedIcon: Icons.home_rounded,
|
||||
icon: Icons.play_circle_outline_rounded,
|
||||
selectedIcon: Icons.play_circle_fill_rounded,
|
||||
label: l10n.homeTab,
|
||||
onTap: () => logic.act(
|
||||
context,
|
||||
@@ -637,8 +572,8 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
),
|
||||
AppBottomNavItem(
|
||||
tabKey: const Key('discover-nav-my'),
|
||||
icon: Icons.person_outline_rounded,
|
||||
selectedIcon: Icons.person_rounded,
|
||||
icon: Icons.video_library_outlined,
|
||||
selectedIcon: Icons.video_library_rounded,
|
||||
label: l10n.myTab,
|
||||
onTap: () => logic.act(
|
||||
context,
|
||||
@@ -651,6 +586,37 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
);
|
||||
}
|
||||
|
||||
class _HeaderIcon extends StatelessWidget {
|
||||
const _HeaderIcon({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.tooltip,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final String tooltip;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: IconButton(
|
||||
tooltip: tooltip,
|
||||
onPressed: onTap,
|
||||
padding: EdgeInsets.zero,
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF2C2D33),
|
||||
foregroundColor: AppColors.onSurface,
|
||||
),
|
||||
icon: Icon(icon, size: 14),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QuickAction extends StatelessWidget {
|
||||
const _QuickAction({
|
||||
super.key,
|
||||
@@ -668,31 +634,20 @@ class _QuickAction extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: AppColors.card,
|
||||
borderRadius: BorderRadius.circular(AppDimens.glassSmallRadius),
|
||||
color: AppColors.quick,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 76,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppDimens.glassSmallRadius),
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
height: 62,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 5),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 17),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Icon(icon, color: color, size: 16),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
maxLines: 2,
|
||||
@@ -700,7 +655,7 @@ class _QuickAction extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontSize: 9.5,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.05,
|
||||
),
|
||||
|
||||
@@ -37,27 +37,22 @@ class _HomeDramaGrid extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (dramas.isEmpty) {
|
||||
final missingCategory = categories.selectedSectionMissing;
|
||||
return _fill(
|
||||
EmptyState(
|
||||
key: const Key('home-all-dramas-empty'),
|
||||
icon: searching
|
||||
? Icons.search_off_rounded
|
||||
: missingCategory
|
||||
? Icons.grid_view_rounded
|
||||
: Icons.movie_filter_outlined,
|
||||
title: searching
|
||||
? l10n.noSearchResults
|
||||
: missingCategory
|
||||
: categories.selectedSectionMissing
|
||||
? l10n.noCategoryDramas
|
||||
: l10n.noSearchResults,
|
||||
subtitle: l10n.emptyHint,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SliverMainAxisGroup(
|
||||
slivers: [
|
||||
_dramaGrid(dramas),
|
||||
@@ -68,14 +63,14 @@ class _HomeDramaGrid extends StatelessWidget {
|
||||
|
||||
Widget _dramaGrid(List<Drama> items) => SliverPadding(
|
||||
key: const Key('home-all-dramas-section'),
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppDimens.pageHorizontal),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
sliver: SliverGrid(
|
||||
key: const Key('home-all-dramas-grid'),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 14,
|
||||
crossAxisSpacing: 12,
|
||||
childAspectRatio: 0.55,
|
||||
mainAxisSpacing: 10,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisExtent: 164,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => _HomeDramaCard(
|
||||
@@ -88,9 +83,8 @@ class _HomeDramaGrid extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
|
||||
Widget _fill(Widget child) {
|
||||
return SliverFillRemaining(hasScrollBody: false, child: child);
|
||||
}
|
||||
Widget _fill(Widget child) =>
|
||||
SliverFillRemaining(hasScrollBody: false, child: child);
|
||||
}
|
||||
|
||||
class _HomeDramaCard extends StatelessWidget {
|
||||
@@ -107,18 +101,21 @@ class _HomeDramaCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final genre = drama.genre.isEmpty ? drama.category : drama.genre;
|
||||
final free =
|
||||
drama.freeEpisodes > 0 ||
|
||||
drama.episodes.any((episode) => episode.isFree);
|
||||
return InkWell(
|
||||
key: Key('home-drama-card-${drama.id}'),
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
SizedBox(
|
||||
key: Key('home-drama-cover-${drama.id}'),
|
||||
aspectRatio: 3 / 4.2,
|
||||
height: 120,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
@@ -132,120 +129,76 @@ class _HomeDramaCard extends StatelessWidget {
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x33000000),
|
||||
Color(0x00000000),
|
||||
Color(0xB3000000),
|
||||
],
|
||||
stops: [0, 0.42, 1],
|
||||
colors: [Color(0x0D000000), Color(0xCC000000)],
|
||||
stops: [0.45, 1],
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 8,
|
||||
left: 8,
|
||||
child: Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
children: [
|
||||
if (drama.isHot) _badge(l10n.hot, AppColors.blue),
|
||||
if (drama.freeEpisodes > 0)
|
||||
_badge(l10n.free, AppColors.success),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (drama.viewCount > 0)
|
||||
if (free)
|
||||
Positioned(
|
||||
right: 8,
|
||||
bottom: 8,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.42),
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
top: 6,
|
||||
left: 6,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
child: Text(
|
||||
_compactCount(drama.viewCount),
|
||||
key: Key('home-drama-views-${drama.id}'),
|
||||
style: const TextStyle(
|
||||
color: AppColors.cream,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.blue,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
l10n.free,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: 8,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 6,
|
||||
right: 6,
|
||||
bottom: 6,
|
||||
child: Text(
|
||||
drama.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.w800,
|
||||
shadows: [Shadow(blurRadius: 6, color: Colors.black)],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
drama.title,
|
||||
key: Key('home-drama-title-${drama.id}'),
|
||||
maxLines: 2,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.2,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
genre.isEmpty ? l10n.videoPending : genre,
|
||||
key: Key('home-drama-genre-${drama.id}'),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.subtle,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.2,
|
||||
),
|
||||
style: const TextStyle(color: Color(0xFFBBBBBB), fontSize: 9),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _badge(String label, Color color) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 0.3,
|
||||
height: 1.1,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _compactCount(int value) {
|
||||
if (value >= 1000000) {
|
||||
final scaled = value / 1000000;
|
||||
return '${scaled.toStringAsFixed(scaled >= 10 ? 0 : 1)}M';
|
||||
}
|
||||
if (value >= 1000) {
|
||||
final scaled = value / 1000;
|
||||
return '${scaled.toStringAsFixed(scaled >= 10 ? 0 : 1)}K';
|
||||
}
|
||||
return '$value';
|
||||
}
|
||||
Reference in new issue
Block a user