From 0fcf1f3d1934978595042f6dd82875d0703cef60 Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Sun, 15 Sep 2024 12:03:08 -0400 Subject: [PATCH 01/12] feat(domain): implement restaurants use case Add RestaurantsUsecase class Implement methods for fetching restaurants and managing favorites Integrate with RestaurantsRepository add memory implementations for data source interfaces add test for the use case --- .fvm/fvm_config.json | 3 +- .fvmrc | 4 + .gitignore | 4 +- .vscode/settings.json | 14 +- lib/common/exceptions/exceptions.dart | 19 ++ ...emory_favorite_restaurants_datasource.dart | 25 ++ .../memory_restaurants_datasource.dart | 81 +++++ .../favorite_restaurants_datasource.dart | 8 + .../datasource/restaurants_datasource.dart | 8 + .../repository/restaurants_repository.dart | 30 ++ lib/domain/usecase/restaurants_usecase.dart | 25 ++ lib/models/restaurant.g.dart | 6 +- pubspec.lock | 279 ++++++++++++------ pubspec.yaml | 2 + .../usecase/restaurants_usecase_test.dart | 58 ++++ .../restaurants_usecase_test.mocks.dart | 99 +++++++ 16 files changed, 560 insertions(+), 105 deletions(-) create mode 100644 .fvmrc create mode 100644 lib/common/exceptions/exceptions.dart create mode 100644 lib/data/datasource/memory_favorite_restaurants_datasource.dart create mode 100644 lib/data/datasource/memory_restaurants_datasource.dart create mode 100644 lib/domain/datasource/favorite_restaurants_datasource.dart create mode 100644 lib/domain/datasource/restaurants_datasource.dart create mode 100644 lib/domain/repository/restaurants_repository.dart create mode 100644 lib/domain/usecase/restaurants_usecase.dart create mode 100644 test/domain/usecase/restaurants_usecase_test.dart create mode 100644 test/domain/usecase/restaurants_usecase_test.mocks.dart diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index 160b5b2..305f34d 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,4 +1,3 @@ { - "flutterSdkVersion": "3.22.3", - "flavors": {} + "flutterSdkVersion": "3.24.3" } \ No newline at end of file diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 0000000..c62692b --- /dev/null +++ b/.fvmrc @@ -0,0 +1,4 @@ +{ + "flutter": "3.24.3", + "flavors": {} +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1be2d87..7040cb0 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,6 @@ app.*.map.json /android/app/release # fvm -.fvm/flutter_sdk \ No newline at end of file + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index f285aa4..ceaf9a6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,9 @@ { - "dart.flutterSdkPath": ".fvm/flutter_sdk", - "search.exclude": { - "**/.fvm": true - }, - "files.watcherExclude": { - "**/.fvm": true - } + "dart.flutterSdkPath": ".fvm/versions/3.24.3", + "search.exclude": { + "**/.fvm": true + }, + "files.watcherExclude": { + "**/.fvm": true + } } \ No newline at end of file diff --git a/lib/common/exceptions/exceptions.dart b/lib/common/exceptions/exceptions.dart new file mode 100644 index 0000000..f5e9a74 --- /dev/null +++ b/lib/common/exceptions/exceptions.dart @@ -0,0 +1,19 @@ +class AppException implements Exception { + String code; + String description; + AppException(this.code, this.description); + @override + String toString() { + return { + 'code': code, + 'description': description, + }.toString(); + } +} + +class RestaurantNotFoundException extends AppException { + RestaurantNotFoundException({ + code = "Restaurant not found", + description = "The restaurant searched is not our dataset", + }) : super(code, description); +} diff --git a/lib/data/datasource/memory_favorite_restaurants_datasource.dart b/lib/data/datasource/memory_favorite_restaurants_datasource.dart new file mode 100644 index 0000000..72b7062 --- /dev/null +++ b/lib/data/datasource/memory_favorite_restaurants_datasource.dart @@ -0,0 +1,25 @@ +import 'package:restaurant_tour/domain/datasource/favorite_restaurants_datasource.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; + +class MemoryFavoriteRestaurantsDatasource + implements FavoriteRestaurantsDatasource { + final List _favoriteRestaurantsIds = []; + @override + Future addFavoriteRestaurant(Restaurant restaurant) async { + if (restaurant.id != null && restaurant.id!.isNotEmpty) { + _favoriteRestaurantsIds.add(restaurant.id!); + } + } + + @override + Future> getListRestaurantsIds() { + return Future.value(_favoriteRestaurantsIds); + } + + @override + Future removeFavoriteRestaurant(Restaurant restaurant) async { + if (restaurant.id != null && restaurant.id!.isNotEmpty) { + _favoriteRestaurantsIds.remove(restaurant.id!); + } + } +} diff --git a/lib/data/datasource/memory_restaurants_datasource.dart b/lib/data/datasource/memory_restaurants_datasource.dart new file mode 100644 index 0000000..75af7e6 --- /dev/null +++ b/lib/data/datasource/memory_restaurants_datasource.dart @@ -0,0 +1,81 @@ +import 'package:restaurant_tour/common/exceptions/exceptions.dart'; +import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; + +class MemoryRestaurantsDatasource implements RestaurantsDatasource { + List restaurants = [ + Restaurant( + id: "restaurant_1", + name: "Amazing Pizza", + price: "\$15-\$20", + rating: 4.8, + photos: ["https://example.com/pizza.jpg"], + categories: [Category(title: "Italian")], + hours: [const Hours(isOpenNow: true)], + location: Location(formattedAddress: "123 Main St, Anytown, CA"), + ), + Restaurant( + id: "restaurant_2", + name: "Cozy Sushi", + price: "\$20-\$30", + rating: 4.7, + photos: ["https://example.com/sushi.jpg"], + categories: [Category(title: "Japanese")], + hours: [const Hours(isOpenNow: false)], + location: Location(formattedAddress: "456 Elm St, Anytown, CA"), + ), + Restaurant( + id: "restaurant_3", + name: "Burger Barn", + price: "\$10-\$15", + rating: 4.2, + photos: ["https://example.com/burger.jpg"], + categories: [Category(title: "American")], + hours: [const Hours(isOpenNow: true)], + location: Location(formattedAddress: "789 Maple St, Anytown, CA"), + ), + Restaurant( + id: "restaurant_4", + name: "Healthy Bowls", + price: "\$12-\$18", + rating: 4.9, + photos: ["https://example.com/salad.jpg"], + categories: [Category(title: "Healthy")], + hours: [const Hours(isOpenNow: false)], + location: Location(formattedAddress: "1011 Oak St, Anytown, CA"), + ), + Restaurant( + id: "restaurant_5", + name: "Taco Fiesta", + price: "\$8-\$12", + rating: 4.5, + photos: ["https://example.com/tacos.jpg"], + categories: [Category(title: "Mexican")], + hours: [const Hours(isOpenNow: true)], + location: Location(formattedAddress: "1234 Pine St, Anytown, CA"), + ), + ]; + @override + Future getRestaurant(String id) { + try { + return Future.value( + restaurants.where((restaurant) => restaurant.id == id).first, + ); + } on StateError { + throw RestaurantNotFoundException(); + } + } + + @override + Future> getRestaurants({ + List filterByIds = const [], + }) { + return Future.value( + filterByIds.isEmpty + ? restaurants + : restaurants + .where((restaurant) => filterByIds.contains(restaurant.id)) + .toList(), + ); + } +} diff --git a/lib/domain/datasource/favorite_restaurants_datasource.dart b/lib/domain/datasource/favorite_restaurants_datasource.dart new file mode 100644 index 0000000..8e42058 --- /dev/null +++ b/lib/domain/datasource/favorite_restaurants_datasource.dart @@ -0,0 +1,8 @@ +import 'package:restaurant_tour/models/restaurant.dart'; + +abstract class FavoriteRestaurantsDatasource { + Future addFavoriteRestaurant(Restaurant restaurant); + Future removeFavoriteRestaurant(Restaurant restaurant); + //TODO(darwin): Review this and decide if returning List or List of restaurants. + Future> getListRestaurantsIds(); +} diff --git a/lib/domain/datasource/restaurants_datasource.dart b/lib/domain/datasource/restaurants_datasource.dart new file mode 100644 index 0000000..323cfcb --- /dev/null +++ b/lib/domain/datasource/restaurants_datasource.dart @@ -0,0 +1,8 @@ +import 'package:restaurant_tour/models/restaurant.dart'; + +abstract class RestaurantsDatasource { + Future> getRestaurants({ + List filterByIds = const [], + }); + Future getRestaurant(String id); +} diff --git a/lib/domain/repository/restaurants_repository.dart b/lib/domain/repository/restaurants_repository.dart new file mode 100644 index 0000000..4cd3e9f --- /dev/null +++ b/lib/domain/repository/restaurants_repository.dart @@ -0,0 +1,30 @@ +import 'package:restaurant_tour/domain/datasource/favorite_restaurants_datasource.dart'; +import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; + +class RestaurantsRepository { + const RestaurantsRepository( + this._restaurantsDatasource, + this._favoriteRestaurantsDatasource, + ); + + final FavoriteRestaurantsDatasource _favoriteRestaurantsDatasource; + final RestaurantsDatasource _restaurantsDatasource; + Future> getRestaurants({ + List filterByIds = const [], + }) { + return _restaurantsDatasource.getRestaurants(filterByIds: filterByIds); + } + + Future getRestaurant(String id) { + return _restaurantsDatasource.getRestaurant(id); + } + + Future addFavoriteRestaurant(Restaurant restaurant) { + return _favoriteRestaurantsDatasource.addFavoriteRestaurant(restaurant); + } + + Future removeFavoriteRestaurant(Restaurant restaurant) { + return _favoriteRestaurantsDatasource.removeFavoriteRestaurant(restaurant); + } +} diff --git a/lib/domain/usecase/restaurants_usecase.dart b/lib/domain/usecase/restaurants_usecase.dart new file mode 100644 index 0000000..bab20a4 --- /dev/null +++ b/lib/domain/usecase/restaurants_usecase.dart @@ -0,0 +1,25 @@ +import 'package:restaurant_tour/domain/repository/restaurants_repository.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; + +class RestaurantsUsecase { + const RestaurantsUsecase(this._repository); + final RestaurantsRepository _repository; + + Future> getRestaurants({ + List filterByIds = const [], + }) { + return _repository.getRestaurants(filterByIds: filterByIds); + } + + Future getRestaurant(String id) { + return _repository.getRestaurant(id); + } + + Future addFavoriteRestaurant(Restaurant restaurant) { + return _repository.addFavoriteRestaurant(restaurant); + } + + Future removeFavoriteRestaurant(Restaurant restaurant) { + return _repository.removeFavoriteRestaurant(restaurant); + } +} diff --git a/lib/models/restaurant.g.dart b/lib/models/restaurant.g.dart index 3ed33f9..dea6677 100644 --- a/lib/models/restaurant.g.dart +++ b/lib/models/restaurant.g.dart @@ -38,15 +38,17 @@ Map _$UserToJson(User instance) => { Review _$ReviewFromJson(Map json) => Review( id: json['id'] as String?, - rating: json['rating'] as int?, + rating: (json['rating'] as num?)?.toInt(), user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + text: json['text'] as String?, ); Map _$ReviewToJson(Review instance) => { 'id': instance.id, 'rating': instance.rating, + 'text': instance.text, 'user': instance.user, }; @@ -95,7 +97,7 @@ Map _$RestaurantToJson(Restaurant instance) => RestaurantQueryResult _$RestaurantQueryResultFromJson( Map json) => RestaurantQueryResult( - total: json['total'] as int?, + total: (json['total'] as num?)?.toInt(), restaurants: (json['business'] as List?) ?.map((e) => Restaurant.fromJson(e as Map)) .toList(), diff --git a/pubspec.lock b/pubspec.lock index f95a63e..4b5cd41 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,31 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.7.0" args: dependency: transitive description: name: args - sha256: "0bd9a99b6eb96f07af141f0eb53eace8983e8e5aa5de59777aca31684680ef22" + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.5.0" async: dependency: transitive description: @@ -45,10 +50,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -61,34 +66,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.4.12" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f4d6244cc071ba842c296cb1c4ee1b31596b9f924300647ac7a1445493471a3f + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "7.2.3" + version: "7.3.2" built_collection: dependency: transitive description: @@ -101,10 +106,10 @@ packages: dependency: transitive description: name: built_value - sha256: b6c9911b2d670376918d5b8779bc27e0e612a94ec3ff0343689e991d8d0a3b8a + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "8.9.2" characters: dependency: transitive description: @@ -113,22 +118,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - charcode: - dependency: transitive - description: - name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 - url: "https://pub.dev" - source: hosted - version: "1.3.1" checked_yaml: dependency: transitive description: name: checked_yaml - sha256: dd007e4fb8270916820a0d66e24f619266b60773cddd082c6439341645af2659 + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.3" clock: dependency: transitive description: @@ -157,26 +154,34 @@ packages: dependency: transitive description: name: convert - sha256: f08428ad63615f96a27e34221c65e1a451439b5f26030f78d790f461c686d65d + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: c1fb2dce3c0085f39dc72668e85f8e0210ec7de05345821ff58530567df345a5 + url: "https://pub.dev" + source: hosted + version: "1.9.2" crypto: dependency: transitive description: name: crypto - sha256: cf75650c66c0316274e21d7c43d3dea246273af5955bd94e8184837cd577575c + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.5" dart_style: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.7" fake_async: dependency: transitive description: @@ -189,18 +194,18 @@ packages: dependency: transitive 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: name: fixnum - sha256: "6a2ef17156f4dc49684f9d99aaf4a93aba8ac49f5eac861755f5730ddf6e2e4e" + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -223,26 +228,26 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: 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: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" http: dependency: "direct main" description: @@ -255,34 +260,34 @@ packages: dependency: transitive description: name: http_multi_server - sha256: bfb651625e251a88804ad6d596af01ea903544757906addcb2dcdf088b5ea185 + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - sha256: e362d639ba3bc07d5a71faebb98cde68c05bfbcfbbb444b60b6f60bb67719185 + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.2" io: dependency: transitive description: name: io - sha256: "0d4c73c3653ab85bf696d51a9657604c900a370549196a91f33e4c39af760852" + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" js: dependency: transitive description: name: js - sha256: d9bdfd70d828eeb352390f81b18d6a354ef2044aa28ef25682079797fa7cd174 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.3" + version: "0.7.1" json_annotation: dependency: "direct main" description: @@ -303,18 +308,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -335,10 +340,18 @@ packages: dependency: transitive description: name: logging - sha256: "293ae2d49fd79d4c04944c3a26dfd313382d5f52e821ec57119230ae16031ad4" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: transitive description: @@ -351,34 +364,50 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: fd5f81041e6a9fc9b9d7fa2cb8a01123f9f5d5d49136e06cb9dc7d33689529f4 + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.6" + mockito: + dependency: "direct main" + description: + name: mockito + sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + url: "https://pub.dev" + source: hosted + version: "5.4.4" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" package_config: dependency: transitive description: name: package_config - sha256: a4d5ede5ca9c3d88a2fef1147a078570c861714c806485c596b109819135bc12 + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.0" path: dependency: transitive description: @@ -391,42 +420,58 @@ packages: dependency: transitive description: name: pool - sha256: "05955e3de2683e1746222efd14b775df7131139e07695dc8e24650f6b4204504" + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.5.1" pub_semver: dependency: transitive description: name: pub_semver - sha256: b5a5fcc6425ea43704852ba4453ba94b08c2226c63418a260240c3a054579014 + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: "3686efe4a4613a4449b1a4ae08670aadbd3376f2e78d93e3f8f0919db02a7256" + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" shelf: dependency: transitive description: name: shelf - sha256: c240984c924796e055e831a0a36db23be8cb04f170b26df572931ab36418421d + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: fd84910bf7d58db109082edf7326b75322b8f186162028482f53dc892f00332d + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -448,6 +493,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.4" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" source_span: dependency: transitive description: @@ -476,10 +537,10 @@ packages: dependency: transitive description: name: stream_transform - sha256: ed464977cb26a1f41537e177e190c67223dbd9f4f683489b6ab2e5d211ec564e + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: @@ -496,30 +557,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + test: + dependency: "direct dev" + description: + name: test + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + url: "https://pub.dev" + source: hosted + version: "1.25.7" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" + test_core: + dependency: transitive + description: + name: test_core + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + url: "https://pub.dev" + source: hosted + version: "0.6.4" timing: dependency: transitive description: name: timing - sha256: c386d07d7f5efc613479a7c4d9d64b03710b03cfaa7e8ad5f2bfb295a1f0dfad + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" typed_data: dependency: transitive description: name: typed_data - sha256: "53bdf7e979cfbf3e28987552fd72f637e63f3c8724c9e56d9246942dc2fa36ee" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.2" vector_math: dependency: transitive description: @@ -532,18 +609,18 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: name: watcher - sha256: e42dfcc48f67618344da967b10f62de57e04bae01d9d3af4c2596f3712a88c99 + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" web: dependency: transitive description: @@ -552,22 +629,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "0c2ada1b1aeb2ad031ca81872add6be049b8cb479262c6ad3c4b0f9c24eaab2f" + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "3.0.1" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" yaml: dependency: transitive description: name: yaml - sha256: "3cee79b1715110341012d27756d9bae38e650588acd38d3f3c610822e1337ace" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.5.0-259.0.dev <4.0.0" flutter: ">=3.19.6" diff --git a/pubspec.yaml b/pubspec.yaml index bc8a205..f090c8b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: sdk: flutter http: ^1.2.2 json_annotation: ^4.9.0 + mockito: ^5.4.4 dev_dependencies: flutter_test: @@ -22,6 +23,7 @@ dev_dependencies: flutter_lints: ^4.0.0 build_runner: ^2.4.10 json_serializable: ^6.8.0 + test: ^1.25.2 flutter: generate: true diff --git a/test/domain/usecase/restaurants_usecase_test.dart b/test/domain/usecase/restaurants_usecase_test.dart new file mode 100644 index 0000000..f442169 --- /dev/null +++ b/test/domain/usecase/restaurants_usecase_test.dart @@ -0,0 +1,58 @@ +import 'package:mockito/mockito.dart'; + +import 'package:restaurant_tour/domain/repository/restaurants_repository.dart'; +import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; + +import 'package:test/test.dart'; +import 'package:mockito/annotations.dart'; + +import 'restaurants_usecase_test.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), +]) +late RestaurantsUsecase usecase; +late RestaurantsRepository repository; + +void main() { + setUpAll(() { + repository = MockRestaurantsRepository(); + usecase = RestaurantsUsecase(repository); + }); + test('get restaurants should return a list of restaurants', () async { + when(repository.getRestaurants()).thenAnswer( + (_) async => const [Restaurant(id: "123", name: "Unit Test")], + ); + final result = await usecase.getRestaurants(); + expect(result, isNotEmpty); + expect(result.first, isA()); + }); + + test('get restaurant should return a restaurant model', () async { + when(repository.getRestaurant('test')).thenAnswer( + (_) async => const Restaurant( + id: 'test', + name: 'Test unit', + ), + ); + final result = await usecase.getRestaurant('test'); + expect(result, isA()); + }); + + test('add favorite restaurant should save a restaurant', () async { + const testRestaurant = Restaurant(id: 'test_id', name: 'Test Restaurant'); + when(repository.addFavoriteRestaurant(testRestaurant)) + .thenAnswer((_) async => {}); + await usecase.addFavoriteRestaurant(testRestaurant); + verify(repository.addFavoriteRestaurant(testRestaurant)).called(1); + }); + + test('remove favorite restaurant should delete a restaurant', () async { + const testRestaurant = Restaurant(id: 'test_id', name: 'Test Restaurant'); + when(repository.addFavoriteRestaurant(testRestaurant)) + .thenAnswer((_) async => {}); + await usecase.removeFavoriteRestaurant(testRestaurant); + verify(repository.removeFavoriteRestaurant(testRestaurant)).called(1); + }); +} diff --git a/test/domain/usecase/restaurants_usecase_test.mocks.dart b/test/domain/usecase/restaurants_usecase_test.mocks.dart new file mode 100644 index 0000000..51877b0 --- /dev/null +++ b/test/domain/usecase/restaurants_usecase_test.mocks.dart @@ -0,0 +1,99 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in restaurant_tour/test/domain/usecase/restaurants_usecase_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:restaurant_tour/domain/repository/restaurants_repository.dart' + as _i3; +import 'package:restaurant_tour/models/restaurant.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeRestaurant_0 extends _i1.SmartFake implements _i2.Restaurant { + _FakeRestaurant_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [RestaurantsRepository]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockRestaurantsRepository extends _i1.Mock + implements _i3.RestaurantsRepository { + @override + _i4.Future> getRestaurants( + {List? filterByIds}) => + (super.noSuchMethod( + Invocation.method( + #getRestaurants, + [], + {#filterByIds: filterByIds}, + ), + returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValueForMissingStub: + _i4.Future>.value(<_i2.Restaurant>[]), + ) as _i4.Future>); + + @override + _i4.Future<_i2.Restaurant> getRestaurant(String? id) => (super.noSuchMethod( + Invocation.method( + #getRestaurant, + [id], + ), + returnValue: _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( + this, + Invocation.method( + #getRestaurant, + [id], + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( + this, + Invocation.method( + #getRestaurant, + [id], + ), + )), + ) as _i4.Future<_i2.Restaurant>); + + @override + _i4.Future addFavoriteRestaurant(_i2.Restaurant? restaurant) => + (super.noSuchMethod( + Invocation.method( + #addFavoriteRestaurant, + [restaurant], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future removeFavoriteRestaurant(_i2.Restaurant? restaurant) => + (super.noSuchMethod( + Invocation.method( + #removeFavoriteRestaurant, + [restaurant], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); +} From 83f09f99a6943bef6cf31288e96b92f1fae0d442 Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Mon, 16 Sep 2024 11:30:28 -0400 Subject: [PATCH 02/12] feat(presentation): Add blocs for restaurant list and favorite restaurant list --- lib/common/exceptions/exceptions.dart | 9 ++++++- .../repository/restaurants_repository.dart | 14 +++++++---- lib/domain/usecase/restaurants_usecase.dart | 10 ++++---- .../favorites/favorites_restaurants_bloc.dart | 24 +++++++++++++++++++ .../favorites_restaurants_event.dart | 10 ++++++++ .../favorites_restaurants_state.dart | 15 ++++++++++++ .../restaurants/restaurants_bloc.dart | 23 ++++++++++++++++++ .../restaurants/restaurants_event.dart | 6 +++++ .../restaurants/restaurants_state.dart | 18 ++++++++++++++ pubspec.lock | 8 +++++++ pubspec.yaml | 1 + 11 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 lib/presentation/favorites/favorites_restaurants_bloc.dart create mode 100644 lib/presentation/favorites/favorites_restaurants_event.dart create mode 100644 lib/presentation/favorites/favorites_restaurants_state.dart create mode 100644 lib/presentation/restaurants/restaurants_bloc.dart create mode 100644 lib/presentation/restaurants/restaurants_event.dart create mode 100644 lib/presentation/restaurants/restaurants_state.dart diff --git a/lib/common/exceptions/exceptions.dart b/lib/common/exceptions/exceptions.dart index f5e9a74..870442e 100644 --- a/lib/common/exceptions/exceptions.dart +++ b/lib/common/exceptions/exceptions.dart @@ -13,7 +13,14 @@ class AppException implements Exception { class RestaurantNotFoundException extends AppException { RestaurantNotFoundException({ - code = "Restaurant not found", + code = "RESTAURANT_NOT_FOUND", description = "The restaurant searched is not our dataset", }) : super(code, description); } + +class RestaurantListException extends AppException { + RestaurantListException({ + code = "RESTAURANTS_LIST_NOT_AVAILABLE", + description = "The restaurant is not available at the moment", + }) : super(code, description); +} diff --git a/lib/domain/repository/restaurants_repository.dart b/lib/domain/repository/restaurants_repository.dart index 4cd3e9f..8d0dcea 100644 --- a/lib/domain/repository/restaurants_repository.dart +++ b/lib/domain/repository/restaurants_repository.dart @@ -10,10 +10,16 @@ class RestaurantsRepository { final FavoriteRestaurantsDatasource _favoriteRestaurantsDatasource; final RestaurantsDatasource _restaurantsDatasource; - Future> getRestaurants({ - List filterByIds = const [], - }) { - return _restaurantsDatasource.getRestaurants(filterByIds: filterByIds); + Future> getRestaurants() { + return _restaurantsDatasource.getRestaurants(); + } + + Future> getFavoriteRestaurants() async { + final favoriteRestaurantIds = + await _favoriteRestaurantsDatasource.getListRestaurantsIds(); + return _restaurantsDatasource.getRestaurants( + filterByIds: favoriteRestaurantIds, + ); } Future getRestaurant(String id) { diff --git a/lib/domain/usecase/restaurants_usecase.dart b/lib/domain/usecase/restaurants_usecase.dart index bab20a4..613f185 100644 --- a/lib/domain/usecase/restaurants_usecase.dart +++ b/lib/domain/usecase/restaurants_usecase.dart @@ -5,10 +5,12 @@ class RestaurantsUsecase { const RestaurantsUsecase(this._repository); final RestaurantsRepository _repository; - Future> getRestaurants({ - List filterByIds = const [], - }) { - return _repository.getRestaurants(filterByIds: filterByIds); + Future> getRestaurants() { + return _repository.getRestaurants(); + } + + Future> getFavoriteRestaurants() { + return _repository.getFavoriteRestaurants(); } Future getRestaurant(String id) { diff --git a/lib/presentation/favorites/favorites_restaurants_bloc.dart b/lib/presentation/favorites/favorites_restaurants_bloc.dart new file mode 100644 index 0000000..b9b469f --- /dev/null +++ b/lib/presentation/favorites/favorites_restaurants_bloc.dart @@ -0,0 +1,24 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; + +part 'favorites_restaurants_event.dart'; +part 'favorites_restaurants_state.dart'; + +class FavoritesRestaurantsBloc + extends Bloc { + FavoritesRestaurantsBloc(this.usecase) + : super(FavoritesRestaurantsInitial()) { + on((event, emit) async { + try { + emit(FavoriteRestaurantsLoading()); + final favorites = await usecase.getFavoriteRestaurants(); + emit(FavoriteRestaurantReady(favorites)); + } catch (e) { + emit(FavoriteRestaurantsError()); + } + }); + } + final RestaurantsUsecase usecase; +} diff --git a/lib/presentation/favorites/favorites_restaurants_event.dart b/lib/presentation/favorites/favorites_restaurants_event.dart new file mode 100644 index 0000000..4a15dfd --- /dev/null +++ b/lib/presentation/favorites/favorites_restaurants_event.dart @@ -0,0 +1,10 @@ +part of 'favorites_restaurants_bloc.dart'; + +@immutable +sealed class FavoritesRestaurantsEvent {} + +class LoadFavoriteRestaurants extends FavoritesRestaurantsEvent {} + +class AddFavoriteRestaurant extends FavoritesRestaurantsEvent {} + +class RemoveFavoriteRestaurant extends FavoritesRestaurantsEvent {} diff --git a/lib/presentation/favorites/favorites_restaurants_state.dart b/lib/presentation/favorites/favorites_restaurants_state.dart new file mode 100644 index 0000000..eb932b4 --- /dev/null +++ b/lib/presentation/favorites/favorites_restaurants_state.dart @@ -0,0 +1,15 @@ +part of 'favorites_restaurants_bloc.dart'; + +@immutable +sealed class FavoritesRestaurantsState {} + +final class FavoritesRestaurantsInitial extends FavoritesRestaurantsState {} + +final class FavoriteRestaurantsLoading extends FavoritesRestaurantsState {} + +final class FavoriteRestaurantsError extends FavoritesRestaurantsState {} + +final class FavoriteRestaurantReady extends FavoritesRestaurantsState { + FavoriteRestaurantReady(this.favoriteRestaurants); + final List favoriteRestaurants; +} diff --git a/lib/presentation/restaurants/restaurants_bloc.dart b/lib/presentation/restaurants/restaurants_bloc.dart new file mode 100644 index 0000000..08648a6 --- /dev/null +++ b/lib/presentation/restaurants/restaurants_bloc.dart @@ -0,0 +1,23 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:restaurant_tour/common/exceptions/exceptions.dart'; +import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; + +part 'restaurants_event.dart'; +part 'restaurants_state.dart'; + +class RestaurantsBloc extends Bloc { + RestaurantsBloc(this.usecase) : super(RestaurantsInitial()) { + on((event, emit) async { + emit(RestaurantsLoading()); + try { + final restaurants = await usecase.getRestaurants(); + emit(RestaurantsReady(restaurants)); + } catch (e) { + emit(RestaurantsError(RestaurantListException())); + } + }); + } + final RestaurantsUsecase usecase; +} diff --git a/lib/presentation/restaurants/restaurants_event.dart b/lib/presentation/restaurants/restaurants_event.dart new file mode 100644 index 0000000..c347867 --- /dev/null +++ b/lib/presentation/restaurants/restaurants_event.dart @@ -0,0 +1,6 @@ +part of 'restaurants_bloc.dart'; + +@immutable +sealed class RestaurantsEvent {} + +class LoadRestaurants extends RestaurantsEvent {} diff --git a/lib/presentation/restaurants/restaurants_state.dart b/lib/presentation/restaurants/restaurants_state.dart new file mode 100644 index 0000000..aff5c1a --- /dev/null +++ b/lib/presentation/restaurants/restaurants_state.dart @@ -0,0 +1,18 @@ +part of 'restaurants_bloc.dart'; + +@immutable +sealed class RestaurantsState {} + +final class RestaurantsInitial extends RestaurantsState {} + +final class RestaurantsLoading extends RestaurantsState {} + +final class RestaurantsReady extends RestaurantsState { + RestaurantsReady(this.restaurants); + final List restaurants; +} + +final class RestaurantsError extends RestaurantsState { + RestaurantsError(this.exception); + final RestaurantListException exception; +} diff --git a/pubspec.lock b/pubspec.lock index 4b5cd41..6450180 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -38,6 +38,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: "direct main" + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" boolean_selector: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f090c8b..59ef014 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ environment: flutter: ">=3.19.6" dependencies: + bloc: ^8.1.4 flutter: sdk: flutter http: ^1.2.2 From cc467b004ecdd2fcd796c9dfddb2424a4c72e57e Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Mon, 16 Sep 2024 13:34:57 -0400 Subject: [PATCH 03/12] test(presentation): Add test cases for blocs Add Exceptions for add/remove favorite Update restaurants get methods --- lib/common/exceptions/exceptions.dart | 21 ++++ .../favorite_restaurants_datasource.dart | 1 - .../favorites/favorites_restaurants_bloc.dart | 38 ++++++- .../favorites_restaurants_event.dart | 12 +- .../favorites_restaurants_state.dart | 23 +++- pubspec.lock | 24 ++++ pubspec.yaml | 1 + .../restaurants_usecase_test.mocks.dart | 17 ++- .../test_favorite_restaurants_bloc.dart | 95 ++++++++++++++++ .../test_favorite_restaurants_bloc.mocks.dart | 107 ++++++++++++++++++ .../restaurants/test_restaurants_bloc.dart | 49 ++++++++ .../test_restaurants_bloc.mocks.dart | 107 ++++++++++++++++++ 12 files changed, 478 insertions(+), 17 deletions(-) create mode 100644 test/presentation/favorites/test_favorite_restaurants_bloc.dart create mode 100644 test/presentation/favorites/test_favorite_restaurants_bloc.mocks.dart create mode 100644 test/presentation/restaurants/test_restaurants_bloc.dart create mode 100644 test/presentation/restaurants/test_restaurants_bloc.mocks.dart diff --git a/lib/common/exceptions/exceptions.dart b/lib/common/exceptions/exceptions.dart index 870442e..d206ade 100644 --- a/lib/common/exceptions/exceptions.dart +++ b/lib/common/exceptions/exceptions.dart @@ -24,3 +24,24 @@ class RestaurantListException extends AppException { description = "The restaurant is not available at the moment", }) : super(code, description); } + +class FavoritesRestaurantsListException extends AppException { + FavoritesRestaurantsListException({ + code = "FAVORITES_RESTAURANTS_LIST_NOT_AVAILABLE", + description = "The favorite restaurant is not available at the moment", + }) : super(code, description); +} + +class AddFavoriteRestaurantException extends AppException { + AddFavoriteRestaurantException({ + code = "ADD_FAVORITE_RESTAURANT_EXCEPTION", + description = "Couldn't register the favorite restaurant", + }) : super(code, description); +} + +class RemoveFavoriteRestaurantException extends AppException { + RemoveFavoriteRestaurantException({ + code = "REMOVE_FAVORITE_RESTAURANT_EXCEPTION", + description = "Couldn't remove the favorite restaurant", + }) : super(code, description); +} diff --git a/lib/domain/datasource/favorite_restaurants_datasource.dart b/lib/domain/datasource/favorite_restaurants_datasource.dart index 8e42058..dff1290 100644 --- a/lib/domain/datasource/favorite_restaurants_datasource.dart +++ b/lib/domain/datasource/favorite_restaurants_datasource.dart @@ -3,6 +3,5 @@ import 'package:restaurant_tour/models/restaurant.dart'; abstract class FavoriteRestaurantsDatasource { Future addFavoriteRestaurant(Restaurant restaurant); Future removeFavoriteRestaurant(Restaurant restaurant); - //TODO(darwin): Review this and decide if returning List or List of restaurants. Future> getListRestaurantsIds(); } diff --git a/lib/presentation/favorites/favorites_restaurants_bloc.dart b/lib/presentation/favorites/favorites_restaurants_bloc.dart index b9b469f..2ccbaee 100644 --- a/lib/presentation/favorites/favorites_restaurants_bloc.dart +++ b/lib/presentation/favorites/favorites_restaurants_bloc.dart @@ -1,5 +1,6 @@ import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; +import 'package:restaurant_tour/common/exceptions/exceptions.dart'; import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; import 'package:restaurant_tour/models/restaurant.dart'; @@ -10,15 +11,44 @@ class FavoritesRestaurantsBloc extends Bloc { FavoritesRestaurantsBloc(this.usecase) : super(FavoritesRestaurantsInitial()) { - on((event, emit) async { + on((event, emit) async { try { - emit(FavoriteRestaurantsLoading()); + emit(FavoritesRestaurantsLoading()); final favorites = await usecase.getFavoriteRestaurants(); - emit(FavoriteRestaurantReady(favorites)); + emit(FavoritesRestaurantsReady(favorites)); } catch (e) { - emit(FavoriteRestaurantsError()); + emit(FavoriteRestaurantsListError(FavoritesRestaurantsListException())); } }); + on( + (event, emit) async { + try { + emit(FavoritesRestaurantsLoading()); + await usecase.addFavoriteRestaurant(event.restaurant); + final favoritesRestaurants = await usecase.getFavoriteRestaurants(); + emit(FavoritesRestaurantsReady(favoritesRestaurants)); + } catch (e) { + emit(AddFavoriteRestaurantsError(AddFavoriteRestaurantException())); + } + }, + ); + + on( + (event, emit) async { + try { + emit(FavoritesRestaurantsLoading()); + await usecase.removeFavoriteRestaurant(event.restaurant); + final favoritesRestaurants = await usecase.getFavoriteRestaurants(); + emit(FavoritesRestaurantsReady(favoritesRestaurants)); + } catch (e) { + emit( + RemoveFavoriteRestaurantsError( + RemoveFavoriteRestaurantException(), + ), + ); + } + }, + ); } final RestaurantsUsecase usecase; } diff --git a/lib/presentation/favorites/favorites_restaurants_event.dart b/lib/presentation/favorites/favorites_restaurants_event.dart index 4a15dfd..1cb68ee 100644 --- a/lib/presentation/favorites/favorites_restaurants_event.dart +++ b/lib/presentation/favorites/favorites_restaurants_event.dart @@ -3,8 +3,14 @@ part of 'favorites_restaurants_bloc.dart'; @immutable sealed class FavoritesRestaurantsEvent {} -class LoadFavoriteRestaurants extends FavoritesRestaurantsEvent {} +class LoadFavoritesRestaurants extends FavoritesRestaurantsEvent {} -class AddFavoriteRestaurant extends FavoritesRestaurantsEvent {} +class AddFavoriteRestaurant extends FavoritesRestaurantsEvent { + AddFavoriteRestaurant(this.restaurant); + final Restaurant restaurant; +} -class RemoveFavoriteRestaurant extends FavoritesRestaurantsEvent {} +class RemoveFavoriteRestaurant extends FavoritesRestaurantsEvent { + RemoveFavoriteRestaurant(this.restaurant); + final Restaurant restaurant; +} diff --git a/lib/presentation/favorites/favorites_restaurants_state.dart b/lib/presentation/favorites/favorites_restaurants_state.dart index eb932b4..394c9f7 100644 --- a/lib/presentation/favorites/favorites_restaurants_state.dart +++ b/lib/presentation/favorites/favorites_restaurants_state.dart @@ -5,11 +5,24 @@ sealed class FavoritesRestaurantsState {} final class FavoritesRestaurantsInitial extends FavoritesRestaurantsState {} -final class FavoriteRestaurantsLoading extends FavoritesRestaurantsState {} +final class FavoritesRestaurantsLoading extends FavoritesRestaurantsState {} -final class FavoriteRestaurantsError extends FavoritesRestaurantsState {} +final class FavoriteRestaurantsListError extends FavoritesRestaurantsState { + FavoriteRestaurantsListError(this.exception); + final FavoritesRestaurantsListException exception; +} + +final class AddFavoriteRestaurantsError extends FavoritesRestaurantsState { + AddFavoriteRestaurantsError(this.exception); + final AddFavoriteRestaurantException exception; +} + +final class RemoveFavoriteRestaurantsError extends FavoritesRestaurantsState { + RemoveFavoriteRestaurantsError(this.exception); + final RemoveFavoriteRestaurantException exception; +} -final class FavoriteRestaurantReady extends FavoritesRestaurantsState { - FavoriteRestaurantReady(this.favoriteRestaurants); - final List favoriteRestaurants; +final class FavoritesRestaurantsReady extends FavoritesRestaurantsState { + FavoritesRestaurantsReady(this.favoritesRestaurants); + final List favoritesRestaurants; } diff --git a/pubspec.lock b/pubspec.lock index 6450180..1f19d24 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -46,6 +46,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.4" + bloc_test: + dependency: "direct main" + description: + name: bloc_test + sha256: "165a6ec950d9252ebe36dc5335f2e6eb13055f33d56db0eeb7642768849b43d2" + url: "https://pub.dev" + source: hosted + version: "9.1.7" boolean_selector: dependency: transitive description: @@ -190,6 +198,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.7" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" fake_async: dependency: transitive description: @@ -400,6 +416,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.4" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" + url: "https://pub.dev" + source: hosted + version: "1.0.4" node_preamble: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 59ef014..f5ec5f1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,7 @@ environment: dependencies: bloc: ^8.1.4 + bloc_test: ^9.1.7 flutter: sdk: flutter http: ^1.2.2 diff --git a/test/domain/usecase/restaurants_usecase_test.mocks.dart b/test/domain/usecase/restaurants_usecase_test.mocks.dart index 51877b0..a22b856 100644 --- a/test/domain/usecase/restaurants_usecase_test.mocks.dart +++ b/test/domain/usecase/restaurants_usecase_test.mocks.dart @@ -39,13 +39,22 @@ class _FakeRestaurant_0 extends _i1.SmartFake implements _i2.Restaurant { class MockRestaurantsRepository extends _i1.Mock implements _i3.RestaurantsRepository { @override - _i4.Future> getRestaurants( - {List? filterByIds}) => - (super.noSuchMethod( + _i4.Future> getRestaurants() => (super.noSuchMethod( Invocation.method( #getRestaurants, [], - {#filterByIds: filterByIds}, + ), + returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValueForMissingStub: + _i4.Future>.value(<_i2.Restaurant>[]), + ) as _i4.Future>); + + @override + _i4.Future> getFavoriteRestaurants() => + (super.noSuchMethod( + Invocation.method( + #getFavoriteRestaurants, + [], ), returnValue: _i4.Future>.value(<_i2.Restaurant>[]), returnValueForMissingStub: diff --git a/test/presentation/favorites/test_favorite_restaurants_bloc.dart b/test/presentation/favorites/test_favorite_restaurants_bloc.dart new file mode 100644 index 0000000..bf35593 --- /dev/null +++ b/test/presentation/favorites/test_favorite_restaurants_bloc.dart @@ -0,0 +1,95 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:restaurant_tour/common/exceptions/exceptions.dart'; +import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; +import 'package:test/test.dart'; + +import 'test_favorite_restaurants_bloc.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +late RestaurantsUsecase usecase; +late List testFavoritesRestaurants; +late Restaurant testFavoriteRestaurant; +void main() { + setUp(() { + usecase = MockRestaurantsUsecase(); + testFavoriteRestaurant = + const Restaurant(id: 'test_3', name: 'Test Restaurant'); + testFavoritesRestaurants = [ + const Restaurant(id: 'test_1', name: 'Test Restaurant'), + const Restaurant(id: 'test_2', name: 'Test Restaurant'), + ]; + }); + + blocTest( + 'FavoritesRestaurantsBloc should load favorite restaurants' + ' and emit FavoritesRestaurantsReady state', + setUp: () => when(usecase.getFavoriteRestaurants()) + .thenAnswer((_) async => (testFavoritesRestaurants)), + build: () => FavoritesRestaurantsBloc(usecase), + act: (bloc) => bloc.add(LoadFavoritesRestaurants()), + wait: const Duration(milliseconds: 300), + expect: () => + [isA(), isA()], + verify: (bloc) => verify(usecase.getFavoriteRestaurants()).called(1), + ); + + blocTest( + 'FavoritesRestaurantsBloc should load favorite restaurants' + ' and emit FavoritesRestaurantError state', + setUp: () => when(usecase.getFavoriteRestaurants()) + .thenThrow(FavoritesRestaurantsListException()), + build: () => FavoritesRestaurantsBloc(usecase), + act: (bloc) => bloc.add(LoadFavoritesRestaurants()), + wait: const Duration(milliseconds: 300), + expect: () => [ + isA(), + isA(), + ], + verify: (bloc) => verify(usecase.getFavoriteRestaurants()).called(1), + ); + + blocTest( + 'FavoritesRestaurantsBloc should add a new favorite restaurant' + ' and return a FavoritesRestaurantsListState', + setUp: () { + when(usecase.addFavoriteRestaurant(testFavoriteRestaurant)) + .thenAnswer((_) async {}); + when(usecase.getFavoriteRestaurants()) + .thenAnswer((_) async => testFavoritesRestaurants); + }, + build: () => FavoritesRestaurantsBloc(usecase), + act: (bloc) => bloc.add(AddFavoriteRestaurant(testFavoriteRestaurant)), + wait: const Duration(milliseconds: 300), + expect: () => + [isA(), isA()], + verify: (bloc) { + verify(usecase.getFavoriteRestaurants()).called(1); + verify(usecase.addFavoriteRestaurant(testFavoriteRestaurant)).called(1); + }, + ); + + blocTest( + 'FavoritesRestaurantsBloc should remove a favorite restaurant' + ' and return a FavoritesRestaurantsListState', + setUp: () { + when(usecase.removeFavoriteRestaurant(testFavoriteRestaurant)) + .thenAnswer((_) async {}); + when(usecase.getFavoriteRestaurants()) + .thenAnswer((_) async => testFavoritesRestaurants); + }, + build: () => FavoritesRestaurantsBloc(usecase), + act: (bloc) => bloc.add(RemoveFavoriteRestaurant(testFavoriteRestaurant)), + wait: const Duration(milliseconds: 300), + expect: () => + [isA(), isA()], + verify: (bloc) { + verify(usecase.removeFavoriteRestaurant(testFavoriteRestaurant)) + .called(1); + verify(usecase.getFavoriteRestaurants()).called(1); + }, + ); +} diff --git a/test/presentation/favorites/test_favorite_restaurants_bloc.mocks.dart b/test/presentation/favorites/test_favorite_restaurants_bloc.mocks.dart new file mode 100644 index 0000000..c0e3846 --- /dev/null +++ b/test/presentation/favorites/test_favorite_restaurants_bloc.mocks.dart @@ -0,0 +1,107 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in restaurant_tour/test/presentation/favorites/test_favorite_restaurants_bloc.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart' as _i3; +import 'package:restaurant_tour/models/restaurant.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeRestaurant_0 extends _i1.SmartFake implements _i2.Restaurant { + _FakeRestaurant_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [RestaurantsUsecase]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockRestaurantsUsecase extends _i1.Mock + implements _i3.RestaurantsUsecase { + @override + _i4.Future> getRestaurants() => (super.noSuchMethod( + Invocation.method( + #getRestaurants, + [], + ), + returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValueForMissingStub: + _i4.Future>.value(<_i2.Restaurant>[]), + ) as _i4.Future>); + + @override + _i4.Future> getFavoriteRestaurants() => + (super.noSuchMethod( + Invocation.method( + #getFavoriteRestaurants, + [], + ), + returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValueForMissingStub: + _i4.Future>.value(<_i2.Restaurant>[]), + ) as _i4.Future>); + + @override + _i4.Future<_i2.Restaurant> getRestaurant(String? id) => (super.noSuchMethod( + Invocation.method( + #getRestaurant, + [id], + ), + returnValue: _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( + this, + Invocation.method( + #getRestaurant, + [id], + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( + this, + Invocation.method( + #getRestaurant, + [id], + ), + )), + ) as _i4.Future<_i2.Restaurant>); + + @override + _i4.Future addFavoriteRestaurant(_i2.Restaurant? restaurant) => + (super.noSuchMethod( + Invocation.method( + #addFavoriteRestaurant, + [restaurant], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future removeFavoriteRestaurant(_i2.Restaurant? restaurant) => + (super.noSuchMethod( + Invocation.method( + #removeFavoriteRestaurant, + [restaurant], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); +} diff --git a/test/presentation/restaurants/test_restaurants_bloc.dart b/test/presentation/restaurants/test_restaurants_bloc.dart new file mode 100644 index 0000000..e97d96f --- /dev/null +++ b/test/presentation/restaurants/test_restaurants_bloc.dart @@ -0,0 +1,49 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:restaurant_tour/common/exceptions/exceptions.dart'; +import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; +import 'package:test/test.dart'; + +import 'test_restaurants_bloc.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), +]) +late RestaurantsUsecase usecase; +late List testRestaurants; +late Restaurant testRestaurant; +void main() { + setUp(() { + usecase = MockRestaurantsUsecase(); + testRestaurant = const Restaurant(id: 'test', name: 'Test Restaurant'); + testRestaurants = [ + const Restaurant(id: 'test_1', name: 'Test Restaurant'), + const Restaurant(id: 'test_2', name: 'Test Restaurant'), + ]; + }); + + blocTest( + 'RestaurantsBloc should load restaurants and emit RestaurantsReadyState', + setUp: () => when(usecase.getRestaurants()) + .thenAnswer((_) async => (testRestaurants)), + build: () => RestaurantsBloc(usecase), + act: (bloc) => bloc.add(LoadRestaurants()), + wait: const Duration(milliseconds: 300), + expect: () => [isA(), isA()], + verify: (bloc) => verify(usecase.getRestaurants()).called(1), + ); + + blocTest( + 'RestaurantsBloc should error RestaurantsListError', + setUp: () => + when(usecase.getRestaurants()).thenThrow(RestaurantListException()), + build: () => RestaurantsBloc(usecase), + act: (bloc) => bloc.add(LoadRestaurants()), + wait: const Duration(milliseconds: 300), + expect: () => [isA(), isA()], + verify: (bloc) => verify(usecase.getRestaurants()).called(1), + ); +} diff --git a/test/presentation/restaurants/test_restaurants_bloc.mocks.dart b/test/presentation/restaurants/test_restaurants_bloc.mocks.dart new file mode 100644 index 0000000..10cc7bf --- /dev/null +++ b/test/presentation/restaurants/test_restaurants_bloc.mocks.dart @@ -0,0 +1,107 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in restaurant_tour/test/presentation/restaurants/test_restaurants_bloc.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart' as _i3; +import 'package:restaurant_tour/models/restaurant.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeRestaurant_0 extends _i1.SmartFake implements _i2.Restaurant { + _FakeRestaurant_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [RestaurantsUsecase]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockRestaurantsUsecase extends _i1.Mock + implements _i3.RestaurantsUsecase { + @override + _i4.Future> getRestaurants() => (super.noSuchMethod( + Invocation.method( + #getRestaurants, + [], + ), + returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValueForMissingStub: + _i4.Future>.value(<_i2.Restaurant>[]), + ) as _i4.Future>); + + @override + _i4.Future> getFavoriteRestaurants() => + (super.noSuchMethod( + Invocation.method( + #getFavoriteRestaurants, + [], + ), + returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValueForMissingStub: + _i4.Future>.value(<_i2.Restaurant>[]), + ) as _i4.Future>); + + @override + _i4.Future<_i2.Restaurant> getRestaurant(String? id) => (super.noSuchMethod( + Invocation.method( + #getRestaurant, + [id], + ), + returnValue: _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( + this, + Invocation.method( + #getRestaurant, + [id], + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( + this, + Invocation.method( + #getRestaurant, + [id], + ), + )), + ) as _i4.Future<_i2.Restaurant>); + + @override + _i4.Future addFavoriteRestaurant(_i2.Restaurant? restaurant) => + (super.noSuchMethod( + Invocation.method( + #addFavoriteRestaurant, + [restaurant], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future removeFavoriteRestaurant(_i2.Restaurant? restaurant) => + (super.noSuchMethod( + Invocation.method( + #removeFavoriteRestaurant, + [restaurant], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); +} From 43f15476e72c299535c27ac78c73d7bd17db1c9a Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Mon, 16 Sep 2024 18:18:47 -0400 Subject: [PATCH 04/12] feat(view): add list page add restaurant page create app theme file --- ios/Runner/AppDelegate.swift | 2 +- lib/main.dart | 7 +- lib/view/pages/main_page.dart | 83 ++++++++++++ lib/view/pages/restaurant_page.dart | 130 +++++++++++++++++++ lib/view/theme/app_theme.dart | 65 ++++++++++ lib/view/widgets/restaurant_open.dart | 31 +++++ lib/view/widgets/restaurant_star_rating.dart | 21 +++ lib/view/widgets/restaurant_tile.dart | 85 ++++++++++++ lib/view/widgets/review_tile.dart | 35 +++++ pubspec.lock | 8 ++ pubspec.yaml | 1 + 11 files changed, 465 insertions(+), 3 deletions(-) create mode 100644 lib/view/pages/main_page.dart create mode 100644 lib/view/pages/restaurant_page.dart create mode 100644 lib/view/theme/app_theme.dart create mode 100644 lib/view/widgets/restaurant_open.dart create mode 100644 lib/view/widgets/restaurant_star_rating.dart create mode 100644 lib/view/widgets/restaurant_tile.dart create mode 100644 lib/view/widgets/review_tile.dart diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 9074fee..6266644 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/lib/main.dart b/lib/main.dart index ae7012a..09b784a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,8 @@ 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'; +import 'package:restaurant_tour/view/pages/main_page.dart'; +import './view/theme/app_theme.dart'; const _apiKey = ''; const _baseUrl = 'https://api.yelp.com/v3/graphql'; @@ -17,9 +19,10 @@ class RestaurantTour extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( + return MaterialApp( title: 'Restaurant Tour', - home: HomePage(), + theme: AppTheme.lightTheme, + home: const MainPage(), ); } } diff --git a/lib/view/pages/main_page.dart b/lib/view/pages/main_page.dart new file mode 100644 index 0000000..28474b5 --- /dev/null +++ b/lib/view/pages/main_page.dart @@ -0,0 +1,83 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_tile.dart'; + +class MainPage extends StatelessWidget { + const MainPage({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + title: const Text('RestauranTour'), + bottom: const TabBar(tabs: [ + Tab( + text: 'Restaurants', + ), + Tab(text: 'Favorites'), + ]), + ), + body: TabBarView(children: [ + Padding( + padding: const EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), + child: _buildRestaurantList(), + ), + Padding( + padding: const EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), + child: _buildRestaurantList(), + ), + ]), + ), + ); + } +} + +Widget _buildRestaurantList() { + final _random = new Random(); + final min = 1; + final max = 5; + final restaurants = [1, 2, 3, 4, 5] + .map( + (e) => Restaurant( + id: "restaurant_$e", + name: "Amazing Pizza", + price: "\$\$\$", + rating: 4.8, + photos: ["https://picsum.photos/361/360"], + categories: [Category(alias: 'demo', title: 'Italian')], + hours: [const Hours(isOpenNow: true)], + location: Location(formattedAddress: "123 Main St, Anytown, CA"), + reviews: [1, 2, 3, 4, 5, 6] + .map((e) => Review( + id: 'review_id', + text: + 'Review text goes here. Review text goes here. This is a review. This is a review that is 3 lines long.', + rating: min + _random.nextInt(max - min), + user: const User( + id: 'user_id', + imageUrl: 'https://picsum.photos/200/300', + name: 'Test User', + ), + )) + .toList(), + ), + ) + .toList(); + + return ListView.separated( + separatorBuilder: (context, index) => const SizedBox( + height: 2, + ), + itemCount: restaurants.length, + itemBuilder: (context, index) { + return RestaurantTile(restaurant: restaurants[index]); + }); + // return const Center( + // child: CircularProgressIndicator(), + // ); +} diff --git a/lib/view/pages/restaurant_page.dart b/lib/view/pages/restaurant_page.dart new file mode 100644 index 0000000..ed3ed1d --- /dev/null +++ b/lib/view/pages/restaurant_page.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_open.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_star_rating.dart'; +import 'package:restaurant_tour/view/widgets/review_tile.dart'; + +class RestaurantPage extends StatelessWidget { + const RestaurantPage(this.restaurant, {super.key}); + final Restaurant restaurant; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(restaurant.name ?? 'Restaurant Name'), + actions: [ + IconButton(onPressed: () {}, icon: Icon(Icons.favorite_outline)) + ], + ), + body: SingleChildScrollView( + child: Column( + children: [ + Hero( + tag: restaurant.id ?? 'restaurant_id', + child: Image.network( + height: 375, + restaurant.photos?.first ?? 'https://picsum.photos/375/361', + ), + ), + Padding( + padding: + const EdgeInsets.only(left: 24.0, top: 24.0, right: 24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + restaurant.price ?? "\$\$", + style: Theme.of(context).textTheme.bodyMedium, + ), + Gap(4), + Text( + restaurant.categories?.first.title ?? "", + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + RestaurantOpen(restaurant.isOpen) + ], + ), + const Gap(16), + const Divider(), + const Gap(16), + const Text('Address'), + const Gap(16), + Text( + restaurant.location?.formattedAddress ?? 'Address info', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontFamily: 'OpenSans', fontWeight: FontWeight.w600), + ), + const Gap(16), + const Divider(), + _buildOverallRating(context), + const Divider(), + _buildReviewSection(context) + ], + ), + ) + ], + ), + ), + ); + } + + Widget _buildReviewSection(BuildContext context) { + return (restaurant.reviews != null) + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Gap(16), + Text( + '${restaurant.reviews!.length} Reviews', + style: Theme.of(context).textTheme.bodyMedium, + ), + Gap(16), + ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (context, index) { + return ReviewTile(restaurant.reviews![index]); + }, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), + itemCount: restaurant.reviews!.length), + ], + ) + : const Text('This restaurant has no reviews'); + } + + Widget _buildOverallRating(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(16), + const Text('Overall Rating'), + const Gap(16), + Row( + children: [ + Text(restaurant.rating.toString(), + style: Theme.of(context) + .textTheme + .headlineLarge! + .copyWith(fontFamily: 'Lora', fontWeight: FontWeight.bold)), + const Padding( + padding: const EdgeInsets.only(left: 2, top: 16.0), + child: const RestaurantStarRating(1), + ), + ], + ), + const Gap(16) + ], + ); + } +} diff --git a/lib/view/theme/app_theme.dart b/lib/view/theme/app_theme.dart new file mode 100644 index 0000000..de87939 --- /dev/null +++ b/lib/view/theme/app_theme.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class AppTheme { + static const Color _primaryColor = + Color(0xFF007AFF); // Replace with your desired primary color + static const Color _accentColor = + Color(0xFFF0F0F0); // Replace with your desired accent color + + static final ThemeData lightTheme = ThemeData( + colorSchemeSeed: Colors.black, + fontFamily: 'OpenSans', + cardTheme: + CardTheme(color: Colors.white, surfaceTintColor: Colors.transparent), + textTheme: const TextTheme( + headlineMedium: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 16.0, + color: Colors.black, + ), + titleLarge: TextStyle( + fontFamily: 'Lora', + fontWeight: FontWeight.w500, + fontSize: 16.0, + ), + titleMedium: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 14.0, + color: Colors.black, + ), + bodyMedium: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 12.0, + color: Colors.black, + ), + ), + appBarTheme: const AppBarTheme( + backgroundColor: Colors.white, + surfaceTintColor: Colors.transparent, + elevation: 1, + titleTextStyle: TextStyle( + fontFamily: 'Lora', + fontWeight: FontWeight.w700, + fontSize: 18.0, + color: Colors.black), + ), + indicatorColor: Colors.black, + tabBarTheme: const TabBarTheme( + tabAlignment: TabAlignment.center, + indicatorColor: Colors.black, + labelStyle: TextStyle( + fontFamily: 'OpenSans', + fontWeight: FontWeight.w600, + fontSize: 14.0, + ), + dividerColor: Colors.transparent, + labelColor: Colors.black, + unselectedLabelColor: Color(0xFF606060), + unselectedLabelStyle: TextStyle( + fontFamily: 'OpenSans', + fontWeight: FontWeight.w600, + fontSize: 14.0, + )), + ); +} diff --git a/lib/view/widgets/restaurant_open.dart b/lib/view/widgets/restaurant_open.dart new file mode 100644 index 0000000..79a5201 --- /dev/null +++ b/lib/view/widgets/restaurant_open.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; + +class RestaurantOpen extends StatelessWidget { + const RestaurantOpen(this.isOpen, {super.key}); + final bool isOpen; + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + isOpen ? 'Open Now' : 'Closed', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontStyle: FontStyle.italic), + ), + const Gap(6), + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Icon( + Icons.circle, + size: 8, + color: isOpen ? Colors.green : Colors.red, + ), + ) + ], + ); + } +} diff --git a/lib/view/widgets/restaurant_star_rating.dart b/lib/view/widgets/restaurant_star_rating.dart new file mode 100644 index 0000000..c90d5ff --- /dev/null +++ b/lib/view/widgets/restaurant_star_rating.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class RestaurantStarRating extends StatelessWidget { + const RestaurantStarRating(this.rating, {super.key}); + final starColor = const Color(0xFFFFB800); + final int rating; + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + for (int i = 0; i <= rating; i++) + Icon( + Icons.star, + size: 12, + color: starColor, + ) + ], + ); + } +} diff --git a/lib/view/widgets/restaurant_tile.dart b/lib/view/widgets/restaurant_tile.dart new file mode 100644 index 0000000..9e10708 --- /dev/null +++ b/lib/view/widgets/restaurant_tile.dart @@ -0,0 +1,85 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:gap/gap.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/view/pages/restaurant_page.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_open.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_star_rating.dart'; + +class RestaurantTile extends StatelessWidget { + const RestaurantTile({required this.restaurant, super.key}); + final Restaurant restaurant; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => RestaurantPage(restaurant), + )), + child: Container( + decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface), + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Hero( + tag: restaurant.id ?? 'restaurant_id', + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.network( + width: 88, + height: 88, + restaurant.photos?.first ?? + 'https://picsum.photos/200/300'), + ), + ), + const Gap(12), + Expanded( + child: SizedBox( + height: 88, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + restaurant.name ?? 'Restaurant Name', + style: Theme.of(context).textTheme.titleLarge, + ), + ), + Row( + children: [ + Text( + restaurant.price ?? "\$\$", + style: Theme.of(context).textTheme.bodyMedium, + ), + Gap(4), + Text( + restaurant.categories?.first.title ?? "", + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RestaurantStarRating( + restaurant.rating?.toInt() ?? 2), + RestaurantOpen(restaurant.isOpen) + ], + ) + ], + ), + ), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/view/widgets/review_tile.dart b/lib/view/widgets/review_tile.dart new file mode 100644 index 0000000..a64f58f --- /dev/null +++ b/lib/view/widgets/review_tile.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_star_rating.dart'; + +class ReviewTile extends StatelessWidget { + const ReviewTile(this.review, {super.key}); + final Review review; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RestaurantStarRating(review.rating ?? 1), + const Gap(12), + Text( + review.text ?? 'no comment', + style: Theme.of(context).textTheme.bodyMedium, + ), + Gap(12), + Row( + children: [ + CircleAvatar( + foregroundImage: NetworkImage( + review.user?.imageUrl ?? 'https://picsum.photos/200/300'), + ), + const Gap(8), + Text(review.user?.name ?? 'user name') + ], + ) + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 1f19d24..6869ad5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -256,6 +256,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + gap: + dependency: "direct main" + description: + name: gap + sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d + url: "https://pub.dev" + source: hosted + version: "3.0.1" glob: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f5ec5f1..82ac669 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: bloc_test: ^9.1.7 flutter: sdk: flutter + gap: ^3.0.1 http: ^1.2.2 json_annotation: ^4.9.0 mockito: ^5.4.4 From 99b05600e9142e12ac3be4c7dbbeb2b520c8568e Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Mon, 16 Sep 2024 20:24:50 -0400 Subject: [PATCH 05/12] feat(view) Add flutter bloc for list and detail Add dependency injection Add favorite property Update repository for to check favorite restaurants --- .../memory_restaurants_datasource.dart | 81 ++++------- lib/di/di.dart | 25 ++++ .../repository/restaurants_repository.dart | 24 +++- lib/main.dart | 14 +- lib/models/restaurant.dart | 28 ++++ lib/view/pages/main_page.dart | 135 ++++++++++++------ lib/view/pages/restaurant_page.dart | 65 +++++++-- lib/view/theme/app_theme.dart | 9 +- lib/view/widgets/restaurant_tile.dart | 116 +++++++-------- pubspec.lock | 32 +++++ pubspec.yaml | 2 + 11 files changed, 349 insertions(+), 182 deletions(-) create mode 100644 lib/di/di.dart diff --git a/lib/data/datasource/memory_restaurants_datasource.dart b/lib/data/datasource/memory_restaurants_datasource.dart index 75af7e6..6809274 100644 --- a/lib/data/datasource/memory_restaurants_datasource.dart +++ b/lib/data/datasource/memory_restaurants_datasource.dart @@ -1,60 +1,37 @@ +import 'dart:math'; + import 'package:restaurant_tour/common/exceptions/exceptions.dart'; import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; import 'package:restaurant_tour/models/restaurant.dart'; class MemoryRestaurantsDatasource implements RestaurantsDatasource { - List restaurants = [ - Restaurant( - id: "restaurant_1", - name: "Amazing Pizza", - price: "\$15-\$20", - rating: 4.8, - photos: ["https://example.com/pizza.jpg"], - categories: [Category(title: "Italian")], - hours: [const Hours(isOpenNow: true)], - location: Location(formattedAddress: "123 Main St, Anytown, CA"), - ), - Restaurant( - id: "restaurant_2", - name: "Cozy Sushi", - price: "\$20-\$30", - rating: 4.7, - photos: ["https://example.com/sushi.jpg"], - categories: [Category(title: "Japanese")], - hours: [const Hours(isOpenNow: false)], - location: Location(formattedAddress: "456 Elm St, Anytown, CA"), - ), - Restaurant( - id: "restaurant_3", - name: "Burger Barn", - price: "\$10-\$15", - rating: 4.2, - photos: ["https://example.com/burger.jpg"], - categories: [Category(title: "American")], - hours: [const Hours(isOpenNow: true)], - location: Location(formattedAddress: "789 Maple St, Anytown, CA"), - ), - Restaurant( - id: "restaurant_4", - name: "Healthy Bowls", - price: "\$12-\$18", - rating: 4.9, - photos: ["https://example.com/salad.jpg"], - categories: [Category(title: "Healthy")], - hours: [const Hours(isOpenNow: false)], - location: Location(formattedAddress: "1011 Oak St, Anytown, CA"), - ), - Restaurant( - id: "restaurant_5", - name: "Taco Fiesta", - price: "\$8-\$12", - rating: 4.5, - photos: ["https://example.com/tacos.jpg"], - categories: [Category(title: "Mexican")], - hours: [const Hours(isOpenNow: true)], - location: Location(formattedAddress: "1234 Pine St, Anytown, CA"), - ), - ]; + final restaurants = [1, 2, 3, 4, 5] + .map( + (e) => Restaurant( + id: "restaurant_$e", + name: "Amazing Pizza", + price: "\$\$\$", + rating: 4.8, + photos: ["https://picsum.photos/361/360"], + categories: [Category(alias: 'demo', title: 'Italian')], + hours: [const Hours(isOpenNow: true)], + location: Location(formattedAddress: "123 Main St, Anytown, CA"), + reviews: [1, 2, 3, 4, 5, 6] + .map((e) => const Review( + id: 'review_id', + text: + 'Review text goes here. Review text goes here. This is a review. This is a review that is 3 lines long.', + rating: 5, + user: const User( + id: 'user_id', + imageUrl: 'https://picsum.photos/200/300', + name: 'Test User', + ), + )) + .toList(), + ), + ) + .toList(); @override Future getRestaurant(String id) { try { diff --git a/lib/di/di.dart b/lib/di/di.dart new file mode 100644 index 0000000..6295d71 --- /dev/null +++ b/lib/di/di.dart @@ -0,0 +1,25 @@ +import 'package:get_it/get_it.dart'; +import 'package:restaurant_tour/data/datasource/memory_favorite_restaurants_datasource.dart'; +import 'package:restaurant_tour/data/datasource/memory_restaurants_datasource.dart'; + +import 'package:restaurant_tour/domain/repository/restaurants_repository.dart'; +import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; +import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; +import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; + +final getIt = GetIt.instance; + +void setup() { + final restaurantDataSource = MemoryRestaurantsDatasource(); + final favoriteRestaurantsDatasource = MemoryFavoriteRestaurantsDatasource(); + final repository = RestaurantsRepository( + restaurantDataSource, favoriteRestaurantsDatasource); + final usecase = RestaurantsUsecase(repository); + + getIt.registerSingleton( + RestaurantsBloc(usecase), + ); + getIt.registerSingleton( + FavoritesRestaurantsBloc(usecase), + ); +} diff --git a/lib/domain/repository/restaurants_repository.dart b/lib/domain/repository/restaurants_repository.dart index 8d0dcea..fffabdc 100644 --- a/lib/domain/repository/restaurants_repository.dart +++ b/lib/domain/repository/restaurants_repository.dart @@ -10,16 +10,34 @@ class RestaurantsRepository { final FavoriteRestaurantsDatasource _favoriteRestaurantsDatasource; final RestaurantsDatasource _restaurantsDatasource; - Future> getRestaurants() { - return _restaurantsDatasource.getRestaurants(); + Future> getRestaurants() async { + final favoriteRestaurantIds = + await _favoriteRestaurantsDatasource.getListRestaurantsIds(); + final restaurants = await _restaurantsDatasource.getRestaurants(); + + return restaurants + .map( + (e) => favoriteRestaurantIds.contains(e.id!) + ? e.copyWith(isFavorite: true) + : e, + ) + .toList(); } Future> getFavoriteRestaurants() async { final favoriteRestaurantIds = await _favoriteRestaurantsDatasource.getListRestaurantsIds(); - return _restaurantsDatasource.getRestaurants( + if (favoriteRestaurantIds.isEmpty) { + return []; + } + final favoriteRestaurants = await _restaurantsDatasource.getRestaurants( filterByIds: favoriteRestaurantIds, ); + + return favoriteRestaurants + .map((e) => e.copyWith(isFavorite: true)) + .toList(); + ; } Future getRestaurant(String id) { diff --git a/lib/main.dart b/lib/main.dart index 09b784a..68fad1f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; +import 'package:restaurant_tour/di/di.dart'; import 'package:restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/query.dart'; import 'package:restaurant_tour/view/pages/main_page.dart'; @@ -14,9 +15,20 @@ void main() { runApp(const RestaurantTour()); } -class RestaurantTour extends StatelessWidget { +class RestaurantTour extends StatefulWidget { const RestaurantTour({super.key}); + @override + State createState() => _RestaurantTourState(); +} + +class _RestaurantTourState extends State { + @override + void initState() { + setup(); + super.initState(); + } + @override Widget build(BuildContext context) { return MaterialApp( diff --git a/lib/models/restaurant.dart b/lib/models/restaurant.dart index 1c7ad2f..896e540 100644 --- a/lib/models/restaurant.dart +++ b/lib/models/restaurant.dart @@ -95,6 +95,7 @@ class Restaurant { final List? hours; final List? reviews; final Location? location; + final bool? isFavorite; const Restaurant({ this.id, @@ -106,8 +107,35 @@ class Restaurant { this.hours, this.reviews, this.location, + this.isFavorite, }); + Restaurant copyWith({ + String? id, + String? name, + String? price, + double? rating, + List? photos, + List? categories, + List? hours, + List? reviews, + Location? location, + bool? isFavorite, + }) { + return Restaurant( + id: id ?? this.id, + name: name ?? this.name, + price: price ?? this.price, + rating: rating ?? this.rating, + photos: photos ?? this.photos, + categories: categories ?? this.categories, + hours: hours ?? this.hours, + reviews: reviews ?? this.reviews, + location: location ?? this.location, + isFavorite: isFavorite ?? this.isFavorite, + ); + } + factory Restaurant.fromJson(Map json) => _$RestaurantFromJson(json); diff --git a/lib/view/pages/main_page.dart b/lib/view/pages/main_page.dart index 28474b5..6d614da 100644 --- a/lib/view/pages/main_page.dart +++ b/lib/view/pages/main_page.dart @@ -2,12 +2,28 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; +import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; +import 'package:restaurant_tour/view/pages/restaurant_page.dart'; import 'package:restaurant_tour/view/widgets/restaurant_tile.dart'; -class MainPage extends StatelessWidget { +class MainPage extends StatefulWidget { const MainPage({super.key}); + @override + State createState() => _MainPageState(); +} + +class _MainPageState extends State { + @override + void initState() { + GetIt.I.get().add(LoadRestaurants()); + GetIt.I.get().add(LoadFavoritesRestaurants()); + super.initState(); + } + @override Widget build(BuildContext context) { return DefaultTabController( @@ -29,7 +45,7 @@ class MainPage extends StatelessWidget { ), Padding( padding: const EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), - child: _buildRestaurantList(), + child: _buildFavoritesRestaurantsList(), ), ]), ), @@ -38,46 +54,79 @@ class MainPage extends StatelessWidget { } Widget _buildRestaurantList() { - final _random = new Random(); - final min = 1; - final max = 5; - final restaurants = [1, 2, 3, 4, 5] - .map( - (e) => Restaurant( - id: "restaurant_$e", - name: "Amazing Pizza", - price: "\$\$\$", - rating: 4.8, - photos: ["https://picsum.photos/361/360"], - categories: [Category(alias: 'demo', title: 'Italian')], - hours: [const Hours(isOpenNow: true)], - location: Location(formattedAddress: "123 Main St, Anytown, CA"), - reviews: [1, 2, 3, 4, 5, 6] - .map((e) => Review( - id: 'review_id', - text: - 'Review text goes here. Review text goes here. This is a review. This is a review that is 3 lines long.', - rating: min + _random.nextInt(max - min), - user: const User( - id: 'user_id', - imageUrl: 'https://picsum.photos/200/300', - name: 'Test User', + return BlocBuilder( + bloc: GetIt.I.get(), + builder: (context, state) { + if (state is RestaurantsReady) { + return ListView.separated( + separatorBuilder: (context, index) => const SizedBox( + height: 2, + ), + itemCount: state.restaurants.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => RestaurantPage( + restaurant: state.restaurants[index], + ), + ), + ), + child: RestaurantTile(restaurant: state.restaurants[index])); + }); + } + if (state is RestaurantsError) { + return Center( + child: Text(state.exception.description), + ); + } + + if (state is RestaurantsLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return Container(); + }, + ); +} + +Widget _buildFavoritesRestaurantsList() { + return BlocBuilder( + bloc: GetIt.I.get(), + builder: (context, state) { + if (state is FavoritesRestaurantsReady) { + return ListView.separated( + separatorBuilder: (context, index) => const SizedBox( + height: 2, + ), + itemCount: state.favoritesRestaurants.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => RestaurantPage( + restaurant: state.favoritesRestaurants[index], ), - )) - .toList(), - ), - ) - .toList(); + ), + ), + child: RestaurantTile( + restaurant: state.favoritesRestaurants[index]), + ); + }); + } + if (state is FavoriteRestaurantsListError) { + return Center( + child: Text(state.exception.description), + ); + } - return ListView.separated( - separatorBuilder: (context, index) => const SizedBox( - height: 2, - ), - itemCount: restaurants.length, - itemBuilder: (context, index) { - return RestaurantTile(restaurant: restaurants[index]); - }); - // return const Center( - // child: CircularProgressIndicator(), - // ); + if (state is FavoritesRestaurantsLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return Container(); + }, + ); } diff --git a/lib/view/pages/restaurant_page.dart b/lib/view/pages/restaurant_page.dart index ed3ed1d..28116cd 100644 --- a/lib/view/pages/restaurant_page.dart +++ b/lib/view/pages/restaurant_page.dart @@ -1,30 +1,64 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; +import 'package:get_it/get_it.dart'; import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; +import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; import 'package:restaurant_tour/view/widgets/restaurant_open.dart'; import 'package:restaurant_tour/view/widgets/restaurant_star_rating.dart'; import 'package:restaurant_tour/view/widgets/review_tile.dart'; -class RestaurantPage extends StatelessWidget { - const RestaurantPage(this.restaurant, {super.key}); +class RestaurantPage extends StatefulWidget { + const RestaurantPage({ + super.key, + required this.restaurant, + }); final Restaurant restaurant; + + @override + State createState() => _RestaurantPageState(); +} + +class _RestaurantPageState extends State { + late bool isFavoriteState = false; + @override + void initState() { + isFavoriteState = widget.restaurant.isFavorite ?? false; + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(restaurant.name ?? 'Restaurant Name'), + title: Text(widget.restaurant.name ?? 'Restaurant Name'), actions: [ - IconButton(onPressed: () {}, icon: Icon(Icons.favorite_outline)) + IconButton( + onPressed: () { + GetIt.I.get().add((isFavoriteState) + ? RemoveFavoriteRestaurant(widget.restaurant) + : AddFavoriteRestaurant(widget.restaurant)); + + GetIt.I.get().add(LoadRestaurants()); + + setState(() { + isFavoriteState = !isFavoriteState; + }); + }, + icon: (isFavoriteState) + ? const Icon(Icons.favorite) + : const Icon(Icons.favorite_outline)) ], ), body: SingleChildScrollView( child: Column( children: [ Hero( - tag: restaurant.id ?? 'restaurant_id', + tag: widget.restaurant.id ?? 'restaurant_id', child: Image.network( height: 375, - restaurant.photos?.first ?? 'https://picsum.photos/375/361', + widget.restaurant.photos?.first ?? + 'https://picsum.photos/375/361', ), ), Padding( @@ -39,17 +73,17 @@ class RestaurantPage extends StatelessWidget { Row( children: [ Text( - restaurant.price ?? "\$\$", + widget.restaurant.price ?? "\$\$", style: Theme.of(context).textTheme.bodyMedium, ), Gap(4), Text( - restaurant.categories?.first.title ?? "", + widget.restaurant.categories?.first.title ?? "", style: Theme.of(context).textTheme.bodyMedium, ), ], ), - RestaurantOpen(restaurant.isOpen) + RestaurantOpen(widget.restaurant.isOpen) ], ), const Gap(16), @@ -58,7 +92,8 @@ class RestaurantPage extends StatelessWidget { const Text('Address'), const Gap(16), Text( - restaurant.location?.formattedAddress ?? 'Address info', + widget.restaurant.location?.formattedAddress ?? + 'Address info', style: Theme.of(context).textTheme.titleLarge!.copyWith( fontFamily: 'OpenSans', fontWeight: FontWeight.w600), ), @@ -77,13 +112,13 @@ class RestaurantPage extends StatelessWidget { } Widget _buildReviewSection(BuildContext context) { - return (restaurant.reviews != null) + return (widget.restaurant.reviews != null) ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Gap(16), Text( - '${restaurant.reviews!.length} Reviews', + '${widget.restaurant.reviews!.length} Reviews', style: Theme.of(context).textTheme.bodyMedium, ), Gap(16), @@ -91,13 +126,13 @@ class RestaurantPage extends StatelessWidget { physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemBuilder: (context, index) { - return ReviewTile(restaurant.reviews![index]); + return ReviewTile(widget.restaurant.reviews![index]); }, separatorBuilder: (context, index) => const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Divider(), ), - itemCount: restaurant.reviews!.length), + itemCount: widget.restaurant.reviews!.length), ], ) : const Text('This restaurant has no reviews'); @@ -112,7 +147,7 @@ class RestaurantPage extends StatelessWidget { const Gap(16), Row( children: [ - Text(restaurant.rating.toString(), + Text(widget.restaurant.rating.toString(), style: Theme.of(context) .textTheme .headlineLarge! diff --git a/lib/view/theme/app_theme.dart b/lib/view/theme/app_theme.dart index de87939..d2896b0 100644 --- a/lib/view/theme/app_theme.dart +++ b/lib/view/theme/app_theme.dart @@ -2,16 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class AppTheme { - static const Color _primaryColor = - Color(0xFF007AFF); // Replace with your desired primary color - static const Color _accentColor = - Color(0xFFF0F0F0); // Replace with your desired accent color - static final ThemeData lightTheme = ThemeData( colorSchemeSeed: Colors.black, fontFamily: 'OpenSans', - cardTheme: - CardTheme(color: Colors.white, surfaceTintColor: Colors.transparent), + cardTheme: const CardTheme( + color: Colors.white, surfaceTintColor: Colors.transparent), textTheme: const TextTheme( headlineMedium: TextStyle( fontWeight: FontWeight.w400, diff --git a/lib/view/widgets/restaurant_tile.dart b/lib/view/widgets/restaurant_tile.dart index 9e10708..532ad06 100644 --- a/lib/view/widgets/restaurant_tile.dart +++ b/lib/view/widgets/restaurant_tile.dart @@ -13,70 +13,64 @@ class RestaurantTile extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => RestaurantPage(restaurant), - )), - child: Container( - decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface), - child: Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Hero( - tag: restaurant.id ?? 'restaurant_id', - child: ClipRRect( - borderRadius: BorderRadius.circular(8.0), - child: Image.network( - width: 88, - height: 88, - restaurant.photos?.first ?? - 'https://picsum.photos/200/300'), - ), + return Container( + decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface), + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Hero( + tag: restaurant.id ?? 'restaurant_id', + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.network( + width: 88, + height: 88, + restaurant.photos?.first ?? + 'https://picsum.photos/200/300'), ), - const Gap(12), - Expanded( - child: SizedBox( - height: 88, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - restaurant.name ?? 'Restaurant Name', - style: Theme.of(context).textTheme.titleLarge, - ), - ), - Row( - children: [ - Text( - restaurant.price ?? "\$\$", - style: Theme.of(context).textTheme.bodyMedium, - ), - Gap(4), - Text( - restaurant.categories?.first.title ?? "", - style: Theme.of(context).textTheme.bodyMedium, - ), - ], + ), + const Gap(12), + Expanded( + child: SizedBox( + height: 88, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + restaurant.name ?? 'Restaurant Name', + style: Theme.of(context).textTheme.titleLarge, ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - RestaurantStarRating( - restaurant.rating?.toInt() ?? 2), - RestaurantOpen(restaurant.isOpen) - ], - ) - ], - ), + ), + Row( + children: [ + Text( + restaurant.price ?? "\$\$", + style: Theme.of(context).textTheme.bodyMedium, + ), + Gap(4), + Text( + restaurant.categories?.first.title ?? "", + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RestaurantStarRating(restaurant.rating?.toInt() ?? 2), + RestaurantOpen(restaurant.isOpen) + ], + ) + ], ), - ) - ], - ), + ), + ) + ], ), ), ), diff --git a/pubspec.lock b/pubspec.lock index 6869ad5..d4417b1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -235,6 +235,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a + url: "https://pub.dev" + source: hosted + version: "8.1.6" flutter_lints: dependency: "direct dev" description: @@ -264,6 +272,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 + url: "https://pub.dev" + source: hosted + version: "7.7.0" glob: dependency: transitive description: @@ -432,6 +448,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" node_preamble: dependency: transitive description: @@ -464,6 +488,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" pub_semver: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 82ac669..34c2f38 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,9 @@ dependencies: bloc_test: ^9.1.7 flutter: sdk: flutter + flutter_bloc: ^8.1.6 gap: ^3.0.1 + get_it: ^7.7.0 http: ^1.2.2 json_annotation: ^4.9.0 mockito: ^5.4.4 From ee58b968fba2d286f3890d56cc51369d49db76f4 Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Wed, 18 Sep 2024 08:36:13 -0400 Subject: [PATCH 06/12] refactor(view): add widgets and test widgets refactor for restaurant pagination add presentation tests --- .gitignore | 5 +- .vscode/launch.json | 6 +- ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Podfile | 44 ++ ios/Podfile.lock | 23 + ios/Runner.xcodeproj/project.pbxproj | 112 +++ .../contents.xcworkspacedata | 3 + lib/common/enviorment/enviorment.dart | 2 + lib/common/exceptions/exceptions.dart | 2 +- .../graphql_restaurants_datasource.dart | 47 ++ lib/{ => data/datasource/graphql}/query.dart | 4 +- .../sp_favorite_restaurants_datasource.dart | 40 + ...emory_favorite_restaurants_datasource.dart | 16 +- .../memory/memory_restaurants_datasource.dart | 42 ++ .../memory_restaurants_datasource.dart | 58 -- lib/di/di.dart | 60 +- .../favorite_restaurants_datasource.dart | 2 +- .../datasource/restaurants_datasource.dart | 5 +- .../repository/restaurants_repository.dart | 82 +- lib/domain/usecase/restaurants_usecase.dart | 8 +- lib/main.dart | 79 +- lib/models/restaurant.dart | 6 +- lib/models/restaurant.g.dart | 2 + .../favorites/favorites_restaurants_bloc.dart | 66 +- .../restaurants/restaurants_bloc.dart | 37 +- .../restaurants/restaurants_event.dart | 6 + .../restaurants/restaurants_state.dart | 25 +- lib/view/pages/main_page.dart | 90 +-- lib/view/pages/restaurant_page.dart | 129 +--- lib/view/widgets/address_widget.dart | 26 + lib/view/widgets/favorite_button_widget.dart | 34 + lib/view/widgets/favorite_list_widget.dart | 57 ++ lib/view/widgets/overall_rating_widget.dart | 35 + .../restaurant_category_price_widget.dart | 25 + lib/view/widgets/restaurant_hero_widget.dart | 21 + lib/view/widgets/restaurant_list_widget.dart | 94 +++ ..._open.dart => restaurant_open_widget.dart} | 4 +- ...art => restaurant_star_rating_widget.dart} | 6 +- lib/view/widgets/restaurant_tile.dart | 12 +- lib/view/widgets/review_list_widget.dart | 37 + lib/view/widgets/review_tile.dart | 7 +- pubspec.lock | 145 +++- pubspec.yaml | 7 +- .../usecase/restaurants_usecase_test.dart | 27 +- .../restaurants_usecase_test.mocks.dart | 86 +-- .../test_favorite_restaurants_bloc.mocks.dart | 89 +-- .../restaurants/test_restaurants_bloc.dart | 22 +- .../test_restaurants_bloc.mocks.dart | 89 +-- test/view/widgets/address_widget_test.dart | 31 + .../widgets/favorite_list_widget_test.dart | 63 ++ .../favorite_list_widget_test.mocks.dart | 712 ++++++++++++++++++ test/view/widgets/favorite_widget_test.dart | 43 ++ test/view/widgets/open_restaurant_widget.dart | 43 ++ test/view/widgets/overall_rating_widget.dart | 31 + .../widgets/price_category_widget_test.dart | 35 + .../widgets/restaurant_hero_widget_test.dart | 30 + .../widgets/restaurant_list_widget_test.dart | 58 ++ .../restaurant_star_rating_widget.dart | 37 + test/view/widgets/restaurant_tile_test.dart | 51 ++ test/view/widgets/review_tile_test.dart | 33 + 61 files changed, 2405 insertions(+), 588 deletions(-) create mode 100644 ios/Podfile create mode 100644 ios/Podfile.lock create mode 100644 lib/common/enviorment/enviorment.dart create mode 100644 lib/data/datasource/graphql/graphql_restaurants_datasource.dart rename lib/{ => data/datasource/graphql}/query.dart (81%) create mode 100644 lib/data/datasource/local/sp_favorite_restaurants_datasource.dart rename lib/data/datasource/{ => memory}/memory_favorite_restaurants_datasource.dart (68%) create mode 100644 lib/data/datasource/memory/memory_restaurants_datasource.dart delete mode 100644 lib/data/datasource/memory_restaurants_datasource.dart create mode 100644 lib/view/widgets/address_widget.dart create mode 100644 lib/view/widgets/favorite_button_widget.dart create mode 100644 lib/view/widgets/favorite_list_widget.dart create mode 100644 lib/view/widgets/overall_rating_widget.dart create mode 100644 lib/view/widgets/restaurant_category_price_widget.dart create mode 100644 lib/view/widgets/restaurant_hero_widget.dart create mode 100644 lib/view/widgets/restaurant_list_widget.dart rename lib/view/widgets/{restaurant_open.dart => restaurant_open_widget.dart} (86%) rename lib/view/widgets/{restaurant_star_rating.dart => restaurant_star_rating_widget.dart} (69%) create mode 100644 lib/view/widgets/review_list_widget.dart create mode 100644 test/view/widgets/address_widget_test.dart create mode 100644 test/view/widgets/favorite_list_widget_test.dart create mode 100644 test/view/widgets/favorite_list_widget_test.mocks.dart create mode 100644 test/view/widgets/favorite_widget_test.dart create mode 100644 test/view/widgets/open_restaurant_widget.dart create mode 100644 test/view/widgets/overall_rating_widget.dart create mode 100644 test/view/widgets/price_category_widget_test.dart create mode 100644 test/view/widgets/restaurant_hero_widget_test.dart create mode 100644 test/view/widgets/restaurant_list_widget_test.dart create mode 100644 test/view/widgets/restaurant_star_rating_widget.dart create mode 100644 test/view/widgets/restaurant_tile_test.dart create mode 100644 test/view/widgets/review_tile_test.dart diff --git a/.gitignore b/.gitignore index 7040cb0..6602f6e 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,7 @@ app.*.map.json # fvm # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ + +#Enviorment file +.env \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 5d0f1d3..82011f7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,11 @@ { "name": "app", "request": "launch", - "type": "dart" + "type": "dart", + "args": [ + "--dart-define-from-file", + ".env" + ] } ] } \ No newline at end of file diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..d97f17e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..05c6066 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,23 @@ +PODS: + - Flutter (1.0.0) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - Flutter (from `Flutter`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + +PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 + +COCOAPODS: 1.15.0 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 182fb57..1a323dc 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,12 +8,14 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 2E7AC0E2BF1914619CC87154 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CAF89E9B64414FEEC5D2A12E /* Pods_Runner.framework */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + CAFD963A19518EE06B28572F /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB9E9B0776B6D1DFE565129 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,8 +42,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 052B8C3ECEA6F7626E6ABB78 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2BF43EFC5F34CD0DCD05C5B2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; @@ -55,13 +59,28 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B3483201A35C22C977D73AEC /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + B9570B291C027B904606AAEE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + BA66C17775294B8B99618420 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + CAF89E9B64414FEEC5D2A12E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D7366470E54C01FD66673A14 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + DAB9E9B0776B6D1DFE565129 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 964F26580A7E44CC7BEBCCF8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CAFD963A19518EE06B28572F /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2E7AC0E2BF1914619CC87154 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -76,6 +95,20 @@ path = RunnerTests; sourceTree = ""; }; + 45B4539ABC9C5B0032715023 /* Pods */ = { + isa = PBXGroup; + children = ( + B9570B291C027B904606AAEE /* Pods-Runner.debug.xcconfig */, + B3483201A35C22C977D73AEC /* Pods-Runner.release.xcconfig */, + 2BF43EFC5F34CD0DCD05C5B2 /* Pods-Runner.profile.xcconfig */, + 052B8C3ECEA6F7626E6ABB78 /* Pods-RunnerTests.debug.xcconfig */, + BA66C17775294B8B99618420 /* Pods-RunnerTests.release.xcconfig */, + D7366470E54C01FD66673A14 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -94,6 +127,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 45B4539ABC9C5B0032715023 /* Pods */, + FE910C7D9B36D82D28120FD1 /* Frameworks */, ); sourceTree = ""; }; @@ -121,6 +156,15 @@ path = Runner; sourceTree = ""; }; + FE910C7D9B36D82D28120FD1 /* Frameworks */ = { + isa = PBXGroup; + children = ( + CAF89E9B64414FEEC5D2A12E /* Pods_Runner.framework */, + DAB9E9B0776B6D1DFE565129 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,8 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 72A43B65184BD54708A6665F /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 964F26580A7E44CC7BEBCCF8 /* Frameworks */, ); buildRules = ( ); @@ -145,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + E927B33ED62B0169B756632F /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 2874CF3702615B08A5FE951E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -222,6 +270,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2874CF3702615B08A5FE951E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -238,6 +303,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 72A43B65184BD54708A6665F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +340,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + E927B33ED62B0169B756632F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -379,6 +488,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 052B8C3ECEA6F7626E6ABB78 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -396,6 +506,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = BA66C17775294B8B99618420 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -411,6 +522,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D7366470E54C01FD66673A14 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/lib/common/enviorment/enviorment.dart b/lib/common/enviorment/enviorment.dart new file mode 100644 index 0000000..f80bc1e --- /dev/null +++ b/lib/common/enviorment/enviorment.dart @@ -0,0 +1,2 @@ +const yelpApiKey = String.fromEnvironment('YELP_API_KEY', defaultValue: ''); +const yelpBaseUrl = String.fromEnvironment('YELP_API_URL', defaultValue: ''); diff --git a/lib/common/exceptions/exceptions.dart b/lib/common/exceptions/exceptions.dart index d206ade..74e49f0 100644 --- a/lib/common/exceptions/exceptions.dart +++ b/lib/common/exceptions/exceptions.dart @@ -28,7 +28,7 @@ class RestaurantListException extends AppException { class FavoritesRestaurantsListException extends AppException { FavoritesRestaurantsListException({ code = "FAVORITES_RESTAURANTS_LIST_NOT_AVAILABLE", - description = "The favorite restaurant is not available at the moment", + description = "The favorite restaurant are not available at the moment", }) : super(code, description); } diff --git a/lib/data/datasource/graphql/graphql_restaurants_datasource.dart b/lib/data/datasource/graphql/graphql_restaurants_datasource.dart new file mode 100644 index 0000000..efabd94 --- /dev/null +++ b/lib/data/datasource/graphql/graphql_restaurants_datasource.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:restaurant_tour/common/exceptions/exceptions.dart'; +import 'package:restaurant_tour/data/datasource/graphql/query.dart'; +import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:http/http.dart' as http; +import 'package:restaurant_tour/common/enviorment/enviorment.dart'; + +class GraphqlRestaurantsDatasource implements RestaurantsDatasource { + @override + Future> getRestaurants({ + int offset = 0, + int limit = 10, + }) async { + final headers = { + 'Authorization': 'Bearer $yelpApiKey', + 'Content-Type': 'application/graphql', + }; + + try { + final response = await http.post( + Uri.parse(yelpBaseUrl), + headers: headers, + body: query(offset, limit), + ); + + if (response.statusCode == 200) { + debugPrint( + jsonDecode(response.body)['data']['search'], + ); + final restaurantsResult = RestaurantQueryResult.fromJson( + jsonDecode(response.body)['data']['search'], + ); + return restaurantsResult.restaurants ?? []; + } else { + throw RestaurantListException( + description: 'Failed to load restaurants: ${response.statusCode}'); + } + } catch (e) { + throw RestaurantListException( + description: 'Error fetching restaurants: $e', + ); + } + } +} diff --git a/lib/query.dart b/lib/data/datasource/graphql/query.dart similarity index 81% rename from lib/query.dart rename to lib/data/datasource/graphql/query.dart index 7a8993b..d0b325b 100644 --- a/lib/query.dart +++ b/lib/data/datasource/graphql/query.dart @@ -1,6 +1,6 @@ -String query(int offset) => ''' +String query(int offset, int limit) => ''' query getRestaurants { - search(location: "Las Vegas", limit: 20, offset: $offset) { + search(location: "Las Vegas", limit: $limit, offset: $offset) { total business { id diff --git a/lib/data/datasource/local/sp_favorite_restaurants_datasource.dart b/lib/data/datasource/local/sp_favorite_restaurants_datasource.dart new file mode 100644 index 0000000..f753b2c --- /dev/null +++ b/lib/data/datasource/local/sp_favorite_restaurants_datasource.dart @@ -0,0 +1,40 @@ +import 'dart:convert'; + +import 'package:restaurant_tour/domain/datasource/favorite_restaurants_datasource.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class SpFavoriteRestaurantsDatasource implements FavoriteRestaurantsDatasource { + final String _favoriteResturantsKey = 'spFavoritesRestaurants'; + final SharedPreferences _sharedPreferences; + SpFavoriteRestaurantsDatasource(this._sharedPreferences); + @override + Future addFavoriteRestaurant(Restaurant restaurant) async { + final result = _sharedPreferences.getStringList(_favoriteResturantsKey); + final restaurantJsonString = jsonEncode(restaurant.toJson()); + if (result == null) { + _sharedPreferences + .setStringList(_favoriteResturantsKey, [restaurantJsonString]); + } else { + result.add(restaurantJsonString); + _sharedPreferences.setStringList(_favoriteResturantsKey, result); + } + } + + @override + Future> getFavoritesRestaurants() async { + final result = _sharedPreferences.getStringList(_favoriteResturantsKey); + + return result?.map((e) => Restaurant.fromJson(jsonDecode(e))).toList() ?? + []; + } + + @override + Future removeFavoriteRestaurant(Restaurant restaurant) async { + final result = _sharedPreferences.getStringList(_favoriteResturantsKey); + result?.removeWhere( + (restaurantJsonString) => restaurantJsonString.contains(restaurant.id!), + ); + _sharedPreferences.setStringList(_favoriteResturantsKey, result ?? []); + } +} diff --git a/lib/data/datasource/memory_favorite_restaurants_datasource.dart b/lib/data/datasource/memory/memory_favorite_restaurants_datasource.dart similarity index 68% rename from lib/data/datasource/memory_favorite_restaurants_datasource.dart rename to lib/data/datasource/memory/memory_favorite_restaurants_datasource.dart index 72b7062..aea2ce8 100644 --- a/lib/data/datasource/memory_favorite_restaurants_datasource.dart +++ b/lib/data/datasource/memory/memory_favorite_restaurants_datasource.dart @@ -3,23 +3,23 @@ import 'package:restaurant_tour/models/restaurant.dart'; class MemoryFavoriteRestaurantsDatasource implements FavoriteRestaurantsDatasource { - final List _favoriteRestaurantsIds = []; + final List _favoritesRestaurants = []; @override Future addFavoriteRestaurant(Restaurant restaurant) async { if (restaurant.id != null && restaurant.id!.isNotEmpty) { - _favoriteRestaurantsIds.add(restaurant.id!); + _favoritesRestaurants.add(restaurant); } } - @override - Future> getListRestaurantsIds() { - return Future.value(_favoriteRestaurantsIds); - } - @override Future removeFavoriteRestaurant(Restaurant restaurant) async { if (restaurant.id != null && restaurant.id!.isNotEmpty) { - _favoriteRestaurantsIds.remove(restaurant.id!); + _favoritesRestaurants.remove(restaurant); } } + + @override + Future> getFavoritesRestaurants() { + return Future.value(_favoritesRestaurants); + } } diff --git a/lib/data/datasource/memory/memory_restaurants_datasource.dart b/lib/data/datasource/memory/memory_restaurants_datasource.dart new file mode 100644 index 0000000..34a947d --- /dev/null +++ b/lib/data/datasource/memory/memory_restaurants_datasource.dart @@ -0,0 +1,42 @@ +import 'dart:math'; + +import 'package:restaurant_tour/common/exceptions/exceptions.dart'; +import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; + +class MemoryRestaurantsDatasource implements RestaurantsDatasource { + var restaurants = List.generate( + 100, + (i) => Restaurant( + id: "restaurant_$i", + name: "Amazing Pizza ${i + 1}", + price: "\$\$\$", + rating: 4.8, + photos: ["https://picsum.photos/361/360"], + categories: [Category(alias: 'demo', title: 'Italian')], + hours: [const Hours(isOpenNow: true)], + location: Location(formattedAddress: "123 Main St, Anytown, CA"), + reviews: [1, 2, 3, 4, 5, 6] + .map((e) => const Review( + id: 'review_id', + text: + 'Review text goes here. Review text goes here. This is a review. This is a review that is 3 lines long.', + rating: 5, + user: const User( + id: 'user_id', + imageUrl: 'https://picsum.photos/200/300', + name: 'Test User', + ), + )) + .toList(), + ), + growable: true); + + @override + Future> getRestaurants( + {int offset = 0, int limit = 10}) async { + final result = restaurants.sublist(offset, offset + limit); + await Future.delayed(const Duration(seconds: 4)); + return Future.value(result); + } +} diff --git a/lib/data/datasource/memory_restaurants_datasource.dart b/lib/data/datasource/memory_restaurants_datasource.dart deleted file mode 100644 index 6809274..0000000 --- a/lib/data/datasource/memory_restaurants_datasource.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'dart:math'; - -import 'package:restaurant_tour/common/exceptions/exceptions.dart'; -import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; -import 'package:restaurant_tour/models/restaurant.dart'; - -class MemoryRestaurantsDatasource implements RestaurantsDatasource { - final restaurants = [1, 2, 3, 4, 5] - .map( - (e) => Restaurant( - id: "restaurant_$e", - name: "Amazing Pizza", - price: "\$\$\$", - rating: 4.8, - photos: ["https://picsum.photos/361/360"], - categories: [Category(alias: 'demo', title: 'Italian')], - hours: [const Hours(isOpenNow: true)], - location: Location(formattedAddress: "123 Main St, Anytown, CA"), - reviews: [1, 2, 3, 4, 5, 6] - .map((e) => const Review( - id: 'review_id', - text: - 'Review text goes here. Review text goes here. This is a review. This is a review that is 3 lines long.', - rating: 5, - user: const User( - id: 'user_id', - imageUrl: 'https://picsum.photos/200/300', - name: 'Test User', - ), - )) - .toList(), - ), - ) - .toList(); - @override - Future getRestaurant(String id) { - try { - return Future.value( - restaurants.where((restaurant) => restaurant.id == id).first, - ); - } on StateError { - throw RestaurantNotFoundException(); - } - } - - @override - Future> getRestaurants({ - List filterByIds = const [], - }) { - return Future.value( - filterByIds.isEmpty - ? restaurants - : restaurants - .where((restaurant) => filterByIds.contains(restaurant.id)) - .toList(), - ); - } -} diff --git a/lib/di/di.dart b/lib/di/di.dart index 6295d71..d8a10c7 100644 --- a/lib/di/di.dart +++ b/lib/di/di.dart @@ -1,25 +1,61 @@ import 'package:get_it/get_it.dart'; -import 'package:restaurant_tour/data/datasource/memory_favorite_restaurants_datasource.dart'; -import 'package:restaurant_tour/data/datasource/memory_restaurants_datasource.dart'; - +import 'package:restaurant_tour/data/datasource/graphql/graphql_restaurants_datasource.dart'; +import 'package:restaurant_tour/data/datasource/local/sp_favorite_restaurants_datasource.dart'; +import 'package:restaurant_tour/data/datasource/memory/memory_restaurants_datasource.dart'; +import 'package:restaurant_tour/domain/datasource/favorite_restaurants_datasource.dart'; +import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; import 'package:restaurant_tour/domain/repository/restaurants_repository.dart'; import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; +import 'package:shared_preferences/shared_preferences.dart'; final getIt = GetIt.instance; void setup() { - final restaurantDataSource = MemoryRestaurantsDatasource(); - final favoriteRestaurantsDatasource = MemoryFavoriteRestaurantsDatasource(); - final repository = RestaurantsRepository( - restaurantDataSource, favoriteRestaurantsDatasource); - final usecase = RestaurantsUsecase(repository); + getIt.registerSingletonAsync( + () => SharedPreferences.getInstance(), + ); + + getIt.registerSingletonAsync( + () async => SpFavoriteRestaurantsDatasource(GetIt.I.get()), + dependsOn: [InitDependency(SharedPreferences)]); + getIt.registerSingleton( + MemoryRestaurantsDatasource(), + ); - getIt.registerSingleton( - RestaurantsBloc(usecase), + getIt.registerSingletonAsync( + () async => RestaurantsRepository( + GetIt.I.get(), + GetIt.I.get(), + ), + dependsOn: [ + InitDependency(FavoriteRestaurantsDatasource), + ], + ); + + getIt.registerSingletonAsync( + () async => RestaurantsUsecase( + GetIt.I.get(), + ), + dependsOn: [ + InitDependency(RestaurantsRepository), + ], + ); + getIt.registerSingletonAsync( + () async => RestaurantsBloc( + GetIt.I.get(), + ), + dependsOn: [ + InitDependency(RestaurantsUsecase), + ], ); - getIt.registerSingleton( - FavoritesRestaurantsBloc(usecase), + getIt.registerSingletonAsync( + () async => FavoritesRestaurantsBloc( + GetIt.I.get(), + ), + dependsOn: [ + InitDependency(RestaurantsUsecase), + ], ); } diff --git a/lib/domain/datasource/favorite_restaurants_datasource.dart b/lib/domain/datasource/favorite_restaurants_datasource.dart index dff1290..5038d86 100644 --- a/lib/domain/datasource/favorite_restaurants_datasource.dart +++ b/lib/domain/datasource/favorite_restaurants_datasource.dart @@ -3,5 +3,5 @@ import 'package:restaurant_tour/models/restaurant.dart'; abstract class FavoriteRestaurantsDatasource { Future addFavoriteRestaurant(Restaurant restaurant); Future removeFavoriteRestaurant(Restaurant restaurant); - Future> getListRestaurantsIds(); + Future> getFavoritesRestaurants(); } diff --git a/lib/domain/datasource/restaurants_datasource.dart b/lib/domain/datasource/restaurants_datasource.dart index 323cfcb..fe00284 100644 --- a/lib/domain/datasource/restaurants_datasource.dart +++ b/lib/domain/datasource/restaurants_datasource.dart @@ -1,8 +1,5 @@ import 'package:restaurant_tour/models/restaurant.dart'; abstract class RestaurantsDatasource { - Future> getRestaurants({ - List filterByIds = const [], - }); - Future getRestaurant(String id); + Future> getRestaurants({int offset, int limit}); } diff --git a/lib/domain/repository/restaurants_repository.dart b/lib/domain/repository/restaurants_repository.dart index fffabdc..15abada 100644 --- a/lib/domain/repository/restaurants_repository.dart +++ b/lib/domain/repository/restaurants_repository.dart @@ -3,52 +3,72 @@ import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; import 'package:restaurant_tour/models/restaurant.dart'; class RestaurantsRepository { - const RestaurantsRepository( + RestaurantsRepository( this._restaurantsDatasource, this._favoriteRestaurantsDatasource, ); final FavoriteRestaurantsDatasource _favoriteRestaurantsDatasource; final RestaurantsDatasource _restaurantsDatasource; + final List _restaurants = []; + final Set _favorites = {}; + Future> getRestaurants() async { - final favoriteRestaurantIds = - await _favoriteRestaurantsDatasource.getListRestaurantsIds(); - final restaurants = await _restaurantsDatasource.getRestaurants(); - - return restaurants - .map( - (e) => favoriteRestaurantIds.contains(e.id!) - ? e.copyWith(isFavorite: true) - : e, - ) - .toList(); + if (_restaurants.isEmpty) { + final remoteRestaurants = await _restaurantsDatasource.getRestaurants(); + final favoritesRestaurants = + await _favoriteRestaurantsDatasource.getFavoritesRestaurants(); + _restaurants.addAll( + remoteRestaurants + .map( + (e) => favoritesRestaurants.contains(e) + ? e.copyWith(isFavorite: true) + : e, + ) + .toList(), + ); + } + return _restaurants; } - Future> getFavoriteRestaurants() async { - final favoriteRestaurantIds = - await _favoriteRestaurantsDatasource.getListRestaurantsIds(); - if (favoriteRestaurantIds.isEmpty) { - return []; - } - final favoriteRestaurants = await _restaurantsDatasource.getRestaurants( - filterByIds: favoriteRestaurantIds, + Future getMoreRestaurants(int offset, int limit) async { + final newRemoteRestaurants = await _restaurantsDatasource.getRestaurants( + offset: offset, limit: limit); + _restaurants.addAll( + newRemoteRestaurants + .map( + (e) => _favorites.contains(e) ? e.copyWith(isFavorite: true) : e, + ) + .toList(), ); - - return favoriteRestaurants - .map((e) => e.copyWith(isFavorite: true)) - .toList(); - ; } - Future getRestaurant(String id) { - return _restaurantsDatasource.getRestaurant(id); + Future> getFavoriteRestaurants() async { + if (_favorites.isEmpty) { + final favoritesRestaurants = + await _favoriteRestaurantsDatasource.getFavoritesRestaurants(); + _favorites.addAll( + favoritesRestaurants.map((e) => e.copyWith(isFavorite: true))); + } + + return _favorites.toList(); } - Future addFavoriteRestaurant(Restaurant restaurant) { - return _favoriteRestaurantsDatasource.addFavoriteRestaurant(restaurant); + Future addFavoriteRestaurant(Restaurant restaurant) async { + _favorites.add(restaurant.copyWith(isFavorite: true)); + final restaurantIndex = + _restaurants.indexWhere((e) => e.id == restaurant.id); + _restaurants[restaurantIndex] = restaurant.copyWith(isFavorite: true); + await _favoriteRestaurantsDatasource + .addFavoriteRestaurant(restaurant.copyWith(isFavorite: true)); } - Future removeFavoriteRestaurant(Restaurant restaurant) { - return _favoriteRestaurantsDatasource.removeFavoriteRestaurant(restaurant); + Future removeFavoriteRestaurant(Restaurant restaurant) async { + final restaurantIndex = + _restaurants.indexWhere((e) => e.id == restaurant.id); + _restaurants[restaurantIndex] = restaurant.copyWith(isFavorite: false); + _favorites.remove(restaurant); + await _favoriteRestaurantsDatasource + .removeFavoriteRestaurant(restaurant.copyWith(isFavorite: false)); } } diff --git a/lib/domain/usecase/restaurants_usecase.dart b/lib/domain/usecase/restaurants_usecase.dart index 613f185..d567c10 100644 --- a/lib/domain/usecase/restaurants_usecase.dart +++ b/lib/domain/usecase/restaurants_usecase.dart @@ -13,10 +13,6 @@ class RestaurantsUsecase { return _repository.getFavoriteRestaurants(); } - Future getRestaurant(String id) { - return _repository.getRestaurant(id); - } - Future addFavoriteRestaurant(Restaurant restaurant) { return _repository.addFavoriteRestaurant(restaurant); } @@ -24,4 +20,8 @@ class RestaurantsUsecase { Future removeFavoriteRestaurant(Restaurant restaurant) { return _repository.removeFavoriteRestaurant(restaurant); } + + Future loadMoreRestaurants({required int offset, required int limit}) { + return _repository.getMoreRestaurants(offset, limit); + } } diff --git a/lib/main.dart b/lib/main.dart index 68fad1f..ecca42c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,16 +1,13 @@ import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; +import 'package:get_it/get_it.dart'; import 'package:restaurant_tour/di/di.dart'; import 'package:restaurant_tour/models/restaurant.dart'; -import 'package:restaurant_tour/query.dart'; +import 'package:restaurant_tour/data/datasource/graphql/query.dart'; import 'package:restaurant_tour/view/pages/main_page.dart'; import './view/theme/app_theme.dart'; -const _apiKey = ''; -const _baseUrl = 'https://api.yelp.com/v3/graphql'; - void main() { runApp(const RestaurantTour()); } @@ -34,69 +31,15 @@ class _RestaurantTourState extends State { return MaterialApp( title: 'Restaurant Tour', theme: AppTheme.lightTheme, - home: const MainPage(), - ); - } -} - -// 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'); - } - }, - ), - ], - ), - ), + home: FutureBuilder( + future: GetIt.I.allReady(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return const MainPage(); + } else { + return const Center(child: CircularProgressIndicator()); + } + }), ); } } diff --git a/lib/models/restaurant.dart b/lib/models/restaurant.dart index 896e540..3d081d7 100644 --- a/lib/models/restaurant.dart +++ b/lib/models/restaurant.dart @@ -1,3 +1,4 @@ +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; part 'restaurant.g.dart'; @@ -85,7 +86,7 @@ class Location { } @JsonSerializable() -class Restaurant { +class Restaurant extends Equatable { final String? id; final String? name; final String? price; @@ -165,6 +166,9 @@ class Restaurant { } return false; } + + @override + List get props => [id, isFavorite]; } @JsonSerializable() diff --git a/lib/models/restaurant.g.dart b/lib/models/restaurant.g.dart index dea6677..8cb457c 100644 --- a/lib/models/restaurant.g.dart +++ b/lib/models/restaurant.g.dart @@ -79,6 +79,7 @@ Restaurant _$RestaurantFromJson(Map json) => Restaurant( location: json['location'] == null ? null : Location.fromJson(json['location'] as Map), + isFavorite: json['isFavorite'] as bool?, ); Map _$RestaurantToJson(Restaurant instance) => @@ -92,6 +93,7 @@ Map _$RestaurantToJson(Restaurant instance) => 'hours': instance.hours, 'reviews': instance.reviews, 'location': instance.location, + 'isFavorite': instance.isFavorite, }; RestaurantQueryResult _$RestaurantQueryResultFromJson( diff --git a/lib/presentation/favorites/favorites_restaurants_bloc.dart b/lib/presentation/favorites/favorites_restaurants_bloc.dart index 2ccbaee..a84335b 100644 --- a/lib/presentation/favorites/favorites_restaurants_bloc.dart +++ b/lib/presentation/favorites/favorites_restaurants_bloc.dart @@ -1,4 +1,6 @@ import 'package:bloc/bloc.dart'; +import 'package:bloc_concurrency/bloc_concurrency.dart'; +import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; import 'package:restaurant_tour/common/exceptions/exceptions.dart'; import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; @@ -19,36 +21,46 @@ class FavoritesRestaurantsBloc } catch (e) { emit(FavoriteRestaurantsListError(FavoritesRestaurantsListException())); } - }); - on( - (event, emit) async { - try { - emit(FavoritesRestaurantsLoading()); - await usecase.addFavoriteRestaurant(event.restaurant); - final favoritesRestaurants = await usecase.getFavoriteRestaurants(); - emit(FavoritesRestaurantsReady(favoritesRestaurants)); - } catch (e) { - emit(AddFavoriteRestaurantsError(AddFavoriteRestaurantException())); + }, transformer: sequential()); + on((event, emit) async { + try { + while (state is FavoritesRestaurantsLoading) { + Future.delayed(const Duration(milliseconds: 500)); } - }, - ); + emit(FavoritesRestaurantsLoading()); + await usecase.addFavoriteRestaurant(event.restaurant); + final favoritesRestaurants = await usecase.getFavoriteRestaurants(); + emit(FavoritesRestaurantsReady(favoritesRestaurants)); + } catch (e) { + debugPrint(e.toString()); + emit(AddFavoriteRestaurantsError(AddFavoriteRestaurantException())); + } + }, transformer: sequential()); - on( - (event, emit) async { - try { - emit(FavoritesRestaurantsLoading()); - await usecase.removeFavoriteRestaurant(event.restaurant); - final favoritesRestaurants = await usecase.getFavoriteRestaurants(); - emit(FavoritesRestaurantsReady(favoritesRestaurants)); - } catch (e) { - emit( - RemoveFavoriteRestaurantsError( - RemoveFavoriteRestaurantException(), - ), - ); + on((event, emit) async { + try { + while (state is FavoritesRestaurantsLoading) { + Future.delayed(const Duration(milliseconds: 500)); } - }, - ); + emit(FavoritesRestaurantsLoading()); + await usecase.removeFavoriteRestaurant(event.restaurant); + final favoritesRestaurants = await usecase.getFavoriteRestaurants(); + emit(FavoritesRestaurantsReady(favoritesRestaurants)); + } catch (e) { + emit( + RemoveFavoriteRestaurantsError( + RemoveFavoriteRestaurantException(), + ), + ); + } + }, transformer: sequential()); } final RestaurantsUsecase usecase; + + @override + void onChange(Change change) { + // debugPrint( + // change.currentState.toString() + " - " + change.nextState.toString()); + super.onChange(change); + } } diff --git a/lib/presentation/restaurants/restaurants_bloc.dart b/lib/presentation/restaurants/restaurants_bloc.dart index 08648a6..420e1c7 100644 --- a/lib/presentation/restaurants/restaurants_bloc.dart +++ b/lib/presentation/restaurants/restaurants_bloc.dart @@ -1,4 +1,6 @@ import 'package:bloc/bloc.dart'; +import 'package:bloc_concurrency/bloc_concurrency.dart'; +import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; import 'package:restaurant_tour/common/exceptions/exceptions.dart'; import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; @@ -13,11 +15,42 @@ class RestaurantsBloc extends Bloc { emit(RestaurantsLoading()); try { final restaurants = await usecase.getRestaurants(); - emit(RestaurantsReady(restaurants)); + emit(RestaurantsReady( + restaurants, + size: restaurants.length, + )); + } on RestaurantListException catch (e) { + emit(RestaurantsError(e)); } catch (e) { emit(RestaurantsError(RestaurantListException())); } - }); + }, transformer: sequential()); + on( + (event, emit) async { + try { + if (state is RestaurantsReady) { + emit((state as RestaurantsReady).copyWith(isLoadingMore: true)); + await usecase.loadMoreRestaurants( + offset: event.offset, limit: event.limit); + final restaurants = await usecase.getRestaurants(); + + emit((state as RestaurantsReady).copyWith( + isLoadingMore: false, + restaurants: restaurants, + size: restaurants.length)); + } + } catch (e) { + emit((state as RestaurantsReady) + .copyWith(isLoadingMore: false, hasError: true)); + } + }, + ); } final RestaurantsUsecase usecase; + @override + void onChange(Change change) { + // debugPrint( + // change.currentState.toString() + " - " + change.nextState.toString()); + super.onChange(change); + } } diff --git a/lib/presentation/restaurants/restaurants_event.dart b/lib/presentation/restaurants/restaurants_event.dart index c347867..c190750 100644 --- a/lib/presentation/restaurants/restaurants_event.dart +++ b/lib/presentation/restaurants/restaurants_event.dart @@ -4,3 +4,9 @@ part of 'restaurants_bloc.dart'; sealed class RestaurantsEvent {} class LoadRestaurants extends RestaurantsEvent {} + +class AddMoreRestaurants extends RestaurantsEvent { + AddMoreRestaurants({required this.limit, required this.offset}); + final int limit; + final int offset; +} diff --git a/lib/presentation/restaurants/restaurants_state.dart b/lib/presentation/restaurants/restaurants_state.dart index aff5c1a..865bff7 100644 --- a/lib/presentation/restaurants/restaurants_state.dart +++ b/lib/presentation/restaurants/restaurants_state.dart @@ -8,8 +8,31 @@ final class RestaurantsInitial extends RestaurantsState {} final class RestaurantsLoading extends RestaurantsState {} final class RestaurantsReady extends RestaurantsState { - RestaurantsReady(this.restaurants); + RestaurantsReady(this.restaurants, + {this.isLoadingMore = false, + this.size = 10, + this.limit = 10, + this.hasError = false}); final List restaurants; + final bool isLoadingMore; + final bool hasError; + final int size; + final int limit; + RestaurantsReady copyWith({ + List? restaurants, + bool? isLoadingMore, + int? size, + int? limit, + bool? hasError, + }) { + return RestaurantsReady( + restaurants ?? this.restaurants, + isLoadingMore: isLoadingMore ?? this.isLoadingMore, + size: size ?? this.size, + hasError: hasError ?? this.hasError, + limit: limit ?? this.limit, + ); + } } final class RestaurantsError extends RestaurantsState { diff --git a/lib/view/pages/main_page.dart b/lib/view/pages/main_page.dart index 6d614da..7a6b059 100644 --- a/lib/view/pages/main_page.dart +++ b/lib/view/pages/main_page.dart @@ -7,6 +7,8 @@ import 'package:get_it/get_it.dart'; import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; import 'package:restaurant_tour/view/pages/restaurant_page.dart'; +import 'package:restaurant_tour/view/widgets/favorite_list_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_list_widget.dart'; import 'package:restaurant_tour/view/widgets/restaurant_tile.dart'; class MainPage extends StatefulWidget { @@ -38,95 +40,17 @@ class _MainPageState extends State { Tab(text: 'Favorites'), ]), ), - body: TabBarView(children: [ + body: const TabBarView(children: [ Padding( - padding: const EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), - child: _buildRestaurantList(), + padding: EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), + child: RestaurantListWidget(), ), Padding( - padding: const EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), - child: _buildFavoritesRestaurantsList(), + padding: EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), + child: FavoriteListWidget(), ), ]), ), ); } } - -Widget _buildRestaurantList() { - return BlocBuilder( - bloc: GetIt.I.get(), - builder: (context, state) { - if (state is RestaurantsReady) { - return ListView.separated( - separatorBuilder: (context, index) => const SizedBox( - height: 2, - ), - itemCount: state.restaurants.length, - itemBuilder: (context, index) { - return GestureDetector( - onTap: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => RestaurantPage( - restaurant: state.restaurants[index], - ), - ), - ), - child: RestaurantTile(restaurant: state.restaurants[index])); - }); - } - if (state is RestaurantsError) { - return Center( - child: Text(state.exception.description), - ); - } - - if (state is RestaurantsLoading) { - return const Center( - child: CircularProgressIndicator(), - ); - } - return Container(); - }, - ); -} - -Widget _buildFavoritesRestaurantsList() { - return BlocBuilder( - bloc: GetIt.I.get(), - builder: (context, state) { - if (state is FavoritesRestaurantsReady) { - return ListView.separated( - separatorBuilder: (context, index) => const SizedBox( - height: 2, - ), - itemCount: state.favoritesRestaurants.length, - itemBuilder: (context, index) { - return GestureDetector( - onTap: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => RestaurantPage( - restaurant: state.favoritesRestaurants[index], - ), - ), - ), - child: RestaurantTile( - restaurant: state.favoritesRestaurants[index]), - ); - }); - } - if (state is FavoriteRestaurantsListError) { - return Center( - child: Text(state.exception.description), - ); - } - - if (state is FavoritesRestaurantsLoading) { - return const Center( - child: CircularProgressIndicator(), - ); - } - return Container(); - }, - ); -} diff --git a/lib/view/pages/restaurant_page.dart b/lib/view/pages/restaurant_page.dart index 28116cd..9811184 100644 --- a/lib/view/pages/restaurant_page.dart +++ b/lib/view/pages/restaurant_page.dart @@ -4,9 +4,13 @@ import 'package:get_it/get_it.dart'; import 'package:restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; -import 'package:restaurant_tour/view/widgets/restaurant_open.dart'; -import 'package:restaurant_tour/view/widgets/restaurant_star_rating.dart'; -import 'package:restaurant_tour/view/widgets/review_tile.dart'; +import 'package:restaurant_tour/view/widgets/address_widget.dart'; +import 'package:restaurant_tour/view/widgets/favorite_button_widget.dart'; +import 'package:restaurant_tour/view/widgets/overall_rating_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_category_price_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_hero_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_open_widget.dart'; +import 'package:restaurant_tour/view/widgets/review_list_widget.dart'; class RestaurantPage extends StatefulWidget { const RestaurantPage({ @@ -33,33 +37,24 @@ class _RestaurantPageState extends State { appBar: AppBar( title: Text(widget.restaurant.name ?? 'Restaurant Name'), actions: [ - IconButton( - onPressed: () { - GetIt.I.get().add((isFavoriteState) - ? RemoveFavoriteRestaurant(widget.restaurant) - : AddFavoriteRestaurant(widget.restaurant)); + FavoriteButtonWidget( + callback: (isFavorite) { + GetIt.I.get().add((isFavorite) + ? AddFavoriteRestaurant(widget.restaurant) + : RemoveFavoriteRestaurant(widget.restaurant)); - GetIt.I.get().add(LoadRestaurants()); - - setState(() { - isFavoriteState = !isFavoriteState; - }); - }, - icon: (isFavoriteState) - ? const Icon(Icons.favorite) - : const Icon(Icons.favorite_outline)) + GetIt.I.get().add(LoadRestaurants()); + }, + isFavorite: widget.restaurant.isFavorite, + ) ], ), body: SingleChildScrollView( child: Column( children: [ - Hero( - tag: widget.restaurant.id ?? 'restaurant_id', - child: Image.network( - height: 375, - widget.restaurant.photos?.first ?? - 'https://picsum.photos/375/361', - ), + RestaurantHeroWidget( + imageUrl: widget.restaurant.photos?.first, + tag: widget.restaurant.id, ), Padding( padding: @@ -70,38 +65,20 @@ class _RestaurantPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Text( - widget.restaurant.price ?? "\$\$", - style: Theme.of(context).textTheme.bodyMedium, - ), - Gap(4), - Text( - widget.restaurant.categories?.first.title ?? "", - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - RestaurantOpen(widget.restaurant.isOpen) + RestaurantCategoryPriceWidget( + price: widget.restaurant.price, + category: widget.restaurant.categories?.first.title), + RestaurantOpenWidget(widget.restaurant.isOpen) ], ), const Gap(16), const Divider(), - const Gap(16), - const Text('Address'), - const Gap(16), - Text( - widget.restaurant.location?.formattedAddress ?? - 'Address info', - style: Theme.of(context).textTheme.titleLarge!.copyWith( - fontFamily: 'OpenSans', fontWeight: FontWeight.w600), - ), - const Gap(16), + AddressWidget( + address: widget.restaurant.location?.formattedAddress), const Divider(), - _buildOverallRating(context), + OverallRatingWidget(rating: widget.restaurant.rating), const Divider(), - _buildReviewSection(context) + ReviewListWidget(reviews: widget.restaurant.reviews) ], ), ) @@ -110,56 +87,4 @@ class _RestaurantPageState extends State { ), ); } - - Widget _buildReviewSection(BuildContext context) { - return (widget.restaurant.reviews != null) - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Gap(16), - Text( - '${widget.restaurant.reviews!.length} Reviews', - style: Theme.of(context).textTheme.bodyMedium, - ), - Gap(16), - ListView.separated( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: (context, index) { - return ReviewTile(widget.restaurant.reviews![index]); - }, - separatorBuilder: (context, index) => const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Divider(), - ), - itemCount: widget.restaurant.reviews!.length), - ], - ) - : const Text('This restaurant has no reviews'); - } - - Widget _buildOverallRating(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Gap(16), - const Text('Overall Rating'), - const Gap(16), - Row( - children: [ - Text(widget.restaurant.rating.toString(), - style: Theme.of(context) - .textTheme - .headlineLarge! - .copyWith(fontFamily: 'Lora', fontWeight: FontWeight.bold)), - const Padding( - padding: const EdgeInsets.only(left: 2, top: 16.0), - child: const RestaurantStarRating(1), - ), - ], - ), - const Gap(16) - ], - ); - } } diff --git a/lib/view/widgets/address_widget.dart b/lib/view/widgets/address_widget.dart new file mode 100644 index 0000000..50ef9d2 --- /dev/null +++ b/lib/view/widgets/address_widget.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; + +class AddressWidget extends StatelessWidget { + const AddressWidget({required this.address, super.key}); + final String? address; + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(16), + const Text('Address'), + const Gap(16), + Text( + address ?? 'Address info', + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith(fontFamily: 'OpenSans', fontWeight: FontWeight.w600), + ), + const Gap(16), + ], + ); + } +} diff --git a/lib/view/widgets/favorite_button_widget.dart b/lib/view/widgets/favorite_button_widget.dart new file mode 100644 index 0000000..9f24973 --- /dev/null +++ b/lib/view/widgets/favorite_button_widget.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class FavoriteButtonWidget extends StatefulWidget { + const FavoriteButtonWidget( + {required this.callback, required this.isFavorite, super.key}); + final Function(bool) callback; + final bool? isFavorite; + + @override + State createState() => _FavoriteButtonWidgetState(); +} + +class _FavoriteButtonWidgetState extends State { + bool isFavoriteState = false; + @override + void initState() { + isFavoriteState = widget.isFavorite ?? false; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return IconButton( + onPressed: () { + setState(() { + isFavoriteState = !isFavoriteState; + }); + widget.callback(isFavoriteState); + }, + icon: (isFavoriteState) + ? const Icon(Icons.favorite) + : const Icon(Icons.favorite_outline)); + } +} diff --git a/lib/view/widgets/favorite_list_widget.dart b/lib/view/widgets/favorite_list_widget.dart new file mode 100644 index 0000000..adaefba --- /dev/null +++ b/lib/view/widgets/favorite_list_widget.dart @@ -0,0 +1,57 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; +import 'package:restaurant_tour/view/pages/restaurant_page.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_tile.dart'; + +class FavoriteListWidget extends StatefulWidget { + const FavoriteListWidget({super.key}); + + @override + State createState() => _FavoriteListWidgetState(); +} + +class _FavoriteListWidgetState extends State { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: GetIt.I.get(), + builder: (context, state) { + if (state is FavoritesRestaurantsReady) { + return ListView.separated( + separatorBuilder: (context, index) => const SizedBox( + height: 2, + ), + itemCount: state.favoritesRestaurants.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => RestaurantPage( + restaurant: state.favoritesRestaurants[index], + ), + ), + ), + child: RestaurantTile( + restaurant: state.favoritesRestaurants[index]), + ); + }); + } + if (state is FavoriteRestaurantsListError) { + return Center( + child: Text(state.exception.description), + ); + } + + if (state is FavoritesRestaurantsLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return Container(); + }, + ); + } +} diff --git a/lib/view/widgets/overall_rating_widget.dart b/lib/view/widgets/overall_rating_widget.dart new file mode 100644 index 0000000..224ccb5 --- /dev/null +++ b/lib/view/widgets/overall_rating_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_star_rating_widget.dart'; + +class OverallRatingWidget extends StatelessWidget { + const OverallRatingWidget({required this.rating, super.key}); + final double? rating; + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(16), + const Text('Overall Rating'), + const Gap(16), + Row( + children: [ + Text( + rating?.toString() ?? '0', + style: Theme.of(context).textTheme.headlineLarge!.copyWith( + fontFamily: 'Lora', + fontWeight: FontWeight.bold, + ), + ), + const Padding( + padding: EdgeInsets.only(left: 2, top: 16.0), + child: RestaurantStarRatingWidget(1), + ), + ], + ), + const Gap(16), + ], + ); + } +} diff --git a/lib/view/widgets/restaurant_category_price_widget.dart b/lib/view/widgets/restaurant_category_price_widget.dart new file mode 100644 index 0000000..b67091e --- /dev/null +++ b/lib/view/widgets/restaurant_category_price_widget.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; + +class RestaurantCategoryPriceWidget extends StatelessWidget { + const RestaurantCategoryPriceWidget( + {required this.price, required this.category, super.key}); + final String? price; + final String? category; + @override + Widget build(BuildContext context) { + return Row( + children: [ + Text( + price ?? "\$\$", + style: Theme.of(context).textTheme.bodyMedium, + ), + const Gap(4), + Text( + category ?? "", + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ); + } +} diff --git a/lib/view/widgets/restaurant_hero_widget.dart b/lib/view/widgets/restaurant_hero_widget.dart new file mode 100644 index 0000000..895e1d2 --- /dev/null +++ b/lib/view/widgets/restaurant_hero_widget.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class RestaurantHeroWidget extends StatelessWidget { + const RestaurantHeroWidget( + {required this.imageUrl, required this.tag, super.key}); + final String? tag; + final String? imageUrl; + + @override + Widget build(BuildContext context) { + return Hero( + tag: tag ?? 'restaurant_id', + child: Image.network( + errorBuilder: (context, error, stackTrace) => + const Center(child: Icon(Icons.error)), + imageUrl ?? 'https://picsum.photos/375/361', + fit: BoxFit.fitWidth, + ), + ); + } +} diff --git a/lib/view/widgets/restaurant_list_widget.dart b/lib/view/widgets/restaurant_list_widget.dart new file mode 100644 index 0000000..2de6701 --- /dev/null +++ b/lib/view/widgets/restaurant_list_widget.dart @@ -0,0 +1,94 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; +import 'package:restaurant_tour/view/pages/restaurant_page.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_tile.dart'; + +class RestaurantListWidget extends StatefulWidget { + const RestaurantListWidget({super.key}); + + @override + State createState() => _RestaurantListWidgetState(); +} + +class _RestaurantListWidgetState extends State { + final _scrollController = ScrollController(); + @override + void initState() { + super.initState(); + } + + void addMore() { + final blocState = + (GetIt.I.get().state) as RestaurantsReady; + GetIt.I.get().add( + AddMoreRestaurants(offset: blocState.size, limit: blocState.limit)); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: GetIt.I.get(), + builder: (context, state) { + if (state is RestaurantsReady) { + return ListView.separated( + separatorBuilder: (context, index) => const SizedBox( + height: 2, + ), + itemCount: state.restaurants.length + 1, + controller: _scrollController, + itemBuilder: (context, index) { + if (index == state.restaurants.length && state.isLoadingMore) { + return const Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + + if (index == state.restaurants.length && state.hasError) { + return ElevatedButton( + onPressed: addMore, + child: const Text( + 'Error trying to fetch more, try again', + )); + } + if (index == state.restaurants.length) { + return ElevatedButton( + onPressed: addMore, + child: const Text( + 'Load more', + )); + } + + return GestureDetector( + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => RestaurantPage( + restaurant: state.restaurants[index], + ), + ), + ), + child: + RestaurantTile(restaurant: state.restaurants[index])); + }); + } + if (state is RestaurantsError) { + return Center( + child: Text(state.exception.description), + ); + } + + if (state is RestaurantsLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return Container(); + }, + ); + } +} diff --git a/lib/view/widgets/restaurant_open.dart b/lib/view/widgets/restaurant_open_widget.dart similarity index 86% rename from lib/view/widgets/restaurant_open.dart rename to lib/view/widgets/restaurant_open_widget.dart index 79a5201..7468e66 100644 --- a/lib/view/widgets/restaurant_open.dart +++ b/lib/view/widgets/restaurant_open_widget.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -class RestaurantOpen extends StatelessWidget { - const RestaurantOpen(this.isOpen, {super.key}); +class RestaurantOpenWidget extends StatelessWidget { + const RestaurantOpenWidget(this.isOpen, {super.key}); final bool isOpen; @override Widget build(BuildContext context) { diff --git a/lib/view/widgets/restaurant_star_rating.dart b/lib/view/widgets/restaurant_star_rating_widget.dart similarity index 69% rename from lib/view/widgets/restaurant_star_rating.dart rename to lib/view/widgets/restaurant_star_rating_widget.dart index c90d5ff..ec4d1f1 100644 --- a/lib/view/widgets/restaurant_star_rating.dart +++ b/lib/view/widgets/restaurant_star_rating_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -class RestaurantStarRating extends StatelessWidget { - const RestaurantStarRating(this.rating, {super.key}); +class RestaurantStarRatingWidget extends StatelessWidget { + const RestaurantStarRatingWidget(this.rating, {super.key}); final starColor = const Color(0xFFFFB800); final int rating; @override @@ -9,7 +9,7 @@ class RestaurantStarRating extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - for (int i = 0; i <= rating; i++) + for (int i = 0; i < rating; i++) Icon( Icons.star, size: 12, diff --git a/lib/view/widgets/restaurant_tile.dart b/lib/view/widgets/restaurant_tile.dart index 532ad06..cb7fe0d 100644 --- a/lib/view/widgets/restaurant_tile.dart +++ b/lib/view/widgets/restaurant_tile.dart @@ -3,9 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:gap/gap.dart'; import 'package:restaurant_tour/models/restaurant.dart'; -import 'package:restaurant_tour/view/pages/restaurant_page.dart'; -import 'package:restaurant_tour/view/widgets/restaurant_open.dart'; -import 'package:restaurant_tour/view/widgets/restaurant_star_rating.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_open_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_star_rating_widget.dart'; class RestaurantTile extends StatelessWidget { const RestaurantTile({required this.restaurant, super.key}); @@ -26,6 +25,8 @@ class RestaurantTile extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(8.0), child: Image.network( + errorBuilder: (context, error, stackTrace) => + const Center(child: Icon(Icons.error)), width: 88, height: 88, restaurant.photos?.first ?? @@ -62,8 +63,9 @@ class RestaurantTile extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - RestaurantStarRating(restaurant.rating?.toInt() ?? 2), - RestaurantOpen(restaurant.isOpen) + RestaurantStarRatingWidget( + restaurant.rating?.toInt() ?? 2), + RestaurantOpenWidget(restaurant.isOpen) ], ) ], diff --git a/lib/view/widgets/review_list_widget.dart b/lib/view/widgets/review_list_widget.dart new file mode 100644 index 0000000..ac36fc6 --- /dev/null +++ b/lib/view/widgets/review_list_widget.dart @@ -0,0 +1,37 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/view/widgets/review_tile.dart'; + +class ReviewListWidget extends StatelessWidget { + const ReviewListWidget({required this.reviews, super.key}); + final List? reviews; + @override + Widget build(BuildContext context) { + return (reviews?.isNotEmpty ?? false) + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(16), + Text( + '${reviews!.length} Reviews', + style: Theme.of(context).textTheme.bodyMedium, + ), + const Gap(16), + ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (context, index) { + return ReviewTile(reviews![index]); + }, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), + itemCount: reviews!.length), + ], + ) + : const Text('This restaurant has no reviews'); + } +} diff --git a/lib/view/widgets/review_tile.dart b/lib/view/widgets/review_tile.dart index a64f58f..43ba6e3 100644 --- a/lib/view/widgets/review_tile.dart +++ b/lib/view/widgets/review_tile.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:restaurant_tour/models/restaurant.dart'; -import 'package:restaurant_tour/view/widgets/restaurant_star_rating.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_star_rating_widget.dart'; class ReviewTile extends StatelessWidget { const ReviewTile(this.review, {super.key}); @@ -12,7 +12,7 @@ class ReviewTile extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - RestaurantStarRating(review.rating ?? 1), + RestaurantStarRatingWidget(review.rating ?? 1), const Gap(12), Text( review.text ?? 'no comment', @@ -22,6 +22,9 @@ class ReviewTile extends StatelessWidget { Row( children: [ CircleAvatar( + onForegroundImageError: (exception, stackTrace) => const Center( + child: Icon(Icons.error), + ), foregroundImage: NetworkImage( review.user?.imageUrl ?? 'https://picsum.photos/200/300'), ), diff --git a/pubspec.lock b/pubspec.lock index d4417b1..11f1c53 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -46,6 +46,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.4" + bloc_concurrency: + dependency: "direct main" + description: + name: bloc_concurrency + sha256: "456b7a3616a7c1ceb975c14441b3f198bf57d81cb95b7c6de5cb0c60201afcd8" + url: "https://pub.dev" + source: hosted + version: "0.2.5" bloc_test: dependency: "direct main" description: @@ -206,6 +214,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.1" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -214,6 +230,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" file: dependency: transitive description: @@ -256,6 +280,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: @@ -433,7 +462,7 @@ packages: source: hosted version: "1.0.6" mockito: - dependency: "direct main" + dependency: "direct dev" description: name: mockito sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" @@ -456,6 +485,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + network_image_mock: + dependency: "direct dev" + description: + name: network_image_mock + sha256: "855cdd01d42440e0cffee0d6c2370909fc31b3bcba308a59829f24f64be42db7" + url: "https://pub.dev" + source: hosted + version: "2.1.1" node_preamble: dependency: transitive description: @@ -480,6 +517,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: @@ -512,6 +589,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.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: @@ -725,6 +858,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" yaml: dependency: transitive description: @@ -735,4 +876,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.5.0-259.0.dev <4.0.0" - flutter: ">=3.19.6" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 34c2f38..636b912 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,9 @@ environment: dependencies: bloc: ^8.1.4 + bloc_concurrency: ^0.2.5 bloc_test: ^9.1.7 + equatable: ^2.0.5 flutter: sdk: flutter flutter_bloc: ^8.1.6 @@ -20,7 +22,7 @@ dependencies: get_it: ^7.7.0 http: ^1.2.2 json_annotation: ^4.9.0 - mockito: ^5.4.4 + shared_preferences: ^2.3.2 dev_dependencies: flutter_test: @@ -29,6 +31,9 @@ dev_dependencies: build_runner: ^2.4.10 json_serializable: ^6.8.0 test: ^1.25.2 + mockito: ^5.4.4 + network_image_mock: ^2.1.1 + flutter: generate: true diff --git a/test/domain/usecase/restaurants_usecase_test.dart b/test/domain/usecase/restaurants_usecase_test.dart index f442169..f9f9fe5 100644 --- a/test/domain/usecase/restaurants_usecase_test.dart +++ b/test/domain/usecase/restaurants_usecase_test.dart @@ -29,17 +29,17 @@ void main() { expect(result.first, isA()); }); - test('get restaurant should return a restaurant model', () async { - when(repository.getRestaurant('test')).thenAnswer( - (_) async => const Restaurant( - id: 'test', - name: 'Test unit', - ), + test('get favorite restaurants should return a list of favoirte restaurants', + () async { + when(repository.getFavoriteRestaurants()).thenAnswer( + (_) async => + const [Restaurant(id: "123", name: "Unit Test", isFavorite: true)], ); - final result = await usecase.getRestaurant('test'); - expect(result, isA()); + final result = await usecase.getFavoriteRestaurants(); + expect(result, isNotEmpty); + expect(result.first, isA()); + expect(result.first.isFavorite, true); }); - test('add favorite restaurant should save a restaurant', () async { const testRestaurant = Restaurant(id: 'test_id', name: 'Test Restaurant'); when(repository.addFavoriteRestaurant(testRestaurant)) @@ -50,9 +50,16 @@ void main() { test('remove favorite restaurant should delete a restaurant', () async { const testRestaurant = Restaurant(id: 'test_id', name: 'Test Restaurant'); - when(repository.addFavoriteRestaurant(testRestaurant)) + when(repository.removeFavoriteRestaurant(testRestaurant)) .thenAnswer((_) async => {}); await usecase.removeFavoriteRestaurant(testRestaurant); verify(repository.removeFavoriteRestaurant(testRestaurant)).called(1); }); + + test('add more favorite restaurant should add more restaurant to the list', + () async { + when(repository.getMoreRestaurants(10, 10)).thenAnswer((_) async => {}); + await usecase.loadMoreRestaurants(offset: 10, limit: 10); + verify(repository.getMoreRestaurants(10, 10)).called(1); + }); } diff --git a/test/domain/usecase/restaurants_usecase_test.mocks.dart b/test/domain/usecase/restaurants_usecase_test.mocks.dart index a22b856..d25f052 100644 --- a/test/domain/usecase/restaurants_usecase_test.mocks.dart +++ b/test/domain/usecase/restaurants_usecase_test.mocks.dart @@ -3,12 +3,12 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i4; +import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:restaurant_tour/domain/repository/restaurants_repository.dart' - as _i3; -import 'package:restaurant_tour/models/restaurant.dart' as _i2; + as _i2; +import 'package:restaurant_tour/models/restaurant.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -23,86 +23,70 @@ import 'package:restaurant_tour/models/restaurant.dart' as _i2; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeRestaurant_0 extends _i1.SmartFake implements _i2.Restaurant { - _FakeRestaurant_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - /// A class which mocks [RestaurantsRepository]. /// /// See the documentation for Mockito's code generation for more information. class MockRestaurantsRepository extends _i1.Mock - implements _i3.RestaurantsRepository { + implements _i2.RestaurantsRepository { @override - _i4.Future> getRestaurants() => (super.noSuchMethod( + _i3.Future> getRestaurants() => (super.noSuchMethod( Invocation.method( #getRestaurants, [], ), - returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), returnValueForMissingStub: - _i4.Future>.value(<_i2.Restaurant>[]), - ) as _i4.Future>); + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); @override - _i4.Future> getFavoriteRestaurants() => + _i3.Future getMoreRestaurants( + int? offset, + int? limit, + ) => (super.noSuchMethod( Invocation.method( - #getFavoriteRestaurants, - [], + #getMoreRestaurants, + [ + offset, + limit, + ], ), - returnValue: _i4.Future>.value(<_i2.Restaurant>[]), - returnValueForMissingStub: - _i4.Future>.value(<_i2.Restaurant>[]), - ) as _i4.Future>); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); @override - _i4.Future<_i2.Restaurant> getRestaurant(String? id) => (super.noSuchMethod( + _i3.Future> getFavoriteRestaurants() => + (super.noSuchMethod( Invocation.method( - #getRestaurant, - [id], + #getFavoriteRestaurants, + [], ), - returnValue: _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( - this, - Invocation.method( - #getRestaurant, - [id], - ), - )), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), returnValueForMissingStub: - _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( - this, - Invocation.method( - #getRestaurant, - [id], - ), - )), - ) as _i4.Future<_i2.Restaurant>); + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); @override - _i4.Future addFavoriteRestaurant(_i2.Restaurant? restaurant) => + _i3.Future addFavoriteRestaurant(_i4.Restaurant? restaurant) => (super.noSuchMethod( Invocation.method( #addFavoriteRestaurant, [restaurant], ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); @override - _i4.Future removeFavoriteRestaurant(_i2.Restaurant? restaurant) => + _i3.Future removeFavoriteRestaurant(_i4.Restaurant? restaurant) => (super.noSuchMethod( Invocation.method( #removeFavoriteRestaurant, [restaurant], ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); } diff --git a/test/presentation/favorites/test_favorite_restaurants_bloc.mocks.dart b/test/presentation/favorites/test_favorite_restaurants_bloc.mocks.dart index c0e3846..3389e4b 100644 --- a/test/presentation/favorites/test_favorite_restaurants_bloc.mocks.dart +++ b/test/presentation/favorites/test_favorite_restaurants_bloc.mocks.dart @@ -3,11 +3,11 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i4; +import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart' as _i3; -import 'package:restaurant_tour/models/restaurant.dart' as _i2; +import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart' as _i2; +import 'package:restaurant_tour/models/restaurant.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -22,86 +22,71 @@ import 'package:restaurant_tour/models/restaurant.dart' as _i2; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeRestaurant_0 extends _i1.SmartFake implements _i2.Restaurant { - _FakeRestaurant_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - /// A class which mocks [RestaurantsUsecase]. /// /// See the documentation for Mockito's code generation for more information. class MockRestaurantsUsecase extends _i1.Mock - implements _i3.RestaurantsUsecase { + implements _i2.RestaurantsUsecase { @override - _i4.Future> getRestaurants() => (super.noSuchMethod( + _i3.Future> getRestaurants() => (super.noSuchMethod( Invocation.method( #getRestaurants, [], ), - returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), returnValueForMissingStub: - _i4.Future>.value(<_i2.Restaurant>[]), - ) as _i4.Future>); + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); @override - _i4.Future> getFavoriteRestaurants() => + _i3.Future> getFavoriteRestaurants() => (super.noSuchMethod( Invocation.method( #getFavoriteRestaurants, [], ), - returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), returnValueForMissingStub: - _i4.Future>.value(<_i2.Restaurant>[]), - ) as _i4.Future>); + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); @override - _i4.Future<_i2.Restaurant> getRestaurant(String? id) => (super.noSuchMethod( + _i3.Future addFavoriteRestaurant(_i4.Restaurant? restaurant) => + (super.noSuchMethod( Invocation.method( - #getRestaurant, - [id], + #addFavoriteRestaurant, + [restaurant], ), - returnValue: _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( - this, - Invocation.method( - #getRestaurant, - [id], - ), - )), - returnValueForMissingStub: - _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( - this, - Invocation.method( - #getRestaurant, - [id], - ), - )), - ) as _i4.Future<_i2.Restaurant>); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); @override - _i4.Future addFavoriteRestaurant(_i2.Restaurant? restaurant) => + _i3.Future removeFavoriteRestaurant(_i4.Restaurant? restaurant) => (super.noSuchMethod( Invocation.method( - #addFavoriteRestaurant, + #removeFavoriteRestaurant, [restaurant], ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); @override - _i4.Future removeFavoriteRestaurant(_i2.Restaurant? restaurant) => + _i3.Future loadMoreRestaurants({ + required int? offset, + required int? limit, + }) => (super.noSuchMethod( Invocation.method( - #removeFavoriteRestaurant, - [restaurant], + #loadMoreRestaurants, + [], + { + #offset: offset, + #limit: limit, + }, ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); } diff --git a/test/presentation/restaurants/test_restaurants_bloc.dart b/test/presentation/restaurants/test_restaurants_bloc.dart index e97d96f..19cf9dc 100644 --- a/test/presentation/restaurants/test_restaurants_bloc.dart +++ b/test/presentation/restaurants/test_restaurants_bloc.dart @@ -37,7 +37,7 @@ void main() { ); blocTest( - 'RestaurantsBloc should error RestaurantsListError', + 'RestaurantsBloc should throw an RestaurantsListError', setUp: () => when(usecase.getRestaurants()).thenThrow(RestaurantListException()), build: () => RestaurantsBloc(usecase), @@ -46,4 +46,24 @@ void main() { expect: () => [isA(), isA()], verify: (bloc) => verify(usecase.getRestaurants()).called(1), ); + + blocTest( + 'RestaurantsBloc should throw an RestaurantsListError', + setUp: () => when(usecase.getRestaurants()).thenAnswer( + (_) async => [const Restaurant(id: 'id', name: 'restaurant')]), + build: () => RestaurantsBloc(usecase), + act: (bloc) async { + bloc.add(LoadRestaurants()); + await Future.delayed(const Duration(milliseconds: 500)); + bloc.add(AddMoreRestaurants(limit: 10, offset: 10)); + }, + wait: const Duration(milliseconds: 300), + expect: () => [ + isA(), + isA(), + isA(), + isA(), + ], + verify: (bloc) => verify(usecase.getRestaurants()).called(2), + ); } diff --git a/test/presentation/restaurants/test_restaurants_bloc.mocks.dart b/test/presentation/restaurants/test_restaurants_bloc.mocks.dart index 10cc7bf..8be2844 100644 --- a/test/presentation/restaurants/test_restaurants_bloc.mocks.dart +++ b/test/presentation/restaurants/test_restaurants_bloc.mocks.dart @@ -3,11 +3,11 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i4; +import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart' as _i3; -import 'package:restaurant_tour/models/restaurant.dart' as _i2; +import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart' as _i2; +import 'package:restaurant_tour/models/restaurant.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -22,86 +22,71 @@ import 'package:restaurant_tour/models/restaurant.dart' as _i2; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeRestaurant_0 extends _i1.SmartFake implements _i2.Restaurant { - _FakeRestaurant_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - /// A class which mocks [RestaurantsUsecase]. /// /// See the documentation for Mockito's code generation for more information. class MockRestaurantsUsecase extends _i1.Mock - implements _i3.RestaurantsUsecase { + implements _i2.RestaurantsUsecase { @override - _i4.Future> getRestaurants() => (super.noSuchMethod( + _i3.Future> getRestaurants() => (super.noSuchMethod( Invocation.method( #getRestaurants, [], ), - returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), returnValueForMissingStub: - _i4.Future>.value(<_i2.Restaurant>[]), - ) as _i4.Future>); + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); @override - _i4.Future> getFavoriteRestaurants() => + _i3.Future> getFavoriteRestaurants() => (super.noSuchMethod( Invocation.method( #getFavoriteRestaurants, [], ), - returnValue: _i4.Future>.value(<_i2.Restaurant>[]), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), returnValueForMissingStub: - _i4.Future>.value(<_i2.Restaurant>[]), - ) as _i4.Future>); + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); @override - _i4.Future<_i2.Restaurant> getRestaurant(String? id) => (super.noSuchMethod( + _i3.Future addFavoriteRestaurant(_i4.Restaurant? restaurant) => + (super.noSuchMethod( Invocation.method( - #getRestaurant, - [id], + #addFavoriteRestaurant, + [restaurant], ), - returnValue: _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( - this, - Invocation.method( - #getRestaurant, - [id], - ), - )), - returnValueForMissingStub: - _i4.Future<_i2.Restaurant>.value(_FakeRestaurant_0( - this, - Invocation.method( - #getRestaurant, - [id], - ), - )), - ) as _i4.Future<_i2.Restaurant>); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); @override - _i4.Future addFavoriteRestaurant(_i2.Restaurant? restaurant) => + _i3.Future removeFavoriteRestaurant(_i4.Restaurant? restaurant) => (super.noSuchMethod( Invocation.method( - #addFavoriteRestaurant, + #removeFavoriteRestaurant, [restaurant], ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); @override - _i4.Future removeFavoriteRestaurant(_i2.Restaurant? restaurant) => + _i3.Future loadMoreRestaurants({ + required int? offset, + required int? limit, + }) => (super.noSuchMethod( Invocation.method( - #removeFavoriteRestaurant, - [restaurant], + #loadMoreRestaurants, + [], + { + #offset: offset, + #limit: limit, + }, ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); } diff --git a/test/view/widgets/address_widget_test.dart b/test/view/widgets/address_widget_test.dart new file mode 100644 index 0000000..4b5e35f --- /dev/null +++ b/test/view/widgets/address_widget_test.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_tour/view/widgets/address_widget.dart'; + +void main() { + testWidgets('AddressWidget displays address correctly', + (WidgetTester tester) async { + // Test with a provided address + const address = '123 Main St, Anytown, CA'; + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: AddressWidget(address: address), + ), + ), + ); + + expect(find.text(address), findsOneWidget); + + // Test with a null address + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: AddressWidget(address: null), + ), + ), + ); + + expect(find.text('Address info'), findsOneWidget); + }); +} diff --git a/test/view/widgets/favorite_list_widget_test.dart b/test/view/widgets/favorite_list_widget_test.dart new file mode 100644 index 0000000..1707195 --- /dev/null +++ b/test/view/widgets/favorite_list_widget_test.dart @@ -0,0 +1,63 @@ +import 'dart:io'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/mockito.dart'; +import 'package:network_image_mock/network_image_mock.dart'; + +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; +import 'package:restaurant_tour/view/widgets/favorite_list_widget.dart'; + +class MockFavoritesRestaurantsBloc + extends MockBloc + implements FavoritesRestaurantsBloc {} + +void main() { + testWidgets('FavoriteListWidget displays restaurants correctly', + (WidgetTester tester) async { + // Create mock bloc and state + final mockBloc = MockFavoritesRestaurantsBloc(); + + whenListen( + mockBloc, + Stream.fromIterable([ + FavoritesRestaurantsLoading(), + FavoritesRestaurantsReady(const [ + Restaurant( + id: '1', + name: 'Restaurant 1', + rating: 4.5, + photos: ['https://picsum.photos/200/300']), + Restaurant( + id: '2', + name: 'Restaurant 2', + rating: 4.2, + photos: ['https://picsum.photos/200/300']), + ]) + ]), + initialState: FavoritesRestaurantsInitial(), + ); + + // Provide the mock bloc to GetIt + GetIt.I.registerSingleton(mockBloc); + + // Pump the widget + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: FavoriteListWidget(), + ), + ), + ); + expect(find.text('Restaurant 1'), findsNothing); + expect(find.text('Restaurant 2'), findsNothing); + await tester.pump(); + // Verify that the list contains the expected restaurants + expect(find.text('Restaurant 1'), findsOneWidget); + expect(find.text('Restaurant 2'), findsOneWidget); + }); +} diff --git a/test/view/widgets/favorite_list_widget_test.mocks.dart b/test/view/widgets/favorite_list_widget_test.mocks.dart new file mode 100644 index 0000000..7646e05 --- /dev/null +++ b/test/view/widgets/favorite_list_widget_test.mocks.dart @@ -0,0 +1,712 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in restaurant_tour/test/view/widgets/favorite_list_widget_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; +import 'dart:io' as _i2; + +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeDuration_0 extends _i1.SmartFake implements Duration { + _FakeDuration_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeHttpClientRequest_1 extends _i1.SmartFake + implements _i2.HttpClientRequest { + _FakeHttpClientRequest_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [HttpClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHttpClient extends _i1.Mock implements _i2.HttpClient { + @override + Duration get idleTimeout => (super.noSuchMethod( + Invocation.getter(#idleTimeout), + returnValue: _FakeDuration_0( + this, + Invocation.getter(#idleTimeout), + ), + returnValueForMissingStub: _FakeDuration_0( + this, + Invocation.getter(#idleTimeout), + ), + ) as Duration); + + @override + set idleTimeout(Duration? _idleTimeout) => super.noSuchMethod( + Invocation.setter( + #idleTimeout, + _idleTimeout, + ), + returnValueForMissingStub: null, + ); + + @override + set connectionTimeout(Duration? _connectionTimeout) => super.noSuchMethod( + Invocation.setter( + #connectionTimeout, + _connectionTimeout, + ), + returnValueForMissingStub: null, + ); + + @override + set maxConnectionsPerHost(int? _maxConnectionsPerHost) => super.noSuchMethod( + Invocation.setter( + #maxConnectionsPerHost, + _maxConnectionsPerHost, + ), + returnValueForMissingStub: null, + ); + + @override + bool get autoUncompress => (super.noSuchMethod( + Invocation.getter(#autoUncompress), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + set autoUncompress(bool? _autoUncompress) => super.noSuchMethod( + Invocation.setter( + #autoUncompress, + _autoUncompress, + ), + returnValueForMissingStub: null, + ); + + @override + set userAgent(String? _userAgent) => super.noSuchMethod( + Invocation.setter( + #userAgent, + _userAgent, + ), + returnValueForMissingStub: null, + ); + + @override + set authenticate( + _i3.Future Function( + Uri, + String, + String?, + )? f) => + super.noSuchMethod( + Invocation.setter( + #authenticate, + f, + ), + returnValueForMissingStub: null, + ); + + @override + set connectionFactory( + _i3.Future<_i2.ConnectionTask<_i2.Socket>> Function( + Uri, + String?, + int?, + )? f) => + super.noSuchMethod( + Invocation.setter( + #connectionFactory, + f, + ), + returnValueForMissingStub: null, + ); + + @override + set findProxy(String Function(Uri)? f) => super.noSuchMethod( + Invocation.setter( + #findProxy, + f, + ), + returnValueForMissingStub: null, + ); + + @override + set authenticateProxy( + _i3.Future Function( + String, + int, + String, + String?, + )? f) => + super.noSuchMethod( + Invocation.setter( + #authenticateProxy, + f, + ), + returnValueForMissingStub: null, + ); + + @override + set badCertificateCallback( + bool Function( + _i2.X509Certificate, + String, + int, + )? callback) => + super.noSuchMethod( + Invocation.setter( + #badCertificateCallback, + callback, + ), + returnValueForMissingStub: null, + ); + + @override + set keyLog(dynamic Function(String)? callback) => super.noSuchMethod( + Invocation.setter( + #keyLog, + callback, + ), + returnValueForMissingStub: null, + ); + + @override + _i3.Future<_i2.HttpClientRequest> open( + String? method, + String? host, + int? port, + String? path, + ) => + (super.noSuchMethod( + Invocation.method( + #open, + [ + method, + host, + port, + path, + ], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #open, + [ + method, + host, + port, + path, + ], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #open, + [ + method, + host, + port, + path, + ], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> openUrl( + String? method, + Uri? url, + ) => + (super.noSuchMethod( + Invocation.method( + #openUrl, + [ + method, + url, + ], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #openUrl, + [ + method, + url, + ], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #openUrl, + [ + method, + url, + ], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> get( + String? host, + int? port, + String? path, + ) => + (super.noSuchMethod( + Invocation.method( + #get, + [ + host, + port, + path, + ], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #get, + [ + host, + port, + path, + ], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #get, + [ + host, + port, + path, + ], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> getUrl(Uri? url) => (super.noSuchMethod( + Invocation.method( + #getUrl, + [url], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #getUrl, + [url], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #getUrl, + [url], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> post( + String? host, + int? port, + String? path, + ) => + (super.noSuchMethod( + Invocation.method( + #post, + [ + host, + port, + path, + ], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #post, + [ + host, + port, + path, + ], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #post, + [ + host, + port, + path, + ], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> postUrl(Uri? url) => (super.noSuchMethod( + Invocation.method( + #postUrl, + [url], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #postUrl, + [url], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #postUrl, + [url], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> put( + String? host, + int? port, + String? path, + ) => + (super.noSuchMethod( + Invocation.method( + #put, + [ + host, + port, + path, + ], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #put, + [ + host, + port, + path, + ], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #put, + [ + host, + port, + path, + ], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> putUrl(Uri? url) => (super.noSuchMethod( + Invocation.method( + #putUrl, + [url], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #putUrl, + [url], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #putUrl, + [url], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> delete( + String? host, + int? port, + String? path, + ) => + (super.noSuchMethod( + Invocation.method( + #delete, + [ + host, + port, + path, + ], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #delete, + [ + host, + port, + path, + ], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #delete, + [ + host, + port, + path, + ], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> deleteUrl(Uri? url) => (super.noSuchMethod( + Invocation.method( + #deleteUrl, + [url], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #deleteUrl, + [url], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #deleteUrl, + [url], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> patch( + String? host, + int? port, + String? path, + ) => + (super.noSuchMethod( + Invocation.method( + #patch, + [ + host, + port, + path, + ], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #patch, + [ + host, + port, + path, + ], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #patch, + [ + host, + port, + path, + ], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> patchUrl(Uri? url) => (super.noSuchMethod( + Invocation.method( + #patchUrl, + [url], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #patchUrl, + [url], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #patchUrl, + [url], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> head( + String? host, + int? port, + String? path, + ) => + (super.noSuchMethod( + Invocation.method( + #head, + [ + host, + port, + path, + ], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #head, + [ + host, + port, + path, + ], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #head, + [ + host, + port, + path, + ], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + _i3.Future<_i2.HttpClientRequest> headUrl(Uri? url) => (super.noSuchMethod( + Invocation.method( + #headUrl, + [url], + ), + returnValue: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #headUrl, + [url], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( + this, + Invocation.method( + #headUrl, + [url], + ), + )), + ) as _i3.Future<_i2.HttpClientRequest>); + + @override + void addCredentials( + Uri? url, + String? realm, + _i2.HttpClientCredentials? credentials, + ) => + super.noSuchMethod( + Invocation.method( + #addCredentials, + [ + url, + realm, + credentials, + ], + ), + returnValueForMissingStub: null, + ); + + @override + void addProxyCredentials( + String? host, + int? port, + String? realm, + _i2.HttpClientCredentials? credentials, + ) => + super.noSuchMethod( + Invocation.method( + #addProxyCredentials, + [ + host, + port, + realm, + credentials, + ], + ), + returnValueForMissingStub: null, + ); + + @override + void close({bool? force = false}) => super.noSuchMethod( + Invocation.method( + #close, + [], + {#force: force}, + ), + returnValueForMissingStub: null, + ); +} diff --git a/test/view/widgets/favorite_widget_test.dart b/test/view/widgets/favorite_widget_test.dart new file mode 100644 index 0000000..dc6f23e --- /dev/null +++ b/test/view/widgets/favorite_widget_test.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_tour/view/widgets/favorite_button_widget.dart'; + +void main() { + testWidgets('FavoriteButtonWidget toggles icon and calls callback', + (WidgetTester tester) async { + bool isFavorite = false; + callback(bool newValue) { + isFavorite = newValue; + } + + // Build the widget + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: + FavoriteButtonWidget(callback: callback, isFavorite: isFavorite), + ), + ), + ); + + // Verify initial state + expect(find.byIcon(Icons.favorite_outline), findsOneWidget); + expect(isFavorite, false); + + // Tap the button + await tester.tap(find.byIcon(Icons.favorite_outline)); + await tester.pump(); + + // Verify updated state + expect(find.byIcon(Icons.favorite), findsOneWidget); + expect(isFavorite, true); + + // Tap the button again + await tester.tap(find.byIcon(Icons.favorite)); + await tester.pump(); + + // Verify final state + expect(find.byIcon(Icons.favorite_outline), findsOneWidget); + expect(isFavorite, false); + }); +} diff --git a/test/view/widgets/open_restaurant_widget.dart b/test/view/widgets/open_restaurant_widget.dart new file mode 100644 index 0000000..a4b68b7 --- /dev/null +++ b/test/view/widgets/open_restaurant_widget.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_open_widget.dart'; // Import your widget + +void main() { + testWidgets('RestaurantOpenWidget displays status and icon correctly', + (WidgetTester tester) async { + // Test with restaurant open + var isOpen = true; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RestaurantOpenWidget(isOpen), + ), + ), + ); + // Verify "Open Now" text + expect(find.text('Open Now'), findsOneWidget); + expect(find.text('Closed'), findsNothing); + + // Verify icon + final greenCircle = find.widgetWithIcon(Row, Icons.circle); + expect(greenCircle, findsOneWidget); + + // Test with restaurant closed + isOpen = false; + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: RestaurantOpenWidget(false), + ), + ), + ); + + // Verify "Closed" text + expect(find.text('Open Now'), findsNothing); + expect(find.text('Closed'), findsOneWidget); + + // Verify icon + final redCircle = find.widgetWithIcon(Row, Icons.circle); + expect(redCircle, findsOneWidget); + }); +} diff --git a/test/view/widgets/overall_rating_widget.dart b/test/view/widgets/overall_rating_widget.dart new file mode 100644 index 0000000..3d25f0e --- /dev/null +++ b/test/view/widgets/overall_rating_widget.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_tour/view/widgets/overall_rating_widget.dart'; // Import your widget + +void main() { + testWidgets('OverallRatingWidget displays rating correctly', + (WidgetTester tester) async { + // Test with a provided rating + const rating = 4.5; + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: OverallRatingWidget(rating: rating), + ), + ), + ); + + expect(find.text(rating.toString()), findsOneWidget); + + // Test with a null rating + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: OverallRatingWidget(rating: null), + ), + ), + ); + + expect(find.text('0'), findsOneWidget); + }); +} diff --git a/test/view/widgets/price_category_widget_test.dart b/test/view/widgets/price_category_widget_test.dart new file mode 100644 index 0000000..9d23b07 --- /dev/null +++ b/test/view/widgets/price_category_widget_test.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_category_price_widget.dart'; // Import your widget + +void main() { + testWidgets( + 'RestaurantCategoryPriceWidget displays price and category correctly', + (WidgetTester tester) async { + // Test with provided price and category + const price = "\$\$"; + const category = "Italian"; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RestaurantCategoryPriceWidget(price: price, category: category), + ), + ), + ); + + expect(find.text(price), findsOneWidget); + expect(find.text(category), findsOneWidget); + + // Test with null price and category + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RestaurantCategoryPriceWidget(price: null, category: null), + ), + ), + ); + + expect(find.text("\$\$"), findsOneWidget); + expect(find.text(""), findsOneWidget); + }); +} diff --git a/test/view/widgets/restaurant_hero_widget_test.dart b/test/view/widgets/restaurant_hero_widget_test.dart new file mode 100644 index 0000000..5cddbcc --- /dev/null +++ b/test/view/widgets/restaurant_hero_widget_test.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_hero_widget.dart'; + +class MockNavigatorObserver extends Mock implements NavigatorObserver {} + +void main() { + testWidgets('RestaurantHeroWidget displays image and handles errors', + (WidgetTester tester) async { + // Mock NavigatorObserver to capture Hero transitions + final mockObserver = MockNavigatorObserver(); + + // Test with provided image URL and tag + const imageUrl = 'https://picsum.photos/200/300'; + const tag = 'restaurant_detail'; + await tester.pumpWidget( + MaterialApp( + navigatorObservers: [mockObserver], + home: const Scaffold( + body: RestaurantHeroWidget(imageUrl: imageUrl, tag: tag), + ), + ), + ); + + // Verify image is displayed + final image = find.image(const NetworkImage(imageUrl)); + expect(image, findsOneWidget); + }); +} diff --git a/test/view/widgets/restaurant_list_widget_test.dart b/test/view/widgets/restaurant_list_widget_test.dart new file mode 100644 index 0000000..1599f5a --- /dev/null +++ b/test/view/widgets/restaurant_list_widget_test.dart @@ -0,0 +1,58 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; +import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; +import 'package:restaurant_tour/view/widgets/favorite_list_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_list_widget.dart'; + +class MockRestaurantsBloc extends MockBloc + implements RestaurantsBloc {} + +void main() { + testWidgets('FavoriteListWidget displays restaurants correctly', + (WidgetTester tester) async { + // Create mock bloc and state + final mockBloc = MockRestaurantsBloc(); + + whenListen( + mockBloc, + Stream.fromIterable([ + RestaurantsLoading(), + RestaurantsReady(const [ + Restaurant( + id: '1', + name: 'Restaurant 1', + rating: 4.5, + photos: ['https://picsum.photos/200/300']), + Restaurant( + id: '2', + name: 'Restaurant 2', + rating: 4.2, + photos: ['https://picsum.photos/200/300']), + ]) + ]), + initialState: RestaurantsInitial(), + ); + + // Provide the mock bloc to GetIt + GetIt.I.registerSingleton(mockBloc); + + // Pump the widget + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: RestaurantListWidget(), + ), + ), + ); + expect(find.text('Restaurant 1'), findsNothing); + expect(find.text('Restaurant 2'), findsNothing); + await tester.pump(); + // Verify that the list contains the expected restaurants + expect(find.text('Restaurant 1'), findsOneWidget); + expect(find.text('Restaurant 2'), findsOneWidget); + }); +} diff --git a/test/view/widgets/restaurant_star_rating_widget.dart b/test/view/widgets/restaurant_star_rating_widget.dart new file mode 100644 index 0000000..04844ba --- /dev/null +++ b/test/view/widgets/restaurant_star_rating_widget.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_star_rating_widget.dart'; // Import your widget + +void main() { + testWidgets('RestaurantStarRating displays correct number of stars', + (WidgetTester tester) async { + // Test with rating 3 + const rating = 3; + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: RestaurantStarRatingWidget(rating), + ), + ), + ); + + await tester.pump(); + + // Verify 3 star icons + expect(find.widgetWithIcon(Row, Icons.star), findsNWidgets(rating)); + + // Test with rating 0 + const zeroRating = 0; + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: RestaurantStarRatingWidget(zeroRating), + ), + ), + ); + await tester.pump(); + + // Verify no star icons + expect(find.widgetWithIcon(Row, Icons.star), findsNothing); + }); +} diff --git a/test/view/widgets/restaurant_tile_test.dart b/test/view/widgets/restaurant_tile_test.dart new file mode 100644 index 0000000..d5ba557 --- /dev/null +++ b/test/view/widgets/restaurant_tile_test.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_open_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_star_rating_widget.dart'; + +import 'package:restaurant_tour/view/widgets/restaurant_tile.dart'; + +class MockNavigatorObserver extends Mock implements NavigatorObserver {} + +void main() { + testWidgets('RestaurantTile displays restaurant details correctly', + (WidgetTester tester) async { + // Create a sample restaurant object + final restaurant = Restaurant( + id: '123', + name: 'My Restaurant', + photos: const ['https://picsum.photos/200/300'], + price: '\$', + categories: [Category(title: 'Italian')], + rating: 4.5, + hours: const [Hours(isOpenNow: true)], + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RestaurantTile(restaurant: restaurant), + ), + ), + ); + + // Verify image is displayed + final image = find.image(NetworkImage(restaurant.photos!.first)); + expect(image, findsOneWidget); + + // Verify restaurant name, price & category + expect(find.text(restaurant.name!), findsOneWidget); + expect(find.text(restaurant.price!), findsOneWidget); + expect(find.text(restaurant.categories!.first.title!), findsOneWidget); + + // Verify star rating + expect(find.widgetWithIcon(RestaurantStarRatingWidget, Icons.star), + findsNWidgets(restaurant.rating!.toInt())); + + // Verify open status + expect( + find.widgetWithText(RestaurantOpenWidget, 'Open Now'), findsOneWidget); + }); +} diff --git a/test/view/widgets/review_tile_test.dart b/test/view/widgets/review_tile_test.dart new file mode 100644 index 0000000..a54cb67 --- /dev/null +++ b/test/view/widgets/review_tile_test.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_star_rating_widget.dart'; +import 'package:restaurant_tour/view/widgets/review_tile.dart'; + +void main() { + testWidgets('ReviewTile displays review details correctly', + (WidgetTester tester) async { + // Create sample Review and User objects + final user = + User(name: 'John Doe', imageUrl: 'https://picsum.photos/200/300'); + final review = Review(rating: 4, text: 'Great food!', user: user); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ReviewTile(review), + ), + ), + ); + await tester.pump(); + // Verify star rating + expect(find.widgetWithIcon(RestaurantStarRatingWidget, Icons.star), + findsNWidgets(review.rating!.toInt())); + + // Verify review text + expect(find.text(review.text!), findsOneWidget); + + // Verify user name + expect(find.text(user.name!), findsOneWidget); + }); +} From c54b163fdfb3b65fde4487fcddad7e6683620140 Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Wed, 18 Sep 2024 09:58:26 -0400 Subject: [PATCH 07/12] test(e2e): add integration test --- .../restaurant_tour_integreation_test.dart | 150 ++++++++++++++++++ ios/Podfile.lock | 6 + .../memory/memory_restaurants_datasource.dart | 1 - lib/di/di.dart | 3 +- lib/main.dart | 5 +- pubspec.lock | 39 +++++ pubspec.yaml | 2 + 7 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 integration_test/restaurant_tour_integreation_test.dart diff --git a/integration_test/restaurant_tour_integreation_test.dart b/integration_test/restaurant_tour_integreation_test.dart new file mode 100644 index 0000000..a13bceb --- /dev/null +++ b/integration_test/restaurant_tour_integreation_test.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:restaurant_tour/main.dart'; +import 'package:restaurant_tour/view/widgets/favorite_button_widget.dart'; +import 'package:restaurant_tour/view/widgets/favorite_list_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_hero_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_tile.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('end-to-end test', () { + testWidgets('Tapping on RestaurantTitle navigates to correct page', + (WidgetTester tester) async { + // init + await tester.pumpWidget(const RestaurantTour( + testMode: true, + )); + await tester.pumpAndSettle(); + + // find and tap restaurant tile + final restaurantTitleFinder = find.byType(RestaurantTile); + expect(restaurantTitleFinder, findsAny); + await tester.tap(restaurantTitleFinder.first); + + // // Trigger a frame. + await tester.pumpAndSettle(); + + // // Verify hero widget is found. + expect( + find.byWidgetPredicate((widget) => widget is RestaurantHeroWidget), + findsOneWidget, + ); + }); + + testWidgets('Tapping on Favorite Tab navigates to correct page', + (WidgetTester tester) async { + await tester.pumpWidget(const RestaurantTour( + testMode: true, + )); + await tester.pumpAndSettle(); + + await tester.tap(find.byType(Tab).last); + + // // Trigger a frame. + await tester.pumpAndSettle(); + + // // Verify hero widget is found. + expect( + find.byType(FavoriteListWidget), + findsOneWidget, + ); + }); + + testWidgets( + 'Tapping on FavoriteButtonWidget add Restaurant to Favorite Restaurant List page', + (WidgetTester tester) async { + await tester.pumpWidget(const RestaurantTour( + testMode: true, + )); + await tester.pumpAndSettle(); + + final restaurantTitleFinder = + find.byWidgetPredicate((widget) => widget is RestaurantTile); + expect(restaurantTitleFinder, findsAny); + await tester.tap(restaurantTitleFinder.first); + + // Trigger a frame. + await tester.pumpAndSettle(); + + // // Verify hero widget is found. + final favoriteButton = find.byType(FavoriteButtonWidget); + expect( + favoriteButton, + findsOneWidget, + ); + await tester.tap(favoriteButton); + // Trigger a frame. + await tester.pumpAndSettle(); + await tester.pageBack(); + // Trigger a frame. + await tester.pumpAndSettle(); + final favoriteTab = find.byType(Tab).last; + await tester.tap(favoriteTab); + // Trigger a frame. + await tester.pumpAndSettle(); + + expect(find.byType(RestaurantTile), findsOne); + }); + + testWidgets( + 'Tapping on FavoriteButtonWidget again removes Restaurant to Favorite Restaurant List page', + (WidgetTester tester) async { + //Init + await tester.pumpWidget(const RestaurantTour( + testMode: true, + )); + await tester.pumpAndSettle(); + + // Tap first Restaurant on the list + final restaurantTitleFinder = find.byType(RestaurantTile); + expect(restaurantTitleFinder, findsAny); + await tester.tap(restaurantTitleFinder.first); + + // Trigger a frame. + await tester.pumpAndSettle(); + + // Tap Favorite Widget + final favoriteButton = find.byType(FavoriteButtonWidget); + expect( + favoriteButton, + findsOneWidget, + ); + await tester.tap(favoriteButton); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Check if restaurant was added + await tester.pageBack(); + // Trigger a frame. + await tester.pumpAndSettle(); + final favoriteTab = find.byType(Tab).last; + await tester.tap(favoriteTab); + // Trigger a frame. + await tester.pumpAndSettle(); + + expect(find.byType(RestaurantTile), findsOne); + + // Now open the favorite restaurant + await tester.tap(restaurantTitleFinder.first); + + // Trigger a frame. + await tester.pumpAndSettle(); + + // Tap Favorite Widget to remove it + expect( + favoriteButton, + findsOneWidget, + ); + await tester.tap(favoriteButton); + + // go back and check if list is empty + await tester.pageBack(); + await tester.pumpAndSettle(); + + expect(find.byType(RestaurantTile), findsNothing); + }); + }); +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 05c6066..4248ccc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,21 +1,27 @@ PODS: - Flutter (1.0.0) + - integration_test (0.0.1): + - Flutter - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS DEPENDENCIES: - Flutter (from `Flutter`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) EXTERNAL SOURCES: Flutter: :path: Flutter + integration_test: + :path: ".symlinks/plugins/integration_test/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/lib/data/datasource/memory/memory_restaurants_datasource.dart b/lib/data/datasource/memory/memory_restaurants_datasource.dart index 34a947d..1d77df9 100644 --- a/lib/data/datasource/memory/memory_restaurants_datasource.dart +++ b/lib/data/datasource/memory/memory_restaurants_datasource.dart @@ -36,7 +36,6 @@ class MemoryRestaurantsDatasource implements RestaurantsDatasource { Future> getRestaurants( {int offset = 0, int limit = 10}) async { final result = restaurants.sublist(offset, offset + limit); - await Future.delayed(const Duration(seconds: 4)); return Future.value(result); } } diff --git a/lib/di/di.dart b/lib/di/di.dart index d8a10c7..fedbe5e 100644 --- a/lib/di/di.dart +++ b/lib/di/di.dart @@ -12,7 +12,8 @@ import 'package:shared_preferences/shared_preferences.dart'; final getIt = GetIt.instance; -void setup() { +void setup({testMode = false}) { + getIt.allowReassignment = testMode; getIt.registerSingletonAsync( () => SharedPreferences.getInstance(), ); diff --git a/lib/main.dart b/lib/main.dart index ecca42c..091719a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,7 +13,8 @@ void main() { } class RestaurantTour extends StatefulWidget { - const RestaurantTour({super.key}); + const RestaurantTour({super.key, this.testMode = false}); + final bool testMode; @override State createState() => _RestaurantTourState(); @@ -22,7 +23,7 @@ class RestaurantTour extends StatefulWidget { class _RestaurantTourState extends State { @override void initState() { - setup(); + setup(testMode: widget.testMode); super.initState(); } diff --git a/pubspec.lock b/pubspec.lock index 11f1c53..84c7136 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -267,6 +267,11 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.6" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_lints: dependency: "direct dev" description: @@ -293,6 +298,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" gap: dependency: "direct main" description: @@ -349,6 +359,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" io: dependency: transitive description: @@ -565,6 +580,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + process: + dependency: transitive + description: + name: process + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + url: "https://pub.dev" + source: hosted + version: "5.0.2" provider: dependency: transitive description: @@ -754,6 +777,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" term_glyph: dependency: transitive description: @@ -850,6 +881,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + url: "https://pub.dev" + source: hosted + version: "3.0.3" webkit_inspection_protocol: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 636b912..6c553ec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,8 @@ dev_dependencies: test: ^1.25.2 mockito: ^5.4.4 network_image_mock: ^2.1.1 + integration_test: + sdk: flutter flutter: From b3c15d7d351f2f601f8b6f5b24d2747449f53d72 Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Wed, 18 Sep 2024 10:13:10 -0400 Subject: [PATCH 08/12] refactor(app): clean warnings --- .../restaurant_tour_integreation_test.dart | 32 ++++--- .../graphql_restaurants_datasource.dart | 3 +- .../memory/memory_restaurants_datasource.dart | 62 ++++++------- lib/di/di.dart | 6 +- .../repository/restaurants_repository.dart | 7 +- lib/main.dart | 21 ++--- .../favorites/favorites_restaurants_bloc.dart | 88 +++++++++++-------- .../restaurants/restaurants_bloc.dart | 51 +++++++---- .../restaurants/restaurants_state.dart | 12 +-- lib/typography.dart | 49 ----------- lib/view/pages/main_page.dart | 42 ++++----- lib/view/pages/restaurant_page.dart | 24 ++--- lib/view/theme/app_theme.dart | 45 +++++----- lib/view/widgets/favorite_button_widget.dart | 26 +++--- lib/view/widgets/favorite_list_widget.dart | 33 +++---- .../restaurant_category_price_widget.dart | 7 +- lib/view/widgets/restaurant_hero_widget.dart | 7 +- lib/view/widgets/restaurant_list_widget.dart | 83 ++++++++--------- lib/view/widgets/restaurant_open_widget.dart | 2 +- .../restaurant_star_rating_widget.dart | 2 +- lib/view/widgets/restaurant_tile.dart | 25 +++--- lib/view/widgets/review_list_widget.dart | 22 ++--- lib/view/widgets/review_tile.dart | 9 +- .../restaurants/test_restaurants_bloc.dart | 3 +- .../widgets/favorite_list_widget_test.dart | 28 +++--- .../widgets/price_category_widget_test.dart | 4 +- .../widgets/restaurant_list_widget_test.dart | 22 ++--- test/view/widgets/restaurant_tile_test.dart | 10 ++- test/view/widgets/review_tile_test.dart | 12 +-- test/widget_test.dart | 19 ---- 30 files changed, 376 insertions(+), 380 deletions(-) delete mode 100644 lib/typography.dart delete mode 100644 test/widget_test.dart diff --git a/integration_test/restaurant_tour_integreation_test.dart b/integration_test/restaurant_tour_integreation_test.dart index a13bceb..df50f5d 100644 --- a/integration_test/restaurant_tour_integreation_test.dart +++ b/integration_test/restaurant_tour_integreation_test.dart @@ -14,9 +14,11 @@ void main() { testWidgets('Tapping on RestaurantTitle navigates to correct page', (WidgetTester tester) async { // init - await tester.pumpWidget(const RestaurantTour( - testMode: true, - )); + await tester.pumpWidget( + const RestaurantTour( + testMode: true, + ), + ); await tester.pumpAndSettle(); // find and tap restaurant tile @@ -36,9 +38,11 @@ void main() { testWidgets('Tapping on Favorite Tab navigates to correct page', (WidgetTester tester) async { - await tester.pumpWidget(const RestaurantTour( - testMode: true, - )); + await tester.pumpWidget( + const RestaurantTour( + testMode: true, + ), + ); await tester.pumpAndSettle(); await tester.tap(find.byType(Tab).last); @@ -56,9 +60,11 @@ void main() { testWidgets( 'Tapping on FavoriteButtonWidget add Restaurant to Favorite Restaurant List page', (WidgetTester tester) async { - await tester.pumpWidget(const RestaurantTour( - testMode: true, - )); + await tester.pumpWidget( + const RestaurantTour( + testMode: true, + ), + ); await tester.pumpAndSettle(); final restaurantTitleFinder = @@ -93,9 +99,11 @@ void main() { 'Tapping on FavoriteButtonWidget again removes Restaurant to Favorite Restaurant List page', (WidgetTester tester) async { //Init - await tester.pumpWidget(const RestaurantTour( - testMode: true, - )); + await tester.pumpWidget( + const RestaurantTour( + testMode: true, + ), + ); await tester.pumpAndSettle(); // Tap first Restaurant on the list diff --git a/lib/data/datasource/graphql/graphql_restaurants_datasource.dart b/lib/data/datasource/graphql/graphql_restaurants_datasource.dart index efabd94..a3e1dd6 100644 --- a/lib/data/datasource/graphql/graphql_restaurants_datasource.dart +++ b/lib/data/datasource/graphql/graphql_restaurants_datasource.dart @@ -36,7 +36,8 @@ class GraphqlRestaurantsDatasource implements RestaurantsDatasource { return restaurantsResult.restaurants ?? []; } else { throw RestaurantListException( - description: 'Failed to load restaurants: ${response.statusCode}'); + description: 'Failed to load restaurants: ${response.statusCode}', + ); } } catch (e) { throw RestaurantListException( diff --git a/lib/data/datasource/memory/memory_restaurants_datasource.dart b/lib/data/datasource/memory/memory_restaurants_datasource.dart index 1d77df9..efc7e6c 100644 --- a/lib/data/datasource/memory/memory_restaurants_datasource.dart +++ b/lib/data/datasource/memory/memory_restaurants_datasource.dart @@ -1,40 +1,42 @@ -import 'dart:math'; - -import 'package:restaurant_tour/common/exceptions/exceptions.dart'; import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; import 'package:restaurant_tour/models/restaurant.dart'; class MemoryRestaurantsDatasource implements RestaurantsDatasource { var restaurants = List.generate( - 100, - (i) => Restaurant( - id: "restaurant_$i", - name: "Amazing Pizza ${i + 1}", - price: "\$\$\$", - rating: 4.8, - photos: ["https://picsum.photos/361/360"], - categories: [Category(alias: 'demo', title: 'Italian')], - hours: [const Hours(isOpenNow: true)], - location: Location(formattedAddress: "123 Main St, Anytown, CA"), - reviews: [1, 2, 3, 4, 5, 6] - .map((e) => const Review( - id: 'review_id', - text: - 'Review text goes here. Review text goes here. This is a review. This is a review that is 3 lines long.', - rating: 5, - user: const User( - id: 'user_id', - imageUrl: 'https://picsum.photos/200/300', - name: 'Test User', - ), - )) - .toList(), - ), - growable: true); + 100, + (i) => Restaurant( + id: "restaurant_$i", + name: "Amazing Pizza ${i + 1}", + price: "\$\$\$", + rating: 4.8, + photos: const ["https://picsum.photos/361/360"], + categories: [Category(alias: 'demo', title: 'Italian')], + hours: const [Hours(isOpenNow: true)], + location: Location(formattedAddress: "123 Main St, Anytown, CA"), + reviews: [1, 2, 3, 4, 5, 6] + .map( + (e) => const Review( + id: 'review_id', + text: + 'Review text goes here. Review text goes here. This is a review. This is a review that is 3 lines long.', + rating: 5, + user: User( + id: 'user_id', + imageUrl: 'https://picsum.photos/200/300', + name: 'Test User', + ), + ), + ) + .toList(), + ), + growable: true, + ); @override - Future> getRestaurants( - {int offset = 0, int limit = 10}) async { + Future> getRestaurants({ + int offset = 0, + int limit = 10, + }) async { final result = restaurants.sublist(offset, offset + limit); return Future.value(result); } diff --git a/lib/di/di.dart b/lib/di/di.dart index fedbe5e..20add26 100644 --- a/lib/di/di.dart +++ b/lib/di/di.dart @@ -1,5 +1,4 @@ import 'package:get_it/get_it.dart'; -import 'package:restaurant_tour/data/datasource/graphql/graphql_restaurants_datasource.dart'; import 'package:restaurant_tour/data/datasource/local/sp_favorite_restaurants_datasource.dart'; import 'package:restaurant_tour/data/datasource/memory/memory_restaurants_datasource.dart'; import 'package:restaurant_tour/domain/datasource/favorite_restaurants_datasource.dart'; @@ -19,8 +18,9 @@ void setup({testMode = false}) { ); getIt.registerSingletonAsync( - () async => SpFavoriteRestaurantsDatasource(GetIt.I.get()), - dependsOn: [InitDependency(SharedPreferences)]); + () async => SpFavoriteRestaurantsDatasource(GetIt.I.get()), + dependsOn: [InitDependency(SharedPreferences)], + ); getIt.registerSingleton( MemoryRestaurantsDatasource(), ); diff --git a/lib/domain/repository/restaurants_repository.dart b/lib/domain/repository/restaurants_repository.dart index 15abada..8ecd732 100644 --- a/lib/domain/repository/restaurants_repository.dart +++ b/lib/domain/repository/restaurants_repository.dart @@ -33,7 +33,9 @@ class RestaurantsRepository { Future getMoreRestaurants(int offset, int limit) async { final newRemoteRestaurants = await _restaurantsDatasource.getRestaurants( - offset: offset, limit: limit); + offset: offset, + limit: limit, + ); _restaurants.addAll( newRemoteRestaurants .map( @@ -48,7 +50,8 @@ class RestaurantsRepository { final favoritesRestaurants = await _favoriteRestaurantsDatasource.getFavoritesRestaurants(); _favorites.addAll( - favoritesRestaurants.map((e) => e.copyWith(isFavorite: true))); + favoritesRestaurants.map((e) => e.copyWith(isFavorite: true)), + ); } return _favorites.toList(); diff --git a/lib/main.dart b/lib/main.dart index 091719a..f2801db 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,6 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:restaurant_tour/di/di.dart'; -import 'package:restaurant_tour/models/restaurant.dart'; -import 'package:restaurant_tour/data/datasource/graphql/query.dart'; import 'package:restaurant_tour/view/pages/main_page.dart'; import './view/theme/app_theme.dart'; @@ -33,14 +29,15 @@ class _RestaurantTourState extends State { title: 'Restaurant Tour', theme: AppTheme.lightTheme, home: FutureBuilder( - future: GetIt.I.allReady(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - return const MainPage(); - } else { - return const Center(child: CircularProgressIndicator()); - } - }), + future: GetIt.I.allReady(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return const MainPage(); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), ); } } diff --git a/lib/presentation/favorites/favorites_restaurants_bloc.dart b/lib/presentation/favorites/favorites_restaurants_bloc.dart index a84335b..02233a4 100644 --- a/lib/presentation/favorites/favorites_restaurants_bloc.dart +++ b/lib/presentation/favorites/favorites_restaurants_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:flutter/material.dart'; +// ignore: depend_on_referenced_packages, unnecessary_import import 'package:meta/meta.dart'; import 'package:restaurant_tour/common/exceptions/exceptions.dart'; import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; @@ -13,47 +14,58 @@ class FavoritesRestaurantsBloc extends Bloc { FavoritesRestaurantsBloc(this.usecase) : super(FavoritesRestaurantsInitial()) { - on((event, emit) async { - try { - emit(FavoritesRestaurantsLoading()); - final favorites = await usecase.getFavoriteRestaurants(); - emit(FavoritesRestaurantsReady(favorites)); - } catch (e) { - emit(FavoriteRestaurantsListError(FavoritesRestaurantsListException())); - } - }, transformer: sequential()); - on((event, emit) async { - try { - while (state is FavoritesRestaurantsLoading) { - Future.delayed(const Duration(milliseconds: 500)); + on( + (event, emit) async { + try { + emit(FavoritesRestaurantsLoading()); + final favorites = await usecase.getFavoriteRestaurants(); + emit(FavoritesRestaurantsReady(favorites)); + } catch (e) { + emit( + FavoriteRestaurantsListError(FavoritesRestaurantsListException()), + ); } - emit(FavoritesRestaurantsLoading()); - await usecase.addFavoriteRestaurant(event.restaurant); - final favoritesRestaurants = await usecase.getFavoriteRestaurants(); - emit(FavoritesRestaurantsReady(favoritesRestaurants)); - } catch (e) { - debugPrint(e.toString()); - emit(AddFavoriteRestaurantsError(AddFavoriteRestaurantException())); - } - }, transformer: sequential()); + }, + transformer: sequential(), + ); + on( + (event, emit) async { + try { + while (state is FavoritesRestaurantsLoading) { + Future.delayed(const Duration(milliseconds: 500)); + } + emit(FavoritesRestaurantsLoading()); + await usecase.addFavoriteRestaurant(event.restaurant); + final favoritesRestaurants = await usecase.getFavoriteRestaurants(); + emit(FavoritesRestaurantsReady(favoritesRestaurants)); + } catch (e) { + debugPrint(e.toString()); + emit(AddFavoriteRestaurantsError(AddFavoriteRestaurantException())); + } + }, + transformer: sequential(), + ); - on((event, emit) async { - try { - while (state is FavoritesRestaurantsLoading) { - Future.delayed(const Duration(milliseconds: 500)); + on( + (event, emit) async { + try { + while (state is FavoritesRestaurantsLoading) { + Future.delayed(const Duration(milliseconds: 500)); + } + emit(FavoritesRestaurantsLoading()); + await usecase.removeFavoriteRestaurant(event.restaurant); + final favoritesRestaurants = await usecase.getFavoriteRestaurants(); + emit(FavoritesRestaurantsReady(favoritesRestaurants)); + } catch (e) { + emit( + RemoveFavoriteRestaurantsError( + RemoveFavoriteRestaurantException(), + ), + ); } - emit(FavoritesRestaurantsLoading()); - await usecase.removeFavoriteRestaurant(event.restaurant); - final favoritesRestaurants = await usecase.getFavoriteRestaurants(); - emit(FavoritesRestaurantsReady(favoritesRestaurants)); - } catch (e) { - emit( - RemoveFavoriteRestaurantsError( - RemoveFavoriteRestaurantException(), - ), - ); - } - }, transformer: sequential()); + }, + transformer: sequential(), + ); } final RestaurantsUsecase usecase; diff --git a/lib/presentation/restaurants/restaurants_bloc.dart b/lib/presentation/restaurants/restaurants_bloc.dart index 420e1c7..9436022 100644 --- a/lib/presentation/restaurants/restaurants_bloc.dart +++ b/lib/presentation/restaurants/restaurants_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:flutter/material.dart'; +// ignore: depend_on_referenced_packages, unnecessary_import import 'package:meta/meta.dart'; import 'package:restaurant_tour/common/exceptions/exceptions.dart'; import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart'; @@ -11,37 +12,49 @@ part 'restaurants_state.dart'; class RestaurantsBloc extends Bloc { RestaurantsBloc(this.usecase) : super(RestaurantsInitial()) { - on((event, emit) async { - emit(RestaurantsLoading()); - try { - final restaurants = await usecase.getRestaurants(); - emit(RestaurantsReady( - restaurants, - size: restaurants.length, - )); - } on RestaurantListException catch (e) { - emit(RestaurantsError(e)); - } catch (e) { - emit(RestaurantsError(RestaurantListException())); - } - }, transformer: sequential()); + on( + (event, emit) async { + emit(RestaurantsLoading()); + try { + final restaurants = await usecase.getRestaurants(); + emit( + RestaurantsReady( + restaurants, + size: restaurants.length, + ), + ); + } on RestaurantListException catch (e) { + emit(RestaurantsError(e)); + } catch (e) { + emit(RestaurantsError(RestaurantListException())); + } + }, + transformer: sequential(), + ); on( (event, emit) async { try { if (state is RestaurantsReady) { emit((state as RestaurantsReady).copyWith(isLoadingMore: true)); await usecase.loadMoreRestaurants( - offset: event.offset, limit: event.limit); + offset: event.offset, + limit: event.limit, + ); final restaurants = await usecase.getRestaurants(); - emit((state as RestaurantsReady).copyWith( + emit( + (state as RestaurantsReady).copyWith( isLoadingMore: false, restaurants: restaurants, - size: restaurants.length)); + size: restaurants.length, + ), + ); } } catch (e) { - emit((state as RestaurantsReady) - .copyWith(isLoadingMore: false, hasError: true)); + emit( + (state as RestaurantsReady) + .copyWith(isLoadingMore: false, hasError: true), + ); } }, ); diff --git a/lib/presentation/restaurants/restaurants_state.dart b/lib/presentation/restaurants/restaurants_state.dart index 865bff7..335e049 100644 --- a/lib/presentation/restaurants/restaurants_state.dart +++ b/lib/presentation/restaurants/restaurants_state.dart @@ -8,11 +8,13 @@ final class RestaurantsInitial extends RestaurantsState {} final class RestaurantsLoading extends RestaurantsState {} final class RestaurantsReady extends RestaurantsState { - RestaurantsReady(this.restaurants, - {this.isLoadingMore = false, - this.size = 10, - this.limit = 10, - this.hasError = false}); + RestaurantsReady( + this.restaurants, { + this.isLoadingMore = false, + this.size = 10, + this.limit = 10, + this.hasError = false, + }); final List restaurants; final bool isLoadingMore; final bool hasError; diff --git a/lib/typography.dart b/lib/typography.dart deleted file mode 100644 index e165260..0000000 --- a/lib/typography.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; - -class AppTextStyles { - ////----- Lora -----// - static const loraRegularHeadline = TextStyle( - fontFamily: 'Lora', - fontWeight: FontWeight.w700, - fontSize: 18.0, - ); - static const loraRegularTitle = TextStyle( - fontFamily: 'Lora', - fontWeight: FontWeight.w500, - fontSize: 16.0, - ); - - //----- Open Sans -----// - static const openRegularHeadline = TextStyle( - fontFamily: 'OpenSans', - fontWeight: FontWeight.w400, - fontSize: 16.0, - color: Colors.black, - ); - static const openRegularTitleSemiBold = TextStyle( - fontFamily: 'OpenSans', - fontWeight: FontWeight.w600, - fontSize: 14.0, - color: Colors.black, - ); - static const openRegularTitle = TextStyle( - fontFamily: 'OpenSans', - fontWeight: FontWeight.w400, - fontSize: 14.0, - color: Colors.black, - ); - static const openRegularText = TextStyle( - fontFamily: 'OpenSans', - fontWeight: FontWeight.w400, - fontSize: 12.0, - color: Colors.black, - ); - - static const openRegularItalic = TextStyle( - fontFamily: 'OpenSans', - fontWeight: FontWeight.w400, - fontStyle: FontStyle.italic, - fontSize: 12.0, - color: Colors.black, - ); -} diff --git a/lib/view/pages/main_page.dart b/lib/view/pages/main_page.dart index 7a6b059..461c144 100644 --- a/lib/view/pages/main_page.dart +++ b/lib/view/pages/main_page.dart @@ -1,15 +1,11 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; + import 'package:get_it/get_it.dart'; import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; -import 'package:restaurant_tour/view/pages/restaurant_page.dart'; + import 'package:restaurant_tour/view/widgets/favorite_list_widget.dart'; import 'package:restaurant_tour/view/widgets/restaurant_list_widget.dart'; -import 'package:restaurant_tour/view/widgets/restaurant_tile.dart'; class MainPage extends StatefulWidget { const MainPage({super.key}); @@ -33,23 +29,27 @@ class _MainPageState extends State { child: Scaffold( appBar: AppBar( title: const Text('RestauranTour'), - bottom: const TabBar(tabs: [ - Tab( - text: 'Restaurants', + bottom: const TabBar( + tabs: [ + Tab( + text: 'Restaurants', + ), + Tab(text: 'Favorites'), + ], + ), + ), + body: const TabBarView( + children: [ + Padding( + padding: EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), + child: RestaurantListWidget(), + ), + Padding( + padding: EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), + child: FavoriteListWidget(), ), - Tab(text: 'Favorites'), - ]), + ], ), - body: const TabBarView(children: [ - Padding( - padding: EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), - child: RestaurantListWidget(), - ), - Padding( - padding: EdgeInsets.only(top: 16.0, right: 8.0, left: 8.0), - child: FavoriteListWidget(), - ), - ]), ), ); } diff --git a/lib/view/pages/restaurant_page.dart b/lib/view/pages/restaurant_page.dart index 9811184..b1d4f15 100644 --- a/lib/view/pages/restaurant_page.dart +++ b/lib/view/pages/restaurant_page.dart @@ -39,14 +39,16 @@ class _RestaurantPageState extends State { actions: [ FavoriteButtonWidget( callback: (isFavorite) { - GetIt.I.get().add((isFavorite) - ? AddFavoriteRestaurant(widget.restaurant) - : RemoveFavoriteRestaurant(widget.restaurant)); + GetIt.I.get().add( + (isFavorite) + ? AddFavoriteRestaurant(widget.restaurant) + : RemoveFavoriteRestaurant(widget.restaurant), + ); GetIt.I.get().add(LoadRestaurants()); }, isFavorite: widget.restaurant.isFavorite, - ) + ), ], ), body: SingleChildScrollView( @@ -66,22 +68,24 @@ class _RestaurantPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ RestaurantCategoryPriceWidget( - price: widget.restaurant.price, - category: widget.restaurant.categories?.first.title), - RestaurantOpenWidget(widget.restaurant.isOpen) + price: widget.restaurant.price, + category: widget.restaurant.categories?.first.title, + ), + RestaurantOpenWidget(widget.restaurant.isOpen), ], ), const Gap(16), const Divider(), AddressWidget( - address: widget.restaurant.location?.formattedAddress), + address: widget.restaurant.location?.formattedAddress, + ), const Divider(), OverallRatingWidget(rating: widget.restaurant.rating), const Divider(), - ReviewListWidget(reviews: widget.restaurant.reviews) + ReviewListWidget(reviews: widget.restaurant.reviews), ], ), - ) + ), ], ), ), diff --git a/lib/view/theme/app_theme.dart b/lib/view/theme/app_theme.dart index d2896b0..d322b2e 100644 --- a/lib/view/theme/app_theme.dart +++ b/lib/view/theme/app_theme.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; class AppTheme { static final ThemeData lightTheme = ThemeData( colorSchemeSeed: Colors.black, fontFamily: 'OpenSans', cardTheme: const CardTheme( - color: Colors.white, surfaceTintColor: Colors.transparent), + color: Colors.white, + surfaceTintColor: Colors.transparent, + ), textTheme: const TextTheme( headlineMedium: TextStyle( fontWeight: FontWeight.w400, @@ -34,27 +35,29 @@ class AppTheme { surfaceTintColor: Colors.transparent, elevation: 1, titleTextStyle: TextStyle( - fontFamily: 'Lora', - fontWeight: FontWeight.w700, - fontSize: 18.0, - color: Colors.black), + fontFamily: 'Lora', + fontWeight: FontWeight.w700, + fontSize: 18.0, + color: Colors.black, + ), ), indicatorColor: Colors.black, tabBarTheme: const TabBarTheme( - tabAlignment: TabAlignment.center, - indicatorColor: Colors.black, - labelStyle: TextStyle( - fontFamily: 'OpenSans', - fontWeight: FontWeight.w600, - fontSize: 14.0, - ), - dividerColor: Colors.transparent, - labelColor: Colors.black, - unselectedLabelColor: Color(0xFF606060), - unselectedLabelStyle: TextStyle( - fontFamily: 'OpenSans', - fontWeight: FontWeight.w600, - fontSize: 14.0, - )), + tabAlignment: TabAlignment.center, + indicatorColor: Colors.black, + labelStyle: TextStyle( + fontFamily: 'OpenSans', + fontWeight: FontWeight.w600, + fontSize: 14.0, + ), + dividerColor: Colors.transparent, + labelColor: Colors.black, + unselectedLabelColor: Color(0xFF606060), + unselectedLabelStyle: TextStyle( + fontFamily: 'OpenSans', + fontWeight: FontWeight.w600, + fontSize: 14.0, + ), + ), ); } diff --git a/lib/view/widgets/favorite_button_widget.dart b/lib/view/widgets/favorite_button_widget.dart index 9f24973..df71237 100644 --- a/lib/view/widgets/favorite_button_widget.dart +++ b/lib/view/widgets/favorite_button_widget.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; class FavoriteButtonWidget extends StatefulWidget { - const FavoriteButtonWidget( - {required this.callback, required this.isFavorite, super.key}); + const FavoriteButtonWidget({ + required this.callback, + required this.isFavorite, + super.key, + }); final Function(bool) callback; final bool? isFavorite; @@ -21,14 +24,15 @@ class _FavoriteButtonWidgetState extends State { @override Widget build(BuildContext context) { return IconButton( - onPressed: () { - setState(() { - isFavoriteState = !isFavoriteState; - }); - widget.callback(isFavoriteState); - }, - icon: (isFavoriteState) - ? const Icon(Icons.favorite) - : const Icon(Icons.favorite_outline)); + onPressed: () { + setState(() { + isFavoriteState = !isFavoriteState; + }); + widget.callback(isFavoriteState); + }, + icon: (isFavoriteState) + ? const Icon(Icons.favorite) + : const Icon(Icons.favorite_outline), + ); } } diff --git a/lib/view/widgets/favorite_list_widget.dart b/lib/view/widgets/favorite_list_widget.dart index adaefba..1fa2686 100644 --- a/lib/view/widgets/favorite_list_widget.dart +++ b/lib/view/widgets/favorite_list_widget.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; @@ -21,23 +20,25 @@ class _FavoriteListWidgetState extends State { builder: (context, state) { if (state is FavoritesRestaurantsReady) { return ListView.separated( - separatorBuilder: (context, index) => const SizedBox( - height: 2, - ), - itemCount: state.favoritesRestaurants.length, - itemBuilder: (context, index) { - return GestureDetector( - onTap: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => RestaurantPage( - restaurant: state.favoritesRestaurants[index], - ), + separatorBuilder: (context, index) => const SizedBox( + height: 2, + ), + itemCount: state.favoritesRestaurants.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => RestaurantPage( + restaurant: state.favoritesRestaurants[index], ), ), - child: RestaurantTile( - restaurant: state.favoritesRestaurants[index]), - ); - }); + ), + child: RestaurantTile( + restaurant: state.favoritesRestaurants[index], + ), + ); + }, + ); } if (state is FavoriteRestaurantsListError) { return Center( diff --git a/lib/view/widgets/restaurant_category_price_widget.dart b/lib/view/widgets/restaurant_category_price_widget.dart index b67091e..eba8121 100644 --- a/lib/view/widgets/restaurant_category_price_widget.dart +++ b/lib/view/widgets/restaurant_category_price_widget.dart @@ -2,8 +2,11 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; class RestaurantCategoryPriceWidget extends StatelessWidget { - const RestaurantCategoryPriceWidget( - {required this.price, required this.category, super.key}); + const RestaurantCategoryPriceWidget({ + required this.price, + required this.category, + super.key, + }); final String? price; final String? category; @override diff --git a/lib/view/widgets/restaurant_hero_widget.dart b/lib/view/widgets/restaurant_hero_widget.dart index 895e1d2..e9b648e 100644 --- a/lib/view/widgets/restaurant_hero_widget.dart +++ b/lib/view/widgets/restaurant_hero_widget.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; class RestaurantHeroWidget extends StatelessWidget { - const RestaurantHeroWidget( - {required this.imageUrl, required this.tag, super.key}); + const RestaurantHeroWidget({ + required this.imageUrl, + required this.tag, + super.key, + }); final String? tag; final String? imageUrl; diff --git a/lib/view/widgets/restaurant_list_widget.dart b/lib/view/widgets/restaurant_list_widget.dart index 2de6701..ec7b6a1 100644 --- a/lib/view/widgets/restaurant_list_widget.dart +++ b/lib/view/widgets/restaurant_list_widget.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; @@ -24,7 +23,8 @@ class _RestaurantListWidgetState extends State { final blocState = (GetIt.I.get().state) as RestaurantsReady; GetIt.I.get().add( - AddMoreRestaurants(offset: blocState.size, limit: blocState.limit)); + AddMoreRestaurants(offset: blocState.size, limit: blocState.limit), + ); } @override @@ -34,47 +34,50 @@ class _RestaurantListWidgetState extends State { builder: (context, state) { if (state is RestaurantsReady) { return ListView.separated( - separatorBuilder: (context, index) => const SizedBox( - height: 2, + separatorBuilder: (context, index) => const SizedBox( + height: 2, + ), + itemCount: state.restaurants.length + 1, + controller: _scrollController, + itemBuilder: (context, index) { + if (index == state.restaurants.length && state.isLoadingMore) { + return const Padding( + padding: EdgeInsets.all(16.0), + child: Center( + child: CircularProgressIndicator(), ), - itemCount: state.restaurants.length + 1, - controller: _scrollController, - itemBuilder: (context, index) { - if (index == state.restaurants.length && state.isLoadingMore) { - return const Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: CircularProgressIndicator(), - ), - ); - } + ); + } - if (index == state.restaurants.length && state.hasError) { - return ElevatedButton( - onPressed: addMore, - child: const Text( - 'Error trying to fetch more, try again', - )); - } - if (index == state.restaurants.length) { - return ElevatedButton( - onPressed: addMore, - child: const Text( - 'Load more', - )); - } + if (index == state.restaurants.length && state.hasError) { + return ElevatedButton( + onPressed: addMore, + child: const Text( + 'Error trying to fetch more, try again', + ), + ); + } + if (index == state.restaurants.length) { + return ElevatedButton( + onPressed: addMore, + child: const Text( + 'Load more', + ), + ); + } - return GestureDetector( - onTap: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => RestaurantPage( - restaurant: state.restaurants[index], - ), - ), - ), - child: - RestaurantTile(restaurant: state.restaurants[index])); - }); + return GestureDetector( + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => RestaurantPage( + restaurant: state.restaurants[index], + ), + ), + ), + child: RestaurantTile(restaurant: state.restaurants[index]), + ); + }, + ); } if (state is RestaurantsError) { return Center( diff --git a/lib/view/widgets/restaurant_open_widget.dart b/lib/view/widgets/restaurant_open_widget.dart index 7468e66..57ab4ee 100644 --- a/lib/view/widgets/restaurant_open_widget.dart +++ b/lib/view/widgets/restaurant_open_widget.dart @@ -24,7 +24,7 @@ class RestaurantOpenWidget extends StatelessWidget { size: 8, color: isOpen ? Colors.green : Colors.red, ), - ) + ), ], ); } diff --git a/lib/view/widgets/restaurant_star_rating_widget.dart b/lib/view/widgets/restaurant_star_rating_widget.dart index ec4d1f1..14612b4 100644 --- a/lib/view/widgets/restaurant_star_rating_widget.dart +++ b/lib/view/widgets/restaurant_star_rating_widget.dart @@ -14,7 +14,7 @@ class RestaurantStarRatingWidget extends StatelessWidget { Icons.star, size: 12, color: starColor, - ) + ), ], ); } diff --git a/lib/view/widgets/restaurant_tile.dart b/lib/view/widgets/restaurant_tile.dart index cb7fe0d..d97fa13 100644 --- a/lib/view/widgets/restaurant_tile.dart +++ b/lib/view/widgets/restaurant_tile.dart @@ -1,6 +1,4 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:gap/gap.dart'; import 'package:restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/view/widgets/restaurant_open_widget.dart'; @@ -25,12 +23,12 @@ class RestaurantTile extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(8.0), child: Image.network( - errorBuilder: (context, error, stackTrace) => - const Center(child: Icon(Icons.error)), - width: 88, - height: 88, - restaurant.photos?.first ?? - 'https://picsum.photos/200/300'), + errorBuilder: (context, error, stackTrace) => + const Center(child: Icon(Icons.error)), + width: 88, + height: 88, + restaurant.photos?.first ?? 'https://picsum.photos/200/300', + ), ), ), const Gap(12), @@ -53,7 +51,7 @@ class RestaurantTile extends StatelessWidget { restaurant.price ?? "\$\$", style: Theme.of(context).textTheme.bodyMedium, ), - Gap(4), + const Gap(4), Text( restaurant.categories?.first.title ?? "", style: Theme.of(context).textTheme.bodyMedium, @@ -64,14 +62,15 @@ class RestaurantTile extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ RestaurantStarRatingWidget( - restaurant.rating?.toInt() ?? 2), - RestaurantOpenWidget(restaurant.isOpen) + restaurant.rating?.toInt() ?? 2, + ), + RestaurantOpenWidget(restaurant.isOpen), ], - ) + ), ], ), ), - ) + ), ], ), ), diff --git a/lib/view/widgets/review_list_widget.dart b/lib/view/widgets/review_list_widget.dart index ac36fc6..e4cdc59 100644 --- a/lib/view/widgets/review_list_widget.dart +++ b/lib/view/widgets/review_list_widget.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:restaurant_tour/models/restaurant.dart'; @@ -20,16 +19,17 @@ class ReviewListWidget extends StatelessWidget { ), const Gap(16), ListView.separated( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: (context, index) { - return ReviewTile(reviews![index]); - }, - separatorBuilder: (context, index) => const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Divider(), - ), - itemCount: reviews!.length), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (context, index) { + return ReviewTile(reviews![index]); + }, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), + itemCount: reviews!.length, + ), ], ) : const Text('This restaurant has no reviews'); diff --git a/lib/view/widgets/review_tile.dart b/lib/view/widgets/review_tile.dart index 43ba6e3..543a396 100644 --- a/lib/view/widgets/review_tile.dart +++ b/lib/view/widgets/review_tile.dart @@ -18,7 +18,7 @@ class ReviewTile extends StatelessWidget { review.text ?? 'no comment', style: Theme.of(context).textTheme.bodyMedium, ), - Gap(12), + const Gap(12), Row( children: [ CircleAvatar( @@ -26,12 +26,13 @@ class ReviewTile extends StatelessWidget { child: Icon(Icons.error), ), foregroundImage: NetworkImage( - review.user?.imageUrl ?? 'https://picsum.photos/200/300'), + review.user?.imageUrl ?? 'https://picsum.photos/200/300', + ), ), const Gap(8), - Text(review.user?.name ?? 'user name') + Text(review.user?.name ?? 'user name'), ], - ) + ), ], ); } diff --git a/test/presentation/restaurants/test_restaurants_bloc.dart b/test/presentation/restaurants/test_restaurants_bloc.dart index 19cf9dc..5f62367 100644 --- a/test/presentation/restaurants/test_restaurants_bloc.dart +++ b/test/presentation/restaurants/test_restaurants_bloc.dart @@ -50,7 +50,8 @@ void main() { blocTest( 'RestaurantsBloc should throw an RestaurantsListError', setUp: () => when(usecase.getRestaurants()).thenAnswer( - (_) async => [const Restaurant(id: 'id', name: 'restaurant')]), + (_) async => [const Restaurant(id: 'id', name: 'restaurant')], + ), build: () => RestaurantsBloc(usecase), act: (bloc) async { bloc.add(LoadRestaurants()); diff --git a/test/view/widgets/favorite_list_widget_test.dart b/test/view/widgets/favorite_list_widget_test.dart index 1707195..8abeb02 100644 --- a/test/view/widgets/favorite_list_widget_test.dart +++ b/test/view/widgets/favorite_list_widget_test.dart @@ -1,13 +1,7 @@ -import 'dart:io'; - import 'package:bloc_test/bloc_test.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; -import 'package:mockito/mockito.dart'; -import 'package:network_image_mock/network_image_mock.dart'; - import 'package:restaurant_tour/models/restaurant.dart'; import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; import 'package:restaurant_tour/view/widgets/favorite_list_widget.dart'; @@ -24,21 +18,25 @@ void main() { whenListen( mockBloc, - Stream.fromIterable([ - FavoritesRestaurantsLoading(), - FavoritesRestaurantsReady(const [ - Restaurant( + Stream.fromIterable( + [ + FavoritesRestaurantsLoading(), + FavoritesRestaurantsReady(const [ + Restaurant( id: '1', name: 'Restaurant 1', rating: 4.5, - photos: ['https://picsum.photos/200/300']), - Restaurant( + photos: ['https://picsum.photos/200/300'], + ), + Restaurant( id: '2', name: 'Restaurant 2', rating: 4.2, - photos: ['https://picsum.photos/200/300']), - ]) - ]), + photos: ['https://picsum.photos/200/300'], + ), + ]), + ], + ), initialState: FavoritesRestaurantsInitial(), ); diff --git a/test/view/widgets/price_category_widget_test.dart b/test/view/widgets/price_category_widget_test.dart index 9d23b07..d215610 100644 --- a/test/view/widgets/price_category_widget_test.dart +++ b/test/view/widgets/price_category_widget_test.dart @@ -10,7 +10,7 @@ void main() { const price = "\$\$"; const category = "Italian"; await tester.pumpWidget( - MaterialApp( + const MaterialApp( home: Scaffold( body: RestaurantCategoryPriceWidget(price: price, category: category), ), @@ -22,7 +22,7 @@ void main() { // Test with null price and category await tester.pumpWidget( - MaterialApp( + const MaterialApp( home: Scaffold( body: RestaurantCategoryPriceWidget(price: null, category: null), ), diff --git a/test/view/widgets/restaurant_list_widget_test.dart b/test/view/widgets/restaurant_list_widget_test.dart index 1599f5a..47f794c 100644 --- a/test/view/widgets/restaurant_list_widget_test.dart +++ b/test/view/widgets/restaurant_list_widget_test.dart @@ -3,9 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; import 'package:restaurant_tour/models/restaurant.dart'; -import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart'; import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart'; -import 'package:restaurant_tour/view/widgets/favorite_list_widget.dart'; import 'package:restaurant_tour/view/widgets/restaurant_list_widget.dart'; class MockRestaurantsBloc extends MockBloc @@ -23,16 +21,18 @@ void main() { RestaurantsLoading(), RestaurantsReady(const [ Restaurant( - id: '1', - name: 'Restaurant 1', - rating: 4.5, - photos: ['https://picsum.photos/200/300']), + id: '1', + name: 'Restaurant 1', + rating: 4.5, + photos: ['https://picsum.photos/200/300'], + ), Restaurant( - id: '2', - name: 'Restaurant 2', - rating: 4.2, - photos: ['https://picsum.photos/200/300']), - ]) + id: '2', + name: 'Restaurant 2', + rating: 4.2, + photos: ['https://picsum.photos/200/300'], + ), + ]), ]), initialState: RestaurantsInitial(), ); diff --git a/test/view/widgets/restaurant_tile_test.dart b/test/view/widgets/restaurant_tile_test.dart index d5ba557..8ae785d 100644 --- a/test/view/widgets/restaurant_tile_test.dart +++ b/test/view/widgets/restaurant_tile_test.dart @@ -41,11 +41,15 @@ void main() { expect(find.text(restaurant.categories!.first.title!), findsOneWidget); // Verify star rating - expect(find.widgetWithIcon(RestaurantStarRatingWidget, Icons.star), - findsNWidgets(restaurant.rating!.toInt())); + expect( + find.widgetWithIcon(RestaurantStarRatingWidget, Icons.star), + findsNWidgets(restaurant.rating!.toInt()), + ); // Verify open status expect( - find.widgetWithText(RestaurantOpenWidget, 'Open Now'), findsOneWidget); + find.widgetWithText(RestaurantOpenWidget, 'Open Now'), + findsOneWidget, + ); }); } diff --git a/test/view/widgets/review_tile_test.dart b/test/view/widgets/review_tile_test.dart index a54cb67..5f282dc 100644 --- a/test/view/widgets/review_tile_test.dart +++ b/test/view/widgets/review_tile_test.dart @@ -8,12 +8,12 @@ void main() { testWidgets('ReviewTile displays review details correctly', (WidgetTester tester) async { // Create sample Review and User objects - final user = + const user = User(name: 'John Doe', imageUrl: 'https://picsum.photos/200/300'); - final review = Review(rating: 4, text: 'Great food!', user: user); + const review = Review(rating: 4, text: 'Great food!', user: user); await tester.pumpWidget( - MaterialApp( + const MaterialApp( home: Scaffold( body: ReviewTile(review), ), @@ -21,8 +21,10 @@ void main() { ); await tester.pump(); // Verify star rating - expect(find.widgetWithIcon(RestaurantStarRatingWidget, Icons.star), - findsNWidgets(review.rating!.toInt())); + expect( + find.widgetWithIcon(RestaurantStarRatingWidget, Icons.star), + findsNWidgets(review.rating!.toInt()), + ); // Verify review text expect(find.text(review.text!), findsOneWidget); 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 7ab9c73428508f94785edd76cc89a6f1212e2848 Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Wed, 18 Sep 2024 11:37:27 -0400 Subject: [PATCH 09/12] refactor(test): add repository test review integration test share preference behavior --- .../restaurant_tour_integreation_test.dart | 21 +- .../repository/restaurants_repository.dart | 24 +- lib/models/restaurant.dart | 2 +- .../restaurant_respository_test.dart | 131 ++++ .../restaurant_respository_test.mocks.dart | 91 +++ .../restaurants/test_restaurants_bloc.dart | 2 +- .../favorite_list_widget_test.mocks.dart | 712 ------------------ 7 files changed, 239 insertions(+), 744 deletions(-) create mode 100644 test/domain/repository/restaurant_respository_test.dart create mode 100644 test/domain/repository/restaurant_respository_test.mocks.dart delete mode 100644 test/view/widgets/favorite_list_widget_test.mocks.dart diff --git a/integration_test/restaurant_tour_integreation_test.dart b/integration_test/restaurant_tour_integreation_test.dart index df50f5d..d569369 100644 --- a/integration_test/restaurant_tour_integreation_test.dart +++ b/integration_test/restaurant_tour_integreation_test.dart @@ -120,6 +120,8 @@ void main() { favoriteButton, findsOneWidget, ); + //Check if already favorite + expect(find.byIcon(Icons.favorite), findsOne); await tester.tap(favoriteButton); // Trigger a frame. await tester.pumpAndSettle(); @@ -133,25 +135,6 @@ void main() { // Trigger a frame. await tester.pumpAndSettle(); - expect(find.byType(RestaurantTile), findsOne); - - // Now open the favorite restaurant - await tester.tap(restaurantTitleFinder.first); - - // Trigger a frame. - await tester.pumpAndSettle(); - - // Tap Favorite Widget to remove it - expect( - favoriteButton, - findsOneWidget, - ); - await tester.tap(favoriteButton); - - // go back and check if list is empty - await tester.pageBack(); - await tester.pumpAndSettle(); - expect(find.byType(RestaurantTile), findsNothing); }); }); diff --git a/lib/domain/repository/restaurants_repository.dart b/lib/domain/repository/restaurants_repository.dart index 8ecd732..8a3be6a 100644 --- a/lib/domain/repository/restaurants_repository.dart +++ b/lib/domain/repository/restaurants_repository.dart @@ -59,19 +59,21 @@ class RestaurantsRepository { Future addFavoriteRestaurant(Restaurant restaurant) async { _favorites.add(restaurant.copyWith(isFavorite: true)); - final restaurantIndex = - _restaurants.indexWhere((e) => e.id == restaurant.id); - _restaurants[restaurantIndex] = restaurant.copyWith(isFavorite: true); - await _favoriteRestaurantsDatasource - .addFavoriteRestaurant(restaurant.copyWith(isFavorite: true)); + final restaurantIndex = _restaurants.indexWhere((e) => e == restaurant); + if (restaurantIndex != -1) { + _restaurants[restaurantIndex] = restaurant.copyWith(isFavorite: true); + await _favoriteRestaurantsDatasource + .addFavoriteRestaurant(restaurant.copyWith(isFavorite: true)); + } } Future removeFavoriteRestaurant(Restaurant restaurant) async { - final restaurantIndex = - _restaurants.indexWhere((e) => e.id == restaurant.id); - _restaurants[restaurantIndex] = restaurant.copyWith(isFavorite: false); - _favorites.remove(restaurant); - await _favoriteRestaurantsDatasource - .removeFavoriteRestaurant(restaurant.copyWith(isFavorite: false)); + final restaurantIndex = _restaurants.indexWhere((e) => e == restaurant); + if (restaurantIndex != -1) { + _restaurants[restaurantIndex] = restaurant.copyWith(isFavorite: false); + _favorites.remove(restaurant); + await _favoriteRestaurantsDatasource + .removeFavoriteRestaurant(restaurant.copyWith(isFavorite: false)); + } } } diff --git a/lib/models/restaurant.dart b/lib/models/restaurant.dart index 3d081d7..642cb8a 100644 --- a/lib/models/restaurant.dart +++ b/lib/models/restaurant.dart @@ -168,7 +168,7 @@ class Restaurant extends Equatable { } @override - List get props => [id, isFavorite]; + List get props => [id]; } @JsonSerializable() diff --git a/test/domain/repository/restaurant_respository_test.dart b/test/domain/repository/restaurant_respository_test.dart new file mode 100644 index 0000000..5685a31 --- /dev/null +++ b/test/domain/repository/restaurant_respository_test.dart @@ -0,0 +1,131 @@ +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:restaurant_tour/domain/datasource/favorite_restaurants_datasource.dart'; +import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; +import 'package:restaurant_tour/models/restaurant.dart'; +import 'package:restaurant_tour/domain/repository/restaurants_repository.dart'; +import 'package:test/test.dart'; + +import 'restaurant_respository_test.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), +]) +void main() { + group('RestaurantsRepository', () { + late RestaurantsRepository repository; + late RestaurantsDatasource mockRestaurantsDatasource; + late FavoriteRestaurantsDatasource mockFavoritesDatasource; + List remoteRestaurants = [ + const Restaurant(id: "restaurant_a", name: 'Restaurant A'), + const Restaurant(id: "restaurant_b", name: 'Restaurant B'), + ]; + List favoriteRestaurants = [ + const Restaurant( + id: 'restaurant_a', + name: 'Restaurant A', + isFavorite: true, + ), + ]; + + setUp(() { + mockRestaurantsDatasource = MockRestaurantsDatasource(); + mockFavoritesDatasource = MockFavoriteRestaurantsDatasource(); + repository = RestaurantsRepository( + mockRestaurantsDatasource, + mockFavoritesDatasource, + ); + }); + + test('getRestaurants should return restaurant with favorite flag', + () async { + // Mock datasource behavior + when(mockRestaurantsDatasource.getRestaurants()) + .thenAnswer((_) => Future.value(remoteRestaurants)); + when(mockFavoritesDatasource.getFavoritesRestaurants()) + .thenAnswer((_) => Future.value(favoriteRestaurants)); + + // Call the method + final restaurants = await repository.getRestaurants(); + + // Assert the result + expect(restaurants.length, remoteRestaurants.length); + expect(restaurants.first.isFavorite, true); // Favorited from datasource + }); + + test( + 'getFavoriteRestaurants should empty cache and populates from datasource', + () async { + // Mock datasource behavior + when(mockFavoritesDatasource.getFavoritesRestaurants()).thenAnswer( + (_) => Future.value([ + const Restaurant( + id: 'restaurant_a', + name: 'Restaurant A', + isFavorite: true, + ), + ]), + ); + + // Call the method + final favoriteRestaurants = await repository.getFavoriteRestaurants(); + + // Assert the result + expect(favoriteRestaurants, favoriteRestaurants); + expect(favoriteRestaurants.first.isFavorite, true); + }); + + test('addFavoriteRestaurant should adds to cache and datasource', () async { + const restaurant = Restaurant(id: 'restaurant_b', name: 'Restaurant B'); + // Mock datasource behavior + when(mockRestaurantsDatasource.getRestaurants()) + .thenAnswer((_) async => remoteRestaurants); + when(mockFavoritesDatasource.addFavoriteRestaurant(restaurant)) + .thenAnswer((_) => Future.value()); + // Call restaurant to load cache + await repository.getRestaurants(); + // Call the method + await repository.addFavoriteRestaurant(restaurant); + + // Verify datasource call + verify(mockFavoritesDatasource.addFavoriteRestaurant(restaurant)); + }); + + test('removeFavoriteRestaurant should remove from cache and datasource', + () async { + const restaurant = Restaurant( + id: 'restaurant_a', + name: 'Restaurant A', + isFavorite: true, + ); + final favoriteList = [restaurant]; + // Mock datasource behavior + when(mockRestaurantsDatasource.getRestaurants()) + .thenAnswer((_) async => remoteRestaurants); + when(mockFavoritesDatasource.getFavoritesRestaurants()) + .thenAnswer((_) async => favoriteList); + when(mockFavoritesDatasource.removeFavoriteRestaurant(restaurant)) + .thenAnswer((_) => Future.value()); + + // Call the method + await repository.getRestaurants(); + await repository.removeFavoriteRestaurant(restaurant); + //Clear favorite list + favoriteList.clear(); + + final restaurantsResult = await repository.getRestaurants(); + final favoritesResult = await repository.getFavoriteRestaurants(); + + // Assert + expect(favoritesResult.contains(restaurant), false); + expect( + restaurantsResult.firstWhere((r) => r.id == restaurant.id).isFavorite, + false, + ); + + // Verify datasource call + verify(mockFavoritesDatasource.removeFavoriteRestaurant(restaurant)); + }); + }); +} diff --git a/test/domain/repository/restaurant_respository_test.mocks.dart b/test/domain/repository/restaurant_respository_test.mocks.dart new file mode 100644 index 0000000..5aec593 --- /dev/null +++ b/test/domain/repository/restaurant_respository_test.mocks.dart @@ -0,0 +1,91 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in restaurant_tour/test/domain/repository/restaurant_respository_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:restaurant_tour/domain/datasource/favorite_restaurants_datasource.dart' + as _i5; +import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart' + as _i2; +import 'package:restaurant_tour/models/restaurant.dart' as _i4; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [RestaurantsDatasource]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockRestaurantsDatasource extends _i1.Mock + implements _i2.RestaurantsDatasource { + @override + _i3.Future> getRestaurants({ + int? offset, + int? limit, + }) => + (super.noSuchMethod( + Invocation.method( + #getRestaurants, + [], + { + #offset: offset, + #limit: limit, + }, + ), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), + returnValueForMissingStub: + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); +} + +/// A class which mocks [FavoriteRestaurantsDatasource]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFavoriteRestaurantsDatasource extends _i1.Mock + implements _i5.FavoriteRestaurantsDatasource { + @override + _i3.Future addFavoriteRestaurant(_i4.Restaurant? restaurant) => + (super.noSuchMethod( + Invocation.method( + #addFavoriteRestaurant, + [restaurant], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future removeFavoriteRestaurant(_i4.Restaurant? restaurant) => + (super.noSuchMethod( + Invocation.method( + #removeFavoriteRestaurant, + [restaurant], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future> getFavoritesRestaurants() => + (super.noSuchMethod( + Invocation.method( + #getFavoritesRestaurants, + [], + ), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), + returnValueForMissingStub: + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); +} diff --git a/test/presentation/restaurants/test_restaurants_bloc.dart b/test/presentation/restaurants/test_restaurants_bloc.dart index 5f62367..d90c228 100644 --- a/test/presentation/restaurants/test_restaurants_bloc.dart +++ b/test/presentation/restaurants/test_restaurants_bloc.dart @@ -48,7 +48,7 @@ void main() { ); blocTest( - 'RestaurantsBloc should throw an RestaurantsListError', + 'RestaurantsBloc should throw an RestaurantsListError', setUp: () => when(usecase.getRestaurants()).thenAnswer( (_) async => [const Restaurant(id: 'id', name: 'restaurant')], ), diff --git a/test/view/widgets/favorite_list_widget_test.mocks.dart b/test/view/widgets/favorite_list_widget_test.mocks.dart deleted file mode 100644 index 7646e05..0000000 --- a/test/view/widgets/favorite_list_widget_test.mocks.dart +++ /dev/null @@ -1,712 +0,0 @@ -// Mocks generated by Mockito 5.4.4 from annotations -// in restaurant_tour/test/view/widgets/favorite_list_widget_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i3; -import 'dart:io' as _i2; - -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -class _FakeDuration_0 extends _i1.SmartFake implements Duration { - _FakeDuration_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeHttpClientRequest_1 extends _i1.SmartFake - implements _i2.HttpClientRequest { - _FakeHttpClientRequest_1( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -/// A class which mocks [HttpClient]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockHttpClient extends _i1.Mock implements _i2.HttpClient { - @override - Duration get idleTimeout => (super.noSuchMethod( - Invocation.getter(#idleTimeout), - returnValue: _FakeDuration_0( - this, - Invocation.getter(#idleTimeout), - ), - returnValueForMissingStub: _FakeDuration_0( - this, - Invocation.getter(#idleTimeout), - ), - ) as Duration); - - @override - set idleTimeout(Duration? _idleTimeout) => super.noSuchMethod( - Invocation.setter( - #idleTimeout, - _idleTimeout, - ), - returnValueForMissingStub: null, - ); - - @override - set connectionTimeout(Duration? _connectionTimeout) => super.noSuchMethod( - Invocation.setter( - #connectionTimeout, - _connectionTimeout, - ), - returnValueForMissingStub: null, - ); - - @override - set maxConnectionsPerHost(int? _maxConnectionsPerHost) => super.noSuchMethod( - Invocation.setter( - #maxConnectionsPerHost, - _maxConnectionsPerHost, - ), - returnValueForMissingStub: null, - ); - - @override - bool get autoUncompress => (super.noSuchMethod( - Invocation.getter(#autoUncompress), - returnValue: false, - returnValueForMissingStub: false, - ) as bool); - - @override - set autoUncompress(bool? _autoUncompress) => super.noSuchMethod( - Invocation.setter( - #autoUncompress, - _autoUncompress, - ), - returnValueForMissingStub: null, - ); - - @override - set userAgent(String? _userAgent) => super.noSuchMethod( - Invocation.setter( - #userAgent, - _userAgent, - ), - returnValueForMissingStub: null, - ); - - @override - set authenticate( - _i3.Future Function( - Uri, - String, - String?, - )? f) => - super.noSuchMethod( - Invocation.setter( - #authenticate, - f, - ), - returnValueForMissingStub: null, - ); - - @override - set connectionFactory( - _i3.Future<_i2.ConnectionTask<_i2.Socket>> Function( - Uri, - String?, - int?, - )? f) => - super.noSuchMethod( - Invocation.setter( - #connectionFactory, - f, - ), - returnValueForMissingStub: null, - ); - - @override - set findProxy(String Function(Uri)? f) => super.noSuchMethod( - Invocation.setter( - #findProxy, - f, - ), - returnValueForMissingStub: null, - ); - - @override - set authenticateProxy( - _i3.Future Function( - String, - int, - String, - String?, - )? f) => - super.noSuchMethod( - Invocation.setter( - #authenticateProxy, - f, - ), - returnValueForMissingStub: null, - ); - - @override - set badCertificateCallback( - bool Function( - _i2.X509Certificate, - String, - int, - )? callback) => - super.noSuchMethod( - Invocation.setter( - #badCertificateCallback, - callback, - ), - returnValueForMissingStub: null, - ); - - @override - set keyLog(dynamic Function(String)? callback) => super.noSuchMethod( - Invocation.setter( - #keyLog, - callback, - ), - returnValueForMissingStub: null, - ); - - @override - _i3.Future<_i2.HttpClientRequest> open( - String? method, - String? host, - int? port, - String? path, - ) => - (super.noSuchMethod( - Invocation.method( - #open, - [ - method, - host, - port, - path, - ], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #open, - [ - method, - host, - port, - path, - ], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #open, - [ - method, - host, - port, - path, - ], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> openUrl( - String? method, - Uri? url, - ) => - (super.noSuchMethod( - Invocation.method( - #openUrl, - [ - method, - url, - ], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #openUrl, - [ - method, - url, - ], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #openUrl, - [ - method, - url, - ], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> get( - String? host, - int? port, - String? path, - ) => - (super.noSuchMethod( - Invocation.method( - #get, - [ - host, - port, - path, - ], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #get, - [ - host, - port, - path, - ], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #get, - [ - host, - port, - path, - ], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> getUrl(Uri? url) => (super.noSuchMethod( - Invocation.method( - #getUrl, - [url], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #getUrl, - [url], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #getUrl, - [url], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> post( - String? host, - int? port, - String? path, - ) => - (super.noSuchMethod( - Invocation.method( - #post, - [ - host, - port, - path, - ], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #post, - [ - host, - port, - path, - ], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #post, - [ - host, - port, - path, - ], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> postUrl(Uri? url) => (super.noSuchMethod( - Invocation.method( - #postUrl, - [url], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #postUrl, - [url], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #postUrl, - [url], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> put( - String? host, - int? port, - String? path, - ) => - (super.noSuchMethod( - Invocation.method( - #put, - [ - host, - port, - path, - ], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #put, - [ - host, - port, - path, - ], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #put, - [ - host, - port, - path, - ], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> putUrl(Uri? url) => (super.noSuchMethod( - Invocation.method( - #putUrl, - [url], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #putUrl, - [url], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #putUrl, - [url], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> delete( - String? host, - int? port, - String? path, - ) => - (super.noSuchMethod( - Invocation.method( - #delete, - [ - host, - port, - path, - ], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #delete, - [ - host, - port, - path, - ], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #delete, - [ - host, - port, - path, - ], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> deleteUrl(Uri? url) => (super.noSuchMethod( - Invocation.method( - #deleteUrl, - [url], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #deleteUrl, - [url], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #deleteUrl, - [url], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> patch( - String? host, - int? port, - String? path, - ) => - (super.noSuchMethod( - Invocation.method( - #patch, - [ - host, - port, - path, - ], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #patch, - [ - host, - port, - path, - ], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #patch, - [ - host, - port, - path, - ], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> patchUrl(Uri? url) => (super.noSuchMethod( - Invocation.method( - #patchUrl, - [url], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #patchUrl, - [url], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #patchUrl, - [url], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> head( - String? host, - int? port, - String? path, - ) => - (super.noSuchMethod( - Invocation.method( - #head, - [ - host, - port, - path, - ], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #head, - [ - host, - port, - path, - ], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #head, - [ - host, - port, - path, - ], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - _i3.Future<_i2.HttpClientRequest> headUrl(Uri? url) => (super.noSuchMethod( - Invocation.method( - #headUrl, - [url], - ), - returnValue: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #headUrl, - [url], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.HttpClientRequest>.value(_FakeHttpClientRequest_1( - this, - Invocation.method( - #headUrl, - [url], - ), - )), - ) as _i3.Future<_i2.HttpClientRequest>); - - @override - void addCredentials( - Uri? url, - String? realm, - _i2.HttpClientCredentials? credentials, - ) => - super.noSuchMethod( - Invocation.method( - #addCredentials, - [ - url, - realm, - credentials, - ], - ), - returnValueForMissingStub: null, - ); - - @override - void addProxyCredentials( - String? host, - int? port, - String? realm, - _i2.HttpClientCredentials? credentials, - ) => - super.noSuchMethod( - Invocation.method( - #addProxyCredentials, - [ - host, - port, - realm, - credentials, - ], - ), - returnValueForMissingStub: null, - ); - - @override - void close({bool? force = false}) => super.noSuchMethod( - Invocation.method( - #close, - [], - {#force: force}, - ), - returnValueForMissingStub: null, - ); -} From 6d7f9787d440620ea997e59f5a9b68c51a96586c Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Wed, 18 Sep 2024 11:44:08 -0400 Subject: [PATCH 10/12] test(coverage): add coverage file --- coverage/lcov.info | 591 +++++++++ coverage/report/amber.png | Bin 0 -> 141 bytes coverage/report/cmd_line | 1 + .../exceptions/exceptions.dart.func-c.html | 75 ++ .../exceptions/exceptions.dart.func.html | 75 ++ .../exceptions/exceptions.dart.gcov.html | 123 ++ .../common/exceptions/index-sort-f.html | 105 ++ .../common/exceptions/index-sort-l.html | 105 ++ coverage/report/common/exceptions/index.html | 105 ++ .../domain/repository/index-sort-f.html | 105 ++ .../domain/repository/index-sort-l.html | 105 ++ coverage/report/domain/repository/index.html | 105 ++ .../restaurants_repository.dart.func-c.html | 75 ++ .../restaurants_repository.dart.func.html | 75 ++ .../restaurants_repository.dart.gcov.html | 155 +++ .../report/domain/usecase/index-sort-f.html | 105 ++ .../report/domain/usecase/index-sort-l.html | 105 ++ coverage/report/domain/usecase/index.html | 105 ++ .../restaurants_usecase.dart.func-c.html | 75 ++ .../restaurants_usecase.dart.func.html | 75 ++ .../restaurants_usecase.dart.gcov.html | 103 ++ coverage/report/emerald.png | Bin 0 -> 141 bytes coverage/report/gcov.css | 1101 +++++++++++++++++ coverage/report/glass.png | Bin 0 -> 167 bytes coverage/report/index-sort-f.html | 189 +++ coverage/report/index-sort-l.html | 189 +++ coverage/report/index.html | 189 +++ coverage/report/models/index-sort-f.html | 117 ++ coverage/report/models/index-sort-l.html | 117 ++ coverage/report/models/index.html | 117 ++ .../report/models/restaurant.dart.func-c.html | 75 ++ .../report/models/restaurant.dart.func.html | 75 ++ .../report/models/restaurant.dart.gcov.html | 265 ++++ .../models/restaurant.g.dart.func-c.html | 75 ++ .../report/models/restaurant.g.dart.func.html | 75 ++ .../report/models/restaurant.g.dart.gcov.html | 189 +++ ...avorites_restaurants_bloc.dart.func-c.html | 75 ++ .../favorites_restaurants_bloc.dart.func.html | 75 ++ .../favorites_restaurants_bloc.dart.gcov.html | 154 +++ ...vorites_restaurants_event.dart.func-c.html | 75 ++ ...favorites_restaurants_event.dart.func.html | 75 ++ ...favorites_restaurants_event.dart.gcov.html | 92 ++ ...vorites_restaurants_state.dart.func-c.html | 75 ++ ...favorites_restaurants_state.dart.func.html | 75 ++ ...favorites_restaurants_state.dart.gcov.html | 104 ++ .../presentation/favorites/index-sort-f.html | 129 ++ .../presentation/favorites/index-sort-l.html | 129 ++ .../report/presentation/favorites/index.html | 129 ++ .../restaurants/index-sort-f.html | 129 ++ .../restaurants/index-sort-l.html | 129 ++ .../presentation/restaurants/index.html | 129 ++ .../restaurants_bloc.dart.func-c.html | 75 ++ .../restaurants_bloc.dart.func.html | 75 ++ .../restaurants_bloc.dart.gcov.html | 145 +++ .../restaurants_event.dart.func-c.html | 75 ++ .../restaurants_event.dart.func.html | 75 ++ .../restaurants_event.dart.gcov.html | 88 ++ .../restaurants_state.dart.func-c.html | 75 ++ .../restaurants_state.dart.func.html | 75 ++ .../restaurants_state.dart.gcov.html | 119 ++ coverage/report/ruby.png | Bin 0 -> 141 bytes coverage/report/snow.png | Bin 0 -> 141 bytes coverage/report/updown.png | Bin 0 -> 117 bytes coverage/report/view/pages/index-sort-f.html | 105 ++ coverage/report/view/pages/index-sort-l.html | 105 ++ coverage/report/view/pages/index.html | 105 ++ .../pages/restaurant_page.dart.func-c.html | 75 ++ .../view/pages/restaurant_page.dart.func.html | 75 ++ .../view/pages/restaurant_page.dart.gcov.html | 170 +++ .../widgets/address_widget.dart.func-c.html | 75 ++ .../widgets/address_widget.dart.func.html | 75 ++ .../widgets/address_widget.dart.gcov.html | 102 ++ .../favorite_button_widget.dart.func-c.html | 75 ++ .../favorite_button_widget.dart.func.html | 75 ++ .../favorite_button_widget.dart.gcov.html | 114 ++ .../favorite_list_widget.dart.func-c.html | 75 ++ .../favorite_list_widget.dart.func.html | 75 ++ .../favorite_list_widget.dart.gcov.html | 134 ++ .../report/view/widgets/index-sort-f.html | 237 ++++ .../report/view/widgets/index-sort-l.html | 237 ++++ coverage/report/view/widgets/index.html | 237 ++++ .../overall_rating_widget.dart.func-c.html | 75 ++ .../overall_rating_widget.dart.func.html | 75 ++ .../overall_rating_widget.dart.gcov.html | 111 ++ ...ant_category_price_widget.dart.func-c.html | 75 ++ ...urant_category_price_widget.dart.func.html | 75 ++ ...urant_category_price_widget.dart.gcov.html | 104 ++ .../restaurant_hero_widget.dart.func-c.html | 75 ++ .../restaurant_hero_widget.dart.func.html | 75 ++ .../restaurant_hero_widget.dart.gcov.html | 100 ++ .../restaurant_list_widget.dart.func-c.html | 75 ++ .../restaurant_list_widget.dart.func.html | 75 ++ .../restaurant_list_widget.dart.gcov.html | 173 +++ .../restaurant_open_widget.dart.func-c.html | 75 ++ .../restaurant_open_widget.dart.func.html | 75 ++ .../restaurant_open_widget.dart.gcov.html | 107 ++ ...aurant_star_rating_widget.dart.func-c.html | 75 ++ ...staurant_star_rating_widget.dart.func.html | 75 ++ ...staurant_star_rating_widget.dart.gcov.html | 97 ++ .../widgets/restaurant_tile.dart.func-c.html | 75 ++ .../widgets/restaurant_tile.dart.func.html | 75 ++ .../widgets/restaurant_tile.dart.gcov.html | 156 +++ .../review_list_widget.dart.func-c.html | 75 ++ .../widgets/review_list_widget.dart.func.html | 75 ++ .../widgets/review_list_widget.dart.gcov.html | 113 ++ .../view/widgets/review_tile.dart.func-c.html | 75 ++ .../view/widgets/review_tile.dart.func.html | 75 ++ .../view/widgets/review_tile.dart.gcov.html | 115 ++ 108 files changed, 12089 insertions(+) create mode 100644 coverage/lcov.info create mode 100644 coverage/report/amber.png create mode 100644 coverage/report/cmd_line create mode 100644 coverage/report/common/exceptions/exceptions.dart.func-c.html create mode 100644 coverage/report/common/exceptions/exceptions.dart.func.html create mode 100644 coverage/report/common/exceptions/exceptions.dart.gcov.html create mode 100644 coverage/report/common/exceptions/index-sort-f.html create mode 100644 coverage/report/common/exceptions/index-sort-l.html create mode 100644 coverage/report/common/exceptions/index.html create mode 100644 coverage/report/domain/repository/index-sort-f.html create mode 100644 coverage/report/domain/repository/index-sort-l.html create mode 100644 coverage/report/domain/repository/index.html create mode 100644 coverage/report/domain/repository/restaurants_repository.dart.func-c.html create mode 100644 coverage/report/domain/repository/restaurants_repository.dart.func.html create mode 100644 coverage/report/domain/repository/restaurants_repository.dart.gcov.html create mode 100644 coverage/report/domain/usecase/index-sort-f.html create mode 100644 coverage/report/domain/usecase/index-sort-l.html create mode 100644 coverage/report/domain/usecase/index.html create mode 100644 coverage/report/domain/usecase/restaurants_usecase.dart.func-c.html create mode 100644 coverage/report/domain/usecase/restaurants_usecase.dart.func.html create mode 100644 coverage/report/domain/usecase/restaurants_usecase.dart.gcov.html create mode 100644 coverage/report/emerald.png create mode 100644 coverage/report/gcov.css create mode 100644 coverage/report/glass.png create mode 100644 coverage/report/index-sort-f.html create mode 100644 coverage/report/index-sort-l.html create mode 100644 coverage/report/index.html create mode 100644 coverage/report/models/index-sort-f.html create mode 100644 coverage/report/models/index-sort-l.html create mode 100644 coverage/report/models/index.html create mode 100644 coverage/report/models/restaurant.dart.func-c.html create mode 100644 coverage/report/models/restaurant.dart.func.html create mode 100644 coverage/report/models/restaurant.dart.gcov.html create mode 100644 coverage/report/models/restaurant.g.dart.func-c.html create mode 100644 coverage/report/models/restaurant.g.dart.func.html create mode 100644 coverage/report/models/restaurant.g.dart.gcov.html create mode 100644 coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.func-c.html create mode 100644 coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.func.html create mode 100644 coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.gcov.html create mode 100644 coverage/report/presentation/favorites/favorites_restaurants_event.dart.func-c.html create mode 100644 coverage/report/presentation/favorites/favorites_restaurants_event.dart.func.html create mode 100644 coverage/report/presentation/favorites/favorites_restaurants_event.dart.gcov.html create mode 100644 coverage/report/presentation/favorites/favorites_restaurants_state.dart.func-c.html create mode 100644 coverage/report/presentation/favorites/favorites_restaurants_state.dart.func.html create mode 100644 coverage/report/presentation/favorites/favorites_restaurants_state.dart.gcov.html create mode 100644 coverage/report/presentation/favorites/index-sort-f.html create mode 100644 coverage/report/presentation/favorites/index-sort-l.html create mode 100644 coverage/report/presentation/favorites/index.html create mode 100644 coverage/report/presentation/restaurants/index-sort-f.html create mode 100644 coverage/report/presentation/restaurants/index-sort-l.html create mode 100644 coverage/report/presentation/restaurants/index.html create mode 100644 coverage/report/presentation/restaurants/restaurants_bloc.dart.func-c.html create mode 100644 coverage/report/presentation/restaurants/restaurants_bloc.dart.func.html create mode 100644 coverage/report/presentation/restaurants/restaurants_bloc.dart.gcov.html create mode 100644 coverage/report/presentation/restaurants/restaurants_event.dart.func-c.html create mode 100644 coverage/report/presentation/restaurants/restaurants_event.dart.func.html create mode 100644 coverage/report/presentation/restaurants/restaurants_event.dart.gcov.html create mode 100644 coverage/report/presentation/restaurants/restaurants_state.dart.func-c.html create mode 100644 coverage/report/presentation/restaurants/restaurants_state.dart.func.html create mode 100644 coverage/report/presentation/restaurants/restaurants_state.dart.gcov.html create mode 100644 coverage/report/ruby.png create mode 100644 coverage/report/snow.png create mode 100644 coverage/report/updown.png create mode 100644 coverage/report/view/pages/index-sort-f.html create mode 100644 coverage/report/view/pages/index-sort-l.html create mode 100644 coverage/report/view/pages/index.html create mode 100644 coverage/report/view/pages/restaurant_page.dart.func-c.html create mode 100644 coverage/report/view/pages/restaurant_page.dart.func.html create mode 100644 coverage/report/view/pages/restaurant_page.dart.gcov.html create mode 100644 coverage/report/view/widgets/address_widget.dart.func-c.html create mode 100644 coverage/report/view/widgets/address_widget.dart.func.html create mode 100644 coverage/report/view/widgets/address_widget.dart.gcov.html create mode 100644 coverage/report/view/widgets/favorite_button_widget.dart.func-c.html create mode 100644 coverage/report/view/widgets/favorite_button_widget.dart.func.html create mode 100644 coverage/report/view/widgets/favorite_button_widget.dart.gcov.html create mode 100644 coverage/report/view/widgets/favorite_list_widget.dart.func-c.html create mode 100644 coverage/report/view/widgets/favorite_list_widget.dart.func.html create mode 100644 coverage/report/view/widgets/favorite_list_widget.dart.gcov.html create mode 100644 coverage/report/view/widgets/index-sort-f.html create mode 100644 coverage/report/view/widgets/index-sort-l.html create mode 100644 coverage/report/view/widgets/index.html create mode 100644 coverage/report/view/widgets/overall_rating_widget.dart.func-c.html create mode 100644 coverage/report/view/widgets/overall_rating_widget.dart.func.html create mode 100644 coverage/report/view/widgets/overall_rating_widget.dart.gcov.html create mode 100644 coverage/report/view/widgets/restaurant_category_price_widget.dart.func-c.html create mode 100644 coverage/report/view/widgets/restaurant_category_price_widget.dart.func.html create mode 100644 coverage/report/view/widgets/restaurant_category_price_widget.dart.gcov.html create mode 100644 coverage/report/view/widgets/restaurant_hero_widget.dart.func-c.html create mode 100644 coverage/report/view/widgets/restaurant_hero_widget.dart.func.html create mode 100644 coverage/report/view/widgets/restaurant_hero_widget.dart.gcov.html create mode 100644 coverage/report/view/widgets/restaurant_list_widget.dart.func-c.html create mode 100644 coverage/report/view/widgets/restaurant_list_widget.dart.func.html create mode 100644 coverage/report/view/widgets/restaurant_list_widget.dart.gcov.html create mode 100644 coverage/report/view/widgets/restaurant_open_widget.dart.func-c.html create mode 100644 coverage/report/view/widgets/restaurant_open_widget.dart.func.html create mode 100644 coverage/report/view/widgets/restaurant_open_widget.dart.gcov.html create mode 100644 coverage/report/view/widgets/restaurant_star_rating_widget.dart.func-c.html create mode 100644 coverage/report/view/widgets/restaurant_star_rating_widget.dart.func.html create mode 100644 coverage/report/view/widgets/restaurant_star_rating_widget.dart.gcov.html create mode 100644 coverage/report/view/widgets/restaurant_tile.dart.func-c.html create mode 100644 coverage/report/view/widgets/restaurant_tile.dart.func.html create mode 100644 coverage/report/view/widgets/restaurant_tile.dart.gcov.html create mode 100644 coverage/report/view/widgets/review_list_widget.dart.func-c.html create mode 100644 coverage/report/view/widgets/review_list_widget.dart.func.html create mode 100644 coverage/report/view/widgets/review_list_widget.dart.gcov.html create mode 100644 coverage/report/view/widgets/review_tile.dart.func-c.html create mode 100644 coverage/report/view/widgets/review_tile.dart.func.html create mode 100644 coverage/report/view/widgets/review_tile.dart.gcov.html diff --git a/coverage/lcov.info b/coverage/lcov.info new file mode 100644 index 0000000..810f5ef --- /dev/null +++ b/coverage/lcov.info @@ -0,0 +1,591 @@ +SF:lib/view/widgets/restaurant_category_price_widget.dart +DA:5,1 +DA:12,1 +DA:14,1 +DA:15,1 +DA:16,1 +DA:17,1 +DA:18,3 +DA:21,1 +DA:22,1 +DA:23,3 +LF:10 +LH:10 +end_of_record +SF:lib/models/restaurant.dart +DA:11,1 +DA:16,0 +DA:17,0 +DA:19,0 +DA:27,1 +DA:31,0 +DA:33,0 +DA:43,1 +DA:49,0 +DA:51,0 +DA:61,1 +DA:68,0 +DA:70,0 +DA:78,0 +DA:82,0 +DA:83,0 +DA:85,0 +DA:101,6 +DA:114,1 +DA:126,1 +DA:127,1 +DA:128,1 +DA:129,1 +DA:130,1 +DA:131,1 +DA:132,1 +DA:133,1 +DA:134,1 +DA:135,1 +DA:136,0 +DA:140,0 +DA:141,0 +DA:143,0 +DA:146,0 +DA:147,0 +DA:148,0 +DA:154,0 +DA:155,0 +DA:156,0 +DA:163,3 +DA:164,5 +DA:165,3 +DA:170,1 +DA:171,2 +DA:180,0 +DA:185,0 +DA:186,0 +DA:188,0 +LF:48 +LH:21 +end_of_record +SF:lib/models/restaurant.g.dart +DA:9,0 +DA:10,0 +DA:11,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:19,0 +DA:20,0 +DA:23,0 +DA:24,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:44,0 +DA:45,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:55,0 +DA:56,0 +DA:59,0 +DA:60,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:69,0 +DA:70,0 +DA:71,0 +DA:72,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:81,0 +DA:82,0 +DA:85,0 +DA:86,0 +DA:87,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:96,0 +DA:99,0 +DA:101,0 +DA:102,0 +DA:103,0 +DA:104,0 +DA:105,0 +DA:108,0 +DA:110,0 +DA:111,0 +DA:112,0 +LF:73 +LH:0 +end_of_record +SF:lib/view/widgets/restaurant_star_rating_widget.dart +DA:4,8 +DA:7,4 +DA:9,4 +DA:11,4 +DA:12,12 +DA:13,4 +DA:16,4 +LF:7 +LH:7 +end_of_record +SF:lib/view/widgets/review_tile.dart +DA:7,1 +DA:10,1 +DA:12,1 +DA:14,1 +DA:15,3 +DA:17,1 +DA:18,2 +DA:19,3 +DA:22,1 +DA:23,1 +DA:24,1 +DA:25,1 +DA:28,1 +DA:29,3 +DA:33,4 +LF:15 +LH:15 +end_of_record +SF:lib/domain/repository/restaurants_repository.dart +DA:6,1 +DA:16,1 +DA:17,2 +DA:18,2 +DA:20,2 +DA:21,2 +DA:23,1 +DA:24,2 +DA:25,1 +DA:28,1 +DA:31,1 +DA:34,0 +DA:35,0 +DA:39,0 +DA:41,0 +DA:42,0 +DA:44,0 +DA:48,1 +DA:49,2 +DA:51,2 +DA:52,2 +DA:53,3 +DA:57,2 +DA:60,1 +DA:61,3 +DA:62,4 +DA:63,2 +DA:64,3 +DA:65,1 +DA:66,2 +DA:70,1 +DA:71,4 +DA:72,2 +DA:73,3 +DA:74,2 +DA:75,1 +DA:76,2 +LF:37 +LH:31 +end_of_record +SF:lib/view/widgets/address_widget.dart +DA:5,1 +DA:7,1 +DA:9,1 +DA:11,1 +DA:15,1 +DA:16,1 +DA:17,1 +DA:18,1 +DA:19,1 +DA:20,1 +LF:10 +LH:10 +end_of_record +SF:lib/view/widgets/restaurant_hero_widget.dart +DA:4,1 +DA:12,1 +DA:14,1 +DA:15,1 +DA:16,1 +DA:17,0 +DA:19,1 +LF:7 +LH:6 +end_of_record +SF:lib/view/widgets/favorite_button_widget.dart +DA:4,1 +DA:12,1 +DA:13,1 +DA:18,1 +DA:20,3 +DA:21,1 +DA:24,1 +DA:26,1 +DA:27,1 +DA:28,2 +DA:29,2 +DA:31,4 +DA:33,1 +LF:13 +LH:13 +end_of_record +SF:lib/presentation/restaurants/restaurants_bloc.dart +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:23,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:32,0 +DA:34,0 +DA:35,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:45,0 +DA:46,0 +DA:49,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:63,0 +DA:67,0 +LF:28 +LH:0 +end_of_record +SF:lib/presentation/restaurants/restaurants_event.dart +DA:9,0 +LF:1 +LH:0 +end_of_record +SF:lib/presentation/restaurants/restaurants_state.dart +DA:11,1 +DA:23,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:41,0 +LF:9 +LH:1 +end_of_record +SF:lib/view/widgets/restaurant_list_widget.dart +DA:9,1 +DA:11,1 +DA:12,1 +DA:17,1 +DA:19,1 +DA:22,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:30,1 +DA:32,1 +DA:33,2 +DA:34,1 +DA:35,1 +DA:36,1 +DA:37,1 +DA:40,3 +DA:41,1 +DA:42,1 +DA:43,4 +DA:52,4 +DA:53,0 +DA:54,0 +DA:60,3 +DA:61,1 +DA:62,1 +DA:69,1 +DA:70,0 +DA:71,0 +DA:72,0 +DA:73,0 +DA:77,3 +DA:82,1 +DA:83,0 +DA:84,0 +DA:88,1 +DA:93,1 +LF:37 +LH:25 +end_of_record +SF:lib/common/exceptions/exceptions.dart +DA:4,0 +DA:5,0 +DA:7,0 +DA:8,0 +DA:9,0 +DA:10,0 +DA:15,0 +DA:18,0 +DA:22,0 +DA:25,0 +DA:29,0 +DA:32,0 +DA:36,0 +DA:39,0 +DA:43,0 +DA:46,0 +LF:16 +LH:0 +end_of_record +SF:lib/domain/usecase/restaurants_usecase.dart +DA:5,1 +DA:8,1 +DA:9,2 +DA:12,1 +DA:13,2 +DA:16,1 +DA:17,2 +DA:20,1 +DA:21,2 +DA:24,1 +DA:25,2 +LF:11 +LH:11 +end_of_record +SF:lib/presentation/favorites/favorites_restaurants_event.dart +DA:9,0 +DA:14,0 +LF:2 +LH:0 +end_of_record +SF:lib/presentation/favorites/favorites_restaurants_state.dart +DA:11,0 +DA:16,0 +DA:21,0 +DA:26,1 +LF:4 +LH:1 +end_of_record +SF:lib/presentation/favorites/favorites_restaurants_bloc.dart +DA:15,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:24,0 +DA:25,0 +DA:29,0 +DA:31,0 +DA:32,0 +DA:34,0 +DA:35,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:43,0 +DA:46,0 +DA:49,0 +DA:50,0 +DA:52,0 +DA:53,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:67,0 +DA:72,0 +DA:76,0 +LF:35 +LH:0 +end_of_record +SF:lib/view/pages/restaurant_page.dart +DA:16,0 +DA:22,0 +DA:23,0 +DA:28,0 +DA:30,0 +DA:31,0 +DA:34,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:44,0 +DA:45,0 +DA:48,0 +DA:50,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:61,0 +DA:64,0 +DA:66,0 +DA:67,0 +DA:69,0 +DA:70,0 +DA:71,0 +DA:72,0 +DA:74,0 +DA:79,0 +DA:80,0 +DA:83,0 +DA:85,0 +LF:37 +LH:0 +end_of_record +SF:lib/view/widgets/overall_rating_widget.dart +DA:6,0 +DA:8,0 +DA:10,0 +DA:12,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:20,0 +LF:9 +LH:0 +end_of_record +SF:lib/view/widgets/restaurant_open_widget.dart +DA:5,3 +DA:7,3 +DA:9,3 +DA:11,3 +DA:12,3 +DA:13,3 +DA:14,3 +DA:15,3 +DA:16,3 +DA:17,3 +DA:20,3 +DA:22,3 +DA:25,3 +LF:13 +LH:13 +end_of_record +SF:lib/view/widgets/review_list_widget.dart +DA:7,0 +DA:9,0 +DA:11,0 +DA:12,0 +DA:14,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:21,0 +DA:24,0 +DA:25,0 +DA:27,0 +DA:31,0 +LF:13 +LH:0 +end_of_record +SF:lib/view/widgets/restaurant_tile.dart +DA:8,3 +DA:11,3 +DA:13,3 +DA:14,12 +DA:15,3 +DA:16,3 +DA:18,3 +DA:20,3 +DA:21,3 +DA:22,6 +DA:23,3 +DA:24,3 +DA:25,3 +DA:26,0 +DA:30,9 +DA:35,3 +DA:36,3 +DA:38,3 +DA:41,3 +DA:42,3 +DA:43,3 +DA:44,6 +DA:45,9 +DA:48,3 +DA:49,3 +DA:50,3 +DA:51,6 +DA:52,9 +DA:55,3 +DA:56,8 +DA:57,9 +DA:61,3 +DA:63,3 +DA:64,3 +DA:65,9 +DA:67,9 +LF:36 +LH:35 +end_of_record +SF:lib/view/widgets/favorite_list_widget.dart +DA:9,1 +DA:11,1 +DA:12,1 +DA:16,1 +DA:18,1 +DA:19,2 +DA:20,1 +DA:21,1 +DA:22,1 +DA:23,1 +DA:26,2 +DA:27,1 +DA:28,1 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:36,1 +DA:37,2 +DA:43,1 +DA:44,0 +DA:45,0 +DA:49,1 +DA:54,1 +LF:24 +LH:18 +end_of_record diff --git a/coverage/report/amber.png b/coverage/report/amber.png new file mode 100644 index 0000000000000000000000000000000000000000..2cab170d8359081983a4e343848dfe06bc490f12 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^G2tW}LqE04T&+ z;1OBOz`!j8!i<;h*8KqrvZOouIx;Y9?C1WI$O`1M1^9%x{(levWG + + + + + + LCOV - lcov.info - common/exceptions/exceptions.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - common/exceptions - exceptions.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %160
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/common/exceptions/exceptions.dart.func.html b/coverage/report/common/exceptions/exceptions.dart.func.html new file mode 100644 index 0000000..49d0f1f --- /dev/null +++ b/coverage/report/common/exceptions/exceptions.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - common/exceptions/exceptions.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - common/exceptions - exceptions.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %160
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/common/exceptions/exceptions.dart.gcov.html b/coverage/report/common/exceptions/exceptions.dart.gcov.html new file mode 100644 index 0000000..2bc5120 --- /dev/null +++ b/coverage/report/common/exceptions/exceptions.dart.gcov.html @@ -0,0 +1,123 @@ + + + + + + + LCOV - lcov.info - common/exceptions/exceptions.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - common/exceptions - exceptions.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %160
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : class AppException implements Exception {
+       2              :   String code;
+       3              :   String description;
+       4            0 :   AppException(this.code, this.description);
+       5            0 :   @override
+       6              :   String toString() {
+       7            0 :     return {
+       8            0 :       'code': code,
+       9            0 :       'description': description,
+      10            0 :     }.toString();
+      11              :   }
+      12              : }
+      13              : 
+      14              : class RestaurantNotFoundException extends AppException {
+      15            0 :   RestaurantNotFoundException({
+      16              :     code = "RESTAURANT_NOT_FOUND",
+      17              :     description = "The restaurant searched is not our dataset",
+      18            0 :   }) : super(code, description);
+      19              : }
+      20              : 
+      21              : class RestaurantListException extends AppException {
+      22            0 :   RestaurantListException({
+      23              :     code = "RESTAURANTS_LIST_NOT_AVAILABLE",
+      24              :     description = "The restaurant is not available at the moment",
+      25            0 :   }) : super(code, description);
+      26              : }
+      27              : 
+      28              : class FavoritesRestaurantsListException extends AppException {
+      29            0 :   FavoritesRestaurantsListException({
+      30              :     code = "FAVORITES_RESTAURANTS_LIST_NOT_AVAILABLE",
+      31              :     description = "The favorite restaurant are not available at the moment",
+      32            0 :   }) : super(code, description);
+      33              : }
+      34              : 
+      35              : class AddFavoriteRestaurantException extends AppException {
+      36            0 :   AddFavoriteRestaurantException({
+      37              :     code = "ADD_FAVORITE_RESTAURANT_EXCEPTION",
+      38              :     description = "Couldn't register the favorite restaurant",
+      39            0 :   }) : super(code, description);
+      40              : }
+      41              : 
+      42              : class RemoveFavoriteRestaurantException extends AppException {
+      43            0 :   RemoveFavoriteRestaurantException({
+      44              :     code = "REMOVE_FAVORITE_RESTAURANT_EXCEPTION",
+      45              :     description = "Couldn't remove the favorite restaurant",
+      46            0 :   }) : super(code, description);
+      47              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/common/exceptions/index-sort-f.html b/coverage/report/common/exceptions/index-sort-f.html new file mode 100644 index 0000000..628c79b --- /dev/null +++ b/coverage/report/common/exceptions/index-sort-f.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - common/exceptions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - common/exceptionsCoverageTotalHit
Test:lcov.infoLines:0.0 %160
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
exceptions.dart +
0.0%
+
0.0 %16-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/common/exceptions/index-sort-l.html b/coverage/report/common/exceptions/index-sort-l.html new file mode 100644 index 0000000..c839c96 --- /dev/null +++ b/coverage/report/common/exceptions/index-sort-l.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - common/exceptions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - common/exceptionsCoverageTotalHit
Test:lcov.infoLines:0.0 %160
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
exceptions.dart +
0.0%
+
0.0 %16-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/common/exceptions/index.html b/coverage/report/common/exceptions/index.html new file mode 100644 index 0000000..c9945ed --- /dev/null +++ b/coverage/report/common/exceptions/index.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - common/exceptions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - common/exceptionsCoverageTotalHit
Test:lcov.infoLines:0.0 %160
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
exceptions.dart +
0.0%
+
0.0 %16-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/repository/index-sort-f.html b/coverage/report/domain/repository/index-sort-f.html new file mode 100644 index 0000000..581b6c0 --- /dev/null +++ b/coverage/report/domain/repository/index-sort-f.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - domain/repository + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/repositoryCoverageTotalHit
Test:lcov.infoLines:83.8 %3731
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurants_repository.dart +
83.8%83.8%
+
83.8 %3731-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/repository/index-sort-l.html b/coverage/report/domain/repository/index-sort-l.html new file mode 100644 index 0000000..35cb4b2 --- /dev/null +++ b/coverage/report/domain/repository/index-sort-l.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - domain/repository + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/repositoryCoverageTotalHit
Test:lcov.infoLines:83.8 %3731
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurants_repository.dart +
83.8%83.8%
+
83.8 %3731-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/repository/index.html b/coverage/report/domain/repository/index.html new file mode 100644 index 0000000..72ae3ca --- /dev/null +++ b/coverage/report/domain/repository/index.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - domain/repository + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/repositoryCoverageTotalHit
Test:lcov.infoLines:83.8 %3731
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurants_repository.dart +
83.8%83.8%
+
83.8 %3731-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/repository/restaurants_repository.dart.func-c.html b/coverage/report/domain/repository/restaurants_repository.dart.func-c.html new file mode 100644 index 0000000..b8d5f93 --- /dev/null +++ b/coverage/report/domain/repository/restaurants_repository.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - domain/repository/restaurants_repository.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/repository - restaurants_repository.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:83.8 %3731
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/repository/restaurants_repository.dart.func.html b/coverage/report/domain/repository/restaurants_repository.dart.func.html new file mode 100644 index 0000000..b0be46b --- /dev/null +++ b/coverage/report/domain/repository/restaurants_repository.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - domain/repository/restaurants_repository.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/repository - restaurants_repository.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:83.8 %3731
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/repository/restaurants_repository.dart.gcov.html b/coverage/report/domain/repository/restaurants_repository.dart.gcov.html new file mode 100644 index 0000000..af9a7db --- /dev/null +++ b/coverage/report/domain/repository/restaurants_repository.dart.gcov.html @@ -0,0 +1,155 @@ + + + + + + + LCOV - lcov.info - domain/repository/restaurants_repository.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/repository - restaurants_repository.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:83.8 %3731
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:restaurant_tour/domain/datasource/favorite_restaurants_datasource.dart';
+       2              : import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart';
+       3              : import 'package:restaurant_tour/models/restaurant.dart';
+       4              : 
+       5              : class RestaurantsRepository {
+       6            1 :   RestaurantsRepository(
+       7              :     this._restaurantsDatasource,
+       8              :     this._favoriteRestaurantsDatasource,
+       9              :   );
+      10              : 
+      11              :   final FavoriteRestaurantsDatasource _favoriteRestaurantsDatasource;
+      12              :   final RestaurantsDatasource _restaurantsDatasource;
+      13              :   final List<Restaurant> _restaurants = [];
+      14              :   final Set<Restaurant> _favorites = {};
+      15              : 
+      16            1 :   Future<List<Restaurant>> getRestaurants() async {
+      17            2 :     if (_restaurants.isEmpty) {
+      18            2 :       final remoteRestaurants = await _restaurantsDatasource.getRestaurants();
+      19              :       final favoritesRestaurants =
+      20            2 :           await _favoriteRestaurantsDatasource.getFavoritesRestaurants();
+      21            2 :       _restaurants.addAll(
+      22              :         remoteRestaurants
+      23            1 :             .map(
+      24            2 :               (e) => favoritesRestaurants.contains(e)
+      25            1 :                   ? e.copyWith(isFavorite: true)
+      26              :                   : e,
+      27              :             )
+      28            1 :             .toList(),
+      29              :       );
+      30              :     }
+      31            1 :     return _restaurants;
+      32              :   }
+      33              : 
+      34            0 :   Future<void> getMoreRestaurants(int offset, int limit) async {
+      35            0 :     final newRemoteRestaurants = await _restaurantsDatasource.getRestaurants(
+      36              :       offset: offset,
+      37              :       limit: limit,
+      38              :     );
+      39            0 :     _restaurants.addAll(
+      40              :       newRemoteRestaurants
+      41            0 :           .map(
+      42            0 :             (e) => _favorites.contains(e) ? e.copyWith(isFavorite: true) : e,
+      43              :           )
+      44            0 :           .toList(),
+      45              :     );
+      46              :   }
+      47              : 
+      48            1 :   Future<List<Restaurant>> getFavoriteRestaurants() async {
+      49            2 :     if (_favorites.isEmpty) {
+      50              :       final favoritesRestaurants =
+      51            2 :           await _favoriteRestaurantsDatasource.getFavoritesRestaurants();
+      52            2 :       _favorites.addAll(
+      53            3 :         favoritesRestaurants.map((e) => e.copyWith(isFavorite: true)),
+      54              :       );
+      55              :     }
+      56              : 
+      57            2 :     return _favorites.toList();
+      58              :   }
+      59              : 
+      60            1 :   Future<void> addFavoriteRestaurant(Restaurant restaurant) async {
+      61            3 :     _favorites.add(restaurant.copyWith(isFavorite: true));
+      62            4 :     final restaurantIndex = _restaurants.indexWhere((e) => e == restaurant);
+      63            2 :     if (restaurantIndex != -1) {
+      64            3 :       _restaurants[restaurantIndex] = restaurant.copyWith(isFavorite: true);
+      65            1 :       await _favoriteRestaurantsDatasource
+      66            2 :           .addFavoriteRestaurant(restaurant.copyWith(isFavorite: true));
+      67              :     }
+      68              :   }
+      69              : 
+      70            1 :   Future<void> removeFavoriteRestaurant(Restaurant restaurant) async {
+      71            4 :     final restaurantIndex = _restaurants.indexWhere((e) => e == restaurant);
+      72            2 :     if (restaurantIndex != -1) {
+      73            3 :       _restaurants[restaurantIndex] = restaurant.copyWith(isFavorite: false);
+      74            2 :       _favorites.remove(restaurant);
+      75            1 :       await _favoriteRestaurantsDatasource
+      76            2 :           .removeFavoriteRestaurant(restaurant.copyWith(isFavorite: false));
+      77              :     }
+      78              :   }
+      79              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/usecase/index-sort-f.html b/coverage/report/domain/usecase/index-sort-f.html new file mode 100644 index 0000000..8f72b56 --- /dev/null +++ b/coverage/report/domain/usecase/index-sort-f.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - domain/usecase + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/usecaseCoverageTotalHit
Test:lcov.infoLines:100.0 %1111
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurants_usecase.dart +
100.0%
+
100.0 %1111-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/usecase/index-sort-l.html b/coverage/report/domain/usecase/index-sort-l.html new file mode 100644 index 0000000..ecd8136 --- /dev/null +++ b/coverage/report/domain/usecase/index-sort-l.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - domain/usecase + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/usecaseCoverageTotalHit
Test:lcov.infoLines:100.0 %1111
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurants_usecase.dart +
100.0%
+
100.0 %1111-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/usecase/index.html b/coverage/report/domain/usecase/index.html new file mode 100644 index 0000000..d372152 --- /dev/null +++ b/coverage/report/domain/usecase/index.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - domain/usecase + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/usecaseCoverageTotalHit
Test:lcov.infoLines:100.0 %1111
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurants_usecase.dart +
100.0%
+
100.0 %1111-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/usecase/restaurants_usecase.dart.func-c.html b/coverage/report/domain/usecase/restaurants_usecase.dart.func-c.html new file mode 100644 index 0000000..a899fb2 --- /dev/null +++ b/coverage/report/domain/usecase/restaurants_usecase.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - domain/usecase/restaurants_usecase.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/usecase - restaurants_usecase.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1111
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/usecase/restaurants_usecase.dart.func.html b/coverage/report/domain/usecase/restaurants_usecase.dart.func.html new file mode 100644 index 0000000..d9a6ee2 --- /dev/null +++ b/coverage/report/domain/usecase/restaurants_usecase.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - domain/usecase/restaurants_usecase.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/usecase - restaurants_usecase.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1111
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/domain/usecase/restaurants_usecase.dart.gcov.html b/coverage/report/domain/usecase/restaurants_usecase.dart.gcov.html new file mode 100644 index 0000000..1149ec0 --- /dev/null +++ b/coverage/report/domain/usecase/restaurants_usecase.dart.gcov.html @@ -0,0 +1,103 @@ + + + + + + + LCOV - lcov.info - domain/usecase/restaurants_usecase.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - domain/usecase - restaurants_usecase.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1111
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:restaurant_tour/domain/repository/restaurants_repository.dart';
+       2              : import 'package:restaurant_tour/models/restaurant.dart';
+       3              : 
+       4              : class RestaurantsUsecase {
+       5            1 :   const RestaurantsUsecase(this._repository);
+       6              :   final RestaurantsRepository _repository;
+       7              : 
+       8            1 :   Future<List<Restaurant>> getRestaurants() {
+       9            2 :     return _repository.getRestaurants();
+      10              :   }
+      11              : 
+      12            1 :   Future<List<Restaurant>> getFavoriteRestaurants() {
+      13            2 :     return _repository.getFavoriteRestaurants();
+      14              :   }
+      15              : 
+      16            1 :   Future<void> addFavoriteRestaurant(Restaurant restaurant) {
+      17            2 :     return _repository.addFavoriteRestaurant(restaurant);
+      18              :   }
+      19              : 
+      20            1 :   Future<void> removeFavoriteRestaurant(Restaurant restaurant) {
+      21            2 :     return _repository.removeFavoriteRestaurant(restaurant);
+      22              :   }
+      23              : 
+      24            1 :   Future loadMoreRestaurants({required int offset, required int limit}) {
+      25            2 :     return _repository.getMoreRestaurants(offset, limit);
+      26              :   }
+      27              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/emerald.png b/coverage/report/emerald.png new file mode 100644 index 0000000000000000000000000000000000000000..38ad4f4068b935643d2486f323005fb294a9bd7e GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^Jb!lvI6;R0X`wF(yt=9xVZRt1vCRixIA4P dLn>}1Cji+@42)0J?}79&c)I$ztaD0e0sy@GAL0N2 literal 0 HcmV?d00001 diff --git a/coverage/report/gcov.css b/coverage/report/gcov.css new file mode 100644 index 0000000..6c23ba9 --- /dev/null +++ b/coverage/report/gcov.css @@ -0,0 +1,1101 @@ +/* All views: initial background and text color */ +body +{ + color: #000000; + background-color: #ffffff; +} + +/* All views: standard link format*/ +a:link +{ + color: #284fa8; + text-decoration: underline; +} + +/* All views: standard link - visited format */ +a:visited +{ + color: #00cb40; + text-decoration: underline; +} + +/* All views: standard link - activated format */ +a:active +{ + color: #ff0040; + text-decoration: underline; +} + +/* All views: main title format */ +td.title +{ + text-align: center; + padding-bottom: 10px; + font-family: sans-serif; + font-size: 20pt; + font-style: italic; + font-weight: bold; +} +/* "Line coverage date bins" leader */ +td.subTableHeader +{ + text-align: center; + padding-bottom: 6px; + font-family: sans-serif; + font-weight: bold; + vertical-align: center; +} + +/* All views: header item format */ +td.headerItem +{ + text-align: right; + padding-right: 6px; + font-family: sans-serif; + font-weight: bold; + vertical-align: top; + white-space: nowrap; +} + +/* All views: header item value format */ +td.headerValue +{ + text-align: left; + color: #284fa8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; +} + +/* All views: header item coverage table heading */ +td.headerCovTableHead +{ + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; +} + +/* All views: header item coverage table entry */ +td.headerCovTableEntry +{ + text-align: right; + color: #284fa8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #dae7fe; +} + +/* All views: header item coverage table entry for high coverage rate */ +td.headerCovTableEntryHi +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #a7fc9d; +} + +/* All views: header item coverage table entry for medium coverage rate */ +td.headerCovTableEntryMed +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #ffea20; +} + +/* All views: header item coverage table entry for ow coverage rate */ +td.headerCovTableEntryLo +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #ff0000; +} + +/* All views: header legend value for legend entry */ +td.headerValueLeg +{ + text-align: left; + color: #000000; + font-family: sans-serif; + font-size: 80%; + white-space: nowrap; + padding-top: 4px; +} + +/* All views: color of horizontal ruler */ +td.ruler +{ + background-color: #6688d4; +} + +/* All views: version string format */ +td.versionInfo +{ + text-align: center; + padding-top: 2px; + font-family: sans-serif; + font-style: italic; +} + +/* Directory view/File view (all)/Test case descriptions: + table headline format */ +td.tableHead +{ + text-align: center; + color: #ffffff; + background-color: #6688d4; + font-family: sans-serif; + font-size: 120%; + font-weight: bold; + white-space: nowrap; + padding-left: 4px; + padding-right: 4px; +} + +span.tableHeadSort +{ + padding-right: 4px; +} + +/* Directory view/File view (all): filename entry format */ +td.coverFile +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + background-color: #dae7fe; + font-family: monospace; +} + +/* Directory view/File view (all): directory name entry format */ +td.coverDirectory +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + background-color: #b8d0ff; + font-family: monospace; +} + +/* Directory view/File view (all): filename entry format */ +td.overallOwner +{ + text-align: center; + font-weight: bold; + font-family: sans-serif; + background-color: #dae7fe; + padding-right: 10px; + padding-left: 10px; +} + +/* Directory view/File view (all): filename entry format */ +td.ownerName +{ + text-align: right; + font-style: italic; + font-family: sans-serif; + background-color: #E5DBDB; + padding-right: 10px; + padding-left: 20px; +} + +/* Directory view/File view (all): bar-graph entry format*/ +td.coverBar +{ + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; +} + +/* Directory view/File view (all): bar-graph entry format*/ +td.owner_coverBar +{ + padding-left: 10px; + padding-right: 10px; + background-color: #E5DBDB; +} + +/* Directory view/File view (all): bar-graph outline color */ +td.coverBarOutline +{ + background-color: #000000; +} + +/* Directory view/File view (all): percentage entry for files with + high coverage rate */ +td.coverPerHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #a7fc9d; + font-weight: bold; + font-family: sans-serif; +} + +/* 'owner' entry: slightly lighter color than 'coverPerHi' */ +td.owner_coverPerHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #82E0AA; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry */ +td.coverNumDflt +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + white-space: nowrap; + font-family: sans-serif; +} + +/* td background color and font for the 'owner' section of the table */ +td.ownerTla +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #E5DBDB; + white-space: nowrap; + font-family: sans-serif; + font-style: italic; +} + +/* Directory view/File view (all): line count entry for files with + high coverage rate */ +td.coverNumHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #a7fc9d; + white-space: nowrap; + font-family: sans-serif; +} + +td.owner_coverNumHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #82E0AA; + white-space: nowrap; + font-family: sans-serif; +} + +/* Directory view/File view (all): percentage entry for files with + medium coverage rate */ +td.coverPerMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ffea20; + font-weight: bold; + font-family: sans-serif; +} + +td.owner_coverPerMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #F9E79F; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + medium coverage rate */ +td.coverNumMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ffea20; + white-space: nowrap; + font-family: sans-serif; +} + +td.owner_coverNumMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #F9E79F; + white-space: nowrap; + font-family: sans-serif; +} + +/* Directory view/File view (all): percentage entry for files with + low coverage rate */ +td.coverPerLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ff0000; + font-weight: bold; + font-family: sans-serif; +} + +td.owner_coverPerLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #EC7063; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + low coverage rate */ +td.coverNumLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ff0000; + white-space: nowrap; + font-family: sans-serif; +} + +td.owner_coverNumLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #EC7063; + white-space: nowrap; + font-family: sans-serif; +} + +/* File view (all): "show/hide details" link format */ +a.detail:link +{ + color: #b8d0ff; + font-size:80%; +} + +/* File view (all): "show/hide details" link - visited format */ +a.detail:visited +{ + color: #b8d0ff; + font-size:80%; +} + +/* File view (all): "show/hide details" link - activated format */ +a.detail:active +{ + color: #ffffff; + font-size:80%; +} + +/* File view (detail): test name entry */ +td.testName +{ + text-align: right; + padding-right: 10px; + background-color: #dae7fe; + font-family: sans-serif; +} + +/* File view (detail): test percentage entry */ +td.testPer +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-family: sans-serif; +} + +/* File view (detail): test lines count entry */ +td.testNum +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-family: sans-serif; +} + +/* Test case descriptions: test name format*/ +dt +{ + font-family: sans-serif; + font-weight: bold; +} + +/* Test case descriptions: description table body */ +td.testDescription +{ + padding-top: 10px; + padding-left: 30px; + padding-bottom: 10px; + padding-right: 30px; + background-color: #dae7fe; +} + +/* Source code view: function entry */ +td.coverFn +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + background-color: #dae7fe; + font-family: monospace; +} + +/* Source code view: function entry zero count*/ +td.coverFnLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ff0000; + font-weight: bold; + font-family: sans-serif; +} + +/* Source code view: function entry nonzero count*/ +td.coverFnHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-weight: bold; + font-family: sans-serif; +} + +td.coverFnAlias +{ + text-align: right; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + /* make this a slightly different color than the leader - otherwise, + otherwise the alias is hard to distinguish in the table */ + background-color: #E5DBDB; /* very light pale grey/blue */ + font-family: monospace; +} + +/* Source code view: function entry zero count*/ +td.coverFnAliasLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #EC7063; /* lighter red */ + font-family: sans-serif; +} + +/* Source code view: function entry nonzero count*/ +td.coverFnAliasHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-weight: bold; + font-family: sans-serif; +} + +/* Source code view: source code format */ +pre.source +{ + font-family: monospace; + white-space: pre; + margin-top: 2px; +} + +/* elided/removed code */ +span.elidedSource +{ + font-family: sans-serif; + /*font-size: 8pt; */ + font-style: italic; + background-color: lightgrey; +} + +/* Source code view: line number format */ +span.lineNum +{ + background-color: #efe383; +} + +/* Source code view: line number format when there are deleted + lines in the corresponding location */ +span.lineNumWithDelete +{ + foreground-color: #efe383; + background-color: lightgrey; +} + +/* Source code view: format for Cov legend */ +span.coverLegendCov +{ + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #cad7fe; +} + +/* Source code view: format for NoCov legend */ +span.coverLegendNoCov +{ + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #ff6230; +} + +/* Source code view: format for the source code heading line */ +pre.sourceHeading +{ + white-space: pre; + font-family: monospace; + font-weight: bold; + margin: 0px; +} + +/* All views: header legend value for low rate */ +td.headerValueLegL +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 4px; + padding-right: 2px; + background-color: #ff0000; + font-size: 80%; +} + +/* All views: header legend value for med rate */ +td.headerValueLegM +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 2px; + background-color: #ffea20; + font-size: 80%; +} + +/* All views: header legend value for hi rate */ +td.headerValueLegH +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 4px; + background-color: #a7fc9d; + font-size: 80%; +} + +/* All views except source code view: legend format for low coverage */ +span.coverLegendCovLo +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #ff0000; +} + +/* All views except source code view: legend format for med coverage */ +span.coverLegendCovMed +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #ffea20; +} + +/* All views except source code view: legend format for hi coverage */ +span.coverLegendCovHi +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #a7fc9d; +} + +a.branchTla:link +{ + color: #000000; +} + +a.branchTla:visited +{ + color: #000000; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): +Newly added code is not tested" */ +td.tlaUNC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgUNC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): +Newly added code is not tested" */ +span.tlaUNC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgUNC { + background-color: #FF6230; +} +a.tlaBgUNC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadUNC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): +Unchanged code is no longer tested" */ +td.tlaLBC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgLBC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): +Unchanged code is no longer tested" */ +span.tlaLBC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgLBC { + background-color: #FF6230; +} +a.tlaBgLBC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadLBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): +Previously unused code is untested" */ +td.tlaUIC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgUIC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): +Previously unused code is untested" */ +span.tlaUIC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgUIC { + background-color: #FF6230; +} +a.tlaBgUIC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadUIC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): +Unchanged code was untested before, is untested now" */ +td.tlaUBC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgUBC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): +Unchanged code was untested before, is untested now" */ +span.tlaUBC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgUBC { + background-color: #FF6230; +} +a.tlaBgUBC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadUBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): +Unchanged code is tested now" */ +td.tlaGBC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgGBC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): +Unchanged code is tested now" */ +span.tlaGBC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgGBC { + background-color: #CAD7FE; +} +a.tlaBgGBC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadGBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): +Previously unused code is tested now" */ +td.tlaGIC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgGIC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): +Previously unused code is tested now" */ +span.tlaGIC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgGIC { + background-color: #CAD7FE; +} +a.tlaBgGIC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadGIC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): +Newly added code is tested" */ +td.tlaGNC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgGNC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): +Newly added code is tested" */ +span.tlaGNC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgGNC { + background-color: #CAD7FE; +} +a.tlaBgGNC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadGNC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): +Unchanged code was tested before and is still tested" */ +td.tlaCBC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgCBC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): +Unchanged code was tested before and is still tested" */ +span.tlaCBC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgCBC { + background-color: #CAD7FE; +} +a.tlaBgCBC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadCBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): +Previously untested code is unused now" */ +td.tlaEUB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgEUB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): +Previously untested code is unused now" */ +span.tlaEUB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgEUB { + background-color: #FFFFFF; +} +a.tlaBgEUB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadEUB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): +Previously tested code is unused now" */ +td.tlaECB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgECB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): +Previously tested code is unused now" */ +span.tlaECB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgECB { + background-color: #FFFFFF; +} +a.tlaBgECB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadECB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): +Previously untested code has been deleted" */ +td.tlaDUB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgDUB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): +Previously untested code has been deleted" */ +span.tlaDUB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgDUB { + background-color: #FFFFFF; +} +a.tlaBgDUB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadDUB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): +Previously tested code has been deleted" */ +td.tlaDCB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgDCB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): +Previously tested code has been deleted" */ +span.tlaDCB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgDCB { + background-color: #FFFFFF; +} +a.tlaBgDCB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadDCB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view: format for date/owner bin that is not hit */ +span.missBins +{ + background-color: #ff0000 /* red */ +} diff --git a/coverage/report/glass.png b/coverage/report/glass.png new file mode 100644 index 0000000000000000000000000000000000000000..e1abc00680a3093c49fdb775ae6bdb6764c95af2 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)gaEa{HEjtmSN`?>!lvI6;R0X`wF z|Ns97GD8ntt^-nxB|(0{3=Yq3q=7g|-tI089jvk*Kn`btM`SSr1Gf+eGhVt|_XjA* zUgGKN%6^Gmn4d%Ph(nkFP>9RZ#WAE}PI3Z}&BVayv3^M*kj3EX>gTe~DWM4f=_Dpv literal 0 HcmV?d00001 diff --git a/coverage/report/index-sort-f.html b/coverage/report/index-sort-f.html new file mode 100644 index 0000000..c22cf48 --- /dev/null +++ b/coverage/report/index-sort-f.html @@ -0,0 +1,189 @@ + + + + + + + LCOV - lcov.info + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelCoverageTotalHit
Test:lcov.infoLines:43.8 %495217
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
common/exceptions/ +
0.0%
+
0.0 %16-
domain/repository/ +
83.8%83.8%
+
83.8 %3731-
domain/usecase/ +
100.0%
+
100.0 %1111-
models/ +
17.4%17.4%
+
17.4 %12121-
presentation/favorites/ +
2.4%2.4%
+
2.4 %411-
presentation/restaurants/ +
2.6%2.6%
+
2.6 %381-
view/pages/ +
0.0%
+
0.0 %37-
view/widgets/ +
78.4%78.4%
+
78.4 %194152-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/index-sort-l.html b/coverage/report/index-sort-l.html new file mode 100644 index 0000000..c28804c --- /dev/null +++ b/coverage/report/index-sort-l.html @@ -0,0 +1,189 @@ + + + + + + + LCOV - lcov.info + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelCoverageTotalHit
Test:lcov.infoLines:43.8 %495217
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
common/exceptions/ +
0.0%
+
0.0 %16-
view/pages/ +
0.0%
+
0.0 %37-
presentation/favorites/ +
2.4%2.4%
+
2.4 %411-
presentation/restaurants/ +
2.6%2.6%
+
2.6 %381-
models/ +
17.4%17.4%
+
17.4 %12121-
view/widgets/ +
78.4%78.4%
+
78.4 %194152-
domain/repository/ +
83.8%83.8%
+
83.8 %3731-
domain/usecase/ +
100.0%
+
100.0 %1111-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/index.html b/coverage/report/index.html new file mode 100644 index 0000000..17e9bc3 --- /dev/null +++ b/coverage/report/index.html @@ -0,0 +1,189 @@ + + + + + + + LCOV - lcov.info + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelCoverageTotalHit
Test:lcov.infoLines:43.8 %495217
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
common/exceptions/ +
0.0%
+
0.0 %16-
domain/repository/ +
83.8%83.8%
+
83.8 %3731-
domain/usecase/ +
100.0%
+
100.0 %1111-
models/ +
17.4%17.4%
+
17.4 %12121-
presentation/favorites/ +
2.4%2.4%
+
2.4 %411-
presentation/restaurants/ +
2.6%2.6%
+
2.6 %381-
view/pages/ +
0.0%
+
0.0 %37-
view/widgets/ +
78.4%78.4%
+
78.4 %194152-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/models/index-sort-f.html b/coverage/report/models/index-sort-f.html new file mode 100644 index 0000000..e8801d3 --- /dev/null +++ b/coverage/report/models/index-sort-f.html @@ -0,0 +1,117 @@ + + + + + + + LCOV - lcov.info - models + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - modelsCoverageTotalHit
Test:lcov.infoLines:17.4 %12121
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurant.dart +
43.8%43.8%
+
43.8 %4821-
restaurant.g.dart +
0.0%
+
0.0 %73-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/models/index-sort-l.html b/coverage/report/models/index-sort-l.html new file mode 100644 index 0000000..b220af2 --- /dev/null +++ b/coverage/report/models/index-sort-l.html @@ -0,0 +1,117 @@ + + + + + + + LCOV - lcov.info - models + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - modelsCoverageTotalHit
Test:lcov.infoLines:17.4 %12121
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurant.g.dart +
0.0%
+
0.0 %73-
restaurant.dart +
43.8%43.8%
+
43.8 %4821-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/models/index.html b/coverage/report/models/index.html new file mode 100644 index 0000000..80e09e8 --- /dev/null +++ b/coverage/report/models/index.html @@ -0,0 +1,117 @@ + + + + + + + LCOV - lcov.info - models + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - modelsCoverageTotalHit
Test:lcov.infoLines:17.4 %12121
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurant.dart +
43.8%43.8%
+
43.8 %4821-
restaurant.g.dart +
0.0%
+
0.0 %73-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/models/restaurant.dart.func-c.html b/coverage/report/models/restaurant.dart.func-c.html new file mode 100644 index 0000000..02c30fa --- /dev/null +++ b/coverage/report/models/restaurant.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - models/restaurant.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - models - restaurant.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:43.8 %4821
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/models/restaurant.dart.func.html b/coverage/report/models/restaurant.dart.func.html new file mode 100644 index 0000000..fa27c71 --- /dev/null +++ b/coverage/report/models/restaurant.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - models/restaurant.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - models - restaurant.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:43.8 %4821
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/models/restaurant.dart.gcov.html b/coverage/report/models/restaurant.dart.gcov.html new file mode 100644 index 0000000..d1df434 --- /dev/null +++ b/coverage/report/models/restaurant.dart.gcov.html @@ -0,0 +1,265 @@ + + + + + + + LCOV - lcov.info - models/restaurant.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - models - restaurant.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:43.8 %4821
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:equatable/equatable.dart';
+       2              : import 'package:json_annotation/json_annotation.dart';
+       3              : 
+       4              : part 'restaurant.g.dart';
+       5              : 
+       6              : @JsonSerializable()
+       7              : class Category {
+       8              :   final String? alias;
+       9              :   final String? title;
+      10              : 
+      11            1 :   Category({
+      12              :     this.alias,
+      13              :     this.title,
+      14              :   });
+      15              : 
+      16            0 :   factory Category.fromJson(Map<String, dynamic> json) =>
+      17            0 :       _$CategoryFromJson(json);
+      18              : 
+      19            0 :   Map<String, dynamic> toJson() => _$CategoryToJson(this);
+      20              : }
+      21              : 
+      22              : @JsonSerializable()
+      23              : class Hours {
+      24              :   @JsonKey(name: 'is_open_now')
+      25              :   final bool? isOpenNow;
+      26              : 
+      27            1 :   const Hours({
+      28              :     this.isOpenNow,
+      29              :   });
+      30              : 
+      31            0 :   factory Hours.fromJson(Map<String, dynamic> json) => _$HoursFromJson(json);
+      32              : 
+      33            0 :   Map<String, dynamic> toJson() => _$HoursToJson(this);
+      34              : }
+      35              : 
+      36              : @JsonSerializable()
+      37              : class User {
+      38              :   final String? id;
+      39              :   @JsonKey(name: 'image_url')
+      40              :   final String? imageUrl;
+      41              :   final String? name;
+      42              : 
+      43            1 :   const User({
+      44              :     this.id,
+      45              :     this.imageUrl,
+      46              :     this.name,
+      47              :   });
+      48              : 
+      49            0 :   factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
+      50              : 
+      51            0 :   Map<String, dynamic> toJson() => _$UserToJson(this);
+      52              : }
+      53              : 
+      54              : @JsonSerializable()
+      55              : class Review {
+      56              :   final String? id;
+      57              :   final int? rating;
+      58              :   final String? text;
+      59              :   final User? user;
+      60              : 
+      61            1 :   const Review({
+      62              :     this.id,
+      63              :     this.rating,
+      64              :     this.user,
+      65              :     this.text,
+      66              :   });
+      67              : 
+      68            0 :   factory Review.fromJson(Map<String, dynamic> json) => _$ReviewFromJson(json);
+      69              : 
+      70            0 :   Map<String, dynamic> toJson() => _$ReviewToJson(this);
+      71              : }
+      72              : 
+      73              : @JsonSerializable()
+      74              : class Location {
+      75              :   @JsonKey(name: 'formatted_address')
+      76              :   final String? formattedAddress;
+      77              : 
+      78            0 :   Location({
+      79              :     this.formattedAddress,
+      80              :   });
+      81              : 
+      82            0 :   factory Location.fromJson(Map<String, dynamic> json) =>
+      83            0 :       _$LocationFromJson(json);
+      84              : 
+      85            0 :   Map<String, dynamic> toJson() => _$LocationToJson(this);
+      86              : }
+      87              : 
+      88              : @JsonSerializable()
+      89              : class Restaurant extends Equatable {
+      90              :   final String? id;
+      91              :   final String? name;
+      92              :   final String? price;
+      93              :   final double? rating;
+      94              :   final List<String>? photos;
+      95              :   final List<Category>? categories;
+      96              :   final List<Hours>? hours;
+      97              :   final List<Review>? reviews;
+      98              :   final Location? location;
+      99              :   final bool? isFavorite;
+     100              : 
+     101            6 :   const Restaurant({
+     102              :     this.id,
+     103              :     this.name,
+     104              :     this.price,
+     105              :     this.rating,
+     106              :     this.photos,
+     107              :     this.categories,
+     108              :     this.hours,
+     109              :     this.reviews,
+     110              :     this.location,
+     111              :     this.isFavorite,
+     112              :   });
+     113              : 
+     114            1 :   Restaurant copyWith({
+     115              :     String? id,
+     116              :     String? name,
+     117              :     String? price,
+     118              :     double? rating,
+     119              :     List<String>? photos,
+     120              :     List<Category>? categories,
+     121              :     List<Hours>? hours,
+     122              :     List<Review>? reviews,
+     123              :     Location? location,
+     124              :     bool? isFavorite,
+     125              :   }) {
+     126            1 :     return Restaurant(
+     127            1 :       id: id ?? this.id,
+     128            1 :       name: name ?? this.name,
+     129            1 :       price: price ?? this.price,
+     130            1 :       rating: rating ?? this.rating,
+     131            1 :       photos: photos ?? this.photos,
+     132            1 :       categories: categories ?? this.categories,
+     133            1 :       hours: hours ?? this.hours,
+     134            1 :       reviews: reviews ?? this.reviews,
+     135            1 :       location: location ?? this.location,
+     136            0 :       isFavorite: isFavorite ?? this.isFavorite,
+     137              :     );
+     138              :   }
+     139              : 
+     140            0 :   factory Restaurant.fromJson(Map<String, dynamic> json) =>
+     141            0 :       _$RestaurantFromJson(json);
+     142              : 
+     143            0 :   Map<String, dynamic> toJson() => _$RestaurantToJson(this);
+     144              : 
+     145              :   /// Use the first category for the category shown to the user
+     146            0 :   String get displayCategory {
+     147            0 :     if (categories != null && categories!.isNotEmpty) {
+     148            0 :       return categories!.first.title ?? '';
+     149              :     }
+     150              :     return '';
+     151              :   }
+     152              : 
+     153              :   /// Use the first image as the image shown to the user
+     154            0 :   String get heroImage {
+     155            0 :     if (photos != null && photos!.isNotEmpty) {
+     156            0 :       return photos!.first;
+     157              :     }
+     158              :     return '';
+     159              :   }
+     160              : 
+     161              :   /// This logic is probably not correct in all cases but it is ok
+     162              :   /// for this application
+     163            3 :   bool get isOpen {
+     164            5 :     if (hours != null && hours!.isNotEmpty) {
+     165            3 :       return hours!.first.isOpenNow ?? false;
+     166              :     }
+     167              :     return false;
+     168              :   }
+     169              : 
+     170            1 :   @override
+     171            2 :   List<Object?> get props => [id];
+     172              : }
+     173              : 
+     174              : @JsonSerializable()
+     175              : class RestaurantQueryResult {
+     176              :   final int? total;
+     177              :   @JsonKey(name: 'business')
+     178              :   final List<Restaurant>? restaurants;
+     179              : 
+     180            0 :   const RestaurantQueryResult({
+     181              :     this.total,
+     182              :     this.restaurants,
+     183              :   });
+     184              : 
+     185            0 :   factory RestaurantQueryResult.fromJson(Map<String, dynamic> json) =>
+     186            0 :       _$RestaurantQueryResultFromJson(json);
+     187              : 
+     188            0 :   Map<String, dynamic> toJson() => _$RestaurantQueryResultToJson(this);
+     189              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/models/restaurant.g.dart.func-c.html b/coverage/report/models/restaurant.g.dart.func-c.html new file mode 100644 index 0000000..8d8a077 --- /dev/null +++ b/coverage/report/models/restaurant.g.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - models/restaurant.g.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - models - restaurant.g.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %730
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/models/restaurant.g.dart.func.html b/coverage/report/models/restaurant.g.dart.func.html new file mode 100644 index 0000000..5a36319 --- /dev/null +++ b/coverage/report/models/restaurant.g.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - models/restaurant.g.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - models - restaurant.g.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %730
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/models/restaurant.g.dart.gcov.html b/coverage/report/models/restaurant.g.dart.gcov.html new file mode 100644 index 0000000..f51d9b3 --- /dev/null +++ b/coverage/report/models/restaurant.g.dart.gcov.html @@ -0,0 +1,189 @@ + + + + + + + LCOV - lcov.info - models/restaurant.g.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - models - restaurant.g.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %730
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : // GENERATED CODE - DO NOT MODIFY BY HAND
+       2              : 
+       3              : part of 'restaurant.dart';
+       4              : 
+       5              : // **************************************************************************
+       6              : // JsonSerializableGenerator
+       7              : // **************************************************************************
+       8              : 
+       9            0 : Category _$CategoryFromJson(Map<String, dynamic> json) => Category(
+      10            0 :       alias: json['alias'] as String?,
+      11            0 :       title: json['title'] as String?,
+      12              :     );
+      13              : 
+      14            0 : Map<String, dynamic> _$CategoryToJson(Category instance) => <String, dynamic>{
+      15            0 :       'alias': instance.alias,
+      16            0 :       'title': instance.title,
+      17              :     };
+      18              : 
+      19            0 : Hours _$HoursFromJson(Map<String, dynamic> json) => Hours(
+      20            0 :       isOpenNow: json['is_open_now'] as bool?,
+      21              :     );
+      22              : 
+      23            0 : Map<String, dynamic> _$HoursToJson(Hours instance) => <String, dynamic>{
+      24            0 :       'is_open_now': instance.isOpenNow,
+      25              :     };
+      26              : 
+      27            0 : User _$UserFromJson(Map<String, dynamic> json) => User(
+      28            0 :       id: json['id'] as String?,
+      29            0 :       imageUrl: json['image_url'] as String?,
+      30            0 :       name: json['name'] as String?,
+      31              :     );
+      32              : 
+      33            0 : Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
+      34            0 :       'id': instance.id,
+      35            0 :       'image_url': instance.imageUrl,
+      36            0 :       'name': instance.name,
+      37              :     };
+      38              : 
+      39            0 : Review _$ReviewFromJson(Map<String, dynamic> json) => Review(
+      40            0 :       id: json['id'] as String?,
+      41            0 :       rating: (json['rating'] as num?)?.toInt(),
+      42            0 :       user: json['user'] == null
+      43              :           ? null
+      44            0 :           : User.fromJson(json['user'] as Map<String, dynamic>),
+      45            0 :       text: json['text'] as String?,
+      46              :     );
+      47              : 
+      48            0 : Map<String, dynamic> _$ReviewToJson(Review instance) => <String, dynamic>{
+      49            0 :       'id': instance.id,
+      50            0 :       'rating': instance.rating,
+      51            0 :       'text': instance.text,
+      52            0 :       'user': instance.user,
+      53              :     };
+      54              : 
+      55            0 : Location _$LocationFromJson(Map<String, dynamic> json) => Location(
+      56            0 :       formattedAddress: json['formatted_address'] as String?,
+      57              :     );
+      58              : 
+      59            0 : Map<String, dynamic> _$LocationToJson(Location instance) => <String, dynamic>{
+      60            0 :       'formatted_address': instance.formattedAddress,
+      61              :     };
+      62              : 
+      63            0 : Restaurant _$RestaurantFromJson(Map<String, dynamic> json) => Restaurant(
+      64            0 :       id: json['id'] as String?,
+      65            0 :       name: json['name'] as String?,
+      66            0 :       price: json['price'] as String?,
+      67            0 :       rating: (json['rating'] as num?)?.toDouble(),
+      68              :       photos:
+      69            0 :           (json['photos'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      70            0 :       categories: (json['categories'] as List<dynamic>?)
+      71            0 :           ?.map((e) => Category.fromJson(e as Map<String, dynamic>))
+      72            0 :           .toList(),
+      73            0 :       hours: (json['hours'] as List<dynamic>?)
+      74            0 :           ?.map((e) => Hours.fromJson(e as Map<String, dynamic>))
+      75            0 :           .toList(),
+      76            0 :       reviews: (json['reviews'] as List<dynamic>?)
+      77            0 :           ?.map((e) => Review.fromJson(e as Map<String, dynamic>))
+      78            0 :           .toList(),
+      79            0 :       location: json['location'] == null
+      80              :           ? null
+      81            0 :           : Location.fromJson(json['location'] as Map<String, dynamic>),
+      82            0 :       isFavorite: json['isFavorite'] as bool?,
+      83              :     );
+      84              : 
+      85            0 : Map<String, dynamic> _$RestaurantToJson(Restaurant instance) =>
+      86            0 :     <String, dynamic>{
+      87            0 :       'id': instance.id,
+      88            0 :       'name': instance.name,
+      89            0 :       'price': instance.price,
+      90            0 :       'rating': instance.rating,
+      91            0 :       'photos': instance.photos,
+      92            0 :       'categories': instance.categories,
+      93            0 :       'hours': instance.hours,
+      94            0 :       'reviews': instance.reviews,
+      95            0 :       'location': instance.location,
+      96            0 :       'isFavorite': instance.isFavorite,
+      97              :     };
+      98              : 
+      99            0 : RestaurantQueryResult _$RestaurantQueryResultFromJson(
+     100              :         Map<String, dynamic> json) =>
+     101            0 :     RestaurantQueryResult(
+     102            0 :       total: (json['total'] as num?)?.toInt(),
+     103            0 :       restaurants: (json['business'] as List<dynamic>?)
+     104            0 :           ?.map((e) => Restaurant.fromJson(e as Map<String, dynamic>))
+     105            0 :           .toList(),
+     106              :     );
+     107              : 
+     108            0 : Map<String, dynamic> _$RestaurantQueryResultToJson(
+     109              :         RestaurantQueryResult instance) =>
+     110            0 :     <String, dynamic>{
+     111            0 :       'total': instance.total,
+     112            0 :       'business': instance.restaurants,
+     113              :     };
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.func-c.html b/coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.func-c.html new file mode 100644 index 0000000..944c347 --- /dev/null +++ b/coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/favorites/favorites_restaurants_bloc.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favorites - favorites_restaurants_bloc.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %350
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.func.html b/coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.func.html new file mode 100644 index 0000000..dfa99d9 --- /dev/null +++ b/coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/favorites/favorites_restaurants_bloc.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favorites - favorites_restaurants_bloc.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %350
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.gcov.html b/coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.gcov.html new file mode 100644 index 0000000..f4268b3 --- /dev/null +++ b/coverage/report/presentation/favorites/favorites_restaurants_bloc.dart.gcov.html @@ -0,0 +1,154 @@ + + + + + + + LCOV - lcov.info - presentation/favorites/favorites_restaurants_bloc.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favorites - favorites_restaurants_bloc.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %350
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:bloc/bloc.dart';
+       2              : import 'package:bloc_concurrency/bloc_concurrency.dart';
+       3              : import 'package:flutter/material.dart';
+       4              : // ignore: depend_on_referenced_packages, unnecessary_import
+       5              : import 'package:meta/meta.dart';
+       6              : import 'package:restaurant_tour/common/exceptions/exceptions.dart';
+       7              : import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart';
+       8              : import 'package:restaurant_tour/models/restaurant.dart';
+       9              : 
+      10              : part 'favorites_restaurants_event.dart';
+      11              : part 'favorites_restaurants_state.dart';
+      12              : 
+      13              : class FavoritesRestaurantsBloc
+      14              :     extends Bloc<FavoritesRestaurantsEvent, FavoritesRestaurantsState> {
+      15            0 :   FavoritesRestaurantsBloc(this.usecase)
+      16            0 :       : super(FavoritesRestaurantsInitial()) {
+      17            0 :     on<LoadFavoritesRestaurants>(
+      18            0 :       (event, emit) async {
+      19              :         try {
+      20            0 :           emit(FavoritesRestaurantsLoading());
+      21            0 :           final favorites = await usecase.getFavoriteRestaurants();
+      22            0 :           emit(FavoritesRestaurantsReady(favorites));
+      23              :         } catch (e) {
+      24            0 :           emit(
+      25            0 :             FavoriteRestaurantsListError(FavoritesRestaurantsListException()),
+      26              :           );
+      27              :         }
+      28              :       },
+      29            0 :       transformer: sequential(),
+      30              :     );
+      31            0 :     on<AddFavoriteRestaurant>(
+      32            0 :       (event, emit) async {
+      33              :         try {
+      34            0 :           while (state is FavoritesRestaurantsLoading) {
+      35            0 :             Future.delayed(const Duration(milliseconds: 500));
+      36              :           }
+      37            0 :           emit(FavoritesRestaurantsLoading());
+      38            0 :           await usecase.addFavoriteRestaurant(event.restaurant);
+      39            0 :           final favoritesRestaurants = await usecase.getFavoriteRestaurants();
+      40            0 :           emit(FavoritesRestaurantsReady(favoritesRestaurants));
+      41              :         } catch (e) {
+      42            0 :           debugPrint(e.toString());
+      43            0 :           emit(AddFavoriteRestaurantsError(AddFavoriteRestaurantException()));
+      44              :         }
+      45              :       },
+      46            0 :       transformer: sequential(),
+      47              :     );
+      48              : 
+      49            0 :     on<RemoveFavoriteRestaurant>(
+      50            0 :       (event, emit) async {
+      51              :         try {
+      52            0 :           while (state is FavoritesRestaurantsLoading) {
+      53            0 :             Future.delayed(const Duration(milliseconds: 500));
+      54              :           }
+      55            0 :           emit(FavoritesRestaurantsLoading());
+      56            0 :           await usecase.removeFavoriteRestaurant(event.restaurant);
+      57            0 :           final favoritesRestaurants = await usecase.getFavoriteRestaurants();
+      58            0 :           emit(FavoritesRestaurantsReady(favoritesRestaurants));
+      59              :         } catch (e) {
+      60            0 :           emit(
+      61            0 :             RemoveFavoriteRestaurantsError(
+      62            0 :               RemoveFavoriteRestaurantException(),
+      63              :             ),
+      64              :           );
+      65              :         }
+      66              :       },
+      67            0 :       transformer: sequential(),
+      68              :     );
+      69              :   }
+      70              :   final RestaurantsUsecase usecase;
+      71              : 
+      72            0 :   @override
+      73              :   void onChange(Change<FavoritesRestaurantsState> change) {
+      74              :     // debugPrint(
+      75              :     //     change.currentState.toString() + " - " + change.nextState.toString());
+      76            0 :     super.onChange(change);
+      77              :   }
+      78              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/favorites_restaurants_event.dart.func-c.html b/coverage/report/presentation/favorites/favorites_restaurants_event.dart.func-c.html new file mode 100644 index 0000000..7fdb847 --- /dev/null +++ b/coverage/report/presentation/favorites/favorites_restaurants_event.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/favorites/favorites_restaurants_event.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favorites - favorites_restaurants_event.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %20
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/favorites_restaurants_event.dart.func.html b/coverage/report/presentation/favorites/favorites_restaurants_event.dart.func.html new file mode 100644 index 0000000..d698840 --- /dev/null +++ b/coverage/report/presentation/favorites/favorites_restaurants_event.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/favorites/favorites_restaurants_event.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favorites - favorites_restaurants_event.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %20
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/favorites_restaurants_event.dart.gcov.html b/coverage/report/presentation/favorites/favorites_restaurants_event.dart.gcov.html new file mode 100644 index 0000000..3983061 --- /dev/null +++ b/coverage/report/presentation/favorites/favorites_restaurants_event.dart.gcov.html @@ -0,0 +1,92 @@ + + + + + + + LCOV - lcov.info - presentation/favorites/favorites_restaurants_event.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favorites - favorites_restaurants_event.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %20
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : part of 'favorites_restaurants_bloc.dart';
+       2              : 
+       3              : @immutable
+       4              : sealed class FavoritesRestaurantsEvent {}
+       5              : 
+       6              : class LoadFavoritesRestaurants extends FavoritesRestaurantsEvent {}
+       7              : 
+       8              : class AddFavoriteRestaurant extends FavoritesRestaurantsEvent {
+       9            0 :   AddFavoriteRestaurant(this.restaurant);
+      10              :   final Restaurant restaurant;
+      11              : }
+      12              : 
+      13              : class RemoveFavoriteRestaurant extends FavoritesRestaurantsEvent {
+      14            0 :   RemoveFavoriteRestaurant(this.restaurant);
+      15              :   final Restaurant restaurant;
+      16              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/favorites_restaurants_state.dart.func-c.html b/coverage/report/presentation/favorites/favorites_restaurants_state.dart.func-c.html new file mode 100644 index 0000000..d4c12c6 --- /dev/null +++ b/coverage/report/presentation/favorites/favorites_restaurants_state.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/favorites/favorites_restaurants_state.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favorites - favorites_restaurants_state.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:25.0 %41
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/favorites_restaurants_state.dart.func.html b/coverage/report/presentation/favorites/favorites_restaurants_state.dart.func.html new file mode 100644 index 0000000..be02594 --- /dev/null +++ b/coverage/report/presentation/favorites/favorites_restaurants_state.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/favorites/favorites_restaurants_state.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favorites - favorites_restaurants_state.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:25.0 %41
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/favorites_restaurants_state.dart.gcov.html b/coverage/report/presentation/favorites/favorites_restaurants_state.dart.gcov.html new file mode 100644 index 0000000..e8d8766 --- /dev/null +++ b/coverage/report/presentation/favorites/favorites_restaurants_state.dart.gcov.html @@ -0,0 +1,104 @@ + + + + + + + LCOV - lcov.info - presentation/favorites/favorites_restaurants_state.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favorites - favorites_restaurants_state.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:25.0 %41
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : part of 'favorites_restaurants_bloc.dart';
+       2              : 
+       3              : @immutable
+       4              : sealed class FavoritesRestaurantsState {}
+       5              : 
+       6              : final class FavoritesRestaurantsInitial extends FavoritesRestaurantsState {}
+       7              : 
+       8              : final class FavoritesRestaurantsLoading extends FavoritesRestaurantsState {}
+       9              : 
+      10              : final class FavoriteRestaurantsListError extends FavoritesRestaurantsState {
+      11            0 :   FavoriteRestaurantsListError(this.exception);
+      12              :   final FavoritesRestaurantsListException exception;
+      13              : }
+      14              : 
+      15              : final class AddFavoriteRestaurantsError extends FavoritesRestaurantsState {
+      16            0 :   AddFavoriteRestaurantsError(this.exception);
+      17              :   final AddFavoriteRestaurantException exception;
+      18              : }
+      19              : 
+      20              : final class RemoveFavoriteRestaurantsError extends FavoritesRestaurantsState {
+      21            0 :   RemoveFavoriteRestaurantsError(this.exception);
+      22              :   final RemoveFavoriteRestaurantException exception;
+      23              : }
+      24              : 
+      25              : final class FavoritesRestaurantsReady extends FavoritesRestaurantsState {
+      26            1 :   FavoritesRestaurantsReady(this.favoritesRestaurants);
+      27              :   final List<Restaurant> favoritesRestaurants;
+      28              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/index-sort-f.html b/coverage/report/presentation/favorites/index-sort-f.html new file mode 100644 index 0000000..bf620c0 --- /dev/null +++ b/coverage/report/presentation/favorites/index-sort-f.html @@ -0,0 +1,129 @@ + + + + + + + LCOV - lcov.info - presentation/favorites + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favoritesCoverageTotalHit
Test:lcov.infoLines:2.4 %411
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
favorites_restaurants_bloc.dart +
0.0%
+
0.0 %35-
favorites_restaurants_event.dart +
0.0%
+
0.0 %2-
favorites_restaurants_state.dart +
25.0%25.0%
+
25.0 %41-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/index-sort-l.html b/coverage/report/presentation/favorites/index-sort-l.html new file mode 100644 index 0000000..fccf352 --- /dev/null +++ b/coverage/report/presentation/favorites/index-sort-l.html @@ -0,0 +1,129 @@ + + + + + + + LCOV - lcov.info - presentation/favorites + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favoritesCoverageTotalHit
Test:lcov.infoLines:2.4 %411
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
favorites_restaurants_event.dart +
0.0%
+
0.0 %2-
favorites_restaurants_bloc.dart +
0.0%
+
0.0 %35-
favorites_restaurants_state.dart +
25.0%25.0%
+
25.0 %41-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/favorites/index.html b/coverage/report/presentation/favorites/index.html new file mode 100644 index 0000000..46fbfc6 --- /dev/null +++ b/coverage/report/presentation/favorites/index.html @@ -0,0 +1,129 @@ + + + + + + + LCOV - lcov.info - presentation/favorites + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/favoritesCoverageTotalHit
Test:lcov.infoLines:2.4 %411
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
favorites_restaurants_bloc.dart +
0.0%
+
0.0 %35-
favorites_restaurants_event.dart +
0.0%
+
0.0 %2-
favorites_restaurants_state.dart +
25.0%25.0%
+
25.0 %41-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/index-sort-f.html b/coverage/report/presentation/restaurants/index-sort-f.html new file mode 100644 index 0000000..1ce5e1f --- /dev/null +++ b/coverage/report/presentation/restaurants/index-sort-f.html @@ -0,0 +1,129 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurantsCoverageTotalHit
Test:lcov.infoLines:2.6 %381
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurants_bloc.dart +
0.0%
+
0.0 %28-
restaurants_event.dart +
0.0%
+
0.0 %1-
restaurants_state.dart +
11.1%11.1%
+
11.1 %91-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/index-sort-l.html b/coverage/report/presentation/restaurants/index-sort-l.html new file mode 100644 index 0000000..ec3d77b --- /dev/null +++ b/coverage/report/presentation/restaurants/index-sort-l.html @@ -0,0 +1,129 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurantsCoverageTotalHit
Test:lcov.infoLines:2.6 %381
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurants_event.dart +
0.0%
+
0.0 %1-
restaurants_bloc.dart +
0.0%
+
0.0 %28-
restaurants_state.dart +
11.1%11.1%
+
11.1 %91-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/index.html b/coverage/report/presentation/restaurants/index.html new file mode 100644 index 0000000..02dae2e --- /dev/null +++ b/coverage/report/presentation/restaurants/index.html @@ -0,0 +1,129 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurantsCoverageTotalHit
Test:lcov.infoLines:2.6 %381
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurants_bloc.dart +
0.0%
+
0.0 %28-
restaurants_event.dart +
0.0%
+
0.0 %1-
restaurants_state.dart +
11.1%11.1%
+
11.1 %91-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/restaurants_bloc.dart.func-c.html b/coverage/report/presentation/restaurants/restaurants_bloc.dart.func-c.html new file mode 100644 index 0000000..ba83a31 --- /dev/null +++ b/coverage/report/presentation/restaurants/restaurants_bloc.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants/restaurants_bloc.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurants - restaurants_bloc.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %280
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/restaurants_bloc.dart.func.html b/coverage/report/presentation/restaurants/restaurants_bloc.dart.func.html new file mode 100644 index 0000000..01ec8d6 --- /dev/null +++ b/coverage/report/presentation/restaurants/restaurants_bloc.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants/restaurants_bloc.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurants - restaurants_bloc.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %280
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/restaurants_bloc.dart.gcov.html b/coverage/report/presentation/restaurants/restaurants_bloc.dart.gcov.html new file mode 100644 index 0000000..7adde54 --- /dev/null +++ b/coverage/report/presentation/restaurants/restaurants_bloc.dart.gcov.html @@ -0,0 +1,145 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants/restaurants_bloc.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurants - restaurants_bloc.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %280
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:bloc/bloc.dart';
+       2              : import 'package:bloc_concurrency/bloc_concurrency.dart';
+       3              : import 'package:flutter/material.dart';
+       4              : // ignore: depend_on_referenced_packages, unnecessary_import
+       5              : import 'package:meta/meta.dart';
+       6              : import 'package:restaurant_tour/common/exceptions/exceptions.dart';
+       7              : import 'package:restaurant_tour/domain/usecase/restaurants_usecase.dart';
+       8              : import 'package:restaurant_tour/models/restaurant.dart';
+       9              : 
+      10              : part 'restaurants_event.dart';
+      11              : part 'restaurants_state.dart';
+      12              : 
+      13              : class RestaurantsBloc extends Bloc<RestaurantsEvent, RestaurantsState> {
+      14            0 :   RestaurantsBloc(this.usecase) : super(RestaurantsInitial()) {
+      15            0 :     on<LoadRestaurants>(
+      16            0 :       (event, emit) async {
+      17            0 :         emit(RestaurantsLoading());
+      18              :         try {
+      19            0 :           final restaurants = await usecase.getRestaurants();
+      20            0 :           emit(
+      21            0 :             RestaurantsReady(
+      22              :               restaurants,
+      23            0 :               size: restaurants.length,
+      24              :             ),
+      25              :           );
+      26            0 :         } on RestaurantListException catch (e) {
+      27            0 :           emit(RestaurantsError(e));
+      28              :         } catch (e) {
+      29            0 :           emit(RestaurantsError(RestaurantListException()));
+      30              :         }
+      31              :       },
+      32            0 :       transformer: sequential(),
+      33              :     );
+      34            0 :     on<AddMoreRestaurants>(
+      35            0 :       (event, emit) async {
+      36              :         try {
+      37            0 :           if (state is RestaurantsReady) {
+      38            0 :             emit((state as RestaurantsReady).copyWith(isLoadingMore: true));
+      39            0 :             await usecase.loadMoreRestaurants(
+      40            0 :               offset: event.offset,
+      41            0 :               limit: event.limit,
+      42              :             );
+      43            0 :             final restaurants = await usecase.getRestaurants();
+      44              : 
+      45            0 :             emit(
+      46            0 :               (state as RestaurantsReady).copyWith(
+      47              :                 isLoadingMore: false,
+      48              :                 restaurants: restaurants,
+      49            0 :                 size: restaurants.length,
+      50              :               ),
+      51              :             );
+      52              :           }
+      53              :         } catch (e) {
+      54            0 :           emit(
+      55            0 :             (state as RestaurantsReady)
+      56            0 :                 .copyWith(isLoadingMore: false, hasError: true),
+      57              :           );
+      58              :         }
+      59              :       },
+      60              :     );
+      61              :   }
+      62              :   final RestaurantsUsecase usecase;
+      63            0 :   @override
+      64              :   void onChange(Change<RestaurantsState> change) {
+      65              :     // debugPrint(
+      66              :     //     change.currentState.toString() + " - " + change.nextState.toString());
+      67            0 :     super.onChange(change);
+      68              :   }
+      69              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/restaurants_event.dart.func-c.html b/coverage/report/presentation/restaurants/restaurants_event.dart.func-c.html new file mode 100644 index 0000000..199545f --- /dev/null +++ b/coverage/report/presentation/restaurants/restaurants_event.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants/restaurants_event.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurants - restaurants_event.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %10
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/restaurants_event.dart.func.html b/coverage/report/presentation/restaurants/restaurants_event.dart.func.html new file mode 100644 index 0000000..c3b9252 --- /dev/null +++ b/coverage/report/presentation/restaurants/restaurants_event.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants/restaurants_event.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurants - restaurants_event.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %10
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/restaurants_event.dart.gcov.html b/coverage/report/presentation/restaurants/restaurants_event.dart.gcov.html new file mode 100644 index 0000000..d25d71d --- /dev/null +++ b/coverage/report/presentation/restaurants/restaurants_event.dart.gcov.html @@ -0,0 +1,88 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants/restaurants_event.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurants - restaurants_event.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %10
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : part of 'restaurants_bloc.dart';
+       2              : 
+       3              : @immutable
+       4              : sealed class RestaurantsEvent {}
+       5              : 
+       6              : class LoadRestaurants extends RestaurantsEvent {}
+       7              : 
+       8              : class AddMoreRestaurants extends RestaurantsEvent {
+       9            0 :   AddMoreRestaurants({required this.limit, required this.offset});
+      10              :   final int limit;
+      11              :   final int offset;
+      12              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/restaurants_state.dart.func-c.html b/coverage/report/presentation/restaurants/restaurants_state.dart.func-c.html new file mode 100644 index 0000000..5383a1d --- /dev/null +++ b/coverage/report/presentation/restaurants/restaurants_state.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants/restaurants_state.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurants - restaurants_state.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:11.1 %91
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/restaurants_state.dart.func.html b/coverage/report/presentation/restaurants/restaurants_state.dart.func.html new file mode 100644 index 0000000..a4365e6 --- /dev/null +++ b/coverage/report/presentation/restaurants/restaurants_state.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants/restaurants_state.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurants - restaurants_state.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:11.1 %91
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/presentation/restaurants/restaurants_state.dart.gcov.html b/coverage/report/presentation/restaurants/restaurants_state.dart.gcov.html new file mode 100644 index 0000000..e093596 --- /dev/null +++ b/coverage/report/presentation/restaurants/restaurants_state.dart.gcov.html @@ -0,0 +1,119 @@ + + + + + + + LCOV - lcov.info - presentation/restaurants/restaurants_state.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - presentation/restaurants - restaurants_state.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:11.1 %91
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : part of 'restaurants_bloc.dart';
+       2              : 
+       3              : @immutable
+       4              : sealed class RestaurantsState {}
+       5              : 
+       6              : final class RestaurantsInitial extends RestaurantsState {}
+       7              : 
+       8              : final class RestaurantsLoading extends RestaurantsState {}
+       9              : 
+      10              : final class RestaurantsReady extends RestaurantsState {
+      11            1 :   RestaurantsReady(
+      12              :     this.restaurants, {
+      13              :     this.isLoadingMore = false,
+      14              :     this.size = 10,
+      15              :     this.limit = 10,
+      16              :     this.hasError = false,
+      17              :   });
+      18              :   final List<Restaurant> restaurants;
+      19              :   final bool isLoadingMore;
+      20              :   final bool hasError;
+      21              :   final int size;
+      22              :   final int limit;
+      23            0 :   RestaurantsReady copyWith({
+      24              :     List<Restaurant>? restaurants,
+      25              :     bool? isLoadingMore,
+      26              :     int? size,
+      27              :     int? limit,
+      28              :     bool? hasError,
+      29              :   }) {
+      30            0 :     return RestaurantsReady(
+      31            0 :       restaurants ?? this.restaurants,
+      32            0 :       isLoadingMore: isLoadingMore ?? this.isLoadingMore,
+      33            0 :       size: size ?? this.size,
+      34            0 :       hasError: hasError ?? this.hasError,
+      35            0 :       limit: limit ?? this.limit,
+      36              :     );
+      37              :   }
+      38              : }
+      39              : 
+      40              : final class RestaurantsError extends RestaurantsState {
+      41            0 :   RestaurantsError(this.exception);
+      42              :   final RestaurantListException exception;
+      43              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/ruby.png b/coverage/report/ruby.png new file mode 100644 index 0000000000000000000000000000000000000000..991b6d4ec9e78be165e3ef757eed1aada287364d GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^FceV#7`HfI^%F z9+AZi4BSE>%y{W;-5;PJOS+@4BLl<6e(pbstUx|nfKQ0)e^Y%R^MdiLxj>4`)5S5Q b;#P73kj=!v_*DHKNFRfztDnm{r-UW|iOwIS literal 0 HcmV?d00001 diff --git a/coverage/report/snow.png b/coverage/report/snow.png new file mode 100644 index 0000000000000000000000000000000000000000..2cdae107fceec6e7f02ac7acb4a34a82a540caa5 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^MM!lvI6;R0X`wF|Ns97GD8ntt^-nBo-U3d c6}OTTfNUlP#;5A{K>8RwUHx3vIVCg!071?oo&W#< literal 0 HcmV?d00001 diff --git a/coverage/report/updown.png b/coverage/report/updown.png new file mode 100644 index 0000000000000000000000000000000000000000..aa56a238b3e6c435265250f9266cd1b8caba0f20 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^AT}Qd8;}%R+`Ae`*?77*hG?8mPH5^{)z4*}Q$iB}huR`+ literal 0 HcmV?d00001 diff --git a/coverage/report/view/pages/index-sort-f.html b/coverage/report/view/pages/index-sort-f.html new file mode 100644 index 0000000..207a439 --- /dev/null +++ b/coverage/report/view/pages/index-sort-f.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - view/pages + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/pagesCoverageTotalHit
Test:lcov.infoLines:0.0 %370
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurant_page.dart +
0.0%
+
0.0 %37-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/pages/index-sort-l.html b/coverage/report/view/pages/index-sort-l.html new file mode 100644 index 0000000..f43bf8e --- /dev/null +++ b/coverage/report/view/pages/index-sort-l.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - view/pages + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/pagesCoverageTotalHit
Test:lcov.infoLines:0.0 %370
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurant_page.dart +
0.0%
+
0.0 %37-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/pages/index.html b/coverage/report/view/pages/index.html new file mode 100644 index 0000000..77a82e4 --- /dev/null +++ b/coverage/report/view/pages/index.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - view/pages + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/pagesCoverageTotalHit
Test:lcov.infoLines:0.0 %370
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
restaurant_page.dart +
0.0%
+
0.0 %37-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/pages/restaurant_page.dart.func-c.html b/coverage/report/view/pages/restaurant_page.dart.func-c.html new file mode 100644 index 0000000..1138856 --- /dev/null +++ b/coverage/report/view/pages/restaurant_page.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/pages/restaurant_page.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/pages - restaurant_page.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %370
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/pages/restaurant_page.dart.func.html b/coverage/report/view/pages/restaurant_page.dart.func.html new file mode 100644 index 0000000..c1b71b7 --- /dev/null +++ b/coverage/report/view/pages/restaurant_page.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/pages/restaurant_page.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/pages - restaurant_page.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %370
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/pages/restaurant_page.dart.gcov.html b/coverage/report/view/pages/restaurant_page.dart.gcov.html new file mode 100644 index 0000000..4f269e6 --- /dev/null +++ b/coverage/report/view/pages/restaurant_page.dart.gcov.html @@ -0,0 +1,170 @@ + + + + + + + LCOV - lcov.info - view/pages/restaurant_page.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/pages - restaurant_page.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %370
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : import 'package:gap/gap.dart';
+       3              : import 'package:get_it/get_it.dart';
+       4              : import 'package:restaurant_tour/models/restaurant.dart';
+       5              : import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart';
+       6              : import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart';
+       7              : import 'package:restaurant_tour/view/widgets/address_widget.dart';
+       8              : import 'package:restaurant_tour/view/widgets/favorite_button_widget.dart';
+       9              : import 'package:restaurant_tour/view/widgets/overall_rating_widget.dart';
+      10              : import 'package:restaurant_tour/view/widgets/restaurant_category_price_widget.dart';
+      11              : import 'package:restaurant_tour/view/widgets/restaurant_hero_widget.dart';
+      12              : import 'package:restaurant_tour/view/widgets/restaurant_open_widget.dart';
+      13              : import 'package:restaurant_tour/view/widgets/review_list_widget.dart';
+      14              : 
+      15              : class RestaurantPage extends StatefulWidget {
+      16            0 :   const RestaurantPage({
+      17              :     super.key,
+      18              :     required this.restaurant,
+      19              :   });
+      20              :   final Restaurant restaurant;
+      21              : 
+      22            0 :   @override
+      23            0 :   State<RestaurantPage> createState() => _RestaurantPageState();
+      24              : }
+      25              : 
+      26              : class _RestaurantPageState extends State<RestaurantPage> {
+      27              :   late bool isFavoriteState = false;
+      28            0 :   @override
+      29              :   void initState() {
+      30            0 :     isFavoriteState = widget.restaurant.isFavorite ?? false;
+      31            0 :     super.initState();
+      32              :   }
+      33              : 
+      34            0 :   @override
+      35              :   Widget build(BuildContext context) {
+      36            0 :     return Scaffold(
+      37            0 :       appBar: AppBar(
+      38            0 :         title: Text(widget.restaurant.name ?? 'Restaurant Name'),
+      39            0 :         actions: [
+      40            0 :           FavoriteButtonWidget(
+      41            0 :             callback: (isFavorite) {
+      42            0 :               GetIt.I.get<FavoritesRestaurantsBloc>().add(
+      43              :                     (isFavorite)
+      44            0 :                         ? AddFavoriteRestaurant(widget.restaurant)
+      45            0 :                         : RemoveFavoriteRestaurant(widget.restaurant),
+      46              :                   );
+      47              : 
+      48            0 :               GetIt.I.get<RestaurantsBloc>().add(LoadRestaurants());
+      49              :             },
+      50            0 :             isFavorite: widget.restaurant.isFavorite,
+      51              :           ),
+      52              :         ],
+      53              :       ),
+      54            0 :       body: SingleChildScrollView(
+      55            0 :         child: Column(
+      56            0 :           children: [
+      57            0 :             RestaurantHeroWidget(
+      58            0 :               imageUrl: widget.restaurant.photos?.first,
+      59            0 :               tag: widget.restaurant.id,
+      60              :             ),
+      61            0 :             Padding(
+      62              :               padding:
+      63              :                   const EdgeInsets.only(left: 24.0, top: 24.0, right: 24.0),
+      64            0 :               child: Column(
+      65              :                 crossAxisAlignment: CrossAxisAlignment.start,
+      66            0 :                 children: [
+      67            0 :                   Row(
+      68              :                     mainAxisAlignment: MainAxisAlignment.spaceBetween,
+      69            0 :                     children: [
+      70            0 :                       RestaurantCategoryPriceWidget(
+      71            0 :                         price: widget.restaurant.price,
+      72            0 :                         category: widget.restaurant.categories?.first.title,
+      73              :                       ),
+      74            0 :                       RestaurantOpenWidget(widget.restaurant.isOpen),
+      75              :                     ],
+      76              :                   ),
+      77              :                   const Gap(16),
+      78              :                   const Divider(),
+      79            0 :                   AddressWidget(
+      80            0 :                     address: widget.restaurant.location?.formattedAddress,
+      81              :                   ),
+      82              :                   const Divider(),
+      83            0 :                   OverallRatingWidget(rating: widget.restaurant.rating),
+      84              :                   const Divider(),
+      85            0 :                   ReviewListWidget(reviews: widget.restaurant.reviews),
+      86              :                 ],
+      87              :               ),
+      88              :             ),
+      89              :           ],
+      90              :         ),
+      91              :       ),
+      92              :     );
+      93              :   }
+      94              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/address_widget.dart.func-c.html b/coverage/report/view/widgets/address_widget.dart.func-c.html new file mode 100644 index 0000000..6e67f69 --- /dev/null +++ b/coverage/report/view/widgets/address_widget.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/address_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - address_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1010
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/address_widget.dart.func.html b/coverage/report/view/widgets/address_widget.dart.func.html new file mode 100644 index 0000000..592b237 --- /dev/null +++ b/coverage/report/view/widgets/address_widget.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/address_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - address_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1010
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/address_widget.dart.gcov.html b/coverage/report/view/widgets/address_widget.dart.gcov.html new file mode 100644 index 0000000..f584d60 --- /dev/null +++ b/coverage/report/view/widgets/address_widget.dart.gcov.html @@ -0,0 +1,102 @@ + + + + + + + LCOV - lcov.info - view/widgets/address_widget.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - address_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1010
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : import 'package:gap/gap.dart';
+       3              : 
+       4              : class AddressWidget extends StatelessWidget {
+       5            1 :   const AddressWidget({required this.address, super.key});
+       6              :   final String? address;
+       7            1 :   @override
+       8              :   Widget build(BuildContext context) {
+       9            1 :     return Column(
+      10              :       crossAxisAlignment: CrossAxisAlignment.start,
+      11            1 :       children: [
+      12              :         const Gap(16),
+      13              :         const Text('Address'),
+      14              :         const Gap(16),
+      15            1 :         Text(
+      16            1 :           address ?? 'Address info',
+      17            1 :           style: Theme.of(context)
+      18            1 :               .textTheme
+      19            1 :               .titleLarge!
+      20            1 :               .copyWith(fontFamily: 'OpenSans', fontWeight: FontWeight.w600),
+      21              :         ),
+      22              :         const Gap(16),
+      23              :       ],
+      24              :     );
+      25              :   }
+      26              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/favorite_button_widget.dart.func-c.html b/coverage/report/view/widgets/favorite_button_widget.dart.func-c.html new file mode 100644 index 0000000..38a2c1e --- /dev/null +++ b/coverage/report/view/widgets/favorite_button_widget.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/favorite_button_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - favorite_button_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1313
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/favorite_button_widget.dart.func.html b/coverage/report/view/widgets/favorite_button_widget.dart.func.html new file mode 100644 index 0000000..8525604 --- /dev/null +++ b/coverage/report/view/widgets/favorite_button_widget.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/favorite_button_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - favorite_button_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1313
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/favorite_button_widget.dart.gcov.html b/coverage/report/view/widgets/favorite_button_widget.dart.gcov.html new file mode 100644 index 0000000..4471e97 --- /dev/null +++ b/coverage/report/view/widgets/favorite_button_widget.dart.gcov.html @@ -0,0 +1,114 @@ + + + + + + + LCOV - lcov.info - view/widgets/favorite_button_widget.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - favorite_button_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1313
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : 
+       3              : class FavoriteButtonWidget extends StatefulWidget {
+       4            1 :   const FavoriteButtonWidget({
+       5              :     required this.callback,
+       6              :     required this.isFavorite,
+       7              :     super.key,
+       8              :   });
+       9              :   final Function(bool) callback;
+      10              :   final bool? isFavorite;
+      11              : 
+      12            1 :   @override
+      13            1 :   State<FavoriteButtonWidget> createState() => _FavoriteButtonWidgetState();
+      14              : }
+      15              : 
+      16              : class _FavoriteButtonWidgetState extends State<FavoriteButtonWidget> {
+      17              :   bool isFavoriteState = false;
+      18            1 :   @override
+      19              :   void initState() {
+      20            3 :     isFavoriteState = widget.isFavorite ?? false;
+      21            1 :     super.initState();
+      22              :   }
+      23              : 
+      24            1 :   @override
+      25              :   Widget build(BuildContext context) {
+      26            1 :     return IconButton(
+      27            1 :       onPressed: () {
+      28            2 :         setState(() {
+      29            2 :           isFavoriteState = !isFavoriteState;
+      30              :         });
+      31            4 :         widget.callback(isFavoriteState);
+      32              :       },
+      33            1 :       icon: (isFavoriteState)
+      34              :           ? const Icon(Icons.favorite)
+      35              :           : const Icon(Icons.favorite_outline),
+      36              :     );
+      37              :   }
+      38              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/favorite_list_widget.dart.func-c.html b/coverage/report/view/widgets/favorite_list_widget.dart.func-c.html new file mode 100644 index 0000000..69cbf8f --- /dev/null +++ b/coverage/report/view/widgets/favorite_list_widget.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/favorite_list_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - favorite_list_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:75.0 %2418
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/favorite_list_widget.dart.func.html b/coverage/report/view/widgets/favorite_list_widget.dart.func.html new file mode 100644 index 0000000..b1ec987 --- /dev/null +++ b/coverage/report/view/widgets/favorite_list_widget.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/favorite_list_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - favorite_list_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:75.0 %2418
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/favorite_list_widget.dart.gcov.html b/coverage/report/view/widgets/favorite_list_widget.dart.gcov.html new file mode 100644 index 0000000..d204a6a --- /dev/null +++ b/coverage/report/view/widgets/favorite_list_widget.dart.gcov.html @@ -0,0 +1,134 @@ + + + + + + + LCOV - lcov.info - view/widgets/favorite_list_widget.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - favorite_list_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:75.0 %2418
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : import 'package:flutter_bloc/flutter_bloc.dart';
+       3              : import 'package:get_it/get_it.dart';
+       4              : import 'package:restaurant_tour/presentation/favorites/favorites_restaurants_bloc.dart';
+       5              : import 'package:restaurant_tour/view/pages/restaurant_page.dart';
+       6              : import 'package:restaurant_tour/view/widgets/restaurant_tile.dart';
+       7              : 
+       8              : class FavoriteListWidget extends StatefulWidget {
+       9            1 :   const FavoriteListWidget({super.key});
+      10              : 
+      11            1 :   @override
+      12            1 :   State<FavoriteListWidget> createState() => _FavoriteListWidgetState();
+      13              : }
+      14              : 
+      15              : class _FavoriteListWidgetState extends State<FavoriteListWidget> {
+      16            1 :   @override
+      17              :   Widget build(BuildContext context) {
+      18            1 :     return BlocBuilder<FavoritesRestaurantsBloc, FavoritesRestaurantsState>(
+      19            2 :       bloc: GetIt.I.get<FavoritesRestaurantsBloc>(),
+      20            1 :       builder: (context, state) {
+      21            1 :         if (state is FavoritesRestaurantsReady) {
+      22            1 :           return ListView.separated(
+      23            1 :             separatorBuilder: (context, index) => const SizedBox(
+      24              :               height: 2,
+      25              :             ),
+      26            2 :             itemCount: state.favoritesRestaurants.length,
+      27            1 :             itemBuilder: (context, index) {
+      28            1 :               return GestureDetector(
+      29            0 :                 onTap: () => Navigator.of(context).push(
+      30            0 :                   MaterialPageRoute<RestaurantPage>(
+      31            0 :                     builder: (context) => RestaurantPage(
+      32            0 :                       restaurant: state.favoritesRestaurants[index],
+      33              :                     ),
+      34              :                   ),
+      35              :                 ),
+      36            1 :                 child: RestaurantTile(
+      37            2 :                   restaurant: state.favoritesRestaurants[index],
+      38              :                 ),
+      39              :               );
+      40              :             },
+      41              :           );
+      42              :         }
+      43            1 :         if (state is FavoriteRestaurantsListError) {
+      44            0 :           return Center(
+      45            0 :             child: Text(state.exception.description),
+      46              :           );
+      47              :         }
+      48              : 
+      49            1 :         if (state is FavoritesRestaurantsLoading) {
+      50              :           return const Center(
+      51              :             child: CircularProgressIndicator(),
+      52              :           );
+      53              :         }
+      54            1 :         return Container();
+      55              :       },
+      56              :     );
+      57              :   }
+      58              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/index-sort-f.html b/coverage/report/view/widgets/index-sort-f.html new file mode 100644 index 0000000..e78866f --- /dev/null +++ b/coverage/report/view/widgets/index-sort-f.html @@ -0,0 +1,237 @@ + + + + + + + LCOV - lcov.info - view/widgets + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgetsCoverageTotalHit
Test:lcov.infoLines:78.4 %194152
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
address_widget.dart +
100.0%
+
100.0 %1010-
favorite_button_widget.dart +
100.0%
+
100.0 %1313-
favorite_list_widget.dart +
75.0%75.0%
+
75.0 %2418-
overall_rating_widget.dart +
0.0%
+
0.0 %9-
restaurant_category_price_widget.dart +
100.0%
+
100.0 %1010-
restaurant_hero_widget.dart +
85.7%85.7%
+
85.7 %76-
restaurant_list_widget.dart +
67.6%67.6%
+
67.6 %3725-
restaurant_open_widget.dart +
100.0%
+
100.0 %1313-
restaurant_star_rating_widget.dart +
100.0%
+
100.0 %77-
restaurant_tile.dart +
97.2%97.2%
+
97.2 %3635-
review_list_widget.dart +
0.0%
+
0.0 %13-
review_tile.dart +
100.0%
+
100.0 %1515-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/index-sort-l.html b/coverage/report/view/widgets/index-sort-l.html new file mode 100644 index 0000000..ebac973 --- /dev/null +++ b/coverage/report/view/widgets/index-sort-l.html @@ -0,0 +1,237 @@ + + + + + + + LCOV - lcov.info - view/widgets + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgetsCoverageTotalHit
Test:lcov.infoLines:78.4 %194152
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
overall_rating_widget.dart +
0.0%
+
0.0 %9-
review_list_widget.dart +
0.0%
+
0.0 %13-
restaurant_list_widget.dart +
67.6%67.6%
+
67.6 %3725-
favorite_list_widget.dart +
75.0%75.0%
+
75.0 %2418-
restaurant_hero_widget.dart +
85.7%85.7%
+
85.7 %76-
restaurant_tile.dart +
97.2%97.2%
+
97.2 %3635-
restaurant_star_rating_widget.dart +
100.0%
+
100.0 %77-
address_widget.dart +
100.0%
+
100.0 %1010-
restaurant_category_price_widget.dart +
100.0%
+
100.0 %1010-
favorite_button_widget.dart +
100.0%
+
100.0 %1313-
restaurant_open_widget.dart +
100.0%
+
100.0 %1313-
review_tile.dart +
100.0%
+
100.0 %1515-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/index.html b/coverage/report/view/widgets/index.html new file mode 100644 index 0000000..05421e6 --- /dev/null +++ b/coverage/report/view/widgets/index.html @@ -0,0 +1,237 @@ + + + + + + + LCOV - lcov.info - view/widgets + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgetsCoverageTotalHit
Test:lcov.infoLines:78.4 %194152
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
address_widget.dart +
100.0%
+
100.0 %1010-
favorite_button_widget.dart +
100.0%
+
100.0 %1313-
favorite_list_widget.dart +
75.0%75.0%
+
75.0 %2418-
overall_rating_widget.dart +
0.0%
+
0.0 %9-
restaurant_category_price_widget.dart +
100.0%
+
100.0 %1010-
restaurant_hero_widget.dart +
85.7%85.7%
+
85.7 %76-
restaurant_list_widget.dart +
67.6%67.6%
+
67.6 %3725-
restaurant_open_widget.dart +
100.0%
+
100.0 %1313-
restaurant_star_rating_widget.dart +
100.0%
+
100.0 %77-
restaurant_tile.dart +
97.2%97.2%
+
97.2 %3635-
review_list_widget.dart +
0.0%
+
0.0 %13-
review_tile.dart +
100.0%
+
100.0 %1515-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/overall_rating_widget.dart.func-c.html b/coverage/report/view/widgets/overall_rating_widget.dart.func-c.html new file mode 100644 index 0000000..6cbb92e --- /dev/null +++ b/coverage/report/view/widgets/overall_rating_widget.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/overall_rating_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - overall_rating_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %90
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/overall_rating_widget.dart.func.html b/coverage/report/view/widgets/overall_rating_widget.dart.func.html new file mode 100644 index 0000000..9187a36 --- /dev/null +++ b/coverage/report/view/widgets/overall_rating_widget.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/overall_rating_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - overall_rating_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %90
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/overall_rating_widget.dart.gcov.html b/coverage/report/view/widgets/overall_rating_widget.dart.gcov.html new file mode 100644 index 0000000..9289558 --- /dev/null +++ b/coverage/report/view/widgets/overall_rating_widget.dart.gcov.html @@ -0,0 +1,111 @@ + + + + + + + LCOV - lcov.info - view/widgets/overall_rating_widget.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - overall_rating_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %90
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : import 'package:gap/gap.dart';
+       3              : import 'package:restaurant_tour/view/widgets/restaurant_star_rating_widget.dart';
+       4              : 
+       5              : class OverallRatingWidget extends StatelessWidget {
+       6            0 :   const OverallRatingWidget({required this.rating, super.key});
+       7              :   final double? rating;
+       8            0 :   @override
+       9              :   Widget build(BuildContext context) {
+      10            0 :     return Column(
+      11              :       crossAxisAlignment: CrossAxisAlignment.start,
+      12            0 :       children: [
+      13              :         const Gap(16),
+      14              :         const Text('Overall Rating'),
+      15              :         const Gap(16),
+      16            0 :         Row(
+      17            0 :           children: [
+      18            0 :             Text(
+      19            0 :               rating?.toString() ?? '0',
+      20            0 :               style: Theme.of(context).textTheme.headlineLarge!.copyWith(
+      21              :                     fontFamily: 'Lora',
+      22              :                     fontWeight: FontWeight.bold,
+      23              :                   ),
+      24              :             ),
+      25              :             const Padding(
+      26              :               padding: EdgeInsets.only(left: 2, top: 16.0),
+      27              :               child: RestaurantStarRatingWidget(1),
+      28              :             ),
+      29              :           ],
+      30              :         ),
+      31              :         const Gap(16),
+      32              :       ],
+      33              :     );
+      34              :   }
+      35              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_category_price_widget.dart.func-c.html b/coverage/report/view/widgets/restaurant_category_price_widget.dart.func-c.html new file mode 100644 index 0000000..d93e854 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_category_price_widget.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_category_price_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_category_price_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1010
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_category_price_widget.dart.func.html b/coverage/report/view/widgets/restaurant_category_price_widget.dart.func.html new file mode 100644 index 0000000..ae14663 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_category_price_widget.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_category_price_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_category_price_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1010
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_category_price_widget.dart.gcov.html b/coverage/report/view/widgets/restaurant_category_price_widget.dart.gcov.html new file mode 100644 index 0000000..2583746 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_category_price_widget.dart.gcov.html @@ -0,0 +1,104 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_category_price_widget.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_category_price_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1010
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : import 'package:gap/gap.dart';
+       3              : 
+       4              : class RestaurantCategoryPriceWidget extends StatelessWidget {
+       5            1 :   const RestaurantCategoryPriceWidget({
+       6              :     required this.price,
+       7              :     required this.category,
+       8              :     super.key,
+       9              :   });
+      10              :   final String? price;
+      11              :   final String? category;
+      12            1 :   @override
+      13              :   Widget build(BuildContext context) {
+      14            1 :     return Row(
+      15            1 :       children: [
+      16            1 :         Text(
+      17            1 :           price ?? "\$\$",
+      18            3 :           style: Theme.of(context).textTheme.bodyMedium,
+      19              :         ),
+      20              :         const Gap(4),
+      21            1 :         Text(
+      22            1 :           category ?? "",
+      23            3 :           style: Theme.of(context).textTheme.bodyMedium,
+      24              :         ),
+      25              :       ],
+      26              :     );
+      27              :   }
+      28              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_hero_widget.dart.func-c.html b/coverage/report/view/widgets/restaurant_hero_widget.dart.func-c.html new file mode 100644 index 0000000..5133f87 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_hero_widget.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_hero_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_hero_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:85.7 %76
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_hero_widget.dart.func.html b/coverage/report/view/widgets/restaurant_hero_widget.dart.func.html new file mode 100644 index 0000000..be21443 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_hero_widget.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_hero_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_hero_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:85.7 %76
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_hero_widget.dart.gcov.html b/coverage/report/view/widgets/restaurant_hero_widget.dart.gcov.html new file mode 100644 index 0000000..13f4914 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_hero_widget.dart.gcov.html @@ -0,0 +1,100 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_hero_widget.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_hero_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:85.7 %76
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : 
+       3              : class RestaurantHeroWidget extends StatelessWidget {
+       4            1 :   const RestaurantHeroWidget({
+       5              :     required this.imageUrl,
+       6              :     required this.tag,
+       7              :     super.key,
+       8              :   });
+       9              :   final String? tag;
+      10              :   final String? imageUrl;
+      11              : 
+      12            1 :   @override
+      13              :   Widget build(BuildContext context) {
+      14            1 :     return Hero(
+      15            1 :       tag: tag ?? 'restaurant_id',
+      16            1 :       child: Image.network(
+      17            0 :         errorBuilder: (context, error, stackTrace) =>
+      18              :             const Center(child: Icon(Icons.error)),
+      19            1 :         imageUrl ?? 'https://picsum.photos/375/361',
+      20              :         fit: BoxFit.fitWidth,
+      21              :       ),
+      22              :     );
+      23              :   }
+      24              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_list_widget.dart.func-c.html b/coverage/report/view/widgets/restaurant_list_widget.dart.func-c.html new file mode 100644 index 0000000..79f43b8 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_list_widget.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_list_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_list_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:67.6 %3725
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_list_widget.dart.func.html b/coverage/report/view/widgets/restaurant_list_widget.dart.func.html new file mode 100644 index 0000000..1d158dc --- /dev/null +++ b/coverage/report/view/widgets/restaurant_list_widget.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_list_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_list_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:67.6 %3725
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_list_widget.dart.gcov.html b/coverage/report/view/widgets/restaurant_list_widget.dart.gcov.html new file mode 100644 index 0000000..f135ca3 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_list_widget.dart.gcov.html @@ -0,0 +1,173 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_list_widget.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_list_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:67.6 %3725
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : import 'package:flutter_bloc/flutter_bloc.dart';
+       3              : import 'package:get_it/get_it.dart';
+       4              : import 'package:restaurant_tour/presentation/restaurants/restaurants_bloc.dart';
+       5              : import 'package:restaurant_tour/view/pages/restaurant_page.dart';
+       6              : import 'package:restaurant_tour/view/widgets/restaurant_tile.dart';
+       7              : 
+       8              : class RestaurantListWidget extends StatefulWidget {
+       9            1 :   const RestaurantListWidget({super.key});
+      10              : 
+      11            1 :   @override
+      12            1 :   State<RestaurantListWidget> createState() => _RestaurantListWidgetState();
+      13              : }
+      14              : 
+      15              : class _RestaurantListWidgetState extends State<RestaurantListWidget> {
+      16              :   final _scrollController = ScrollController();
+      17            1 :   @override
+      18              :   void initState() {
+      19            1 :     super.initState();
+      20              :   }
+      21              : 
+      22            0 :   void addMore() {
+      23              :     final blocState =
+      24            0 :         (GetIt.I.get<RestaurantsBloc>().state) as RestaurantsReady;
+      25            0 :     GetIt.I.get<RestaurantsBloc>().add(
+      26            0 :           AddMoreRestaurants(offset: blocState.size, limit: blocState.limit),
+      27              :         );
+      28              :   }
+      29              : 
+      30            1 :   @override
+      31              :   Widget build(BuildContext context) {
+      32            1 :     return BlocBuilder<RestaurantsBloc, RestaurantsState>(
+      33            2 :       bloc: GetIt.I.get<RestaurantsBloc>(),
+      34            1 :       builder: (context, state) {
+      35            1 :         if (state is RestaurantsReady) {
+      36            1 :           return ListView.separated(
+      37            1 :             separatorBuilder: (context, index) => const SizedBox(
+      38              :               height: 2,
+      39              :             ),
+      40            3 :             itemCount: state.restaurants.length + 1,
+      41            1 :             controller: _scrollController,
+      42            1 :             itemBuilder: (context, index) {
+      43            4 :               if (index == state.restaurants.length && state.isLoadingMore) {
+      44              :                 return const Padding(
+      45              :                   padding: EdgeInsets.all(16.0),
+      46              :                   child: Center(
+      47              :                     child: CircularProgressIndicator(),
+      48              :                   ),
+      49              :                 );
+      50              :               }
+      51              : 
+      52            4 :               if (index == state.restaurants.length && state.hasError) {
+      53            0 :                 return ElevatedButton(
+      54            0 :                   onPressed: addMore,
+      55              :                   child: const Text(
+      56              :                     'Error trying to fetch more, try again',
+      57              :                   ),
+      58              :                 );
+      59              :               }
+      60            3 :               if (index == state.restaurants.length) {
+      61            1 :                 return ElevatedButton(
+      62            1 :                   onPressed: addMore,
+      63              :                   child: const Text(
+      64              :                     'Load more',
+      65              :                   ),
+      66              :                 );
+      67              :               }
+      68              : 
+      69            1 :               return GestureDetector(
+      70            0 :                 onTap: () => Navigator.of(context).push(
+      71            0 :                   MaterialPageRoute<RestaurantPage>(
+      72            0 :                     builder: (context) => RestaurantPage(
+      73            0 :                       restaurant: state.restaurants[index],
+      74              :                     ),
+      75              :                   ),
+      76              :                 ),
+      77            3 :                 child: RestaurantTile(restaurant: state.restaurants[index]),
+      78              :               );
+      79              :             },
+      80              :           );
+      81              :         }
+      82            1 :         if (state is RestaurantsError) {
+      83            0 :           return Center(
+      84            0 :             child: Text(state.exception.description),
+      85              :           );
+      86              :         }
+      87              : 
+      88            1 :         if (state is RestaurantsLoading) {
+      89              :           return const Center(
+      90              :             child: CircularProgressIndicator(),
+      91              :           );
+      92              :         }
+      93            1 :         return Container();
+      94              :       },
+      95              :     );
+      96              :   }
+      97              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_open_widget.dart.func-c.html b/coverage/report/view/widgets/restaurant_open_widget.dart.func-c.html new file mode 100644 index 0000000..84df7b5 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_open_widget.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_open_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_open_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1313
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_open_widget.dart.func.html b/coverage/report/view/widgets/restaurant_open_widget.dart.func.html new file mode 100644 index 0000000..0bce58f --- /dev/null +++ b/coverage/report/view/widgets/restaurant_open_widget.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_open_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_open_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1313
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_open_widget.dart.gcov.html b/coverage/report/view/widgets/restaurant_open_widget.dart.gcov.html new file mode 100644 index 0000000..ae2555d --- /dev/null +++ b/coverage/report/view/widgets/restaurant_open_widget.dart.gcov.html @@ -0,0 +1,107 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_open_widget.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_open_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1313
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : import 'package:gap/gap.dart';
+       3              : 
+       4              : class RestaurantOpenWidget extends StatelessWidget {
+       5            3 :   const RestaurantOpenWidget(this.isOpen, {super.key});
+       6              :   final bool isOpen;
+       7            3 :   @override
+       8              :   Widget build(BuildContext context) {
+       9            3 :     return Row(
+      10              :       crossAxisAlignment: CrossAxisAlignment.center,
+      11            3 :       children: [
+      12            3 :         Text(
+      13            3 :           isOpen ? 'Open Now' : 'Closed',
+      14            3 :           style: Theme.of(context)
+      15            3 :               .textTheme
+      16            3 :               .bodyMedium!
+      17            3 :               .copyWith(fontStyle: FontStyle.italic),
+      18              :         ),
+      19              :         const Gap(6),
+      20            3 :         Padding(
+      21              :           padding: const EdgeInsets.only(top: 4.0),
+      22            3 :           child: Icon(
+      23              :             Icons.circle,
+      24              :             size: 8,
+      25            3 :             color: isOpen ? Colors.green : Colors.red,
+      26              :           ),
+      27              :         ),
+      28              :       ],
+      29              :     );
+      30              :   }
+      31              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_star_rating_widget.dart.func-c.html b/coverage/report/view/widgets/restaurant_star_rating_widget.dart.func-c.html new file mode 100644 index 0000000..11fe6b5 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_star_rating_widget.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_star_rating_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_star_rating_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %77
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_star_rating_widget.dart.func.html b/coverage/report/view/widgets/restaurant_star_rating_widget.dart.func.html new file mode 100644 index 0000000..3bb9846 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_star_rating_widget.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_star_rating_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_star_rating_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %77
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_star_rating_widget.dart.gcov.html b/coverage/report/view/widgets/restaurant_star_rating_widget.dart.gcov.html new file mode 100644 index 0000000..8faf5a9 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_star_rating_widget.dart.gcov.html @@ -0,0 +1,97 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_star_rating_widget.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_star_rating_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %77
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : 
+       3              : class RestaurantStarRatingWidget extends StatelessWidget {
+       4            8 :   const RestaurantStarRatingWidget(this.rating, {super.key});
+       5              :   final starColor = const Color(0xFFFFB800);
+       6              :   final int rating;
+       7            4 :   @override
+       8              :   Widget build(BuildContext context) {
+       9            4 :     return Row(
+      10              :       mainAxisAlignment: MainAxisAlignment.start,
+      11            4 :       children: [
+      12           12 :         for (int i = 0; i < rating; i++)
+      13            4 :           Icon(
+      14              :             Icons.star,
+      15              :             size: 12,
+      16            4 :             color: starColor,
+      17              :           ),
+      18              :       ],
+      19              :     );
+      20              :   }
+      21              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_tile.dart.func-c.html b/coverage/report/view/widgets/restaurant_tile.dart.func-c.html new file mode 100644 index 0000000..57c862d --- /dev/null +++ b/coverage/report/view/widgets/restaurant_tile.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_tile.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_tile.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:97.2 %3635
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_tile.dart.func.html b/coverage/report/view/widgets/restaurant_tile.dart.func.html new file mode 100644 index 0000000..47813fb --- /dev/null +++ b/coverage/report/view/widgets/restaurant_tile.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_tile.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_tile.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:97.2 %3635
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/restaurant_tile.dart.gcov.html b/coverage/report/view/widgets/restaurant_tile.dart.gcov.html new file mode 100644 index 0000000..7e1fc02 --- /dev/null +++ b/coverage/report/view/widgets/restaurant_tile.dart.gcov.html @@ -0,0 +1,156 @@ + + + + + + + LCOV - lcov.info - view/widgets/restaurant_tile.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - restaurant_tile.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:97.2 %3635
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : import 'package:gap/gap.dart';
+       3              : import 'package:restaurant_tour/models/restaurant.dart';
+       4              : import 'package:restaurant_tour/view/widgets/restaurant_open_widget.dart';
+       5              : import 'package:restaurant_tour/view/widgets/restaurant_star_rating_widget.dart';
+       6              : 
+       7              : class RestaurantTile extends StatelessWidget {
+       8            3 :   const RestaurantTile({required this.restaurant, super.key});
+       9              :   final Restaurant restaurant;
+      10              : 
+      11            3 :   @override
+      12              :   Widget build(BuildContext context) {
+      13            3 :     return Container(
+      14           12 :       decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface),
+      15            3 :       child: Card(
+      16            3 :         child: Padding(
+      17              :           padding: const EdgeInsets.all(8.0),
+      18            3 :           child: Row(
+      19              :             crossAxisAlignment: CrossAxisAlignment.start,
+      20            3 :             children: [
+      21            3 :               Hero(
+      22            6 :                 tag: restaurant.id ?? 'restaurant_id',
+      23            3 :                 child: ClipRRect(
+      24            3 :                   borderRadius: BorderRadius.circular(8.0),
+      25            3 :                   child: Image.network(
+      26            0 :                     errorBuilder: (context, error, stackTrace) =>
+      27              :                         const Center(child: Icon(Icons.error)),
+      28              :                     width: 88,
+      29              :                     height: 88,
+      30            9 :                     restaurant.photos?.first ?? 'https://picsum.photos/200/300',
+      31              :                   ),
+      32              :                 ),
+      33              :               ),
+      34              :               const Gap(12),
+      35            3 :               Expanded(
+      36            3 :                 child: SizedBox(
+      37              :                   height: 88,
+      38            3 :                   child: Column(
+      39              :                     mainAxisAlignment: MainAxisAlignment.start,
+      40              :                     crossAxisAlignment: CrossAxisAlignment.start,
+      41            3 :                     children: [
+      42            3 :                       Expanded(
+      43            3 :                         child: Text(
+      44            6 :                           restaurant.name ?? 'Restaurant Name',
+      45            9 :                           style: Theme.of(context).textTheme.titleLarge,
+      46              :                         ),
+      47              :                       ),
+      48            3 :                       Row(
+      49            3 :                         children: [
+      50            3 :                           Text(
+      51            6 :                             restaurant.price ?? "\$\$",
+      52            9 :                             style: Theme.of(context).textTheme.bodyMedium,
+      53              :                           ),
+      54              :                           const Gap(4),
+      55            3 :                           Text(
+      56            8 :                             restaurant.categories?.first.title ?? "",
+      57            9 :                             style: Theme.of(context).textTheme.bodyMedium,
+      58              :                           ),
+      59              :                         ],
+      60              :                       ),
+      61            3 :                       Row(
+      62              :                         mainAxisAlignment: MainAxisAlignment.spaceBetween,
+      63            3 :                         children: [
+      64            3 :                           RestaurantStarRatingWidget(
+      65            9 :                             restaurant.rating?.toInt() ?? 2,
+      66              :                           ),
+      67            9 :                           RestaurantOpenWidget(restaurant.isOpen),
+      68              :                         ],
+      69              :                       ),
+      70              :                     ],
+      71              :                   ),
+      72              :                 ),
+      73              :               ),
+      74              :             ],
+      75              :           ),
+      76              :         ),
+      77              :       ),
+      78              :     );
+      79              :   }
+      80              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/review_list_widget.dart.func-c.html b/coverage/report/view/widgets/review_list_widget.dart.func-c.html new file mode 100644 index 0000000..dde4b0f --- /dev/null +++ b/coverage/report/view/widgets/review_list_widget.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/review_list_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - review_list_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %130
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/review_list_widget.dart.func.html b/coverage/report/view/widgets/review_list_widget.dart.func.html new file mode 100644 index 0000000..da73755 --- /dev/null +++ b/coverage/report/view/widgets/review_list_widget.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/review_list_widget.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - review_list_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %130
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/review_list_widget.dart.gcov.html b/coverage/report/view/widgets/review_list_widget.dart.gcov.html new file mode 100644 index 0000000..d2ce1e2 --- /dev/null +++ b/coverage/report/view/widgets/review_list_widget.dart.gcov.html @@ -0,0 +1,113 @@ + + + + + + + LCOV - lcov.info - view/widgets/review_list_widget.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - review_list_widget.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:0.0 %130
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : import 'package:gap/gap.dart';
+       3              : import 'package:restaurant_tour/models/restaurant.dart';
+       4              : import 'package:restaurant_tour/view/widgets/review_tile.dart';
+       5              : 
+       6              : class ReviewListWidget extends StatelessWidget {
+       7            0 :   const ReviewListWidget({required this.reviews, super.key});
+       8              :   final List<Review>? reviews;
+       9            0 :   @override
+      10              :   Widget build(BuildContext context) {
+      11            0 :     return (reviews?.isNotEmpty ?? false)
+      12            0 :         ? Column(
+      13              :             crossAxisAlignment: CrossAxisAlignment.start,
+      14            0 :             children: [
+      15              :               const Gap(16),
+      16            0 :               Text(
+      17            0 :                 '${reviews!.length} Reviews',
+      18            0 :                 style: Theme.of(context).textTheme.bodyMedium,
+      19              :               ),
+      20              :               const Gap(16),
+      21            0 :               ListView.separated(
+      22              :                 physics: const NeverScrollableScrollPhysics(),
+      23              :                 shrinkWrap: true,
+      24            0 :                 itemBuilder: (context, index) {
+      25            0 :                   return ReviewTile(reviews![index]);
+      26              :                 },
+      27            0 :                 separatorBuilder: (context, index) => const Padding(
+      28              :                   padding: EdgeInsets.symmetric(vertical: 8.0),
+      29              :                   child: Divider(),
+      30              :                 ),
+      31            0 :                 itemCount: reviews!.length,
+      32              :               ),
+      33              :             ],
+      34              :           )
+      35              :         : const Text('This restaurant has no reviews');
+      36              :   }
+      37              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/review_tile.dart.func-c.html b/coverage/report/view/widgets/review_tile.dart.func-c.html new file mode 100644 index 0000000..ce82bb5 --- /dev/null +++ b/coverage/report/view/widgets/review_tile.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/review_tile.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - review_tile.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1515
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/review_tile.dart.func.html b/coverage/report/view/widgets/review_tile.dart.func.html new file mode 100644 index 0000000..7b7bf00 --- /dev/null +++ b/coverage/report/view/widgets/review_tile.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - view/widgets/review_tile.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - review_tile.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1515
Test Date:2024-09-18 11:41:15Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/coverage/report/view/widgets/review_tile.dart.gcov.html b/coverage/report/view/widgets/review_tile.dart.gcov.html new file mode 100644 index 0000000..a1141c9 --- /dev/null +++ b/coverage/report/view/widgets/review_tile.dart.gcov.html @@ -0,0 +1,115 @@ + + + + + + + LCOV - lcov.info - view/widgets/review_tile.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - view/widgets - review_tile.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %1515
Test Date:2024-09-18 11:41:15Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'package:flutter/material.dart';
+       2              : import 'package:gap/gap.dart';
+       3              : import 'package:restaurant_tour/models/restaurant.dart';
+       4              : import 'package:restaurant_tour/view/widgets/restaurant_star_rating_widget.dart';
+       5              : 
+       6              : class ReviewTile extends StatelessWidget {
+       7            1 :   const ReviewTile(this.review, {super.key});
+       8              :   final Review review;
+       9              : 
+      10            1 :   @override
+      11              :   Widget build(BuildContext context) {
+      12            1 :     return Column(
+      13              :       crossAxisAlignment: CrossAxisAlignment.start,
+      14            1 :       children: [
+      15            3 :         RestaurantStarRatingWidget(review.rating ?? 1),
+      16              :         const Gap(12),
+      17            1 :         Text(
+      18            2 :           review.text ?? 'no comment',
+      19            3 :           style: Theme.of(context).textTheme.bodyMedium,
+      20              :         ),
+      21              :         const Gap(12),
+      22            1 :         Row(
+      23            1 :           children: [
+      24            1 :             CircleAvatar(
+      25            1 :               onForegroundImageError: (exception, stackTrace) => const Center(
+      26              :                 child: Icon(Icons.error),
+      27              :               ),
+      28            1 :               foregroundImage: NetworkImage(
+      29            3 :                 review.user?.imageUrl ?? 'https://picsum.photos/200/300',
+      30              :               ),
+      31              :             ),
+      32              :             const Gap(8),
+      33            4 :             Text(review.user?.name ?? 'user name'),
+      34              :           ],
+      35              :         ),
+      36              :       ],
+      37              :     );
+      38              :   }
+      39              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + From 5e9470755246895bcdd9ace4e5addbe72a1e59c8 Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Wed, 18 Sep 2024 12:21:02 -0400 Subject: [PATCH 11/12] refactor(data): add json parser for graphql add condition for test mode for restaurant datasource add logs lib --- .../graphql_restaurants_datasource.dart | 24 +++++++++++++------ lib/di/di.dart | 3 ++- pubspec.lock | 8 +++++++ pubspec.yaml | 1 + 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/lib/data/datasource/graphql/graphql_restaurants_datasource.dart b/lib/data/datasource/graphql/graphql_restaurants_datasource.dart index a3e1dd6..9b0b6ed 100644 --- a/lib/data/datasource/graphql/graphql_restaurants_datasource.dart +++ b/lib/data/datasource/graphql/graphql_restaurants_datasource.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:logger/logger.dart'; import 'package:restaurant_tour/common/exceptions/exceptions.dart'; import 'package:restaurant_tour/data/datasource/graphql/query.dart'; import 'package:restaurant_tour/domain/datasource/restaurants_datasource.dart'; @@ -27,13 +28,22 @@ class GraphqlRestaurantsDatasource implements RestaurantsDatasource { ); if (response.statusCode == 200) { - debugPrint( - jsonDecode(response.body)['data']['search'], - ); - final restaurantsResult = RestaurantQueryResult.fromJson( - jsonDecode(response.body)['data']['search'], - ); - return restaurantsResult.restaurants ?? []; + var logger = Logger(); + logger.t("Trace log"); + + final map = jsonDecode(response.body); + final data = map['data'] as Map; + final search = data['search'] as Map; + final total = search['total'] as int; + final businessesJson = search['business'] as List; + + final businesses = businessesJson + .map((business) => Restaurant.fromJson(business)) + .toList(); + + debugPrint('Total restaurants: $total'); + + return businesses ?? []; } else { throw RestaurantListException( description: 'Failed to load restaurants: ${response.statusCode}', diff --git a/lib/di/di.dart b/lib/di/di.dart index 20add26..9cf6754 100644 --- a/lib/di/di.dart +++ b/lib/di/di.dart @@ -1,4 +1,5 @@ import 'package:get_it/get_it.dart'; +import 'package:restaurant_tour/data/datasource/graphql/graphql_restaurants_datasource.dart'; import 'package:restaurant_tour/data/datasource/local/sp_favorite_restaurants_datasource.dart'; import 'package:restaurant_tour/data/datasource/memory/memory_restaurants_datasource.dart'; import 'package:restaurant_tour/domain/datasource/favorite_restaurants_datasource.dart'; @@ -22,7 +23,7 @@ void setup({testMode = false}) { dependsOn: [InitDependency(SharedPreferences)], ); getIt.registerSingleton( - MemoryRestaurantsDatasource(), + testMode ? MemoryRestaurantsDatasource() : GraphqlRestaurantsDatasource(), ); getIt.registerSingletonAsync( diff --git a/pubspec.lock b/pubspec.lock index 84c7136..198dcf7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -428,6 +428,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + logger: + dependency: "direct main" + description: + name: logger + sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" + url: "https://pub.dev" + source: hosted + version: "2.4.0" logging: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6c553ec..60a5eb5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: get_it: ^7.7.0 http: ^1.2.2 json_annotation: ^4.9.0 + logger: ^2.4.0 shared_preferences: ^2.3.2 dev_dependencies: From 60c54d342d14152b6547919f5b2714d4b12fe688 Mon Sep 17 00:00:00 2001 From: Darwin Jimenez Date: Wed, 18 Sep 2024 16:32:43 -0400 Subject: [PATCH 12/12] fix(widgets) add box fit and dimens to restaurant tile and hero --- lib/view/widgets/restaurant_hero_widget.dart | 4 +++- lib/view/widgets/restaurant_tile.dart | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/view/widgets/restaurant_hero_widget.dart b/lib/view/widgets/restaurant_hero_widget.dart index e9b648e..cc7045f 100644 --- a/lib/view/widgets/restaurant_hero_widget.dart +++ b/lib/view/widgets/restaurant_hero_widget.dart @@ -17,7 +17,9 @@ class RestaurantHeroWidget extends StatelessWidget { errorBuilder: (context, error, stackTrace) => const Center(child: Icon(Icons.error)), imageUrl ?? 'https://picsum.photos/375/361', - fit: BoxFit.fitWidth, + height: 361, + width: MediaQuery.of(context).size.width, + fit: BoxFit.fill, ), ); } diff --git a/lib/view/widgets/restaurant_tile.dart b/lib/view/widgets/restaurant_tile.dart index d97fa13..a812c0f 100644 --- a/lib/view/widgets/restaurant_tile.dart +++ b/lib/view/widgets/restaurant_tile.dart @@ -23,6 +23,7 @@ class RestaurantTile extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(8.0), child: Image.network( + fit: BoxFit.fill, errorBuilder: (context, error, stackTrace) => const Center(child: Icon(Icons.error)), width: 88,