diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1853912 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.ipynb inguist-detectable=false diff --git a/.gitignore b/.gitignore index b5445c2..75d9de4 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,3 @@ app.*.map.json # Exclude sensitive information lib/data/sensitive.dart - -# Exclude tensorflow lite model files -*.tflite diff --git a/README.md b/README.md index 09f0bd6..c63e332 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ -# modern_art_app +# Mobile app for the Cypriot National Gallery of Modern Art -A Flutter application for the Cypriot National Gallery of Modern Art. +The repository contains the source code for a cross-platform mobile app (built using Flutter) for +the [Cypriot National Gallery of Modern Art](https://www.nicosia.org.cy/en-GB/discover/picture-galleries/state-gallery-of-contemporary-art/) +in Nicosia, Cyprus. + +The app was built by the [BIO-SCENT](https://bioscent.cyens.org.cy/) research group at +the [CYENS Centre of Excellence](https://www.cyens.org.cy), as part of the study _"A systematic +approach for developing a robust artwork recognition framework using smartphone cameras"_ (currently +under review). + +The app is able to automatically identify a number of artworks at the Gallery through the use of +computer vision, and display details about them to the user. The artwork identification is performed +on-device, using a Convolutional Neural Network (CNN) that trained using transfer learning on +MobileNet V2. + +![App screenshots](assets/app_screenshots.png) diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..13d7ec3 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,24 @@ +# This file configures the analyzer to use the lint rule set from `package:lint` + +# For apps, use the default set +include: package:lint/analysis_options.yaml + +# Packages, that may be distributed (i.e. via pub.dev) should use the package +# version, resulting in a better pub score. +# include: package:lint/analysis_options_package.yaml + +# You might want to exclude auto-generated files from dart analysis +analyzer: + exclude: + #- '**.freezed.dart' + +# A list of all rules can be found at https://dart-lang.github.io/linter/lints/options/options.html +linter: + rules: + # avoid_classes_with_only_static_members: false + + # Make constructors the first thing in every class + sort_constructors_first: true + + # Choose wisely, but you don't have to + prefer_single_quotes: true \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d419a06..1470d03 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -23,19 +23,6 @@ the Android process has started. This theme is visible to the user while the Flutter UI initializes. After that, this theme continues to determine the Window background behind the Flutter UI. --> - - - diff --git a/assets/CHANGELOG.md b/assets/CHANGELOG.md index 5f9f6bc..482aee5 100644 --- a/assets/CHANGELOG.md +++ b/assets/CHANGELOG.md @@ -1,3 +1,7 @@ +## Version 1.0.0 (2021-12-21) +- App migrated to null-safety +- Many UI improvements and bug fixes + ## Version 0.12.2 (2021-02-17) - Minor UI improvements - New tile in settings with link to the Gallery's website diff --git a/assets/app_screenshots.png b/assets/app_screenshots.png new file mode 100644 index 0000000..e5c81c1 Binary files /dev/null and b/assets/app_screenshots.png differ diff --git a/assets/tflite/MobNetNoArt500Frames_4.tflite b/assets/tflite/MobNetNoArt500Frames_4.tflite new file mode 100644 index 0000000..d81648b Binary files /dev/null and b/assets/tflite/MobNetNoArt500Frames_4.tflite differ diff --git a/lib/data/artists_dao.dart b/lib/data/artists_dao.dart index ee6fe7a..2310a73 100644 --- a/lib/data/artists_dao.dart +++ b/lib/data/artists_dao.dart @@ -13,34 +13,42 @@ class ArtistsDao extends DatabaseAccessor with _$ArtistsDaoMixin { into(artists).insertOnConflictUpdate(artist); /// Gets a stream of all artists in the db. - Stream> watchAllArtists({String languageCode = "en"}) => - (select(artists).join([ - leftOuterJoin( - artistTranslations, artistTranslations.id.equalsExp(artists.id)) - ]) - ..where(artistTranslations.languageCode.equals(languageCode))) - .map((e) { - Artist artist = e.readTable(artists); - return artist.copyWith( + Stream> watchAllArtists({String languageCode = 'en'}) => + (select(artists).join( + [ + leftOuterJoin( + artistTranslations, + artistTranslations.id.equalsExp(artists.id), + ) + ], + )..where(artistTranslations.languageCode.equals(languageCode))) + .map( + (e) { + final Artist artist = e.readTable(artists); + return artist.copyWith( name: e.readTable(artistTranslations).name, - biography: e.readTable(artistTranslations).biography); - }).watch(); + biography: e.readTable(artistTranslations).biography, + ); + }, + ).watch(); /// Get artist by id. Future getArtistById({ - @required String artistId, - String languageCode = "en", + required String artistId, + String languageCode = 'en', }) { return ((select(artists)..where((tbl) => tbl.id.equals(artistId))).join( [ leftOuterJoin( - artistTranslations, artistTranslations.id.equalsExp(artists.id)) + artistTranslations, + artistTranslations.id.equalsExp(artists.id), + ) ], )..where(artistTranslations.languageCode.equals(languageCode))) .map( (e) { - Artist artist = e.readTable(artists); - ArtistTranslation translation = e.readTable(artistTranslations); + final Artist artist = e.readTable(artists); + final ArtistTranslation translation = e.readTable(artistTranslations); return artist.copyWith( name: translation.name, biography: translation.biography, diff --git a/lib/data/artworks_dao.dart b/lib/data/artworks_dao.dart index 331a602..fce2524 100644 --- a/lib/data/artworks_dao.dart +++ b/lib/data/artworks_dao.dart @@ -14,58 +14,87 @@ class ArtworksDao extends DatabaseAccessor /// Gets a live stream of all artworks in the db (automatically emits new items /// whenever the underlying data changes). - Stream> watchAllArtworks({String languageCode = "en"}) => + Stream> watchAllArtworks({String languageCode = 'en'}) => (select(artworks).join([ leftOuterJoin( - artworkTranslations, artworkTranslations.id.equalsExp(artworks.id)), - leftOuterJoin(artistTranslations, - artistTranslations.id.equalsExp(artworks.artistId)) + artworkTranslations, + artworkTranslations.id.equalsExp(artworks.id), + ), + leftOuterJoin( + artistTranslations, + artistTranslations.id.equalsExp(artworks.artistId), + ), ]) - ..where(artworkTranslations.languageCode.equals(languageCode) & - artistTranslations.languageCode.equals(languageCode))) - .map((e) => composeTranslatedArtwork( + ..where( + artworkTranslations.languageCode.equals(languageCode) & + artistTranslations.languageCode.equals(languageCode), + )) + .map( + (e) => composeTranslatedArtwork( artwork: e.readTable(artworks), artworkTranslation: e.readTable(artworkTranslations), - artistTranslation: e.readTable(artistTranslations))) + artistTranslation: e.readTable(artistTranslations), + ), + ) .watch(); /// Gets a list of all [Artwork]s for a given [Artist]. Stream> watchArtworksByArtist({ - @required String artistId, - String languageCode = "en", + required String artistId, + String languageCode = 'en', }) => ((select(artworks)..where((artwork) => artwork.artistId.equals(artistId))) - .join([ - leftOuterJoin( - artworkTranslations, artworkTranslations.id.equalsExp(artworks.id)), - leftOuterJoin(artistTranslations, - artistTranslations.id.equalsExp(artworks.artistId)) - ]) - ..where(artworkTranslations.languageCode.equals(languageCode) & - artistTranslations.languageCode.equals(languageCode))) - .map((e) => composeTranslatedArtwork( + .join( + [ + leftOuterJoin( + artworkTranslations, + artworkTranslations.id.equalsExp(artworks.id), + ), + leftOuterJoin( + artistTranslations, + artistTranslations.id.equalsExp(artworks.artistId), + ), + ], + )..where( + artworkTranslations.languageCode.equals(languageCode) & + artistTranslations.languageCode.equals(languageCode), + )) + .map( + (e) => composeTranslatedArtwork( artwork: e.readTable(artworks), artworkTranslation: e.readTable(artworkTranslations), - artistTranslation: e.readTable(artistTranslations))) + artistTranslation: e.readTable(artistTranslations), + ), + ) .watch(); /// Get artwork by id. Future getArtworkById({ - @required String artworkId, - String languageCode = "en", + required String artworkId, + String languageCode = 'en', }) => - ((select(artworks)..where((tbl) => tbl.id.equals(artworkId))).join([ - leftOuterJoin( - artworkTranslations, artworkTranslations.id.equalsExp(artworks.id)), - leftOuterJoin(artistTranslations, - artistTranslations.id.equalsExp(artworks.artistId)) - ]) - ..where(artworkTranslations.languageCode.equals(languageCode) & - artistTranslations.languageCode.equals(languageCode))) - .map((e) => composeTranslatedArtwork( + ((select(artworks)..where((tbl) => tbl.id.equals(artworkId))).join( + [ + leftOuterJoin( + artworkTranslations, + artworkTranslations.id.equalsExp(artworks.id), + ), + leftOuterJoin( + artistTranslations, + artistTranslations.id.equalsExp(artworks.artistId), + ), + ], + )..where( + artworkTranslations.languageCode.equals(languageCode) & + artistTranslations.languageCode.equals(languageCode), + )) + .map( + (e) => composeTranslatedArtwork( artwork: e.readTable(artworks), artworkTranslation: e.readTable(artworkTranslations), - artistTranslation: e.readTable(artistTranslations))) + artistTranslation: e.readTable(artistTranslations), + ), + ) .getSingle(); /// Safely insert an [Artwork] into db, with the use of an [ArtworksCompanion]. @@ -80,11 +109,12 @@ class ArtworksDao extends DatabaseAccessor } Artwork composeTranslatedArtwork({ - Artwork artwork, - ArtworkTranslation artworkTranslation, - ArtistTranslation artistTranslation, + required Artwork artwork, + required ArtworkTranslation artworkTranslation, + required ArtistTranslation artistTranslation, }) => artwork.copyWith( - name: artworkTranslation.name, - description: artworkTranslation.description, - artist: artistTranslation.name); + name: artworkTranslation.name, + description: artworkTranslation.description, + artist: artistTranslation.name, + ); diff --git a/lib/data/data_processing.dart b/lib/data/data_processing.dart index 145d8ed..4896a83 100644 --- a/lib/data/data_processing.dart +++ b/lib/data/data_processing.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import 'package:modern_art_app/data/artists_dao.dart'; @@ -11,10 +12,10 @@ import 'package:modern_art_app/data/url_data_sources.dart'; /// Returns a list with the paths of all asset files; the [assetType] argument /// can optionally be used to get assets only from specific subdirectories in /// assets. -Future> getAllAssets({String assetType = "assets"}) async { - final assetManifest = await rootBundle.loadString("AssetManifest.json"); - final Map assetMap = json.decode(assetManifest); -// await Future.delayed(Duration(seconds: 1)); +Future> getAllAssets({String assetType = 'assets'}) async { + final assetManifest = await rootBundle.loadString('AssetManifest.json'); + final Map assetMap = + json.decode(assetManifest) as Map; return assetMap.keys .where((String key) => key.contains(assetType.toLowerCase())) .toList(); @@ -23,67 +24,72 @@ Future> getAllAssets({String assetType = "assets"}) async { /// Reads the provided json file at [assetsPath] and returns a future list of map /// item entries (the entries must be processed with [parseItemMap] first to be /// valid db entities). -Future> getLocalJsonItemList(String assetsPath) async { - return rootBundle - .loadString(assetsPath) - .then((jsonStr) => List.from(json.decode(jsonStr)["feed"]["entry"])); -} +Future> getLocalJsonItemList(String assetsPath) async => + rootBundle.loadString(assetsPath).then( + (jsonStr) => + List.from(json.decode(jsonStr)['feed']['entry'] as Iterable), + ); /// Gets and parses the json entry at the provided [url] and returns a future /// list of map item entries (the entries must be processed with [parseItemMap] /// first to be valid db entities). Future> getRemoteJsonItemList(String url) async { - var itemsJson = await http.get(url); + final itemsJson = await http.get(Uri.parse(url)); if (itemsJson.statusCode == 200) { - Map body = json.decode(itemsJson.body); - return List.from(body["feed"]["entry"]); + final Map body = json.decode(itemsJson.body) as Map; + return List.from(body['feed']['entry'] as Iterable); } else { throw HttpException( - "Error getting remote json: statusCode ${itemsJson.statusCode}"); + 'Error getting remote json: statusCode ${itemsJson.statusCode}', + ); } } /// Updates the app's db from the remote Google spreadsheet. -void updateDbFromGSheets(ArtworksDao artworksDao, ArtistsDao artistsDao) async { +Future updateDbFromGSheets( + ArtworksDao artworksDao, + ArtistsDao artistsDao, +) async { // first update artists - getRemoteJsonItemList(gSheetUrlArtists) - .then((artists) => artists.forEach((entry) { - var artist = Artist.fromJson(parseItemMap(entry)); - artistsDao.upsertArtist(artist); - print("Updated artist ${artist.name}"); - })); + getRemoteJsonItemList(gSheetUrlArtists).then( + (artists) => artists.forEach( + (entry) { + final artist = Artist.fromJson(parseItemMap(entry)); + artistsDao.upsertArtist(artist); + debugPrint('Updated artist ${artist.name}'); + }, + ), + ); // then update artworks - getRemoteJsonItemList(gSheetUrlArtworks) - .then((artworks) => artworks.forEach((entry) { - var artwork = Artwork.fromJson(parseItemMap(entry)); - artworksDao.upsertArtwork(artwork); - print("Updated artwork ${artwork.name}"); - })); + getRemoteJsonItemList(gSheetUrlArtworks).then( + (artworks) => artworks.forEach( + (entry) { + final artwork = Artwork.fromJson(parseItemMap(entry)); + artworksDao.upsertArtwork(artwork); + debugPrint('Updated artwork ${artwork.name}'); + }, + ), + ); } /// Extracts the necessary fields from each [mapItem] and discards the excess /// information; the returned map objects can be used as input data to create /// [Artist] or [Artwork] objects with .fromJson(). -Map parseItemMap(Map mapItem) => - Map.fromIterable( +Map parseItemMap(Map mapItem) => { // filter keys, only interested in the ones that start with "gsx$" - mapItem.keys.where((k) => k.startsWith("gsx")), - // remove "gsx$" from keys, to match with local data class column names - key: (k) => k.replaceAll("gsx\$", ""), - // get value for key, in the case of id parse it into int first - // value: (k) => - // k == "gsx\$id" ? int.parse(mapItem[k]["\$t"]) : mapItem[k]["\$t"], - value: (k) => mapItem[k]["\$t"], - ); + for (var k in mapItem.keys.where((k) => k.toString().startsWith('gsx'))) + k.toString().replaceAll('gsx\$', ''): mapItem[k]['\$t'] + }; Map parseItemTranslations( - Map item, String languageCode) => - Map.fromIterable( + Map item, + String languageCode, +) => + { // add languageCode here as additional key, so it's included in translations - item.keys.toList()..add("languageCode"), - key: (k) => - k.endsWith("-$languageCode") ? k.replaceAll("-$languageCode", "") : k, // note that key for languageCode must be in all lowercase, in order to // be recognised by the json serializer - value: (k) => k == "languageCode" ? languageCode : item[k], - ); + for (var k in item.keys.toList()..add('languageCode')) + k.endsWith('-$languageCode') ? k.replaceAll('-$languageCode', '') : k: + k == 'languageCode' ? languageCode : item[k] + }; diff --git a/lib/data/database.dart b/lib/data/database.dart index 8487744..4bdacc3 100644 --- a/lib/data/database.dart +++ b/lib/data/database.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:modern_art_app/data/artists_dao.dart'; import 'package:modern_art_app/data/artworks_dao.dart'; import 'package:modern_art_app/data/data_processing.dart'; @@ -12,18 +13,16 @@ import 'package:path_provider/path_provider.dart'; part 'database.g.dart'; -// TODO add supported locales table - // To auto-generate the necessary moor-related code, run the following in the terminal: // 'flutter packages pub run build_runner build' // or the following to continuously regenerate code when code changes // 'flutter packages pub run build_runner watch' /// Path of locally cached json file with artists info. -const String artistsJsonPath = "assets/data/artists.json"; +const String artistsJsonPath = 'assets/data/artists.json'; /// Path of locally cached json file with artworks info. -const String artworksJsonPath = "assets/data/artworks.json"; +const String artworksJsonPath = 'assets/data/artworks.json'; /// Table for [Artwork]s in database. /// @@ -37,24 +36,23 @@ class Artworks extends Table { Set get primaryKey => {id}; // GSheets returns columns keys in all lowercase, so it's necessary to specify - // the JsonKey below. Setting the column title in the spreadsheet as "artist_id", - // for example, does not help either, since GSheets removes the underscore in json - @JsonKey("artistid") + // the JsonKey below. Setting the column title in the spreadsheet as + // "artist_id", for example, does not help either, since GSheets removes the + // underscore in json + @JsonKey('artistid') TextColumn get artistId => - text().customConstraint("NULL REFERENCES artists(id)")(); + text().customConstraint('NULL REFERENCES artists(id)')(); TextColumn get year => text().nullable()(); - // the fields below are specified so they are included in the generated Artwork - // class, but will remain null in the table, and only filled on demand from - // the ArtworkTranslations table, according to the desired locale + // the fields below are specified so they are included in the generated + // Artwork class, but will remain null in the table, and only filled on demand + // from the ArtworkTranslations table, according to the desired locale TextColumn get name => text().nullable()(); TextColumn get description => text().nullable()(); TextColumn get artist => text().nullable()(); - -// TODO add current locale } /// Table for [ArtworkTranslation]s in database. Each [ArtworkTranslation] @@ -63,7 +61,7 @@ class Artworks extends Table { /// translated artwork objects. class ArtworkTranslations extends Table { TextColumn get id => - text().customConstraint("NULL REFERENCES artworks(id)")(); + text().customConstraint('NULL REFERENCES artworks(id)')(); TextColumn get languageCode => text()(); @@ -82,10 +80,10 @@ class Artists extends Table { @override Set get primaryKey => {id}; - @JsonKey("yearbirth") + @JsonKey('yearbirth') TextColumn get yearBirth => text().nullable()(); - @JsonKey("yeardeath") + @JsonKey('yeardeath') TextColumn get yearDeath => text().nullable()(); // the fields below are specified so they are included in the generated Artist @@ -101,7 +99,7 @@ class Artists extends Table { /// locale; these are then joined in database queries to produce fully /// translated artwork objects. class ArtistTranslations extends Table { - TextColumn get id => text().customConstraint("NULL REFERENCES artists(id)")(); + TextColumn get id => text().customConstraint('NULL REFERENCES artists(id)')(); TextColumn get languageCode => text()(); @@ -116,8 +114,9 @@ class ArtistTranslations extends Table { class Viewings extends Table { IntColumn get id => integer().autoIncrement()(); - TextColumn get artworkId => - text().customConstraint("NULL REFERENCES artworks(id)")(); + TextColumn get artworkId => text().customConstraint( + 'NULL REFERENCES artworks(id)', + )(); TextColumn get cnnModelUsed => text().nullable()(); @@ -134,30 +133,31 @@ class Viewings extends Table { LazyDatabase _openConnection() { // the LazyDatabase util lets us find the right location for the file async. - return LazyDatabase(() async { - // put the database file, called db.sqlite here, into the documents folder - // for your app. - final dbFolder = await getApplicationDocumentsDirectory(); - final file = File(p.join(dbFolder.path, 'db.sqlite')); - return VmDatabase(file, logStatements: false); - }); + return LazyDatabase( + () async { + // put the database file, called db.sqlite here, into the documents folder + // for your app. + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, 'db.sqlite')); + return VmDatabase(file); + }, + ); } /// Creates an instance of [AppDatabase] (lazily). /// /// During the first use of [AppDatabase], it is automatically populated from a /// Json file with the necessary information in assets. -@UseMoor(tables: [ - Artworks, - ArtworkTranslations, - Artists, - ArtistTranslations, - Viewings, -], daos: [ - ArtworksDao, - ArtistsDao, - ViewingsDao, -]) +@UseMoor( + tables: [ + Artworks, + ArtworkTranslations, + Artists, + ArtistTranslations, + Viewings, + ], + daos: [ArtworksDao, ArtistsDao, ViewingsDao], +) class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @@ -171,50 +171,61 @@ class AppDatabase extends _$AppDatabase { MigrationStrategy get migration => MigrationStrategy( beforeOpen: (details) async { /// Enables foreign keys in the db. - await customStatement("PRAGMA foreign_keys = ON"); + await customStatement('PRAGMA foreign_keys = ON'); /// When db is first created, populate it from cached json asset files. if (details.wasCreated) { // determine supported locales - var languageCodes = AppLocalizations.languages.keys + final languageCodes = localizedLabels.keys .toList() .map((locale) => locale.languageCode) .toList(); - // TODO make logic into function that accepts generics, since it's the same code repeated twice // populate artists and artistTranslations tables - await getLocalJsonItemList(artistsJsonPath) - .then((artistEntries) => artistEntries.forEach((entry) { - var parsedEntry = parseItemMap(entry); - var artist = Artist.fromJson(parsedEntry); - into(artists).insertOnConflictUpdate(artist); - print("Created entry for artist with id ${artist.id}"); - languageCodes.forEach((languageCode) { - var translatedEntry = ArtistTranslation.fromJson( - parseItemTranslations(parsedEntry, languageCode)); - into(artistTranslations) - .insertOnConflictUpdate(translatedEntry); - print("Created entry for language $languageCode for " - "artist with id ${artist.id}"); - }); - })); + await getLocalJsonItemList(artistsJsonPath).then( + (artistEntries) => artistEntries.forEach( + (entry) { + final parsedEntry = parseItemMap(entry); + final artist = Artist.fromJson(parsedEntry); + into(artists).insertOnConflictUpdate(artist); + debugPrint('Created entry for artist with id ${artist.id}'); + for (final languageCode in languageCodes) { + into(artistTranslations).insertOnConflictUpdate( + ArtistTranslation.fromJson( + parseItemTranslations(parsedEntry, languageCode), + ), + ); + debugPrint( + 'Created entry for language $languageCode for ' + 'artist with id ${artist.id}', + ); + } + }, + ), + ); // populate artworks and artworkTranslations tables - await getLocalJsonItemList(artworksJsonPath) - .then((artworkEntries) => artworkEntries.forEach((entry) { - var parsedEntry = parseItemMap(entry); - var artwork = Artwork.fromJson(parsedEntry); - into(artworks).insertOnConflictUpdate(artwork); - print("Created entry for artwork with id ${artwork.id}"); - languageCodes.forEach((languageCode) { - var translatedEntry = ArtworkTranslation.fromJson( - parseItemTranslations(parsedEntry, languageCode)); - into(artworkTranslations) - .insertOnConflictUpdate(translatedEntry); - print("Created entry for language $languageCode for " - "artwork with id ${artwork.id}"); - }); - })); + await getLocalJsonItemList(artworksJsonPath).then( + (artworkEntries) => artworkEntries.forEach( + (entry) { + final parsedEntry = parseItemMap(entry); + final artwork = Artwork.fromJson(parsedEntry); + into(artworks).insertOnConflictUpdate(artwork); + debugPrint('Created entry for artwork with id ${artwork.id}'); + for (final languageCode in languageCodes) { + into(artworkTranslations).insertOnConflictUpdate( + ArtworkTranslation.fromJson( + parseItemTranslations(parsedEntry, languageCode), + ), + ); + debugPrint( + 'Created entry for language $languageCode for ' + 'artwork with id ${artwork.id}', + ); + } + }, + ), + ); } }, ); diff --git a/lib/data/database.g.dart b/lib/data/database.g.dart index 432e22e..b8dabb3 100644 --- a/lib/data/database.g.dart +++ b/lib/data/database.g.dart @@ -10,63 +10,59 @@ part of 'database.dart'; class Artwork extends DataClass implements Insertable { final String id; final String artistId; - final String year; - final String name; - final String description; - final String artist; + final String? year; + final String? name; + final String? description; + final String? artist; Artwork( - {@required this.id, - @required this.artistId, + {required this.id, + required this.artistId, this.year, this.name, this.description, this.artist}); factory Artwork.fromData(Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); return Artwork( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), - artistId: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}artist_id']), - year: stringType.mapFromDatabaseResponse(data['${effectivePrefix}year']), - name: stringType.mapFromDatabaseResponse(data['${effectivePrefix}name']), - description: stringType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + artistId: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}artist_id'])!, + year: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}year']), + name: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}name']), + description: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}description']), - artist: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}artist']), + artist: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}artist']), ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } - if (!nullToAbsent || artistId != null) { - map['artist_id'] = Variable(artistId); - } + map['id'] = Variable(id); + map['artist_id'] = Variable(artistId); if (!nullToAbsent || year != null) { - map['year'] = Variable(year); + map['year'] = Variable(year); } if (!nullToAbsent || name != null) { - map['name'] = Variable(name); + map['name'] = Variable(name); } if (!nullToAbsent || description != null) { - map['description'] = Variable(description); + map['description'] = Variable(description); } if (!nullToAbsent || artist != null) { - map['artist'] = Variable(artist); + map['artist'] = Variable(artist); } return map; } ArtworksCompanion toCompanion(bool nullToAbsent) { return ArtworksCompanion( - id: id == null && nullToAbsent ? const Value.absent() : Value(id), - artistId: artistId == null && nullToAbsent - ? const Value.absent() - : Value(artistId), + id: Value(id), + artistId: Value(artistId), year: year == null && nullToAbsent ? const Value.absent() : Value(year), name: name == null && nullToAbsent ? const Value.absent() : Value(name), description: description == null && nullToAbsent @@ -78,37 +74,37 @@ class Artwork extends DataClass implements Insertable { } factory Artwork.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return Artwork( id: serializer.fromJson(json['id']), artistId: serializer.fromJson(json['artistid']), - year: serializer.fromJson(json['year']), - name: serializer.fromJson(json['name']), - description: serializer.fromJson(json['description']), - artist: serializer.fromJson(json['artist']), + year: serializer.fromJson(json['year']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + artist: serializer.fromJson(json['artist']), ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), 'artistid': serializer.toJson(artistId), - 'year': serializer.toJson(year), - 'name': serializer.toJson(name), - 'description': serializer.toJson(description), - 'artist': serializer.toJson(artist), + 'year': serializer.toJson(year), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'artist': serializer.toJson(artist), }; } Artwork copyWith( - {String id, - String artistId, - String year, - String name, - String description, - String artist}) => + {String? id, + String? artistId, + String? year, + String? name, + String? description, + String? artist}) => Artwork( id: id ?? this.id, artistId: artistId ?? this.artistId, @@ -131,16 +127,10 @@ class Artwork extends DataClass implements Insertable { } @override - int get hashCode => $mrjf($mrjc( - id.hashCode, - $mrjc( - artistId.hashCode, - $mrjc( - year.hashCode, - $mrjc(name.hashCode, - $mrjc(description.hashCode, artist.hashCode)))))); + int get hashCode => + Object.hash(id, artistId, year, name, description, artist); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is Artwork && other.id == this.id && @@ -154,10 +144,10 @@ class Artwork extends DataClass implements Insertable { class ArtworksCompanion extends UpdateCompanion { final Value id; final Value artistId; - final Value year; - final Value name; - final Value description; - final Value artist; + final Value year; + final Value name; + final Value description; + final Value artist; const ArtworksCompanion({ this.id = const Value.absent(), this.artistId = const Value.absent(), @@ -167,8 +157,8 @@ class ArtworksCompanion extends UpdateCompanion { this.artist = const Value.absent(), }); ArtworksCompanion.insert({ - @required String id, - @required String artistId, + required String id, + required String artistId, this.year = const Value.absent(), this.name = const Value.absent(), this.description = const Value.absent(), @@ -176,12 +166,12 @@ class ArtworksCompanion extends UpdateCompanion { }) : id = Value(id), artistId = Value(artistId); static Insertable custom({ - Expression id, - Expression artistId, - Expression year, - Expression name, - Expression description, - Expression artist, + Expression? id, + Expression? artistId, + Expression? year, + Expression? name, + Expression? description, + Expression? artist, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -194,12 +184,12 @@ class ArtworksCompanion extends UpdateCompanion { } ArtworksCompanion copyWith( - {Value id, - Value artistId, - Value year, - Value name, - Value description, - Value artist}) { + {Value? id, + Value? artistId, + Value? year, + Value? name, + Value? description, + Value? artist}) { return ArtworksCompanion( id: id ?? this.id, artistId: artistId ?? this.artistId, @@ -220,16 +210,16 @@ class ArtworksCompanion extends UpdateCompanion { map['artist_id'] = Variable(artistId.value); } if (year.present) { - map['year'] = Variable(year.value); + map['year'] = Variable(year.value); } if (name.present) { - map['name'] = Variable(name.value); + map['name'] = Variable(name.value); } if (description.present) { - map['description'] = Variable(description.value); + map['description'] = Variable(description.value); } if (artist.present) { - map['artist'] = Variable(artist.value); + map['artist'] = Variable(artist.value); } return map; } @@ -250,121 +240,75 @@ class ArtworksCompanion extends UpdateCompanion { class $ArtworksTable extends Artworks with TableInfo<$ArtworksTable, Artwork> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $ArtworksTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedTextColumn _id; - @override - GeneratedTextColumn get id => _id ??= _constructId(); - GeneratedTextColumn _constructId() { - return GeneratedTextColumn( - 'id', - $tableName, - false, - ); - } - + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _artistIdMeta = const VerificationMeta('artistId'); - GeneratedTextColumn _artistId; - @override - GeneratedTextColumn get artistId => _artistId ??= _constructArtistId(); - GeneratedTextColumn _constructArtistId() { - return GeneratedTextColumn('artist_id', $tableName, false, - $customConstraints: 'NULL REFERENCES artists(id)'); - } - + late final GeneratedColumn artistId = GeneratedColumn( + 'artist_id', aliasedName, false, + typeName: 'TEXT', + requiredDuringInsert: true, + $customConstraints: 'NULL REFERENCES artists(id)'); final VerificationMeta _yearMeta = const VerificationMeta('year'); - GeneratedTextColumn _year; - @override - GeneratedTextColumn get year => _year ??= _constructYear(); - GeneratedTextColumn _constructYear() { - return GeneratedTextColumn( - 'year', - $tableName, - true, - ); - } - + late final GeneratedColumn year = GeneratedColumn( + 'year', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _nameMeta = const VerificationMeta('name'); - GeneratedTextColumn _name; - @override - GeneratedTextColumn get name => _name ??= _constructName(); - GeneratedTextColumn _constructName() { - return GeneratedTextColumn( - 'name', - $tableName, - true, - ); - } - + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _descriptionMeta = const VerificationMeta('description'); - GeneratedTextColumn _description; - @override - GeneratedTextColumn get description => - _description ??= _constructDescription(); - GeneratedTextColumn _constructDescription() { - return GeneratedTextColumn( - 'description', - $tableName, - true, - ); - } - + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _artistMeta = const VerificationMeta('artist'); - GeneratedTextColumn _artist; - @override - GeneratedTextColumn get artist => _artist ??= _constructArtist(); - GeneratedTextColumn _constructArtist() { - return GeneratedTextColumn( - 'artist', - $tableName, - true, - ); - } - + late final GeneratedColumn artist = GeneratedColumn( + 'artist', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); @override List get $columns => [id, artistId, year, name, description, artist]; @override - $ArtworksTable get asDslTable => this; - @override - String get $tableName => _alias ?? 'artworks'; + String get aliasedName => _alias ?? 'artworks'; @override - final String actualTableName = 'artworks'; + String get actualTableName => 'artworks'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } else if (isInserting) { context.missing(_idMeta); } if (data.containsKey('artist_id')) { context.handle(_artistIdMeta, - artistId.isAcceptableOrUnknown(data['artist_id'], _artistIdMeta)); + artistId.isAcceptableOrUnknown(data['artist_id']!, _artistIdMeta)); } else if (isInserting) { context.missing(_artistIdMeta); } if (data.containsKey('year')) { context.handle( - _yearMeta, year.isAcceptableOrUnknown(data['year'], _yearMeta)); + _yearMeta, year.isAcceptableOrUnknown(data['year']!, _yearMeta)); } if (data.containsKey('name')) { context.handle( - _nameMeta, name.isAcceptableOrUnknown(data['name'], _nameMeta)); + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); } if (data.containsKey('description')) { context.handle( _descriptionMeta, description.isAcceptableOrUnknown( - data['description'], _descriptionMeta)); + data['description']!, _descriptionMeta)); } if (data.containsKey('artist')) { context.handle(_artistMeta, - artist.isAcceptableOrUnknown(data['artist'], _artistMeta)); + artist.isAcceptableOrUnknown(data['artist']!, _artistMeta)); } return context; } @@ -372,9 +316,9 @@ class $ArtworksTable extends Artworks with TableInfo<$ArtworksTable, Artwork> { @override Set get $primaryKey => {id}; @override - Artwork map(Map data, {String tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return Artwork.fromData(data, _db, prefix: effectivePrefix); + Artwork map(Map data, {String? tablePrefix}) { + return Artwork.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -388,51 +332,44 @@ class ArtworkTranslation extends DataClass final String id; final String languageCode; final String name; - final String description; + final String? description; ArtworkTranslation( - {@required this.id, - @required this.languageCode, - @required this.name, + {required this.id, + required this.languageCode, + required this.name, this.description}); factory ArtworkTranslation.fromData( Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); return ArtworkTranslation( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), - languageCode: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}language_code']), - name: stringType.mapFromDatabaseResponse(data['${effectivePrefix}name']), - description: stringType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + languageCode: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}language_code'])!, + name: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}name'])!, + description: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}description']), ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } - if (!nullToAbsent || languageCode != null) { - map['language_code'] = Variable(languageCode); - } - if (!nullToAbsent || name != null) { - map['name'] = Variable(name); - } + map['id'] = Variable(id); + map['language_code'] = Variable(languageCode); + map['name'] = Variable(name); if (!nullToAbsent || description != null) { - map['description'] = Variable(description); + map['description'] = Variable(description); } return map; } ArtworkTranslationsCompanion toCompanion(bool nullToAbsent) { return ArtworkTranslationsCompanion( - id: id == null && nullToAbsent ? const Value.absent() : Value(id), - languageCode: languageCode == null && nullToAbsent - ? const Value.absent() - : Value(languageCode), - name: name == null && nullToAbsent ? const Value.absent() : Value(name), + id: Value(id), + languageCode: Value(languageCode), + name: Value(name), description: description == null && nullToAbsent ? const Value.absent() : Value(description), @@ -440,28 +377,31 @@ class ArtworkTranslation extends DataClass } factory ArtworkTranslation.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return ArtworkTranslation( id: serializer.fromJson(json['id']), languageCode: serializer.fromJson(json['languageCode']), name: serializer.fromJson(json['name']), - description: serializer.fromJson(json['description']), + description: serializer.fromJson(json['description']), ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), 'languageCode': serializer.toJson(languageCode), 'name': serializer.toJson(name), - 'description': serializer.toJson(description), + 'description': serializer.toJson(description), }; } ArtworkTranslation copyWith( - {String id, String languageCode, String name, String description}) => + {String? id, + String? languageCode, + String? name, + String? description}) => ArtworkTranslation( id: id ?? this.id, languageCode: languageCode ?? this.languageCode, @@ -480,12 +420,9 @@ class ArtworkTranslation extends DataClass } @override - int get hashCode => $mrjf($mrjc( - id.hashCode, - $mrjc( - languageCode.hashCode, $mrjc(name.hashCode, description.hashCode)))); + int get hashCode => Object.hash(id, languageCode, name, description); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ArtworkTranslation && other.id == this.id && @@ -498,7 +435,7 @@ class ArtworkTranslationsCompanion extends UpdateCompanion { final Value id; final Value languageCode; final Value name; - final Value description; + final Value description; const ArtworkTranslationsCompanion({ this.id = const Value.absent(), this.languageCode = const Value.absent(), @@ -506,18 +443,18 @@ class ArtworkTranslationsCompanion extends UpdateCompanion { this.description = const Value.absent(), }); ArtworkTranslationsCompanion.insert({ - @required String id, - @required String languageCode, - @required String name, + required String id, + required String languageCode, + required String name, this.description = const Value.absent(), }) : id = Value(id), languageCode = Value(languageCode), name = Value(name); static Insertable custom({ - Expression id, - Expression languageCode, - Expression name, - Expression description, + Expression? id, + Expression? languageCode, + Expression? name, + Expression? description, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -528,10 +465,10 @@ class ArtworkTranslationsCompanion extends UpdateCompanion { } ArtworkTranslationsCompanion copyWith( - {Value id, - Value languageCode, - Value name, - Value description}) { + {Value? id, + Value? languageCode, + Value? name, + Value? description}) { return ArtworkTranslationsCompanion( id: id ?? this.id, languageCode: languageCode ?? this.languageCode, @@ -553,7 +490,7 @@ class ArtworkTranslationsCompanion extends UpdateCompanion { map['name'] = Variable(name.value); } if (description.present) { - map['description'] = Variable(description.value); + map['description'] = Variable(description.value); } return map; } @@ -573,72 +510,41 @@ class ArtworkTranslationsCompanion extends UpdateCompanion { class $ArtworkTranslationsTable extends ArtworkTranslations with TableInfo<$ArtworkTranslationsTable, ArtworkTranslation> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $ArtworkTranslationsTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedTextColumn _id; - @override - GeneratedTextColumn get id => _id ??= _constructId(); - GeneratedTextColumn _constructId() { - return GeneratedTextColumn('id', $tableName, false, - $customConstraints: 'NULL REFERENCES artworks(id)'); - } - + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + typeName: 'TEXT', + requiredDuringInsert: true, + $customConstraints: 'NULL REFERENCES artworks(id)'); final VerificationMeta _languageCodeMeta = const VerificationMeta('languageCode'); - GeneratedTextColumn _languageCode; - @override - GeneratedTextColumn get languageCode => - _languageCode ??= _constructLanguageCode(); - GeneratedTextColumn _constructLanguageCode() { - return GeneratedTextColumn( - 'language_code', - $tableName, - false, - ); - } - + late final GeneratedColumn languageCode = GeneratedColumn( + 'language_code', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _nameMeta = const VerificationMeta('name'); - GeneratedTextColumn _name; - @override - GeneratedTextColumn get name => _name ??= _constructName(); - GeneratedTextColumn _constructName() { - return GeneratedTextColumn( - 'name', - $tableName, - false, - ); - } - + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _descriptionMeta = const VerificationMeta('description'); - GeneratedTextColumn _description; - @override - GeneratedTextColumn get description => - _description ??= _constructDescription(); - GeneratedTextColumn _constructDescription() { - return GeneratedTextColumn( - 'description', - $tableName, - true, - ); - } - + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); @override List get $columns => [id, languageCode, name, description]; @override - $ArtworkTranslationsTable get asDslTable => this; + String get aliasedName => _alias ?? 'artwork_translations'; @override - String get $tableName => _alias ?? 'artwork_translations'; - @override - final String actualTableName = 'artwork_translations'; + String get actualTableName => 'artwork_translations'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } else if (isInserting) { context.missing(_idMeta); } @@ -646,13 +552,13 @@ class $ArtworkTranslationsTable extends ArtworkTranslations context.handle( _languageCodeMeta, languageCode.isAcceptableOrUnknown( - data['language_code'], _languageCodeMeta)); + data['language_code']!, _languageCodeMeta)); } else if (isInserting) { context.missing(_languageCodeMeta); } if (data.containsKey('name')) { context.handle( - _nameMeta, name.isAcceptableOrUnknown(data['name'], _nameMeta)); + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); } else if (isInserting) { context.missing(_nameMeta); } @@ -660,7 +566,7 @@ class $ArtworkTranslationsTable extends ArtworkTranslations context.handle( _descriptionMeta, description.isAcceptableOrUnknown( - data['description'], _descriptionMeta)); + data['description']!, _descriptionMeta)); } return context; } @@ -668,9 +574,9 @@ class $ArtworkTranslationsTable extends ArtworkTranslations @override Set get $primaryKey => {id, languageCode}; @override - ArtworkTranslation map(Map data, {String tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return ArtworkTranslation.fromData(data, _db, prefix: effectivePrefix); + ArtworkTranslation map(Map data, {String? tablePrefix}) { + return ArtworkTranslation.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -681,55 +587,54 @@ class $ArtworkTranslationsTable extends ArtworkTranslations class Artist extends DataClass implements Insertable { final String id; - final String yearBirth; - final String yearDeath; - final String name; - final String biography; + final String? yearBirth; + final String? yearDeath; + final String? name; + final String? biography; Artist( - {@required this.id, + {required this.id, this.yearBirth, this.yearDeath, this.name, this.biography}); factory Artist.fromData(Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); return Artist( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), - yearBirth: stringType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + yearBirth: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}year_birth']), - yearDeath: stringType + yearDeath: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}year_death']), - name: stringType.mapFromDatabaseResponse(data['${effectivePrefix}name']), - biography: stringType + name: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}name']), + biography: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}biography']), ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } + map['id'] = Variable(id); if (!nullToAbsent || yearBirth != null) { - map['year_birth'] = Variable(yearBirth); + map['year_birth'] = Variable(yearBirth); } if (!nullToAbsent || yearDeath != null) { - map['year_death'] = Variable(yearDeath); + map['year_death'] = Variable(yearDeath); } if (!nullToAbsent || name != null) { - map['name'] = Variable(name); + map['name'] = Variable(name); } if (!nullToAbsent || biography != null) { - map['biography'] = Variable(biography); + map['biography'] = Variable(biography); } return map; } ArtistsCompanion toCompanion(bool nullToAbsent) { return ArtistsCompanion( - id: id == null && nullToAbsent ? const Value.absent() : Value(id), + id: Value(id), yearBirth: yearBirth == null && nullToAbsent ? const Value.absent() : Value(yearBirth), @@ -744,34 +649,34 @@ class Artist extends DataClass implements Insertable { } factory Artist.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return Artist( id: serializer.fromJson(json['id']), - yearBirth: serializer.fromJson(json['yearbirth']), - yearDeath: serializer.fromJson(json['yeardeath']), - name: serializer.fromJson(json['name']), - biography: serializer.fromJson(json['biography']), + yearBirth: serializer.fromJson(json['yearbirth']), + yearDeath: serializer.fromJson(json['yeardeath']), + name: serializer.fromJson(json['name']), + biography: serializer.fromJson(json['biography']), ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'yearbirth': serializer.toJson(yearBirth), - 'yeardeath': serializer.toJson(yearDeath), - 'name': serializer.toJson(name), - 'biography': serializer.toJson(biography), + 'yearbirth': serializer.toJson(yearBirth), + 'yeardeath': serializer.toJson(yearDeath), + 'name': serializer.toJson(name), + 'biography': serializer.toJson(biography), }; } Artist copyWith( - {String id, - String yearBirth, - String yearDeath, - String name, - String biography}) => + {String? id, + String? yearBirth, + String? yearDeath, + String? name, + String? biography}) => Artist( id: id ?? this.id, yearBirth: yearBirth ?? this.yearBirth, @@ -792,14 +697,9 @@ class Artist extends DataClass implements Insertable { } @override - int get hashCode => $mrjf($mrjc( - id.hashCode, - $mrjc( - yearBirth.hashCode, - $mrjc( - yearDeath.hashCode, $mrjc(name.hashCode, biography.hashCode))))); + int get hashCode => Object.hash(id, yearBirth, yearDeath, name, biography); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is Artist && other.id == this.id && @@ -811,10 +711,10 @@ class Artist extends DataClass implements Insertable { class ArtistsCompanion extends UpdateCompanion { final Value id; - final Value yearBirth; - final Value yearDeath; - final Value name; - final Value biography; + final Value yearBirth; + final Value yearDeath; + final Value name; + final Value biography; const ArtistsCompanion({ this.id = const Value.absent(), this.yearBirth = const Value.absent(), @@ -823,18 +723,18 @@ class ArtistsCompanion extends UpdateCompanion { this.biography = const Value.absent(), }); ArtistsCompanion.insert({ - @required String id, + required String id, this.yearBirth = const Value.absent(), this.yearDeath = const Value.absent(), this.name = const Value.absent(), this.biography = const Value.absent(), }) : id = Value(id); static Insertable custom({ - Expression id, - Expression yearBirth, - Expression yearDeath, - Expression name, - Expression biography, + Expression? id, + Expression? yearBirth, + Expression? yearDeath, + Expression? name, + Expression? biography, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -846,11 +746,11 @@ class ArtistsCompanion extends UpdateCompanion { } ArtistsCompanion copyWith( - {Value id, - Value yearBirth, - Value yearDeath, - Value name, - Value biography}) { + {Value? id, + Value? yearBirth, + Value? yearDeath, + Value? name, + Value? biography}) { return ArtistsCompanion( id: id ?? this.id, yearBirth: yearBirth ?? this.yearBirth, @@ -867,16 +767,16 @@ class ArtistsCompanion extends UpdateCompanion { map['id'] = Variable(id.value); } if (yearBirth.present) { - map['year_birth'] = Variable(yearBirth.value); + map['year_birth'] = Variable(yearBirth.value); } if (yearDeath.present) { - map['year_death'] = Variable(yearDeath.value); + map['year_death'] = Variable(yearDeath.value); } if (name.present) { - map['name'] = Variable(name.value); + map['name'] = Variable(name.value); } if (biography.present) { - map['biography'] = Variable(biography.value); + map['biography'] = Variable(biography.value); } return map; } @@ -896,102 +796,60 @@ class ArtistsCompanion extends UpdateCompanion { class $ArtistsTable extends Artists with TableInfo<$ArtistsTable, Artist> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $ArtistsTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedTextColumn _id; - @override - GeneratedTextColumn get id => _id ??= _constructId(); - GeneratedTextColumn _constructId() { - return GeneratedTextColumn( - 'id', - $tableName, - false, - ); - } - + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _yearBirthMeta = const VerificationMeta('yearBirth'); - GeneratedTextColumn _yearBirth; - @override - GeneratedTextColumn get yearBirth => _yearBirth ??= _constructYearBirth(); - GeneratedTextColumn _constructYearBirth() { - return GeneratedTextColumn( - 'year_birth', - $tableName, - true, - ); - } - + late final GeneratedColumn yearBirth = GeneratedColumn( + 'year_birth', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _yearDeathMeta = const VerificationMeta('yearDeath'); - GeneratedTextColumn _yearDeath; - @override - GeneratedTextColumn get yearDeath => _yearDeath ??= _constructYearDeath(); - GeneratedTextColumn _constructYearDeath() { - return GeneratedTextColumn( - 'year_death', - $tableName, - true, - ); - } - + late final GeneratedColumn yearDeath = GeneratedColumn( + 'year_death', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _nameMeta = const VerificationMeta('name'); - GeneratedTextColumn _name; - @override - GeneratedTextColumn get name => _name ??= _constructName(); - GeneratedTextColumn _constructName() { - return GeneratedTextColumn( - 'name', - $tableName, - true, - ); - } - + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _biographyMeta = const VerificationMeta('biography'); - GeneratedTextColumn _biography; - @override - GeneratedTextColumn get biography => _biography ??= _constructBiography(); - GeneratedTextColumn _constructBiography() { - return GeneratedTextColumn( - 'biography', - $tableName, - true, - ); - } - + late final GeneratedColumn biography = GeneratedColumn( + 'biography', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); @override List get $columns => [id, yearBirth, yearDeath, name, biography]; @override - $ArtistsTable get asDslTable => this; + String get aliasedName => _alias ?? 'artists'; @override - String get $tableName => _alias ?? 'artists'; - @override - final String actualTableName = 'artists'; + String get actualTableName => 'artists'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } else if (isInserting) { context.missing(_idMeta); } if (data.containsKey('year_birth')) { context.handle(_yearBirthMeta, - yearBirth.isAcceptableOrUnknown(data['year_birth'], _yearBirthMeta)); + yearBirth.isAcceptableOrUnknown(data['year_birth']!, _yearBirthMeta)); } if (data.containsKey('year_death')) { context.handle(_yearDeathMeta, - yearDeath.isAcceptableOrUnknown(data['year_death'], _yearDeathMeta)); + yearDeath.isAcceptableOrUnknown(data['year_death']!, _yearDeathMeta)); } if (data.containsKey('name')) { context.handle( - _nameMeta, name.isAcceptableOrUnknown(data['name'], _nameMeta)); + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); } if (data.containsKey('biography')) { context.handle(_biographyMeta, - biography.isAcceptableOrUnknown(data['biography'], _biographyMeta)); + biography.isAcceptableOrUnknown(data['biography']!, _biographyMeta)); } return context; } @@ -999,9 +857,9 @@ class $ArtistsTable extends Artists with TableInfo<$ArtistsTable, Artist> { @override Set get $primaryKey => {id}; @override - Artist map(Map data, {String tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return Artist.fromData(data, _db, prefix: effectivePrefix); + Artist map(Map data, {String? tablePrefix}) { + return Artist.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -1015,51 +873,44 @@ class ArtistTranslation extends DataClass final String id; final String languageCode; final String name; - final String biography; + final String? biography; ArtistTranslation( - {@required this.id, - @required this.languageCode, - @required this.name, + {required this.id, + required this.languageCode, + required this.name, this.biography}); factory ArtistTranslation.fromData( Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); return ArtistTranslation( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), - languageCode: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}language_code']), - name: stringType.mapFromDatabaseResponse(data['${effectivePrefix}name']), - biography: stringType + id: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + languageCode: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}language_code'])!, + name: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}name'])!, + biography: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}biography']), ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } - if (!nullToAbsent || languageCode != null) { - map['language_code'] = Variable(languageCode); - } - if (!nullToAbsent || name != null) { - map['name'] = Variable(name); - } + map['id'] = Variable(id); + map['language_code'] = Variable(languageCode); + map['name'] = Variable(name); if (!nullToAbsent || biography != null) { - map['biography'] = Variable(biography); + map['biography'] = Variable(biography); } return map; } ArtistTranslationsCompanion toCompanion(bool nullToAbsent) { return ArtistTranslationsCompanion( - id: id == null && nullToAbsent ? const Value.absent() : Value(id), - languageCode: languageCode == null && nullToAbsent - ? const Value.absent() - : Value(languageCode), - name: name == null && nullToAbsent ? const Value.absent() : Value(name), + id: Value(id), + languageCode: Value(languageCode), + name: Value(name), biography: biography == null && nullToAbsent ? const Value.absent() : Value(biography), @@ -1067,28 +918,31 @@ class ArtistTranslation extends DataClass } factory ArtistTranslation.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return ArtistTranslation( id: serializer.fromJson(json['id']), languageCode: serializer.fromJson(json['languageCode']), name: serializer.fromJson(json['name']), - biography: serializer.fromJson(json['biography']), + biography: serializer.fromJson(json['biography']), ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), 'languageCode': serializer.toJson(languageCode), 'name': serializer.toJson(name), - 'biography': serializer.toJson(biography), + 'biography': serializer.toJson(biography), }; } ArtistTranslation copyWith( - {String id, String languageCode, String name, String biography}) => + {String? id, + String? languageCode, + String? name, + String? biography}) => ArtistTranslation( id: id ?? this.id, languageCode: languageCode ?? this.languageCode, @@ -1107,10 +961,9 @@ class ArtistTranslation extends DataClass } @override - int get hashCode => $mrjf($mrjc(id.hashCode, - $mrjc(languageCode.hashCode, $mrjc(name.hashCode, biography.hashCode)))); + int get hashCode => Object.hash(id, languageCode, name, biography); @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => identical(this, other) || (other is ArtistTranslation && other.id == this.id && @@ -1123,7 +976,7 @@ class ArtistTranslationsCompanion extends UpdateCompanion { final Value id; final Value languageCode; final Value name; - final Value biography; + final Value biography; const ArtistTranslationsCompanion({ this.id = const Value.absent(), this.languageCode = const Value.absent(), @@ -1131,18 +984,18 @@ class ArtistTranslationsCompanion extends UpdateCompanion { this.biography = const Value.absent(), }); ArtistTranslationsCompanion.insert({ - @required String id, - @required String languageCode, - @required String name, + required String id, + required String languageCode, + required String name, this.biography = const Value.absent(), }) : id = Value(id), languageCode = Value(languageCode), name = Value(name); static Insertable custom({ - Expression id, - Expression languageCode, - Expression name, - Expression biography, + Expression? id, + Expression? languageCode, + Expression? name, + Expression? biography, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -1153,10 +1006,10 @@ class ArtistTranslationsCompanion extends UpdateCompanion { } ArtistTranslationsCompanion copyWith( - {Value id, - Value languageCode, - Value name, - Value biography}) { + {Value? id, + Value? languageCode, + Value? name, + Value? biography}) { return ArtistTranslationsCompanion( id: id ?? this.id, languageCode: languageCode ?? this.languageCode, @@ -1178,7 +1031,7 @@ class ArtistTranslationsCompanion extends UpdateCompanion { map['name'] = Variable(name.value); } if (biography.present) { - map['biography'] = Variable(biography.value); + map['biography'] = Variable(biography.value); } return map; } @@ -1198,70 +1051,40 @@ class ArtistTranslationsCompanion extends UpdateCompanion { class $ArtistTranslationsTable extends ArtistTranslations with TableInfo<$ArtistTranslationsTable, ArtistTranslation> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $ArtistTranslationsTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedTextColumn _id; - @override - GeneratedTextColumn get id => _id ??= _constructId(); - GeneratedTextColumn _constructId() { - return GeneratedTextColumn('id', $tableName, false, - $customConstraints: 'NULL REFERENCES artists(id)'); - } - + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + typeName: 'TEXT', + requiredDuringInsert: true, + $customConstraints: 'NULL REFERENCES artists(id)'); final VerificationMeta _languageCodeMeta = const VerificationMeta('languageCode'); - GeneratedTextColumn _languageCode; - @override - GeneratedTextColumn get languageCode => - _languageCode ??= _constructLanguageCode(); - GeneratedTextColumn _constructLanguageCode() { - return GeneratedTextColumn( - 'language_code', - $tableName, - false, - ); - } - + late final GeneratedColumn languageCode = GeneratedColumn( + 'language_code', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _nameMeta = const VerificationMeta('name'); - GeneratedTextColumn _name; - @override - GeneratedTextColumn get name => _name ??= _constructName(); - GeneratedTextColumn _constructName() { - return GeneratedTextColumn( - 'name', - $tableName, - false, - ); - } - + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _biographyMeta = const VerificationMeta('biography'); - GeneratedTextColumn _biography; - @override - GeneratedTextColumn get biography => _biography ??= _constructBiography(); - GeneratedTextColumn _constructBiography() { - return GeneratedTextColumn( - 'biography', - $tableName, - true, - ); - } - + late final GeneratedColumn biography = GeneratedColumn( + 'biography', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); @override List get $columns => [id, languageCode, name, biography]; @override - $ArtistTranslationsTable get asDslTable => this; - @override - String get $tableName => _alias ?? 'artist_translations'; + String get aliasedName => _alias ?? 'artist_translations'; @override - final String actualTableName = 'artist_translations'; + String get actualTableName => 'artist_translations'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } else if (isInserting) { context.missing(_idMeta); } @@ -1269,19 +1092,19 @@ class $ArtistTranslationsTable extends ArtistTranslations context.handle( _languageCodeMeta, languageCode.isAcceptableOrUnknown( - data['language_code'], _languageCodeMeta)); + data['language_code']!, _languageCodeMeta)); } else if (isInserting) { context.missing(_languageCodeMeta); } if (data.containsKey('name')) { context.handle( - _nameMeta, name.isAcceptableOrUnknown(data['name'], _nameMeta)); + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); } else if (isInserting) { context.missing(_nameMeta); } if (data.containsKey('biography')) { context.handle(_biographyMeta, - biography.isAcceptableOrUnknown(data['biography'], _biographyMeta)); + biography.isAcceptableOrUnknown(data['biography']!, _biographyMeta)); } return context; } @@ -1289,9 +1112,9 @@ class $ArtistTranslationsTable extends ArtistTranslations @override Set get $primaryKey => {id, languageCode}; @override - ArtistTranslation map(Map data, {String tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return ArtistTranslation.fromData(data, _db, prefix: effectivePrefix); + ArtistTranslation map(Map data, {String? tablePrefix}) { + return ArtistTranslation.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -1303,109 +1126,81 @@ class $ArtistTranslationsTable extends ArtistTranslations class Viewing extends DataClass implements Insertable { final int id; final String artworkId; - final String cnnModelUsed; + final String? cnnModelUsed; final String algorithmUsed; final DateTime startTime; final DateTime endTime; final int totalTime; final String additionalInfo; Viewing( - {@required this.id, - @required this.artworkId, + {required this.id, + required this.artworkId, this.cnnModelUsed, - @required this.algorithmUsed, - @required this.startTime, - @required this.endTime, - @required this.totalTime, - @required this.additionalInfo}); + required this.algorithmUsed, + required this.startTime, + required this.endTime, + required this.totalTime, + required this.additionalInfo}); factory Viewing.fromData(Map data, GeneratedDatabase db, - {String prefix}) { + {String? prefix}) { final effectivePrefix = prefix ?? ''; - final intType = db.typeSystem.forDartType(); - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); return Viewing( - id: intType.mapFromDatabaseResponse(data['${effectivePrefix}id']), - artworkId: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}artwork_id']), - cnnModelUsed: stringType + id: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + artworkId: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}artwork_id'])!, + cnnModelUsed: const StringType() .mapFromDatabaseResponse(data['${effectivePrefix}cnn_model_used']), - algorithmUsed: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}algorithm_used']), - startTime: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}start_time']), - endTime: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}end_time']), - totalTime: - intType.mapFromDatabaseResponse(data['${effectivePrefix}total_time']), - additionalInfo: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}additional_info']), + algorithmUsed: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}algorithm_used'])!, + startTime: const DateTimeType() + .mapFromDatabaseResponse(data['${effectivePrefix}start_time'])!, + endTime: const DateTimeType() + .mapFromDatabaseResponse(data['${effectivePrefix}end_time'])!, + totalTime: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}total_time'])!, + additionalInfo: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}additional_info'])!, ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } - if (!nullToAbsent || artworkId != null) { - map['artwork_id'] = Variable(artworkId); - } + map['id'] = Variable(id); + map['artwork_id'] = Variable(artworkId); if (!nullToAbsent || cnnModelUsed != null) { - map['cnn_model_used'] = Variable(cnnModelUsed); - } - if (!nullToAbsent || algorithmUsed != null) { - map['algorithm_used'] = Variable(algorithmUsed); - } - if (!nullToAbsent || startTime != null) { - map['start_time'] = Variable(startTime); - } - if (!nullToAbsent || endTime != null) { - map['end_time'] = Variable(endTime); - } - if (!nullToAbsent || totalTime != null) { - map['total_time'] = Variable(totalTime); - } - if (!nullToAbsent || additionalInfo != null) { - map['additional_info'] = Variable(additionalInfo); + map['cnn_model_used'] = Variable(cnnModelUsed); } + map['algorithm_used'] = Variable(algorithmUsed); + map['start_time'] = Variable(startTime); + map['end_time'] = Variable(endTime); + map['total_time'] = Variable(totalTime); + map['additional_info'] = Variable(additionalInfo); return map; } ViewingsCompanion toCompanion(bool nullToAbsent) { return ViewingsCompanion( - id: id == null && nullToAbsent ? const Value.absent() : Value(id), - artworkId: artworkId == null && nullToAbsent - ? const Value.absent() - : Value(artworkId), + id: Value(id), + artworkId: Value(artworkId), cnnModelUsed: cnnModelUsed == null && nullToAbsent ? const Value.absent() : Value(cnnModelUsed), - algorithmUsed: algorithmUsed == null && nullToAbsent - ? const Value.absent() - : Value(algorithmUsed), - startTime: startTime == null && nullToAbsent - ? const Value.absent() - : Value(startTime), - endTime: endTime == null && nullToAbsent - ? const Value.absent() - : Value(endTime), - totalTime: totalTime == null && nullToAbsent - ? const Value.absent() - : Value(totalTime), - additionalInfo: additionalInfo == null && nullToAbsent - ? const Value.absent() - : Value(additionalInfo), + algorithmUsed: Value(algorithmUsed), + startTime: Value(startTime), + endTime: Value(endTime), + totalTime: Value(totalTime), + additionalInfo: Value(additionalInfo), ); } factory Viewing.fromJson(Map json, - {ValueSerializer serializer}) { + {ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return Viewing( id: serializer.fromJson(json['id']), artworkId: serializer.fromJson(json['artworkId']), - cnnModelUsed: serializer.fromJson(json['cnnModelUsed']), + cnnModelUsed: serializer.fromJson(json['cnnModelUsed']), algorithmUsed: serializer.fromJson(json['algorithmUsed']), startTime: serializer.fromJson(json['startTime']), endTime: serializer.fromJson(json['endTime']), @@ -1414,12 +1209,12 @@ class Viewing extends DataClass implements Insertable { ); } @override - Map toJson({ValueSerializer serializer}) { + Map toJson({ValueSerializer? serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), 'artworkId': serializer.toJson(artworkId), - 'cnnModelUsed': serializer.toJson(cnnModelUsed), + 'cnnModelUsed': serializer.toJson(cnnModelUsed), 'algorithmUsed': serializer.toJson(algorithmUsed), 'startTime': serializer.toJson(startTime), 'endTime': serializer.toJson(endTime), @@ -1429,14 +1224,14 @@ class Viewing extends DataClass implements Insertable { } Viewing copyWith( - {int id, - String artworkId, - String cnnModelUsed, - String algorithmUsed, - DateTime startTime, - DateTime endTime, - int totalTime, - String additionalInfo}) => + {int? id, + String? artworkId, + String? cnnModelUsed, + String? algorithmUsed, + DateTime? startTime, + DateTime? endTime, + int? totalTime, + String? additionalInfo}) => Viewing( id: id ?? this.id, artworkId: artworkId ?? this.artworkId, @@ -1463,22 +1258,10 @@ class Viewing extends DataClass implements Insertable { } @override - int get hashCode => $mrjf($mrjc( - id.hashCode, - $mrjc( - artworkId.hashCode, - $mrjc( - cnnModelUsed.hashCode, - $mrjc( - algorithmUsed.hashCode, - $mrjc( - startTime.hashCode, - $mrjc( - endTime.hashCode, - $mrjc(totalTime.hashCode, - additionalInfo.hashCode)))))))); - @override - bool operator ==(dynamic other) => + int get hashCode => Object.hash(id, artworkId, cnnModelUsed, algorithmUsed, + startTime, endTime, totalTime, additionalInfo); + @override + bool operator ==(Object other) => identical(this, other) || (other is Viewing && other.id == this.id && @@ -1494,7 +1277,7 @@ class Viewing extends DataClass implements Insertable { class ViewingsCompanion extends UpdateCompanion { final Value id; final Value artworkId; - final Value cnnModelUsed; + final Value cnnModelUsed; final Value algorithmUsed; final Value startTime; final Value endTime; @@ -1512,13 +1295,13 @@ class ViewingsCompanion extends UpdateCompanion { }); ViewingsCompanion.insert({ this.id = const Value.absent(), - @required String artworkId, + required String artworkId, this.cnnModelUsed = const Value.absent(), - @required String algorithmUsed, - @required DateTime startTime, - @required DateTime endTime, - @required int totalTime, - @required String additionalInfo, + required String algorithmUsed, + required DateTime startTime, + required DateTime endTime, + required int totalTime, + required String additionalInfo, }) : artworkId = Value(artworkId), algorithmUsed = Value(algorithmUsed), startTime = Value(startTime), @@ -1526,14 +1309,14 @@ class ViewingsCompanion extends UpdateCompanion { totalTime = Value(totalTime), additionalInfo = Value(additionalInfo); static Insertable custom({ - Expression id, - Expression artworkId, - Expression cnnModelUsed, - Expression algorithmUsed, - Expression startTime, - Expression endTime, - Expression totalTime, - Expression additionalInfo, + Expression? id, + Expression? artworkId, + Expression? cnnModelUsed, + Expression? algorithmUsed, + Expression? startTime, + Expression? endTime, + Expression? totalTime, + Expression? additionalInfo, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -1548,14 +1331,14 @@ class ViewingsCompanion extends UpdateCompanion { } ViewingsCompanion copyWith( - {Value id, - Value artworkId, - Value cnnModelUsed, - Value algorithmUsed, - Value startTime, - Value endTime, - Value totalTime, - Value additionalInfo}) { + {Value? id, + Value? artworkId, + Value? cnnModelUsed, + Value? algorithmUsed, + Value? startTime, + Value? endTime, + Value? totalTime, + Value? additionalInfo}) { return ViewingsCompanion( id: id ?? this.id, artworkId: artworkId ?? this.artworkId, @@ -1578,7 +1361,7 @@ class ViewingsCompanion extends UpdateCompanion { map['artwork_id'] = Variable(artworkId.value); } if (cnnModelUsed.present) { - map['cnn_model_used'] = Variable(cnnModelUsed.value); + map['cnn_model_used'] = Variable(cnnModelUsed.value); } if (algorithmUsed.present) { map['algorithm_used'] = Variable(algorithmUsed.value); @@ -1616,104 +1399,47 @@ class ViewingsCompanion extends UpdateCompanion { class $ViewingsTable extends Viewings with TableInfo<$ViewingsTable, Viewing> { final GeneratedDatabase _db; - final String _alias; + final String? _alias; $ViewingsTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedIntColumn _id; - @override - GeneratedIntColumn get id => _id ??= _constructId(); - GeneratedIntColumn _constructId() { - return GeneratedIntColumn('id', $tableName, false, - hasAutoIncrement: true, declaredAsPrimaryKey: true); - } - + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + typeName: 'INTEGER', + requiredDuringInsert: false, + defaultConstraints: 'PRIMARY KEY AUTOINCREMENT'); final VerificationMeta _artworkIdMeta = const VerificationMeta('artworkId'); - GeneratedTextColumn _artworkId; - @override - GeneratedTextColumn get artworkId => _artworkId ??= _constructArtworkId(); - GeneratedTextColumn _constructArtworkId() { - return GeneratedTextColumn('artwork_id', $tableName, false, - $customConstraints: 'NULL REFERENCES artworks(id)'); - } - + late final GeneratedColumn artworkId = GeneratedColumn( + 'artwork_id', aliasedName, false, + typeName: 'TEXT', + requiredDuringInsert: true, + $customConstraints: 'NULL REFERENCES artworks(id)'); final VerificationMeta _cnnModelUsedMeta = const VerificationMeta('cnnModelUsed'); - GeneratedTextColumn _cnnModelUsed; - @override - GeneratedTextColumn get cnnModelUsed => - _cnnModelUsed ??= _constructCnnModelUsed(); - GeneratedTextColumn _constructCnnModelUsed() { - return GeneratedTextColumn( - 'cnn_model_used', - $tableName, - true, - ); - } - + late final GeneratedColumn cnnModelUsed = GeneratedColumn( + 'cnn_model_used', aliasedName, true, + typeName: 'TEXT', requiredDuringInsert: false); final VerificationMeta _algorithmUsedMeta = const VerificationMeta('algorithmUsed'); - GeneratedTextColumn _algorithmUsed; - @override - GeneratedTextColumn get algorithmUsed => - _algorithmUsed ??= _constructAlgorithmUsed(); - GeneratedTextColumn _constructAlgorithmUsed() { - return GeneratedTextColumn( - 'algorithm_used', - $tableName, - false, - ); - } - + late final GeneratedColumn algorithmUsed = GeneratedColumn( + 'algorithm_used', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); final VerificationMeta _startTimeMeta = const VerificationMeta('startTime'); - GeneratedDateTimeColumn _startTime; - @override - GeneratedDateTimeColumn get startTime => _startTime ??= _constructStartTime(); - GeneratedDateTimeColumn _constructStartTime() { - return GeneratedDateTimeColumn( - 'start_time', - $tableName, - false, - ); - } - + late final GeneratedColumn startTime = GeneratedColumn( + 'start_time', aliasedName, false, + typeName: 'INTEGER', requiredDuringInsert: true); final VerificationMeta _endTimeMeta = const VerificationMeta('endTime'); - GeneratedDateTimeColumn _endTime; - @override - GeneratedDateTimeColumn get endTime => _endTime ??= _constructEndTime(); - GeneratedDateTimeColumn _constructEndTime() { - return GeneratedDateTimeColumn( - 'end_time', - $tableName, - false, - ); - } - + late final GeneratedColumn endTime = GeneratedColumn( + 'end_time', aliasedName, false, + typeName: 'INTEGER', requiredDuringInsert: true); final VerificationMeta _totalTimeMeta = const VerificationMeta('totalTime'); - GeneratedIntColumn _totalTime; - @override - GeneratedIntColumn get totalTime => _totalTime ??= _constructTotalTime(); - GeneratedIntColumn _constructTotalTime() { - return GeneratedIntColumn( - 'total_time', - $tableName, - false, - ); - } - + late final GeneratedColumn totalTime = GeneratedColumn( + 'total_time', aliasedName, false, + typeName: 'INTEGER', requiredDuringInsert: true); final VerificationMeta _additionalInfoMeta = const VerificationMeta('additionalInfo'); - GeneratedTextColumn _additionalInfo; - @override - GeneratedTextColumn get additionalInfo => - _additionalInfo ??= _constructAdditionalInfo(); - GeneratedTextColumn _constructAdditionalInfo() { - return GeneratedTextColumn( - 'additional_info', - $tableName, - false, - ); - } - + late final GeneratedColumn additionalInfo = GeneratedColumn( + 'additional_info', aliasedName, false, + typeName: 'TEXT', requiredDuringInsert: true); @override List get $columns => [ id, @@ -1726,22 +1452,20 @@ class $ViewingsTable extends Viewings with TableInfo<$ViewingsTable, Viewing> { additionalInfo ]; @override - $ViewingsTable get asDslTable => this; - @override - String get $tableName => _alias ?? 'viewings'; + String get aliasedName => _alias ?? 'viewings'; @override - final String actualTableName = 'viewings'; + String get actualTableName => 'viewings'; @override VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('artwork_id')) { context.handle(_artworkIdMeta, - artworkId.isAcceptableOrUnknown(data['artwork_id'], _artworkIdMeta)); + artworkId.isAcceptableOrUnknown(data['artwork_id']!, _artworkIdMeta)); } else if (isInserting) { context.missing(_artworkIdMeta); } @@ -1749,31 +1473,31 @@ class $ViewingsTable extends Viewings with TableInfo<$ViewingsTable, Viewing> { context.handle( _cnnModelUsedMeta, cnnModelUsed.isAcceptableOrUnknown( - data['cnn_model_used'], _cnnModelUsedMeta)); + data['cnn_model_used']!, _cnnModelUsedMeta)); } if (data.containsKey('algorithm_used')) { context.handle( _algorithmUsedMeta, algorithmUsed.isAcceptableOrUnknown( - data['algorithm_used'], _algorithmUsedMeta)); + data['algorithm_used']!, _algorithmUsedMeta)); } else if (isInserting) { context.missing(_algorithmUsedMeta); } if (data.containsKey('start_time')) { context.handle(_startTimeMeta, - startTime.isAcceptableOrUnknown(data['start_time'], _startTimeMeta)); + startTime.isAcceptableOrUnknown(data['start_time']!, _startTimeMeta)); } else if (isInserting) { context.missing(_startTimeMeta); } if (data.containsKey('end_time')) { context.handle(_endTimeMeta, - endTime.isAcceptableOrUnknown(data['end_time'], _endTimeMeta)); + endTime.isAcceptableOrUnknown(data['end_time']!, _endTimeMeta)); } else if (isInserting) { context.missing(_endTimeMeta); } if (data.containsKey('total_time')) { context.handle(_totalTimeMeta, - totalTime.isAcceptableOrUnknown(data['total_time'], _totalTimeMeta)); + totalTime.isAcceptableOrUnknown(data['total_time']!, _totalTimeMeta)); } else if (isInserting) { context.missing(_totalTimeMeta); } @@ -1781,7 +1505,7 @@ class $ViewingsTable extends Viewings with TableInfo<$ViewingsTable, Viewing> { context.handle( _additionalInfoMeta, additionalInfo.isAcceptableOrUnknown( - data['additional_info'], _additionalInfoMeta)); + data['additional_info']!, _additionalInfoMeta)); } else if (isInserting) { context.missing(_additionalInfoMeta); } @@ -1791,9 +1515,9 @@ class $ViewingsTable extends Viewings with TableInfo<$ViewingsTable, Viewing> { @override Set get $primaryKey => {id}; @override - Viewing map(Map data, {String tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return Viewing.fromData(data, _db, prefix: effectivePrefix); + Viewing map(Map data, {String? tablePrefix}) { + return Viewing.fromData(data, _db, + prefix: tablePrefix != null ? '$tablePrefix.' : null); } @override @@ -1804,26 +1528,16 @@ class $ViewingsTable extends Viewings with TableInfo<$ViewingsTable, Viewing> { abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e); - $ArtworksTable _artworks; - $ArtworksTable get artworks => _artworks ??= $ArtworksTable(this); - $ArtworkTranslationsTable _artworkTranslations; - $ArtworkTranslationsTable get artworkTranslations => - _artworkTranslations ??= $ArtworkTranslationsTable(this); - $ArtistsTable _artists; - $ArtistsTable get artists => _artists ??= $ArtistsTable(this); - $ArtistTranslationsTable _artistTranslations; - $ArtistTranslationsTable get artistTranslations => - _artistTranslations ??= $ArtistTranslationsTable(this); - $ViewingsTable _viewings; - $ViewingsTable get viewings => _viewings ??= $ViewingsTable(this); - ArtworksDao _artworksDao; - ArtworksDao get artworksDao => - _artworksDao ??= ArtworksDao(this as AppDatabase); - ArtistsDao _artistsDao; - ArtistsDao get artistsDao => _artistsDao ??= ArtistsDao(this as AppDatabase); - ViewingsDao _viewingsDao; - ViewingsDao get viewingsDao => - _viewingsDao ??= ViewingsDao(this as AppDatabase); + late final $ArtworksTable artworks = $ArtworksTable(this); + late final $ArtworkTranslationsTable artworkTranslations = + $ArtworkTranslationsTable(this); + late final $ArtistsTable artists = $ArtistsTable(this); + late final $ArtistTranslationsTable artistTranslations = + $ArtistTranslationsTable(this); + late final $ViewingsTable viewings = $ViewingsTable(this); + late final ArtworksDao artworksDao = ArtworksDao(this as AppDatabase); + late final ArtistsDao artistsDao = ArtistsDao(this as AppDatabase); + late final ViewingsDao viewingsDao = ViewingsDao(this as AppDatabase); @override Iterable get allTables => allSchemaEntities.whereType(); @override diff --git a/lib/data/inference_algorithms.dart b/lib/data/inference_algorithms.dart index aa16547..06b73e3 100644 --- a/lib/data/inference_algorithms.dart +++ b/lib/data/inference_algorithms.dart @@ -10,7 +10,7 @@ abstract class InferenceAlgorithm { final startTime = DateTime.now(); /// Variable that indicates that no result is available yet. - final noResult = ""; + final noResult = ''; /// List that holds a history of all model inferences so far. List history = []; @@ -24,7 +24,7 @@ abstract class InferenceAlgorithm { double _fps = 0.0; /// Current artwork picked by the algorithm as the most likely inference. - String _topInference = ""; + String _topInference = ''; /// All classes extending [InferenceAlgorithm] must implement this method and /// provide the logic of how the identity of the most likely artwork should @@ -56,11 +56,11 @@ abstract class InferenceAlgorithm { /// Returns the number of frames analysed by the algorithm per second, as a /// formatted string. - String get fps => "${_fps.toStringAsPrecision(2)} fps"; + String get fps => '${_fps.toStringAsPrecision(2)} fps'; /// Indicates whether the algorithm reached a decision about the current /// artwork identity or not. - bool hasResult() => _topInference != ""; + bool hasResult() => _topInference != ''; /// Sets the current [value] of the top inference by the algorithm. void setTopInference(String value) => _topInference = value; @@ -87,7 +87,7 @@ abstract class InferenceAlgorithm { /// [inferenceTimeHistory] list. void _updateFps() { if (inferenceTimeHistory.length >= 5) { - double meanInferenceTime = inferenceTimeHistory + final double meanInferenceTime = inferenceTimeHistory .sublist(inferenceTimeHistory.length - 5) .reduce((a, b) => a + b) / 5; @@ -112,14 +112,14 @@ abstract class InferenceAlgorithm { /// library. ViewingsCompanion resultAsDbObject() { // TODO throw when _topInference is "no_artwork", or maybe make an entry in db with it and restart inferring - var endTime = DateTime.now(); + final endTime = DateTime.now(); return ViewingsCompanion.insert( artworkId: _topInference, startTime: startTime, endTime: endTime, totalTime: endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch, - algorithmUsed: this.runtimeType.toString(), + algorithmUsed: runtimeType.toString(), additionalInfo: _additionalDetailsToSave(), ); } @@ -138,12 +138,12 @@ abstract class InferenceAlgorithm { /// threshold, it is chosen as the "winner". In case of ties in artwork top /// means, no winner is chosen and the algorithm proceeds to the next window. class WindowAverageAlgo extends InferenceAlgorithm { + WindowAverageAlgo({required this.sensitivity, required this.windowLength}); + final double sensitivity; final int windowLength; double _topMean = 0.0; - var _probsByID = DefaultDict>(() => []); - - WindowAverageAlgo({this.sensitivity, this.windowLength}); + final _probsByID = DefaultDict>(() => []); @override void updateRecognitions(List recognitions, int inferenceTime) { @@ -151,14 +151,17 @@ class WindowAverageAlgo extends InferenceAlgorithm { if (history.length >= windowLength) { // sort probabilities of the last windowLength recognitions by artworkId - history.sublist(history.length - windowLength).forEach((recognition) { - _probsByID[recognition["label"]].add(recognition["confidence"]); - }); + history.sublist(history.length - windowLength).forEach( + (recognition) { + _probsByID[recognition['label']] + .add(recognition['confidence'] as double); + }, + ); // get mean probability for each artworkId var meansByID = { for (var id in _probsByID.entries) - id.key: id.value.reduce((a, b) => a + b) / id.value.length + id.key: id.value.reduce((a, b) => a + b) / id.value.length, }; // sort artworkIds by mean, largest to smallest @@ -168,7 +171,7 @@ class WindowAverageAlgo extends InferenceAlgorithm { _topMean = 0.0; - var entries = meansByID.entries.toList(); + final entries = meansByID.entries.toList(); // check if the first artwork's probability exceeds the sensitivity if (entries[0].value >= sensitivity / 100) { @@ -189,7 +192,6 @@ class WindowAverageAlgo extends InferenceAlgorithm { // no winner yet resetTopInference(); } - _probsByID.clear(); } } @@ -197,7 +199,7 @@ class WindowAverageAlgo extends InferenceAlgorithm { @override String get topInferenceFormatted { if (hasResult()) { - return _topInference + " (${(_topMean * 100).toStringAsFixed(1)}%)"; + return '$_topInference (${(_topMean * 100).toStringAsFixed(1)}%)'; } else { return noResult; } @@ -205,9 +207,9 @@ class WindowAverageAlgo extends InferenceAlgorithm { @override String _additionalDetailsToSave() => { - "topMean": _topMean, - "sensitivity": sensitivity, - "windowLength": windowLength, + 'topMean': _topMean, + 'sensitivity': sensitivity, + 'windowLength': windowLength, }.toString(); } @@ -220,11 +222,14 @@ class WindowAverageAlgo extends InferenceAlgorithm { /// We could improve this algorithm by only accepting as winner the artwork /// that has a majority count, i.e. more than half, in the window. class WindowHighestCountAlgo extends InferenceAlgorithm { + WindowHighestCountAlgo({ + required this.windowLength, + this.sensitivitySetting = 0.0, + }); + final double sensitivitySetting; final int windowLength; - var _countsByID = DefaultDict(() => 0); - - WindowHighestCountAlgo({this.windowLength, this.sensitivitySetting = 0.0}); + final _countsByID = DefaultDict(() => 0); @override void updateRecognitions(List recognitions, int inferenceTime) { @@ -235,16 +240,16 @@ class WindowHighestCountAlgo extends InferenceAlgorithm { if (history.length >= windowLength) { // count the occurrences of the last windowLength recognitions by artworkId history.sublist(history.length - windowLength).forEach((recognition) { - _countsByID[recognition["label"]] += 1; + _countsByID[recognition['label'] as String] += 1; }); // sort artworkIds by their counts, largest to smallest // sortedCounts is of type LinkedHashMap, that guarantees preserving key // insertion order - var sortedCounts = + final sortedCounts = _countsByID.sortedByValue((count) => count, order: Order.desc); - var topEntry = sortedCounts.entries.toList()[0]; + final topEntry = sortedCounts.entries.toList()[0]; if (sortedCounts.length == 1) { // if there is only one artwork in map, set it as top @@ -256,7 +261,6 @@ class WindowHighestCountAlgo extends InferenceAlgorithm { // in case of a tie, wait for next round to decide resetTopInference(); } - _countsByID.clear(); } } @@ -264,9 +268,7 @@ class WindowHighestCountAlgo extends InferenceAlgorithm { @override String get topInferenceFormatted { if (hasResult()) { - return _topInference + - " (count of ${_countsByID[_topInference]} in last " - "$windowLength inferences)"; + return '$_topInference (count of ${_countsByID[_topInference]} in last $windowLength inferences)'; } else { return noResult; } @@ -274,9 +276,9 @@ class WindowHighestCountAlgo extends InferenceAlgorithm { @override String _additionalDetailsToSave() => { - "topCount": _countsByID[_topInference], - "sensitivity": sensitivitySetting, - "windowLength": windowLength, + 'topCount': _countsByID[_topInference], + 'sensitivity': sensitivitySetting, + 'windowLength': windowLength, }.toString(); } @@ -287,33 +289,33 @@ class WindowHighestCountAlgo extends InferenceAlgorithm { /// Resembles [First-past-the-post voting](https://en.wikipedia.org/wiki/First-past-the-post_voting), /// hence the name. class FirstPastThePostAlgo extends InferenceAlgorithm { + FirstPastThePostAlgo({required this.countThreshold, this.sensitivity = 0.0}); + final double sensitivity; final int countThreshold; - var _countsByID = DefaultDict(() => 0); - - FirstPastThePostAlgo({this.countThreshold, this.sensitivity = 0.0}); + final _countsByID = DefaultDict(() => 0); @override void updateRecognitions(List recognitions, int inferenceTime) { _updateHistories(recognitions, inferenceTime); // keep count of each inference per artworkId - recognitions.forEach((recognition) { - double recProbability = recognition["confidence"]; + for (final recognition in recognitions) { + final double recProbability = recognition['confidence'] as double; // here if sensitivitySetting is not specified, every inference counts, // otherwise inferences with lower values are not counted if (recProbability >= (sensitivity / 100)) { - _countsByID[recognition["label"]] += 1; + _countsByID[recognition['label'] as String] += 1; } - }); + } // sort artworkIds by their counts, largest to smallest // sortedCounts is of type LinkedHashMap, that guarantees preserving key // insertion order - var sortedCounts = + final sortedCounts = _countsByID.sortedByValue((count) => count, order: Order.desc); - var entries = sortedCounts.entries.toList(); + final entries = sortedCounts.entries.toList(); if (entries.isNotEmpty) { // check the first artwork's count exceeds the count threshold @@ -342,9 +344,7 @@ class FirstPastThePostAlgo extends InferenceAlgorithm { // top inference may change, and the return value below will no longer be // "correct", but in normal circumstances the app will not reach such a // scenario, since it will pick the first result and stop counting - return _topInference + - " (First to reach count of $countThreshold - current " - "count ${_countsByID[_topInference]})"; + return '$_topInference (First to reach count of $countThreshold - current count ${_countsByID[_topInference]})'; } else { return noResult; } @@ -352,9 +352,9 @@ class FirstPastThePostAlgo extends InferenceAlgorithm { @override String _additionalDetailsToSave() => { - "topCount": _countsByID[_topInference], - "sensitivity": sensitivity, - "countThreshold": countThreshold, + 'topCount': _countsByID[_topInference], + 'sensitivity': sensitivity, + 'countThreshold': countThreshold, }.toString(); } @@ -365,29 +365,29 @@ class FirstPastThePostAlgo extends InferenceAlgorithm { /// predictions when a new prediction is different. If [sensitivity] is /// specified, only those predictions that equal or exceed it are counted. class SeidenaryAlgo extends InferenceAlgorithm { + SeidenaryAlgo({required this.P, this.sensitivity = 0.0}); + final int P; final double sensitivity; - var _counters = DefaultDict(() => 0); - - SeidenaryAlgo({this.P, this.sensitivity = 0.0}); + final _counters = DefaultDict(() => 0); @override void updateRecognitions(List recognitions, int inferenceTime) { _updateHistories(recognitions, inferenceTime); - if (recognitions.length > 0) { + if (recognitions.isNotEmpty) { // here if sensitivitySetting is not specified, every inference counts, // otherwise inferences with lower values are not counted - if (recognitions.first["confidence"] * 100 >= sensitivity) { + if ((recognitions.first['confidence'] as double) * 100 >= sensitivity) { // add 1 to the top inference of this round - var topArtwork = recognitions.first["label"]; + final topArtwork = recognitions.first['label'] as String; _counters[topArtwork] += 1; // subtract 1 from all other previous inferences - _counters.keys.forEach((key) { + for (final key in _counters.keys) { if (key != topArtwork) { _counters[key] -= 1; } - }); + } } } @@ -395,9 +395,9 @@ class SeidenaryAlgo extends InferenceAlgorithm { // sort artwork counters by their counts, largest to smallest // sortedCounters is converted to LinkedHashMap here, that guarantees // preserving key insertion order - var sortedCounters = _counters.sortedByValue((count) => count); + final sortedCounters = _counters.sortedByValue((count) => count); - var entries = sortedCounters.entries.toList(); + final entries = sortedCounters.entries.toList(); // check if the first counter exceeds P if (entries[0].value >= P) { @@ -421,7 +421,7 @@ class SeidenaryAlgo extends InferenceAlgorithm { @override String get topInferenceFormatted { if (hasResult()) { - return _topInference + " (p=${_counters[_topInference]})"; + return '$_topInference (p=${_counters[_topInference]})'; } else { return noResult; } @@ -429,17 +429,17 @@ class SeidenaryAlgo extends InferenceAlgorithm { @override String _additionalDetailsToSave() => { - "topCount (p)": _counters[_topInference], - "sensitivity": sensitivity, - "P": P, + 'topCount (p)': _counters[_topInference], + 'sensitivity': sensitivity, + 'P': P, }.toString(); } // Variables with descriptions of algorithms, to be used elsewhere in the app -const firstAlgorithm = "1 - Window average probability"; -const secondAlgorithm = "2 - Window highest count"; -const thirdAlgorithm = "3 - First past the post"; -const fourthAlgorithm = "4 - Seidenary et al. 2017 Persistence"; +const firstAlgorithm = '1 - Window average probability'; +const secondAlgorithm = '2 - Window highest count'; +const thirdAlgorithm = '3 - First past the post'; +const fourthAlgorithm = '4 - Seidenary et al. 2017 Persistence'; /// All algorithms mapped as functions, so they can be easily initialised /// without may if-else statements. @@ -460,5 +460,5 @@ final allAlgorithms = { fourthAlgorithm: (double sensitivity, int winThreshP) => SeidenaryAlgo( sensitivity: sensitivity, P: winThreshP, - ) + ), }; diff --git a/lib/data/url_data_sources.dart b/lib/data/url_data_sources.dart index b9706e1..3302812 100644 --- a/lib/data/url_data_sources.dart +++ b/lib/data/url_data_sources.dart @@ -8,18 +8,18 @@ import 'package:modern_art_app/data/sensitive.dart'; /// The following url provides a json of the first sheet only, which is not very /// useful, so it is not used anymore; left here for future reference. const String gSheetDbUrlOld = - "https://spreadsheets.google.com/feeds/list/$docId/od6/public/values?alt=json"; + 'https://spreadsheets.google.com/feeds/list/$docId/od6/public/values?alt=json'; /// Google Sheets doc url with [Artwork] information in JSON format, that can /// be parsed by the app and update its database. const String gSheetUrlArtworks = - "https://spreadsheets.google.com/feeds/list/$docId/3/public/values?alt=json"; + 'https://spreadsheets.google.com/feeds/list/$docId/3/public/values?alt=json'; /// Google Sheets doc url with [Artist] information in JSON format, that can /// be parsed by the app and update its database. const String gSheetUrlArtists = - "https://spreadsheets.google.com/feeds/list/$docId/4/public/values?alt=json"; + 'https://spreadsheets.google.com/feeds/list/$docId/4/public/values?alt=json'; /// Sheet id that contains localization/internationalization fields, used by the /// "flutter_sheet_localization" library, along with the [docId]. -const String i18nSheetID = "1681157874"; +const String i18nSheetID = '1681157874'; diff --git a/lib/explore_page_parallax.dart b/lib/explore_page_parallax.dart deleted file mode 100644 index a10a29d..0000000 --- a/lib/explore_page_parallax.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:sensors/sensors.dart'; - -class ExplorePageParallax extends StatefulWidget { - @override - _ExplorePageParallaxState createState() => _ExplorePageParallaxState(); -} - -class _ExplorePageParallaxState extends State { - StreamSubscription _subscription; - List _accelerometerValues; - List _xValues = []; - - double _offset = 100; - double _start = -320; - double _sign = 0; - Size _screenSize; - - @override - void initState() { - super.initState(); - _subscription = accelerometerEvents.listen((event) { - setState(() { - _accelerometerValues = [event.x, event.y, event.z]; - _xValues.add(event.x); - if (_xValues.length > 15) { - _xValues.removeAt(0); - } - double mean = _xValues.reduce((value, element) => value + element) / - _xValues.length; - // get x acceleration sign (pos or neg) - _sign = event.x / event.x.abs(); - - if (mean < 0) { - _start = _start < 0 ? _start + 1 : 0; - } else { - _start = _start > -640 ? _start - 1 : -640; - } - }); - }); - } - - @override - Widget build(BuildContext context) { - _screenSize = MediaQuery.of(context).size; - double scale = 1280 / 960; - return SafeArea( - child: Scaffold( - body: Container( - height: _screenSize.height, - width: _screenSize.width, - child: Stack( - children: [ - Positioned( - top: 0, - left: _start, - child: Container( - height: _screenSize.height, - width: _screenSize.height * scale, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage("assets/pinakothiki_building.jpg"), - fit: BoxFit.fitHeight)), - ), - ), - Center(child: Text("$_start $_sign\n$_xValues")), - Container( - // add gradient in front of building photo to make text in - // front of it legible, based on the example provided here - // https://api.flutter.dev/flutter/widgets/Stack-class.html - alignment: Alignment.bottomRight, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withAlpha(0), - Colors.black12, - Colors.black38, - Colors.black - ], - ), - ), - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 20, horizontal: 8), - child: Text( - "Κρατική Πινακοθήκη\nΣύγχρονης Κυπριακής Τέχνης", - style: TextStyle(fontSize: 30), - textAlign: TextAlign.end, - ), - ), - ), - ], - ), - ), - ), - ); - } - - @override - void dispose() { - super.dispose(); - _subscription.cancel(); - } -} diff --git a/lib/lang/localization.dart b/lib/lang/localization.dart index 65a921c..9bc4218 100644 --- a/lib/lang/localization.dart +++ b/lib/lang/localization.dart @@ -20,19 +20,19 @@ part 'localization.g.dart'; /// /// In case the commands above report conflicts, append /// `--delete-conflicting-outputs` and re-run. -@SheetLocalization(docId, i18nSheetID, 23) +@SheetLocalization(docId, i18nSheetID, 29) // The number is the generated version, and must be incremented each time the // GSheet is updated, to regenerate a new version of the labels. -class AppLocalizationsDelegate extends LocalizationsDelegate { +class AppLocalizationsDelegate + extends LocalizationsDelegate { const AppLocalizationsDelegate(); @override - bool isSupported(Locale locale) => - AppLocalizations.languages.containsKey(locale); + bool isSupported(Locale locale) => localizedLabels.containsKey(locale); @override - Future load(Locale locale) => - SynchronousFuture(AppLocalizations(locale)); + Future load(Locale locale) => + SynchronousFuture(localizedLabels[locale]!); @override bool shouldReload(AppLocalizationsDelegate old) => false; diff --git a/lib/lang/localization.g.dart b/lib/lang/localization.g.dart index 5fbd087..48384d1 100644 --- a/lib/lang/localization.g.dart +++ b/lib/lang/localization.g.dart @@ -6,365 +6,512 @@ part of 'localization.dart'; // SheetLocalizationGenerator // ************************************************************************** -// ignore_for_file: camel_case_types - -class AppLocalizations { - AppLocalizations(this.locale) : labels = languages[locale]; - - final Locale locale; - - static final Map languages = { - Locale.fromSubtags(languageCode: 'en'): AppLocalizations_Labels( - artworks: 'Artworks', - artists: 'Artists', - featuredArtwork: 'Featured artwork', - biography: 'Short biography', - artworksBy: 'Artworks by', - description: 'Description', - galleryName: 'State Gallery of Contemporary Art', - artistDetails: 'Artist details', - artworkDetails: 'Artwork details', - nav: AppLocalizations_Labels_Nav( - explore: 'Explore', - identify: 'Identify artwork', - ), - button: AppLocalizations_Labels_Button( - more: 'more', - close: 'Close', - ), - msg: AppLocalizations_Labels_Msg( - analysing: 'Analysing...', - noneIdentified: 'No artworks identified', - pointTheCamera: 'Point the camera to an artwork', - unableToLanchUrl: 'Unable to launch url', - ), - stngs: AppLocalizations_Labels_Stngs( - title: 'Settings', - groupAbout: 'About', - groupDatabase: 'Database', - groupOther: 'Other', - expandableOther: 'Other settings', - stng: AppLocalizations_Labels_Stngs_Stng( - appInfo: 'App information', - appInfoSummary: 'App version & Open source libraries', - appDescription: - 'App for the State Gallery of Contemporary Cypriot Art.', - appMadeBy: - 'Made by the BIO-SCENT MRG at CYENS (RISE) Centre of Excellence.', - appVersion: 'App version', - changelog: 'Changelog', - changelogSummary: 'Timeline of changes in the app', - databaseBrowser: 'App database browser', - databaseBrowserSummary: - 'Shows all tables and items in the app\'s database', - galleryWebsiteSummary: 'Website for the Gallery', - historyExport: 'Export recognition history', - historyExportSummary: - 'Allows exporting & sharing the artwork recognition history so far', - ), +final localizedLabels = { + Locale.fromSubtags(languageCode: 'en'): const AppLocalizationsData( + artworkDetails: 'Artwork details', + artistDetails: 'Artist details', + galleryName: 'State Gallery of Contemporary Art', + description: 'Description', + artworksBy: 'Artworks by', + biography: 'Short biography', + featuredArtwork: 'Featured artwork', + artists: 'Artists', + artworks: 'Artworks', + stngs: const AppLocalizationsDataStngs( + expandableOther: 'Other settings', + groupOther: 'Other', + groupDatabase: 'Database', + groupAbout: 'About', + title: 'Settings', + stng: const AppLocalizationsDataStngsStng( + historyExportSummary: + 'Allows exporting & sharing the artwork recognition history so far', + historyExport: 'Export recognition history', + galleryWebsiteSummary: 'Website for the Gallery', + databaseBrowserSummary: + 'Shows all tables and items in the app\'s database', + databaseBrowser: 'App database browser', + changelogSummary: 'Timeline of changes in the app', + changelog: 'Changelog', + appVersion: 'App version', + appMadeBy: + 'Made by the BIO-SCENT MRG at CYENS (RISE) Centre of Excellence.', + appDescription: + 'App for the State Gallery of Contemporary Cypriot Art.', + appInfoSummary: 'App version & Open source libraries', + appInfo: 'App information', ), ), - Locale.fromSubtags(languageCode: 'el'): AppLocalizations_Labels( - artworks: 'Εργα τέχνης', - artists: 'Καλλιτέχνες', - featuredArtwork: 'Προτεινόμενο έργο τέχνης', - biography: 'Σύντομο βιογραφικό', - artworksBy: 'Έργα τέχνης του/της', - description: 'Περιγραφή', - galleryName: 'Κρατική Πινακοθήκη Σύγχρονης Τέχνης', - artistDetails: 'Στοιχεία καλλιτέχνη/ιδας', - artworkDetails: 'Στοιχεία έργου τέχνης', - nav: AppLocalizations_Labels_Nav( - explore: 'Εξερευνήστε', - identify: 'Αναγνώριση έργου', - ), - button: AppLocalizations_Labels_Button( - more: 'περισσότερα', - close: 'Κλείσιμο', - ), - msg: AppLocalizations_Labels_Msg( - analysing: 'Ανάλυση εικόνας...', - noneIdentified: 'Καμία αναγνώριση', - pointTheCamera: 'Στρέψτε την κάμερα σε ένα έργο τέχνης', - unableToLanchUrl: 'Δεν είναι δυνατή η εκκίνηση του url', - ), - stngs: AppLocalizations_Labels_Stngs( - title: 'Ρυθμίσεις', - groupAbout: 'Σχετικά', - groupDatabase: 'Βάση δεδομένων', - groupOther: 'Πρόσθετα', - expandableOther: 'Άλλες ρυθμίσεις', - stng: AppLocalizations_Labels_Stngs_Stng( - appInfo: 'Πληροφορίες για την εφαρμογή', - appInfoSummary: 'Εκδοση εφαρμογής & Βιβλιοθήκες ανοιχτού κώδικα', - appDescription: - 'Εφαρμογή για την Κρατική Πινακοθήκη Σύγχρονης Κυπριακής Τέχνης.', - appMadeBy: - 'Δημιουργία του BIO-SCENT MRG στο CYENS (RISE) Centre of Excellence.', - appVersion: 'Έκδοση εφαρμογής', - changelog: 'Ιστορικό αλλαγών', - changelogSummary: 'Ιστορικό αλλαγών στην εφαρμογή', - databaseBrowser: 'Περιήγηση βάσης δεδομένων εφαρμογής', - databaseBrowserSummary: - 'Όλοι οι πίνακες και αντικείμενα στην βάση δεδομένων της εφαρμογής', - galleryWebsiteSummary: 'Ιστοσελίδα της Πινακοθήκης', - historyExport: 'Εξαγωγή ιστορικού αναγνωρίσεων', - historyExportSummary: - 'Επιτρέπει την εξαγωγή και κοινοποίηση του ιστορικού αναγνώρισης έργων τέχνης μέχρι τώρα', - ), + msg: const AppLocalizationsDataMsg( + unableToLaunchUrl: 'Unable to launch url', + pointTheCamera: 'Point the camera to an artwork', + noneIdentified: 'No artworks identified', + analysing: 'Analysing...', + ), + button: const AppLocalizationsDataButton( + close: 'Close', + more: 'more', + ), + nav: const AppLocalizationsDataNav( + identify: 'Identify artwork', + explore: 'Explore', + ), + ), + Locale.fromSubtags(languageCode: 'el'): const AppLocalizationsData( + artworkDetails: 'Στοιχεία έργου τέχνης', + artistDetails: 'Στοιχεία καλλιτέχνη/ιδας', + galleryName: 'Κρατική Πινακοθήκη Σύγχρονης Τέχνης', + description: 'Περιγραφή', + artworksBy: 'Έργα τέχνης του/της', + biography: 'Σύντομο βιογραφικό', + featuredArtwork: 'Προτεινόμενο έργο τέχνης', + artists: 'Καλλιτέχνες', + artworks: 'Εργα τέχνης', + stngs: const AppLocalizationsDataStngs( + expandableOther: 'Άλλες ρυθμίσεις', + groupOther: 'Πρόσθετα', + groupDatabase: 'Βάση δεδομένων', + groupAbout: 'Σχετικά', + title: 'Ρυθμίσεις', + stng: const AppLocalizationsDataStngsStng( + historyExportSummary: + 'Επιτρέπει την εξαγωγή και κοινοποίηση του ιστορικού αναγνώρισης έργων τέχνης μέχρι τώρα', + historyExport: 'Εξαγωγή ιστορικού αναγνωρίσεων', + galleryWebsiteSummary: 'Ιστοσελίδα της Πινακοθήκης', + databaseBrowserSummary: + 'Όλοι οι πίνακες και αντικείμενα στην βάση δεδομένων της εφαρμογής', + databaseBrowser: 'Περιήγηση βάσης δεδομένων εφαρμογής', + changelogSummary: 'Ιστορικό αλλαγών στην εφαρμογή', + changelog: 'Ιστορικό αλλαγών', + appVersion: 'Έκδοση εφαρμογής', + appMadeBy: + 'Δημιουργία του BIO-SCENT MRG στο CYENS (RISE) Centre of Excellence.', + appDescription: + 'Εφαρμογή για την Κρατική Πινακοθήκη Σύγχρονης Κυπριακής Τέχνης.', + appInfoSummary: 'Εκδοση εφαρμογής & Βιβλιοθήκες ανοιχτού κώδικα', + appInfo: 'Πληροφορίες για την εφαρμογή', ), ), - }; - - final AppLocalizations_Labels labels; + msg: const AppLocalizationsDataMsg( + unableToLaunchUrl: 'Δεν είναι δυνατή η εκκίνηση του url', + pointTheCamera: 'Στρέψτε την κάμερα σε ένα έργο τέχνης', + noneIdentified: 'Καμία αναγνώριση', + analysing: 'Ανάλυση εικόνας...', + ), + button: const AppLocalizationsDataButton( + close: 'Κλείσιμο', + more: 'περισσότερα', + ), + nav: const AppLocalizationsDataNav( + identify: 'Αναγνώριση έργου', + explore: 'Εξερευνήστε', + ), + ), +}; + +class AppLocalizationsData { + const AppLocalizationsData({ + required this.artworkDetails, + required this.artistDetails, + required this.galleryName, + required this.description, + required this.artworksBy, + required this.biography, + required this.featuredArtwork, + required this.artists, + required this.artworks, + required this.stngs, + required this.msg, + required this.button, + required this.nav, + }); - static AppLocalizations_Labels of(BuildContext context) => - Localizations.of(context, AppLocalizations)?.labels; + final String artworkDetails; + final String artistDetails; + final String galleryName; + final String description; + final String artworksBy; + final String biography; + final String featuredArtwork; + final String artists; + final String artworks; + final AppLocalizationsDataStngs stngs; + final AppLocalizationsDataMsg msg; + final AppLocalizationsDataButton button; + final AppLocalizationsDataNav nav; + factory AppLocalizationsData.fromJson(Map map) => + AppLocalizationsData( + artworkDetails: map['artworkDetails']! as String, + artistDetails: map['artistDetails']! as String, + galleryName: map['galleryName']! as String, + description: map['description']! as String, + artworksBy: map['artworksBy']! as String, + biography: map['biography']! as String, + featuredArtwork: map['featuredArtwork']! as String, + artists: map['artists']! as String, + artworks: map['artworks']! as String, + stngs: AppLocalizationsDataStngs.fromJson( + map['stngs']! as Map), + msg: AppLocalizationsDataMsg.fromJson( + map['msg']! as Map), + button: AppLocalizationsDataButton.fromJson( + map['button']! as Map), + nav: AppLocalizationsDataNav.fromJson( + map['nav']! as Map), + ); + + AppLocalizationsData copyWith({ + String? artworkDetails, + String? artistDetails, + String? galleryName, + String? description, + String? artworksBy, + String? biography, + String? featuredArtwork, + String? artists, + String? artworks, + AppLocalizationsDataStngs? stngs, + AppLocalizationsDataMsg? msg, + AppLocalizationsDataButton? button, + AppLocalizationsDataNav? nav, + }) => + AppLocalizationsData( + artworkDetails: artworkDetails ?? this.artworkDetails, + artistDetails: artistDetails ?? this.artistDetails, + galleryName: galleryName ?? this.galleryName, + description: description ?? this.description, + artworksBy: artworksBy ?? this.artworksBy, + biography: biography ?? this.biography, + featuredArtwork: featuredArtwork ?? this.featuredArtwork, + artists: artists ?? this.artists, + artworks: artworks ?? this.artworks, + stngs: stngs ?? this.stngs, + msg: msg ?? this.msg, + button: button ?? this.button, + nav: nav ?? this.nav, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AppLocalizationsData && + artworkDetails == other.artworkDetails && + artistDetails == other.artistDetails && + galleryName == other.galleryName && + description == other.description && + artworksBy == other.artworksBy && + biography == other.biography && + featuredArtwork == other.featuredArtwork && + artists == other.artists && + artworks == other.artworks && + stngs == other.stngs && + msg == other.msg && + button == other.button && + nav == other.nav); + @override + int get hashCode => + runtimeType.hashCode ^ + artworkDetails.hashCode ^ + artistDetails.hashCode ^ + galleryName.hashCode ^ + description.hashCode ^ + artworksBy.hashCode ^ + biography.hashCode ^ + featuredArtwork.hashCode ^ + artists.hashCode ^ + artworks.hashCode ^ + stngs.hashCode ^ + msg.hashCode ^ + button.hashCode ^ + nav.hashCode; } -class AppLocalizations_Labels_Nav { - const AppLocalizations_Labels_Nav({this.explore, this.identify}); - - final String explore; +class AppLocalizationsDataStngs { + const AppLocalizationsDataStngs({ + required this.expandableOther, + required this.groupOther, + required this.groupDatabase, + required this.groupAbout, + required this.title, + required this.stng, + }); - final String identify; - - String getByKey(String key) { - switch (key) { - case 'explore': - return explore; - case 'identify': - return identify; - default: - return ''; - } - } + final String expandableOther; + final String groupOther; + final String groupDatabase; + final String groupAbout; + final String title; + final AppLocalizationsDataStngsStng stng; + factory AppLocalizationsDataStngs.fromJson(Map map) => + AppLocalizationsDataStngs( + expandableOther: map['expandableOther']! as String, + groupOther: map['groupOther']! as String, + groupDatabase: map['groupDatabase']! as String, + groupAbout: map['groupAbout']! as String, + title: map['title']! as String, + stng: AppLocalizationsDataStngsStng.fromJson( + map['stng']! as Map), + ); + + AppLocalizationsDataStngs copyWith({ + String? expandableOther, + String? groupOther, + String? groupDatabase, + String? groupAbout, + String? title, + AppLocalizationsDataStngsStng? stng, + }) => + AppLocalizationsDataStngs( + expandableOther: expandableOther ?? this.expandableOther, + groupOther: groupOther ?? this.groupOther, + groupDatabase: groupDatabase ?? this.groupDatabase, + groupAbout: groupAbout ?? this.groupAbout, + title: title ?? this.title, + stng: stng ?? this.stng, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AppLocalizationsDataStngs && + expandableOther == other.expandableOther && + groupOther == other.groupOther && + groupDatabase == other.groupDatabase && + groupAbout == other.groupAbout && + title == other.title && + stng == other.stng); + @override + int get hashCode => + runtimeType.hashCode ^ + expandableOther.hashCode ^ + groupOther.hashCode ^ + groupDatabase.hashCode ^ + groupAbout.hashCode ^ + title.hashCode ^ + stng.hashCode; } -class AppLocalizations_Labels_Button { - const AppLocalizations_Labels_Button({this.more, this.close}); - - final String more; - - final String close; +class AppLocalizationsDataStngsStng { + const AppLocalizationsDataStngsStng({ + required this.historyExportSummary, + required this.historyExport, + required this.galleryWebsiteSummary, + required this.databaseBrowserSummary, + required this.databaseBrowser, + required this.changelogSummary, + required this.changelog, + required this.appVersion, + required this.appMadeBy, + required this.appDescription, + required this.appInfoSummary, + required this.appInfo, + }); - String getByKey(String key) { - switch (key) { - case 'more': - return more; - case 'close': - return close; - default: - return ''; - } - } + final String historyExportSummary; + final String historyExport; + final String galleryWebsiteSummary; + final String databaseBrowserSummary; + final String databaseBrowser; + final String changelogSummary; + final String changelog; + final String appVersion; + final String appMadeBy; + final String appDescription; + final String appInfoSummary; + final String appInfo; + factory AppLocalizationsDataStngsStng.fromJson(Map map) => + AppLocalizationsDataStngsStng( + historyExportSummary: map['historyExportSummary']! as String, + historyExport: map['historyExport']! as String, + galleryWebsiteSummary: map['galleryWebsiteSummary']! as String, + databaseBrowserSummary: map['databaseBrowserSummary']! as String, + databaseBrowser: map['databaseBrowser']! as String, + changelogSummary: map['changelogSummary']! as String, + changelog: map['changelog']! as String, + appVersion: map['appVersion']! as String, + appMadeBy: map['appMadeBy']! as String, + appDescription: map['appDescription']! as String, + appInfoSummary: map['appInfoSummary']! as String, + appInfo: map['appInfo']! as String, + ); + + AppLocalizationsDataStngsStng copyWith({ + String? historyExportSummary, + String? historyExport, + String? galleryWebsiteSummary, + String? databaseBrowserSummary, + String? databaseBrowser, + String? changelogSummary, + String? changelog, + String? appVersion, + String? appMadeBy, + String? appDescription, + String? appInfoSummary, + String? appInfo, + }) => + AppLocalizationsDataStngsStng( + historyExportSummary: historyExportSummary ?? this.historyExportSummary, + historyExport: historyExport ?? this.historyExport, + galleryWebsiteSummary: + galleryWebsiteSummary ?? this.galleryWebsiteSummary, + databaseBrowserSummary: + databaseBrowserSummary ?? this.databaseBrowserSummary, + databaseBrowser: databaseBrowser ?? this.databaseBrowser, + changelogSummary: changelogSummary ?? this.changelogSummary, + changelog: changelog ?? this.changelog, + appVersion: appVersion ?? this.appVersion, + appMadeBy: appMadeBy ?? this.appMadeBy, + appDescription: appDescription ?? this.appDescription, + appInfoSummary: appInfoSummary ?? this.appInfoSummary, + appInfo: appInfo ?? this.appInfo, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AppLocalizationsDataStngsStng && + historyExportSummary == other.historyExportSummary && + historyExport == other.historyExport && + galleryWebsiteSummary == other.galleryWebsiteSummary && + databaseBrowserSummary == other.databaseBrowserSummary && + databaseBrowser == other.databaseBrowser && + changelogSummary == other.changelogSummary && + changelog == other.changelog && + appVersion == other.appVersion && + appMadeBy == other.appMadeBy && + appDescription == other.appDescription && + appInfoSummary == other.appInfoSummary && + appInfo == other.appInfo); + @override + int get hashCode => + runtimeType.hashCode ^ + historyExportSummary.hashCode ^ + historyExport.hashCode ^ + galleryWebsiteSummary.hashCode ^ + databaseBrowserSummary.hashCode ^ + databaseBrowser.hashCode ^ + changelogSummary.hashCode ^ + changelog.hashCode ^ + appVersion.hashCode ^ + appMadeBy.hashCode ^ + appDescription.hashCode ^ + appInfoSummary.hashCode ^ + appInfo.hashCode; } -class AppLocalizations_Labels_Msg { - const AppLocalizations_Labels_Msg( - {this.analysing, - this.noneIdentified, - this.pointTheCamera, - this.unableToLanchUrl}); - - final String analysing; - - final String noneIdentified; +class AppLocalizationsDataMsg { + const AppLocalizationsDataMsg({ + required this.unableToLaunchUrl, + required this.pointTheCamera, + required this.noneIdentified, + required this.analysing, + }); + final String unableToLaunchUrl; final String pointTheCamera; - - final String unableToLanchUrl; - - String getByKey(String key) { - switch (key) { - case 'analysing': - return analysing; - case 'noneIdentified': - return noneIdentified; - case 'pointTheCamera': - return pointTheCamera; - case 'unableToLanchUrl': - return unableToLanchUrl; - default: - return ''; - } - } -} - -class AppLocalizations_Labels_Stngs_Stng { - const AppLocalizations_Labels_Stngs_Stng( - {this.appInfo, - this.appInfoSummary, - this.appDescription, - this.appMadeBy, - this.appVersion, - this.changelog, - this.changelogSummary, - this.databaseBrowser, - this.databaseBrowserSummary, - this.galleryWebsiteSummary, - this.historyExport, - this.historyExportSummary}); - - final String appInfo; - - final String appInfoSummary; - - final String appDescription; - - final String appMadeBy; - - final String appVersion; - - final String changelog; - - final String changelogSummary; - - final String databaseBrowser; - - final String databaseBrowserSummary; - - final String galleryWebsiteSummary; - - final String historyExport; - - final String historyExportSummary; - - String getByKey(String key) { - switch (key) { - case 'appInfo': - return appInfo; - case 'appInfoSummary': - return appInfoSummary; - case 'appDescription': - return appDescription; - case 'appMadeBy': - return appMadeBy; - case 'appVersion': - return appVersion; - case 'changelog': - return changelog; - case 'changelogSummary': - return changelogSummary; - case 'databaseBrowser': - return databaseBrowser; - case 'databaseBrowserSummary': - return databaseBrowserSummary; - case 'galleryWebsiteSummary': - return galleryWebsiteSummary; - case 'historyExport': - return historyExport; - case 'historyExportSummary': - return historyExportSummary; - default: - return ''; - } - } + final String noneIdentified; + final String analysing; + factory AppLocalizationsDataMsg.fromJson(Map map) => + AppLocalizationsDataMsg( + unableToLaunchUrl: map['unableToLaunchUrl']! as String, + pointTheCamera: map['pointTheCamera']! as String, + noneIdentified: map['noneIdentified']! as String, + analysing: map['analysing']! as String, + ); + + AppLocalizationsDataMsg copyWith({ + String? unableToLaunchUrl, + String? pointTheCamera, + String? noneIdentified, + String? analysing, + }) => + AppLocalizationsDataMsg( + unableToLaunchUrl: unableToLaunchUrl ?? this.unableToLaunchUrl, + pointTheCamera: pointTheCamera ?? this.pointTheCamera, + noneIdentified: noneIdentified ?? this.noneIdentified, + analysing: analysing ?? this.analysing, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AppLocalizationsDataMsg && + unableToLaunchUrl == other.unableToLaunchUrl && + pointTheCamera == other.pointTheCamera && + noneIdentified == other.noneIdentified && + analysing == other.analysing); + @override + int get hashCode => + runtimeType.hashCode ^ + unableToLaunchUrl.hashCode ^ + pointTheCamera.hashCode ^ + noneIdentified.hashCode ^ + analysing.hashCode; } -class AppLocalizations_Labels_Stngs { - const AppLocalizations_Labels_Stngs( - {this.title, - this.groupAbout, - this.groupDatabase, - this.groupOther, - this.expandableOther, - this.stng}); +class AppLocalizationsDataButton { + const AppLocalizationsDataButton({ + required this.close, + required this.more, + }); - final String title; - - final String groupAbout; - - final String groupDatabase; - - final String groupOther; - - final String expandableOther; - - final AppLocalizations_Labels_Stngs_Stng stng; - - String getByKey(String key) { - switch (key) { - case 'title': - return title; - case 'groupAbout': - return groupAbout; - case 'groupDatabase': - return groupDatabase; - case 'groupOther': - return groupOther; - case 'expandableOther': - return expandableOther; - default: - return ''; - } - } + final String close; + final String more; + factory AppLocalizationsDataButton.fromJson(Map map) => + AppLocalizationsDataButton( + close: map['close']! as String, + more: map['more']! as String, + ); + + AppLocalizationsDataButton copyWith({ + String? close, + String? more, + }) => + AppLocalizationsDataButton( + close: close ?? this.close, + more: more ?? this.more, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AppLocalizationsDataButton && + close == other.close && + more == other.more); + @override + int get hashCode => runtimeType.hashCode ^ close.hashCode ^ more.hashCode; } -class AppLocalizations_Labels { - const AppLocalizations_Labels( - {this.artworks, - this.artists, - this.featuredArtwork, - this.biography, - this.artworksBy, - this.description, - this.galleryName, - this.artistDetails, - this.artworkDetails, - this.nav, - this.button, - this.msg, - this.stngs}); - - final String artworks; - - final String artists; - - final String featuredArtwork; - - final String biography; - - final String artworksBy; - - final String description; - - final String galleryName; - - final String artistDetails; +class AppLocalizationsDataNav { + const AppLocalizationsDataNav({ + required this.identify, + required this.explore, + }); - final String artworkDetails; - - final AppLocalizations_Labels_Nav nav; - - final AppLocalizations_Labels_Button button; - - final AppLocalizations_Labels_Msg msg; - - final AppLocalizations_Labels_Stngs stngs; - - String getByKey(String key) { - switch (key) { - case 'artworks': - return artworks; - case 'artists': - return artists; - case 'featuredArtwork': - return featuredArtwork; - case 'biography': - return biography; - case 'artworksBy': - return artworksBy; - case 'description': - return description; - case 'galleryName': - return galleryName; - case 'artistDetails': - return artistDetails; - case 'artworkDetails': - return artworkDetails; - default: - return ''; - } - } + final String identify; + final String explore; + factory AppLocalizationsDataNav.fromJson(Map map) => + AppLocalizationsDataNav( + identify: map['identify']! as String, + explore: map['explore']! as String, + ); + + AppLocalizationsDataNav copyWith({ + String? identify, + String? explore, + }) => + AppLocalizationsDataNav( + identify: identify ?? this.identify, + explore: explore ?? this.explore, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AppLocalizationsDataNav && + identify == other.identify && + explore == other.explore); + @override + int get hashCode => + runtimeType.hashCode ^ identify.hashCode ^ explore.hashCode; } diff --git a/lib/main.dart b/lib/main.dart index a25fca3..ba2ad45 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,14 +5,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_settings_screens/flutter_settings_screens.dart'; import 'package:modern_art_app/data/database.dart'; -import 'package:modern_art_app/data/sensitive.dart'; +import 'package:modern_art_app/lang/localization.dart'; import 'package:modern_art_app/ui/pages/main_page.dart'; import 'package:provider/provider.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; -import 'lang/localization.dart'; - -List cameras; +List cameras = []; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -20,19 +17,11 @@ Future main() async { // get back camera cameras = await availableCameras(); } on CameraException catch (e) { - print("Error ${e.code}\nError msg: ${e.description}"); + debugPrint('Error ${e.code}\nError msg: ${e.description}'); } // init settings await Settings.init(); - // init Sentry crash monitoring library - await SentryFlutter.init( - (options) { - options.dsn = sentryDsn; - // options.useNativeBreadcrumbTracking(); - }, - appRunner: () => runApp(MyApp()), - ); - // runApp(MyApp()); + runApp(MyApp()); } class MyApp extends StatelessWidget { @@ -49,15 +38,15 @@ class MyApp extends StatelessWidget { child: MaterialApp( // can specify app locale here explicitly // locale: AppLocalizations.languages.keys.first, - localizationsDelegates: [ + localizationsDelegates: const [ // Custom localization delegate, gen. by flutter_sheet_localization lib - const AppLocalizationsDelegate(), + AppLocalizationsDelegate(), // Built-in localization of basic text for Material widgets GlobalMaterialLocalizations.delegate, // Built-in localization for text direction LTR/RTL GlobalWidgetsLocalizations.delegate, ], - supportedLocales: AppLocalizations.languages.keys.toList(), + supportedLocales: localizedLabels.keys.toList(), localeResolutionCallback: (locale, supportedLocales) { /// Algorithm for determining which locale to choose for the app; the /// algorithm used is very simple, just checking the languageCode, and @@ -66,8 +55,8 @@ class MyApp extends StatelessWidget { /// - https://api.flutter.dev/flutter/widgets/WidgetsApp/localeResolutionCallback.html /// - https://flutter.dev/docs/development/accessibility-and-localization/internationalization /// - https://resocoder.com/2019/06/01/flutter-localization-the-easy-way-internationalization-with-json/ - for (var supportedLocale in supportedLocales) { - if (supportedLocale.languageCode == locale.languageCode) { + for (final supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale?.languageCode) { return supportedLocale; } } @@ -75,7 +64,6 @@ class MyApp extends StatelessWidget { return supportedLocales.first; }, debugShowCheckedModeBanner: false, - navigatorObservers: [SentryNavigatorObserver()], theme: ThemeData.dark(), home: MainPage(cameras: cameras), ), diff --git a/lib/painting_details.dart b/lib/painting_details.dart deleted file mode 100644 index bccb6ff..0000000 --- a/lib/painting_details.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_lorem/flutter_lorem.dart'; - -class PaintingDetails extends StatelessWidget { - final String path; - final String name; - final String painter; - - // todo add hero tag as argument - - const PaintingDetails({ - Key key, - this.path = "assets/paintings/mona_lisa.webp", - this.name = "Painting name", - this.painter = "Painter", - }) : super(key: key); - - @override - Widget build(BuildContext context) { - Size size = MediaQuery.of(context).size; - return Scaffold( - body: SafeArea( - child: Stack( - children: [ - Positioned.fill( - child: Container( - height: size.height, - child: Image.asset(path, fit: BoxFit.cover)), - ), - Container( - child: Column( - children: [ - Text("Description", style: TextStyle(fontSize: 25)), - Text(lorem(paragraphs: 2, words: 100)) - ], - ), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withAlpha(0), - Colors.black12, - Colors.black12, - Colors.black38, - Colors.black - ], - ), - ), - ), - Positioned( - child: Container( - height: size.width * 0.3, - width: size.width * 0.3, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage( - "assets/painters/leonardo_da_vinci.webp"), - fit: BoxFit.cover), - shape: BoxShape.circle), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/painting_list.dart b/lib/painting_list.dart deleted file mode 100644 index a140585..0000000 --- a/lib/painting_list.dart +++ /dev/null @@ -1,170 +0,0 @@ -// import 'package:flutter/material.dart'; -// import 'package:flutter/painting.dart'; -// import 'package:modern_art_app/data_processing.dart'; -// -// import 'ui/widgets/artwork_details_page.dart'; -// -// class PaintingListVertical extends StatelessWidget { -// final String listType; -// -// const PaintingListVertical({Key key, @required this.listType}) -// : super(key: key); -// -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// appBar: AppBar(title: Text("${listType}s")), -// body: listVerticalFuture(listType), -// ); -// } -// } -// -// class PaintingRow extends StatelessWidget { -// final String paintingName; -// final String _path; -// -// PaintingRow({Key key, this.paintingName, path}) -// : _path = path ?? "assets/paintings/mona_lisa.webp", -// super(key: key); -// -// @override -// Widget build(BuildContext context) { -// double size = MediaQuery.of(context).size.width * 0.3; -// return Padding( -// padding: EdgeInsets.all(8), -// child: InkWell( -// onTap: () { -// Navigator.push( -// context, -// MaterialPageRoute( -// builder: (context) => ArtworkDetailsPage( -// name: paintingName, -// path: _path, -// ))); -// }, -// child: Row( -// mainAxisAlignment: MainAxisAlignment.start, -// children: [ -// Hero( -// tag: paintingName, -// child: Image.asset( -// _path, -// fit: BoxFit.cover, -// height: size, -// width: size, -// )), -// Text(paintingName), -// ], -// ), -// ), -// ); -// } -// } -// -// Widget listVerticalFuture(String listType) { -// return FutureBuilder( -// future: getAllAssets(assetType: listType), -// builder: (context, snapshot) { -// if (snapshot.hasData) { -// return ListView.builder( -// padding: const EdgeInsets.all(8), -// itemBuilder: (BuildContext context, int index) { -// List paintings = snapshot.data; -// int len = paintings.length; -// return PaintingRow( -// paintingName: "$listType $index", -// path: paintings[index % len], -// ); -// }); -// } -// return Center(child: CircularProgressIndicator()); -// }, -// ); -// } -// -// class PaintingListHorizontal extends StatelessWidget { -// final String listType; -// -// const PaintingListHorizontal({Key key, @required this.listType}) -// : super(key: key); -// -// @override -// Widget build(BuildContext context) { -// Size size = MediaQuery.of(context).size; -// return Container( -// height: listType == "Painting" ? size.width * 0.5 : size.width * 0.28, -// child: listHorizontalFuture(listType), -// ); -// } -// } -// -// class PaintingTile extends StatelessWidget { -// final String paintingName; -// final String _path; -// final double tileSideLength; -// final double optionalTileHeight; -// -// PaintingTile( -// {Key key, -// @required this.paintingName, -// @required this.tileSideLength, -// this.optionalTileHeight, -// String path}) -// : _path = path ?? "assets/paintings/mona_lisa.webp", -// super(key: key); -// -// @override -// Widget build(BuildContext context) { -// return InkWell( -// onTap: () { -// Navigator.push( -// context, -// MaterialPageRoute( -// builder: (context) => ArtworkDetailsPage( -// name: paintingName, -// path: _path, -// ))); -// }, -// child: Container( -// margin: EdgeInsets.symmetric(horizontal: 8), -// width: tileSideLength, -// height: optionalTileHeight ?? tileSideLength, -// child: ClipRRect( -// borderRadius: BorderRadius.circular(10), -// child: Hero( -// tag: paintingName, -// child: Image.asset( -// _path, -// fit: BoxFit.cover, -// ), -// ), -// ), -// ), -// ); -// } -// } -// -// Widget listHorizontalFuture(String listType) { -// return FutureBuilder( -// future: getAllAssets(assetType: listType), -// builder: (context, snapshot) { -// double width = MediaQuery.of(context).size.width; -// if (snapshot.hasData) { -// return ListView.builder( -// scrollDirection: Axis.horizontal, -// itemBuilder: (BuildContext context, int index) { -// List results = snapshot.data; -// int len = results.length; -// return PaintingTile( -// paintingName: "$listType $index", -// tileSideLength: -// listType == "Painting" ? width * 0.5 / 1.618 : width * 0.28, -// optionalTileHeight: listType == "Painting" ? width * 0.5 : null, -// path: results[index % len], -// ); -// }); -// } -// return Center(child: CircularProgressIndicator()); -// }, -// ); -// } diff --git a/lib/tensorflow/bbox.dart b/lib/tensorflow/bbox.dart index a4c95b2..115ae8d 100644 --- a/lib/tensorflow/bbox.dart +++ b/lib/tensorflow/bbox.dart @@ -1,10 +1,19 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; - -import 'models.dart'; +import 'package:modern_art_app/tensorflow/models.dart'; class BBox extends StatelessWidget { + const BBox( + this.results, + this.previewHeight, + this.previewWidth, + this.screenHeight, + this.screenWidth, + this.model, + this.inferenceTime, + ); + final List results; final int previewHeight; final int previewWidth; @@ -13,133 +22,137 @@ class BBox extends StatelessWidget { final String model; final int inferenceTime; - BBox(this.results, this.previewHeight, this.previewWidth, this.screenHeight, - this.screenWidth, this.model, this.inferenceTime); - @override Widget build(BuildContext context) { List _renderBoxes() { - return results.map((re) { - var _x = re["rect"]["x"]; - var _w = re["rect"]["w"]; - var _y = re["rect"]["y"]; - var _h = re["rect"]["h"]; - var scaleW, scaleH, x, y, w, h; - - if (screenHeight / screenWidth > previewHeight / previewWidth) { - scaleW = screenHeight / previewHeight * previewWidth; - scaleH = screenHeight; - var difW = (scaleW - screenWidth) / scaleW; - x = (_x - difW / 2) * scaleW; - w = _w * scaleW; - if (_x < difW / 2) w -= (difW / 2 - _x) * scaleW; - y = _y * scaleH; - h = _h * scaleH; - } else { - scaleH = screenWidth / previewWidth * previewHeight; - scaleW = screenWidth; - var difH = (scaleH - screenHeight) / scaleH; - x = _x * scaleW; - w = _w * scaleW; - y = (_y - difH / 2) * scaleH; - h = _h * scaleH; - if (_y < difH / 2) h -= (difH / 2 - _y) * scaleH; - } - - return Positioned( - left: math.max(0, x), - top: math.max(0, y), - width: w, - height: h, - child: Container( - padding: EdgeInsets.only(top: 5.0, left: 5.0), - decoration: BoxDecoration( - border: Border.all( - color: Color.fromRGBO(37, 213, 253, 1.0), - width: 3.0, - ), - ), - child: Text( - "${re["detectedClass"]} ${(re["confidenceInClass"] * 100).toStringAsFixed(0)}%", - style: TextStyle( - color: Color.fromRGBO(37, 213, 253, 1.0), - fontSize: 14.0, - fontWeight: FontWeight.bold, - ), - ), - ), - ); - }).toList(); - } - - List _renderStrings() { - double offset = -10; - return results.map((re) { - offset = offset + 30; - return Positioned( - left: 10, - top: offset, - // width: screenWidth, - // height: screenHeight, - child: Row( - children: [ - if (!["multiple_artworks", "no_artwork", "one_artwork"] - .contains(re['label'])) - Image.asset( - "assets/paintings/${re['label']}.webp", - width: screenWidth / 5, - height: screenWidth / 5, - ), - Text( - "${re["label"]} ${(re["confidence"] * 100).toStringAsFixed(0)}%\n$inferenceTime ms", - style: TextStyle(color: Colors.white, fontSize: 15.0), - ), - ], - ), - ); - }).toList(); - } - - List _renderKeypoints() { - var lists = []; - results.forEach((re) { - var list = re["keypoints"].values.map((k) { - var _x = k["x"]; - var _y = k["y"]; - var scaleW, scaleH, x, y; + return results.map( + (re) { + final _x = re['rect']['x']; + final _w = re['rect']['w']; + final _y = re['rect']['y']; + final _h = re['rect']['h']; + var scaleW, scaleH, x, y, w, h; if (screenHeight / screenWidth > previewHeight / previewWidth) { scaleW = screenHeight / previewHeight * previewWidth; scaleH = screenHeight; - var difW = (scaleW - screenWidth) / scaleW; + final difW = (scaleW - screenWidth) / scaleW; x = (_x - difW / 2) * scaleW; + w = _w * scaleW; + if (_x < difW / 2) w -= (difW / 2 - _x) * scaleW; y = _y * scaleH; + h = _h * scaleH; } else { scaleH = screenWidth / previewWidth * previewHeight; scaleW = screenWidth; - var difH = (scaleH - screenHeight) / scaleH; + final difH = (scaleH - screenHeight) / scaleH; x = _x * scaleW; + w = _w * scaleW; y = (_y - difH / 2) * scaleH; + h = _h * scaleH; + if (_y < difH / 2) h -= (difH / 2 - _y) * scaleH; } + return Positioned( - left: x - 6, - top: y - 6, - width: 100, - height: 12, + left: math.max(0, x), + top: math.max(0, y), + width: w, + height: h, child: Container( + padding: const EdgeInsets.only(top: 5.0, left: 5.0), + decoration: BoxDecoration( + border: Border.all( + color: const Color.fromRGBO(37, 213, 253, 1.0), + width: 3.0, + ), + ), child: Text( - "● ${k["part"]}", - style: TextStyle( + "${re["detectedClass"]} " + "${(re["confidenceInClass"] * 100).toStringAsFixed(0)}%", + style: const TextStyle( color: Color.fromRGBO(37, 213, 253, 1.0), - fontSize: 12.0, + fontSize: 14.0, + fontWeight: FontWeight.bold, ), ), ), ); - }).toList(); + }, + ).toList(); + } + + List _renderStrings() { + double offset = -10; + return results.map( + (re) { + offset = offset + 30; + return Positioned( + left: 10, + top: offset, + // width: screenWidth, + // height: screenHeight, + child: Row( + children: [ + if (!['multiple_artworks', 'no_artwork', 'one_artwork'] + .contains(re['label'])) + Image.asset( + "assets/paintings/${re['label']}.webp", + width: screenWidth / 5, + height: screenWidth / 5, + ), + Text( + "${re["label"]} ${(re["confidence"] * 100).toStringAsFixed(0)}%" + '\n$inferenceTime ms', + style: const TextStyle(color: Colors.white, fontSize: 15.0), + ), + ], + ), + ); + }, + ).toList(); + } + + List _renderKeypoints() { + final lists = []; + results.forEach( + (re) { + final list = re['keypoints'].values.map( + (k) { + final _x = k['x']; + final _y = k['y']; + var scaleW, scaleH, x, y; - lists..addAll(list); - }); + if (screenHeight / screenWidth > previewHeight / previewWidth) { + scaleW = screenHeight / previewHeight * previewWidth; + scaleH = screenHeight; + final difW = (scaleW - screenWidth) / scaleW; + x = (_x - difW / 2) * scaleW; + y = _y * scaleH; + } else { + scaleH = screenWidth / previewWidth * previewHeight; + scaleW = screenWidth; + final difH = (scaleH - screenHeight) / scaleH; + x = _x * scaleW; + y = (_y - difH / 2) * scaleH; + } + return Positioned( + left: x - 6, + top: y - 6, + width: 100, + height: 12, + child: Text( + "● ${k["part"]}", + style: const TextStyle( + color: Color.fromRGBO(37, 213, 253, 1.0), + fontSize: 12.0, + ), + ), + ); + }, + ).toList(); + lists.addAll(list); + }, + ); return lists; } diff --git a/lib/tensorflow/model_selection.dart b/lib/tensorflow/model_selection.dart index c6f74e8..ae1be45 100644 --- a/lib/tensorflow/model_selection.dart +++ b/lib/tensorflow/model_selection.dart @@ -7,6 +7,7 @@ import 'package:modern_art_app/data/artworks_dao.dart'; import 'package:modern_art_app/data/database.dart'; import 'package:modern_art_app/data/inference_algorithms.dart'; import 'package:modern_art_app/data/viewings_dao.dart'; +import 'package:modern_art_app/tensorflow/models.dart'; import 'package:modern_art_app/tensorflow/tensorflow_camera.dart'; import 'package:modern_art_app/ui/pages/artwork_details_page.dart'; import 'package:modern_art_app/ui/pages/settings_page.dart'; @@ -16,34 +17,31 @@ import 'package:provider/provider.dart'; import 'package:tflite/tflite.dart'; import 'package:vibration/vibration.dart'; -import 'models.dart'; - class ModelSelection extends StatefulWidget { - final List cameras; + const ModelSelection(this.cameras); - ModelSelection(this.cameras); + final List cameras; @override - _ModelSelectionState createState() => new _ModelSelectionState(); + _ModelSelectionState createState() => _ModelSelectionState(); } class _ModelSelectionState extends State with WidgetsBindingObserver { - List _recognitions; + List _recognitions = []; int _imageHeight = 0; int _imageWidth = 0; int _inferenceTime = 0; - String _fps = ""; - String _model = ""; + String _fps = ''; + String _model = ''; double _preferredSensitivity = 0.0; bool _navigateToDetails = true; // bool addedViewing = false; - var currentAlgorithm; - String _currentRes = ""; - String _currentAlgo = ""; - ViewingsDao viewingsDao; + late InferenceAlgorithm currentAlgorithm; + String _currentRes = ''; + String _currentAlgo = ''; bool _canVibrate = false; @@ -68,7 +66,7 @@ class _ModelSelectionState extends State super.initState(); // observe for AppLifecycleState changes, to pause CNN if app state changes // https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html - WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance?.addObserver(this); // initialize model initModel(); } @@ -76,193 +74,205 @@ class _ModelSelectionState extends State @override void dispose() { // remove AppLifecycleState observer - WidgetsBinding.instance.removeObserver(this); + WidgetsBinding.instance?.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { - print("CHANGE STATE $state"); + debugPrint('CHANGE STATE $state'); if ([AppLifecycleState.inactive, AppLifecycleState.paused] .contains(state)) { // pause the CNN if user puts app in the background - setState(() { - _model = ""; - }); + setState(() => _model = ''); } else if (state == AppLifecycleState.resumed) { // resume the CNN when user comes back initModel(); } } - initModel() { + void initModel() { // get preferred algorithm from settings - String preferredModel = Settings.getValue(keyCnnModel, mobNetNoArt500_4); + final String preferredModel = + Settings.getValue(keyCnnModel, mobNetNoArt500_4); // get preferred algorithm, sensitivity and winThreshP from settings - String preferredAlgorithm = + final String preferredAlgorithm = Settings.getValue(keyRecognitionAlgo, firstAlgorithm); - double sensitivity = Settings.getValue( + final double sensitivity = Settings.getValue( keyCnnSensitivity, - defaultSettings(preferredAlgorithm)[keyCnnSensitivity], + defaultSettings(preferredAlgorithm)![keyCnnSensitivity] as double, ); // keyWinThreshP's value is stored as double, converted to int here - int winThreshP = Settings.getValue( + final int winThreshP = Settings.getValue( keyWinThreshP, - defaultSettings(preferredAlgorithm)[keyWinThreshP], + defaultSettings(preferredAlgorithm)![keyWinThreshP] as double, ).round(); // determine from settings whether to automatically navigate to an artwork's // details when a recognition occurs - bool navigateToDetails = Settings.getValue(keyNavigateToDetails, true); + final bool navigateToDetails = + Settings.getValue(keyNavigateToDetails, true); - setState(() { - _model = preferredModel; - _preferredSensitivity = sensitivity; - currentAlgorithm = - allAlgorithms[preferredAlgorithm](sensitivity, winThreshP); - _currentAlgo = preferredAlgorithm; - _navigateToDetails = navigateToDetails; - }); + setState( + () { + _model = preferredModel; + _preferredSensitivity = sensitivity; + currentAlgorithm = allAlgorithms[preferredAlgorithm]!( + sensitivity, + winThreshP, + ) as InferenceAlgorithm; + _currentAlgo = preferredAlgorithm; + _navigateToDetails = navigateToDetails; + }, + ); loadModelFromSettings(); } - loadModelFromSettings() async { - TfLiteModel model = tfLiteModels[_model]; - String res = await Tflite.loadModel( + Future loadModelFromSettings() async { + final TfLiteModel model = tfLiteModels[_model]!; + final String? res = await Tflite.loadModel( model: model.modelPath, labels: model.labelsPath, ); - print("$res loading model $_model, as specified in Settings"); + debugPrint('$res loading model $_model, as specified in Settings'); - Vibration.hasVibrator().then((canVibrate) { - if (canVibrate) { - setState(() { - _canVibrate = canVibrate; - }); - } - }); + Vibration.hasVibrator().then( + (canVibrate) { + if (canVibrate != null) { + setState(() => _canVibrate = canVibrate); + } + }, + ); } - setRecognitions(recognitions, imageHeight, imageWidth, inferenceTime) { + void setRecognitions( + List recognitions, + int imageHeight, + int imageWidth, + int inferenceTime, + ) { if (_model.isNotEmpty) { if (currentAlgorithm.hasResult() && _navigateToDetails && - currentAlgorithm.topInference != "no_artwork") { - setState(() { - _model = ""; - }); + currentAlgorithm.topInference != 'no_artwork') { + setState(() => _model = ''); } - setState(() { - _recognitions = recognitions; - _imageHeight = imageHeight; - _imageWidth = imageWidth; - _inferenceTime = inferenceTime; + setState( + () { + _recognitions = recognitions; + _imageHeight = imageHeight; + _imageWidth = imageWidth; + _inferenceTime = inferenceTime; - // each item in recognitions is a LinkedHashMap in the form of - // {confidence: 0.5562283396720886, index: 15, label: untitled_votsis} - currentAlgorithm.updateRecognitions(recognitions, inferenceTime); - _currentRes = currentAlgorithm.topInferenceFormatted; - _fps = currentAlgorithm.fps; - if (currentAlgorithm.hasResult() && _navigateToDetails) { - // && !addedViewing - if (currentAlgorithm.topInference != "no_artwork") { - _model = ""; - if (_canVibrate) { - Vibration.vibrate(pattern: [0, 40, 100, 40]); - } + // each item in recognitions is a LinkedHashMap in the form of + // {confidence: 0.5562283396720886, index: 15, label: untitled_votsis} + currentAlgorithm.updateRecognitions(recognitions, inferenceTime); + _currentRes = currentAlgorithm.topInferenceFormatted; + _fps = currentAlgorithm.fps; + if (currentAlgorithm.hasResult() && _navigateToDetails) { + // && !addedViewing + if (currentAlgorithm.topInference != 'no_artwork') { + _model = ''; + if (_canVibrate) { + Vibration.vibrate(pattern: [0, 40, 100, 40]); + } - // get top inference as an object ready to insert in db - ViewingsCompanion vc = currentAlgorithm.resultAsDbObject(); - // add current model to object - vc = vc.copyWith(cnnModelUsed: Value(_model)); - viewingsDao.insertTask(vc); - print("Added VIEWING: $vc"); - // addedViewing = true; + // get top inference as an object ready to insert in db + ViewingsCompanion vc = currentAlgorithm.resultAsDbObject(); + // add current model to object + vc = vc.copyWith(cnnModelUsed: Value(_model)); + Provider.of(context, listen: false).insertTask(vc); + debugPrint('Added VIEWING: $vc'); + // addedViewing = true; - if (_navigateToDetails) { - // navigate to artwork details - Provider.of(context, listen: false) - .getArtworkById( - artworkId: currentAlgorithm.topInference, - languageCode: context.locale().languageCode) - .then((artwork) { - // set model to empty here, so that the camera stream stops - _model = ""; - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ArtworkDetailsPage(artwork: artwork); - }, - ), - ).then((_) { - // re-initialize model when user is back to this screen - initModel(); + if (_navigateToDetails) { + // navigate to artwork details + Provider.of(context, listen: false) + .getArtworkById( + artworkId: currentAlgorithm.topInference, + languageCode: context.locale().languageCode, + ) + .then((artwork) { + // set model to empty here, so that the camera stream stops + _model = ''; + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + ArtworkDetailsPage(artwork: artwork), + ), + ).then((_) { + // re-initialize model when user is back to this screen + initModel(); + }); }); - }); + } + } else { + debugPrint('Not adding VIEWING no_artwork'); } - } else { - print("Not adding VIEWING no_artwork"); } - } - }); + }, + ); } } @override Widget build(BuildContext context) { - var strings = context.strings(); - Size screen = MediaQuery.of(context).size; - viewingsDao = Provider.of(context); + final strings = context.strings(); + final Size screen = MediaQuery.of(context).size; return Scaffold( appBar: AppBar( title: AutoSizeText(strings.msg.pointTheCamera, maxLines: 1), backgroundColor: ThemeData.dark().primaryColor.withOpacity(0.2), ), - body: _model == "" + body: _model == '' // todo here check if model was loaded properly (see res in loadFrom...()) // instead of checking if _model is empty; if loading fails show an // appropriate msg - ? Center(child: CircularProgressIndicator()) + ? const Center(child: CircularProgressIndicator()) : Stack( children: [ TensorFlowCamera( - widget.cameras, - setRecognitions, - _model, + cameras: widget.cameras, + setRecognitions: setRecognitions, + model: _model, ), - if (_recognitions != null && - Settings.getValue(keyDisplayExtraInfo, false)) + if (Settings.getValue(keyDisplayExtraInfo, false) && + _recognitions.isNotEmpty) Align( alignment: Alignment.topLeft, child: Padding( padding: const EdgeInsets.fromLTRB(4, 4, 4, 30), child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text("Analysing $_fps", - style: TextStyle(fontSize: 14)), Text( - "Current consensus: ${_currentRes.isEmpty ? 'Calculating...' : _currentRes}", - style: TextStyle(fontSize: 12), + 'Analysing $_fps', + style: const TextStyle(fontSize: 14), + ), + Text( + 'Current consensus: ' + "${_currentRes.isEmpty ? 'Calculating...' : _currentRes}", + style: const TextStyle(fontSize: 12), ), Text( - "Algorithm used: $_currentAlgo", - style: TextStyle(fontSize: 12), + 'Algorithm used: $_currentAlgo', + style: const TextStyle(fontSize: 12), ), - Text(""), + const Text(''), Text( - "Latest: ${_recognitions?.last['label']} ${(_recognitions?.last["confidence"] * 100).toStringAsFixed(0)}%, $_inferenceTime ms", - style: TextStyle(fontSize: 12), + "Latest: ${_recognitions.last['label']} " + "${(_recognitions.last["confidence"] * 100).toStringAsFixed(0)}%, " + '$_inferenceTime ms', + style: const TextStyle(fontSize: 12), ), - if (!["no_artwork", ""] + if (!['no_artwork', ''] .contains(currentAlgorithm.topInference)) Image.asset( - "assets/paintings/${currentAlgorithm.topInference}.webp", + 'assets/paintings/' + '${currentAlgorithm.topInference}.webp', width: screen.width / 5, height: screen.width / 5, ), @@ -276,7 +286,6 @@ class _ModelSelectionState extends State padding: const EdgeInsets.fromLTRB(4, 4, 4, 50), child: Column( mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.center, children: [ Text(_msgForUser(currentAlgorithm.topInference)), SpinKitThreeBounce( @@ -293,19 +302,19 @@ class _ModelSelectionState extends State } String _msgForUser(String topInference) { - String current = ""; + String current = ''; switch (topInference) { - case "no_artwork": + case 'no_artwork': current = context.strings().msg.noneIdentified; break; - case "": + case '': break; default: current = topInference - .split("_") + .split('_') .map((String e) => e[0].toUpperCase() + e.substring(1)) - .join(" "); + .join(' '); } - return "${context.strings().msg.analysing} $current"; + return '${context.strings().msg.analysing} $current'; } } diff --git a/lib/tensorflow/models.dart b/lib/tensorflow/models.dart index f0cd606..7b73976 100644 --- a/lib/tensorflow/models.dart +++ b/lib/tensorflow/models.dart @@ -1,29 +1,24 @@ -import 'package:flutter/material.dart'; - -// older example models -// TODO remove these -const String mobilenet = "MobileNet"; -const String ssd = "SSD MobileNet"; -const String yolo = "Tiny YOLOv2"; -const String posenet = "PoseNet"; - // custom models for modern art gallery -const String vgg19 = "VGG19"; -const String vgg19Quant = "VGG19 (Quantized)"; -const String vgg19NoArtQuant = "VGG19 - No Artwork category (Quantized)"; -const String vgg19ZeroOneMultiQuant = "VGG19 - 0, 1, or Multiple (Quantized)"; -const String mobileNetNoArt = "MobileNet - No Artwork category"; +const String vgg19 = 'VGG19'; +const String vgg19Quant = 'VGG19 (Quantized)'; +const String vgg19NoArtQuant = 'VGG19 - No Artwork category (Quantized)'; +const String vgg19ZeroOneMultiQuant = 'VGG19 - 0, 1, or Multiple (Quantized)'; +const String mobileNetNoArt = 'MobileNet - No Artwork category'; const String mobileNetNoArtQuant = - "MobileNet - No Artwork category (Quantized)"; -const String inceptionV3NoArt500 = "Inception V3 500 - No Artwork category"; + 'MobileNet - No Artwork category (Quantized)'; +const String inceptionV3NoArt500 = 'Inception V3 500 - No Artwork category'; const String inceptionV3NoArt500Quant = - "Inception V3 500 - No Artwork category (Quantized)"; -const String mobNetNoArt500_4 = "MobileNet 500 - V.4 - No Artwork category"; + 'Inception V3 500 - No Artwork category (Quantized)'; +const String mobNetNoArt500_4 = 'MobileNet 500 - V.4 - No Artwork category'; const String mobNetNoArt500Quant_4 = - "MobileNet 500 - V.4 - No Artwork category (Quantized)"; + 'MobileNet 500 - V.4 - No Artwork category (Quantized)'; /// Map containing information about all TfLite models bundled with the app. const Map tfLiteModels = { + mobNetNoArt500_4: TfLiteModel( + modelPath: 'MobNetNoArt500Frames_4.tflite', + labelsPath: 'MobileNet_No_Art_labels.txt', + ), // vgg19: TfLiteModel( // modelPath: "cnn224RGB_VGG19.tflite", // labelsPath: "cnn224RGB_VGG19_labels.txt"), @@ -42,9 +37,6 @@ const Map tfLiteModels = { // mobileNetNoArtQuant: TfLiteModel( // modelPath: "MobileNet_No_Art_quant.tflite", // labelsPath: "MobileNet_No_Art_labels.txt"), - mobNetNoArt500_4: TfLiteModel( - modelPath: "MobNetNoArt500Frames_4.tflite", - labelsPath: "MobileNet_No_Art_labels.txt"), // mobNetNoArt500Quant_4: TfLiteModel( // modelPath: "MobNetNoArt500Frames_4_quant.tflite", // labelsPath: "MobileNet_No_Art_labels.txt"), @@ -57,31 +49,33 @@ const Map tfLiteModels = { }; /// Map with tfLiteModels names, used in settings to allow selection of model. -final tfLiteModelNames = Map.fromIterable( - tfLiteModels.keys, - key: (key) => key, - value: (key) => key, -); +final tfLiteModelNames = {for (var key in tfLiteModels.keys) key: key}; /// Class for holding information about the TfLite models bundled with the app, /// specifically the file paths to the model itself, as well as to the text /// files with the model's list of labels. class TfLiteModel { + const TfLiteModel({required String modelPath, required String labelsPath}) + : _modelPath = modelPath, + _labelsPath = labelsPath; + /// Prefix to the assets directory that contains the models, so it can be /// easily updated in the future if needed. - static String _modelAssetsPath = "assets/tflite/"; + static const String _modelAssetsPath = 'assets/tflite/'; final String _modelPath; final String _labelsPath; - const TfLiteModel({@required String modelPath, @required String labelsPath}) - : _modelPath = modelPath, - _labelsPath = labelsPath; - /// Returns the full path to the model's file. String get modelPath => _modelAssetsPath + _modelPath; /// Returns the full path to the model's labels. String get labelsPath => _modelAssetsPath + _labelsPath; } + +// older example models +const String mobilenet = 'MobileNet'; +const String ssd = 'SSD MobileNet'; +const String yolo = 'Tiny YOLOv2'; +const String posenet = 'PoseNet'; diff --git a/lib/tensorflow/tensorflow_camera.dart b/lib/tensorflow/tensorflow_camera.dart index e493306..d21b7c7 100644 --- a/lib/tensorflow/tensorflow_camera.dart +++ b/lib/tensorflow/tensorflow_camera.dart @@ -2,35 +2,43 @@ import 'dart:math' as math; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; +import 'package:modern_art_app/tensorflow/models.dart'; import 'package:tflite/tflite.dart'; -import 'models.dart'; - -typedef void Callback(List list, int h, int w, int inferenceTime); +typedef Callback = void Function( + List list, + int h, + int w, + int inferenceTime, +); class TensorFlowCamera extends StatefulWidget { + const TensorFlowCamera({ + required this.cameras, + required this.setRecognitions, + required this.model, + }); + final List cameras; final Callback setRecognitions; final String model; - TensorFlowCamera(this.cameras, this.setRecognitions, this.model); - @override - _TensorFlowCameraState createState() => new _TensorFlowCameraState(); + _TensorFlowCameraState createState() => _TensorFlowCameraState(); } class _TensorFlowCameraState extends State { - CameraController controller; + late CameraController controller; bool isDetecting = false; @override void initState() { super.initState(); - if (widget.cameras == null || widget.cameras.length < 1) { - print('No camera is found'); + if (widget.cameras.isEmpty) { + debugPrint('No camera is found'); } else { - controller = new CameraController( + controller = CameraController( widget.cameras[0], ResolutionPreset.high, // we don't need audio in the app, so by passing false below, the @@ -39,129 +47,153 @@ class _TensorFlowCameraState extends State { // done for this app) enableAudio: false, ); - controller.initialize().then((_) { - if (!mounted) { - return; - } - setState(() {}); - - controller.startImageStream((CameraImage img) { - if (!isDetecting) { - isDetecting = true; - - int startTime = new DateTime.now().millisecondsSinceEpoch; - - if ([vgg19, vgg19Quant, vgg19NoArtQuant, vgg19ZeroOneMultiQuant] - .contains(widget.model)) { - print("calculating with ${widget.model}"); - Tflite.runModelOnFrame( - bytesList: img.planes.map((plane) { - return plane.bytes; - }).toList(), - imageHeight: img.height, - imageWidth: img.width, - imageMean: 0, - imageStd: 255.0, - numResults: 1, - ).then((recognitions) { - int endTime = new DateTime.now().millisecondsSinceEpoch; - var inferenceTime = endTime - startTime; - print("Detection took $inferenceTime ms"); - widget.setRecognitions( - recognitions, img.height, img.width, inferenceTime); - - isDetecting = false; - }); - } else if ([ - mobilenet, - mobileNetNoArt, - mobileNetNoArtQuant, - inceptionV3NoArt500, - mobNetNoArt500Quant_4, - inceptionV3NoArt500Quant, - mobNetNoArt500_4, - ].contains(widget.model)) { - Tflite.runModelOnFrame( - bytesList: img.planes.map((plane) { - return plane.bytes; - }).toList(), - imageHeight: img.height, - imageWidth: img.width, - numResults: 1, - ).then((recognitions) { - int endTime = new DateTime.now().millisecondsSinceEpoch; - var inferenceTime = endTime - startTime; - print("Detection took $inferenceTime ms"); - widget.setRecognitions( - recognitions, img.height, img.width, inferenceTime); - - isDetecting = false; - }); - } else if (widget.model == posenet) { - Tflite.runPoseNetOnFrame( - bytesList: img.planes.map((plane) { - return plane.bytes; - }).toList(), - imageHeight: img.height, - imageWidth: img.width, - numResults: 1, - ).then((recognitions) { - int endTime = new DateTime.now().millisecondsSinceEpoch; - var inferenceTime = endTime - startTime; - print("Detection took $inferenceTime ms"); - widget.setRecognitions( - recognitions, img.height, img.width, inferenceTime); - - isDetecting = false; - }); - } else { - Tflite.detectObjectOnFrame( - bytesList: img.planes.map((plane) { - return plane.bytes; - }).toList(), - model: widget.model == yolo ? "YOLO" : "SSDMobileNet", - imageHeight: img.height, - imageWidth: img.width, - imageMean: widget.model == yolo ? 0 : 127.5, - imageStd: widget.model == yolo ? 255.0 : 127.5, - numResultsPerClass: 1, - threshold: widget.model == yolo ? 0.2 : 0.4, - ).then((recognitions) { - int endTime = new DateTime.now().millisecondsSinceEpoch; - var inferenceTime = endTime - startTime; - print("Detection took $inferenceTime ms"); - widget.setRecognitions( - recognitions, img.height, img.width, inferenceTime); - - isDetecting = false; - }); - } + controller.initialize().then( + (_) { + if (!mounted) { + return; } - }); - }); + setState(() {}); + + controller.startImageStream( + (CameraImage img) { + if (!isDetecting) { + isDetecting = true; + + final int startTime = DateTime.now().millisecondsSinceEpoch; + + if ([vgg19, vgg19Quant, vgg19NoArtQuant, vgg19ZeroOneMultiQuant] + .contains(widget.model)) { + debugPrint('calculating with ${widget.model}'); + Tflite.runModelOnFrame( + bytesList: img.planes.map((plane) => plane.bytes).toList(), + imageHeight: img.height, + imageWidth: img.width, + imageMean: 0, + imageStd: 255.0, + numResults: 1, + ).then( + (recognitions) { + if (recognitions != null) { + final inferenceTime = + DateTime.now().millisecondsSinceEpoch - startTime; + debugPrint('Detection took $inferenceTime ms'); + widget.setRecognitions( + recognitions, + img.height, + img.width, + inferenceTime, + ); + } + isDetecting = false; + }, + ); + } else if ([ + mobilenet, + mobileNetNoArt, + mobileNetNoArtQuant, + inceptionV3NoArt500, + mobNetNoArt500Quant_4, + inceptionV3NoArt500Quant, + mobNetNoArt500_4, + ].contains(widget.model)) { + Tflite.runModelOnFrame( + bytesList: img.planes.map((plane) => plane.bytes).toList(), + imageHeight: img.height, + imageWidth: img.width, + numResults: 1, + ).then( + (recognitions) { + if (recognitions != null) { + final inferenceTime = + DateTime.now().millisecondsSinceEpoch - startTime; + debugPrint('Detection took $inferenceTime ms'); + widget.setRecognitions( + recognitions, + img.height, + img.width, + inferenceTime, + ); + } + isDetecting = false; + }, + ); + } else if (widget.model == posenet) { + Tflite.runPoseNetOnFrame( + bytesList: img.planes.map((plane) => plane.bytes).toList(), + imageHeight: img.height, + imageWidth: img.width, + numResults: 1, + ).then( + (recognitions) { + if (recognitions != null) { + final inferenceTime = + DateTime.now().millisecondsSinceEpoch - startTime; + debugPrint('Detection took $inferenceTime ms'); + widget.setRecognitions( + recognitions, + img.height, + img.width, + inferenceTime, + ); + } + isDetecting = false; + }, + ); + } else { + Tflite.detectObjectOnFrame( + bytesList: img.planes.map((plane) => plane.bytes).toList(), + model: widget.model == yolo ? 'YOLO' : 'SSDMobileNet', + imageHeight: img.height, + imageWidth: img.width, + imageMean: widget.model == yolo ? 0 : 127.5, + imageStd: widget.model == yolo ? 255.0 : 127.5, + numResultsPerClass: 1, + threshold: widget.model == yolo ? 0.2 : 0.4, + ).then( + (recognitions) { + if (recognitions != null) { + final inferenceTime = + DateTime.now().millisecondsSinceEpoch - startTime; + debugPrint('Detection took $inferenceTime ms'); + widget.setRecognitions( + recognitions, + img.height, + img.width, + inferenceTime, + ); + } + isDetecting = false; + }, + ); + } + } + }, + ); + }, + ); } } @override void dispose() { - controller?.dispose(); + controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - if (controller == null || !controller.value.isInitialized) { + if (!controller.value.isInitialized) { return Container(); } var tmp = MediaQuery.of(context).size; - var screenH = math.max(tmp.height, tmp.width); - var screenW = math.min(tmp.height, tmp.width); - tmp = controller.value.previewSize; - var previewH = math.max(tmp.height, tmp.width); - var previewW = math.min(tmp.height, tmp.width); - var screenRatio = screenH / screenW; - var previewRatio = previewH / previewW; + final screenH = math.max(tmp.height, tmp.width); + final screenW = math.min(tmp.height, tmp.width); + tmp = controller.value.previewSize!; + final previewH = math.max(tmp.height, tmp.width); + final previewW = math.min(tmp.height, tmp.width); + final screenRatio = screenH / screenW; + final previewRatio = previewH / previewW; return OverflowBox( maxHeight: diff --git a/lib/ui/pages/artist_details_page.dart b/lib/ui/pages/artist_details_page.dart index cb971d6..ae98c43 100644 --- a/lib/ui/pages/artist_details_page.dart +++ b/lib/ui/pages/artist_details_page.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_lorem/flutter_lorem.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:modern_art_app/data/artworks_dao.dart'; import 'package:modern_art_app/data/database.dart'; @@ -9,20 +8,18 @@ import 'package:modern_art_app/utils/utils.dart'; import 'package:provider/provider.dart'; class ArtistDetailsPage extends StatelessWidget { - const ArtistDetailsPage({Key key, this.artist}) : super(key: key); + const ArtistDetailsPage({Key? key, required this.artist}) : super(key: key); final Artist artist; @override Widget build(BuildContext context) { - Size size = MediaQuery.of(context).size; + final Size size = MediaQuery.of(context).size; // to change appbar color according to image, use below with futurebuilder // PaletteGenerator.fromImageProvider(AssetImage(artist.fileName)); return Scaffold( backgroundColor: Colors.black, - appBar: AppBar( - title: Text(context.strings().artistDetails), - ), + appBar: AppBar(title: Text(context.strings().artistDetails)), body: SingleChildScrollView( // padding to account for the convex app bar padding: const EdgeInsets.only(bottom: 30.0), @@ -31,7 +28,7 @@ class ArtistDetailsPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Hero( - tag: artist.name, + tag: artist.name!, child: Container( height: size.height * 0.4, width: size.width, @@ -46,7 +43,7 @@ class ArtistDetailsPage extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: Text( - artist.name, + artist.name!, style: GoogleFonts.openSansCondensed( fontSize: 30, fontWeight: FontWeight.bold, @@ -57,7 +54,10 @@ class ArtistDetailsPage extends StatelessWidget { padding: const EdgeInsets.fromLTRB(16.0, 0.0, 8.0, 8.0), child: Text( lifespan(artist), - style: TextStyle(fontSize: 16, fontStyle: FontStyle.italic), + style: const TextStyle( + fontSize: 16, + fontStyle: FontStyle.italic, + ), ), ), Padding( @@ -73,16 +73,14 @@ class ArtistDetailsPage extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(16.0, 0.0, 8.0, 8.0), child: Text( - artist.biography.isNotEmpty - ? artist.biography - : lorem(paragraphs: 1, words: 150), - style: TextStyle(fontSize: 16), + artist.biography ?? '', + style: const TextStyle(fontSize: 16), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( - "${context.strings().artworksBy} ${artist.name}", + '${context.strings().artworksBy} ${artist.name}', style: GoogleFonts.openSansCondensed( fontSize: 20, fontWeight: FontWeight.bold, @@ -92,10 +90,12 @@ class ArtistDetailsPage extends StatelessWidget { Padding( padding: const EdgeInsets.only(left: 12, top: 4.0), child: ListHorizontal( - itemList: Provider.of(context) - .watchArtworksByArtist( - artistId: artist.id, - languageCode: context.locale().languageCode), + itemList: Provider.of( + context, + ).watchArtworksByArtist( + artistId: artist.id, + languageCode: context.locale().languageCode, + ), ), ), ], diff --git a/lib/ui/pages/artwork_details_page.dart b/lib/ui/pages/artwork_details_page.dart index adcc6a7..eb0649f 100644 --- a/lib/ui/pages/artwork_details_page.dart +++ b/lib/ui/pages/artwork_details_page.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_lorem/flutter_lorem.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:modern_art_app/data/artists_dao.dart'; import 'package:modern_art_app/data/database.dart'; @@ -10,16 +9,18 @@ import 'package:photo_view/photo_view.dart'; import 'package:provider/provider.dart'; class ArtworkDetailsPage extends StatelessWidget { - const ArtworkDetailsPage( - {Key key, @required this.artwork, this.customHeroTag}) - : super(key: key); + const ArtworkDetailsPage({ + Key? key, + required this.artwork, + this.customHeroTag, + }) : super(key: key); final Artwork artwork; - final String customHeroTag; + final String? customHeroTag; @override Widget build(BuildContext context) { - Size size = MediaQuery.of(context).size; + final Size size = MediaQuery.of(context).size; return Scaffold( backgroundColor: Colors.black, appBar: AppBar( @@ -57,16 +58,18 @@ class ArtworkDetailsPage extends StatelessWidget { height: size.height * 0.55, width: size.width, decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage(getArtworkFilename(artwork)), - fit: BoxFit.contain)), + image: DecorationImage( + image: AssetImage(getArtworkFilename(artwork)), + fit: BoxFit.contain, + ), + ), ), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( - artwork.name, + artwork.name!, style: GoogleFonts.openSansCondensed( fontSize: 30, fontWeight: FontWeight.bold, @@ -81,12 +84,15 @@ class ArtworkDetailsPage extends StatelessWidget { artistId: artwork.artistId, languageCode: context.locale().languageCode, ) - .then((artist) => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - ArtistDetailsPage(artist: artist)), - )); + .then( + (artist) => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + ArtistDetailsPage(artist: artist), + ), + ), + ); }, child: Padding( padding: const EdgeInsets.fromLTRB(16.0, 0.0, 8.0, 8.0), @@ -98,12 +104,12 @@ class ArtworkDetailsPage extends StatelessWidget { style: TextStyle( fontSize: 16, fontStyle: FontStyle.italic, - color: Theme.of(context).accentColor, + color: Theme.of(context).colorScheme.secondary, ), ), TextSpan( - text: " (${artwork.year})", - style: TextStyle( + text: ' (${artwork.year})', + style: const TextStyle( fontSize: 16, fontStyle: FontStyle.italic, ), @@ -126,10 +132,8 @@ class ArtworkDetailsPage extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(16.0, 0.0, 8.0, 8.0), child: Text( - artwork.description.isNotEmpty - ? artwork.description - : lorem(paragraphs: 2, words: 50), - style: TextStyle(fontSize: 16), + artwork.description ?? '', + style: const TextStyle(fontSize: 16), ), ), ], @@ -140,12 +144,12 @@ class ArtworkDetailsPage extends StatelessWidget { } class ItemZoomPage extends StatelessWidget { + const ItemZoomPage({Key? key, required this.fileName, required this.heroTag}) + : super(key: key); + final String fileName; final String heroTag; - const ItemZoomPage({Key key, @required this.fileName, this.heroTag}) - : super(key: key); - @override Widget build(BuildContext context) { return Container( @@ -160,9 +164,3 @@ class ItemZoomPage extends StatelessWidget { ); } } - -// Text( -// "${artwork.artist}" + -// (artwork.year.isNotEmpty ? ", ${artwork.year}" : ""), -// style: TextStyle(fontSize: 16, fontStyle: FontStyle.italic), -// ) diff --git a/lib/ui/pages/changelog_page.dart b/lib/ui/pages/changelog_page.dart index db04752..22594b0 100644 --- a/lib/ui/pages/changelog_page.dart +++ b/lib/ui/pages/changelog_page.dart @@ -4,24 +4,22 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:modern_art_app/utils/extensions.dart'; class ChangeLogPage extends StatefulWidget { - final String changelogAssetsPath; - - const ChangeLogPage({Key key, @required this.changelogAssetsPath}) + const ChangeLogPage({Key? key, required this.changelogAssetsPath}) : super(key: key); + final String changelogAssetsPath; + @override _ChangeLogPageState createState() => _ChangeLogPageState(); } class _ChangeLogPageState extends State { - String _changelog = ""; + String _changelog = ''; @override void initState() { rootBundle.loadString(widget.changelogAssetsPath).then((data) { - setState(() { - _changelog = data; - }); + setState(() => _changelog = data); }); super.initState(); } @@ -30,11 +28,9 @@ class _ChangeLogPageState extends State { Widget build(BuildContext context) { final strings = context.strings(); return Scaffold( - appBar: AppBar( - title: Text(strings.stngs.stng.changelog), - ), + appBar: AppBar(title: Text(strings.stngs.stng.changelog)), body: _changelog.isEmpty - ? Center(child: CircularProgressIndicator()) + ? const Center(child: CircularProgressIndicator()) : SafeArea( child: Stack( children: [ @@ -46,13 +42,11 @@ class _ChangeLogPageState extends State { ), Align( alignment: Alignment.bottomCenter, - child: RaisedButton( - onPressed: () { - Navigator.pop(context); - }, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), child: Text(strings.button.close.customToUpperCase()), ), - ) + ), ], ), ), diff --git a/lib/ui/pages/demo_identify_page.dart b/lib/ui/pages/demo_identify_page.dart new file mode 100644 index 0000000..6d69ac7 --- /dev/null +++ b/lib/ui/pages/demo_identify_page.dart @@ -0,0 +1,43 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:modern_art_app/utils/extensions.dart'; + +class DemoIdentifyPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + final strings = context.strings(); + final Size screen = MediaQuery.of(context).size; + return Scaffold( + appBar: AppBar( + title: AutoSizeText(strings.msg.pointTheCamera, maxLines: 1), + backgroundColor: ThemeData.dark().primaryColor.withOpacity(0.2), + ), + body: OverflowBox( + maxHeight: screen.height - 20, + maxWidth: screen.width, + child: Stack( + fit: StackFit.expand, + children: [ + Image.asset('assets/example_frame.webp', fit: BoxFit.fitHeight), + Positioned.fill( + child: Padding( + padding: const EdgeInsets.fromLTRB(4, 4, 4, 100), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const Text('Analysing...', style: TextStyle(fontSize: 16)), + SpinKitThreeBounce( + color: Colors.white, + size: screen.width / 6, + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/ui/pages/explore_page.dart b/lib/ui/pages/explore_page.dart index a41b7d6..5621bbb 100644 --- a/lib/ui/pages/explore_page.dart +++ b/lib/ui/pages/explore_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:modern_art_app/data/artists_dao.dart'; import 'package:modern_art_app/data/artworks_dao.dart'; +import 'package:modern_art_app/data/database.dart'; import 'package:modern_art_app/ui/widgets/item_featured.dart'; import 'package:modern_art_app/ui/widgets/item_list.dart'; import 'package:modern_art_app/utils/extensions.dart'; @@ -11,26 +12,24 @@ import 'package:provider/provider.dart'; class ExplorePage extends StatelessWidget { @override Widget build(BuildContext context) { - ArtworksDao artworksDao = Provider.of(context); - ArtistsDao artistsDao = Provider.of(context); - Size size = MediaQuery.of(context).size; + final ArtworksDao artworksDao = Provider.of(context); + final ArtistsDao artistsDao = Provider.of(context); + final Size size = MediaQuery.of(context).size; final strings = context.strings(); return SafeArea( child: Container( - decoration: BoxDecoration(color: Colors.black), + decoration: const BoxDecoration(color: Colors.black), child: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ SliverAppBar( stretch: true, - onStretchTrigger: () { - return; - }, + // onStretchTrigger: () { }, expandedHeight: size.height * 0.3, pinned: true, flexibleSpace: FlexibleSpaceBar( titlePadding: const EdgeInsets.all(8.0), - stretchModes: [ + stretchModes: const [ StretchMode.zoomBackground, StretchMode.blurBackground, ], @@ -46,7 +45,7 @@ class ExplorePage extends StatelessWidget { fit: StackFit.expand, children: [ Image.asset( - "assets/pinakothiki_building.webp", + 'assets/pinakothiki_building.webp', fit: BoxFit.cover, ), DecoratedBox( @@ -75,36 +74,46 @@ class ExplorePage extends StatelessWidget { child: headline(context.strings().featuredArtwork), ), FutureBuilder( - future: artworksDao.getArtworkById( - artworkId: "the_cyclist_votsis", - languageCode: context.locale().languageCode), - builder: (context, snapshot) { - if (snapshot.hasData) { - return FeaturedTile( - artwork: snapshot.data, - tileHeight: size.height * 0.35, - tileWidth: size.width, - ); - } - return Container(); - }), + future: artworksDao.getArtworkById( + artworkId: 'the_cyclist_votsis', + languageCode: context.locale().languageCode, + ), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return FeaturedTile( + artwork: snapshot.data!, + tileHeight: size.height * 0.35, + tileWidth: size.width, + ); + } + return Container(); + }, + ), HeadlineAndMoreRow( - listType: "Artworks", - itemList: artworksDao.watchAllArtworks( - languageCode: context.locale().languageCode)), + listType: 'Artworks', + itemList: artworksDao.watchAllArtworks( + languageCode: context.locale().languageCode, + ), + ), ListHorizontal( - itemList: artworksDao.watchAllArtworks( - languageCode: context.locale().languageCode)), + itemList: artworksDao.watchAllArtworks( + languageCode: context.locale().languageCode, + ), + ), HeadlineAndMoreRow( - listType: "Artists", - itemList: artistsDao.watchAllArtists( - languageCode: context.locale().languageCode)), + listType: 'Artists', + itemList: artistsDao.watchAllArtists( + languageCode: context.locale().languageCode, + ), + ), Padding( // padding to account for the convex app bar padding: const EdgeInsets.only(bottom: 30.0), child: ListHorizontal( - itemList: artistsDao.watchAllArtists( - languageCode: context.locale().languageCode)), + itemList: artistsDao.watchAllArtists( + languageCode: context.locale().languageCode, + ), + ), ), // Spacer ], @@ -118,17 +127,21 @@ class ExplorePage extends StatelessWidget { } class HeadlineAndMoreRow extends StatelessWidget { + const HeadlineAndMoreRow({ + Key? key, + required this.listType, + required this.itemList, + }) : super(key: key); + final String listType; final Stream> itemList; - const HeadlineAndMoreRow({Key key, @required this.listType, this.itemList}) - : super(key: key); - @override Widget build(BuildContext context) { - var strings = context.strings(); + final strings = context.strings(); // TODO: fix this - String title = listType == "Artists" ? strings.artists : strings.artworks; + final String title = + listType == 'Artists' ? strings.artists : strings.artworks; return InkWell( onTap: () => _goToMore(context, title, itemList), child: Padding( @@ -136,9 +149,9 @@ class HeadlineAndMoreRow extends StatelessWidget { child: Row( children: [ headline(title), - Spacer(), + const Spacer(), IconButton( - icon: Icon(Icons.arrow_forward_rounded), + icon: const Icon(Icons.arrow_forward_rounded), tooltip: strings.button.more, onPressed: () => _goToMore(context, title, itemList), ), diff --git a/lib/ui/pages/identify_page.dart b/lib/ui/pages/identify_page.dart index 11ca945..10a5ab4 100644 --- a/lib/ui/pages/identify_page.dart +++ b/lib/ui/pages/identify_page.dart @@ -1,187 +1,187 @@ -import 'dart:math' as math; - -import 'package:camera/camera.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_settings_screens/flutter_settings_screens.dart'; -import 'package:modern_art_app/data/artworks_dao.dart'; -import 'package:modern_art_app/tensorflow/models.dart'; -import 'package:modern_art_app/ui/pages/artwork_details_page.dart'; -import 'package:modern_art_app/ui/pages/settings_page.dart'; -import 'package:modern_art_app/utils/extensions.dart'; -import 'package:provider/provider.dart'; -import 'package:tflite/tflite.dart'; - -class IdentifyPage extends StatefulWidget { - final List cameras; - - const IdentifyPage({Key key, this.cameras}) : super(key: key); - - @override - _IdentifyPageState createState() => _IdentifyPageState(); -} - -class _IdentifyPageState extends State { - CameraController _controller; - bool _cameraOn = true; - bool _busy = false; - String _model = ""; - - // initTfLiteModel() { - // String preferredModel = Settings.getValue(keyCnnModel, mobileNetNoArt); - // setState(() => _model = preferredModel); - // loadModel(); - // } - - Future loadModel() async { - print("LOADING MODEL++++++++++++++++++++++++++++++"); - Tflite.close(); - String preferredModel = Settings.getValue(keyCnnModel, mobileNetNoArt); - TfLiteModel model = tfLiteModels[preferredModel]; - String result = await Tflite.loadModel( - // todo modelPath is called on null on first launch, before getting camera permission? - model: model.modelPath, - labels: model.labelsPath, - ); - setState(() => _model = preferredModel); - print("$result loading model $_model, as specified in Settings"); - } - - @override - void initState() { - super.initState(); - - _busy = true; - - // Steps that have to happen: - // - init model - // - init camera controller (this on first launch triggers asking for camera permission?) - // Which order should they happen in? - - // initTfLiteModel(); - loadModel().then((_) => setState(() { - _busy = false; - })); - - // todo here should make sure the model was initialized? - if (widget.cameras == null || widget.cameras.length < 1) { - print("No camera found!"); - } else { - // initialize camera controller - _controller = CameraController( - widget.cameras[0], - ResolutionPreset.high, - // we don't need audio in the app, so by passing false below, the - // microphone permission is not requested from the user on Android; - // on iOS the permission has to be manually specified, which was not - // done for this app - enableAudio: false, - ); - - _controller.initialize().then((_) { - // check that the user has not navigated away - if (!mounted) { - // todo if setState is overridden like in model_selection, this may be unnecessary? - return; - } - - setState(() {}); - - _controller.startImageStream((CameraImage img) { - print("NEW FRAME..............................."); - if (!_busy) { - _busy = true; - - Tflite.runModelOnFrame( - bytesList: img.planes.map((plane) => plane.bytes).toList(), - imageHeight: img.height, - imageWidth: img.width, - imageMean: 127.5, - imageStd: 127.5, - numResults: 1, - ).then((recognitions) { - print("Inference for ${recognitions[0]['label']}"); - if (recognitions[0]['label'] != "no_artwork") { - setState(() { - _busy = true; - _cameraOn = false; - }); - if (_controller.value.isStreamingImages) { - _controller.stopImageStream(); - } - // _controller.dispose(); - // Tflite.close(); - Provider.of(context, listen: false) - .getArtworkById( - artworkId: recognitions[0]['label'], - languageCode: context.locale().languageCode) - .then((artwork) => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - ArtworkDetailsPage(artwork: artwork)), - ).then((value) => setState(() { - print("back to tensorflow"); - _cameraOn = true; - _busy = false; - if (!_controller.value.isStreamingImages) { - _controller.startImageStream((image) => null); - } - }))); - } - }); - - _busy = false; - } - }); - }); - } - } - - @override - void dispose() { - // dispose controller when user navigates away - _controller?.dispose(); - // Tflite?.close(); - print("camera controller disposed"); - super.dispose(); - } - - @override - void setState(VoidCallback fn) { - // checks that page is still mounted before calling setState, to avoid - // "setState after dispose" errors - if (mounted) { - print("SET STATE!!!!!!!!!!!!!!!!!!!!!"); - super.setState(fn); - } - } - - @override - Widget build(BuildContext context) { - if (_controller == null || !_controller.value.isInitialized || !_cameraOn) { - // todo show error message to user here - print("show container!!!!!!"); - return Container(); - } - - // the following logic does not work properly when camera preview is in - // landscape, see this issue https://github.com/flutter/flutter/issues/29951 - // todo fix preview orientation issue - var tmp = MediaQuery.of(context).size; - var screenH = math.max(tmp.height, tmp.width); - var screenW = math.min(tmp.height, tmp.width); - tmp = _controller.value.previewSize; - var previewH = math.max(tmp.height, tmp.width); - var previewW = math.min(tmp.height, tmp.width); - var screenRatio = screenH / screenW; - var previewRatio = previewH / previewW; - - return OverflowBox( - maxHeight: - screenRatio > previewRatio ? screenH : screenW / previewW * previewH, - maxWidth: - screenRatio > previewRatio ? screenH / previewH * previewW : screenW, - child: CameraPreview(_controller), - ); - } -} +// import 'dart:math' as math; +// +// import 'package:camera/camera.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_settings_screens/flutter_settings_screens.dart'; +// import 'package:modern_art_app/data/artworks_dao.dart'; +// import 'package:modern_art_app/tensorflow/models.dart'; +// import 'package:modern_art_app/ui/pages/artwork_details_page.dart'; +// import 'package:modern_art_app/ui/pages/settings_page.dart'; +// import 'package:modern_art_app/utils/extensions.dart'; +// import 'package:provider/provider.dart'; +// import 'package:tflite/tflite.dart'; +// +// class IdentifyPage extends StatefulWidget { +// final List cameras; +// +// const IdentifyPage({Key? key, required this.cameras}) : super(key: key); +// +// @override +// _IdentifyPageState createState() => _IdentifyPageState(); +// } +// +// class _IdentifyPageState extends State { +// late CameraController _controller; +// bool _cameraOn = true; +// bool _busy = false; +// String _model = ""; +// +// // initTfLiteModel() { +// // String preferredModel = Settings.getValue(keyCnnModel, mobileNetNoArt); +// // setState(() => _model = preferredModel); +// // loadModel(); +// // } +// +// Future loadModel() async { +// print("LOADING MODEL++++++++++++++++++++++++++++++"); +// Tflite.close(); +// String preferredModel = Settings.getValue(keyCnnModel, mobileNetNoArt); +// TfLiteModel model = tfLiteModels[preferredModel]!; +// String? result = await Tflite.loadModel( +// // todo modelPath is called on null on first launch, before getting camera permission? +// model: model.modelPath, +// labels: model.labelsPath, +// ); +// setState(() => _model = preferredModel); +// print("$result loading model $_model, as specified in Settings"); +// } +// +// @override +// void initState() { +// super.initState(); +// +// _busy = true; +// +// // Steps that have to happen: +// // - init model +// // - init camera controller (this on first launch triggers asking for camera permission?) +// // Which order should they happen in? +// +// // initTfLiteModel(); +// loadModel().then((_) => setState(() { +// _busy = false; +// })); +// +// // todo here should make sure the model was initialized? +// if (widget.cameras.length < 1) { +// print("No camera found!"); +// } else { +// // initialize camera controller +// _controller = CameraController( +// widget.cameras[0], +// ResolutionPreset.high, +// // we don't need audio in the app, so by passing false below, the +// // microphone permission is not requested from the user on Android; +// // on iOS the permission has to be manually specified, which was not +// // done for this app +// enableAudio: false, +// ); +// +// _controller.initialize().then((_) { +// // check that the user has not navigated away +// if (!mounted) { +// // todo if setState is overridden like in model_selection, this may be unnecessary? +// return; +// } +// +// setState(() {}); +// +// _controller.startImageStream((CameraImage img) { +// print("NEW FRAME..............................."); +// if (!_busy) { +// _busy = true; +// +// Tflite.runModelOnFrame( +// bytesList: img.planes.map((plane) => plane.bytes).toList(), +// imageHeight: img.height, +// imageWidth: img.width, +// imageMean: 127.5, +// imageStd: 127.5, +// numResults: 1, +// ).then((recognitions) { +// print("Inference for ${recognitions[0]['label']}"); +// if (recognitions[0]['label'] != "no_artwork") { +// setState(() { +// _busy = true; +// _cameraOn = false; +// }); +// if (_controller.value.isStreamingImages) { +// _controller.stopImageStream(); +// } +// // _controller.dispose(); +// // Tflite.close(); +// Provider.of(context, listen: false) +// .getArtworkById( +// artworkId: recognitions[0]['label'], +// languageCode: context.locale().languageCode) +// .then((artwork) => Navigator.push( +// context, +// MaterialPageRoute( +// builder: (context) => +// ArtworkDetailsPage(artwork: artwork)), +// ).then((value) => setState(() { +// print("back to tensorflow"); +// _cameraOn = true; +// _busy = false; +// if (!_controller.value.isStreamingImages) { +// _controller.startImageStream((image) => null); +// } +// }))); +// } +// }); +// +// _busy = false; +// } +// }); +// }); +// } +// } +// +// @override +// void dispose() { +// // dispose controller when user navigates away +// _controller.dispose(); +// // Tflite?.close(); +// print("camera controller disposed"); +// super.dispose(); +// } +// +// @override +// void setState(VoidCallback fn) { +// // checks that page is still mounted before calling setState, to avoid +// // "setState after dispose" errors +// if (mounted) { +// print("SET STATE!!!!!!!!!!!!!!!!!!!!!"); +// super.setState(fn); +// } +// } +// +// @override +// Widget build(BuildContext context) { +// if (!_controller.value.isInitialized || !_cameraOn) { +// // todo show error message to user here +// print("show container!!!!!!"); +// return Container(); +// } +// +// // the following logic does not work properly when camera preview is in +// // landscape, see this issue https://github.com/flutter/flutter/issues/29951 +// // todo fix preview orientation issue +// var tmp = MediaQuery.of(context).size; +// var screenH = math.max(tmp.height, tmp.width); +// var screenW = math.min(tmp.height, tmp.width); +// tmp = _controller.value.previewSize; +// var previewH = math.max(tmp.height, tmp.width); +// var previewW = math.min(tmp.height, tmp.width); +// var screenRatio = screenH / screenW; +// var previewRatio = previewH / previewW; +// +// return OverflowBox( +// maxHeight: +// screenRatio > previewRatio ? screenH : screenW / previewW * previewH, +// maxWidth: +// screenRatio > previewRatio ? screenH / previewH * previewW : screenW, +// child: CameraPreview(_controller), +// ); +// } +// } diff --git a/lib/ui/pages/main_page.dart b/lib/ui/pages/main_page.dart index 8b02c94..fe39906 100644 --- a/lib/ui/pages/main_page.dart +++ b/lib/ui/pages/main_page.dart @@ -5,12 +5,11 @@ import 'package:modern_art_app/tensorflow/model_selection.dart'; import 'package:modern_art_app/ui/pages/explore_page.dart'; import 'package:modern_art_app/ui/pages/settings_page.dart'; import 'package:modern_art_app/utils/extensions.dart'; -import 'package:sentry/sentry.dart'; class MainPage extends StatefulWidget { - final List cameras; + const MainPage({Key? key, required this.cameras}) : super(key: key); - const MainPage({Key key, @required this.cameras}) : super(key: key); + final List cameras; @override _MainPageState createState() => _MainPageState(); @@ -19,7 +18,7 @@ class MainPage extends StatefulWidget { class _MainPageState extends State { /// The HeroController is needed for enabling Hero animations in the custom /// Navigator below, see this post https://stackoverflow.com/a/60729122 - HeroController _heroController; + late HeroController _heroController; /// The global [_navigatorKey] is necessary so that the nav bar can push /// routes to the navigator (the nav bar is located above/in another branch @@ -28,7 +27,8 @@ class _MainPageState extends State { /// Navigator.of(context) methods. final _navigatorKey = GlobalKey(); - GlobalKey _appBarKey = GlobalKey(); + final GlobalKey _appBarKey = + GlobalKey(); int _currentIndex = 0; @@ -39,31 +39,28 @@ class _MainPageState extends State { } /// Used for enabling Hero animations in the custom Navigator. - RectTween _createRectTween(Rect begin, Rect end) { + Tween _createRectTween(Rect? begin, Rect? end) { return MaterialRectArcTween(begin: begin, end: end); } @override Widget build(BuildContext context) { final strings = context.strings(); - try { - throw Exception("Test exception!"); - } catch (exception, stackTrace) { - Sentry.captureException(exception, stackTrace: stackTrace); - } return Scaffold( body: WillPopScope( // wrapping the Navigator with a WillPopScope enables the correct // handling of the back button on Android onWillPop: () async { - if (_navigatorKey.currentState.canPop()) { - print("CAN POP"); - _navigatorKey.currentState.pop(); - setState(() { - _appBarKey.currentState.animateTo(0); - _currentIndex = 0; - }); - return false; + if (_navigatorKey.currentState != null) { + if (_navigatorKey.currentState!.canPop()) { + debugPrint('CAN POP'); + _navigatorKey.currentState!.pop(); + setState(() { + _appBarKey.currentState!.animateTo(0); + _currentIndex = 0; + }); + return false; + } } return true; }, @@ -85,7 +82,7 @@ class _MainPageState extends State { bldr = (BuildContext context) => SettingsPage(); break; default: - throw Exception("Invalid route: ${settings.name}"); + throw Exception('Invalid route: ${settings.name}'); } return MaterialPageRoute(builder: bldr, settings: settings); }, @@ -95,7 +92,7 @@ class _MainPageState extends State { key: _appBarKey, style: TabStyle.fixed, backgroundColor: Colors.grey.shade800, - activeColor: Theme.of(context).accentColor, + activeColor: Theme.of(context).colorScheme.secondary, elevation: 20, initialActiveIndex: _currentIndex, items: [ @@ -108,11 +105,13 @@ class _MainPageState extends State { case 0: // when the Explore tab is selected, remove everything else from // the navigator's stack - _navigatorKey.currentState - .pushNamedAndRemoveUntil(Routes.explorePage, (_) => false); + _navigatorKey.currentState?.pushNamedAndRemoveUntil( + Routes.explorePage, + (_) => false, + ); break; case 1: - _navigatorKey.currentState.pushNamedAndRemoveUntil( + _navigatorKey.currentState?.pushNamedAndRemoveUntil( Routes.identifyPage, ModalRoute.withName(Routes.explorePage), ); @@ -121,24 +120,24 @@ class _MainPageState extends State { // prevent multiple pushes of Settings page // if (_currentIndex != 2) { // here also remove everything else apart from "/" - _navigatorKey.currentState.pushNamedAndRemoveUntil( + _navigatorKey.currentState?.pushNamedAndRemoveUntil( Routes.settingsPage, ModalRoute.withName(Routes.explorePage), ); // } break; } - setState(() { - _currentIndex = index; - }); + setState(() => _currentIndex = index); }, ), ); } } +/// The [Routes] class is used to specify the navigator route names in a +/// centralized fashion. class Routes { - static const String explorePage = "/"; - static const String identifyPage = "/identify"; - static const String settingsPage = "/settings"; + static const String explorePage = '/'; + static const String identifyPage = '/identify'; + static const String settingsPage = '/settings'; } diff --git a/lib/ui/pages/settings_page.dart b/lib/ui/pages/settings_page.dart index 5db4d05..f405da0 100644 --- a/lib/ui/pages/settings_page.dart +++ b/lib/ui/pages/settings_page.dart @@ -8,6 +8,7 @@ import 'package:modern_art_app/data/inference_algorithms.dart'; import 'package:modern_art_app/data/viewings_dao.dart'; import 'package:modern_art_app/tensorflow/models.dart'; import 'package:modern_art_app/ui/pages/changelog_page.dart'; +import 'package:modern_art_app/ui/pages/demo_identify_page.dart'; import 'package:modern_art_app/utils/extensions.dart'; import 'package:modern_art_app/utils/utils.dart'; import 'package:moor_db_viewer/moor_db_viewer.dart'; @@ -18,13 +19,13 @@ import 'package:url_launcher/url_launcher.dart'; // All settings keys are specified here, to avoid mistakes typing them // manually every time. -const String keyCnnModel = "keyCnnModel"; -const String keyRecognitionAlgo = "recognitionAlgorithm"; -const String keyCnnSensitivity = "keyCnnSensitivity"; -const String keyWinThreshP = "keyWinThreshP"; -const String keyWinThreshPName = "keyWinThreshPName"; -const String keyNavigateToDetails = "keyNavigateToDetails"; -const String keyDisplayExtraInfo = "keyDisplayExtraInfo"; +const String keyCnnModel = 'keyCnnModel'; +const String keyRecognitionAlgo = 'recognitionAlgorithm'; +const String keyCnnSensitivity = 'keyCnnSensitivity'; +const String keyWinThreshP = 'keyWinThreshP'; +const String keyWinThreshPName = 'keyWinThreshPName'; +const String keyNavigateToDetails = 'keyNavigateToDetails'; +const String keyDisplayExtraInfo = 'keyDisplayExtraInfo'; class SettingsPage extends StatefulWidget { @override @@ -32,191 +33,211 @@ class SettingsPage extends StatefulWidget { } class _SettingsPageState extends State { + @override Widget build(BuildContext context) { // TODO check if settings are set on first launch final strings = context.strings(); - ViewingsDao viewingsDao = Provider.of(context); - return Container( - key: UniqueKey(), - child: SettingsScreen( - title: strings.stngs.title, + final ViewingsDao viewingsDao = Provider.of(context); + final List _settingsList = [ + SettingsGroup( + title: strings.stngs.groupAbout.customToUpperCase(), children: [ - SettingsGroup( - title: strings.stngs.groupAbout.customToUpperCase(), - children: [ - SimpleSettingsTile( - // launch the Gallery's url in external browser - title: strings.galleryName, - subtitle: strings.stngs.stng.galleryWebsiteSummary, - leading: Icon(Icons.web), - onTap: () async { - String url = "https://www.nicosia.org.cy/" - "${context.locale().languageCode == "en" ? 'en-GB' : 'el-GR'}" - "/discover/picture-galleries/state-gallery-of-contemporary-art/"; - if (await canLaunch(url)) { - launch(url); - } else { - Fluttertoast.showToast(msg: strings.msg.unableToLanchUrl); - } + SimpleSettingsTile( + // launch the Gallery's url in external browser + title: strings.galleryName, + subtitle: strings.stngs.stng.galleryWebsiteSummary, + leading: const Icon(Icons.web), + onTap: () async { + final String url = 'https://www.nicosia.org.cy/' + "${context.locale().languageCode == "en" ? 'en-GB' : 'el-GR'}" + '/discover/picture-galleries/state-gallery-of-contemporary-art/'; + if (await canLaunch(url)) { + launch(url); + } else { + Fluttertoast.showToast(msg: strings.msg.unableToLaunchUrl); + } + }, + ), + SimpleSettingsTile( + title: strings.stngs.stng.appInfo, + subtitle: strings.stngs.stng.appInfoSummary, + leading: const Icon(Icons.info_outline_rounded), + onTap: () { + PackageInfo.fromPlatform().then( + (packageInfo) => showAboutDialog( + context: context, + applicationIcon: const SizedBox( + width: 80, + height: 80, + child: Image( + image: AssetImage( + 'assets/app_launcher_icons/hadjida_untitled_app' + '_icon_square_android_adaptive.png', + ), + ), + ), + applicationName: strings.galleryName, + applicationVersion: '${strings.stngs.stng.appVersion}: ' + '${packageInfo.version}', + children: [ + Text(strings.stngs.stng.appDescription), + const Text(''), + Text(strings.stngs.stng.appMadeBy), + ], + ), + ); + }, + ), + SimpleSettingsTile( + title: strings.stngs.stng.changelog, + subtitle: strings.stngs.stng.changelogSummary, + leading: const Icon(Icons.history_rounded), + onTap: () { + // here the root navigator is used, so that the changelog is + // displayed on top of the rest of the UI (and the NavBar) + Navigator.of(context, rootNavigator: true).push( + MaterialPageRoute( + builder: (context) => const ChangeLogPage( + changelogAssetsPath: 'assets/CHANGELOG.md', + ), + ), + ); + }, + ), + ], + ), + SettingsGroup( + title: strings.stngs.groupDatabase.customToUpperCase(), + children: [ + SimpleSettingsTile( + title: strings.stngs.stng.historyExport, + subtitle: strings.stngs.stng.historyExportSummary, + leading: const Icon(Icons.share_rounded), + onTap: () async { + viewingsDao.allViewingEntries.then( + (viewings) { + final String viewingsInStr = jsonEncode(viewings); + debugPrint(viewingsInStr); + // write to file + saveToJsonFile(viewingsInStr).then( + (jsonFile) { + debugPrint(jsonFile); + // share saved json file via share dialog + Share.shareFiles([jsonFile], subject: 'Viewings history'); + }, + ); }, + ); + }, + ), + ], + ), + SettingsGroup( + title: strings.stngs.groupOther.customToUpperCase(), + children: [ + ExpandableSettingsTile( + title: strings.stngs.expandableOther, + leading: const Icon(Icons.settings_applications_rounded), + children: [ + RadioModalSettingsTile( + title: 'CNN type used', + settingKey: keyCnnModel, + values: tfLiteModelNames, + selected: mobNetNoArt500_4, + onChange: (value) => debugPrint('$keyCnnModel: $value'), ), - SimpleSettingsTile( - title: strings.stngs.stng.appInfo, - subtitle: strings.stngs.stng.appInfoSummary, - leading: Icon(Icons.info_outline_rounded), - onTap: () { - PackageInfo.fromPlatform() - .then((packageInfo) => showAboutDialog( - context: context, - applicationIcon: const SizedBox( - width: 80, - height: 80, - child: Image( - image: AssetImage( - 'assets/app_launcher_icons/hadjida_untitled_app_icon_square_android_adaptive.png'), - ), - ), - applicationName: strings.galleryName, - applicationVersion: - "${strings.stngs.stng.appVersion}: ${packageInfo.version}", - children: [ - Text(strings.stngs.stng.appDescription), - Text(""), - Text(strings.stngs.stng.appMadeBy), - ], - )); + RadioModalSettingsTile( + title: 'Recognition algorithm', + settingKey: keyRecognitionAlgo, + values: {for (var key in allAlgorithms.keys) key: key}, + selected: firstAlgorithm, + onChange: (value) { + debugPrint('$keyRecognitionAlgo: $value'); + // reset values for algorithm settings every time a new + // algorithm is chosen + _setDefaultAlgorithmSettings(value); + setState(() {}); }, ), + SliderSettingsTile( + leading: const Icon(Icons.adjust), + title: 'CNN sensitivity', + settingKey: keyCnnSensitivity, + defaultValue: 75.0, + min: 0.0, + max: 100.0, + step: 0.2, + onChange: (value) => debugPrint('$keyCnnSensitivity: $value'), + ), + SliderSettingsTile( + leading: const Icon(Icons.space_bar), + title: _getCurrentWinThreshPName(), + settingKey: keyWinThreshP, + defaultValue: 6, + min: 5, + max: 50, + onChange: (value) => debugPrint('$keyWinThreshP: $value'), + ), + SwitchSettingsTile( + leading: const Icon(Icons.navigation), + title: "Navigate to recognised artworks' details", + settingKey: keyNavigateToDetails, + defaultValue: true, + ), + SwitchSettingsTile( + leading: const Icon(Icons.list_alt_outlined), + title: 'Display model & algorithm information in camera view', + settingKey: keyDisplayExtraInfo, + ), SimpleSettingsTile( - title: strings.stngs.stng.changelog, - subtitle: strings.stngs.stng.changelogSummary, - leading: Icon(Icons.history_rounded), + title: 'Navigate to demo Identify page', + subtitle: 'Used only for demo purposes', + leading: const Icon(Icons.history_rounded), onTap: () { - // here the root navigator is used, so that the changelog is - // displayed on top of the rest of the UI (and the NavBar) - Navigator.of(context, rootNavigator: true).push( + Navigator.of(context).push( MaterialPageRoute( - builder: (context) => ChangeLogPage( - changelogAssetsPath: "assets/CHANGELOG.md", - ), + builder: (context) => DemoIdentifyPage(), ), ); }, ), - ], - ), - SettingsGroup( - title: strings.stngs.groupDatabase.customToUpperCase(), - children: [ - SimpleSettingsTile( - title: strings.stngs.stng.historyExport, - subtitle: strings.stngs.stng.historyExportSummary, - leading: Icon(Icons.share_rounded), - onTap: () async { - viewingsDao.allViewingEntries.then((viewings) { - String viewingsInStr = jsonEncode(viewings); - print(viewingsInStr); - // write to file - saveToJsonFile(viewingsInStr).then((jsonFile) { - print(jsonFile); - // share saved json file via share dialog - Share.shareFiles([jsonFile], subject: "Viewings history"); - }); - }); - }, - ), - ], - ), - SettingsGroup( - title: strings.stngs.groupOther.customToUpperCase(), - children: [ - ExpandableSettingsTile( - title: strings.stngs.expandableOther, - leading: Icon(Icons.settings_applications_rounded), - children: [ - RadioModalSettingsTile( - title: "CNN type used", - settingKey: keyCnnModel, - values: tfLiteModelNames, - selected: mobNetNoArt500_4, - onChange: (value) { - debugPrint("$keyCnnModel: $value"); - }, - ), - RadioModalSettingsTile( - title: "Recognition algorithm", - settingKey: keyRecognitionAlgo, - values: Map.fromIterable( - allAlgorithms.keys, - key: (key) => key, - value: (key) => key, - ), - selected: firstAlgorithm, - onChange: (value) { - debugPrint("$keyRecognitionAlgo: $value"); - // reset values for algorithm settings every time a new - // algorithm is chosen - _setDefaultAlgorithmSettings(value); - setState(() {}); - }, - ), - SliderSettingsTile( - leading: Icon(Icons.adjust), - title: "CNN sensitivity", - settingKey: keyCnnSensitivity, - defaultValue: 75.0, - min: 0.0, - max: 100.0, - step: 0.2, - onChange: (value) { - debugPrint("$keyCnnSensitivity: $value"); - }, - ), - SliderSettingsTile( - leading: Icon(Icons.space_bar), - title: _getCurrentWinThreshPName(), - settingKey: keyWinThreshP, - defaultValue: 6, - min: 5, - max: 50, - step: 1, - onChange: (value) { - debugPrint("$keyWinThreshP: $value"); - }, - ), - SwitchSettingsTile( - leading: Icon(Icons.navigation), - title: "Navigate to recognised artworks' details", - settingKey: keyNavigateToDetails, - defaultValue: true, - ), - SwitchSettingsTile( - leading: Icon(Icons.list_alt_outlined), - title: - "Display model & algorithm information in camera view", - settingKey: keyDisplayExtraInfo, - defaultValue: false, - ), - Padding( - // padding to account for the convex app bar - padding: const EdgeInsets.only(bottom: 30.0), - child: SimpleSettingsTile( - title: strings.stngs.stng.databaseBrowser, - subtitle: strings.stngs.stng.databaseBrowserSummary, - leading: Icon(Icons.table_rows), - onTap: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => MoorDbViewer( - Provider.of(context))), + Padding( + // padding to account for the convex app bar + padding: const EdgeInsets.only(bottom: 30.0), + child: SimpleSettingsTile( + title: strings.stngs.stng.databaseBrowser, + subtitle: strings.stngs.stng.databaseBrowserSummary, + leading: const Icon(Icons.table_rows), + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => MoorDbViewer( + Provider.of(context), ), ), ), - ], + ), ), ], ), ], ), + ]; + return Scaffold( + // by giving the scaffold a unique key, it's rebuild every time a setting + // changes, so that the screen is updated with the new setting values + key: UniqueKey(), + appBar: AppBar( + // todo override up button's default behaviour to fix nav bar issue + title: Text(strings.stngs.title), + ), + // here a custom ListView is used instead of the SettingsScreen widget + // provided by the library, to allow customizing the AppBar above and to + // set BouncingScrollPhysics below + body: ListView.builder( + physics: const BouncingScrollPhysics(), + itemCount: _settingsList.length, + itemBuilder: (BuildContext context, int index) => _settingsList[index], + ), ); } } @@ -224,33 +245,36 @@ class _SettingsPageState extends State { /// (Re)Sets the default values for the algorithms' settings each time a /// new algorithm is chosen in settings. void _setDefaultAlgorithmSettings(String algorithmName) { - var defValues = defaultSettings(algorithmName); - Settings.setValue(keyCnnSensitivity, defValues[keyCnnSensitivity]); - Settings.setValue(keyWinThreshP, defValues[keyWinThreshP]); + final defValues = defaultSettings(algorithmName); + Settings.setValue( + keyCnnSensitivity, + defValues![keyCnnSensitivity] as double, + ); + Settings.setValue(keyWinThreshP, defValues[keyWinThreshP] as double); } /// Returns the default values for the settings of each algorithm used. -Map defaultSettings(String algorithmName) { +Map? defaultSettings(String algorithmName) { return { firstAlgorithm: { keyCnnSensitivity: 75.0, keyWinThreshP: 6.0, - keyWinThreshPName: "Window length", + keyWinThreshPName: 'Window length', }, secondAlgorithm: { keyCnnSensitivity: 75.0, keyWinThreshP: 10.0, - keyWinThreshPName: "Window length", + keyWinThreshPName: 'Window length', }, thirdAlgorithm: { keyCnnSensitivity: 75.0, keyWinThreshP: 10.0, - keyWinThreshPName: "Count threshold" + keyWinThreshPName: 'Count threshold' }, fourthAlgorithm: { keyCnnSensitivity: 75.0, keyWinThreshP: 15.0, - keyWinThreshPName: "P", + keyWinThreshPName: 'P', }, }[algorithmName]; } @@ -258,7 +282,7 @@ Map defaultSettings(String algorithmName) { /// Returns the actual name for the 2nd setting (besides sensitivity) of each /// of the recognition algorithms. String _getCurrentWinThreshPName() { - var currentAlgo = + final currentAlgo = Settings.getValue(keyRecognitionAlgo, firstAlgorithm); - return defaultSettings(currentAlgo)[keyWinThreshPName]; + return defaultSettings(currentAlgo)![keyWinThreshPName] as String; } diff --git a/lib/ui/widgets/item_featured.dart b/lib/ui/widgets/item_featured.dart index 38caa71..fefccb2 100644 --- a/lib/ui/widgets/item_featured.dart +++ b/lib/ui/widgets/item_featured.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:flutter/painting.dart'; import 'package:modern_art_app/data/database.dart'; import 'package:modern_art_app/ui/widgets/item_tile.dart'; class FeaturedTile extends StatelessWidget { - const FeaturedTile({Key key, this.artwork, this.tileWidth, this.tileHeight}) - : super(key: key); + const FeaturedTile({ + Key? key, + required this.artwork, + required this.tileWidth, + required this.tileHeight, + }) : super(key: key); final Artwork artwork; @@ -24,7 +27,7 @@ class FeaturedTile extends StatelessWidget { artwork: artwork, tileWidth: tileWidth, tileHeight: tileHeight, - customHeroTag: artwork.name + "_featured", + customHeroTag: '${artwork.name!}_featured', ), Container( color: Colors.black45, @@ -32,27 +35,32 @@ class FeaturedTile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: RichText( text: TextSpan( children: [ TextSpan( - text: "${artwork.name}, ", - style: TextStyle( - fontSize: 28, fontStyle: FontStyle.normal), + text: '${artwork.name}, ', + style: const TextStyle( + fontSize: 28, + fontStyle: FontStyle.normal, + ), ), TextSpan( text: artwork.artist, - style: TextStyle( - fontSize: 18, fontStyle: FontStyle.italic), - ) + style: const TextStyle( + fontSize: 18, + fontStyle: FontStyle.italic, + ), + ), ], ), ), - ) + ), ], ), - ) + ), ], ); } diff --git a/lib/ui/widgets/item_list.dart b/lib/ui/widgets/item_list.dart index 56595cb..2077634 100644 --- a/lib/ui/widgets/item_list.dart +++ b/lib/ui/widgets/item_list.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/painting.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:modern_art_app/data/database.dart'; import 'package:modern_art_app/ui/widgets/item_row.dart'; @@ -7,41 +6,50 @@ import 'package:modern_art_app/ui/widgets/item_tile.dart'; class ListHorizontal extends StatelessWidget { const ListHorizontal({ - Key key, - @required this.itemList, + Key? key, + required this.itemList, this.listHeight, }) : super(key: key); // todo make input argument simple List final Stream> itemList; - final double listHeight; + final double? listHeight; @override Widget build(BuildContext context) { - double height = listHeight ?? MediaQuery.of(context).size.height * 0.2; + final double height = + listHeight ?? MediaQuery.of(context).size.height * 0.2; return Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: Container( + child: SizedBox( height: height, child: StreamBuilder( stream: itemList, - builder: (context, snapshot) { + builder: (context, AsyncSnapshot> snapshot) { if (snapshot.hasData) { - List items = snapshot.data; + final List items = snapshot.data!; return ListView.builder( scrollDirection: Axis.horizontal, physics: const BouncingScrollPhysics(), itemCount: items.length, itemBuilder: (context, index) { - return items[0].runtimeType == Artist - ? ItemTile.artist(artist: items[index], tileWidth: height) - : ItemTile.artwork( - artwork: items[index], tileWidth: height); + if (items[0].runtimeType == Artist) { + return ItemTile.artist( + artist: items[index] as Artist, + tileWidth: height, + ); + } else { + return ItemTile.artwork( + artwork: items[index] as Artwork, + tileWidth: height, + ); + } }, ); } return const Center( - child: SpinKitRotatingPlain(color: Colors.white, size: 50.0)); + child: SpinKitRotatingPlain(color: Colors.white), + ); }, ), ), @@ -50,7 +58,7 @@ class ListHorizontal extends StatelessWidget { } class ListVertical extends StatelessWidget { - const ListVertical({Key key, @required this.itemList}) : super(key: key); + const ListVertical({Key? key, required this.itemList}) : super(key: key); // todo make input argument simple List final Stream> itemList; @@ -59,19 +67,20 @@ class ListVertical extends StatelessWidget { Widget build(BuildContext context) { return StreamBuilder( stream: itemList, - builder: (context, snapshot) { + builder: (context, AsyncSnapshot> snapshot) { if (snapshot.hasData) { - List items = snapshot.data; + final List items = snapshot.data!; return ListView.builder( - scrollDirection: Axis.vertical, // padding to account for the convex app bar padding: const EdgeInsets.only(bottom: 30.0), physics: const BouncingScrollPhysics(), itemCount: items.length, itemBuilder: (context, index) { - return items[0].runtimeType == Artist - ? ItemRow.artist(artist: items[index]) - : ItemRow.artwork(artwork: items[index]); + if (items[0].runtimeType == Artist) { + return ItemRow.artist(artist: items[index] as Artist); + } else { + return ItemRow.artwork(artwork: items[index] as Artwork); + } }, ); } diff --git a/lib/ui/widgets/item_row.dart b/lib/ui/widgets/item_row.dart index e9f43fe..a13fd13 100644 --- a/lib/ui/widgets/item_row.dart +++ b/lib/ui/widgets/item_row.dart @@ -6,40 +6,40 @@ import 'package:modern_art_app/ui/widgets/item_tile.dart'; import 'package:modern_art_app/utils/utils.dart'; class ItemRow extends StatelessWidget { - // todo make arguments private - final String title; - final String subtitle; - final String imgFileName; - final double rowHeight; - final dynamic detailsPage; - final String _heroTag; - - ItemRow.artist({Key key, @required Artist artist, this.rowHeight}) - : title = artist.name, + ItemRow.artist({Key? key, required Artist artist, this.rowHeight}) + : title = artist.name!, subtitle = lifespan(artist), imgFileName = getArtistFilename(artist), - _heroTag = artist.name, + _heroTag = artist.name!, detailsPage = ArtistDetailsPage(artist: artist), super(key: key); - ItemRow.artwork({Key key, @required Artwork artwork, this.rowHeight}) - : title = artwork.name, - subtitle = "${artwork.artist}" + - (artwork.year != "" ? ", ${artwork.year}" : ""), + ItemRow.artwork({Key? key, required Artwork artwork, this.rowHeight}) + : title = artwork.name!, + subtitle = "${artwork.artist}${artwork.year != "" ? ", " + "${artwork.year}" : ""}", imgFileName = getArtworkFilename(artwork), _heroTag = artwork.id, detailsPage = ArtworkDetailsPage(artwork: artwork), super(key: key); + final String title; + final String subtitle; + final String imgFileName; + final double? rowHeight; + final Widget detailsPage; + final String _heroTag; + @override Widget build(BuildContext context) => Card( child: InkWell( onTap: () { Navigator.push( - context, MaterialPageRoute(builder: (context) => detailsPage)); + context, + MaterialPageRoute(builder: (context) => detailsPage), + ); }, child: Row( - mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(4.0), @@ -58,7 +58,7 @@ class ItemRow extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Text( title, - style: TextStyle(fontSize: 20), + style: const TextStyle(fontSize: 20), overflow: TextOverflow.ellipsis, maxLines: 2, ), @@ -67,7 +67,7 @@ class ItemRow extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( subtitle, - style: TextStyle( + style: const TextStyle( fontSize: 15, fontStyle: FontStyle.italic, ), diff --git a/lib/ui/widgets/item_tile.dart b/lib/ui/widgets/item_tile.dart index 2c449d0..bf2fceb 100644 --- a/lib/ui/widgets/item_tile.dart +++ b/lib/ui/widgets/item_tile.dart @@ -12,9 +12,9 @@ class Tile extends StatelessWidget { /// is not specified, it will be set to equal to [tileWidth], i.e. a square /// tile will be created. const Tile({ - Key key, - @required this.imagePath, - @required this.tileWidth, + Key? key, + required this.imagePath, + required this.tileWidth, this.tileHeight, this.heroTag, }) : super(key: key); @@ -26,25 +26,23 @@ class Tile extends StatelessWidget { final double tileWidth; /// Desired height of the tile. - final double tileHeight; + final double? tileHeight; /// Desired hero tag for the image displayed in the tile. - final String heroTag; + final String? heroTag; @override Widget build(BuildContext context) { - return Container( - child: SizedBox( - width: tileWidth, - height: tileHeight ?? tileWidth, - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - child: Hero( - tag: heroTag ?? imagePath, - child: Image.asset( - imagePath, - fit: BoxFit.cover, - ), + return SizedBox( + width: tileWidth, + height: tileHeight ?? tileWidth, + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Hero( + tag: heroTag ?? imagePath, + child: Image.asset( + imagePath, + fit: BoxFit.cover, ), ), ), @@ -62,27 +60,27 @@ class Tile extends StatelessWidget { class ItemTile extends StatelessWidget { /// Creates a tile with rounded corners displaying the provided [Artist]. ItemTile.artist({ - Key key, - @required Artist artist, + Key? key, + required Artist artist, this.tileWidth, this.tileHeight, - String customHeroTag, // Optional custom hero tag - }) : _title = artist.name, - _subtitle = artist.yearBirth, + String? customHeroTag, // Optional custom hero tag + }) : _title = artist.name!, + _subtitle = artist.yearBirth!, _imgFileName = getArtistFilename(artist), - _customHeroTag = customHeroTag ?? artist.name, + _customHeroTag = customHeroTag ?? artist.name!, _detailsPage = ArtistDetailsPage(artist: artist), super(key: key); /// Creates a tile with rounded corners displaying the provided [Artwork]. ItemTile.artwork({ - Key key, - @required Artwork artwork, + Key? key, + required Artwork artwork, this.tileWidth, this.tileHeight, - String customHeroTag, // Optional custom hero tag - }) : _title = artwork.name, - _subtitle = artwork.year, + String? customHeroTag, // Optional custom hero tag + }) : _title = artwork.name!, + _subtitle = artwork.year!, _imgFileName = getArtworkFilename(artwork), _customHeroTag = customHeroTag ?? artwork.id, _detailsPage = ArtworkDetailsPage( @@ -95,13 +93,13 @@ class ItemTile extends StatelessWidget { final String _title; final String _subtitle; final String _imgFileName; - final dynamic _detailsPage; + final Widget _detailsPage; /// Desired width of the tile. - final double tileWidth; + final double? tileWidth; /// Desired height of the tile. - final double tileHeight; + final double? tileHeight; final String _customHeroTag; @@ -109,18 +107,17 @@ class ItemTile extends StatelessWidget { Widget build(BuildContext context) => InkWell( onTap: () { Navigator.push( - context, MaterialPageRoute(builder: (context) => _detailsPage)); + context, + MaterialPageRoute(builder: (context) => _detailsPage), + ); }, - child: Container( - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Tile( - imagePath: _imgFileName, - heroTag: _customHeroTag, - tileWidth: tileWidth ?? MediaQuery.of(context).size.height * 0.2, - tileHeight: - tileHeight ?? MediaQuery.of(context).size.height * 0.2, - ), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Tile( + imagePath: _imgFileName, + heroTag: _customHeroTag, + tileWidth: tileWidth ?? MediaQuery.of(context).size.height * 0.2, + tileHeight: tileHeight ?? MediaQuery.of(context).size.height * 0.2, ), ), ); diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index d93dfd2..7f40977 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -1,13 +1,23 @@ import 'dart:collection'; import 'package:flutter/material.dart'; +import 'package:modern_art_app/lang/localization.dart'; -import '../lang/localization.dart'; +extension AppLocalizationsExtensions on BuildContext { + AppLocalizationsData get localizations => + Localizations.of( + this, + AppLocalizationsData, + )!; +} extension Localization on BuildContext { /// Extension method on [BuildContext] that provides access to the localized /// list of strings generated by the flutter_sheet_localization library. - AppLocalizations_Labels strings() => AppLocalizations.of(this); + AppLocalizationsData strings() => Localizations.of( + this, + AppLocalizationsData, + )!; /// Extension method on [BuildContext] that provides access to the current /// locale of the device. @@ -32,26 +42,30 @@ extension CustomStringMethods on String { /// achieve this functionality (one disadvantage is that it replaces spaces /// with "_"). String customToUpperCase() { - RegExp greek = RegExp(r'[α-ωΑ-Ω]'); - if (this.contains(greek)) { - Map greekAccentMap = Map.fromIterables( - ["ά", "έ", "ή", "ί", "ό", "ύ", "ώ"], - ["α", "ε", "η", "ι", "ο", "υ", "ω"], + final RegExp greek = RegExp('[α-ωΑ-Ω]'); + if (contains(greek)) { + final Map greekAccentMap = Map.fromIterables( + ['ά', 'έ', 'ή', 'ί', 'ό', 'ύ', 'ώ'], + ['α', 'ε', 'η', 'ι', 'ο', 'υ', 'ω'], ); return greekAccentMap.entries .fold( - this.toLowerCase(), - (String prev, MapEntry vowelToReplace) => - prev.replaceAll(vowelToReplace.key, vowelToReplace.value)) + toLowerCase(), + (String prev, MapEntry vowelToReplace) => + prev.replaceAll(vowelToReplace.key, vowelToReplace.value), + ) .toUpperCase(); } - return this.toUpperCase(); + return toUpperCase(); } } extension MapExt on Map { - Map sortedByValue(Comparable value(U u), {Order order = Order.asc}) { + Map sortedByValue( + Comparable Function(U u) value, { + Order order = Order.asc, + }) { final entries = this.entries.toList(); if (order == Order.asc) { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 0b156aa..a128ddc 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -7,18 +7,16 @@ import 'package:path_provider/path_provider.dart'; /// Formats an [Artist]'s lifespan properly, returning only year of birth if /// they are alive, or their lifespan otherwise. -/// -/// TODO implement this in [ArtistTranslated] class perhaps -/// -/// TODO return e.g. "b. 2000" in the first case, if [ArtistTranslated] is used as an input argument, the locale will be available, and can adjust accordingly for language -String lifespan(Artist artist) => artist.yearDeath.isNotEmpty - ? "${artist.yearBirth}–${artist.yearDeath}" - : artist.yearBirth; +String lifespan(Artist artist) => [ + artist.yearBirth!, + if (artist.yearDeath != null && artist.yearDeath!.isNotEmpty) + artist.yearDeath!, + ].join('–'); -String getArtistFilename(Artist artist) => "assets/painters/${artist.id}.webp"; +String getArtistFilename(Artist artist) => 'assets/painters/${artist.id}.webp'; String getArtworkFilename(Artwork artwork) => - "assets/paintings/${artwork.id}.webp"; + 'assets/paintings/${artwork.id}.webp'; /// Class that mirrors the functionality of [collections.defaultdict] in /// Python, using Dart's Map class to implement similar behaviour. If a key is @@ -27,15 +25,15 @@ String getArtworkFilename(Artwork artwork) => /// /// See https://stackoverflow.com/a/61198678 class DefaultDict extends MapBase { - final Map _map = {}; - final V Function() _ifAbsent; - /// Initialise a [DefaultDict] by providing a default value for keys that do /// not yet have values. DefaultDict(this._ifAbsent); + final Map _map = {}; + final V Function() _ifAbsent; + @override - V operator [](Object key) => _map.putIfAbsent(key as K, _ifAbsent); + V operator [](Object? key) => _map.putIfAbsent(key as K, _ifAbsent); @override void operator []=(K key, V value) => _map[key] = value; @@ -47,20 +45,20 @@ class DefaultDict extends MapBase { Iterable get keys => _map.keys; @override - V remove(Object key) => _map.remove(key); + V? remove(Object? key) => _map.remove(key); } /// Returns a file path for a json file to be saved in the temporary directory. Future getTempFilePath() async { - Directory tempDir = await getTemporaryDirectory(); - String appDocumentsPath = tempDir.path; - return join(appDocumentsPath, "${DateTime.now().toIso8601String()}.json"); + final Directory tempDir = await getTemporaryDirectory(); + final String appDocumentsPath = tempDir.path; + return join(appDocumentsPath, '${DateTime.now().toIso8601String()}.json'); } /// Saves the provided [text] string as a JSON file in the temporary directory. /// Returns the file path of the saved JSON file. Future saveToJsonFile(String text) async { - File file = File(await getTempFilePath()); + final File file = File(await getTempFilePath()); file.writeAsString(text); return file.path; } diff --git a/pubspec.lock b/pubspec.lock index d673c5c..31f4607 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,252 +7,287 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "7.0.0" + version: "31.0.0" analyzer: dependency: "direct dev" description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.39.17" + version: "2.8.0" analyzer_plugin: dependency: transitive description: name: analyzer_plugin url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.8.0" archive: dependency: transitive description: name: archive url: "https://pub.dartlang.org" source: hosted - version: "2.0.13" + version: "3.1.6" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.3.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.8.2" auto_size_text: dependency: "direct main" description: name: auto_size_text url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "3.0.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" build: dependency: transitive description: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "2.2.1" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "1.0.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.7" + version: "3.0.1" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.3.11" + version: "2.0.6" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.10.2" + version: "2.1.7" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.0.1" + version: "7.2.3" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "4.3.2" + version: "5.1.1" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "7.1.0" + version: "8.1.3" camera: dependency: "direct main" description: name: camera url: "https://pub.dartlang.org" source: hosted - version: "0.5.8+17" + version: "0.9.4+5" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + camera_web: + dependency: transitive + description: + name: camera_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1+1" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.2.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.3.1" checked_yaml: dependency: transitive description: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" cli_util: dependency: transitive description: name: cli_util url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.3.5" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" code_builder: dependency: transitive description: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.6.0" + version: "4.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0" convert: dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.1" convex_bottom_bar: dependency: "direct main" description: name: convex_bottom_bar url: "https://pub.dartlang.org" source: hosted - version: "2.6.0" - coverage: + version: "3.0.0" + cross_file: dependency: transitive description: - name: coverage + name: cross_file url: "https://pub.dartlang.org" source: hosted - version: "0.14.2" + version: "0.3.2" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.2" + version: "3.0.1" csv: dependency: transitive description: name: csv url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "5.0.1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.4" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.6" + version: "2.2.1" + db_viewer: + dependency: transitive + description: + name: db_viewer + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + drift: + dependency: transitive + description: + name: drift + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + drift_dev: + dependency: transitive + description: + name: drift_dev + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.1.2" file: dependency: transitive description: name: file url: "https://pub.dartlang.org" source: hosted - version: "5.2.1" + version: "6.1.2" fixnum: dependency: transitive description: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "0.10.11" + version: "1.0.0" flutter: dependency: "direct main" description: flutter @@ -264,68 +299,63 @@ packages: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.8.1" + version: "0.9.2" flutter_localizations: dependency: "direct main" description: flutter source: sdk version: "0.0.0" - flutter_lorem: - dependency: "direct main" - description: - name: flutter_lorem - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" flutter_markdown: dependency: "direct main" description: name: flutter_markdown url: "https://pub.dartlang.org" source: hosted - version: "0.5.2" - flutter_material_color_picker: + version: "0.6.9" + flutter_plugin_android_lifecycle: dependency: transitive description: - name: flutter_material_color_picker + name: flutter_plugin_android_lifecycle url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.0.5" flutter_settings_screens: dependency: "direct main" description: name: flutter_settings_screens url: "https://pub.dartlang.org" source: hosted - version: "0.2.1+1" + version: "0.3.2-null-safety" flutter_sheet_localization: dependency: "direct main" description: name: flutter_sheet_localization url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "5.1.1" flutter_sheet_localization_generator: dependency: "direct dev" description: - name: flutter_sheet_localization_generator - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" + path: flutter_sheet_localization_generator + ref: master + resolved-ref: f3fd9bd4bf775fc33354230e33372e31022c4935 + url: "https://github.com/msthoma/flutter_sheet_localization.git" + source: git + version: "5.1.7" flutter_spinkit: dependency: "direct main" description: name: flutter_spinkit url: "https://pub.dartlang.org" source: hosted - version: "4.1.2+1" + version: "5.1.0" flutter_staggered_grid_view: dependency: "direct main" description: name: flutter_staggered_grid_view url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "0.4.1" flutter_test: dependency: "direct dev" description: flutter @@ -342,427 +372,413 @@ packages: name: fluttertoast url: "https://pub.dartlang.org" source: hosted - version: "7.1.6" + version: "8.0.8" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" glob: dependency: transitive description: name: glob url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.2" google_fonts: dependency: "direct main" description: name: google_fonts url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "2.1.0" graphs: dependency: transitive description: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+4" + version: "2.1.0" http: dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.4" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.0.1" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" image: dependency: transitive description: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.19" + version: "3.1.0" intl: dependency: "direct main" description: name: intl url: "https://pub.dartlang.org" source: hosted - version: "0.16.1" + version: "0.17.0" io: dependency: transitive description: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "1.0.3" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3-nullsafety.2" + version: "0.6.3" json_annotation: dependency: transitive description: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.1.1" + version: "4.4.0" + lint: + dependency: "direct dev" + description: + name: lint + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + localization_builder: + dependency: transitive + description: + name: localization_builder + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.7" logging: dependency: transitive description: name: logging url: "https://pub.dartlang.org" source: hosted - version: "0.11.4" + version: "1.0.2" markdown: dependency: transitive description: name: markdown url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "4.0.1" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.11" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.7.0" mime: dependency: transitive description: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.7" + version: "1.0.1" moor: dependency: "direct main" description: name: moor url: "https://pub.dartlang.org" source: hosted - version: "3.4.0" + version: "4.6.1+1" moor_db_viewer: dependency: "direct main" description: name: moor_db_viewer url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" - moor_flutter: - dependency: transitive - description: - name: moor_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" + version: "5.0.0" moor_generator: dependency: "direct dev" description: name: moor_generator url: "https://pub.dartlang.org" source: hosted - version: "3.3.1" + version: "4.6.0+1" nested: dependency: transitive description: name: nested url: "https://pub.dartlang.org" source: hosted - version: "0.0.4" - node_interop: - dependency: transitive - description: - name: node_interop - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.1" - node_io: - dependency: transitive - description: - name: node_io - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.13" + version: "1.0.0" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.0.2" package_info: dependency: "direct main" description: name: package_info url: "https://pub.dartlang.org" source: hosted - version: "0.4.3+4" + version: "2.0.2" palette_generator: dependency: "direct main" description: name: palette_generator url: "https://pub.dartlang.org" source: hosted - version: "0.2.3" + version: "0.3.2" path: dependency: "direct main" description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0" path_provider: dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.27" + version: "2.0.8" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+2" + version: "2.1.4" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+8" + version: "2.0.4" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+3" + version: "2.0.4" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.2" + version: "1.11.1" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "4.4.0" photo_view: dependency: "direct main" description: name: photo_view url: "https://pub.dartlang.org" source: hosted - version: "0.10.3" + version: "0.13.0" platform: dependency: transitive description: name: platform url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.2" pool: dependency: transitive description: name: pool url: "https://pub.dartlang.org" source: hosted - version: "1.5.0-nullsafety.2" + version: "1.5.0" process: dependency: transitive description: name: process url: "https://pub.dartlang.org" source: hosted - version: "3.0.13" + version: "4.2.4" provider: dependency: "direct main" description: name: provider url: "https://pub.dartlang.org" source: hosted - version: "4.3.3" + version: "6.0.1" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.4" + version: "2.1.0" pubspec_parse: dependency: transitive description: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.8" + version: "1.2.0" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.1+1" recase: dependency: transitive description: name: recase url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "4.0.0" sensors: dependency: "direct main" description: name: sensors url: "https://pub.dartlang.org" source: hosted - version: "0.4.2+6" - sentry: - dependency: transitive + version: "2.0.3" + share: + dependency: "direct main" description: - name: sentry + name: share url: "https://pub.dartlang.org" source: hosted - version: "4.0.4" - sentry_flutter: - dependency: "direct main" + version: "2.0.4" + shared_preferences: + dependency: transitive description: - name: sentry_flutter + name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "4.0.4" - share: - dependency: "direct main" + version: "2.0.11" + shared_preferences_android: + dependency: transitive description: - name: share + name: shared_preferences_android url: "https://pub.dartlang.org" source: hosted - version: "0.6.5+4" - shared_preferences: + version: "2.0.9" + shared_preferences_ios: dependency: transitive description: - name: shared_preferences + name: shared_preferences_ios url: "https://pub.dartlang.org" source: hosted - version: "0.5.12+4" + version: "2.0.8" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.2+4" + version: "2.0.3" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+11" + version: "2.0.2" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.2+7" + version: "2.0.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.2+3" + version: "2.0.3" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - shelf_static: - dependency: transitive - description: - name: shelf_static - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.9+2" + version: "1.2.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4" + version: "1.0.1" sky_engine: dependency: transitive description: flutter @@ -774,275 +790,226 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+1" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0-nullsafety.3" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.10-nullsafety.2" + version: "1.2.1" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" - sqflite: - dependency: transitive - description: - name: sqflite - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.2+3" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3+1" + version: "1.8.1" sqlite3: dependency: transitive description: name: sqlite3 url: "https://pub.dartlang.org" source: hosted - version: "0.1.8" + version: "1.4.0" sqlite3_flutter_libs: dependency: "direct main" description: name: sqlite3_flutter_libs url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.5.2" sqlparser: dependency: transitive description: name: sqlparser url: "https://pub.dartlang.org" source: hosted - version: "0.10.1" + version: "0.18.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" stream_transform: dependency: transitive description: name: stream_transform url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" - synchronized: + version: "1.1.0" + template_string: dependency: transitive description: - name: synchronized + name: template_string url: "https://pub.dartlang.org" source: hosted - version: "2.2.0+2" + version: "0.6.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" - test: - dependency: "direct dev" - description: - name: test - url: "https://pub.dartlang.org" - source: hosted - version: "1.16.0-nullsafety.5" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" - test_core: - dependency: transitive - description: - name: test_core - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.12-nullsafety.5" + version: "0.4.3" tflite: dependency: "direct main" description: path: "." ref: master - resolved-ref: "1ba5faf21f6641a518f3ccc8ed7e8466681fb616" + resolved-ref: ce8075a391e02e0cc0bc6c2db950aa961a17d745 url: "https://github.com/shaqian/flutter_tflite.git" source: git - version: "1.1.1" + version: "1.1.2" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+3" + version: "1.0.0" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" url_launcher: dependency: "direct main" description: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.7.10" + version: "6.0.17" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.13" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.13" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+4" + version: "2.0.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+9" + version: "2.0.2" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "2.0.4" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.5+3" + version: "2.0.5" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+3" - uuid: - dependency: transitive - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.2" + version: "2.0.2" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.1" vibration: dependency: "direct main" description: name: vibration url: "https://pub.dartlang.org" source: hosted - version: "1.7.3" + version: "1.7.4-nullsafety.0" vibration_web: dependency: transitive description: name: vibration_web url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" - vm_service: - dependency: transitive - description: - name: vm_service - url: "https://pub.dartlang.org" - source: hosted - version: "5.5.0" + version: "1.6.3-nullsafety.0" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+15" + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" + version: "2.1.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "1.7.4+1" + version: "2.3.1" xdg_directories: dependency: transitive description: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0" xml: dependency: transitive description: name: xml url: "https://pub.dartlang.org" source: hosted - version: "4.5.1" + version: "5.3.1" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.1.0" sdks: - dart: ">=2.10.2 <2.11.0" - flutter: ">=1.22.2 <2.0.0" + dart: ">=2.15.0-7.0.dev <3.0.0" + flutter: ">=2.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index 213de22..ab40287 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,67 +17,64 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.12.2+1 +version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: + auto_size_text: ^3.0.0 + camera: ^0.9.4+5 + convex_bottom_bar: ^3.0.0 + cupertino_icons: ^1.0.0 flutter: sdk: flutter - auto_size_text: ^2.1.0 - camera: ^0.5.8+11 - convex_bottom_bar: ^2.6.0 - cupertino_icons: ^1.0.0 - flutter_lorem: ^1.1.0 - flutter_markdown: ^0.5.2 - flutter_settings_screens: ^0.2.1 - flutter_spinkit: "^4.1.2" - flutter_staggered_grid_view: ^0.3.2 - fluttertoast: ^7.1.6 - google_fonts: ^1.1.2 - # getwidget: ^1.2.2 - http: ^0.12.2 - moor: ^3.4.0 - moor_db_viewer: ^2.0.3 - palette_generator: ^0.2.3 + flutter_localizations: + sdk: flutter + flutter_markdown: ^0.6.9 + flutter_settings_screens: ^0.3.2-null-safety + flutter_sheet_localization: ^5.1.1 + flutter_spinkit: ^5.1.0 + flutter_staggered_grid_view: ^0.4.1 + fluttertoast: ^8.0.8 + google_fonts: ^2.1.0 + http: ^0.13.1 + intl: ^0.17.0 + moor: ^4.6.1+1 + moor_db_viewer: ^5.0.0 + package_info: ^2.0.2 + palette_generator: ^0.3.2 path: path_provider: - package_info: '>=0.4.3 <2.0.0' - photo_view: ^0.10.2 - provider: ^4.3.2+2 - sensors: '>=0.4.2+4 <2.0.0' - sentry_flutter: ^4.0.4 - share: '>=0.6.5+4 <2.0.0' - sqlite3_flutter_libs: ^0.3.0 - # adding tflite from Github for now, since the pub version contains a bug for Android - # versions <= 6.0, see https://github.com/shaqian/flutter_tflite/issues/165, could be reverted - # once the new version is available officially + photo_view: ^0.13.0 + provider: ^6.0.1 + sensors: ^2.0.3 + share: ^2.0.4 + sqlite3_flutter_libs: ^0.5.2 tflite: + # adding tflite from Github for now, since the pub version contains a bug for Android + # versions <= 6.0, see https://github.com/shaqian/flutter_tflite/issues/165, could be reverted + # once the new version is available officially git: url: https://github.com/shaqian/flutter_tflite.git ref: master - vibration: ^1.7.3 - url_launcher: ^5.7.10 - - # localization related dependencies - flutter_localizations: - sdk: flutter - flutter_sheet_localization: ^2.0.0 - intl: ^0.16.1 + url_launcher: ^6.0.17 + vibration: ^1.7.4-nullsafety.0 dev_dependencies: - build_runner: - flutter_launcher_icons: "^0.8.0" - flutter_sheet_localization_generator: ^2.0.2 + analyzer: ^2.1.0 + build_runner: ^2.1.5 + flutter_launcher_icons: ^0.9.2 + flutter_sheet_localization_generator: + # adding localization generator below from own fork, to resolve dependency conflicts + git: + url: https://github.com/msthoma/flutter_sheet_localization.git + path: flutter_sheet_localization_generator + ref: master flutter_test: sdk: flutter - moor_generator: ^3.3.1 - test: ^1.15.7 - # temporarily necessary to fix the issue below, try removing in the future - # https://github.com/simolus3/moor/issues/553 - # https://github.com/simolus3/moor/issues/777 - analyzer: 0.39.17 + lint: ^1.8.1 + moor_generator: ^4.6.0+1 flutter_icons: # run `flutter pub run flutter_launcher_icons:main` in the terminal to create app launcher icons @@ -89,11 +86,9 @@ flutter_icons: # adaptive_icon_background: "#674545" # adaptive_icon_foreground: "assets/app_launcher_icons/hadjida_untitled_app_icon_android_adaptive.png" -# The following section is specific to Flutter. flutter: uses-material-design: true - # To add assets to your application, add an assets section, like this: assets: - CHANGELOG.md - assets/ @@ -101,10 +96,6 @@ flutter: - assets/google_fonts/ - assets/painters/ - assets/paintings/ - # - assets/tflite/VGG_with_no_art_quant.tflite - assets/tflite/MobNetNoArt500Frames_4.tflite - # - assets/tflite/InceptionV3NoArt500Frames_quant.tflite - assets/tflite/MobileNet_No_Art_labels.txt - # - assets/tflite/ - assets/app_launcher_icons/hadjida_untitled_app_icon_square_android_adaptive.png -# - assets/tflite/other/