From ce90c7c1c97531f091ce1379e70027f104b5d935 Mon Sep 17 00:00:00 2001 From: daniel-michel <65034538+daniel-michel@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:51:45 +0100 Subject: [PATCH] fix: improve used dates and add genres --- lib/api/wikidata_movie_api.dart | 216 +++++++++++++++++++++++++--- lib/model/movie.dart | 89 +++++++----- lib/model/movie_manager.dart | 7 +- lib/view/movie_item.dart | 5 +- pubspec.lock | 248 ++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + 6 files changed, 510 insertions(+), 56 deletions(-) diff --git a/lib/api/wikidata_movie_api.dart b/lib/api/wikidata_movie_api.dart index 5d80940..15b37d5 100644 --- a/lib/api/wikidata_movie_api.dart +++ b/lib/api/wikidata_movie_api.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:math'; import 'package:http/http.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/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( + dynamic json, String path) sync* { + if (path.isEmpty) { + if (json is T) { + yield (value: json, path: ""); + } + return; + } + List 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(e, continueWithPath)) + .map(addFirstToPath); + } else if (json is Map) { + for (String key in json.keys) { + yield* _selectInJsonWithPath(json[key], continueWithPath) + .map(addFirstToPath); + } + } + } else if (json is List) { + try { + int index = int.parse(first); + yield* _selectInJsonWithPath(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(value, pathParts.join(".")); + } + } +} + +Iterable _selectInJson(dynamic json, String path) { + return _selectInJsonWithPath(json, path).map((e) => e.value); +} + +Map> _selectMultipleInJson( + dynamic json, Map selector) { + Map> 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 _labelCache = {}; +Future> _getLabelsForEntities( + List entityIds) async { + const batchSize = 50; + Map 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 result = jsonDecode(response.body); + Map batchEntities = result["entities"]; + for (String entityId in batchEntities.keys) { + Map 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 { - int entityId; - WikidataMovieData(String title, DateTime releaseDate, - DatePrecision releaseDatePrecision, this.entityId) - : super(title, releaseDate, releaseDatePrecision); + String entityId; + WikidataMovieData( + String title, DateWithPrecisionAndCountry releaseDate, this.entityId) + : super(title, releaseDate); WikidataMovieData.fromEncodable(Map encodable) : entityId = encodable["entityId"], @@ -25,10 +141,49 @@ class WikidataMovieData extends MovieData { Map toJsonEncodable() { return super.toJsonEncodable()..addAll({"entityId": entityId}); } + + static WikidataMovieData fromWikidataEntity( + String entityId, Map entity) { + String title = + _selectInJson(entity, "labels.en.value").firstOrNull ?? + _selectInJson(entity, "labels.*.value").first; + Map claims = entity["claims"]; + List? titles = _selectInJson( + claims, "${WikidataProperties.title}.*.mainsnak.datavalue.value") + .map((value) => ( + title: value["text"], + language: value["language"], + ) as TitleInLanguage) + .toList(); + List releaseDates = + _selectInJson(claims, "${WikidataProperties.publicationDate}.*") + .map((dateClaim) { + var value = _selectInJson(dateClaim, "mainsnak.datavalue.value").first; + String country = _getCachedLabelForEntity(_selectInJson(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? genres = _selectInJson( + 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 { - ApiManager searchApi = ApiManager("https://www.wikidata.org/w/api.php"); ApiManager queryApi = ApiManager("https://query.wikidata.org/sparql?format=json"); @@ -49,17 +204,44 @@ class WikidataMovieApi implements MovieApi { } Map result = jsonDecode(response.body); List entries = result["results"]["bindings"]; - List movies = []; - for (Map entry in entries) { - String identifier = - RegExp(r"Q\d+$").firstMatch(entry["movie"]["value"])![0]!; - movies.add(WikidataMovieData( - entry["movieLabel"]["value"] as String, - DateTime.parse(entry["minReleaseDate"]["value"] as String), - _precisionFromWikidata(int.parse(entry["datePrecision"]["value"])), - int.parse(identifier.substring(1)))); + List ids = entries + .map((entry) => + RegExp(r"Q\d+$").firstMatch(entry["movie"]["value"])![0]!) + .toList(); + return _getMovieDataFromIds(ids); + } + + Future> _getMovieDataFromIds( + List movieIds) async { + // Wikidata limits the number of entities per request to 50 + const batchSize = 50; + Map 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 result = jsonDecode(response.body); + Map batchEntities = result["entities"]; + entities.addAll(batchEntities); } - return movies; + + List allCountryAndGenreIds = []; + // Add the country ids from the publication dates + allCountryAndGenreIds.addAll(_selectInJson(entities, + "*.claims.${WikidataProperties.publicationDate}.*.qualifiers.${WikidataProperties.placeOfPublication}.*.datavalue.value.id")); + // Add the genre ids + allCountryAndGenreIds.addAll(_selectInJson(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 @@ -95,7 +277,9 @@ LIMIT $limit"""; DatePrecision _precisionFromWikidata(int precision) { return switch (precision) { - >= 11 => DatePrecision.day, + >= 13 => DatePrecision.minute, + 12 => DatePrecision.hour, + 11 => DatePrecision.day, 10 => DatePrecision.month, 9 => DatePrecision.year, 8 => DatePrecision.decade, diff --git a/lib/model/movie.dart b/lib/model/movie.dart index 1d153bb..60494fd 100644 --- a/lib/model/movie.dart +++ b/lib/model/movie.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; class Review { String score; @@ -23,37 +24,61 @@ class Review { } } -typedef ReleaseDateInCountry = (String country, DateTime date); -typedef TitleInCountry = (String country, String title); +typedef TitleInLanguage = ({String title, String language}); + +class DateWithPrecisionAndCountry { + DateTime date; + DatePrecision precision; + String country; + + DateWithPrecisionAndCountry(this.date, this.precision, this.country); + + DateWithPrecisionAndCountry.fromJsonEncodable(List 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 } class MovieData extends ChangeNotifier { String _title; - DateTime _releaseDate; - DatePrecision _releaseDatePrecision; + DateWithPrecisionAndCountry _releaseDate; bool _hasDetails = false; - List? _releaseDates; + List? _releaseDates; List? _genres; - List? _titles; + List? _titles; List? _reviews; - MovieData(this._title, this._releaseDate, this._releaseDatePrecision); + MovieData(this._title, this._releaseDate); String get title { return _title; } - DateTime get releaseDate { + DateWithPrecisionAndCountry get releaseDate { return _releaseDate; } - DatePrecision get releaseDatePrecision { - return _releaseDatePrecision; - } - - List? get releaseDates { + List? get releaseDates { return _releaseDates; } @@ -61,7 +86,7 @@ class MovieData extends ChangeNotifier { return _genres; } - List? get titles { + List? get titles { return _titles; } @@ -77,7 +102,6 @@ class MovieData extends ChangeNotifier { setDetails( title: movie.title, releaseDate: movie.releaseDate, - releaseDatePrecision: movie.releaseDatePrecision, releaseDates: movie.releaseDates, genres: movie.genres, titles: movie.titles, @@ -86,11 +110,10 @@ class MovieData extends ChangeNotifier { void setDetails( {String? title, - DateTime? releaseDate, - DatePrecision? releaseDatePrecision, - List? releaseDates, + DateWithPrecisionAndCountry? releaseDate, + List? releaseDates, List? genres, - List? titles, + List? titles, List? reviews}) { if (title != null) { _title = title; @@ -98,9 +121,6 @@ class MovieData extends ChangeNotifier { if (releaseDate != null) { _releaseDate = releaseDate; } - if (releaseDatePrecision != null) { - _releaseDatePrecision = releaseDatePrecision; - } if (releaseDates != null) { _releaseDates = releaseDates; } @@ -119,17 +139,16 @@ class MovieData extends ChangeNotifier { @override String toString() { - return "$title (${releaseDate.year}${_genres?.isNotEmpty ?? true ? "; ${_genres?.join(", ")}" : ""})"; + return "$title (${_releaseDate.toString()}${_genres?.isNotEmpty ?? true ? "; ${_genres?.join(", ")}" : ""})"; } Map toJsonEncodable() { List? releaseDatesByCountry = - _releaseDates?.map((e) => [e.$1, e.$2.toIso8601String()]).toList(); - List? titlesByCountry = _titles?.map((e) => [e.$1, e.$2]).toList(); + _releaseDates?.map((e) => e.toJsonEncodable()).toList(); + List? titlesByCountry = _titles?.map((e) => [e.title, e.language]).toList(); return { "title": title, - "releaseDate": releaseDate.toIso8601String(), - "releaseDatePrecision": _releaseDatePrecision.name, + "releaseDate": _releaseDate.toJsonEncodable(), "releaseDates": releaseDatesByCountry, "genres": genres, "titles": titlesByCountry, @@ -143,15 +162,16 @@ class MovieData extends ChangeNotifier { MovieData.fromJsonEncodable(Map json) : _title = json["title"], - _releaseDate = DateTime.parse(json["releaseDate"]), - _releaseDatePrecision = DatePrecision.values.firstWhere( - (element) => element.name == json["releaseDatePrecision"]) { + _releaseDate = + DateWithPrecisionAndCountry.fromJsonEncodable(json["releaseDate"]) { setDetails( - genres: json["genres"], + genres: (json["genres"] as List?) + ?.map((genre) => genre as String) + .toList(), releaseDates: json["releaseDates"] != null ? (json["releaseDates"] as List>) - .map((release) => ((release[0], DateTime.parse(release[1])) - as ReleaseDateInCountry)) + .map((release) => + DateWithPrecisionAndCountry.fromJsonEncodable(release)) .toList() : null, reviews: json["reviews"] != null @@ -161,7 +181,8 @@ class MovieData extends ChangeNotifier { : null, titles: json["titles"] != null ? (json["titles"] as List) - .map((title) => (title[0], title[1]) as TitleInCountry) + .map((title) => + (title: title[0], language: title[1]) as TitleInLanguage) .toList() : null); } diff --git a/lib/model/movie_manager.dart b/lib/model/movie_manager.dart index 74be0f5..aa348fd 100644 --- a/lib/model/movie_manager.dart +++ b/lib/model/movie_manager.dart @@ -96,7 +96,8 @@ class MovieManager extends ChangeNotifier { int max = movies.length - 1; while (min - 1 < max) { 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) { max = center - 1; } else { @@ -110,7 +111,9 @@ class MovieManager extends ChangeNotifier { for (int i = 0; i < movies.length; i++) { var temp = movies[i]; 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] = temp; diff --git a/lib/view/movie_item.dart b/lib/view/movie_item.dart index b0005db..bbef14b 100644 --- a/lib/view/movie_item.dart +++ b/lib/view/movie_item.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; import 'package:release_schedule/model/date_format.dart'; import 'package:release_schedule/model/movie.dart'; @@ -9,15 +8,13 @@ class MovieItem extends StatelessWidget { @override Widget build(BuildContext context) { - final format = DateFormat(DateFormat.YEAR_MONTH_DAY); - return AnimatedBuilder( animation: movie, builder: (context, widget) { return ListTile( title: Text(movie.title), subtitle: Text( - "${dateRelativeToNow(movie.releaseDate)}, ${format.format(movie.releaseDate)}")); + "${dateRelativeToNow(movie.releaseDate.date)}, ${movie.releaseDate.toString()}, ${movie.genres?.join(", ") ?? ""}")); }, ); } diff --git a/pubspec.lock b/pubspec.lock index 3b6f4b0..b475779 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile 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: dependency: transitive description: @@ -41,6 +65,30 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: "direct main" description: @@ -65,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -83,6 +139,14 @@ packages: description: flutter source: sdk 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: dependency: transitive description: @@ -99,6 +163,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" http: dependency: "direct main" description: @@ -107,6 +179,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -123,6 +203,22 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -131,6 +227,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: @@ -155,6 +259,30 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -227,11 +355,75 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: flutter source: sdk 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: dependency: transitive description: @@ -272,6 +464,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -280,6 +480,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -296,6 +504,22 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -304,6 +528,22 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -320,6 +560,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.1.3 <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index ae43aa3..9500c7a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: get_storage: ^2.1.1 dev_dependencies: + test: flutter_test: sdk: flutter