From 0ea9aef7be00c0b3a62765cf0b3e9a9301c02627 Mon Sep 17 00:00:00 2001 From: daniel-michel <65034538+daniel-michel@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:30:32 +0100 Subject: [PATCH] feature: add page for displaying a movie fix: only load and show upcoming movies with at least month date precision --- lib/api/wikidata_movie_api.dart | 6 +- lib/main.dart | 39 ++++++++++-- lib/model/movie.dart | 25 +++++++- lib/view/movie_item.dart | 11 ++++ lib/view/movie_page.dart | 108 ++++++++++++++++++++++++++++++++ 5 files changed, 179 insertions(+), 10 deletions(-) create mode 100644 lib/view/movie_page.dart diff --git a/lib/api/wikidata_movie_api.dart b/lib/api/wikidata_movie_api.dart index 0115434..4d23ac6 100644 --- a/lib/api/wikidata_movie_api.dart +++ b/lib/api/wikidata_movie_api.dart @@ -138,7 +138,7 @@ class WikidataMovieData extends MovieData { String country = _getCachedLabelForEntity(selectInJson(dateClaim, "qualifiers.${WikidataProperties.placeOfPublication}.*.datavalue.value.id") .firstOrNull ?? - "no country"); + "unknown location"); return DateWithPrecisionAndCountry(DateTime.parse(value["time"]), _precisionFromWikidata(value["precision"]), country); }).toList(); @@ -153,6 +153,7 @@ class WikidataMovieData extends MovieData { WikidataMovieData(title, releaseDates[0], entityId); movie.setDetails( titles: titles, + releaseDates: releaseDates, genres: genres, ); return movie; @@ -168,7 +169,8 @@ SELECT WHERE { ?movie wdt:P31 wd:Q11424; # Q11424 is the item for "film" wdt:P577 ?releaseDate. # P577 is the "publication date" property - FILTER (xsd:date(?releaseDate) >= xsd:date("$date"^^xsd:dateTime)) + ?movie p:P577/psv:P577 [wikibase:timePrecision ?precision]. + FILTER (xsd:date(?releaseDate) >= xsd:date("$date"^^xsd:dateTime) && ?precision >= 10) } GROUP BY ?movie ORDER BY ?minReleaseDate diff --git a/lib/main.dart b/lib/main.dart index 866ea6d..5df387c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:release_schedule/api/movie_api.dart'; import 'package:release_schedule/api/wikidata_movie_api.dart'; +import 'package:release_schedule/model/movie.dart'; import 'package:release_schedule/model/movie_manager.dart'; import 'package:release_schedule/view/movie_manager_list.dart'; @@ -37,11 +38,7 @@ class HomePage extends StatelessWidget { return Scaffold( appBar: AppBar( title: const Text("Release Schedule"), - actions: [ - FilledButton( - onPressed: () => manager.removeMoviesWhere((movie) => true), - child: const Icon(Icons.delete)) - ], + actions: [HamburgerMenu(manager)], ), body: DefaultTabController( length: 2, @@ -51,7 +48,12 @@ class HomePage extends StatelessWidget { child: TabBarView( children: [ Scaffold( - body: MovieManagerList(manager), + body: MovieManagerList( + manager, + // Only show movies that have a release date with at least month precision + filter: (movie) => + movie.releaseDate.precision >= DatePrecision.month, + ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.refresh), onPressed: () => manager.loadUpcomingMovies(), @@ -71,3 +73,28 @@ class HomePage extends StatelessWidget { ); } } + +class HamburgerMenu extends StatelessWidget { + final MovieManager manager; + const HamburgerMenu(this.manager, {super.key}); + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + icon: const Icon(Icons.menu), + itemBuilder: (context) { + return [ + PopupMenuItem( + child: const Text("Remove all not bookmarked"), + onTap: () => + manager.removeMoviesWhere((movie) => !movie.bookmarked), + ), + PopupMenuItem( + child: const Text("Remove all"), + onTap: () => manager.removeMoviesWhere((movie) => true), + ), + ]; + }, + ); + } +} diff --git a/lib/model/movie.dart b/lib/model/movie.dart index f162247..d42e640 100644 --- a/lib/model/movie.dart +++ b/lib/model/movie.dart @@ -146,6 +146,24 @@ class MovieData extends ChangeNotifier { enum DatePrecision { decade, year, month, day, hour, minute } +extension DatePrecisionComparison on DatePrecision { + bool operator <(DatePrecision other) { + return index < other.index; + } + + bool operator <=(DatePrecision other) { + return index <= other.index; + } + + bool operator >(DatePrecision other) { + return index > other.index; + } + + bool operator >=(DatePrecision other) { + return index >= other.index; + } +} + typedef TitleInLanguage = ({String title, String language}); class DateWithPrecisionAndCountry { @@ -167,7 +185,11 @@ class DateWithPrecisionAndCountry { @override String toString() { - String dateString = switch (precision) { + return "${toDateString()} ($country)"; + } + + String toDateString() { + return switch (precision) { DatePrecision.decade => "${DateFormat("yyyy").format(date).substring(0, 3)}0s", DatePrecision.year => date.year.toString(), @@ -176,7 +198,6 @@ class DateWithPrecisionAndCountry { DatePrecision.hour => DateFormat("MMMM d, yyyy, HH").format(date), DatePrecision.minute => DateFormat("MMMM d, yyyy, HH:mm").format(date) }; - return "$dateString ($country)"; } } diff --git a/lib/view/movie_item.dart b/lib/view/movie_item.dart index 35c68c9..df0080e 100644 --- a/lib/view/movie_item.dart +++ b/lib/view/movie_item.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:release_schedule/model/date_format.dart'; import 'package:release_schedule/model/movie.dart'; +import 'package:release_schedule/view/movie_page.dart'; class MovieItem extends StatelessWidget { final MovieData movie; @@ -21,6 +22,16 @@ class MovieItem extends StatelessWidget { : Icons.bookmark_border), onPressed: () => movie.setDetails(bookmarked: !movie.bookmarked), ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return MoviePage(movie); + }, + ), + ); + }, ); }, ); diff --git a/lib/view/movie_page.dart b/lib/view/movie_page.dart new file mode 100644 index 0000000..206f525 --- /dev/null +++ b/lib/view/movie_page.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:release_schedule/model/movie.dart'; + +class Heading extends StatelessWidget { + final String text; + + const Heading(this.text, {super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 20, bottom: 10), + child: Text( + text, + style: Theme.of(context).textTheme.headlineSmall, + ), + ); + } +} + +class MoviePage extends StatelessWidget { + final MovieData movie; + + const MoviePage(this.movie, {super.key}); + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: movie, + builder: (context, child) { + return Scaffold( + appBar: AppBar( + title: Text(movie.title), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: 10, + runSpacing: 10, + children: movie.genres + ?.map((genre) => Chip(label: Text(genre))) + .toList() ?? + [], + ), + const SizedBox(height: 20), + const Heading("Titles"), + Table( + border: TableBorder.symmetric( + inside: BorderSide( + color: Theme.of(context).dividerColor, + ), + ), + children: movie.titles?.map((title) { + return TableRow( + children: [ + TableCell( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(title.language), + )), + TableCell( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(title.title), + )) + ], + ); + }).toList() ?? + [], + ), + const Heading("Release Dates"), + Table( + border: TableBorder.symmetric( + inside: BorderSide( + color: Theme.of(context).dividerColor, + ), + ), + children: movie.releaseDates?.map((releaseDate) { + return TableRow( + children: [ + TableCell( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(releaseDate.country), + )), + TableCell( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(releaseDate.toDateString()), + )) + ], + ); + }).toList() ?? + [], + ), + ], + ), + ), + ), + ); + }, + ); + } +}