diff --git a/lib/core/router/routers_config.dart b/lib/core/router/routers_config.dart index fba9fab..7e4b1eb 100644 --- a/lib/core/router/routers_config.dart +++ b/lib/core/router/routers_config.dart @@ -2,11 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:recipes/core/router/routes.dart'; +import 'package:recipes/features/drinks/infrastructure/dto/drink_details/drink_details_model.dart'; import 'package:recipes/features/home/presentation/screens/home.dart'; import 'package:recipes/features/drinks/presentation/screens/drinks_by_category.dart'; import 'package:recipes/features/on_boarding/presentation/screens/on_boarding_screen.dart'; import '../../features/drinks/presentation/screens/drinkDetails.dart'; +import '../../features/favourites/presentation/screens/favourites.dart'; import 'custom_transition_page.dart'; final goRouterProvider = Provider((ref) => _router); @@ -55,8 +57,17 @@ final GoRouter _router = GoRouter( ), ], ), + GoRoute( + path: Routes.favorites.name, + name: Routes.favorites.name, + pageBuilder: (context, state) { + return buildCustomTransitionPage( + state, + const FavoritesScreen(), + ); + }, + ), ], ), ], ); - diff --git a/lib/core/router/routes.dart b/lib/core/router/routes.dart index 1990a3c..24d4f12 100644 --- a/lib/core/router/routes.dart +++ b/lib/core/router/routes.dart @@ -4,4 +4,5 @@ enum Routes { drinkDetails, onBoarding, drinksByCategory, + favorites, } diff --git a/lib/features/drinks/presentation/screens/drinks_by_category.dart b/lib/features/drinks/presentation/screens/drinks_by_category.dart index 31abef7..160a3db 100644 --- a/lib/features/drinks/presentation/screens/drinks_by_category.dart +++ b/lib/features/drinks/presentation/screens/drinks_by_category.dart @@ -3,7 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:recipes/features/drinks/presentation/riverpod/drink/drinks_provider.dart'; import 'package:recipes/features/drinks/presentation/riverpod/drink_details/selected_drink_provider.dart'; -import 'package:recipes/features/drinks/presentation/screens/drinkDetails.dart'; import 'package:recipes/features/drinks/presentation/widgets/drink_item.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import '../../../../configs/configs.dart'; @@ -58,13 +57,7 @@ class DrinksByCategoryScreen extends ConsumerWidget { .read(selectedDrinkProvider.notifier) .state = state.data![index].idDrink.toString(); -/* - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => - const DrinkDetailsScreen(), - ), - );*/ + context.goNamed( Routes.drinkDetails.name, ); diff --git a/lib/features/favourites/domain/repositories/favourites_repository.dart b/lib/features/favourites/domain/repositories/favourites_repository.dart new file mode 100644 index 0000000..f5f62d1 --- /dev/null +++ b/lib/features/favourites/domain/repositories/favourites_repository.dart @@ -0,0 +1,11 @@ +import '../../../drinks/infrastructure/dto/drink_details/drink_details_model.dart'; + +abstract class FavoriteDrinksRepository { + void addFavorite(DrinkDetails drink); + + void removeFavorite(String idDrink); + + List getFavorites(); + + bool isFavorite(String idDrink); +} diff --git a/lib/features/favourites/domain/use_cases/add_to_favourites.dart b/lib/features/favourites/domain/use_cases/add_to_favourites.dart new file mode 100644 index 0000000..d3f9e37 --- /dev/null +++ b/lib/features/favourites/domain/use_cases/add_to_favourites.dart @@ -0,0 +1,12 @@ +import '../../../drinks/infrastructure/dto/drink_details/drink_details_model.dart'; +import '../repositories/favourites_repository.dart'; + +class AddFavoriteUseCase { + final FavoriteDrinksRepository _repository; + + AddFavoriteUseCase(this._repository); + + void call(DrinkDetails drink) { + _repository.addFavorite(drink); + } +} \ No newline at end of file diff --git a/lib/features/favourites/domain/use_cases/get_favourites.dart b/lib/features/favourites/domain/use_cases/get_favourites.dart new file mode 100644 index 0000000..4b65d69 --- /dev/null +++ b/lib/features/favourites/domain/use_cases/get_favourites.dart @@ -0,0 +1,12 @@ +import '../../../drinks/infrastructure/dto/drink_details/drink_details_model.dart'; +import '../repositories/favourites_repository.dart'; + +class GetFavoritesUseCase { + final FavoriteDrinksRepository _repository; + + GetFavoritesUseCase(this._repository); + + List call() { + return _repository.getFavorites(); + } +} \ No newline at end of file diff --git a/lib/features/favourites/domain/use_cases/is_favourite.dart b/lib/features/favourites/domain/use_cases/is_favourite.dart new file mode 100644 index 0000000..d184650 --- /dev/null +++ b/lib/features/favourites/domain/use_cases/is_favourite.dart @@ -0,0 +1,11 @@ +import '../repositories/favourites_repository.dart'; + +class IsFavoriteUseCase { + final FavoriteDrinksRepository _repository; + + IsFavoriteUseCase(this._repository); + + bool call(String idDrink) { + return _repository.isFavorite(idDrink); + } +} \ No newline at end of file diff --git a/lib/features/favourites/domain/use_cases/remove_from_favourites.dart b/lib/features/favourites/domain/use_cases/remove_from_favourites.dart new file mode 100644 index 0000000..06952d4 --- /dev/null +++ b/lib/features/favourites/domain/use_cases/remove_from_favourites.dart @@ -0,0 +1,11 @@ +import '../repositories/favourites_repository.dart'; + +class RemoveFavoriteUseCase { + final FavoriteDrinksRepository _repository; + + RemoveFavoriteUseCase(this._repository); + + void call(String idDrink) { + _repository.removeFavorite(idDrink); + } +} \ No newline at end of file diff --git a/lib/features/favourites/infrastructure/data_sources/local_data_source.dart b/lib/features/favourites/infrastructure/data_sources/local_data_source.dart index 24df93b..5d3b884 100644 --- a/lib/features/favourites/infrastructure/data_sources/local_data_source.dart +++ b/lib/features/favourites/infrastructure/data_sources/local_data_source.dart @@ -1,23 +1,36 @@ import 'package:hive/hive.dart'; import '../../../drinks/infrastructure/dto/drink_details/drink_details_model.dart'; -class FavoriteDrinksLocalDataSource { +abstract class FavoriteDrinksDataSource { + void addFavorite(DrinkDetails drink); + void removeFavorite(String idDrink); + List getFavorites(); + bool isFavorite(String idDrink); +} + + + +class FavoriteDrinksLocalDataSource implements FavoriteDrinksDataSource { final Box _favoritesBox; FavoriteDrinksLocalDataSource(this._favoritesBox); + @override void addFavorite(DrinkDetails drink) { _favoritesBox.put(drink.idDrink, drink); } + @override void removeFavorite(String idDrink) { _favoritesBox.delete(idDrink); } + @override List getFavorites() { return _favoritesBox.values.toList(); } + @override bool isFavorite(String idDrink) { return _favoritesBox.containsKey(idDrink); } diff --git a/lib/features/favourites/infrastructure/repositories/favourites_repository_impl.dart b/lib/features/favourites/infrastructure/repositories/favourites_repository_impl.dart new file mode 100644 index 0000000..61a143e --- /dev/null +++ b/lib/features/favourites/infrastructure/repositories/favourites_repository_impl.dart @@ -0,0 +1,29 @@ +import '../../../drinks/infrastructure/dto/drink_details/drink_details_model.dart'; +import '../../domain/repositories/favourites_repository.dart'; +import '../data_sources/local_data_source.dart'; + +class FavoriteDrinksRepositoryImpl implements FavoriteDrinksRepository { + final FavoriteDrinksDataSource _localDataSource; + + FavoriteDrinksRepositoryImpl(this._localDataSource); + + @override + void addFavorite(DrinkDetails drink) { + _localDataSource.addFavorite(drink); + } + + @override + void removeFavorite(String idDrink) { + _localDataSource.removeFavorite(idDrink); + } + + @override + List getFavorites() { + return _localDataSource.getFavorites(); + } + + @override + bool isFavorite(String idDrink) { + return _localDataSource.isFavorite(idDrink); + } +} diff --git a/lib/features/favourites/presentation/riverpod/favourites_notifier.dart b/lib/features/favourites/presentation/riverpod/favourites_notifier.dart index 76f0378..b1a414a 100644 --- a/lib/features/favourites/presentation/riverpod/favourites_notifier.dart +++ b/lib/features/favourites/presentation/riverpod/favourites_notifier.dart @@ -1,25 +1,37 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../drinks/infrastructure/dto/drink_details/drink_details_model.dart'; -import '../../infrastructure/data_sources/local_data_source.dart'; +import '../../domain/use_cases/add_to_favourites.dart'; +import '../../domain/use_cases/get_favourites.dart'; +import '../../domain/use_cases/is_favourite.dart'; +import '../../domain/use_cases/remove_from_favourites.dart'; class FavoriteDrinksNotifier extends StateNotifier> { - final FavoriteDrinksLocalDataSource _localDataSource; + final AddFavoriteUseCase _addFavoriteUseCase; + final RemoveFavoriteUseCase _removeFavoriteUseCase; + final GetFavoritesUseCase _getFavoritesUseCase; + final IsFavoriteUseCase _isFavoriteUseCase; - FavoriteDrinksNotifier(this._localDataSource) : super(_localDataSource.getFavorites()); + FavoriteDrinksNotifier( + this._addFavoriteUseCase, + this._removeFavoriteUseCase, + this._getFavoritesUseCase, + this._isFavoriteUseCase, + ) : super(_getFavoritesUseCase()); void addFavorite(DrinkDetails drink) { - _localDataSource.addFavorite(drink); - state = _localDataSource.getFavorites(); + _addFavoriteUseCase(drink); + state = _getFavoritesUseCase(); } void removeFavorite(String idDrink) { - _localDataSource.removeFavorite(idDrink); - state = _localDataSource.getFavorites(); + _removeFavoriteUseCase(idDrink); + state = _getFavoritesUseCase(); } bool isFavorite(String idDrink) { - return _localDataSource.isFavorite(idDrink); + return _isFavoriteUseCase(idDrink); } } + diff --git a/lib/features/favourites/presentation/riverpod/favourites_provider.dart b/lib/features/favourites/presentation/riverpod/favourites_provider.dart index 8b1fab6..dd1f566 100644 --- a/lib/features/favourites/presentation/riverpod/favourites_provider.dart +++ b/lib/features/favourites/presentation/riverpod/favourites_provider.dart @@ -2,24 +2,67 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive/hive.dart'; import '../../../drinks/infrastructure/dto/drink_details/drink_details_model.dart'; +import '../../domain/repositories/favourites_repository.dart'; +import '../../domain/use_cases/add_to_favourites.dart'; +import '../../domain/use_cases/get_favourites.dart'; +import '../../domain/use_cases/is_favourite.dart'; +import '../../domain/use_cases/remove_from_favourites.dart'; import '../../infrastructure/data_sources/local_data_source.dart'; +import '../../infrastructure/repositories/favourites_repository_impl.dart'; import 'favourites_notifier.dart'; -// Initialize the Hive box provider +// Hive box provider final favoritesBoxProvider = Provider>((ref) { return Hive.box('favorites'); }); -// Initialize the local data source provider +// Data source provider final favoriteDrinksLocalDataSourceProvider = - Provider((ref) { + Provider((ref) { final box = ref.watch(favoritesBoxProvider); return FavoriteDrinksLocalDataSource(box); }); -// Initialize the notifier provider +// Repository provider +final favoriteDrinksRepositoryProvider = + Provider((ref) { + final dataSource = ref.watch(favoriteDrinksLocalDataSourceProvider); + return FavoriteDrinksRepositoryImpl(dataSource); +}); + +// Use case providers +final addFavoriteUseCaseProvider = Provider((ref) { + final repository = ref.watch(favoriteDrinksRepositoryProvider); + return AddFavoriteUseCase(repository); +}); + +final removeFavoriteUseCaseProvider = Provider((ref) { + final repository = ref.watch(favoriteDrinksRepositoryProvider); + return RemoveFavoriteUseCase(repository); +}); + +final getFavoritesUseCaseProvider = Provider((ref) { + final repository = ref.watch(favoriteDrinksRepositoryProvider); + return GetFavoritesUseCase(repository); +}); + +final isFavoriteUseCaseProvider = Provider((ref) { + final repository = ref.watch(favoriteDrinksRepositoryProvider); + return IsFavoriteUseCase(repository); +}); + +// Notifier provider final favoriteDrinksNotifierProvider = StateNotifierProvider>((ref) { - final localDataSource = ref.watch(favoriteDrinksLocalDataSourceProvider); - return FavoriteDrinksNotifier(localDataSource); + final addFavoriteUseCase = ref.watch(addFavoriteUseCaseProvider); + final removeFavoriteUseCase = ref.watch(removeFavoriteUseCaseProvider); + final getFavoritesUseCase = ref.watch(getFavoritesUseCaseProvider); + final isFavoriteUseCase = ref.watch(isFavoriteUseCaseProvider); + + return FavoriteDrinksNotifier( + addFavoriteUseCase, + removeFavoriteUseCase, + getFavoritesUseCase, + isFavoriteUseCase, + ); }); diff --git a/lib/features/favourites/presentation/screens/favourites.dart b/lib/features/favourites/presentation/screens/favourites.dart new file mode 100644 index 0000000..398f726 --- /dev/null +++ b/lib/features/favourites/presentation/screens/favourites.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:go_router/go_router.dart'; +import '../../../../core/router/routes.dart'; +import '../../../drinks/presentation/riverpod/drink_details/selected_drink_provider.dart'; +import '../riverpod/favourites_provider.dart'; +import '../widgets/favourite_drink_item.dart'; + +class FavoritesScreen extends ConsumerWidget { + const FavoritesScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final favorites = ref.watch(favoriteDrinksNotifierProvider); + + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.pop(context); + }, + ), + elevation: 2, + title: const Text( + 'Favorites', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + ), + body: SingleChildScrollView( + child: favorites.isEmpty + ? const Center( + child: Text( + 'No favorites yet', + style: TextStyle(fontSize: 18), + ), + ) + : RefreshIndicator( + onRefresh: () async { + // ref.refresh(favoriteDrinksNotifierProvider); + }, + child: StaggeredGrid.count( + crossAxisCount: 2, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + children: List.generate( + favorites.length, + (index) { + return GestureDetector( + onTap: () { + ref.read(selectedDrinkProvider.notifier).state = + favorites[index].idDrink; + + context.goNamed( + Routes.drinkDetails.name, + ); + }, + child: FavouriteDrinkItem( + drink: favorites[index], + ), + ); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/favourites/presentation/widgets/favourite_drink_item.dart b/lib/features/favourites/presentation/widgets/favourite_drink_item.dart new file mode 100644 index 0000000..d99efae --- /dev/null +++ b/lib/features/favourites/presentation/widgets/favourite_drink_item.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import '../../../drinks/infrastructure/dto/drink_details/drink_details_model.dart'; + +class FavouriteDrinkItem extends StatelessWidget { + final DrinkDetails drink; + + const FavouriteDrinkItem({Key? key, required this.drink}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Card( + child: Column( + children: [ + Image.network(drink.strDrinkThumb ?? '', fit: BoxFit.cover), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + drink.strDrink, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ], + ), + ); + } +}