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/.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..d5a84108 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ .pub-cache/ .pub/ /build/ +**/build/ +**/*.mocks.dart # Web related lib/generated_plugin_registrant.dart diff --git a/.vscode/launch.json b/.vscode/launch.json index 5d0f1d3e..376bcb43 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,9 +5,28 @@ "version": "0.2.0", "configurations": [ { - "name": "app", + "name": "app-dev", "request": "launch", - "type": "dart" - } + "type": "dart", + "program": "${workspaceFolder}\/lib\/main_dev.dart", + }, + { + "name": "app-prod", + "request": "launch", + "type": "dart", + "program": "${workspaceFolder}\/lib\/main_prod.dart", + }, + { + "name": "app-dev-windows", + "request": "launch", + "type": "dart", + "program": "${workspaceFolder}\\lib\\main_dev.dart", + }, + { + "name": "app-prod-windows", + "request": "launch", + "type": "dart", + "program": "${workspaceFolder}\\lib\\main_prod.dart", + }, ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index f285aa4a..5cc2e6d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,11 @@ { "dart.flutterSdkPath": ".fvm/flutter_sdk", - "search.exclude": { - "**/.fvm": true - }, - "files.watcherExclude": { - "**/.fvm": true - } + // Remove .fvm files from search + "search.exclude": { + "**/.fvm": true + }, + // Remove from file watching + "files.watcherExclude": { + "**/.fvm": true + } } \ No newline at end of file diff --git a/README.md b/README.md index 6c2ea7c9..effdcce4 100644 --- a/README.md +++ b/README.md @@ -181,3 +181,52 @@ Just create a new issue in this repo and we will respond and get back to you qui ## Review The coding challenge is a take-home test upon which we'll be conducting a thorough code review once complete. The review will consist of meeting some more of our mobile engineers and giving a review of the solution you have designed. Please be prepared to share your screen and run/demo the application to the group. During this process, the engineers will be asking questions. + +--- + +## Clear documentation on the structure and architecture of your application. + +I based the app structure on a mix of "package by layer" and "package by feature" called as "package by convenience" on [this book](https://www.kodeco.com/14214369-infinite-scrolling-pagination-in-flutter). + +Basically follow similar rules: +### 1 Features get their own package + +In this project a feature can be considered as any screen + +### 2 Features don't know each other + +The navigation responsability is designated to the [AppRouter](lib\app_router.dart) + +### 3 Repositories get their own package + +In this project, repository was understood as data sources and each one got their own package +- [YelpRepository](packages\yelp_repository\lib\src\yelp_repository.dart): Responsible to retrieve data from yelp api +- [LocalStorage](packages\local_storage\lib\src\local_storage.dart): Responsible to store data in the device + +### 4 Specialized packages + +Code that are meant for one specific purpose, but can be used in more than one place +- [Component Library](packages\component_library): Widgets that were made by design team and are meant to be used in more than one place +- [Domain Models](packages\domain_models): Data classes that will be used in the features (screens). Other packages that handle other data type must use the other data type only internally. + +## Tests + +The tests were made in the following files: +- [unit test](packages\features\restaurant_detail\test\unit\restaurant_detail_cubit_test.dart) +- [widget/flow test](test\widget_test.dart) + +## Environment setup +If you are using a mac you can: +- update the [.env_prod](assets\env\/.env_prod) file with the yelp api key + - the other file was created to showcase the ability to work on a different environment and do not impact production +- open the terminal at the project folder and execute: `make setup;` +- execute the project + - the vs code is already setup to launch the correct files, so you can find the runner at the top left on yours and choose `app-prod` and run it + - in android studio is necessary to setup the corrent files: + - Open your project in Android Studio. + - Go to Run -> Edit Configurations.... + - Click on the + button at the top left of the Run/Debug Configurations dialog to add a new configuration. + - Select Flutter. + - In the Name field, enter the name of the configuration (e.g., "app-dev"). + - In the Dart entrypoint field, enter the path to the Dart file you want to run (e.g., "lib/main_dev.dart"). + - Click OK to save the configuration. \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index e47cb81d..48e4ccd6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 34 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/android/build.gradle b/android/build.gradle index 24047dce..705e0375 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.23' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:4.2.2' 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.properties b/android/gradle.properties index 94adc3a3..40d43bcf 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,9 @@ -org.gradle.jvmargs=-Xmx1536M +# Fix made following instruction from: https://stackoverflow.com/a/69179997/10507546 +org.gradle.jvmargs=-Xmx1536M \ +--add-exports=java.base/sun.nio.ch=ALL-UNNAMED \ +--add-opens=java.base/java.lang=ALL-UNNAMED \ +--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \ +--add-opens=java.base/java.io=ALL-UNNAMED \ +--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED android.useAndroidX=true android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index bc6a58af..cfe88f69 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.6.1-all.zip diff --git a/assets/env/.env_dev b/assets/env/.env_dev new file mode 100644 index 00000000..5e98a47e --- /dev/null +++ b/assets/env/.env_dev @@ -0,0 +1 @@ +yelpApiKey=YOUR_API_KEY_HERE \ No newline at end of file diff --git a/assets/env/.env_prod b/assets/env/.env_prod new file mode 100644 index 00000000..5e98a47e --- /dev/null +++ b/assets/env/.env_prod @@ -0,0 +1 @@ +yelpApiKey=YOUR_API_KEY_HERE \ No newline at end of file diff --git a/assets/fonts/Lora/Lora-Bold.ttf b/assets/fonts/Lora/Lora-Bold.ttf new file mode 100644 index 00000000..530c9e11 Binary files /dev/null and b/assets/fonts/Lora/Lora-Bold.ttf differ diff --git a/assets/fonts/Lora/Lora-BoldItalic.ttf b/assets/fonts/Lora/Lora-BoldItalic.ttf new file mode 100644 index 00000000..6bcc76b0 Binary files /dev/null and b/assets/fonts/Lora/Lora-BoldItalic.ttf differ diff --git a/assets/fonts/Lora/Lora-Italic.ttf b/assets/fonts/Lora/Lora-Italic.ttf new file mode 100644 index 00000000..d93bc5fc Binary files /dev/null and b/assets/fonts/Lora/Lora-Italic.ttf differ diff --git a/assets/fonts/Lora/Lora-Medium.ttf b/assets/fonts/Lora/Lora-Medium.ttf new file mode 100644 index 00000000..85ca5a27 Binary files /dev/null and b/assets/fonts/Lora/Lora-Medium.ttf differ diff --git a/assets/fonts/Lora/Lora-MediumItalic.ttf b/assets/fonts/Lora/Lora-MediumItalic.ttf new file mode 100644 index 00000000..42208fbe Binary files /dev/null and b/assets/fonts/Lora/Lora-MediumItalic.ttf differ diff --git a/assets/fonts/Lora/Lora-Regular.ttf b/assets/fonts/Lora/Lora-Regular.ttf new file mode 100644 index 00000000..2b1dab45 Binary files /dev/null and b/assets/fonts/Lora/Lora-Regular.ttf differ diff --git a/assets/fonts/Lora/Lora-SemiBold.ttf b/assets/fonts/Lora/Lora-SemiBold.ttf new file mode 100644 index 00000000..3a7c6d75 Binary files /dev/null and b/assets/fonts/Lora/Lora-SemiBold.ttf differ diff --git a/assets/fonts/Lora/Lora-SemiBoldItalic.ttf b/assets/fonts/Lora/Lora-SemiBoldItalic.ttf new file mode 100644 index 00000000..16c8254d Binary files /dev/null and b/assets/fonts/Lora/Lora-SemiBoldItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-Bold.ttf b/assets/fonts/OpenSans/OpenSans-Bold.ttf new file mode 100644 index 00000000..98c74e0a Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-Bold.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-BoldItalic.ttf b/assets/fonts/OpenSans/OpenSans-BoldItalic.ttf new file mode 100644 index 00000000..85589283 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-BoldItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-ExtraBold.ttf b/assets/fonts/OpenSans/OpenSans-ExtraBold.ttf new file mode 100644 index 00000000..4eb33935 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-ExtraBold.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf b/assets/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf new file mode 100644 index 00000000..75789b42 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-Italic.ttf b/assets/fonts/OpenSans/OpenSans-Italic.ttf new file mode 100644 index 00000000..29ff6938 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-Italic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-Light.ttf b/assets/fonts/OpenSans/OpenSans-Light.ttf new file mode 100644 index 00000000..ea175cc3 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-Light.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-LightItalic.ttf b/assets/fonts/OpenSans/OpenSans-LightItalic.ttf new file mode 100644 index 00000000..edbfe0b7 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-LightItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-Medium.ttf b/assets/fonts/OpenSans/OpenSans-Medium.ttf new file mode 100644 index 00000000..ae716936 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-Medium.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-MediumItalic.ttf b/assets/fonts/OpenSans/OpenSans-MediumItalic.ttf new file mode 100644 index 00000000..6d1e09b2 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-MediumItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-Regular.ttf b/assets/fonts/OpenSans/OpenSans-Regular.ttf new file mode 100644 index 00000000..67803bb6 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-Regular.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-SemiBold.ttf b/assets/fonts/OpenSans/OpenSans-SemiBold.ttf new file mode 100644 index 00000000..e5ab4644 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-SemiBold.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf b/assets/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf new file mode 100644 index 00000000..cd23e154 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-Bold.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-Bold.ttf new file mode 100644 index 00000000..525397da Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-Bold.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-BoldItalic.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-BoldItalic.ttf new file mode 100644 index 00000000..d6c9bc0a Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-BoldItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-ExtraBold.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-ExtraBold.ttf new file mode 100644 index 00000000..3e600b9a Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-ExtraBold.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-ExtraBoldItalic.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-ExtraBoldItalic.ttf new file mode 100644 index 00000000..03936508 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-ExtraBoldItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-Italic.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-Italic.ttf new file mode 100644 index 00000000..fdf0a52e Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-Italic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-Light.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-Light.ttf new file mode 100644 index 00000000..459be7b4 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-Light.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-LightItalic.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-LightItalic.ttf new file mode 100644 index 00000000..5f05d08e Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-LightItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-Medium.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-Medium.ttf new file mode 100644 index 00000000..802200d2 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-Medium.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-MediumItalic.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-MediumItalic.ttf new file mode 100644 index 00000000..b43786bb Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-MediumItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-Regular.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-Regular.ttf new file mode 100644 index 00000000..a2a83ac6 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-Regular.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-SemiBold.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-SemiBold.ttf new file mode 100644 index 00000000..75bcd43c Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-SemiBold.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_Condensed-SemiBoldItalic.ttf b/assets/fonts/OpenSans/OpenSans_Condensed-SemiBoldItalic.ttf new file mode 100644 index 00000000..9fcaa52e Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_Condensed-SemiBoldItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-Bold.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-Bold.ttf new file mode 100644 index 00000000..dc927fc9 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-Bold.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-BoldItalic.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-BoldItalic.ttf new file mode 100644 index 00000000..7601048e Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-BoldItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-ExtraBold.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-ExtraBold.ttf new file mode 100644 index 00000000..d6864b1d Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-ExtraBold.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-ExtraBoldItalic.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-ExtraBoldItalic.ttf new file mode 100644 index 00000000..ec7ade58 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-ExtraBoldItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-Italic.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-Italic.ttf new file mode 100644 index 00000000..7fc00c82 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-Italic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-Light.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-Light.ttf new file mode 100644 index 00000000..5936496a Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-Light.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-LightItalic.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-LightItalic.ttf new file mode 100644 index 00000000..7ced21a7 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-LightItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-Medium.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-Medium.ttf new file mode 100644 index 00000000..25b1aadf Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-Medium.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-MediumItalic.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-MediumItalic.ttf new file mode 100644 index 00000000..fd87f785 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-MediumItalic.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-Regular.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-Regular.ttf new file mode 100644 index 00000000..5b09b35b Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-Regular.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-SemiBold.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-SemiBold.ttf new file mode 100644 index 00000000..fff3a372 Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-SemiBold.ttf differ diff --git a/assets/fonts/OpenSans/OpenSans_SemiCondensed-SemiBoldItalic.ttf b/assets/fonts/OpenSans/OpenSans_SemiCondensed-SemiBoldItalic.ttf new file mode 100644 index 00000000..3874205d Binary files /dev/null and b/assets/fonts/OpenSans/OpenSans_SemiCondensed-SemiBoldItalic.ttf differ diff --git a/assets/svg/app_got_lost.svg b/assets/svg/app_got_lost.svg new file mode 100644 index 00000000..1fe9c479 --- /dev/null +++ b/assets/svg/app_got_lost.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/svg/issue.svg b/assets/svg/issue.svg new file mode 100644 index 00000000..b77e9a18 --- /dev/null +++ b/assets/svg/issue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/svg/no_chef_appreciated_yet.svg b/assets/svg/no_chef_appreciated_yet.svg new file mode 100644 index 00000000..389f25a5 --- /dev/null +++ b/assets/svg/no_chef_appreciated_yet.svg @@ -0,0 +1 @@ +Chef \ No newline at end of file diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee8..ec97fc6f 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 592ceee8..c4855bfe 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 00000000..d7fc3bde --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,51 @@ +# 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| + # https://github.com/CocoaPods/CocoaPods/issues/12012#issuecomment-1653051943 + target.build_configurations.each do |config| + xcconfig_path = config.base_configuration_reference.real_path + xcconfig = File.read(xcconfig_path) + xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR") + File.open(xcconfig_path, "w") { |file| file << xcconfig_mod } + end + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 00000000..0d8b0171 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,43 @@ +PODS: + - Flutter (1.0.0) + - ObjectBox (1.9.2) + - objectbox_flutter_libs (0.0.1): + - Flutter + - ObjectBox (= 1.9.2) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite (0.0.3): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - Flutter (from `Flutter`) + - objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - sqflite (from `.symlinks/plugins/sqflite/darwin`) + +SPEC REPOS: + trunk: + - ObjectBox + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + objectbox_flutter_libs: + :path: ".symlinks/plugins/objectbox_flutter_libs/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + sqflite: + :path: ".symlinks/plugins/sqflite/darwin" + +SPEC CHECKSUMS: + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + ObjectBox: 46757a559717ab49b1cc0612eaff82b705acf394 + objectbox_flutter_libs: 103f3a273b594cb664255086a386740bc179f97d + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec + +PODFILE CHECKSUM: 42c92566d5242878ca32fa04ae2b7002854ec459 + +COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 73cf3f6d..f73f9b3b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,13 +8,26 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 27B2B69F4F528801D31E4221 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EC8FFB785943631E6DAAE7B /* Pods_RunnerTests.framework */; }; 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 */; }; + D45975ABB65572E60A7DE381 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 445083E4E4E1F31CC10529AA /* Pods_Runner.framework */; }; + FF4BDE782BEE83B7003AC0E6 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4BDE772BEE83B7003AC0E6 /* RunnerTests.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + FF4BDE792BEE83B7003AC0E6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,10 +44,16 @@ /* Begin PBXFileReference section */ 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 = ""; }; + 2EC8FFB785943631E6DAAE7B /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 445083E4E4E1F31CC10529AA /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 465FF8388E2F9BF083C3C86A /* 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 = ""; }; + 734002476802E6293C8EFA65 /* 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 = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7C21E76A019CE4C169FD3141 /* 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 = ""; }; + 965236B055DD3D800B1D5FB3 /* 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -42,6 +61,10 @@ 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 = ""; }; + BC2D277017879990F31E1D04 /* 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 = ""; }; + DF59BF3B908830DF6D981809 /* 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 = ""; }; + FF4BDE752BEE83B7003AC0E6 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FF4BDE772BEE83B7003AC0E6 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,12 +72,30 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D45975ABB65572E60A7DE381 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FF4BDE722BEE83B7003AC0E6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 27B2B69F4F528801D31E4221 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3D12DCF291EC50950AAC15CD /* Frameworks */ = { + isa = PBXGroup; + children = ( + 445083E4E4E1F31CC10529AA /* Pods_Runner.framework */, + 2EC8FFB785943631E6DAAE7B /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -71,7 +112,10 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + FF4BDE762BEE83B7003AC0E6 /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, + E41CCC2180D851D22E7B492C /* Pods */, + 3D12DCF291EC50950AAC15CD /* Frameworks */, ); sourceTree = ""; }; @@ -79,6 +123,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + FF4BDE752BEE83B7003AC0E6 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -98,6 +143,28 @@ path = Runner; sourceTree = ""; }; + E41CCC2180D851D22E7B492C /* Pods */ = { + isa = PBXGroup; + children = ( + DF59BF3B908830DF6D981809 /* Pods-Runner.debug.xcconfig */, + 465FF8388E2F9BF083C3C86A /* Pods-Runner.release.xcconfig */, + 734002476802E6293C8EFA65 /* Pods-Runner.profile.xcconfig */, + BC2D277017879990F31E1D04 /* Pods-RunnerTests.debug.xcconfig */, + 965236B055DD3D800B1D5FB3 /* Pods-RunnerTests.release.xcconfig */, + 7C21E76A019CE4C169FD3141 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + FF4BDE762BEE83B7003AC0E6 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + FF4BDE772BEE83B7003AC0E6 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -105,12 +172,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 60A65498CC328DE8E4CC5442 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + B070597A623337A9F80BF63A /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -121,12 +190,32 @@ productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; + FF4BDE742BEE83B7003AC0E6 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FF4BDE7E2BEE83B7003AC0E6 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 5CB8A8538F0A92267E4CEFA9 /* [CP] Check Pods Manifest.lock */, + FF4BDE712BEE83B7003AC0E6 /* Sources */, + FF4BDE722BEE83B7003AC0E6 /* Frameworks */, + FF4BDE732BEE83B7003AC0E6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + FF4BDE7A2BEE83B7003AC0E6 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = FF4BDE752BEE83B7003AC0E6 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1520; LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -134,6 +223,10 @@ CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; + FF4BDE742BEE83B7003AC0E6 = { + CreatedOnToolsVersion = 15.2; + TestTargetID = 97C146ED1CF9000F007C117D; + }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; @@ -150,6 +243,7 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + FF4BDE742BEE83B7003AC0E6 /* RunnerTests */, ); }; /* End PBXProject section */ @@ -166,6 +260,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FF4BDE732BEE83B7003AC0E6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -185,6 +286,50 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 5CB8A8538F0A92267E4CEFA9 /* [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; + }; + 60A65498CC328DE8E4CC5442 /* [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; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -200,6 +345,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + B070597A623337A9F80BF63A /* [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; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -212,8 +374,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FF4BDE712BEE83B7003AC0E6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FF4BDE782BEE83B7003AC0E6 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + FF4BDE7A2BEE83B7003AC0E6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = FF4BDE792BEE83B7003AC0E6 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -455,6 +633,99 @@ }; name = Release; }; + FF4BDE7B2BEE83B7003AC0E6 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BC2D277017879990F31E1D04 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = orgId.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + FF4BDE7C2BEE83B7003AC0E6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 965236B055DD3D800B1D5FB3 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = orgId.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + FF4BDE7D2BEE83B7003AC0E6 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7C21E76A019CE4C169FD3141 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = orgId.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -478,6 +749,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + FF4BDE7E2BEE83B7003AC0E6 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FF4BDE7B2BEE83B7003AC0E6 /* Debug */, + FF4BDE7C2BEE83B7003AC0E6 /* Release */, + FF4BDE7D2BEE83B7003AC0E6 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a6b826db..200649d9 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -37,6 +37,17 @@ + + + + + + diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..7736a6f0 --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,35 @@ +// +// RunnerTests.swift +// RunnerTests +// +// Created by Matheus Massula on 10/05/24. +// + +import XCTest + +final class RunnerTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/lib/app_router.dart b/lib/app_router.dart new file mode 100644 index 00000000..54f5cb6e --- /dev/null +++ b/lib/app_router.dart @@ -0,0 +1,56 @@ +import 'package:domain_models/domain_models.dart'; +import 'package:flutter/material.dart'; +import 'package:local_storage/local_storage.dart'; +import 'package:lost_app/lost_app.dart'; +import 'package:restaurant_detail/restaurant_detail.dart'; +import 'package:restaurant_list/restaurant_list.dart'; +import 'package:yelp_repository/yelp_repository.dart'; + +class AppRouter { + static Route generateRoute( + RouteSettings settings, { + required YelpRepository yelpRepository, + required LocalStorage localStorage, + }) { + final args = settings.arguments; + + switch (settings.name) { + case PageName.restaurantList: + return MaterialPageRoute( + builder: (context) => RestaurantListView( + yelpRepository: yelpRepository, + localStorage: localStorage, + onRestaurantTapped: (restaurant) => Navigator.of(context).pushNamed( + PageName.restaurantDetail, + arguments: restaurant, + ), + ), + ); + + case PageName.restaurantDetail: + final restaurant = args as Restaurant; + return MaterialPageRoute( + builder: (context) => RestaurantDetailView( + yelpRepository: yelpRepository, + localStorage: localStorage, + restaurant: restaurant, + ), + ); + + default: + return MaterialPageRoute( + builder: (context) => LostAppView( + onTapStartOver: () => Navigator.of(context).pushNamedAndRemoveUntil( + PageName.restaurantList, + (route) => false, + ), + ), + ); + } + } +} + +class PageName { + static const String restaurantList = 'restaurantList'; + static const String restaurantDetail = 'restaurantDetail'; +} diff --git a/lib/app_theme.dart b/lib/app_theme.dart new file mode 100644 index 00000000..9fa5be42 --- /dev/null +++ b/lib/app_theme.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; + +class AppTheme { + static ThemeData get lightTheme => themeData( + colorScheme: lightColorScheme, + focusColor: Colors.black.withOpacity(0.12), + ); + + static ThemeData themeData({ + required ColorScheme colorScheme, + required Color focusColor, + }) => + ThemeData( + useMaterial3: true, + colorScheme: colorScheme, + textTheme: _textTheme, + primaryColor: colorScheme.primary, + canvasColor: colorScheme.background, + scaffoldBackgroundColor: colorScheme.background, + highlightColor: Colors.transparent, + focusColor: focusColor, + tabBarTheme: TabBarTheme(labelColor: colorScheme.primary), + dividerTheme: const DividerThemeData( + color: Color(0xffeeeeee), + thickness: 1.0, + space: 0.0, + ), + ); + + static const ColorScheme lightColorScheme = ColorScheme( + primary: Color(0xff000000), + secondary: Color(0xFFEFF3F3), + background: Color(0xfffafafa), + surface: Colors.white, + onBackground: Colors.white, + error: Colors.red, + onError: Colors.redAccent, + onPrimary: Colors.black, + onSecondary: Colors.blue, + onSurface: Color(0xff000000), + brightness: Brightness.light, + ); + + // TODO: Figma file referenciate deprecated theme attributes, + // comunicate to design team + static final TextTheme _textTheme = TextTheme( + headline6: TextStyle( + fontFamily: loraFontFamily, + fontSize: 18.0, + height: calcFontHeight( + fontSize: 18.0, + desiredHeightInPixels: 24.0, + ), + fontWeight: bold, + ), + subtitle1: TextStyle( + fontFamily: loraFontFamily, + fontSize: 16.0, + fontWeight: medium, + height: calcFontHeight( + fontSize: 16.0, + desiredHeightInPixels: 24.0, + ), + ), + caption: TextStyle( + fontFamily: openSansFontFamily, + fontSize: 12.0, + fontWeight: regular, + height: calcFontHeight( + fontSize: 12.0, + desiredHeightInPixels: 20.0, + ), + ), + overline: TextStyle( + fontFamily: openSansFontFamily, + fontSize: 12.0, + fontWeight: regular, + fontStyle: FontStyle.italic, + height: calcFontHeight( + fontSize: 12.0, + desiredHeightInPixels: 12.0, + ), + ), + button: TextStyle( + fontFamily: openSansFontFamily, + fontSize: 14.0, + height: calcFontHeight( + fontSize: 14.0, + desiredHeightInPixels: 24.0, + ), + fontWeight: semiBold, + ), + bodyText1: TextStyle( + fontFamily: openSansFontFamily, + fontSize: 16.0, + height: calcFontHeight( + fontSize: 16.0, + desiredHeightInPixels: 24.0, + ), + fontWeight: regular, + ), + ); + + static double calcFontHeight({ + required double fontSize, + required double desiredHeightInPixels, + }) => + desiredHeightInPixels / fontSize; + + static const loraFontFamily = 'Lora'; + + static const openSansFontFamily = 'OpenSans'; + + /// FontWeight.w700 + static const bold = FontWeight.w700; + + /// FontWeight.w600 + static const semiBold = FontWeight.w600; + + /// FontWeight.w500 + static const medium = FontWeight.w500; + + /// FontWeight.w400 + static const regular = FontWeight.w400; +} diff --git a/lib/main.dart b/lib/main.dart deleted file mode 100644 index c6ce7473..00000000 --- a/lib/main.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:restaurantour/repositories/yelp_repository.dart'; - -void main() { - runApp(const Restaurantour()); -} - -class Restaurantour extends StatelessWidget { - // This widget is the root of your application. - const Restaurantour({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - 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'); - } - }, - ), - ], - ), - ), - ); - } -} diff --git a/lib/main_dev.dart b/lib/main_dev.dart new file mode 100644 index 00000000..783aaf5e --- /dev/null +++ b/lib/main_dev.dart @@ -0,0 +1,18 @@ +import 'package:domain_models/domain_models.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:get_it/get_it.dart'; +import 'package:restaurantour/start.dart'; + +void main() async { + await dotenv.load(fileName: "assets/env/.env_dev"); + GetIt.instance.registerSingleton( + const AppConfig( + environemnt: Environment.dev, + networkConfig: NetworkConfig( + yelpBaseUrl: 'mock.server.com', + yelpGraphqlEndpoint: 'mock-endpoint', + ), + ), + ); + start(); +} diff --git a/lib/main_prod.dart b/lib/main_prod.dart new file mode 100644 index 00000000..610b275d --- /dev/null +++ b/lib/main_prod.dart @@ -0,0 +1,18 @@ +import 'package:domain_models/domain_models.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:get_it/get_it.dart'; +import 'package:restaurantour/start.dart'; + +void main() async { + await dotenv.load(fileName: "assets/env/.env_prod"); + GetIt.instance.registerSingleton( + const AppConfig( + environemnt: Environment.prod, + networkConfig: NetworkConfig( + yelpBaseUrl: 'https://api.yelp.com', + yelpGraphqlEndpoint: 'v3/graphql', + ), + ), + ); + start(); +} diff --git a/lib/models/restaurant.dart b/lib/models/restaurant.dart deleted file mode 100644 index 87c7aab5..00000000 --- a/lib/models/restaurant.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'restaurant.g.dart'; - -@JsonSerializable() -class Category { - final String? alias; - final String? title; - - Category({ - this.alias, - this.title, - }); - - factory Category.fromJson(Map json) => - _$CategoryFromJson(json); - - Map toJson() => _$CategoryToJson(this); -} - -@JsonSerializable() -class Hours { - @JsonKey(name: 'is_open_now') - final bool? isOpenNow; - - const Hours({ - this.isOpenNow, - }); - - factory Hours.fromJson(Map json) => _$HoursFromJson(json); - - Map toJson() => _$HoursToJson(this); -} - -@JsonSerializable() -class User { - final String? id; - @JsonKey(name: 'image_url') - final String? imageUrl; - final String? name; - - const User({ - this.id, - this.imageUrl, - this.name, - }); - - factory User.fromJson(Map json) => _$UserFromJson(json); - - Map toJson() => _$UserToJson(this); -} - -@JsonSerializable() -class Review { - final String? id; - final int? rating; - final User? user; - - const Review({ - this.id, - this.rating, - this.user, - }); - - factory Review.fromJson(Map json) => _$ReviewFromJson(json); - - Map toJson() => _$ReviewToJson(this); -} - -@JsonSerializable() -class Location { - @JsonKey(name: 'formatted_address') - final String? formattedAddress; - - Location({ - this.formattedAddress, - }); - - factory Location.fromJson(Map json) => - _$LocationFromJson(json); - - Map toJson() => _$LocationToJson(this); -} - -@JsonSerializable() -class Restaurant { - final String? id; - final String? name; - final String? price; - final double? rating; - final List? photos; - final List? categories; - final List? hours; - final List? reviews; - final Location? location; - - const Restaurant({ - this.id, - this.name, - this.price, - this.rating, - this.photos, - this.categories, - this.hours, - this.reviews, - this.location, - }); - - factory Restaurant.fromJson(Map json) => - _$RestaurantFromJson(json); - - Map toJson() => _$RestaurantToJson(this); - - /// Use the first category for the category shown to the user - String get displayCategory { - if (categories != null && categories!.isNotEmpty) { - return categories!.first.title ?? ''; - } - return ''; - } - - /// Use the first image as the image shown to the user - String get heroImage { - if (photos != null && photos!.isNotEmpty) { - return photos!.first; - } - return ''; - } - - /// This logic is probably not correct in all cases but it is ok - /// for this application - bool get isOpen { - if (hours != null && hours!.isNotEmpty) { - return hours!.first.isOpenNow ?? false; - } - return false; - } -} - -@JsonSerializable() -class RestaurantQueryResult { - final int? total; - @JsonKey(name: 'business') - final List? restaurants; - - const RestaurantQueryResult({ - this.total, - this.restaurants, - }); - - factory RestaurantQueryResult.fromJson(Map json) => - _$RestaurantQueryResultFromJson(json); - - Map toJson() => _$RestaurantQueryResultToJson(this); -} diff --git a/lib/models/restaurant.g.dart b/lib/models/restaurant.g.dart deleted file mode 100644 index 3ed33f9a..00000000 --- a/lib/models/restaurant.g.dart +++ /dev/null @@ -1,109 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'restaurant.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Category _$CategoryFromJson(Map json) => Category( - alias: json['alias'] as String?, - title: json['title'] as String?, - ); - -Map _$CategoryToJson(Category instance) => { - 'alias': instance.alias, - 'title': instance.title, - }; - -Hours _$HoursFromJson(Map json) => Hours( - isOpenNow: json['is_open_now'] as bool?, - ); - -Map _$HoursToJson(Hours instance) => { - 'is_open_now': instance.isOpenNow, - }; - -User _$UserFromJson(Map json) => User( - id: json['id'] as String?, - imageUrl: json['image_url'] as String?, - name: json['name'] as String?, - ); - -Map _$UserToJson(User instance) => { - 'id': instance.id, - 'image_url': instance.imageUrl, - 'name': instance.name, - }; - -Review _$ReviewFromJson(Map json) => Review( - id: json['id'] as String?, - rating: json['rating'] as int?, - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - ); - -Map _$ReviewToJson(Review instance) => { - 'id': instance.id, - 'rating': instance.rating, - 'user': instance.user, - }; - -Location _$LocationFromJson(Map json) => Location( - formattedAddress: json['formatted_address'] as String?, - ); - -Map _$LocationToJson(Location instance) => { - 'formatted_address': instance.formattedAddress, - }; - -Restaurant _$RestaurantFromJson(Map json) => Restaurant( - id: json['id'] as String?, - name: json['name'] as String?, - price: json['price'] as String?, - rating: (json['rating'] as num?)?.toDouble(), - photos: - (json['photos'] as List?)?.map((e) => e as String).toList(), - categories: (json['categories'] as List?) - ?.map((e) => Category.fromJson(e as Map)) - .toList(), - hours: (json['hours'] as List?) - ?.map((e) => Hours.fromJson(e as Map)) - .toList(), - reviews: (json['reviews'] as List?) - ?.map((e) => Review.fromJson(e as Map)) - .toList(), - location: json['location'] == null - ? null - : Location.fromJson(json['location'] as Map), - ); - -Map _$RestaurantToJson(Restaurant instance) => - { - 'id': instance.id, - 'name': instance.name, - 'price': instance.price, - 'rating': instance.rating, - 'photos': instance.photos, - 'categories': instance.categories, - 'hours': instance.hours, - 'reviews': instance.reviews, - 'location': instance.location, - }; - -RestaurantQueryResult _$RestaurantQueryResultFromJson( - Map json) => - RestaurantQueryResult( - total: json['total'] as int?, - restaurants: (json['business'] as List?) - ?.map((e) => Restaurant.fromJson(e as Map)) - .toList(), - ); - -Map _$RestaurantQueryResultToJson( - RestaurantQueryResult instance) => - { - 'total': instance.total, - 'business': instance.restaurants, - }; diff --git a/lib/repositories/yelp_repository.dart b/lib/repositories/yelp_repository.dart deleted file mode 100644 index f251d7b4..00000000 --- a/lib/repositories/yelp_repository.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; -import 'package:restaurantour/models/restaurant.dart'; - -const _apiKey = ''; - -class YelpRepository { - late Dio dio; - - YelpRepository({ - @visibleForTesting Dio? dio, - }) : dio = dio ?? - Dio( - BaseOptions( - baseUrl: 'https://api.yelp.com', - headers: { - 'Authorization': 'Bearer $_apiKey', - 'Content-Type': 'application/graphql', - }, - ), - ); - - /// Returns a response in this shape - /// { - /// "data": { - /// "search": { - /// "total": 5056, - /// "business": [ - /// { - /// "id": "faPVqws-x-5k2CQKDNtHxw", - /// "name": "Yardbird Southern Table & Bar", - /// "price": "$$", - /// "rating": 4.5, - /// "photos": [ - /// "https:///s3-media4.fl.yelpcdn.com/bphoto/_zXRdYX4r1OBfF86xKMbDw/o.jpg" - /// ], - /// "reviews": [ - /// { - /// "id": "sjZoO8wcK1NeGJFDk5i82Q", - /// "rating": 5, - /// "user": { - /// "id": "BuBCkWFNT_O2dbSnBZvpoQ", - /// "image_url": "https:///s3-media2.fl.yelpcdn.com/photo/v8tbTjYaFvkzh1d7iE-pcQ/o.jpg", - /// "name": "Gina T." - /// } - /// }, - /// { - /// "id": "okpO9hfpxQXssbTZTKq9hA", - /// "rating": 5, - /// "user": { - /// "id": "0x9xu_b0Ct_6hG6jaxpztw", - /// "image_url": "https:///s3-media3.fl.yelpcdn.com/photo/gjz8X6tqE3e4praK4HfCiA/o.jpg", - /// "name": "Crystal L." - /// } - /// }, - /// ... - /// ] - /// } - /// } - /// - Future getRestaurants({int offset = 0}) async { - try { - final response = await dio.post>( - '/v3/graphql', - data: _getQuery(offset), - ); - return RestaurantQueryResult.fromJson(response.data!['data']['search']); - } catch (e) { - return null; - } - } - - String _getQuery(int offset) { - return ''' -query getRestaurants { - search(location: "Las Vegas", limit: 20, offset: $offset) { - total - business { - id - name - price - rating - photos - reviews { - id - rating - user { - id - image_url - name - } - } - categories { - title - alias - } - hours { - is_open_now - } - location { - formatted_address - } - } - } -} -'''; - } -} diff --git a/lib/start.dart b/lib/start.dart new file mode 100644 index 00000000..1ad697de --- /dev/null +++ b/lib/start.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:local_storage/local_storage.dart'; +import 'package:lost_app/lost_app.dart'; +import 'package:restaurant_detail/restaurant_detail.dart'; +import 'package:restaurant_list/restaurant_list.dart'; +import 'package:restaurantour/app_router.dart'; +import 'package:restaurantour/app_theme.dart'; +import 'package:yelp_repository/yelp_repository.dart'; + +void start() async { + WidgetsFlutterBinding.ensureInitialized(); + final localStorage = await LocalStorage.create(); + runApp( + Restaurantour( + yelpRepository: YelpRepository(), + localStorage: localStorage, + ), + ); +} + +class Restaurantour extends StatelessWidget { + final YelpRepository _yelpRepository; + final LocalStorage _localStorage; + + const Restaurantour({ + Key? key, + required YelpRepository yelpRepository, + required LocalStorage localStorage, + }) : _yelpRepository = yelpRepository, + _localStorage = localStorage, + super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'RestauranTour', + supportedLocales: const [Locale('en', '')], + localizationsDelegates: const [ + GlobalCupertinoLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + RestaurantListLocalizations.delegate, + RestaurantDetailLocalizations.delegate, + LostAppLocalizations.delegate, + ], + theme: AppTheme.lightTheme, + onGenerateRoute: (settings) => AppRouter.generateRoute( + settings, + yelpRepository: _yelpRepository, + localStorage: _localStorage, + ), + initialRoute: PageName.restaurantList, + ); + } +} diff --git a/makefile b/makefile new file mode 100644 index 00000000..9ba4b1c1 --- /dev/null +++ b/makefile @@ -0,0 +1,43 @@ +PACKAGES := $(wildcard packages/*) +FEATURES := $(wildcard packages/features/*) + +get: + fvm flutter pub get + for feature in $(FEATURES); do \ + cd $${feature} ; \ + echo "Updating dependencies on $${feature}" ; \ + fvm flutter pub get ; \ + cd ../../../ ; \ + done + for package in $(PACKAGES); do \ + cd $${package} ; \ + echo "Updating dependencies on $${package}" ; \ + fvm flutter pub get ; \ + cd ../../ ; \ + done + +clean: + fvm flutter clean + for feature in $(FEATURES); do \ + cd $${feature} ; \ + echo "Running clean on $${feature}" ; \ + fvm flutter clean ; \ + cd ../../../ ; \ + done + for package in $(PACKAGES); do \ + cd $${package} ; \ + echo "Running clean on $${package}" ; \ + fvm flutter clean ; \ + cd ../../ ; \ + done + +build-runner: + for package in $(PACKAGES); do \ + cd $${package} ; \ + echo "Running build-runner on $${package}" ; \ + fvm flutter pub run build_runner build --delete-conflicting-outputs ; \ + cd ../../ ; \ + done + +setup: + make clean; make get; make build-runner; \ No newline at end of file diff --git a/packages/component_library/analysis_options.yaml b/packages/component_library/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/component_library/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/component_library/lib/component_library.dart b/packages/component_library/lib/component_library.dart new file mode 100644 index 00000000..e7952c60 --- /dev/null +++ b/packages/component_library/lib/component_library.dart @@ -0,0 +1,2 @@ +export 'src/rating_star.dart'; +export 'src/availability_indicator.dart'; \ No newline at end of file diff --git a/packages/component_library/lib/src/availability_indicator.dart b/packages/component_library/lib/src/availability_indicator.dart new file mode 100644 index 00000000..8fec32fa --- /dev/null +++ b/packages/component_library/lib/src/availability_indicator.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class AvailabilityIndicator extends StatelessWidget { + final bool isOpen; + final String isOpenText; + final String isClosedText; + + const AvailabilityIndicator({ + super.key, + required this.isOpen, + required this.isOpenText, + required this.isClosedText, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Row( + children: [ + Text( + isOpen ? isOpenText : isClosedText, + style: theme.textTheme.overline, + ), + const SizedBox(width: 8.0), + Icon( + Icons.circle, + color: isOpen ? const Color(0xff5CD313) : const Color(0xffEA5E5E), + size: 8.0, + ), + ], + ); + } +} diff --git a/packages/component_library/lib/src/rating_star.dart b/packages/component_library/lib/src/rating_star.dart new file mode 100644 index 00000000..627ab5eb --- /dev/null +++ b/packages/component_library/lib/src/rating_star.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class RatingStar extends StatelessWidget { + const RatingStar({super.key}); + + @override + Widget build(BuildContext context) => const Icon( + Icons.star, + color: Color(0XFFFFB800), + size: 12.0, + ); +} diff --git a/packages/component_library/pubspec.lock b/packages/component_library/pubspec.lock new file mode 100644 index 00000000..349de023 --- /dev/null +++ b/packages/component_library/pubspec.lock @@ -0,0 +1,204 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + domain_models: + dependency: "direct main" + description: + path: "../domain_models" + relative: true + source: path + version: "0.0.1" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_dotenv: + dependency: transitive + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" +sdks: + dart: ">=3.1.5 <4.0.0" + flutter: ">=1.17.0" diff --git a/packages/component_library/pubspec.yaml b/packages/component_library/pubspec.yaml new file mode 100644 index 00000000..20741e66 --- /dev/null +++ b/packages/component_library/pubspec.yaml @@ -0,0 +1,58 @@ +name: component_library +description: A new Flutter package project. +publish_to: 'none' +version: 0.0.1 +homepage: + +environment: + sdk: '>=3.1.5 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + + domain_models: + path: ../domain_models + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/component_library/test/component_library_test.dart b/packages/component_library/test/component_library_test.dart new file mode 100644 index 00000000..f20e2261 --- /dev/null +++ b/packages/component_library/test/component_library_test.dart @@ -0,0 +1,12 @@ +// import 'package:flutter_test/flutter_test.dart'; + +// import 'package:component_library/component_library.dart'; + +// void main() { +// test('adds one to input values', () { +// final calculator = Calculator(); +// expect(calculator.addOne(2), 3); +// expect(calculator.addOne(-7), -6); +// expect(calculator.addOne(0), 1); +// }); +// } diff --git a/packages/domain_models/analysis_options.yaml b/packages/domain_models/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/domain_models/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/domain_models/lib/domain_models.dart b/packages/domain_models/lib/domain_models.dart new file mode 100644 index 00000000..ed94fd7a --- /dev/null +++ b/packages/domain_models/lib/domain_models.dart @@ -0,0 +1,7 @@ +export 'src/restaurant.dart'; +export 'src/review.dart'; +export 'src/user.dart'; +export 'src/exceptions/network_exception.dart'; +export 'src/app_config.dart'; +export 'src/environment.dart'; +export 'src/network_config.dart'; \ No newline at end of file diff --git a/packages/domain_models/lib/src/app_config.dart b/packages/domain_models/lib/src/app_config.dart new file mode 100644 index 00000000..b23be623 --- /dev/null +++ b/packages/domain_models/lib/src/app_config.dart @@ -0,0 +1,19 @@ +import 'package:domain_models/src/environment.dart'; +import 'package:domain_models/src/network_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class AppConfig extends Equatable { + final Environment environemnt; + final NetworkConfig networkConfig; + + String get yelpApiKey => dotenv.env['yelpApiKey']!; + + const AppConfig({ + required this.environemnt, + required this.networkConfig, + }); + + @override + List get props => [environemnt, networkConfig]; +} diff --git a/packages/domain_models/lib/src/environment.dart b/packages/domain_models/lib/src/environment.dart new file mode 100644 index 00000000..bd995154 --- /dev/null +++ b/packages/domain_models/lib/src/environment.dart @@ -0,0 +1,6 @@ +enum Environment { dev, prod } + +extension EnvironmentX on Environment { + bool get isDev => this == Environment.dev; + bool get isProd => this == Environment.prod; +} \ No newline at end of file diff --git a/packages/domain_models/lib/src/exceptions/network_exception.dart b/packages/domain_models/lib/src/exceptions/network_exception.dart new file mode 100644 index 00000000..12f2eceb --- /dev/null +++ b/packages/domain_models/lib/src/exceptions/network_exception.dart @@ -0,0 +1,6 @@ +class NetworkException implements Exception { + final int statusCode; + final String message; + + NetworkException({required this.statusCode, required this.message}); +} \ No newline at end of file diff --git a/packages/domain_models/lib/src/network_config.dart b/packages/domain_models/lib/src/network_config.dart new file mode 100644 index 00000000..3e9cffaf --- /dev/null +++ b/packages/domain_models/lib/src/network_config.dart @@ -0,0 +1,16 @@ +import 'package:equatable/equatable.dart'; + +class NetworkConfig extends Equatable { + final String yelpBaseUrl; + final String yelpGraphqlEndpoint; + + const NetworkConfig({ + required this.yelpBaseUrl, + required this.yelpGraphqlEndpoint, + }); + + Uri get yelpUri => Uri.parse('$yelpBaseUrl/$yelpGraphqlEndpoint'); + + @override + List get props => [yelpBaseUrl, yelpGraphqlEndpoint]; +} diff --git a/packages/domain_models/lib/src/restaurant.dart b/packages/domain_models/lib/src/restaurant.dart new file mode 100644 index 00000000..c340c2be --- /dev/null +++ b/packages/domain_models/lib/src/restaurant.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; + +class Restaurant extends Equatable { + final String? id; + final String? name; + final String? price; + final double? rating; + final String? photoUrl; + final String? category; + final bool? isOpen; + final String? address; + + const Restaurant({ + required this.id, + required this.name, + required this.price, + required this.rating, + required this.photoUrl, + required this.category, + required this.isOpen, + required this.address, + }); + + @override + List get props => [ + id, + name, + price, + rating, + photoUrl, + category, + isOpen, + address, + ]; +} diff --git a/packages/domain_models/lib/src/review.dart b/packages/domain_models/lib/src/review.dart new file mode 100644 index 00000000..97c7489d --- /dev/null +++ b/packages/domain_models/lib/src/review.dart @@ -0,0 +1,35 @@ +import 'package:domain_models/src/user.dart'; +import 'package:equatable/equatable.dart'; + +class Review extends Equatable { + final String? id; + final int? rating; + final String? text; + final User? user; + + const Review({ + this.id, + this.rating, + this.text, + this.user, + }); + + @override + List get props => [id, rating, text, user]; + + @override + bool? get stringify => true; + + Review copyWith({ + String? id, + int? rating, + String? text, + User? user, + }) => + Review( + id: id ?? this.id, + rating: rating ?? this.rating, + text: text ?? this.text, + user: user ?? this.user, + ); +} diff --git a/packages/domain_models/lib/src/user.dart b/packages/domain_models/lib/src/user.dart new file mode 100644 index 00000000..42320d5f --- /dev/null +++ b/packages/domain_models/lib/src/user.dart @@ -0,0 +1,17 @@ +import 'package:equatable/equatable.dart'; + +class User extends Equatable { + final String? name; + final String? imageUrl; + + const User({ + this.name, + this.imageUrl, + }); + + @override + List get props => [name, imageUrl]; + + @override + bool? get stringify => true; +} diff --git a/packages/domain_models/pubspec.lock b/packages/domain_models/pubspec.lock new file mode 100644 index 00000000..602b5c10 --- /dev/null +++ b/packages/domain_models/pubspec.lock @@ -0,0 +1,197 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" +sdks: + dart: ">=3.1.5 <4.0.0" + flutter: ">=1.17.0" diff --git a/packages/domain_models/pubspec.yaml b/packages/domain_models/pubspec.yaml new file mode 100644 index 00000000..7d2292de --- /dev/null +++ b/packages/domain_models/pubspec.yaml @@ -0,0 +1,56 @@ +name: domain_models +description: A new Flutter package project. +version: 0.0.1 +homepage: + +environment: + sdk: '>=3.1.5 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + equatable: ^2.0.5 + flutter_dotenv: ^5.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/domain_models/test/domain_models_test.dart b/packages/domain_models/test/domain_models_test.dart new file mode 100644 index 00000000..55264b17 --- /dev/null +++ b/packages/domain_models/test/domain_models_test.dart @@ -0,0 +1,12 @@ +// import 'package:flutter_test/flutter_test.dart'; + +// import 'package:domain_models/domain_models.dart'; + +// void main() { +// test('adds one to input values', () { +// final calculator = Calculator(); +// expect(calculator.addOne(2), 3); +// expect(calculator.addOne(-7), -6); +// expect(calculator.addOne(0), 1); +// }); +// } diff --git a/packages/features/lost_app/analysis_options.yaml b/packages/features/lost_app/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/features/lost_app/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/features/lost_app/l10n.yaml b/packages/features/lost_app/l10n.yaml new file mode 100644 index 00000000..5471f6f1 --- /dev/null +++ b/packages/features/lost_app/l10n.yaml @@ -0,0 +1,6 @@ +arb-dir: lib/src/l10n +template-arb-file: map_en.arb +output-localization-file: lost_app_localizations.dart +output-class: LostAppLocalizations +synthetic-package: false +nullable-getter: false \ No newline at end of file diff --git a/packages/features/lost_app/lib/lost_app.dart b/packages/features/lost_app/lib/lost_app.dart new file mode 100644 index 00000000..41eff5cf --- /dev/null +++ b/packages/features/lost_app/lib/lost_app.dart @@ -0,0 +1,2 @@ +export 'src/lost_app_view.dart'; +export 'src/l10n/lost_app_localizations.dart'; \ No newline at end of file diff --git a/packages/features/lost_app/lib/src/assets.dart b/packages/features/lost_app/lib/src/assets.dart new file mode 100644 index 00000000..263dd1e9 --- /dev/null +++ b/packages/features/lost_app/lib/src/assets.dart @@ -0,0 +1,3 @@ +class Assets { + static const String appGotLostPath = 'assets/svg/app_got_lost.svg'; +} \ No newline at end of file diff --git a/packages/features/lost_app/lib/src/l10n/lost_app_localizations.dart b/packages/features/lost_app/lib/src/l10n/lost_app_localizations.dart new file mode 100644 index 00000000..b0ac1a78 --- /dev/null +++ b/packages/features/lost_app/lib/src/l10n/lost_app_localizations.dart @@ -0,0 +1,136 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'lost_app_localizations_en.dart'; + +/// Callers can lookup localized strings with an instance of LostAppLocalizations +/// returned by `LostAppLocalizations.of(context)`. +/// +/// Applications need to include `LostAppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/lost_app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: LostAppLocalizations.localizationsDelegates, +/// supportedLocales: LostAppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the LostAppLocalizations.supportedLocales +/// property. +abstract class LostAppLocalizations { + LostAppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static LostAppLocalizations of(BuildContext context) { + return Localizations.of(context, LostAppLocalizations)!; + } + + static const LocalizationsDelegate delegate = _LostAppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en') + ]; + + /// No description provided for @appLostMessage. + /// + /// In en, this message translates to: + /// **'Oops! Looks like we got lost.'** + String get appLostMessage; + + /// No description provided for @startOverButton. + /// + /// In en, this message translates to: + /// **'Start over'** + String get startOverButton; +} + +class _LostAppLocalizationsDelegate extends LocalizationsDelegate { + const _LostAppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupLostAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => ['en'].contains(locale.languageCode); + + @override + bool shouldReload(_LostAppLocalizationsDelegate old) => false; +} + +LostAppLocalizations lookupLostAppLocalizations(Locale locale) { + + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': return LostAppLocalizationsEn(); + } + + throw FlutterError( + 'LostAppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); +} diff --git a/packages/features/lost_app/lib/src/l10n/lost_app_localizations_en.dart b/packages/features/lost_app/lib/src/l10n/lost_app_localizations_en.dart new file mode 100644 index 00000000..db8f3b7f --- /dev/null +++ b/packages/features/lost_app/lib/src/l10n/lost_app_localizations_en.dart @@ -0,0 +1,12 @@ +import 'lost_app_localizations.dart'; + +/// The translations for English (`en`). +class LostAppLocalizationsEn extends LostAppLocalizations { + LostAppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get appLostMessage => 'Oops! Looks like we got lost.'; + + @override + String get startOverButton => 'Start over'; +} diff --git a/packages/features/lost_app/lib/src/l10n/map_en.arb b/packages/features/lost_app/lib/src/l10n/map_en.arb new file mode 100644 index 00000000..57634882 --- /dev/null +++ b/packages/features/lost_app/lib/src/l10n/map_en.arb @@ -0,0 +1,4 @@ +{ + "appLostMessage": "Oops! Looks like we got lost.", + "startOverButton": "Start over" +} \ No newline at end of file diff --git a/packages/features/lost_app/lib/src/lost_app_view.dart b/packages/features/lost_app/lib/src/lost_app_view.dart new file mode 100644 index 00000000..d5450897 --- /dev/null +++ b/packages/features/lost_app/lib/src/lost_app_view.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:lost_app/src/assets.dart'; +import 'package:lost_app/src/l10n/lost_app_localizations.dart'; + +class LostAppView extends StatelessWidget { + final VoidCallback onTapStartOver; + const LostAppView({Key? key, required this.onTapStartOver}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = LostAppLocalizations.of(context); + + return Scaffold( + body: Center( + child: Column( + children: [ + const Spacer(), + SizedBox.square( + dimension: 200, + child: SvgPicture.asset(Assets.appGotLostPath), + ), + Text(l10n.appLostMessage), + const SizedBox(height: 20.0), + ElevatedButton( + onPressed: onTapStartOver, + child: Text(l10n.startOverButton), + ), + const Spacer(), + ], + ), + ), + ); + } +} diff --git a/packages/features/lost_app/pubspec.lock b/packages/features/lost_app/pubspec.lock new file mode 100644 index 00000000..9bf9994c --- /dev/null +++ b/packages/features/lost_app/pubspec.lock @@ -0,0 +1,282 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + url: "https://pub.dev" + source: hosted + version: "2.0.10+1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" + xml: + dependency: transitive + description: + name: xml + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + url: "https://pub.dev" + source: hosted + version: "6.3.0" +sdks: + dart: ">=3.1.5 <4.0.0" + flutter: ">=3.7.0-0" diff --git a/packages/features/lost_app/pubspec.yaml b/packages/features/lost_app/pubspec.yaml new file mode 100644 index 00000000..8e6f8f64 --- /dev/null +++ b/packages/features/lost_app/pubspec.yaml @@ -0,0 +1,59 @@ +name: lost_app +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +homepage: + +environment: + sdk: '>=3.1.5 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + intl: ^0.18.1 + flutter_svg: ^2.0.9 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/features/restaurant_detail/analysis_options.yaml b/packages/features/restaurant_detail/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/features/restaurant_detail/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/features/restaurant_detail/l10n.yaml b/packages/features/restaurant_detail/l10n.yaml new file mode 100644 index 00000000..3e0b5df2 --- /dev/null +++ b/packages/features/restaurant_detail/l10n.yaml @@ -0,0 +1,6 @@ +arb-dir: lib/src/l10n +template-arb-file: map_en.arb +output-localization-file: restaurant_detail_localizations.dart +output-class: RestaurantDetailLocalizations +synthetic-package: false +nullable-getter: false \ No newline at end of file diff --git a/packages/features/restaurant_detail/lib/restaurant_detail.dart b/packages/features/restaurant_detail/lib/restaurant_detail.dart new file mode 100644 index 00000000..4dbf8d87 --- /dev/null +++ b/packages/features/restaurant_detail/lib/restaurant_detail.dart @@ -0,0 +1,2 @@ +export 'src/restaurant_detail_view.dart'; +export 'src/l10n/restaurant_detail_localizations.dart'; \ No newline at end of file diff --git a/packages/features/restaurant_detail/lib/src/l10n/map_en.arb b/packages/features/restaurant_detail/lib/src/l10n/map_en.arb new file mode 100644 index 00000000..eeef2f1e --- /dev/null +++ b/packages/features/restaurant_detail/lib/src/l10n/map_en.arb @@ -0,0 +1,8 @@ +{ + "errorToFetchReviewsMessage": "Failed to fetch reviews", + "reviewsQuantity": "%s Reviews", + "addressSectionTitle": "Address", + "ratingSectionTitle": "Overall rating", + "isOpen": "Open Now", + "isClosed": "Closed" +} \ No newline at end of file diff --git a/packages/features/restaurant_detail/lib/src/l10n/restaurant_detail_localizations.dart b/packages/features/restaurant_detail/lib/src/l10n/restaurant_detail_localizations.dart new file mode 100644 index 00000000..1cf608a6 --- /dev/null +++ b/packages/features/restaurant_detail/lib/src/l10n/restaurant_detail_localizations.dart @@ -0,0 +1,160 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'restaurant_detail_localizations_en.dart'; + +/// Callers can lookup localized strings with an instance of RestaurantDetailLocalizations +/// returned by `RestaurantDetailLocalizations.of(context)`. +/// +/// Applications need to include `RestaurantDetailLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/restaurant_detail_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: RestaurantDetailLocalizations.localizationsDelegates, +/// supportedLocales: RestaurantDetailLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the RestaurantDetailLocalizations.supportedLocales +/// property. +abstract class RestaurantDetailLocalizations { + RestaurantDetailLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static RestaurantDetailLocalizations of(BuildContext context) { + return Localizations.of(context, RestaurantDetailLocalizations)!; + } + + static const LocalizationsDelegate delegate = _RestaurantDetailLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en') + ]; + + /// No description provided for @errorToFetchReviewsMessage. + /// + /// In en, this message translates to: + /// **'Failed to fetch reviews'** + String get errorToFetchReviewsMessage; + + /// No description provided for @reviewsQuantity. + /// + /// In en, this message translates to: + /// **'%s Reviews'** + String get reviewsQuantity; + + /// No description provided for @addressSectionTitle. + /// + /// In en, this message translates to: + /// **'Address'** + String get addressSectionTitle; + + /// No description provided for @ratingSectionTitle. + /// + /// In en, this message translates to: + /// **'Overall rating'** + String get ratingSectionTitle; + + /// No description provided for @isOpen. + /// + /// In en, this message translates to: + /// **'Open Now'** + String get isOpen; + + /// No description provided for @isClosed. + /// + /// In en, this message translates to: + /// **'Closed'** + String get isClosed; +} + +class _RestaurantDetailLocalizationsDelegate extends LocalizationsDelegate { + const _RestaurantDetailLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupRestaurantDetailLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => ['en'].contains(locale.languageCode); + + @override + bool shouldReload(_RestaurantDetailLocalizationsDelegate old) => false; +} + +RestaurantDetailLocalizations lookupRestaurantDetailLocalizations(Locale locale) { + + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': return RestaurantDetailLocalizationsEn(); + } + + throw FlutterError( + 'RestaurantDetailLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); +} diff --git a/packages/features/restaurant_detail/lib/src/l10n/restaurant_detail_localizations_en.dart b/packages/features/restaurant_detail/lib/src/l10n/restaurant_detail_localizations_en.dart new file mode 100644 index 00000000..d7f8f6c7 --- /dev/null +++ b/packages/features/restaurant_detail/lib/src/l10n/restaurant_detail_localizations_en.dart @@ -0,0 +1,24 @@ +import 'restaurant_detail_localizations.dart'; + +/// The translations for English (`en`). +class RestaurantDetailLocalizationsEn extends RestaurantDetailLocalizations { + RestaurantDetailLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get errorToFetchReviewsMessage => 'Failed to fetch reviews'; + + @override + String get reviewsQuantity => '%s Reviews'; + + @override + String get addressSectionTitle => 'Address'; + + @override + String get ratingSectionTitle => 'Overall rating'; + + @override + String get isOpen => 'Open Now'; + + @override + String get isClosed => 'Closed'; +} diff --git a/packages/features/restaurant_detail/lib/src/restaurant_detail_cubit.dart b/packages/features/restaurant_detail/lib/src/restaurant_detail_cubit.dart new file mode 100644 index 00000000..05cc4238 --- /dev/null +++ b/packages/features/restaurant_detail/lib/src/restaurant_detail_cubit.dart @@ -0,0 +1,67 @@ +import 'package:domain_models/domain_models.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:local_storage/local_storage.dart'; +import 'package:yelp_repository/yelp_repository.dart'; + +part 'restaurant_detail_state.dart'; + +class RestaurantDetailCubit extends Cubit { + final YelpRepository _yelpRepository; + final LocalStorage _localStorage; + final Restaurant _restaurant; + final int _pageSize = 20; + + RestaurantDetailCubit({ + required YelpRepository yelpRepository, + required LocalStorage localStorage, + required Restaurant restaurant, + }) : _yelpRepository = yelpRepository, + _localStorage = localStorage, + _restaurant = restaurant, + super(const RestaurantDetailState()); + + Future getReviews() async { + try { + if (_restaurant.id == null) { + throw Exception('Restaurant id is null'); + } + final result = await _yelpRepository.getReviews( + restaurantId: _restaurant.id!, + offset: state.pageIndex * _pageSize, + ); + emit( + state.copyWith( + pageStatus: PageStatus.success, + pageIndex: state.pageIndex + 1, + reviews: result, + isLastPage: result.length < _pageSize, + ), + ); + } catch (e) { + emit(state.copyWith(pageStatus: PageStatus.error)); + } + } + + void addFavoriteRestaurant() { + try { + _localStorage.addRestaurant(_restaurant); + } catch (e) { + // log error at loging tool (firebase, new relic,...) + } + } + + void removeFavoriteRestaurant() { + if (_restaurant.id != null) { + final isRestaurantDeleted = _localStorage.removeRestaurant( + _restaurant.id!, + ); + if (!isRestaurantDeleted) { + // log error at loging tool (firebase, new relic,...) + } + } + } + + Stream get isFavoriteRestaurant => + _localStorage.containsRestaurantListener(_restaurant.id ?? ''); +} diff --git a/packages/features/restaurant_detail/lib/src/restaurant_detail_state.dart b/packages/features/restaurant_detail/lib/src/restaurant_detail_state.dart new file mode 100644 index 00000000..34678c64 --- /dev/null +++ b/packages/features/restaurant_detail/lib/src/restaurant_detail_state.dart @@ -0,0 +1,50 @@ +part of 'restaurant_detail_cubit.dart'; + +class RestaurantDetailState extends Equatable { + final PageStatus pageStatus; + final int pageIndex; + final List reviews; + final bool isLastPage; + + const RestaurantDetailState({ + this.pageStatus = PageStatus.intial, + this.pageIndex = 0, + this.reviews = const [], + this.isLastPage = false, + }); + + @override + List get props => [pageStatus, pageIndex, reviews, isLastPage]; + + RestaurantDetailState copyWith({ + PageStatus? pageStatus, + int? pageIndex, + List? reviews, + bool? isLastPage, + }) => + RestaurantDetailState( + pageStatus: pageStatus ?? this.pageStatus, + pageIndex: pageIndex ?? this.pageIndex, + reviews: reviews ?? this.reviews, + isLastPage: isLastPage ?? this.isLastPage, + ); +} + +enum PageStatus { + /// The user has just opened the screen and the app must display the loading + /// to the entire page. + intial, + + /// The app has already fetched the data and the user can see the content. + success, + + /// The app has failed to fetch the data and the user must see an error + /// message. + error, +} + +extension PageStatusX on PageStatus { + bool get isInitial => this == PageStatus.intial; + bool get isSuccess => this == PageStatus.success; + bool get isError => this == PageStatus.error; +} diff --git a/packages/features/restaurant_detail/lib/src/restaurant_detail_view.dart b/packages/features/restaurant_detail/lib/src/restaurant_detail_view.dart new file mode 100644 index 00000000..3a261fc7 --- /dev/null +++ b/packages/features/restaurant_detail/lib/src/restaurant_detail_view.dart @@ -0,0 +1,282 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:component_library/component_library.dart'; +import 'package:domain_models/domain_models.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:local_storage/local_storage.dart'; +import 'package:restaurant_detail/restaurant_detail.dart'; +import 'package:restaurant_detail/src/restaurant_detail_cubit.dart'; +import 'package:restaurant_detail/src/review_tile.dart'; +import 'package:restaurant_detail/src/string_extension.dart'; +import 'package:yelp_repository/yelp_repository.dart'; + +class RestaurantDetailView extends StatelessWidget { + final YelpRepository _yelpRepository; + final LocalStorage _localStorage; + final Restaurant _restaurant; + + const RestaurantDetailView({ + super.key, + required YelpRepository yelpRepository, + required LocalStorage localStorage, + required Restaurant restaurant, + }) : _yelpRepository = yelpRepository, + _localStorage = localStorage, + _restaurant = restaurant; + + @override + Widget build(BuildContext context) => BlocProvider( + create: (context) => RestaurantDetailCubit( + yelpRepository: _yelpRepository, + localStorage: _localStorage, + restaurant: _restaurant, + ), + child: _RestaurantDetailView(restaurant: _restaurant), + ); +} + +class _RestaurantDetailView extends StatefulWidget { + final Restaurant restaurant; + const _RestaurantDetailView({ + super.key, + required this.restaurant, + }); + + @override + State<_RestaurantDetailView> createState() => _RestaurantDetailViewState(); +} + +class _RestaurantDetailViewState extends State<_RestaurantDetailView> { + final PagingController _pagingController = PagingController( + firstPageKey: 0, + ); + + @override + void initState() { + final cubit = context.read(); + _pagingController.addPageRequestListener((_) => cubit.getReviews()); + cubit.getReviews(); + super.initState(); + } + + @override + void dispose() { + _pagingController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final cubit = context.read(); + final l10n = RestaurantDetailLocalizations.of(context); + + return Scaffold( + appBar: AppBar( + title: Text(widget.restaurant.name ?? ''), + centerTitle: true, + actions: [ + StreamBuilder( + stream: cubit.isFavoriteRestaurant, + builder: (context, snapshot) { + final isFavorite = snapshot.hasData && (snapshot.data ?? false); + + return IconButton( + icon: Icon( + isFavorite ? Icons.favorite : Icons.favorite_border, + ), + onPressed: snapshot.hasError + ? null + : isFavorite + ? cubit.removeFavoriteRestaurant + : cubit.addFavoriteRestaurant, + ); + }), + ], + ), + body: BlocConsumer( + listener: (context, state) { + if (state.pageStatus.isError) { + ScaffoldMessenger.of(context) + ..removeCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text(l10n.errorToFetchReviewsMessage), + behavior: SnackBarBehavior.floating, + ), + ); + } else if (state.pageStatus.isSuccess) { + if (state.isLastPage) { + _pagingController.appendLastPage(state.reviews); + } else { + _pagingController.appendPage( + state.reviews, + state.pageIndex, + ); + } + } + }, + builder: (context, state) { + if (state.pageStatus.isInitial) { + return const Center(child: CircularProgressIndicator()); + } + return PagedListView( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + noItemsFoundIndicatorBuilder: (context) => + _buildRestaurantDetail(), + newPageErrorIndicatorBuilder: (context) => + _buildRestaurantDetail(), + itemBuilder: (context, review, index) { + if (index == 0) { + return Column( + children: [ + _buildRestaurantDetail(), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 16.0, + horizontal: 24.0, + ), + child: ReviewTile(review: review), + ), + ], + ); + } + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + children: [ + const Divider(), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: ReviewTile(review: review), + ), + ], + ), + ); + }, + ), + ); + }, + ), + ); + } + + Widget _buildRestaurantDetail() { + final l10n = RestaurantDetailLocalizations.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildImage(), + const SizedBox(height: 24.0), + ...[ + _buildCostCategoryAndAvailability(), + const Padding( + padding: EdgeInsets.symmetric(vertical: 24.0), + child: Divider(), + ), + ..._buildAddress(l10n), + const Padding( + padding: EdgeInsets.symmetric(vertical: 24.0), + child: Divider(), + ), + ..._buildOverallRating(l10n), + const Padding( + padding: EdgeInsets.symmetric(vertical: 24.0), + child: Divider(), + ), + Text( + l10n.reviewsQuantity.format( + ['${_pagingController.value.itemList?.length ?? 0}'], + ), + style: Theme.of(context).textTheme.caption, + ), + const SizedBox(height: 20.0), + ] + .map( + (widget) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: widget, + ), + ) + .toList(), + ], + ); + } + + Row _buildCostCategoryAndAvailability() { + final l10n = RestaurantDetailLocalizations.of(context); + final theme = Theme.of(context); + return Row( + children: [ + Text( + '${widget.restaurant.price ?? ''}, ${widget.restaurant.category ?? ''}', + style: theme.textTheme.caption, + ), + const Spacer(), + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: AvailabilityIndicator( + isOpen: widget.restaurant.isOpen ?? false, + isOpenText: l10n.isOpen, + isClosedText: l10n.isClosed, + ), + ) + ], + ); + } + + SizedBox _buildImage() => SizedBox.square( + dimension: MediaQuery.of(context).size.width, + child: Hero( + tag: 'restaurant_${widget.restaurant.id}', + child: CachedNetworkImage( + imageUrl: widget.restaurant.photoUrl ?? '', + errorWidget: (_, __, ___) => const Icon(Icons.restaurant), + fit: BoxFit.cover, + ), + ), + ); + + List _buildAddress(RestaurantDetailLocalizations l10n) { + final theme = Theme.of(context); + return [ + Text( + l10n.addressSectionTitle, + style: theme.textTheme.caption, + ), + const SizedBox(height: 24.0), + Text( + widget.restaurant.address ?? '', + style: theme.textTheme.button, + ) + ]; + } + + List _buildOverallRating(RestaurantDetailLocalizations l10n) { + final theme = Theme.of(context); + return [ + Text( + l10n.ratingSectionTitle, + style: theme.textTheme.caption, + ), + const SizedBox(height: 16.0), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + widget.restaurant.rating?.toStringAsFixed(2) ?? '', + style: theme.textTheme.headline6?.copyWith( + fontSize: 28.0, + height: 36.0/28.0, + ), + ), + const Padding( + padding: EdgeInsets.only(bottom: 4.0), + child: RatingStar(), + ), + ], + ), + ]; + } +} diff --git a/packages/features/restaurant_detail/lib/src/review_tile.dart b/packages/features/restaurant_detail/lib/src/review_tile.dart new file mode 100644 index 00000000..a456aa32 --- /dev/null +++ b/packages/features/restaurant_detail/lib/src/review_tile.dart @@ -0,0 +1,50 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:component_library/component_library.dart'; +import 'package:domain_models/domain_models.dart'; +import 'package:flutter/material.dart'; + +class ReviewTile extends StatelessWidget { + final Review review; + const ReviewTile({super.key, required this.review}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: + List.generate(review.rating ?? 0, (index) => const RatingStar()) + .toList(), + ), + const SizedBox(height: 8.0), + Text( + review.text ?? '', + maxLines: 3, + style: theme.textTheme.bodyText1, + ), + Row( + children: [ + SizedBox.square( + dimension: 40.0, + child: ClipRRect( + borderRadius: BorderRadius.circular(50.0), + child: CachedNetworkImage( + imageUrl: review.user?.imageUrl ?? '', + errorWidget: (_, __, ___) => const Icon(Icons.person), + fit: BoxFit.cover, + ), + ), + ), + const SizedBox(width: 8.0), + Text( + review.user?.name ?? '', + style: theme.textTheme.caption, + ), + ], + ), + ], + ); + } +} diff --git a/packages/features/restaurant_detail/lib/src/string_extension.dart b/packages/features/restaurant_detail/lib/src/string_extension.dart new file mode 100644 index 00000000..5e46efb4 --- /dev/null +++ b/packages/features/restaurant_detail/lib/src/string_extension.dart @@ -0,0 +1,13 @@ +extension StringExtension on String { + /// This will search every instace of [%s] on the given string and replace it by the given [values]. + /// If the qunatity of [%s] is bigger than the values length, the method will throw a error. + String format(List values) { + int count = 0; + final result = replaceAllMapped('%s', (match) { + final stringOnPosition = values[count]; + count++; + return stringOnPosition; + }); + return result; + } +} diff --git a/packages/features/restaurant_detail/pubspec.lock b/packages/features/restaurant_detail/pubspec.lock new file mode 100644 index 00000000..cfcb8d9d --- /dev/null +++ b/packages/features/restaurant_detail/pubspec.lock @@ -0,0 +1,958 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" + bloc_test: + dependency: "direct dev" + description: + name: bloc_test + sha256: "165a6ec950d9252ebe36dc5335f2e6eb13055f33d56db0eeb7642768849b43d2" + url: "https://pub.dev" + source: hosted + version: "9.1.7" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + url: "https://pub.dev" + source: hosted + version: "2.4.9" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + 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: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" + collection: + dependency: transitive + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + component_library: + dependency: "direct main" + description: + path: "../../component_library" + relative: true + source: path + version: "0.0.1" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb" + url: "https://pub.dev" + source: hosted + version: "1.6.4" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" + domain_models: + dependency: "direct main" + description: + path: "../../domain_models" + relative: true + source: path + version: "0.0.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: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + 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: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flat_buffers: + dependency: transitive + description: + name: flat_buffers + sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3" + url: "https://pub.dev" + source: hosted + version: "23.5.26" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + url: "https://pub.dev" + source: hosted + version: "8.1.5" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + flutter_dotenv: + dependency: transitive + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_staggered_grid_view: + dependency: transitive + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + get_it: + dependency: transitive + description: + name: get_it + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 + url: "https://pub.dev" + source: hosted + version: "7.6.7" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + http: + dependency: transitive + description: + name: http + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + url: "https://pub.dev" + source: hosted + version: "0.13.6" + http_interceptor: + dependency: transitive + description: + name: http_interceptor + sha256: "5f3dde028e67789339c250252c09510a74aff21ce16b06d07d9096bda6582bab" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + infinite_scroll_pagination: + dependency: "direct main" + description: + name: infinite_scroll_pagination + sha256: b68bce20752fcf36c7739e60de4175494f74e99e9a69b4dd2fe3a1dd07a7f16a + url: "https://pub.dev" + source: hosted + version: "4.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + local_storage: + dependency: "direct main" + description: + path: "../../local_storage" + relative: true + source: path + version: "0.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + 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: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + objectbox: + dependency: transitive + description: + name: objectbox + sha256: "9fb2810156e8f78d82ecf672c36a1aba2c1de16d7903675335e00e374bdc3ba8" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + objectbox_flutter_libs: + dependency: transitive + description: + name: objectbox_flutter_libs + sha256: dca86b2d1074110573b69cbd9afb6b67ab9d2c824704c6ac5187e546418baf9c + url: "https://pub.dev" + source: hosted + version: "2.5.1" + 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: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "51f0d2c554cfbc9d6a312ab35152fc77e2f0b758ce9f1a444a3a1e5b8f3c6b7f" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + 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" + 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: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + 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: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + sliver_tools: + dependency: transitive + description: + name: sliver_tools + sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6 + url: "https://pub.dev" + source: hosted + version: "0.2.12" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + 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: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + 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: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + 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: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: transitive + description: + name: test + sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" + url: "https://pub.dev" + source: hosted + version: "1.24.3" + test_api: + dependency: transitive + description: + name: test_api + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + test_core: + dependency: transitive + description: + name: test_core + sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" + url: "https://pub.dev" + source: hosted + version: "0.5.3" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" + url: "https://pub.dev" + source: hosted + version: "4.2.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + url: "https://pub.dev" + source: hosted + version: "11.10.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + 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" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + yelp_repository: + dependency: "direct main" + description: + path: "../../yelp_repository" + relative: true + source: path + version: "0.0.1" +sdks: + dart: ">=3.1.5 <4.0.0" + flutter: ">=3.13.0" diff --git a/packages/features/restaurant_detail/pubspec.yaml b/packages/features/restaurant_detail/pubspec.yaml new file mode 100644 index 00000000..f096b633 --- /dev/null +++ b/packages/features/restaurant_detail/pubspec.yaml @@ -0,0 +1,74 @@ +name: restaurant_detail +description: A new Flutter package project. +publish_to: none +version: 0.0.1 +homepage: + +environment: + sdk: '>=3.1.5 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + infinite_scroll_pagination: ^4.0.0 + flutter_bloc: ^8.1.5 + equatable: ^2.0.5 + cached_network_image: ^3.3.1 + flutter_localizations: + sdk: flutter + intl: ^0.18.1 + + yelp_repository: + path: ../../../packages/yelp_repository + local_storage: + path: ../../../packages/local_storage + domain_models: + path: ../../../packages/domain_models + component_library: + path: ../../../packages/component_library + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + bloc_test: ^9.1.7 + mockito: 5.4.4 + build_runner: ^2.4.8 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/features/restaurant_detail/test/unit/restaurant_detail_cubit_test.dart b/packages/features/restaurant_detail/test/unit/restaurant_detail_cubit_test.dart new file mode 100644 index 00000000..c4cd1229 --- /dev/null +++ b/packages/features/restaurant_detail/test/unit/restaurant_detail_cubit_test.dart @@ -0,0 +1,213 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:domain_models/domain_models.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:local_storage/local_storage.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:restaurant_detail/src/restaurant_detail_cubit.dart'; +import 'package:yelp_repository/yelp_repository.dart'; + +import 'restaurant_detail_cubit_test.mocks.dart'; + +@GenerateMocks([YelpRepository, LocalStorage]) +void main() { + final yelpRepository = MockYelpRepository(); + final localStorage = MockLocalStorage(); + const restaurant = Restaurant( + id: 'id', + name: 'name', + price: 'price', + rating: 4.3, + photoUrl: 'photoUrl', + category: 'category', + isOpen: true, + address: 'address', + ); + const review = Review( + id: 'id', + rating: 2, + text: 'text', + user: User( + name: 'name', + imageUrl: 'imageUrl', + ), + ); + + final reviews = List.generate( + 21, + (index) => review.copyWith(id: '$index'), + ); + + group('RestaurantDetailCubit.getReviews', () { + setUp(() { + when(yelpRepository.getReviews( + restaurantId: anyNamed('restaurantId'), + offset: anyNamed('offset'), + )).thenAnswer((_) => Future.value([review])); + }); + + tearDown(() { + reset(yelpRepository); + reset(localStorage); + resetMockitoState(); + }); + + blocTest( + 'getReviews success', + build: () => RestaurantDetailCubit( + yelpRepository: yelpRepository, + localStorage: localStorage, + restaurant: restaurant, + ), + act: (cubit) => cubit.getReviews(), + verify: (_) { + verify(yelpRepository.getReviews( + restaurantId: restaurant.id, + offset: 0, + )).called(1); + }, + expect: () => const [ + RestaurantDetailState( + pageStatus: PageStatus.success, + pageIndex: 1, + reviews: [review], + isLastPage: true, + ), + ], + ); + + blocTest( + 'getReviews more than one page success', + build: () => RestaurantDetailCubit( + yelpRepository: yelpRepository, + localStorage: localStorage, + restaurant: restaurant, + ), + setUp: () { + when(yelpRepository.getReviews( + restaurantId: anyNamed('restaurantId'), + offset: anyNamed('offset'), + )).thenAnswer((_) => Future.value(reviews)); + }, + act: (cubit) async { + await cubit.getReviews(); + await cubit.getReviews(); + return cubit; + }, + verify: (_) { + verify(yelpRepository.getReviews( + restaurantId: restaurant.id, + offset: 0, + )).called(1); + }, + expect: () => [ + RestaurantDetailState( + pageStatus: PageStatus.success, + pageIndex: 1, + reviews: reviews, + isLastPage: false, + ), + RestaurantDetailState( + pageStatus: PageStatus.success, + pageIndex: 2, + reviews: reviews, + isLastPage: false, + ), + ], + ); + + blocTest( + 'getReviews error', + setUp: () { + when(yelpRepository.getReviews( + restaurantId: anyNamed('restaurantId'), + offset: anyNamed('offset'), + )).thenThrow(Exception()); + }, + build: () => RestaurantDetailCubit( + yelpRepository: yelpRepository, + localStorage: localStorage, + restaurant: restaurant, + ), + act: (cubit) => cubit.getReviews(), + verify: (_) { + verify(yelpRepository.getReviews( + restaurantId: restaurant.id, + offset: 0, + )).called(1); + }, + expect: () => const [ + RestaurantDetailState( + pageStatus: PageStatus.error, + pageIndex: 0, + reviews: [], + isLastPage: false, + ), + ], + ); + }); + + group('RestaurantDetailCubit toggle FavoriteRestaurant', () { + setUp(() { + when(localStorage.addRestaurant(restaurant)).thenReturn(1); + when(localStorage.removeRestaurant(any)).thenReturn(true); + }); + + tearDown(() { + reset(yelpRepository); + reset(localStorage); + resetMockitoState(); + }); + + blocTest( + 'addFavoriteRestaurant', + build: () => RestaurantDetailCubit( + yelpRepository: yelpRepository, + localStorage: localStorage, + restaurant: restaurant, + ), + act: (cubit) => cubit.addFavoriteRestaurant(), + verify: (_) => verify(localStorage.addRestaurant(restaurant)).called(1), + ); + + blocTest( + 'removeFavoriteRestaurant', + build: () => RestaurantDetailCubit( + yelpRepository: yelpRepository, + localStorage: localStorage, + restaurant: restaurant, + ), + act: (cubit) => cubit.removeFavoriteRestaurant(), + verify: (_) => + verify(localStorage.removeRestaurant(restaurant.id)).called(1), + ); + }); + + group('isFavoriteRestaurant stream', () { + setUp(() => when(localStorage.containsRestaurantListener(restaurant.id!)) + .thenAnswer((_) => Stream.value(true))); + + tearDown(() { + reset(yelpRepository); + reset(localStorage); + resetMockitoState(); + }); + + // Errors with the stream are handled in the screen + test( + 'consume isFavoriteRestaurant stream', + () { + final cubit = RestaurantDetailCubit( + yelpRepository: yelpRepository, + localStorage: localStorage, + restaurant: restaurant, + ); + + expectLater( + cubit.isFavoriteRestaurant, + emitsInOrder([true]), + ); + }, + ); + }); +} diff --git a/packages/features/restaurant_list/analysis_options.yaml b/packages/features/restaurant_list/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/features/restaurant_list/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/features/restaurant_list/l10n.yaml b/packages/features/restaurant_list/l10n.yaml new file mode 100644 index 00000000..0ed683f2 --- /dev/null +++ b/packages/features/restaurant_list/l10n.yaml @@ -0,0 +1,6 @@ +arb-dir: lib/src/l10n +template-arb-file: map_en.arb +output-localization-file: restaurant_list_localizations.dart +output-class: RestaurantListLocalizations +synthetic-package: false +nullable-getter: false \ No newline at end of file diff --git a/packages/features/restaurant_list/lib/restaurant_list.dart b/packages/features/restaurant_list/lib/restaurant_list.dart new file mode 100644 index 00000000..ae9bc7c4 --- /dev/null +++ b/packages/features/restaurant_list/lib/restaurant_list.dart @@ -0,0 +1,3 @@ +export 'src/restaurant_list_view.dart'; +export 'src/restaurant_card.dart'; +export 'src/l10n/restaurant_list_localizations.dart'; \ No newline at end of file diff --git a/packages/features/restaurant_list/lib/src/assets.dart b/packages/features/restaurant_list/lib/src/assets.dart new file mode 100644 index 00000000..e9766c07 --- /dev/null +++ b/packages/features/restaurant_list/lib/src/assets.dart @@ -0,0 +1,4 @@ +class Assets { + static const String emptyFavoritesPath = 'assets/svg/no_chef_appreciated_yet.svg'; + static const String errorToFetchRestaurantsPath = 'assets/svg/issue.svg'; +} \ No newline at end of file diff --git a/packages/features/restaurant_list/lib/src/l10n/map_en.arb b/packages/features/restaurant_list/lib/src/l10n/map_en.arb new file mode 100644 index 00000000..e89ada8e --- /dev/null +++ b/packages/features/restaurant_list/lib/src/l10n/map_en.arb @@ -0,0 +1,10 @@ +{ + "appBarTitle": "RestauranTour", + "allRestaurantsTabTitle": "All Restaurants", + "favoritesTabTitle": "My Favorites", + "errorToFetchAllRestaurantsErrorMessage": "Failed to fetch restaurants", + "errorToFetchFavoritesErrorMessage": "Failed to fetch favorite restaurants", + "emptyFavoritesMessage": "No chef has achieved your heart yet", + "isOpen": "Open Now", + "isClosed": "Closed" +} \ No newline at end of file diff --git a/packages/features/restaurant_list/lib/src/l10n/restaurant_list_localizations.dart b/packages/features/restaurant_list/lib/src/l10n/restaurant_list_localizations.dart new file mode 100644 index 00000000..7f6262c7 --- /dev/null +++ b/packages/features/restaurant_list/lib/src/l10n/restaurant_list_localizations.dart @@ -0,0 +1,172 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'restaurant_list_localizations_en.dart'; + +/// Callers can lookup localized strings with an instance of RestaurantListLocalizations +/// returned by `RestaurantListLocalizations.of(context)`. +/// +/// Applications need to include `RestaurantListLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/restaurant_list_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: RestaurantListLocalizations.localizationsDelegates, +/// supportedLocales: RestaurantListLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the RestaurantListLocalizations.supportedLocales +/// property. +abstract class RestaurantListLocalizations { + RestaurantListLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static RestaurantListLocalizations of(BuildContext context) { + return Localizations.of(context, RestaurantListLocalizations)!; + } + + static const LocalizationsDelegate delegate = _RestaurantListLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en') + ]; + + /// No description provided for @appBarTitle. + /// + /// In en, this message translates to: + /// **'RestauranTour'** + String get appBarTitle; + + /// No description provided for @allRestaurantsTabTitle. + /// + /// In en, this message translates to: + /// **'All Restaurants'** + String get allRestaurantsTabTitle; + + /// No description provided for @favoritesTabTitle. + /// + /// In en, this message translates to: + /// **'My Favorites'** + String get favoritesTabTitle; + + /// No description provided for @errorToFetchAllRestaurantsErrorMessage. + /// + /// In en, this message translates to: + /// **'Failed to fetch restaurants'** + String get errorToFetchAllRestaurantsErrorMessage; + + /// No description provided for @errorToFetchFavoritesErrorMessage. + /// + /// In en, this message translates to: + /// **'Failed to fetch favorite restaurants'** + String get errorToFetchFavoritesErrorMessage; + + /// No description provided for @emptyFavoritesMessage. + /// + /// In en, this message translates to: + /// **'No chef has achieved your heart yet'** + String get emptyFavoritesMessage; + + /// No description provided for @isOpen. + /// + /// In en, this message translates to: + /// **'Open Now'** + String get isOpen; + + /// No description provided for @isClosed. + /// + /// In en, this message translates to: + /// **'Closed'** + String get isClosed; +} + +class _RestaurantListLocalizationsDelegate extends LocalizationsDelegate { + const _RestaurantListLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupRestaurantListLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => ['en'].contains(locale.languageCode); + + @override + bool shouldReload(_RestaurantListLocalizationsDelegate old) => false; +} + +RestaurantListLocalizations lookupRestaurantListLocalizations(Locale locale) { + + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': return RestaurantListLocalizationsEn(); + } + + throw FlutterError( + 'RestaurantListLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); +} diff --git a/packages/features/restaurant_list/lib/src/l10n/restaurant_list_localizations_en.dart b/packages/features/restaurant_list/lib/src/l10n/restaurant_list_localizations_en.dart new file mode 100644 index 00000000..fc7b00cf --- /dev/null +++ b/packages/features/restaurant_list/lib/src/l10n/restaurant_list_localizations_en.dart @@ -0,0 +1,30 @@ +import 'restaurant_list_localizations.dart'; + +/// The translations for English (`en`). +class RestaurantListLocalizationsEn extends RestaurantListLocalizations { + RestaurantListLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get appBarTitle => 'RestauranTour'; + + @override + String get allRestaurantsTabTitle => 'All Restaurants'; + + @override + String get favoritesTabTitle => 'My Favorites'; + + @override + String get errorToFetchAllRestaurantsErrorMessage => 'Failed to fetch restaurants'; + + @override + String get errorToFetchFavoritesErrorMessage => 'Failed to fetch favorite restaurants'; + + @override + String get emptyFavoritesMessage => 'No chef has achieved your heart yet'; + + @override + String get isOpen => 'Open Now'; + + @override + String get isClosed => 'Closed'; +} diff --git a/packages/features/restaurant_list/lib/src/restaurant_card.dart b/packages/features/restaurant_list/lib/src/restaurant_card.dart new file mode 100644 index 00000000..b53c43c2 --- /dev/null +++ b/packages/features/restaurant_list/lib/src/restaurant_card.dart @@ -0,0 +1,107 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:component_library/component_library.dart'; +import 'package:domain_models/domain_models.dart'; +import 'package:flutter/material.dart'; +import 'package:restaurant_list/restaurant_list.dart'; + +class RestaurantCard extends StatelessWidget { + final Function(Restaurant) onRestaurantTapped; + final Restaurant restaurant; + + const RestaurantCard({ + super.key, + required this.restaurant, + required this.onRestaurantTapped, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final l10n = RestaurantListLocalizations.of(context); + return Card( + color: theme.colorScheme.surface, + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: () => onRestaurantTapped(restaurant), + child: SizedBox( + height: 112.0, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(8.0, 8.0, 12.0, 8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(15.0), + child: SizedBox.square( + dimension: 88.0, + child: Hero( + tag: 'restaurant_${restaurant.id}', + child: CachedNetworkImage( + imageUrl: restaurant.photoUrl ?? '', + errorWidget: (_, __, ___) => + const Icon(Icons.restaurant), + fit: BoxFit.cover, + ), + ), + ), + ), + ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 8.0, right: 8.0), + child: Text( + restaurant.name ?? '', + maxLines: 2, + style: theme.textTheme.subtitle1, + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${restaurant.price ?? ''} ${restaurant.category ?? ''}', + style: theme.textTheme.caption, + ), + Row( + children: [ + ...List.generate( + restaurant.rating?.round() ?? 0, + (index) => const RatingStar(), + ).toList(), + ], + ), + ], + ), + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: AvailabilityIndicator( + isOpen: restaurant.isOpen ?? false, + isOpenText: l10n.isOpen, + isClosedText: l10n.isClosed, + ), + ), + ], + ), + ), + ], + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/packages/features/restaurant_list/lib/src/restaurant_list_cubit.dart b/packages/features/restaurant_list/lib/src/restaurant_list_cubit.dart new file mode 100644 index 00000000..99a6dee8 --- /dev/null +++ b/packages/features/restaurant_list/lib/src/restaurant_list_cubit.dart @@ -0,0 +1,41 @@ +import 'package:domain_models/domain_models.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:local_storage/local_storage.dart'; +import 'package:yelp_repository/yelp_repository.dart'; + +part 'restaurant_list_state.dart'; + +class RestaurantListCubit extends Cubit { + final YelpRepository _yelpRepository; + final LocalStorage _localStorage; + final int _pageSize = 20; + + RestaurantListCubit({ + required YelpRepository yelpRepository, + required LocalStorage localStorage, + }) : _yelpRepository = yelpRepository, + _localStorage = localStorage, + super(const RestaurantListState()); + + Future getRestaurants() async { + try { + final result = await _yelpRepository.getRestaurants( + offset: state.pageIndex * _pageSize, + ); + emit( + state.copyWith( + pageStatus: PageStatus.success, + pageIndex: state.pageIndex + 1, + restaurants: result, + isLastPage: result.length < _pageSize, + ), + ); + } catch (e) { + emit(state.copyWith(pageStatus: PageStatus.error)); + } + } + + Stream> get favoriteRestaurants => + _localStorage.restaurantListener; +} diff --git a/packages/features/restaurant_list/lib/src/restaurant_list_state.dart b/packages/features/restaurant_list/lib/src/restaurant_list_state.dart new file mode 100644 index 00000000..9292eb7a --- /dev/null +++ b/packages/features/restaurant_list/lib/src/restaurant_list_state.dart @@ -0,0 +1,50 @@ +part of 'restaurant_list_cubit.dart'; + +class RestaurantListState extends Equatable { + final PageStatus pageStatus; + final int pageIndex; + final List restaurants; + final bool isLastPage; + + const RestaurantListState({ + this.pageStatus = PageStatus.intial, + this.pageIndex = 0, + this.restaurants = const [], + this.isLastPage = false, + }); + + RestaurantListState copyWith({ + PageStatus? pageStatus, + int? pageIndex, + List? restaurants, + bool? isLastPage, + }) => + RestaurantListState( + pageStatus: pageStatus ?? this.pageStatus, + pageIndex: pageIndex ?? this.pageIndex, + restaurants: restaurants ?? this.restaurants, + isLastPage: isLastPage ?? this.isLastPage, + ); + + @override + List get props => [pageStatus, pageIndex, restaurants, isLastPage]; +} + +enum PageStatus { + /// The user has just opened the screen and the app must display the loading + /// to the entire page. + intial, + + /// The app has already fetched the data and the user can see the content. + success, + + /// The app has failed to fetch the data and the user must see an error + /// message. + error, +} + +extension PageStatusX on PageStatus { + bool get isInitial => this == PageStatus.intial; + bool get isSuccess => this == PageStatus.success; + bool get isError => this == PageStatus.error; +} diff --git a/packages/features/restaurant_list/lib/src/restaurant_list_view.dart b/packages/features/restaurant_list/lib/src/restaurant_list_view.dart new file mode 100644 index 00000000..7b4473ee --- /dev/null +++ b/packages/features/restaurant_list/lib/src/restaurant_list_view.dart @@ -0,0 +1,210 @@ +import 'package:domain_models/domain_models.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:local_storage/local_storage.dart'; +import 'package:restaurant_list/restaurant_list.dart'; +import 'package:restaurant_list/src/assets.dart'; +import 'package:restaurant_list/src/restaurant_list_cubit.dart'; +import 'package:yelp_repository/yelp_repository.dart'; + +const restaurantListAllRestaurantsTabKey = Key( + 'restaurantListAllRestaurantsTabKey', +); +const restaurantListFavoritesRestaurantsTabKey = Key( + 'restaurantListFavoritesRestaurantsTabKey', +); +const emptyFavoritesImageKey = Key('emptyFavoritesImageKey'); + +class RestaurantListView extends StatelessWidget { + final YelpRepository _yelpRepository; + final LocalStorage _localStorage; + final Function(Restaurant) onRestaurantTapped; + + const RestaurantListView({ + super.key, + required YelpRepository yelpRepository, + required LocalStorage localStorage, + required this.onRestaurantTapped, + }) : _localStorage = localStorage, + _yelpRepository = yelpRepository; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => RestaurantListCubit( + yelpRepository: _yelpRepository, + localStorage: _localStorage, + ), + child: _RestaurantListView(onRestaurantTapped: onRestaurantTapped), + ); + } +} + +class _RestaurantListView extends StatefulWidget { + final Function(Restaurant) onRestaurantTapped; + + const _RestaurantListView({super.key, required this.onRestaurantTapped}); + + @override + State<_RestaurantListView> createState() => _RestaurantListViewState(); +} + +class _RestaurantListViewState extends State<_RestaurantListView> { + final PagingController _pagingController = PagingController( + firstPageKey: 0, + ); + + @override + void initState() { + final cubit = context.read(); + _pagingController.addPageRequestListener((_) => cubit.getRestaurants()); + cubit.getRestaurants(); + super.initState(); + } + + @override + void dispose() { + _pagingController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = RestaurantListLocalizations.of(context); + final theme = Theme.of(context); + + return DefaultTabController( + initialIndex: 0, + length: 2, + child: Scaffold( + appBar: AppBar( + title: Text( + l10n.appBarTitle, + style: theme.textTheme.headline6, + ), + centerTitle: true, + bottom: TabBar( + tabs: [ + Tab( + key: restaurantListAllRestaurantsTabKey, + text: l10n.allRestaurantsTabTitle, + ), + Tab( + key: restaurantListFavoritesRestaurantsTabKey, + text: l10n.favoritesTabTitle, + ), + ], + ), + ), + body: TabBarView( + children: [ + _buildAllRestaurants(l10n), + _buildFavoriteRestaurants(l10n), + ], + ), + ), + ); + } + + Widget _buildFavoriteRestaurants(RestaurantListLocalizations l10n) => + BlocBuilder( + builder: (context, state) { + final cubit = context.read(); + return StreamBuilder>( + stream: cubit.favoriteRestaurants, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text(l10n.errorToFetchFavoritesErrorMessage), + ); + } + + if (snapshot.hasData) { + final restaurants = snapshot.data ?? []; + if (restaurants.isEmpty) { + return Column( + children: [ + const Spacer(), + SizedBox.square( + key: emptyFavoritesImageKey, + dimension: 200, + child: SvgPicture.asset( + Assets.emptyFavoritesPath, + ), + ), + Text(l10n.emptyFavoritesMessage), + const Spacer(), + ], + ); + } else { + return ListView.builder( + itemCount: restaurants.length, + itemBuilder: (context, index) { + final restaurant = restaurants[index]; + return _buildRestaurantCard(restaurant); + }, + ); + } + } + + return const Center(child: CircularProgressIndicator()); + }, + ); + }, + ); + + Widget _buildAllRestaurants(RestaurantListLocalizations l10n) => + BlocConsumer( + listener: (context, state) { + if (state.pageStatus.isSuccess) { + if (state.isLastPage) { + _pagingController.appendLastPage(state.restaurants); + } else { + _pagingController.appendPage( + state.restaurants, + state.pageIndex, + ); + } + } + }, + builder: (context, state) { + if (state.pageStatus.isInitial) { + return const Center(child: CircularProgressIndicator()); + } + + if (state.pageStatus.isError) { + return Column( + children: [ + const Spacer(), + SizedBox.square( + dimension: 200, + child: SvgPicture.asset( + Assets.errorToFetchRestaurantsPath, + ), + ), + Text(l10n.errorToFetchAllRestaurantsErrorMessage), + const Spacer(), + ], + ); + } + + return PagedListView( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, restaurant, index) => + _buildRestaurantCard(restaurant), + ), + ); + }, + ); + + Padding _buildRestaurantCard(Restaurant restaurant) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: RestaurantCard( + restaurant: restaurant, + onRestaurantTapped: widget.onRestaurantTapped, + ), + ); +} diff --git a/packages/features/restaurant_list/pubspec.lock b/packages/features/restaurant_list/pubspec.lock new file mode 100644 index 00000000..cd5a61d9 --- /dev/null +++ b/packages/features/restaurant_list/pubspec.lock @@ -0,0 +1,622 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + 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: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + component_library: + dependency: "direct main" + description: + path: "../../component_library" + relative: true + source: path + version: "0.0.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + domain_models: + dependency: "direct main" + description: + path: "../../domain_models" + relative: true + source: path + version: "0.0.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: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + 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: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flat_buffers: + dependency: transitive + description: + name: flat_buffers + sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3" + url: "https://pub.dev" + source: hosted + version: "23.5.26" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + url: "https://pub.dev" + source: hosted + version: "8.1.5" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + flutter_dotenv: + dependency: transitive + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_staggered_grid_view: + dependency: transitive + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + url: "https://pub.dev" + source: hosted + version: "2.0.9" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + get_it: + dependency: transitive + description: + name: get_it + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 + url: "https://pub.dev" + source: hosted + version: "7.6.7" + http: + dependency: transitive + description: + name: http + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + url: "https://pub.dev" + source: hosted + version: "0.13.6" + http_interceptor: + dependency: transitive + description: + name: http_interceptor + sha256: "5f3dde028e67789339c250252c09510a74aff21ce16b06d07d9096bda6582bab" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + infinite_scroll_pagination: + dependency: "direct main" + description: + name: infinite_scroll_pagination + sha256: b68bce20752fcf36c7739e60de4175494f74e99e9a69b4dd2fe3a1dd07a7f16a + url: "https://pub.dev" + source: hosted + version: "4.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + local_storage: + dependency: "direct main" + description: + path: "../../local_storage" + relative: true + source: path + version: "0.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + objectbox: + dependency: transitive + description: + name: objectbox + sha256: "9fb2810156e8f78d82ecf672c36a1aba2c1de16d7903675335e00e374bdc3ba8" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + objectbox_flutter_libs: + dependency: transitive + description: + name: objectbox_flutter_libs + sha256: dca86b2d1074110573b69cbd9afb6b67ab9d2c824704c6ac5187e546418baf9c + url: "https://pub.dev" + source: hosted + version: "2.5.1" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "51f0d2c554cfbc9d6a312ab35152fc77e2f0b758ce9f1a444a3a1e5b8f3c6b7f" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + 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: 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: "2.1.8" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + sliver_tools: + dependency: transitive + description: + name: sliver_tools + sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6 + url: "https://pub.dev" + source: hosted + version: "0.2.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + 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: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + 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: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" + url: "https://pub.dev" + source: hosted + version: "4.2.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "4ac59808bbfca6da38c99f415ff2d3a5d7ca0a6b4809c71d9cf30fba5daf9752" + url: "https://pub.dev" + source: hosted + version: "1.1.10+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: f3247e7ab0ec77dc759263e68394990edc608fb2b480b80db8aa86ed09279e33 + url: "https://pub.dev" + source: hosted + version: "1.1.10+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "18489bdd8850de3dd7ca8a34e0c446f719ec63e2bab2e7a8cc66a9028dd76c5a" + url: "https://pub.dev" + source: hosted + version: "1.1.10+1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" + 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: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + yelp_repository: + dependency: "direct main" + description: + path: "../../yelp_repository" + relative: true + source: path + version: "0.0.1" +sdks: + dart: ">=3.1.5 <4.0.0" + flutter: ">=3.13.0" diff --git a/packages/features/restaurant_list/pubspec.yaml b/packages/features/restaurant_list/pubspec.yaml new file mode 100644 index 00000000..751e8179 --- /dev/null +++ b/packages/features/restaurant_list/pubspec.yaml @@ -0,0 +1,71 @@ +name: restaurant_list +description: A new Flutter package project. +publish_to: 'none' +version: 0.0.1 +homepage: + +environment: + sdk: '>=3.1.5 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + infinite_scroll_pagination: ^4.0.0 + flutter_bloc: ^8.1.5 + equatable: ^2.0.5 + cached_network_image: ^3.3.1 + flutter_localizations: + sdk: flutter + intl: ^0.18.1 + flutter_svg: ^2.0.9 + + yelp_repository: + path: ../../../packages/yelp_repository + local_storage: + path: ../../../packages/local_storage + domain_models: + path: ../../../packages/domain_models + component_library: + path: ../../../packages/component_library + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/local_storage/analysis_options.yaml b/packages/local_storage/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/local_storage/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/local_storage/lib/local_storage.dart b/packages/local_storage/lib/local_storage.dart new file mode 100644 index 00000000..c537e256 --- /dev/null +++ b/packages/local_storage/lib/local_storage.dart @@ -0,0 +1 @@ +export 'src/local_storage.dart'; \ No newline at end of file diff --git a/packages/local_storage/lib/objectbox-model.json b/packages/local_storage/lib/objectbox-model.json new file mode 100644 index 00000000..3e04e766 --- /dev/null +++ b/packages/local_storage/lib/objectbox-model.json @@ -0,0 +1,72 @@ +{ + "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", + "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", + "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", + "entities": [ + { + "id": "1:1950204987501505857", + "lastPropertyId": "9:3751905172721504818", + "name": "Restaurant", + "properties": [ + { + "id": "1:2933840238743062448", + "name": "primaryKey", + "type": 6, + "flags": 1 + }, + { + "id": "2:684941778482449000", + "name": "id", + "type": 9 + }, + { + "id": "3:8672494060826536731", + "name": "name", + "type": 9 + }, + { + "id": "4:7113108354217126608", + "name": "price", + "type": 9 + }, + { + "id": "5:384872261558721555", + "name": "rating", + "type": 8 + }, + { + "id": "6:1679538245948052992", + "name": "photoUrl", + "type": 9 + }, + { + "id": "7:196308161532898365", + "name": "category", + "type": 9 + }, + { + "id": "8:6299227706741662120", + "name": "isOpen", + "type": 1 + }, + { + "id": "9:3751905172721504818", + "name": "address", + "type": 9 + } + ], + "relations": [] + } + ], + "lastEntityId": "1:1950204987501505857", + "lastIndexId": "0:0", + "lastRelationId": "0:0", + "lastSequenceId": "0:0", + "modelVersion": 5, + "modelVersionParserMinimum": 5, + "retiredEntityUids": [], + "retiredIndexUids": [], + "retiredPropertyUids": [], + "retiredRelationUids": [], + "version": 1 +} \ No newline at end of file diff --git a/packages/local_storage/lib/objectbox.g.dart b/packages/local_storage/lib/objectbox.g.dart new file mode 100644 index 00000000..2aabe7fa --- /dev/null +++ b/packages/local_storage/lib/objectbox.g.dart @@ -0,0 +1,239 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// This code was generated by ObjectBox. To update it run the generator again: +// With a Flutter package, run `flutter pub run build_runner build`. +// With a Dart package, run `dart run build_runner build`. +// See also https://docs.objectbox.io/getting-started#generate-objectbox-code + +// ignore_for_file: camel_case_types, depend_on_referenced_packages +// coverage:ignore-file + +import 'dart:typed_data'; + +import 'package:flat_buffers/flat_buffers.dart' as fb; +import 'package:objectbox/internal.dart' + as obx_int; // generated code can access "internal" functionality +import 'package:objectbox/objectbox.dart' as obx; +import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart'; + +import 'src/models/restaurant.dart'; + +export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file + +final _entities = [ + obx_int.ModelEntity( + id: const obx_int.IdUid(1, 1950204987501505857), + name: 'Restaurant', + lastPropertyId: const obx_int.IdUid(9, 3751905172721504818), + flags: 0, + properties: [ + obx_int.ModelProperty( + id: const obx_int.IdUid(1, 2933840238743062448), + name: 'primaryKey', + type: 6, + flags: 1), + obx_int.ModelProperty( + id: const obx_int.IdUid(2, 684941778482449000), + name: 'id', + type: 9, + flags: 0), + obx_int.ModelProperty( + id: const obx_int.IdUid(3, 8672494060826536731), + name: 'name', + type: 9, + flags: 0), + obx_int.ModelProperty( + id: const obx_int.IdUid(4, 7113108354217126608), + name: 'price', + type: 9, + flags: 0), + obx_int.ModelProperty( + id: const obx_int.IdUid(5, 384872261558721555), + name: 'rating', + type: 8, + flags: 0), + obx_int.ModelProperty( + id: const obx_int.IdUid(6, 1679538245948052992), + name: 'photoUrl', + type: 9, + flags: 0), + obx_int.ModelProperty( + id: const obx_int.IdUid(7, 196308161532898365), + name: 'category', + type: 9, + flags: 0), + obx_int.ModelProperty( + id: const obx_int.IdUid(8, 6299227706741662120), + name: 'isOpen', + type: 1, + flags: 0), + obx_int.ModelProperty( + id: const obx_int.IdUid(9, 3751905172721504818), + name: 'address', + type: 9, + flags: 0) + ], + relations: [], + backlinks: []) +]; + +/// Shortcut for [Store.new] that passes [getObjectBoxModel] and for Flutter +/// apps by default a [directory] using `defaultStoreDirectory()` from the +/// ObjectBox Flutter library. +/// +/// Note: for desktop apps it is recommended to specify a unique [directory]. +/// +/// See [Store.new] for an explanation of all parameters. +/// +/// For Flutter apps, also calls `loadObjectBoxLibraryAndroidCompat()` from +/// the ObjectBox Flutter library to fix loading the native ObjectBox library +/// on Android 6 and older. +Future openStore( + {String? directory, + int? maxDBSizeInKB, + int? maxDataSizeInKB, + int? fileMode, + int? maxReaders, + bool queriesCaseSensitiveDefault = true, + String? macosApplicationGroup}) async { + await loadObjectBoxLibraryAndroidCompat(); + return obx.Store(getObjectBoxModel(), + directory: directory ?? (await defaultStoreDirectory()).path, + maxDBSizeInKB: maxDBSizeInKB, + maxDataSizeInKB: maxDataSizeInKB, + fileMode: fileMode, + maxReaders: maxReaders, + queriesCaseSensitiveDefault: queriesCaseSensitiveDefault, + macosApplicationGroup: macosApplicationGroup); +} + +/// Returns the ObjectBox model definition for this project for use with +/// [Store.new]. +obx_int.ModelDefinition getObjectBoxModel() { + final model = obx_int.ModelInfo( + entities: _entities, + lastEntityId: const obx_int.IdUid(1, 1950204987501505857), + lastIndexId: const obx_int.IdUid(0, 0), + lastRelationId: const obx_int.IdUid(0, 0), + lastSequenceId: const obx_int.IdUid(0, 0), + retiredEntityUids: const [], + retiredIndexUids: const [], + retiredPropertyUids: const [], + retiredRelationUids: const [], + modelVersion: 5, + modelVersionParserMinimum: 5, + version: 1); + + final bindings = { + Restaurant: obx_int.EntityDefinition( + model: _entities[0], + toOneRelations: (Restaurant object) => [], + toManyRelations: (Restaurant object) => {}, + getId: (Restaurant object) => object.primaryKey, + setId: (Restaurant object, int id) { + object.primaryKey = id; + }, + objectToFB: (Restaurant object, fb.Builder fbb) { + final idOffset = + object.id == null ? null : fbb.writeString(object.id!); + final nameOffset = + object.name == null ? null : fbb.writeString(object.name!); + final priceOffset = + object.price == null ? null : fbb.writeString(object.price!); + final photoUrlOffset = object.photoUrl == null + ? null + : fbb.writeString(object.photoUrl!); + final categoryOffset = object.category == null + ? null + : fbb.writeString(object.category!); + final addressOffset = + object.address == null ? null : fbb.writeString(object.address!); + fbb.startTable(10); + fbb.addInt64(0, object.primaryKey); + fbb.addOffset(1, idOffset); + fbb.addOffset(2, nameOffset); + fbb.addOffset(3, priceOffset); + fbb.addFloat64(4, object.rating); + fbb.addOffset(5, photoUrlOffset); + fbb.addOffset(6, categoryOffset); + fbb.addBool(7, object.isOpen); + fbb.addOffset(8, addressOffset); + fbb.finish(fbb.endTable()); + return object.primaryKey; + }, + objectFromFB: (obx.Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + final primaryKeyParam = + const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); + final idParam = const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 6); + final nameParam = const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 8); + final priceParam = const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 10); + final ratingParam = const fb.Float64Reader() + .vTableGetNullable(buffer, rootOffset, 12); + final photoUrlParam = const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 14); + final categoryParam = const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 16); + final isOpenParam = + const fb.BoolReader().vTableGetNullable(buffer, rootOffset, 18); + final addressParam = const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 20); + final object = Restaurant( + primaryKey: primaryKeyParam, + id: idParam, + name: nameParam, + price: priceParam, + rating: ratingParam, + photoUrl: photoUrlParam, + category: categoryParam, + isOpen: isOpenParam, + address: addressParam); + + return object; + }) + }; + + return obx_int.ModelDefinition(model, bindings); +} + +/// [Restaurant] entity fields to define ObjectBox queries. +class Restaurant_ { + /// see [Restaurant.primaryKey] + static final primaryKey = + obx.QueryIntegerProperty(_entities[0].properties[0]); + + /// see [Restaurant.id] + static final id = + obx.QueryStringProperty(_entities[0].properties[1]); + + /// see [Restaurant.name] + static final name = + obx.QueryStringProperty(_entities[0].properties[2]); + + /// see [Restaurant.price] + static final price = + obx.QueryStringProperty(_entities[0].properties[3]); + + /// see [Restaurant.rating] + static final rating = + obx.QueryDoubleProperty(_entities[0].properties[4]); + + /// see [Restaurant.photoUrl] + static final photoUrl = + obx.QueryStringProperty(_entities[0].properties[5]); + + /// see [Restaurant.category] + static final category = + obx.QueryStringProperty(_entities[0].properties[6]); + + /// see [Restaurant.isOpen] + static final isOpen = + obx.QueryBooleanProperty(_entities[0].properties[7]); + + /// see [Restaurant.address] + static final address = + obx.QueryStringProperty(_entities[0].properties[8]); +} diff --git a/packages/local_storage/lib/src/dao/restaurant_dao.dart b/packages/local_storage/lib/src/dao/restaurant_dao.dart new file mode 100644 index 00000000..b042cecb --- /dev/null +++ b/packages/local_storage/lib/src/dao/restaurant_dao.dart @@ -0,0 +1,36 @@ +import 'package:local_storage/objectbox.g.dart'; +import 'package:local_storage/src/models/restaurant.dart'; + +class RestaurantDao { + final Store _store; + + RestaurantDao({required Store store}) : _store = store; + + int addRestaurant(Restaurant restaurant) => + _store.box().put(restaurant); + + bool removeRestaurant(String id) { + final restaurant = _store + .box() + .query(Restaurant_.id.equals(id)) + .build() + .findFirst(); + if (restaurant != null) { + return _store.box().remove(restaurant.primaryKey); + } + return false; + } + + Stream> get restaurantListener => _store + .box() + .query() + .watch(triggerImmediately: true) + .map((query) => query.find()); + + Stream constainsRestaurantListener(String id) => _store + .box() + .query(Restaurant_.id.equals(id)) + .watch(triggerImmediately: true) + .map((query) => query.find().isNotEmpty); + +} diff --git a/packages/local_storage/lib/src/local_storage.dart b/packages/local_storage/lib/src/local_storage.dart new file mode 100644 index 00000000..3a2fc289 --- /dev/null +++ b/packages/local_storage/lib/src/local_storage.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:local_storage/objectbox.g.dart'; +import 'package:local_storage/src/dao/restaurant_dao.dart'; +import 'package:local_storage/src/models/restaurant.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'package:domain_models/domain_models.dart' as domain; + +class LocalStorage { + late final Store _store; + late final RestaurantDao _restaurantDao; + + LocalStorage._create(this._store) { + _restaurantDao = RestaurantDao(store: _store); + } + + static Future create() async { + final docsDir = await getApplicationDocumentsDirectory(); + final store = await openStore( + directory: p.join(docsDir.path, "local-storage"), + ); + return LocalStorage._create(store); + } + + Stream> get restaurantListener => + _restaurantDao.restaurantListener.map( + (restaurants) => restaurants.map((e) => e.toDomainModel()).toList(), + ); + + int addRestaurant(domain.Restaurant restaurant) => + _restaurantDao.addRestaurant(Restaurant.fromDomainModel(restaurant)); + + bool removeRestaurant(String id) => _restaurantDao.removeRestaurant(id); + + Stream containsRestaurantListener(String id) => + _restaurantDao.constainsRestaurantListener(id); +} diff --git a/packages/local_storage/lib/src/models/restaurant.dart b/packages/local_storage/lib/src/models/restaurant.dart new file mode 100644 index 00000000..95824530 --- /dev/null +++ b/packages/local_storage/lib/src/models/restaurant.dart @@ -0,0 +1,52 @@ +import 'package:objectbox/objectbox.dart'; +import 'package:domain_models/domain_models.dart' as domain; + +@Entity() +class Restaurant { + /// Unique ID for ObjectBox + @Id() + int primaryKey; + + final String? id; + final String? name; + final String? price; + final double? rating; + final String? photoUrl; + final String? category; + final bool? isOpen; + final String? address; + + Restaurant({ + this.primaryKey = 0, + this.id, + this.name, + this.price, + this.rating, + this.photoUrl, + this.category, + this.isOpen, + this.address, + }); + + Restaurant.fromDomainModel(domain.Restaurant restaurant) + : primaryKey = 0, + id = restaurant.id, + name = restaurant.name, + price = restaurant.price, + rating = restaurant.rating, + photoUrl = restaurant.photoUrl, + category = restaurant.category, + isOpen = restaurant.isOpen, + address = restaurant.address; + + domain.Restaurant toDomainModel() => domain.Restaurant( + id: id, + name: name, + price: price, + rating: rating, + photoUrl: photoUrl, + category: category, + isOpen: isOpen, + address: address, + ); +} diff --git a/packages/local_storage/pubspec.lock b/packages/local_storage/pubspec.lock new file mode 100644 index 00000000..50cce04d --- /dev/null +++ b/packages/local_storage/pubspec.lock @@ -0,0 +1,668 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + url: "https://pub.dev" + source: hosted + version: "2.4.9" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" + collection: + dependency: transitive + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + cryptography: + dependency: transitive + description: + name: cryptography + sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + domain_models: + dependency: "direct main" + description: + path: "../domain_models" + relative: true + source: path + version: "0.0.1" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + 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: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flat_buffers: + dependency: transitive + description: + name: flat_buffers + sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3" + url: "https://pub.dev" + source: hosted + version: "23.5.26" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_dotenv: + dependency: transitive + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + 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: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + objectbox: + dependency: "direct main" + description: + name: objectbox + sha256: "9fb2810156e8f78d82ecf672c36a1aba2c1de16d7903675335e00e374bdc3ba8" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + objectbox_flutter_libs: + dependency: "direct main" + description: + name: objectbox_flutter_libs + sha256: dca86b2d1074110573b69cbd9afb6b67ab9d2c824704c6ac5187e546418baf9c + url: "https://pub.dev" + source: hosted + version: "2.5.1" + objectbox_generator: + dependency: "direct dev" + description: + name: objectbox_generator + sha256: c22c59c27edb90e709da00f0b2e788a5774a4cdce12d393d117a39500877cfb7 + url: "https://pub.dev" + source: hosted + version: "2.5.1" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: "direct main" + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "51f0d2c554cfbc9d6a312ab35152fc77e2f0b758ce9f1a444a3a1e5b8f3c6b7f" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + 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" + 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: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.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" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.1.5 <4.0.0" + flutter: ">=3.13.0" diff --git a/packages/local_storage/pubspec.yaml b/packages/local_storage/pubspec.yaml new file mode 100644 index 00000000..f6da4c4d --- /dev/null +++ b/packages/local_storage/pubspec.yaml @@ -0,0 +1,64 @@ +name: local_storage +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +homepage: + +environment: + sdk: '>=3.1.5 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + objectbox: ^2.5.1 + objectbox_flutter_libs: ^2.5.1 + path: ^1.8.3 + path_provider: ^2.1.3 + + domain_models: + path: ../domain_models + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + build_runner: ^2.4.9 + objectbox_generator: ^2.5.1 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/local_storage/test/local_storage_test.dart b/packages/local_storage/test/local_storage_test.dart new file mode 100644 index 00000000..bb74a270 --- /dev/null +++ b/packages/local_storage/test/local_storage_test.dart @@ -0,0 +1,12 @@ +// import 'package:flutter_test/flutter_test.dart'; + +// import 'package:local_storage/local_storage.dart'; + +// void main() { +// test('adds one to input values', () { +// final calculator = Calculator(); +// expect(calculator.addOne(2), 3); +// expect(calculator.addOne(-7), -6); +// expect(calculator.addOne(0), 1); +// }); +// } diff --git a/packages/yelp_repository/analysis_options.yaml b/packages/yelp_repository/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/yelp_repository/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/yelp_repository/lib/src/models/category.dart b/packages/yelp_repository/lib/src/models/category.dart new file mode 100644 index 00000000..9d49619e --- /dev/null +++ b/packages/yelp_repository/lib/src/models/category.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'category.g.dart'; + +@JsonSerializable() +class Category { + final String? alias; + final String? title; + + Category({ + this.alias, + this.title, + }); + + factory Category.fromJson(Map json) => + _$CategoryFromJson(json); + + Map toJson() => _$CategoryToJson(this); +} diff --git a/packages/yelp_repository/lib/src/models/category.g.dart b/packages/yelp_repository/lib/src/models/category.g.dart new file mode 100644 index 00000000..6965921b --- /dev/null +++ b/packages/yelp_repository/lib/src/models/category.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'category.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Category _$CategoryFromJson(Map json) => Category( + alias: json['alias'] as String?, + title: json['title'] as String?, + ); + +Map _$CategoryToJson(Category instance) => { + 'alias': instance.alias, + 'title': instance.title, + }; diff --git a/packages/yelp_repository/lib/src/models/hours.dart b/packages/yelp_repository/lib/src/models/hours.dart new file mode 100644 index 00000000..6913a26b --- /dev/null +++ b/packages/yelp_repository/lib/src/models/hours.dart @@ -0,0 +1,17 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'hours.g.dart'; + +@JsonSerializable() +class Hours { + @JsonKey(name: 'is_open_now') + final bool? isOpenNow; + + const Hours({ + this.isOpenNow, + }); + + factory Hours.fromJson(Map json) => _$HoursFromJson(json); + + Map toJson() => _$HoursToJson(this); +} diff --git a/packages/yelp_repository/lib/src/models/hours.g.dart b/packages/yelp_repository/lib/src/models/hours.g.dart new file mode 100644 index 00000000..334d39b1 --- /dev/null +++ b/packages/yelp_repository/lib/src/models/hours.g.dart @@ -0,0 +1,15 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'hours.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Hours _$HoursFromJson(Map json) => Hours( + isOpenNow: json['is_open_now'] as bool?, + ); + +Map _$HoursToJson(Hours instance) => { + 'is_open_now': instance.isOpenNow, + }; diff --git a/packages/yelp_repository/lib/src/models/location.dart b/packages/yelp_repository/lib/src/models/location.dart new file mode 100644 index 00000000..d895e600 --- /dev/null +++ b/packages/yelp_repository/lib/src/models/location.dart @@ -0,0 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'location.g.dart'; + +@JsonSerializable() +class Location { + @JsonKey(name: 'formatted_address') + final String? formattedAddress; + + Location({ + this.formattedAddress, + }); + + factory Location.fromJson(Map json) => + _$LocationFromJson(json); + + Map toJson() => _$LocationToJson(this); +} diff --git a/packages/yelp_repository/lib/src/models/location.g.dart b/packages/yelp_repository/lib/src/models/location.g.dart new file mode 100644 index 00000000..0504a7c6 --- /dev/null +++ b/packages/yelp_repository/lib/src/models/location.g.dart @@ -0,0 +1,15 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'location.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Location _$LocationFromJson(Map json) => Location( + formattedAddress: json['formatted_address'] as String?, + ); + +Map _$LocationToJson(Location instance) => { + 'formatted_address': instance.formattedAddress, + }; diff --git a/packages/yelp_repository/lib/src/models/restaurant.dart b/packages/yelp_repository/lib/src/models/restaurant.dart new file mode 100644 index 00000000..eb607c27 --- /dev/null +++ b/packages/yelp_repository/lib/src/models/restaurant.dart @@ -0,0 +1,71 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:yelp_repository/src/models/category.dart'; +import 'package:yelp_repository/src/models/hours.dart'; +import 'package:yelp_repository/src/models/location.dart'; +import 'package:domain_models/domain_models.dart' as domain; + +part 'restaurant.g.dart'; + +@JsonSerializable() +class Restaurant { + final String? id; + final String? name; + final String? price; + final double? rating; + final List? photos; + final List? categories; + final List? hours; + final Location? location; + + const Restaurant({ + this.id, + this.name, + this.price, + this.rating, + this.photos, + this.categories, + this.hours, + this.location, + }); + + factory Restaurant.fromJson(Map json) => + _$RestaurantFromJson(json); + + Map toJson() => _$RestaurantToJson(this); + + /// Use the first category for the category shown to the user + String get displayCategory { + if (categories != null && categories!.isNotEmpty) { + return categories!.first.title ?? ''; + } + return ''; + } + + /// Use the first image as the image shown to the user + String get heroImage { + if (photos != null && photos!.isNotEmpty) { + return photos!.first; + } + return ''; + } + + /// This logic is probably not correct in all cases but it is ok + /// for this application + bool get isOpen { + if (hours != null && hours!.isNotEmpty) { + return hours!.first.isOpenNow ?? false; + } + return false; + } + + domain.Restaurant toDomain() => domain.Restaurant( + id: id, + name: name, + price: price, + rating: rating, + photoUrl: heroImage, + category: displayCategory, + isOpen: isOpen, + address: location?.formattedAddress, + ); +} diff --git a/packages/yelp_repository/lib/src/models/restaurant.g.dart b/packages/yelp_repository/lib/src/models/restaurant.g.dart new file mode 100644 index 00000000..313be8ce --- /dev/null +++ b/packages/yelp_repository/lib/src/models/restaurant.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'restaurant.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Restaurant _$RestaurantFromJson(Map json) => Restaurant( + id: json['id'] as String?, + name: json['name'] as String?, + price: json['price'] as String?, + rating: (json['rating'] as num?)?.toDouble(), + photos: + (json['photos'] as List?)?.map((e) => e as String).toList(), + categories: (json['categories'] as List?) + ?.map((e) => Category.fromJson(e as Map)) + .toList(), + hours: (json['hours'] as List?) + ?.map((e) => Hours.fromJson(e as Map)) + .toList(), + location: json['location'] == null + ? null + : Location.fromJson(json['location'] as Map), + ); + +Map _$RestaurantToJson(Restaurant instance) => + { + 'id': instance.id, + 'name': instance.name, + 'price': instance.price, + 'rating': instance.rating, + 'photos': instance.photos, + 'categories': instance.categories, + 'hours': instance.hours, + 'location': instance.location, + }; diff --git a/packages/yelp_repository/lib/src/models/restaurant_query_result.dart b/packages/yelp_repository/lib/src/models/restaurant_query_result.dart new file mode 100644 index 00000000..81449932 --- /dev/null +++ b/packages/yelp_repository/lib/src/models/restaurant_query_result.dart @@ -0,0 +1,25 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:yelp_repository/src/models/restaurant.dart'; +import 'package:domain_models/domain_models.dart' as domain; + +part 'restaurant_query_result.g.dart'; + +@JsonSerializable() +class RestaurantQueryResult { + final int? total; + @JsonKey(name: 'business') + final List? restaurants; + + const RestaurantQueryResult({ + this.total, + this.restaurants, + }); + + factory RestaurantQueryResult.fromJson(Map json) => + _$RestaurantQueryResultFromJson(json); + + Map toJson() => _$RestaurantQueryResultToJson(this); + + List get domainRestaurants => + restaurants?.map((e) => e.toDomain()).toList() ?? []; +} diff --git a/packages/yelp_repository/lib/src/models/restaurant_query_result.g.dart b/packages/yelp_repository/lib/src/models/restaurant_query_result.g.dart new file mode 100644 index 00000000..c94b14a8 --- /dev/null +++ b/packages/yelp_repository/lib/src/models/restaurant_query_result.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'restaurant_query_result.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RestaurantQueryResult _$RestaurantQueryResultFromJson( + Map json) => + RestaurantQueryResult( + total: json['total'] as int?, + restaurants: (json['business'] as List?) + ?.map((e) => Restaurant.fromJson(e as Map)) + .toList(), + ); + +Map _$RestaurantQueryResultToJson( + RestaurantQueryResult instance) => + { + 'total': instance.total, + 'business': instance.restaurants, + }; diff --git a/packages/yelp_repository/lib/src/models/review.dart b/packages/yelp_repository/lib/src/models/review.dart new file mode 100644 index 00000000..2950bc45 --- /dev/null +++ b/packages/yelp_repository/lib/src/models/review.dart @@ -0,0 +1,34 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:yelp_repository/src/models/user.dart'; +import 'package:domain_models/domain_models.dart' as domain; + +part 'review.g.dart'; + +@JsonSerializable() +class Review { + final String? id; + final int? rating; + final String? text; + final User? user; + + const Review({ + this.id, + this.rating, + this.text, + this.user, + }); + + factory Review.fromJson(Map json) => _$ReviewFromJson(json); + + Map toJson() => _$ReviewToJson(this); + + domain.Review get toDomain => domain.Review( + id: id ?? '', + rating: rating ?? 0, + text: text ?? '', + user: domain.User( + imageUrl: user?.imageUrl ?? '', + name: user?.name ?? '', + ), + ); +} diff --git a/packages/yelp_repository/lib/src/models/review.g.dart b/packages/yelp_repository/lib/src/models/review.g.dart new file mode 100644 index 00000000..7576b513 --- /dev/null +++ b/packages/yelp_repository/lib/src/models/review.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'review.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Review _$ReviewFromJson(Map json) => Review( + id: json['id'] as String?, + rating: json['rating'] as int?, + text: json['text'] as String?, + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + ); + +Map _$ReviewToJson(Review instance) => { + 'id': instance.id, + 'rating': instance.rating, + 'text': instance.text, + 'user': instance.user, + }; diff --git a/packages/yelp_repository/lib/src/models/user.dart b/packages/yelp_repository/lib/src/models/user.dart new file mode 100644 index 00000000..88511500 --- /dev/null +++ b/packages/yelp_repository/lib/src/models/user.dart @@ -0,0 +1,21 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'user.g.dart'; + +@JsonSerializable() +class User { + final String? id; + @JsonKey(name: 'image_url') + final String? imageUrl; + final String? name; + + const User({ + this.id, + this.imageUrl, + this.name, + }); + + factory User.fromJson(Map json) => _$UserFromJson(json); + + Map toJson() => _$UserToJson(this); +} diff --git a/packages/yelp_repository/lib/src/models/user.g.dart b/packages/yelp_repository/lib/src/models/user.g.dart new file mode 100644 index 00000000..4c49488e --- /dev/null +++ b/packages/yelp_repository/lib/src/models/user.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +User _$UserFromJson(Map json) => User( + id: json['id'] as String?, + imageUrl: json['image_url'] as String?, + name: json['name'] as String?, + ); + +Map _$UserToJson(User instance) => { + 'id': instance.id, + 'image_url': instance.imageUrl, + 'name': instance.name, + }; diff --git a/packages/yelp_repository/lib/src/web/client.dart b/packages/yelp_repository/lib/src/web/client.dart new file mode 100644 index 00000000..1d92368a --- /dev/null +++ b/packages/yelp_repository/lib/src/web/client.dart @@ -0,0 +1,8 @@ +import 'package:http/http.dart'; +import 'package:http_interceptor/http_interceptor.dart'; + +// This allow us to add an analytics tool as FirebaseAnalytics/NewRelic +Client get webClient => InterceptedClient.build( + interceptors: [], + requestTimeout: const Duration(seconds: 15), + ); diff --git a/packages/yelp_repository/lib/src/web/log_interceptor.dart b/packages/yelp_repository/lib/src/web/log_interceptor.dart new file mode 100644 index 00000000..30d9160f --- /dev/null +++ b/packages/yelp_repository/lib/src/web/log_interceptor.dart @@ -0,0 +1,16 @@ +import 'package:flutter/foundation.dart'; +import 'package:http_interceptor/http_interceptor.dart'; + +class LogInterceptor implements InterceptorContract { + @override + Future interceptRequest({required RequestData data}) async { + kDebugMode ? print(data.toString()) : null; + return data; + } + + @override + Future interceptResponse({required ResponseData data}) async { + kDebugMode ? print(data.toString()) : null; + return data; + } +} diff --git a/packages/yelp_repository/lib/src/yelp_repository.dart b/packages/yelp_repository/lib/src/yelp_repository.dart new file mode 100644 index 00000000..20f59b62 --- /dev/null +++ b/packages/yelp_repository/lib/src/yelp_repository.dart @@ -0,0 +1,114 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:get_it/get_it.dart'; +import 'package:http/http.dart'; +import 'package:yelp_repository/src/models/review.dart'; +import 'package:yelp_repository/src/web/client.dart'; +import 'package:yelp_repository/src/models/restaurant_query_result.dart'; +import 'package:domain_models/domain_models.dart' as domain; + +class YelpRepository { + late final String _apiKey; + late final Map _header; + late final domain.AppConfig _appConfig; + late final Client _client; + + YelpRepository({@visibleForTesting Client? client}) + : _client = client ?? webClient { + _appConfig = GetIt.instance.get(); + _apiKey = _appConfig.yelpApiKey; + _header = { + 'Authorization': 'Bearer $_apiKey', + 'Content-Type': 'application/graphql', + }; + } + + Future> getRestaurants({int offset = 0}) async { + final response = await _client.post( + _appConfig.networkConfig.yelpUri, + headers: _header, + body: _getRestaurantsQuery(offset), + ); + + if (response.statusCode != 200) { + throw domain.NetworkException( + statusCode: response.statusCode, + message: response.body, + ); + } + final restaurantQueryResult = RestaurantQueryResult.fromJson( + json.decode(utf8.decode(response.bodyBytes))['data']['search'], + ); + return restaurantQueryResult.domainRestaurants; + } + + String _getRestaurantsQuery(int offset) { + return ''' +query getRestaurants { + search(location: "Las Vegas", limit: 20, offset: $offset) { + total + business { + id + name + price + rating + photos + categories { + title + alias + } + hours { + is_open_now + } + location { + formatted_address + } + } + } +} +'''; + } + + Future> getReviews({ + required String restaurantId, + int offset = 0, + }) async { + final response = await _client.post( + _appConfig.networkConfig.yelpUri, + headers: _header, + body: _getReviewsQuery(restaurantId, offset), + ); + + if (response.statusCode != 200) { + throw domain.NetworkException( + statusCode: response.statusCode, + message: response.body, + ); + } + + final jsonList = json.decode(utf8.decode(response.bodyBytes))['data'] + ['business']['reviews'] as List; + return jsonList.map((json) => Review.fromJson(json).toDomain).toList(); + } + + String _getReviewsQuery(String businessId, int offset) { + return ''' +query getReviews { + business(id: "$businessId") { + reviews(limit: 20, offset: $offset) { + id + rating + text + time_created + user { + id + image_url + name + } + } + } +} +'''; + } +} diff --git a/packages/yelp_repository/lib/yelp_repository.dart b/packages/yelp_repository/lib/yelp_repository.dart new file mode 100644 index 00000000..e041e879 --- /dev/null +++ b/packages/yelp_repository/lib/yelp_repository.dart @@ -0,0 +1 @@ +export 'src/yelp_repository.dart'; \ No newline at end of file diff --git a/packages/yelp_repository/pubspec.lock b/packages/yelp_repository/pubspec.lock new file mode 100644 index 00000000..590167c5 --- /dev/null +++ b/packages/yelp_repository/pubspec.lock @@ -0,0 +1,572 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + url: "https://pub.dev" + source: hosted + version: "64.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + url: "https://pub.dev" + source: hosted + version: "2.4.9" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" + collection: + dependency: transitive + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" + domain_models: + dependency: "direct main" + description: + path: "../domain_models" + relative: true + source: path + version: "0.0.1" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_dotenv: + dependency: transitive + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 + url: "https://pub.dev" + source: hosted + version: "7.6.7" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + http: + dependency: "direct main" + description: + name: http + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + url: "https://pub.dev" + source: hosted + version: "0.13.6" + http_interceptor: + dependency: "direct main" + description: + name: http_interceptor + sha256: "5f3dde028e67789339c250252c09510a74aff21ce16b06d07d9096bda6582bab" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 + url: "https://pub.dev" + source: hosted + version: "6.7.1" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.1.5 <4.0.0" + flutter: ">=1.17.0" diff --git a/packages/yelp_repository/pubspec.yaml b/packages/yelp_repository/pubspec.yaml new file mode 100644 index 00000000..921b0be8 --- /dev/null +++ b/packages/yelp_repository/pubspec.yaml @@ -0,0 +1,64 @@ +name: yelp_repository +description: A new Flutter package project. +publish_to: 'none' +version: 0.0.1 +homepage: + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + http: ^0.13.3 + http_interceptor: ^1.0.2 + json_annotation: ^4.8.1 + get_it: ^7.6.7 + + domain_models: + path: ../domain_models + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + json_serializable: ^6.7.1 + build_runner: ^2.4.8 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/yelp_repository/test/yelp_repository_test.dart b/packages/yelp_repository/test/yelp_repository_test.dart new file mode 100644 index 00000000..bd1919b5 --- /dev/null +++ b/packages/yelp_repository/test/yelp_repository_test.dart @@ -0,0 +1,12 @@ +// import 'package:flutter_test/flutter_test.dart'; + +// import 'package:restaurant_repository/restaurant_repository.dart'; + +// void main() { +// test('adds one to input values', () { +// final calculator = Calculator(); +// expect(calculator.addOne(2), 3); +// expect(calculator.addOne(-7), -6); +// expect(calculator.addOne(0), 1); +// }); +// } diff --git a/pubspec.lock b/pubspec.lock index 0b052c68..d6d8bd51 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "64.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.2.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 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" boolean_selector: dependency: transitive description: @@ -45,10 +53,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 +69,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" 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: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.9" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f4d6244cc071ba842c296cb1c4ee1b31596b9f924300647ac7a1445493471a3f + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.3" + version: "7.3.0" built_collection: dependency: transitive description: @@ -101,34 +109,50 @@ packages: dependency: transitive description: name: built_value - sha256: b6c9911b2d670376918d5b8779bc27e0e612a94ec3ff0343689e991d8d0a3b8a + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.1.4" - characters: + version: "8.9.2" + cached_network_image: dependency: transitive description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + name: cached_network_image + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" url: "https://pub.dev" source: hosted - version: "1.3.0" - charcode: + version: "3.3.1" + cached_network_image_platform_interface: dependency: transitive description: - name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + name: cached_network_image_platform_interface + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "4.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" 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: @@ -149,50 +173,72 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.2" + component_library: + dependency: transitive + description: + path: "packages/component_library" + relative: true + source: path + version: "0.0.1" convert: dependency: transitive description: name: convert - sha256: f08428ad63615f96a27e34221c65e1a451439b5f26030f78d790f461c686d65d + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.1" crypto: dependency: transitive description: name: crypto - sha256: cf75650c66c0316274e21d7c43d3dea246273af5955bd94e8184837cd577575c + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.3" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" dart_style: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.6" dio: dependency: "direct main" description: name: dio - sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" + sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.4.3+1" + domain_models: + dependency: "direct main" + description: + path: "packages/domain_models" + relative: true + source: path + version: "0.0.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: @@ -201,27 +247,67 @@ 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: "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" + flat_buffers: + dependency: transitive + description: + name: flat_buffers + sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3" + url: "https://pub.dev" + source: hosted + version: "23.5.26" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: transitive + description: + name: flutter_bloc + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + url: "https://pub.dev" + source: hosted + version: "8.1.5" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + flutter_dotenv: + dependency: transitive + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" flutter_lints: dependency: "direct dev" description: @@ -230,6 +316,19 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_staggered_grid_view: + dependency: transitive + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.dev" + source: hosted + version: "0.7.0" flutter_svg: dependency: "direct main" description: @@ -247,18 +346,26 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 + url: "https://pub.dev" + source: hosted + version: "7.6.7" 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: @@ -267,54 +374,86 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + http: + dependency: transitive + description: + name: http + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + url: "https://pub.dev" + source: hosted + version: "0.13.6" + http_interceptor: + dependency: transitive + description: + name: http_interceptor + sha256: "5f3dde028e67789339c250252c09510a74aff21ce16b06d07d9096bda6582bab" + url: "https://pub.dev" + source: hosted + version: "1.0.2" http_multi_server: 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.2" + infinite_scroll_pagination: + dependency: transitive + description: + name: infinite_scroll_pagination + sha256: b68bce20752fcf36c7739e60de4175494f74e99e9a69b4dd2fe3a1dd07a7f16a url: "https://pub.dev" source: hosted version: "4.0.0" + intl: + dependency: transitive + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" 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: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b url: "https://pub.dev" source: hosted - version: "6.7.1" + version: "6.8.0" lints: dependency: transitive description: @@ -323,14 +462,28 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + local_storage: + dependency: "direct main" + description: + path: "packages/local_storage" + relative: true + source: path + version: "0.0.1" logging: dependency: transitive description: name: logging - sha256: "293ae2d49fd79d4c04944c3a26dfd313382d5f52e821ec57119230ae16031ad4" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.2.0" + lost_app: + dependency: "direct main" + description: + path: "packages/features/lost_app" + relative: true + source: path + version: "0.0.1" matcher: dependency: transitive description: @@ -351,26 +504,66 @@ 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: name: mime - sha256: fd5f81041e6a9fc9b9d7fa2cb8a01123f9f5d5d49136e06cb9dc7d33689529f4 + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.4" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + url: "https://pub.dev" + source: hosted + version: "5.4.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + objectbox: + dependency: transitive + description: + name: objectbox + sha256: "9fb2810156e8f78d82ecf672c36a1aba2c1de16d7903675335e00e374bdc3ba8" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + objectbox_flutter_libs: + dependency: transitive + description: + name: objectbox_flutter_libs + sha256: dca86b2d1074110573b69cbd9afb6b67ab9d2c824704c6ac5187e546418baf9c + url: "https://pub.dev" + source: hosted + version: "2.5.1" + 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: 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,59 +580,161 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "51f0d2c554cfbc9d6a312ab35152fc77e2f0b758ce9f1a444a3a1e5b8f3c6b7f" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + 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: "6.0.2" + version: "3.1.4" + 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" + 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: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.3" + restaurant_detail: + dependency: "direct main" + description: + path: "packages/features/restaurant_detail" + relative: true + source: path + version: "0.0.1" + restaurant_list: + dependency: "direct main" + description: + path: "packages/features/restaurant_list" + relative: true + source: path + version: "0.0.1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" 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_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: fd84910bf7d58db109082edf7326b75322b8f186162028482f53dc892f00332d + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.4" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + sliver_tools: + dependency: transitive + description: + name: sliver_tools + sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6 + url: "https://pub.dev" + source: hosted + version: "0.2.12" source_gen: dependency: transitive description: @@ -464,30 +759,54 @@ 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: name: stream_transform - sha256: ed464977cb26a1f41537e177e190c67223dbd9f4f683489b6ab2e5d211ec564e + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: @@ -496,6 +815,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,50 +835,58 @@ 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: 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" + uuid: + dependency: transitive + description: + name: uuid + sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" + url: "https://pub.dev" + source: hosted + version: "4.2.2" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" + sha256: "4ac59808bbfca6da38c99f415ff2d3a5d7ca0a6b4809c71d9cf30fba5daf9752" url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.10+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" + sha256: f3247e7ab0ec77dc759263e68394990edc608fb2b480b80db8aa86ed09279e33 url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.10+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" + sha256: "18489bdd8850de3dd7ca8a34e0c446f719ec63e2bab2e7a8cc66a9028dd76c5a" url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.10+1" vector_math: dependency: transitive description: @@ -564,42 +899,65 @@ packages: 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: 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: name: web_socket_channel - sha256: "0c2ada1b1aeb2ad031ca81872add6be049b8cb479262c6ad3c4b0f9c24eaab2f" + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.4.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: name: yaml - sha256: "3cee79b1715110341012d27756d9bae38e650588acd38d3f3c610822e1337ace" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" + yelp_repository: + dependency: "direct main" + description: + path: "packages/yelp_repository" + relative: true + source: path + version: "0.0.1" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.7.0-0" + dart: ">=3.1.5 <4.0.0" + flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index be3055e0..efa1b4dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,16 +15,107 @@ dependencies: cupertino_icons: ^1.0.6 dio: ^5.4.0 json_annotation: ^4.8.1 + flutter_localizations: + sdk: flutter + equatable: ^2.0.5 + get_it: ^7.6.7 flutter_svg: ^2.0.9 + yelp_repository: + path: packages/yelp_repository + domain_models: + path: packages/domain_models + restaurant_list: + path: packages/features/restaurant_list + restaurant_detail: + path: packages/features/restaurant_detail + lost_app: + path: packages/features/lost_app + local_storage: + path: packages/local_storage + dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^1.0.2 build_runner: ^2.4.8 json_serializable: ^6.7.1 + mockito: 5.4.4 flutter: uses-material-design: true -# assets: -# - assets/svg/ \ No newline at end of file + assets: + - assets/svg/ + - assets/env/ + fonts: + - family: Lora + fonts: + - asset: assets/fonts/Lora/Lora-Bold.ttf + - asset: assets/fonts/Lora/Lora-BoldItalic.ttf + style: italic + - asset: assets/fonts/Lora/Lora-Italic.ttf + style: italic + - asset: assets/fonts/Lora/Lora-Medium.ttf + - asset: assets/fonts/Lora/Lora-MediumItalic.ttf + style: italic + - asset: assets/fonts/Lora/Lora-Regular.ttf + - asset: assets/fonts/Lora/Lora-SemiBold.ttf + - asset: assets/fonts/Lora/Lora-SemiBoldItalic.ttf + style: italic + + - family: OpenSans + fonts: + - asset: assets/fonts/OpenSans/OpenSans_Condensed-Bold.ttf + - asset: assets/fonts/OpenSans/OpenSans_Condensed-BoldItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_Condensed-ExtraBold.ttf + - asset: assets/fonts/OpenSans/OpenSans_Condensed-ExtraBoldItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_Condensed-Italic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_Condensed-Light.ttf + - asset: assets/fonts/OpenSans/OpenSans_Condensed-LightItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_Condensed-Medium.ttf + - asset: assets/fonts/OpenSans/OpenSans_Condensed-MediumItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_Condensed-Regular.ttf + - asset: assets/fonts/OpenSans/OpenSans_Condensed-SemiBold.ttf + - asset: assets/fonts/OpenSans/OpenSans_Condensed-SemiBoldItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-Bold.ttf + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-BoldItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-ExtraBold.ttf + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-ExtraBoldItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-Italic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-Light.ttf + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-LightItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-Medium.ttf + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-MediumItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-Regular.ttf + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-SemiBold.ttf + - asset: assets/fonts/OpenSans/OpenSans_SemiCondensed-SemiBoldItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans-Bold.ttf + - asset: assets/fonts/OpenSans/OpenSans-BoldItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans-ExtraBold.ttf + - asset: assets/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans-Italic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans-Light.ttf + - asset: assets/fonts/OpenSans/OpenSans-LightItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans-Medium.ttf + - asset: assets/fonts/OpenSans/OpenSans-MediumItalic.ttf + style: italic + - asset: assets/fonts/OpenSans/OpenSans-Regular.ttf + - asset: assets/fonts/OpenSans/OpenSans-SemiBold.ttf + - asset: assets/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf + style: italic \ No newline at end of file diff --git a/test/events/events.dart b/test/events/events.dart new file mode 100644 index 00000000..db66e4ee --- /dev/null +++ b/test/events/events.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:restaurant_list/restaurant_list.dart'; + +Future tapOnFavoritesTab(WidgetTester tester) async { + final favoritesTab = find.byKey(restaurantListFavoritesRestaurantsTabKey); + expect(favoritesTab, findsOneWidget); + await tester.ensureVisible(favoritesTab); + return tester.tap(favoritesTab); +} + +Future tapOnAllRestaurantsTab(WidgetTester tester) async { + final allRestaurantsTab = find.byKey(restaurantListAllRestaurantsTabKey); + expect(allRestaurantsTab, findsOneWidget); + await tester.ensureVisible(allRestaurantsTab); + return tester.tap(allRestaurantsTab); +} + +Future tapFirstRestaurantCard(WidgetTester tester) async { + final restaurantCards = find.byType(RestaurantCard); + expect(restaurantCards, findsWidgets); + return tester.tap(restaurantCards.first); +} + +Future tapOnBorderedHeart(WidgetTester tester) async { + final borderedHeart = find.byIcon(Icons.favorite_border); + expect(borderedHeart, findsOneWidget); + await tester.ensureVisible(borderedHeart); + return tester.tap(borderedHeart); +} \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart index 83fbeae4..11529cf6 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,20 +1,84 @@ -// 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:domain_models/domain_models.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:local_storage/local_storage.dart'; + +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:restaurant_detail/restaurant_detail.dart'; +import 'package:restaurant_list/restaurant_list.dart'; +import 'package:restaurantour/start.dart'; +import 'package:yelp_repository/yelp_repository.dart'; -import 'package:restaurantour/main.dart'; +import 'events/events.dart'; +import 'widget_test.mocks.dart'; +@GenerateMocks([LocalStorage, YelpRepository]) void main() { - testWidgets('Page loads', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const Restaurantour()); + final yelpRepository = MockYelpRepository(); + final localStorage = MockLocalStorage(); + final restaurants = List.generate( + 21, + (index) => Restaurant( + id: 'id_$index', + name: 'name_$index', + price: 'price_$index', + rating: 4.6, + photoUrl: 'photoUrl_$index', + category: 'category_$index', + isOpen: true, + address: 'address_$index', + ), + ); + + setUp(() { + when(yelpRepository.getRestaurants()) + .thenAnswer((_) => Future.value(restaurants)); + when( + yelpRepository.getReviews( + restaurantId: anyNamed('restaurantId'), + offset: anyNamed('offset'), + ), + ).thenAnswer((_) => Future.value([])); + when(localStorage.restaurantListener).thenAnswer((_) => Stream.value([])); + when(localStorage.containsRestaurantListener(any)) + .thenAnswer((_) => Stream.value(false)); + when(localStorage.addRestaurant(any)).thenReturn(1); + }); + + tearDown(() { + reset(yelpRepository); + reset(localStorage); + resetMockitoState(); + }); + + testWidgets('Set restaurant as favorite', (WidgetTester tester) async { + await tester.pumpWidget( + Restaurantour( + yelpRepository: yelpRepository, + localStorage: localStorage, + ), + ); + + final restaurantListView = find.byType(RestaurantListView); + expect(restaurantListView, findsOneWidget); + + await tapOnFavoritesTab(tester); + await tester.pumpAndSettle(); + + final emptyFavoriteImage = find.byKey(emptyFavoritesImageKey); + expect(emptyFavoriteImage, findsOneWidget); + + await tapOnAllRestaurantsTab(tester); + await tester.pumpAndSettle(); + + await tapFirstRestaurantCard(tester); + await tester.pumpAndSettle(); + + final restaurantDetailView = find.byType(RestaurantDetailView); + expect(restaurantDetailView, findsOneWidget); - // Verify that tests will run - expect(find.text('Fetch Restaurants'), findsOneWidget); + await tapOnBorderedHeart(tester); + await tester.pumpAndSettle(); + verify(localStorage.addRestaurant(restaurants.first)).called(1); }); }