Compare commits

...

2 Commits

Author SHA1 Message Date
daniel-michel 92b4a302dc refactor: add dates for when information was retrieved and adjust everything 2024-01-10 23:50:43 +01:00
daniel-michel 0520120ccf feature: movie description 2024-01-10 14:46:03 +01:00
16 changed files with 680 additions and 542 deletions

View File

@ -0,0 +1,85 @@
import 'package:release_schedule/api/json_helper.dart';
import 'package:release_schedule/api/wikidata/wikidata_movie_api.dart';
import 'package:release_schedule/model/dates.dart';
import 'package:release_schedule/model/movie.dart';
class WikidataMovieData extends MovieData {
String entityId;
WikidataMovieData(this.entityId);
WikidataMovieData.fromEncodable(Map encodable)
: entityId = encodable["entityId"],
super.fromJsonEncodable(encodable);
@override
bool same(MovieData other) {
return other is WikidataMovieData && entityId == other.entityId;
}
@override
Map toJsonEncodable() {
return super.toJsonEncodable()..addAll({"entityId": entityId});
}
static WikidataMovieData fromWikidataEntity(
String entityId, Map<String, dynamic> entity) {
Map<String, dynamic> claims = entity["claims"];
List<TextInLanguage>? titles = selectInJson(
claims, "${WikidataProperties.title}.*.mainsnak.datavalue.value")
.map((value) => (
text: value["text"],
language: value["language"],
) as TextInLanguage)
.toList();
List<TextInLanguage>? labels = selectInJson(entity, "labels.*")
.map((value) => (
text: value["value"],
language: value["language"],
) as TextInLanguage)
.toList();
String? wikipediaTitle = selectInJson(entity, "sitelinks.enwiki.url")
.firstOrNull
?.split("/")
.last;
Dated<String?>? description = wikipediaTitle != null
? getCachedWikipediaExplainTextFotTitle(wikipediaTitle)
: null;
List<DateWithPrecisionAndCountry> releaseDates =
_getReleaseDates(claims).toList();
// Sort release dates with higher precision to the beginning
releaseDates.sort((a, b) => -a.dateWithPrecision.precision.index
.compareTo(b.dateWithPrecision.precision.index));
List<String>? genres = selectInJson<String>(
claims, "${WikidataProperties.genre}.*.mainsnak.datavalue.value.id")
.map(getCachedLabelForEntity)
.toList();
WikidataMovieData movie = WikidataMovieData(entityId);
movie.setDetails(
titles: Dated.now(titles),
labels: Dated.now(labels),
releaseDates: Dated.now(releaseDates),
genres: Dated.now(genres),
description: description,
);
return movie;
}
static Iterable<DateWithPrecisionAndCountry> _getReleaseDates(
Map<String, dynamic> claims) {
return selectInJson(claims, "${WikidataProperties.publicationDate}.*")
.where((dateClaim) => dateClaim["rank"] != "deprecated")
.expand<DateWithPrecisionAndCountry>((dateClaim) {
var value = selectInJson(dateClaim, "mainsnak.datavalue.value").first;
Iterable<String> countries = (selectInJson<String>(dateClaim,
"qualifiers.${WikidataProperties.placeOfPublication}.*.datavalue.value.id"))
.map(getCachedLabelForEntity);
if (countries.isEmpty) {
countries = ["unknown location"];
}
return countries.map((country) => DateWithPrecisionAndCountry(
DateTime.parse(value["time"]),
precisionFromWikidata(value["precision"]),
country));
});
}
}

View File

@ -6,8 +6,8 @@ import 'package:intl/intl.dart';
import 'package:release_schedule/api/api_manager.dart';
import 'package:release_schedule/api/json_helper.dart';
import 'package:release_schedule/api/movie_api.dart';
import 'package:release_schedule/api/wikidata/wikidata_movie.dart';
import 'package:release_schedule/model/dates.dart';
import 'package:release_schedule/model/movie.dart';
class WikidataProperties {
static const String instanceOf = "P31";
@ -75,7 +75,7 @@ class WikidataMovieApi implements MovieApi {
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("|")}");
"&action=wbgetentities&format=json&props=labels|claims|sitelinks/urls&ids=${movieIds.sublist(start, end).join("|")}");
Map<String, dynamic> result = jsonDecode(response.body);
Map<String, dynamic> batchEntities = result["entities"];
entities.addAll(batchEntities);
@ -94,6 +94,12 @@ class WikidataMovieApi implements MovieApi {
// they will be retrieved from the cache in fromWikidataEntity
await _getLabelsForEntities(allCountryAndGenreIds);
// Get wikipedia explaintexts
Iterable<String> allWikipediaTitles =
selectInJson<String>(entities, "*.sitelinks.enwiki.url")
.map((url) => url.split("/").last);
await _getWikipediaExplainTextForTitles(allWikipediaTitles.toList());
return movieIds
.map((id) => WikidataMovieData.fromWikidataEntity(id, entities[id]))
.toList();
@ -117,83 +123,6 @@ class WikidataMovieApi implements MovieApi {
}
}
class WikidataMovieData extends MovieData {
String entityId;
WikidataMovieData(
String title, DateWithPrecisionAndCountry releaseDate, this.entityId)
: super(title, releaseDate);
WikidataMovieData.fromEncodable(Map encodable)
: entityId = encodable["entityId"],
super.fromJsonEncodable(encodable);
@override
bool same(MovieData other) {
return other is WikidataMovieData && entityId == other.entityId;
}
@override
Map toJsonEncodable() {
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 =
_getReleaseDates(claims).toList();
// Sort release dates with higher precision to the beginning
releaseDates.sort((a, b) => -a.dateWithPrecision.precision.index
.compareTo(b.dateWithPrecision.precision.index));
List<String>? genres = selectInJson<String>(
claims, "${WikidataProperties.genre}.*.mainsnak.datavalue.value.id")
.map(_getCachedLabelForEntity)
.toList();
WikidataMovieData movie = WikidataMovieData(
title,
releaseDates.isNotEmpty
? releaseDates[0]
: DateWithPrecisionAndCountry(
DateTime.now(), DatePrecision.decade, "unknown location"),
entityId);
movie.setDetails(
titles: titles,
releaseDates: releaseDates,
genres: genres,
);
return movie;
}
static Iterable<DateWithPrecisionAndCountry> _getReleaseDates(
Map<String, dynamic> claims) {
return selectInJson(claims, "${WikidataProperties.publicationDate}.*")
.where((dateClaim) => dateClaim["rank"] != "deprecated")
.expand<DateWithPrecisionAndCountry>((dateClaim) {
var value = selectInJson(dateClaim, "mainsnak.datavalue.value").first;
Iterable<String> countries = (selectInJson<String>(dateClaim,
"qualifiers.${WikidataProperties.placeOfPublication}.*.datavalue.value.id"))
.map(_getCachedLabelForEntity);
if (countries.isEmpty) {
countries = ["unknown location"];
}
return countries.map((country) => DateWithPrecisionAndCountry(
DateTime.parse(value["time"]),
_precisionFromWikidata(value["precision"]),
country));
});
}
}
String _createUpcomingMovieQuery(
DateTime startDate, String instanceOf, int limit) {
String date = DateFormat("yyyy-MM-dd").format(startDate);
@ -213,7 +142,7 @@ ORDER BY ?minReleaseDate
LIMIT $limit""";
}
DatePrecision _precisionFromWikidata(int precision) {
DatePrecision precisionFromWikidata(int precision) {
return switch (precision) {
>= 13 => DatePrecision.minute,
12 => DatePrecision.hour,
@ -264,6 +193,48 @@ Future<Map<String, String>> _getLabelsForEntities(
return labels;
}
String _getCachedLabelForEntity(String entityId) {
String getCachedLabelForEntity(String entityId) {
return _labelCache[entityId] ?? entityId;
}
ApiManager _wikipediaApi =
ApiManager("https://en.wikipedia.org/w/api.php?format=json&origin=*");
Map<String, Dated<String?>> _wikipediaExplainTextCache = {};
Future<Map<String, Dated<String?>>> _getWikipediaExplainTextForTitles(
List<String> pageTitles) async {
const batchSize = 50;
Map<String, Dated<String?>> explainTexts = {};
for (int i = pageTitles.length - 1; i >= 0; i--) {
if (_wikipediaExplainTextCache.containsKey(pageTitles[i])) {
explainTexts[pageTitles[i]] = _wikipediaExplainTextCache[pageTitles[i]]!;
pageTitles.removeAt(i);
}
}
for (int i = 0; i < (pageTitles.length / batchSize).ceil(); i++) {
final start = i * batchSize;
final end = min((i + 1) * batchSize, pageTitles.length);
Response response = await _wikipediaApi.get(
"&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=${pageTitles.sublist(start, end).join("|")}");
Map<String, dynamic> result = jsonDecode(response.body);
List<dynamic> normalize = result["query"]["normalized"];
Map<String, dynamic> batchPages = result["query"]["pages"];
for (String pageId in batchPages.keys) {
String pageTitle = batchPages[pageId]["title"];
String originalTitle = normalize
.where((element) => element["to"] == pageTitle)
.firstOrNull?["from"] ??
pageTitle;
String? explainText = batchPages[pageId]["extract"];
if (explainText != null) {
_wikipediaExplainTextCache[originalTitle] =
explainTexts[originalTitle] = Dated.now(explainText);
}
}
}
return explainTexts;
}
Dated<String?>? getCachedWikipediaExplainTextFotTitle(String title) {
return _wikipediaExplainTextCache[title];
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:release_schedule/api/wikidata_movie_api.dart';
import 'package:release_schedule/api/wikidata/wikidata_movie.dart';
import 'package:release_schedule/api/wikidata/wikidata_movie_api.dart';
import 'package:release_schedule/model/dates.dart';
import 'package:release_schedule/model/live_search.dart';
import 'package:release_schedule/model/local_movie_storage.dart';
@ -17,6 +18,10 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MovieManager manager = MovieManager(
WikidataMovieApi(),
LocalMovieStorageGetStorage(WikidataMovieData.fromEncodable),
);
return MaterialApp(
title: 'Movie Schedule',
themeMode: ThemeMode.dark,
@ -32,10 +37,7 @@ class MyApp extends StatelessWidget {
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
useMaterial3: true,
),
home: HomePage(
MovieManager(WikidataMovieApi(),
LocalMovieStorageGetStorage(WikidataMovieData.fromEncodable)),
),
home: HomePage(manager),
);
}
}
@ -187,9 +189,10 @@ class OverviewPage extends StatelessWidget {
// Only show movies that are bookmarked or have a release date with at least month precision and at least one title
filter: (movie) =>
movie.bookmarked ||
(movie.releaseDate.dateWithPrecision.precision >=
(movie.releaseDate != null &&
movie.releaseDate!.dateWithPrecision.precision >=
DatePrecision.month &&
(movie.titles?.length ?? 0) >= 1),
(movie.titles?.value?.length ?? 0) >= 1),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
@ -234,7 +237,7 @@ class HamburgerMenu extends StatelessWidget {
child: const Text("Remove irrelevant"),
onTap: () => manager.removeMoviesWhere((movie) =>
!movie.bookmarked &&
!(movie.releaseDates?.any((date) =>
!(movie.releaseDates?.value?.any((date) =>
date.dateWithPrecision.precision >=
DatePrecision.month &&
date.dateWithPrecision.date.isAfter(DateTime.now()

View File

@ -55,6 +55,7 @@ class DateWithPrecision implements Comparable<DateWithPrecision> {
.firstWhere((element) => element.name == json[1]);
DateWithPrecision.today() : this(DateTime.now().toUtc(), DatePrecision.day);
DateWithPrecision.unspecified() : this(DateTime(0), DatePrecision.decade);
List<dynamic> toJsonEncodable() {
return [date.toIso8601String(), precision.name];
@ -122,3 +123,28 @@ class DateWithPrecision implements Comparable<DateWithPrecision> {
}
}
}
class Dated<T> {
final T value;
final DateTime date;
Dated(this.value, this.date);
Dated.now(this.value) : date = DateTime.now().toUtc();
Dated.fromJsonEncodable(
dynamic json, T Function(dynamic) valueFromJsonEncodable)
: value = valueFromJsonEncodable(json["value"]),
date = DateTime.parse(json["date"]);
Map<String, dynamic> toJsonEncodable(
dynamic Function(T) valueToJsonEncodable) {
return {
"value": valueToJsonEncodable(value),
"date": date.toIso8601String()
};
}
@override
toString() => "$value as of $date";
}

View File

@ -2,22 +2,26 @@ import 'package:flutter/material.dart';
import 'package:release_schedule/model/dates.dart';
class MovieData extends ChangeNotifier {
String _title;
DateWithPrecisionAndCountry _releaseDate;
bool _bookmarked = false;
bool _hasDetails = false;
List<DateWithPrecisionAndCountry>? _releaseDates;
List<String>? _genres;
List<TitleInLanguage>? _titles;
String? _title;
DateWithPrecisionAndCountry? _releaseDate;
MovieData(this._title, this._releaseDate);
// if it is entirely null the information was never loaded
// if only the value is null it was loaded but nothing was found
Dated<List<TextInLanguage>?>? _titles;
Dated<List<TextInLanguage>?>? _labels;
Dated<List<DateWithPrecisionAndCountry>?>? _releaseDates;
Dated<String?>? _description;
Dated<List<String>?>? _genres;
String get title {
MovieData();
String? get title {
return _title;
}
DateWithPrecisionAndCountry get releaseDate {
DateWithPrecisionAndCountry? get releaseDate {
return _releaseDate;
}
@ -25,111 +29,188 @@ class MovieData extends ChangeNotifier {
return _bookmarked;
}
List<DateWithPrecisionAndCountry>? get releaseDates {
Dated<String?>? get description {
return _description;
}
Dated<List<DateWithPrecisionAndCountry>?>? get releaseDates {
return _releaseDates;
}
List<String>? get genres {
Dated<List<String>?>? get genres {
return _genres;
}
List<TitleInLanguage>? get titles {
Dated<List<TextInLanguage>?>? get titles {
return _titles;
}
bool get hasDetails {
return _hasDetails;
Dated<List<TextInLanguage>?>? get labels {
return _labels;
}
/// Updates the information with that of a new version of the movie
/// but ignores fields that are user controlled, like whether the movie was bookmarked.
void updateWithNewIgnoringUserControlled(MovieData movie) {
setDetails(
title: movie.title,
releaseDate: movie.releaseDate,
titles: movie.titles,
labels: movie.labels,
releaseDates: movie.releaseDates,
genres: movie.genres,
titles: movie.titles);
description: movie.description,
);
}
void setDetails(
{String? title,
DateWithPrecisionAndCountry? releaseDate,
void setNewDetails({
bool? bookmarked,
List<TextInLanguage>? titles,
List<TextInLanguage>? labels,
List<DateWithPrecisionAndCountry>? releaseDates,
List<String>? genres,
List<TitleInLanguage>? titles}) {
if (title != null) {
_title = title;
}
if (releaseDate != null) {
_releaseDate = releaseDate;
String? description,
}) {
setDetails(
bookmarked: bookmarked,
titles: titles != null ? Dated.now(titles) : null,
labels: labels != null ? Dated.now(labels) : null,
releaseDates: releaseDates != null ? Dated.now(releaseDates) : null,
genres: genres != null ? Dated.now(genres) : null,
description: description != null ? Dated.now(description) : null,
);
}
void setDetails({
bool? bookmarked,
Dated<List<TextInLanguage>?>? titles,
Dated<List<TextInLanguage>?>? labels,
Dated<List<DateWithPrecisionAndCountry>?>? releaseDates,
Dated<List<String>?>? genres,
Dated<String?>? description,
}) {
if (bookmarked != null) {
_bookmarked = bookmarked;
}
if (releaseDates != null) {
_releaseDates = releaseDates;
}
if (genres != null) {
_genres = genres;
}
if (titles != null) {
_titles = titles;
}
_hasDetails = true;
if (labels != null) {
_labels = labels;
}
if (titles != null || labels != null) {
_title = null;
_title ??= _titles?.value
?.where((title) => title.language == "en")
.firstOrNull
?.text;
_title ??= _labels?.value
?.where((label) => label.language == "en")
.firstOrNull
?.text;
_title ??= _labels?.value?.firstOrNull?.text;
_title ??= _titles?.value?.firstOrNull?.text;
}
if (description != null) {
_description = description;
}
if (releaseDates != null) {
_releaseDates = releaseDates;
DateWithPrecisionAndCountry? mostPrecise =
_releaseDates?.value?.isNotEmpty ?? false
? _releaseDates?.value?.reduce((a, b) =>
a.dateWithPrecision.precision > b.dateWithPrecision.precision
? a
: b)
: null;
_releaseDate = mostPrecise;
}
if (genres != null) {
_genres = genres;
}
notifyListeners();
}
@override
String toString() {
return "$title (${_releaseDate.toString()}${_genres?.isNotEmpty ?? false ? "; ${_genres?.join(", ")}" : ""})";
return "$title (${_releaseDate.toString()}${_genres?.value?.isNotEmpty ?? false ? "; ${_genres?.value?.join(", ")}" : ""})";
}
bool same(MovieData other) {
return title == other.title &&
releaseDate.dateWithPrecision == other.releaseDate.dateWithPrecision;
return title != null &&
title == other.title &&
(releaseDate == null ||
other.releaseDate == null ||
releaseDate!.dateWithPrecision.date.year ==
other.releaseDate!.dateWithPrecision.date.year);
}
Map toJsonEncodable() {
List? releaseDatesByCountry =
_releaseDates?.map((e) => e.toJsonEncodable()).toList();
List? titlesByCountry = _titles?.map((e) => [e.title, e.language]).toList();
dynamic releaseDatesByCountry = _releaseDates?.toJsonEncodable(
(releaseDates) => releaseDates
?.map((releaseDate) => releaseDate.toJsonEncodable())
.toList());
dynamic titlesByCountry = _titles?.toJsonEncodable(
(titles) => titles?.map((e) => [e.text, e.language]).toList());
dynamic labels = _labels?.toJsonEncodable(
(labels) => labels?.map((e) => [e.text, e.language]).toList());
dynamic genres = _genres?.toJsonEncodable((genres) => genres);
return {
"title": title,
"releaseDate": _releaseDate.toJsonEncodable(),
"bookmarked": _bookmarked,
"titles": titlesByCountry,
"labels": labels,
"releaseDates": releaseDatesByCountry,
"genres": genres,
"titles": titlesByCountry,
"description":
_description?.toJsonEncodable((description) => description),
};
}
MovieData.fromJsonEncodable(Map json)
: _title = json["title"],
_releaseDate =
DateWithPrecisionAndCountry.fromJsonEncodable(json["releaseDate"]) {
MovieData.fromJsonEncodable(Map json) {
setDetails(
bookmarked: json["bookmarked"] as bool,
genres: (json["genres"] as List<dynamic>?)
?.map((genre) => genre as String)
.toList(),
releaseDates: json["releaseDates"] != null
? (json["releaseDates"] as List<dynamic>)
.map((release) =>
DateWithPrecisionAndCountry.fromJsonEncodable(release))
.toList()
: null,
titles: json["titles"] != null
? (json["titles"] as List<dynamic>)
bookmarked: json["bookmarked"] as bool? ?? false,
titles: decodeOptionalJson<Dated<List<TextInLanguage>?>>(
json["titles"],
(json) => Dated.fromJsonEncodable(
json,
(value) => (value as List<dynamic>)
.map((title) =>
(title: title[0], language: title[1]) as TitleInLanguage)
.toList()
: null);
(text: title[0], language: title[1]) as TextInLanguage)
.toList())),
labels: decodeOptionalJson<Dated<List<TextInLanguage>?>>(
json["labels"],
(json) => Dated.fromJsonEncodable(
json,
(value) => (value as List<dynamic>)
.map((label) =>
(text: label[0], language: label[1]) as TextInLanguage)
.toList())),
genres: decodeOptionalJson<Dated<List<String>?>>(
json["genres"],
(json) =>
Dated.fromJsonEncodable(json, (value) => value.cast<String>())),
releaseDates:
decodeOptionalJson<Dated<List<DateWithPrecisionAndCountry>?>>(
json["releaseDates"],
(json) => Dated.fromJsonEncodable(
json,
(value) => (value as List<dynamic>)
.map((releaseDate) =>
DateWithPrecisionAndCountry.fromJsonEncodable(
releaseDate))
.toList())),
description: decodeOptionalJson<Dated<String?>>(json["description"],
(json) => Dated.fromJsonEncodable(json, (value) => value)),
);
}
}
typedef TitleInLanguage = ({String title, String language});
T? decodeOptionalJson<T>(dynamic json, T Function(dynamic) decode) {
if (json == null) {
return null;
}
return decode(json);
}
typedef TextInLanguage = ({String text, String language});
class DateWithPrecisionAndCountry {
final DateWithPrecision dateWithPrecision;

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:release_schedule/api/movie_api.dart';
import 'package:release_schedule/model/dates.dart';
import 'package:release_schedule/model/delayed_function_caller.dart';
import 'package:release_schedule/model/local_movie_storage.dart';
import 'package:release_schedule/model/movie.dart';
@ -64,11 +65,15 @@ class MovieManager extends ChangeNotifier {
void _insertMovie(MovieData movie) {
int min = 0;
int max = movies.length - 1;
DateWithPrecision? movieDate = movie.releaseDate?.dateWithPrecision;
while (min <= max) {
int center = (min + max) ~/ 2;
int diff = movie.releaseDate.dateWithPrecision
.compareTo(movies[center].releaseDate.dateWithPrecision);
if (diff < 0) {
DateWithPrecision? centerDate =
movies[center].releaseDate?.dateWithPrecision;
int diff = movieDate != null && centerDate != null
? movieDate.compareTo(centerDate)
: 0;
if (movieDate == null || centerDate != null && diff < 0) {
max = center - 1;
} else {
min = center + 1;
@ -80,15 +85,13 @@ class MovieManager extends ChangeNotifier {
void _resortMovies() {
for (int i = 0; i < movies.length; i++) {
var temp = movies[i];
DateWithPrecision? tempDate = temp.releaseDate?.dateWithPrecision;
int j = i - 1;
for (;
j >= 0 &&
movies[j]
.releaseDate
.dateWithPrecision
.compareTo(temp.releaseDate.dateWithPrecision) >
0;
j--) {
for (; j >= 0; j--) {
DateWithPrecision? date = movies[j].releaseDate?.dateWithPrecision;
if (date == null || tempDate != null && date.compareTo(tempDate) <= 0) {
break;
}
movies[j + 1] = movies[j];
}
movies[j + 1] = temp;
@ -115,8 +118,8 @@ class MovieManager extends ChangeNotifier {
movies,
search,
(movie) => [
movie.title,
...(movie.titles?.map((title) => title.title) ?? []),
movie.title ?? "",
...(movie.titles?.value?.map((title) => title.text) ?? []),
]);
return results;
}

View File

@ -13,10 +13,10 @@ class MovieItem extends StatelessWidget {
animation: movie,
builder: (context, widget) {
return ListTile(
title: Text(movie.title),
title: Text(movie.title ?? "-"),
subtitle: Text(
(showReleaseDate ? "${movie.releaseDate} " : "") +
(movie.genres?.join(", ") ?? ""),
(movie.genres?.value?.join(", ") ?? ""),
),
trailing: IconButton(
icon: Icon(movie.bookmarked

View File

@ -78,9 +78,9 @@ class MovieList extends StatelessWidget {
int max = indexMap.length;
while (min < max) {
int center = (min + max) ~/ 2;
DateWithPrecision date =
movies[indexMap[center]].releaseDate.dateWithPrecision;
if (date.compareTo(today) < 0) {
DateWithPrecision? date =
movies[indexMap[center]].releaseDate?.dateWithPrecision;
if (date != null && date.compareTo(today) < 0) {
min = center + 1;
} else {
max = center;
@ -91,7 +91,8 @@ class MovieList extends StatelessWidget {
return GroupedList<DateWithPrecision>(
itemCount: indexMap.length,
groupBy: (index) =>
movies[indexMap[index]].releaseDate.dateWithPrecision,
movies[indexMap[index]].releaseDate?.dateWithPrecision ??
DateWithPrecision.unspecified(),
groupSeparatorBuilder: (date) => buildGroupSeparator(context, date),
itemBuilder: (context, index) {
return MovieItem(movies[indexMap[index]]);
@ -106,8 +107,8 @@ class MovieList extends StatelessWidget {
int max = movies.length;
while (min < max) {
int center = (min + max) ~/ 2;
DateWithPrecision date = movies[center].releaseDate.dateWithPrecision;
if (date.compareTo(today) < 0) {
DateWithPrecision? date = movies[center].releaseDate?.dateWithPrecision;
if (date != null && date.compareTo(today) < 0) {
min = center + 1;
} else {
max = center;
@ -117,7 +118,9 @@ class MovieList extends StatelessWidget {
}();
return GroupedList<DateWithPrecision>(
itemCount: movies.length,
groupBy: (index) => movies[index].releaseDate.dateWithPrecision,
groupBy: (index) =>
movies[index].releaseDate?.dateWithPrecision ??
DateWithPrecision.unspecified(),
groupSeparatorBuilder: (date) => buildGroupSeparator(context, date),
itemBuilder: (context, index) {
return MovieItem(movies[index]);

View File

@ -29,7 +29,7 @@ class MoviePage extends StatelessWidget {
animation: movie,
builder: (context, child) {
return Scaffold(
appBar: AppBar(title: Text(movie.title), actions: [
appBar: AppBar(title: Text(movie.title ?? "-"), actions: [
IconButton(
icon: Icon(movie.bookmarked
? Icons.bookmark_added
@ -39,19 +39,27 @@ class MoviePage extends StatelessWidget {
]),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(12.0),
padding: const EdgeInsets.all(18.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 10,
runSpacing: 10,
children: movie.genres
children: movie.genres?.value
?.map((genre) => Chip(label: Text(genre)))
.toList() ??
[],
),
const SizedBox(height: 20),
const Heading("About"),
Text(
movie.description?.value?.trim().replaceAll("\n", "\n\n") ??
"-",
textAlign: TextAlign.justify,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
height: 1.6,
),
),
const Heading("Titles"),
Table(
border: TableBorder.symmetric(
@ -59,7 +67,7 @@ class MoviePage extends StatelessWidget {
color: Theme.of(context).dividerColor,
),
),
children: movie.titles?.map((title) {
children: movie.titles?.value?.map((title) {
return TableRow(
children: [
TableCell(
@ -70,7 +78,7 @@ class MoviePage extends StatelessWidget {
TableCell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(title.title),
child: Text(title.text),
))
],
);
@ -84,7 +92,7 @@ class MoviePage extends StatelessWidget {
color: Theme.of(context).dividerColor,
),
),
children: movie.releaseDates?.map((releaseDate) {
children: movie.releaseDates?.value?.map((releaseDate) {
return TableRow(
children: [
TableCell(

View File

@ -9,6 +9,23 @@ void main() {
group('MovieManager', () {
late MovieManager movieManager;
final theMatrix = MovieData()
..setNewDetails(
labels: [(text: 'The Matrix', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(1999, 3, 31), DatePrecision.day, 'USA')
],
);
final theMatrixReloaded = MovieData()
..setNewDetails(
labels: [(text: 'The Matrix Reloaded', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2003, 5, 7), DatePrecision.day, 'USA')
],
);
setUp(() {
movieManager = MovieManager(
MovieApi(),
@ -18,16 +35,8 @@ void main() {
test('addMovies should add movies to the list', () {
final movies = [
MovieData(
'The Matrix',
DateWithPrecisionAndCountry(DateTime(1999, 3, 31), DatePrecision.day,
'United States of America'),
),
MovieData(
'The Matrix Reloaded',
DateWithPrecisionAndCountry(DateTime(2003, 5, 7), DatePrecision.day,
'United States of America'),
),
MovieData()..updateWithNewIgnoringUserControlled(theMatrix),
MovieData()..updateWithNewIgnoringUserControlled(theMatrixReloaded),
];
movieManager.addMovies(movies);
@ -37,56 +46,45 @@ void main() {
test('addMovies should add new movies', () {
final movies = [
MovieData(
'The Matrix',
DateWithPrecisionAndCountry(DateTime(1999, 3, 31), DatePrecision.day,
'United States of America'),
),
MovieData(
'The Matrix Reloaded',
DateWithPrecisionAndCountry(DateTime(2003, 5, 7), DatePrecision.day,
'United States of America'),
),
MovieData()..updateWithNewIgnoringUserControlled(theMatrix),
MovieData()..updateWithNewIgnoringUserControlled(theMatrixReloaded),
];
movieManager.addMovies(movies);
final newMovies = [
MovieData(
'The Matrix Revolutions',
DateWithPrecisionAndCountry(DateTime(2003, 11, 5), DatePrecision.day,
'United States of America'),
MovieData()
..setNewDetails(
labels: [(text: 'The Matrix Revolutions', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2003, 11, 5), DatePrecision.day, 'USA')
],
),
];
movieManager.addMovies(newMovies);
expect(movieManager.movies, equals([...movies, ...newMovies]));
expect(movieManager.movies, equals(movies + newMovies));
});
test('addMovies should update existing movies', () {
final movies = [
MovieData(
'The Matrix',
DateWithPrecisionAndCountry(DateTime(1999, 3, 31), DatePrecision.day,
'United States of America'),
),
MovieData(
'The Matrix Reloaded',
DateWithPrecisionAndCountry(DateTime(2003, 5, 7), DatePrecision.day,
'United States of America'),
),
MovieData()..updateWithNewIgnoringUserControlled(theMatrix),
MovieData()..updateWithNewIgnoringUserControlled(theMatrixReloaded),
];
movieManager.addMovies(movies);
final updatedMovie = MovieData(
'The Matrix Reloaded',
DateWithPrecisionAndCountry(DateTime(2003, 5, 7), DatePrecision.day,
'United States of America'),
)..setDetails(
final updatedMovie = MovieData()
..setNewDetails(
bookmarked: true,
genres: ['Action', 'Adventure'],
labels: [(text: 'The Matrix Reloaded', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2003, 5, 7), DatePrecision.day, 'USA')
],
);
movieManager.addMovies([updatedMovie]);
@ -97,36 +95,34 @@ void main() {
test('addMovies should sort movies by their release dates', () {
final movies = [
MovieData(
'The Matrix Reloaded',
DateWithPrecisionAndCountry(DateTime(2003, 5, 7), DatePrecision.day,
'United States of America'),
),
MovieData(
'The Matrix',
DateWithPrecisionAndCountry(DateTime(1999, 3, 31), DatePrecision.day,
'United States of America'),
),
MovieData()..updateWithNewIgnoringUserControlled(theMatrixReloaded),
MovieData()..updateWithNewIgnoringUserControlled(theMatrix),
];
movieManager.addMovies(movies);
expect(movieManager.movies, equals([...movies.reversed]));
expect(movieManager.movies, equals(movies.reversed.toList()));
});
test(
'addMovies should sort movies that have a less precise release date before movies with more precise release dates',
() {
final movies = [
MovieData(
'The Matrix Reloaded',
DateWithPrecisionAndCountry(DateTime(2003, 5, 7), DatePrecision.day,
'United States of America'),
MovieData()
..updateWithNewIgnoringUserControlled(theMatrixReloaded)
..setNewDetails(
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2003, 5, 7), DatePrecision.day, 'USA')
],
),
MovieData(
'The Matrix',
DateWithPrecisionAndCountry(DateTime(2003, 5, 7), DatePrecision.month,
'United States of America'),
MovieData()
..updateWithNewIgnoringUserControlled(theMatrix)
..setNewDetails(
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2003, 5, 7), DatePrecision.month, 'USA')
],
),
];
@ -139,68 +135,59 @@ void main() {
'when a movie is modified and it\'s date is changed the movies should be resorted',
() async {
final movies = [
MovieData(
'The Matrix Reloaded',
DateWithPrecisionAndCountry(DateTime(1998, 5, 7), DatePrecision.day,
'United States of America'),
),
MovieData(
'The Matrix',
DateWithPrecisionAndCountry(DateTime(1999, 3, 31), DatePrecision.day,
'United States of America'),
MovieData()
..updateWithNewIgnoringUserControlled(theMatrixReloaded)
..setNewDetails(
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(1998, 5, 7), DatePrecision.day, 'USA')
],
),
MovieData()..updateWithNewIgnoringUserControlled(theMatrix),
];
movieManager.addMovies(movies);
final movie = movieManager.movies.first;
movie.setDetails(
releaseDate: DateWithPrecisionAndCountry(DateTime(2003, 5, 7),
DatePrecision.day, 'United States of America'),
movie.setNewDetails(
releaseDates: [
DateWithPrecisionAndCountry(DateTime(2003, 5, 7), DatePrecision.day,
'United States of America')
],
);
await Future.delayed(const Duration(milliseconds: 100));
expect(movieManager.movies, equals([...movies.reversed]));
expect(movieManager.movies, equals(movies.reversed.toList()));
});
test('removeMoviesWhere should remove movies from the list', () {
final movies = [
MovieData(
'The Matrix',
DateWithPrecisionAndCountry(DateTime(1999, 3, 31), DatePrecision.day,
'United States of America'),
),
MovieData(
'The Matrix Reloaded',
DateWithPrecisionAndCountry(DateTime(2003, 5, 7), DatePrecision.day,
'United States of America'),
),
MovieData()..updateWithNewIgnoringUserControlled(theMatrix),
MovieData()..updateWithNewIgnoringUserControlled(theMatrixReloaded),
];
MovieData notRemoved = MovieData(
'Harry Potter and the Philosopher\'s Stone',
MovieData notRemoved = MovieData()
..setNewDetails(
labels: [
(text: 'Harry Potter and the Philosopher\'s Stone', language: 'en')
],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2001, 11, 4), DatePrecision.day, 'United Kingdom'),
DateTime(2001, 11, 4), DatePrecision.day, 'UK')
],
);
movieManager.addMovies([...movies, notRemoved]);
movieManager.addMovies(movies + [notRemoved]);
movieManager.removeMoviesWhere((movie) => movie.title.contains('Matrix'));
movieManager.removeMoviesWhere(
(movie) => movie.title?.contains('Matrix') == true);
expect(movieManager.movies, equals([notRemoved]));
});
test("localSearch", () {
final movies = [
MovieData(
'The Matrix',
DateWithPrecisionAndCountry(DateTime(1999, 3, 31), DatePrecision.day,
'United States of America'),
),
MovieData(
'The Matrix Reloaded',
DateWithPrecisionAndCountry(DateTime(2003, 5, 7), DatePrecision.day,
'United States of America'),
),
MovieData()..updateWithNewIgnoringUserControlled(theMatrix),
MovieData()..updateWithNewIgnoringUserControlled(theMatrixReloaded),
];
movieManager.addMovies(movies);

View File

@ -4,114 +4,108 @@ import 'package:release_schedule/model/movie.dart';
void main() {
group('MovieData', () {
test('updateWithNew() updates all fields', () {
final movie1 = MovieData(
'Title 1',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'));
final movie2 = MovieData(
'Title 2',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'UK'));
movie2.setDetails(releaseDates: [
MovieData firstMovie = MovieData()
..setNewDetails(
labels: [(text: 'Title 1', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')
], genres: [
'Action',
'Adventure'
], titles: [
(title: 'Title 2', language: 'en')
]);
],
);
MovieData secondMovie = MovieData()
..setNewDetails(
labels: [(text: 'Title 2', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')
],
);
test('updateWithNew() updates all fields', () {
final movie1 = MovieData()
..updateWithNewIgnoringUserControlled(firstMovie);
final movie2 = MovieData()
..updateWithNewIgnoringUserControlled(secondMovie)
..setNewDetails(
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'UK')
],
genres: ['Action', 'Adventure'],
titles: [(text: 'Titel 2', language: 'de')],
);
movie1.updateWithNewIgnoringUserControlled(movie2);
expect(movie1.title, equals('Title 2'));
expect(movie1.releaseDate.country, equals('UK'));
expect(movie1.releaseDates!.length, equals(1));
expect(movie1.releaseDates![0].country, equals('US'));
expect(movie1.genres!.length, equals(2));
expect(movie1.genres![0], equals('Action'));
expect(movie1.genres![1], equals('Adventure'));
expect(movie1.titles!.length, equals(1));
expect(movie1.titles![0].title, equals('Title 2'));
expect(movie1.titles![0].language, equals('en'));
expect(movie1.releaseDate?.country, equals('UK'));
expect(movie1.releaseDates?.value?.length, equals(1));
expect(movie1.releaseDates?.value?[0].country, equals('UK'));
expect(movie1.genres?.value?.length, equals(2));
expect(movie1.genres?.value?[0], equals('Action'));
expect(movie1.genres?.value?[1], equals('Adventure'));
expect(movie1.titles?.value?.length, equals(1));
expect(movie1.titles?.value?[0].text, equals('Titel 2'));
expect(movie1.titles?.value?[0].language, equals('de'));
});
test('same() returns true for same title and release date', () {
final movie1 = MovieData(
'Title 1',
test('same() returns true for same title and release year', () {
final movie1 = MovieData()
..updateWithNewIgnoringUserControlled(firstMovie);
final movie2 = MovieData()
..updateWithNewIgnoringUserControlled(firstMovie)
..setNewDetails(
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'));
final movie2 = MovieData(
'Title 1',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'));
DateTime(2023, 4, 27), DatePrecision.day, 'US')
],
);
expect(movie1.same(movie2), isTrue);
});
test('same() returns false for different title', () {
final movie1 = MovieData(
'Title 1',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'));
final movie2 = MovieData(
'Title 2',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'));
final movie1 = MovieData()
..updateWithNewIgnoringUserControlled(firstMovie);
final movie2 = MovieData()
..updateWithNewIgnoringUserControlled(secondMovie);
expect(movie1.same(movie2), isFalse);
});
test('same() returns false for different release date', () {
final movie1 = MovieData(
'Title 1',
test('same() returns false for different release years', () {
final movie1 = MovieData()
..updateWithNewIgnoringUserControlled(firstMovie);
final movie2 = MovieData()
..updateWithNewIgnoringUserControlled(firstMovie)
..setNewDetails(
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'));
final movie2 = MovieData(
'Title 1',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 2), DatePrecision.day, 'US'));
DateTime(2022, 1, 1), DatePrecision.day, 'US')
],
);
expect(movie1.same(movie2), isFalse);
});
test('can be encoded to JSON and back', () {
final movie = MovieData(
'Title 1',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'));
movie.setDetails(releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')
], genres: [
'Action',
'Adventure'
], titles: [
(title: 'Title 2', language: 'en')
]);
final movie = MovieData()
..updateWithNewIgnoringUserControlled(firstMovie)
..setNewDetails(
genres: ['Action', 'Adventure'],
);
final json = movie.toJsonEncodable();
final movie2 = MovieData.fromJsonEncodable(json);
expect(movie2.title, equals('Title 1'));
expect(movie2.releaseDate.country, equals('US'));
expect(movie2.releaseDates!.length, equals(1));
expect(movie2.releaseDates![0].country, equals('US'));
expect(movie2.genres!.length, equals(2));
expect(movie2.genres![0], equals('Action'));
expect(movie2.genres![1], equals('Adventure'));
expect(movie2.titles!.length, equals(1));
expect(movie2.titles![0].title, equals('Title 2'));
expect(movie2.titles![0].language, equals('en'));
expect(movie2.releaseDate?.country, equals('US'));
expect(movie2.releaseDates?.value?.length, equals(1));
expect(movie2.releaseDates?.value?[0].country, equals('US'));
expect(movie2.genres?.value?.length, equals(2));
expect(movie2.genres?.value?[0], equals('Action'));
expect(movie2.genres?.value?[1], equals('Adventure'));
expect(movie2.titles, equals(null));
});
test('toString()', () {
final movie = MovieData(
'Title 1',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'));
movie.setDetails(releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')
], genres: [
'Action',
'Adventure'
], titles: [
(title: 'Title 2', language: 'en')
]);
final movie = MovieData()
..updateWithNewIgnoringUserControlled(firstMovie)
..setNewDetails(
genres: ['Action', 'Adventure'],
);
expect(movie.toString(),
equals('Title 1 (January 1, 2023 (US); Action, Adventure)'));
});

View File

@ -15,20 +15,29 @@ void main() {
setUp(() {
storage = InMemoryMovieStorage();
storage.update([
MovieData(
'The Shawshank Redemption',
MovieData()
..setNewDetails(
labels: [(text: 'The Shawshank Redemption', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(1994, 9, 22), DatePrecision.day, 'US'),
DateTime(1994, 9, 22), DatePrecision.day, 'US')
],
),
MovieData(
'The Godfather',
MovieData()
..setNewDetails(
labels: [(text: 'The Godfather', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(1972, 3, 24), DatePrecision.day, 'US'),
DateTime(1972, 3, 24), DatePrecision.day, 'US')
],
),
MovieData(
'The Dark Knight',
MovieData()
..setNewDetails(
labels: [(text: 'The Dark Knight', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2008, 7, 18), DatePrecision.day, 'US'),
DateTime(2008, 7, 18), DatePrecision.day, 'US')
],
),
]);
});

View File

@ -6,20 +6,26 @@ import 'package:release_schedule/view/movie_item.dart';
import 'package:release_schedule/view/movie_page.dart';
void main() {
testWidgets('MovieItem displays movie data', (WidgetTester tester) async {
final movie = MovieData(
'Test Movie',
late MovieData testMovie;
setUp(() {
testMovie = MovieData()
..setNewDetails(
labels: [(text: 'Test Movie', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'),
DateTime(2023, 1, 1), DatePrecision.day, 'US')
],
);
movie.setDetails(
});
testWidgets('MovieItem displays movie data', (WidgetTester tester) async {
testMovie.setNewDetails(
genres: ['Action', 'Adventure'],
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MovieItem(movie),
body: MovieItem(testMovie),
),
),
);
@ -30,26 +36,17 @@ void main() {
});
testWidgets('should update when the movie is modified', (tester) async {
final movie = MovieData(
'Test Movie',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'),
);
movie.setDetails(
genres: ['Action', 'Adventure'],
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MovieItem(movie),
body: MovieItem(testMovie),
),
),
);
expect(find.text('Test Movie'), findsOneWidget);
movie.setDetails(
testMovie.setNewDetails(
genres: ['Action', 'Adventure', 'Comedy'],
);
@ -59,26 +56,17 @@ void main() {
});
testWidgets('should update when the movie is bookmarked', (tester) async {
final movie = MovieData(
'Test Movie',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'),
);
movie.setDetails(
genres: ['Action', 'Adventure'],
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MovieItem(movie),
body: MovieItem(testMovie),
),
),
);
expect(find.byIcon(Icons.bookmark_border), findsOneWidget);
movie.setDetails(
testMovie.setDetails(
bookmarked: true,
);
@ -89,19 +77,10 @@ void main() {
testWidgets("should update the bookmark state when the icon is tapped",
(tester) async {
final movie = MovieData(
'Test Movie',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'),
);
movie.setDetails(
genres: ['Action', 'Adventure'],
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MovieItem(movie),
body: MovieItem(testMovie),
),
),
);
@ -116,19 +95,10 @@ void main() {
});
testWidgets("should navigate to MoviePage when tapped", (tester) async {
final movie = MovieData(
'Test Movie',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US'),
);
movie.setDetails(
genres: ['Action', 'Adventure'],
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MovieItem(movie),
body: MovieItem(testMovie),
),
),
);

View File

@ -7,20 +7,30 @@ import 'package:release_schedule/view/movie_list.dart';
void main() {
group('MovieList', () {
testWidgets('should render a list of movies', (WidgetTester tester) async {
final movies = [
MovieData(
'The Shawshank Redemption',
DateWithPrecisionAndCountry(
DateTime(1994, 9, 22), DatePrecision.day, 'US'),
),
MovieData(
'The Godfather',
DateWithPrecisionAndCountry(
DateTime(1972, 3, 24), DatePrecision.day, 'US'),
),
];
late List<MovieData> movies;
setUp(() {
movies = [
MovieData()
..setNewDetails(
labels: [(text: 'The Shawshank Redemption', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(1994, 9, 22), DatePrecision.day, 'US')
],
),
MovieData()
..setNewDetails(
labels: [(text: 'The Godfather', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(1972, 3, 24), DatePrecision.day, 'US')
],
)
];
});
testWidgets('should render a list of movies', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
@ -36,25 +46,12 @@ void main() {
testWidgets("should filter the list of movies",
(WidgetTester tester) async {
final movies = [
MovieData(
'The Shawshank Redemption',
DateWithPrecisionAndCountry(
DateTime(1994, 9, 22), DatePrecision.day, 'US'),
),
MovieData(
'The Godfather',
DateWithPrecisionAndCountry(
DateTime(1972, 3, 24), DatePrecision.day, 'US'),
),
];
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MovieList(
movies,
filter: (movie) => movie.title.contains('Godfather'),
filter: (movie) => movie.title?.contains('Godfather') == true,
),
),
),

View File

@ -11,18 +11,31 @@ import 'package:release_schedule/view/movie_manager_list.dart';
void main() {
group('MovieManagerList', () {
late List<MovieData> movies;
setUp(() {
movies = [
MovieData()
..setNewDetails(
labels: [(text: 'Movie 1', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')
],
),
MovieData()
..setNewDetails(
labels: [(text: 'Movie 2', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')
],
)
];
});
testWidgets('displays movie list', (tester) async {
final manager = MovieManager(MovieApi(), InMemoryMovieStorage());
manager.addMovies([
MovieData(
'Movie 1',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')),
MovieData(
'Movie 2',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')),
]);
manager.addMovies(movies);
// pump the delay until the change is written to the cache, so no timers run when the test finishes
await tester.pump(const Duration(seconds: 5));
@ -35,25 +48,20 @@ void main() {
testWidgets('updates when new movies are added', (tester) async {
final manager = MovieManager(MovieApi(), InMemoryMovieStorage());
manager.addMovies([
MovieData(
'Movie 1',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')),
MovieData(
'Movie 2',
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')),
]);
manager.addMovies(movies);
await tester.pumpWidget(
MaterialApp(home: Scaffold(body: MovieManagerList(manager))));
manager.addMovies([
MovieData(
'Movie 3',
MovieData()
..setNewDetails(
labels: [(text: 'Movie 3', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(2023, 1, 1), DatePrecision.day, 'US')),
DateTime(2023, 1, 1), DatePrecision.day, 'US')
],
)
]);
// pump the delay until the change is written to the cache, so no timers run when the test finishes
await tester.pump(const Duration(seconds: 5));

View File

@ -6,13 +6,20 @@ import 'package:release_schedule/view/movie_page.dart';
void main() {
group('MoviePage', () {
testWidgets('should render the movie details', (WidgetTester tester) async {
final movie = MovieData(
'The Shawshank Redemption',
DateWithPrecisionAndCountry(
DateTime(1994, 9, 22), DatePrecision.day, 'US'),
);
late MovieData movie;
setUp(() {
movie = MovieData()
..setNewDetails(
labels: [(text: 'The Shawshank Redemption', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(1994, 9, 22), DatePrecision.day, 'US')
],
);
});
testWidgets('should render the movie details', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
@ -23,16 +30,10 @@ void main() {
await tester.pumpAndSettle();
expect(find.text(movie.title), findsAtLeastNWidgets(1));
expect(find.text(movie.title!), findsAtLeastNWidgets(1));
});
testWidgets('should bookmark the movie', (WidgetTester tester) async {
final movie = MovieData(
'The Shawshank Redemption',
DateWithPrecisionAndCountry(
DateTime(1994, 9, 22), DatePrecision.day, 'US'),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
@ -50,14 +51,9 @@ void main() {
expect(movie.bookmarked, isTrue);
});
});
testWidgets("should display the movie's genres", (WidgetTester tester) async {
final movie = MovieData(
'The Shawshank Redemption',
DateWithPrecisionAndCountry(
DateTime(1994, 9, 22), DatePrecision.day, 'US'),
)..setDetails(genres: ['Drama']);
testWidgets("should display the movie's genres",
(WidgetTester tester) async {
movie.setNewDetails(genres: ['Drama']);
await tester.pumpWidget(
MaterialApp(
@ -74,12 +70,8 @@ void main() {
testWidgets("should display the movie's titles and release dates",
(WidgetTester tester) async {
final movie = MovieData(
'The Shawshank Redemption',
DateWithPrecisionAndCountry(
DateTime(1994, 9, 22), DatePrecision.day, 'US'),
)..setDetails(
titles: [(title: 'The Shawshank Redemption', language: 'en')],
movie.setNewDetails(
titles: [(text: 'The Shawshank Redemption', language: 'en')],
releaseDates: [
DateWithPrecisionAndCountry(
DateTime(1994, 9, 22), DatePrecision.day, 'US')
@ -102,4 +94,5 @@ void main() {
expect(find.text('US'), findsOneWidget);
expect(find.textContaining('1994'), findsOneWidget);
});
});
}