diff --git a/assets/icons/star_filled.svg b/assets/icons/star_filled.svg deleted file mode 100644 index 3ae6fef..0000000 --- a/assets/icons/star_filled.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/icons/star_outline.svg b/assets/icons/star_outline.svg deleted file mode 100644 index 99b6849..0000000 --- a/assets/icons/star_outline.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lib/features/core/abstracts/app_setup.dart b/lib/features/core/abstracts/app_setup.dart index 05b08be..854f6a2 100644 --- a/lib/features/core/abstracts/app_setup.dart +++ b/lib/features/core/abstracts/app_setup.dart @@ -1,14 +1,14 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:hive_flutter/hive_flutter.dart'; import '/l10n/generated/l10n.dart'; import '/locator.dart'; abstract class AppSetup { + // TODO: When locator is properly refactored we should not have to use these stub methods for testing static Future initialise() async { WidgetsFlutterBinding.ensureInitialized(); @@ -19,8 +19,6 @@ abstract class AppSetup { DeviceOrientation.portraitDown, ]); - await Hive.initFlutter(); - await Locator.setup(); } diff --git a/lib/features/core/data/constants/const_media.dart b/lib/features/core/data/constants/const_media.dart deleted file mode 100644 index 9e2af06..0000000 --- a/lib/features/core/data/constants/const_media.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_svg/flutter_svg.dart'; - -abstract class ConstMedia { - static const String favStarFilled = 'assets/icons/star_filled.svg'; - static const String favStarOutline = 'assets/icons/star_outline.svg'; - - static SvgPicture buildIcon( - String iconReference, { - Color? color, - double? width, - double? height, - BoxFit fit = BoxFit.contain, - Clip clipBehavior = Clip.hardEdge, - Alignment alignment = Alignment.center, - }) => - SvgPicture.asset( - iconReference, - color: color, - width: width, - height: height, - fit: fit, - clipBehavior: clipBehavior, - alignment: alignment, - ); -} diff --git a/lib/features/core/data/extensions/map_extensions.dart b/lib/features/core/data/extensions/map_extensions.dart index 74de75a..26f310a 100644 --- a/lib/features/core/data/extensions/map_extensions.dart +++ b/lib/features/core/data/extensions/map_extensions.dart @@ -1,17 +1,6 @@ -import 'dart:collection'; - extension MapExtensions on Map { Map get deepCopy => {...this}; /// Returns the values of a [Map] at given [keys] indices. Iterable valuesByKeys({required Iterable keys}) => keys.map((final key) => this[key]!); } - -extension LinkedHashMapExtensions on LinkedHashMap { - /// Updated the value at [valueIndex] to [newValue], in addition to preserving the order. - void updateValueAt({ - required int valueIndex, - required B newValue, - }) => - this[keys.toList()[valueIndex]] = newValue; -} diff --git a/lib/features/core/data/extensions/value_notifier_extensions.dart b/lib/features/core/data/extensions/value_notifier_extensions.dart deleted file mode 100644 index 8d4b278..0000000 --- a/lib/features/core/data/extensions/value_notifier_extensions.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:flutter/cupertino.dart'; - -extension ValueNotifierBoolExtensions on ValueNotifier { - void flipValue() => value = !value; -} diff --git a/lib/features/core/services/local_storage_service.dart b/lib/features/core/services/local_storage_service.dart deleted file mode 100644 index 0b4898c..0000000 --- a/lib/features/core/services/local_storage_service.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:hive/hive.dart'; -import 'package:mc_gallery/features/core/services/logging_service.dart'; -import 'package:mc_gallery/locator.dart'; - -class LocalStorageService { - LocalStorageService() { - _init(); - } - - final LoggingService _loggingService = LoggingService.locate; - - static const String _userBoxKey = 'userBoxKey'; - - late final Box _userBox; - - Future _init() async { - _userBox = await Hive.openBox(_userBoxKey); - - Locator.instance().signalReady(this); - } - - Iterable get storedFavouritesStates => _userBox.values; - - void initNewFavourites({required Iterable newValues}) { - _userBox.addAll(newValues); - _loggingService.info('Adding new favourites value'); - } - - void updateFavourite({ - required index, - required bool newValue, - }) { - try { - _userBox.putAt(index, newValue); - _loggingService.good('Successfully updated favourite status at $index -> $newValue'); - } on Exception catch (ex, stackTrace) { - _loggingService.handleException(ex, stackTrace); - } - } - - void resetFavourites() { - _userBox.clear(); - _loggingService.info('Cleared favourites table'); - } - - static LocalStorageService get locate => Locator.locate(); -} diff --git a/lib/features/core/services/logging_service.dart b/lib/features/core/services/logging_service.dart index 8a968b1..6144583 100644 --- a/lib/features/core/services/logging_service.dart +++ b/lib/features/core/services/logging_service.dart @@ -25,8 +25,7 @@ class LoggingService { void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get fine => _talker.fine; void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get good => _talker.good; - void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get info => - _talker.verbose; + void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get info => _talker.info; void Function(dynamic msg, [Object exception, StackTrace stackTrace]) get warning => _talker.warning; diff --git a/lib/features/home/abstracts/images_api.dart b/lib/features/home/abstracts/images_api.dart index ecc1e1c..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/dtos/image_model_dto.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 283fcab..9afe018 100644 --- a/lib/features/home/api/unsplash_images_api.dart +++ b/lib/features/home/api/unsplash_images_api.dart @@ -14,12 +14,11 @@ 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)); - final Iterable> fetchedImageModelDtos; try { // Create fixed number of images final dummyImageModels = @@ -30,7 +29,7 @@ class UnsplashImagesApi implements ImagesApi { final imageUri = _imageUrlGenerator(imageSide: imageSide); - return ImageModelDTO( + return ImageModelDto( imageIndex: imageIndex, uri: imageUri, // Custom dummy name for the image @@ -40,19 +39,15 @@ class UnsplashImagesApi implements ImagesApi { }); // Emulating serialization - fetchedImageModelDtos = dummyImageModels.map((final dummyModel) => dummyModel.toJson()); + return dummyImageModels.map((final dummyModel) => dummyModel.toJson()); } on Exception catch (ex, stackTrace) { _loggingService.handleException(ex, stackTrace); return const Iterable.empty(); } - - // Emulating deserialization - return fetchedImageModelDtos - .map((final emulatedModelSerialized) => ImageModelDTO.fromJson(emulatedModelSerialized)); } @override - FutureOr> searchImages({ + FutureOr>> searchImages({ required String searchStr, required String token, }) async { @@ -62,7 +57,6 @@ class UnsplashImagesApi implements ImagesApi { await Future.delayed( Duration(milliseconds: ConstValues.defaultEmulatedLatencyMillis * numberOfResults)); - final Iterable> searchImageModelDtos; try { // Create (randomly-bounded) dummy number of images final dummyImageModels = Iterable.generate(numberOfResults).map((final imageIndex) { @@ -72,7 +66,7 @@ class UnsplashImagesApi implements ImagesApi { final imageUri = _imageUrlGenerator(imageSide: imageSide); - return ImageModelDTO( + return ImageModelDto( imageIndex: imageIndex, uri: imageUri, // Custom dummy name for the image @@ -81,14 +75,11 @@ class UnsplashImagesApi implements ImagesApi { }); // Emulating serialization - searchImageModelDtos = dummyImageModels.map((final dummyModel) => dummyModel.toJson()); + return dummyImageModels.map((final dummyModel) => dummyModel.toJson()); } on Exception catch (ex, stackTrace) { _loggingService.handleException(ex, stackTrace); return List.empty(); } - - return searchImageModelDtos - .map((final emulatedModelSerialized) => ImageModelDTO.fromJson(emulatedModelSerialized)); } Uri _imageUrlGenerator({required int imageSide}) => Uri( diff --git a/lib/features/home/data/dtos/image_model_dto.dart b/lib/features/home/data/dtos/image_model_dto.dart index be81dab..d6f7d77 100644 --- a/lib/features/home/data/dtos/image_model_dto.dart +++ b/lib/features/home/data/dtos/image_model_dto.dart @@ -3,8 +3,8 @@ import 'package:json_annotation/json_annotation.dart'; part 'image_model_dto.g.dart'; @JsonSerializable() -class ImageModelDTO { - const ImageModelDTO({ +class ImageModelDto { + const ImageModelDto({ required this.uri, required this.imageIndex, required this.imageName, @@ -21,6 +21,8 @@ class ImageModelDTO { /// Given name of the image. final String imageName; - factory ImageModelDTO.fromJson(Map json) => _$ImageModelDTOFromJson(json); - Map toJson() => _$ImageModelDTOToJson(this); + 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/dtos/image_model_dto.g.dart b/lib/features/home/data/dtos/image_model_dto.g.dart index da8769d..ccaa9e1 100644 --- a/lib/features/home/data/dtos/image_model_dto.g.dart +++ b/lib/features/home/data/dtos/image_model_dto.g.dart @@ -6,14 +6,14 @@ part of 'image_model_dto.dart'; // JsonSerializableGenerator // ************************************************************************** -ImageModelDTO _$ImageModelDTOFromJson(Map json) => - ImageModelDTO( +ImageModelDto _$ImageModelDtoFromJson(Map json) => + ImageModelDto( uri: Uri.parse(json['uri'] as String), imageIndex: json['imageIndex'] as int, imageName: json['imageName'] as String, ); -Map _$ImageModelDTOToJson(ImageModelDTO 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 60ddb65..b70b381 100644 --- a/lib/features/home/data/models/image_model.dart +++ b/lib/features/home/data/models/image_model.dart @@ -1,7 +1,8 @@ -import '../dtos/image_model_dto.dart'; +import 'package:json_annotation/json_annotation.dart'; -class ImageModel { - const ImageModel({ +@JsonSerializable() +class ImageModelDto { + const ImageModelDto({ required this.uri, required this.imageIndex, required this.imageName, @@ -21,29 +22,4 @@ class ImageModel { /// Whether the image was 'Starred' ot not. final bool isFavourite; - - factory ImageModel.fromDto({ - required ImageModelDTO imageModelDto, - required bool isFavourite, - }) => - ImageModel( - uri: imageModelDto.uri, - imageIndex: imageModelDto.imageIndex, - imageName: imageModelDto.imageName, - isFavourite: isFavourite, - ); - - ImageModel copyWith({ - Uri? uri, - int? imageIndex, - String? imageName, - bool? isFavourite, - }) { - return ImageModel( - uri: uri ?? this.uri, - imageIndex: imageIndex ?? this.imageIndex, - imageName: imageName ?? this.imageName, - isFavourite: isFavourite ?? this.isFavourite, - ); - } } diff --git a/lib/features/home/services/image_cache_manager_service.dart b/lib/features/home/services/image_cache_manager_service.dart index fcba448..c0282e4 100644 --- a/lib/features/home/services/image_cache_manager_service.dart +++ b/lib/features/home/services/image_cache_manager_service.dart @@ -2,24 +2,19 @@ import 'dart:ui'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:mc_gallery/features/core/services/app_lifecycle_service.dart'; -import 'package:mc_gallery/features/core/services/local_storage_service.dart'; import 'package:mc_gallery/features/core/services/logging_service.dart'; import 'package:mc_gallery/locator.dart'; class ImageCacheManagerService with LoggingService { - ImageCacheManagerService( - {required AppLifecycleService appLifecycleService, - required LocalStorageService localStorageService}) - : _appLifecycleService = appLifecycleService, - _localStorageService = localStorageService { + ImageCacheManagerService({ + required AppLifecycleService appLifecycleService, + }) : _appLifecycleService = appLifecycleService { _init(); } final AppLifecycleService _appLifecycleService; - final LocalStorageService _localStorageService; - final _cacheManager = DefaultCacheManager(); - Future emptyCache() async => await _cacheManager.emptyCache(); + Future emptyCache() async => await DefaultCacheManager().emptyCache(); Future _init() async { _appLifecycleService.addListener( @@ -32,8 +27,7 @@ class ImageCacheManagerService with LoggingService { case AppLifecycleState.paused: case AppLifecycleState.detached: info('Discarding cached images'); - await _cacheManager.emptyCache(); - _localStorageService.resetFavourites(); + await DefaultCacheManager().emptyCache(); } }, ); diff --git a/lib/features/home/services/images_service.dart b/lib/features/home/services/images_service.dart index 212a2ee..e909608 100644 --- a/lib/features/home/services/images_service.dart +++ b/lib/features/home/services/images_service.dart @@ -1,20 +1,15 @@ import 'dart:async'; -import 'dart:collection'; - -import 'package:collection/collection.dart'; -import 'package:mc_gallery/features/home/data/dtos/image_model_dto.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/local_storage_service.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. /// @@ -23,20 +18,17 @@ import '../data/models/image_model.dart'; class ImagesService { ImagesService({ required ImagesApi imagesApi, - required LocalStorageService localStorageService, required LoggingService loggingService, }) : _imagesApi = imagesApi, - _localStorageService = localStorageService, _loggingService = loggingService { _init(); } final ImagesApi _imagesApi; - final LocalStorageService _localStorageService; final LoggingService _loggingService; - late final LinkedHashMap _imageModels; - Iterable get imageModels => _imageModels.values.deepCopy; + late final Map _imageModels; + Iterable get imageModels => _imageModels.values.deepCopy; final Mutex _searchMutex = Mutex(); @@ -48,37 +40,11 @@ class ImagesService { Future _init() async { _loggingService.info('Fetching and creating image models...'); - - final fetchedImageModelDtos = await _imagesApi.fetchImageUri(token: ''); - final favouritesStatuses = _localStorageService.storedFavouritesStates; - - // Prefill from stored values - if (favouritesStatuses.isNotEmpty) { - _loggingService.good('Found favourites statuses on device -> Prefilling'); - assert(fetchedImageModelDtos.length == favouritesStatuses.length); - _imageModels = LinkedHashMap.of({ - for (final pair in IterableZip([fetchedImageModelDtos, favouritesStatuses])) - (pair[0] as ImageModelDTO).imageName: ImageModel.fromDto( - imageModelDto: pair[0] as ImageModelDTO, - isFavourite: pair[1] as bool, - ) - }); - - // Set to false and create the stored values - } else { - _loggingService.good('NO favourites statuses found -> creating new'); - _imageModels = LinkedHashMap.of({ - for (final fetchedImageModelDto in fetchedImageModelDtos) - fetchedImageModelDto.imageName: ImageModel.fromDto( - imageModelDto: fetchedImageModelDto, - isFavourite: false, - ) - }); - - _localStorageService.initNewFavourites( - newValues: _imageModels.values.map((final imageModel) => imageModel.isFavourite), - ); - } + _imageModels = { + for (final imageModel in (await _imagesApi.fetchImageUri(token: '')) + .map((final emulatedModelSerialized) => ImageModelDto.fromJson(emulatedModelSerialized))) + imageModel.imageName: imageModel + }; _imageModels.isNotEmpty ? _loggingService.good("Created ${_imageModels.length} images' models") @@ -91,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; @@ -103,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, @@ -131,10 +97,8 @@ class ImagesService { token: '', )) .map( - (final imageModelDto) => ImageModel.fromDto( - imageModelDto: imageModelDto, - isFavourite: false, - ), + (final emulatedModelSerialized) => + ImageModelDto.fromJson(emulatedModelSerialized), ) .toList(growable: false); } @@ -144,19 +108,5 @@ class ImagesService { }); } - void updateImageFavouriteStatus({ - required ImageModel imageModel, - required bool newFavouriteStatus, - }) { - _imageModels.updateValueAt( - valueIndex: imageModel.imageIndex, - newValue: imageModel.copyWith(isFavourite: newFavouriteStatus), - ); - - //todo(mehul): Consider adding an update listener to _imageModels, sync with _localStorageService - _localStorageService.updateFavourite( - index: imageModel.imageIndex, newValue: newFavouriteStatus); - } - static ImagesService get locate => Locator.locate(); } diff --git a/lib/features/home/views/gallery/downloaded_gallery_view.dart b/lib/features/home/views/gallery/downloaded_gallery_view.dart index 4c5df38..36b9746 100644 --- a/lib/features/home/views/gallery/downloaded_gallery_view.dart +++ b/lib/features/home/views/gallery/downloaded_gallery_view.dart @@ -15,96 +15,30 @@ class _DownloadedGalleryView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), // Using Wrap instead of GridView, to make use of different image sizes - child: ValueListenableBuilder( - valueListenable: galleryViewModel.isViewingFavouriteListenable, - builder: (context, final isViewingFavourites, _) => !isViewingFavourites - ? CustomWrap(children: [ - for (final imageModel in galleryViewModel.imageModels) - _StarrableImage( - key: ValueKey(imageModel.imageIndex), - imageModel: imageModel, - galleryViewModel: galleryViewModel, - ), - ]) - : CustomWrap( - children: [ - for (final favouriteImageModel in galleryViewModel.favouriteImageModels) - _StarrableImage( - key: ValueKey(favouriteImageModel.imageIndex), - imageModel: favouriteImageModel, - galleryViewModel: galleryViewModel, - ), - ], + child: Wrap( + runSpacing: 24, + spacing: 8, + alignment: WrapAlignment.center, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + for (final imageModel in galleryViewModel.imageModels) + GestureDetector( + onTap: () => galleryViewModel.pushImageCarouselView( + context, + imageModel: imageModel, ), + child: CachedNetworkImage( + imageUrl: imageModel.uri.toString(), + cacheKey: imageModel.imageIndex.toString(), + progressIndicatorBuilder: (_, __, final progress) => CircularProgressIndicator( + value: galleryViewModel.downloadProgressValue(progress: progress), + ), + ), + ), + ], ), ), ); } } - -class _StarrableImage extends StatefulWidget { - const _StarrableImage({ - required this.galleryViewModel, - required this.imageModel, - super.key, - }); - - final GalleryViewModel galleryViewModel; - final ImageModel imageModel; - - @override - State<_StarrableImage> createState() => _StarrableImageState(); -} - -class _StarrableImageState extends State<_StarrableImage> { - late bool isMarkedFavourite; - - @override - void initState() { - super.initState(); - - isMarkedFavourite = widget.imageModel.isFavourite; - } - - @override - Widget build(BuildContext context) { - return Stack( - alignment: Alignment.topRight.add(const Alignment(0.2, -0.2)), - children: [ - GestureDetector( - onTap: () => widget.galleryViewModel.pushImageCarouselView( - context, - imageModel: widget.imageModel, - ), - child: CachedNetworkImage( - imageUrl: widget.imageModel.uri.toString(), - cacheKey: widget.imageModel.imageIndex.toString(), - progressIndicatorBuilder: (_, __, final progress) => CircularProgressIndicator( - value: widget.galleryViewModel.downloadProgressValue(progress: progress), - ), - ), - ), - GestureDetector( - child: isMarkedFavourite - ? ConstMedia.buildIcon( - ConstMedia.favStarFilled, - width: 16, - height: 16, - ) - : ConstMedia.buildIcon( - ConstMedia.favStarOutline, - width: 16, - height: 16, - ), - onTap: () { - widget.galleryViewModel.updateImageFavouriteStatus( - imageModel: widget.imageModel, - newFavouriteStatus: !isMarkedFavourite, - ); - setState(() => isMarkedFavourite = !isMarkedFavourite); - }, - ), - ], - ); - } -} diff --git a/lib/features/home/views/gallery/gallery_view.dart b/lib/features/home/views/gallery/gallery_view.dart index e732622..ef720af 100644 --- a/lib/features/home/views/gallery/gallery_view.dart +++ b/lib/features/home/views/gallery/gallery_view.dart @@ -1,7 +1,5 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:mc_gallery/features/core/data/constants/const_media.dart'; -import 'package:mc_gallery/features/home/widgets/custom_wrap.dart'; import '/features/core/data/constants/const_colors.dart'; import '/features/core/data/constants/const_durations.dart'; @@ -9,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'; @@ -83,21 +81,7 @@ class GalleryView extends StatelessWidget { builder: (context, final isSearching, _) => AnimatedSwitcher( duration: ConstDurations.oneAndHalfDefaultAnimationDuration, child: !isSearching - ? Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ValueListenableBuilder( - valueListenable: model.isViewingFavouriteListenable, - builder: - (context, final isViewingFavourites, child) => - Switch( - value: isViewingFavourites, - onChanged: model.onFavouriteViewChange, - ), - ), - _DownloadedGalleryView(galleryViewModel: model), - ], - ) + ? _DownloadedGalleryView(galleryViewModel: model) : _SearchGalleryView(galleryViewModel: model), ), ); diff --git a/lib/features/home/views/gallery/gallery_view_model.dart b/lib/features/home/views/gallery/gallery_view_model.dart index f10b515..1b37bc3 100644 --- a/lib/features/home/views/gallery/gallery_view_model.dart +++ b/lib/features/home/views/gallery/gallery_view_model.dart @@ -3,14 +3,13 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; -import 'package:mc_gallery/features/core/data/extensions/value_notifier_extensions.dart'; 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'; @@ -28,7 +27,6 @@ class GalleryViewModel extends BaseViewModel { final ImagesService _imagesService; final NavigationService _navigationService; - //todo(mehul): Use to implement pull-to-refresh or an extra widget final ImageCacheManagerService _imageCacheManagerService; final LoggingService _loggingService; @@ -39,11 +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 _isViewingFavouriteNotifier = ValueNotifier(false); - ValueListenable get isViewingFavouriteListenable => _isViewingFavouriteNotifier; + final ValueNotifier> _imageSearchResultsNotifier = ValueNotifier([]); + ValueListenable> get imageSearchResultsListenable => + _imageSearchResultsNotifier; @override Future initialise(bool Function() mounted, [arguments]) async { @@ -85,7 +81,7 @@ class GalleryViewModel extends BaseViewModel { _loggingService.info('Clearing of results on view mode change'); } - _isSearchingNotifier.flipValue(); + _isSearchingNotifier.value = !_isSearchingNotifier.value; } Future get lastQueryResultDone => _imagesService.lastQueryIsCompleted; @@ -97,33 +93,18 @@ class GalleryViewModel extends BaseViewModel { _imageSearchResultsNotifier.value = []; _loggingService.info('Cleared resultsw from view'); - //todo(mehul): Either redo search or force user to type in new (trigger) by clearing field + //todo(mehul): Either redo search or force user to type in by clearing field } - void onFavouriteViewChange(bool newValue) => _isViewingFavouriteNotifier.value = newValue; - - void updateImageFavouriteStatus({ - required ImageModel imageModel, - required bool newFavouriteStatus, - }) { - _imagesService.updateImageFavouriteStatus( - imageModel: imageModel, - newFavouriteStatus: newFavouriteStatus, - ); - } - - Iterable get favouriteImageModels => - imageModels.where((final imageModel) => imageModel.isFavourite); - 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 87b8df7..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, @@ -29,7 +29,12 @@ class _SearchGalleryView extends StatelessWidget { builder: (context, final searchOption, child) { switch (searchOption) { case SearchOption.local: - return CustomWrap( + return Wrap( + runSpacing: 24, + spacing: 8, + alignment: WrapAlignment.center, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, children: [ for (final resultsImageModel in resultsImageModels) CachedNetworkImage( @@ -43,7 +48,12 @@ class _SearchGalleryView extends StatelessWidget { ], ); case SearchOption.web: - return CustomWrap( + return Wrap( + runSpacing: 24, + spacing: 8, + alignment: WrapAlignment.center, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, children: [ for (final imageResult in resultsImageModels) Image.network( 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 c8f5259..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,6 +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/dtos/image_model_dto.dart'; import '/features/core/data/constants/const_colors.dart'; import '/features/core/data/constants/const_text.dart'; @@ -10,7 +11,6 @@ import '/features/core/widgets/gap.dart'; import '/features/core/widgets/mcg_scaffold.dart'; import '/features/home/views/image_carousel/image_carousel_view_model.dart'; import '../../../core/widgets/state/view_model_builder.dart'; -import '../../data/models/image_model.dart'; class ImageCarouselViewArguments { const ImageCarouselViewArguments({required this.imageIndexKey}); @@ -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 695e2df..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 @@ -7,7 +7,7 @@ import '/features/core/services/navigation_service.dart'; import '/features/home/services/images_service.dart'; import '/features/home/views/image_carousel/image_carousel_view.dart'; import '/locator.dart'; -import '../../data/models/image_model.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 { diff --git a/lib/features/home/widgets/custom_wrap.dart b/lib/features/home/widgets/custom_wrap.dart deleted file mode 100644 index c843143..0000000 --- a/lib/features/home/widgets/custom_wrap.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/widgets.dart'; - -class CustomWrap extends StatelessWidget { - const CustomWrap({ - required this.children, - super.key, - }); - - final List children; - - @override - Widget build(BuildContext context) { - return Wrap( - runSpacing: 24, - spacing: 8, - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - children: children, - ); - } -} diff --git a/lib/locator.dart b/lib/locator.dart index 1f10137..ecc5b57 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -7,7 +7,6 @@ import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'features/core/abstracts/router/app_router.dart'; import 'features/core/services/app_lifecycle_service.dart'; import 'features/core/services/connections_service.dart'; -import 'features/core/services/local_storage_service.dart'; import 'features/core/services/logging_service.dart'; import 'features/core/services/navigation_service.dart'; import 'features/core/services/overlay_service.dart'; @@ -95,24 +94,14 @@ class Locator { dispose: (final param) async => await param.dispose(), ); - it.registerSingleton( - LocalStorageService(), - signalsReady: true, - ); - await it.isReady(); - it.registerSingleton( - ImagesService( - imagesApi: UnsplashImagesApi.locate, - localStorageService: LocalStorageService.locate, - loggingService: LoggingService.locate, - ), + ImagesService(imagesApi: UnsplashImagesApi.locate, loggingService: LoggingService.locate), ); + //await it.isReady(); it.registerSingleton( ImageCacheManagerService( appLifecycleService: AppLifecycleService.locate, - localStorageService: LocalStorageService.locate, ), ); } diff --git a/pubspec.lock b/pubspec.lock index 41b1854..5a3a54c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -345,27 +345,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.0" - hive: - dependency: "direct main" - description: - name: hive - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.3" - hive_flutter: - dependency: "direct main" - description: - name: hive_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - hive_generator: - dependency: "direct dev" - description: - name: hive_generator - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" http: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 123390b..5bc5132 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,10 +25,6 @@ dependencies: cached_network_image: ^3.2.3 flutter_cache_manager: ^3.3.0 - # Storage - hive: ^2.2.3 - hive_flutter: ^1.1.0 - # Util backend intl_utils: ^2.8.1 connectivity_plus: ^3.0.2 @@ -56,11 +52,8 @@ dev_dependencies: # Builders build_runner: ^2.3.3 - json_serializable: ^6.5.4 - hive_generator: ^2.0.0 - - # Annotations json_annotation: ^4.7.0 + json_serializable: ^6.5.4 flutter: uses-material-design: true