diff options
Diffstat (limited to 'lib/screens/popular_detail_screen.dart')
-rw-r--r-- | lib/screens/popular_detail_screen.dart | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/lib/screens/popular_detail_screen.dart b/lib/screens/popular_detail_screen.dart new file mode 100644 index 0000000..16f7811 --- /dev/null +++ b/lib/screens/popular_detail_screen.dart @@ -0,0 +1,385 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:infinite_carousel/infinite_carousel.dart'; +import 'package:pmsna1/database/helper.dart'; +import 'package:pmsna1/models/popular_detail.dart'; +import 'package:pmsna1/models/popular_video.dart'; +import 'package:pmsna1/network/popular_api.dart'; +import 'package:pmsna1/widgets/loading_modal_widget.dart'; +import 'package:youtube/youtube_thumbnail.dart'; +import 'package:youtube_player_iframe/youtube_player_iframe.dart'; + +import '../models/popular.dart'; +import '../models/popular_credit.dart'; + +class PopularDetailScreen extends StatefulWidget { + const PopularDetailScreen({super.key}); + + @override + State<PopularDetailScreen> createState() => _PopularDetailScreenState(); +} + +class _PopularDetailScreenState extends State<PopularDetailScreen> { + late Popular popular; + late PopularApi api; + late DatabaseHelper _db; + + @override + void initState() { + super.initState(); + api = PopularApi(); + _db = DatabaseHelper(); + } + + @override + Widget build(BuildContext context) { + popular = ModalRoute.of(context)?.settings.arguments as Popular; + + ImageFiltered backdrop = ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 9.0, sigmaY: 9.0), + child: Image.network( + 'https://image.tmdb.org/t/p/w500/${popular.backdropPath!}', + fit: BoxFit.cover, + alignment: Alignment.center, + ), + ); + + return Scaffold( + body: NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) => <Widget>[ + SliverAppBar( + expandedHeight: 200.0, + pinned: true, + actions: [ + FutureBuilder( + future: _db.hasFavorite(popular.id!), + builder: (context, snapshot) { + if (snapshot.hasData) { + return snapshot.data == true + ? IconButton( + icon: const Icon(Icons.favorite), + onPressed: () { + _db.unfavoritePopular(popular).whenComplete(() { + setState(() {}); + }); + }, + ) + : IconButton( + icon: const Icon(Icons.favorite_outline), + onPressed: () { + _db.favoritePopular(popular).whenComplete(() { + setState(() {}); + }); + }, + ); + } else if (snapshot.hasError) { + return const SizedBox.shrink(); + } else { + return const CircularProgressIndicator(value: null); + } + }, + ) + ], + flexibleSpace: FlexibleSpaceBar( + title: Text(popular.title!), + background: backdrop, + ), + ) + ], + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + Synopsis(popular: popular), + FutureBuilder( + future: api.getPopularDetail(popular.id!), + builder: (context, snapshot) { + if (snapshot.hasData) { + PopularDetail detail = snapshot.data!; + return Column( + children: [ + Rating(detail.voteAverage), + Trailers(detail.videos), + Casting(detail.credits), + ], + ); + } else if (snapshot.hasError) { + print(snapshot.error); + return const Center(child: Text('Ocurrió un error')); + } else { + return const LoadingModal(); + } + }, + ) + ], + ), + ), + ), + ), + ); + } +} + +class Synopsis extends StatelessWidget { + const Synopsis({ + super.key, + required this.popular, + }); + + final Popular popular; + + @override + Widget build(BuildContext context) { + return Card( + clipBehavior: Clip.antiAliasWithSaveLayer, + semanticContainer: true, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Hero( + tag: popular.posterPath!, + child: Card( + clipBehavior: Clip.antiAliasWithSaveLayer, + semanticContainer: true, + child: FadeInImage( + height: 160.0, + placeholder: const AssetImage('assets/loading.gif'), + image: NetworkImage( + 'https://image.tmdb.org/t/p/w500/${popular.posterPath!}', + ), + ), + ), + ), + const SizedBox(width: 16.0), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Sinopsis', + style: Theme.of(context).typography.englishLike.titleLarge, + ), + const SizedBox(height: 10), + Text(popular.overview ?? ""), + ], + ), + ), + // Expanded(child: Text(popular.overview ?? "")), + ], + ), + ), + ); + } +} + +class Rating extends StatelessWidget { + final double? voteAverage; + const Rating(this.voteAverage, {super.key}); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Rating', + style: Theme.of(context).typography.englishLike.headlineLarge, + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RatingBar.builder( + initialRating: (voteAverage ?? 0.0) / 2, + minRating: 0.0, + direction: Axis.horizontal, + allowHalfRating: true, + itemCount: 5, + itemPadding: const EdgeInsets.symmetric(horizontal: 4.0), + itemBuilder: (context, _) => Icon( + Icons.star, + color: Theme.of(context).colorScheme.primary, + ), + ignoreGestures: true, + onRatingUpdate: (rating) {}, + ), + ], + ), + ], + ), + ), + ); + } +} + +class Trailers extends StatefulWidget { + final List<PopularVideo> videos; + const Trailers(this.videos, {super.key}); + + @override + State<Trailers> createState() => _TrailersState(); +} + +class _TrailersState extends State<Trailers> { + List<PopularVideo> get filteredVideos => widget.videos + .where((v) => + v.site == 'YouTube' && v.official == true && v.type == 'Trailer') + .toList(); + + final _controller = YoutubePlayerController( + params: const YoutubePlayerParams( + mute: false, + showControls: true, + showFullscreenButton: false, + ), + ); + + bool isLoaded = false; + + @override + Widget build(BuildContext context) { + return Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), + child: Text( + 'Trailers', + style: Theme.of(context).typography.englishLike.headlineLarge, + ), + ), + isLoaded + ? Padding( + padding: const EdgeInsets.only(top: 16), + child: YoutubePlayer( + controller: _controller, + aspectRatio: 16 / 9, + ), + ) + : const SizedBox.shrink(), + ListView.builder( + padding: const EdgeInsets.all(16), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: filteredVideos.length, + itemBuilder: (context, index) { + PopularVideo video = widget.videos[index]; + return Card( + clipBehavior: Clip.antiAliasWithSaveLayer, + semanticContainer: true, + child: InkWell( + onTap: () { + _controller.loadVideoById(videoId: video.key!); + setState(() { + isLoaded = true; + }); + }, + child: Row( + children: [ + Image.network( + YoutubeThumbnail(youtubeId: video.key!).standard(), + height: 100.0, + errorBuilder: (context, error, stackTrace) => Container( + color: Theme.of(context).colorScheme.primary, + height: 100.0, + width: (4 * 100) / 3, + child: const Icon(Icons.movie), + ), + ), + const SizedBox(width: 16.0), + Expanded( + child: Text( + video.name ?? "", + style: Theme.of(context) + .typography + .englishLike + .titleMedium, + ), + ), + ], + ), + ), + ); + }, + ) + ], + ), + ); + } +} + +class Casting extends StatelessWidget { + final List<PopularCredit> credits; + const Casting(this.credits, {super.key}); + + @override + Widget build(BuildContext context) { + return Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), + child: Text( + 'Reparto', + style: Theme.of(context).typography.englishLike.headlineLarge, + ), + ), + SizedBox( + height: 350.0, + child: InfiniteCarousel.builder( + axisDirection: Axis.horizontal, + velocityFactor: 0.5, + itemCount: credits.length, + itemExtent: 160, + itemBuilder: (context, itemIndex, realIndex) { + PopularCredit credit = credits[itemIndex]; + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + radius: 50.0, + backgroundImage: credit.profilePath != null + ? NetworkImage( + 'https://image.tmdb.org/t/p/w500/${credit.profilePath!}', + ) + : null, + child: credit.profilePath == null + ? const Icon(Icons.person) + : null, + ), + const SizedBox(height: 16.0), + Text( + credit.name ?? "", + style: Theme.of(context) + .typography + .englishLike + .titleMedium, + ), + Text('como «${credit.character ?? ""}»'), + ], + ), + ), + ), + ); + }, + ), + ) + ], + ), + ); + } +} |