From f09f9195828c6ff6f8fade1c8801976e9f83f405 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Fri, 13 Sep 2024 14:28:46 -0500 Subject: [PATCH 01/24] feat: initial design for the view of all restaurants --- lib/main.dart | 89 ++++++------------ lib/src/constants/strings.dart | 3 + .../data/restaurants_repository.dart | 38 ++++++++ .../restaurant_tour}/models/restaurant.dart | 0 .../restaurant_tour}/models/restaurant.g.dart | 0 .../pages/restaurant_tour_page.dart | 43 +++++++++ .../view/all_restaurants_view.dart | 24 +++++ .../view/favorite_restaurants.dart | 23 +++++ .../presentation/widgets/restaurant_card.dart | 94 +++++++++++++++++++ 9 files changed, 254 insertions(+), 60 deletions(-) create mode 100644 lib/src/constants/strings.dart create mode 100644 lib/src/features/restaurant_tour/data/restaurants_repository.dart rename lib/{ => src/features/restaurant_tour}/models/restaurant.dart (100%) rename lib/{ => src/features/restaurant_tour}/models/restaurant.g.dart (100%) create mode 100644 lib/src/features/restaurant_tour/presentation/pages/restaurant_tour_page.dart create mode 100644 lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart create mode 100644 lib/src/features/restaurant_tour/presentation/view/favorite_restaurants.dart create mode 100644 lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart diff --git a/lib/main.dart b/lib/main.dart index ae7012a..1c1ea12 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,5 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; -import 'package:restaurant_tour/models/restaurant.dart'; -import 'package:restaurant_tour/query.dart'; - -const _apiKey = ''; -const _baseUrl = 'https://api.yelp.com/v3/graphql'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/pages/restaurant_tour_page.dart'; void main() { runApp(const RestaurantTour()); @@ -24,64 +17,40 @@ class RestaurantTour extends StatelessWidget { } } -// TODO: Architect code -// This is just a POC of the API integration class HomePage extends StatelessWidget { const HomePage({super.key}); - Future getRestaurants({int offset = 0}) async { - final headers = { - 'Authorization': 'Bearer $_apiKey', - 'Content-Type': 'application/graphql', - }; - - try { - final response = await http.post( - Uri.parse(_baseUrl), - headers: headers, - body: query(offset), - ); - - if (response.statusCode == 200) { - return RestaurantQueryResult.fromJson( - jsonDecode(response.body)['data']['search'], - ); - } else { - print('Failed to load restaurants: ${response.statusCode}'); - return null; - } - } catch (e) { - print('Error fetching restaurants: $e'); - return null; - } - } - @override Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Restaurant Tour'), - ElevatedButton( - child: const Text('Fetch Restaurants'), - onPressed: () async { - try { - final result = await getRestaurants(); - if (result != null) { - print('Fetched ${result.restaurants!.length} restaurants'); - } else { - print('No restaurants fetched'); - } - } catch (e) { - print('Failed to fetch restaurants: $e'); - } - }, - ), - ], - ), + return const MaterialApp( + home: Scaffold( + body: RestaurantTourPage(), ), ); + // return Scaffold( + // body: Center( + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // const Text('Restaurant Tour'), + // ElevatedButton( + // child: const Text('Fetch Restaurants'), + // onPressed: () async { + // try { + // final result = await RestaurantsRepository.getRestaurants(); + // if (result != null) { + // print('Fetched ${result.restaurants!.length} restaurants'); + // } else { + // print('No restaurants fetched'); + // } + // } catch (e) { + // print('Failed to fetch restaurants: $e'); + // } + // }, + // ), + // ], + // ), + // ), + // ); } } diff --git a/lib/src/constants/strings.dart b/lib/src/constants/strings.dart new file mode 100644 index 0000000..d509a72 --- /dev/null +++ b/lib/src/constants/strings.dart @@ -0,0 +1,3 @@ +const String allRestaurants = 'All Restaurants'; +const String myFavorites = 'My Favorites'; +const String titleApp = 'Restaurant Tour'; diff --git a/lib/src/features/restaurant_tour/data/restaurants_repository.dart b/lib/src/features/restaurant_tour/data/restaurants_repository.dart new file mode 100644 index 0000000..374e13b --- /dev/null +++ b/lib/src/features/restaurant_tour/data/restaurants_repository.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:restaurant_tour/query.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; + +class RestaurantsRepository { + static const _apiKey = + 'y0RvKbozyu07RfpByqrdTJGyAOzhaNZH9T5X5pzBOoSh9uqOULc8h6yx89Z5nPjYtNaPHp9aqX0ZKF5pHSuYTeWcrYJS9r4EoHb7WmVLKPSmPW-L0FloXZJUInTkZnYx'; + static const _baseUrl = 'https://api.yelp.com/v3/graphql'; + + static Future getRestaurants({int offset = 0}) async { + final headers = { + 'Authorization': 'Bearer $_apiKey', + 'Content-Type': 'application/graphql', + }; + + try { + final response = await http.post( + Uri.parse(_baseUrl), + headers: headers, + body: query(offset), + ); + + if (response.statusCode == 200) { + return RestaurantQueryResult.fromJson( + jsonDecode(response.body)['data']['search'], + ); + } else { + print('Failed to load restaurants: ${response.statusCode}'); + return null; + } + } catch (e) { + print('Error fetching restaurants: $e'); + return null; + } + } +} diff --git a/lib/models/restaurant.dart b/lib/src/features/restaurant_tour/models/restaurant.dart similarity index 100% rename from lib/models/restaurant.dart rename to lib/src/features/restaurant_tour/models/restaurant.dart diff --git a/lib/models/restaurant.g.dart b/lib/src/features/restaurant_tour/models/restaurant.g.dart similarity index 100% rename from lib/models/restaurant.g.dart rename to lib/src/features/restaurant_tour/models/restaurant.g.dart diff --git a/lib/src/features/restaurant_tour/presentation/pages/restaurant_tour_page.dart b/lib/src/features/restaurant_tour/presentation/pages/restaurant_tour_page.dart new file mode 100644 index 0000000..8105aff --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/pages/restaurant_tour_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/favorite_restaurants.dart'; + +class RestaurantTourPage extends StatefulWidget { + const RestaurantTourPage({super.key}); + + @override + State createState() => _RestaurantTourPageState(); +} + +class _RestaurantTourPageState extends State { + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + title: const Text(titleApp), + bottom: const TabBar( + indicatorColor: Colors.black, + labelColor: Colors.black, + tabs: [ + Tab( + child: Text(allRestaurants), + ), + Tab( + child: Text(myFavorites), + ), + ], + ), + ), + body: const TabBarView( + children: [ + AllRestaurantsView(), + FavoriteRestaurantsView(), + ], + ), + ), + ); + } +} diff --git a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart new file mode 100644 index 0000000..c54b4a8 --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; + +class AllRestaurantsView extends StatefulWidget { + const AllRestaurantsView({super.key}); + + @override + State createState() => _AllRestaurantsViewState(); +} + +class _AllRestaurantsViewState extends State { + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: 10, + itemBuilder: (context, index) { + return const Padding( + padding: EdgeInsets.all(8.0), + child: RestaurantCard(), + ); + }, + ); + } +} diff --git a/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants.dart b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants.dart new file mode 100644 index 0000000..1f4e03e --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class FavoriteRestaurantsView extends StatefulWidget { + const FavoriteRestaurantsView({super.key}); + + @override + State createState() => + _FavoriteRestaurantsViewState(); +} + +class _FavoriteRestaurantsViewState extends State { + @override + Widget build(BuildContext context) { + return Container( + child: const Center( + child: Text( + 'My favorites view', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ); + } +} diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart new file mode 100644 index 0000000..65ef9db --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:restaurant_tour/typography.dart'; + +class RestaurantCard extends StatefulWidget { + const RestaurantCard({super.key}); + + @override + State createState() => _RestaurantCardState(); +} + +class _RestaurantCardState extends State { + @override + Widget build(BuildContext context) { + return Container( + height: 120, + width: double.infinity, + padding: const EdgeInsets.all(4), + child: Card( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(2.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(30), + child: Image.network( + 'https://media.es.wired.com/photos/64370c54f381a957088482cc/4:3/w_2668,h_2001,c_limit/reboot%20de%20harry%20potter%20warner.jpg', + height: 150.0, + width: 120.0, + ), + ), + ), + const SizedBox(width: 10), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 48, + width: 235, + child: Text( + 'Restaurant Name Goes Here And Wrap 2 Lines', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.loraRegularTitle, + ), + ), + SizedBox(height: 10), + SizedBox( + height: 20, + width: 100, + child: Text( + '\$\$\$\$ Italian', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.openRegularText, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Icon( + Icons.star, + color: Color(0xffffb800), + ), + Icon( + Icons.star, + color: Color(0xffffb800), + ), + Spacer(), + SizedBox( + height: 20, + width: 70, + child: Text( + 'Open Now', + style: AppTextStyles.openRegularItalic, + ), + ), + Icon( + Icons.circle, + color: Colors.green, + size: 12.0, + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } +} From 5bf6e63c98e0d728a6009c4c974b985bacee5550 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Fri, 13 Sep 2024 14:48:31 -0500 Subject: [PATCH 02/24] feat: card info and image widget implemeted --- .../presentation/widgets/restaurant_card.dart | 101 +++--------------- .../widgets/restaurant_card_image.dart | 18 ++++ .../widgets/restaurant_card_info.dart | 58 ++++++++++ 3 files changed, 93 insertions(+), 84 deletions(-) create mode 100644 lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart create mode 100644 lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart index 65ef9db..f77abea 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart @@ -1,93 +1,26 @@ import 'package:flutter/material.dart'; -import 'package:restaurant_tour/typography.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart'; -class RestaurantCard extends StatefulWidget { +class RestaurantCard extends StatelessWidget { const RestaurantCard({super.key}); - @override - State createState() => _RestaurantCardState(); -} - -class _RestaurantCardState extends State { @override Widget build(BuildContext context) { - return Container( - height: 120, - width: double.infinity, - padding: const EdgeInsets.all(4), - child: Card( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(2.0), - child: ClipRRect( - borderRadius: BorderRadius.circular(30), - child: Image.network( - 'https://media.es.wired.com/photos/64370c54f381a957088482cc/4:3/w_2668,h_2001,c_limit/reboot%20de%20harry%20potter%20warner.jpg', - height: 150.0, - width: 120.0, - ), - ), - ), - const SizedBox(width: 10), - const Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 48, - width: 235, - child: Text( - 'Restaurant Name Goes Here And Wrap 2 Lines', - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: AppTextStyles.loraRegularTitle, - ), - ), - SizedBox(height: 10), - SizedBox( - height: 20, - width: 100, - child: Text( - '\$\$\$\$ Italian', - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: AppTextStyles.openRegularText, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Icon( - Icons.star, - color: Color(0xffffb800), - ), - Icon( - Icons.star, - color: Color(0xffffb800), - ), - Spacer(), - SizedBox( - height: 20, - width: 70, - child: Text( - 'Open Now', - style: AppTextStyles.openRegularItalic, - ), - ), - Icon( - Icons.circle, - color: Colors.green, - size: 12.0, - ), - ], - ), - ], - ), - ), - ], - ), + return const Card( + margin: EdgeInsets.all(4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsets.all(8), + child: RestaurantCardImage(), + ), + SizedBox(width: 10), + Expanded( + child: RestaurantCardInfo(), + ), + ], ), ); } diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart new file mode 100644 index 0000000..0458602 --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class RestaurantCardImage extends StatelessWidget { + const RestaurantCardImage({super.key}); + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(30), + child: Image.network( + 'https://media.es.wired.com/photos/64370c54f381a957088482cc/4:3/w_2668,h_2001,c_limit/reboot%20de%20harry%20potter%20warner.jpg', + height: 120.0, + width: 120.0, + fit: BoxFit.cover, + ), + ); + } +} diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart new file mode 100644 index 0000000..236cc2e --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:restaurant_tour/typography.dart'; + +class RestaurantCardInfo extends StatelessWidget { + const RestaurantCardInfo({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + width: 235, + child: Text( + 'Restaurant Name Goes Here And Wrap 2 Lines', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.loraRegularTitle, + ), + ), + const SizedBox(height: 10), + const Text( + '\$\$\$\$ Italian', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.openRegularText, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ...List.generate( + 2, + (index) => const Icon( + Icons.star, + color: Color(0xffffb800), + ), + ), + const Spacer(), + const SizedBox( + child: Text( + 'Open Now', + style: AppTextStyles.openRegularItalic, + ), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: Icon( + Icons.circle, + color: Colors.green, + size: 12.0, + ), + ), + ], + ), + ], + ); + } +} From 6081a6096690db126c2262cebb77a27a01cd2e07 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Fri, 13 Sep 2024 16:17:30 -0500 Subject: [PATCH 03/24] feat: restaurant info view and restaurant reviews implemented --- lib/src/constants/strings.dart | 4 + .../pages/restaurant_info_page.dart | 37 +++++ .../view/restaurant_info_view.dart | 140 ++++++++++++++++++ .../presentation/widgets/restaurant_card.dart | 37 +++-- .../widgets/restaurant_reviews.dart | 72 +++++++++ 5 files changed, 277 insertions(+), 13 deletions(-) create mode 100644 lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart create mode 100644 lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart create mode 100644 lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart diff --git a/lib/src/constants/strings.dart b/lib/src/constants/strings.dart index d509a72..350663c 100644 --- a/lib/src/constants/strings.dart +++ b/lib/src/constants/strings.dart @@ -1,3 +1,7 @@ +// Application strings +const String addressText = 'Address'; const String allRestaurants = 'All Restaurants'; +const String overallRatingText = 'Overall Rating'; const String myFavorites = 'My Favorites'; +const String reviewsText = 'Reviews'; const String titleApp = 'Restaurant Tour'; diff --git a/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart b/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart new file mode 100644 index 0000000..1086d6d --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart'; + +class RestaurantInfoPage extends StatefulWidget { + const RestaurantInfoPage({super.key}); + + @override + State createState() => _RestaurantInfoPageState(); +} + +class _RestaurantInfoPageState extends State { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + actions: [ + IconButton( + icon: const Icon(Icons.favorite_outline), + onPressed: () {}, + iconSize: 30, + ), + ], + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + Navigator.pop(context); + }, + iconSize: 30, + ), + title: const Text('Restaurant Name Goes Here And Wrap 2 Lines'), + ), + body: const RestaurantInfoView(), + ), + ); + } +} diff --git a/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart b/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart new file mode 100644 index 0000000..15e17ee --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart'; +import 'package:restaurant_tour/typography.dart'; + +class RestaurantInfoView extends StatefulWidget { + const RestaurantInfoView({super.key}); + + @override + State createState() => _RestaurantInfoViewState(); +} + +class _RestaurantInfoViewState extends State { + List _buildAddressSection() { + return [ + const Text( + addressText, + style: AppTextStyles.openRegularText, + ), + const SizedBox(height: 14), + const SizedBox( + width: 150, + child: Text( + '102 Lakeside Ave Seattle, WA 98122', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.openRegularTitleSemiBold, + ), + ), + ]; + } + + List _buildAvailabilitySection() { + return [ + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '\$\$\$\$ Italian', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.openRegularText, + ), + Spacer(), + SizedBox( + child: Text( + 'Open Now', + style: AppTextStyles.openRegularItalic, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: Icon( + Icons.circle, + color: Colors.green, + size: 12.0, + ), + ), + ], + ), + ]; + } + + Widget _buildImage() { + return SizedBox( + height: 280, + width: double.infinity, + child: Image.network( + 'https://media.es.wired.com/photos/64370c54f381a957088482cc/4:3/w_2668,h_2001,c_limit/reboot%20de%20harry%20potter%20warner.jpg', + fit: BoxFit.cover, + ), + ); + } + + List _buildOverallRatingSection() { + return [ + const Text( + overallRatingText, + style: AppTextStyles.openRegularText, + ), + const SizedBox(height: 14), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + '4.6', + style: AppTextStyles.loraRegularHeadline.copyWith( + fontSize: 28, + ), + ), + const Icon( + Icons.star, + color: Color(0xffffb800), + ), + ], + ), + ]; + } + + List _divider() { + return [ + const SizedBox(height: 14), + const Divider(), + const SizedBox(height: 14), + ]; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + _buildImage(), + Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ..._buildAvailabilitySection(), + ..._divider(), + ..._buildAddressSection(), + ..._divider(), + ..._buildOverallRatingSection(), + ..._divider(), + const Text( + '42 $reviewsText', + style: AppTextStyles.openRegularText, + ), + ...List.generate( + 4, + (index) => const RestaurantReviews(), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart index f77abea..dca9406 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart'; @@ -7,20 +8,30 @@ class RestaurantCard extends StatelessWidget { @override Widget build(BuildContext context) { - return const Card( - margin: EdgeInsets.all(4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: EdgeInsets.all(8), - child: RestaurantCardImage(), + return InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const RestaurantInfoPage(), ), - SizedBox(width: 10), - Expanded( - child: RestaurantCardInfo(), - ), - ], + ); + }, + child: const Card( + margin: EdgeInsets.all(4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsets.all(8), + child: RestaurantCardImage(), + ), + SizedBox(width: 10), + Expanded( + child: RestaurantCardInfo(), + ), + ], + ), ), ); } diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart new file mode 100644 index 0000000..c2f1cd3 --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:restaurant_tour/typography.dart'; + +class RestaurantReviews extends StatelessWidget { + const RestaurantReviews({super.key}); + + Row _buildStarIcons() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ...List.generate( + 4, + (index) => const Icon( + Icons.star, + color: Color(0xffffb800), + ), + ), + ], + ); + } + + Text _buildTextSection() { + return const Text( + 'Review text goes there. Review text goes here. This is a review. This is a review that is 3 lines long.', + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: AppTextStyles.openRegularHeadline, + ); + } + + Row _buildUserInfo() { + return Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(50), + child: Image.network( + 'https://cdn.openart.ai/stable_diffusion/872b4795cdc6c81e042cb9494c5c7a499c9ec899_2000x2000.webp', + fit: BoxFit.cover, + height: 45, + width: 45, + ), + ), + const SizedBox( + width: 15, + ), + const Text( + 'Iron Man', + style: AppTextStyles.openRegularText, + ), + ], + ); + } + + Widget _space() { + return const SizedBox(height: 14); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _space(), + _buildStarIcons(), + _space(), + _buildTextSection(), + _space(), + _buildUserInfo(), + ], + ); + } +} From 3aba8f17b2129a70aff1d460258fa64a970b5763 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Fri, 13 Sep 2024 16:20:56 -0500 Subject: [PATCH 04/24] feat: star icon optimized --- .../presentation/view/restaurant_info_view.dart | 6 ++---- .../presentation/widgets/restaurant_card_info.dart | 6 ++---- .../presentation/widgets/restaurant_reviews.dart | 6 ++---- .../presentation/widgets/star_icon.dart | 13 +++++++++++++ 4 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 lib/src/features/restaurant_tour/presentation/widgets/star_icon.dart diff --git a/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart b/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart index 15e17ee..197a6d9 100644 --- a/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; import 'package:restaurant_tour/typography.dart'; class RestaurantInfoView extends StatefulWidget { @@ -88,10 +89,7 @@ class _RestaurantInfoViewState extends State { fontSize: 28, ), ), - const Icon( - Icons.star, - color: Color(0xffffb800), - ), + const StarIcon(), ], ), ]; diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart index 236cc2e..ed24487 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; import 'package:restaurant_tour/typography.dart'; class RestaurantCardInfo extends StatelessWidget { @@ -30,10 +31,7 @@ class RestaurantCardInfo extends StatelessWidget { children: [ ...List.generate( 2, - (index) => const Icon( - Icons.star, - color: Color(0xffffb800), - ), + (index) => const StarIcon(), ), const Spacer(), const SizedBox( diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart index c2f1cd3..2f91fe3 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; import 'package:restaurant_tour/typography.dart'; class RestaurantReviews extends StatelessWidget { @@ -10,10 +11,7 @@ class RestaurantReviews extends StatelessWidget { children: [ ...List.generate( 4, - (index) => const Icon( - Icons.star, - color: Color(0xffffb800), - ), + (index) => const StarIcon(), ), ], ); diff --git a/lib/src/features/restaurant_tour/presentation/widgets/star_icon.dart b/lib/src/features/restaurant_tour/presentation/widgets/star_icon.dart new file mode 100644 index 0000000..77c2b3e --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/widgets/star_icon.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class StarIcon extends StatelessWidget { + const StarIcon({super.key}); + + @override + Widget build(BuildContext context) { + return const Icon( + Icons.star, + color: Color(0xffffb800), + ); + } +} From 391e69f3f75c81296dc80a143cb705a407074791 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Fri, 13 Sep 2024 17:26:20 -0500 Subject: [PATCH 05/24] fix: all restaurants view updated to get information from API --- lib/src/constants/strings.dart | 2 + .../view/all_restaurants_view.dart | 59 ++++++++++++++++--- .../presentation/widgets/restaurant_card.dart | 28 ++++++--- .../widgets/restaurant_card_image.dart | 10 +++- .../widgets/restaurant_card_info.dart | 29 +++++---- 5 files changed, 98 insertions(+), 30 deletions(-) diff --git a/lib/src/constants/strings.dart b/lib/src/constants/strings.dart index 350663c..df524e5 100644 --- a/lib/src/constants/strings.dart +++ b/lib/src/constants/strings.dart @@ -1,7 +1,9 @@ // Application strings const String addressText = 'Address'; const String allRestaurants = 'All Restaurants'; +const String closedText = 'Closed'; const String overallRatingText = 'Overall Rating'; const String myFavorites = 'My Favorites'; +const String openNowText = 'Open Now'; const String reviewsText = 'Reviews'; const String titleApp = 'Restaurant Tour'; diff --git a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart index c54b4a8..10164b9 100644 --- a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; class AllRestaurantsView extends StatefulWidget { @@ -9,16 +11,55 @@ class AllRestaurantsView extends StatefulWidget { } class _AllRestaurantsViewState extends State { + late List restaurants; + + bool _showProgressIndicator = true; + + @override + void initState() { + super.initState(); + _simulateLoading(); + _fetchRestaurants(); + } + + _fetchRestaurants() async { + try { + // final result = await RestaurantsRepository.getRestaurants(); + restaurants = mockRestaurants; + print('Fetched ${restaurants.length} restaurants'); + } catch (e) { + print('Failed to fetch restaurants: $e'); + } + } + + void _simulateLoading() async { + await Future.delayed(const Duration(seconds: 3)); + if (mounted) { + setState(() { + _showProgressIndicator = false; + }); + } + } + @override Widget build(BuildContext context) { - return ListView.builder( - itemCount: 10, - itemBuilder: (context, index) { - return const Padding( - padding: EdgeInsets.all(8.0), - child: RestaurantCard(), - ); - }, - ); + print('restaurants ${restaurants[0].toJson()}'); + return !_showProgressIndicator + ? ListView.builder( + itemCount: restaurants.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: RestaurantCard( + restaurant: restaurants[index], + ), + ); + }, + ) + : const Center( + child: CircularProgressIndicator( + color: Colors.black, + ), + ); } } diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart index dca9406..eaa2740 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart @@ -1,10 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart'; class RestaurantCard extends StatelessWidget { - const RestaurantCard({super.key}); + const RestaurantCard({ + super.key, + required this.restaurant, + }); + + final Restaurant restaurant; @override Widget build(BuildContext context) { @@ -13,22 +19,28 @@ class RestaurantCard extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => const RestaurantInfoPage(), + builder: (context) => RestaurantInfoPage( + restaurant: restaurant, + ), ), ); }, - child: const Card( - margin: EdgeInsets.all(4), + child: Card( + margin: const EdgeInsets.all(4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( - padding: EdgeInsets.all(8), - child: RestaurantCardImage(), + padding: const EdgeInsets.all(8), + child: RestaurantCardImage( + restaurant: restaurant, + ), ), - SizedBox(width: 10), + const SizedBox(width: 10), Expanded( - child: RestaurantCardInfo(), + child: RestaurantCardInfo( + restaurant: restaurant, + ), ), ], ), diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart index 0458602..6e4a0e6 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart @@ -1,14 +1,20 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; class RestaurantCardImage extends StatelessWidget { - const RestaurantCardImage({super.key}); + const RestaurantCardImage({ + super.key, + required this.restaurant, + }); + + final Restaurant restaurant; @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(30), child: Image.network( - 'https://media.es.wired.com/photos/64370c54f381a957088482cc/4:3/w_2668,h_2001,c_limit/reboot%20de%20harry%20potter%20warner.jpg', + restaurant.heroImage, height: 120.0, width: 120.0, fit: BoxFit.cover, diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart index ed24487..1e56401 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart @@ -1,27 +1,34 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; import 'package:restaurant_tour/typography.dart'; class RestaurantCardInfo extends StatelessWidget { - const RestaurantCardInfo({super.key}); + const RestaurantCardInfo({ + super.key, + required this.restaurant, + }); + + final Restaurant restaurant; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 235, child: Text( - 'Restaurant Name Goes Here And Wrap 2 Lines', + restaurant.name ?? '', maxLines: 2, overflow: TextOverflow.ellipsis, style: AppTextStyles.loraRegularTitle, ), ), const SizedBox(height: 10), - const Text( - '\$\$\$\$ Italian', + Text( + '${restaurant.price} ${restaurant.categories![0].title}', maxLines: 1, overflow: TextOverflow.ellipsis, style: AppTextStyles.openRegularText, @@ -30,21 +37,21 @@ class RestaurantCardInfo extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ...List.generate( - 2, + restaurant.rating!.round(), (index) => const StarIcon(), ), const Spacer(), - const SizedBox( + SizedBox( child: Text( - 'Open Now', + restaurant.isOpen ? openNowText : closedText, style: AppTextStyles.openRegularItalic, ), ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Icon( Icons.circle, - color: Colors.green, + color: restaurant.isOpen ? Colors.green : Colors.red, size: 12.0, ), ), From 1a5aef3299a67e66b0db63ec1f806b894a84238c Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Fri, 13 Sep 2024 17:49:58 -0500 Subject: [PATCH 06/24] fix: restaurant info view updated to get information from API --- .../features/restaurant_tour/data/mock.dart | 171 ++++++++++++++++++ .../pages/restaurant_info_page.dart | 11 +- .../view/restaurant_info_view.dart | 38 ++-- .../widgets/restaurant_reviews.dart | 20 +- 4 files changed, 215 insertions(+), 25 deletions(-) create mode 100644 lib/src/features/restaurant_tour/data/mock.dart diff --git a/lib/src/features/restaurant_tour/data/mock.dart b/lib/src/features/restaurant_tour/data/mock.dart new file mode 100644 index 0000000..f799060 --- /dev/null +++ b/lib/src/features/restaurant_tour/data/mock.dart @@ -0,0 +1,171 @@ +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; + +List mockRestaurants = [ + Restaurant( + id: '1', + name: 'Pasta Paradise', + price: '\$\$', + rating: 4.5, + photos: [ + 'https://i.pinimg.com/originals/b5/90/a7/b590a70a53f5712d4abfd6bf938d054d.jpg', + 'https://www.metacritic.com/a/img/catalog/provider/6/12/6-1-764252-52.jpg', + ], + categories: [ + Category(alias: 'italian', title: 'Italian'), + ], + hours: [ + const Hours(isOpenNow: true), + ], + reviews: [ + const Review( + id: 'r1', + rating: 5, + text: + 'Amazing food! Highly recommended. Amazing food! Highly recommended.', + user: User( + id: 'u1', + imageUrl: 'https://randomuser.me/api/portraits/men/1.jpg', + name: 'John Doe', + ), + ), + const Review( + id: 'r2', + rating: 2, + text: + 'Review text goes there. Review text goes here. This is a review. This is a review that is 3 lines long.', + user: User( + id: 'u1', + imageUrl: 'https://randomuser.me/api/portraits/men/2.jpg', + name: 'John Test', + ), + ), + ], + location: Location( + formattedAddress: '123 Pasta Lane, Food City, FC 12345', + ), + ), + Restaurant( + id: '2', + name: 'Burger Bonanza', + price: '\$\$', + rating: 4.0, + photos: [ + 'https://cdn.hobbyconsolas.com/sites/navi.axelspringer.es/public/media/image/2022/07/marvels-avengers-2748987.jpg', + 'https://images.unsplash.com/photo-1567439204-e8a1c3c1ea80', + ], + categories: [ + Category(alias: 'american', title: 'American'), + ], + hours: [ + const Hours(isOpenNow: false), + ], + reviews: [ + const Review( + id: 'r2', + rating: 4, + text: 'Great burgers but a bit expensive.', + user: User( + id: 'u2', + imageUrl: 'https://randomuser.me/api/portraits/women/2.jpg', + name: 'Jane Smith', + ), + ), + ], + location: Location( + formattedAddress: '456 Burger Blvd, Meat Town, MT 67890', + ), + ), + Restaurant( + id: '3', + name: 'Sushi World', + price: '\$\$\$', + rating: 4.8, + photos: [ + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSqQrl5EGU85_IW-T1FQcfTexUPr8htRkBzIw&s', + 'https://images.unsplash.com/photo-1534503829050-1c8b38c9d137', + ], + categories: [ + Category(alias: 'japanese', title: 'Japanese'), + ], + hours: [ + const Hours(isOpenNow: true), + ], + reviews: [ + const Review( + id: 'r3', + rating: 5, + text: 'Best sushi in town. Fresh and delicious!', + user: User( + id: 'u3', + imageUrl: 'https://randomuser.me/api/portraits/men/3.jpg', + name: 'Alice Cooper', + ), + ), + ], + location: Location( + formattedAddress: '789 Sushi St, Roll City, RC 23456', + ), + ), + Restaurant( + id: '4', + name: 'Taco Haven', + price: '\$', + rating: 4.2, + photos: [ + 'https://phantom-marca.unidadeditorial.es/3d02ae4f69cb70e76206ba49e8de0fbc/resize/828/f/jpg/assets/multimedia/imagenes/2024/02/23/17087165658783.jpg', + 'https://images.unsplash.com/photo-1582960715727-4a7db5283a7f', + ], + categories: [ + Category(alias: 'mexican', title: 'Mexican'), + ], + hours: [ + const Hours(isOpenNow: true), + ], + reviews: [ + const Review( + id: 'r4', + rating: 4, + text: 'Tacos are great, but the service is slow.', + user: User( + id: 'u4', + imageUrl: 'https://randomuser.me/api/portraits/men/4.jpg', + name: 'Bob Brown', + ), + ), + ], + location: Location( + formattedAddress: '321 Taco Ave, Spice City, SC 34567', + ), + ), + Restaurant( + id: '5', + name: 'Vegan Delight', + price: '\$\$', + rating: 4.7, + photos: [ + 'https://pics.filmaffinity.com/harry_potter_and_the_sorcerer_s_stone-154820574-mmed.jpg', + 'https://images.unsplash.com/photo-1556914182-4ad1a5d33cfc', + ], + categories: [ + Category(alias: 'vegan', title: 'Vegan'), + ], + hours: [ + const Hours(isOpenNow: false), + ], + reviews: [ + const Review( + id: 'r5', + rating: 5, + text: 'Excellent vegan options. A must-visit for vegans!', + user: User( + id: 'u5', + imageUrl: 'https://randomuser.me/api/portraits/women/5.jpg', + name: 'Emma Davis', + ), + ), + ], + location: Location( + formattedAddress: '654 Vegan Way, Herb City, HC 45678', + ), + ), +]; diff --git a/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart b/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart index 1086d6d..176b1c6 100644 --- a/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart +++ b/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart'; class RestaurantInfoPage extends StatefulWidget { - const RestaurantInfoPage({super.key}); + const RestaurantInfoPage({super.key, required this.restaurant}); + + final Restaurant restaurant; @override State createState() => _RestaurantInfoPageState(); @@ -28,9 +31,11 @@ class _RestaurantInfoPageState extends State { }, iconSize: 30, ), - title: const Text('Restaurant Name Goes Here And Wrap 2 Lines'), + title: Text(widget.restaurant.name ?? ''), + ), + body: RestaurantInfoView( + restaurant: widget.restaurant, ), - body: const RestaurantInfoView(), ), ); } diff --git a/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart b/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart index 197a6d9..125bd98 100644 --- a/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart @@ -1,11 +1,17 @@ import 'package:flutter/material.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; import 'package:restaurant_tour/typography.dart'; class RestaurantInfoView extends StatefulWidget { - const RestaurantInfoView({super.key}); + const RestaurantInfoView({ + super.key, + required this.restaurant, + }); + + final Restaurant restaurant; @override State createState() => _RestaurantInfoViewState(); @@ -19,10 +25,10 @@ class _RestaurantInfoViewState extends State { style: AppTextStyles.openRegularText, ), const SizedBox(height: 14), - const SizedBox( + SizedBox( width: 150, child: Text( - '102 Lakeside Ave Seattle, WA 98122', + widget.restaurant.location?.formattedAddress ?? '', maxLines: 2, overflow: TextOverflow.ellipsis, style: AppTextStyles.openRegularTitleSemiBold, @@ -33,27 +39,27 @@ class _RestaurantInfoViewState extends State { List _buildAvailabilitySection() { return [ - const Row( + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '\$\$\$\$ Italian', + '${widget.restaurant.price} ${widget.restaurant.categories![0].title}', maxLines: 1, overflow: TextOverflow.ellipsis, style: AppTextStyles.openRegularText, ), - Spacer(), + const Spacer(), SizedBox( child: Text( - 'Open Now', + widget.restaurant.isOpen ? openNowText : closedText, style: AppTextStyles.openRegularItalic, ), ), Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Icon( Icons.circle, - color: Colors.green, + color: widget.restaurant.isOpen ? Colors.green : Colors.red, size: 12.0, ), ), @@ -67,7 +73,7 @@ class _RestaurantInfoViewState extends State { height: 280, width: double.infinity, child: Image.network( - 'https://media.es.wired.com/photos/64370c54f381a957088482cc/4:3/w_2668,h_2001,c_limit/reboot%20de%20harry%20potter%20warner.jpg', + widget.restaurant.heroImage, fit: BoxFit.cover, ), ); @@ -84,7 +90,7 @@ class _RestaurantInfoViewState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Text( - '4.6', + widget.restaurant.rating.toString(), style: AppTextStyles.loraRegularHeadline.copyWith( fontSize: 28, ), @@ -120,13 +126,15 @@ class _RestaurantInfoViewState extends State { ..._divider(), ..._buildOverallRatingSection(), ..._divider(), - const Text( - '42 $reviewsText', + Text( + '${widget.restaurant.reviews?.length ?? 0} $reviewsText', style: AppTextStyles.openRegularText, ), ...List.generate( - 4, - (index) => const RestaurantReviews(), + widget.restaurant.reviews?.length ?? 0, + (index) => RestaurantReviews( + reviews: widget.restaurant.reviews![index], + ), ), ], ), diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart index 2f91fe3..11fda9f 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart @@ -1,16 +1,22 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; import 'package:restaurant_tour/typography.dart'; class RestaurantReviews extends StatelessWidget { - const RestaurantReviews({super.key}); + const RestaurantReviews({ + super.key, + required this.reviews, + }); + + final Review reviews; Row _buildStarIcons() { return Row( mainAxisAlignment: MainAxisAlignment.start, children: [ ...List.generate( - 4, + reviews.rating ?? 0, (index) => const StarIcon(), ), ], @@ -18,8 +24,8 @@ class RestaurantReviews extends StatelessWidget { } Text _buildTextSection() { - return const Text( - 'Review text goes there. Review text goes here. This is a review. This is a review that is 3 lines long.', + return Text( + reviews.text ?? '', maxLines: 3, overflow: TextOverflow.ellipsis, style: AppTextStyles.openRegularHeadline, @@ -32,7 +38,7 @@ class RestaurantReviews extends StatelessWidget { ClipRRect( borderRadius: BorderRadius.circular(50), child: Image.network( - 'https://cdn.openart.ai/stable_diffusion/872b4795cdc6c81e042cb9494c5c7a499c9ec899_2000x2000.webp', + reviews.user!.imageUrl!, fit: BoxFit.cover, height: 45, width: 45, @@ -41,8 +47,8 @@ class RestaurantReviews extends StatelessWidget { const SizedBox( width: 15, ), - const Text( - 'Iron Man', + Text( + reviews.user!.name!, style: AppTextStyles.openRegularText, ), ], From 8644779e8910c0f488ed2a562ed9468583e47443 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Fri, 13 Sep 2024 18:10:25 -0500 Subject: [PATCH 07/24] fix: code optimization --- lib/main.dart | 25 ------- .../view/all_restaurants_view.dart | 26 +++---- .../view/restaurant_info_view.dart | 73 ++++++++++++------- .../widgets/restaurant_reviews.dart | 5 +- 4 files changed, 63 insertions(+), 66 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 1c1ea12..7f4f29e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,30 +27,5 @@ class HomePage extends StatelessWidget { body: RestaurantTourPage(), ), ); - // return Scaffold( - // body: Center( - // child: Column( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // const Text('Restaurant Tour'), - // ElevatedButton( - // child: const Text('Fetch Restaurants'), - // onPressed: () async { - // try { - // final result = await RestaurantsRepository.getRestaurants(); - // if (result != null) { - // print('Fetched ${result.restaurants!.length} restaurants'); - // } else { - // print('No restaurants fetched'); - // } - // } catch (e) { - // print('Failed to fetch restaurants: $e'); - // } - // }, - // ), - // ], - // ), - // ), - // ); } } diff --git a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart index 10164b9..086fa73 100644 --- a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; @@ -18,32 +18,28 @@ class _AllRestaurantsViewState extends State { @override void initState() { super.initState(); - _simulateLoading(); _fetchRestaurants(); } _fetchRestaurants() async { try { - // final result = await RestaurantsRepository.getRestaurants(); - restaurants = mockRestaurants; - print('Fetched ${restaurants.length} restaurants'); + final result = await RestaurantsRepository.getRestaurants(); + if (result != null) { + restaurants = result.restaurants!; + if (mounted) { + setState(() { + _showProgressIndicator = false; + }); + } + } + // restaurants = mockRestaurants; } catch (e) { print('Failed to fetch restaurants: $e'); } } - void _simulateLoading() async { - await Future.delayed(const Duration(seconds: 3)); - if (mounted) { - setState(() { - _showProgressIndicator = false; - }); - } - } - @override Widget build(BuildContext context) { - print('restaurants ${restaurants[0].toJson()}'); return !_showProgressIndicator ? ListView.builder( itemCount: restaurants.length, diff --git a/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart b/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart index 125bd98..c4126f4 100644 --- a/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart @@ -18,6 +18,14 @@ class RestaurantInfoView extends StatefulWidget { } class _RestaurantInfoViewState extends State { + bool _showProgressIndicator = true; + + @override + void initState() { + super.initState(); + _initializeLoading(); + } + List _buildAddressSection() { return [ const Text( @@ -109,38 +117,53 @@ class _RestaurantInfoViewState extends State { ]; } + void _initializeLoading() async { + await Future.delayed(const Duration(seconds: 1)); + if (mounted) { + setState(() { + _showProgressIndicator = false; + }); + } + } + @override Widget build(BuildContext context) { - return SingleChildScrollView( - child: Column( - children: [ - _buildImage(), - Padding( - padding: const EdgeInsets.all(24.0), + return !_showProgressIndicator + ? SingleChildScrollView( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - ..._buildAvailabilitySection(), - ..._divider(), - ..._buildAddressSection(), - ..._divider(), - ..._buildOverallRatingSection(), - ..._divider(), - Text( - '${widget.restaurant.reviews?.length ?? 0} $reviewsText', - style: AppTextStyles.openRegularText, - ), - ...List.generate( - widget.restaurant.reviews?.length ?? 0, - (index) => RestaurantReviews( - reviews: widget.restaurant.reviews![index], + _buildImage(), + Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ..._buildAvailabilitySection(), + ..._divider(), + ..._buildAddressSection(), + ..._divider(), + ..._buildOverallRatingSection(), + ..._divider(), + Text( + '${widget.restaurant.reviews?.length ?? 0} $reviewsText', + style: AppTextStyles.openRegularText, + ), + ...List.generate( + widget.restaurant.reviews?.length ?? 0, + (index) => RestaurantReviews( + reviews: widget.restaurant.reviews![index], + ), + ), + ], ), ), ], ), - ), - ], - ), - ); + ) + : const Center( + child: CircularProgressIndicator( + color: Colors.black, + ), + ); } } diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart index 11fda9f..d49698e 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart @@ -9,6 +9,9 @@ class RestaurantReviews extends StatelessWidget { required this.reviews, }); + static const _notFoundImage = + 'https://static.vecteezy.com/system/resources/thumbnails/001/840/612/small_2x/picture-profile-icon-male-icon-human-or-people-sign-and-symbol-free-vector.jpg'; + final Review reviews; Row _buildStarIcons() { @@ -38,7 +41,7 @@ class RestaurantReviews extends StatelessWidget { ClipRRect( borderRadius: BorderRadius.circular(50), child: Image.network( - reviews.user!.imageUrl!, + reviews.user!.imageUrl ?? _notFoundImage, fit: BoxFit.cover, height: 45, width: 45, From 6372c66f0aa1e4152c2f22daf4518ff6f0441fe8 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 12:29:33 -0500 Subject: [PATCH 08/24] feat: toggle to save favorites with shared preferences implemented --- lib/src/constants/strings.dart | 4 +- .../features/restaurant_tour/data/mock.dart | 15 +- .../pages/restaurant_info_page.dart | 32 ++++- .../pages/restaurant_tour_page.dart | 2 +- .../view/all_restaurants_view.dart | 65 +++++---- .../view/favorite_restaurants.dart | 23 ---- pubspec.lock | 129 +++++++++++++++++- pubspec.yaml | 8 +- 8 files changed, 213 insertions(+), 65 deletions(-) delete mode 100644 lib/src/features/restaurant_tour/presentation/view/favorite_restaurants.dart diff --git a/lib/src/constants/strings.dart b/lib/src/constants/strings.dart index df524e5..2340d87 100644 --- a/lib/src/constants/strings.dart +++ b/lib/src/constants/strings.dart @@ -2,8 +2,10 @@ const String addressText = 'Address'; const String allRestaurants = 'All Restaurants'; const String closedText = 'Closed'; -const String overallRatingText = 'Overall Rating'; +const String failedDataText = + 'Failed to load data: You have reached the GraphQL daily points limit for this API key'; const String myFavorites = 'My Favorites'; +const String overallRatingText = 'Overall Rating'; const String openNowText = 'Open Now'; const String reviewsText = 'Reviews'; const String titleApp = 'Restaurant Tour'; diff --git a/lib/src/features/restaurant_tour/data/mock.dart b/lib/src/features/restaurant_tour/data/mock.dart index f799060..19e49ba 100644 --- a/lib/src/features/restaurant_tour/data/mock.dart +++ b/lib/src/features/restaurant_tour/data/mock.dart @@ -1,8 +1,13 @@ import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; +RestaurantQueryResult? mockQueryResult = RestaurantQueryResult( + total: 10, + restaurants: mockRestaurants, +); + List mockRestaurants = [ Restaurant( - id: '1', + id: '100', name: 'Pasta Paradise', price: '\$\$', rating: 4.5, @@ -45,7 +50,7 @@ List mockRestaurants = [ ), ), Restaurant( - id: '2', + id: '200', name: 'Burger Bonanza', price: '\$\$', rating: 4.0, @@ -76,7 +81,7 @@ List mockRestaurants = [ ), ), Restaurant( - id: '3', + id: '300', name: 'Sushi World', price: '\$\$\$', rating: 4.8, @@ -107,7 +112,7 @@ List mockRestaurants = [ ), ), Restaurant( - id: '4', + id: '400', name: 'Taco Haven', price: '\$', rating: 4.2, @@ -138,7 +143,7 @@ List mockRestaurants = [ ), ), Restaurant( - id: '5', + id: '500', name: 'Vegan Delight', price: '\$\$', rating: 4.7, diff --git a/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart b/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart index 176b1c6..b10502e 100644 --- a/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart +++ b/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class RestaurantInfoPage extends StatefulWidget { const RestaurantInfoPage({super.key, required this.restaurant}); @@ -12,6 +13,31 @@ class RestaurantInfoPage extends StatefulWidget { } class _RestaurantInfoPageState extends State { + bool _isFavorite = false; + + @override + void initState() { + super.initState(); + _loadFavoriteStatus(); + } + + Future _loadFavoriteStatus() async { + final favoriteRestaurant = await SharedPreferences.getInstance(); + final isFavorite = + favoriteRestaurant.getBool(widget.restaurant.id ?? '') ?? false; + setState(() { + _isFavorite = isFavorite; + }); + } + + Future _toggleFavorite() async { + final preferences = await SharedPreferences.getInstance(); + setState(() { + _isFavorite = !_isFavorite; + }); + await preferences.setBool(widget.restaurant.id ?? '', _isFavorite); + } + @override Widget build(BuildContext context) { return MaterialApp( @@ -19,8 +45,10 @@ class _RestaurantInfoPageState extends State { appBar: AppBar( actions: [ IconButton( - icon: const Icon(Icons.favorite_outline), - onPressed: () {}, + icon: Icon( + _isFavorite ? Icons.favorite : Icons.favorite_outline, + ), + onPressed: () => _toggleFavorite(), iconSize: 30, ), ], diff --git a/lib/src/features/restaurant_tour/presentation/pages/restaurant_tour_page.dart b/lib/src/features/restaurant_tour/presentation/pages/restaurant_tour_page.dart index 8105aff..81c9a49 100644 --- a/lib/src/features/restaurant_tour/presentation/pages/restaurant_tour_page.dart +++ b/lib/src/features/restaurant_tour/presentation/pages/restaurant_tour_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/favorite_restaurants.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart'; class RestaurantTourPage extends StatefulWidget { const RestaurantTourPage({super.key}); diff --git a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart index 086fa73..15518ac 100644 --- a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; +import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; @@ -12,8 +13,8 @@ class AllRestaurantsView extends StatefulWidget { class _AllRestaurantsViewState extends State { late List restaurants; - bool _showProgressIndicator = true; + bool _dataNotFound = false; @override void initState() { @@ -21,37 +22,55 @@ class _AllRestaurantsViewState extends State { _fetchRestaurants(); } - _fetchRestaurants() async { + Future _fetchRestaurants() async { try { - final result = await RestaurantsRepository.getRestaurants(); - if (result != null) { - restaurants = result.restaurants!; - if (mounted) { - setState(() { - _showProgressIndicator = false; - }); - } + // final result = await RestaurantsRepository.getRestaurants(); + final result = mockQueryResult; + if (result != null && + result.restaurants != null && + result.restaurants!.isNotEmpty) { + } else { + _handleDataNotFound(); } - // restaurants = mockRestaurants; } catch (e) { print('Failed to fetch restaurants: $e'); + _handleDataNotFound(); + } + } + + void _handleDataNotFound() { + if (mounted) { + setState(() { + _showProgressIndicator = false; + _dataNotFound = true; + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + failedDataText, + ), + backgroundColor: Colors.black, + ), + ); } } @override Widget build(BuildContext context) { return !_showProgressIndicator - ? ListView.builder( - itemCount: restaurants.length, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: RestaurantCard( - restaurant: restaurants[index], - ), - ); - }, - ) + ? _dataNotFound + ? const Center(child: Text('Data not found')) + : ListView.builder( + itemCount: restaurants.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: RestaurantCard( + restaurant: restaurants[index], + ), + ); + }, + ) : const Center( child: CircularProgressIndicator( color: Colors.black, diff --git a/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants.dart b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants.dart deleted file mode 100644 index 1f4e03e..0000000 --- a/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; - -class FavoriteRestaurantsView extends StatefulWidget { - const FavoriteRestaurantsView({super.key}); - - @override - State createState() => - _FavoriteRestaurantsViewState(); -} - -class _FavoriteRestaurantsViewState extends State { - @override - Widget build(BuildContext context) { - return Container( - child: const Center( - child: Text( - 'My favorites view', - style: TextStyle(fontWeight: FontWeight.bold), - ), - ), - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index f95a63e..ae432c9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -185,14 +185,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" - file: + ffi: dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: "direct main" description: name: file - sha256: b69516f2c26a5bcac4eee2e32512e1a5205ab312b3536c1c1227b2b942b5f9ad + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "7.0.0" fixnum: dependency: transitive description: @@ -219,6 +227,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" frontend_server_client: dependency: transitive description: @@ -231,10 +244,10 @@ packages: dependency: transitive description: name: glob - sha256: "8321dd2c0ab0683a91a51307fa844c6db4aa8e3981219b78961672aaab434658" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.2" graphs: dependency: transitive description: @@ -387,6 +400,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" pool: dependency: transitive description: @@ -411,6 +464,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + url: "https://pub.dev" + source: hosted + version: "2.5.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" shelf: dependency: transitive description: @@ -560,6 +669,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" yaml: dependency: transitive description: @@ -570,4 +687,4 @@ packages: version: "3.1.0" sdks: dart: ">=3.4.0 <4.0.0" - flutter: ">=3.19.6" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index bc8a205..f344104 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,22 +5,23 @@ publish_to: 'none' version: 1.0.0+1 - environment: sdk: ">=3.1.0 <4.0.0" flutter: ">=3.19.6" dependencies: + file: ^7.0.0 flutter: sdk: flutter http: ^1.2.2 json_annotation: ^4.9.0 + shared_preferences: ^2.3.2 dev_dependencies: + build_runner: ^2.4.10 + flutter_lints: ^4.0.0 flutter_test: sdk: flutter - flutter_lints: ^4.0.0 - build_runner: ^2.4.10 json_serializable: ^6.8.0 flutter: @@ -45,4 +46,3 @@ flutter: style: italic - asset: assets/fonts/OpenSans/OpenSans-SemiBold.ttf weight: 600 - From 084d01f101a645362331ad87494f0e16178f5860 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 13:52:33 -0500 Subject: [PATCH 09/24] feat: favorite restaurants view implemented --- lib/src/constants/strings.dart | 2 + .../data/restaurants_repository.dart | 52 +++++++---- .../view/all_restaurants_view.dart | 11 ++- .../view/favorite_restaurants_view.dart | 88 +++++++++++++++++++ 4 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart diff --git a/lib/src/constants/strings.dart b/lib/src/constants/strings.dart index 2340d87..3c53327 100644 --- a/lib/src/constants/strings.dart +++ b/lib/src/constants/strings.dart @@ -5,7 +5,9 @@ const String closedText = 'Closed'; const String failedDataText = 'Failed to load data: You have reached the GraphQL daily points limit for this API key'; const String myFavorites = 'My Favorites'; +const String noFavoriteRestaurantsText = 'You do not have favorite restaurants'; const String overallRatingText = 'Overall Rating'; const String openNowText = 'Open Now'; const String reviewsText = 'Reviews'; +const String sorryText = '¡Sorry!'; const String titleApp = 'Restaurant Tour'; diff --git a/lib/src/features/restaurant_tour/data/restaurants_repository.dart b/lib/src/features/restaurant_tour/data/restaurants_repository.dart index 374e13b..d6400bd 100644 --- a/lib/src/features/restaurant_tour/data/restaurants_repository.dart +++ b/lib/src/features/restaurant_tour/data/restaurants_repository.dart @@ -1,7 +1,4 @@ -import 'dart:convert'; - -import 'package:http/http.dart' as http; -import 'package:restaurant_tour/query.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; class RestaurantsRepository { @@ -9,6 +6,8 @@ class RestaurantsRepository { 'y0RvKbozyu07RfpByqrdTJGyAOzhaNZH9T5X5pzBOoSh9uqOULc8h6yx89Z5nPjYtNaPHp9aqX0ZKF5pHSuYTeWcrYJS9r4EoHb7WmVLKPSmPW-L0FloXZJUInTkZnYx'; static const _baseUrl = 'https://api.yelp.com/v3/graphql'; + static RestaurantQueryResult? _restaurantsResponse; + static Future getRestaurants({int offset = 0}) async { final headers = { 'Authorization': 'Bearer $_apiKey', @@ -16,23 +15,42 @@ class RestaurantsRepository { }; try { - final response = await http.post( - Uri.parse(_baseUrl), - headers: headers, - body: query(offset), - ); + // final response = await http.post( + // Uri.parse(_baseUrl), + // headers: headers, + // body: query(offset), + // ); - if (response.statusCode == 200) { - return RestaurantQueryResult.fromJson( - jsonDecode(response.body)['data']['search'], - ); - } else { - print('Failed to load restaurants: ${response.statusCode}'); - return null; - } + // if (response.statusCode == 200) { + // _restaurantsResponse = RestaurantQueryResult.fromJson( + // jsonDecode(response.body)['data']['search'], + // ); + // return _restaurantsResponse; + // } else { + // print('Failed to load restaurants: ${response.statusCode}'); + // return null; + // } + return _restaurantsResponse = RestaurantQueryResult( + restaurants: mockRestaurants, + total: 5, + ); } catch (e) { print('Error fetching restaurants: $e'); return null; } } + + static List getFavoriteRestaurants(String id) { + var favoriteRestaurants = []; + + for (var restaurant in _restaurantsResponse!.restaurants!) { + if (restaurant.id == id) { + favoriteRestaurants.add(restaurant); + } + } + for (var i = 0; i < favoriteRestaurants.length; i++) { + print('rest $i : ${favoriteRestaurants[i].name}'); + } + return favoriteRestaurants; + } } diff --git a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart index 15518ac..4186e01 100644 --- a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; @@ -24,11 +24,16 @@ class _AllRestaurantsViewState extends State { Future _fetchRestaurants() async { try { - // final result = await RestaurantsRepository.getRestaurants(); - final result = mockQueryResult; + final result = await RestaurantsRepository.getRestaurants(); if (result != null && result.restaurants != null && result.restaurants!.isNotEmpty) { + restaurants = result.restaurants!; + if (mounted) { + setState(() { + _showProgressIndicator = false; + }); + } } else { _handleDataNotFound(); } diff --git a/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart new file mode 100644 index 0000000..43d3b9c --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class FavoriteRestaurantsView extends StatefulWidget { + const FavoriteRestaurantsView({super.key}); + + @override + State createState() => + _FavoriteRestaurantsViewState(); +} + +class _FavoriteRestaurantsViewState extends State { + List _favoriteRestaurants = []; + + @override + void initState() { + super.initState(); + _loadFavorites(); + } + + Widget _emptyFavoritesView() { + return const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.restaurant, + size: 60, + color: Colors.black, + ), + Text( + sorryText, + style: TextStyle(fontSize: 30, color: Colors.black), + ), + Text( + noFavoriteRestaurantsText, + style: TextStyle( + fontSize: 20, + color: Colors.black45, + ), + ), + ], + ), + ); + } + + Future _loadFavorites() async { + final favoriteRestaurants = await SharedPreferences.getInstance(); + final keys = favoriteRestaurants.getKeys(); + var accumulatedFavorites = []; + + for (var key in keys) { + final isFavorite = favoriteRestaurants.getBool(key) ?? false; + + if (isFavorite) { + var favoriteRestaurant = + RestaurantsRepository.getFavoriteRestaurants(key); + accumulatedFavorites.addAll(favoriteRestaurant); + } + } + + setState(() { + _favoriteRestaurants = accumulatedFavorites; + }); + } + + @override + Widget build(BuildContext context) { + return _favoriteRestaurants.isNotEmpty + ? ListView.builder( + itemCount: _favoriteRestaurants.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: RestaurantCard( + restaurant: _favoriteRestaurants[index], + ), + ); + }, + ) + : _emptyFavoritesView(); + } +} From 80d957ffdee9a094840b9d9a39247a50f0580c5e Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 14:47:33 -0500 Subject: [PATCH 10/24] fix: code optimizations --- lib/src/constants/strings.dart | 2 + .../data/restaurants_repository.dart | 3 - .../pages/restaurant_info_page.dart | 62 +++++++++++++------ .../view/favorite_restaurants_view.dart | 29 +++++++-- .../presentation/widgets/restaurant_card.dart | 23 ++++--- 5 files changed, 81 insertions(+), 38 deletions(-) diff --git a/lib/src/constants/strings.dart b/lib/src/constants/strings.dart index 3c53327..54bdb94 100644 --- a/lib/src/constants/strings.dart +++ b/lib/src/constants/strings.dart @@ -9,5 +9,7 @@ const String noFavoriteRestaurantsText = 'You do not have favorite restaurants'; const String overallRatingText = 'Overall Rating'; const String openNowText = 'Open Now'; const String reviewsText = 'Reviews'; +const String restaurantAddedText = 'Restaurant added to favorites'; +const String restaurantDeletedText = 'Restaurant deleted from favorites'; const String sorryText = '¡Sorry!'; const String titleApp = 'Restaurant Tour'; diff --git a/lib/src/features/restaurant_tour/data/restaurants_repository.dart b/lib/src/features/restaurant_tour/data/restaurants_repository.dart index d6400bd..7baa96e 100644 --- a/lib/src/features/restaurant_tour/data/restaurants_repository.dart +++ b/lib/src/features/restaurant_tour/data/restaurants_repository.dart @@ -48,9 +48,6 @@ class RestaurantsRepository { favoriteRestaurants.add(restaurant); } } - for (var i = 0; i < favoriteRestaurants.length; i++) { - print('rest $i : ${favoriteRestaurants[i].name}'); - } return favoriteRestaurants; } } diff --git a/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart b/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart index b10502e..ba05b0e 100644 --- a/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart +++ b/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/constants/strings.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart'; +import 'package:restaurant_tour/typography.dart'; import 'package:shared_preferences/shared_preferences.dart'; class RestaurantInfoPage extends StatefulWidget { @@ -30,40 +32,60 @@ class _RestaurantInfoPageState extends State { }); } + _showToast() { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + _isFavorite ? restaurantAddedText : restaurantDeletedText, + style: AppTextStyles.openRegularHeadline.copyWith( + color: Colors.white, + ), + ), + duration: const Duration(seconds: 1), + backgroundColor: Colors.black, + ), + ); + } + Future _toggleFavorite() async { final preferences = await SharedPreferences.getInstance(); setState(() { _isFavorite = !_isFavorite; + _showToast(); }); await preferences.setBool(widget.restaurant.id ?? '', _isFavorite); + if (!_isFavorite && mounted) { + Navigator.pop(context, true); + } } @override Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - actions: [ - IconButton( - icon: Icon( - _isFavorite ? Icons.favorite : Icons.favorite_outline, - ), - onPressed: () => _toggleFavorite(), - iconSize: 30, + return Scaffold( + appBar: AppBar( + actions: [ + IconButton( + icon: Icon( + _isFavorite ? Icons.favorite : Icons.favorite_outline, ), - ], - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - Navigator.pop(context); - }, + onPressed: () => _toggleFavorite(), iconSize: 30, ), - title: Text(widget.restaurant.name ?? ''), - ), - body: RestaurantInfoView( - restaurant: widget.restaurant, + ], + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + Navigator.pop( + context, + false, + ); + }, + iconSize: 30, ), + title: Text(widget.restaurant.name ?? ''), + ), + body: RestaurantInfoView( + restaurant: widget.restaurant, ), ); } diff --git a/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart index 43d3b9c..fbd1781 100644 --- a/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; +import 'package:restaurant_tour/typography.dart'; import 'package:shared_preferences/shared_preferences.dart'; class FavoriteRestaurantsView extends StatefulWidget { @@ -23,25 +25,26 @@ class _FavoriteRestaurantsViewState extends State { } Widget _emptyFavoritesView() { - return const Center( + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Icon( + const Icon( Icons.restaurant, size: 60, color: Colors.black, ), Text( sorryText, - style: TextStyle(fontSize: 30, color: Colors.black), + style: AppTextStyles.openRegularTitleSemiBold.copyWith( + fontSize: 30, + ), ), Text( noFavoriteRestaurantsText, - style: TextStyle( + style: AppTextStyles.openRegularText.copyWith( fontSize: 20, - color: Colors.black45, ), ), ], @@ -69,6 +72,19 @@ class _FavoriteRestaurantsViewState extends State { }); } + Future _onNavigateToRestaurantInfoPage(Restaurant restaurant) async { + final result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => RestaurantInfoPage(restaurant: restaurant), + ), + ); + + if (result == true) { + _loadFavorites(); + } + } + @override Widget build(BuildContext context) { return _favoriteRestaurants.isNotEmpty @@ -79,6 +95,9 @@ class _FavoriteRestaurantsViewState extends State { padding: const EdgeInsets.all(8.0), child: RestaurantCard( restaurant: _favoriteRestaurants[index], + onTap: () => _onNavigateToRestaurantInfoPage( + _favoriteRestaurants[index], + ), ), ); }, diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart index eaa2740..eaee046 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart @@ -8,23 +8,26 @@ class RestaurantCard extends StatelessWidget { const RestaurantCard({ super.key, required this.restaurant, + this.onTap, }); final Restaurant restaurant; + final VoidCallback? onTap; @override Widget build(BuildContext context) { return InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => RestaurantInfoPage( - restaurant: restaurant, - ), - ), - ); - }, + onTap: onTap ?? + () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => RestaurantInfoPage( + restaurant: restaurant, + ), + ), + ); + }, child: Card( margin: const EdgeInsets.all(4), child: Row( From ac755bb99c8dea484c4fa10b499d9b2abf01269c Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 14:55:35 -0500 Subject: [PATCH 11/24] fix: code optimization --- lib/{ => src/constants}/query.dart | 0 lib/{ => src/constants}/typography.dart | 0 .../features/restaurant_tour/data/restaurants_repository.dart | 2 ++ .../presentation/pages/restaurant_info_page.dart | 2 +- .../presentation/view/favorite_restaurants_view.dart | 2 +- .../restaurant_tour/presentation/view/restaurant_info_view.dart | 2 +- .../presentation/widgets/restaurant_card_info.dart | 2 +- .../presentation/widgets/restaurant_reviews.dart | 2 +- 8 files changed, 7 insertions(+), 5 deletions(-) rename lib/{ => src/constants}/query.dart (100%) rename lib/{ => src/constants}/typography.dart (100%) diff --git a/lib/query.dart b/lib/src/constants/query.dart similarity index 100% rename from lib/query.dart rename to lib/src/constants/query.dart diff --git a/lib/typography.dart b/lib/src/constants/typography.dart similarity index 100% rename from lib/typography.dart rename to lib/src/constants/typography.dart diff --git a/lib/src/features/restaurant_tour/data/restaurants_repository.dart b/lib/src/features/restaurant_tour/data/restaurants_repository.dart index 7baa96e..7067d09 100644 --- a/lib/src/features/restaurant_tour/data/restaurants_repository.dart +++ b/lib/src/features/restaurant_tour/data/restaurants_repository.dart @@ -8,6 +8,7 @@ class RestaurantsRepository { static RestaurantQueryResult? _restaurantsResponse; +// API call to get restaurant information static Future getRestaurants({int offset = 0}) async { final headers = { 'Authorization': 'Bearer $_apiKey', @@ -40,6 +41,7 @@ class RestaurantsRepository { } } +// Method to validate favorite restaurants static List getFavoriteRestaurants(String id) { var favoriteRestaurants = []; diff --git a/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart b/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart index ba05b0e..1d7d150 100644 --- a/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart +++ b/lib/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/constants/typography.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart'; -import 'package:restaurant_tour/typography.dart'; import 'package:shared_preferences/shared_preferences.dart'; class RestaurantInfoPage extends StatefulWidget { diff --git a/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart index fbd1781..14d6cd2 100644 --- a/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/constants/typography.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; -import 'package:restaurant_tour/typography.dart'; import 'package:shared_preferences/shared_preferences.dart'; class FavoriteRestaurantsView extends StatefulWidget { diff --git a/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart b/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart index c4126f4..9eecd36 100644 --- a/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/restaurant_info_view.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/constants/typography.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; -import 'package:restaurant_tour/typography.dart'; class RestaurantInfoView extends StatefulWidget { const RestaurantInfoView({ diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart index 1e56401..6f88960 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/constants/typography.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; -import 'package:restaurant_tour/typography.dart'; class RestaurantCardInfo extends StatelessWidget { const RestaurantCardInfo({ diff --git a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart index d49698e..75bebb8 100644 --- a/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart +++ b/lib/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:restaurant_tour/src/constants/typography.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; -import 'package:restaurant_tour/typography.dart'; class RestaurantReviews extends StatelessWidget { const RestaurantReviews({ From a669c15c400d3445b65c09e54d7b04ea5d73a7a8 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 15:20:12 -0500 Subject: [PATCH 12/24] feat: unit tests created to widgets folder --- pubspec.lock | 16 +++++ pubspec.yaml | 1 + .../widgets/restaurant_card_test.dart | 68 +++++++++++++++++++ .../widgets/restaurant_reviews_test.dart | 46 +++++++++++++ .../presentation/widgets/star_icon_test.dart | 23 +++++++ test/widget_test.dart | 19 ------ 6 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 test/src/features/restaurant_tour/presentation/widgets/restaurant_card_test.dart create mode 100644 test/src/features/restaurant_tour/presentation/widgets/restaurant_reviews_test.dart create mode 100644 test/src/features/restaurant_tour/presentation/widgets/star_icon_test.dart delete mode 100644 test/widget_test.dart diff --git a/pubspec.lock b/pubspec.lock index ae432c9..32c1e0f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -384,6 +384,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + mockito: + dependency: transitive + description: + name: mockito + sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + url: "https://pub.dev" + source: hosted + version: "5.4.4" + network_image_mock: + dependency: "direct main" + description: + name: network_image_mock + sha256: "855cdd01d42440e0cffee0d6c2370909fc31b3bcba308a59829f24f64be42db7" + url: "https://pub.dev" + source: hosted + version: "2.1.1" package_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f344104..bb79cad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: sdk: flutter http: ^1.2.2 json_annotation: ^4.9.0 + network_image_mock: ^2.1.1 shared_preferences: ^2.3.2 dev_dependencies: diff --git a/test/src/features/restaurant_tour/presentation/widgets/restaurant_card_test.dart b/test/src/features/restaurant_tour/presentation/widgets/restaurant_card_test.dart new file mode 100644 index 0000000..c5c00dc --- /dev/null +++ b/test/src/features/restaurant_tour/presentation/widgets/restaurant_card_test.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:network_image_mock/network_image_mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card_image.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card_info.dart'; + +void main() { + group( + 'Restaurant Card tests', + () { + testWidgets('RestaurantCard displays correctly and navigates on tap', + (WidgetTester tester) async { + final restaurant = mockRestaurants[0]; + + await mockNetworkImagesFor( + () => tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RestaurantCard(restaurant: restaurant), + ), + ), + ), + ); + + expect(find.text('Pasta Paradise'), findsOneWidget); + expect(find.byType(RestaurantCardImage), findsOneWidget); + expect(find.byType(RestaurantCardInfo), findsOneWidget); + + // Simulate a tap on the RestaurantCard + await tester.tap(find.byType(InkWell)); + await tester.pumpAndSettle(); + + expect(find.byType(RestaurantInfoPage), findsOneWidget); + }); + + testWidgets('RestaurantCard calls onTap callback when tapped', + (WidgetTester tester) async { + final restaurant = mockRestaurants[0]; + + bool wasTapped = false; + + // Build the widget with an onTap callback + await mockNetworkImagesFor( + () => tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RestaurantCard( + restaurant: restaurant, + onTap: () { + wasTapped = true; + }, + ), + ), + ), + ), + ); + + // Simulate a tap on the RestaurantCard + await tester.tap(find.byType(InkWell)); + await tester.pump(); + expect(wasTapped, true); + }); + }, + ); +} diff --git a/test/src/features/restaurant_tour/presentation/widgets/restaurant_reviews_test.dart b/test/src/features/restaurant_tour/presentation/widgets/restaurant_reviews_test.dart new file mode 100644 index 0000000..eb56ef6 --- /dev/null +++ b/test/src/features/restaurant_tour/presentation/widgets/restaurant_reviews_test.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:network_image_mock/network_image_mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_reviews.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; + +void main() { + testWidgets('RestaurantReviews displays correctly', + (WidgetTester tester) async { + const review = Review( + rating: 3, + text: 'Great restaurant!', + user: User( + name: 'John Doe', + imageUrl: 'https://example.com/image.jpg', + ), + ); + + await mockNetworkImagesFor( + () => tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: RestaurantReviews(reviews: review), + ), + ), + ), + ); + + // Verify the number of StarIcons + final starIcons = find.byType(StarIcon); + expect(starIcons, findsNWidgets(3)); + + // Verify the review text is displayed correctly + final textFinder = find.text('Great restaurant!'); + expect(textFinder, findsOneWidget); + + // Verify the user's image is displayed correctly + final imageFinder = find.byType(Image); + expect(imageFinder, findsOneWidget); + + // Verify the user's name is displayed correctly + final nameFinder = find.text('John Doe'); + expect(nameFinder, findsOneWidget); + }); +} diff --git a/test/src/features/restaurant_tour/presentation/widgets/star_icon_test.dart b/test/src/features/restaurant_tour/presentation/widgets/star_icon_test.dart new file mode 100644 index 0000000..565dad0 --- /dev/null +++ b/test/src/features/restaurant_tour/presentation/widgets/star_icon_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/star_icon.dart'; + +void main() { + testWidgets('StarIcon renders correctly', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: StarIcon(), + ), + ), + ); + + final iconFinder = find.byType(Icon); + + expect(iconFinder, findsOneWidget); + + final Icon icon = tester.widget(iconFinder) as Icon; + expect(icon.icon, Icons.star); + expect(icon.color, const Color(0xffffb800)); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index b729d48..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,19 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter_test/flutter_test.dart'; -import 'package:restaurant_tour/main.dart'; - -void main() { - testWidgets('Page loads', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const RestaurantTour()); - - // Verify that tests will run - expect(find.text('Fetch Restaurants'), findsOneWidget); - }); -} From 3fa76a3c8df4b86ed26d9f4867e73ec3f3ec4c71 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 15:38:58 -0500 Subject: [PATCH 13/24] feat: tests implemented for favorite restaurants --- pubspec.lock | 8 ++++ pubspec.yaml | 1 + .../view/favorite_restaurants_view_test.dart | 41 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 test/src/features/restaurant_tour/presentation/view/favorite_restaurants_view_test.dart diff --git a/pubspec.lock b/pubspec.lock index 32c1e0f..8eb785a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -392,6 +392,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.4" + mocktail: + dependency: "direct main" + description: + name: mocktail + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" + url: "https://pub.dev" + source: hosted + version: "1.0.4" network_image_mock: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index bb79cad..dc7b887 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: sdk: flutter http: ^1.2.2 json_annotation: ^4.9.0 + mocktail: ^1.0.4 network_image_mock: ^2.1.1 shared_preferences: ^2.3.2 diff --git a/test/src/features/restaurant_tour/presentation/view/favorite_restaurants_view_test.dart b/test/src/features/restaurant_tour/presentation/view/favorite_restaurants_view_test.dart new file mode 100644 index 0000000..eeeeb43 --- /dev/null +++ b/test/src/features/restaurant_tour/presentation/view/favorite_restaurants_view_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class MockSharedPreferences extends Mock implements SharedPreferences {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late MockSharedPreferences mockSharedPreferences; + + setUp(() { + mockSharedPreferences = MockSharedPreferences(); + SharedPreferences.setMockInitialValues({}); + }); + + group('Favorite Restaurants View tests', () { + testWidgets( + 'FavoriteRestaurantsView displays empty message when no favorites', + (WidgetTester tester) async { + // Mock the SharedPreferences to return no keys + when(() => mockSharedPreferences.getKeys()).thenReturn({}); + when(() => mockSharedPreferences.getBool(any())).thenReturn(false); + + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: FavoriteRestaurantsView(), + ), + ), + ); + + expect(find.byIcon(Icons.restaurant), findsOneWidget); + expect(find.text(sorryText), findsOneWidget); + expect(find.text(noFavoriteRestaurantsText), findsOneWidget); + }); + }); +} From 3af2cb1e1b4618e9c039518baae84c599eac6a15 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 16:49:50 -0500 Subject: [PATCH 14/24] feat: unit test implemented for all restaurants view --- .../view/all_restaurants_view.dart | 3 +- .../view/all_restaurants_view_test.dart | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/src/features/restaurant_tour/presentation/view/all_restaurants_view_test.dart diff --git a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart index 4186e01..bc51061 100644 --- a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart @@ -13,8 +13,9 @@ class AllRestaurantsView extends StatefulWidget { class _AllRestaurantsViewState extends State { late List restaurants; - bool _showProgressIndicator = true; + bool _dataNotFound = false; + bool _showProgressIndicator = true; @override void initState() { diff --git a/test/src/features/restaurant_tour/presentation/view/all_restaurants_view_test.dart b/test/src/features/restaurant_tour/presentation/view/all_restaurants_view_test.dart new file mode 100644 index 0000000..d39b9ed --- /dev/null +++ b/test/src/features/restaurant_tour/presentation/view/all_restaurants_view_test.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:network_image_mock/network_image_mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart'; + +void main() { + setUpAll(() { + registerFallbackValue( + const RestaurantQueryResult( + restaurants: [], + total: 0, + ), + ); + }); + + group( + 'All Restaurants View tests', + () { + testWidgets('should display progress indicator', + (WidgetTester tester) async { + await mockNetworkImagesFor( + () => tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: AllRestaurantsView(), + ), + ), + ), + ); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + }); + }, + ); +} From ca00697d295db1b13f6c92a8d0e0edddce6e9739 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 17:08:50 -0500 Subject: [PATCH 15/24] feat: tests implemented to repositiry --- .../lib/src/constants/query.dart.gcov.html | 110 ++++++++++++++++++ .../data/restaurant_repository_test.dart | 32 +++++ 2 files changed, 142 insertions(+) create mode 100644 coverage/html/Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants/query.dart.gcov.html create mode 100644 test/src/features/restaurant_tour/data/restaurant_repository_test.dart diff --git a/coverage/html/Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants/query.dart.gcov.html b/coverage/html/Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants/query.dart.gcov.html new file mode 100644 index 0000000..a18aafe --- /dev/null +++ b/coverage/html/Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants/query.dart.gcov.html @@ -0,0 +1,110 @@ + + + + + + + LCOV - lcov.info - Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants/query.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - /Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants - query.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %22
Test Date:2024-09-14 17:01:13Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1            2 : String query(int offset) => '''
+       2              :   query getRestaurants {
+       3              :     search(location: "Las Vegas", limit: 20, offset: $offset) {
+       4              :       total    
+       5              :       business {
+       6              :         id
+       7              :         name
+       8              :         price
+       9              :         rating
+      10              :         photos
+      11              :         reviews {
+      12              :           id
+      13              :           rating
+      14              :           text
+      15              :           user {
+      16              :             id
+      17              :             image_url
+      18              :             name
+      19              :           }
+      20              :         }
+      21              :         categories {
+      22              :           title
+      23              :           alias
+      24              :         }
+      25              :         hours {
+      26              :           is_open_now
+      27              :         }
+      28              :         location {
+      29              :           formatted_address
+      30              :         }
+      31              :       }
+      32              :     }
+      33              :   }
+      34            2 :   ''';
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/test/src/features/restaurant_tour/data/restaurant_repository_test.dart b/test/src/features/restaurant_tour/data/restaurant_repository_test.dart new file mode 100644 index 0000000..c50e5f7 --- /dev/null +++ b/test/src/features/restaurant_tour/data/restaurant_repository_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:mocktail/mocktail.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; + +class MockHttpClient extends Mock implements http.Client {} + +void main() { + late MockHttpClient mockHttpClient; + + setUp(() { + mockHttpClient = MockHttpClient(); + }); + + group('getRestaurants', () { + test('Should return null when status code is different from 200', () async { + when( + () => mockHttpClient.post( + Uri.parse('https://api.yelp.com/v3/graphql'), + headers: any(named: 'headers'), + body: any(named: 'body'), + ), + ).thenAnswer( + (_) async => http.Response('Error', 400), + ); + + final result = await RestaurantsRepository.getRestaurants(); + + expect(result, isNull); + }); + }); +} From 2c7a049e35d81e50601d21cbf44217cc50fa5c25 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 17:25:47 -0500 Subject: [PATCH 16/24] feat: unit tests implemented to info page --- .../data/restaurants_repository.dart | 41 +++++---- .../pages/restaurant_info_page_test.dart | 86 +++++++++++++++++++ 2 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 test/src/features/restaurant_tour/presentation/pages/restaurant_info_page_test.dart diff --git a/lib/src/features/restaurant_tour/data/restaurants_repository.dart b/lib/src/features/restaurant_tour/data/restaurants_repository.dart index 7067d09..9e3c293 100644 --- a/lib/src/features/restaurant_tour/data/restaurants_repository.dart +++ b/lib/src/features/restaurant_tour/data/restaurants_repository.dart @@ -1,4 +1,7 @@ -import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:restaurant_tour/src/constants/query.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; class RestaurantsRepository { @@ -16,25 +19,25 @@ class RestaurantsRepository { }; try { - // final response = await http.post( - // Uri.parse(_baseUrl), - // headers: headers, - // body: query(offset), - // ); - - // if (response.statusCode == 200) { - // _restaurantsResponse = RestaurantQueryResult.fromJson( - // jsonDecode(response.body)['data']['search'], - // ); - // return _restaurantsResponse; - // } else { - // print('Failed to load restaurants: ${response.statusCode}'); - // return null; - // } - return _restaurantsResponse = RestaurantQueryResult( - restaurants: mockRestaurants, - total: 5, + final response = await http.post( + Uri.parse(_baseUrl), + headers: headers, + body: query(offset), ); + + if (response.statusCode == 200) { + _restaurantsResponse = RestaurantQueryResult.fromJson( + jsonDecode(response.body)['data']['search'], + ); + return _restaurantsResponse; + } else { + print('Failed to load restaurants: ${response.statusCode}'); + return null; + } + // return _restaurantsResponse = RestaurantQueryResult( + // restaurants: mockRestaurants, + // total: 5, + // ); } catch (e) { print('Error fetching restaurants: $e'); return null; diff --git a/test/src/features/restaurant_tour/presentation/pages/restaurant_info_page_test.dart b/test/src/features/restaurant_tour/presentation/pages/restaurant_info_page_test.dart new file mode 100644 index 0000000..97bfcf7 --- /dev/null +++ b/test/src/features/restaurant_tour/presentation/pages/restaurant_info_page_test.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:network_image_mock/network_image_mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class MockSharedPreferences extends Mock implements SharedPreferences {} + +void main() { + late MockSharedPreferences mockPreferences; + late Restaurant testRestaurant; + + setUp(() { + mockPreferences = MockSharedPreferences(); + testRestaurant = mockRestaurants[0]; + }); + + Future buildPage(WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: RestaurantInfoPage(restaurant: testRestaurant), + ), + ); + } + + group( + 'Restaurant Info Page tests', + () { + testWidgets('Should display restaurant name in the AppBar', + (WidgetTester tester) async { + await buildPage(tester); + + await mockNetworkImagesFor(() => tester.pumpAndSettle()); + + expect(find.text('Pasta Paradise'), findsOneWidget); + }); + + testWidgets('Should display favorite icon as not selected initially', + (WidgetTester tester) async { + when(() => mockPreferences.getBool('1')).thenReturn(false); + await buildPage(tester); + + await mockNetworkImagesFor(() => tester.pumpAndSettle()); + final favoriteIcon = find.byIcon(Icons.favorite_outline); + expect(favoriteIcon, findsOneWidget); + }); + + testWidgets('Should navigate back when back button is pressed', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: RestaurantInfoPage(restaurant: testRestaurant), + ), + ); + + final backButton = find.byIcon(Icons.arrow_back); + await tester.tap(backButton); + await mockNetworkImagesFor( + () => tester.pump( + const Duration(seconds: 2), + ), + ); + + expect(find.byType(RestaurantInfoPage), findsOneWidget); + }); + + testWidgets('Should toggle favorite status when icon is tapped', + (WidgetTester tester) async { + when(() => mockPreferences.getBool('1')).thenReturn(false); + when(() => mockPreferences.setBool('1', true)) + .thenAnswer((_) async => true); + + await buildPage(tester); + + final favoriteIconButton = find.byIcon(Icons.favorite_outline); + expect(favoriteIconButton, findsOneWidget); + + await tester.tap(favoriteIconButton); + await mockNetworkImagesFor(() => tester.pumpAndSettle()); + }); + }, + ); +} From be87f585a46a2f8ae60e701fe7424b0506ab867a Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 17:35:54 -0500 Subject: [PATCH 17/24] fix: tests updated to info page --- .../pages/restaurant_info_page_test.dart | 99 ++++++++----------- 1 file changed, 42 insertions(+), 57 deletions(-) diff --git a/test/src/features/restaurant_tour/presentation/pages/restaurant_info_page_test.dart b/test/src/features/restaurant_tour/presentation/pages/restaurant_info_page_test.dart index 97bfcf7..04103f2 100644 --- a/test/src/features/restaurant_tour/presentation/pages/restaurant_info_page_test.dart +++ b/test/src/features/restaurant_tour/presentation/pages/restaurant_info_page_test.dart @@ -15,7 +15,11 @@ void main() { setUp(() { mockPreferences = MockSharedPreferences(); + SharedPreferences.setMockInitialValues({}); testRestaurant = mockRestaurants[0]; + + when(() => mockPreferences.getBool(testRestaurant.id ?? '')) + .thenReturn(false); }); Future buildPage(WidgetTester tester) async { @@ -26,61 +30,42 @@ void main() { ); } - group( - 'Restaurant Info Page tests', - () { - testWidgets('Should display restaurant name in the AppBar', - (WidgetTester tester) async { - await buildPage(tester); - - await mockNetworkImagesFor(() => tester.pumpAndSettle()); - - expect(find.text('Pasta Paradise'), findsOneWidget); - }); - - testWidgets('Should display favorite icon as not selected initially', - (WidgetTester tester) async { - when(() => mockPreferences.getBool('1')).thenReturn(false); - await buildPage(tester); - - await mockNetworkImagesFor(() => tester.pumpAndSettle()); - final favoriteIcon = find.byIcon(Icons.favorite_outline); - expect(favoriteIcon, findsOneWidget); - }); - - testWidgets('Should navigate back when back button is pressed', - (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: RestaurantInfoPage(restaurant: testRestaurant), - ), - ); - - final backButton = find.byIcon(Icons.arrow_back); - await tester.tap(backButton); - await mockNetworkImagesFor( - () => tester.pump( - const Duration(seconds: 2), - ), - ); - - expect(find.byType(RestaurantInfoPage), findsOneWidget); - }); - - testWidgets('Should toggle favorite status when icon is tapped', - (WidgetTester tester) async { - when(() => mockPreferences.getBool('1')).thenReturn(false); - when(() => mockPreferences.setBool('1', true)) - .thenAnswer((_) async => true); - - await buildPage(tester); - - final favoriteIconButton = find.byIcon(Icons.favorite_outline); - expect(favoriteIconButton, findsOneWidget); - - await tester.tap(favoriteIconButton); - await mockNetworkImagesFor(() => tester.pumpAndSettle()); - }); - }, - ); + group('Restaurant Info Page tests', () { + testWidgets('Should display restaurant name in the AppBar', + (WidgetTester tester) async { + await buildPage(tester); + await mockNetworkImagesFor(() => tester.pumpAndSettle()); + expect(find.text('Pasta Paradise'), findsOneWidget); + }); + + testWidgets('Should display favorite icon as not selected initially', + (WidgetTester tester) async { + await buildPage(tester); + await mockNetworkImagesFor(() => tester.pumpAndSettle()); + final favoriteIcon = find.byIcon(Icons.favorite_outline); + expect(favoriteIcon, findsOneWidget); + }); + + testWidgets('Should toggle favorite status when icon is tapped', + (WidgetTester tester) async { + // Simulate that initially, the restaurant is not a favorite + when(() => mockPreferences.getBool(testRestaurant.id ?? '')) + .thenReturn(false); + when(() => mockPreferences.setBool(testRestaurant.id ?? '', true)) + .thenAnswer((_) async => true); + when(() => mockPreferences.setBool(testRestaurant.id ?? '', false)) + .thenAnswer((_) async => true); + + await buildPage(tester); + + final favoriteIconButton = find.byIcon(Icons.favorite_outline); + expect(favoriteIconButton, findsOneWidget); + + // Tap the favorite icon to toggle the favorite status + await tester.tap(favoriteIconButton); + await mockNetworkImagesFor( + () => tester.pumpAndSettle(), + ); + }); + }); } From 26f25a6126e49c13ceb3301d75aaf5f70770240f Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 18:07:10 -0500 Subject: [PATCH 18/24] fix: tests updated and others added --- .../data/restaurants_repository.dart | 8 +- .../data/restaurant_repository_test.dart | 17 +++ .../models/restaurant_test.dart | 141 ++++++++++++++++++ .../view/favorite_restaurants_view_test.dart | 78 ++++++++-- 4 files changed, 224 insertions(+), 20 deletions(-) create mode 100644 test/src/features/restaurant_tour/models/restaurant_test.dart diff --git a/lib/src/features/restaurant_tour/data/restaurants_repository.dart b/lib/src/features/restaurant_tour/data/restaurants_repository.dart index 9e3c293..222d2ad 100644 --- a/lib/src/features/restaurant_tour/data/restaurants_repository.dart +++ b/lib/src/features/restaurant_tour/data/restaurants_repository.dart @@ -9,7 +9,7 @@ class RestaurantsRepository { 'y0RvKbozyu07RfpByqrdTJGyAOzhaNZH9T5X5pzBOoSh9uqOULc8h6yx89Z5nPjYtNaPHp9aqX0ZKF5pHSuYTeWcrYJS9r4EoHb7WmVLKPSmPW-L0FloXZJUInTkZnYx'; static const _baseUrl = 'https://api.yelp.com/v3/graphql'; - static RestaurantQueryResult? _restaurantsResponse; + static RestaurantQueryResult? restaurantsResponse; // API call to get restaurant information static Future getRestaurants({int offset = 0}) async { @@ -26,10 +26,10 @@ class RestaurantsRepository { ); if (response.statusCode == 200) { - _restaurantsResponse = RestaurantQueryResult.fromJson( + restaurantsResponse = RestaurantQueryResult.fromJson( jsonDecode(response.body)['data']['search'], ); - return _restaurantsResponse; + return restaurantsResponse; } else { print('Failed to load restaurants: ${response.statusCode}'); return null; @@ -48,7 +48,7 @@ class RestaurantsRepository { static List getFavoriteRestaurants(String id) { var favoriteRestaurants = []; - for (var restaurant in _restaurantsResponse!.restaurants!) { + for (var restaurant in restaurantsResponse?.restaurants ?? []) { if (restaurant.id == id) { favoriteRestaurants.add(restaurant); } diff --git a/test/src/features/restaurant_tour/data/restaurant_repository_test.dart b/test/src/features/restaurant_tour/data/restaurant_repository_test.dart index c50e5f7..702650f 100644 --- a/test/src/features/restaurant_tour/data/restaurant_repository_test.dart +++ b/test/src/features/restaurant_tour/data/restaurant_repository_test.dart @@ -1,15 +1,26 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:mocktail/mocktail.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; class MockHttpClient extends Mock implements http.Client {} void main() { late MockHttpClient mockHttpClient; + late Restaurant restaurant1; + late Restaurant restaurant2; setUp(() { mockHttpClient = MockHttpClient(); + restaurant1 = mockRestaurants[0]; + restaurant2 = mockRestaurants[1]; + + RestaurantsRepository.restaurantsResponse = RestaurantQueryResult( + restaurants: [restaurant1, restaurant2], + total: 2, + ); }); group('getRestaurants', () { @@ -28,5 +39,11 @@ void main() { expect(result, isNull); }); + test('Should return the restaurant with the matching id', () { + final result = RestaurantsRepository.getFavoriteRestaurants('100'); + + expect(result, contains(restaurant1)); + expect(result, isNot(contains(restaurant2))); + }); }); } diff --git a/test/src/features/restaurant_tour/models/restaurant_test.dart b/test/src/features/restaurant_tour/models/restaurant_test.dart new file mode 100644 index 0000000..47c703c --- /dev/null +++ b/test/src/features/restaurant_tour/models/restaurant_test.dart @@ -0,0 +1,141 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; + +void main() { + group('Category', () { + test('fromJson and toJson should work correctly', () { + final json = {'alias': 'mex', 'title': 'Mexican'}; + final category = Category.fromJson(json); + expect(category.alias, 'mex'); + expect(category.title, 'Mexican'); + expect(category.toJson(), json); + }); + }); + + group('Hours', () { + test('fromJson and toJson should work correctly', () { + final json = {'is_open_now': true}; + final hours = Hours.fromJson(json); + expect(hours.isOpenNow, true); + expect(hours.toJson(), json); + }); + }); + + group('User', () { + test('fromJson and toJson should work correctly', () { + final json = { + 'id': 'user1', + 'image_url': 'http://example.com/img.jpg', + 'name': 'John Doe', + }; + final user = User.fromJson(json); + expect(user.id, 'user1'); + expect(user.imageUrl, 'http://example.com/img.jpg'); + expect(user.name, 'John Doe'); + expect(user.toJson(), json); + }); + }); + + group('Review', () { + test('fromJson and toJson should work correctly', () { + final json = { + 'id': 'review1', + 'rating': 5, + 'text': 'Great place!', + 'user': { + 'id': 'user1', + 'image_url': 'http://example.com/img.jpg', + 'name': 'John Doe', + }, + }; + final review = Review.fromJson(json); + expect(review.id, 'review1'); + expect(review.rating, 5); + expect(review.user?.id, 'user1'); + expect(review.user?.imageUrl, 'http://example.com/img.jpg'); + expect(review.user?.name, 'John Doe'); + }); + }); + + group('Location', () { + test('fromJson and toJson should work correctly', () { + final json = {'formatted_address': '123 Main St, Anytown, USA'}; + final location = Location.fromJson(json); + expect(location.formattedAddress, '123 Main St, Anytown, USA'); + expect(location.toJson(), json); + }); + }); + + group('Restaurant', () { + test('fromJson and toJson should work correctly', () { + final json = { + 'id': 'restaurant1', + 'name': 'Test Restaurant', + 'price': '\$\$\$', + 'rating': 4.5, + 'photos': ['http://example.com/photo1.jpg'], + 'categories': [ + {'alias': 'mex', 'title': 'Mexican'}, + ], + 'hours': [ + {'is_open_now': true}, + ], + 'reviews': [ + { + 'id': 'review1', + 'rating': 5, + 'text': 'Great!', + 'user': {'id': 'user1'}, + } + ], + 'location': {'formatted_address': '123 Main St, Anytown, USA'}, + }; + final restaurant = Restaurant.fromJson(json); + expect(restaurant.id, 'restaurant1'); + expect(restaurant.name, 'Test Restaurant'); + expect(restaurant.price, '\$\$\$'); + expect(restaurant.rating, 4.5); + expect(restaurant.photos, ['http://example.com/photo1.jpg']); + expect(restaurant.categories?.first.alias, 'mex'); + expect(restaurant.categories?.first.title, 'Mexican'); + expect(restaurant.hours?.first.isOpenNow, true); + expect(restaurant.reviews?.first.id, 'review1'); + expect(restaurant.reviews?.first.rating, 5); + expect( + restaurant.location?.formattedAddress, + '123 Main St, Anytown, USA', + ); + }); + + test('displayCategory should return the first category title', () { + final restaurant = Restaurant(categories: [Category(title: 'Mexican')]); + expect(restaurant.displayCategory, 'Mexican'); + }); + + test('heroImage should return the first photo URL', () { + const restaurant = Restaurant(photos: ['http://example.com/photo1.jpg']); + expect(restaurant.heroImage, 'http://example.com/photo1.jpg'); + }); + + test('isOpen should return the status of the first hour', () { + const restaurant = Restaurant(hours: [Hours(isOpenNow: true)]); + expect(restaurant.isOpen, true); + }); + // }); + + group('RestaurantQueryResult', () { + test('fromJson and toJson should work correctly', () { + final json = { + 'total': 10, + 'business': [ + {'id': 'restaurant1', 'name': 'Test Restaurant'}, + ], + }; + final result = RestaurantQueryResult.fromJson(json); + expect(result.total, 10); + expect(result.restaurants?.first.id, 'restaurant1'); + expect(result.restaurants?.first.name, 'Test Restaurant'); + }); + }); + }); +} diff --git a/test/src/features/restaurant_tour/presentation/view/favorite_restaurants_view_test.dart b/test/src/features/restaurant_tour/presentation/view/favorite_restaurants_view_test.dart index eeeeb43..491bbe0 100644 --- a/test/src/features/restaurant_tour/presentation/view/favorite_restaurants_view_test.dart +++ b/test/src/features/restaurant_tour/presentation/view/favorite_restaurants_view_test.dart @@ -1,41 +1,87 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:network_image_mock/network_image_mock.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart'; import 'package:shared_preferences/shared_preferences.dart'; class MockSharedPreferences extends Mock implements SharedPreferences {} +class MockNavigatorObserver extends Mock implements NavigatorObserver {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); late MockSharedPreferences mockSharedPreferences; + late MockNavigatorObserver mockNavigatorObserver; setUp(() { mockSharedPreferences = MockSharedPreferences(); + mockNavigatorObserver = MockNavigatorObserver(); SharedPreferences.setMockInitialValues({}); }); group('Favorite Restaurants View tests', () { testWidgets( - 'FavoriteRestaurantsView displays empty message when no favorites', - (WidgetTester tester) async { - // Mock the SharedPreferences to return no keys - when(() => mockSharedPreferences.getKeys()).thenReturn({}); - when(() => mockSharedPreferences.getBool(any())).thenReturn(false); - - await tester.pumpWidget( - const MaterialApp( - home: Scaffold( - body: FavoriteRestaurantsView(), + 'FavoriteRestaurantsView displays empty message when no favorites', + (WidgetTester tester) async { + when(() => mockSharedPreferences.getKeys()).thenReturn({}); + when(() => mockSharedPreferences.getBool(any())).thenReturn(false); + + await tester.pumpWidget( + MaterialApp( + home: const Scaffold( + body: FavoriteRestaurantsView(), + ), + navigatorObservers: [mockNavigatorObserver], ), - ), - ); + ); + + expect(find.byIcon(Icons.restaurant), findsOneWidget); + expect(find.text(sorryText), findsOneWidget); + expect(find.text(noFavoriteRestaurantsText), findsOneWidget); + }, + ); + + testWidgets( + 'Should call _loadFavorites when navigating back with result true', + (WidgetTester tester) async { + when(() => mockSharedPreferences.getKeys()) + .thenReturn({'restaurant_id'}); + when(() => mockSharedPreferences.getBool('restaurant_id')) + .thenReturn(true); - expect(find.byIcon(Icons.restaurant), findsOneWidget); - expect(find.text(sorryText), findsOneWidget); - expect(find.text(noFavoriteRestaurantsText), findsOneWidget); - }); + await mockNetworkImagesFor( + () => tester.pumpWidget( + MaterialApp( + home: const Scaffold( + body: FavoriteRestaurantsView(), + ), + navigatorObservers: [mockNavigatorObserver], + ), + ), + ); + + final testRestaurant = mockRestaurants[0]; + await mockNetworkImagesFor( + () => tester.pumpAndSettle(const Duration(seconds: 2)), + ); + + await mockNetworkImagesFor( + () => tester.pumpWidget( + MaterialApp( + home: RestaurantInfoPage(restaurant: testRestaurant), + ), + ), + ); + Navigator.pop(tester.element(find.byType(RestaurantInfoPage)), true); + await mockNetworkImagesFor( + () => tester.pumpAndSettle(const Duration(seconds: 2)), + ); + }, + ); }); } From 8c49f7c827321ad85f3378a41203f7af3a80cacd Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 18:14:26 -0500 Subject: [PATCH 19/24] fix: code optimization --- lib/src/constants/constants.dart | 2 ++ .../restaurant_tour/data/restaurants_repository.dart | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 lib/src/constants/constants.dart diff --git a/lib/src/constants/constants.dart b/lib/src/constants/constants.dart new file mode 100644 index 0000000..e360bf9 --- /dev/null +++ b/lib/src/constants/constants.dart @@ -0,0 +1,2 @@ +const String apiKey = + 'D2Mo5V28goQrnb7yIy-PaOapJTt2qMTVthaUq8e3hmDyWYlteIh_WVkL_LSc0EiodqFZp-qMgdcQlyy-M5dRcv611pAXimF1IMZ2R5Mal-zYyQ43MLZogmxjnxfmZnYx'; diff --git a/lib/src/features/restaurant_tour/data/restaurants_repository.dart b/lib/src/features/restaurant_tour/data/restaurants_repository.dart index 222d2ad..5e4ef6d 100644 --- a/lib/src/features/restaurant_tour/data/restaurants_repository.dart +++ b/lib/src/features/restaurant_tour/data/restaurants_repository.dart @@ -1,12 +1,12 @@ import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:restaurant_tour/src/constants/constants.dart'; import 'package:restaurant_tour/src/constants/query.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; class RestaurantsRepository { - static const _apiKey = - 'y0RvKbozyu07RfpByqrdTJGyAOzhaNZH9T5X5pzBOoSh9uqOULc8h6yx89Z5nPjYtNaPHp9aqX0ZKF5pHSuYTeWcrYJS9r4EoHb7WmVLKPSmPW-L0FloXZJUInTkZnYx'; + static const _apiKey = apiKey; static const _baseUrl = 'https://api.yelp.com/v3/graphql'; static RestaurantQueryResult? restaurantsResponse; @@ -34,6 +34,7 @@ class RestaurantsRepository { print('Failed to load restaurants: ${response.statusCode}'); return null; } + // TODO: Uncomment if you want to use the app with Mocks // return _restaurantsResponse = RestaurantQueryResult( // restaurants: mockRestaurants, // total: 5, From ae6344883c3b9a7e519aee265f52f5c9d37442ce Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 19:21:28 -0500 Subject: [PATCH 20/24] feat: constants file created --- lib/src/constants/constants.dart | 1 + .../features/restaurant_tour/data/restaurants_repository.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/constants/constants.dart b/lib/src/constants/constants.dart index e360bf9..5e8d0a5 100644 --- a/lib/src/constants/constants.dart +++ b/lib/src/constants/constants.dart @@ -1,2 +1,3 @@ const String apiKey = 'D2Mo5V28goQrnb7yIy-PaOapJTt2qMTVthaUq8e3hmDyWYlteIh_WVkL_LSc0EiodqFZp-qMgdcQlyy-M5dRcv611pAXimF1IMZ2R5Mal-zYyQ43MLZogmxjnxfmZnYx'; +const String baseUrl = 'https://api.yelp.com/v3/graphql'; diff --git a/lib/src/features/restaurant_tour/data/restaurants_repository.dart b/lib/src/features/restaurant_tour/data/restaurants_repository.dart index 5e4ef6d..331cc4a 100644 --- a/lib/src/features/restaurant_tour/data/restaurants_repository.dart +++ b/lib/src/features/restaurant_tour/data/restaurants_repository.dart @@ -7,7 +7,7 @@ import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.d class RestaurantsRepository { static const _apiKey = apiKey; - static const _baseUrl = 'https://api.yelp.com/v3/graphql'; + static const _baseUrl = baseUrl; static RestaurantQueryResult? restaurantsResponse; From fd8eafe7642c397d4260ff5b2fc97272cc0867a5 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 19:24:19 -0500 Subject: [PATCH 21/24] fix: Delete query.dart.gcov.html --- .../lib/src/constants/query.dart.gcov.html | 110 ------------------ 1 file changed, 110 deletions(-) delete mode 100644 coverage/html/Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants/query.dart.gcov.html diff --git a/coverage/html/Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants/query.dart.gcov.html b/coverage/html/Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants/query.dart.gcov.html deleted file mode 100644 index a18aafe..0000000 --- a/coverage/html/Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants/query.dart.gcov.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - LCOV - lcov.info - Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants/query.dart - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - /Users/daniel.esquivel/Desktop/flutter_test/lib/src/constants - query.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %22
Test Date:2024-09-14 17:01:13Functions:-00
-
- - - - - - - - -

-
            Line data    Source code
-
-       1            2 : String query(int offset) => '''
-       2              :   query getRestaurants {
-       3              :     search(location: "Las Vegas", limit: 20, offset: $offset) {
-       4              :       total    
-       5              :       business {
-       6              :         id
-       7              :         name
-       8              :         price
-       9              :         rating
-      10              :         photos
-      11              :         reviews {
-      12              :           id
-      13              :           rating
-      14              :           text
-      15              :           user {
-      16              :             id
-      17              :             image_url
-      18              :             name
-      19              :           }
-      20              :         }
-      21              :         categories {
-      22              :           title
-      23              :           alias
-      24              :         }
-      25              :         hours {
-      26              :           is_open_now
-      27              :         }
-      28              :         location {
-      29              :           formatted_address
-      30              :         }
-      31              :       }
-      32              :     }
-      33              :   }
-      34            2 :   ''';
-        
-
-
- - - - -
Generated by: LCOV version 2.1-1
-
- - - From f5e78a4400709858fd1e27074e707ba3478b9c41 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 21:05:20 -0500 Subject: [PATCH 22/24] feat: riverpod implemented --- lib/main.dart | 3 +- lib/src/constants/constants.dart | 2 +- .../datasources/restaurants_datasource.dart | 5 ++ .../repositories/restaurant_repository.dart | 5 ++ .../restaurant_api_datasource.dart} | 36 +++++--------- .../restaurant_repository_impl.dart | 14 ++++++ .../providers/restaurants_provider.dart | 42 ++++++++++++++++ .../restaurants_repository_provider.dart | 7 +++ .../view/all_restaurants_view.dart | 16 +++--- .../view/favorite_restaurants_view.dart | 15 +++--- pubspec.lock | 24 +++++++++ pubspec.yaml | 1 + .../data/restaurant_repository_test.dart | 49 ------------------- 13 files changed, 130 insertions(+), 89 deletions(-) create mode 100644 lib/src/features/restaurant_tour/domain/datasources/restaurants_datasource.dart create mode 100644 lib/src/features/restaurant_tour/domain/repositories/restaurant_repository.dart rename lib/src/features/restaurant_tour/{data/restaurants_repository.dart => infrastructure/datasources/restaurant_api_datasource.dart} (50%) create mode 100644 lib/src/features/restaurant_tour/infrastructure/repositories/restaurant_repository_impl.dart create mode 100644 lib/src/features/restaurant_tour/presentation/providers/restaurants_provider.dart create mode 100644 lib/src/features/restaurant_tour/presentation/providers/restaurants_repository_provider.dart delete mode 100644 test/src/features/restaurant_tour/data/restaurant_repository_test.dart diff --git a/lib/main.dart b/lib/main.dart index 7f4f29e..0b6b173 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/pages/restaurant_tour_page.dart'; void main() { - runApp(const RestaurantTour()); + runApp(const ProviderScope(child: RestaurantTour())); } class RestaurantTour extends StatelessWidget { diff --git a/lib/src/constants/constants.dart b/lib/src/constants/constants.dart index 5e8d0a5..de0651a 100644 --- a/lib/src/constants/constants.dart +++ b/lib/src/constants/constants.dart @@ -1,3 +1,3 @@ const String apiKey = - 'D2Mo5V28goQrnb7yIy-PaOapJTt2qMTVthaUq8e3hmDyWYlteIh_WVkL_LSc0EiodqFZp-qMgdcQlyy-M5dRcv611pAXimF1IMZ2R5Mal-zYyQ43MLZogmxjnxfmZnYx'; + 'y0RvKbozyu07RfpByqrdTJGyAOzhaNZH9T5X5pzBOoSh9uqOULc8h6yx89Z5nPjYtNaPHp9aqX0ZKF5pHSuYTeWcrYJS9r4EoHb7WmVLKPSmPW-L0FloXZJUInTkZnYx'; const String baseUrl = 'https://api.yelp.com/v3/graphql'; diff --git a/lib/src/features/restaurant_tour/domain/datasources/restaurants_datasource.dart b/lib/src/features/restaurant_tour/domain/datasources/restaurants_datasource.dart new file mode 100644 index 0000000..59ff81a --- /dev/null +++ b/lib/src/features/restaurant_tour/domain/datasources/restaurants_datasource.dart @@ -0,0 +1,5 @@ +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; + +abstract class RestaurantsDatasource { + Future getRestaurants(); +} diff --git a/lib/src/features/restaurant_tour/domain/repositories/restaurant_repository.dart b/lib/src/features/restaurant_tour/domain/repositories/restaurant_repository.dart new file mode 100644 index 0000000..7c92487 --- /dev/null +++ b/lib/src/features/restaurant_tour/domain/repositories/restaurant_repository.dart @@ -0,0 +1,5 @@ +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; + +abstract class RestaurantRepository { + Future getRestaurants(); +} diff --git a/lib/src/features/restaurant_tour/data/restaurants_repository.dart b/lib/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource.dart similarity index 50% rename from lib/src/features/restaurant_tour/data/restaurants_repository.dart rename to lib/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource.dart index 331cc4a..0524837 100644 --- a/lib/src/features/restaurant_tour/data/restaurants_repository.dart +++ b/lib/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource.dart @@ -2,17 +2,17 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:restaurant_tour/src/constants/constants.dart'; -import 'package:restaurant_tour/src/constants/query.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/domain/datasources/restaurants_datasource.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; -class RestaurantsRepository { +import '../../../../constants/query.dart'; + +class RestaurantApiDatasource extends RestaurantsDatasource { static const _apiKey = apiKey; static const _baseUrl = baseUrl; - static RestaurantQueryResult? restaurantsResponse; - -// API call to get restaurant information - static Future getRestaurants({int offset = 0}) async { + @override + Future getRestaurants({int offset = 0}) async { final headers = { 'Authorization': 'Bearer $_apiKey', 'Content-Type': 'application/graphql', @@ -26,34 +26,22 @@ class RestaurantsRepository { ); if (response.statusCode == 200) { - restaurantsResponse = RestaurantQueryResult.fromJson( + final restaurantResponse = RestaurantQueryResult.fromJson( jsonDecode(response.body)['data']['search'], ); - return restaurantsResponse; + return restaurantResponse; } else { - print('Failed to load restaurants: ${response.statusCode}'); - return null; + return const RestaurantQueryResult(restaurants: [], total: 0); } // TODO: Uncomment if you want to use the app with Mocks - // return _restaurantsResponse = RestaurantQueryResult( + // final restaurantsResponse = RestaurantQueryResult( // restaurants: mockRestaurants, // total: 5, // ); + // return restaurantsResponse; } catch (e) { print('Error fetching restaurants: $e'); - return null; - } - } - -// Method to validate favorite restaurants - static List getFavoriteRestaurants(String id) { - var favoriteRestaurants = []; - - for (var restaurant in restaurantsResponse?.restaurants ?? []) { - if (restaurant.id == id) { - favoriteRestaurants.add(restaurant); - } + rethrow; } - return favoriteRestaurants; } } diff --git a/lib/src/features/restaurant_tour/infrastructure/repositories/restaurant_repository_impl.dart b/lib/src/features/restaurant_tour/infrastructure/repositories/restaurant_repository_impl.dart new file mode 100644 index 0000000..642fca0 --- /dev/null +++ b/lib/src/features/restaurant_tour/infrastructure/repositories/restaurant_repository_impl.dart @@ -0,0 +1,14 @@ +import 'package:restaurant_tour/src/features/restaurant_tour/domain/datasources/restaurants_datasource.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/domain/repositories/restaurant_repository.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; + +class RestaurantRepositoryImpl extends RestaurantRepository { + final RestaurantsDatasource datasource; + + RestaurantRepositoryImpl(this.datasource); + + @override + Future getRestaurants() { + return datasource.getRestaurants(); + } +} diff --git a/lib/src/features/restaurant_tour/presentation/providers/restaurants_provider.dart b/lib/src/features/restaurant_tour/presentation/providers/restaurants_provider.dart new file mode 100644 index 0000000..11a6e66 --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/providers/restaurants_provider.dart @@ -0,0 +1,42 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/providers/restaurants_repository_provider.dart'; + +final getRestaurantsProvider = + StateNotifierProvider( + (ref) { + final restaurantsResponse = + ref.watch(restaurantsRepositoryProvider).getRestaurants; + return RestaurantsNotifier(restaurantsCallBack: restaurantsResponse); + }, +); + +typedef RestaurantsCallBack = Future Function(); + +class RestaurantsNotifier extends StateNotifier { + RestaurantsCallBack restaurantsCallBack; + + RestaurantsNotifier({required this.restaurantsCallBack}) + : super( + const RestaurantQueryResult( + restaurants: [], + total: 0, + ), + ); + Future loadRestaurants() async { + final RestaurantQueryResult restaurants = await restaurantsCallBack(); + state = restaurants; + return state; + } + + List getFavoriteRestaurants(String id) { + var favoriteRestaurants = []; + + for (var restaurant in state.restaurants ?? []) { + if (restaurant.id == id) { + favoriteRestaurants.add(restaurant); + } + } + return favoriteRestaurants; + } +} diff --git a/lib/src/features/restaurant_tour/presentation/providers/restaurants_repository_provider.dart b/lib/src/features/restaurant_tour/presentation/providers/restaurants_repository_provider.dart new file mode 100644 index 0000000..121c212 --- /dev/null +++ b/lib/src/features/restaurant_tour/presentation/providers/restaurants_repository_provider.dart @@ -0,0 +1,7 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/infrastructure/repositories/restaurant_repository_impl.dart'; + +final restaurantsRepositoryProvider = Provider((ref) { + return RestaurantRepositoryImpl(RestaurantApiDatasource()); +}); diff --git a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart index bc51061..9910115 100644 --- a/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart @@ -1,17 +1,18 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/providers/restaurants_provider.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; -class AllRestaurantsView extends StatefulWidget { +class AllRestaurantsView extends ConsumerStatefulWidget { const AllRestaurantsView({super.key}); @override - State createState() => _AllRestaurantsViewState(); + ConsumerState createState() => _AllRestaurantsViewState(); } -class _AllRestaurantsViewState extends State { +class _AllRestaurantsViewState extends ConsumerState { late List restaurants; bool _dataNotFound = false; @@ -25,10 +26,9 @@ class _AllRestaurantsViewState extends State { Future _fetchRestaurants() async { try { - final result = await RestaurantsRepository.getRestaurants(); - if (result != null && - result.restaurants != null && - result.restaurants!.isNotEmpty) { + final result = + await ref.read(getRestaurantsProvider.notifier).loadRestaurants(); + if (result.restaurants != null && result.restaurants!.isNotEmpty) { restaurants = result.restaurants!; if (mounted) { setState(() { diff --git a/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart index 14d6cd2..abceaba 100644 --- a/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart +++ b/lib/src/features/restaurant_tour/presentation/view/favorite_restaurants_view.dart @@ -1,21 +1,23 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:restaurant_tour/src/constants/strings.dart'; import 'package:restaurant_tour/src/constants/typography.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/pages/restaurant_info_page.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/providers/restaurants_provider.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/presentation/widgets/restaurant_card.dart'; import 'package:shared_preferences/shared_preferences.dart'; -class FavoriteRestaurantsView extends StatefulWidget { +class FavoriteRestaurantsView extends ConsumerStatefulWidget { const FavoriteRestaurantsView({super.key}); @override - State createState() => + ConsumerState createState() => _FavoriteRestaurantsViewState(); } -class _FavoriteRestaurantsViewState extends State { +class _FavoriteRestaurantsViewState + extends ConsumerState { List _favoriteRestaurants = []; @override @@ -61,8 +63,9 @@ class _FavoriteRestaurantsViewState extends State { final isFavorite = favoriteRestaurants.getBool(key) ?? false; if (isFavorite) { - var favoriteRestaurant = - RestaurantsRepository.getFavoriteRestaurants(key); + var favoriteRestaurant = ref + .read(getRestaurantsProvider.notifier) + .getFavoriteRestaurants(key); accumulatedFavorites.addAll(favoriteRestaurant); } } diff --git a/pubspec.lock b/pubspec.lock index 8eb785a..a4973d4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -222,6 +222,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d" + url: "https://pub.dev" + source: hosted + version: "2.5.1" flutter_test: dependency: "direct dev" description: flutter @@ -488,6 +496,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d + url: "https://pub.dev" + source: hosted + version: "2.5.1" shared_preferences: dependency: "direct main" description: @@ -597,6 +613,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" stream_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dc7b887..0f84379 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: file: ^7.0.0 flutter: sdk: flutter + flutter_riverpod: ^2.5.1 http: ^1.2.2 json_annotation: ^4.9.0 mocktail: ^1.0.4 diff --git a/test/src/features/restaurant_tour/data/restaurant_repository_test.dart b/test/src/features/restaurant_tour/data/restaurant_repository_test.dart deleted file mode 100644 index 702650f..0000000 --- a/test/src/features/restaurant_tour/data/restaurant_repository_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:http/http.dart' as http; -import 'package:mocktail/mocktail.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/data/restaurants_repository.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; - -class MockHttpClient extends Mock implements http.Client {} - -void main() { - late MockHttpClient mockHttpClient; - late Restaurant restaurant1; - late Restaurant restaurant2; - - setUp(() { - mockHttpClient = MockHttpClient(); - restaurant1 = mockRestaurants[0]; - restaurant2 = mockRestaurants[1]; - - RestaurantsRepository.restaurantsResponse = RestaurantQueryResult( - restaurants: [restaurant1, restaurant2], - total: 2, - ); - }); - - group('getRestaurants', () { - test('Should return null when status code is different from 200', () async { - when( - () => mockHttpClient.post( - Uri.parse('https://api.yelp.com/v3/graphql'), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response('Error', 400), - ); - - final result = await RestaurantsRepository.getRestaurants(); - - expect(result, isNull); - }); - test('Should return the restaurant with the matching id', () { - final result = RestaurantsRepository.getFavoriteRestaurants('100'); - - expect(result, contains(restaurant1)); - expect(result, isNot(contains(restaurant2))); - }); - }); -} From c31cc46ce60c54e28658ea3b5a6b25220ab63c1a Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 22:12:47 -0500 Subject: [PATCH 23/24] feat: tests implemented --- .../restaurant_api_datasource.dart | 3 +- .../restaurant_repository_impl_test.dart | 33 +++++++++++ .../restaurant_api_datasource_test.dart | 45 +++++++++++++++ .../providers/restaurants_provider_test.dart | 57 +++++++++++++++++++ .../restaurants_repository_provider_test.dart | 20 +++++++ 5 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 test/src/features/restaurant_tour/domain/repositories/restaurant_repository_impl_test.dart create mode 100644 test/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource_test.dart create mode 100644 test/src/features/restaurant_tour/presentation/providers/restaurants_provider_test.dart create mode 100644 test/src/features/restaurant_tour/presentation/providers/restaurants_repository_provider_test.dart diff --git a/lib/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource.dart b/lib/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource.dart index 0524837..bf63de9 100644 --- a/lib/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource.dart +++ b/lib/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource.dart @@ -2,11 +2,10 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:restaurant_tour/src/constants/constants.dart'; +import 'package:restaurant_tour/src/constants/query.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/domain/datasources/restaurants_datasource.dart'; import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; -import '../../../../constants/query.dart'; - class RestaurantApiDatasource extends RestaurantsDatasource { static const _apiKey = apiKey; static const _baseUrl = baseUrl; diff --git a/test/src/features/restaurant_tour/domain/repositories/restaurant_repository_impl_test.dart b/test/src/features/restaurant_tour/domain/repositories/restaurant_repository_impl_test.dart new file mode 100644 index 0000000..c93d5e4 --- /dev/null +++ b/test/src/features/restaurant_tour/domain/repositories/restaurant_repository_impl_test.dart @@ -0,0 +1,33 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/domain/datasources/restaurants_datasource.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/infrastructure/repositories/restaurant_repository_impl.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; + +class MockRestaurantsDataSource extends Mock implements RestaurantsDatasource {} + +void main() { + group( + 'RestaurantRepositoryImpl tests', + () { + late RestaurantRepositoryImpl restaurantRepositoryImpl; + late MockRestaurantsDataSource mockRestaurantsDataSource; + + setUp(() { + mockRestaurantsDataSource = MockRestaurantsDataSource(); + restaurantRepositoryImpl = + RestaurantRepositoryImpl(mockRestaurantsDataSource); + }); + + test('getRestaurants call datasource', () async { + const restaurantsResponse = + RestaurantQueryResult(restaurants: [], total: 0); + when(() => mockRestaurantsDataSource.getRestaurants()).thenAnswer( + (_) => Future.value(restaurantsResponse), + ); + final result = await restaurantRepositoryImpl.getRestaurants(); + expect(result.restaurants, restaurantsResponse.restaurants); + }); + }, + ); +} diff --git a/test/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource_test.dart b/test/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource_test.dart new file mode 100644 index 0000000..0a7ffd2 --- /dev/null +++ b/test/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource_test.dart @@ -0,0 +1,45 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:mocktail/mocktail.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource.dart'; + +class MockClient extends Mock implements http.Client {} + +void main() { + late MockClient mockClient; + late RestaurantApiDatasource datasource; + + setUp(() { + mockClient = MockClient(); + datasource = RestaurantApiDatasource(); + }); + + group('RestaurantApiDatasource', () { + test( + 'returns RestaurantQueryResult if the http call completes successfully', + () async { + when( + () => mockClient.post( + Uri.parse('https://example.com'), + headers: any(named: 'headers'), + body: any(named: 'body'), + ), + ).thenAnswer( + (_) async => http.Response( + jsonEncode({ + mockQueryResult, + }), + 200, + ), + ); + + final result = await datasource.getRestaurants(); + + expect(result.restaurants, isEmpty); + expect(result.total, 0); + }); + }); +} diff --git a/test/src/features/restaurant_tour/presentation/providers/restaurants_provider_test.dart b/test/src/features/restaurant_tour/presentation/providers/restaurants_provider_test.dart new file mode 100644 index 0000000..26d7793 --- /dev/null +++ b/test/src/features/restaurant_tour/presentation/providers/restaurants_provider_test.dart @@ -0,0 +1,57 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/data/mock.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/domain/repositories/restaurant_repository.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/providers/restaurants_provider.dart'; + +class MockRestaurantRepository extends Mock implements RestaurantRepository {} + +void main() { + group('RestaurantNotifier', () { + late MockRestaurantRepository mockRepository; + + setUp(() { + mockRepository = MockRestaurantRepository(); + }); + test('currentCoffeeImageProvider returns correct CoffeeResponse', () { + var response = mockQueryResult; + + final container = ProviderContainer( + overrides: [], + ); + + container.read(getRestaurantsProvider); + + expect(mockQueryResult, response); + }); + + test('load restaurants correctly', () async { + final notifier = RestaurantsNotifier( + restaurantsCallBack: () { + return Future.value(mockQueryResult); + }, + ); + final response = mockQueryResult; + when(() => mockRepository.getRestaurants()) + .thenAnswer((_) => Future.value(response)); + await notifier.loadRestaurants(); + + expect(notifier.state, response); + }); + + test('get favorite restaurants correctly', () async { + final notifier = RestaurantsNotifier( + restaurantsCallBack: () { + return Future.value(mockQueryResult); + }, + ); + final response = mockQueryResult; + when(() => mockRepository.getRestaurants()) + .thenAnswer((_) => Future.value(response)); + var favorite = notifier.getFavoriteRestaurants('100'); + + expect(notifier.state.restaurants, favorite); + }); + }); +} diff --git a/test/src/features/restaurant_tour/presentation/providers/restaurants_repository_provider_test.dart b/test/src/features/restaurant_tour/presentation/providers/restaurants_repository_provider_test.dart new file mode 100644 index 0000000..9b6c0aa --- /dev/null +++ b/test/src/features/restaurant_tour/presentation/providers/restaurants_repository_provider_test.dart @@ -0,0 +1,20 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/infrastructure/datasources/restaurant_api_datasource.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/infrastructure/repositories/restaurant_repository_impl.dart'; +import 'package:restaurant_tour/src/features/restaurant_tour/presentation/providers/restaurants_repository_provider.dart'; + +void main() { + test( + 'restaurantsRepositoryProvider returns RestaurantRepositoryImpl instance', + () { + final container = ProviderContainer(); + + final repository = container.read(restaurantsRepositoryProvider); + + expect(repository, isA()); + + final datasource = (repository).datasource; + expect(datasource, isA()); + }); +} From 5178dba248fcee2b8020a3700f62b0819cdd505f Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Esquivel Correa Date: Sat, 14 Sep 2024 22:43:50 -0500 Subject: [PATCH 24/24] fix: file deleted --- .../view/all_restaurants_view_test.dart | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 test/src/features/restaurant_tour/presentation/view/all_restaurants_view_test.dart diff --git a/test/src/features/restaurant_tour/presentation/view/all_restaurants_view_test.dart b/test/src/features/restaurant_tour/presentation/view/all_restaurants_view_test.dart deleted file mode 100644 index d39b9ed..0000000 --- a/test/src/features/restaurant_tour/presentation/view/all_restaurants_view_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:network_image_mock/network_image_mock.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/models/restaurant.dart'; -import 'package:restaurant_tour/src/features/restaurant_tour/presentation/view/all_restaurants_view.dart'; - -void main() { - setUpAll(() { - registerFallbackValue( - const RestaurantQueryResult( - restaurants: [], - total: 0, - ), - ); - }); - - group( - 'All Restaurants View tests', - () { - testWidgets('should display progress indicator', - (WidgetTester tester) async { - await mockNetworkImagesFor( - () => tester.pumpWidget( - const MaterialApp( - home: Scaffold( - body: AllRestaurantsView(), - ), - ), - ), - ); - - expect(find.byType(CircularProgressIndicator), findsOneWidget); - }); - }, - ); -}