fix: improve used dates and add genres
parent
fa0e0bbbcb
commit
ce90c7c1c9
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
@ -6,11 +7,126 @@ import 'package:release_schedule/api/api_manager.dart';
|
||||||
import 'package:release_schedule/api/movie_api.dart';
|
import 'package:release_schedule/api/movie_api.dart';
|
||||||
import 'package:release_schedule/model/movie.dart';
|
import 'package:release_schedule/model/movie.dart';
|
||||||
|
|
||||||
|
class WikidataProperties {
|
||||||
|
static const String instanceOf = "P31";
|
||||||
|
static const String publicationDate = "P577";
|
||||||
|
static const String title = "P1476";
|
||||||
|
static const String partOfTheSeries = "P179";
|
||||||
|
static const String basedOn = "P144";
|
||||||
|
static const String derivativeWork = "P4969";
|
||||||
|
static const String genre = "P136";
|
||||||
|
static const String countryOfOrigin = "P496";
|
||||||
|
static const String director = "P57";
|
||||||
|
static const String castMember = "P161";
|
||||||
|
static const String distributedBy = "P750";
|
||||||
|
static const String afterAWorkBy = "P1877";
|
||||||
|
static const String duration = "P2047";
|
||||||
|
static const String reviewScore = "P444";
|
||||||
|
static const String fskFilmRating = "P1981";
|
||||||
|
static const String placeOfPublication = "P291";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Select values in nested List and Map structures using a path that may contain wildcards.
|
||||||
|
/// The maps must always use String keys.
|
||||||
|
/// The path is a dot-separated list of keys and indices.
|
||||||
|
/// The wildcard "*" can be used to select all elements of a list or map.
|
||||||
|
/// The wildcard "**" can be used to select all elements of a list or map and all elements of nested lists and maps.
|
||||||
|
Iterable<({T value, String path})> _selectInJsonWithPath<T>(
|
||||||
|
dynamic json, String path) sync* {
|
||||||
|
if (path.isEmpty) {
|
||||||
|
if (json is T) {
|
||||||
|
yield (value: json, path: "");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> pathParts = path.split(".");
|
||||||
|
String first = pathParts.removeAt(0);
|
||||||
|
String rest = pathParts.join(".");
|
||||||
|
addFirstToPath(({T value, String path}) element) => (
|
||||||
|
value: element.value,
|
||||||
|
path: element.path.isEmpty ? first : "$first.${element.path}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (first == "*" || first == "**") {
|
||||||
|
String continueWithPath = first == "*" ? rest : path;
|
||||||
|
if (json is List) {
|
||||||
|
yield* json
|
||||||
|
.expand((e) => _selectInJsonWithPath<T>(e, continueWithPath))
|
||||||
|
.map(addFirstToPath);
|
||||||
|
} else if (json is Map) {
|
||||||
|
for (String key in json.keys) {
|
||||||
|
yield* _selectInJsonWithPath<T>(json[key], continueWithPath)
|
||||||
|
.map(addFirstToPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (json is List) {
|
||||||
|
try {
|
||||||
|
int index = int.parse(first);
|
||||||
|
yield* _selectInJsonWithPath<T>(json[index], rest);
|
||||||
|
} catch (e) {
|
||||||
|
// The first part of the path is not an index or out of bounds -> ignore
|
||||||
|
}
|
||||||
|
} else if (json is Map) {
|
||||||
|
dynamic value = json[first];
|
||||||
|
if (value != null) {
|
||||||
|
yield* _selectInJsonWithPath<T>(value, pathParts.join("."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<T> _selectInJson<T>(dynamic json, String path) {
|
||||||
|
return _selectInJsonWithPath<T>(json, path).map((e) => e.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Iterable<dynamic>> _selectMultipleInJson(
|
||||||
|
dynamic json, Map<String, String> selector) {
|
||||||
|
Map<String, Iterable<dynamic>> result = {};
|
||||||
|
for (String key in selector.keys) {
|
||||||
|
result[key] = _selectInJsonWithPath(json, selector[key]!);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiManager _wikidataApi = ApiManager("https://www.wikidata.org/w/api.php");
|
||||||
|
Map<String, String> _labelCache = {};
|
||||||
|
Future<Map<String, String>> _getLabelsForEntities(
|
||||||
|
List<String> entityIds) async {
|
||||||
|
const batchSize = 50;
|
||||||
|
Map<String, String> labels = {};
|
||||||
|
for (int i = entityIds.length - 1; i >= 0; i--) {
|
||||||
|
if (_labelCache.containsKey(entityIds[i])) {
|
||||||
|
labels[entityIds[i]] = _labelCache[entityIds[i]]!;
|
||||||
|
entityIds.removeAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < (entityIds.length / batchSize).ceil(); i++) {
|
||||||
|
final start = i * batchSize;
|
||||||
|
final end = min((i + 1) * batchSize, entityIds.length);
|
||||||
|
Response response = await _wikidataApi.get(
|
||||||
|
"?action=wbgetentities&format=json&props=labels&ids=${entityIds.sublist(start, end).join("|")}");
|
||||||
|
Map<String, dynamic> result = jsonDecode(response.body);
|
||||||
|
Map<String, dynamic> batchEntities = result["entities"];
|
||||||
|
for (String entityId in batchEntities.keys) {
|
||||||
|
Map<String, dynamic> labels = batchEntities[entityId]["labels"];
|
||||||
|
String label = labels.containsKey("en")
|
||||||
|
? labels["en"]["value"]
|
||||||
|
: labels[labels.keys.first]["value"];
|
||||||
|
labels[entityId] = label;
|
||||||
|
_labelCache[entityId] = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getCachedLabelForEntity(String entityId) {
|
||||||
|
return _labelCache[entityId] ?? entityId;
|
||||||
|
}
|
||||||
|
|
||||||
class WikidataMovieData extends MovieData {
|
class WikidataMovieData extends MovieData {
|
||||||
int entityId;
|
String entityId;
|
||||||
WikidataMovieData(String title, DateTime releaseDate,
|
WikidataMovieData(
|
||||||
DatePrecision releaseDatePrecision, this.entityId)
|
String title, DateWithPrecisionAndCountry releaseDate, this.entityId)
|
||||||
: super(title, releaseDate, releaseDatePrecision);
|
: super(title, releaseDate);
|
||||||
|
|
||||||
WikidataMovieData.fromEncodable(Map encodable)
|
WikidataMovieData.fromEncodable(Map encodable)
|
||||||
: entityId = encodable["entityId"],
|
: entityId = encodable["entityId"],
|
||||||
|
@ -25,10 +141,49 @@ class WikidataMovieData extends MovieData {
|
||||||
Map toJsonEncodable() {
|
Map toJsonEncodable() {
|
||||||
return super.toJsonEncodable()..addAll({"entityId": entityId});
|
return super.toJsonEncodable()..addAll({"entityId": entityId});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static WikidataMovieData fromWikidataEntity(
|
||||||
|
String entityId, Map<String, dynamic> entity) {
|
||||||
|
String title =
|
||||||
|
_selectInJson<String>(entity, "labels.en.value").firstOrNull ??
|
||||||
|
_selectInJson<String>(entity, "labels.*.value").first;
|
||||||
|
Map<String, dynamic> claims = entity["claims"];
|
||||||
|
List<TitleInLanguage>? titles = _selectInJson(
|
||||||
|
claims, "${WikidataProperties.title}.*.mainsnak.datavalue.value")
|
||||||
|
.map((value) => (
|
||||||
|
title: value["text"],
|
||||||
|
language: value["language"],
|
||||||
|
) as TitleInLanguage)
|
||||||
|
.toList();
|
||||||
|
List<DateWithPrecisionAndCountry> releaseDates =
|
||||||
|
_selectInJson(claims, "${WikidataProperties.publicationDate}.*")
|
||||||
|
.map<DateWithPrecisionAndCountry>((dateClaim) {
|
||||||
|
var value = _selectInJson(dateClaim, "mainsnak.datavalue.value").first;
|
||||||
|
String country = _getCachedLabelForEntity(_selectInJson<String>(dateClaim,
|
||||||
|
"qualifiers.${WikidataProperties.placeOfPublication}.*.datavalue.value.id")
|
||||||
|
.firstOrNull ??
|
||||||
|
"no country");
|
||||||
|
return DateWithPrecisionAndCountry(DateTime.parse(value["time"]),
|
||||||
|
_precisionFromWikidata(value["precision"]), country);
|
||||||
|
}).toList();
|
||||||
|
// Sort release dates with higher precision to the beginning
|
||||||
|
releaseDates
|
||||||
|
.sort((a, b) => -a.precision.index.compareTo(b.precision.index));
|
||||||
|
List<String>? genres = _selectInJson<String>(
|
||||||
|
claims, "${WikidataProperties.genre}.*.mainsnak.datavalue.value.id")
|
||||||
|
.map(_getCachedLabelForEntity)
|
||||||
|
.toList();
|
||||||
|
WikidataMovieData movie =
|
||||||
|
WikidataMovieData(title, releaseDates[0], entityId);
|
||||||
|
movie.setDetails(
|
||||||
|
titles: titles,
|
||||||
|
genres: genres,
|
||||||
|
);
|
||||||
|
return movie;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WikidataMovieApi implements MovieApi {
|
class WikidataMovieApi implements MovieApi {
|
||||||
ApiManager searchApi = ApiManager("https://www.wikidata.org/w/api.php");
|
|
||||||
ApiManager queryApi =
|
ApiManager queryApi =
|
||||||
ApiManager("https://query.wikidata.org/sparql?format=json");
|
ApiManager("https://query.wikidata.org/sparql?format=json");
|
||||||
|
|
||||||
|
@ -49,17 +204,44 @@ class WikidataMovieApi implements MovieApi {
|
||||||
}
|
}
|
||||||
Map<String, dynamic> result = jsonDecode(response.body);
|
Map<String, dynamic> result = jsonDecode(response.body);
|
||||||
List<dynamic> entries = result["results"]["bindings"];
|
List<dynamic> entries = result["results"]["bindings"];
|
||||||
List<WikidataMovieData> movies = [];
|
List<String> ids = entries
|
||||||
for (Map<String, dynamic> entry in entries) {
|
.map((entry) =>
|
||||||
String identifier =
|
RegExp(r"Q\d+$").firstMatch(entry["movie"]["value"])![0]!)
|
||||||
RegExp(r"Q\d+$").firstMatch(entry["movie"]["value"])![0]!;
|
.toList();
|
||||||
movies.add(WikidataMovieData(
|
return _getMovieDataFromIds(ids);
|
||||||
entry["movieLabel"]["value"] as String,
|
}
|
||||||
DateTime.parse(entry["minReleaseDate"]["value"] as String),
|
|
||||||
_precisionFromWikidata(int.parse(entry["datePrecision"]["value"])),
|
Future<List<WikidataMovieData>> _getMovieDataFromIds(
|
||||||
int.parse(identifier.substring(1))));
|
List<String> movieIds) async {
|
||||||
|
// Wikidata limits the number of entities per request to 50
|
||||||
|
const batchSize = 50;
|
||||||
|
Map<String, dynamic> entities = {};
|
||||||
|
for (int i = 0; i < (movieIds.length / batchSize).ceil(); i++) {
|
||||||
|
final start = i * batchSize;
|
||||||
|
final end = min((i + 1) * batchSize, movieIds.length);
|
||||||
|
var response = await _wikidataApi.get(
|
||||||
|
"?action=wbgetentities&format=json&props=labels|claims&ids=${movieIds.sublist(start, end).join("|")}");
|
||||||
|
Map<String, dynamic> result = jsonDecode(response.body);
|
||||||
|
Map<String, dynamic> batchEntities = result["entities"];
|
||||||
|
entities.addAll(batchEntities);
|
||||||
}
|
}
|
||||||
return movies;
|
|
||||||
|
List<String> allCountryAndGenreIds = [];
|
||||||
|
// Add the country ids from the publication dates
|
||||||
|
allCountryAndGenreIds.addAll(_selectInJson<String>(entities,
|
||||||
|
"*.claims.${WikidataProperties.publicationDate}.*.qualifiers.${WikidataProperties.placeOfPublication}.*.datavalue.value.id"));
|
||||||
|
// Add the genre ids
|
||||||
|
allCountryAndGenreIds.addAll(_selectInJson<String>(entities,
|
||||||
|
"*.claims.${WikidataProperties.genre}.*.mainsnak.datavalue.value.id"));
|
||||||
|
allCountryAndGenreIds = allCountryAndGenreIds.toSet().toList();
|
||||||
|
// Prefetch all labels for countries and genres
|
||||||
|
// to reduce the number of api calls,
|
||||||
|
// they will be retrieved from the cache in fromWikidataEntity
|
||||||
|
await _getLabelsForEntities(allCountryAndGenreIds);
|
||||||
|
|
||||||
|
return movieIds
|
||||||
|
.map((id) => WikidataMovieData.fromWikidataEntity(id, entities[id]))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -95,7 +277,9 @@ LIMIT $limit""";
|
||||||
|
|
||||||
DatePrecision _precisionFromWikidata(int precision) {
|
DatePrecision _precisionFromWikidata(int precision) {
|
||||||
return switch (precision) {
|
return switch (precision) {
|
||||||
>= 11 => DatePrecision.day,
|
>= 13 => DatePrecision.minute,
|
||||||
|
12 => DatePrecision.hour,
|
||||||
|
11 => DatePrecision.day,
|
||||||
10 => DatePrecision.month,
|
10 => DatePrecision.month,
|
||||||
9 => DatePrecision.year,
|
9 => DatePrecision.year,
|
||||||
8 => DatePrecision.decade,
|
8 => DatePrecision.decade,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class Review {
|
class Review {
|
||||||
String score;
|
String score;
|
||||||
|
@ -23,37 +24,61 @@ class Review {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef ReleaseDateInCountry = (String country, DateTime date);
|
typedef TitleInLanguage = ({String title, String language});
|
||||||
typedef TitleInCountry = (String country, String title);
|
|
||||||
|
class DateWithPrecisionAndCountry {
|
||||||
|
DateTime date;
|
||||||
|
DatePrecision precision;
|
||||||
|
String country;
|
||||||
|
|
||||||
|
DateWithPrecisionAndCountry(this.date, this.precision, this.country);
|
||||||
|
|
||||||
|
DateWithPrecisionAndCountry.fromJsonEncodable(List<dynamic> json)
|
||||||
|
: date = DateTime.parse(json[0]),
|
||||||
|
precision = DatePrecision.values
|
||||||
|
.firstWhere((element) => element.name == json[1]),
|
||||||
|
country = json[2];
|
||||||
|
|
||||||
|
toJsonEncodable() {
|
||||||
|
return [date.toIso8601String(), precision.name, country];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
String dateString = switch (precision) {
|
||||||
|
DatePrecision.decade || DatePrecision.year => date.year.toString(),
|
||||||
|
DatePrecision.month => DateFormat("MMMM yyyy").format(date),
|
||||||
|
DatePrecision.day => DateFormat("MMMM d, yyyy").format(date),
|
||||||
|
DatePrecision.hour => DateFormat("MMMM d, yyyy, HH").format(date),
|
||||||
|
DatePrecision.minute => DateFormat("MMMM d, yyyy, HH:mm").format(date)
|
||||||
|
};
|
||||||
|
return "$dateString ($country)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum DatePrecision { decade, year, month, day, hour, minute }
|
enum DatePrecision { decade, year, month, day, hour, minute }
|
||||||
|
|
||||||
class MovieData extends ChangeNotifier {
|
class MovieData extends ChangeNotifier {
|
||||||
String _title;
|
String _title;
|
||||||
DateTime _releaseDate;
|
DateWithPrecisionAndCountry _releaseDate;
|
||||||
DatePrecision _releaseDatePrecision;
|
|
||||||
|
|
||||||
bool _hasDetails = false;
|
bool _hasDetails = false;
|
||||||
List<ReleaseDateInCountry>? _releaseDates;
|
List<DateWithPrecisionAndCountry>? _releaseDates;
|
||||||
List<String>? _genres;
|
List<String>? _genres;
|
||||||
List<TitleInCountry>? _titles;
|
List<TitleInLanguage>? _titles;
|
||||||
List<Review>? _reviews;
|
List<Review>? _reviews;
|
||||||
|
|
||||||
MovieData(this._title, this._releaseDate, this._releaseDatePrecision);
|
MovieData(this._title, this._releaseDate);
|
||||||
|
|
||||||
String get title {
|
String get title {
|
||||||
return _title;
|
return _title;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime get releaseDate {
|
DateWithPrecisionAndCountry get releaseDate {
|
||||||
return _releaseDate;
|
return _releaseDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
DatePrecision get releaseDatePrecision {
|
List<DateWithPrecisionAndCountry>? get releaseDates {
|
||||||
return _releaseDatePrecision;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ReleaseDateInCountry>? get releaseDates {
|
|
||||||
return _releaseDates;
|
return _releaseDates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +86,7 @@ class MovieData extends ChangeNotifier {
|
||||||
return _genres;
|
return _genres;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TitleInCountry>? get titles {
|
List<TitleInLanguage>? get titles {
|
||||||
return _titles;
|
return _titles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +102,6 @@ class MovieData extends ChangeNotifier {
|
||||||
setDetails(
|
setDetails(
|
||||||
title: movie.title,
|
title: movie.title,
|
||||||
releaseDate: movie.releaseDate,
|
releaseDate: movie.releaseDate,
|
||||||
releaseDatePrecision: movie.releaseDatePrecision,
|
|
||||||
releaseDates: movie.releaseDates,
|
releaseDates: movie.releaseDates,
|
||||||
genres: movie.genres,
|
genres: movie.genres,
|
||||||
titles: movie.titles,
|
titles: movie.titles,
|
||||||
|
@ -86,11 +110,10 @@ class MovieData extends ChangeNotifier {
|
||||||
|
|
||||||
void setDetails(
|
void setDetails(
|
||||||
{String? title,
|
{String? title,
|
||||||
DateTime? releaseDate,
|
DateWithPrecisionAndCountry? releaseDate,
|
||||||
DatePrecision? releaseDatePrecision,
|
List<DateWithPrecisionAndCountry>? releaseDates,
|
||||||
List<ReleaseDateInCountry>? releaseDates,
|
|
||||||
List<String>? genres,
|
List<String>? genres,
|
||||||
List<TitleInCountry>? titles,
|
List<TitleInLanguage>? titles,
|
||||||
List<Review>? reviews}) {
|
List<Review>? reviews}) {
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
_title = title;
|
_title = title;
|
||||||
|
@ -98,9 +121,6 @@ class MovieData extends ChangeNotifier {
|
||||||
if (releaseDate != null) {
|
if (releaseDate != null) {
|
||||||
_releaseDate = releaseDate;
|
_releaseDate = releaseDate;
|
||||||
}
|
}
|
||||||
if (releaseDatePrecision != null) {
|
|
||||||
_releaseDatePrecision = releaseDatePrecision;
|
|
||||||
}
|
|
||||||
if (releaseDates != null) {
|
if (releaseDates != null) {
|
||||||
_releaseDates = releaseDates;
|
_releaseDates = releaseDates;
|
||||||
}
|
}
|
||||||
|
@ -119,17 +139,16 @@ class MovieData extends ChangeNotifier {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "$title (${releaseDate.year}${_genres?.isNotEmpty ?? true ? "; ${_genres?.join(", ")}" : ""})";
|
return "$title (${_releaseDate.toString()}${_genres?.isNotEmpty ?? true ? "; ${_genres?.join(", ")}" : ""})";
|
||||||
}
|
}
|
||||||
|
|
||||||
Map toJsonEncodable() {
|
Map toJsonEncodable() {
|
||||||
List? releaseDatesByCountry =
|
List? releaseDatesByCountry =
|
||||||
_releaseDates?.map((e) => [e.$1, e.$2.toIso8601String()]).toList();
|
_releaseDates?.map((e) => e.toJsonEncodable()).toList();
|
||||||
List? titlesByCountry = _titles?.map((e) => [e.$1, e.$2]).toList();
|
List? titlesByCountry = _titles?.map((e) => [e.title, e.language]).toList();
|
||||||
return {
|
return {
|
||||||
"title": title,
|
"title": title,
|
||||||
"releaseDate": releaseDate.toIso8601String(),
|
"releaseDate": _releaseDate.toJsonEncodable(),
|
||||||
"releaseDatePrecision": _releaseDatePrecision.name,
|
|
||||||
"releaseDates": releaseDatesByCountry,
|
"releaseDates": releaseDatesByCountry,
|
||||||
"genres": genres,
|
"genres": genres,
|
||||||
"titles": titlesByCountry,
|
"titles": titlesByCountry,
|
||||||
|
@ -143,15 +162,16 @@ class MovieData extends ChangeNotifier {
|
||||||
|
|
||||||
MovieData.fromJsonEncodable(Map json)
|
MovieData.fromJsonEncodable(Map json)
|
||||||
: _title = json["title"],
|
: _title = json["title"],
|
||||||
_releaseDate = DateTime.parse(json["releaseDate"]),
|
_releaseDate =
|
||||||
_releaseDatePrecision = DatePrecision.values.firstWhere(
|
DateWithPrecisionAndCountry.fromJsonEncodable(json["releaseDate"]) {
|
||||||
(element) => element.name == json["releaseDatePrecision"]) {
|
|
||||||
setDetails(
|
setDetails(
|
||||||
genres: json["genres"],
|
genres: (json["genres"] as List<dynamic>?)
|
||||||
|
?.map((genre) => genre as String)
|
||||||
|
.toList(),
|
||||||
releaseDates: json["releaseDates"] != null
|
releaseDates: json["releaseDates"] != null
|
||||||
? (json["releaseDates"] as List<List<dynamic>>)
|
? (json["releaseDates"] as List<List<dynamic>>)
|
||||||
.map((release) => ((release[0], DateTime.parse(release[1]))
|
.map((release) =>
|
||||||
as ReleaseDateInCountry))
|
DateWithPrecisionAndCountry.fromJsonEncodable(release))
|
||||||
.toList()
|
.toList()
|
||||||
: null,
|
: null,
|
||||||
reviews: json["reviews"] != null
|
reviews: json["reviews"] != null
|
||||||
|
@ -161,7 +181,8 @@ class MovieData extends ChangeNotifier {
|
||||||
: null,
|
: null,
|
||||||
titles: json["titles"] != null
|
titles: json["titles"] != null
|
||||||
? (json["titles"] as List<dynamic>)
|
? (json["titles"] as List<dynamic>)
|
||||||
.map((title) => (title[0], title[1]) as TitleInCountry)
|
.map((title) =>
|
||||||
|
(title: title[0], language: title[1]) as TitleInLanguage)
|
||||||
.toList()
|
.toList()
|
||||||
: null);
|
: null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,8 @@ class MovieManager extends ChangeNotifier {
|
||||||
int max = movies.length - 1;
|
int max = movies.length - 1;
|
||||||
while (min - 1 < max) {
|
while (min - 1 < max) {
|
||||||
int center = ((min + max) / 2).floor();
|
int center = ((min + max) / 2).floor();
|
||||||
int diff = movie.releaseDate.compareTo(movies[center].releaseDate);
|
int diff =
|
||||||
|
movie.releaseDate.date.compareTo(movies[center].releaseDate.date);
|
||||||
if (diff < 0) {
|
if (diff < 0) {
|
||||||
max = center - 1;
|
max = center - 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -110,7 +111,9 @@ class MovieManager extends ChangeNotifier {
|
||||||
for (int i = 0; i < movies.length; i++) {
|
for (int i = 0; i < movies.length; i++) {
|
||||||
var temp = movies[i];
|
var temp = movies[i];
|
||||||
int j = i - 1;
|
int j = i - 1;
|
||||||
for (; j >= 0 && movies[j].releaseDate.isAfter(temp.releaseDate); j--) {
|
for (;
|
||||||
|
j >= 0 && movies[j].releaseDate.date.isAfter(temp.releaseDate.date);
|
||||||
|
j--) {
|
||||||
movies[j + 1] = movies[j];
|
movies[j + 1] = movies[j];
|
||||||
}
|
}
|
||||||
movies[j + 1] = temp;
|
movies[j + 1] = temp;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.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';
|
||||||
|
|
||||||
|
@ -9,15 +8,13 @@ class MovieItem extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final format = DateFormat(DateFormat.YEAR_MONTH_DAY);
|
|
||||||
|
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: movie,
|
animation: movie,
|
||||||
builder: (context, widget) {
|
builder: (context, widget) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(movie.title),
|
title: Text(movie.title),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
"${dateRelativeToNow(movie.releaseDate)}, ${format.format(movie.releaseDate)}"));
|
"${dateRelativeToNow(movie.releaseDate.date)}, ${movie.releaseDate.toString()}, ${movie.genres?.join(", ") ?? ""}"));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
248
pubspec.lock
248
pubspec.lock
|
@ -1,6 +1,30 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "61.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.13.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -41,6 +65,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.2"
|
version: "1.17.2"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.4"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -65,6 +113,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -83,6 +139,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.0"
|
||||||
get:
|
get:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -99,6 +163,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -107,6 +179,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -123,6 +203,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.18.1"
|
version: "0.18.1"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.7"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -131,6 +227,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -155,6 +259,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -227,11 +355,75 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.6"
|
version: "2.1.6"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.1"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
version: "0.0.99"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.12"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -272,6 +464,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.1"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.24.3"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -280,6 +480,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.6.0"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.3"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -296,6 +504,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.10.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -304,6 +528,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4-beta"
|
version: "0.1.4-beta"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -320,6 +560,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.3"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.1.3 <4.0.0"
|
dart: ">=3.1.3 <4.0.0"
|
||||||
flutter: ">=3.13.0"
|
flutter: ">=3.13.0"
|
||||||
|
|
|
@ -40,6 +40,7 @@ dependencies:
|
||||||
get_storage: ^2.1.1
|
get_storage: ^2.1.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
test:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue