diff --git a/README.md b/README.md index 9567448..5b90e68 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,13 @@ # mc_gallery -A new Flutter project. +## Dart docs explanation -## Getting Started +## Emulation -This project is a starting point for a Flutter application. +## Maintaining scope +It's an 'assignment' -A few resources to get you started if this is your first Flutter project: +## Model vs. DTO -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +## Extra quirks +Just because I had those assets lying around \ No newline at end of file diff --git a/lib/features/home/abstracts/images_api.dart b/lib/features/home/abstracts/images_api.dart index a0b2475..de9314f 100644 --- a/lib/features/home/abstracts/images_api.dart +++ b/lib/features/home/abstracts/images_api.dart @@ -1,15 +1,13 @@ import 'dart:async'; -import '../data/models/image_model.dart'; - /// Interface for implementing image-fetching strategies, specific to a resource location on the internet. /// /// Since I used a site that was more obscure than the ones in the examples, this (otherwise pointless /// and convoluting) interface is for adding a bit of flexibility to change strategy to some other site. abstract class ImagesApi { - FutureOr> fetchImageUri({required String token}); + FutureOr>> fetchImageUri({required String token}); - FutureOr> searchImages({ + FutureOr>> searchImages({ required String searchStr, required String token, }); diff --git a/lib/features/home/api/unsplash_images_api.dart b/lib/features/home/api/unsplash_images_api.dart index 8efe727..0ed3974 100644 --- a/lib/features/home/api/unsplash_images_api.dart +++ b/lib/features/home/api/unsplash_images_api.dart @@ -14,14 +14,15 @@ class UnsplashImagesApi implements ImagesApi { final random = Random(); @override - FutureOr> fetchImageUri({required String token}) async { + FutureOr>> fetchImageUri({required String token}) async { // Dummy fetching delay emulation await Future.delayed(const Duration( milliseconds: ConstValues.defaultEmulatedLatencyMillis * ConstValues.numberOfImages)); try { // Create fixed number of images - return Iterable.generate(ConstValues.numberOfImages).map((final imageIndex) { + final dummyImageModels = + Iterable.generate(ConstValues.numberOfImages).map((final imageIndex) { // Drawing from a normal distribution final imageSide = random.nextIntInRange(min: ConstValues.minImageSize, max: ConstValues.maxImageSize); @@ -36,6 +37,9 @@ class UnsplashImagesApi implements ImagesApi { imageName: Strings.current.imageNameFetch(imageIndex + 1, imageSide), ); }); + + // Emulating serialization + return dummyImageModels.map((final dummyModel) => dummyModel.toJson()); } on Exception catch (ex, stackTrace) { _loggingService.handleException(ex, stackTrace); return const Iterable.empty(); @@ -43,7 +47,7 @@ class UnsplashImagesApi implements ImagesApi { } @override - FutureOr> searchImages({ + FutureOr>> searchImages({ required String searchStr, required String token, }) async { @@ -55,7 +59,7 @@ class UnsplashImagesApi implements ImagesApi { try { // Create (randomly-bounded) dummy number of images - return Iterable.generate(numberOfResults).map((final imageIndex) { + final dummyImageModels = Iterable.generate(numberOfResults).map((final imageIndex) { // Drawing from a normal distribution final imageSide = random.nextIntInRange(min: ConstValues.minImageSize, max: ConstValues.maxImageSize); @@ -68,7 +72,10 @@ class UnsplashImagesApi implements ImagesApi { // Custom dummy name for the image imageName: Strings.current.imageNameSearch(searchStr, imageIndex + 1), ); - }).toList(growable: false); + }); + + // Emulating serialization + return dummyImageModels.map((final dummyModel) => dummyModel.toJson()); } on Exception catch (ex, stackTrace) { _loggingService.handleException(ex, stackTrace); return List.empty(); diff --git a/lib/features/home/data/models/image_model.dart b/lib/features/home/data/models/image_model.dart index 959fb3e..4765552 100644 --- a/lib/features/home/data/models/image_model.dart +++ b/lib/features/home/data/models/image_model.dart @@ -1,3 +1,8 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'image_model.g.dart'; + +@JsonSerializable() class ImageModel { const ImageModel({ required this.uri, @@ -15,4 +20,9 @@ class ImageModel { /// Given name of the image. final String imageName; + + factory ImageModel.fromJson(Map json) => _$ImageModelFromJson(json); + + /// Connect the generated [_$PersonToJson] function to the `toJson` method. + Map toJson() => _$ImageModelToJson(this); } diff --git a/lib/features/home/data/models/image_model.g.dart b/lib/features/home/data/models/image_model.g.dart new file mode 100644 index 0000000..fd16a20 --- /dev/null +++ b/lib/features/home/data/models/image_model.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'image_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ImageModel _$ImageModelFromJson(Map json) => ImageModel( + uri: Uri.parse(json['uri'] as String), + imageIndex: json['imageIndex'] as int, + imageName: json['imageName'] as String, + ); + +Map _$ImageModelToJson(ImageModel instance) => + { + 'uri': instance.uri.toString(), + 'imageIndex': instance.imageIndex, + 'imageName': instance.imageName, + }; diff --git a/lib/features/home/services/images_service.dart b/lib/features/home/services/images_service.dart index a00f294..c69a80f 100644 --- a/lib/features/home/services/images_service.dart +++ b/lib/features/home/services/images_service.dart @@ -42,7 +42,8 @@ class ImagesService { Future _init() async { _loggingService.info('Fetching and creating image models...'); _imageModels = { - for (final imageModel in await _imagesApi.fetchImageUri(token: '')) + for (final imageModel in (await _imagesApi.fetchImageUri(token: '')) + .map((final emulatedModelSerialized) => ImageModel.fromJson(emulatedModelSerialized))) imageModel.imageName: imageModel }; @@ -90,10 +91,13 @@ class ImagesService { ..reversed; return _imageModels.valuesByKeys(keys: rankedKeys).toList(growable: false); case SearchOption.web: - return await _imagesApi.searchImages( + return (await _imagesApi.searchImages( searchStr: imageNamePart, token: '', - ); + )) + .map( + (final emulatedModelSerialized) => ImageModel.fromJson(emulatedModelSerialized)) + .toList(growable: false); } } finally { unlock(); diff --git a/lib/features/home/views/gallery/gallery_view.dart b/lib/features/home/views/gallery/gallery_view.dart index 75a01c4..2e5e914 100644 --- a/lib/features/home/views/gallery/gallery_view.dart +++ b/lib/features/home/views/gallery/gallery_view.dart @@ -46,9 +46,9 @@ class GalleryView extends StatelessWidget { ], builder: (context, final values, child) => !model.isDisplayingPressingPrompt.value ? IconButton( - icon: !model.isSearchingListenable.value - ? const Icon(Icons.search) - : const Icon(Icons.close), + isSelected: model.isSearchingListenable.value, + icon: const Icon(Icons.search), + selectedIcon: const Icon(Icons.close), onPressed: model.searchPressed, ) : const SizedBox.shrink(), diff --git a/pubspec.lock b/pubspec.lock index 67d91e0..5a3a54c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.3" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.7" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.4.2" cached_network_image: dependency: "direct main" description: @@ -92,6 +148,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" clock: dependency: transitive description: @@ -99,6 +162,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" collection: dependency: transitive description: @@ -183,6 +253,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" flutter: dependency: "direct main" description: flutter @@ -233,6 +310,13 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" get_it: dependency: "direct main" description: @@ -254,6 +338,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.0.0" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" http: dependency: transitive description: @@ -261,6 +352,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.13.5" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -289,6 +387,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.8.1" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" js: dependency: transitive description: @@ -296,6 +401,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.4" + json_annotation: + dependency: "direct dev" + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.7.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "6.5.4" lints: dependency: transitive description: @@ -338,6 +457,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" nested: dependency: transitive description: @@ -471,6 +597,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.6.2" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" process: dependency: transitive description: @@ -492,6 +625,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" rxdart: dependency: transitive description: @@ -499,11 +639,39 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.27.7" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.6" + source_helper: + dependency: transitive + description: + name: source_helper + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.3" source_span: dependency: transitive description: @@ -539,6 +707,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -595,6 +770,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.12" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" typed_data: dependency: transitive description: @@ -623,6 +805,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7495f6e..5bc5132 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,6 +50,11 @@ dev_dependencies: flutter_lints: ^2.0.1 + # Builders + build_runner: ^2.3.3 + json_annotation: ^4.7.0 + json_serializable: ^6.5.4 + flutter: uses-material-design: true