// Author: fruit.com.cn import 'dart:async'; 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/theme/app_dimens.dart'; import '../../../core/widgets/glass_panel.dart'; import '../../../l10n/app_localizations.dart'; /// Home feed layer from `docs/移动端设计预览.html` screen 01. class HomeFeedOverlay extends StatelessWidget { const HomeFeedOverlay({ super.key, required this.l10n, required this.drama, required this.episode, required this.liked, required this.saved, required this.onLike, required this.onSave, required this.onComments, required this.onShare, required this.onEpisodes, required this.onDownload, required this.onClear, required this.onSearch, this.onPip, }); final AppLocalizations l10n; final Drama drama; final Episode episode; final bool liked; final bool saved; final VoidCallback onLike; final VoidCallback onSave; final VoidCallback onComments; final VoidCallback onShare; final VoidCallback onEpisodes; final VoidCallback onDownload; final VoidCallback onClear; final VoidCallback onSearch; final VoidCallback? onPip; @override Widget build(BuildContext context) { return SafeArea( bottom: false, child: Column( children: [ _header(context), Expanded( child: Align( alignment: Alignment.centerRight, child: Padding( padding: const EdgeInsets.fromLTRB(0, 0, 12, 16), child: _rail(), ), ), ), Padding( padding: const EdgeInsets.fromLTRB(14, 0, 14, 14), child: _infoCard(), ), ], ), ); } Widget _header(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(14, 6, 10, 4), child: Row( children: [ Expanded( child: Text( localizedDramaTitle(l10n, drama), key: const Key('home-feed-title'), maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontFamily: 'Outfit', color: AppColors.onSurface, fontSize: 13, fontWeight: FontWeight.w800, shadows: [Shadow(blurRadius: 8, color: Colors.black)], ), ), ), _RoundIcon( key: const Key('home-feed-search'), tooltip: l10n.search, icon: Icons.search_rounded, onPressed: onSearch, ), const SizedBox(width: 8), _RoundIcon( key: const Key('home-feed-clear-screen'), tooltip: l10n.clearScreen, icon: Icons.fullscreen_rounded, onPressed: onClear, ), const SizedBox(width: 8), _RoundIcon( key: const Key('home-feed-more'), tooltip: l10n.more, icon: Icons.menu_rounded, onPressed: () => _openMore(context), ), ], ), ); } Widget _rail() { return Column( mainAxisSize: MainAxisSize.min, children: [ _RailItem( key: const Key('rail-like'), icon: liked ? Icons.favorite_rounded : Icons.favorite_border_rounded, label: l10n.like, selected: liked, onTap: onLike, ), _RailItem( key: const Key('rail-comments'), icon: Icons.chat_bubble_outline_rounded, label: l10n.comments, onTap: onComments, ), _RailItem( key: const Key('rail-share'), icon: Icons.ios_share_rounded, label: l10n.share, onTap: onShare, ), _RailItem( key: const Key('rail-save'), icon: saved ? Icons.star_rounded : Icons.star_border_rounded, label: l10n.favorite, selected: saved, onTap: onSave, ), ], ); } Widget _infoCard() { final genre = drama.genre.isEmpty ? drama.category : drama.genre; return GlassPanel( key: const Key('home-feed-info-card'), radius: 14, blur: 6, padding: const EdgeInsets.all(12), gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xBF0F0F14), Color(0xA60F0F14)], ), borderColor: Colors.white.withValues(alpha: 0.06), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Wrap( spacing: 6, runSpacing: 4, children: [ if (genre.isNotEmpty) _tag( genre, background: AppColors.blue, foreground: AppColors.onPrimary, ), if (drama.category.isNotEmpty && drama.category != genre) _tag( drama.category, background: const Color(0xFF333333), foreground: const Color(0xFFDDDDDD), ), ], ), ), const SizedBox(width: 8), Flexible( child: Text( l10n.episodeOfTotal(episode.number, drama.episodeCount), key: const Key('home-feed-episode'), maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.right, style: const TextStyle(color: Color(0xFFBBBBBB), fontSize: 9), ), ), ], ), const SizedBox(height: 8), Text( episode.title.trim().isEmpty ? localizedDramaTitle(l10n, drama) : episode.title, key: const Key('home-feed-episode-title'), maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.onSurface, fontSize: 13, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 10), Row( children: [ Expanded( child: SizedBox( height: 34, child: FilledButton.icon( key: const Key('home-feed-follow-series'), onPressed: onSave, style: FilledButton.styleFrom( backgroundColor: AppColors.blue, foregroundColor: AppColors.onPrimary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( AppDimens.buttonRadius, ), ), padding: EdgeInsets.zero, textStyle: const TextStyle( fontSize: 12, fontWeight: FontWeight.w700, ), ), icon: const Icon(Icons.play_arrow_rounded, size: 16), label: Text( saved ? l10n.followingTab : l10n.followSeries, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ), ), const SizedBox(width: 8), Expanded( child: SizedBox( height: 34, child: FilledButton( key: const Key('home-feed-episodes'), onPressed: onEpisodes, style: FilledButton.styleFrom( backgroundColor: const Color(0xFF2A2A30), foregroundColor: AppColors.onSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( AppDimens.buttonRadius, ), ), padding: EdgeInsets.zero, textStyle: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, ), ), child: Text( l10n.episodes, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ), ), ], ), ], ), ); } Widget _tag( String label, { required Color background, required Color foreground, }) { return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: background, borderRadius: BorderRadius.circular(6), ), child: Text( label, maxLines: 1, style: TextStyle( color: foreground, fontSize: 8, fontWeight: FontWeight.w800, ), ), ); } Future _openMore(BuildContext context) async { await showModalBottomSheet( context: context, backgroundColor: AppColors.card, showDragHandle: true, builder: (sheetContext) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( key: const Key('home-feed-download'), leading: const Icon( Icons.download_outlined, color: AppColors.secondary, ), title: Text(l10n.downloads), onTap: () { Navigator.pop(sheetContext); onDownload(); }, ), if (onPip != null) ListTile( key: const Key('home-feed-pip'), leading: const Icon( Icons.picture_in_picture_alt_outlined, color: AppColors.secondary, ), title: Text(l10n.pictureInPicture), onTap: () { Navigator.pop(sheetContext); onPip!(); }, ), ], ), ), ); } } class _RoundIcon extends StatelessWidget { const _RoundIcon({ super.key, required this.tooltip, required this.icon, required this.onPressed, }); final String tooltip; final IconData icon; final VoidCallback onPressed; @override Widget build(BuildContext context) { return SizedBox( width: 36, height: 36, child: IconButton( tooltip: tooltip, onPressed: onPressed, padding: EdgeInsets.zero, style: IconButton.styleFrom( backgroundColor: const Color(0xCC2C2D33), foregroundColor: AppColors.onSurface, ), icon: Icon(icon, size: 18), ), ); } } class _RailItem extends StatelessWidget { const _RailItem({ super.key, required this.icon, required this.label, required this.onTap, this.selected = false, }); final IconData icon; final String label; final VoidCallback onTap; final bool selected; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 14), child: Semantics( button: true, label: label, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(22), child: SizedBox( width: 44, child: Column( children: [ Icon( icon, size: 22, color: selected ? AppColors.blue : AppColors.onSurface, ), const SizedBox(height: 2), Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.cream, fontSize: 9, shadows: [Shadow(blurRadius: 6, color: Colors.black)], ), ), ], ), ), ), ), ); } }