import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import '/features/core/abstracts/base_view_model.dart'; import '/features/core/data/extensions/value_notifier_extensions.dart'; import '/features/core/services/navigation_service.dart'; import '/locator.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'; class GalleryViewModel extends BaseViewModel { GalleryViewModel({ required ImagesService imagesService, required NavigationService navigationService, required ImageCacheManagerService imageCacheManagerService, }) : _imagesService = imagesService, _navigationService = navigationService, _imageCacheManagerService = imageCacheManagerService; final ImagesService _imagesService; final NavigationService _navigationService; //todo(mehul): Use to implement pull-to-refresh or an extra widget final ImageCacheManagerService _imageCacheManagerService; final ValueNotifier _isDisplayingPressingPrompt = ValueNotifier(true); ValueListenable get isDisplayingPressingPrompt => _isDisplayingPressingPrompt; final ValueNotifier _isSearchingNotifier = ValueNotifier(false); 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; @override Future initialise(bool Function() mounted, [Object? arguments]) async { super.initialise(mounted, arguments); } @override Future dispose() async { super.dispose(); } Future onSearchTermUpdate(String searchTerm) async { // If empty-string (from backspacing) -> reset state. if (searchTerm.isEmpty) { _imageSearchResultsNotifier.value = []; log.info('Clearing results on search string removal'); return; } // Detached call to prevent UI blocking unawaited( _imagesService .searchImages( imageNamePart: searchTerm, searchOption: searchOptionListenable.value, ) .then( (final fetchedImageModels) => _imageSearchResultsNotifier.value = fetchedImageModels), ); // Force-update to trigger listening to `lastQueryResultDone()`. _imageSearchResultsNotifier.notifyListeners(); } void searchPressed() { // If transitioning from 'Searching', clear previous results immediately if (_isSearchingNotifier.value) { _imageSearchResultsNotifier.value = []; log.info('Clearing of results on view mode change'); } _isSearchingNotifier.flipValue(); } Future get lastQueryResultDone => _imagesService.lastQueryIsCompleted; void onSearchOptionChanged(SearchOption? option) { _searchOptionNotifier.value = option!; log.info('Switched over to $option search'); _imageSearchResultsNotifier.value = []; log.info('Cleared resultsw from view'); //todo(mehul): Either redo search or force user to type in new (trigger) 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; 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}) => _navigationService.pushImageCarouselView( context, imageCarouselViewArguments: ImageCarouselViewArguments( imageIndexKey: imageModel.imageIndex, ), ); static GalleryViewModel get locate => Locator.locate(); }