diff --git a/.env b/.env new file mode 100644 index 00000000..3aa5a596 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +OPEN_WEATHER_API_KEY=YOUR_API_KEY +OPEN_WEATHER_BASE_URL=https://api.openweathermap.org/ \ No newline at end of file diff --git a/.env.dev b/.env.dev new file mode 100644 index 00000000..3aa5a596 --- /dev/null +++ b/.env.dev @@ -0,0 +1,2 @@ +OPEN_WEATHER_API_KEY=YOUR_API_KEY +OPEN_WEATHER_BASE_URL=https://api.openweathermap.org/ \ No newline at end of file diff --git a/.env.qa b/.env.qa new file mode 100644 index 00000000..3aa5a596 --- /dev/null +++ b/.env.qa @@ -0,0 +1,2 @@ +OPEN_WEATHER_API_KEY=YOUR_API_KEY +OPEN_WEATHER_BASE_URL=https://api.openweathermap.org/ \ No newline at end of file diff --git a/.env.test b/.env.test new file mode 100644 index 00000000..3e3afa7b --- /dev/null +++ b/.env.test @@ -0,0 +1,4 @@ +OPEN_WEATHER_API_KEY=YOUR_API_KEY +OPEN_WEATHER_BASE_URL=https://api.openweathermap.org/ +USE_GOOGLE_FONTS=false +RENDER_FONTS_IN_TEST=false \ No newline at end of file diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index bb93ae0a..215b679d 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -12,7 +12,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Replace with your Personal Access Tokens with: token: ${{ secrets.SHOUNAK_GITHUB_TOKEN }} @@ -20,18 +20,26 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' + cache: true - - name: Setup app_secrets.dart + - name: Setup .env.qa env: - APP_SECRETS: ${{ secrets.APP_SECRETS }} - APP_SECRETS_PATH: lib/secrets/app_secrets.dart - run: echo -n $APP_SECRETS | base64 --decode > $APP_SECRETS_PATH + ENV_FILE: ${{ secrets.ENV_QA }} + run: echo -n $ENV_FILE | base64 --decode > .env.qa + + - name: Setup .env.prod + env: + ENV_FILE: ${{ secrets.ENV_PROD }} + run: echo -n $ENV_FILE | base64 --decode > .env.prod + + - name: Activate derry + run: flutter pub global activate derry - name: Get Dependencies run: flutter pub get - name: Run build_runner - run: flutter pub run build_runner build + run: derry build_runner - name: Bump Build Number run: scripts/bump-build-number.sh @@ -47,7 +55,7 @@ jobs: run: scripts/set-flavor-and-apk-path-to-env.sh - name: Build APK - run: scripts/build.sh apk $FLAVOR_NAME --release --obfuscate --split-debug-info=debug-info + run: derry build release-apk $FLAVOR_NAME env: KEY_STORE_PATH: ${{ steps.decode_keystore.outputs.filePath }} RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }} @@ -80,25 +88,33 @@ jobs: name: Release iOS to TestFlight runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: token: ${{ secrets.SHOUNAK_GITHUB_TOKEN }} - uses: subosito/flutter-action@v2 with: channel: 'stable' + cache: true - - name: Setup app_secrets.dart + - name: Setup .env.qa env: - APP_SECRETS: ${{ secrets.APP_SECRETS }} - APP_SECRETS_PATH: lib/secrets/app_secrets.dart - run: echo -n $APP_SECRETS | base64 --decode > $APP_SECRETS_PATH + ENV_FILE: ${{ secrets.ENV_QA }} + run: echo -n $ENV_FILE | base64 --decode > .env.qa + + - name: Setup .env.prod + env: + ENV_FILE: ${{ secrets.ENV_PROD }} + run: echo -n $ENV_FILE | base64 --decode > .env.prod + + - name: Activate derry + run: flutter pub global activate derry - name: Get Dependencies run: flutter pub get - name: Run build_runner - run: flutter pub run build_runner build + run: derry build_runner - uses: maxim-lobanov/setup-xcode@v1 with: @@ -122,6 +138,7 @@ jobs: run: echo -n "$PLIST" | base64 --decode --output $PLIST_PATH - name: Build the signed ipa + # TODO: Move to derry run: chmod u+x scripts/build.sh && scripts/build.sh ipa prod --release - name: Upload the signed ipa to TestFlight @@ -138,7 +155,7 @@ jobs: always() && (needs.build-android.result == 'success' || needs.build-ios.result == 'success') steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Replace with your Personal Access Token with: token: ${{ secrets.SHOUNAK_GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43af9974..8e983554 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,62 +1,178 @@ name: CI on: pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - lint-test-build-android: - name: Lint, Test And Build Android + lint-test: + name: Lint and Unit Tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: channel: 'stable' + cache: true - - name: Setup app_secrets.dart + - name: Setup .env.dev env: - APP_SECRETS: ${{ secrets.APP_SECRETS }} - APP_SECRETS_PATH: lib/secrets/app_secrets.dart - run: echo -n $APP_SECRETS | base64 --decode > $APP_SECRETS_PATH + ENV_FILE: ${{ secrets.ENV_DEV }} + run: echo -n $ENV_FILE | base64 --decode > .env.dev + + - name: Activate derry + run: flutter pub global activate derry - name: Get Dependencies run: flutter pub get - name: Run build_runner - run: flutter pub run build_runner build --delete-conflicting-outputs + run: derry build_runner - name: Lint - run: flutter analyze --fatal-infos --fatal-warnings + run: derry analyze + + - name: Format + run: derry format - name: Unit tests - run: flutter test --exclude-tags=golden + run: derry test exclude-goldens -- --coverage-path=coverage/lcov.base.info --coverage + + - uses: actions/upload-artifact@v3 + with: + name: base-coverage-${{ github.event.number }} + path: coverage + retention-days: 7 + + build-android: + name: Build Android + runs-on: ubuntu-latest + needs: [ lint-test ] + steps: + - uses: actions/checkout@v3 + + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + + - name: Setup .env.dev + env: + ENV_FILE: ${{ secrets.ENV_DEV }} + run: echo -n $ENV_FILE | base64 --decode > .env.dev + + - name: Activate derry + run: flutter pub global activate derry + + - name: Get Dependencies + run: flutter pub get + + - name: Run build_runner + run: derry build_runner - name: Build Android - run: scripts/build.sh apk dev --debug + run: derry build apk dev + build-ios: name: Golden Tests and Build iOS runs-on: macos-latest - needs: [ lint-test-build-android ] + needs: [ lint-test ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - uses: subosito/flutter-action@v2 with: channel: 'stable' + cache: true - - name: Setup app_secrets.dart + - name: Setup .env.dev env: - APP_SECRETS: ${{ secrets.APP_SECRETS }} - APP_SECRETS_PATH: lib/secrets/app_secrets.dart - run: echo -n $APP_SECRETS | base64 --decode > $APP_SECRETS_PATH + ENV_FILE: ${{ secrets.ENV_DEV }} + run: echo -n $ENV_FILE | base64 --decode > .env.dev + + - name: Activate derry + run: flutter pub global activate derry - name: Get Dependencies run: flutter pub get - name: Run build_runner - run: flutter pub run build_runner build + run: derry build_runner + + - uses: actions/download-artifact@v3 + with: + name: base-coverage-${{ github.event.number }} + path: coverage + + - name: Setup lcov + run: brew install lcov - name: Golden tests - run: flutter test --tags=golden test/presentation/goldens + run: derry test goldens -- --merge-coverage --coverage + + - uses: actions/upload-artifact@v3 + with: + name: coverage-${{ github.event.number }} + path: coverage + retention-days: 7 - name: Build iOS - run: scripts/build.sh ios dev --debug --no-codesign \ No newline at end of file + run: derry build ios dev + + build-web: + name: Build Web + runs-on: ubuntu-latest + needs: [ lint-test ] + steps: + - uses: actions/checkout@v3 + + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + + - name: Setup .env.dev + env: + ENV_FILE: ${{ secrets.ENV_DEV }} + run: echo -n $ENV_FILE | base64 --decode > .env.dev + + - name: Activate derry + run: flutter pub global activate derry + + - name: Get Dependencies + run: flutter pub get + + - name: Run build_runner + run: derry build_runner + + - name: Build Web + run: derry build web dev + + sonar-scan: + name: SonarQube Scan + runs-on: ubuntu-latest + needs: [build-android,build-ios,build-web] + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - uses: actions/download-artifact@v3 + with: + name: coverage-${{ github.event.number }} + path: coverage + - uses: sonarsource/sonarqube-scan-action@master + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + # If you wish to fail your job when the Quality Gate is red, uncomment the + # following lines. This would typically be used to fail a deployment. + # We do not recommend to use this in a pull request. Prefer using pull request + # decoration instead. + # - uses: sonarsource/sonarqube-quality-gate-action@master + # timeout-minutes: 5 + # env: + # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/update_goldens.yml b/.github/workflows/update_goldens.yml index f1499332..8f46a15f 100644 --- a/.github/workflows/update_goldens.yml +++ b/.github/workflows/update_goldens.yml @@ -7,26 +7,29 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: channel: 'stable' + cache: true - - name: Setup app_secrets.dart + - name: Setup .env.dev env: - APP_SECRETS: ${{ secrets.APP_SECRETS }} - APP_SECRETS_PATH: lib/secrets/app_secrets.dart - run: echo -n $APP_SECRETS | base64 --decode > $APP_SECRETS_PATH + ENV_FILE: ${{ secrets.ENV_DEV }} + run: echo -n $ENV_FILE | base64 --decode > .env.dev + + - name: Activate derry + run: flutter pub global activate derry - name: Get Dependencies run: flutter pub get - name: Run build_runner - run: flutter pub run build_runner build + run: derry build_runner - name: Generate Goldens - run: flutter test --tags=golden test/presentation/goldens --update-goldens + run: derry test update-goldens - name: Configure Git run: | diff --git a/.gitignore b/.gitignore index 8d81ac04..6ec7013b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,6 @@ /build/ # Web related -lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols @@ -71,3 +70,7 @@ app.*.map.json # Secrets lib/secrets/ !lib/secrets/app_secrets.skeleton.dart + +.mason + +.scannerwork diff --git a/.idea/runConfigurations/main_dev_dart.xml b/.idea/runConfigurations/main_dev_dart.xml deleted file mode 100644 index 8df82937..00000000 --- a/.idea/runConfigurations/main_dev_dart.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/main_dev_preview_dart.xml b/.idea/runConfigurations/main_dev_preview_dart.xml deleted file mode 100644 index cd70c68f..00000000 --- a/.idea/runConfigurations/main_dev_preview_dart.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/main_qa_dart.xml b/.idea/runConfigurations/main_qa_dart.xml deleted file mode 100644 index 3528eb2e..00000000 --- a/.idea/runConfigurations/main_qa_dart.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/README.md b/README.md index 1c43458d..8e084078 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ + +
@@ -31,28 +33,91 @@ We’re always looking for people who value their work, so come and join us. We are hiring!
- -### Check out the [multi-package branch](https://github.com/wednesday-solutions/flutter_template/tree/multi-package) for a multi package flutter architecture. ## Getting Started Clone the repo and follow these steps to setup the project. -#### App Secrets -Sensitive information like api keys, credentials, etc should not be checked into git repos, especially public ones. To keep such data safe the template uses `app_secrets.dart` file. -If you want to run the app locally, you will need to create a new file `app_secrets.dart` under [`lib/secrets`](lib/secrets). To help with setting up the secrets file, the template inclued a skeleton secrets file. Copy all the content from [`app_secrets.skeleton.dart`](lib/secrets/app_secrets.skeleton.dart) to the `app_secrets.dart` file you just created. Uncomment the code and replace the the placeholders with your secret data. +#### Environment +The template was build using dart null safety. Dart 2.19.2 or greater and Flutter 3 or greater is required. -You can get your Open Weather API key from [here](https://openweathermap.org/appid#start). +[Follow this guide to setup your flutter environment](https://docs.flutter.dev/get-started/install) based on your platform. + +#### Flutter +First and foremost make sure you have Flutter 3 setup on your system. +You can check the version by running +```bash +flutter --version +``` +You should see output similar to this. Check if the version is `3.x.x`. +```bash +Flutter 3.7.7 • channel stable • https://github.com/flutter/flutter.git +Framework • revision 2ad6cd72c0 (13 days ago) • 2023-03-08 09:41:59 -0800 +Engine • revision 1837b5be5f +Tools • Dart 2.19.4 • DevTools 2.20.1 +``` +If not run this command to update flutter to the latest version +```bash +flutter upgrade +``` +#### Derry +This template uses [`derry`](https://pub.dev/packages/derry) as it's script manager. +Run this command to setup derry +```bash +dart pub global activate derry +``` +Most of the scripts we will use are abstracted away by derry. If you want to know more about the scirpts, read the [scripts documentation](scripts/README.md). #### Get Dependencies ```shell flutter pub get ``` #### Run Code Generation ```shell -bash scripts/generate-all.sh +derry generate all ``` -Read the [scripts documentation](scripts/README.md) to learn about all the scrips used in the project. +#### API Key + +> ##### You can skip this step if you just want to get the template running. If you skip this step, the weather search will not give you any results. + +Sensitive information like api keys, credentials, etc should not be checked into git repos, especially public ones. To keep such data safe the template uses `.env` files. Each [Flavor](#flavors) uses it's own `.env` file. + +The tempalte uses weather api from `openweathermap.org`. +You can get your Open Weather API key from [here](https://openweathermap.org/appid#start). + +Once you have the key, update the `.env` files with your api key. Replace `YOUR_API_KEY` with the key that you got from open weather api. +``` +OPEN_WEATHER_API_KEY=YOUR_API_KEY +OPEN_WEATHER_BASE_URL=https://api.openweathermap.org/ +``` + +## Running the app +With the setup done, we can get the app running. + +#### Flavors +The template comes with built-in support for 3 flavors. Each flavor has it's own `.env` file. +- dev - [`.env.dev`](.env.dev) +- qa - [`.env.qa`](.env.qa) +- prod - [`.env`](.env) + +You can setup any environment specific values in the respective `.env` files. + +#### Launch +To launch the app run the following command and specify the flavor name. +```shell + derry launch dev +``` +#### Android Studio +On android studio, you will find pre defined run configurations. +- Select a flavor from the dropdown +Screenshot 2023-03-21 at 11 24 35 AM + +- Select a device to launch on +Screenshot 2023-03-21 at 11 24 25 AM + +- Click `Run` to launch the app + +Screenshot 2023-03-21 at 11 24 45 AM ## Architecture The architecture of the template facilitates separation of concerns and avoids tight coupling between it's various layers. The goal is to have the ability to make changes to individual layers without affecting the entire app. This architecture is an adaptation of concepts from [`The Clean Architecture`](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html). @@ -85,7 +150,6 @@ The layers `presentation`, `domain` and `services` each have an `entity` directo Apart from the main layers, the template has - [`lib/foundation`](lib/foundation): Extensions on primitive data types, loggers, global type alias etc. - [`lib/flavors`](lib/flavors): Flavor i.e. Environment related classes. -- [`lib/entrypoints`](lib/entrypoints): Target files for flutter to run for each flavor. - [`lib/app.dart`](lib/app.dart): App initialization code. ## Understanding the Presentation Layer @@ -126,28 +190,45 @@ Each page accepts the [`Screen`](#screen) object as input. ### Widgets Each destination has a `widgets` directory. It holds all the widgets that appear on a [`Page`](#page) excluding the page itself. -Each widget the requires access to data from the view model it split into two dart files. The connector widget communicates with the view model, and the content widget has the actual UI. The connector widget passes all the required data to the content widget. Thus the content widget never depends on the state managent solution used. This helps in easy replacement of state management solution if needed and also makes it easier to test widgets. +Each widget the requires access to data from the view model it split into two dart files. The `connector widget` communicates with the view model, and the `content widget` has the actual UI. The connector widget passes all the required data to the content widget. Thus the content widget never depends on the state managent solution used. This helps in easy replacement of state management solution if needed and also makes it easier to test widgets. ### Screen A [`Screen`](lib/presentation/entity/screen/screen.dart) is a class that represents a `Page` in the context of navigation. It holds the `path` used by the navigator to navigate to a `Page` and also holds any arguments required to navigate to that `Page`. -## Flavors -The template comes with built-in support for 3 flavors. Each flavor uses a different `main.dart` file. -- Dev - [`main_dev.dart`](lib/entrypoints/main_dev.dart) -- QA - [`main_qa.dart`](lib/entrypoints/main_qa.dart) -- Prod - [`main_prod.dart`](lib/entrypoints/main_prod.dart) +## Templating +As you can read from the [Architecture](#architecture) section, adding a new page in the app can require a lot of files to be created. The template uses [`mason`](https://pub.dev/packages/mason_cli) as it's templating engine to automate some of this work. -You can setup any environment specific values in the respective `main.dart` files. +To get started with mason, first activate mason globally +```bash +dart pub global activate mason_cli +``` -To run a specific flavor you need to specify the flavor and target file. -```shell - flutter run --flavor qa -t lib/entrypoints/main_qa.dart +Similar to using `pub get` we need to run `mason get` to setup the `bricks` (templates are called brick in mason). +```bash +mason get ``` +You can learn more about `mason` [here](https://docs.brickhub.dev/). -**To avoid specifying all the flags every time, use the [`run.sh`](scripts/README.md#run) script** +#### Destination Brick +The template comes with a pre setup brick called `destination`. +Run the `destination` brick using the following command. +```bash +mason make destination -o lib/presentation/destinations/notes --name notesList +``` +`-o` flag sets the output directory for the `brick` and `--name` is the name used for the files and classes. This brick generates the required file structure and runs `build_runner` (via mason hooks) to trigger code generation. After running the command, this is what you should see: + +Screenshot 2023-03-21 at 2 55 45 PM + +## Testing + +The template also includes a testing setup for +- [`Unit Tests`](test/repository). +- [`Widget Tests`](test/presentation/integration) +- [`Golden Tests`](test/presentation/goldens) + +The test coverage and code quality reporting is done using [`sonarqube`](https://docs.sonarqube.org/latest/). +You can read the documentation about integrating `sonarqube` in you CI workflow [here](https://docs.sonarqube.org/latest/devops-platform-integration/github-integration/#analyzing-projects-with-github-actions). -Read the [scripts documentation](scripts/README.md) to learn about all the scrips used in the project. - ## Content The Flutter Template contains: - A [`Flutter`](https://flutter.dev/) application. @@ -159,26 +240,27 @@ The Flutter Template contains: - [`Freezed`](https://pub.dev/packages/freezed) for data class functionality. - [`Get It`](https://pub.dev/packages/get_it) for dependency injection. - [`Flutter Lints`](https://pub.dev/packages/flutter_lints) for linting. +- [`derry`](https://pub.dev/packages/derry) for script management. +- [`mason`](https://pub.dev/packages/mason_cli) for templating. +- [`sonarqube`](https://docs.sonarqube.org/latest/) for code inspection. The template contains an example (displaying weather data) with responsive widgets, reactive state management, offline storage and api calls. -## Requirements -The template was build using dart null safety. Dart 2.12 or greater and Flutter 2 or greater is required. - -Dart 2.15 or greater and Flutter 2.10 or greater is recommended. - -[Follow this guide to setup your flutter environment](https://docs.flutter.dev/get-started/install) based on your platform. - ## Continuous Integration and Deployment The Flutter template comes with built-in support for CI/CD using Github Actions. ### CI The [`CI`](.github/workflows/ci.yml) workflow performs the following checks on every pull request: - Lints the code with `flutter analyze`. +- Check formatting with `dart format` - Runs tests using `flutter test`. +- Run golden test. +- Report code coverage and code quality using `sonarqube`. - Build the android app. - Build the ios app. +You can read the documentation about integrating `sonarqube` in you CI workflow [here](https://docs.sonarqube.org/latest/devops-platform-integration/github-integration/#analyzing-projects-with-github-actions). + ### CD The [`CD`](.github/workflows/cd.yml) workflow performs the following actions: - Bump the build number by 1. @@ -190,6 +272,12 @@ The [`CD`](.github/workflows/cd.yml) workflow performs the following actions: - Upload the ipa as an artifact to release the tag. - Commit the updated version to git. +### .env files on CD +The CI and CD workflows grab the `.env` files from github secrets. The secrets are name `ENV_` followed by environment name. +So for dev the secret name is `ENV_DEV`, qa is `ENV_QA` and prod is `ENV_PROD`. +Convert your `.env` files with all the api keys populated to base64 strings and set them as secrets on github with the appropriate secret name. +You can learn more about github actions secrets [here](https://docs.github.com/en/actions/security-guides/encrypted-secrets). + ### Android CD setup For the android CD workflow to run, we need to perform the following setup steps: - Follow these instructions to [generate an upload keystore](https://developer.android.com/studio/publish/app-signing#generate-key). Note down the `store password`, `key alias` and `key password`. You will need these in later steps. @@ -284,12 +372,24 @@ openssl base64 < FILENAME | tr -d '\n' | tee ENCODED_FILENAME.txt - If the branches that you will be running CD on are protected, you will need to use a [`Personal Access Token (PAT)`](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) to commit the version changes. - After creating the `PAT`, exclude the account that the token belongs to from the [`branch protection rules`](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule#creating-a-branch-protection-rule). - Save the token in github secrets and update the key name in the `cd.yml` file under each `checkout` action. -- Since our `CD` workflow is triggered on a push, and we create a new commit in the workflow itself, the commit message created by the `CD` workflow includes `[skip ci]` tag so that the workflow does not end up in an infinite loop.Read more about this [here](https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs) +- Since our `CD` workflow is triggered on a push, and we create a new commit in the workflow itself, the commit message created by the `CD` workflow includes `[skip ci]` tag so that the workflow does not end up in an infinite loop. Read more about this [here](https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs) **If you do not plan to use the CD workflow on protected branches, you can remove the token part from the checkout actions.** ## Gotchas -- Flutter apps might have issues on some android devices with variable refresh rate where the app is locked at 60fps instead of running at the highest refresh rate. This might make your app look like it is running slower than other apps on the device. To fix this the template uses the [`flutter_displaymode`](https://pub.dev/packages/flutter_displaymode) package. The template sets the highest refresh rate available. If you don't want this behaviour you can remove the lines 40 to 46 in [`app.dart`](lib/app.dart#L40). [`Link to frame rate issue on flutter`](https://github.com/flutter/flutter/issues/35162). -## Issues -- Additionally look into the Issues for the repository for some commonly faced problems while setup. They are marked with the label `documentation` and are mostly closed +#### Refresh Rate +Flutter apps might have issues on some android devices with variable refresh rate where the app is locked at 60fps instead of running at the highest refresh rate. This might make your app look like it is running slower than other apps on the device. To fix this the template uses the [`flutter_displaymode`](https://pub.dev/packages/flutter_displaymode) package. The template sets the highest refresh rate available. If you don't want this behaviour you can remove the lines 40 to 46 in [`app.dart`](lib/app.dart#L40). [`Link to frame rate issue on flutter`](https://github.com/flutter/flutter/issues/35162). + +#### Golden Tests +Golden test screenshots (goldens) are rendered using the rendering mechanisms on the os that you are running the tests on. Because of the slight differences in each os, the goldens generated on each os differ slightly from each other. Goldens generated on macos won't match exactly to the goldens generated on windows or linux and your tests will fail. +To work around this, make sure to generate goldens and run golden tests on a single os. This template uses macos as it's os of choice to deal with goldens. You will find that on [CI](.github/workflows/ci.yml), the golden tests are run on a macos host. + +- `What if your team members use different operating systems for development?` - In that case, the devs not using your os of choice should have a way to generate goldens on your os of choice. This template has a [`update_goldens`](.github/workflows/update_goldens.yml) workflow that can be manually triggered on any branch. It will generate the golden files on macos and commit the changes to the same branch. + +## License +Flutter Template is licensed under the MIT license. Check the [LICENSE](LICENSE) file for details. + +## Other Versions +##### Check out the [multi-package branch](https://github.com/wednesday-solutions/flutter_template/tree/multi-package) for a multi package flutter architecture. + diff --git a/analysis_options.yaml b/analysis_options.yaml index ed152bcc..71143ffb 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -12,3 +12,4 @@ analyzer: - "**.gr.dart" - "**/translation_keys.dart" - "**/translation_loader.dart" + - "bricks/**" diff --git a/android/gradle.properties b/android/gradle.properties index f47073bb..febaef16 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -2,6 +2,6 @@ org.gradle.jvmargs=-Xmx1536M --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --ad android.useAndroidX=true android.enableJetifier=true # Flutter specific properties -flutter.minSdkVersion=21 -flutter.targetSdkVersion=31 -flutter.compileSdkVersion=31 +flutter.minSdkVersion=23 +flutter.targetSdkVersion=33 +flutter.compileSdkVersion=33 diff --git a/assets/google_fonts/OFL.txt b/assets/google_fonts/OFL.txt new file mode 100644 index 00000000..9b448d40 --- /dev/null +++ b/assets/google_fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/google_fonts/OpenSans-Bold.ttf b/assets/google_fonts/OpenSans-Bold.ttf new file mode 100644 index 00000000..a1398b33 Binary files /dev/null and b/assets/google_fonts/OpenSans-Bold.ttf differ diff --git a/assets/google_fonts/OpenSans-ExtraBold.ttf b/assets/google_fonts/OpenSans-ExtraBold.ttf new file mode 100644 index 00000000..08d7185f Binary files /dev/null and b/assets/google_fonts/OpenSans-ExtraBold.ttf differ diff --git a/assets/google_fonts/OpenSans-Light.ttf b/assets/google_fonts/OpenSans-Light.ttf new file mode 100644 index 00000000..d9a9e277 Binary files /dev/null and b/assets/google_fonts/OpenSans-Light.ttf differ diff --git a/assets/google_fonts/OpenSans-Medium.ttf b/assets/google_fonts/OpenSans-Medium.ttf new file mode 100644 index 00000000..ba6db9b7 Binary files /dev/null and b/assets/google_fonts/OpenSans-Medium.ttf differ diff --git a/assets/google_fonts/OpenSans-Regular.ttf b/assets/google_fonts/OpenSans-Regular.ttf new file mode 100644 index 00000000..1dc226dd Binary files /dev/null and b/assets/google_fonts/OpenSans-Regular.ttf differ diff --git a/assets/google_fonts/OpenSans-SemiBold.ttf b/assets/google_fonts/OpenSans-SemiBold.ttf new file mode 100644 index 00000000..66acb207 Binary files /dev/null and b/assets/google_fonts/OpenSans-SemiBold.ttf differ diff --git a/bricks/destination/CHANGELOG.md b/bricks/destination/CHANGELOG.md new file mode 100644 index 00000000..f0640d6f --- /dev/null +++ b/bricks/destination/CHANGELOG.md @@ -0,0 +1,3 @@ +# 0.1.0+1 + +- TODO: Describe initial release. diff --git a/bricks/destination/LICENSE b/bricks/destination/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/bricks/destination/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/bricks/destination/README.md b/bricks/destination/README.md new file mode 100644 index 00000000..3ba91aba --- /dev/null +++ b/bricks/destination/README.md @@ -0,0 +1,27 @@ +# destination + +[![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason) + +A new brick created with the Mason CLI. + +_Generated by [mason][1] 🧱_ + +## Getting Started 🚀 + +This is a starting point for a new brick. +A few resources to get you started if this is your first brick template: + +- [Official Mason Documentation][2] +- [Code generation with Mason Blog][3] +- [Very Good Livestream: Felix Angelov Demos Mason][4] +- [Flutter Package of the Week: Mason][5] +- [Observable Flutter: Building a Mason brick][6] +- [Meet Mason: Flutter Vikings 2022][7] + +[1]: https://github.com/felangel/mason +[2]: https://docs.brickhub.dev +[3]: https://verygood.ventures/blog/code-generation-with-mason +[4]: https://youtu.be/G4PTjA6tpTU +[5]: https://youtu.be/qjA0JFiPMnQ +[6]: https://youtu.be/o8B1EfcUisw +[7]: https://youtu.be/LXhgiF5HiQg diff --git a/bricks/destination/__brick__/{{name.snakeCase()}}/widgets/{{name.snakeCase()}}_page_body/{{name.snakeCase()}}_page_body.dart b/bricks/destination/__brick__/{{name.snakeCase()}}/widgets/{{name.snakeCase()}}_page_body/{{name.snakeCase()}}_page_body.dart new file mode 100644 index 00000000..cdfbc2f6 --- /dev/null +++ b/bricks/destination/__brick__/{{name.snakeCase()}}/widgets/{{name.snakeCase()}}_page_body/{{name.snakeCase()}}_page_body.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class {{name.pascalCase()}}PageBody extends ConsumerWidget { + const {{name.pascalCase()}}PageBody({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final {{name.camelCase()}}ViewModel = ref.watch({{name.camelCase()}}ViewModelProvider.notifier); + + return {{name.pascalCase()}}PageBodyContent(); + } +} \ No newline at end of file diff --git a/bricks/destination/__brick__/{{name.snakeCase()}}/widgets/{{name.snakeCase()}}_page_body/{{name.snakeCase()}}_page_body_content.dart b/bricks/destination/__brick__/{{name.snakeCase()}}/widgets/{{name.snakeCase()}}_page_body/{{name.snakeCase()}}_page_body_content.dart new file mode 100644 index 00000000..1ac24120 --- /dev/null +++ b/bricks/destination/__brick__/{{name.snakeCase()}}/widgets/{{name.snakeCase()}}_page_body/{{name.snakeCase()}}_page_body_content.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + + +class {{name.pascalCase()}}PageBodyContent extends StatelessWidget { + final IntentHandlerCallback<{{name.pascalCase()}}ScreenIntent> intentHandler; + + const {{name.pascalCase()}}PageBodyContent({ + super.key, + required this.intentHandler, + }); + + @override + Widget build(BuildContext context) { + return SizedBox(); + } +} diff --git a/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_page.dart b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_page.dart new file mode 100644 index 00000000..bf2efcd6 --- /dev/null +++ b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_page.dart @@ -0,0 +1,27 @@ +import 'package:auto_route/annotations.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_template/presentation/base/page/base_page.dart'; + +import 'widgets/{{name.snakeCase()}}_page_body/{{name.snakeCase()}}_page_body.dart'; + +@RoutePage() +class {{name.pascalCase()}}Page extends ConsumerWidget { + final {{name.pascalCase()}}Screen {{name.camelCase()}}Screen; + + const {{name.pascalCase()}}Page({ + super.key, + required this.{{name.camelCase()}}Screen, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BasePage<{{name.pascalCase()}}Screen, {{name.pascalCase()}}ScreenState, {{name.pascalCase()}}ViewModel>( + viewModelProvider: {{name.camelCase()}}ViewModelProvider, + screen: {{name.camelCase()}}Screen, + appBarActions: () => [], + body: const {{name.pascalCase()}}PageBody(), + ); + } +} diff --git a/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen.dart b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen.dart new file mode 100644 index 00000000..42478d6b --- /dev/null +++ b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen.dart @@ -0,0 +1,8 @@ +import 'package:flutter_template/presentation/entity/routes/routes.dart'; +import 'package:flutter_template/presentation/entity/screen/screen.dart'; + +class {{name.pascalCase()}}Screen extends Screen { + const {{name.pascalCase()}}Screen() : super(); + + static get path => Routes.{{name.camelCase()}}; +} \ No newline at end of file diff --git a/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen_intent.dart b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen_intent.dart new file mode 100644 index 00000000..35942929 --- /dev/null +++ b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen_intent.dart @@ -0,0 +1,9 @@ +import 'package:flutter_template/presentation/entity/intent/intent.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part '{{name.snakeCase()}}_screen_intent.freezed.dart'; + +@freezed +class {{name.pascalCase()}}ScreenIntent with _${{name.pascalCase()}}ScreenIntent implements BaseIntent { + factory {{name.pascalCase()}}ScreenIntent.newIntent() = _HomeScreenIntent_NewIntent; +} diff --git a/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen_state.dart b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen_state.dart new file mode 100644 index 00000000..121e91bf --- /dev/null +++ b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen_state.dart @@ -0,0 +1,14 @@ +import 'package:flutter_template/presentation/entity/base/ui_list_item.dart'; +import 'package:flutter_template/presentation/entity/base/ui_toolbar.dart'; +import 'package:flutter_template/presentation/entity/screen/screen_state.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part '{{name.snakeCase()}}_screen_state.freezed.dart'; + +@freezed +class {{name.pascalCase()}}ScreenState with _${{name.pascalCase()}}ScreenState implements ScreenState { + const factory {{name.pascalCase()}}ScreenState({ + required UIToolbar toolbar, + required bool showLoading, + }) = _{{name.pascalCase()}}ScreenState; +} diff --git a/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_view_model.dart b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_view_model.dart new file mode 100644 index 00000000..b60828ff --- /dev/null +++ b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_view_model.dart @@ -0,0 +1,14 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_template/presentation/base/intent/intent_handler.dart'; +import 'package:flutter_template/presentation/base/view_model_provider/base_view_model.dart'; +import 'package:flutter_template/presentation/entity/screen/screen.dart'; +import 'package:get_it/get_it.dart'; + +final {{name.camelCase()}}ViewModelProvider = + StateNotifierProvider.autoDispose<{{name.pascalCase()}}ViewModel, {{name.pascalCase()}}ScreenState>( + (ref) => GetIt.I.get()); + +abstract class {{name.pascalCase()}}ViewModel extends BaseViewModel<{{name.pascalCase()}}Screen, {{name.pascalCase()}}ScreenState> + implements IntentHandler<{{name.pascalCase()}}ScreenIntent> { + {{name.pascalCase()}}ViewModel({{name.pascalCase()}}ScreenState state) : super(state); +} diff --git a/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_view_model_impl.dart b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_view_model_impl.dart new file mode 100644 index 00000000..cad04b54 --- /dev/null +++ b/bricks/destination/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_view_model_impl.dart @@ -0,0 +1,34 @@ +import 'package:flutter_template/interactor/weather/favorite/favorite_weather_interactor.dart'; +import 'package:flutter_template/navigation/weather/home/home_navigator.dart'; +import 'package:flutter_template/presentation/destinations/weather/home/home_screen_intent.dart'; +import 'package:flutter_template/presentation/destinations/weather/home/home_screen_state.dart'; +import 'package:flutter_template/presentation/destinations/weather/home/home_view_model.dart'; +import 'package:flutter_template/presentation/entity/base/ui_list_item.dart'; +import 'package:flutter_template/presentation/entity/base/ui_toolbar.dart'; +import 'package:flutter_template/presentation/intl/translations/translation_keys.dart'; + +class {{name.pascalCase()}}ViewModelImpl extends {{name.pascalCase()}}ViewModel { + final {{name.pascalCase()}}Navigator {{name.camelCase()}}Navigator; + + {{name.pascalCase()}}ViewModelImpl({ + required this.{{name.camelCase()}}Navigator, + }) : super(_initialState); + + static get _initialState => {{name.pascalCase()}}ScreenState( + toolbar: UIToolbar( + title: LocaleKeys.{{name.camelCase()}}PageTitle, + hasBackButton: false, + ), + showLoading: false, + ); + + @override + void onInit() { + + } + + @override + void onIntent({{name.pascalCase()}}ScreenIntent intent) { + + } +} diff --git a/bricks/destination/brick.yaml b/bricks/destination/brick.yaml new file mode 100644 index 00000000..fcbe0956 --- /dev/null +++ b/bricks/destination/brick.yaml @@ -0,0 +1,31 @@ +name: destination +description: A new brick created with the Mason CLI. + +# The following defines the brick repository url. +# Uncomment and update the following line before publishing the brick. +# repository: https://github.com/my_org/my_repo + +# The following defines the version and build number for your brick. +# A version number is three numbers separated by dots, like 1.2.34 +# followed by an optional build number (separated by a +). +version: 0.1.0+1 + +# The following defines the environment for the current brick. +# It includes the version of mason that the brick requires. +environment: + mason: ">=0.1.0-dev.47 <0.1.0" + +# Variables specify dynamic values that your brick depends on. +# Zero or more variables can be specified for a given brick. +# Each variable has: +# * a type (string, number, boolean, enum, or array) +# * an optional short description +# * an optional default value +# * an optional list of default values (array only) +# * an optional prompt phrase used when asking for the variable +# * a list of values (enums only) +vars: + name: + type: string + description: The name use for all the classes and dart files + prompt: What is the name of the destination? diff --git a/bricks/destination/hooks/.gitignore b/bricks/destination/hooks/.gitignore new file mode 100644 index 00000000..7b37acf4 --- /dev/null +++ b/bricks/destination/hooks/.gitignore @@ -0,0 +1,4 @@ +.dart_tool +.packages +pubspec.lock +build diff --git a/bricks/destination/hooks/post_gen.dart b/bricks/destination/hooks/post_gen.dart new file mode 100644 index 00000000..05e217ce --- /dev/null +++ b/bricks/destination/hooks/post_gen.dart @@ -0,0 +1,11 @@ +import 'dart:io'; +import 'package:mason/mason.dart'; + +Future run(HookContext context) async { + final progress = context.logger.progress('Running generate...'); + + await Process.run('flutter', + ['pub', 'run', 'build_runner', 'build', '--delete-conflicting-outputs']); + + progress.complete(); +} diff --git a/bricks/destination/hooks/pubspec.yaml b/bricks/destination/hooks/pubspec.yaml new file mode 100644 index 00000000..0fb8ad87 --- /dev/null +++ b/bricks/destination/hooks/pubspec.yaml @@ -0,0 +1,7 @@ +name: destination_hooks + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + mason: ">=0.1.0-dev.47 <0.1.0" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 0a8b040f..eafe22cd 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 55; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -225,6 +225,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -278,6 +279,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index d124aefd..54260f6c 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -49,5 +49,7 @@ CADisableMinimumFrameDurationOnPhone + UIApplicationSupportsIndirectInputEvents + diff --git a/lib/app.dart b/lib/app.dart index 8f65c1f4..075f2add 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -20,13 +20,21 @@ import 'package:shared_preferences/shared_preferences.dart'; void startApp() async { await _initialiseApp(); - runApp(EasyLocalization( - supportedLocales: const [Locale("en", "US"), Locale("hi", "IN")], - path: "assets/translations", - fallbackLocale: const Locale("en", "US"), - assetLoader: const CodegenLoader(), - child: TemplateApp(), - )); + // Add fonts license + LicenseRegistry.addLicense(() async* { + final license = await rootBundle.loadString('assets/google_fonts/OFL.txt'); + yield LicenseEntryWithLineBreaks(['google_fonts'], license); + }); + + runApp( + EasyLocalization( + supportedLocales: const [Locale("en", "US"), Locale("hi", "IN")], + path: "assets/translations", + fallbackLocale: const Locale("en", "US"), + assetLoader: const CodegenLoader(), + child: TemplateApp(), + ), + ); } Future _initialiseApp() async { @@ -68,4 +76,4 @@ void _initialiseGetIt() { ..interactorModule() ..presentationModule() ..navigationModule(); -} \ No newline at end of file +} diff --git a/lib/entrypoints/main_dev.dart b/lib/entrypoints/main_dev.dart deleted file mode 100644 index bd856687..00000000 --- a/lib/entrypoints/main_dev.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter_template/app.dart'; -import 'package:flutter_template/flavors/flavor.dart'; -import 'package:flutter_template/flavors/flavor_config.dart'; -import 'package:flutter_template/flavors/flavor_values.dart'; -import 'package:flutter_template/secrets/app_secrets.dart'; - -void main() { - FlavorConfig.initialize( - flavor: Flavor.dev, - values: const FlavorValues( - apiBaseUrl: "https://api.openweathermap.org/", - secrets: AppSecrets.appSecretsDev, - logSqlStatements: true, - showLogs: true, - useFakeData: false, - ), - ); - startApp(); -} diff --git a/lib/entrypoints/main_prod.dart b/lib/entrypoints/main_prod.dart deleted file mode 100644 index b9170d12..00000000 --- a/lib/entrypoints/main_prod.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter_template/app.dart'; -import 'package:flutter_template/flavors/flavor.dart'; -import 'package:flutter_template/flavors/flavor_config.dart'; -import 'package:flutter_template/flavors/flavor_values.dart'; -import 'package:flutter_template/secrets/app_secrets.dart'; - -void main() { - FlavorConfig.initialize( - flavor: Flavor.prod, - values: const FlavorValues( - apiBaseUrl: "https://api.openweathermap.org/", - secrets: AppSecrets.appSecretsProd, - ), - ); - startApp(); -} diff --git a/lib/entrypoints/main_qa.dart b/lib/entrypoints/main_qa.dart deleted file mode 100644 index c9421ebb..00000000 --- a/lib/entrypoints/main_qa.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter_template/app.dart'; -import 'package:flutter_template/flavors/flavor.dart'; -import 'package:flutter_template/flavors/flavor_config.dart'; -import 'package:flutter_template/flavors/flavor_values.dart'; -import 'package:flutter_template/secrets/app_secrets.dart'; - -void main() { - FlavorConfig.initialize( - flavor: Flavor.qa, - values: const FlavorValues( - apiBaseUrl: "https://api.openweathermap.org/", - secrets: AppSecrets.appSecretsQA, - ), - ); - startApp(); -} diff --git a/lib/flavors/flavor.dart b/lib/flavors/flavor.dart index 4ffe8338..d22ae441 100644 --- a/lib/flavors/flavor.dart +++ b/lib/flavors/flavor.dart @@ -1,5 +1,16 @@ enum Flavor { dev, qa, - prod, + prod; + + static Flavor fromString({required String flavor}) { + switch (flavor) { + case "prod": + return Flavor.prod; + case "qa": + return Flavor.qa; + default: + return Flavor.dev; + } + } } diff --git a/lib/flavors/flavor_config.dart b/lib/flavors/flavor_config.dart index cd6ba181..8f8a3b3c 100644 --- a/lib/flavors/flavor_config.dart +++ b/lib/flavors/flavor_config.dart @@ -2,15 +2,16 @@ import 'package:flutter_template/flavors/flavor.dart'; import 'package:flutter_template/flavors/flavor_values.dart'; class FlavorConfig { - final Flavor flavor; - final FlavorValues values; + final Flavor _flavor; + final FlavorValues _values; static late FlavorConfig _instance; static var _initialized = false; - factory FlavorConfig.initialize( - {required Flavor flavor, required FlavorValues values}) { + factory FlavorConfig.initialize({required String flavorString}) { if (!_initialized) { + final flavor = Flavor.fromString(flavor: flavorString); + final values = FlavorValues.fromEnvironment(); _instance = FlavorConfig._internal(flavor: flavor, values: values); _initialized = true; } @@ -18,15 +19,18 @@ class FlavorConfig { } FlavorConfig._internal({ - required this.flavor, - required this.values, - }); + required Flavor flavor, + required FlavorValues values, + }) : _flavor = flavor, + _values = values; - static FlavorConfig get instance => _instance; + static Flavor get flavor => _instance._flavor; - static bool isPROD() => _instance.flavor == Flavor.prod; + static FlavorValues get values => _instance._values; - static bool isQA() => _instance.flavor == Flavor.qa; + static bool isPROD() => _instance._flavor == Flavor.prod; - static bool isDEV() => _instance.flavor == Flavor.dev; + static bool isQA() => _instance._flavor == Flavor.qa; + + static bool isDEV() => _instance._flavor == Flavor.dev; } diff --git a/lib/flavors/flavor_values.dart b/lib/flavors/flavor_values.dart index d77e3f6b..1d3983b3 100644 --- a/lib/flavors/flavor_values.dart +++ b/lib/flavors/flavor_values.dart @@ -1,20 +1,35 @@ -import 'package:flutter_template/secrets/app_secrets.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_template/foundation/extensions/dotenv_ext.dart'; class FlavorValues { final String apiBaseUrl; - final AppSecrets secrets; final bool useFakeData; final bool showLogs; + final String openWeatherApiKey; + final bool useGoogleFonts; + final bool renderFontsInTest; final bool _logSqlStatements; const FlavorValues({ required this.apiBaseUrl, - required this.secrets, + required this.openWeatherApiKey, logSqlStatements = false, this.showLogs = false, this.useFakeData = false, + required this.useGoogleFonts, + required this.renderFontsInTest, }) : _logSqlStatements = showLogs && logSqlStatements; bool get logSqlStatements => _logSqlStatements; + + static FlavorValues fromEnvironment() { + return FlavorValues( + apiBaseUrl: dotenv.get("OPEN_WEATHER_BASE_URL"), + openWeatherApiKey: dotenv.get("OPEN_WEATHER_API_KEY"), + useGoogleFonts: + dotenv.getBoolOrDefault("USE_GOOGLE_FONTS", fallback: true), + renderFontsInTest: + dotenv.getBoolOrDefault("RENDER_FONTS_IN_TEST", fallback: false)); + } } diff --git a/lib/foundation/extensions/dotenv_ext.dart b/lib/foundation/extensions/dotenv_ext.dart new file mode 100644 index 00000000..cc6016e4 --- /dev/null +++ b/lib/foundation/extensions/dotenv_ext.dart @@ -0,0 +1,14 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +extension DotEnvExt on DotEnv { + bool getBoolOrDefault(String key, {required bool fallback}) { + final booleanString = dotenv.maybeGet(key); + + if (booleanString == null) return fallback; + + if (booleanString.toLowerCase() == 'true') return true; + if (booleanString.toLowerCase() == 'false') return false; + + return fallback; + } +} diff --git a/lib/foundation/logger/logger.dart b/lib/foundation/logger/logger.dart index 14b4832d..6b0a9612 100644 --- a/lib/foundation/logger/logger.dart +++ b/lib/foundation/logger/logger.dart @@ -41,7 +41,7 @@ class _FlutterTemplateLogFilter extends LogFilter { @override bool shouldLog(LogEvent event) { try { - return kDebugMode && FlavorConfig.instance.values.showLogs; + return kDebugMode && FlavorConfig.values.showLogs; } catch (_) { return true; } diff --git a/lib/main.dart b/lib/main.dart index 7d057b4c..9b260eeb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,26 @@ -// DO NOT EDIT. -// This import will be edited when build of ios by scripts/build.sh -// This file is ignored in git for any further modifications. -// To add file to git again run 'git update-index --no-skip-worktree lib/main.dart' -import 'package:flutter_template/entrypoints/main_prod.dart' as entrypoint; - -/// Running `flutter build ios` has an error currently where -/// the `generated_main.dart` file does not point to the -/// correct target (-t) defined in the build command. -/// It always points to `main.dart` thus this file is used -/// until a fix is released. -void main() { - entrypoint.main(); +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +import 'app.dart'; +import 'flavors/flavor_config.dart'; + +void main() async { + const flavor = String.fromEnvironment("flavor", defaultValue: "dev"); + + await dotenv.load( + fileName: getEnvFileName(flavor), + ); + + FlavorConfig.initialize(flavorString: flavor); + startApp(); +} + +String getEnvFileName(String flavor) { + switch (flavor) { + case "prod": + return ".env"; + case "qa": + return ".env.qa"; + default: + return ".env.dev"; + } } diff --git a/lib/navigation/base/app_router.dart b/lib/navigation/base/app_router.dart index 62f274b4..15ae7216 100644 --- a/lib/navigation/base/app_router.dart +++ b/lib/navigation/base/app_router.dart @@ -1,18 +1,26 @@ // ignore_for_file: unnecessary_import import 'package:auto_route/auto_route.dart'; -import 'package:flutter_template/navigation/base/routes.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_template/presentation/entity/screen/screen.dart'; import 'package:flutter_template/presentation/destinations/weather/home/home_page.dart'; +import 'package:flutter_template/presentation/destinations/weather/home/home_screen.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_page.dart'; +import 'package:flutter_template/presentation/destinations/weather/search/search_screen.dart'; part 'app_router.gr.dart'; -@MaterialAutoRouter( - routes: autoRoutes, - preferRelativeImports: false, - replaceInRouteName: "Page,Route", -) -class AppRouter extends _$AppRouter {} +@AutoRouterConfig() +class AppRouter extends _$AppRouter { + @override + List get routes => [ + AutoRoute( + path: HomeScreen.path, + page: HomeRoute.page, + ), + AutoRoute( + path: SearchScreen.path, + page: SearchRoute.page, + ), + ]; +} diff --git a/lib/navigation/base/base_navigator_impl.dart b/lib/navigation/base/base_navigator_impl.dart index dfec3f2c..53301b09 100644 --- a/lib/navigation/base/base_navigator_impl.dart +++ b/lib/navigation/base/base_navigator_impl.dart @@ -9,7 +9,7 @@ class BaseNavigatorImpl implements BaseNavigator { @override void back() { - appRouter.navigateBack(); + appRouter.back(); } @override diff --git a/lib/navigation/base/routes.dart b/lib/navigation/base/routes.dart index cf12749b..9c05c96d 100644 --- a/lib/navigation/base/routes.dart +++ b/lib/navigation/base/routes.dart @@ -1,16 +1 @@ -import 'package:auto_route/annotations.dart'; -import 'package:flutter_template/presentation/destinations/weather/home/home_page.dart'; -import 'package:flutter_template/presentation/destinations/weather/search/search_page.dart'; -import 'package:flutter_template/presentation/entity/routes/routes.dart'; - -const autoRoutes = [ - AutoRoute( - path: Routes.home, - page: HomePage, - initial: true, - ), - AutoRoute( - path: Routes.search, - page: SearchPage, - ), -]; +final autoRoutes = []; diff --git a/lib/navigation/weather/home/home_navigator_impl.dart b/lib/navigation/weather/home/home_navigator_impl.dart index 897efc26..a8bed361 100644 --- a/lib/navigation/weather/home/home_navigator_impl.dart +++ b/lib/navigation/weather/home/home_navigator_impl.dart @@ -1,7 +1,7 @@ import 'package:flutter_template/navigation/base/app_router.dart'; import 'package:flutter_template/navigation/base/base_navigator.dart'; import 'package:flutter_template/navigation/weather/home/home_navigator.dart'; -import 'package:flutter_template/presentation/entity/screen/screen.dart'; +import 'package:flutter_template/presentation/destinations/weather/search/search_screen.dart'; class HomeNavigatorImpl implements HomeNavigator { final BaseNavigator navigator; diff --git a/lib/presentation/base/theme/theme_data/template_app_theme_data.dart b/lib/presentation/base/theme/theme_data/template_app_theme_data.dart index c901f74e..dadfe690 100644 --- a/lib/presentation/base/theme/theme_data/template_app_theme_data.dart +++ b/lib/presentation/base/theme/theme_data/template_app_theme_data.dart @@ -1,16 +1,22 @@ import 'package:flutter/material.dart'; +import 'package:flutter_template/flavors/flavor_config.dart'; import 'package:flutter_template/presentation/base/theme/color/color_palette.dart'; import 'package:flutter_template/presentation/base/theme/theme_data/card_theme_data.dart'; -import 'package:flutter_template/presentation/base/theme/type/type.dart'; +import 'package:flutter_template/presentation/base/theme/type/text_theme.dart'; +import 'package:google_fonts/google_fonts.dart'; final material3LightTheme = buildTheme(lightColorScheme); final material3DarkTheme = buildTheme(darkColorScheme); ThemeData buildTheme(ColorScheme colorScheme) { + final useGoogleFonts = FlavorConfig.values.useGoogleFonts; + final textTheme = getTextTheme(colorScheme); return ThemeData( useMaterial3: true, - fontFamily: primaryFontFamily, + textTheme: + useGoogleFonts ? GoogleFonts.openSansTextTheme(textTheme) : textTheme, + fontFamily: useGoogleFonts ? GoogleFonts.openSans().fontFamily : null, popupMenuTheme: PopupMenuThemeData(color: colorScheme.surface), cardTheme: AppCardTheme(cardColor: colorScheme.surface), snackBarTheme: SnackBarThemeData(backgroundColor: colorScheme.surface), diff --git a/lib/presentation/base/theme/type/text_theme.dart b/lib/presentation/base/theme/type/text_theme.dart new file mode 100644 index 00000000..4c6c53c0 --- /dev/null +++ b/lib/presentation/base/theme/type/text_theme.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; + +TextTheme getTextTheme(ColorScheme colorScheme) { + return TextTheme( + displayLarge: TextStyle( + fontSize: 96, + fontWeight: FontWeight.w300, + color: colorScheme.onSurface, + ), + displayMedium: TextStyle( + fontSize: 60, + fontWeight: FontWeight.w300, + color: colorScheme.onSurface, + ), + displaySmall: TextStyle( + fontSize: 48, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + headlineLarge: TextStyle( + fontSize: 42, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + headlineMedium: TextStyle( + fontSize: 34, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + headlineSmall: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + titleLarge: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: colorScheme.onSurface, + ), + titleMedium: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + titleSmall: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: colorScheme.onSurface, + ), + bodyLarge: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + bodyMedium: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + bodySmall: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + labelLarge: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: colorScheme.onSurface, + ), + labelMedium: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + labelSmall: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + ); +} diff --git a/lib/presentation/base/theme/type/type.dart b/lib/presentation/base/theme/type/type.dart deleted file mode 100644 index 94d79110..00000000 --- a/lib/presentation/base/theme/type/type.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:google_fonts/google_fonts.dart'; - -final primaryFontFamily = GoogleFonts.openSans().fontFamily; diff --git a/lib/presentation/base/widgets/theme/dynamic_theme_switch/dynamic_theme_switch_content.dart b/lib/presentation/base/widgets/theme/dynamic_theme_switch/dynamic_theme_switch_content.dart index 5b2524ee..e9aa6cb1 100644 --- a/lib/presentation/base/widgets/theme/dynamic_theme_switch/dynamic_theme_switch_content.dart +++ b/lib/presentation/base/widgets/theme/dynamic_theme_switch/dynamic_theme_switch_content.dart @@ -17,6 +17,7 @@ class DynamicThemeSwitchContent extends StatelessWidget { return Row( children: [ Text(context.tr(LocaleKeys.dynamicTheme)), + const Spacer(), Switch( value: isDynamic, onChanged: onIsDynamicToggled, diff --git a/lib/presentation/destinations/weather/home/home_page.dart b/lib/presentation/destinations/weather/home/home_page.dart index 5753859f..652ca10a 100644 --- a/lib/presentation/destinations/weather/home/home_page.dart +++ b/lib/presentation/destinations/weather/home/home_page.dart @@ -1,15 +1,17 @@ +import 'package:auto_route/annotations.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_template/presentation/base/page/base_page.dart'; import 'package:flutter_template/presentation/base/widgets/theme/theme_picker/theme_picker.dart'; +import 'package:flutter_template/presentation/destinations/weather/home/home_screen.dart'; import 'package:flutter_template/presentation/destinations/weather/home/home_screen_intent.dart'; import 'package:flutter_template/presentation/destinations/weather/home/home_screen_state.dart'; import 'package:flutter_template/presentation/destinations/weather/home/home_view_model.dart'; -import 'package:flutter_template/presentation/entity/screen/screen.dart'; import 'widgets/home_page_body/home_page_body.dart'; +@RoutePage() class HomePage extends ConsumerWidget { final HomeScreen homeScreen; diff --git a/lib/presentation/destinations/weather/home/home_screen.dart b/lib/presentation/destinations/weather/home/home_screen.dart new file mode 100644 index 00000000..2f1a8e0f --- /dev/null +++ b/lib/presentation/destinations/weather/home/home_screen.dart @@ -0,0 +1,8 @@ +import 'package:flutter_template/presentation/entity/routes/routes.dart'; +import 'package:flutter_template/presentation/entity/screen/screen.dart'; + +class HomeScreen extends Screen { + const HomeScreen() : super(); + + static get path => Routes.home; +} diff --git a/lib/presentation/destinations/weather/home/home_view_model.dart b/lib/presentation/destinations/weather/home/home_view_model.dart index 51de9c2e..1cc1aeea 100644 --- a/lib/presentation/destinations/weather/home/home_view_model.dart +++ b/lib/presentation/destinations/weather/home/home_view_model.dart @@ -1,9 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_template/presentation/base/intent/intent_handler.dart'; import 'package:flutter_template/presentation/base/view_model_provider/base_view_model.dart'; +import 'package:flutter_template/presentation/destinations/weather/home/home_screen.dart'; import 'package:flutter_template/presentation/destinations/weather/home/home_screen_intent.dart'; import 'package:flutter_template/presentation/destinations/weather/home/home_screen_state.dart'; -import 'package:flutter_template/presentation/entity/screen/screen.dart'; import 'package:get_it/get_it.dart'; final homeViewModelProvider = diff --git a/lib/presentation/destinations/weather/home/widgets/home_page_body/home_page_body_content.dart b/lib/presentation/destinations/weather/home/widgets/home_page_body/home_page_body_content.dart index 7f4f6940..f11558b7 100644 --- a/lib/presentation/destinations/weather/home/widgets/home_page_body/home_page_body_content.dart +++ b/lib/presentation/destinations/weather/home/widgets/home_page_body/home_page_body_content.dart @@ -15,10 +15,10 @@ class HomePageBodyContent extends StatelessWidget { final IntentHandlerCallback intentHandler; const HomePageBodyContent({ - Key? key, + super.key, required this.weatherList, required this.intentHandler, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/presentation/destinations/weather/home/widgets/list/ui_weather_list_item.dart b/lib/presentation/destinations/weather/home/widgets/list/ui_weather_list_item.dart index f3bfd3b2..5fa2000b 100644 --- a/lib/presentation/destinations/weather/home/widgets/list/ui_weather_list_item.dart +++ b/lib/presentation/destinations/weather/home/widgets/list/ui_weather_list_item.dart @@ -1,5 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_template/foundation/extensions/theme_ext.dart'; import 'package:flutter_template/presentation/entity/weather/ui_weather.dart'; class UIWeatherListItem extends StatelessWidget { @@ -31,11 +32,11 @@ class UIWeatherListItem extends StatelessWidget { children: [ Text( weather.title, - style: const TextStyle(fontSize: 20), + style: context.theme.textTheme.titleLarge, ), Text( weather.currentTemp, - style: const TextStyle(fontSize: 40), + style: context.theme.textTheme.headlineLarge, ), ], ), @@ -53,19 +54,22 @@ class UIWeatherListItem extends StatelessWidget { ), Text( weather.feelsLike, - style: const TextStyle(fontSize: 20), + style: context.theme.textTheme.titleLarge, ), const SizedBox( height: 10, ), Text( weather.minMaxTemp, - style: const TextStyle(fontSize: 18), + style: context.theme.textTheme.bodySmall, ), const SizedBox( height: 10, ), - Text(weather.description), + Text( + weather.description, + style: context.theme.textTheme.bodySmall, + ) ], ), ), diff --git a/lib/presentation/destinations/weather/search/search_page.dart b/lib/presentation/destinations/weather/search/search_page.dart index c1b4023f..3b369683 100644 --- a/lib/presentation/destinations/weather/search/search_page.dart +++ b/lib/presentation/destinations/weather/search/search_page.dart @@ -1,14 +1,16 @@ +import 'package:auto_route/annotations.dart'; import 'package:flutter/material.dart'; import 'package:flutter_template/presentation/base/exceptions/unhandled_effect_exception.dart'; import 'package:flutter_template/presentation/base/page/base_page.dart'; import 'package:flutter_template/presentation/base/widgets/snackbar/snackbar.dart'; +import 'package:flutter_template/presentation/destinations/weather/search/search_screen.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_screen_intent.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_screen_state.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_view_model.dart'; import 'package:flutter_template/presentation/destinations/weather/search/widgets/search_page_body/search_page_body.dart'; import 'package:flutter_template/presentation/entity/effect/effect.dart'; -import 'package:flutter_template/presentation/entity/screen/screen.dart'; +@RoutePage() class SearchPage extends StatelessWidget { final SearchScreen searchScreen; diff --git a/lib/presentation/destinations/weather/search/search_screen.dart b/lib/presentation/destinations/weather/search/search_screen.dart new file mode 100644 index 00000000..1803d09b --- /dev/null +++ b/lib/presentation/destinations/weather/search/search_screen.dart @@ -0,0 +1,8 @@ +import 'package:flutter_template/presentation/entity/routes/routes.dart'; +import 'package:flutter_template/presentation/entity/screen/screen.dart'; + +class SearchScreen extends Screen { + const SearchScreen() : super(); + + static get path => Routes.search; +} diff --git a/lib/presentation/destinations/weather/search/search_view_model.dart b/lib/presentation/destinations/weather/search/search_view_model.dart index 42e3ac5c..bd60b712 100644 --- a/lib/presentation/destinations/weather/search/search_view_model.dart +++ b/lib/presentation/destinations/weather/search/search_view_model.dart @@ -1,9 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_template/presentation/base/intent/intent_handler.dart'; import 'package:flutter_template/presentation/base/view_model_provider/base_view_model.dart'; +import 'package:flutter_template/presentation/destinations/weather/search/search_screen.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_screen_intent.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_screen_state.dart'; -import 'package:flutter_template/presentation/entity/screen/screen.dart'; import 'package:get_it/get_it.dart'; final searchViewModelProvider = diff --git a/lib/presentation/entity/routes/routes.dart b/lib/presentation/entity/routes/routes.dart index 01d8d448..eddb911a 100644 --- a/lib/presentation/entity/routes/routes.dart +++ b/lib/presentation/entity/routes/routes.dart @@ -1,5 +1,5 @@ class Routes { - static const home = "/home"; + static const home = "/"; static const search = "/search"; static const unknown = "/unknown"; diff --git a/lib/presentation/entity/screen/screen.dart b/lib/presentation/entity/screen/screen.dart index 8eb6a73b..f1846d5b 100644 --- a/lib/presentation/entity/screen/screen.dart +++ b/lib/presentation/entity/screen/screen.dart @@ -1,21 +1,3 @@ -import 'package:flutter_template/presentation/entity/routes/routes.dart'; - abstract class Screen { - const Screen._(); - - String get path; -} - -class SearchScreen extends Screen { - const SearchScreen() : super._(); - - @override - String get path => Routes.search; -} - -class HomeScreen extends Screen { - const HomeScreen() : super._(); - - @override - String get path => Routes.home; + const Screen(); } diff --git a/lib/presentation/intl/translations/translation_keys.dart b/lib/presentation/intl/translations/translation_keys.dart index 62636592..887c9cbc 100644 --- a/lib/presentation/intl/translations/translation_keys.dart +++ b/lib/presentation/intl/translations/translation_keys.dart @@ -1,6 +1,6 @@ // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart -abstract class LocaleKeys { +abstract class LocaleKeys { static const homePageTitle = 'homePageTitle'; static const searchPageTitle = 'searchPageTitle'; static const forecast = 'forecast'; @@ -9,5 +9,4 @@ abstract class LocaleKeys { static const searchResultsAppearHere = 'searchResultsAppearHere'; static const noResultsFound = 'noResultsFound'; static const dynamicTheme = 'dynamicTheme'; - } diff --git a/lib/presentation/intl/translations/translation_loader.dart b/lib/presentation/intl/translations/translation_loader.dart index fe73b230..586485f0 100644 --- a/lib/presentation/intl/translations/translation_loader.dart +++ b/lib/presentation/intl/translations/translation_loader.dart @@ -6,33 +6,38 @@ import 'dart:ui'; import 'package:easy_localization/easy_localization.dart' show AssetLoader; -class CodegenLoader extends AssetLoader{ +class CodegenLoader extends AssetLoader { const CodegenLoader(); @override - Future> load(String fullPath, Locale locale ) { + Future> load(String fullPath, Locale locale) { return Future.value(mapLocales[locale.toString()]); } - static const Map en_US = { - "homePageTitle": "Home", - "searchPageTitle": "Search", - "forecast": "Forecast", - "favCitiesAppearHere": "Weather for your favorite cities will appear here. Search cities to add to favorites.", - "startTypingToSearch": "Start typing to search", - "searchResultsAppearHere": "Search results appear here...", - "noResultsFound": "No results found...", - "dynamicTheme": "Dynamic Theme" -}; -static const Map hi_IN = { - "homePageTitle": "मुख्य पृष्ठ", - "searchPageTitle": "खोज", - "forecast": "पूर्वानुमान", - "favCitiesAppearHere": "आपके पसंदीदा शहरों का मौसम यहां दिखाई देगा। पसंदीदा में जोड़ने के लिए शहर खोजें।", - "startTypingToSearch": "खोजने के लिए टाइप करना प्रारंभ करें", - "searchResultsAppearHere": "खोज परिणाम यहां दिखाई देते हैं...", - "noResultsFound": "कोई परिणाम नहीं मिला...", - "dynamicTheme": "डायनामिक थीम" -}; -static const Map> mapLocales = {"en_US": en_US, "hi_IN": hi_IN}; + static const Map en_US = { + "homePageTitle": "Home", + "searchPageTitle": "Search", + "forecast": "Forecast", + "favCitiesAppearHere": + "Weather for your favorite cities will appear here. Search cities to add to favorites.", + "startTypingToSearch": "Start typing to search", + "searchResultsAppearHere": "Search results appear here...", + "noResultsFound": "No results found...", + "dynamicTheme": "Dynamic Theme" + }; + static const Map hi_IN = { + "homePageTitle": "मुख्य पृष्ठ", + "searchPageTitle": "खोज", + "forecast": "पूर्वानुमान", + "favCitiesAppearHere": + "आपके पसंदीदा शहरों का मौसम यहां दिखाई देगा। पसंदीदा में जोड़ने के लिए शहर खोजें।", + "startTypingToSearch": "खोजने के लिए टाइप करना प्रारंभ करें", + "searchResultsAppearHere": "खोज परिणाम यहां दिखाई देते हैं...", + "noResultsFound": "कोई परिणाम नहीं मिला...", + "dynamicTheme": "डायनामिक थीम" + }; + static const Map> mapLocales = { + "en_US": en_US, + "hi_IN": hi_IN + }; } diff --git a/lib/presentation/template_app.dart b/lib/presentation/template_app.dart index c940be6b..49ad4929 100644 --- a/lib/presentation/template_app.dart +++ b/lib/presentation/template_app.dart @@ -9,7 +9,7 @@ import 'package:flutter_template/navigation/base/app_router.dart'; import 'package:flutter_template/presentation/base/theme/theme_data/template_app_theme_data.dart'; import 'package:flutter_template/presentation/base/widgets/snackbar/snackbar.dart'; import 'package:flutter_template/presentation/base/widgets/theme/theme_listener.dart'; -import 'package:flutter_template/presentation/entity/screen/screen.dart'; +import 'package:flutter_template/presentation/destinations/weather/home/home_screen.dart'; import 'package:get_it/get_it.dart'; class TemplateApp extends StatelessWidget { @@ -21,9 +21,8 @@ class TemplateApp extends StatelessWidget { Widget build(BuildContext context) { const enableDevicePreview = String.fromEnvironment("enableDevicePreview") == "true"; - final useDevicePreview = enableDevicePreview && - kDebugMode && - FlavorConfig.instance.flavor == Flavor.dev; + final useDevicePreview = + enableDevicePreview && kDebugMode && FlavorConfig.flavor == Flavor.dev; return ProviderScope( child: ThemeStateListener( diff --git a/lib/repository/date/date_repository_impl.dart b/lib/repository/date/date_repository_impl.dart index ee5cebd9..970d3ddc 100644 --- a/lib/repository/date/date_repository_impl.dart +++ b/lib/repository/date/date_repository_impl.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_template/domain/entity/base/datetime/date.dart'; import 'package:flutter_template/domain/entity/base/datetime/date_time.dart'; import 'package:flutter_template/domain/entity/base/datetime/time.dart'; @@ -7,7 +8,6 @@ import 'package:flutter_template/repository/date/date_mapper.dart'; import 'package:flutter_template/repository/date/date_repository.dart'; import 'package:flutter_template/repository/date/date_time_mapper.dart'; import 'package:flutter_template/repository/date/time_mapper.dart'; -import 'package:intl/intl.dart'; class DateRepositoryImpl implements DateRepository { final DateMapper dateMapper; diff --git a/lib/secrets/app_secrets.skeleton.dart b/lib/secrets/app_secrets.skeleton.dart deleted file mode 100644 index 89ce555c..00000000 --- a/lib/secrets/app_secrets.skeleton.dart +++ /dev/null @@ -1,19 +0,0 @@ -// class AppSecrets { -// final String openWeatherApiKey; -// -// const AppSecrets({ -// required this.openWeatherApiKey, -// }); -// -// static const appSecretsDev = AppSecrets( -// openWeatherApiKey: "Your API Key", -// ); -// -// static const appSecretsQA = AppSecrets( -// openWeatherApiKey: "Your API Key", -// ); -// -// static const appSecretsProd = AppSecrets( -// openWeatherApiKey: "Your API Key", -// ); -// } diff --git a/lib/services/base/database/app_database.dart b/lib/services/base/database/app_database.dart index 25896866..4834a151 100644 --- a/lib/services/base/database/app_database.dart +++ b/lib/services/base/database/app_database.dart @@ -8,9 +8,8 @@ export 'connection/connection.dart'; part 'app_database.g.dart'; @DriftDatabase( - tables: [LocalLocation, LocalCurrentWeather], - daos: [WeatherLocalServiceImpl] -) + tables: [LocalLocation, LocalCurrentWeather], + daos: [WeatherLocalServiceImpl]) class AppDatabase extends _$AppDatabase { AppDatabase(QueryExecutor e) : super(e); @@ -20,4 +19,3 @@ class AppDatabase extends _$AppDatabase { @override MigrationStrategy get migration => migrations(); } - diff --git a/lib/services/base/database/connection/connection.dart b/lib/services/base/database/connection/connection.dart index fc67ef6e..8f78dac7 100644 --- a/lib/services/base/database/connection/connection.dart +++ b/lib/services/base/database/connection/connection.dart @@ -1,3 +1,3 @@ export 'unsupported.dart' - if (dart.library.js) 'web.dart' - if (dart.library.ffi) 'mobile.dart'; \ No newline at end of file + if (dart.library.js) 'web.dart' + if (dart.library.ffi) 'mobile.dart'; diff --git a/lib/services/base/database/connection/mobile.dart b/lib/services/base/database/connection/mobile.dart index b616b40e..b7a2d27f 100644 --- a/lib/services/base/database/connection/mobile.dart +++ b/lib/services/base/database/connection/mobile.dart @@ -15,14 +15,15 @@ AppDatabase connect() { if (!await file.exists()) { final blob = await rootBundle.load('assets/pre_populated_cities.sqlite'); final buffer = blob.buffer; - await file.writeAsBytes(buffer.asUint8List(blob.offsetInBytes, blob.lengthInBytes)); + await file.writeAsBytes( + buffer.asUint8List(blob.offsetInBytes, blob.lengthInBytes)); } - + return NativeDatabase( file, - logStatements: FlavorConfig.instance.values.logSqlStatements, + logStatements: FlavorConfig.values.logSqlStatements, ); }); return AppDatabase(db); -} \ No newline at end of file +} diff --git a/lib/services/base/database/connection/unsupported.dart b/lib/services/base/database/connection/unsupported.dart index 8a0a785a..e1016c90 100644 --- a/lib/services/base/database/connection/unsupported.dart +++ b/lib/services/base/database/connection/unsupported.dart @@ -3,4 +3,4 @@ import 'package:flutter_template/services/base/database/app_database.dart'; AppDatabase connect() { throw UnsupportedError( 'No suitable database implementation was found on this platform.'); -} \ No newline at end of file +} diff --git a/lib/services/base/database/connection/web.dart b/lib/services/base/database/connection/web.dart index add66a59..04b309f1 100644 --- a/lib/services/base/database/connection/web.dart +++ b/lib/services/base/database/connection/web.dart @@ -3,9 +3,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_template/services/base/database/app_database.dart'; AppDatabase connect() { - return AppDatabase(WebDatabase('db', - initializer: () async { - return (await rootBundle.load('assets/pre_populated_cities.sqlite')).buffer.asUint8List(); - })); + return AppDatabase(WebDatabase('db', initializer: () async { + return (await rootBundle.load('assets/pre_populated_cities.sqlite')) + .buffer + .asUint8List(); + })); //typedef CreateWebDatabase = FutureOr Function(); -} \ No newline at end of file +} diff --git a/lib/services/base/database/helpers/foreign_key_helper.dart b/lib/services/base/database/helpers/foreign_key_helper.dart index cace24ae..f222096a 100644 --- a/lib/services/base/database/helpers/foreign_key_helper.dart +++ b/lib/services/base/database/helpers/foreign_key_helper.dart @@ -1,6 +1,6 @@ import 'package:drift/drift.dart'; -extension ForeignKeyHelper on ColumnBuilder { +extension ForeignKeyHelper on ColumnBuilder { ColumnBuilder foreignKey({ required String column, required bool cascadeOnDelete, diff --git a/lib/services/base/di/dio_provider.dart b/lib/services/base/di/dio_provider.dart index 68fc0308..23a1afb7 100644 --- a/lib/services/base/di/dio_provider.dart +++ b/lib/services/base/di/dio_provider.dart @@ -3,7 +3,7 @@ import 'package:flutter_template/flavors/flavor_config.dart'; Dio provideDio({List interceptors = const []}) { final baseOption = BaseOptions( - baseUrl: FlavorConfig.instance.values.apiBaseUrl, + baseUrl: FlavorConfig.values.apiBaseUrl, ); final dio = Dio(baseOption); @@ -12,7 +12,7 @@ Dio provideDio({List interceptors = const []}) { responseBody: true, ); - if (FlavorConfig.instance.values.showLogs) { + if (FlavorConfig.values.showLogs) { dio.interceptors.add(logInterceptor); } diff --git a/lib/services/base/dio/interceptors/open_weather_api_key_interceptor.dart b/lib/services/base/dio/interceptors/open_weather_api_key_interceptor.dart index ff336082..752ae462 100644 --- a/lib/services/base/dio/interceptors/open_weather_api_key_interceptor.dart +++ b/lib/services/base/dio/interceptors/open_weather_api_key_interceptor.dart @@ -8,7 +8,7 @@ class OpenWeatherApiKeyInterceptor extends Interceptor { void onRequest(RequestOptions options, RequestInterceptorHandler handler) { final newOptions = options.copyWith(queryParameters: { ...options.queryParameters, - _appId: FlavorConfig.instance.values.secrets.openWeatherApiKey, + _appId: FlavorConfig.values.openWeatherApiKey, }); handler.next(newOptions); diff --git a/lib/services/di/service_module.dart b/lib/services/di/service_module.dart index 549371c8..9a125904 100644 --- a/lib/services/di/service_module.dart +++ b/lib/services/di/service_module.dart @@ -36,7 +36,7 @@ extension ServiceModule on GetIt { // weather registerLazySingleton( () { - if (FlavorConfig.instance.values.useFakeData) { + if (FlavorConfig.values.useFakeData) { return FakeWeatherRemoteService(); } @@ -48,7 +48,7 @@ extension ServiceModule on GetIt { registerLazySingleton( () { - if (FlavorConfig.instance.values.useFakeData) { + if (FlavorConfig.values.useFakeData) { return FakeWeatherLocalService(); } diff --git a/mason-lock.json b/mason-lock.json new file mode 100644 index 00000000..d868aaa9 --- /dev/null +++ b/mason-lock.json @@ -0,0 +1 @@ +{"bricks":{"destination":{"path":"/Users/shounakmulay/Documents/Work/Flutter/flutter_template/bricks/destination"}}} \ No newline at end of file diff --git a/mason.yaml b/mason.yaml new file mode 100644 index 00000000..9a7f54b9 --- /dev/null +++ b/mason.yaml @@ -0,0 +1,3 @@ +bricks: + destination: + path: bricks/destination \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 9a0d29f6..c5dd62ae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,303 +5,346 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.dartlang.org" + sha256: "503361166f4a100e0d7eb7fb5a62c6f0322512f2bcb48d6922caf98f24b0ab90" + url: "https://pub.dev" source: hosted - version: "43.0.0" + version: "56.0.0" analyzer: dependency: transitive description: name: analyzer - url: "https://pub.dartlang.org" + sha256: "93fcd81a6716e69864516750590cf1e699420615046bda19100238aa7b429785" + url: "https://pub.dev" source: hosted - version: "4.3.1" + version: "5.8.0" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - url: "https://pub.dartlang.org" + sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d + url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.11.2" + archive: + dependency: transitive + description: + name: archive + sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d + url: "https://pub.dev" + source: hosted + version: "3.3.6" args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" auto_route: dependency: "direct main" description: name: auto_route - url: "https://pub.dartlang.org" + sha256: "7faca1e83d05794deec4e160976c06345da3eeaca7b0e78e39edd657c1b86335" + url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "6.0.0+1" auto_route_generator: dependency: "direct dev" description: name: auto_route_generator - url: "https://pub.dartlang.org" + sha256: a3f11c3b1e6e884d1592924f3b7212855f1c7c8791c12d3b41b87ab81fb9d3b8 + url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "6.0.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" build: dependency: transitive description: name: build - url: "https://pub.dartlang.org" + sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + url: "https://pub.dev" source: hosted version: "2.3.1" build_config: dependency: transitive description: name: build_config - url: "https://pub.dartlang.org" + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" source: hosted version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - url: "https://pub.dartlang.org" + sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" build_resolvers: dependency: transitive description: name: build_resolvers - url: "https://pub.dartlang.org" + sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 + url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.2.0" build_runner: dependency: "direct dev" description: name: build_runner - url: "https://pub.dartlang.org" + sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.3" build_runner_core: dependency: transitive description: name: build_runner_core - url: "https://pub.dartlang.org" + sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + url: "https://pub.dev" source: hosted version: "7.2.7" built_collection: dependency: transitive description: name: built_collection - url: "https://pub.dartlang.org" + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - url: "https://pub.dartlang.org" + sha256: "31b7c748fd4b9adf8d25d72a4c4a59ef119f12876cf414f94f8af5131d5fa2b0" + url: "https://pub.dev" source: hosted - version: "8.4.1" + version: "8.4.4" cached_network_image: dependency: "direct main" description: name: cached_network_image - url: "https://pub.dartlang.org" + sha256: fd3d0dc1d451f9a252b32d95d3f0c3c487bc41a75eba2e6097cb0b9c71491b15 + url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.3" cached_network_image_platform_interface: dependency: transitive description: name: cached_network_image_platform_interface - url: "https://pub.dartlang.org" + sha256: bb2b8403b4ccdc60ef5f25c70dead1f3d32d24b9d6117cfc087f496b178594a7 + url: "https://pub.dev" source: hosted version: "2.0.0" cached_network_image_web: dependency: transitive description: name: cached_network_image_web - url: "https://pub.dartlang.org" + sha256: b8eb814ebfcb4dea049680f8c1ffb2df399e4d03bf7a352c775e26fa06e02fa0 + url: "https://pub.dev" source: hosted version: "1.0.2" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted version: "1.2.1" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" source: hosted version: "1.3.1" checked_yaml: dependency: transitive description: name: checked_yaml - url: "https://pub.dartlang.org" + sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" cli_util: dependency: transitive description: name: cli_util - url: "https://pub.dartlang.org" + sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c" + url: "https://pub.dev" source: hosted version: "0.3.5" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" code_builder: dependency: transitive description: name: code_builder - url: "https://pub.dartlang.org" + sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" collection: dependency: "direct main" description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" source: hosted version: "3.1.1" coverage: dependency: transitive description: name: coverage - url: "https://pub.dartlang.org" + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + url: "https://pub.dev" source: hosted - version: "1.6.1" + version: "1.6.3" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" source: hosted version: "3.0.2" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.17.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" source: hosted version: "1.0.5" dart_style: dependency: transitive description: name: dart_style - url: "https://pub.dartlang.org" + sha256: "6d691edde054969f0e0f26abb1b30834b5138b963793e56f69d3a9a4435e6352" + url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.0" dio: dependency: "direct main" description: name: dio - url: "https://pub.dartlang.org" + sha256: "3e5c4a94d112540d0c9a6b7f3969832e1604eb8cde0f88d0808382f9f632100b" + url: "https://pub.dev" source: hosted - version: "4.0.6" + version: "5.0.3" drift: dependency: "direct main" description: name: drift - url: "https://pub.dartlang.org" + sha256: "2a16754641485ca4eaf31bb7961f3ec26bcddad1ce4869a37f238791e164c073" + url: "https://pub.dev" source: hosted - version: "1.7.1" + version: "2.5.0" drift_dev: dependency: "direct dev" description: name: drift_dev - url: "https://pub.dartlang.org" + sha256: d71542e30e27c920d5a73ccad409a0828a0c9588f5bbbc58aad6407259299a8b + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "2.5.2" dynamic_color: dependency: "direct main" description: name: dynamic_color - url: "https://pub.dartlang.org" + sha256: c4a508284b14ec4dda5adba2c28b2cdd34fbae1afead7e8c52cad87d51c5405b + url: "https://pub.dev" source: hosted - version: "1.5.4" + version: "1.6.2" easy_localization: dependency: "direct main" description: name: easy_localization - url: "https://pub.dartlang.org" + sha256: "6a2e99fa0bfe5765bf4c6ca9b137d5de2c75593007178c5e4cd2ae985f870080" + url: "https://pub.dev" source: hosted version: "3.0.1" easy_logger: dependency: "direct main" description: name: easy_logger - url: "https://pub.dartlang.org" + sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 + url: "https://pub.dev" source: hosted version: "0.0.2" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" faker: dependency: "direct main" description: name: faker - url: "https://pub.dartlang.org" + sha256: "746e59f91d8b06a389e74cf76e909a05ed69c12691768e2f93557fdf29200fd0" + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + url: "https://pub.dev" source: hosted version: "2.0.1" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" source: hosted version: "6.1.4" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -311,35 +354,48 @@ packages: dependency: transitive description: name: flutter_blurhash - url: "https://pub.dartlang.org" + sha256: "05001537bd3fac7644fa6558b09ec8c0a3f2eba78c0765f88912882b1331a5c6" + url: "https://pub.dev" source: hosted version: "0.7.0" flutter_cache_manager: dependency: transitive description: name: flutter_cache_manager - url: "https://pub.dartlang.org" + sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3" + url: "https://pub.dev" source: hosted version: "3.3.0" flutter_displaymode: dependency: "direct main" description: name: flutter_displaymode - url: "https://pub.dartlang.org" + sha256: "136b0314fdc78fe995b0b75061fe9ff8210dffca84f8f8110f8f71029479db3b" + url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.5.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: d9283d92059a22e9834bc0a31336658ffba77089fb6f3cc36751f1fc7c6661a3 + url: "https://pub.dev" + source: hosted + version: "5.0.2" flutter_hooks: dependency: "direct main" description: name: flutter_hooks - url: "https://pub.dartlang.org" + sha256: "6a126f703b89499818d73305e4ce1e3de33b4ae1c5512e3b8eab4b986f46774c" + url: "https://pub.dev" source: hosted - version: "0.18.5+1" + version: "0.18.6" flutter_lints: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" source: hosted version: "2.0.1" flutter_localizations: @@ -351,23 +407,26 @@ packages: dependency: "direct main" description: name: flutter_riverpod - url: "https://pub.dartlang.org" + sha256: b3c3a8a9714b7f88dd2a41e1efbc47f76d620b06ab427c62ae7bc82298cd7dbb + url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.3.2" flutter_staggered_grid_view: dependency: "direct main" description: name: flutter_staggered_grid_view - url: "https://pub.dartlang.org" + sha256: "1312314293acceb65b92754298754801b0e1f26a1845833b740b30415bbbcf07" + url: "https://pub.dev" source: hosted version: "0.6.2" flutter_state_notifier: dependency: "direct main" description: name: flutter_state_notifier - url: "https://pub.dartlang.org" + sha256: "4a1d282df8ab549d0c6aed0a2cdb486111c830778028f14048980c886bbb74fa" + url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.3" flutter_test: dependency: "direct dev" description: flutter @@ -382,441 +441,512 @@ packages: dependency: "direct dev" description: name: freezed - url: "https://pub.dartlang.org" + sha256: e819441678f1679b719008ff2ff0ef045d66eed9f9ec81166ca0d9b02a187454 + url: "https://pub.dev" source: hosted - version: "2.1.0+1" + version: "2.3.2" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - url: "https://pub.dartlang.org" + sha256: aeac15850ef1b38ee368d4c53ba9a847e900bb2c53a4db3f6881cbb3cb684338 + url: "https://pub.dev" source: hosted version: "2.2.0" frontend_server_client: dependency: transitive description: name: frontend_server_client - url: "https://pub.dartlang.org" + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "3.2.0" get_it: dependency: "direct main" description: name: get_it - url: "https://pub.dartlang.org" + sha256: "290fde3a86072e4b37dbb03c07bec6126f0ecc28dad403c12ffe2e5a2d751ab7" + url: "https://pub.dev" source: hosted version: "7.2.0" glob: dependency: transitive description: name: glob - url: "https://pub.dartlang.org" + sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" golden_toolkit: dependency: "direct dev" description: name: golden_toolkit - url: "https://pub.dartlang.org" + sha256: "8f74adab33154fe7b731395782797021f97d2edc52f7bfb85ff4f1b5c4a215f0" + url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.15.0" google_fonts: dependency: "direct main" description: name: google_fonts - url: "https://pub.dartlang.org" + sha256: "927573f2e8a8d65c17931e21918ad0ab0666b1b636537de7c4932bdb487b190f" + url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "4.0.3" graphs: dependency: transitive description: name: graphs - url: "https://pub.dartlang.org" + sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + url: "https://pub.dev" source: hosted version: "2.2.0" hooks_riverpod: dependency: "direct main" description: name: hooks_riverpod - url: "https://pub.dartlang.org" + sha256: "7e673817a7db4d6403a079882485affd3156d79c06209389353d0977d513f905" + url: "https://pub.dev" source: hosted - version: "1.0.4" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.1" + version: "2.3.2" http: dependency: transitive description: name: http - url: "https://pub.dartlang.org" + sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + url: "https://pub.dev" source: hosted version: "0.13.5" http_multi_server: dependency: transitive description: name: http_multi_server - url: "https://pub.dartlang.org" + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" source: hosted version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted version: "4.0.2" intl: - dependency: "direct main" + dependency: transitive description: name: intl - url: "https://pub.dartlang.org" + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" source: hosted version: "0.17.0" io: dependency: transitive description: name: io - url: "https://pub.dartlang.org" + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" json_annotation: dependency: "direct main" description: name: json_annotation - url: "https://pub.dartlang.org" + sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + url: "https://pub.dev" source: hosted - version: "4.6.0" + version: "4.8.0" json_serializable: dependency: "direct main" description: name: json_serializable - url: "https://pub.dartlang.org" + sha256: dadc08bd61f72559f938dd08ec20dbfec6c709bba83515085ea943d2078d187a + url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.6.1" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" source: hosted version: "2.0.1" logger: dependency: "direct main" description: name: logger - url: "https://pub.dartlang.org" + sha256: db2ff852ed77090ba9f62d3611e4208a3d11dfa35991a81ae724c113fcb3e3f7 + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.3.0" logging: dependency: transitive description: name: logging - url: "https://pub.dartlang.org" + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" + mason: + dependency: "direct dev" + description: + name: mason + sha256: b4030d8799f255caa914beb2604dcd749d0ebd815c3f0209ed223f9370098c10 + url: "https://pub.dev" + source: hosted + version: "0.1.0-dev.47" + mason_logger: + dependency: transitive + description: + name: mason_logger + sha256: ca34d14e998cd7a7738e7320b102aa45fb363aa49a290084b211ababf75bb7ee + url: "https://pub.dev" + source: hosted + version: "0.2.5" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.13" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.2.0" meta: dependency: "direct dev" description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted version: "1.8.0" mime: dependency: transitive description: name: mime - url: "https://pub.dartlang.org" + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.4" mocktail: dependency: "direct dev" description: name: mocktail - url: "https://pub.dartlang.org" + sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53" + url: "https://pub.dev" source: hosted version: "0.3.0" + mustache_template: + dependency: transitive + description: + name: mustache_template + sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c + url: "https://pub.dev" + source: hosted + version: "2.0.0" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" source: hosted version: "1.0.0" node_preamble: dependency: transitive description: name: node_preamble - url: "https://pub.dartlang.org" + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" octo_image: dependency: transitive description: name: octo_image - url: "https://pub.dartlang.org" + sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143" + url: "https://pub.dev" source: hosted version: "1.0.2" package_config: dependency: transitive description: name: package_config - url: "https://pub.dartlang.org" + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" source: hosted version: "2.1.0" path: dependency: "direct main" description: name: path - url: "https://pub.dartlang.org" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" source: hosted version: "1.8.2" path_provider: dependency: "direct main" description: name: path_provider - url: "https://pub.dartlang.org" + sha256: "04890b994ee89bfa80bf3080bfec40d5a92c5c7a785ebb02c13084a099d2b6f9" + url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.0.13" path_provider_android: dependency: transitive description: name: path_provider_android - url: "https://pub.dartlang.org" + sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7" + url: "https://pub.dev" source: hosted - version: "2.0.20" - path_provider_ios: + version: "2.0.24" + path_provider_foundation: dependency: transitive description: - name: path_provider_ios - url: "https://pub.dartlang.org" + name: path_provider_foundation + sha256: "12eee51abdf4d34c590f043f45073adbb45514a108bd9db4491547a2fd891059" + url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.2.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" + url: "https://pub.dev" source: hosted - version: "2.1.7" - path_provider_macos: - dependency: transitive - description: - name: path_provider_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" + version: "2.1.10" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.6" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130 + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.5" pedantic: dependency: transitive description: name: pedantic - url: "https://pub.dartlang.org" + sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" + url: "https://pub.dev" source: hosted version: "1.11.1" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + url: "https://pub.dev" source: hosted version: "5.1.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "57b6b78df14175658f09c5dfcfc51a46ad9561a3504fe679913dab404d0cc0f2" + url: "https://pub.dev" + source: hosted + version: "3.7.0" pool: dependency: transitive description: name: pool - url: "https://pub.dartlang.org" + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" source: hosted version: "1.5.1" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" source: hosted version: "4.2.4" provider: dependency: transitive description: name: provider - url: "https://pub.dartlang.org" + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" source: hosted - version: "6.0.4" + version: "6.0.5" pub_semver: dependency: transitive description: name: pub_semver - url: "https://pub.dartlang.org" + sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" pubspec_parse: dependency: transitive description: name: pubspec_parse - url: "https://pub.dartlang.org" + sha256: ec85d7d55339d85f44ec2b682a82fea340071e8978257e5a43e69f79e98ef50c + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" recase: dependency: transitive description: name: recase - url: "https://pub.dartlang.org" + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" source: hosted version: "4.1.0" riverpod: dependency: transitive description: name: riverpod - url: "https://pub.dartlang.org" + sha256: b0fbf7927333c5c318f7e2c22c8b4fd2542ba294de0373e80ecdb34e0dcd8dc4 + url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.3.2" rxdart: dependency: "direct main" description: name: rxdart - url: "https://pub.dartlang.org" + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" source: hosted - version: "0.27.5" + version: "0.27.7" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.dartlang.org" + sha256: ee6257848f822b8481691f20c3e6d2bfee2e9eccb2a3d249907fcfb198c55b41 + url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.0.18" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - url: "https://pub.dartlang.org" + sha256: ad423a80fe7b4e48b50d6111b3ea1027af0e959e49d485712e134863d9c1c521 + url: "https://pub.dev" source: hosted - version: "2.0.14" - shared_preferences_ios: + version: "2.0.17" + shared_preferences_foundation: dependency: transitive description: - name: shared_preferences_ios - url: "https://pub.dartlang.org" + name: shared_preferences_foundation + sha256: "1e755f8583229f185cfca61b1d80fb2344c9d660e1c69ede5450d8f478fa5310" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.5" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.dartlang.org" + sha256: "3a59ed10890a8409ad0faad7bb2957dab4b92b8fbe553257b05d30ed8af2c707" + url: "https://pub.dev" source: hosted - version: "2.1.1" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" + version: "2.1.5" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + sha256: "824bfd02713e37603b2bdade0842e47d56e7db32b1dcdd1cae533fb88e2913fc" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + sha256: "0dc2633f215a3d4aa3184c9b2c5766f4711e4e5a6b256e62aafee41f89f1bfb8" + url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.6" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + sha256: "71bcd669bb9cdb6b39f22c4a7728b6d49e934f6cba73157ffa5a54f1eed67436" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.5" shelf: dependency: transitive description: name: shelf - url: "https://pub.dartlang.org" + sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + url: "https://pub.dev" source: hosted version: "1.4.0" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler - url: "https://pub.dartlang.org" + sha256: aef74dc9195746a384843102142ab65b6a4735bb3beea791e63527b88cc83306 + url: "https://pub.dev" source: hosted version: "3.0.1" shelf_static: dependency: transitive description: name: shelf_static - url: "https://pub.dartlang.org" + sha256: e792b76b96a36d4a41b819da593aff4bdd413576b3ba6150df5d8d9996d2e74c + url: "https://pub.dev" source: hosted version: "1.1.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - url: "https://pub.dartlang.org" + sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.3" shimmer: dependency: "direct main" description: name: shimmer - url: "https://pub.dartlang.org" + sha256: "1f1009b5845a1f88f1c5630212279540486f97409e9fc3f63883e71070d107bf" + url: "https://pub.dev" source: hosted version: "2.0.0" sky_engine: @@ -828,247 +958,266 @@ packages: dependency: transitive description: name: source_gen - url: "https://pub.dartlang.org" + sha256: c2bea18c95cfa0276a366270afaa2850b09b4a76db95d546f3d003dcc7011298 + url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.7" source_helper: dependency: transitive description: name: source_helper - url: "https://pub.dartlang.org" + sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" source_map_stack_trace: dependency: transitive description: name: source_map_stack_trace - url: "https://pub.dartlang.org" + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" source: hosted version: "2.1.1" source_maps: dependency: transitive description: name: source_maps - url: "https://pub.dartlang.org" + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" source: hosted - version: "0.10.11" + version: "0.10.12" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" sqflite: dependency: transitive description: name: sqflite - url: "https://pub.dartlang.org" + sha256: "500d6fec583d2c021f2d25a056d96654f910662c64f836cd2063167b8f1fa758" + url: "https://pub.dev" source: hosted - version: "2.1.0+1" + version: "2.2.6" sqflite_common: dependency: transitive description: name: sqflite_common - url: "https://pub.dartlang.org" + sha256: "963dad8c4aa2f814ce7d2d5b1da2f36f31bd1a439d8f27e3dc189bb9d26bc684" + url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.3" sqlite3: dependency: transitive description: name: sqlite3 - url: "https://pub.dartlang.org" + sha256: cab735283879114cef78ee69071d652a7303d8376727c688ad56a2013997a8d0 + url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" sqlite3_flutter_libs: dependency: "direct main" description: name: sqlite3_flutter_libs - url: "https://pub.dartlang.org" + sha256: "02f80aea54a19a36b347dedf6d4181ecd9107f5831ea6139cfd0376a3de197ba" + url: "https://pub.dev" source: hosted - version: "0.5.10" + version: "0.5.13" sqlparser: dependency: transitive description: name: sqlparser - url: "https://pub.dartlang.org" + sha256: "91f47610aa54d8abf9d795a7b4e49b2a788f65d7493d5a68fbf180c3cbcc6f38" + url: "https://pub.dev" source: hosted - version: "0.22.0" + version: "0.27.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" state_notifier: dependency: transitive description: name: state_notifier - url: "https://pub.dartlang.org" + sha256: "8fe42610f179b843b12371e40db58c9444f8757f8b69d181c97e50787caed289" + url: "https://pub.dev" source: hosted version: "0.7.2+1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" stream_transform: dependency: transitive description: name: stream_transform - url: "https://pub.dartlang.org" + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" synchronized: dependency: transitive description: name: synchronized - url: "https://pub.dartlang.org" + sha256: "33b31b6beb98100bf9add464a36a8dd03eb10c7a8cf15aeec535e9b054aaf04b" + url: "https://pub.dev" source: hosted - version: "3.0.0+3" + version: "3.0.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test: dependency: transitive description: name: test - url: "https://pub.dartlang.org" + sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d + url: "https://pub.dev" source: hosted - version: "1.21.4" + version: "1.22.0" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.4.16" test_core: dependency: transitive description: name: test_core - url: "https://pub.dartlang.org" + sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" + url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.4.20" timing: dependency: transitive description: name: timing - url: "https://pub.dartlang.org" + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" tuple: dependency: "direct main" description: name: tuple - url: "https://pub.dartlang.org" + sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" + url: "https://pub.dev" source: hosted version: "2.0.1" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" source: hosted version: "1.3.1" - universal_html: - dependency: transitive - description: - name: universal_html - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.8" - universal_io: - dependency: transitive - description: - name: universal_io - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" uuid: dependency: "direct main" description: name: uuid - url: "https://pub.dartlang.org" + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" vm_service: dependency: transitive description: name: vm_service - url: "https://pub.dartlang.org" + sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + url: "https://pub.dev" source: hosted version: "9.4.0" watcher: dependency: transitive description: name: watcher - url: "https://pub.dartlang.org" + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" source: hosted version: "1.0.2" web_socket_channel: dependency: transitive description: name: web_socket_channel - url: "https://pub.dartlang.org" + sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol - url: "https://pub.dartlang.org" + sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + url: "https://pub.dev" source: hosted version: "1.2.0" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.3" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + url: "https://pub.dev" source: hosted - version: "0.2.0+2" + version: "1.0.0" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.2" yaml: dependency: transitive description: name: yaml - url: "https://pub.dartlang.org" + sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + url: "https://pub.dev" source: hosted version: "3.1.1" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=3.3.0" + dart: ">=2.19.2 <3.0.0" + flutter: ">=3.4.0-17.0.pre" diff --git a/pubspec.yaml b/pubspec.yaml index e8f60c04..9d85e56a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A new Flutter project. # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -18,85 +18,102 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+24 environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.19.2 <3.0.0" + +scripts: + generate: + all: ./scripts/generate-all.sh + translations: ./scripts/generate-translations.sh + build_runner: flutter pub run build_runner build --delete-conflicting-outputs + launch: + dev: flutter run --flavor dev --dart-define=flavor=dev + dev-preview: flutter run --flavor dev --dart-define=flavor=dev --dart-define=enableDevicePreview="true" + qa: flutter run --flavor qa --dart-define=flavor=qa + prod: flutter run --flavor prod --dart-define=flavor=prod + test: + all: flutter test + exclude-goldens: flutter test --exclude-tags=golden + goldens: flutter test --tags=golden test/presentation/goldens + update-goldens: flutter test --tags=golden test/presentation/goldens --update-goldens + analyze: flutter analyze --fatal-infos --fatal-warnings + format: dart format --set-exit-if-changed lib + fix: dart fix --apply + build: + apk: + dev: flutter build apk --flavor=dev --dart-define=flavor=dev --debug + qa: flutter build apk --flavor=qa --dart-define=flavor=qa --debug + prod: flutter build apk --flavor=prod --dart-define=flavor=prod --debug + release-apk: + qa: flutter build apk --flavor=qa --dart-define=flavor=qa --release --obfuscate --split-debug-info=debug-info + prod: flutter build apk --flavor=prod --dart-define=flavor=prod --release --obfuscate --split-debug-info=debug-info + appbundle: + dev: flutter build appbundle --flavor=dev --dart-define=flavor=dev + qa: flutter build appbundle --flavor=qa --dart-define=flavor=qa + prod: flutter build appbundle --flavor=prod --dart-define=flavor=prod + ios: + dev: flutter build ios --flavor=dev --dart-define=flavor=dev --debug --no-codesign + qa: flutter build ios --flavor=qa --dart-define=flavor=qa --debug --no-codesign + prod: flutter build ios --flavor=prod --dart-define=flavor=prod --debug --no-codesign + web: + dev: flutter build web --dart-define=flavor=dev + qa: flutter build web --dart-define=flavor=qa + prod: flutter build web --dart-define=flavor=prod + dependencies: - auto_route: ^4.0.1 - collection: ^1.16.0 - cupertino_icons: ^1.0.4 - dio: ^4.0.6 - drift: ^1.6.0 + auto_route: ^6.0.0+1 + collection: ^1.17.0 + cupertino_icons: ^1.0.5 + dio: ^5.0.3 + drift: ^2.5.0 easy_localization: ^3.0.1 flutter: sdk: flutter - flutter_displaymode: ^0.4.0 - flutter_hooks: ^0.18.4 - flutter_riverpod: ^1.0.4 - flutter_staggered_grid_view: ^0.6.1 - flutter_state_notifier: ^0.7.1 - freezed_annotation: ^2.0.3 + flutter_displaymode: ^0.5.0 + flutter_hooks: ^0.18.6 + flutter_riverpod: ^2.3.2 + flutter_staggered_grid_view: ^0.6.2 + flutter_state_notifier: ^0.7.3 + freezed_annotation: ^2.2.0 get_it: ^7.2.0 - hooks_riverpod: ^1.0.4 - intl: ^0.17.0 - json_annotation: ^4.5.0 - json_serializable: ^6.2.0 - logger: ^1.1.0 - path: ^1.8.1 - rxdart: ^0.27.3 - shared_preferences: ^2.0.15 + hooks_riverpod: ^2.3.2 + json_annotation: ^4.8.0 + json_serializable: ^6.6.1 + logger: ^1.3.0 + path: ^1.8.2 + rxdart: ^0.27.7 + shared_preferences: ^2.0.18 shimmer: ^2.0.0 - sqlite3_flutter_libs: ^0.5.7 - tuple: ^2.0.0 - uuid: ^3.0.6 - google_fonts: ^2.3.2 + sqlite3_flutter_libs: ^0.5.13 + tuple: ^2.0.1 + uuid: ^3.0.7 + google_fonts: ^4.0.3 easy_logger: ^0.0.2 - path_provider: ^2.0.10 - dynamic_color: ^1.2.2 - faker: ^2.0.0 - cached_network_image: ^3.2.0 + path_provider: ^2.0.13 + dynamic_color: ^1.6.2 + faker: ^2.1.0 + cached_network_image: ^3.2.3 + flutter_dotenv: ^5.0.2 dev_dependencies: - auto_route_generator: ^4.0.0 - build_runner: ^2.1.10 - drift_dev: ^1.6.0 + auto_route_generator: ^6.0.0 + build_runner: ^2.3.3 + drift_dev: ^2.5.2 flutter_lints: ^2.0.1 flutter_test: sdk: flutter - freezed: ^2.0.3+1 - golden_toolkit: ^0.13.0 - meta: ^1.7.0 + freezed: ^2.3.2 + golden_toolkit: ^0.15.0 + meta: ^1.8.0 mocktail: ^0.3.0 + mason: ^0.1.0-dev.47 -# The following section is specific to Flutter. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - # To add assets to your application, add an assets section, like this: assets: - assets/pre_populated_cities.sqlite - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + - assets/google_fonts/ + - .env + - .env.dev + - .env.qa + - .env.test diff --git a/scripts/build.sh b/scripts/build.sh index 05480d4b..3b6dfddc 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -3,24 +3,14 @@ ENTRYPOINT="" PLIST_LOCATION=$RUNNER_TEMP/options.plist -case $2 in - dev) ENTRYPOINT="entrypoints/main_dev.dart";; - qa) ENTRYPOINT="entrypoints/main_qa.dart";; - prod) ENTRYPOINT="entrypoints/main_prod.dart";; -esac - -FLAGS="$1 --flavor $2 -t $GITHUB_WORKSPACE/lib/$ENTRYPOINT $3 $4 $5" +FLAGS="$1 --flavor $2 $3 $4 $5" echo "Requested Flavour: $2" -echo "Setting entrypoint: $GITHUB_WORKSPACE/lib/$ENTRYPOINT" +echo "Setting entrypoint: $GITHUB_WORKSPACE/lib/main.dart" echo "Building $1 .......... " echo "" if [ $1 == ipa ] then - sed -i '' "s#.*entrypoints/main.*#import 'package:flutter_template/$ENTRYPOINT' as entrypoint;#" lib/main.dart FLAGS="$FLAGS --export-options-plist=$PLIST_LOCATION" -elif [ $1 == ios ] -then - sed -i '' "s#.*entrypoints/main.*#import 'package:flutter_template/$ENTRYPOINT' as entrypoint;#" lib/main.dart fi echo "************************************************************************************************************" echo "Running flutter build $FLAGS" diff --git a/scripts/device-preview.sh b/scripts/device-preview.sh index d7138acf..cc999723 100755 --- a/scripts/device-preview.sh +++ b/scripts/device-preview.sh @@ -1,3 +1,3 @@ #!/bin/bash -flutter run --flavor dev -t "lib/entrypoints/main_dev.dart" --dart-define=enableDevicePreview="true" \ No newline at end of file +flutter run --flavor dev --dart-define=flavor=dev --dart-define=enableDevicePreview="true" \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..bf589841 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,9 @@ +sonar.projectKey=wednesday-solutions_flutter_template_AYcHtdyiJ8HzBHNzdZ31 +sonar.projectName=flutter_template +sonar.host.url=https://sonar.wednesday.is +sonar.sources=lib +sonar.tests=test +sonar.sourceEncoding=UTF-8 +sonar.exclusions=test/mocks/**/*.dart,lib/**/*.g.dart,lib/**/*.freezed.dart,lib/**/*.gr.dart,assets/** +sonar.test.inclusions=**/*_test.dart +sonar.test.exclusions=**/mocks/**/*.dart diff --git a/test/extensions/mock_extensions.dart b/test/extensions/mock_extensions.dart index a295075c..64eb129a 100644 --- a/test/extensions/mock_extensions.dart +++ b/test/extensions/mock_extensions.dart @@ -2,7 +2,9 @@ import 'package:mocktail/mocktail.dart'; extension WhenJustRunExt on When> { void justRun() { - thenAnswer((_) async { return; }); + thenAnswer((_) async { + return; + }); } } diff --git a/test/extensions/stream_extensions.dart b/test/extensions/stream_extensions.dart index f69d5aa4..93d6b057 100644 --- a/test/extensions/stream_extensions.dart +++ b/test/extensions/stream_extensions.dart @@ -6,4 +6,4 @@ extension StreamEmitsInOrderExt on Stream { } get emitsNothing => expect(this, neverEmits(isA())); -} \ No newline at end of file +} diff --git a/test/mocks/mocks.dart b/test/mocks/mocks.dart index e8b2e768..308c0b7c 100644 --- a/test/mocks/mocks.dart +++ b/test/mocks/mocks.dart @@ -33,7 +33,8 @@ class MockDateRepository extends Mock implements DateRepository {} class MockWeatherRepository extends Mock implements WeatherRepository {} // Interactors -class MockFavoriteWeatherInteractor extends Mock implements FavoriteWeatherInteractor {} +class MockFavoriteWeatherInteractor extends Mock + implements FavoriteWeatherInteractor {} class MockSearchCityInteractor extends Mock implements SearchCityInteractor {} @@ -41,4 +42,5 @@ class MockSearchCityInteractor extends Mock implements SearchCityInteractor {} class MockSearchNavigator extends Mock implements SearchNavigator {} // Intent Handler -class MockIntentHandler extends Mock implements IntentHandler {} +class MockIntentHandler extends Mock + implements IntentHandler {} diff --git a/test/presentation/base/test_helpers.dart b/test/presentation/base/test_helpers.dart index ec9a4bc5..cd41dff8 100644 --- a/test/presentation/base/test_helpers.dart +++ b/test/presentation/base/test_helpers.dart @@ -1,15 +1,7 @@ -import 'package:flutter_template/flavors/flavor.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_template/flavors/flavor_config.dart'; -import 'package:flutter_template/flavors/flavor_values.dart'; -import 'package:flutter_template/secrets/app_secrets.dart'; -void baseSetupAll() { - FlavorConfig.initialize( - flavor: Flavor.dev, - values: const FlavorValues( - apiBaseUrl: "", - secrets: AppSecrets( - openWeatherApiKey: "openWeatherApiKey", - )), - ); +Future baseSetupAll() async { + await dotenv.load(fileName: ".env.test"); + FlavorConfig.initialize(flavorString: "test"); } diff --git a/test/presentation/base/widget_tester_ext.dart b/test/presentation/base/widget_tester_ext.dart index b0d0ff6f..68b03589 100644 --- a/test/presentation/base/widget_tester_ext.dart +++ b/test/presentation/base/widget_tester_ext.dart @@ -14,7 +14,10 @@ extension WidgetTesterExt on WidgetTester { Future loadPageForGolden({ required Widget page, required AutoDisposeStateNotifierProvider viewModelProvider, - required AutoDisposeStateNotifierProvider fakeViewModelProvider, + required StateNotifier Function( + AutoDisposeStateNotifierProviderRef, + dynamic>) + fakeViewModelGenerator, }) async { TestWidgetsFlutterBinding.ensureInitialized(); SharedPreferences.setMockInitialValues({}); @@ -38,7 +41,7 @@ extension WidgetTesterExt on WidgetTester { locale: context.locale, home: ProviderScope( overrides: [ - viewModelProvider.overrideWithProvider(fakeViewModelProvider), + viewModelProvider.overrideWith(fakeViewModelGenerator), ], child: page, ), @@ -52,7 +55,10 @@ extension WidgetTesterExt on WidgetTester { Future loadPage({ required Widget widget, required AutoDisposeStateNotifierProvider viewModelProvider, - required AutoDisposeStateNotifierProvider fakeViewModelProvider, + required StateNotifier Function( + AutoDisposeStateNotifierProviderRef, + dynamic>) + fakeViewModelGenerator, MediaQueryData mediaQueryData = const MediaQueryData( size: Size(320, 640), devicePixelRatio: 3, @@ -77,8 +83,7 @@ extension WidgetTesterExt on WidgetTester { useInheritedMediaQuery: true, home: ProviderScope( overrides: [ - viewModelProvider - .overrideWithProvider(fakeViewModelProvider), + viewModelProvider.overrideWith(fakeViewModelGenerator), ], child: widget, ), @@ -122,8 +127,8 @@ testPageGolden( customPump: customPump, ); }, - // Only run golden tests on one platform (macos in this case) to maintain - // consistency of rendered png images. +// Only run golden tests on one platform (macos in this case) to maintain +// consistency of rendered png images. skip: !Platform.isMacOS, ); } diff --git a/test/presentation/goldens/destinations/weather/search/goldens/search_page_default_state.iphone11.png b/test/presentation/goldens/destinations/weather/search/goldens/search_page_default_state.iphone11.png index e9f9fda7..7b236c5e 100644 Binary files a/test/presentation/goldens/destinations/weather/search/goldens/search_page_default_state.iphone11.png and b/test/presentation/goldens/destinations/weather/search/goldens/search_page_default_state.iphone11.png differ diff --git a/test/presentation/goldens/destinations/weather/search/goldens/search_page_default_state.smallPhone.png b/test/presentation/goldens/destinations/weather/search/goldens/search_page_default_state.smallPhone.png index 82129ae8..e9136131 100644 Binary files a/test/presentation/goldens/destinations/weather/search/goldens/search_page_default_state.smallPhone.png and b/test/presentation/goldens/destinations/weather/search/goldens/search_page_default_state.smallPhone.png differ diff --git a/test/presentation/goldens/destinations/weather/search/goldens/search_page_favorite_icon.iphone11.png b/test/presentation/goldens/destinations/weather/search/goldens/search_page_favorite_icon.iphone11.png index 03451854..b353e7c9 100644 Binary files a/test/presentation/goldens/destinations/weather/search/goldens/search_page_favorite_icon.iphone11.png and b/test/presentation/goldens/destinations/weather/search/goldens/search_page_favorite_icon.iphone11.png differ diff --git a/test/presentation/goldens/destinations/weather/search/goldens/search_page_favorite_icon.smallPhone.png b/test/presentation/goldens/destinations/weather/search/goldens/search_page_favorite_icon.smallPhone.png index 337c3b68..41c4a248 100644 Binary files a/test/presentation/goldens/destinations/weather/search/goldens/search_page_favorite_icon.smallPhone.png and b/test/presentation/goldens/destinations/weather/search/goldens/search_page_favorite_icon.smallPhone.png differ diff --git a/test/presentation/goldens/destinations/weather/search/goldens/search_page_loading.iphone11.png b/test/presentation/goldens/destinations/weather/search/goldens/search_page_loading.iphone11.png index 8aea9043..a2f8c950 100644 Binary files a/test/presentation/goldens/destinations/weather/search/goldens/search_page_loading.iphone11.png and b/test/presentation/goldens/destinations/weather/search/goldens/search_page_loading.iphone11.png differ diff --git a/test/presentation/goldens/destinations/weather/search/goldens/search_page_loading.smallPhone.png b/test/presentation/goldens/destinations/weather/search/goldens/search_page_loading.smallPhone.png index 88ba72d3..abbb9e0d 100644 Binary files a/test/presentation/goldens/destinations/weather/search/goldens/search_page_loading.smallPhone.png and b/test/presentation/goldens/destinations/weather/search/goldens/search_page_loading.smallPhone.png differ diff --git a/test/presentation/goldens/destinations/weather/search/goldens/search_page_no_results.iphone11.png b/test/presentation/goldens/destinations/weather/search/goldens/search_page_no_results.iphone11.png index 99b5f1e6..93d0fb84 100644 Binary files a/test/presentation/goldens/destinations/weather/search/goldens/search_page_no_results.iphone11.png and b/test/presentation/goldens/destinations/weather/search/goldens/search_page_no_results.iphone11.png differ diff --git a/test/presentation/goldens/destinations/weather/search/goldens/search_page_no_results.smallPhone.png b/test/presentation/goldens/destinations/weather/search/goldens/search_page_no_results.smallPhone.png index 37f92ce4..48e454ce 100644 Binary files a/test/presentation/goldens/destinations/weather/search/goldens/search_page_no_results.smallPhone.png and b/test/presentation/goldens/destinations/weather/search/goldens/search_page_no_results.smallPhone.png differ diff --git a/test/presentation/goldens/destinations/weather/search/goldens/search_page_results.iphone11.png b/test/presentation/goldens/destinations/weather/search/goldens/search_page_results.iphone11.png index bf4ba9f6..b353e7c9 100644 Binary files a/test/presentation/goldens/destinations/weather/search/goldens/search_page_results.iphone11.png and b/test/presentation/goldens/destinations/weather/search/goldens/search_page_results.iphone11.png differ diff --git a/test/presentation/goldens/destinations/weather/search/goldens/search_page_results.smallPhone.png b/test/presentation/goldens/destinations/weather/search/goldens/search_page_results.smallPhone.png index 8c06b295..41c4a248 100644 Binary files a/test/presentation/goldens/destinations/weather/search/goldens/search_page_results.smallPhone.png and b/test/presentation/goldens/destinations/weather/search/goldens/search_page_results.smallPhone.png differ diff --git a/test/presentation/goldens/destinations/weather/search/search_page_golden_test.dart b/test/presentation/goldens/destinations/weather/search/search_page_golden_test.dart index 1dfb4a5a..7922ff88 100644 --- a/test/presentation/goldens/destinations/weather/search/search_page_golden_test.dart +++ b/test/presentation/goldens/destinations/weather/search/search_page_golden_test.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_page.dart'; +import 'package:flutter_template/presentation/destinations/weather/search/search_screen.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_screen_state.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_view_model.dart'; import 'package:flutter_template/presentation/destinations/weather/search/widgets/list/ui_city_list_item.dart'; import 'package:flutter_template/presentation/destinations/weather/search/widgets/search_page_loading_shimmer/search_page_loading_shimmer.dart'; import 'package:flutter_template/presentation/entity/base/ui_toolbar.dart'; -import 'package:flutter_template/presentation/entity/screen/screen.dart'; import 'package:flutter_template/presentation/entity/weather/ui_city.dart'; import 'package:flutter_template/presentation/intl/translations/translation_keys.dart'; import 'package:flutter_template/presentation/intl/translations/translations.dart'; @@ -21,20 +20,6 @@ import '../../../../base/widget_tester_ext.dart'; void main() { late FakeSearchViewModel fakeSearchViewModel; - var fakeSearchViewModelProvider = - StateNotifierProvider.autoDispose( - (ref) { - fakeSearchViewModel = FakeSearchViewModel(SearchScreenState( - toolbar: UIToolbar( - title: LocaleKeys.searchPageTitle, - hasBackButton: true, - ), - showLoading: false, - searchList: List.empty(), - )); - return fakeSearchViewModel; - }); - setUpAll(baseSetupAll); setUp(() {}); @@ -48,7 +33,17 @@ void main() { await tester.loadPageForGolden( page: const SearchPage(searchScreen: SearchScreen()), viewModelProvider: searchViewModelProvider, - fakeViewModelProvider: fakeSearchViewModelProvider, + fakeViewModelGenerator: (ref) { + fakeSearchViewModel = FakeSearchViewModel(SearchScreenState( + toolbar: UIToolbar( + title: LocaleKeys.searchPageTitle, + hasBackButton: true, + ), + showLoading: false, + searchList: List.empty(), + )); + return fakeSearchViewModel; + }, ); } diff --git a/test/presentation/goldens/flutter_test_config.dart b/test/presentation/goldens/flutter_test_config.dart index 6e42a643..bdab46a4 100644 --- a/test/presentation/goldens/flutter_test_config.dart +++ b/test/presentation/goldens/flutter_test_config.dart @@ -1,10 +1,16 @@ import 'dart:async'; +import 'package:flutter_template/flavors/flavor_config.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; +import '../base/test_helpers.dart'; + Future testExecutable(FutureOr Function() testMain) async { TestWidgetsFlutterBinding.ensureInitialized(); - await loadAppFonts(); + await baseSetupAll(); + if (FlavorConfig.values.renderFontsInTest) { + await loadAppFonts(); + } return testMain(); -} \ No newline at end of file +} diff --git a/test/presentation/integration/destinations/weather/search/search_page_test.dart b/test/presentation/integration/destinations/weather/search/search_page_test.dart index efed0898..d8caf268 100644 --- a/test/presentation/integration/destinations/weather/search/search_page_test.dart +++ b/test/presentation/integration/destinations/weather/search/search_page_test.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_page.dart'; +import 'package:flutter_template/presentation/destinations/weather/search/search_screen.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_screen_intent.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_screen_state.dart'; import 'package:flutter_template/presentation/destinations/weather/search/search_view_model.dart'; import 'package:flutter_template/presentation/destinations/weather/search/widgets/list/ui_city_list_item.dart'; import 'package:flutter_template/presentation/destinations/weather/search/widgets/search_page_loading_shimmer/search_page_loading_shimmer.dart'; import 'package:flutter_template/presentation/entity/base/ui_toolbar.dart'; -import 'package:flutter_template/presentation/entity/screen/screen.dart'; import 'package:flutter_template/presentation/entity/weather/ui_city.dart'; import 'package:flutter_template/presentation/intl/translations/translation_keys.dart'; import 'package:flutter_template/presentation/intl/translations/translations.dart'; @@ -22,16 +21,6 @@ import '../../../../base/widget_tester_ext.dart'; void main() { late FakeSearchViewModel fakeSearchViewModel; - var fakeSearchViewModelProvider = - StateNotifierProvider.autoDispose((ref) { - fakeSearchViewModel = FakeSearchViewModel(SearchScreenState( - toolbar: UIToolbar(title: LocaleKeys.searchPageTitle, hasBackButton: true), - showLoading: false, - searchList: List.empty(), - )); - return fakeSearchViewModel; - }); - setUpAll(baseSetupAll); setUp(() {}); @@ -45,7 +34,17 @@ void main() { await tester.loadPage( widget: const SearchPage(searchScreen: SearchScreen()), viewModelProvider: searchViewModelProvider, - fakeViewModelProvider: fakeSearchViewModelProvider, + fakeViewModelGenerator: (ref) { + fakeSearchViewModel = FakeSearchViewModel( + SearchScreenState( + toolbar: UIToolbar( + title: LocaleKeys.searchPageTitle, hasBackButton: true), + showLoading: false, + searchList: List.empty(), + ), + ); + return fakeSearchViewModel; + }, ); } @@ -84,17 +83,18 @@ void main() { await loadPage(tester); // When - fakeSearchViewModel.setState((state) => state.copyWith(showLoading: true, searchList: [ - UICity( - cityId: 1, - title: "title", - locationType: "locationType", - location: "location", - isFavourite: false, - state: '', - displayTitle: '', - ) - ])); + fakeSearchViewModel + .setState((state) => state.copyWith(showLoading: true, searchList: [ + UICity( + cityId: 1, + title: "title", + locationType: "locationType", + location: "location", + isFavourite: false, + state: '', + displayTitle: '', + ) + ])); await tester.pump(); fakeSearchViewModel.updateSearchTerm("newTerm"); fakeSearchViewModel.setState((state) => state.copyWith( @@ -117,31 +117,33 @@ void main() { await loadPage(tester); // When - fakeSearchViewModel.setState((state) => state.copyWith(showLoading: false, searchList: [ - UICity( - cityId: 1, - title: "title", - locationType: "locationType", - location: "location", - isFavourite: false, - state: '', - displayTitle: '', - ), - UICity( - cityId: 2, - title: "title 2", - locationType: "locationType 2", - location: "location 2", - isFavourite: false, - state: '', - displayTitle: '', - ), - ])); + fakeSearchViewModel + .setState((state) => state.copyWith(showLoading: false, searchList: [ + UICity( + cityId: 1, + title: "title", + locationType: "locationType", + location: "location", + isFavourite: false, + state: '', + displayTitle: '', + ), + UICity( + cityId: 2, + title: "title 2", + locationType: "locationType 2", + location: "location 2", + isFavourite: false, + state: '', + displayTitle: '', + ), + ])); await tester.pumpAndSettle(); // Then expect(find.byType(TextField), findsOneWidget); - expect(find.text(englishUS[LocaleKeys.searchResultsAppearHere]), findsNothing); + expect( + find.text(englishUS[LocaleKeys.searchResultsAppearHere]), findsNothing); expect(find.byType(SearchPageLoadingShimmer), findsNothing); expect(find.text(englishUS[LocaleKeys.noResultsFound]), findsNothing); expect(find.byType(UICityListItem), findsNWidgets(2)); @@ -174,8 +176,8 @@ void main() { ]; // When - fakeSearchViewModel - .setState((state) => state.copyWith(showLoading: false, searchList: uiCityList)); + fakeSearchViewModel.setState( + (state) => state.copyWith(showLoading: false, searchList: uiCityList)); await tester.pump(); // Then @@ -193,11 +195,12 @@ void main() { await tester.pump(); // Then - verify(() => fakeSearchViewModel.onIntent(SearchScreenIntent.search(searchTerm: "search"))) - .called(1); + verify(() => fakeSearchViewModel + .onIntent(SearchScreenIntent.search(searchTerm: "search"))).called(1); }); - testWidgets("Given search page is opened, When back button is pressed, Then back intent is fired", + testWidgets( + "Given search page is opened, When back button is pressed, Then back intent is fired", (tester) async { // Given await loadPage(tester); @@ -208,7 +211,8 @@ void main() { await tester.pump(); // Then - verify(() => fakeSearchViewModel.onIntent(SearchScreenIntent.back())).called(1); + verify(() => fakeSearchViewModel.onIntent(SearchScreenIntent.back())) + .called(1); }); testWidgets( @@ -238,22 +242,24 @@ void main() { ]; // When - fakeSearchViewModel - .setState((state) => state.copyWith(showLoading: false, searchList: uiCityList)); + fakeSearchViewModel.setState( + (state) => state.copyWith(showLoading: false, searchList: uiCityList)); await tester.pump(); - await tester.tap( - find.descendant(of: find.byType(UICityListItem), matching: find.byType(IconButton)).first); + await tester.tap(find + .descendant( + of: find.byType(UICityListItem), matching: find.byType(IconButton)) + .first); await tester.pump(); - await tester.tap( - find.descendant(of: find.byType(UICityListItem), matching: find.byType(IconButton)).last); + await tester.tap(find + .descendant( + of: find.byType(UICityListItem), matching: find.byType(IconButton)) + .last); await tester.pump(); // Then - verify(() => - fakeSearchViewModel.onIntent(SearchScreenIntent.toggleFavorite(city: uiCityList.first))) - .called(1); - verify(() => - fakeSearchViewModel.onIntent(SearchScreenIntent.toggleFavorite(city: uiCityList.last))) - .called(1); + verify(() => fakeSearchViewModel.onIntent( + SearchScreenIntent.toggleFavorite(city: uiCityList.first))).called(1); + verify(() => fakeSearchViewModel.onIntent( + SearchScreenIntent.toggleFavorite(city: uiCityList.last))).called(1); }); } diff --git a/test/repository/weather/domain_city_mapper_impl_test.dart b/test/repository/weather/domain_city_mapper_impl_test.dart index de866742..2a721a02 100644 --- a/test/repository/weather/domain_city_mapper_impl_test.dart +++ b/test/repository/weather/domain_city_mapper_impl_test.dart @@ -23,7 +23,7 @@ void main() { "Given local city data, When map called, Then city is returned", () { // Given - final localCityData = localCityData1; + const localCityData = localCityData1; final city = city1; // When diff --git a/test/repository/weather/weather_repository_impl_test.dart b/test/repository/weather/weather_repository_impl_test.dart index 011f426f..db76c7d7 100644 --- a/test/repository/weather/weather_repository_impl_test.dart +++ b/test/repository/weather/weather_repository_impl_test.dart @@ -81,7 +81,8 @@ void main() { () async { // Given final testException = TestException(); - when(() => weatherLocalService.getFavouriteCities()).thenThrow(testException); + when(() => weatherLocalService.getFavouriteCities()) + .thenThrow(testException); // When expect( @@ -110,7 +111,8 @@ void main() { (localCityList) { // Then expect(localCityList, cityData); - verify(() => weatherLocalService.getFavoriteCitiesStream()).called(1); + verify(() => weatherLocalService.getFavoriteCitiesStream()) + .called(1); verify(() => domainCityMapper.mapList(localCityData)).called(1); }, count: 1, @@ -126,7 +128,8 @@ void main() { final weatherData = weatherList; when(() => weatherLocalService.getFavoriteCitiesWeatherList()) .thenAnswer((invocation) => Future.value(localCityWithWeather)); - when(() => domainWeatherMapper.mapList(localCityWithWeather)).thenReturn(weatherData); + when(() => domainWeatherMapper.mapList(localCityWithWeather)) + .thenReturn(weatherData); // When final result = await weatherRepository.getFavoriteCitiesWeatherList(); @@ -142,7 +145,8 @@ void main() { () async { // Given final testException = TestException(); - when(() => weatherLocalService.getFavoriteCitiesWeatherList()).thenThrow(testException); + when(() => weatherLocalService.getFavoriteCitiesWeatherList()) + .thenThrow(testException); // When expect( @@ -163,7 +167,8 @@ void main() { final weatherData = weatherList; when(() => weatherLocalService.getFavoriteCitiesWeatherStream()) .thenAnswer((invocation) => Stream.value(localCityWithWeather)); - when(() => domainWeatherMapper.mapList(localCityWithWeather)).thenReturn(weatherData); + when(() => domainWeatherMapper.mapList(localCityWithWeather)) + .thenReturn(weatherData); // When weatherRepository.getFavoriteCitiesWeatherStream().listen( @@ -171,8 +176,10 @@ void main() { (data) { // Then expect(data, same(weatherData)); - verify(() => weatherLocalService.getFavoriteCitiesWeatherStream()).called(1); - verify(() => domainWeatherMapper.mapList(localCityWithWeather)).called(1); + verify(() => weatherLocalService.getFavoriteCitiesWeatherStream()) + .called(1); + verify(() => domainWeatherMapper.mapList(localCityWithWeather)) + .called(1); }, count: 1, ), @@ -183,8 +190,14 @@ void main() { "Given getFavouriteCities returns empty list, When fetchWeatherForFavoriteCities is called, No processing should happen", () async { // Given - const date = - DateTime(year: 1970, month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0); + const date = DateTime( + year: 1970, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0); when(() => dateRepository.nowDateTime()).thenReturn(date); when(() => weatherLocalService.getFavouriteCities()) .thenAnswer((_) => Future.value(List.empty())); @@ -209,22 +222,31 @@ void main() { "Given dayWeatherList is empty, When fetchWeatherForFavoriteCities is called, weather should be fetch from remote and updated in local", () async { // Given - const date = - DateTime(year: 1970, month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0); + const date = DateTime( + year: 1970, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0); final localCityData = singleLocalCityDataList; final localCity = localCityData.first; final remoteWeatherData = remoteWeather; final localWeatherCompanionData = localWeatherCompanion; when(() => weatherLocalService.getFavouriteCities()) .thenAnswer((_) => Future.value(localCityData)); - when(() => weatherLocalService.getLocalCurrentWeather(lat: localCity.lat, lon: localCity.lon)) + when(() => weatherLocalService.getLocalCurrentWeather( + lat: localCity.lat, lon: localCity.lon)) .thenAnswer((invocation) => Future.value(localWeatherData)); - when(() => weatherRemoteService.currentWeather(cityAndState: any(named: "cityAndState"))) + when(() => weatherRemoteService.currentWeather( + cityAndState: any(named: "cityAndState"))) .thenAnswer((_) => Future.value(remoteWeatherData)); - when(() => localWeatherMapper.map(remoteWeatherData, localCity.lat, localCity.lon)) + when(() => localWeatherMapper.map( + remoteWeatherData, localCity.lat, localCity.lon)) .thenReturn(localWeatherCompanionData); - when(() => weatherLocalService.upsertLocalCurrentWeather(weather: localWeatherCompanionData)) - .justRun(); + when(() => weatherLocalService.upsertLocalCurrentWeather( + weather: localWeatherCompanionData)).justRun(); when(() => dateRepository.nowDateTime()).thenReturn(date); when(() => dateRepository.convertDateTimeToMillis(date)) .thenReturn(DartDateTime.now().millisecondsSinceEpoch); @@ -236,35 +258,45 @@ void main() { verify(() => dateRepository.nowDateTime()).called(1); verify(() => dateRepository.convertDateTimeToMillis(date)).called(1); verify(() => weatherLocalService.getFavouriteCities()).called(1); - verify(() => weatherRemoteService.currentWeather(cityAndState: any(named: "cityAndState"))) - .called(1); - verify(() => localWeatherMapper.map(remoteWeatherData, localCity.lat, localCity.lon)).called(1); - verify(() => weatherLocalService.getLocalCurrentWeather(lat: localCity.lat, lon: localCity.lon)) - .called(1); - verify(() => weatherLocalService.upsertLocalCurrentWeather(weather: localWeatherCompanionData)) - .called(1); + verify(() => weatherRemoteService.currentWeather( + cityAndState: any(named: "cityAndState"))).called(1); + verify(() => localWeatherMapper.map( + remoteWeatherData, localCity.lat, localCity.lon)).called(1); + verify(() => weatherLocalService.getLocalCurrentWeather( + lat: localCity.lat, lon: localCity.lon)).called(1); + verify(() => weatherLocalService.upsertLocalCurrentWeather( + weather: localWeatherCompanionData)).called(1); }); test( "Given weather list is stale, When fetchWeatherForFavoriteCities is called, weather should be fetch from remote and updated in local", () async { // Given - const nowDate = - DateTime(year: 2021, month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0); + const nowDate = DateTime( + year: 2021, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0); final localCityData = singleLocalCityDataList; final localCity = localCityData.first; final remoteWeatherData = remoteWeather; final localWeatherCompanionData = localWeatherCompanion; when(() => weatherLocalService.getFavouriteCities()) .thenAnswer((_) => Future.value(localCityData)); - when(() => weatherRemoteService.currentWeather(cityAndState: any(named: "cityAndState"))) + when(() => weatherRemoteService.currentWeather( + cityAndState: any(named: "cityAndState"))) .thenAnswer((_) => Future.value(remoteWeatherData)); - when(() => weatherLocalService.getLocalCurrentWeather(lat: localCity.lat, lon: localCity.lon)) + when(() => weatherLocalService.getLocalCurrentWeather( + lat: localCity.lat, lon: localCity.lon)) .thenAnswer((invocation) => Future.value(localWeatherData)); - when(() => localWeatherMapper.map(remoteWeatherData, localCity.lat, localCity.lon)) + when(() => localWeatherMapper.map( + remoteWeatherData, localCity.lat, localCity.lon)) .thenReturn(localWeatherCompanionData); - when(() => weatherLocalService.upsertLocalCurrentWeather(weather: localWeatherCompanionData)) - .justRun(); + when(() => weatherLocalService.upsertLocalCurrentWeather( + weather: localWeatherCompanionData)).justRun(); when(() => dateRepository.nowDateTime()).thenReturn(nowDate); when(() => dateRepository.convertDateTimeToMillis(nowDate)) .thenReturn(DartDateTime.now().millisecondsSinceEpoch); @@ -276,28 +308,36 @@ void main() { verify(() => dateRepository.nowDateTime()).called(1); verify(() => dateRepository.convertDateTimeToMillis(nowDate)).called(1); verify(() => weatherLocalService.getFavouriteCities()).called(1); - verify(() => weatherRemoteService.currentWeather(cityAndState: any(named: "cityAndState"))) - .called(1); - verify(() => localWeatherMapper.map(remoteWeatherData, localCity.lat, localCity.lon)).called(1); - verify(() => weatherLocalService.getLocalCurrentWeather(lat: localCity.lat, lon: localCity.lon)) - .called(1); - verify(() => weatherLocalService.upsertLocalCurrentWeather(weather: localWeatherCompanionData)) - .called(1); + verify(() => weatherRemoteService.currentWeather( + cityAndState: any(named: "cityAndState"))).called(1); + verify(() => localWeatherMapper.map( + remoteWeatherData, localCity.lat, localCity.lon)).called(1); + verify(() => weatherLocalService.getLocalCurrentWeather( + lat: localCity.lat, lon: localCity.lon)).called(1); + verify(() => weatherLocalService.upsertLocalCurrentWeather( + weather: localWeatherCompanionData)).called(1); }); test( "Given weather list is not stale, When fetchWeatherForFavoriteCities is called, weather should not be fetch from remote", () async { // Given - const date = - DateTime(year: 1970, month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0); + const date = DateTime( + year: 1970, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0); final localCityData = singleLocalCityDataList; final localCity = localCityData.first; when(() => weatherLocalService.getFavouriteCities()) .thenAnswer((_) => Future.value(localCityData)); when(() => dateRepository.nowDateTime()).thenReturn(date); when(() => dateRepository.convertDateTimeToMillis(date)).thenReturn(10); - when(() => weatherLocalService.getLocalCurrentWeather(lat: localCity.lat, lon: localCity.lon)) + when(() => weatherLocalService.getLocalCurrentWeather( + lat: localCity.lat, lon: localCity.lon)) .thenAnswer((invocation) => Future.value(localWeatherData)); // When @@ -307,8 +347,8 @@ void main() { verify(() => dateRepository.nowDateTime()).called(1); verify(() => dateRepository.convertDateTimeToMillis(date)).called(1); verify(() => weatherLocalService.getFavouriteCities()).called(1); - verify(() => weatherLocalService.getLocalCurrentWeather(lat: localCity.lat, lon: localCity.lon)) - .called(1); + verify(() => weatherLocalService.getLocalCurrentWeather( + lat: localCity.lat, lon: localCity.lon)).called(1); verifyZeroInteractions(weatherRemoteService); verifyZeroInteractions(localWeatherMapper); }); diff --git a/test/test_models/local_city_data_models.dart b/test/test_models/local_city_data_models.dart index 33b8b632..b0d5a097 100644 --- a/test/test_models/local_city_data_models.dart +++ b/test/test_models/local_city_data_models.dart @@ -9,7 +9,7 @@ final allLocalCityDataList = [ localCityData2, ]; -final localCityData1 = LocalLocationData( +const localCityData1 = LocalLocationData( name: "name 1", country: "country 1", lat: 100.45, @@ -17,7 +17,7 @@ final localCityData1 = LocalLocationData( state: "state 1", ); -final localCityData2 = LocalLocationData( +const localCityData2 = LocalLocationData( name: "name 2", country: "country 2", lat: -45.45,