@@ -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
+
+
+- Select a device to launch on
+
+
+- Click `Run` to launch the app
+
+
## 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:
+
+
+
+## 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