summaryrefslogtreecommitdiff
path: root/lib/screens/popular_detail_screen.dart
diff options
context:
space:
mode:
Diffstat (limited to 'lib/screens/popular_detail_screen.dart')
-rw-r--r--lib/screens/popular_detail_screen.dart385
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 ?? ""}»'),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ )
+ ],
+ ),
+ );
+ }
+}