From 78ed73b53002fe6445d328adadafe429351d4723 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 20 Feb 2024 01:08:38 -0600 Subject: [PATCH 1/5] chore: Initial commit - Set up Flutter project - Add basic folder structure - Configure dependencies and plugins --- .fvmrc | 3 + .gitignore | 4 +- .vscode/settings.json | 14 +- android/build.gradle | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- pubspec.lock | 305 ++++++++++++++++-- pubspec.yaml | 3 + 7 files changed, 303 insertions(+), 34 deletions(-) create mode 100644 .fvmrc diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 00000000..05b430ce --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.13.9" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1be2d875..7040cb04 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,6 @@ app.*.map.json /android/app/release # fvm -.fvm/flutter_sdk \ No newline at end of file + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index f285aa4a..7578bf48 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.13.9", + "search.exclude": { + "**/.fvm": true + }, + "files.watcherExclude": { + "**/.fvm": true + } } \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 24047dce..fd59c3d1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.9.22' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index bc6a58af..6b665338 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/pubspec.lock b/pubspec.lock index 0b052c68..48bfca29 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -105,6 +105,30 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.4" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" + url: "https://pub.dev" + source: hosted + version: "1.1.1" characters: dependency: transitive description: @@ -149,10 +173,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.2" convert: dependency: transitive description: @@ -201,14 +225,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" file: dependency: transitive description: name: file - sha256: b69516f2c26a5bcac4eee2e32512e1a5205ab312b3536c1c1227b2b942b5f9ad + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.4" fixnum: dependency: transitive description: @@ -222,6 +254,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" flutter_lints: dependency: "direct dev" description: @@ -243,6 +283,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" frontend_server_client: dependency: transitive description: @@ -267,6 +312,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" http_multi_server: dependency: transitive description: @@ -295,10 +348,10 @@ packages: dependency: transitive description: name: js - sha256: d9bdfd70d828eeb352390f81b18d6a354ef2044aa28ef25682079797fa7cd174 + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.3" + version: "0.6.7" json_annotation: dependency: "direct main" description: @@ -351,10 +404,10 @@ packages: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" mime: dependency: transitive description: @@ -363,6 +416,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + url: "https://pub.dev" + source: hosted + version: "2.0.0" package_config: dependency: transitive description: @@ -387,14 +456,78 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + 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: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" petitparser: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "2.1.8" pool: dependency: transitive description: @@ -403,6 +536,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + provider: + dependency: "direct main" + description: + name: provider + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + url: "https://pub.dev" + source: hosted + version: "6.1.1" pub_semver: dependency: transitive description: @@ -419,6 +560,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + url: "https://pub.dev" + source: hosted + version: "2.3.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" shelf: dependency: transitive description: @@ -464,22 +669,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 + url: "https://pub.dev" + source: hosted + version: "2.3.2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" stream_transform: dependency: transitive description: @@ -496,6 +725,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -508,10 +745,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.0" timing: dependency: transitive description: @@ -528,6 +765,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" + url: "https://pub.dev" + source: hosted + version: "4.2.2" vector_graphics: dependency: transitive description: @@ -572,10 +817,10 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -584,14 +829,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + win32: + dependency: transitive + description: + name: win32 + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" xml: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.3.0" yaml: dependency: transitive description: @@ -601,5 +862,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.7.0-0" + dart: ">=3.1.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index be3055e0..08d5c018 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,9 @@ dependencies: dio: ^5.4.0 json_annotation: ^4.8.1 flutter_svg: ^2.0.9 + provider: ^6.1.1 + shared_preferences: ^2.2.2 + cached_network_image: ^3.3.1 dev_dependencies: flutter_test: From c7729ddc0727e235c0184dda324d2565660e3832 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 20 Feb 2024 01:10:44 -0600 Subject: [PATCH 2/5] feat: Update Yelp API integration - Replace old Yelp API endpoints with the new ones --- lib/repositories/yelp_repository.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/repositories/yelp_repository.dart b/lib/repositories/yelp_repository.dart index f251d7b4..27cb92c6 100644 --- a/lib/repositories/yelp_repository.dart +++ b/lib/repositories/yelp_repository.dart @@ -2,7 +2,8 @@ import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:restaurantour/models/restaurant.dart'; -const _apiKey = ''; +const _apiKey = + 'xPkMA-zzjOcUpYI7PV4lcnjJNCklJ38n9L7Dg7AAG3PUiA9eHbNpcgUJP5qxTCemRufUBC-MtlMxwzpEIpxGGOYcuhliCYWsQwJg_1KCMNjVEiJ7Nmu0fp_gJqPTZXYx'; class YelpRepository { late Dio dio; @@ -73,8 +74,8 @@ class YelpRepository { String _getQuery(int offset) { return ''' query getRestaurants { - search(location: "Las Vegas", limit: 20, offset: $offset) { - total + search(location: "Miami", limit: 20, offset: $offset) { + total business { id name @@ -89,6 +90,7 @@ query getRestaurants { image_url name } + text } categories { title From 1b36cd23d162ac7acef84ff6fbb69fbd10eeace3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 20 Feb 2024 01:11:51 -0600 Subject: [PATCH 3/5] feat: Add RestaurantProvider for managing restaurant data - Implement RestaurantProvider class with methods to fetch and manage restaurant data - Integrate Provider into relevant screens and widgets - Use ChangeNotifier to notify listeners of data changes --- lib/providers/restaurantProvider.dart | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 lib/providers/restaurantProvider.dart diff --git a/lib/providers/restaurantProvider.dart b/lib/providers/restaurantProvider.dart new file mode 100644 index 00000000..403c531b --- /dev/null +++ b/lib/providers/restaurantProvider.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:restaurantour/models/restaurant.dart'; +import 'package:restaurantour/repositories/yelp_repository.dart'; + +class RestaurantProvider extends ChangeNotifier { + final YelpRepository _repository = YelpRepository(); + List? _restaurants; + Set _favorites = {}; + + List? get restaurants => _restaurants; + + Set get favorites => _favorites; + + Future fetchRestaurants() async { + print('Fetching restaurants...'); + final result = await _repository.getRestaurants(); + if (result != null) { + _restaurants = result.restaurants; + notifyListeners(); + } + } + + List getFavoriteRestaurants() { + return _restaurants?.where((restaurant) { + return _favorites.contains(restaurant.id); + }).toList() ?? + []; + } + + void toggleFavorite(String restaurantId, bool isFavorite) { + if (isFavorite) { + _favorites.add(restaurantId); + } else { + _favorites.remove(restaurantId); + } + + notifyListeners(); + } +} From 04ff63ce1e6faceb9d4ce49c7a16882373d0505a Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 20 Feb 2024 01:13:43 -0600 Subject: [PATCH 4/5] feat: Add new screens and widgets for enhanced user experience - Introduce HomeScreen with tabs for All Restaurants and My Favorites - Implement AllRestaurantsTab and MyFavoritesTab widgets - Create RestaurantCard and ReviewWidget for displaying restaurant information - Develop RestaurantDetailScreen for detailed view of a restaurant --- .fvm/fvm_config.json | 4 - lib/main.dart | 90 +++++----- lib/screens/homeScreen.dart | 47 ++++++ lib/screens/restaurantDetailScreen.dart | 155 ++++++++++++++++++ lib/widgets/home/allRestaurantTab.dart | 29 ++++ lib/widgets/home/myFavoritesTab.dart | 29 ++++ lib/widgets/home/restaurantCard.dart | 85 ++++++++++ lib/widgets/myDivider.dart | 15 ++ .../restaurantDetail/addressWidget.dart | 31 ++++ lib/widgets/restaurantDetail/ratingStars.dart | 20 +++ .../restaurantDetail/ratingWidget.dart | 41 +++++ .../restaurantDetail/restaurantStatus.dart | 32 ++++ .../restaurantDetail/reviewWidget.dart | 55 +++++++ test/widget_test.dart | 20 --- 14 files changed, 590 insertions(+), 63 deletions(-) delete mode 100644 .fvm/fvm_config.json create mode 100644 lib/screens/homeScreen.dart create mode 100644 lib/screens/restaurantDetailScreen.dart create mode 100644 lib/widgets/home/allRestaurantTab.dart create mode 100644 lib/widgets/home/myFavoritesTab.dart create mode 100644 lib/widgets/home/restaurantCard.dart create mode 100644 lib/widgets/myDivider.dart create mode 100644 lib/widgets/restaurantDetail/addressWidget.dart create mode 100644 lib/widgets/restaurantDetail/ratingStars.dart create mode 100644 lib/widgets/restaurantDetail/ratingWidget.dart create mode 100644 lib/widgets/restaurantDetail/restaurantStatus.dart create mode 100644 lib/widgets/restaurantDetail/reviewWidget.dart delete mode 100644 test/widget_test.dart diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json deleted file mode 100644 index d8abe1b9..00000000 --- a/.fvm/fvm_config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "flutterSdkVersion": "3.13.9", - "flavors": {} -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index c6ce7473..66d06eb2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:restaurantour/repositories/yelp_repository.dart'; +import 'package:provider/provider.dart'; +import 'package:restaurantour/providers/restaurantProvider.dart'; +import 'package:restaurantour/screens/homeScreen.dart'; void main() { - runApp(const Restaurantour()); + runApp( + ChangeNotifierProvider( + create: (context) => RestaurantProvider(), + child: const Restaurantour(), + ), + ); } class Restaurantour extends StatelessWidget { - // This widget is the root of your application. const Restaurantour({Key? key}) : super(key: key); @override @@ -14,44 +20,50 @@ class Restaurantour extends StatelessWidget { return MaterialApp( title: 'RestauranTour', theme: ThemeData( - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: const HomePage(), - ); - } -} - -class HomePage extends StatelessWidget { - const HomePage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Restaurantour'), - ElevatedButton( - child: const Text('Fetch Restaurants'), - onPressed: () async { - final yelpRepo = YelpRepository(); - - try { - final result = await yelpRepo.getRestaurants(); - if (result != null) { - print('Fetched ${result.restaurants!.length} restaurants'); - } else { - print('No restaurants fetched'); - } - } catch (e) { - print('Failed to fetch restaurants: $e'); - } - }, - ), - ], + brightness: Brightness.light, + primaryColor: Colors.white, // Color de fondo de la barra de aplicación + appBarTheme: AppBarTheme( + color: Colors.white, + toolbarTextStyle: ThemeData.light() + .textTheme + .copyWith( + titleLarge: const TextStyle( + color: Colors + .black, // Color del texto del título de la barra de aplicación + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ) + .bodyMedium, + titleTextStyle: ThemeData.light() + .textTheme + .copyWith( + titleLarge: const TextStyle( + color: Colors + .black, // Color del texto del título de la barra de aplicación + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ) + .titleLarge, + ), + colorScheme: const ColorScheme( + // Configuración de colores general + primary: Colors.black, + secondary: Colors.black, + brightness: Brightness.light, + onBackground: Colors.white, + onPrimary: Colors.black, + surface: Colors.white, + onSurface: Colors.white, + error: Colors.white, + onError: Colors.white, + onSecondary: Colors.white, + background: Colors.white, ), + visualDensity: VisualDensity.adaptivePlatformDensity, ), + home: const HomeScreen(), ); } } diff --git a/lib/screens/homeScreen.dart b/lib/screens/homeScreen.dart new file mode 100644 index 00000000..122d8361 --- /dev/null +++ b/lib/screens/homeScreen.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:restaurantour/providers/restaurantProvider.dart'; +import 'package:restaurantour/widgets/home/allRestaurantTab.dart'; +import 'package:restaurantour/widgets/home/myFavoritesTab.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({Key? key}) : super(key: key); + + @override + _HomeScreenState createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + @override + void initState() { + super.initState(); + final restaurantProvider = + Provider.of(context, listen: false); + restaurantProvider.fetchRestaurants(); + } + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + title: const Text('RestauranTour'), + bottom: const TabBar( + tabs: [ + Tab(text: 'All Restaurants'), + Tab(text: 'My Favorites'), + ], + ), + ), + body: const TabBarView( + children: [ + AllRestaurantsTab(), + MyFavoritesTab(), + ], + ), + ), + ); + } +} diff --git a/lib/screens/restaurantDetailScreen.dart b/lib/screens/restaurantDetailScreen.dart new file mode 100644 index 00000000..175a2378 --- /dev/null +++ b/lib/screens/restaurantDetailScreen.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:restaurantour/models/restaurant.dart'; +import 'package:restaurantour/providers/restaurantProvider.dart'; +import 'package:restaurantour/widgets/restaurantDetail/addressWidget.dart'; +import 'package:restaurantour/widgets/myDivider.dart'; +import 'package:restaurantour/widgets/restaurantDetail/ratingWidget.dart'; +import 'package:restaurantour/widgets/restaurantDetail/restaurantStatus.dart'; +import 'package:restaurantour/widgets/restaurantDetail/reviewWidget.dart'; + +class RestaurantDetailScreen extends StatefulWidget { + final Restaurant restaurant; + + const RestaurantDetailScreen({Key? key, required this.restaurant}) + : super(key: key); + + @override + _RestaurantDetailScreenState createState() => _RestaurantDetailScreenState(); +} + +class _RestaurantDetailScreenState extends State { + bool isFavorite = false; + + @override + void initState() { + super.initState(); + + final restaurantProvider = + Provider.of(context, listen: false); + + isFavorite = restaurantProvider.favorites.contains(widget.restaurant.id); + } + + @override + Widget build(BuildContext context) { + final restaurantProvider = + Provider.of(context, listen: false); + return Scaffold( + appBar: AppBar( + title: Text(widget.restaurant.name ?? ''), + actions: [ + IconButton( + icon: Icon( + isFavorite ? Icons.favorite : Icons.favorite_border, + color: Colors.black, + ), + onPressed: () { + setState(() { + isFavorite = !isFavorite; + }); + + restaurantProvider.toggleFavorite( + widget.restaurant.id!, + isFavorite, + ); + + final snackBar = SnackBar( + content: Text( + isFavorite ? 'Saved as favorite' : 'Removed from favorites', + style: const TextStyle(color: Colors.white), + ), + backgroundColor: isFavorite + ? const Color(0xCC5FC95F) + : const Color.fromRGBO( + 252, + 76, + 76, + 0.8, + ), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + }, + ), + ], + ), + body: ListView( + children: [ + if (widget.restaurant.photos != null && + widget.restaurant.photos!.isNotEmpty) + Image.network( + widget.restaurant.photos!.first, + width: double.infinity, + height: 400.0, + fit: BoxFit.cover, + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.restaurant.price ?? '', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16.0, + ), + ), + const SizedBox(height: 8.0), + Text( + ' ${widget.restaurant.categories?.first.title ?? ''}', + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 16.0, + ), + ), + ], + ), + RestaurantStatus( + isOpen: widget.restaurant.hours != null && + widget.restaurant.isOpen), + ], + ), + ), + myDivider(), + Padding( + padding: const EdgeInsets.all(16.0), + child: AddressWidget( + address: widget.restaurant.location!.formattedAddress ?? ''), + ), + myDivider(), + Padding( + padding: const EdgeInsets.all(16.0), + child: RatingWidget(rating: widget.restaurant.rating!), + ), + myDivider(), + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + '${widget.restaurant.reviews!.length} reviews', + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 16.0, + ), + ), + ), + if (widget.restaurant.reviews != null) + Column( + children: [ + for (var i = 0; i < widget.restaurant.reviews!.length; i++) + Column( + children: [ + if (i > 0) myDivider(), + ReviewWidget(review: widget.restaurant.reviews![i]), + ], + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/widgets/home/allRestaurantTab.dart b/lib/widgets/home/allRestaurantTab.dart new file mode 100644 index 00000000..e81be574 --- /dev/null +++ b/lib/widgets/home/allRestaurantTab.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:restaurantour/providers/restaurantProvider.dart'; +import 'package:restaurantour/widgets/home/restaurantCard.dart'; + +class AllRestaurantsTab extends StatelessWidget { + const AllRestaurantsTab({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, restaurantProvider, child) { + final restaurants = restaurantProvider.restaurants; + + if (restaurants != null) { + return ListView.builder( + itemCount: restaurants.length, + itemBuilder: (context, index) { + final restaurant = restaurants[index]; + return RestaurantCard(restaurant: restaurant); + }, + ); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ); + } +} diff --git a/lib/widgets/home/myFavoritesTab.dart b/lib/widgets/home/myFavoritesTab.dart new file mode 100644 index 00000000..1958fc74 --- /dev/null +++ b/lib/widgets/home/myFavoritesTab.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:restaurantour/providers/restaurantProvider.dart'; +import 'package:restaurantour/widgets/home/restaurantCard.dart'; + +class MyFavoritesTab extends StatelessWidget { + const MyFavoritesTab({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, restaurantProvider, child) { + final favoriteRestaurants = restaurantProvider.getFavoriteRestaurants(); + + if (favoriteRestaurants.isNotEmpty) { + return ListView.builder( + itemCount: favoriteRestaurants.length, + itemBuilder: (context, index) { + final restaurant = favoriteRestaurants[index]; + return RestaurantCard(restaurant: restaurant); + }, + ); + } else { + return const Center(child: Text('No favorite restaurants yet.')); + } + }, + ); + } +} diff --git a/lib/widgets/home/restaurantCard.dart b/lib/widgets/home/restaurantCard.dart new file mode 100644 index 00000000..c67874c3 --- /dev/null +++ b/lib/widgets/home/restaurantCard.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:restaurantour/models/restaurant.dart'; +import 'package:restaurantour/screens/restaurantDetailScreen.dart'; +import 'package:restaurantour/widgets/restaurantDetail/ratingStars.dart'; +import 'package:restaurantour/widgets/restaurantDetail/restaurantStatus.dart'; + +class RestaurantCard extends StatelessWidget { + final Restaurant restaurant; + + const RestaurantCard({Key? key, required this.restaurant}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15.0), + ), + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + RestaurantDetailScreen(restaurant: restaurant), + ), + ); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (restaurant.photos != null && restaurant.photos!.isNotEmpty) + ClipRRect( + borderRadius: BorderRadius.circular(15.0), + child: Image.network( + restaurant.photos!.first, + width: 100.0, + height: 100.0, + fit: BoxFit.cover, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + restaurant.name ?? '', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18.0, + ), + ), + Row( + children: [ + Text(restaurant.price ?? ''), + const SizedBox(width: 8.0), + Text( + restaurant.categories?.first.title ?? '', + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + RatingStars(rating: restaurant.rating?.round() ?? 0), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: RestaurantStatus( + isOpen: restaurant.hours != null && restaurant.isOpen, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/myDivider.dart b/lib/widgets/myDivider.dart new file mode 100644 index 00000000..0f9d383c --- /dev/null +++ b/lib/widgets/myDivider.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class myDivider extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Container( + width: double.infinity, + height: 0.5, + color: const Color.fromARGB(255, 213, 213, 213), + ), + ); + } +} diff --git a/lib/widgets/restaurantDetail/addressWidget.dart b/lib/widgets/restaurantDetail/addressWidget.dart new file mode 100644 index 00000000..5863e1ab --- /dev/null +++ b/lib/widgets/restaurantDetail/addressWidget.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class AddressWidget extends StatelessWidget { + final String address; + + const AddressWidget({Key? key, required this.address}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Address', + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: 16.0, + ), + ), + const SizedBox(height: 8.0), + Text( + address, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18.0, + ), + ), + ], + ); + } +} diff --git a/lib/widgets/restaurantDetail/ratingStars.dart b/lib/widgets/restaurantDetail/ratingStars.dart new file mode 100644 index 00000000..6efab654 --- /dev/null +++ b/lib/widgets/restaurantDetail/ratingStars.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class RatingStars extends StatelessWidget { + final int rating; + + const RatingStars({Key? key, required this.rating}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: List.generate( + rating, + (index) => const Icon( + Icons.star, + color: Colors.amber, + ), + ), + ); + } +} diff --git a/lib/widgets/restaurantDetail/ratingWidget.dart b/lib/widgets/restaurantDetail/ratingWidget.dart new file mode 100644 index 00000000..31e3ebf5 --- /dev/null +++ b/lib/widgets/restaurantDetail/ratingWidget.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class RatingWidget extends StatelessWidget { + final double rating; + + const RatingWidget({Key? key, required this.rating}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Overall Rating', + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: 16.0, + ), + ), + const SizedBox(height: 8.0), + Row( + children: [ + Text( + rating.toString(), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24.0, + ), + ), + const SizedBox(width: 3.0), + const Icon( + Icons.star, + color: Colors.amber, + size: 16.0, + ), + ], + ), + ], + ); + } +} diff --git a/lib/widgets/restaurantDetail/restaurantStatus.dart b/lib/widgets/restaurantDetail/restaurantStatus.dart new file mode 100644 index 00000000..ac064549 --- /dev/null +++ b/lib/widgets/restaurantDetail/restaurantStatus.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class RestaurantStatus extends StatelessWidget { + final bool isOpen; + + const RestaurantStatus({Key? key, required this.isOpen}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + isOpen ? 'Open Now' : 'Closed', + style: const TextStyle( + fontStyle: FontStyle.italic, + color: Colors.black, + ), + ), + const SizedBox(width: 8.0), + Container( + width: 10.0, + height: 10.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isOpen ? Colors.green : Colors.red, + ), + ), + ], + ); + } +} diff --git a/lib/widgets/restaurantDetail/reviewWidget.dart b/lib/widgets/restaurantDetail/reviewWidget.dart new file mode 100644 index 00000000..3db4bb1f --- /dev/null +++ b/lib/widgets/restaurantDetail/reviewWidget.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:restaurantour/models/restaurant.dart'; +import 'package:restaurantour/widgets/restaurantDetail/ratingStars.dart'; + +class ReviewWidget extends StatelessWidget { + final Review review; + + const ReviewWidget({ + Key? key, + required this.review, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [RatingStars(rating: review.rating!)], + ), + const SizedBox(height: 8.0), + const Text( + 'Feel free to share your thoughts and experiences about this restaurant. Your feedback is valuable to us and helps other diners make informed choices. Unfortunately, the current repository doesnt include specific comments.', + style: TextStyle( + fontSize: 16.0, + ), + ), + const SizedBox(height: 8.0), + Row( + children: [ + review.user?.imageUrl != null + ? CircleAvatar( + backgroundImage: NetworkImage(review.user!.imageUrl!), + ) + : const Icon( + Icons.person, + size: 40.0, // Ajusta el tamaño según sea necesario + ), + const SizedBox(width: 8.0), + Text( + review.user?.name ?? '', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16.0, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index 83fbeae4..00000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,20 +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:restaurantour/main.dart'; - -void main() { - testWidgets('Page loads', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const Restaurantour()); - - // Verify that tests will run - expect(find.text('Fetch Restaurants'), findsOneWidget); - }); -} From 86d1853275fb944204f1351b84c28071be2afd40 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 20 Feb 2024 01:14:51 -0600 Subject: [PATCH 5/5] test: Add unit and widget tests for enhanced code coverage - Include unit tests for RestaurantProvider to ensure proper data fetching - Incorporate widget tests for AddressWidget to verify address rendering --- test/stateManagement/provider_test.dart | 17 +++++++++++++++++ test/widgets/address_test.dart | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 test/stateManagement/provider_test.dart create mode 100644 test/widgets/address_test.dart diff --git a/test/stateManagement/provider_test.dart b/test/stateManagement/provider_test.dart new file mode 100644 index 00000000..50d33d7e --- /dev/null +++ b/test/stateManagement/provider_test.dart @@ -0,0 +1,17 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurantour/providers/restaurantProvider.dart'; + +void main() { + group('RestaurantProvider', () { + test('fetchRestaurants should update restaurants list', () async { + final restaurantProvider = RestaurantProvider(); + + // Act + await restaurantProvider.fetchRestaurants(); + + // Assert + expect(restaurantProvider.restaurants, isNotNull); + expect(restaurantProvider.restaurants!.isNotEmpty, true); + }); + }); +} diff --git a/test/widgets/address_test.dart b/test/widgets/address_test.dart new file mode 100644 index 00000000..df2edb8b --- /dev/null +++ b/test/widgets/address_test.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurantour/widgets/restaurantDetail/addressWidget.dart'; + +void main() { + testWidgets('AddressWidget should render correctly', + (WidgetTester tester) async { + // Arrange + const address = '123 Main St, City, Country'; + await tester.pumpWidget( + const MaterialApp( + home: AddressWidget(address: address), + ), + ); + + // Assert + expect(find.text('Address'), findsOneWidget); + expect(find.text(address), findsOneWidget); + }); +}