import 'package:flutter/material.dart'; import 'package:novyronst/common/api/video_request.dart'; import 'package:novyronst/common/const/const_color.dart'; import 'package:novyronst/common/const/const_string.dart'; import 'package:novyronst/common/help/app_nav.dart'; import 'package:novyronst/common/help/help_image.dart'; import 'package:novyronst/common/model/ny_drama_model.dart'; import 'package:novyronst/common/model/ny_episode_model.dart'; import 'package:novyronst/pages/first/player_detail/video_detail_page.dart'; import 'package:novyronst/pages/widgets/cut_corner_clipper.dart'; import 'package:novyronst/tools/widgets/novy_com_widget.dart'; import '../../../common/model/ny_detail_model.dart'; const int episodeSeriesPageSize = 6; class HomeRemainingSections extends StatefulWidget { const HomeRemainingSections({super.key, required this.dramaOne}); final NyDramaModel dramaOne; @override State createState() { return _HomeRemainingSectionsState(); } } class _HomeRemainingSectionsState extends State { NyDetailModel? _detailModel; @override void initState() { super.initState(); _getDetailsData(); } void _getDetailsData() { VideoRequest.getVideoDetail( shortPlayId: widget.dramaOne.shortPlayId ?? 0, shortPlayVideoId: widget.dramaOne.shortPlayVideoId ?? 0, ).then((value) { if (value != null) { setState(() { _detailModel = value; }); } }); } final _ActorInfo _actor = _ActorInfo( name: 'Cheng Zi', tags: 'Romantic Lead | Revenge Drama | Lead Actor', bornYear: '2002', height: '168cm', nationality: 'China', ); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(16, 3, 16, 16), child: Stack( children: [ Container( margin: const EdgeInsets.only(top: 12), child: _ActorSection(actor: _actor, detailModel: _detailModel), ), Positioned( left: -10, top: 0, child: HelpImage.asset(ConstImage.homeEyes, width: 72), ), ], ), ); } } class _ActorSection extends StatelessWidget { const _ActorSection({required this.actor, required this.detailModel}); final _ActorInfo actor; final NyDetailModel? detailModel; @override Widget build(BuildContext context) { return CutCornerClipper( cutCorner: CutCorner.topLeft, cutSize: 66, child: Stack( children: [ Padding( padding: const EdgeInsets.fromLTRB(10, 16, 10, 10), child: Column( children: [ // const SizedBox(height: 10), Align( alignment: Alignment.topRight, child: OutlinedTitle( 'Read This Series Of Short Plays', fontSize: 17, ), ), const SizedBox(height: 26), Container( width: 100, height: 100, decoration: const BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFFFA6D9), Color(0xFFFFE6F5)], ), ), child: HelpImage.asset(ConstImage.homeHead, width: 100), ), const SizedBox(height: 10), NovyText( actor.name, color: Colors.black, fontSize: 18, fontFamily: ConstFont.montBlack, ), const SizedBox(height: 10), NovyText( actor.tags, color: const Color(0xFF888888), fontSize: 12, fontFamily: ConstFont.montRegular, ), const SizedBox(height: 12), _ActorStats(actor: actor), const SizedBox(height: 8), _SectionHeader(title: 'Series'), const SizedBox(height: 8), _SeriesGrid(detailModel: detailModel), ], ), ), ], ), ); } } class _SeriesGrid extends StatefulWidget { const _SeriesGrid({required this.detailModel}); final NyDetailModel? detailModel; @override State<_SeriesGrid> createState() => _SeriesGridState(); } class _SeriesGridState extends State<_SeriesGrid> { final PageController _pageController = PageController(); int _currentPage = 0; List get _items => widget.detailModel?.episodeList ?? []; String get _coverUrl { final shortPlayImage = widget.detailModel?.shortPlayInfo?.imageUrl ?? ''; if (shortPlayImage.isNotEmpty) return shortPlayImage; return widget.detailModel?.videoInfo?.imageUrl ?? ''; } @override void didUpdateWidget(covariant _SeriesGrid oldWidget) { super.didUpdateWidget(oldWidget); final pageCount = episodeSeriesPageCount(_items.length); if (_currentPage >= pageCount) { _currentPage = pageCount - 1; if (_pageController.hasClients) { _pageController.jumpToPage(_currentPage); } } } @override void dispose() { _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final pageCount = episodeSeriesPageCount(_items.length); return LayoutBuilder( builder: (context, constraints) { final gridHeight = 200.0 * 2; return Column( children: [ SizedBox( height: gridHeight, child: PageView.builder( controller: _pageController, itemCount: pageCount, onPageChanged: (index) { setState(() { _currentPage = index; }); }, itemBuilder: (context, pageIndex) { final pageItems = episodeSeriesPageItems(_items, pageIndex); return GridView.builder( padding: EdgeInsets.zero, itemCount: pageItems.length, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 9, mainAxisSpacing: 10, childAspectRatio: 104 / 190, ), itemBuilder: (context, index) { final item = pageItems[index]; return _SeriesCard( item: item, coverUrl: _coverUrl, onTap: () => _openDetail(item), ); }, ); }, ), ), const SizedBox(height: 12), _PagerDots(count: pageCount, currentIndex: _currentPage), ], ); }, ); } void _openDetail(NyEpisodeModel item) { final detail = widget.detailModel; if (detail == null) return; final videos = buildEpisodeDramaList(detail); if (videos.isEmpty) return; final selected = videos.firstWhere( (video) => video.shortPlayVideoId == item.shortPlayVideoId, orElse: () => videos.first, ); AppNav.to(VideoDetailPage(drama: selected)); } } class _SeriesCard extends StatelessWidget { const _SeriesCard({ required this.item, required this.coverUrl, required this.onTap, }); final NyEpisodeModel item; final String coverUrl; final VoidCallback onTap; @override Widget build(BuildContext context) { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: onTap, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: 0.68, child: HelpImage.network(coverUrl, fit: BoxFit.cover), ), const SizedBox(height: 7), NovyText( 'Episode ${item.episode}', color: Colors.black, fontSize: 14, lineHeight: 1.05, fontFamily: ConstFont.montMedium, ), ], ), ); } } class _ActorStats extends StatelessWidget { const _ActorStats({required this.actor}); final _ActorInfo actor; @override Widget build(BuildContext context) { final List> stats = [ [actor.bornYear, 'Born Year'], [actor.height, 'Height'], [actor.nationality, 'Nationality'], ]; return Container( height: 68, decoration: BoxDecoration( color: const Color(0xFFF4F4F4), border: Border.all(color: const Color(0xFFD8D8D8)), ), child: Row( children: List.generate(stats.length, (index) { return Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ NovyText( stats[index][0], color: Colors.black, fontSize: 16, fontFamily: ConstFont.montMedium, ), const SizedBox(height: 7), NovyText( stats[index][1], color: const Color(0xFF777777), fontSize: 12, fontFamily: ConstFont.montRegular, ), ], ), ); }), ), ); } } class _SectionHeader extends StatelessWidget { const _SectionHeader({required this.title}); final String title; @override Widget build(BuildContext context) { return Align( alignment: Alignment.centerLeft, child: NovyText( title, color: Colors.black, fontSize: 19, fontFamily: ConstFont.montBlack, ), ); } } class OutlinedTitle extends StatelessWidget { const OutlinedTitle(this.text, {super.key, required this.fontSize}); final String text; final double fontSize; @override Widget build(BuildContext context) { return Stack( children: [ Text( text, maxLines: 2, style: TextStyle( fontSize: fontSize, height: 1.05, fontFamily: ConstFont.montBlack, foreground: Paint() ..style = PaintingStyle.stroke ..strokeWidth = 3.5 ..color = Colors.black, ), ), Text( text, maxLines: 2, style: TextStyle( color: Colors.white, fontSize: fontSize, height: 1.05, fontFamily: ConstFont.montBlack, ), ), ], ); } } class _PagerDots extends StatelessWidget { const _PagerDots({required this.count, required this.currentIndex}); final int count; final int currentIndex; @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(count, (index) { final active = index == currentIndex; return Container( width: active ? 26 : 7, height: 7, margin: const EdgeInsets.symmetric(horizontal: 3), color: active ? ConstColor.btGreen : const Color(0xFFD9D9D9), ); }), ); } } class _ActorInfo { const _ActorInfo({ required this.name, required this.tags, required this.bornYear, required this.height, required this.nationality, }); final String name; final String tags; final String bornYear; final String height; final String nationality; } int episodeSeriesPageCount(int itemCount) { if (itemCount <= 0) return 1; return (itemCount / episodeSeriesPageSize).ceil(); } List episodeSeriesPageItems( List items, int pageIndex, ) { final start = pageIndex * episodeSeriesPageSize; if (start >= items.length) return []; final end = (start + episodeSeriesPageSize).clamp(0, items.length); return items.sublist(start, end); }