feature: add page for displaying a movie
fix: only load and show upcoming movies with at least month date precisionmain
parent
618f5d135b
commit
0ea9aef7be
|
@ -138,7 +138,7 @@ class WikidataMovieData extends MovieData {
|
||||||
String country = _getCachedLabelForEntity(selectInJson<String>(dateClaim,
|
String country = _getCachedLabelForEntity(selectInJson<String>(dateClaim,
|
||||||
"qualifiers.${WikidataProperties.placeOfPublication}.*.datavalue.value.id")
|
"qualifiers.${WikidataProperties.placeOfPublication}.*.datavalue.value.id")
|
||||||
.firstOrNull ??
|
.firstOrNull ??
|
||||||
"no country");
|
"unknown location");
|
||||||
return DateWithPrecisionAndCountry(DateTime.parse(value["time"]),
|
return DateWithPrecisionAndCountry(DateTime.parse(value["time"]),
|
||||||
_precisionFromWikidata(value["precision"]), country);
|
_precisionFromWikidata(value["precision"]), country);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
@ -153,6 +153,7 @@ class WikidataMovieData extends MovieData {
|
||||||
WikidataMovieData(title, releaseDates[0], entityId);
|
WikidataMovieData(title, releaseDates[0], entityId);
|
||||||
movie.setDetails(
|
movie.setDetails(
|
||||||
titles: titles,
|
titles: titles,
|
||||||
|
releaseDates: releaseDates,
|
||||||
genres: genres,
|
genres: genres,
|
||||||
);
|
);
|
||||||
return movie;
|
return movie;
|
||||||
|
@ -168,7 +169,8 @@ SELECT
|
||||||
WHERE {
|
WHERE {
|
||||||
?movie wdt:P31 wd:Q11424; # Q11424 is the item for "film"
|
?movie wdt:P31 wd:Q11424; # Q11424 is the item for "film"
|
||||||
wdt:P577 ?releaseDate. # P577 is the "publication date" property
|
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
|
GROUP BY ?movie
|
||||||
ORDER BY ?minReleaseDate
|
ORDER BY ?minReleaseDate
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:release_schedule/api/movie_api.dart';
|
import 'package:release_schedule/api/movie_api.dart';
|
||||||
import 'package:release_schedule/api/wikidata_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/model/movie_manager.dart';
|
||||||
import 'package:release_schedule/view/movie_manager_list.dart';
|
import 'package:release_schedule/view/movie_manager_list.dart';
|
||||||
|
|
||||||
|
@ -37,11 +38,7 @@ class HomePage extends StatelessWidget {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text("Release Schedule"),
|
title: const Text("Release Schedule"),
|
||||||
actions: [
|
actions: [HamburgerMenu(manager)],
|
||||||
FilledButton(
|
|
||||||
onPressed: () => manager.removeMoviesWhere((movie) => true),
|
|
||||||
child: const Icon(Icons.delete))
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: DefaultTabController(
|
body: DefaultTabController(
|
||||||
length: 2,
|
length: 2,
|
||||||
|
@ -51,7 +48,12 @@ class HomePage extends StatelessWidget {
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
children: [
|
children: [
|
||||||
Scaffold(
|
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(
|
floatingActionButton: FloatingActionButton(
|
||||||
child: const Icon(Icons.refresh),
|
child: const Icon(Icons.refresh),
|
||||||
onPressed: () => manager.loadUpcomingMovies(),
|
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),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -146,6 +146,24 @@ class MovieData extends ChangeNotifier {
|
||||||
|
|
||||||
enum DatePrecision { decade, year, month, day, hour, minute }
|
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});
|
typedef TitleInLanguage = ({String title, String language});
|
||||||
|
|
||||||
class DateWithPrecisionAndCountry {
|
class DateWithPrecisionAndCountry {
|
||||||
|
@ -167,7 +185,11 @@ class DateWithPrecisionAndCountry {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
String dateString = switch (precision) {
|
return "${toDateString()} ($country)";
|
||||||
|
}
|
||||||
|
|
||||||
|
String toDateString() {
|
||||||
|
return switch (precision) {
|
||||||
DatePrecision.decade =>
|
DatePrecision.decade =>
|
||||||
"${DateFormat("yyyy").format(date).substring(0, 3)}0s",
|
"${DateFormat("yyyy").format(date).substring(0, 3)}0s",
|
||||||
DatePrecision.year => date.year.toString(),
|
DatePrecision.year => date.year.toString(),
|
||||||
|
@ -176,7 +198,6 @@ class DateWithPrecisionAndCountry {
|
||||||
DatePrecision.hour => DateFormat("MMMM d, yyyy, HH").format(date),
|
DatePrecision.hour => DateFormat("MMMM d, yyyy, HH").format(date),
|
||||||
DatePrecision.minute => DateFormat("MMMM d, yyyy, HH:mm").format(date)
|
DatePrecision.minute => DateFormat("MMMM d, yyyy, HH:mm").format(date)
|
||||||
};
|
};
|
||||||
return "$dateString ($country)";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:release_schedule/model/date_format.dart';
|
import 'package:release_schedule/model/date_format.dart';
|
||||||
import 'package:release_schedule/model/movie.dart';
|
import 'package:release_schedule/model/movie.dart';
|
||||||
|
import 'package:release_schedule/view/movie_page.dart';
|
||||||
|
|
||||||
class MovieItem extends StatelessWidget {
|
class MovieItem extends StatelessWidget {
|
||||||
final MovieData movie;
|
final MovieData movie;
|
||||||
|
@ -21,6 +22,16 @@ class MovieItem extends StatelessWidget {
|
||||||
: Icons.bookmark_border),
|
: Icons.bookmark_border),
|
||||||
onPressed: () => movie.setDetails(bookmarked: !movie.bookmarked),
|
onPressed: () => movie.setDetails(bookmarked: !movie.bookmarked),
|
||||||
),
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return MoviePage(movie);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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() ??
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue