Add local caching of movies
parent
91e18b279d
commit
a977ad1f34
|
@ -1,7 +1,7 @@
|
||||||
import 'package:release_schedule/model/movie.dart';
|
import 'package:release_schedule/model/movie.dart';
|
||||||
|
|
||||||
abstract class MovieApi<CustomMovieData extends MovieData> {
|
abstract class MovieApi {
|
||||||
Future<List<CustomMovieData>> getUpcomingMovies([int count]);
|
Future<List<MovieData>> getUpcomingMovies([int count]);
|
||||||
Future<List<CustomMovieData>> searchForMovies(String searchTerm);
|
Future<List<MovieData>> searchForMovies(String searchTerm);
|
||||||
Future<void> addMovieDetails(List<CustomMovieData> movies);
|
Future<void> addMovieDetails(List<MovieData> movies);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,19 @@ class WikidataMovieData extends MovieData {
|
||||||
WikidataMovieData(String title, DateTime releaseDate, this.entityId)
|
WikidataMovieData(String title, DateTime releaseDate, this.entityId)
|
||||||
: super(title, releaseDate);
|
: super(title, releaseDate);
|
||||||
|
|
||||||
|
WikidataMovieData.fromEncodable(Map encodable)
|
||||||
|
: entityId = encodable["entityId"],
|
||||||
|
super.fromJsonEncodable(encodable);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool same(MovieData other) {
|
bool same(MovieData other) {
|
||||||
return other is WikidataMovieData && entityId == other.entityId;
|
return other is WikidataMovieData && entityId == other.entityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map toJsonEncodable() {
|
||||||
|
return super.toJsonEncodable()..addAll({"entityId": entityId});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String createUpcomingMovieQuery(int limit) {
|
String createUpcomingMovieQuery(int limit) {
|
||||||
|
@ -35,13 +44,13 @@ ORDER BY ?minReleaseDate
|
||||||
LIMIT $limit""";
|
LIMIT $limit""";
|
||||||
}
|
}
|
||||||
|
|
||||||
class WikidataMovieApi implements MovieApi<WikidataMovieData> {
|
class WikidataMovieApi implements MovieApi {
|
||||||
ApiManager searchApi = ApiManager("https://www.wikidata.org/w/api.php");
|
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");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> addMovieDetails(List<WikidataMovieData> movies) {
|
Future<void> addMovieDetails(List<MovieData> movies) {
|
||||||
// TODO: implement addMovieDetails
|
// TODO: implement addMovieDetails
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,10 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:release_schedule/api/movie_api.dart';
|
import 'package:release_schedule/api/movie_api.dart';
|
||||||
import 'package:release_schedule/api/wikidata_movie_api.dart';
|
import 'package:release_schedule/api/wikidata_movie_api.dart';
|
||||||
import 'package:release_schedule/model/movie_manager.dart';
|
import 'package:release_schedule/model/movie_manager.dart';
|
||||||
import 'package:release_schedule/view/movie_list.dart';
|
import 'package:release_schedule/view/movie_manager_list.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
movieManager.loadUpcomingMovies();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
|
@ -22,33 +21,25 @@ class MyApp extends StatelessWidget {
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: HomePage(),
|
home: HomePage(movieManager),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HomePage extends StatelessWidget {
|
class HomePage extends StatelessWidget {
|
||||||
final MovieApi api = WikidataMovieApi();
|
final MovieApi api = WikidataMovieApi();
|
||||||
|
final MovieManager manager;
|
||||||
|
|
||||||
HomePage({super.key});
|
HomePage(this.manager, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text("Release Schedule")),
|
appBar: AppBar(title: const Text("Release Schedule")),
|
||||||
body: AnimatedBuilder(
|
body: MovieManagerList(manager),
|
||||||
animation: movieManager,
|
floatingActionButton: FloatingActionButton(
|
||||||
// future: api.getUpcomingMovies(),
|
child: const Icon(Icons.refresh),
|
||||||
builder: (context, widget) {
|
onPressed: () => manager.loadUpcomingMovies(),
|
||||||
return MovieList(movieManager.movies);
|
|
||||||
// var data = snapshot.data;
|
|
||||||
// if (snapshot.hasData && data != null) {
|
|
||||||
// return MovieList(data);
|
|
||||||
// } else if (snapshot.hasError) {
|
|
||||||
// return ErrorWidget(snapshot.error ?? "Something went wrong");
|
|
||||||
// }
|
|
||||||
// return const Center(child: CircularProgressIndicator());
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:get_storage/get_storage.dart';
|
||||||
|
import 'package:release_schedule/model/movie.dart';
|
||||||
|
|
||||||
|
class LocalMovieStorage {
|
||||||
|
List<MovieData> _storedMovies = [];
|
||||||
|
update(List<MovieData> movies) {
|
||||||
|
_storedMovies = movies;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<MovieData>> retrieve() async {
|
||||||
|
return _storedMovies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalMovieStorageGetStorage extends LocalMovieStorage {
|
||||||
|
Future<void>? initialized;
|
||||||
|
GetStorage? container;
|
||||||
|
MovieData Function(Map jsonEncodable) toMovieData;
|
||||||
|
|
||||||
|
LocalMovieStorageGetStorage(this.toMovieData) {
|
||||||
|
initialized = _init();
|
||||||
|
}
|
||||||
|
_init() async {
|
||||||
|
await GetStorage.init("movies");
|
||||||
|
container = GetStorage("movies");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
update(List<MovieData> movies) async {
|
||||||
|
await initialized;
|
||||||
|
container!.write(
|
||||||
|
"movies", movies.map((movie) => movie.toJsonEncodable()).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<MovieData>> retrieve() async {
|
||||||
|
await initialized;
|
||||||
|
dynamic movies = container!.read("movies");
|
||||||
|
if (movies == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return (movies as List<dynamic>)
|
||||||
|
.map((encodable) => toMovieData(encodable))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,19 +7,41 @@ class Review {
|
||||||
int count;
|
int count;
|
||||||
|
|
||||||
Review(this.score, this.by, this.asOf, this.count);
|
Review(this.score, this.by, this.asOf, this.count);
|
||||||
|
Review.fromJsonEncodable(Map json)
|
||||||
|
: score = json["score"],
|
||||||
|
by = json["by"],
|
||||||
|
asOf = DateTime.parse(json["asOf"]),
|
||||||
|
count = json["count"];
|
||||||
|
|
||||||
|
Map toJsonEncodable() {
|
||||||
|
return {
|
||||||
|
"score": score,
|
||||||
|
"by": by,
|
||||||
|
"asOf": asOf.toIso8601String(),
|
||||||
|
"count": count,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef ReleaseDateInCountry = (String country, DateTime date);
|
typedef ReleaseDateInCountry = (String country, DateTime date);
|
||||||
typedef TitleInCountry = (String country, String title);
|
typedef TitleInCountry = (String country, String title);
|
||||||
|
|
||||||
class MovieData extends ChangeNotifier {
|
class MovieData extends ChangeNotifier {
|
||||||
final String title;
|
String _title;
|
||||||
final DateTime releaseDate;
|
DateTime _releaseDate;
|
||||||
bool _hasDetails = false;
|
bool _hasDetails = false;
|
||||||
List<ReleaseDateInCountry> _releaseDates = [];
|
List<ReleaseDateInCountry>? _releaseDates;
|
||||||
List<String> _genres = [];
|
List<String>? _genres;
|
||||||
List<TitleInCountry> _titles = [];
|
List<TitleInCountry>? _titles;
|
||||||
List<Review> _reviews = [];
|
List<Review>? _reviews;
|
||||||
|
|
||||||
|
String get title {
|
||||||
|
return _title;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime get releaseDate {
|
||||||
|
return _releaseDate;
|
||||||
|
}
|
||||||
|
|
||||||
List<ReleaseDateInCountry>? get releaseDates {
|
List<ReleaseDateInCountry>? get releaseDates {
|
||||||
return _releaseDates;
|
return _releaseDates;
|
||||||
|
@ -41,6 +63,14 @@ class MovieData extends ChangeNotifier {
|
||||||
return _hasDetails;
|
return _hasDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateWithNew(MovieData movie) {
|
||||||
|
setDetails(
|
||||||
|
releaseDates: movie.releaseDates,
|
||||||
|
genres: movie.genres,
|
||||||
|
titles: movie.titles,
|
||||||
|
reviews: movie.reviews);
|
||||||
|
}
|
||||||
|
|
||||||
void setDetails(
|
void setDetails(
|
||||||
{List<ReleaseDateInCountry>? releaseDates,
|
{List<ReleaseDateInCountry>? releaseDates,
|
||||||
List<String>? genres,
|
List<String>? genres,
|
||||||
|
@ -64,12 +94,49 @@ class MovieData extends ChangeNotifier {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "$title (${releaseDate.year}${_genres.isNotEmpty ? "; ${_genres.join(", ")}" : ""})";
|
return "$title (${releaseDate.year}${_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();
|
||||||
|
return {
|
||||||
|
"title": title,
|
||||||
|
"releaseDate": releaseDate.toIso8601String(),
|
||||||
|
"releaseDates": releaseDatesByCountry,
|
||||||
|
"genres": genres,
|
||||||
|
"titles": titlesByCountry,
|
||||||
|
"reviews": reviews,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool same(MovieData other) {
|
bool same(MovieData other) {
|
||||||
return title == other.title && releaseDate == other.releaseDate;
|
return title == other.title && releaseDate == other.releaseDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
MovieData(this.title, this.releaseDate);
|
MovieData(this._title, this._releaseDate);
|
||||||
|
|
||||||
|
MovieData.fromJsonEncodable(Map json)
|
||||||
|
: _title = json["title"],
|
||||||
|
_releaseDate = DateTime.parse(json["releaseDate"]) {
|
||||||
|
setDetails(
|
||||||
|
genres: json["genres"],
|
||||||
|
releaseDates: json["releaseDates"] != null
|
||||||
|
? (json["releaseDates"] as List<List<dynamic>>)
|
||||||
|
.map((release) => ((release[0], DateTime.parse(release[1]))
|
||||||
|
as ReleaseDateInCountry))
|
||||||
|
.toList()
|
||||||
|
: null,
|
||||||
|
reviews: json["reviews"] != null
|
||||||
|
? (json["reviews"] as List<Map<String, dynamic>>)
|
||||||
|
.map((review) => Review.fromJsonEncodable(review))
|
||||||
|
.toList()
|
||||||
|
: null,
|
||||||
|
titles: json["titles"] != null
|
||||||
|
? (json["titles"] as List<dynamic>)
|
||||||
|
.map((title) => (title[0], title[1]) as TitleInCountry)
|
||||||
|
.toList()
|
||||||
|
: null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:release_schedule/api/movie_api.dart';
|
import 'package:release_schedule/api/movie_api.dart';
|
||||||
import 'package:release_schedule/api/wikidata_movie_api.dart';
|
import 'package:release_schedule/api/wikidata_movie_api.dart';
|
||||||
|
import 'package:release_schedule/model/local_movie_storage.dart';
|
||||||
import 'package:release_schedule/model/movie.dart';
|
import 'package:release_schedule/model/movie.dart';
|
||||||
|
|
||||||
T? firstWhereOrNull<T>(List<T> list, bool Function(T element) test) {
|
T? firstWhereOrNull<T>(List<T> list, bool Function(T element) test) {
|
||||||
|
@ -11,22 +14,69 @@ T? firstWhereOrNull<T>(List<T> list, bool Function(T element) test) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final movieManager = MovieManager(WikidataMovieApi());
|
class DelayedFunctionCaller {
|
||||||
|
final Function function;
|
||||||
|
final Duration duration;
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
class MovieManager<CustomMovieData extends MovieData> extends ChangeNotifier {
|
DelayedFunctionCaller(this.function, this.duration);
|
||||||
final List<CustomMovieData> movies = List.empty(growable: true);
|
|
||||||
final MovieApi<CustomMovieData> api;
|
|
||||||
|
|
||||||
MovieManager(this.api);
|
void call() {
|
||||||
|
// If a timer is already active, return.
|
||||||
|
if (_timer != null && _timer!.isActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<CustomMovieData> addMovies(List<CustomMovieData> additionalMovies) {
|
// Create a timer that calls the function after the specified duration.
|
||||||
List<CustomMovieData> actualMovies = [];
|
_timer = Timer(duration, () {
|
||||||
|
function();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final movieManager = MovieManager(WikidataMovieApi(),
|
||||||
|
LocalMovieStorageGetStorage(WikidataMovieData.fromEncodable));
|
||||||
|
|
||||||
|
class MovieManager extends ChangeNotifier {
|
||||||
|
final List<MovieData> movies = List.empty(growable: true);
|
||||||
|
final LocalMovieStorage cache;
|
||||||
|
final MovieApi api;
|
||||||
|
bool loading = false;
|
||||||
|
DelayedFunctionCaller? cacheUpdater;
|
||||||
|
bool cacheLoaded = false;
|
||||||
|
|
||||||
|
MovieManager(this.api, this.cache) {
|
||||||
|
cacheUpdater = DelayedFunctionCaller(() {
|
||||||
|
cache.update(movies);
|
||||||
|
}, const Duration(seconds: 3));
|
||||||
|
|
||||||
|
_loadCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadCache() async {
|
||||||
|
addMovies(await cache.retrieve());
|
||||||
|
}
|
||||||
|
|
||||||
|
_moviesModified({bool withoutAddingOrRemoving = false}) {
|
||||||
|
cacheUpdater?.call();
|
||||||
|
if (!withoutAddingOrRemoving) {
|
||||||
|
// only notify listeners if movies are added or removed
|
||||||
|
// if they are modified in place they will notify listeners themselves
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MovieData> addMovies(List<MovieData> additionalMovies) {
|
||||||
|
List<MovieData> actualMovies = [];
|
||||||
bool added = false;
|
bool added = false;
|
||||||
for (var movie in additionalMovies) {
|
for (var movie in additionalMovies) {
|
||||||
CustomMovieData? existing =
|
MovieData? existing =
|
||||||
firstWhereOrNull(movies, (element) => movie.same(element));
|
firstWhereOrNull(movies, (element) => movie.same(element));
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
movies.add(movie);
|
movies.add(movie);
|
||||||
|
movie.addListener(() {
|
||||||
|
_moviesModified(withoutAddingOrRemoving: true);
|
||||||
|
});
|
||||||
added = true;
|
added = true;
|
||||||
actualMovies.add(movie);
|
actualMovies.add(movie);
|
||||||
} else {
|
} else {
|
||||||
|
@ -34,27 +84,48 @@ class MovieManager<CustomMovieData extends MovieData> extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (added) {
|
if (added) {
|
||||||
notifyListeners();
|
_moviesModified();
|
||||||
}
|
}
|
||||||
return actualMovies;
|
return actualMovies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeMoviesWhere(bool Function(MovieData movie) test) {
|
||||||
|
bool removedMovies = false;
|
||||||
|
for (int i = movies.length - 1; i >= 0; i--) {
|
||||||
|
bool remove = test(movies[i]);
|
||||||
|
if (remove) {
|
||||||
|
removedMovies = true;
|
||||||
|
movies.removeAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removedMovies) {
|
||||||
|
_moviesModified();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Only search locally cached movies.
|
/// Only search locally cached movies.
|
||||||
localSearch(String search) {}
|
localSearch(String search) {}
|
||||||
|
|
||||||
/// Online search for movies.
|
/// Online search for movies.
|
||||||
Future<List<CustomMovieData>> search(String search) async {
|
Future<List<MovieData>> search(String search) async {
|
||||||
List<CustomMovieData> movies = await api.searchForMovies(search);
|
List<MovieData> movies = await api.searchForMovies(search);
|
||||||
return addMovies(movies);
|
return addMovies(movies);
|
||||||
}
|
}
|
||||||
|
|
||||||
expandDetails(List<CustomMovieData> movies) {
|
expandDetails(List<MovieData> movies) {
|
||||||
api.addMovieDetails(movies);
|
api.addMovieDetails(movies);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadUpcomingMovies() async {
|
loadUpcomingMovies() async {
|
||||||
List<CustomMovieData> movies = await api.getUpcomingMovies();
|
try {
|
||||||
addMovies(movies);
|
loading = true;
|
||||||
|
notifyListeners();
|
||||||
|
List<MovieData> movies = await api.getUpcomingMovies();
|
||||||
|
addMovies(movies);
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,8 @@ String durationApproximatedInWords(Duration duration) {
|
||||||
String durationToRelativeTimeString(Duration duration) {
|
String durationToRelativeTimeString(Duration duration) {
|
||||||
if (duration.isNegative) {
|
if (duration.isNegative) {
|
||||||
return "${durationApproximatedInWords(-duration)} ago";
|
return "${durationApproximatedInWords(-duration)} ago";
|
||||||
|
} else if (duration == Duration.zero) {
|
||||||
|
return "now";
|
||||||
} else {
|
} else {
|
||||||
return "in ${durationApproximatedInWords(duration)}";
|
return "in ${durationApproximatedInWords(duration)}";
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:release_schedule/model/movie_manager.dart';
|
||||||
|
import 'package:release_schedule/view/movie_list.dart';
|
||||||
|
|
||||||
|
class MovieManagerList extends StatelessWidget {
|
||||||
|
final MovieManager manager;
|
||||||
|
const MovieManagerList(this.manager, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: manager,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
manager.loading ? const LinearProgressIndicator() : Container(),
|
||||||
|
Expanded(child: MovieList(manager.movies))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,8 @@
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import path_provider_foundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
}
|
}
|
||||||
|
|
105
pubspec.lock
105
pubspec.lock
|
@ -57,6 +57,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -75,6 +83,22 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
get:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: get
|
||||||
|
sha256: e4e7335ede17452b391ed3b2ede016545706c01a02292a6c97619705e7d2a85e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.6.6"
|
||||||
|
get_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: get_storage
|
||||||
|
sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -139,6 +163,70 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.3"
|
version: "1.8.3"
|
||||||
|
path_provider:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.6"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -216,5 +304,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4-beta"
|
version: "0.1.4-beta"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.9"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.1.3 <4.0.0"
|
dart: ">=3.1.3 <4.0.0"
|
||||||
|
flutter: ">=3.13.0"
|
||||||
|
|
|
@ -37,6 +37,7 @@ dependencies:
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
http: ^1.1.0
|
http: ^1.1.0
|
||||||
intl: ^0.18.1
|
intl: ^0.18.1
|
||||||
|
get_storage: ^2.1.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in New Issue