diff --git a/lib/features/core/data/constants/const_colors.dart b/lib/features/core/data/constants/const_colors.dart index 47465c2..4f69947 100644 --- a/lib/features/core/data/constants/const_colors.dart +++ b/lib/features/core/data/constants/const_colors.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; abstract class ConstColours { /// Smoke Gray => a neutral grey with neutral warmth/cold static const galleryBackgroundColour = Color.fromRGBO(127, 127, 125, 1.0); + static const red = Colors.red; static const white = Colors.white; static const black = Colors.black; diff --git a/lib/features/core/data/constants/const_values.dart b/lib/features/core/data/constants/const_values.dart index a468f1b..c8a9a7d 100644 --- a/lib/features/core/data/constants/const_values.dart +++ b/lib/features/core/data/constants/const_values.dart @@ -3,7 +3,7 @@ abstract class ConstValues { static const String backendHost = 'source.unsplash.com'; static const List backendUrlPathSegments = ['user', 'c_v_r']; - static const int numberOfImages = 20; + static const int numberOfImages = 25; static const int minImageSize = 50; static const int maxImageSize = 100; diff --git a/lib/features/home/abstracts/images_api.dart b/lib/features/home/abstracts/images_api.dart index ecc1e1c..5138887 100644 --- a/lib/features/home/abstracts/images_api.dart +++ b/lib/features/home/abstracts/images_api.dart @@ -7,6 +7,7 @@ import '../data/dtos/image_model_dto.dart'; /// 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 { + /// Returns images fetched through an API as [ImageModelDTO]s. FutureOr> fetchImageUri({required String token}); FutureOr> searchImages({ diff --git a/lib/features/home/data/enums/search_option.dart b/lib/features/home/data/enums/search_option.dart index f95cdf9..96654d5 100644 --- a/lib/features/home/data/enums/search_option.dart +++ b/lib/features/home/data/enums/search_option.dart @@ -1,5 +1,6 @@ import '/l10n/generated/l10n.dart'; +/// Represents an option for specifying a search strategy, for an [ImageModel] enum SearchOption { local, web; diff --git a/lib/features/home/data/models/image_model.dart b/lib/features/home/data/models/image_model.dart index 60ddb65..0b04c17 100644 --- a/lib/features/home/data/models/image_model.dart +++ b/lib/features/home/data/models/image_model.dart @@ -1,5 +1,6 @@ import '../dtos/image_model_dto.dart'; +/// Represents an Image, that would be displayed in the gallery. class ImageModel { const ImageModel({ required this.uri, diff --git a/lib/features/home/services/images_service.dart b/lib/features/home/services/images_service.dart index 212a2ee..dd87e38 100644 --- a/lib/features/home/services/images_service.dart +++ b/lib/features/home/services/images_service.dart @@ -2,7 +2,6 @@ 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'; @@ -11,6 +10,7 @@ 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 '/features/home/data/dtos/image_model_dto.dart'; import '/locator.dart'; import '../abstracts/images_api.dart'; import '../data/enums/search_option.dart'; diff --git a/lib/features/home/views/gallery/downloaded_gallery_view.dart b/lib/features/home/views/gallery/downloaded_gallery_view.dart index 4247391..d4f438c 100644 --- a/lib/features/home/views/gallery/downloaded_gallery_view.dart +++ b/lib/features/home/views/gallery/downloaded_gallery_view.dart @@ -18,12 +18,7 @@ class _DownloadedGalleryView extends StatelessWidget { child: ValueListenableBuilder( valueListenable: galleryViewModel.isViewingFavouriteListenable, builder: (context, final isViewingFavourites, _) => !isViewingFavourites - ? Wrap( - runSpacing: 24, - spacing: 8, - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, + ? CustomWrap( children: [ for (final imageModel in galleryViewModel.imageModels) _StarrableImage( @@ -33,12 +28,7 @@ class _DownloadedGalleryView extends StatelessWidget { ), ], ) - : Wrap( - runSpacing: 24, - spacing: 8, - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, + : CustomWrap( children: [ for (final favouriteImageModel in galleryViewModel.favouriteImageModels) _StarrableImage( diff --git a/lib/features/home/views/gallery/gallery_view.dart b/lib/features/home/views/gallery/gallery_view.dart index 211fba5..8faba98 100644 --- a/lib/features/home/views/gallery/gallery_view.dart +++ b/lib/features/home/views/gallery/gallery_view.dart @@ -1,13 +1,14 @@ 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 '/features/core/data/constants/const_colors.dart'; import '/features/core/data/constants/const_durations.dart'; +import '/features/core/data/constants/const_media.dart'; 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 '/features/home/widgets/custom_wrap.dart'; import '../../data/enums/search_option.dart'; import '../../data/models/image_model.dart'; import 'gallery_view_model.dart'; @@ -81,21 +82,38 @@ class GalleryView extends StatelessWidget { valueListenable: model.isSearchingListenable, builder: (context, final isSearching, _) => AnimatedSwitcher( duration: ConstDurations.oneAndHalfDefaultAnimationDuration, - child: Column( - children: [ - ValueListenableBuilder( - valueListenable: model.isViewingFavouriteListenable, - builder: (context, final isViewingFavourites, child) => - Switch( - value: isViewingFavourites, - onChanged: model.onFavouriteViewChange, - ), - ), - !isSearching - ? _DownloadedGalleryView(galleryViewModel: model) - : _SearchGalleryView(galleryViewModel: model), - ], - ), + child: !isSearching + ? Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ValueListenableBuilder( + valueListenable: model.isViewingFavouriteListenable, + builder: + (context, final isViewingFavourites, child) => + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConstMedia.buildIcon( + ConstMedia.favStarOutline, + width: 24, + height: 24, + ), + Switch( + value: isViewingFavourites, + onChanged: model.onFavouriteViewChange, + ), + ConstMedia.buildIcon( + ConstMedia.favStarFilled, + width: 24, + height: 24, + ), + ], + ), + ), + _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..dac601b 100644 --- a/lib/features/home/views/gallery/gallery_view_model.dart +++ b/lib/features/home/views/gallery/gallery_view_model.dart @@ -3,9 +3,9 @@ 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/data/extensions/value_notifier_extensions.dart'; import '/features/core/services/logging_service.dart'; import '/features/core/services/navigation_service.dart'; import '/locator.dart'; diff --git a/lib/features/home/views/gallery/search_gallery_view.dart b/lib/features/home/views/gallery/search_gallery_view.dart index 7ec68b3..87b8df7 100644 --- a/lib/features/home/views/gallery/search_gallery_view.dart +++ b/lib/features/home/views/gallery/search_gallery_view.dart @@ -29,12 +29,7 @@ class _SearchGalleryView extends StatelessWidget { builder: (context, final searchOption, child) { switch (searchOption) { case SearchOption.local: - return Wrap( - runSpacing: 24, - spacing: 8, - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, + return CustomWrap( children: [ for (final resultsImageModel in resultsImageModels) CachedNetworkImage( @@ -48,12 +43,7 @@ class _SearchGalleryView extends StatelessWidget { ], ); case SearchOption.web: - return Wrap( - runSpacing: 24, - spacing: 8, - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, + return CustomWrap( children: [ for (final imageResult in resultsImageModels) Image.network( 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..2a9eb70 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 @@ -3,7 +3,6 @@ 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/services/images_service.dart'; import '/features/home/views/image_carousel/image_carousel_view.dart'; import '/locator.dart'; @@ -12,14 +11,11 @@ import '../../data/models/image_model.dart'; class ImageCarouselViewModel extends BaseViewModel { ImageCarouselViewModel({ required ImagesService imagesService, - required NavigationService navigationService, required LoggingService loggingService, }) : _imagesService = imagesService, - _navigationService = navigationService, _loggingService = loggingService; final ImagesService _imagesService; - final NavigationService _navigationService; final LoggingService _loggingService; late final ValueNotifier _currentImageModelNotifier; diff --git a/lib/features/home/widgets/custom_wrap.dart b/lib/features/home/widgets/custom_wrap.dart new file mode 100644 index 0000000..1e0b3de --- /dev/null +++ b/lib/features/home/widgets/custom_wrap.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +import '/features/core/data/constants/const_colors.dart'; + +class CustomWrap extends StatelessWidget { + const CustomWrap({ + required this.children, + super.key, + }); + + final List children; + + @override + Widget build(BuildContext context) { + return children.isNotEmpty + ? Wrap( + runSpacing: 24, + spacing: 8, + alignment: WrapAlignment.center, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: children, + ) + : const Icon( + Icons.not_interested_sharp, + size: 80, + color: ConstColours.red, + ); + } +} diff --git a/lib/locator.dart b/lib/locator.dart index 1f10137..64d2c70 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -53,7 +53,6 @@ class Locator { instance().registerFactory( () => ImageCarouselViewModel( imagesService: ImagesService.locate, - navigationService: NavigationService.locate, loggingService: LoggingService.locate, ), );