Favourites

This commit is contained in:
Mguy13 2022-12-25 01:55:53 +01:00
parent 6a84a9bef0
commit 1747ab0245
23 changed files with 469 additions and 67 deletions

View file

@ -15,30 +15,108 @@ class _DownloadedGalleryView extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.all(8),
// Using Wrap instead of GridView, to make use of different image sizes
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: ValueListenableBuilder<bool>(
valueListenable: galleryViewModel.isViewingFavouriteListenable,
builder: (context, final isViewingFavourites, _) => !isViewingFavourites
? Wrap(
runSpacing: 24,
spacing: 8,
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
for (final imageModel in galleryViewModel.imageModels)
_StarrableImage(
key: ValueKey(imageModel.imageIndex),
imageModel: imageModel,
galleryViewModel: galleryViewModel,
),
],
)
: Wrap(
runSpacing: 24,
spacing: 8,
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
for (final favouriteImageModel in galleryViewModel.favouriteImageModels)
_StarrableImage(
key: ValueKey(favouriteImageModel.imageIndex),
imageModel: favouriteImageModel,
galleryViewModel: galleryViewModel,
),
],
),
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);
},
),
],
);
}
}

View file

@ -1,5 +1,6 @@
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';
@ -80,9 +81,21 @@ class GalleryView extends StatelessWidget {
valueListenable: model.isSearchingListenable,
builder: (context, final isSearching, _) => AnimatedSwitcher(
duration: ConstDurations.oneAndHalfDefaultAnimationDuration,
child: !isSearching
? _DownloadedGalleryView(galleryViewModel: model)
: _SearchGalleryView(galleryViewModel: model),
child: Column(
children: [
ValueListenableBuilder<bool>(
valueListenable: model.isViewingFavouriteListenable,
builder: (context, final isViewingFavourites, child) =>
Switch(
value: isViewingFavourites,
onChanged: model.onFavouriteViewChange,
),
),
!isSearching
? _DownloadedGalleryView(galleryViewModel: model)
: _SearchGalleryView(galleryViewModel: model),
],
),
),
);
}

View file

@ -3,6 +3,7 @@ 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';
@ -27,6 +28,7 @@ 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;
@ -40,6 +42,9 @@ class GalleryViewModel extends BaseViewModel {
final ValueNotifier<List<ImageModel>> _imageSearchResultsNotifier = ValueNotifier([]);
ValueListenable<List<ImageModel>> get imageSearchResultsListenable => _imageSearchResultsNotifier;
final ValueNotifier<bool> _isViewingFavouriteNotifier = ValueNotifier(false);
ValueListenable<bool> get isViewingFavouriteListenable => _isViewingFavouriteNotifier;
@override
Future<void> initialise(bool Function() mounted, [arguments]) async {
super.initialise(mounted, arguments);
@ -80,7 +85,7 @@ class GalleryViewModel extends BaseViewModel {
_loggingService.info('Clearing of results on view mode change');
}
_isSearchingNotifier.value = !_isSearchingNotifier.value;
_isSearchingNotifier.flipValue();
}
Future<void> get lastQueryResultDone => _imagesService.lastQueryIsCompleted;
@ -92,9 +97,24 @@ class GalleryViewModel extends BaseViewModel {
_imageSearchResultsNotifier.value = [];
_loggingService.info('Cleared resultsw from view');
//todo(mehul): Either redo search or force user to type in by clearing field
//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<ImageModel> get favouriteImageModels =>
imageModels.where((final imageModel) => imageModel.isFavourite);
void onPromptPressed() => _isDisplayingPressingPrompt.value = false;
Iterable<ImageModel> get imageModels => _imagesService.imageModels;