From 0d6402957fb71f09b25293a2be8602d8aa6a9c15 Mon Sep 17 00:00:00 2001 From: Dmytro Turskyi Date: Mon, 29 Apr 2024 18:42:16 -0400 Subject: [PATCH] Add the bug report button to the product info page near the barcode. (#14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Increment version to 0.1.0(10) Signed-off-by: Dmytro Turskyi * Add commit activity in README.md. Signed-off-by: Dmytro Turskyi * Fixed commit activity in README.md. Signed-off-by: Dmytro Turskyi * Changed link to successful release Android build in README.md. Signed-off-by: Dmytro Turskyi * Renamed "core" to "domain" and "components" to "core". Signed-off-by: Dmytro Turskyi * fixed the path to the interface adapters layer in ci Signed-off-by: Dmytro Turskyi * refactoring Signed-off-by: Dmytro Turskyi * refactoring Signed-off-by: Dmytro Turskyi * refactoring and adding more tests to code quality ci Signed-off-by: Dmytro Turskyi * Add mockito. Signed-off-by: Dmytro Turskyi * add timeout 40 seconds to remote data source test Signed-off-by: Dmytro Turskyi * added tests Signed-off-by: Dmytro Turskyi * lower coverage expectation Signed-off-by: Dmytro Turskyi * lower coverage expectation Signed-off-by: Dmytro Turskyi * Update README.md. Signed-off-by: Dmytro Turskyi * Update README.md. Signed-off-by: Dmytro Turskyi * Update README.md. Signed-off-by: Dmytro Turskyi * Renamed "core" to "components" to be even more closely to clean architecture. Signed-off-by: Dmytro Turskyi * Add codecov coverage. Signed-off-by: Dmytro Turskyi * Updated dependencies in pubspec.yaml for flutter_bloc, dart_openai, dio, envied, feedback, flutter_bloc, logger, openfoodfacts, package_info_plus, shared_preferences, build_runner, envied_generator, flutter_lints, and package_info_plus. Also added error handling in the Resources class in components/interface_adapters. Signed-off-by: Dmytro Turskyi * feat(ui): 🖥️ Update product info body method signature - Updated the method signature of `_sendFeedback` in the `ProductInfoBody` widget to accept named parameters for `feedback` and `packageInfo`. - This change enhances readability and maintainability of the code. authored-by: Dmytro Signed-off-by: Dmytro Turskyi * Add bug report button. :recycle: Update dependencies versions in android/app/build.gradle and pubspec.lock, refactor code in components/interface_adapters In this commit, I updated the Firebase dependencies versions in android/app/build.gradle and pubspec.lock to the latest ones. Additionally, I refactored the code in components/interface_adapters to improve readability and maintainability. Signed-off-by: Dmytro Turskyi --------- Signed-off-by: Dmytro Turskyi --- .github/workflows/code-quality-tests.yml | 16 +- README.md | 338 +++--- analysis_options.yaml | 5 +- android/.gitignore | 3 + android/app/build.gradle | 17 +- android/app/src/main/AndroidManifest.xml | 4 +- .../entities/example/entities_example.dart | 3 - .../use_cases/example/use_cases_example.dart | 3 - .../{core => domain}/entities/.gitignore | 0 .../{core => domain}/entities/CHANGELOG.md | 0 .../{core => domain}/entities/README.md | 0 .../entities/analysis_options.yaml | 0 .../entities/example/entities_example.dart | 1 + .../entities/lib/entities.dart | 8 +- .../entities/lib/src/enums/language.dart | 0 .../lib/src/enums/product_info_type.dart | 0 .../entities/lib/src/enums/vegan.dart | 0 .../entities/lib/src/enums/vegetarian.dart | 0 .../src/exceptions}/bad_request_error.dart | 0 .../exceptions}/internal_server_error.dart | 0 .../src/exceptions}/not_found_exception.dart | 0 .../src/localized_code/localized_code.dart} | 4 +- .../logging_interceptor.dart | 0 .../lib/src/product/product_info.dart | 0 .../lib/src/product/product_photo.dart | 0 .../lib/src/rest_client/rest_client.dart | 2 +- .../terrorism_sponsor/terrorism_sponsor.dart | 47 +- .../{core => domain}/entities/pubspec.yaml | 4 +- .../entities/test/entities_test.dart | 0 .../{core => domain}/use_cases/.gitignore | 0 .../{core => domain}/use_cases/CHANGELOG.md | 0 .../{core => domain}/use_cases/README.md | 0 .../use_cases/analysis_options.yaml | 0 .../use_cases/example/use_cases_example.dart | 1 + .../src/gateways/product_info_gateway.dart | 2 +- .../lib/src/gateways/settings_gateway.dart | 0 .../use_cases/add_ingredients_use_case.dart | 2 +- .../src/use_cases/get_language_use_case.dart | 0 .../get_precipitation_state_use_case.dart | 3 +- .../use_cases/get_product_info_use_case.dart | 7 +- .../get_sound_preference_use_case.dart | 0 .../src/use_cases/save_language_use_case.dart | 0 .../save_precipitation_state_use_case.dart | 0 .../save_sound_preference_use_case.dart | 0 .../use_cases/lib/src/use_cases/use_case.dart | 0 .../use_cases/lib/use_cases.dart | 0 .../{core => domain}/use_cases/pubspec.yaml | 4 +- .../test/construct_pm_use_cases_test.dart | 0 .../interface_adapters/coverage/lcov.info | 0 .../lib/interface_adapters.dart | 1 - .../remote/remote_data_source.dart | 5 +- .../gateways/product_info_gateway_impl.dart | 2 +- .../lib/src/ui/app/app.dart | 4 +- .../lib/src/ui/modules/home/home_event.dart | 7 +- .../src/ui/modules/home/home_presenter.dart | 7 +- .../src/ui/modules/home/view/home_view.dart | 1 - .../modules/home/view/widgets/code_tile.dart | 54 +- .../home/view/widgets/product_info_body.dart | 102 +- .../home/view/widgets/product_info_tile.dart | 1 - .../lib/src/ui/modules/photo/photo_event.dart | 1 - .../ui/modules/photo/photo_view_model.dart | 4 +- .../src/ui/modules/scan/view/scan_view.dart | 12 +- .../lib/src/ui/res/resources.dart | 16 +- components/interface_adapters/pubspec.yaml | 28 +- coverage/lcov.info | 981 +++++++++++++++--- ios/Podfile.lock | 2 +- ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../local/local_data_source_impl.dart | 2 +- .../russia_sponsor_response.dart | 13 +- .../remote/remote_data_source_impl.dart | 6 +- .../{dio => }/logging_interceptor_impl.dart | 0 lib/di/dependencies.dart | 12 +- lib/main.dart | 2 +- .../home_bloc_provider.dart | 2 +- lib/{routes => router}/router.dart | 8 +- lib/{routes => router}/routes.dart | 0 pubspec.lock | 214 ++-- pubspec.yaml | 55 +- test/app_route_test.dart | 2 +- test/app_router_test.dart | 6 +- test/dependencies_scope_test.dart | 35 +- test/dependencies_test.dart | 41 +- test/local_data_source_impl_test.dart | 68 +- test/mock_dependencies.dart | 56 + test/mock_local_data_source_impl.dart | 792 ++++++++++++++ test/mock_shared_preferences.dart | 37 + test/remote_data_source_impl_test.dart | 73 +- 88 files changed, 2508 insertions(+), 622 deletions(-) delete mode 100644 components/core/entities/example/entities_example.dart delete mode 100644 components/core/use_cases/example/use_cases_example.dart rename components/{core => domain}/entities/.gitignore (100%) rename components/{core => domain}/entities/CHANGELOG.md (100%) rename components/{core => domain}/entities/README.md (100%) rename components/{core => domain}/entities/analysis_options.yaml (100%) create mode 100644 components/domain/entities/example/entities_example.dart rename components/{core => domain}/entities/lib/entities.dart (82%) rename components/{core => domain}/entities/lib/src/enums/language.dart (100%) rename components/{core => domain}/entities/lib/src/enums/product_info_type.dart (100%) rename components/{core => domain}/entities/lib/src/enums/vegan.dart (100%) rename components/{core => domain}/entities/lib/src/enums/vegetarian.dart (100%) rename components/{core/entities/lib/src/exception => domain/entities/lib/src/exceptions}/bad_request_error.dart (100%) rename components/{core/entities/lib/src/exception => domain/entities/lib/src/exceptions}/internal_server_error.dart (100%) rename components/{core/entities/lib/src/exception => domain/entities/lib/src/exceptions}/not_found_exception.dart (100%) rename components/{core/entities/lib/src/barcode/barcode.dart => domain/entities/lib/src/localized_code/localized_code.dart} (78%) rename components/{core => domain}/entities/lib/src/logging_interceptor/logging_interceptor.dart (100%) rename components/{core => domain}/entities/lib/src/product/product_info.dart (100%) rename components/{core => domain}/entities/lib/src/product/product_photo.dart (100%) rename components/{core => domain}/entities/lib/src/rest_client/rest_client.dart (85%) rename components/{core => domain}/entities/lib/src/terrorism_sponsor/terrorism_sponsor.dart (64%) rename components/{core => domain}/entities/pubspec.yaml (87%) rename components/{core => domain}/entities/test/entities_test.dart (100%) rename components/{core => domain}/use_cases/.gitignore (100%) rename components/{core => domain}/use_cases/CHANGELOG.md (100%) rename components/{core => domain}/use_cases/README.md (100%) rename components/{core => domain}/use_cases/analysis_options.yaml (100%) create mode 100644 components/domain/use_cases/example/use_cases_example.dart rename components/{core => domain}/use_cases/lib/src/gateways/product_info_gateway.dart (77%) rename components/{core => domain}/use_cases/lib/src/gateways/settings_gateway.dart (100%) rename components/{core => domain}/use_cases/lib/src/use_cases/add_ingredients_use_case.dart (94%) rename components/{core => domain}/use_cases/lib/src/use_cases/get_language_use_case.dart (100%) rename components/{core => domain}/use_cases/lib/src/use_cases/get_precipitation_state_use_case.dart (77%) rename components/{core => domain}/use_cases/lib/src/use_cases/get_product_info_use_case.dart (58%) rename components/{core => domain}/use_cases/lib/src/use_cases/get_sound_preference_use_case.dart (100%) rename components/{core => domain}/use_cases/lib/src/use_cases/save_language_use_case.dart (100%) rename components/{core => domain}/use_cases/lib/src/use_cases/save_precipitation_state_use_case.dart (100%) rename components/{core => domain}/use_cases/lib/src/use_cases/save_sound_preference_use_case.dart (100%) rename components/{core => domain}/use_cases/lib/src/use_cases/use_case.dart (100%) rename components/{core => domain}/use_cases/lib/use_cases.dart (100%) rename components/{core => domain}/use_cases/pubspec.yaml (93%) rename components/{core => domain}/use_cases/test/construct_pm_use_cases_test.dart (100%) create mode 100644 components/interface_adapters/coverage/lcov.info rename lib/data/data_sources/remote/rest/{dio => }/logging_interceptor_impl.dart (100%) rename lib/{routes => router}/home_bloc_provider.dart (98%) rename lib/{routes => router}/router.dart (94%) rename lib/{routes => router}/routes.dart (100%) create mode 100644 test/mock_dependencies.dart create mode 100644 test/mock_local_data_source_impl.dart create mode 100644 test/mock_shared_preferences.dart diff --git a/.github/workflows/code-quality-tests.yml b/.github/workflows/code-quality-tests.yml index 2c8ea5b..48604aa 100644 --- a/.github/workflows/code-quality-tests.yml +++ b/.github/workflows/code-quality-tests.yml @@ -3,7 +3,7 @@ name: Code Quality on: [ push ] jobs: - check_lint_errors: + check-code-quality-and-tests: if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-latest steps: @@ -32,10 +32,22 @@ jobs: - name: Get packages run: flutter pub get + - name: Check format errors + run: dart format --set-exit-if-changed . + - name: Check lint errors run: flutter analyze . + - name: Run tests with coverage and random order + run: flutter test --coverage --test-randomize-ordering-seed random + - name: Very Good Coverage uses: VeryGoodOpenSource/very_good_coverage@v2.1.0 with: - min_coverage: 35 \ No newline at end of file + min_coverage: 33.9 + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.0.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: Turskyi/ethical_scanner \ No newline at end of file diff --git a/README.md b/README.md index 10cfbe2..f29f72d 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,20 @@ [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua) -[![Codemagic build status](https://api.codemagic.io/apps/659998e6caf611b8969ad389/659998e6caf611b8969ad388/status_badge.svg)](https://codemagic.io/apps/659998e6caf611b8969ad389/659998e6caf611b8969ad388/latest_build) +[![Codemagic build status](https://api.codemagic.io/apps/659998e6caf611b8969ad389/659998e6caf611b8969ad388/status_badge.svg)](https://play.google.com/store/apps/details?id=com.turskyi.ethical_scanner) [![Build & upload to Firebase App Distribution](https://github.com/Turskyi/ethical_scanner/actions/workflows/flutter_ci.yml/badge.svg?branch=dev&event=push)](https://appdistribution.firebase.dev/i/ad57d28bed182b15) [![Code Quality](https://github.com/Turskyi/ethical_scanner/actions/workflows/code-quality-tests.yml/badge.svg?branch=master&event=push)](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo) [![style: flutter lints](https://img.shields.io/badge/style-flutter__lints-blue)](https://pub.dev/packages/flutter_lints) +[![codecov](https://codecov.io/gh/Turskyi/ethical_scanner/graph/badge.svg?token=8PSVNC8TES)](https://codecov.io/gh/Turskyi/ethical_scanner) +GitHub commit activity # Ethical Scanner -**Ethical Scanner** is a Flutter project for building Android and iOS mobile apps that scans the -barcode -of a product and tells you if the product meets your ethical standards. +**Ethical Scanner** is a Flutter project for building Android and iOS mobile +apps that scans the barcode of a product and tells you if the product meets +your ethical standards. The app allows you to customize your preferences based on -various criteria, such as human rights, environmental impact, animal well-being, and more. +various criteria, such as human rights, environmental impact, animal well-being, +and more. The app uses the data from various sources, such as @@ -23,8 +26,9 @@ The app uses the data from various sources, such as - [European Parliament](https://www.europarl.europa.eu/delegations/en/recognising-the-russian-federation-as-a-/product-details/20221124DPU34521); - [Don't Fund War](https://dontfundwar.com). -The app aims to help you shop with confidence and conscience. -For more information, please visit the project website at https://ethical-scanner.turskyi.com. +The app aims to help you shop with confidence and conscience. For more +information, please visit the project website at +https://ethical-scanner.turskyi.com. ## PROJECT SPECIFICATION @@ -40,52 +44,61 @@ For more information, please visit the project website at https://ethical-scanne • RESTful API: https://api.standwithukraine.how/get-companies-database; -• CI/CD: [GitHub Actions](https://docs.github.com/en/actions) is used to deliver new Android -Package (APK) to [Firebase App Distribution](https://firebase.google.com/docs/app-distribution) -after every push to any other than the **main** branch, [Codemagic](https://codemagic.io/start/) is -used to deliver -new release app bundle to **Google Play** after every merge (push) to **master** branch; +• CI/CD: [GitHub Actions](https://docs.github.com/en/actions) is used to deliver +new Android Package (APK) to +[Firebase App Distribution](https://firebase.google.com/docs/app-distribution) +after every push to any other than the **master** +branch, [Codemagic](https://codemagic.io/start/) +is used to deliver new release app bundle to **Google Play** after every merge +(push) to **master** branch; • State management approach: [BLoC](https://bloclibrary.dev); • App testing platforms: [Firebase App Distribution](https://appdistribution.firebase.dev/i/ad57d28bed182b15); -**Code Readability:** code is easily readable with no unnecessary blank lines, no unused variables -or methods, and no commented-out code, all variables, methods, and resource IDs are descriptively -named such that another developer reading the code can easily understand their function. +**Code Readability:** code is easily readable with no unnecessary blank lines, +no unused variables or methods, and no commented-out code, all variables, +methods, and resource IDs are descriptively named such that another developer +reading the code can easily understand their function. # Getting Started -## To create generated files, run: - -``` -dart run build_runner clean -dart run build_runner build --delete-conflicting-outputs -``` - ## Contribution -Ethical Scanner is an open source project and welcomes contributions from anyone who is interested. -If you want to contribute to Ethical Scanner, you can follow these steps: +**Ethical Scanner** is an open source project and welcomes contributions from +anyone who is interested. If you want to contribute to Ethical Scanner, you can +follow these steps: - Fork this repository and clone it to your local machine. - Create a new branch for your feature or bug-fix. - Make your changes and commit them with a clear and descriptive message. -- Push your branch to your forked repository and create a pull request to the main repository. +- Push your branch to your forked repository and create a pull request to the + master brunch. - Wait for your pull request to be reviewed and merged. -Please follow the Flutter style guide and code of conduct when contributing to Ethical Scanner. You -can also use the issues and discussions tabs to report bugs, request features, or give feedback. +Please follow +[the Flutter style guide](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo) +and code of conduct when contributing to **Ethical Scanner**. You can also use +the issues and discussions tabs to report bugs, request features, or give +feedback. ## Installation -To install Ethical Scanner, you need to have Flutter SDK and Android Studio installed on your -machine. You can follow the official documentation to set up your development environment. -To run an Ethical Scanner on your device or emulator, you need to clone this repository and open it -in -Android Studio. Then, you can use the run button or the command line to launch the app. For more -information, see the Flutter documentation. +To install **Ethical Scanner**, you need to have Flutter SDK and Android Studio +installed on your machine. You can follow +[the official documentation](https://docs.flutter.dev/get-started/install) to +set up your development environment. To run an **Ethical Scanner** on your +device or emulator, you need to clone this repository and open it in Android +Studio. Then, you can use the run button or the command line to launch the app. +For more information, see the Flutter documentation. + +## To create generated files, run: + +``` +dart run build_runner clean +dart run build_runner build --delete-conflicting-outputs +``` ### Data flow @@ -103,43 +116,77 @@ information, see the Flutter documentation. Image of the Flutter Clean Architecture Pattern + +### Components - `components` + +A "component" is a grouping of related functionality behind a nice clean +interface, which resides inside an execution environment like an application. +If the SOLID principles tell us how to arrange the bricks into walls and rooms, +then the component principles tell us how to arrange the rooms into buildings. +Large software systems, like large buildings, are built out of smaller +`components`. +• **REP**: *The Reuse/Release Equivalence Principle*. The granule of reuse is +the granule of release. This means that the classes and modules that are formed +into a component must belong to a cohesive group. +• **CCP**: *The Common Closure Principle*. Gather into components those classes +that change for the same reasons and at the same times. Separate into different +`components` those classes that change at different times and for different +reasons. +• **CRP**: *The Common Reuse Principle*. Don’t force users of a component to +depend on things they don’t need. + ## Layers +### Business/Domain - `domain` + +In the context of the Clean Architecture by Robert C. Martin, the `domain` +refers to the business logic or domain logic of the application. This is the +innermost circle, which encapsulates business logic (`use_cases`) and +`entities`. Domain models, in general, are designed to be highly reusable and +to encapsulate useful business functionality. The domain layer may contain +entities like `User`, `Role`, `Product`, etc. It’s essentially a collection of +best practice design principles that help us keep business logic together and +minimize the dependencies within the system. + #### Enterprise Business Rules - `entities` -An Entity is an object within our computer system that embodies a small set of critical business -rules operating on Critical Business Data. +An **Entity** is an object within our computer system that embodies a small set +of critical business rules operating on Critical Business Data. Entities are a +way to implement and enforce application-independent business rules. +Application-independent business rules are rules or procedures that make or +save the business money. Irrespective of whether they were implemented on a +computer, they would make or save money even if they were executed manually. #### Application Business Rules - `use_cases` -The `use_cases` module defines the business logic of the app. -It is a part that is independent of the development platform, in other words, it is written -purely in the -programming language and doesn't contain any elements from the platform. -In the case of `Flutter`, -`use_cases` would be written purely in `Dart` without any `Flutter` elements. -The reason for that is -that `use_cases` should only be concerned with the business logic of the app, -not with the implementation details. +The `use_cases` module defines the business logic of the app. It is a part that +is independent of the development platform, in other words, it is written +purely in the programming language and doesn't contain any elements from the +platform. In the case of `Flutter`, `use_cases` would be written purely in +`Dart` without any `Flutter` elements. The reason for that is that `use_cases` +should only be concerned with the business logic of the app, not with the +implementation details. #### Interface Adapters - `interface_adapters` -`interface_adapters` is the layer outside `use_cases`. `Interface Adapters` crosses the boundaries -of the layers to communicate with `Application Business Rules`, however, the **Dependency Rule** is +`interface_adapters` is the layer outside `use_cases`. `Interface Adapters` +crosses the boundaries of the layers to communicate with +`Application Business Rules` (Use cases), however, the **Dependency Rule** is never violated. Using `polymorphism`, `Interface Adapters` communicates with -`Application Business Rules` using inherited classes: classes that implement or extend the -`UseCase`s presented in the `Application Business Rules` layer. Since `polymorphism` is used, the -`Gateways` passed to `Interface Adapters` still adhere to the **Dependency Rule** since as far as -`Interface Adapters` is concerned, they are abstract. The implementation is hidden behind the +`Application Business Rules` using inherited classes: classes that implement +or extend the `UseCase`s presented in the `Application Business Rules` layer. +Since `polymorphism` is used, the `Gateways` passed to `Interface Adapters` +still adhere to the **Dependency Rule** since as far as `Interface Adapters` is +concerned, they are abstract. The implementation is hidden behind the `polymorphism`. #### Frameworks and drivers – `lib`, `android`, `ios` etc -The `data` module, which is a part of the outermost `lib` component, is responsible for data -retrieval. This can be in the form of API calls to a server, a local database, or even both. -Part of the `Frameworks and drivers` layer communicates directly with the platform in other -words `android` -and `ios`. `Frameworks and drivers` is responsible for Native functionality. +The `data` module, which is a part of the outermost `lib` component, is +responsible for data retrieval. This can be in the form of API calls to a +server, a local database, or even both. Part of the `Frameworks and drivers` +layer communicates directly with the platform in other words `android` and +`ios`. `Frameworks and drivers` is responsible for Native functionality. `Frameworks and drivers` calls all Native APIs. @@ -158,21 +205,22 @@ and `ios`. `Frameworks and drivers` is responsible for Native functionality. - [PREFER using `const` for instantiating constant constructors](https://dart-lang.github.io/linter/lints/prefer_const_constructors.html) -If a constructor can be invoked as const to produce a canonicalized instance, it's preferable to do -so. +If a constructor can be invoked as const to produce a canonicalized instance, +it's preferable to do so. - [DO sort constructor declarations before other members](https://dart-lang.github.io/linter/lints/sort_constructors_first.html) - ### Avoid Mental Mapping -A single-letter name is a poor choice; it’s just a placeholder that the reader must mentally map to -the actual concept. There can be no worse reason for using the name `c` than because `a` and `b` -were already taken. +A single-letter name is a poor choice; it’s just a placeholder that the reader +must mentally map to the actual concept. There can be no worse reason for using +the name `c` than because `a` and `b` were already taken. - ### Method names -Methods should have verb or verb phrase names like postPayment, deletePage, or save. Accessors, -mutators, and predicates should be named for their value and prefixed with `get`…, `set`…, and `is`… +Methods should have verb or verb phrase names like `postPayment`, `deletePage`, +or `save`. Accessors, mutators, and predicates should be named for their value +and prefixed with `get`…, `set`…, and `is`…. - ### Use Intention-Revealing Names @@ -184,7 +232,8 @@ If you can’t pronounce it, you can’t discuss it without sounding like an idi - ### Class Names -Classes and objects should have noun or noun phrase names and not include indistinct noise words: +Classes and objects should have noun or noun phrase names and not include +indistinct noise words: ``` GOOD: @@ -197,35 +246,38 @@ Manager, Processor, Data, Info. - ### Functions should be small Functions should hardly ever be 20 lines long. -Blocks within if statements, else statements, while statements, and so on should be **_one_** line -long. -Probably that line should be a function call. +Blocks within if statements, else statements, while statements, and so on +should be **_one_** line long. Probably that line should be a function call. - ### Functions should do one thing -To know that a function is doing more than “one thing” is if you can extract another function from -it with a name that is not merely a restatement of its implementation. +To know that a function is doing more than “one thing” is if you can extract +another function from it with a name that is not merely a restatement of its +implementation. - ### One Level of Abstraction per Function -We want the code to read like a top-down narrative. -We want every function to be followed by those at the next level of abstraction so that we can read -the program, descending one level of abstraction at a time as we read down the list of functions. +We want the code to read like a top-down narrative. We want every function to +be followed by those at the next level of abstraction so that we can read the +program, descending one level of abstraction at a time as we read down the list +of functions. - ### Dependent Functions -If one function calls another, they should be vertically close, and the caller should be **_above_** -the callee, if possible. +If one function calls another, they should be vertically close, and the caller +should be **_above_** the callee, if possible. - ### Use Descriptive Names -Don’t be afraid to make a name long. A long descriptive name is better than a short enigmatic name. -A long descriptive name is better than a long descriptive comment. +Don’t be afraid to make a name long. A long descriptive name is better than a +short enigmatic name. A long descriptive name is better than a long descriptive +comment. - ### Function Arguments -The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed -closely by two (dyadic). Three arguments (triadic) should be avoided where possible. +The ideal number of arguments for a function is zero (niladic). Next comes one +(monadic), followed closely by two (dyadic). Three arguments (triadic) should +be avoided where possible. ``` GOOD: @@ -237,9 +289,10 @@ includeSetupPageInto(newPageContent) - ### Flag Arguments -Flag arguments are ugly. Passing a boolean into a function is a truly terrible practice. It -immediately complicates the signature of the method, loudly proclaiming that this function does more -than one thing. It does one thing if the flag is true and another if the flag is false! +Flag arguments are ugly. Passing a boolean into a function is a truly terrible +practice. It immediately complicates the signature of the method, loudly +proclaiming that this function does more than one thing. It does one thing if +the flag is true and another if the flag is false! ``` GOOD: @@ -252,11 +305,10 @@ render(bool isSuite) - ### Explain Yourself in Code -Only the code can truly tell you what it does. -Comments are, at best, a necessary evil. -Rather than spend your time writing the comments that explain the mess you’ve made, spend it -cleaning that mess. -Inaccurate comments are far worse than no comments at all. +Only the code can truly tell you what it does. Comments are, at best, a +necessary evil. Rather than spend your time writing the comments that explain +the mess you’ve made, spend it cleaning that mess. Inaccurate comments are far +worse than no comments at all. ``` BAD: @@ -271,13 +323,13 @@ if (employee.isEligibleForFullBenefits()) - ### TODO Comments -Nowadays, good IDEs provide special gestures and features to locate all the `//TODO` comments, -so it’s not likely that they will get lost. +Nowadays, good IDEs provide special gestures and features to locate all the +`//TODO` comments, so it’s not likely that they will get lost. - ### Public APIs -There is nothing quite so helpful and satisfying as a well-described public API. It would be -challenging, at best, to write programs without them. +There is nothing quite so helpful and satisfying as a well-described public API. +It would be challenging, at best, to write programs without them. ```dart /// dart doc comment @@ -285,14 +337,14 @@ challenging, at best, to write programs without them. - ### Commented-Out Code -We’ve had good source code control systems for a very long time now. Those systems will remember the -code for us. We don’t have to comment it out anymore. +We’ve had good source code control systems for a very long time now. Those +systems will remember the code for us. We don’t have to comment it out anymore. - ### Position Markers -In general, they are the clutter that should be eliminated—especially the noisy train of slashes at -the -end. If you overuse banners, they’ll fall into the background noise and be ignored. +In general, they are the clutter that should be eliminated—especially the noisy +train of slashes at the end. If you overuse banners, they’ll fall into the +background noise and be ignored. ```dart // Actions ////////////////////////////////// @@ -300,57 +352,57 @@ end. If you overuse banners, they’ll fall into the background noise and be ign - ### Don’t Return Null -When we return `null`, we are essentially creating work for ourselves and foisting problems upon our -callers. -All it takes is one missing `null` check to send an app spinning out of control. +When we return `null`, we are essentially creating work for ourselves and +foisting problems upon our callers. All it takes is one missing `null` check to +send an app spinning out of control. - ### Don’t Pass Null -In most programming languages, there is no GOOD way to deal with a null that is passed by a caller -accidentally. Because this is the case, the rational approach is to forbid passing null by default. -When you do, you can code with the knowledge that a null in an argument list is an indication of a -problem, and end up with far fewer careless mistakes. +In most programming languages, there is no **GOOD** way to deal with a `null` +that is passed by a caller accidentally. Because this is the case, the rational +approach is to forbid passing null by default. When you do, you can code with +the knowledge that a `null` in an argument list is an indication of a problem, +and end up with far fewer careless mistakes. - ### Classes Should Be Small! -With functions, we measured size by counting physical lines. With classes, we use a different -measure. -We count responsibilities. -The Single Responsibility Principle (SRP) states that a class or module should have one, and only -one, reason to change. -The name of a class should describe what responsibilities it fulfills. The more ambiguous the class -name, the more likely it has too many responsibilities. -The problem is that too many of us think that we are done once the program works. We move on to the -next problem rather than going back and breaking the overstuffed classes into decoupled units with -single responsibilities. +With functions, we measured size by counting physical lines. With classes, we +use a different measure. **We count responsibilities.** The Single +Responsibility Principle (SRP) states that a class or module should have one, +and only one, reason to change. The name of a class should describe what +responsibilities it fulfills. The more ambiguous the class name, the more +likely it has too many responsibilities. The problem is that too many of us +think that we are done once the program works. We move on to the next problem +rather than going back and breaking the overstuffed classes into decoupled +units with single responsibilities. - ### Artificial Coupling -In general, an artificial coupling is a coupling between two modules that serves no direct purpose. -It is a result of putting a variable, constant, or function in a temporarily convenient, though -inappropriate, location. -For example, general `enum`s should not be contained within more specific classes because this -forces the app to know about these more specific classes. -The same goes for general -purpose `static` functions being declared in specific classes. +In general, an artificial coupling is a coupling between two modules that +serves no direct purpose. It is a result of putting a variable, constant, or +function in a temporarily convenient, though inappropriate, location. For +example, general `enum`s should not be contained within more specific classes +because this forces the app to know about these more specific classes. The same +goes for general purpose `static` functions being declared in specific classes. - ### Prefer Polymorphism to If/Else or Switch/Case -There may be no more than one switch statement for a given type of selection. The cases in that -switch statement must create polymorphic objects that take the place of other such switch statements -in the rest of the system. +There may be no more than one switch statement for a given type of selection. +The cases in that switch statement must create polymorphic objects that take +the place of other such switch statements in the rest of the system. - ### Replace Magic Numbers with Named Constants -In general, it is a bad idea to have raw numbers in your code. You should hide them behind -well-named -constants. The term “Magic Number” does not apply only to numbers. It applies to any token that has -a value that is not self-describing. +In general, it is a bad idea to have raw numbers in your code. You should hide +them behind well-named constants. The term “Magic Number” does not apply only +to numbers. It applies to any token that has a value that is not +self-describing. - ## Encapsulate Conditionals -Boolean logic is hard enough to understand without having to see it in the context of an `if` -or `while` statement. Extract functions that explain the intent of the conditional. +Boolean logic is hard enough to understand without having to see it in the +context of an `if` or `while` statement. Extract functions that explain the +intent of the conditional. ``` GOOD: @@ -362,8 +414,8 @@ if (timer.hasExpired() && !timer.isRecurrent()) - ### Avoid Negative Conditionals -Negatives are just a bit harder to understand than positives. So, when possible, conditionals should -be expressed as positives. +Negatives are just a bit harder to understand than positives. So, when +possible, conditionals should be expressed as positives. ``` GOOD: @@ -375,7 +427,8 @@ if (!buffer.shouldNotCompact()) - ### Encapsulate Boundary Conditions -Boundary conditions are hard to keep track of. Put the processing for them in one place. +Boundary conditions are hard to keep track of. Put the processing for them in +one place. ``` BAD: @@ -394,31 +447,26 @@ if (nextLevel < tags.length) { - ### Constants versus Enums -Don’t keep using the old trick of public `static` `final` `int`s. `enum`s can have methods and -fields. This makes them very powerful tools that allow much more expression and flexibility. +Don’t keep using the old trick of public `static` `final` `int`s. `enum`s can +have methods and fields. This makes them very powerful tools that allow much +more expression and flexibility. ## Additional information When it is time to release the system, the process proceeds from the bottom up. -First the -`Entities` component is compiled, tested, and released. -Then the same is done for interactors from -the `use_cases`. -These components are followed by `interface_adapters` ( -Presenters, View and ViewModels). -(`data` and `main`) go last. -This process -is very clear to deal with. -We know how to build the system +First the `Entities` component is compiled, tested, and released. Then the same +is done for interactors from the `use_cases`. These components are followed by +`interface_adapters` (Presenters, View and ViewModels). `data` and `main` go +last. This process is very clear to deal with. We know how to build the system because we understand the dependencies between its parts. # Usage -To use Ethical Scanner, you need to grant the app permission to access your camera. Then, you can -scan the barcode of any product by pointing your camera at it. The app will show you the product -name and ethical status. +To use **Ethical Scanner**, you need to grant the app permission to access your +camera. Then, you can scan the barcode of any product by pointing your camera +at it. The app will show you the product name and some information. • Screenshots: diff --git a/analysis_options.yaml b/analysis_options.yaml index 61e87fa..f488ab6 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -15,7 +15,8 @@ analyzer: linter: # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # section below to disable rules from the + # `package: flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at https://dart.dev/lints. # @@ -27,7 +28,7 @@ linter: rules: # ------ Enabling individual rules -----# # --- # - # These rules here are good but too # + # These rules here are good but too ----# # opinionated to enable them by default.# # ------------------------------------- # require_trailing_commas: true diff --git a/android/.gitignore b/android/.gitignore index 6f56801..6f8cd4b 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -11,3 +11,6 @@ GeneratedPluginRegistrant.java key.properties **/*.keystore **/*.jks +/keystore +/keystore/ +/app/google-services.json diff --git a/android/app/build.gradle b/android/app/build.gradle index cbbfaa3..db1773b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -45,7 +45,9 @@ def SIGNING_KEY_RELEASE_KEY_PASSWORD = keyProperties.getProperty('SIGNING_KEY_RE android { //noinspection GroovyAssignabilityCheck namespace rootProject.application_id + //TODO: change to dynamic "flutter.compileSdkVersion" version, when the error is gone. compileSdk 34 + //TODO: change to dynamic "flutter.ndkVersion" version, when the error is gone. ndkVersion "25.1.8937393" compileOptions { @@ -65,6 +67,7 @@ android { applicationId rootProject.application_id // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + //TODO: change to dynamic "flutter.minSdkVersion" value, when it will be possible. minSdkVersion 21 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() @@ -73,10 +76,15 @@ android { signingConfigs { production { // FCI_BUILD_ID is exported by Codemagic + //noinspection GroovyAssignabilityCheck if (System.getenv()["FCI_BUILD_ID"] != null) { + //noinspection GroovyAssignabilityCheck storeFile file(System.getenv()["CM_KEYSTORE_PATH"]) + //noinspection GroovyAssignabilityCheck storePassword System.getenv()["CM_KEYSTORE_PASSWORD"] + //noinspection GroovyAssignabilityCheck keyAlias System.getenv()["CM_KEY_ALIAS"] + //noinspection GroovyAssignabilityCheck keyPassword System.getenv()["CM_KEY_PASSWORD"] } else { storeFile file(SIGNING_KEY_RELEASE_PATH) @@ -87,10 +95,15 @@ android { } dev { // FCI_BUILD_ID is exported by Codemagic + //noinspection GroovyAssignabilityCheck if (System.getenv()["FCI_BUILD_ID"] != null) { + //noinspection GroovyAssignabilityCheck storeFile file(System.getenv()["CM_KEYSTORE_PATH"]) + //noinspection GroovyAssignabilityCheck storePassword System.getenv()["CM_KEYSTORE_PASSWORD"] + //noinspection GroovyAssignabilityCheck keyAlias System.getenv()["CM_KEY_ALIAS"] + //noinspection GroovyAssignabilityCheck keyPassword System.getenv()["CM_KEY_PASSWORD"] } else { storeFile file(SIGNING_KEY_DEBUG_PATH) @@ -118,6 +131,6 @@ flutter { } dependencies { - implementation 'com.google.firebase:firebase-crashlytics:18.6.2' - implementation 'com.google.firebase:firebase-analytics:21.5.1' + implementation 'com.google.firebase:firebase-crashlytics:18.6.4' + implementation 'com.google.firebase:firebase-analytics:21.6.2' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index fa9a4ff..0de5211 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="10" + android:versionName="0.1.0"> diff --git a/components/core/entities/example/entities_example.dart b/components/core/entities/example/entities_example.dart deleted file mode 100644 index 153f055..0000000 --- a/components/core/entities/example/entities_example.dart +++ /dev/null @@ -1,3 +0,0 @@ -void main() { - -} diff --git a/components/core/use_cases/example/use_cases_example.dart b/components/core/use_cases/example/use_cases_example.dart deleted file mode 100644 index 153f055..0000000 --- a/components/core/use_cases/example/use_cases_example.dart +++ /dev/null @@ -1,3 +0,0 @@ -void main() { - -} diff --git a/components/core/entities/.gitignore b/components/domain/entities/.gitignore similarity index 100% rename from components/core/entities/.gitignore rename to components/domain/entities/.gitignore diff --git a/components/core/entities/CHANGELOG.md b/components/domain/entities/CHANGELOG.md similarity index 100% rename from components/core/entities/CHANGELOG.md rename to components/domain/entities/CHANGELOG.md diff --git a/components/core/entities/README.md b/components/domain/entities/README.md similarity index 100% rename from components/core/entities/README.md rename to components/domain/entities/README.md diff --git a/components/core/entities/analysis_options.yaml b/components/domain/entities/analysis_options.yaml similarity index 100% rename from components/core/entities/analysis_options.yaml rename to components/domain/entities/analysis_options.yaml diff --git a/components/domain/entities/example/entities_example.dart b/components/domain/entities/example/entities_example.dart new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/components/domain/entities/example/entities_example.dart @@ -0,0 +1 @@ +void main() {} diff --git a/components/core/entities/lib/entities.dart b/components/domain/entities/lib/entities.dart similarity index 82% rename from components/core/entities/lib/entities.dart rename to components/domain/entities/lib/entities.dart index c31690c..0b7a9a9 100644 --- a/components/core/entities/lib/entities.dart +++ b/components/domain/entities/lib/entities.dart @@ -10,14 +10,14 @@ /// Critical business data. library entities; -export 'src/barcode/barcode.dart'; export 'src/enums/language.dart'; export 'src/enums/product_info_type.dart'; export 'src/enums/vegan.dart'; export 'src/enums/vegetarian.dart'; -export 'src/exception/bad_request_error.dart'; -export 'src/exception/internal_server_error.dart'; -export 'src/exception/not_found_exception.dart'; +export 'src/exceptions/bad_request_error.dart'; +export 'src/exceptions/internal_server_error.dart'; +export 'src/exceptions/not_found_exception.dart'; +export 'src/localized_code/localized_code.dart'; export 'src/logging_interceptor/logging_interceptor.dart'; export 'src/product/product_info.dart'; export 'src/product/product_photo.dart'; diff --git a/components/core/entities/lib/src/enums/language.dart b/components/domain/entities/lib/src/enums/language.dart similarity index 100% rename from components/core/entities/lib/src/enums/language.dart rename to components/domain/entities/lib/src/enums/language.dart diff --git a/components/core/entities/lib/src/enums/product_info_type.dart b/components/domain/entities/lib/src/enums/product_info_type.dart similarity index 100% rename from components/core/entities/lib/src/enums/product_info_type.dart rename to components/domain/entities/lib/src/enums/product_info_type.dart diff --git a/components/core/entities/lib/src/enums/vegan.dart b/components/domain/entities/lib/src/enums/vegan.dart similarity index 100% rename from components/core/entities/lib/src/enums/vegan.dart rename to components/domain/entities/lib/src/enums/vegan.dart diff --git a/components/core/entities/lib/src/enums/vegetarian.dart b/components/domain/entities/lib/src/enums/vegetarian.dart similarity index 100% rename from components/core/entities/lib/src/enums/vegetarian.dart rename to components/domain/entities/lib/src/enums/vegetarian.dart diff --git a/components/core/entities/lib/src/exception/bad_request_error.dart b/components/domain/entities/lib/src/exceptions/bad_request_error.dart similarity index 100% rename from components/core/entities/lib/src/exception/bad_request_error.dart rename to components/domain/entities/lib/src/exceptions/bad_request_error.dart diff --git a/components/core/entities/lib/src/exception/internal_server_error.dart b/components/domain/entities/lib/src/exceptions/internal_server_error.dart similarity index 100% rename from components/core/entities/lib/src/exception/internal_server_error.dart rename to components/domain/entities/lib/src/exceptions/internal_server_error.dart diff --git a/components/core/entities/lib/src/exception/not_found_exception.dart b/components/domain/entities/lib/src/exceptions/not_found_exception.dart similarity index 100% rename from components/core/entities/lib/src/exception/not_found_exception.dart rename to components/domain/entities/lib/src/exceptions/not_found_exception.dart diff --git a/components/core/entities/lib/src/barcode/barcode.dart b/components/domain/entities/lib/src/localized_code/localized_code.dart similarity index 78% rename from components/core/entities/lib/src/barcode/barcode.dart rename to components/domain/entities/lib/src/localized_code/localized_code.dart index 6929ac5..4081f9b 100644 --- a/components/core/entities/lib/src/barcode/barcode.dart +++ b/components/domain/entities/lib/src/localized_code/localized_code.dart @@ -1,7 +1,7 @@ import 'package:entities/src/enums/language.dart'; -class Barcode { - const Barcode({ +class LocalizedCode { + const LocalizedCode({ required this.code, this.language = Language.en, }); diff --git a/components/core/entities/lib/src/logging_interceptor/logging_interceptor.dart b/components/domain/entities/lib/src/logging_interceptor/logging_interceptor.dart similarity index 100% rename from components/core/entities/lib/src/logging_interceptor/logging_interceptor.dart rename to components/domain/entities/lib/src/logging_interceptor/logging_interceptor.dart diff --git a/components/core/entities/lib/src/product/product_info.dart b/components/domain/entities/lib/src/product/product_info.dart similarity index 100% rename from components/core/entities/lib/src/product/product_info.dart rename to components/domain/entities/lib/src/product/product_info.dart diff --git a/components/core/entities/lib/src/product/product_photo.dart b/components/domain/entities/lib/src/product/product_photo.dart similarity index 100% rename from components/core/entities/lib/src/product/product_photo.dart rename to components/domain/entities/lib/src/product/product_photo.dart diff --git a/components/core/entities/lib/src/rest_client/rest_client.dart b/components/domain/entities/lib/src/rest_client/rest_client.dart similarity index 85% rename from components/core/entities/lib/src/rest_client/rest_client.dart rename to components/domain/entities/lib/src/rest_client/rest_client.dart index 79304a7..946badd 100644 --- a/components/core/entities/lib/src/rest_client/rest_client.dart +++ b/components/domain/entities/lib/src/rest_client/rest_client.dart @@ -1,6 +1,6 @@ import 'package:entities/src/terrorism_sponsor/terrorism_sponsor.dart'; -abstract class RestClient { +abstract interface class RestClient { const RestClient(); Future> getTerrorismSponsors(); diff --git a/components/core/entities/lib/src/terrorism_sponsor/terrorism_sponsor.dart b/components/domain/entities/lib/src/terrorism_sponsor/terrorism_sponsor.dart similarity index 64% rename from components/core/entities/lib/src/terrorism_sponsor/terrorism_sponsor.dart rename to components/domain/entities/lib/src/terrorism_sponsor/terrorism_sponsor.dart index 5043930..4875a10 100644 --- a/components/core/entities/lib/src/terrorism_sponsor/terrorism_sponsor.dart +++ b/components/domain/entities/lib/src/terrorism_sponsor/terrorism_sponsor.dart @@ -15,14 +15,12 @@ abstract class TerrorismSponsor { } extension TerrorismSponsorList on List { - bool sponsoredBy(ProductInfo product) { - return _isSponsoredByOtherRussiaSponsors(product) || - _isSponsoredByAnyTerrorismSponsor(product); - } + bool sponsoredBy(ProductInfo product) => + _isSponsoredByOtherRussiaSponsors(product) || + _isSponsoredByAnyTerrorismSponsor(product); - bool _isSponsoredByOtherRussiaSponsors(ProductInfo product) { - return _otherRussiaSponsors.contains(product.brand.toLowerCase()); - } + bool _isSponsoredByOtherRussiaSponsors(ProductInfo product) => + _otherTerrorismSponsors.contains(product.brand.toLowerCase()); bool _isSponsoredByAnyTerrorismSponsor(ProductInfo product) { return any( @@ -48,22 +46,20 @@ extension TerrorismSponsorList on List { bool _isSponsorNameNotEmptyAndMatchesProductBrandOrName( TerrorismSponsor terrorismSponsor, ProductInfo product, - ) { - return terrorismSponsor.name.isNotEmpty && - (product.brand.toLowerCase().trim() == - terrorismSponsor.name.toLowerCase().trim() || - product.name.toLowerCase().trim() == - terrorismSponsor.name.toLowerCase().trim()); - } + ) => + terrorismSponsor.name.isNotEmpty && + (product.brand.toLowerCase().trim() == + terrorismSponsor.name.toLowerCase().trim() || + product.name.toLowerCase().trim() == + terrorismSponsor.name.toLowerCase().trim()); bool _isSponsorBrandsNotEmptyAndContainsProductBrand( TerrorismSponsor terrorismSponsor, ProductInfo product, - ) { - return terrorismSponsor.brands.isNotEmpty && - product.brand.isNotEmpty && - _doesSponsorBrandsContainProductBrand(terrorismSponsor, product); - } + ) => + terrorismSponsor.brands.isNotEmpty && + product.brand.isNotEmpty && + _doesSponsorBrandsContainProductBrand(terrorismSponsor, product); bool _doesSponsorBrandsContainProductBrand( TerrorismSponsor terrorismSponsor, @@ -73,14 +69,13 @@ extension TerrorismSponsorList on List { return lowerCaseBrands.contains(product.brand.toLowerCase()); } - List _getLowerCaseBrands(TerrorismSponsor terrorismSponsor) { - return terrorismSponsor.brands - .split(', ') - .map((String brand) => brand.toLowerCase()) - .toList(); - } + List _getLowerCaseBrands(TerrorismSponsor terrorismSponsor) => + terrorismSponsor.brands + .split(', ') + .map((String brand) => brand.toLowerCase()) + .toList(); - List get _otherRussiaSponsors => [ + List get _otherTerrorismSponsors => [ 'twix', 'quaker', 'nestlé', diff --git a/components/core/entities/pubspec.yaml b/components/domain/entities/pubspec.yaml similarity index 87% rename from components/core/entities/pubspec.yaml rename to components/domain/entities/pubspec.yaml index c8c2d29..49f4902 100644 --- a/components/core/entities/pubspec.yaml +++ b/components/domain/entities/pubspec.yaml @@ -2,11 +2,11 @@ name: entities description: An Entity is an object within our computer system that embodies a small set of critical business rules operating on Critical Business Data. version: 1.0.0 -repository: https://github.com/Turskyi/ethical_scanner/tree/master/components/core/entities +repository: https://github.com/Turskyi/ethical_scanner/tree/master/components/domain/entities environment: sdk: '>=3.2.3 <4.0.0' dev_dependencies: lints: ^3.0.0 - test: ^1.25.2 + test: ^1.25.4 diff --git a/components/core/entities/test/entities_test.dart b/components/domain/entities/test/entities_test.dart similarity index 100% rename from components/core/entities/test/entities_test.dart rename to components/domain/entities/test/entities_test.dart diff --git a/components/core/use_cases/.gitignore b/components/domain/use_cases/.gitignore similarity index 100% rename from components/core/use_cases/.gitignore rename to components/domain/use_cases/.gitignore diff --git a/components/core/use_cases/CHANGELOG.md b/components/domain/use_cases/CHANGELOG.md similarity index 100% rename from components/core/use_cases/CHANGELOG.md rename to components/domain/use_cases/CHANGELOG.md diff --git a/components/core/use_cases/README.md b/components/domain/use_cases/README.md similarity index 100% rename from components/core/use_cases/README.md rename to components/domain/use_cases/README.md diff --git a/components/core/use_cases/analysis_options.yaml b/components/domain/use_cases/analysis_options.yaml similarity index 100% rename from components/core/use_cases/analysis_options.yaml rename to components/domain/use_cases/analysis_options.yaml diff --git a/components/domain/use_cases/example/use_cases_example.dart b/components/domain/use_cases/example/use_cases_example.dart new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/components/domain/use_cases/example/use_cases_example.dart @@ -0,0 +1 @@ +void main() {} diff --git a/components/core/use_cases/lib/src/gateways/product_info_gateway.dart b/components/domain/use_cases/lib/src/gateways/product_info_gateway.dart similarity index 77% rename from components/core/use_cases/lib/src/gateways/product_info_gateway.dart rename to components/domain/use_cases/lib/src/gateways/product_info_gateway.dart index fd02158..0f72f00 100644 --- a/components/core/use_cases/lib/src/gateways/product_info_gateway.dart +++ b/components/domain/use_cases/lib/src/gateways/product_info_gateway.dart @@ -3,7 +3,7 @@ import 'package:entities/entities.dart'; abstract interface class ProductInfoGateway { const ProductInfoGateway(); - Future getProductInfoAsFuture(Barcode barcode); + Future getProductInfoAsFuture(LocalizedCode barcode); Future addProduct(ProductInfo productInfo); diff --git a/components/core/use_cases/lib/src/gateways/settings_gateway.dart b/components/domain/use_cases/lib/src/gateways/settings_gateway.dart similarity index 100% rename from components/core/use_cases/lib/src/gateways/settings_gateway.dart rename to components/domain/use_cases/lib/src/gateways/settings_gateway.dart diff --git a/components/core/use_cases/lib/src/use_cases/add_ingredients_use_case.dart b/components/domain/use_cases/lib/src/use_cases/add_ingredients_use_case.dart similarity index 94% rename from components/core/use_cases/lib/src/use_cases/add_ingredients_use_case.dart rename to components/domain/use_cases/lib/src/use_cases/add_ingredients_use_case.dart index 89a3a6a..fd7e4b1 100644 --- a/components/core/use_cases/lib/src/use_cases/add_ingredients_use_case.dart +++ b/components/domain/use_cases/lib/src/use_cases/add_ingredients_use_case.dart @@ -9,7 +9,7 @@ class AddIngredientsUseCase implements UseCase, ProductPhoto> { @override Future call([ProductPhoto productPhoto = const ProductPhoto()]) { - if(productPhoto.info.name.isEmpty){ + if (productPhoto.info.name.isEmpty) { return _productInfoGateway.addProduct(productPhoto.info).whenComplete(() { _productInfoGateway.addIngredients(productPhoto); }); diff --git a/components/core/use_cases/lib/src/use_cases/get_language_use_case.dart b/components/domain/use_cases/lib/src/use_cases/get_language_use_case.dart similarity index 100% rename from components/core/use_cases/lib/src/use_cases/get_language_use_case.dart rename to components/domain/use_cases/lib/src/use_cases/get_language_use_case.dart diff --git a/components/core/use_cases/lib/src/use_cases/get_precipitation_state_use_case.dart b/components/domain/use_cases/lib/src/use_cases/get_precipitation_state_use_case.dart similarity index 77% rename from components/core/use_cases/lib/src/use_cases/get_precipitation_state_use_case.dart rename to components/domain/use_cases/lib/src/use_cases/get_precipitation_state_use_case.dart index d07418a..38eb189 100644 --- a/components/core/use_cases/lib/src/use_cases/get_precipitation_state_use_case.dart +++ b/components/domain/use_cases/lib/src/use_cases/get_precipitation_state_use_case.dart @@ -6,6 +6,5 @@ class GetPrecipitationStateUseCase implements UseCase { final SettingsGateway _settingsGateway; @override - bool call([_]) => - _settingsGateway.getPrecipitationState(); + bool call([_]) => _settingsGateway.getPrecipitationState(); } diff --git a/components/core/use_cases/lib/src/use_cases/get_product_info_use_case.dart b/components/domain/use_cases/lib/src/use_cases/get_product_info_use_case.dart similarity index 58% rename from components/core/use_cases/lib/src/use_cases/get_product_info_use_case.dart rename to components/domain/use_cases/lib/src/use_cases/get_product_info_use_case.dart index d8e6336..d02fd80 100644 --- a/components/core/use_cases/lib/src/use_cases/get_product_info_use_case.dart +++ b/components/domain/use_cases/lib/src/use_cases/get_product_info_use_case.dart @@ -1,12 +1,15 @@ import 'package:entities/entities.dart'; import 'package:use_cases/use_cases.dart'; -class GetProductInfoUseCase implements UseCase, Barcode> { +class GetProductInfoUseCase + implements UseCase, LocalizedCode> { const GetProductInfoUseCase(this._productInfoGateway); final ProductInfoGateway _productInfoGateway; @override - Future call([Barcode barcode = const Barcode(code: '')]) => + Future call([ + LocalizedCode barcode = const LocalizedCode(code: ''), + ]) => _productInfoGateway.getProductInfoAsFuture(barcode); } diff --git a/components/core/use_cases/lib/src/use_cases/get_sound_preference_use_case.dart b/components/domain/use_cases/lib/src/use_cases/get_sound_preference_use_case.dart similarity index 100% rename from components/core/use_cases/lib/src/use_cases/get_sound_preference_use_case.dart rename to components/domain/use_cases/lib/src/use_cases/get_sound_preference_use_case.dart diff --git a/components/core/use_cases/lib/src/use_cases/save_language_use_case.dart b/components/domain/use_cases/lib/src/use_cases/save_language_use_case.dart similarity index 100% rename from components/core/use_cases/lib/src/use_cases/save_language_use_case.dart rename to components/domain/use_cases/lib/src/use_cases/save_language_use_case.dart diff --git a/components/core/use_cases/lib/src/use_cases/save_precipitation_state_use_case.dart b/components/domain/use_cases/lib/src/use_cases/save_precipitation_state_use_case.dart similarity index 100% rename from components/core/use_cases/lib/src/use_cases/save_precipitation_state_use_case.dart rename to components/domain/use_cases/lib/src/use_cases/save_precipitation_state_use_case.dart diff --git a/components/core/use_cases/lib/src/use_cases/save_sound_preference_use_case.dart b/components/domain/use_cases/lib/src/use_cases/save_sound_preference_use_case.dart similarity index 100% rename from components/core/use_cases/lib/src/use_cases/save_sound_preference_use_case.dart rename to components/domain/use_cases/lib/src/use_cases/save_sound_preference_use_case.dart diff --git a/components/core/use_cases/lib/src/use_cases/use_case.dart b/components/domain/use_cases/lib/src/use_cases/use_case.dart similarity index 100% rename from components/core/use_cases/lib/src/use_cases/use_case.dart rename to components/domain/use_cases/lib/src/use_cases/use_case.dart diff --git a/components/core/use_cases/lib/use_cases.dart b/components/domain/use_cases/lib/use_cases.dart similarity index 100% rename from components/core/use_cases/lib/use_cases.dart rename to components/domain/use_cases/lib/use_cases.dart diff --git a/components/core/use_cases/pubspec.yaml b/components/domain/use_cases/pubspec.yaml similarity index 93% rename from components/core/use_cases/pubspec.yaml rename to components/domain/use_cases/pubspec.yaml index 9fa70a6..b1400bc 100644 --- a/components/core/use_cases/pubspec.yaml +++ b/components/domain/use_cases/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.0.1 # This prevents the package from being accidentally published to pub.dev using pub publish. This is # preferred for private packages. publish_to: none -repository: https://github.com/Turskyi/ethical_scanner/tree/master/components/core/use_cases +repository: https://github.com/Turskyi/ethical_scanner/tree/master/components/domain/use_cases environment: sdk: '>=3.2.3 <4.0.0' @@ -17,4 +17,4 @@ dependencies: dev_dependencies: lints: ^3.0.0 - test: ^1.25.2 + test: ^1.25.4 diff --git a/components/core/use_cases/test/construct_pm_use_cases_test.dart b/components/domain/use_cases/test/construct_pm_use_cases_test.dart similarity index 100% rename from components/core/use_cases/test/construct_pm_use_cases_test.dart rename to components/domain/use_cases/test/construct_pm_use_cases_test.dart diff --git a/components/interface_adapters/coverage/lcov.info b/components/interface_adapters/coverage/lcov.info new file mode 100644 index 0000000..e69de29 diff --git a/components/interface_adapters/lib/interface_adapters.dart b/components/interface_adapters/lib/interface_adapters.dart index 48bdb20..55aaef6 100644 --- a/components/interface_adapters/lib/interface_adapters.dart +++ b/components/interface_adapters/lib/interface_adapters.dart @@ -13,7 +13,6 @@ export 'src/env/env.dart'; export 'src/gateways/product_info_gateway_impl.dart'; export 'src/gateways/settings_gateway_impl.dart'; export 'src/ui/app/app.dart'; -export 'src/ui/modules/home/home_event.dart'; export 'src/ui/modules/home/home_presenter.dart'; export 'src/ui/modules/home/view/home_view.dart'; export 'src/ui/modules/photo/photo_presenter.dart'; diff --git a/components/interface_adapters/lib/src/data_sources/remote/remote_data_source.dart b/components/interface_adapters/lib/src/data_sources/remote/remote_data_source.dart index 8520ef6..fd14021 100644 --- a/components/interface_adapters/lib/src/data_sources/remote/remote_data_source.dart +++ b/components/interface_adapters/lib/src/data_sources/remote/remote_data_source.dart @@ -2,9 +2,10 @@ import 'package:entities/entities.dart'; abstract interface class RemoteDataSource { const RemoteDataSource(); - Future getProductInfoAsFuture(Barcode barcode); - Future getIngredientsText(Barcode barcode); + Future getProductInfoAsFuture(LocalizedCode barcode); + + Future getIngredientsText(LocalizedCode barcode); Future getCountryFromAiAsFuture(String barcode); diff --git a/components/interface_adapters/lib/src/gateways/product_info_gateway_impl.dart b/components/interface_adapters/lib/src/gateways/product_info_gateway_impl.dart index 14daf76..379291c 100644 --- a/components/interface_adapters/lib/src/gateways/product_info_gateway_impl.dart +++ b/components/interface_adapters/lib/src/gateways/product_info_gateway_impl.dart @@ -14,7 +14,7 @@ class ProductInfoGatewayImpl implements ProductInfoGateway { final LocalDataSource _localDataSource; @override - Future getProductInfoAsFuture(Barcode input) { + Future getProductInfoAsFuture(LocalizedCode input) { return _remoteDataSource .getProductInfoAsFuture(input) .onError((Object? error, StackTrace stackTrace) { diff --git a/components/interface_adapters/lib/src/ui/app/app.dart b/components/interface_adapters/lib/src/ui/app/app.dart index 2012be2..39d1eed 100644 --- a/components/interface_adapters/lib/src/ui/app/app.dart +++ b/components/interface_adapters/lib/src/ui/app/app.dart @@ -5,6 +5,6 @@ abstract interface class App extends StatelessWidget { const App({super.key}); factory App.factory(RouteFactory routeFactory) => EthicalScannerApp( - routeFactory: routeFactory, - ); + routeFactory: routeFactory, + ); } diff --git a/components/interface_adapters/lib/src/ui/modules/home/home_event.dart b/components/interface_adapters/lib/src/ui/modules/home/home_event.dart index 168cf04..d67185b 100644 --- a/components/interface_adapters/lib/src/ui/modules/home/home_event.dart +++ b/components/interface_adapters/lib/src/ui/modules/home/home_event.dart @@ -1,10 +1,11 @@ -import 'package:entities/entities.dart'; +part of 'home_presenter.dart'; -sealed class HomeEvent { +@immutable +abstract class HomeEvent { const HomeEvent(); } -class LoadHomeEvent extends HomeEvent{ +class LoadHomeEvent extends HomeEvent { const LoadHomeEvent(); } diff --git a/components/interface_adapters/lib/src/ui/modules/home/home_presenter.dart b/components/interface_adapters/lib/src/ui/modules/home/home_presenter.dart index e9f6132..c4d8805 100644 --- a/components/interface_adapters/lib/src/ui/modules/home/home_presenter.dart +++ b/components/interface_adapters/lib/src/ui/modules/home/home_presenter.dart @@ -1,10 +1,11 @@ import 'package:bloc/bloc.dart'; import 'package:entities/entities.dart'; import 'package:interface_adapters/src/error_message_extractor.dart'; -import 'package:interface_adapters/src/ui/modules/home/home_event.dart'; +import 'package:meta/meta.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:use_cases/use_cases.dart'; +part 'home_event.dart'; part 'home_view_model.dart'; class HomePresenter extends Bloc { @@ -84,7 +85,7 @@ class HomePresenter extends Bloc { productInfo = event.productInfo.ingredientList.isEmpty ? await _getProductInfoUseCase.call( - Barcode( + LocalizedCode( code: event.productInfo.barcode, language: event.productInfo.language, ), @@ -358,7 +359,7 @@ class HomePresenter extends Bloc { ); } - final UseCase, Barcode> _getProductInfoUseCase; + final UseCase, LocalizedCode> _getProductInfoUseCase; final UseCase, bool> _savePrecipitationStateUseCase; final UseCase _getPrecipitationStateUseCase; final UseCase, String> _saveLanguageUseCase; diff --git a/components/interface_adapters/lib/src/ui/modules/home/view/home_view.dart b/components/interface_adapters/lib/src/ui/modules/home/view/home_view.dart index 5a2720c..b91acf3 100644 --- a/components/interface_adapters/lib/src/ui/modules/home/view/home_view.dart +++ b/components/interface_adapters/lib/src/ui/modules/home/view/home_view.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import 'package:interface_adapters/src/ui/modules/home/home_event.dart'; import 'package:interface_adapters/src/ui/modules/home/home_presenter.dart'; import 'package:interface_adapters/src/ui/modules/home/view/widgets/fab.dart'; import 'package:interface_adapters/src/ui/modules/home/view/widgets/language_selector.dart'; diff --git a/components/interface_adapters/lib/src/ui/modules/home/view/widgets/code_tile.dart b/components/interface_adapters/lib/src/ui/modules/home/view/widgets/code_tile.dart index 9a81013..0daead2 100644 --- a/components/interface_adapters/lib/src/ui/modules/home/view/widgets/code_tile.dart +++ b/components/interface_adapters/lib/src/ui/modules/home/view/widgets/code_tile.dart @@ -1,10 +1,18 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:entities/entities.dart'; +import 'package:feedback/feedback.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_email_sender/flutter_email_sender.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:interface_adapters/interface_adapters.dart'; import 'package:interface_adapters/src/ui/res/color/material_colors.dart'; import 'package:interface_adapters/src/ui/res/resources.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path_provider/path_provider.dart'; class CodeTile extends StatefulWidget { const CodeTile({ @@ -92,16 +100,56 @@ class _CodeTileState extends State { fontSize: _textTheme.bodyLarge?.fontSize, ), ), + trailing: IconButton( + icon: const Icon(Icons.bug_report_outlined), + onPressed: _onBugReportPressed, + ), ); } - void _onCodeTextChanged(String text) => - _editNotifier.value = text.isNotEmpty && text != widget.value; - @override void dispose() { _codeController.dispose(); _editNotifier.dispose(); super.dispose(); } + + void _onCodeTextChanged(String text) => + _editNotifier.value = text.isNotEmpty && text != widget.value; + + Future _onBugReportPressed() => PackageInfo.fromPlatform().then( + (PackageInfo packageInfo) => BetterFeedback.of(context).show( + (UserFeedback feedback) => _sendFeedback( + feedback: feedback, + packageInfo: packageInfo, + ), + ), + ); + + Future _sendFeedback({ + required UserFeedback feedback, + required PackageInfo packageInfo, + }) => + _writeImageToStorage(feedback.screenshot) + .then((String screenshotFilePath) { + return FlutterEmailSender.send( + Email( + body: '${feedback.text}\n\nApp id: ${packageInfo.packageName}\n' + 'App version: ${packageInfo.version}\n' + 'Build number: ${packageInfo.buildNumber}', + subject: '${translate('app_feedback')}: ' + '${packageInfo.appName}', + recipients: [Env.supportEmail], + attachmentPaths: [screenshotFilePath], + ), + ); + }); + + Future _writeImageToStorage(Uint8List feedbackScreenshot) async { + final Directory output = await getTemporaryDirectory(); + final String screenshotFilePath = '${output.path}/feedback.png'; + final File screenshotFile = File(screenshotFilePath); + await screenshotFile.writeAsBytes(feedbackScreenshot); + return screenshotFilePath; + } } diff --git a/components/interface_adapters/lib/src/ui/modules/home/view/widgets/product_info_body.dart b/components/interface_adapters/lib/src/ui/modules/home/view/widgets/product_info_body.dart index 34b5041..44e4dab 100644 --- a/components/interface_adapters/lib/src/ui/modules/home/view/widgets/product_info_body.dart +++ b/components/interface_adapters/lib/src/ui/modules/home/view/widgets/product_info_body.dart @@ -1,13 +1,6 @@ -import 'dart:async'; -import 'dart:io'; - import 'package:entities/entities.dart'; -import 'package:feedback/feedback.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_email_sender/flutter_email_sender.dart'; -import 'package:flutter_translate/flutter_translate.dart'; import 'package:interface_adapters/interface_adapters.dart'; import 'package:interface_adapters/src/ui/modules/home/view/widgets/code_tile.dart'; import 'package:interface_adapters/src/ui/modules/home/view/widgets/delayed_animation.dart'; @@ -15,8 +8,6 @@ import 'package:interface_adapters/src/ui/modules/home/view/widgets/loading_indi import 'package:interface_adapters/src/ui/modules/home/view/widgets/product_info_tile.dart'; import 'package:interface_adapters/src/ui/res/resources.dart'; import 'package:interface_adapters/src/ui/res/values/dimens.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:path_provider/path_provider.dart'; class ProductInfoBody extends StatelessWidget { const ProductInfoBody({super.key}); @@ -37,47 +28,39 @@ class ProductInfoBody extends StatelessWidget { child: BlocBuilder( builder: (_, HomeViewModel viewModel) { if (viewModel is ProductInfoState) { - return RefreshIndicator( - onRefresh: () => PackageInfo.fromPlatform().then( - (PackageInfo packageInfo) => BetterFeedback.of(context).show( - (UserFeedback feedback) => - _sendFeedback(feedback, packageInfo), - ), + return ListView.builder( + padding: EdgeInsets.only( + top: dimens.productInfoListTopPadding, + bottom: dimens.productInfoListBottomPadding, ), - child: ListView.builder( - padding: EdgeInsets.only( - top: dimens.productInfoListTopPadding, - bottom: dimens.productInfoListBottomPadding, - ), - itemCount: viewModel.productInfoMap.keys.length + - loadingIndicatorCount, - itemBuilder: (BuildContext context, int index) { - if (index == viewModel.productInfoMap.keys.length) { - if (viewModel is LoadingProductInfoState) { - // Return `LoadingIndicatorWidget` as the last item. - return const LoadingIndicatorWidget(); - } else { - return const SizedBox(); - } + itemCount: + viewModel.productInfoMap.keys.length + loadingIndicatorCount, + itemBuilder: (BuildContext context, int index) { + if (index == viewModel.productInfoMap.keys.length) { + if (viewModel is LoadingProductInfoState) { + // Return `LoadingIndicatorWidget` as the last item. + return const LoadingIndicatorWidget(); } else { - ProductInfoType type = - viewModel.productInfoMap.keys.elementAt( - index, - ); - String value = viewModel.productInfoMap[type] ?? ''; - return DelayedAnimation( - delay: index * animationDelay, - child: type.isCode - ? CodeTile(value: value) - : ProductInfoTile( - type: type, - value: value, - info: viewModel.productInfo, - ), - ); + return const SizedBox(); } - }, - ), + } else { + ProductInfoType type = + viewModel.productInfoMap.keys.elementAt( + index, + ); + String value = viewModel.productInfoMap[type] ?? ''; + return DelayedAnimation( + delay: index * animationDelay, + child: type.isCode + ? CodeTile(value: value) + : ProductInfoTile( + type: type, + value: value, + info: viewModel.productInfo, + ), + ); + } + }, ); } else { return const SizedBox(); @@ -86,29 +69,4 @@ class ProductInfoBody extends StatelessWidget { ), ); } - - Future _sendFeedback(UserFeedback feedback, PackageInfo packageInfo) { - return _writeImageToStorage(feedback.screenshot) - .then((String screenshotFilePath) { - return FlutterEmailSender.send( - Email( - body: '${feedback.text}\n\n${packageInfo.packageName}\n' - '${packageInfo.version}\n' - '${packageInfo.buildNumber}', - subject: '${translate('app_feedback')}: ' - '${packageInfo.appName}', - recipients: [Env.openFoodPassword], - attachmentPaths: [screenshotFilePath], - ), - ); - }); - } - - Future _writeImageToStorage(Uint8List feedbackScreenshot) async { - final Directory output = await getTemporaryDirectory(); - final String screenshotFilePath = '${output.path}/feedback.png'; - final File screenshotFile = File(screenshotFilePath); - await screenshotFile.writeAsBytes(feedbackScreenshot); - return screenshotFilePath; - } } diff --git a/components/interface_adapters/lib/src/ui/modules/home/view/widgets/product_info_tile.dart b/components/interface_adapters/lib/src/ui/modules/home/view/widgets/product_info_tile.dart index 5c86c1c..543a7b0 100644 --- a/components/interface_adapters/lib/src/ui/modules/home/view/widgets/product_info_tile.dart +++ b/components/interface_adapters/lib/src/ui/modules/home/view/widgets/product_info_tile.dart @@ -3,7 +3,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import 'package:interface_adapters/src/ui/modules/home/home_event.dart'; import 'package:interface_adapters/src/ui/modules/home/home_presenter.dart'; import 'package:interface_adapters/src/ui/res/color/material_colors.dart'; import 'package:interface_adapters/src/ui/res/resources.dart'; diff --git a/components/interface_adapters/lib/src/ui/modules/photo/photo_event.dart b/components/interface_adapters/lib/src/ui/modules/photo/photo_event.dart index 81a7aee..0c3d5d6 100644 --- a/components/interface_adapters/lib/src/ui/modules/photo/photo_event.dart +++ b/components/interface_adapters/lib/src/ui/modules/photo/photo_event.dart @@ -13,7 +13,6 @@ class PhotoViewBackEvent extends PhotoEvent { const PhotoViewBackEvent(); } - class TakePhotoEvent extends PhotoEvent { const TakePhotoEvent(); } diff --git a/components/interface_adapters/lib/src/ui/modules/photo/photo_view_model.dart b/components/interface_adapters/lib/src/ui/modules/photo/photo_view_model.dart index 465934b..001ab52 100644 --- a/components/interface_adapters/lib/src/ui/modules/photo/photo_view_model.dart +++ b/components/interface_adapters/lib/src/ui/modules/photo/photo_view_model.dart @@ -4,7 +4,7 @@ sealed class PhotoViewModel { const PhotoViewModel(); } -abstract class LoadingState extends PhotoViewModel{ +abstract class LoadingState extends PhotoViewModel { const LoadingState(); } @@ -33,7 +33,7 @@ class AddIngredientsErrorState extends PhotoViewModel { const AddIngredientsErrorState({ required String barcode, required this.errorMessage, - }):super(); + }) : super(); final String errorMessage; } diff --git a/components/interface_adapters/lib/src/ui/modules/scan/view/scan_view.dart b/components/interface_adapters/lib/src/ui/modules/scan/view/scan_view.dart index eb998c6..220d380 100644 --- a/components/interface_adapters/lib/src/ui/modules/scan/view/scan_view.dart +++ b/components/interface_adapters/lib/src/ui/modules/scan/view/scan_view.dart @@ -1,4 +1,5 @@ import 'package:audiofileplayer/audiofileplayer.dart'; +import 'package:entities/entities.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_translate/flutter_translate.dart'; @@ -62,7 +63,16 @@ class _HomeViewState extends State { controller: _scannerController, errorBuilder: (_, MobileScannerException error, __) { _scannerController.stop().whenComplete(() { - _scannerController.start(); + _scannerController + .start() + .catchError((Object error, StackTrace stacktrace) { + debugPrint( + 'Warning: an error occurred in $runtimeType: $error\n' + 'Stacktrace: $stacktrace'); + throw const NotFoundException( + 'No camera found or failed to open camera!', + ); + }); }); return ScannerErrorWidget(error: error); }, diff --git a/components/interface_adapters/lib/src/ui/res/resources.dart b/components/interface_adapters/lib/src/ui/res/resources.dart index 9536419..2f30d74 100644 --- a/components/interface_adapters/lib/src/ui/res/resources.dart +++ b/components/interface_adapters/lib/src/ui/res/resources.dart @@ -3,8 +3,8 @@ import 'package:flutter/widgets.dart'; import 'package:interface_adapters/src/ui/app/ethical_scanner_app.dart'; import 'package:interface_adapters/src/ui/res/color/gradients.dart'; import 'package:interface_adapters/src/ui/res/color/material_colors.dart'; -import 'package:interface_adapters/src/ui/res/values/dimens.dart'; import 'package:interface_adapters/src/ui/res/values/app_durations.dart'; +import 'package:interface_adapters/src/ui/res/values/dimens.dart'; import 'package:interface_adapters/src/ui/res/values/strings.dart'; class Resources extends InheritedWidget { @@ -32,6 +32,16 @@ class Resources extends InheritedWidget { /// This method asserts that the result is not `null`, as we expect the /// [Resources] widget to be always present in the [EthicalScannerApp]. /// If the [Resources] widget is not found, a runtime exception is thrown. - static Resources of(BuildContext context) => - context.dependOnInheritedWidgetOfExactType()!; + static Resources of(BuildContext context) { + Resources? resources = + context.dependOnInheritedWidgetOfExactType(); + if (resources != null) { + return resources; + } else { + throw Exception( + 'You should wrap your app with `Resources InheritedWidget` and pass ' + 'the root app widget to the child parameter.', + ); + } + } } diff --git a/components/interface_adapters/pubspec.yaml b/components/interface_adapters/pubspec.yaml index f00ed5b..e0d9781 100644 --- a/components/interface_adapters/pubspec.yaml +++ b/components/interface_adapters/pubspec.yaml @@ -18,41 +18,43 @@ environment: dependencies: audiofileplayer: ^2.1.1 # state management package that helps implement the Business Logic Component design pattern. - bloc: ^8.1.3 + bloc: ^8.1.4 camera: ^0.10.5+9 entities: - path: ../core/entities + path: ../domain/entities - envied: ^0.5.3 - feedback: ^3.0.0 + envied: ^0.5.4+1 + feedback: ^3.1.0 flutter: sdk: flutter # Built to be used with the `bloc` state management package - flutter_bloc: ^8.1.4 + flutter_bloc: ^8.1.5 # localization / internationalization (i18n) library - flutter_email_sender: ^6.0.2 + flutter_email_sender: ^6.0.3 flutter_translate: ^4.0.4 html: ^0.15.4 + meta: any + # TODO: migrate to 5.0.1 mobile_scanner: ^4.0.0 # For launching a URL. Supports web, phone, SMS, and email schemes. - package_info_plus: ^5.0.1 - path_provider: ^2.1.2 - url_launcher: ^6.2.4 + package_info_plus: ^8.0.0 + path_provider: ^2.1.3 + url_launcher: ^6.2.6 use_cases: - path: ../core/use_cases + path: ../domain/use_cases dev_dependencies: # A build system for Dart code generation and modular compilation. - build_runner: ^2.4.8 + build_runner: ^2.4.9 - envied_generator: ^0.5.3 - flutter_lints: ^3.0.1 + envied_generator: ^0.5.4+1 + flutter_lints: ^3.0.2 flutter_test: sdk: flutter diff --git a/coverage/lcov.info b/coverage/lcov.info index c2509b9..12f78e5 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,9 +1,9 @@ SF:lib/extensions/context_extension.dart -DA:11,2 +DA:11,1 DA:15,1 -DA:16,1 -DA:24,2 -DA:27,2 +DA:16,0 +DA:24,1 +DA:27,1 DA:28,1 DA:29,1 DA:34,1 @@ -11,12 +11,41 @@ DA:35,1 DA:36,1 DA:37,1 LF:11 -LH:11 +LH:10 end_of_record SF:lib/di/dependencies.dart -DA:9,2 -LF:1 -LH:1 +DA:12,0 +DA:16,2 +DA:17,2 +DA:18,2 +DA:19,0 +DA:22,0 +DA:23,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:31,0 +DA:32,0 +DA:34,0 +DA:35,0 +DA:37,0 +DA:38,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:44,0 +DA:46,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:53,0 +DA:54,0 +DA:56,0 +DA:57,0 +DA:59,0 +LF:30 +LH:3 end_of_record SF:lib/data/data_mappers/product_data_mapper.dart DA:5,4 @@ -32,73 +61,74 @@ DA:14,2 DA:15,2 DA:16,2 DA:17,2 -DA:20,2 -DA:21,4 -DA:22,2 -DA:23,0 +DA:18,2 +DA:21,2 +DA:22,4 +DA:23,2 DA:24,0 -DA:26,1 -DA:28,2 -DA:32,2 -DA:33,4 -DA:34,1 -DA:35,2 -DA:36,4 -DA:39,1 +DA:25,0 +DA:27,1 +DA:29,2 +DA:33,2 +DA:34,4 +DA:35,1 +DA:36,2 +DA:37,4 DA:40,1 +DA:41,1 DA:43,2 DA:44,6 DA:45,2 DA:46,4 DA:50,2 DA:51,0 +DA:53,2 DA:54,2 -DA:55,2 -DA:56,0 +DA:55,0 +DA:57,6 DA:58,6 -DA:59,6 -DA:60,2 +DA:59,2 +DA:60,0 DA:61,0 -DA:62,0 -DA:64,0 -DA:66,2 -DA:70,2 -DA:71,6 -DA:73,2 -DA:75,2 -DA:78,3 -DA:79,2 -DA:85,2 -DA:86,6 -DA:89,2 -DA:91,4 +DA:63,0 +DA:65,2 +DA:69,2 +DA:70,6 +DA:72,2 +DA:74,2 +DA:77,3 +DA:78,2 +DA:84,2 +DA:85,6 +DA:88,2 +DA:90,4 +DA:93,0 DA:94,0 -DA:95,0 -DA:102,8 -DA:104,3 +DA:101,8 +DA:103,3 +DA:104,1 DA:105,1 -DA:106,1 +DA:106,2 DA:107,2 -DA:108,2 -DA:111,6 +DA:110,6 +DA:111,2 DA:112,2 DA:113,2 DA:114,2 -DA:115,2 -DA:118,2 -DA:119,6 -DA:120,1 -DA:121,2 -DA:122,1 -DA:123,2 -DA:126,2 -DA:127,6 -DA:129,2 -DA:130,4 -DA:131,2 -DA:132,4 -LF:78 -LH:69 +DA:117,2 +DA:118,6 +DA:119,1 +DA:120,2 +DA:121,1 +DA:122,2 +DA:125,2 +DA:126,6 +DA:128,2 +DA:129,4 +DA:130,2 +DA:131,4 +LF:79 +LH:70 end_of_record SF:lib/data/data_mappers/product_result_data_mapper.dart DA:4,1 @@ -108,125 +138,836 @@ LF:3 LH:2 end_of_record SF:lib/data/data_sources/local/local_data_source_impl.dart -DA:4,8 +DA:8,6 +DA:10,3 +DA:12,9 +DA:15,2 +DA:16,2 +DA:17,2 +DA:26,1 +DA:29,1 +DA:730,1 +DA:733,1 +DA:734,0 +DA:735,0 +DA:739,1 +DA:741,1 +DA:742,4 +DA:743,3 +DA:744,1 +DA:746,2 +DA:751,3 +DA:757,0 +DA:759,0 +DA:760,0 +DA:764,0 +DA:766,0 +DA:767,0 +DA:771,0 +DA:772,0 +DA:773,0 +DA:777,0 +DA:779,0 +DA:780,0 +DA:784,0 +DA:786,0 +DA:787,0 +DA:788,0 +DA:789,0 +DA:791,0 +DA:793,0 +LF:38 +LH:17 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/fields.dart +DA:11,1 +DA:39,1 +DA:40,1 +DA:94,0 +DA:96,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:102,0 +DA:103,0 +DA:104,0 +DA:105,0 +DA:108,0 +DA:110,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +DA:143,0 +DA:144,0 +DA:145,0 +DA:146,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:150,0 +DA:151,0 +DA:152,0 +DA:153,0 +DA:154,0 +DA:155,0 +DA:156,0 +DA:157,0 +DA:158,0 +DA:159,0 +DA:160,0 +DA:161,0 +DA:162,0 +DA:166,0 +DA:169,0 +DA:171,0 +DA:172,0 +DA:175,0 +DA:177,0 +DA:178,0 +DA:179,0 +DA:180,0 +DA:181,0 +DA:182,0 +DA:183,0 +DA:184,0 +DA:185,0 +DA:186,0 +DA:187,0 +DA:188,0 +DA:189,0 +DA:190,0 +DA:191,0 +DA:192,0 +DA:193,0 +DA:194,0 +DA:195,0 +DA:196,0 +DA:197,0 +DA:198,0 +DA:199,0 +DA:200,0 +DA:201,0 +LF:72 +LH:3 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/fields.g.dart +DA:9,2 +DA:10,1 +DA:11,1 +DA:12,1 DA:13,1 +DA:14,3 +DA:15,1 DA:16,1 -DA:717,1 -DA:720,1 -DA:721,0 -DA:722,0 -DA:726,1 -DA:728,1 -DA:729,4 -DA:730,3 -DA:731,1 -DA:733,2 -DA:738,3 -LF:14 -LH:12 +DA:17,1 +DA:18,1 +DA:19,1 +DA:20,1 +DA:21,1 +DA:22,1 +DA:23,1 +DA:24,1 +DA:25,1 +DA:26,1 +DA:27,1 +DA:28,1 +DA:29,1 +DA:30,3 +DA:31,1 +DA:32,2 +DA:33,1 +DA:34,1 +DA:35,1 +DA:36,1 +DA:37,1 +DA:39,2 +DA:40,2 +DA:43,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:68,0 +LF:57 +LH:31 end_of_record -SF:lib/data/data_sources/remote/remote_data_source_impl.dart -DA:9,8 +SF:lib/data/data_sources/remote/models/russia_sponsors_response/investor.dart +DA:8,1 +DA:21,1 +DA:22,1 +DA:36,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:44,0 +DA:46,0 +DA:58,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:68,0 +DA:72,0 +DA:75,0 +DA:77,0 +DA:78,0 +DA:81,0 +DA:83,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:87,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +LF:36 +LH:3 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/investor.g.dart +DA:9,2 +DA:10,1 DA:11,1 +DA:12,2 DA:13,1 DA:14,1 +DA:15,1 +DA:16,1 DA:17,1 -DA:20,2 -DA:21,2 -DA:22,2 +DA:18,1 +DA:19,1 +DA:22,0 DA:23,0 DA:24,0 DA:25,0 DA:26,0 DA:27,0 DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 DA:32,0 +LF:22 +LH:11 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/logo.dart +DA:10,1 +DA:21,2 +DA:31,0 DA:33,0 +DA:34,0 +DA:35,0 +DA:38,0 +DA:40,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:62,0 +DA:65,0 +DA:67,0 +DA:68,0 +DA:71,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:80,0 +LF:30 +LH:2 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/logo.g.dart +DA:9,2 +DA:10,1 +DA:11,1 +DA:12,1 +DA:13,1 +DA:14,1 +DA:15,1 +DA:16,1 +DA:17,1 +DA:19,2 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +LF:19 +LH:10 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/full.dart +DA:8,1 +DA:10,2 +DA:15,0 +DA:16,0 +DA:18,0 +DA:20,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:32,0 +DA:35,0 DA:37,0 -DA:39,0 +DA:38,0 +DA:41,0 +DA:42,0 +LF:16 +LH:2 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/full.g.dart +DA:9,2 +DA:10,1 +DA:11,1 +DA:12,1 +DA:15,0 +DA:16,0 +DA:17,0 +DA:18,0 +LF:8 +LH:4 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/large.dart +DA:8,1 +DA:10,2 +DA:15,0 +DA:16,0 +DA:18,0 +DA:20,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:32,0 +DA:35,0 +DA:37,0 +DA:38,0 +DA:41,0 +DA:42,0 +LF:16 +LH:2 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/large.g.dart +DA:9,2 +DA:10,1 +DA:11,1 +DA:12,1 +DA:15,0 +DA:16,0 +DA:17,0 +DA:18,0 +LF:8 +LH:4 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/thumbnails.dart +DA:12,1 +DA:14,1 +DA:15,1 +DA:22,0 +DA:24,0 +DA:27,0 +DA:29,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:41,0 DA:44,0 -DA:45,0 DA:46,0 DA:47,0 +DA:50,0 +DA:51,0 +LF:17 +LH:3 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/thumbnails.g.dart +DA:9,2 +DA:10,1 +DA:12,2 +DA:13,1 +DA:15,2 +DA:16,1 +DA:18,2 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +LF:12 +LH:7 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/russia_sponsor_response.dart +DA:11,1 +DA:17,1 +DA:18,1 +DA:26,0 +DA:28,0 +DA:29,0 +DA:32,0 +DA:34,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:45,0 DA:48,0 -DA:49,0 +DA:50,0 +DA:51,0 +DA:54,0 DA:55,0 DA:57,0 DA:58,0 DA:60,0 -DA:67,0 +DA:61,0 +DA:63,0 +DA:64,0 +LF:24 +LH:3 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/russia_sponsor_response.g.dart +DA:9,1 +DA:11,1 +DA:12,1 +DA:13,2 +DA:14,1 +DA:16,2 +DA:19,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +LF:11 +LH:6 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/small.dart +DA:8,1 +DA:10,2 +DA:15,0 +DA:16,0 +DA:18,0 +DA:20,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:32,0 +DA:35,0 +DA:37,0 +DA:38,0 +DA:41,0 +DA:42,0 +LF:16 +LH:2 +end_of_record +SF:lib/data/data_sources/remote/models/russia_sponsors_response/small.g.dart +DA:9,2 +DA:10,1 +DA:11,1 +DA:12,1 +DA:15,0 +DA:16,0 +DA:17,0 +DA:18,0 +LF:8 +LH:4 +end_of_record +SF:lib/data/data_sources/remote/remote_data_source_impl.dart +DA:13,1 +DA:17,1 +DA:19,1 +DA:20,1 +DA:21,1 +DA:23,1 +DA:26,2 +DA:27,2 +DA:28,2 +DA:29,2 +DA:30,1 +DA:31,1 +DA:32,2 +DA:33,1 +DA:34,1 +DA:38,1 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:47,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:64,0 +DA:65,0 DA:69,0 +DA:71,0 DA:76,0 DA:77,0 DA:78,0 +DA:79,0 +DA:80,0 DA:81,0 -DA:82,0 -DA:86,0 -DA:95,0 +DA:87,0 +DA:89,0 +DA:90,0 +DA:92,0 +DA:94,0 +DA:96,0 DA:97,0 DA:98,0 +DA:99,0 +DA:100,0 DA:101,0 +DA:102,0 DA:103,0 +DA:104,0 +DA:105,0 +DA:106,0 DA:107,0 DA:108,0 -DA:109,0 -LF:44 -LH:8 +DA:111,0 +DA:114,0 +DA:119,0 +DA:121,0 +DA:122,0 +DA:123,0 +DA:124,0 +DA:127,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:143,0 +DA:144,0 +DA:145,0 +DA:150,0 +DA:152,0 +DA:153,0 +DA:154,0 +DA:158,0 +DA:167,0 +DA:168,0 +DA:169,0 +DA:170,0 +DA:172,0 +DA:173,0 +DA:174,0 +DA:175,0 +DA:183,0 +DA:185,0 +DA:192,0 +DA:193,0 +DA:196,0 +DA:197,0 +DA:198,0 +DA:204,0 +DA:212,0 +DA:215,0 +DA:216,0 +DA:217,0 +DA:222,0 +DA:224,0 +DA:225,0 +DA:229,0 +DA:231,0 +DA:235,0 +DA:236,0 +DA:239,0 +DA:240,0 +DA:241,0 +DA:242,0 +DA:243,0 +DA:244,0 +DA:245,0 +DA:246,0 +DA:248,0 +DA:249,0 +DA:250,0 +DA:251,0 +DA:252,0 +DA:253,0 +DA:254,0 +DA:257,0 +DA:258,0 +DA:259,0 +DA:260,0 +DA:263,0 +DA:266,0 +DA:271,0 +DA:273,0 +DA:276,0 +DA:277,0 +DA:278,0 +DA:279,0 +DA:281,0 +DA:282,0 +DA:286,0 +DA:291,0 +DA:292,0 +DA:296,0 +DA:297,0 +DA:299,0 +DA:304,0 +DA:309,0 +DA:310,0 +DA:311,0 +DA:316,0 +DA:317,0 +DA:321,0 +DA:330,0 +DA:331,0 +DA:332,0 +DA:335,0 +DA:337,0 +DA:341,0 +DA:342,0 +DA:343,0 +LF:157 +LH:16 end_of_record -SF:lib/routes/app_route.dart -DA:4,2 -DA:6,2 -DA:8,2 -DA:9,4 -DA:10,1 -DA:11,2 -LF:6 -LH:6 -end_of_record -SF:lib/routes/app_router.dart -DA:7,1 -DA:8,3 +SF:lib/data/data_sources/remote/rest/logging_interceptor_impl.dart +DA:7,8 DA:9,1 -DA:11,0 -DA:12,0 +DA:14,1 +DA:15,1 +DA:16,1 +DA:19,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:28,1 +DA:30,1 +DA:31,4 +DA:32,4 +DA:33,4 +DA:34,1 +DA:35,1 +LF:18 +LH:12 +end_of_record +SF:lib/data/data_sources/remote/rest/retrofit_client/retrofit_client.g.dart +DA:12,1 +DA:16,1 +DA:23,1 +DA:25,1 +DA:26,1 +DA:27,1 +DA:29,2 +DA:30,2 +DA:35,1 +DA:36,2 +DA:41,1 +DA:42,1 +DA:43,3 +DA:44,1 +DA:46,1 +DA:47,2 +DA:48,1 +DA:49,1 +DA:53,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:59,0 +DA:60,0 +DA:65,0 +DA:66,0 +DA:71,0 +DA:72,0 +DA:73,0 +DA:74,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:83,1 +DA:84,1 +DA:85,2 +DA:86,2 +DA:87,1 +DA:88,0 +DA:90,1 +DA:96,1 +DA:100,2 +DA:104,1 +DA:106,1 +DA:107,1 +DA:110,0 +LF:47 +LH:29 +end_of_record +SF:lib/router/router.dart +DA:12,2 +DA:13,2 +DA:14,2 +DA:16,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:37,0 +DA:38,0 +DA:43,0 +DA:49,0 +DA:50,0 +DA:57,1 +DA:58,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:68,0 +DA:69,0 +DA:70,0 +DA:71,0 +DA:73,0 +DA:74,0 +DA:78,0 +DA:79,0 +DA:80,0 +DA:83,0 +DA:86,0 +DA:88,0 +DA:97,0 +DA:103,0 +DA:104,0 +DA:111,1 +DA:114,1 +DA:115,1 +DA:117,0 +DA:122,0 +DA:123,0 +DA:124,0 +DA:125,0 +DA:129,0 +DA:135,0 +LF:57 +LH:7 +end_of_record +SF:lib/camera_descriptions.dart +DA:3,0 +LF:1 +LH:0 +end_of_record +SF:lib/router/home_bloc_provider.dart +DA:11,2 DA:13,0 -DA:14,0 +DA:15,0 DA:16,0 DA:17,0 DA:18,0 DA:19,0 DA:20,0 DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:26,0 +DA:27,0 DA:28,0 DA:29,0 -DA:31,3 -DA:32,1 +DA:31,0 +DA:32,0 +DA:33,0 DA:34,0 -DA:35,0 DA:36,0 -DA:37,0 -DA:38,0 -DA:39,0 -DA:45,0 -DA:46,0 +DA:43,0 +DA:44,0 DA:47,0 -DA:55,1 +DA:48,0 +DA:49,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:57,0 +DA:58,0 DA:59,0 +DA:61,0 +DA:63,0 DA:64,0 -DA:66,0 -LF:30 -LH:6 +DA:74,0 +DA:78,0 +DA:79,0 +DA:81,0 +LF:39 +LH:1 end_of_record SF:lib/di/dependencies_scope.dart -DA:12,1 -DA:22,1 -DA:23,1 -DA:26,1 +DA:12,0 +DA:22,0 +DA:23,0 +DA:26,0 DA:28,0 DA:30,0 DA:31,0 DA:32,0 DA:36,0 LF:9 -LH:4 +LH:0 end_of_record diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5f21f60..e294d13 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -141,7 +141,7 @@ SPEC CHECKSUMS: path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 - url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e7bac40..b72781d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -215,7 +215,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a0..8e3ca5d 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + RussiaSponsorResponse( + id: id ?? this.id, + createdTime: createdTime ?? this.createdTime, + fields: fields ?? this.fields, + ); @override bool operator ==(Object other) { diff --git a/lib/data/data_sources/remote/remote_data_source_impl.dart b/lib/data/data_sources/remote/remote_data_source_impl.dart index 693893d..ca2d0d8 100644 --- a/lib/data/data_sources/remote/remote_data_source_impl.dart +++ b/lib/data/data_sources/remote/remote_data_source_impl.dart @@ -15,7 +15,7 @@ class RemoteDataSourceImpl implements RemoteDataSource { final RestClient _restClient; @override - Future getProductInfoAsFuture(Barcode input) => + Future getProductInfoAsFuture(LocalizedCode input) => OpenFoodAPIClient.getProductV3( ProductQueryConfiguration( input.code, @@ -181,7 +181,7 @@ class RemoteDataSourceImpl implements RemoteDataSource { /// database that has already ingredient image otherwise it should be added /// first to the server and then this can be called. @override - Future getIngredientsText(Barcode barcode) => + Future getIngredientsText(LocalizedCode barcode) => OpenFoodAPIClient.extractIngredients( OpenFoodAPIConfiguration.globalUser ?? const User( @@ -226,7 +226,7 @@ class RemoteDataSourceImpl implements RemoteDataSource { } OcrIngredientsResult ocrResponse = - await OpenFoodAPIClient.extractIngredients( + await OpenFoodAPIClient.extractIngredients( user, photo.info.barcode, language, diff --git a/lib/data/data_sources/remote/rest/dio/logging_interceptor_impl.dart b/lib/data/data_sources/remote/rest/logging_interceptor_impl.dart similarity index 100% rename from lib/data/data_sources/remote/rest/dio/logging_interceptor_impl.dart rename to lib/data/data_sources/remote/rest/logging_interceptor_impl.dart diff --git a/lib/di/dependencies.dart b/lib/di/dependencies.dart index 60ce7fa..94be11d 100644 --- a/lib/di/dependencies.dart +++ b/lib/di/dependencies.dart @@ -2,12 +2,12 @@ import 'package:dio/dio.dart'; import 'package:entities/entities.dart'; import 'package:ethical_scanner/data/data_sources/local/local_data_source_impl.dart'; import 'package:ethical_scanner/data/data_sources/remote/remote_data_source_impl.dart'; -import 'package:ethical_scanner/data/data_sources/remote/rest/dio/logging_interceptor_impl.dart'; +import 'package:ethical_scanner/data/data_sources/remote/rest/logging_interceptor_impl.dart'; import 'package:ethical_scanner/data/data_sources/remote/rest/retrofit_client/retrofit_client.dart'; import 'package:interface_adapters/interface_adapters.dart'; import 'package:use_cases/use_cases.dart'; -/// Dependencies container +/// Dependencies container. class Dependencies { const Dependencies._(this._localDataSource); @@ -22,16 +22,16 @@ class Dependencies { UseCase get getPrecipitationStateUseCase => GetPrecipitationStateUseCase(_settingsGateway); - UseCase get getLanguageUseCase => - GetLanguageUseCase(_settingsGateway); - UseCase, bool> get savePrecipitationStateUseCase => SavePrecipitationStateUseCase(_settingsGateway); + UseCase get getLanguageUseCase => + GetLanguageUseCase(_settingsGateway); + UseCase, String> get saveLanguageUseCase => SaveLanguageUseCase(_settingsGateway); - UseCase, Barcode> get productInfoUseCase => + UseCase, LocalizedCode> get productInfoUseCase => GetProductInfoUseCase(_productInfoGateway); UseCase, ProductPhoto> get addIngredientsUseCase => diff --git a/lib/main.dart b/lib/main.dart index f027b5b..a661937 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,7 @@ import 'package:ethical_scanner/di/dependencies.dart'; import 'package:ethical_scanner/di/dependencies_scope.dart'; import 'package:ethical_scanner/di/injector.dart'; import 'package:ethical_scanner/localization_delelegate_getter.dart'; -import 'package:ethical_scanner/routes/router.dart'; +import 'package:ethical_scanner/router/router.dart'; import 'package:feedback/feedback.dart'; import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; diff --git a/lib/routes/home_bloc_provider.dart b/lib/router/home_bloc_provider.dart similarity index 98% rename from lib/routes/home_bloc_provider.dart rename to lib/router/home_bloc_provider.dart index 258c300..037bca7 100644 --- a/lib/routes/home_bloc_provider.dart +++ b/lib/router/home_bloc_provider.dart @@ -1,7 +1,7 @@ import 'package:entities/entities.dart'; import 'package:ethical_scanner/di/dependencies.dart'; import 'package:ethical_scanner/di/dependencies_scope.dart'; -import 'package:ethical_scanner/routes/routes.dart' as route; +import 'package:ethical_scanner/router/routes.dart' as route; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_translate/flutter_translate.dart'; diff --git a/lib/routes/router.dart b/lib/router/router.dart similarity index 94% rename from lib/routes/router.dart rename to lib/router/router.dart index 6c0f34c..47c671d 100644 --- a/lib/routes/router.dart +++ b/lib/router/router.dart @@ -2,8 +2,8 @@ import 'package:entities/entities.dart'; import 'package:ethical_scanner/camera_descriptions.dart' as cameras; import 'package:ethical_scanner/di/dependencies.dart'; import 'package:ethical_scanner/di/dependencies_scope.dart'; -import 'package:ethical_scanner/routes/home_bloc_provider.dart'; -import 'package:ethical_scanner/routes/routes.dart' as route; +import 'package:ethical_scanner/router/home_bloc_provider.dart'; +import 'package:ethical_scanner/router/routes.dart' as route; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_translate/flutter_translate.dart'; @@ -12,6 +12,7 @@ import 'package:interface_adapters/interface_adapters.dart'; Route generateRoute(RouteSettings settings) => switch (settings.name) { route.homePath => _getHomePageRouteBuilder(settings), route.scanPath => PageRouteBuilder( + settings: settings, opaque: false, pageBuilder: ( _, @@ -55,7 +56,8 @@ Route generateRoute(RouteSettings settings) => switch (settings.name) { ), ), route.photoPath => PageRouteBuilder( - pageBuilder: (BuildContext context, Animation animation, __) { + settings: settings, + pageBuilder: (BuildContext context, Animation animation, __) { Object? args = settings.arguments; if (args is ProductInfo) { return BlocProvider( diff --git a/lib/routes/routes.dart b/lib/router/routes.dart similarity index 100% rename from lib/routes/routes.dart rename to lib/router/routes.dart diff --git a/pubspec.lock b/pubspec.lock index 6341ad0..a779e7b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.4.1" args: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: "direct main" description: name: bloc - sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" url: "https://pub.dev" source: hosted - version: "8.1.3" + version: "8.1.4" boolean_selector: dependency: transitive description: @@ -93,18 +93,18 @@ packages: 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: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.11" + version: "7.3.0" built_collection: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: built_value - sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309 + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e url: "https://pub.dev" source: hosted - version: "8.8.1" + version: "8.9.1" camera: dependency: "direct main" description: @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: camera_platform_interface - sha256: e971ebca970f7cfee396f76ef02070b5e441b4aa04942da9c108d725f57bbd32 + sha256: fceb2c36038b6392317b1d5790c6ba9e6ca9f1da3031181b8bea03882bf9387a url: "https://pub.dev" source: hosted - version: "2.7.2" + version: "2.7.3" camera_web: dependency: transitive description: @@ -221,10 +221,10 @@ packages: dependency: transitive description: name: cross_file - sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" url: "https://pub.dev" source: hosted - version: "0.3.3+8" + version: "0.3.4+1" crypto: dependency: transitive description: @@ -245,10 +245,10 @@ packages: dependency: "direct main" description: name: dart_openai - sha256: "963939b54e841ac7f663d958afd3d595f3e48b1887a367ff582b9f82220bee78" + sha256: "853bb57fed6a71c3ba0324af5cb40c16d196cf3aa55b91d244964ae4a241ccf1" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.0" dart_style: dependency: transitive description: @@ -261,14 +261,14 @@ packages: dependency: "direct main" description: name: dio - sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" + sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5" url: "https://pub.dev" source: hosted - version: "5.4.1" + version: "5.4.3+1" entities: dependency: "direct main" description: - path: "components/core/entities" + path: "components/domain/entities" relative: true source: path version: "1.0.0" @@ -276,18 +276,26 @@ packages: dependency: "direct main" description: name: envied - sha256: dab29e21452c3d57ec10889d96b06b4a006b01375d4df10b33c9704800c208c4 + sha256: bbff9c76120e4dc5e2e36a46690cf0a26feb65e7765633f4e8d916bcd173a450 url: "https://pub.dev" source: hosted - version: "0.5.3" + version: "0.5.4+1" envied_generator: dependency: "direct dev" description: name: envied_generator - sha256: b8655d5cb39b4d1d449a79ff6f1367b252c23955ff17ec7c03aacdff938598bd + sha256: "517b70de08d13dcd40e97b4e5347e216a0b1c75c99e704f3c85c0474a392d14a" + url: "https://pub.dev" + source: hosted + version: "0.5.4+1" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 url: "https://pub.dev" source: hosted - version: "0.5.3" + version: "2.0.5" fake_async: dependency: transitive description: @@ -300,10 +308,10 @@ packages: dependency: "direct main" description: name: feedback - sha256: "3cb138a56a3f3914dd38471479c351ce6df377abb29c5ebe4042be5481fee271" + sha256: "26769f73de6215add72074d24e4a23542e4c02a8fd1a873e7c93da5dc9c1d362" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.0" fetch_api: dependency: transitive description: @@ -324,10 +332,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" file: dependency: transitive description: @@ -353,26 +361,26 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "8.1.5" flutter_email_sender: dependency: transitive description: name: flutter_email_sender - sha256: "5001e9158f91a8799140fb30a11ad89cd587244f30b4f848d87085985c49b60f" + sha256: fb515d4e073d238d0daf1d765e5318487b6396d46b96e0ae9745dbc9a133f97a url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.0.3" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" flutter_localizations: dependency: transitive description: flutter @@ -440,10 +448,10 @@ packages: dependency: transitive description: name: http - sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -495,18 +503,42 @@ packages: 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.8.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 url: "https://pub.dev" source: hosted - version: "6.7.1" + version: "2.0.1" lints: dependency: transitive description: @@ -519,10 +551,10 @@ packages: dependency: "direct main" description: name: logger - sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" + sha256: "8c94b8c219e7e50194efc8771cd0e9f10807d8d3e219af473d89b06cc2ee4e04" url: "https://pub.dev" source: hosted - version: "2.0.2+1" + version: "2.2.0" logging: dependency: transitive description: @@ -535,42 +567,50 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mobile_scanner: dependency: transitive description: name: mobile_scanner - sha256: "619ed5fd43ca9007a151f00c3dc43feedeaf235fe5647735d0237c38849d49dc" + sha256: "827765afbd4792ff3fd105ad593821ac0f6d8a7d352689013b07ee85be336312" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + url: "https://pub.dev" + source: hosted + version: "5.4.4" nested: dependency: transitive description: @@ -591,10 +631,10 @@ packages: dependency: "direct main" description: name: openfoodfacts - sha256: "3f60e195c47398bdfddf04944fb9a2b41255d6cc281d5494ad1002843351b569" + sha256: "1b9954e093ead9c314e21e25a92e89540adae2212ae0535f6e69804b312ff972" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.6.1" package_config: dependency: transitive description: @@ -607,34 +647,34 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" + sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "8.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" path: dependency: "direct main" description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: @@ -687,10 +727,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" pool: dependency: transitive description: @@ -759,10 +799,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: @@ -799,10 +839,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -988,26 +1028,26 @@ packages: dependency: transitive description: name: url_launcher - sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c + sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.2.6" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.4" url_launcher_linux: dependency: transitive description: @@ -1028,18 +1068,18 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" + sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.1" url_launcher_windows: dependency: transitive description: @@ -1051,7 +1091,7 @@ packages: use_cases: dependency: "direct main" description: - path: "components/core/use_cases" + path: "components/domain/use_cases" relative: true source: path version: "0.0.1" @@ -1091,18 +1131,18 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.5" webkit_inspection_protocol: dependency: transitive description: @@ -1136,5 +1176,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.3 <4.0.0" - flutter: ">=3.16.6" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 30698e4..a4d011d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,8 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # followed by an optional build number separated by a +. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. +# In Android, build-name is used as versionName while build-number is used as +# `versionCode`. # Read more about Android versioning at https://developer.android.com/studio/publish/versioning # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as # CFBundleVersion. @@ -18,13 +19,13 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.0.9+9 +version: 0.1.0+10 environment: sdk: '>=3.2.3 <4.0.0' -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions +# Dependencies specify other packages that your package needs to work. +# To automatically upgrade your package dependencies to the latest versions, # consider running `flutter pub upgrade --major-versions`. Alternatively, # dependencies can be manually updated by changing the version numbers below to # the latest version available on pub.dev. To see which dependencies have newer @@ -39,25 +40,26 @@ dependencies: # the `flutter_bloc` package handles the UI and presentation layer. You can think of the bloc # package as the engine, and the `flutter_bloc` package as the car body. You need both to make a # functional car 🚗. - bloc: ^8.1.3 + bloc: ^8.1.4 camera: ^0.10.5+9 collection: ^1.18.0 - # Dart SDK for openAI Apis (GPT-3 & DALL-E), integrate easily the power of OpenAI's + # Dart SDK for openAI Apis (GPT-3 & DALL-E), easily integrate the power of + # OpenAI's # state-of-the-art AI models into their Dart applications. - dart_openai: ^5.0.0 - dio: ^5.4.1 + dart_openai: ^5.1.0 + dio: ^5.4.3+1 entities: - path: ./components/core/entities + path: ./components/domain/entities # Explicitly reads environment variables into a dart file from a .env file for more security and # faster start up times. - envied: ^0.5.3 - feedback: ^3.0.0 + envied: ^0.5.4+1 + feedback: ^3.1.0 flutter: sdk: flutter # Built to be used with the `bloc` state management package. - flutter_bloc: ^8.1.4 + flutter_bloc: ^8.1.5 # Localization / internationalization (i18n) library. flutter_translate: ^4.0.4 @@ -65,36 +67,38 @@ dependencies: interface_adapters: path: ./components/interface_adapters - json_annotation: ^4.8.1 - logger: ^2.0.2+1 - openfoodfacts: ^3.4.0 + json_annotation: ^4.9.0 + logger: ^2.2.0 + openfoodfacts: ^3.6.1 - package_info_plus: ^5.0.1 + package_info_plus: ^8.0.0 path: any + path_provider: ^2.1.3 retrofit: ^4.1.0 retrofit_generator: ^8.1.0 - shared_preferences: ^2.2.2 + shared_preferences: ^2.2.3 use_cases: - path: ./components/core/use_cases + path: ./components/domain/use_cases dev_dependencies: # A build system for Dart code generation and modular compilation. - build_runner: ^2.4.8 + build_runner: ^2.4.9 # Generator for the Envied package. See https://pub.dev/packages/envied. - envied_generator: ^0.5.3 + envied_generator: ^0.5.4+1 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^3.0.1 + flutter_lints: ^3.0.2 flutter_test: sdk: flutter # A full-featured library for writing and running Dart tests across platforms. - json_serializable: ^6.7.1 + json_serializable: ^6.8.0 + mockito: ^5.4.4 test: any # For information on the generic Dart part of this file, see the @@ -123,13 +127,8 @@ flutter: # 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: + # 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 diff --git a/test/app_route_test.dart b/test/app_route_test.dart index 4fff7eb..33ac60f 100644 --- a/test/app_route_test.dart +++ b/test/app_route_test.dart @@ -1,4 +1,4 @@ -import 'package:ethical_scanner/routes/routes.dart' as route; +import 'package:ethical_scanner/router/routes.dart' as route; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as path; diff --git a/test/app_router_test.dart b/test/app_router_test.dart index 4210b9d..258c98a 100644 --- a/test/app_router_test.dart +++ b/test/app_router_test.dart @@ -1,5 +1,5 @@ -import 'package:ethical_scanner/routes/routes.dart' as route; -import 'package:ethical_scanner/routes/router.dart'; +import 'package:ethical_scanner/router/router.dart'; +import 'package:ethical_scanner/router/routes.dart' as route; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -37,7 +37,7 @@ void main() { final Route route = generateRoute(settings); // Assert - expect(route is MaterialPageRoute, true); + expect(route is PageRouteBuilder, true); }); }); } diff --git a/test/dependencies_scope_test.dart b/test/dependencies_scope_test.dart index 12d4b48..2f53cb6 100644 --- a/test/dependencies_scope_test.dart +++ b/test/dependencies_scope_test.dart @@ -1,26 +1,49 @@ import 'package:ethical_scanner/di/dependencies.dart'; import 'package:ethical_scanner/di/dependencies_scope.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'mock_dependencies.dart'; + void main() { group('DependenciesScope', () { - testWidgets('of method should retrieve dependencies', - (WidgetTester tester) async { - // Arrange - Dependencies dependencies = await Dependencies.create(); - DependenciesScope widget = DependenciesScope( + late Dependencies dependencies; + late DependenciesScope dependenciesScope; + + setUp(() async { + WidgetsFlutterBinding.ensureInitialized(); + dependencies = await MockDependencies.create(); + dependenciesScope = DependenciesScope( dependencies: dependencies, child: const SizedBox(), ); + }); + testWidgets('of method should retrieve dependencies', + (WidgetTester tester) async { // Act - await tester.pumpWidget(widget); + await tester.pumpWidget(dependenciesScope); // Assert final Dependencies retrievedDependencies = DependenciesScope.of(tester.element(find.byType(SizedBox))); + expect(retrievedDependencies, equals(dependencies)); }); + + test('debugFillProperties adds expected properties', () { + final DiagnosticPropertiesBuilder properties = + DiagnosticPropertiesBuilder(); + + dependenciesScope.debugFillProperties(properties); + + final DiagnosticsNode property = properties.properties + .firstWhere((DiagnosticsNode p) => p.name == 'dependencies'); + expect(property.value, equals(dependencies)); + }); + test('updateShouldNotify returns false', () { + expect(dependenciesScope.updateShouldNotify(dependenciesScope), isFalse); + }); }); } diff --git a/test/dependencies_test.dart b/test/dependencies_test.dart index 288d231..da4dadf 100644 --- a/test/dependencies_test.dart +++ b/test/dependencies_test.dart @@ -1,21 +1,46 @@ import 'package:entities/entities.dart'; import 'package:ethical_scanner/di/dependencies.dart'; +import 'package:flutter/material.dart'; import 'package:test/test.dart'; import 'package:use_cases/use_cases.dart'; +import 'mock_dependencies.dart'; + void main() { group('Dependencies', () { - test('getProductInfoUseCase should not be null', () async { - // Arrange - Dependencies dependencies = await Dependencies.create(); - + late Dependencies dependencies; + setUp(() async { + WidgetsFlutterBinding.ensureInitialized(); + dependencies = await MockDependencies.create(); + }); + test( + 'getPrecipitationStateUseCase returns instance of ' + 'GetPrecipitationStateUseCase', () async { + expect( + dependencies.getPrecipitationStateUseCase, + isA(), + ); + }); + test('getLanguageUseCase returns instance of GetLanguageUseCase', () async { + expect( + dependencies.getLanguageUseCase, + isA(), + ); + }); + test( + 'savePrecipitationStateUseCas returns instance of ' + 'SavePrecipitationStateUseCase', () async { + expect( + dependencies.savePrecipitationStateUseCase, + isA(), + ); + }); + test('getProductInfoUseCase should be GetProductInfoUseCase', () async { // Act - final UseCase, Barcode> useCase = + final UseCase, LocalizedCode> useCase = dependencies.productInfoUseCase; - // Assert - expect(useCase, isNotNull); - expect(useCase, isA, String>>()); + expect(useCase, isA()); }); }); } diff --git a/test/local_data_source_impl_test.dart b/test/local_data_source_impl_test.dart index e0929f1..6ca37a4 100644 --- a/test/local_data_source_impl_test.dart +++ b/test/local_data_source_impl_test.dart @@ -1,11 +1,18 @@ -import 'package:ethical_scanner/data/data_sources/local/local_data_source_impl.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'mock_local_data_source_impl.dart'; + void main() { group('LocalDataSourceImpl', () { - test('getCountryFromBarcode should return the correct country', () { - LocalDataSourceImpl localDataSource = LocalDataSourceImpl(); + late MockLocalDataSourceImpl localDataSource; + setUp(() async { + WidgetsFlutterBinding.ensureInitialized(); + // await MockLocalDataSourceImpl().init(); + localDataSource = MockLocalDataSourceImpl(); + }); + test('getCountryFromBarcode should return the correct country', () { // Test cases with known barcode prefixes and expected countries final Map testCases = { '0001234567890': 'United States and Canada', @@ -23,8 +30,6 @@ void main() { }); test('isEnglishBook should return true for English books', () { - LocalDataSourceImpl localDataSource = LocalDataSourceImpl(); - // Test cases with known ISBN-13 barcodes for English books final List testCases = [ '9781234567890', @@ -39,8 +44,6 @@ void main() { }); test('isEnglishBook should return false for non-English books', () { - LocalDataSourceImpl localDataSource = LocalDataSourceImpl(); - // Test cases with non-ISBN-13 barcodes or non-English books final List testCases = [ '1234567890123', // Not ISBN-13 @@ -53,5 +56,56 @@ void main() { expect(result, isFalse); } }); + + test('savePrecipitationState should save and retrieve precipitation state', + () async { + // Arrange + const bool isPrecipitationFalling = true; + + // Act + await localDataSource.savePrecipitationState(isPrecipitationFalling); + final bool result = localDataSource.getPrecipitationState(); + + // Assert + expect(result, equals(isPrecipitationFalling)); + }); + + test('saveSoundPreference should save and retrieve sound preference', + () async { + // Arrange + const bool isSoundOn = true; + + // Act + await localDataSource.saveSoundPreference(isSoundOn); + final bool result = localDataSource.getSoundPreference(); + + // Assert + expect(result, equals(isSoundOn)); + }); + + test('saveLanguageIsoCode should save and retrieve language ISO code', + () async { + // Arrange + const String languageIsoCode = 'mock'; + + // Act + await localDataSource.saveLanguageIsoCode(languageIsoCode); + final String result = localDataSource.getLanguageIsoCode(); + + // Assert + expect(result, equals(languageIsoCode)); + }); + + test('getLanguageIsoCode should return platform language if not saved', + () async { + // Arrange + const String platformLanguage = 'mock'; + + // Act + String result = localDataSource.getLanguageIsoCode(); + + // Assert + expect(result, equals(platformLanguage)); + }); }); } diff --git a/test/mock_dependencies.dart b/test/mock_dependencies.dart new file mode 100644 index 0000000..b60d827 --- /dev/null +++ b/test/mock_dependencies.dart @@ -0,0 +1,56 @@ +import 'package:dio/dio.dart'; +import 'package:entities/entities.dart'; +import 'package:ethical_scanner/data/data_sources/local/local_data_source_impl.dart'; +import 'package:ethical_scanner/data/data_sources/remote/remote_data_source_impl.dart'; +import 'package:ethical_scanner/data/data_sources/remote/rest/logging_interceptor_impl.dart'; +import 'package:ethical_scanner/data/data_sources/remote/rest/retrofit_client/retrofit_client.dart'; +import 'package:ethical_scanner/di/dependencies.dart'; +import 'package:interface_adapters/interface_adapters.dart'; +import 'package:mockito/mockito.dart'; +import 'package:use_cases/use_cases.dart'; + +import 'mock_local_data_source_impl.dart'; + +class MockDependencies extends Mock implements Dependencies { + MockDependencies._(this._localDataSource); + + final LocalDataSourceImpl _localDataSource; + + static Future create() async { + final MockLocalDataSourceImpl localDataSource = MockLocalDataSourceImpl(); + await localDataSource.init(); + return MockDependencies._(localDataSource); + } + + @override + UseCase get getPrecipitationStateUseCase => + GetPrecipitationStateUseCase(_settingsGateway); + + @override + UseCase get getLanguageUseCase => + GetLanguageUseCase(_settingsGateway); + + @override + UseCase, bool> get savePrecipitationStateUseCase => + SavePrecipitationStateUseCase(_settingsGateway); + + @override + UseCase, LocalizedCode> get productInfoUseCase => + GetProductInfoUseCase(_productInfoGateway); + + RestClient get _restClient { + final Dio dio = Dio(); + const LoggingInterceptor loggingInterceptor = LoggingInterceptorImpl(); + if (loggingInterceptor is Interceptor) { + dio.interceptors.add(loggingInterceptor as Interceptor); + } + return RetrofitClient(dio); + } + + ProductInfoGateway get _productInfoGateway => ProductInfoGatewayImpl( + RemoteDataSourceImpl(_restClient), + _localDataSource, + ); + + SettingsGateway get _settingsGateway => SettingsGatewayImpl(_localDataSource); +} diff --git a/test/mock_local_data_source_impl.dart b/test/mock_local_data_source_impl.dart new file mode 100644 index 0000000..216d92a --- /dev/null +++ b/test/mock_local_data_source_impl.dart @@ -0,0 +1,792 @@ +import 'package:ethical_scanner/data/data_sources/local/local_data_source_impl.dart'; +import 'package:ethical_scanner/res/enums/settings.dart'; +import 'package:mockito/mockito.dart'; + +import 'mock_shared_preferences.dart'; + +class MockLocalDataSourceImpl extends Mock implements LocalDataSourceImpl { + factory MockLocalDataSourceImpl() => _instance; + + MockLocalDataSourceImpl._internal(); + + static final MockLocalDataSourceImpl _instance = + MockLocalDataSourceImpl._internal(); + late final MockSharedPreferences _sharedPrefs = MockSharedPreferences(); + + @override + Future init() { + return Future.value(); + } + + /// Function to retrieve the country from barcode. + /// + /// References: + /// https://www.barcodestalk.com/learn-about-barcodes/resources/barcode-country-codes + /// https://internationalbarcodes.com/barcode-country-codes/ + /// https://en.wikipedia.org/wiki/List_of_GS1_country_codes + /// https://en.wikipedia.org/wiki/ISO_3166-1_numeric + @override + String getCountryFromBarcode(String barcode) { + // List of countries with corresponding barcode prefixes + Map countryCodeMap = { + '0': 'USA / Canada', + '000': 'United States and Canada', + '001': 'United States', + '002': 'United States', + '003': 'United States', + // ISO 3166-1 numeric for Afghanistan and GS1 USA country code + '004': 'USA(GS1)/Afghanistan(ISO 3166-1)', + '005': 'United States', + '006': 'United States', + '007': 'United States', + // ISO 3166-1 numeric for Albania and GS1 USA country code + '008': 'USA(GS1)/Albania(ISO 3166-1)', + '009': 'United States', + '010': 'United States', + '011': 'United States', + '012': 'USA(GS1)/Algeria(ISO 3166-1)', + '013': 'United States', + '014': 'United States', + '015': 'United States', + '016': 'United States', + '017': 'United States', + '018': 'United States', + '019': 'United States and Canada', + '020': 'Andorra(ISO 3166-1)', + '024': 'Angola(ISO 3166-1)', + '028': 'Antigua and Barbuda(ISO 3166-1)', + '030': 'United States', + '031': 'United States', + '032': 'USA(GS1)/Argentina(ISO 3166-1)', + '033': 'United States', + '034': 'United States', + '035': 'United States', + '036': 'USA(GS1)/Australia(ISO 3166-1)', + '037': 'United States', + '038': 'United States', + '039': 'United States', + '040': 'Austria(ISO 3166-1)', + '044': 'Bahamas(ISO 3166-1)', + '048': 'Bahrain(ISO 3166-1)', + '050': 'US(GS1)/Bangladesh(ISO 3166-1)', + '051': 'US(GS1)/Armenia(ISO 3166-1)', + '052': 'US(GS1)/Barbados(ISO 3166-1)', + '056': 'US(GS1)/Belgium(ISO 3166-1)', + // 057 is EAN-13 barcode prefix for the United States + '057': 'United States', + '060': 'United States and Canada', + '061': 'United States', + '062': 'United States', + '063': 'United States', + '064': 'Bhutan', + '065': 'United States', + '066': 'United States', + '067': 'United States', + '068': 'Bolivia', + '069': 'United States', + '070': 'Bosnia and Herzegovina', + '071': 'United States', + '072': 'Botswana', + '073': 'United States', + '074': 'United States', + '075': 'United States', + '076': 'Brazil', + '077': 'United States', + '078': 'United States', + '079': 'United States', + '080': 'United States', + '081': 'United States', + '082': 'United States', + '083': 'United States', + '084': 'Belize', + '085': 'United States', + '086': 'United States', + '087': 'United States', + '088': 'United States', + '089': 'United States', + '090': 'United States', + '091': 'United States', + '092': 'United States', + '093': 'United States', + '094': 'United States', + '095': 'United States', + '096': 'United States', + '097': 'United States', + '098': 'United States', + '099': 'United States and Canada', + '1': 'USA', + '100': 'Bulgaria', + '101': 'United States', + '102': 'United States', + '103': 'United States', + '104': 'Myanmar (Burma)', + '105': 'United States', + '106': 'United States', + '107': 'United States', + '108': 'Burundi', + '109': 'United States', + '110': 'United States', + '111': 'United States', + '112': 'Belarus', + '113': 'United States', + '114': 'United States', + '115': 'United States', + '116': 'Cambodia', + '117': 'United States', + '118': 'United States', + '119': 'United States', + '120': 'Cameroon', + '121': 'United States', + '122': 'United States', + '123': 'United States', + '124': 'Canada', + '125': 'United States', + '126': 'United States', + '127': 'United States', + '128': 'United States', + '129': 'United States', + '130': 'United States', + '131': 'United States', + '132': 'Cape Verde', + '133': 'United States', + '134': 'United States', + '135': 'United States', + '136': 'Cayman Islands', + '137': 'United States', + '138': 'United States', + '139': 'United States', + '140': 'Central African Republic', + '144': 'Sri Lanka', + '148': 'Chad', + '152': 'Chile', + '156': 'China', + '170': 'Colombia', + '174': 'Comoros', + '178': 'Congo (Congo-Brazzaville)', + '180': 'Congo (Congo-Kinshasa)', + '184': 'Cook Islands', + '188': 'Costa Rica', + '191': 'Croatia', + '192': 'Cuba', + '196': 'Cyprus', + '203': 'Czechia (Czech Republic)', + '204': 'Benin', + '208': 'Denmark', + '212': 'Dominica', + '214': 'Dominican Republic', + '218': 'Ecuador', + '222': 'El Salvador', + '226': 'Equatorial Guinea', + '231': 'Ethiopia', + '232': 'Eritrea', + '233': 'Estonia', + '234': 'Faroe Islands', + '238': 'Falkland Islands (Malvinas)', + '242': 'Fiji', + '246': 'Finland', + '250': 'France', + '254': 'French Guiana', + '258': 'French Polynesia', + '260': 'French Southern Territories', + '262': 'Djibouti', + '266': 'Gabon', + '268': 'Georgia', + '270': 'Gambia', + '275': 'Palestine', + '276': 'Germany', + '288': 'Ghana', + '292': 'Gibraltar', + '296': 'Kiribati', + '30': 'France & Monaco', + '300': 'Greece', + '301': 'France and Monaco', + '302': 'France and Monaco', + '303': 'France and Monaco', + '304': 'Greenland', + '305': 'France and Monaco', + '306': 'France and Monaco', + '307': 'France and Monaco', + '308': 'Grenada', + '309': 'France and Monaco', + '31': 'France & Monaco', + '310': 'France and Monaco', + '311': 'France and Monaco', + '312': 'Guadeloupe', + '313': 'France and Monaco', + '314': 'France and Monaco', + '315': 'France and Monaco', + '316': 'Guam', + '317': 'France and Monaco', + '318': 'France and Monaco', + '319': 'France and Monaco', + '32': 'France & Monaco', + '320': 'Guatemala', + '321': 'France and Monaco', + '322': 'France and Monaco', + '323': 'France and Monaco', + '324': 'Guinea', + '325': 'France and Monaco', + '326': 'France and Monaco', + '327': 'France and Monaco', + '328': 'Guyana', + '329': 'France and Monaco', + '33': 'France & Monaco', + '330': 'France and Monaco', + '331': 'France and Monaco', + '332': 'Haiti', + '333': 'France and Monaco', + '334': 'Heard Island and McDonald Islands', + '335': 'France and Monaco', + '336': 'Vatican City (Holy See)', + '337': 'France and Monaco', + '338': 'France and Monaco', + '339': 'France and Monaco', + '34': 'France & Monaco', + '340': 'Honduras', + '341': 'France and Monaco', + '342': 'France and Monaco', + '343': 'France and Monaco', + '344': 'Hong Kong', + '345': 'France and Monaco', + '346': 'France and Monaco', + '347': 'France and Monaco', + '348': 'Hungary', + '349': 'France and Monaco', + '35': 'France & Monaco', + '350': 'France and Monaco', + '351': 'France and Monaco', + '352': 'Iceland', + '353': 'France and Monaco', + '354': 'France and Monaco', + '355': 'France and Monaco', + '356': 'India', + '357': 'France and Monaco', + '358': 'France and Monaco', + '359': 'France and Monaco', + '36': 'France & Monaco', + '360': 'Indonesia', + '361': 'France and Monaco', + '362': 'France and Monaco', + '363': 'France and Monaco', + '364': 'Iran', + '365': 'France and Monaco', + '366': 'France and Monaco', + '367': 'France and Monaco', + '368': 'Iraq', + '369': 'France and Monaco', + '37': 'France & Monaco', + '370': 'France and Monaco', + // This is the prefix to the barcode of Toronto public library + '371': 'Canada', + '372': 'Ireland', + '373': 'France and Monaco', + '374': 'France and Monaco', + '375': 'France and Monaco', + '376': 'Israel', + '377': 'France and Monaco', + '378': 'France and Monaco', + '379': 'France and Monaco', + '380': 'Bulgaria', + '383': 'Slovenia', + '384': 'Cote d\'Ivoire (Ivory Coast)', + '385': 'Croatia', + '387': 'Bosnia and Herzegovina', + '388': 'Jamaica', + '389': 'Montenegro', + '390': 'Republic of Kosovo', + '392': 'Japan', + '398': 'Kazakhstan', + '40': 'Germany', + '400': 'Jordan', + '401': 'Germany', + '402': 'Germany', + '403': 'Germany', + '404': 'Kenya', + '405': 'Germany', + '406': 'Germany', + '407': 'Germany', + '408': 'North Korea', + '409': 'Germany', + '41': 'Germany', + '410': 'South Korea', + '411': 'Germany', + '412': 'Germany', + '413': 'Germany', + '414': 'Kuwait', + '415': 'Germany', + '416': 'Germany', + '417': 'Kyrgyzstan', + '418': 'Laos', + '419': 'Germany', + '42': 'Germany', + '420': 'Germany', + '421': 'Germany', + '422': 'Lebanon', + '423': 'Germany', + '424': 'Germany', + '425': 'Germany', + '426': 'Lesotho', + '427': 'Germany', + '428': 'Latvia', + '429': 'Germany', + '43': 'Germany', + '430': 'Liberia', + '431': 'Germany', + '432': 'Germany', + '433': 'Germany', + '434': 'Libya', + '435': 'Germany', + '436': 'Germany', + '437': 'Germany', + '438': 'Liechtenstein', + '439': 'Germany', + '44': 'Germany', + '440': 'Lithuania', + '442': 'Luxembourg', + '446': 'Macao', + '45': 'Japan', + '450': 'Madagascar', + '451': 'Japan', + '452': 'Japan', + '453': 'Japan', + '454': 'Malawi', + '455': 'Japan', + '456': 'Japan', + '457': 'Japan', + '458': 'Malaysia', + '459': 'Japan', + '46': 'Russia', + '460': 'Russia', + '461': 'Russia', + '462': 'Russia', + '463': 'Russia', + '464': 'Russia', + '465': 'Russia', + '466': 'Russia', + '467': 'Russia', + '468': 'Russia', + '469': 'Russia', + '470': 'Kyrgyzstan', + '471': 'Taiwan', + '474': 'Estonia', + '475': 'Latvia', + '476': 'Azerbaijan', + '477': 'Lithuania', + '478': 'Uzbekistan', + '479': 'Sri Lanka', + '480': 'Philippines', + '481': 'Belarus', + // 482 is a EAN-13 barcode prefix for Ukraine + '482': 'Ukraine', + '483': 'Turkmenistan', + // 484 is ISO 3166-1 numeric code for Mexico and EAN-13 barcode prefix for + // Moldova + '484': 'Mexico or Moldova', + '485': 'Armenia', + '486': 'Georgia', + '487': 'Kazakhstan', + '488': 'Tajikistan', + '489': 'Hong Kong', + '49': 'Japan', + '490': 'Japan', + '491': 'Japan', + '492': 'Monaco', + '493': 'Japan', + '494': 'Japan', + '495': 'Japan', + '496': 'Mongolia', + '497': 'Japan', + '498': 'Moldova', + '499': 'Montenegro', + '50': 'United Kingdom', + '500': 'Montserrat', + '501': 'United Kingdom', + '502': 'United Kingdom', + '503': 'United Kingdom', + '504': 'Morocco', + '505': 'United Kingdom', + '506': 'United Kingdom', + '507': 'United Kingdom', + '508': 'Mozambique', + '509': 'United Kingdom', + '512': 'Oman', + '516': 'Namibia', + '520': 'Nauru', + '521': 'Greece', + '524': 'Nepal', + '528': 'Lebanon', + '529': 'Cyprus', + '530': 'Albania', + '531': 'North Macedonia', + '533': 'Aruba', + '534': 'Sint Maarten (Dutch part)', + '535': 'Malta', + '539': 'Ireland', + '540': 'New Caledonia', + '541': 'Belgium and Luxembourg', + '542': 'Belgium and Luxembourg', + '543': 'Belgium and Luxembourg', + '544': 'Belgium and Luxembourg', + '545': 'Belgium and Luxembourg', + '546': 'Belgium and Luxembourg', + '547': 'Belgium and Luxembourg', + '548': 'Vanuatu', + '549': 'Belgium and Luxembourg', + '554': 'New Zealand', + '558': 'Nicaragua', + '560': 'Portugal', + '562': 'Niger', + '566': 'Nigeria', + '569': 'Iceland', + '570': 'Niue', + '571': 'Denmark, Faroe Islands and Greenland', + '572': 'Denmark, Faroe Islands and Greenland', + '573': 'Denmark, Faroe Islands and Greenland', + '574': 'Norfolk Island', + '575': 'Denmark, Faroe Islands and Greenland', + '576': 'Denmark, Faroe Islands and Greenland', + '577': 'Denmark, Faroe Islands and Greenland', + '578': 'Norway', + '579': 'Denmark, Faroe Islands and Greenland', + '580': 'Northern Mariana Islands', + '581': 'United States Minor Outlying Islands', + '583': 'Micronesia', + '584': 'Marshall Islands', + '585': 'Palau', + '586': 'Pakistan', + '590': 'Poland', + '591': 'Panama', + '594': 'Romania', + '598': 'Papua New Guinea', + '599': 'Hungary', + '600': 'Paraguay', + '601': 'South Africa', + '603': 'Ghana', + '604': 'Senegal', + '607': 'Oman', + '608': 'Bahrain', + '609': 'Mauritius', + '611': 'Morocco', + '612': 'Pitcairn', + '613': 'Algeria', + '615': 'Nigeria', + '616': 'Kenya', + '617': 'Cameroon', + '618': 'Ivory Coast', + '619': 'Tunisia', + '620': 'Tanzania', + '621': 'Syria', + '622': 'Egypt', + '624': 'Libya', + '625': 'Jordan', + '626': 'Iran', + '627': 'Kuwait', + // “628” is the GS1 barcode of Saudi Arabia. + '628': 'Saudi Arabia', + '629': 'United Arab Emirates', + '630': 'Qatar', + '631': 'Namibia', + '634': 'Reunion', + '64': 'Finland', + '640': 'Finland', + '641': 'Finland', + '642': 'Romania', + '643': 'Russia', + '644': 'Finland', + '645': 'Finland', + '646': 'Rwanda', + '647': 'Finland', + '648': 'Finland', + '649': 'Finland', + '652': 'Saint Barthelemy', + '654': 'Saint Helena', + '659': 'Saint Kitts and Nevis', + '662': 'Anguilla', + '663': 'Saint Lucia', + '666': 'Saint Martin', + '670': 'Saint Pierre and Miquelon', + '674': 'Saint Vincent and the Grenadines', + '678': 'San Marino', + '680': 'China', + '681': 'China', + '682': 'Sao Tome and Principe', + '686': 'Saudi Arabia', + '688': 'Senegal', + '69': 'China', + '690': 'China', + '691': 'China', + '692': 'China', + '693': 'China', + '694': 'China', + '695': 'China', + '696': 'China', + '697': 'China', + '698': 'China', + '699': 'China', + '70': 'Norway', + '700': 'Norway', + '701': 'Norway', + '702': 'Singapore', + '703': 'Slovakia', + '704': 'Vietnam', + '705': 'Slovenia', + '706': 'Somalia', + '707': 'Norway', + '708': 'Norway', + '709': 'Norway', + '710': 'South Africa', + '716': 'Zimbabwe', + '724': 'Spain', + '728': 'South Sudan', + '729': 'Israel', + '73': 'Sweden', + '730': 'Sweden', + '731': 'Sweden', + '732': 'Western Sahara', + '733': 'Sweden', + '734': 'Sweden', + '735': 'Sweden', + '736': 'Sweden', + '737': 'Sweden', + '738': 'Sweden', + '739': 'Sweden', + '740': 'Guatemala', + '741': 'El Salvador', + '742': 'Honduras', + '743': 'Nicaragua', + '744': 'Costa Rica', + '745': 'Panama', + '746': 'Dominican Republic', + '748': 'Eswatini', + '750': 'Mexico', + '752': 'Sweden', + '754': 'Canada', + '755': 'Canada', + '756': 'Switzerland', + '759': 'Venezuela or Mexico', + // The prefix "76" indicates that it is registered with the UCC + // (Uniform Code Council) system, which is commonly used in the USA. + '76': 'United States', + '760': 'Syria', + '761': 'Switzerland and Liechtenstein', + '762': 'Tajikistan', + '763': 'Switzerland and Liechtenstein', + '764': 'Thailand', + '765': 'Switzerland and Liechtenstein', + '766': 'Switzerland and Liechtenstein', + '767': 'Switzerland and Liechtenstein', + '768': 'Togo', + '769': 'Switzerland and Liechtenstein', + '770': 'Colombia', + '771': 'Colombia', + '772': 'Tokelau', + '773': 'Uruguay', + '775': 'Peru', + '776': 'Tonga', + '777': 'Bolivia', + '778': 'Argentina', + '779': 'Argentina', + '780': 'Chile', + '784': 'Paraguay', + '786': 'Ecuador', + '788': 'Tunisia', + '789': 'Brazil', + '790': 'Brazil', + '792': 'Turkey', + '795': 'Turkmenistan', + '796': 'Turks and Caicos Islands', + '798': 'Tuvalu', + '800': 'Uganda', + '801': 'Italy, San Marino and Vatican City', + '802': 'Italy, San Marino and Vatican City', + '803': 'Italy, San Marino and Vatican City', + '804': 'Ukraine', + '805': 'Italy, San Marino and Vatican City', + '806': 'Italy, San Marino and Vatican City', + '807': 'North Macedonia', + '808': 'Italy, San Marino and Vatican City', + '809': 'Italy, San Marino and Vatican City', + '810': 'Italy, San Marino and Vatican City', + '811': 'Italy, San Marino and Vatican City', + '812': 'Italy, San Marino and Vatican City', + '813': 'Italy, San Marino and Vatican City', + '814': 'Italy, San Marino and Vatican City', + '815': 'Italy, San Marino and Vatican City', + '816': 'Italy, San Marino and Vatican City', + '817': 'Italy, San Marino and Vatican City', + '818': 'Egypt', + '819': 'Italy, San Marino and Vatican City', + '820': 'Italy, San Marino and Vatican City', + '821': 'Italy, San Marino and Vatican City', + '822': 'Italy, San Marino and Vatican City', + '823': 'Italy, San Marino and Vatican City', + '824': 'Italy, San Marino and Vatican City', + '825': 'Italy, San Marino and Vatican City', + '826': 'United Kingdom', + '827': 'Italy, San Marino and Vatican City', + '828': 'Italy, San Marino and Vatican City', + '829': 'Italy, San Marino and Vatican City', + '830': 'Italy, San Marino and Vatican City', + '831': 'Guernsey', + '832': 'Jersey', + '833': 'Isle of Man', + '834': 'Tanzania', + '835': 'Italy, San Marino and Vatican City', + '836': 'Italy, San Marino and Vatican City', + '837': 'Italy, San Marino and Vatican City', + '838': 'Italy, San Marino and Vatican City', + '839': 'Italy, San Marino and Vatican City', + // 840 is ISO 3166-1 numeric code for the United States or GS1 code for + // Spain and Andorra + '840': 'United States or Spain and Andorra', + '841': 'Spain and Andorra', + '842': 'Spain and Andorra', + '843': 'Spain and Andorra', + '844': 'Spain and Andorra', + '845': 'Spain and Andorra', + '846': 'Spain and Andorra', + '847': 'Spain and Andorra', + '848': 'Spain and Andorra', + '849': 'Spain and Andorra', + '850': 'Cuba', + '854': 'Burkina Faso', + '858': 'Slovakia', + '859': 'Czech Republic', + '860': 'Serbia', + '862': 'Venezuela', + '865': 'Mongolia', + '867': 'North Korea', + '868': 'Turkey', + '869': 'Turkey', + '870': 'Netherlands', + '871': 'Netherlands', + '872': 'Netherlands', + '873': 'Netherlands', + '874': 'Netherlands', + '875': 'Netherlands', + '876': 'Wallis and Futuna', + '877': 'Netherlands', + '878': 'Netherlands', + '879': 'Netherlands', + '880': 'South Korea', + '881': 'South Korea', + '882': 'Samoa', + '883': 'Myanmar', + '884': 'Cambodia', + '885': 'Thailand', + '887': 'Yemen', + '888': 'Singapore', + '890': 'India', + '893': 'Vietnam', + '894': 'Zambia', + '896': 'Pakistan', + '899': 'Indonesia', + '90': 'Austria', + '900': 'Austria', + '901': 'Austria', + '902': 'Austria', + '903': 'Austria', + '904': 'Austria', + '905': 'Austria', + '906': 'Austria', + '907': 'Austria', + '908': 'Austria', + '909': 'Austria', + '91': 'Austria', + '910': 'Austria', + '911': 'Austria', + '912': 'Austria', + '913': 'Austria', + '914': 'Austria', + '915': 'Austria', + '916': 'Austria', + '917': 'Austria', + '918': 'Austria', + '919': 'Austria', + '93': 'Australia', + '930': 'Australia', + '931': 'Australia', + '932': 'Australia', + '933': 'Australia', + '934': 'Australia', + '935': 'Australia', + '936': 'Australia', + '937': 'Australia', + '938': 'Australia', + '939': 'Australia', + '94': 'New Zealand', + '940': 'New Zealand', + '941': 'New Zealand', + '942': 'New Zealand', + '943': 'New Zealand', + '944': 'New Zealand', + '945': 'New Zealand', + '946': 'New Zealand', + '947': 'New Zealand', + '948': 'New Zealand', + '949': 'New Zealand', + '955': 'Malaysia', + '958': 'Macau', + }; + + String prefix = barcode.substring(0, 3); + + // Check barcode format and retrieve country + return countryCodeMap[prefix] ?? + countryCodeMap[barcode.substring(0, 2)] ?? + countryCodeMap[barcode.substring(0, 1)] ?? + ''; + } + + @override + bool isEnglishBook(String barcode) { + List digits = barcode.codeUnits + .where((int char) => char >= 48 && char <= 57) + .map((int char) => char - 48) + .toList(); + + if (digits.length != 13) { + return false; + } + + // Check if it starts with "978" (common Book-land prefix) + if (digits.sublist(0, 3).join() != '978') { + return false; + } + return true; + } + + @override + Future savePrecipitationState(bool isPrecipitationFalling) => + _sharedPrefs.setBool( + Settings.precipitationFalling.key, + isPrecipitationFalling, + ); + + @override + bool getPrecipitationState() => + _sharedPrefs.getBool( + Settings.precipitationFalling.key, + ) ?? + true; + + @override + Future saveSoundPreference(bool isSoundOn) => _sharedPrefs.setBool( + Settings.sound.key, + isSoundOn, + ); + + @override + bool getSoundPreference() => + _sharedPrefs.getBool( + Settings.sound.key, + ) ?? + false; + + @override + String getLanguageIsoCode() => 'mock'; + + @override + Future saveLanguageIsoCode(String languageIsoCode) => + _sharedPrefs.setString(Settings.languageIsoCode.key, languageIsoCode); +} diff --git a/test/mock_shared_preferences.dart b/test/mock_shared_preferences.dart new file mode 100644 index 0000000..268b627 --- /dev/null +++ b/test/mock_shared_preferences.dart @@ -0,0 +1,37 @@ +import 'package:mockito/mockito.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class MockSharedPreferences extends Mock implements SharedPreferences { + // You can customize the behavior of specific methods if needed + // For example, mock the behavior of getInstance + static Future getInstance() async { + final MockSharedPreferences mock = MockSharedPreferences(); + // Customize behavior as needed + // when(mock.getString('')).thenReturn(null); // Example behavior, customize as needed + return Future.value(mock); + } + + // Mock the setString method + @override + Future setString(String key, String value) { + return Future.value(true); // Always return true for testing + } + + // Mock the getString method + @override + String? getString(String key) { + return 'mock'; // Always return 'mock' for testing + } + + // Mock the setBool method + @override + Future setBool(String key, bool value) { + return Future.value(true); // Always return true for testing + } + + // Mock the getBool method + @override + bool? getBool(String key) { + return true; // Always return true for testing + } +} diff --git a/test/remote_data_source_impl_test.dart b/test/remote_data_source_impl_test.dart index 1cffe8c..d067102 100644 --- a/test/remote_data_source_impl_test.dart +++ b/test/remote_data_source_impl_test.dart @@ -1,7 +1,7 @@ import 'package:dio/dio.dart'; import 'package:entities/entities.dart'; import 'package:ethical_scanner/data/data_sources/remote/remote_data_source_impl.dart'; -import 'package:ethical_scanner/data/data_sources/remote/rest/dio/logging_interceptor_impl.dart'; +import 'package:ethical_scanner/data/data_sources/remote/rest/logging_interceptor_impl.dart'; import 'package:ethical_scanner/data/data_sources/remote/rest/retrofit_client/retrofit_client.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; @@ -20,36 +20,57 @@ void main() { ]; OpenFoodAPIConfiguration.globalCountry = OpenFoodFactsCountry.CANADA; + final Dio dio = Dio(); + const LoggingInterceptor loggingInterceptor = LoggingInterceptorImpl(); + if (loggingInterceptor is Interceptor) { + dio.interceptors.add(loggingInterceptor as Interceptor); + } // Initialize the RemoteDataSourceImpl - remoteDataSource = RemoteDataSourceImpl(restClient); + remoteDataSource = RemoteDataSourceImpl(RetrofitClient(dio)); }); - test('getProductInfoAsFuture returns ProductInfo on success', () async { - // Arrange - const String input = '0055577105436'; - const ProductInfo expectedResult = ProductInfo( - barcode: input, - country: 'Canada', - ); + test( + 'getProductInfoAsFuture returns ProductInfo on success', + () async { + // Arrange + const String input = '0055577105436'; + const ProductInfo expectedResult = ProductInfo( + barcode: input, + country: 'Canada', + ); - // Act - final ProductInfo result = await remoteDataSource.getProductInfoAsFuture( - const Barcode(code: input), - ); + // Act + final ProductInfo result = + await remoteDataSource.getProductInfoAsFuture( + const LocalizedCode(code: input), + ); - // Assert - expect(result.country, equals(expectedResult.country)); - expect(result.barcode, equals(expectedResult.barcode)); - }); - }); -} + // Assert + expect(result.country, equals(expectedResult.country)); + expect(result.barcode, equals(expectedResult.barcode)); + }, + timeout: const Timeout(Duration(seconds: 40)), + ); + + test( + 'getProductInfoAsFuture returns ProductInfo with backup terrorism ' + 'sponsors on error', + () async { + // Arrange + const String input = 'invalid_barcode'; -RestClient get restClient { - final Dio dio = Dio(); - const LoggingInterceptor loggingInterceptor = LoggingInterceptorImpl(); - if (loggingInterceptor is Interceptor) { - dio.interceptors.add(loggingInterceptor as Interceptor); - } - return RetrofitClient(dio); + // Act and Assert + expect( + () async { + await remoteDataSource.getProductInfoAsFuture( + const LocalizedCode(code: input), + ); + }, + throwsA(isA()), + ); + }, + timeout: const Timeout(Duration(seconds: 40)), + ); + }); }