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..6602f6e 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,9 @@ app.*.map.json /android/app/release # fvm -.fvm/flutter_sdk \ No newline at end of file + +# FVM Version Cache +.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/.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/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 0000000..2cab170 Binary files /dev/null and b/coverage/report/amber.png differ diff --git a/coverage/report/cmd_line b/coverage/report/cmd_line new file mode 100644 index 0000000..01470c3 --- /dev/null +++ b/coverage/report/cmd_line @@ -0,0 +1 @@ +genhtml coverage/lcov.info --output=coverage/report diff --git a/coverage/report/common/exceptions/exceptions.dart.func-c.html b/coverage/report/common/exceptions/exceptions.dart.func-c.html new file mode 100644 index 0000000..ad939e1 --- /dev/null +++ b/coverage/report/common/exceptions/exceptions.dart.func-c.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.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 0000000..38ad4f4 Binary files /dev/null and b/coverage/report/emerald.png differ 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 0000000..e1abc00 Binary files /dev/null and b/coverage/report/glass.png differ 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 0000000..991b6d4 Binary files /dev/null and b/coverage/report/ruby.png differ diff --git a/coverage/report/snow.png b/coverage/report/snow.png new file mode 100644 index 0000000..2cdae10 Binary files /dev/null and b/coverage/report/snow.png differ diff --git a/coverage/report/updown.png b/coverage/report/updown.png new file mode 100644 index 0000000..aa56a23 Binary files /dev/null and b/coverage/report/updown.png differ 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
+
+ + + diff --git a/integration_test/restaurant_tour_integreation_test.dart b/integration_test/restaurant_tour_integreation_test.dart new file mode 100644 index 0000000..d569369 --- /dev/null +++ b/integration_test/restaurant_tour_integreation_test.dart @@ -0,0 +1,141 @@ +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, + ); + //Check if already favorite + expect(find.byIcon(Icons.favorite), findsOne); + 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), findsNothing); + }); + }); +} 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..4248ccc --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,29 @@ +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 + +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/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/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 new file mode 100644 index 0000000..74e49f0 --- /dev/null +++ b/lib/common/exceptions/exceptions.dart @@ -0,0 +1,47 @@ +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); +} + +class RestaurantListException extends AppException { + RestaurantListException({ + code = "RESTAURANTS_LIST_NOT_AVAILABLE", + 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 are 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/data/datasource/graphql/graphql_restaurants_datasource.dart b/lib/data/datasource/graphql/graphql_restaurants_datasource.dart new file mode 100644 index 0000000..9b0b6ed --- /dev/null +++ b/lib/data/datasource/graphql/graphql_restaurants_datasource.dart @@ -0,0 +1,58 @@ +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'; +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) { + 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}', + ); + } + } 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/memory_favorite_restaurants_datasource.dart b/lib/data/datasource/memory/memory_favorite_restaurants_datasource.dart new file mode 100644 index 0000000..aea2ce8 --- /dev/null +++ b/lib/data/datasource/memory/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 _favoritesRestaurants = []; + @override + Future addFavoriteRestaurant(Restaurant restaurant) async { + if (restaurant.id != null && restaurant.id!.isNotEmpty) { + _favoritesRestaurants.add(restaurant); + } + } + + @override + Future removeFavoriteRestaurant(Restaurant restaurant) async { + if (restaurant.id != null && restaurant.id!.isNotEmpty) { + _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..efc7e6c --- /dev/null +++ b/lib/data/datasource/memory/memory_restaurants_datasource.dart @@ -0,0 +1,43 @@ +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: 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 { + final result = restaurants.sublist(offset, offset + limit); + return Future.value(result); + } +} diff --git a/lib/di/di.dart b/lib/di/di.dart new file mode 100644 index 0000000..9cf6754 --- /dev/null +++ b/lib/di/di.dart @@ -0,0 +1,63 @@ +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'; +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({testMode = false}) { + getIt.allowReassignment = testMode; + getIt.registerSingletonAsync( + () => SharedPreferences.getInstance(), + ); + + getIt.registerSingletonAsync( + () async => SpFavoriteRestaurantsDatasource(GetIt.I.get()), + dependsOn: [InitDependency(SharedPreferences)], + ); + getIt.registerSingleton( + testMode ? MemoryRestaurantsDatasource() : GraphqlRestaurantsDatasource(), + ); + + 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.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 new file mode 100644 index 0000000..5038d86 --- /dev/null +++ b/lib/domain/datasource/favorite_restaurants_datasource.dart @@ -0,0 +1,7 @@ +import 'package:restaurant_tour/models/restaurant.dart'; + +abstract class FavoriteRestaurantsDatasource { + Future addFavoriteRestaurant(Restaurant restaurant); + Future removeFavoriteRestaurant(Restaurant restaurant); + Future> getFavoritesRestaurants(); +} diff --git a/lib/domain/datasource/restaurants_datasource.dart b/lib/domain/datasource/restaurants_datasource.dart new file mode 100644 index 0000000..fe00284 --- /dev/null +++ b/lib/domain/datasource/restaurants_datasource.dart @@ -0,0 +1,5 @@ +import 'package:restaurant_tour/models/restaurant.dart'; + +abstract class RestaurantsDatasource { + Future> getRestaurants({int offset, int limit}); +} diff --git a/lib/domain/repository/restaurants_repository.dart b/lib/domain/repository/restaurants_repository.dart new file mode 100644 index 0000000..8a3be6a --- /dev/null +++ b/lib/domain/repository/restaurants_repository.dart @@ -0,0 +1,79 @@ +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 { + RestaurantsRepository( + this._restaurantsDatasource, + this._favoriteRestaurantsDatasource, + ); + + final FavoriteRestaurantsDatasource _favoriteRestaurantsDatasource; + final RestaurantsDatasource _restaurantsDatasource; + final List _restaurants = []; + final Set _favorites = {}; + + Future> getRestaurants() async { + 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 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(), + ); + } + + 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) async { + _favorites.add(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 == restaurant); + if (restaurantIndex != -1) { + _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 new file mode 100644 index 0000000..d567c10 --- /dev/null +++ b/lib/domain/usecase/restaurants_usecase.dart @@ -0,0 +1,27 @@ +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() { + return _repository.getRestaurants(); + } + + Future> getFavoriteRestaurants() { + return _repository.getFavoriteRestaurants(); + } + + Future addFavoriteRestaurant(Restaurant restaurant) { + return _repository.addFavoriteRestaurant(restaurant); + } + + 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 ae7012a..f2801db 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,86 +1,42 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; -import 'package:restaurant_tour/models/restaurant.dart'; -import 'package:restaurant_tour/query.dart'; - -const _apiKey = ''; -const _baseUrl = 'https://api.yelp.com/v3/graphql'; +import 'package:get_it/get_it.dart'; +import 'package:restaurant_tour/di/di.dart'; +import 'package:restaurant_tour/view/pages/main_page.dart'; +import './view/theme/app_theme.dart'; void main() { runApp(const RestaurantTour()); } -class RestaurantTour extends StatelessWidget { - const RestaurantTour({super.key}); +class RestaurantTour extends StatefulWidget { + const RestaurantTour({super.key, this.testMode = false}); + final bool testMode; @override - Widget build(BuildContext context) { - return const MaterialApp( - title: 'Restaurant Tour', - home: HomePage(), - ); - } + State createState() => _RestaurantTourState(); } -// 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; - } +class _RestaurantTourState extends State { + @override + void initState() { + setup(testMode: widget.testMode); + super.initState(); } @override Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Restaurant Tour'), - ElevatedButton( - child: const Text('Fetch Restaurants'), - onPressed: () async { - try { - final result = await getRestaurants(); - if (result != null) { - print('Fetched ${result.restaurants!.length} restaurants'); - } else { - print('No restaurants fetched'); - } - } catch (e) { - print('Failed to fetch restaurants: $e'); - } - }, - ), - ], - ), + return MaterialApp( + 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()); + } + }, ), ); } diff --git a/lib/models/restaurant.dart b/lib/models/restaurant.dart index 1c7ad2f..642cb8a 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; @@ -95,6 +96,7 @@ class Restaurant { final List? hours; final List? reviews; final Location? location; + final bool? isFavorite; const Restaurant({ this.id, @@ -106,8 +108,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); @@ -137,6 +166,9 @@ class Restaurant { } return false; } + + @override + List get props => [id]; } @JsonSerializable() diff --git a/lib/models/restaurant.g.dart b/lib/models/restaurant.g.dart index 3ed33f9..8cb457c 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, }; @@ -77,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) => @@ -90,12 +93,13 @@ Map _$RestaurantToJson(Restaurant instance) => 'hours': instance.hours, 'reviews': instance.reviews, 'location': instance.location, + 'isFavorite': instance.isFavorite, }; 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/lib/presentation/favorites/favorites_restaurants_bloc.dart b/lib/presentation/favorites/favorites_restaurants_bloc.dart new file mode 100644 index 0000000..02233a4 --- /dev/null +++ b/lib/presentation/favorites/favorites_restaurants_bloc.dart @@ -0,0 +1,78 @@ +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'; +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(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)); + } + 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)); + } + 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/favorites/favorites_restaurants_event.dart b/lib/presentation/favorites/favorites_restaurants_event.dart new file mode 100644 index 0000000..1cb68ee --- /dev/null +++ b/lib/presentation/favorites/favorites_restaurants_event.dart @@ -0,0 +1,16 @@ +part of 'favorites_restaurants_bloc.dart'; + +@immutable +sealed class FavoritesRestaurantsEvent {} + +class LoadFavoritesRestaurants extends FavoritesRestaurantsEvent {} + +class AddFavoriteRestaurant extends FavoritesRestaurantsEvent { + AddFavoriteRestaurant(this.restaurant); + final Restaurant restaurant; +} + +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 new file mode 100644 index 0000000..394c9f7 --- /dev/null +++ b/lib/presentation/favorites/favorites_restaurants_state.dart @@ -0,0 +1,28 @@ +part of 'favorites_restaurants_bloc.dart'; + +@immutable +sealed class FavoritesRestaurantsState {} + +final class FavoritesRestaurantsInitial extends FavoritesRestaurantsState {} + +final class FavoritesRestaurantsLoading 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 FavoritesRestaurantsReady extends FavoritesRestaurantsState { + FavoritesRestaurantsReady(this.favoritesRestaurants); + final List favoritesRestaurants; +} diff --git a/lib/presentation/restaurants/restaurants_bloc.dart b/lib/presentation/restaurants/restaurants_bloc.dart new file mode 100644 index 0000000..9436022 --- /dev/null +++ b/lib/presentation/restaurants/restaurants_bloc.dart @@ -0,0 +1,69 @@ +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'; +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, + 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 new file mode 100644 index 0000000..c190750 --- /dev/null +++ b/lib/presentation/restaurants/restaurants_event.dart @@ -0,0 +1,12 @@ +part of 'restaurants_bloc.dart'; + +@immutable +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 new file mode 100644 index 0000000..335e049 --- /dev/null +++ b/lib/presentation/restaurants/restaurants_state.dart @@ -0,0 +1,43 @@ +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, { + 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 { + RestaurantsError(this.exception); + final RestaurantListException exception; +} 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 new file mode 100644 index 0000000..461c144 --- /dev/null +++ b/lib/view/pages/main_page.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.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/widgets/favorite_list_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_list_widget.dart'; + +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( + length: 2, + child: Scaffold( + appBar: AppBar( + title: const Text('RestauranTour'), + 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(), + ), + ], + ), + ), + ); + } +} diff --git a/lib/view/pages/restaurant_page.dart b/lib/view/pages/restaurant_page.dart new file mode 100644 index 0000000..b1d4f15 --- /dev/null +++ b/lib/view/pages/restaurant_page.dart @@ -0,0 +1,94 @@ +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/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({ + 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(widget.restaurant.name ?? 'Restaurant Name'), + actions: [ + FavoriteButtonWidget( + callback: (isFavorite) { + GetIt.I.get().add( + (isFavorite) + ? AddFavoriteRestaurant(widget.restaurant) + : RemoveFavoriteRestaurant(widget.restaurant), + ); + + GetIt.I.get().add(LoadRestaurants()); + }, + isFavorite: widget.restaurant.isFavorite, + ), + ], + ), + body: SingleChildScrollView( + child: Column( + children: [ + RestaurantHeroWidget( + imageUrl: widget.restaurant.photos?.first, + tag: widget.restaurant.id, + ), + 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: [ + RestaurantCategoryPriceWidget( + 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, + ), + const Divider(), + OverallRatingWidget(rating: widget.restaurant.rating), + const Divider(), + ReviewListWidget(reviews: widget.restaurant.reviews), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/view/theme/app_theme.dart b/lib/view/theme/app_theme.dart new file mode 100644 index 0000000..d322b2e --- /dev/null +++ b/lib/view/theme/app_theme.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +class AppTheme { + static final ThemeData lightTheme = ThemeData( + colorSchemeSeed: Colors.black, + fontFamily: 'OpenSans', + cardTheme: const 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/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..df71237 --- /dev/null +++ b/lib/view/widgets/favorite_button_widget.dart @@ -0,0 +1,38 @@ +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..1fa2686 --- /dev/null +++ b/lib/view/widgets/favorite_list_widget.dart @@ -0,0 +1,58 @@ +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..eba8121 --- /dev/null +++ b/lib/view/widgets/restaurant_category_price_widget.dart @@ -0,0 +1,28 @@ +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..cc7045f --- /dev/null +++ b/lib/view/widgets/restaurant_hero_widget.dart @@ -0,0 +1,26 @@ +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', + height: 361, + width: MediaQuery.of(context).size.width, + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/view/widgets/restaurant_list_widget.dart b/lib/view/widgets/restaurant_list_widget.dart new file mode 100644 index 0000000..ec7b6a1 --- /dev/null +++ b/lib/view/widgets/restaurant_list_widget.dart @@ -0,0 +1,97 @@ +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: 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_widget.dart b/lib/view/widgets/restaurant_open_widget.dart new file mode 100644 index 0000000..57ab4ee --- /dev/null +++ b/lib/view/widgets/restaurant_open_widget.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; + +class RestaurantOpenWidget extends StatelessWidget { + const RestaurantOpenWidget(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_widget.dart b/lib/view/widgets/restaurant_star_rating_widget.dart new file mode 100644 index 0000000..14612b4 --- /dev/null +++ b/lib/view/widgets/restaurant_star_rating_widget.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class RestaurantStarRatingWidget extends StatelessWidget { + const RestaurantStarRatingWidget(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..a812c0f --- /dev/null +++ b/lib/view/widgets/restaurant_tile.dart @@ -0,0 +1,81 @@ +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_widget.dart'; +import 'package:restaurant_tour/view/widgets/restaurant_star_rating_widget.dart'; + +class RestaurantTile extends StatelessWidget { + const RestaurantTile({required this.restaurant, super.key}); + final Restaurant restaurant; + + @override + Widget build(BuildContext context) { + 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( + fit: BoxFit.fill, + 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), + 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, + ), + const Gap(4), + Text( + restaurant.categories?.first.title ?? "", + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 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..e4cdc59 --- /dev/null +++ b/lib/view/widgets/review_list_widget.dart @@ -0,0 +1,37 @@ +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 new file mode 100644 index 0000000..543a396 --- /dev/null +++ b/lib/view/widgets/review_tile.dart @@ -0,0 +1,39 @@ +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_widget.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: [ + RestaurantStarRatingWidget(review.rating ?? 1), + const Gap(12), + Text( + review.text ?? 'no comment', + style: Theme.of(context).textTheme.bodyMedium, + ), + const Gap(12), + Row( + children: [ + CircleAvatar( + onForegroundImageError: (exception, stackTrace) => const Center( + child: Icon(Icons.error), + ), + 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 f95a63e..198dcf7 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: @@ -33,6 +38,30 @@ 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" + 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: + name: bloc_test + sha256: "165a6ec950d9252ebe36dc5335f2e6eb13055f33d56db0eeb7642768849b43d2" + url: "https://pub.dev" + source: hosted + version: "9.1.7" boolean_selector: dependency: transitive description: @@ -45,10 +74,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 +90,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 +130,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 +142,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 +178,50 @@ 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" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + 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: @@ -185,27 +230,48 @@ 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: 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 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_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_lints: dependency: "direct dev" description: @@ -219,30 +285,56 @@ 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: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + gap: + dependency: "direct main" + description: + name: gap + sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d + 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: 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 +347,39 @@ 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" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" 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 +400,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: @@ -331,14 +428,30 @@ 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: 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 +464,74 @@ 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 dev" + description: + name: mockito + sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + 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" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + 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: + 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: @@ -387,46 +540,174 @@ 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: name: pool - sha256: "05955e3de2683e1746222efd14b775df7131139e07695dc8e24650f6b4204504" + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" url: "https://pub.dev" source: hosted - version: "1.5.0" + 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: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" 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" + 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: 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 +729,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 +773,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: @@ -488,6 +785,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: @@ -496,30 +801,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.2" + test_core: + dependency: transitive + description: + name: test_core + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" url: "https://pub.dev" source: hosted - version: "0.7.0" + 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 +853,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 +873,54 @@ 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" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + 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: 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" - flutter: ">=3.19.6" + dart: ">=3.5.0-259.0.dev <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index bc8a205..60a5eb5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,10 +11,19 @@ environment: flutter: ">=3.19.6" 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 + gap: ^3.0.1 + 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: flutter_test: @@ -22,6 +31,12 @@ dev_dependencies: flutter_lints: ^4.0.0 build_runner: ^2.4.10 json_serializable: ^6.8.0 + test: ^1.25.2 + mockito: ^5.4.4 + network_image_mock: ^2.1.1 + integration_test: + sdk: flutter + flutter: generate: true 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/domain/usecase/restaurants_usecase_test.dart b/test/domain/usecase/restaurants_usecase_test.dart new file mode 100644 index 0000000..f9f9fe5 --- /dev/null +++ b/test/domain/usecase/restaurants_usecase_test.dart @@ -0,0 +1,65 @@ +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 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.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)) + .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.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 new file mode 100644 index 0000000..d25f052 --- /dev/null +++ b/test/domain/usecase/restaurants_usecase_test.mocks.dart @@ -0,0 +1,92 @@ +// 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 _i3; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:restaurant_tour/domain/repository/restaurants_repository.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 [RestaurantsRepository]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockRestaurantsRepository extends _i1.Mock + implements _i2.RestaurantsRepository { + @override + _i3.Future> getRestaurants() => (super.noSuchMethod( + Invocation.method( + #getRestaurants, + [], + ), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), + returnValueForMissingStub: + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); + + @override + _i3.Future getMoreRestaurants( + int? offset, + int? limit, + ) => + (super.noSuchMethod( + Invocation.method( + #getMoreRestaurants, + [ + offset, + limit, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future> getFavoriteRestaurants() => + (super.noSuchMethod( + Invocation.method( + #getFavoriteRestaurants, + [], + ), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), + returnValueForMissingStub: + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); + + @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); +} 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..3389e4b --- /dev/null +++ b/test/presentation/favorites/test_favorite_restaurants_bloc.mocks.dart @@ -0,0 +1,92 @@ +// 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 _i3; + +import 'package:mockito/mockito.dart' as _i1; +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 +// 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 [RestaurantsUsecase]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockRestaurantsUsecase extends _i1.Mock + implements _i2.RestaurantsUsecase { + @override + _i3.Future> getRestaurants() => (super.noSuchMethod( + Invocation.method( + #getRestaurants, + [], + ), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), + returnValueForMissingStub: + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); + + @override + _i3.Future> getFavoriteRestaurants() => + (super.noSuchMethod( + Invocation.method( + #getFavoriteRestaurants, + [], + ), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), + returnValueForMissingStub: + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); + + @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 loadMoreRestaurants({ + required int? offset, + required int? limit, + }) => + (super.noSuchMethod( + Invocation.method( + #loadMoreRestaurants, + [], + { + #offset: offset, + #limit: limit, + }, + ), + 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 new file mode 100644 index 0000000..d90c228 --- /dev/null +++ b/test/presentation/restaurants/test_restaurants_bloc.dart @@ -0,0 +1,70 @@ +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 throw an 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), + ); + + 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 new file mode 100644 index 0000000..8be2844 --- /dev/null +++ b/test/presentation/restaurants/test_restaurants_bloc.mocks.dart @@ -0,0 +1,92 @@ +// 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 _i3; + +import 'package:mockito/mockito.dart' as _i1; +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 +// 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 [RestaurantsUsecase]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockRestaurantsUsecase extends _i1.Mock + implements _i2.RestaurantsUsecase { + @override + _i3.Future> getRestaurants() => (super.noSuchMethod( + Invocation.method( + #getRestaurants, + [], + ), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), + returnValueForMissingStub: + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); + + @override + _i3.Future> getFavoriteRestaurants() => + (super.noSuchMethod( + Invocation.method( + #getFavoriteRestaurants, + [], + ), + returnValue: _i3.Future>.value(<_i4.Restaurant>[]), + returnValueForMissingStub: + _i3.Future>.value(<_i4.Restaurant>[]), + ) as _i3.Future>); + + @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 loadMoreRestaurants({ + required int? offset, + required int? limit, + }) => + (super.noSuchMethod( + Invocation.method( + #loadMoreRestaurants, + [], + { + #offset: offset, + #limit: limit, + }, + ), + 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..8abeb02 --- /dev/null +++ b/test/view/widgets/favorite_list_widget_test.dart @@ -0,0 +1,61 @@ +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/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_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..d215610 --- /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( + const 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( + const 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..47f794c --- /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/restaurants/restaurants_bloc.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..8ae785d --- /dev/null +++ b/test/view/widgets/restaurant_tile_test.dart @@ -0,0 +1,55 @@ +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..5f282dc --- /dev/null +++ b/test/view/widgets/review_tile_test.dart @@ -0,0 +1,35 @@ +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 + const user = + User(name: 'John Doe', imageUrl: 'https://picsum.photos/200/300'); + const review = Review(rating: 4, text: 'Great food!', user: user); + + await tester.pumpWidget( + const 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); + }); +} 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); - }); -}