From 56a9e3f421cf52b1d74ae8cb9ddf40c3e336772e Mon Sep 17 00:00:00 2001 From: Mguy13 Date: Sun, 25 Dec 2022 01:55:53 +0100 Subject: [PATCH] Favourites --- .../home/api/unsplash_images_api.dart | 6 ++-- .../home/data/dtos/image_model_dto.dart | 28 +++++++++++++++++++ .../image_model_dto.g.dart} | 7 +++-- .../home/data/models/image_model.dart | 13 ++++----- .../home/services/images_service.dart | 21 ++++++++------ .../home/views/gallery/gallery_view.dart | 2 +- .../views/gallery/gallery_view_model.dart | 11 ++++---- .../views/gallery/search_gallery_view.dart | 2 +- .../image_carousel/image_carousel_view.dart | 6 ++-- .../image_carousel_view_model.dart | 6 ++-- 10 files changed, 66 insertions(+), 36 deletions(-) create mode 100644 lib/features/home/data/dtos/image_model_dto.dart rename lib/features/home/data/{models/image_model.g.dart => dtos/image_model_dto.g.dart} (73%) diff --git a/lib/features/home/api/unsplash_images_api.dart b/lib/features/home/api/unsplash_images_api.dart index 0ed3974..9afe018 100644 --- a/lib/features/home/api/unsplash_images_api.dart +++ b/lib/features/home/api/unsplash_images_api.dart @@ -7,7 +7,7 @@ import '/features/core/services/logging_service.dart'; import '/l10n/generated/l10n.dart'; import '/locator.dart'; import '../abstracts/images_api.dart'; -import '../data/models/image_model.dart'; +import '../data/dtos/image_model_dto.dart'; class UnsplashImagesApi implements ImagesApi { final LoggingService _loggingService = LoggingService.locate; @@ -29,7 +29,7 @@ class UnsplashImagesApi implements ImagesApi { final imageUri = _imageUrlGenerator(imageSide: imageSide); - return ImageModel( + return ImageModelDto( imageIndex: imageIndex, uri: imageUri, // Custom dummy name for the image @@ -66,7 +66,7 @@ class UnsplashImagesApi implements ImagesApi { final imageUri = _imageUrlGenerator(imageSide: imageSide); - return ImageModel( + return ImageModelDto( imageIndex: imageIndex, uri: imageUri, // Custom dummy name for the image diff --git a/lib/features/home/data/dtos/image_model_dto.dart b/lib/features/home/data/dtos/image_model_dto.dart new file mode 100644 index 0000000..d6f7d77 --- /dev/null +++ b/lib/features/home/data/dtos/image_model_dto.dart @@ -0,0 +1,28 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'image_model_dto.g.dart'; + +@JsonSerializable() +class ImageModelDto { + const ImageModelDto({ + required this.uri, + required this.imageIndex, + required this.imageName, + }); + + /// An image's target [Uri]. + /// + /// Storing an image's [ByteData] is more expensive, memory-wise. + final Uri uri; + + /// A unique identifier that can be used for indexing the image. + final int imageIndex; + + /// Given name of the image. + final String imageName; + + factory ImageModelDto.fromJson(Map json) => _$ImageModelDtoFromJson(json); + + /// Connect the generated [_$PersonToJson] function to the `toJson` method. + Map toJson() => _$ImageModelDtoToJson(this); +} diff --git a/lib/features/home/data/models/image_model.g.dart b/lib/features/home/data/dtos/image_model_dto.g.dart similarity index 73% rename from lib/features/home/data/models/image_model.g.dart rename to lib/features/home/data/dtos/image_model_dto.g.dart index fd16a20..ccaa9e1 100644 --- a/lib/features/home/data/models/image_model.g.dart +++ b/lib/features/home/data/dtos/image_model_dto.g.dart @@ -1,18 +1,19 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'image_model.dart'; +part of 'image_model_dto.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -ImageModel _$ImageModelFromJson(Map json) => ImageModel( +ImageModelDto _$ImageModelDtoFromJson(Map json) => + ImageModelDto( uri: Uri.parse(json['uri'] as String), imageIndex: json['imageIndex'] as int, imageName: json['imageName'] as String, ); -Map _$ImageModelToJson(ImageModel instance) => +Map _$ImageModelDtoToJson(ImageModelDto instance) => { 'uri': instance.uri.toString(), 'imageIndex': instance.imageIndex, diff --git a/lib/features/home/data/models/image_model.dart b/lib/features/home/data/models/image_model.dart index 4765552..b70b381 100644 --- a/lib/features/home/data/models/image_model.dart +++ b/lib/features/home/data/models/image_model.dart @@ -1,13 +1,12 @@ import 'package:json_annotation/json_annotation.dart'; -part 'image_model.g.dart'; - @JsonSerializable() -class ImageModel { - const ImageModel({ +class ImageModelDto { + const ImageModelDto({ required this.uri, required this.imageIndex, required this.imageName, + required this.isFavourite, }); /// An image's target [Uri]. @@ -21,8 +20,6 @@ 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); + /// Whether the image was 'Starred' ot not. + final bool isFavourite; } diff --git a/lib/features/home/services/images_service.dart b/lib/features/home/services/images_service.dart index c69a80f..e909608 100644 --- a/lib/features/home/services/images_service.dart +++ b/lib/features/home/services/images_service.dart @@ -1,16 +1,15 @@ import 'dart:async'; -import 'package:mc_gallery/features/core/data/extensions/string_extensions.dart'; - import '/features/core/data/constants/const_sorters.dart'; import '/features/core/data/extensions/iterable_extensions.dart'; import '/features/core/data/extensions/map_extensions.dart'; +import '/features/core/data/extensions/string_extensions.dart'; import '/features/core/services/logging_service.dart'; import '/features/core/utils/mutex.dart'; import '/locator.dart'; import '../abstracts/images_api.dart'; +import '../data/dtos/image_model_dto.dart'; import '../data/enums/search_option.dart'; -import '../data/models/image_model.dart'; /// Handles fetching and storing of Images. /// @@ -28,8 +27,8 @@ class ImagesService { final ImagesApi _imagesApi; final LoggingService _loggingService; - late final Map _imageModels; - Iterable get imageModels => _imageModels.values.deepCopy; + late final Map _imageModels; + Iterable get imageModels => _imageModels.values.deepCopy; final Mutex _searchMutex = Mutex(); @@ -43,7 +42,7 @@ class ImagesService { _loggingService.info('Fetching and creating image models...'); _imageModels = { for (final imageModel in (await _imagesApi.fetchImageUri(token: '')) - .map((final emulatedModelSerialized) => ImageModel.fromJson(emulatedModelSerialized))) + .map((final emulatedModelSerialized) => ImageModelDto.fromJson(emulatedModelSerialized))) imageModel.imageName: imageModel }; @@ -58,7 +57,7 @@ class ImagesService { int get lastAvailableImageIndex => _imageModels.length - 1; int get numberOfImages => _imageModels.length; - ImageModel imageModelAt({required int index}) => _imageModels.values.elementAt(index); + ImageModelDto imageModelAt({required int index}) => _imageModels.values.elementAt(index); Future get lastQueryIsCompleted => _searchMutex.lastOperationCompletionAwaiter; @@ -70,7 +69,7 @@ class ImagesService { /// There are lots of optimizations possible for new inputs, for example reducing search frontier /// by using set-cover/subsetting optimizations on backspace, and so on, but again, not the point, /// I think. - Future> searchImages({ + Future> searchImages({ required SearchOption searchOption, required String imageNamePart, bool treatAsInSequence = false, @@ -89,14 +88,18 @@ class ImagesService { ..sort((final a, final b) => ConstSorters.stringsSimilarityTarget(targetWord: imageNamePart, a, b)) ..reversed; + return _imageModels.valuesByKeys(keys: rankedKeys).toList(growable: false); + case SearchOption.web: return (await _imagesApi.searchImages( searchStr: imageNamePart, token: '', )) .map( - (final emulatedModelSerialized) => ImageModel.fromJson(emulatedModelSerialized)) + (final emulatedModelSerialized) => + ImageModelDto.fromJson(emulatedModelSerialized), + ) .toList(growable: false); } } finally { diff --git a/lib/features/home/views/gallery/gallery_view.dart b/lib/features/home/views/gallery/gallery_view.dart index 2e5e914..ef720af 100644 --- a/lib/features/home/views/gallery/gallery_view.dart +++ b/lib/features/home/views/gallery/gallery_view.dart @@ -7,8 +7,8 @@ import '/features/core/widgets/gap.dart'; import '/features/core/widgets/mcg_scaffold.dart'; import '/features/core/widgets/state/multi_value_listenable_builder.dart'; import '/features/core/widgets/state/view_model_builder.dart'; +import '../../data/dtos/image_model_dto.dart'; import '../../data/enums/search_option.dart'; -import '../../data/models/image_model.dart'; import 'gallery_view_model.dart'; part 'downloaded_gallery_view.dart'; diff --git a/lib/features/home/views/gallery/gallery_view_model.dart b/lib/features/home/views/gallery/gallery_view_model.dart index 65cdc5d..1b37bc3 100644 --- a/lib/features/home/views/gallery/gallery_view_model.dart +++ b/lib/features/home/views/gallery/gallery_view_model.dart @@ -8,8 +8,8 @@ import '/features/core/abstracts/base_view_model.dart'; import '/features/core/services/logging_service.dart'; import '/features/core/services/navigation_service.dart'; import '/locator.dart'; +import '../../data/dtos/image_model_dto.dart'; import '../../data/enums/search_option.dart'; -import '../../data/models/image_model.dart'; import '../../services/image_cache_manager_service.dart'; import '../../services/images_service.dart'; import '../image_carousel/image_carousel_view.dart'; @@ -37,8 +37,9 @@ class GalleryViewModel extends BaseViewModel { ValueListenable get isSearchingListenable => _isSearchingNotifier; final ValueNotifier _searchOptionNotifier = ValueNotifier(SearchOption.web); ValueListenable get searchOptionListenable => _searchOptionNotifier; - final ValueNotifier> _imageSearchResultsNotifier = ValueNotifier([]); - ValueListenable> get imageSearchResultsListenable => _imageSearchResultsNotifier; + final ValueNotifier> _imageSearchResultsNotifier = ValueNotifier([]); + ValueListenable> get imageSearchResultsListenable => + _imageSearchResultsNotifier; @override Future initialise(bool Function() mounted, [arguments]) async { @@ -97,13 +98,13 @@ class GalleryViewModel extends BaseViewModel { void onPromptPressed() => _isDisplayingPressingPrompt.value = false; - Iterable get imageModels => _imagesService.imageModels; + Iterable get imageModels => _imagesService.imageModels; Future get initImageFetchIsDone => _imagesService.initAwaiter; double? downloadProgressValue({required DownloadProgress progress}) => progress.totalSize != null ? progress.downloaded / progress.totalSize! : null; - void pushImageCarouselView(BuildContext context, {required ImageModel imageModel}) => + void pushImageCarouselView(BuildContext context, {required ImageModelDto imageModel}) => _navigationService.pushImageCarouselView( context, imageCarouselViewArguments: ImageCarouselViewArguments( diff --git a/lib/features/home/views/gallery/search_gallery_view.dart b/lib/features/home/views/gallery/search_gallery_view.dart index 7ec68b3..01512e5 100644 --- a/lib/features/home/views/gallery/search_gallery_view.dart +++ b/lib/features/home/views/gallery/search_gallery_view.dart @@ -10,7 +10,7 @@ class _SearchGalleryView extends StatelessWidget { @override Widget build(BuildContext context) { - return ValueListenableBuilder>( + return ValueListenableBuilder>( valueListenable: galleryViewModel.imageSearchResultsListenable, builder: (context, final resultsImageModels, _) => FutureBuilder( future: galleryViewModel.lastQueryResultDone, diff --git a/lib/features/home/views/image_carousel/image_carousel_view.dart b/lib/features/home/views/image_carousel/image_carousel_view.dart index 051f4c3..89392f6 100644 --- a/lib/features/home/views/image_carousel/image_carousel_view.dart +++ b/lib/features/home/views/image_carousel/image_carousel_view.dart @@ -3,7 +3,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:mc_gallery/features/home/data/models/image_model.dart'; +import 'package:mc_gallery/features/home/data/dtos/image_model_dto.dart'; import '/features/core/data/constants/const_colors.dart'; import '/features/core/data/constants/const_text.dart'; @@ -59,7 +59,7 @@ class ImageCarouselView extends StatelessWidget { itemBuilder: (context, _, __) => Stack( fit: StackFit.expand, children: [ - ValueListenableBuilder( + ValueListenableBuilder( valueListenable: model.currentImageModelListenable, builder: (context, _, __) => CachedNetworkImage( imageUrl: model.currentImageUrl, @@ -71,7 +71,7 @@ class ImageCarouselView extends StatelessWidget { ), ), ), - ValueListenableBuilder( + ValueListenableBuilder( valueListenable: model.currentImageModelListenable, builder: (context, _, __) => Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/features/home/views/image_carousel/image_carousel_view_model.dart b/lib/features/home/views/image_carousel/image_carousel_view_model.dart index 3381ec1..c0f5f6f 100644 --- a/lib/features/home/views/image_carousel/image_carousel_view_model.dart +++ b/lib/features/home/views/image_carousel/image_carousel_view_model.dart @@ -4,10 +4,10 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import '/features/core/abstracts/base_view_model.dart'; import '/features/core/services/logging_service.dart'; import '/features/core/services/navigation_service.dart'; -import '/features/home/data/models/image_model.dart'; import '/features/home/services/images_service.dart'; import '/features/home/views/image_carousel/image_carousel_view.dart'; import '/locator.dart'; +import '../../data/dtos/image_model_dto.dart'; class ImageCarouselViewModel extends BaseViewModel { ImageCarouselViewModel({ @@ -22,8 +22,8 @@ class ImageCarouselViewModel extends BaseViewModel { final NavigationService _navigationService; final LoggingService _loggingService; - late final ValueNotifier _currentImageModelNotifier; - ValueListenable get currentImageModelListenable => _currentImageModelNotifier; + late final ValueNotifier _currentImageModelNotifier; + ValueListenable get currentImageModelListenable => _currentImageModelNotifier; @override Future initialise(bool Function() mounted, [arguments]) async {